2 * Copyright (c) 2017 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include "texture-manager.h"
22 #include <dali/devel-api/common/hash.h>
23 #include <dali/devel-api/images/texture-set-image.h>
24 #include <dali/integration-api/debug.h>
27 #include <dali/integration-api/debug.h>
28 #include <dali-toolkit/internal/image-loader/async-image-loader-impl.h>
29 #include <dali-toolkit/internal/image-loader/image-atlas-impl.h>
30 #include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
45 Debug::Filter* gTextureManagerLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_TEXTURE_MANAGER" );
48 const uint32_t DEFAULT_ATLAS_SIZE( 1024u ); ///< This size can fit 8 by 8 images of average size 128 * 128
49 const Vector4 FULL_ATLAS_RECT( 0.0f, 0.0f, 1.0f, 1.0f ); ///< UV Rectangle that covers the full Texture
50 const char * const BROKEN_IMAGE_URL( DALI_IMAGE_DIR "broken.png" ); ///< URL For the broken image placeholder
51 const int INVALID_INDEX( -1 ); ///< Invalid index used to represent a non-existant TextureInfo struct
52 const int INVALID_CACHE_INDEX( -1 ); ///< Invalid Cache index
54 } // Anonymous namespace
57 TextureManager::TextureManager()
58 : mAsyncLocalLoader( Toolkit::AsyncImageLoader::New() ),
59 mAsyncRemoteLoader( Toolkit::AsyncImageLoader::New() ),
60 mCurrentTextureId( 0 )
62 mAsyncLocalLoader.ImageLoadedSignal().Connect( this, &TextureManager::AsyncLocalLoadComplete );
63 mAsyncRemoteLoader.ImageLoadedSignal().Connect( this, &TextureManager::AsyncRemoteLoadComplete );
66 TextureManager::TextureId TextureManager::RequestLoad(
68 const ImageDimensions desiredSize,
69 FittingMode::Type fittingMode,
70 Dali::SamplingMode::Type samplingMode,
71 const UseAtlas useAtlas,
72 TextureUploadObserver* observer )
74 // First check if the requested Texture is cached.
75 const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas );
77 // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
78 int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas );
79 TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
81 // Check if the requested Texture exists in the cache.
82 if( cacheIndex != INVALID_CACHE_INDEX )
84 // Mark this texture being used by another client resource.
85 ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
86 textureId = mTextureInfoContainer[ cacheIndex ].textureId;
88 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) Using cached texture @%d, textureId=%d\n", url.GetUrl().c_str(), observer, cacheIndex, textureId );
92 // We need a new Texture.
93 textureId = GenerateUniqueTextureId();
94 mTextureInfoContainer.push_back( TextureInfo( textureId, url.GetUrl(), desiredSize, fittingMode, samplingMode, false, useAtlas, textureHash ) );
95 cacheIndex = mTextureInfoContainer.size() - 1u;
97 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) New texture, cacheIndex:%d, textureId=%d\n", url.GetUrl().c_str(), observer, cacheIndex, textureId );
100 // The below code path is common whether we are using the cache or not.
101 // The textureInfoIndex now refers to either a pre-existing cached TextureInfo, or a new TextureInfo just created.
102 TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
104 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
105 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
106 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
107 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
108 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
110 // Check if we should add the observer. Only do this if we have not loaded yet and it will not have loaded by the end of this method.
111 switch( textureInfo.loadState )
113 case TextureManager::NOT_STARTED:
115 LoadTexture( textureInfo );
116 ObserveTexture( textureInfo, observer );
119 case TextureManager::LOADING:
121 ObserveTexture( textureInfo, observer );
124 case TextureManager::UPLOADED:
128 // The Texture has already loaded. The other observers have already been notified.
129 // We need to send a "late" loaded notification for this observer.
130 observer->UploadComplete( textureInfo.loadingSucceeded,
131 textureInfo.textureSet, textureInfo.useAtlas,
132 textureInfo.atlasRect );
136 case TextureManager::CANCELLED:
138 // A cancelled texture hasn't finished loading yet. Treat as a loading texture
139 // (it's ref count has already been incremented, above)
140 textureInfo.loadState = TextureManager::LOADING;
141 ObserveTexture( textureInfo, observer );
146 // Return the TextureId for which this Texture can now be referenced by externally.
150 void TextureManager::Remove( const TextureManager::TextureId textureId )
152 int textureInfoIndex = GetCacheIndexFromId( textureId );
153 if( textureInfoIndex != INVALID_INDEX )
155 TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
158 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
159 textureId, textureInfoIndex,
160 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
161 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
162 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
163 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
165 // Decrement the reference count and check if this is the last user of this Texture.
166 if( --textureInfo.referenceCount <= 0 )
168 // This is the last remove for this Texture.
169 textureInfo.referenceCount = 0;
170 bool removeTextureInfo = false;
172 // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
173 if( textureInfo.loadState == UPLOADED )
175 if( textureInfo.atlas )
177 textureInfo.atlas.Remove( textureInfo.atlasRect );
179 removeTextureInfo = true;
181 else if( textureInfo.loadState == LOADING )
183 // We mark the textureInfo for removal.
184 // Once the load has completed, this method will be called again.
185 textureInfo.loadState = CANCELLED;
189 // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
190 removeTextureInfo = true;
193 // If the state allows us to remove the TextureInfo data, we do so.
194 if( removeTextureInfo )
196 // Permanently remove the textureInfo struct.
197 mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
203 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
205 LoadState loadState = TextureManager::NOT_STARTED;
207 int cacheIndex = GetCacheIndexFromId( textureId );
208 if( cacheIndex != INVALID_CACHE_INDEX )
210 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
211 loadState = cachedTextureInfo.loadState;
216 TextureSet TextureManager::GetTextureSet( TextureId textureId )
218 TextureSet textureSet;// empty handle
220 int cacheIndex = GetCacheIndexFromId( textureId );
221 if( cacheIndex != INVALID_CACHE_INDEX )
223 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
224 textureSet = cachedTextureInfo.textureSet;
231 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
235 if( textureInfo.loadState == NOT_STARTED )
237 textureInfo.loadState = LOADING;
239 if( !textureInfo.loadSynchronously )
241 if( textureInfo.url.IsLocal() )
243 mAsyncLocalLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
244 mAsyncLocalLoadingInfoContainer.back().loadId = GetImplementation(mAsyncLocalLoader).Load(
245 textureInfo.url, textureInfo.desiredSize,
246 textureInfo.fittingMode, textureInfo.samplingMode, true );
250 mAsyncRemoteLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
251 mAsyncRemoteLoadingInfoContainer.back().loadId = GetImplementation(mAsyncRemoteLoader).Load(
252 textureInfo.url, textureInfo.desiredSize,
253 textureInfo.fittingMode, textureInfo.samplingMode, true );
261 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
262 TextureUploadObserver* observer )
266 textureInfo.observerList.PushBack( observer );
267 observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
271 void TextureManager::AsyncLocalLoadComplete( uint32_t id, PixelData pixelData )
273 AsyncLoadComplete( mAsyncLocalLoadingInfoContainer, id, pixelData );
276 void TextureManager::AsyncRemoteLoadComplete( uint32_t id, PixelData pixelData )
278 AsyncLoadComplete( mAsyncRemoteLoadingInfoContainer, id, pixelData );
281 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, PixelData pixelData )
283 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
285 if( loadingContainer.size() >= 1u )
287 AsyncLoadingInfo loadingInfo = loadingContainer.front();
289 if( loadingInfo.loadId == id )
291 int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
292 if( cacheIndex != INVALID_CACHE_INDEX )
294 // Once we have found the TextureInfo data, we call a common function used to process loaded data for both sync and async loads.
295 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
297 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
299 // Only perform atlasing if the load has not been cancelled since the request.
300 if( textureInfo.loadState != CANCELLED )
302 // Perform atlasing and finalize the load.
303 PostLoad( textureInfo, pixelData );
307 Remove( textureInfo.textureId );
312 loadingContainer.pop_front();
317 bool TextureManager::PostLoad( TextureInfo& textureInfo, PixelData pixelData )
319 bool success = false;
321 // Was the load successful?
322 if( pixelData && ( pixelData.GetWidth() != 0 ) && ( pixelData.GetHeight() != 0 ) )
324 // Regardless of whether the atlasing succeeds or not, we have a valid image, so we mark it as successful.
327 bool usingAtlas = false;
329 // No atlas support for now
330 textureInfo.useAtlas = NO_ATLAS;
334 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " TextureManager::PostLoad() textureId:%d\n", textureInfo.textureId );
336 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelData.GetPixelFormat(), pixelData.GetWidth(), pixelData.GetHeight() );
337 texture.Upload( pixelData );
338 textureInfo.textureSet = TextureSet::New();
339 textureInfo.textureSet.SetTexture( 0u, texture );
345 DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
346 // @todo If the load was unsuccessful, upload the broken image.
349 // Update the load state.
350 // Note: This is regardless of success as we care about whether a
351 // load attempt is in progress or not. If unsuccessful, a broken
352 // image is still loaded.
353 textureInfo.loadState = UPLOADED;
355 // We need to store the load succeeded state as if a future request to load this texture comes in,
356 // we need to re-broadcast the UploadComplete notification to that observer.
357 textureInfo.loadingSucceeded = success;
359 // If there is an observer: Notify the load is complete, whether successful or not:
360 const unsigned int observerCount = textureInfo.observerList.Count();
361 for( unsigned int i = 0; i < observerCount; ++i )
363 TextureUploadObserver* observer = textureInfo.observerList[i];
366 observer->UploadComplete( success, textureInfo.textureSet, textureInfo.useAtlas, textureInfo.atlasRect );
367 observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
371 textureInfo.observerList.Clear();
376 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
378 return mCurrentTextureId++;
381 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
383 const unsigned int size = mTextureInfoContainer.size();
385 for( unsigned int i = 0; i < size; ++i )
387 if( mTextureInfoContainer[i].textureId == textureId )
393 DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
394 return INVALID_CACHE_INDEX;
397 TextureManager::TextureHash TextureManager::GenerateHash(
398 const std::string& url,
399 const ImageDimensions size,
400 const FittingMode::Type fittingMode,
401 const Dali::SamplingMode::Type samplingMode,
402 const UseAtlas useAtlas )
404 std::string hashTarget( url );
405 const size_t urlLength = hashTarget.length();
406 const uint16_t width = size.GetWidth();
407 const uint16_t height = size.GetWidth();
409 // If either the width or height has been specified, include the resizing options in the hash
410 if( width != 0 || height != 0 )
412 // We are appending 5 bytes to the URL to form the hash input.
413 hashTarget.resize( urlLength + 5u );
414 char* hashTargetPtr = &( hashTarget[ urlLength ] );
416 // Pack the width and height (4 bytes total).
417 *hashTargetPtr++ = size.GetWidth() & 0xff;
418 *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
419 *hashTargetPtr++ = size.GetHeight() & 0xff;
420 *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
422 // Bit-pack the FittingMode, SamplingMode and atlasing.
423 // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
424 *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
428 // We are not including sizing information, but we still need an extra byte for atlasing.
429 hashTarget.resize( urlLength + 1u );
430 // Add the atlasing to the hash input.
431 hashTarget[ urlLength ] = useAtlas;
434 return Dali::CalculateHash( hashTarget );
437 int TextureManager::FindCachedTexture(
438 const TextureManager::TextureHash hash,
439 const std::string& url,
440 const ImageDimensions size,
441 const FittingMode::Type fittingMode,
442 const Dali::SamplingMode::Type samplingMode,
443 const bool useAtlas )
445 // Default to an invalid ID, in case we do not find a match.
446 int cacheIndex = INVALID_CACHE_INDEX;
448 // Iterate through our hashes to find a match.
449 const unsigned int count = mTextureInfoContainer.size();
450 for( unsigned int i = 0u; i < count; ++i )
452 if( mTextureInfoContainer[i].hash == hash )
454 // We have a match, now we check all the original parameters in case of a hash collision.
455 TextureInfo& textureInfo( mTextureInfoContainer[i] );
457 if( ( url == textureInfo.url.GetUrl() ) &&
458 ( useAtlas == textureInfo.useAtlas ) &&
459 ( size == textureInfo.desiredSize ) &&
460 ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
461 ( fittingMode == textureInfo.fittingMode &&
462 samplingMode == textureInfo.samplingMode ) ) )
464 // The found Texture is a match.
474 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
476 const unsigned int count = mTextureInfoContainer.size();
477 for( unsigned int i = 0; i < count; ++i )
479 TextureInfo& textureInfo( mTextureInfoContainer[i] );
480 for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); ++j )
484 textureInfo.observerList.Erase( j );
491 TextureManager::~TextureManager()
493 mTextureInfoContainer.clear();
494 mAsyncLocalLoadingInfoContainer.clear();
495 mAsyncRemoteLoadingInfoContainer.clear();
501 } // namespace Internal
503 } // namespace Toolkit