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 <dali-toolkit/internal/visuals/texture-manager-impl.h>
24 #include <dali/public-api/math/vector4.h>
25 #include <dali/devel-api/adaptor-framework/environment-variable.h>
26 #include <dali/devel-api/adaptor-framework/image-loading.h>
27 #include <dali/devel-api/common/hash.h>
28 #include <dali/devel-api/images/texture-set-image.h>
29 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
30 #include <dali/integration-api/debug.h>
33 #include <dali-toolkit/internal/image-loader/image-atlas-impl.h>
34 #include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
35 #include <dali-toolkit/internal/visuals/image-atlas-manager.h>
40 constexpr auto DEFAULT_NUMBER_OF_LOCAL_LOADER_THREADS = size_t{4u};
41 constexpr auto DEFAULT_NUMBER_OF_REMOTE_LOADER_THREADS = size_t{8u};
43 constexpr auto NUMBER_OF_LOCAL_LOADER_THREADS_ENV = "DALI_TEXTURE_LOCAL_THREADS";
44 constexpr auto NUMBER_OF_REMOTE_LOADER_THREADS_ENV = "DALI_TEXTURE_REMOTE_THREADS";
46 size_t GetNumberOfThreads(const char* environmentVariable, size_t defaultValue)
48 using Dali::EnvironmentVariable::GetEnvironmentVariable;
49 auto numberString = GetEnvironmentVariable(environmentVariable);
50 auto numberOfThreads = numberString ? std::strtol(numberString, nullptr, 10) : 0;
51 return (numberOfThreads > 0) ? numberOfThreads : defaultValue;
54 size_t GetNumberOfLocalLoaderThreads()
56 return GetNumberOfThreads(NUMBER_OF_LOCAL_LOADER_THREADS_ENV, DEFAULT_NUMBER_OF_LOCAL_LOADER_THREADS);
59 size_t GetNumberOfRemoteLoaderThreads()
61 return GetNumberOfThreads(NUMBER_OF_REMOTE_LOADER_THREADS_ENV, DEFAULT_NUMBER_OF_REMOTE_LOADER_THREADS);
79 Debug::Filter* gTextureManagerLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_TEXTURE_MANAGER" );
82 const uint32_t DEFAULT_ATLAS_SIZE( 1024u ); ///< This size can fit 8 by 8 images of average size 128 * 128
83 const Vector4 FULL_ATLAS_RECT( 0.0f, 0.0f, 1.0f, 1.0f ); ///< UV Rectangle that covers the full Texture
84 const char * const BROKEN_IMAGE_URL( DALI_IMAGE_DIR "broken.png" ); ///< URL For the broken image placeholder
85 const int INVALID_INDEX( -1 ); ///< Invalid index used to represent a non-existant TextureInfo struct
86 const int INVALID_CACHE_INDEX( -1 ); ///< Invalid Cache index
88 } // Anonymous namespace
90 TextureManager::MaskingData::MaskingData()
92 mAlphaMaskId( INVALID_TEXTURE_ID ),
93 mContentScaleFactor( 1.0f ),
98 TextureManager::TextureManager()
99 : mAsyncLocalLoaders( GetNumberOfLocalLoaderThreads(), [&]() { return AsyncLoadingHelper(*this); } ),
100 mAsyncRemoteLoaders( GetNumberOfRemoteLoaderThreads(), [&]() { return AsyncLoadingHelper(*this); } ),
101 mCurrentTextureId( 0 )
105 TextureSet TextureManager::LoadTexture(
106 VisualUrl& url, Dali::ImageDimensions desiredSize, Dali::FittingMode::Type fittingMode,
107 Dali::SamplingMode::Type samplingMode, const MaskingDataPointer& maskInfo,
108 bool synchronousLoading, TextureManager::TextureId& textureId, Vector4& textureRect,
109 bool& atlasingStatus, bool& loadingStatus, Dali::WrapMode::Type wrapModeU,
110 Dali::WrapMode::Type wrapModeV, TextureUploadObserver* textureObserver,
111 AtlasUploadObserver* atlasObserver, ImageAtlasManagerPtr imageAtlasManager)
113 TextureSet textureSet;
115 loadingStatus = false;
116 textureRect = FULL_ATLAS_RECT;
118 if( VisualUrl::TEXTURE == url.GetProtocolType())
120 std::string location = url.GetLocation();
121 if( location.size() > 0u )
123 TextureId id = std::stoi( location );
124 for( auto&& elem : mExternalTextures )
126 if( elem.textureId == id )
128 return elem.textureSet;
133 else if( synchronousLoading )
138 // if sync loading is required, the loading should immediately when actor is on stage
139 Devel::PixelBuffer pixelBuffer = LoadImageFromFile( url.GetUrl(), desiredSize, fittingMode, samplingMode );
142 data = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
148 textureSet = TextureSet::New();
149 Devel::PixelBuffer pixelBuffer = LoadImageFromFile( BROKEN_IMAGE_URL );
152 data = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
154 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, data.GetPixelFormat(),
155 data.GetWidth(), data.GetHeight() );
156 texture.Upload( data );
157 textureSet = TextureSet::New();
158 textureSet.SetTexture( 0u, texture );
162 if( atlasingStatus ) // attempt atlasing
164 textureSet = imageAtlasManager->Add( textureRect, data );
166 if( !textureSet ) // big image, no atlasing or atlasing failed
168 atlasingStatus = false;
169 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, data.GetPixelFormat(),
170 data.GetWidth(), data.GetHeight() );
171 texture.Upload( data );
172 textureSet = TextureSet::New();
173 textureSet.SetTexture( 0u, texture );
179 loadingStatus = true;
182 textureSet = imageAtlasManager->Add( textureRect, url.GetUrl(), desiredSize, fittingMode, true, atlasObserver );
184 if( !textureSet ) // big image, no atlasing or atlasing failed
186 atlasingStatus = false;
189 textureId = RequestLoad( url, desiredSize, fittingMode, samplingMode, TextureManager::NO_ATLAS, textureObserver );
193 textureId = RequestLoad( url,
194 maskInfo->mAlphaMaskId,
195 maskInfo->mContentScaleFactor,
197 fittingMode, samplingMode,
198 TextureManager::NO_ATLAS,
199 maskInfo->mCropToMask,
203 TextureManager::LoadState loadState = GetTextureState( textureId );
204 loadingStatus = ( loadState == TextureManager::LOADING );
206 if( loadState == TextureManager::UPLOADED )
208 // UploadComplete has already been called - keep the same texture set
209 textureSet = GetTextureSet( textureId );
214 if( ! atlasingStatus && textureSet )
216 Sampler sampler = Sampler::New();
217 sampler.SetWrapMode( wrapModeU, wrapModeV );
218 textureSet.SetSampler( 0u, sampler );
224 TextureManager::TextureId TextureManager::RequestLoad(
225 const VisualUrl& url,
226 const ImageDimensions desiredSize,
227 FittingMode::Type fittingMode,
228 Dali::SamplingMode::Type samplingMode,
229 const UseAtlas useAtlas,
230 TextureUploadObserver* observer )
232 return RequestLoadInternal( url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, useAtlas, false, UPLOAD_TO_TEXTURE, observer );
235 TextureManager::TextureId TextureManager::RequestLoad(
236 const VisualUrl& url,
237 TextureId maskTextureId,
239 const ImageDimensions desiredSize,
240 FittingMode::Type fittingMode,
241 Dali::SamplingMode::Type samplingMode,
242 const UseAtlas useAtlas,
244 TextureUploadObserver* observer )
246 return RequestLoadInternal( url, maskTextureId, contentScale, desiredSize, fittingMode, samplingMode, useAtlas, cropToMask, UPLOAD_TO_TEXTURE, observer );
249 TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl )
251 // Use the normal load procedure to get the alpha mask.
252 return RequestLoadInternal( maskUrl, INVALID_TEXTURE_ID, 1.0f, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::NO_FILTER, NO_ATLAS, false, KEEP_PIXEL_BUFFER, NULL );
255 TextureManager::TextureId TextureManager::RequestLoadInternal(
256 const VisualUrl& url,
257 TextureId maskTextureId,
259 const ImageDimensions desiredSize,
260 FittingMode::Type fittingMode,
261 Dali::SamplingMode::Type samplingMode,
264 StorageType storageType,
265 TextureUploadObserver* observer )
267 // First check if the requested Texture is cached.
268 const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
270 TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
272 // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
273 int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
275 // Check if the requested Texture exists in the cache.
276 if( cacheIndex != INVALID_CACHE_INDEX )
278 // Mark this texture being used by another client resource.
279 ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
280 textureId = mTextureInfoContainer[ cacheIndex ].textureId;
282 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 );
285 if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required
287 // We need a new Texture.
288 textureId = GenerateUniqueTextureId();
289 mTextureInfoContainer.push_back( TextureInfo( textureId, maskTextureId, url.GetUrl(),
290 desiredSize, contentScale, fittingMode, samplingMode,
291 false, cropToMask, useAtlas, textureHash ) );
292 cacheIndex = mTextureInfoContainer.size() - 1u;
294 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 );
297 // The below code path is common whether we are using the cache or not.
298 // The textureInfoIndex now refers to either a pre-existing cached TextureInfo,
299 // or a new TextureInfo just created.
300 TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
301 textureInfo.maskTextureId = maskTextureId;
302 textureInfo.storageType = storageType;
304 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
305 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
306 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
307 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
308 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
310 // 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.
311 switch( textureInfo.loadState )
313 case TextureManager::NOT_STARTED:
315 LoadTexture( textureInfo );
316 ObserveTexture( textureInfo, observer );
319 case TextureManager::LOADING:
321 ObserveTexture( textureInfo, observer );
324 case TextureManager::UPLOADED:
328 // The Texture has already loaded. The other observers have already been notified.
329 // We need to send a "late" loaded notification for this observer.
330 observer->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
331 textureInfo.useAtlas, textureInfo.atlasRect );
335 case TextureManager::CANCELLED:
337 // A cancelled texture hasn't finished loading yet. Treat as a loading texture
338 // (it's ref count has already been incremented, above)
339 textureInfo.loadState = TextureManager::LOADING;
340 ObserveTexture( textureInfo, observer );
343 case TextureManager::LOAD_FINISHED:
344 case TextureManager::WAITING_FOR_MASK:
345 case TextureManager::LOAD_FAILED:
346 // Loading has already completed. Do nothing.
350 // Return the TextureId for which this Texture can now be referenced by externally.
354 void TextureManager::Remove( const TextureManager::TextureId textureId )
356 int textureInfoIndex = GetCacheIndexFromId( textureId );
357 if( textureInfoIndex != INVALID_INDEX )
359 TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
361 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
362 textureId, textureInfoIndex,
363 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
364 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
365 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
366 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
368 // Decrement the reference count and check if this is the last user of this Texture.
369 if( --textureInfo.referenceCount <= 0 )
371 // This is the last remove for this Texture.
372 textureInfo.referenceCount = 0;
373 bool removeTextureInfo = false;
375 // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
376 if( textureInfo.loadState == UPLOADED )
378 if( textureInfo.atlas )
380 textureInfo.atlas.Remove( textureInfo.atlasRect );
382 removeTextureInfo = true;
384 else if( textureInfo.loadState == LOADING )
386 // We mark the textureInfo for removal.
387 // Once the load has completed, this method will be called again.
388 textureInfo.loadState = CANCELLED;
392 // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
393 removeTextureInfo = true;
396 // If the state allows us to remove the TextureInfo data, we do so.
397 if( removeTextureInfo )
399 // Permanently remove the textureInfo struct.
400 mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
406 const VisualUrl& TextureManager::GetVisualUrl( TextureId textureId )
408 int cacheIndex = GetCacheIndexFromId( textureId );
409 DALI_ASSERT_DEBUG( cacheIndex != INVALID_CACHE_INDEX && "TextureId out of range");
411 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
412 return cachedTextureInfo.url;
415 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
417 LoadState loadState = TextureManager::NOT_STARTED;
419 int cacheIndex = GetCacheIndexFromId( textureId );
420 if( cacheIndex != INVALID_CACHE_INDEX )
422 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
423 loadState = cachedTextureInfo.loadState;
428 TextureSet TextureManager::GetTextureSet( TextureId textureId )
430 TextureSet textureSet;// empty handle
432 int cacheIndex = GetCacheIndexFromId( textureId );
433 if( cacheIndex != INVALID_CACHE_INDEX )
435 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
436 textureSet = cachedTextureInfo.textureSet;
441 std::string TextureManager::AddExternalTexture( TextureSet& textureSet )
443 TextureManager::ExternalTextureInfo info;
444 info.textureId = GenerateUniqueTextureId();
445 info.textureSet = textureSet;
446 mExternalTextures.emplace_back( info );
447 return VisualUrl::CreateTextureUrl( std::to_string( info.textureId ) );
450 TextureSet TextureManager::RemoveExternalTexture( const std::string& url )
452 if( url.size() > 0u )
454 // get the location from the Url
455 VisualUrl parseUrl( url );
456 if( VisualUrl::TEXTURE == parseUrl.GetProtocolType() )
458 std::string location = parseUrl.GetLocation();
459 if( location.size() > 0u )
461 TextureId id = std::stoi( location );
462 const auto end = mExternalTextures.end();
463 for( auto iter = mExternalTextures.begin(); iter != end; ++iter )
465 if( iter->textureId == id )
467 auto textureSet = iter->textureSet;
468 mExternalTextures.erase( iter );
478 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
482 if( textureInfo.loadState == NOT_STARTED )
484 textureInfo.loadState = LOADING;
486 if( !textureInfo.loadSynchronously )
488 auto& loadersContainer = textureInfo.url.IsLocalResource() ? mAsyncLocalLoaders : mAsyncRemoteLoaders;
489 auto loadingHelperIt = loadersContainer.GetNext();
490 DALI_ASSERT_ALWAYS(loadingHelperIt != loadersContainer.End());
491 loadingHelperIt->Load(textureInfo.textureId, textureInfo.url,
492 textureInfo.desiredSize, textureInfo.fittingMode,
493 textureInfo.samplingMode, true);
500 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
501 TextureUploadObserver* observer )
505 textureInfo.observerList.PushBack( observer );
506 observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
510 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, Devel::PixelBuffer pixelBuffer )
512 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
514 if( loadingContainer.size() >= 1u )
516 AsyncLoadingInfo loadingInfo = loadingContainer.front();
518 if( loadingInfo.loadId == id )
520 int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
521 if( cacheIndex != INVALID_CACHE_INDEX )
523 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
525 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
527 if( textureInfo.loadState != CANCELLED )
529 // textureInfo can be invalidated after this call (as the mTextureInfoContainer may be modified)
530 PostLoad( textureInfo, pixelBuffer );
534 Remove( textureInfo.textureId );
539 loadingContainer.pop_front();
543 void TextureManager::PostLoad( TextureInfo& textureInfo, Devel::PixelBuffer& pixelBuffer )
545 // Was the load successful?
546 if( pixelBuffer && ( pixelBuffer.GetWidth() != 0 ) && ( pixelBuffer.GetHeight() != 0 ) )
548 // No atlas support for now
549 textureInfo.useAtlas = NO_ATLAS;
551 if( textureInfo.storageType == UPLOAD_TO_TEXTURE )
553 // If there is a mask texture ID associated with this texture, then apply the mask
554 // if it's already loaded. If it hasn't, and the mask is still loading,
555 // wait for the mask to finish loading.
556 if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
558 LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
559 if( maskLoadState == LOADING )
561 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel buffer temporarily
562 textureInfo.loadState = WAITING_FOR_MASK;
564 else if( maskLoadState == LOAD_FINISHED )
566 ApplyMask( pixelBuffer, textureInfo.maskTextureId, textureInfo.scaleFactor, textureInfo.cropToMask );
567 UploadTexture( pixelBuffer, textureInfo );
568 NotifyObservers( textureInfo, true );
573 UploadTexture( pixelBuffer, textureInfo );
574 NotifyObservers( textureInfo, true );
579 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel data
580 textureInfo.loadState = LOAD_FINISHED;
582 // Check if there was another texture waiting for this load to complete
583 // (e.g. if this was an image mask, and its load is on a different thread)
584 CheckForWaitingTexture( textureInfo );
589 DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
590 // @todo If the load was unsuccessful, upload the broken image.
591 textureInfo.loadState = LOAD_FAILED;
592 CheckForWaitingTexture( textureInfo );
593 NotifyObservers( textureInfo, false );
597 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
599 // Search the cache, checking if any texture has this texture id as a
601 const unsigned int size = mTextureInfoContainer.size();
603 for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
605 if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
606 mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
608 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
609 Devel::PixelBuffer pixelBuffer = textureInfo.pixelBuffer;
610 textureInfo.pixelBuffer.Reset();
612 if( maskTextureInfo.loadState == LOAD_FINISHED )
614 ApplyMask( pixelBuffer, maskTextureInfo.textureId, textureInfo.scaleFactor, textureInfo.cropToMask );
615 UploadTexture( pixelBuffer, textureInfo );
616 NotifyObservers( textureInfo, true );
620 DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
621 textureInfo.loadState = LOAD_FAILED;
622 NotifyObservers( textureInfo, false );
628 void TextureManager::ApplyMask(
629 Devel::PixelBuffer& pixelBuffer, TextureId maskTextureId,
630 float contentScale, bool cropToMask )
632 int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
633 Devel::PixelBuffer maskPixelBuffer = mTextureInfoContainer[maskCacheIndex].pixelBuffer;
634 pixelBuffer.ApplyMask( maskPixelBuffer, contentScale, cropToMask );
637 void TextureManager::UploadTexture( Devel::PixelBuffer& pixelBuffer, TextureInfo& textureInfo )
639 if( textureInfo.useAtlas != USE_ATLAS )
641 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
643 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelBuffer.GetPixelFormat(), pixelBuffer.GetWidth(), pixelBuffer.GetHeight() );
644 PixelData pixelData = Devel::PixelBuffer::Convert( pixelBuffer );
645 texture.Upload( pixelData );
646 textureInfo.textureSet = TextureSet::New();
647 textureInfo.textureSet.SetTexture( 0u, texture );
650 // Update the load state.
651 // Note: This is regardless of success as we care about whether a
652 // load attempt is in progress or not. If unsuccessful, a broken
653 // image is still loaded.
654 textureInfo.loadState = UPLOADED;
657 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
659 TextureId textureId = textureInfo.textureId;
661 // If there is an observer: Notify the load is complete, whether successful or not,
662 // and erase it from the list
663 unsigned int observerCount = textureInfo.observerList.Count();
664 TextureInfo* info = &textureInfo;
666 while( observerCount )
668 TextureUploadObserver* observer = info->observerList[0];
670 // During UploadComplete() a Control ResourceReady() signal is emitted.
671 // During that signal the app may add remove /add Textures (e.g. via
672 // ImageViews). At this point no more observers can be added to the
673 // observerList, because textureInfo.loadState = UPLOADED. However it is
674 // possible for observers to be removed, hence we check the observer list
675 // count every iteration.
677 // The reference to the textureInfo struct can also become invalidated,
678 // because new load requests can modify the mTextureInfoContainer list
679 // (e.g. if more requests are pushed back it can cause the list to be
680 // resized invalidating the reference to the TextureInfo ).
681 observer->UploadComplete( success, info->textureId, info->textureSet, info->useAtlas, info->atlasRect );
682 observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
684 // Get the textureInfo from the container again as it may have been
687 int textureInfoIndex = GetCacheIndexFromId( textureId );
688 if( textureInfoIndex == INVALID_CACHE_INDEX)
690 return; // texture has been removed - can stop.
693 info = &mTextureInfoContainer[ textureInfoIndex ];
694 observerCount = info->observerList.Count();
695 if ( observerCount > 0 )
697 // remove the observer that was just triggered if it's still in the list
698 for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
702 info->observerList.Erase( j );
711 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
713 return mCurrentTextureId++;
716 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
718 const unsigned int size = mTextureInfoContainer.size();
720 for( unsigned int i = 0; i < size; ++i )
722 if( mTextureInfoContainer[i].textureId == textureId )
728 DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
729 return INVALID_CACHE_INDEX;
732 TextureManager::TextureHash TextureManager::GenerateHash(
733 const std::string& url,
734 const ImageDimensions size,
735 const FittingMode::Type fittingMode,
736 const Dali::SamplingMode::Type samplingMode,
737 const UseAtlas useAtlas,
738 TextureId maskTextureId )
740 std::string hashTarget( url );
741 const size_t urlLength = hashTarget.length();
742 const uint16_t width = size.GetWidth();
743 const uint16_t height = size.GetWidth();
745 // If either the width or height has been specified, include the resizing options in the hash
746 if( width != 0 || height != 0 )
748 // We are appending 5 bytes to the URL to form the hash input.
749 hashTarget.resize( urlLength + 5u );
750 char* hashTargetPtr = &( hashTarget[ urlLength ] );
752 // Pack the width and height (4 bytes total).
753 *hashTargetPtr++ = size.GetWidth() & 0xff;
754 *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
755 *hashTargetPtr++ = size.GetHeight() & 0xff;
756 *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
758 // Bit-pack the FittingMode, SamplingMode and atlasing.
759 // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
760 *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
764 // We are not including sizing information, but we still need an extra byte for atlasing.
765 hashTarget.resize( urlLength + 1u );
766 // Add the atlasing to the hash input.
767 hashTarget[ urlLength ] = useAtlas;
770 if( maskTextureId != INVALID_TEXTURE_ID )
772 hashTarget.resize( urlLength + sizeof( TextureId ) );
773 TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
775 // Append the hash target to the end of the URL byte by byte:
776 // (to avoid SIGBUS / alignment issues)
777 for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
779 *hashTargetPtr++ = maskTextureId & 0xff;
780 maskTextureId >>= 8u;
784 return Dali::CalculateHash( hashTarget );
787 int TextureManager::FindCachedTexture(
788 const TextureManager::TextureHash hash,
789 const std::string& url,
790 const ImageDimensions size,
791 const FittingMode::Type fittingMode,
792 const Dali::SamplingMode::Type samplingMode,
794 TextureId maskTextureId)
796 // Default to an invalid ID, in case we do not find a match.
797 int cacheIndex = INVALID_CACHE_INDEX;
799 // Iterate through our hashes to find a match.
800 const unsigned int count = mTextureInfoContainer.size();
801 for( unsigned int i = 0u; i < count; ++i )
803 if( mTextureInfoContainer[i].hash == hash )
805 // We have a match, now we check all the original parameters in case of a hash collision.
806 TextureInfo& textureInfo( mTextureInfoContainer[i] );
808 if( ( url == textureInfo.url.GetUrl() ) &&
809 ( useAtlas == textureInfo.useAtlas ) &&
810 ( maskTextureId == textureInfo.maskTextureId ) &&
811 ( size == textureInfo.desiredSize ) &&
812 ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
813 ( fittingMode == textureInfo.fittingMode &&
814 samplingMode == textureInfo.samplingMode ) ) )
816 // The found Texture is a match.
826 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
828 const unsigned int count = mTextureInfoContainer.size();
829 for( unsigned int i = 0; i < count; ++i )
831 TextureInfo& textureInfo( mTextureInfoContainer[i] );
832 for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); )
836 j = textureInfo.observerList.Erase( j );
846 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(TextureManager& textureManager)
847 : AsyncLoadingHelper(Toolkit::AsyncImageLoader::New(), textureManager,
848 AsyncLoadingInfoContainerType())
852 void TextureManager::AsyncLoadingHelper::Load(TextureId textureId,
853 const VisualUrl& url,
854 ImageDimensions desiredSize,
855 FittingMode::Type fittingMode,
856 SamplingMode::Type samplingMode,
857 bool orientationCorrection)
859 mLoadingInfoContainer.push_back(AsyncLoadingInfo(textureId));
860 auto id = mLoader.Load(url.GetUrl(), desiredSize, fittingMode, samplingMode, orientationCorrection);
861 mLoadingInfoContainer.back().loadId = id;
864 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(AsyncLoadingHelper&& rhs)
865 : AsyncLoadingHelper(rhs.mLoader, rhs.mTextureManager, std::move(rhs.mLoadingInfoContainer))
869 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(
870 Toolkit::AsyncImageLoader loader,
871 TextureManager& textureManager,
872 AsyncLoadingInfoContainerType&& loadingInfoContainer)
874 mTextureManager(textureManager),
875 mLoadingInfoContainer(std::move(loadingInfoContainer))
877 DevelAsyncImageLoader::PixelBufferLoadedSignal(mLoader).Connect(
878 this, &AsyncLoadingHelper::AsyncLoadComplete);
881 void TextureManager::AsyncLoadingHelper::AsyncLoadComplete(uint32_t id,
882 Devel::PixelBuffer pixelBuffer)
884 mTextureManager.AsyncLoadComplete(mLoadingInfoContainer, id, pixelBuffer);
887 } // namespace Internal
889 } // namespace Toolkit