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/pixel-data-mask.h>
24 #include <dali/devel-api/images/texture-set-image.h>
25 #include <dali/integration-api/debug.h>
28 #include <dali/integration-api/debug.h>
29 #include <dali-toolkit/internal/image-loader/async-image-loader-impl.h>
30 #include <dali-toolkit/internal/image-loader/image-atlas-impl.h>
31 #include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
47 Debug::Filter* gTextureManagerLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_TEXTURE_MANAGER" );
50 const uint32_t DEFAULT_ATLAS_SIZE( 1024u ); ///< This size can fit 8 by 8 images of average size 128 * 128
51 const Vector4 FULL_ATLAS_RECT( 0.0f, 0.0f, 1.0f, 1.0f ); ///< UV Rectangle that covers the full Texture
52 const char * const BROKEN_IMAGE_URL( DALI_IMAGE_DIR "broken.png" ); ///< URL For the broken image placeholder
53 const int INVALID_INDEX( -1 ); ///< Invalid index used to represent a non-existant TextureInfo struct
54 const int INVALID_CACHE_INDEX( -1 ); ///< Invalid Cache index
56 } // Anonymous namespace
59 TextureManager::TextureManager()
60 : mAsyncLocalLoader( Toolkit::AsyncImageLoader::New() ),
61 mAsyncRemoteLoader( Toolkit::AsyncImageLoader::New() ),
62 mCurrentTextureId( 0 )
64 mAsyncLocalLoader.ImageLoadedSignal().Connect( this, &TextureManager::AsyncLocalLoadComplete );
65 mAsyncRemoteLoader.ImageLoadedSignal().Connect( this, &TextureManager::AsyncRemoteLoadComplete );
68 TextureManager::TextureId TextureManager::RequestLoad(
70 const ImageDimensions desiredSize,
71 FittingMode::Type fittingMode,
72 Dali::SamplingMode::Type samplingMode,
73 const UseAtlas useAtlas,
74 TextureUploadObserver* observer )
76 return RequestInternalLoad( url, INVALID_TEXTURE_ID, desiredSize, fittingMode, samplingMode, useAtlas, GPU_UPLOAD, observer );
79 TextureManager::TextureId TextureManager::RequestLoad(
81 TextureId maskTextureId,
82 const ImageDimensions desiredSize,
83 FittingMode::Type fittingMode,
84 Dali::SamplingMode::Type samplingMode,
85 const UseAtlas useAtlas,
86 TextureUploadObserver* observer )
88 return RequestInternalLoad( url, maskTextureId, desiredSize, fittingMode, samplingMode, useAtlas, GPU_UPLOAD, observer );
91 TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl )
93 // Use the normal load procedure to get the alpha mask.
94 return RequestInternalLoad( maskUrl, INVALID_TEXTURE_ID, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::NO_FILTER, NO_ATLAS, CPU, NULL );
98 TextureManager::TextureId TextureManager::RequestInternalLoad(
100 TextureId maskTextureId,
101 const ImageDimensions desiredSize,
102 FittingMode::Type fittingMode,
103 Dali::SamplingMode::Type samplingMode,
105 StorageType storageType,
106 TextureUploadObserver* observer )
108 // First check if the requested Texture is cached.
109 const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
111 TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
113 // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
114 int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
116 // Check if the requested Texture exists in the cache.
117 if( cacheIndex != INVALID_CACHE_INDEX )
119 // Mark this texture being used by another client resource.
120 ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
121 textureId = mTextureInfoContainer[ cacheIndex ].textureId;
123 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 );
126 if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required
128 // We need a new Texture.
129 textureId = GenerateUniqueTextureId();
130 mTextureInfoContainer.push_back( TextureInfo( textureId, maskTextureId, url.GetUrl(),
131 desiredSize, fittingMode, samplingMode,
132 false, useAtlas, textureHash ) );
133 cacheIndex = mTextureInfoContainer.size() - 1u;
135 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 );
138 // The below code path is common whether we are using the cache or not.
139 // The textureInfoIndex now refers to either a pre-existing cached TextureInfo,
140 // or a new TextureInfo just created.
141 TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
142 textureInfo.maskTextureId = maskTextureId;
143 textureInfo.storageType = storageType;
145 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
146 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
147 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
148 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
149 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
151 // 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.
152 switch( textureInfo.loadState )
154 case TextureManager::NOT_STARTED:
156 LoadTexture( textureInfo );
157 ObserveTexture( textureInfo, observer );
160 case TextureManager::LOADING:
162 ObserveTexture( textureInfo, observer );
165 case TextureManager::UPLOADED:
169 // The Texture has already loaded. The other observers have already been notified.
170 // We need to send a "late" loaded notification for this observer.
171 observer->UploadComplete( true,
172 textureInfo.textureSet, textureInfo.useAtlas,
173 textureInfo.atlasRect );
177 case TextureManager::CANCELLED:
179 // A cancelled texture hasn't finished loading yet. Treat as a loading texture
180 // (it's ref count has already been incremented, above)
181 textureInfo.loadState = TextureManager::LOADING;
182 ObserveTexture( textureInfo, observer );
185 case TextureManager::LOAD_FINISHED:
186 case TextureManager::WAITING_FOR_MASK:
187 case TextureManager::LOAD_FAILED:
188 // Loading has already completed. Do nothing.
192 // Return the TextureId for which this Texture can now be referenced by externally.
196 void TextureManager::Remove( const TextureManager::TextureId textureId )
198 int textureInfoIndex = GetCacheIndexFromId( textureId );
199 if( textureInfoIndex != INVALID_INDEX )
201 TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
204 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
205 textureId, textureInfoIndex,
206 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
207 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
208 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
209 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
211 // Decrement the reference count and check if this is the last user of this Texture.
212 if( --textureInfo.referenceCount <= 0 )
214 // This is the last remove for this Texture.
215 textureInfo.referenceCount = 0;
216 bool removeTextureInfo = false;
218 // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
219 if( textureInfo.loadState == UPLOADED )
221 if( textureInfo.atlas )
223 textureInfo.atlas.Remove( textureInfo.atlasRect );
225 removeTextureInfo = true;
227 else if( textureInfo.loadState == LOADING )
229 // We mark the textureInfo for removal.
230 // Once the load has completed, this method will be called again.
231 textureInfo.loadState = CANCELLED;
235 // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
236 removeTextureInfo = true;
239 // If the state allows us to remove the TextureInfo data, we do so.
240 if( removeTextureInfo )
242 // Permanently remove the textureInfo struct.
243 mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
249 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
251 LoadState loadState = TextureManager::NOT_STARTED;
253 int cacheIndex = GetCacheIndexFromId( textureId );
254 if( cacheIndex != INVALID_CACHE_INDEX )
256 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
257 loadState = cachedTextureInfo.loadState;
262 TextureSet TextureManager::GetTextureSet( TextureId textureId )
264 TextureSet textureSet;// empty handle
266 int cacheIndex = GetCacheIndexFromId( textureId );
267 if( cacheIndex != INVALID_CACHE_INDEX )
269 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
270 textureSet = cachedTextureInfo.textureSet;
275 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
279 if( textureInfo.loadState == NOT_STARTED )
281 textureInfo.loadState = LOADING;
283 if( !textureInfo.loadSynchronously )
285 if( textureInfo.url.IsLocal() )
287 mAsyncLocalLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
288 mAsyncLocalLoadingInfoContainer.back().loadId =
289 GetImplementation(mAsyncLocalLoader).Load( textureInfo.url, textureInfo.desiredSize,
290 textureInfo.fittingMode,
291 textureInfo.samplingMode, true );
295 mAsyncRemoteLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
296 mAsyncRemoteLoadingInfoContainer.back().loadId =
297 GetImplementation(mAsyncRemoteLoader).Load( textureInfo.url, textureInfo.desiredSize,
298 textureInfo.fittingMode,
299 textureInfo.samplingMode, true );
307 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
308 TextureUploadObserver* observer )
312 textureInfo.observerList.PushBack( observer );
313 observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
317 void TextureManager::AsyncLocalLoadComplete( uint32_t id, PixelData pixelData )
319 AsyncLoadComplete( mAsyncLocalLoadingInfoContainer, id, pixelData );
322 void TextureManager::AsyncRemoteLoadComplete( uint32_t id, PixelData pixelData )
324 AsyncLoadComplete( mAsyncRemoteLoadingInfoContainer, id, pixelData );
327 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, PixelData pixelData )
329 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
331 if( loadingContainer.size() >= 1u )
333 AsyncLoadingInfo loadingInfo = loadingContainer.front();
335 if( loadingInfo.loadId == id )
337 int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
338 if( cacheIndex != INVALID_CACHE_INDEX )
340 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
342 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
344 if( textureInfo.loadState != CANCELLED )
346 PostLoad( textureInfo, pixelData );
350 Remove( textureInfo.textureId );
355 loadingContainer.pop_front();
359 void TextureManager::PostLoad( TextureInfo& textureInfo, PixelData pixelData )
361 // Was the load successful?
362 if( pixelData && ( pixelData.GetWidth() != 0 ) && ( pixelData.GetHeight() != 0 ) )
364 // No atlas support for now
365 textureInfo.useAtlas = NO_ATLAS;
367 if( textureInfo.storageType == GPU_UPLOAD )
369 // If there is a mask texture ID associated with this texture, then apply the mask
370 // if it's already loaded. If it hasn't, and the mask is still loading,
371 // wait for the mask to finish loading.
372 if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
374 LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
375 if( maskLoadState == LOADING )
377 textureInfo.pixelData = pixelData; // Store the pixel data temporarily
378 textureInfo.loadState = WAITING_FOR_MASK;
380 else if( maskLoadState == LOAD_FINISHED )
382 ApplyMask( pixelData, textureInfo.maskTextureId );
383 UploadTexture( pixelData, textureInfo );
384 NotifyObservers( textureInfo, true );
389 UploadTexture( pixelData, textureInfo );
390 NotifyObservers( textureInfo, true );
393 else // currently, CPU textures are local to texture manager
395 textureInfo.pixelData = pixelData; // Store the pixel data
396 textureInfo.loadState = LOAD_FINISHED;
398 // Check if there was another texture waiting for this load to complete
399 // (e.g. if this was an image mask, and its load is on a different thread)
400 CheckForWaitingTexture( textureInfo );
405 DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
406 // @todo If the load was unsuccessful, upload the broken image.
407 textureInfo.loadState = LOAD_FAILED;
408 CheckForWaitingTexture( textureInfo );
409 NotifyObservers( textureInfo, false );
413 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
415 // Search the cache, checking if any texture has this texture id as a
417 const unsigned int size = mTextureInfoContainer.size();
419 for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
421 if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
422 mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
424 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
425 PixelData pixelData = textureInfo.pixelData;
426 textureInfo.pixelData.Reset();
428 if( maskTextureInfo.loadState == LOAD_FINISHED )
430 ApplyMask( pixelData, maskTextureInfo.textureId );
431 UploadTexture( pixelData, textureInfo );
432 NotifyObservers( textureInfo, true );
436 DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
437 textureInfo.loadState = LOAD_FAILED;
438 NotifyObservers( textureInfo, false );
444 void TextureManager::ApplyMask( PixelData pixelData, TextureId maskTextureId )
446 int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
447 PixelData maskPixelData = mTextureInfoContainer[maskCacheIndex].pixelData;
448 Dali::ApplyMask( pixelData, maskPixelData );
451 void TextureManager::UploadTexture( PixelData pixelData, TextureInfo& textureInfo )
453 if( textureInfo.useAtlas != USE_ATLAS )
455 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
457 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelData.GetPixelFormat(), pixelData.GetWidth(), pixelData.GetHeight() );
458 texture.Upload( pixelData );
459 textureInfo.textureSet = TextureSet::New();
460 textureInfo.textureSet.SetTexture( 0u, texture );
463 // Update the load state.
464 // Note: This is regardless of success as we care about whether a
465 // load attempt is in progress or not. If unsuccessful, a broken
466 // image is still loaded.
467 textureInfo.loadState = UPLOADED;
470 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
472 // If there is an observer: Notify the upload is complete
473 const unsigned int observerCount = textureInfo.observerList.Count();
474 for( unsigned int i = 0; i < observerCount; ++i )
476 TextureUploadObserver* observer = textureInfo.observerList[i];
479 observer->UploadComplete( success, textureInfo.textureSet, textureInfo.useAtlas, textureInfo.atlasRect );
480 observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
484 textureInfo.observerList.Clear();
488 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
490 return mCurrentTextureId++;
493 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
495 const unsigned int size = mTextureInfoContainer.size();
497 for( unsigned int i = 0; i < size; ++i )
499 if( mTextureInfoContainer[i].textureId == textureId )
505 DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
506 return INVALID_CACHE_INDEX;
509 TextureManager::TextureHash TextureManager::GenerateHash(
510 const std::string& url,
511 const ImageDimensions size,
512 const FittingMode::Type fittingMode,
513 const Dali::SamplingMode::Type samplingMode,
514 const UseAtlas useAtlas,
515 TextureId maskTextureId )
517 std::string hashTarget( url );
518 const size_t urlLength = hashTarget.length();
519 const uint16_t width = size.GetWidth();
520 const uint16_t height = size.GetWidth();
522 // If either the width or height has been specified, include the resizing options in the hash
523 if( width != 0 || height != 0 )
525 // We are appending 5 bytes to the URL to form the hash input.
526 hashTarget.resize( urlLength + 5u );
527 char* hashTargetPtr = &( hashTarget[ urlLength ] );
529 // Pack the width and height (4 bytes total).
530 *hashTargetPtr++ = size.GetWidth() & 0xff;
531 *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
532 *hashTargetPtr++ = size.GetHeight() & 0xff;
533 *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
535 // Bit-pack the FittingMode, SamplingMode and atlasing.
536 // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
537 *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
541 // We are not including sizing information, but we still need an extra byte for atlasing.
542 hashTarget.resize( urlLength + 1u );
543 // Add the atlasing to the hash input.
544 hashTarget[ urlLength ] = useAtlas;
547 if( maskTextureId != INVALID_TEXTURE_ID )
549 hashTarget.resize( urlLength + sizeof( TextureId ) );
550 TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
552 // Append the hash target to the end of the URL byte by byte:
553 // (to avoid SIGBUS / alignment issues)
554 for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
556 *hashTargetPtr++ = maskTextureId & 0xff;
557 maskTextureId >>= 8u;
561 return Dali::CalculateHash( hashTarget );
564 int TextureManager::FindCachedTexture(
565 const TextureManager::TextureHash hash,
566 const std::string& url,
567 const ImageDimensions size,
568 const FittingMode::Type fittingMode,
569 const Dali::SamplingMode::Type samplingMode,
571 TextureId maskTextureId)
573 // Default to an invalid ID, in case we do not find a match.
574 int cacheIndex = INVALID_CACHE_INDEX;
576 // Iterate through our hashes to find a match.
577 const unsigned int count = mTextureInfoContainer.size();
578 for( unsigned int i = 0u; i < count; ++i )
580 if( mTextureInfoContainer[i].hash == hash )
582 // We have a match, now we check all the original parameters in case of a hash collision.
583 TextureInfo& textureInfo( mTextureInfoContainer[i] );
585 if( ( url == textureInfo.url.GetUrl() ) &&
586 ( useAtlas == textureInfo.useAtlas ) &&
587 ( maskTextureId == textureInfo.maskTextureId ) &&
588 ( size == textureInfo.desiredSize ) &&
589 ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
590 ( fittingMode == textureInfo.fittingMode &&
591 samplingMode == textureInfo.samplingMode ) ) )
593 // The found Texture is a match.
603 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
605 const unsigned int count = mTextureInfoContainer.size();
606 for( unsigned int i = 0; i < count; ++i )
608 TextureInfo& textureInfo( mTextureInfoContainer[i] );
609 for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); ++j )
613 textureInfo.observerList.Erase( j );
620 TextureManager::~TextureManager()
622 mTextureInfoContainer.clear();
623 mAsyncLocalLoadingInfoContainer.clear();
624 mAsyncRemoteLoadingInfoContainer.clear();
630 } // namespace Internal
632 } // namespace Toolkit