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 // textureInfo can be invalidated after this call (as the mTextureInfoContainer may be modified)
347 PostLoad( textureInfo, pixelData );
351 Remove( textureInfo.textureId );
356 loadingContainer.pop_front();
360 void TextureManager::PostLoad( TextureInfo& textureInfo, PixelData pixelData )
362 // Was the load successful?
363 if( pixelData && ( pixelData.GetWidth() != 0 ) && ( pixelData.GetHeight() != 0 ) )
365 // No atlas support for now
366 textureInfo.useAtlas = NO_ATLAS;
368 if( textureInfo.storageType == GPU_UPLOAD )
370 // If there is a mask texture ID associated with this texture, then apply the mask
371 // if it's already loaded. If it hasn't, and the mask is still loading,
372 // wait for the mask to finish loading.
373 if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
375 LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
376 if( maskLoadState == LOADING )
378 textureInfo.pixelData = pixelData; // Store the pixel data temporarily
379 textureInfo.loadState = WAITING_FOR_MASK;
381 else if( maskLoadState == LOAD_FINISHED )
383 ApplyMask( pixelData, textureInfo.maskTextureId );
384 UploadTexture( pixelData, textureInfo );
385 NotifyObservers( textureInfo, true );
390 UploadTexture( pixelData, textureInfo );
391 NotifyObservers( textureInfo, true );
394 else // currently, CPU textures are local to texture manager
396 textureInfo.pixelData = pixelData; // Store the pixel data
397 textureInfo.loadState = LOAD_FINISHED;
399 // Check if there was another texture waiting for this load to complete
400 // (e.g. if this was an image mask, and its load is on a different thread)
401 CheckForWaitingTexture( textureInfo );
406 DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
407 // @todo If the load was unsuccessful, upload the broken image.
408 textureInfo.loadState = LOAD_FAILED;
409 CheckForWaitingTexture( textureInfo );
410 NotifyObservers( textureInfo, false );
414 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
416 // Search the cache, checking if any texture has this texture id as a
418 const unsigned int size = mTextureInfoContainer.size();
420 for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
422 if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
423 mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
425 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
426 PixelData pixelData = textureInfo.pixelData;
427 textureInfo.pixelData.Reset();
429 if( maskTextureInfo.loadState == LOAD_FINISHED )
431 ApplyMask( pixelData, maskTextureInfo.textureId );
432 UploadTexture( pixelData, textureInfo );
433 NotifyObservers( textureInfo, true );
437 DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
438 textureInfo.loadState = LOAD_FAILED;
439 NotifyObservers( textureInfo, false );
445 void TextureManager::ApplyMask( PixelData pixelData, TextureId maskTextureId )
447 int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
448 PixelData maskPixelData = mTextureInfoContainer[maskCacheIndex].pixelData;
449 Dali::ApplyMask( pixelData, maskPixelData );
452 void TextureManager::UploadTexture( PixelData pixelData, TextureInfo& textureInfo )
454 if( textureInfo.useAtlas != USE_ATLAS )
456 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
458 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelData.GetPixelFormat(), pixelData.GetWidth(), pixelData.GetHeight() );
459 texture.Upload( pixelData );
460 textureInfo.textureSet = TextureSet::New();
461 textureInfo.textureSet.SetTexture( 0u, texture );
464 // Update the load state.
465 // Note: This is regardless of success as we care about whether a
466 // load attempt is in progress or not. If unsuccessful, a broken
467 // image is still loaded.
468 textureInfo.loadState = UPLOADED;
471 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
473 TextureId textureId = textureInfo.textureId;
475 // If there is an observer: Notify the load is complete, whether successful or not:
476 // And erase it from the list
477 unsigned int observerCount = textureInfo.observerList.Count();
478 TextureInfo* info = &textureInfo;
480 while( observerCount )
482 TextureUploadObserver* observer = info->observerList[0];
484 // During UploadComplete() a Control ResourceReady() signal is emitted
485 // During that signal the app may add remove /add Textures (e.g. via ImageViews).
486 // At this point no more observers can be added to the observerList, because textureInfo.loadState = UPLOADED
487 // However it is possible for observers to be removed, hence we check the observer list count every iteration
489 // Also the reference to the textureInfo struct can become invalidated, because new load requests can modify
490 // the mTextureInfoContainer list (e.g. if more requests are pushed_back it can cause the list to be resized
491 // invalidating the reference to the TextureInfo ).
492 observer->UploadComplete( success, info->textureSet, info->useAtlas, info->atlasRect );
493 observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
495 // regrab the textureInfo from the container as it may have been invalidated, if textures have been removed
496 // or added during the ResourceReady() signal emission (from UploadComplete() )
497 int textureInfoIndex = GetCacheIndexFromId( textureId );
499 if( textureInfoIndex == INVALID_CACHE_INDEX)
501 // texture has been removed
504 info = &mTextureInfoContainer[ textureInfoIndex ];
505 observerCount = info->observerList.Count();
506 if ( observerCount > 0 )
508 // remove the observer that was just triggered if it's still in the list
509 for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
513 info->observerList.Erase( j );
525 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
527 return mCurrentTextureId++;
530 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
532 const unsigned int size = mTextureInfoContainer.size();
534 for( unsigned int i = 0; i < size; ++i )
536 if( mTextureInfoContainer[i].textureId == textureId )
542 DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
543 return INVALID_CACHE_INDEX;
546 TextureManager::TextureHash TextureManager::GenerateHash(
547 const std::string& url,
548 const ImageDimensions size,
549 const FittingMode::Type fittingMode,
550 const Dali::SamplingMode::Type samplingMode,
551 const UseAtlas useAtlas,
552 TextureId maskTextureId )
554 std::string hashTarget( url );
555 const size_t urlLength = hashTarget.length();
556 const uint16_t width = size.GetWidth();
557 const uint16_t height = size.GetWidth();
559 // If either the width or height has been specified, include the resizing options in the hash
560 if( width != 0 || height != 0 )
562 // We are appending 5 bytes to the URL to form the hash input.
563 hashTarget.resize( urlLength + 5u );
564 char* hashTargetPtr = &( hashTarget[ urlLength ] );
566 // Pack the width and height (4 bytes total).
567 *hashTargetPtr++ = size.GetWidth() & 0xff;
568 *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
569 *hashTargetPtr++ = size.GetHeight() & 0xff;
570 *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
572 // Bit-pack the FittingMode, SamplingMode and atlasing.
573 // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
574 *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
578 // We are not including sizing information, but we still need an extra byte for atlasing.
579 hashTarget.resize( urlLength + 1u );
580 // Add the atlasing to the hash input.
581 hashTarget[ urlLength ] = useAtlas;
584 if( maskTextureId != INVALID_TEXTURE_ID )
586 hashTarget.resize( urlLength + sizeof( TextureId ) );
587 TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
589 // Append the hash target to the end of the URL byte by byte:
590 // (to avoid SIGBUS / alignment issues)
591 for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
593 *hashTargetPtr++ = maskTextureId & 0xff;
594 maskTextureId >>= 8u;
598 return Dali::CalculateHash( hashTarget );
601 int TextureManager::FindCachedTexture(
602 const TextureManager::TextureHash hash,
603 const std::string& url,
604 const ImageDimensions size,
605 const FittingMode::Type fittingMode,
606 const Dali::SamplingMode::Type samplingMode,
608 TextureId maskTextureId)
610 // Default to an invalid ID, in case we do not find a match.
611 int cacheIndex = INVALID_CACHE_INDEX;
613 // Iterate through our hashes to find a match.
614 const unsigned int count = mTextureInfoContainer.size();
615 for( unsigned int i = 0u; i < count; ++i )
617 if( mTextureInfoContainer[i].hash == hash )
619 // We have a match, now we check all the original parameters in case of a hash collision.
620 TextureInfo& textureInfo( mTextureInfoContainer[i] );
622 if( ( url == textureInfo.url.GetUrl() ) &&
623 ( useAtlas == textureInfo.useAtlas ) &&
624 ( maskTextureId == textureInfo.maskTextureId ) &&
625 ( size == textureInfo.desiredSize ) &&
626 ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
627 ( fittingMode == textureInfo.fittingMode &&
628 samplingMode == textureInfo.samplingMode ) ) )
630 // The found Texture is a match.
640 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
642 const unsigned int count = mTextureInfoContainer.size();
643 for( unsigned int i = 0; i < count; ++i )
645 TextureInfo& textureInfo( mTextureInfoContainer[i] );
646 for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); ++j )
650 textureInfo.observerList.Erase( j );
657 TextureManager::~TextureManager()
659 mTextureInfoContainer.clear();
660 mAsyncLocalLoadingInfoContainer.clear();
661 mAsyncRemoteLoadingInfoContainer.clear();
667 } // namespace Internal
669 } // namespace Toolkit