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, bool orientationCorrection )
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 Devel::PixelBuffer pixelBuffer = LoadImageFromFile( url.GetUrl(), desiredSize, fittingMode, samplingMode, orientationCorrection );
141 data = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
147 textureSet = TextureSet::New();
148 Devel::PixelBuffer pixelBuffer = LoadImageFromFile( BROKEN_IMAGE_URL );
151 data = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
153 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, data.GetPixelFormat(),
154 data.GetWidth(), data.GetHeight() );
155 texture.Upload( data );
156 textureSet = TextureSet::New();
157 textureSet.SetTexture( 0u, texture );
161 if( atlasingStatus ) // attempt atlasing
163 textureSet = imageAtlasManager->Add( textureRect, data );
165 if( !textureSet ) // big image, no atlasing or atlasing failed
167 atlasingStatus = false;
168 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, data.GetPixelFormat(),
169 data.GetWidth(), data.GetHeight() );
170 texture.Upload( data );
171 textureSet = TextureSet::New();
172 textureSet.SetTexture( 0u, texture );
178 loadingStatus = true;
181 textureSet = imageAtlasManager->Add( textureRect, url.GetUrl(), desiredSize, fittingMode, true, atlasObserver );
183 if( !textureSet ) // big image, no atlasing or atlasing failed
185 atlasingStatus = false;
188 textureId = RequestLoad( url, desiredSize, fittingMode, samplingMode, TextureManager::NO_ATLAS, textureObserver, orientationCorrection );
192 textureId = RequestLoad( url,
193 maskInfo->mAlphaMaskId,
194 maskInfo->mContentScaleFactor,
196 fittingMode, samplingMode,
197 TextureManager::NO_ATLAS,
198 maskInfo->mCropToMask,
200 orientationCorrection);
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,
231 bool orientationCorrection )
233 return RequestLoadInternal( url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, useAtlas, false, UPLOAD_TO_TEXTURE, observer, orientationCorrection );
236 TextureManager::TextureId TextureManager::RequestLoad(
237 const VisualUrl& url,
238 TextureId maskTextureId,
240 const ImageDimensions desiredSize,
241 FittingMode::Type fittingMode,
242 Dali::SamplingMode::Type samplingMode,
243 const UseAtlas useAtlas,
245 TextureUploadObserver* observer,
246 bool orientationCorrection )
248 return RequestLoadInternal( url, maskTextureId, contentScale, desiredSize, fittingMode, samplingMode, useAtlas, cropToMask, UPLOAD_TO_TEXTURE, observer, orientationCorrection );
251 TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl )
253 // Use the normal load procedure to get the alpha mask.
254 return RequestLoadInternal( maskUrl, INVALID_TEXTURE_ID, 1.0f, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::NO_FILTER, NO_ATLAS, false, KEEP_PIXEL_BUFFER, NULL, true );
257 TextureManager::TextureId TextureManager::RequestLoadInternal(
258 const VisualUrl& url,
259 TextureId maskTextureId,
261 const ImageDimensions desiredSize,
262 FittingMode::Type fittingMode,
263 Dali::SamplingMode::Type samplingMode,
266 StorageType storageType,
267 TextureUploadObserver* observer,
268 bool orientationCorrection )
270 // First check if the requested Texture is cached.
271 const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
273 TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
275 // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
276 int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
278 // Check if the requested Texture exists in the cache.
279 if( cacheIndex != INVALID_CACHE_INDEX )
281 // Mark this texture being used by another client resource.
282 ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
283 textureId = mTextureInfoContainer[ cacheIndex ].textureId;
285 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 );
288 if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required
290 // We need a new Texture.
291 textureId = GenerateUniqueTextureId();
292 mTextureInfoContainer.push_back( TextureInfo( textureId, maskTextureId, url.GetUrl(),
293 desiredSize, contentScale, fittingMode, samplingMode,
294 false, cropToMask, useAtlas, textureHash, orientationCorrection ) );
295 cacheIndex = mTextureInfoContainer.size() - 1u;
297 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 );
300 // The below code path is common whether we are using the cache or not.
301 // The textureInfoIndex now refers to either a pre-existing cached TextureInfo,
302 // or a new TextureInfo just created.
303 TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
304 textureInfo.maskTextureId = maskTextureId;
305 textureInfo.storageType = storageType;
306 textureInfo.orientationCorrection = orientationCorrection;
308 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
309 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
310 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
311 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
312 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
314 // 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.
315 switch( textureInfo.loadState )
317 case TextureManager::NOT_STARTED:
319 LoadTexture( textureInfo );
320 ObserveTexture( textureInfo, observer );
323 case TextureManager::LOADING:
325 ObserveTexture( textureInfo, observer );
328 case TextureManager::UPLOADED:
332 // The Texture has already loaded. The other observers have already been notified.
333 // We need to send a "late" loaded notification for this observer.
334 observer->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
335 textureInfo.useAtlas, textureInfo.atlasRect );
339 case TextureManager::CANCELLED:
341 // A cancelled texture hasn't finished loading yet. Treat as a loading texture
342 // (it's ref count has already been incremented, above)
343 textureInfo.loadState = TextureManager::LOADING;
344 ObserveTexture( textureInfo, observer );
347 case TextureManager::LOAD_FINISHED:
348 case TextureManager::WAITING_FOR_MASK:
349 case TextureManager::LOAD_FAILED:
350 // Loading has already completed. Do nothing.
354 // Return the TextureId for which this Texture can now be referenced by externally.
358 void TextureManager::Remove( const TextureManager::TextureId textureId )
360 int textureInfoIndex = GetCacheIndexFromId( textureId );
361 if( textureInfoIndex != INVALID_INDEX )
363 TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
365 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
366 textureId, textureInfoIndex,
367 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
368 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
369 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
370 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
372 // Decrement the reference count and check if this is the last user of this Texture.
373 if( --textureInfo.referenceCount <= 0 )
375 // This is the last remove for this Texture.
376 textureInfo.referenceCount = 0;
377 bool removeTextureInfo = false;
379 // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
380 if( textureInfo.loadState == UPLOADED )
382 if( textureInfo.atlas )
384 textureInfo.atlas.Remove( textureInfo.atlasRect );
386 removeTextureInfo = true;
388 else if( textureInfo.loadState == LOADING )
390 // We mark the textureInfo for removal.
391 // Once the load has completed, this method will be called again.
392 textureInfo.loadState = CANCELLED;
396 // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
397 removeTextureInfo = true;
400 // If the state allows us to remove the TextureInfo data, we do so.
401 if( removeTextureInfo )
403 // Permanently remove the textureInfo struct.
404 mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
410 const VisualUrl& TextureManager::GetVisualUrl( TextureId textureId )
412 int cacheIndex = GetCacheIndexFromId( textureId );
413 DALI_ASSERT_DEBUG( cacheIndex != INVALID_CACHE_INDEX && "TextureId out of range");
415 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
416 return cachedTextureInfo.url;
419 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
421 LoadState loadState = TextureManager::NOT_STARTED;
423 int cacheIndex = GetCacheIndexFromId( textureId );
424 if( cacheIndex != INVALID_CACHE_INDEX )
426 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
427 loadState = cachedTextureInfo.loadState;
432 TextureSet TextureManager::GetTextureSet( TextureId textureId )
434 TextureSet textureSet;// empty handle
436 int cacheIndex = GetCacheIndexFromId( textureId );
437 if( cacheIndex != INVALID_CACHE_INDEX )
439 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
440 textureSet = cachedTextureInfo.textureSet;
445 std::string TextureManager::AddExternalTexture( TextureSet& textureSet )
447 TextureManager::ExternalTextureInfo info;
448 info.textureId = GenerateUniqueTextureId();
449 info.textureSet = textureSet;
450 mExternalTextures.emplace_back( info );
451 return VisualUrl::CreateTextureUrl( std::to_string( info.textureId ) );
454 TextureSet TextureManager::RemoveExternalTexture( const std::string& url )
456 if( url.size() > 0u )
458 // get the location from the Url
459 VisualUrl parseUrl( url );
460 if( VisualUrl::TEXTURE == parseUrl.GetProtocolType() )
462 std::string location = parseUrl.GetLocation();
463 if( location.size() > 0u )
465 TextureId id = std::stoi( location );
466 const auto end = mExternalTextures.end();
467 for( auto iter = mExternalTextures.begin(); iter != end; ++iter )
469 if( iter->textureId == id )
471 auto textureSet = iter->textureSet;
472 mExternalTextures.erase( iter );
482 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
486 if( textureInfo.loadState == NOT_STARTED )
488 textureInfo.loadState = LOADING;
490 if( !textureInfo.loadSynchronously )
492 auto& loadersContainer = textureInfo.url.IsLocalResource() ? mAsyncLocalLoaders : mAsyncRemoteLoaders;
493 auto loadingHelperIt = loadersContainer.GetNext();
494 DALI_ASSERT_ALWAYS(loadingHelperIt != loadersContainer.End());
495 loadingHelperIt->Load(textureInfo.textureId, textureInfo.url,
496 textureInfo.desiredSize, textureInfo.fittingMode,
497 textureInfo.samplingMode, textureInfo.orientationCorrection );
504 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
505 TextureUploadObserver* observer )
509 textureInfo.observerList.PushBack( observer );
510 observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
514 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, Devel::PixelBuffer pixelBuffer )
516 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
518 if( loadingContainer.size() >= 1u )
520 AsyncLoadingInfo loadingInfo = loadingContainer.front();
522 if( loadingInfo.loadId == id )
524 int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
525 if( cacheIndex != INVALID_CACHE_INDEX )
527 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
529 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
531 if( textureInfo.loadState != CANCELLED )
533 // textureInfo can be invalidated after this call (as the mTextureInfoContainer may be modified)
534 PostLoad( textureInfo, pixelBuffer );
538 Remove( textureInfo.textureId );
543 loadingContainer.pop_front();
547 void TextureManager::PostLoad( TextureInfo& textureInfo, Devel::PixelBuffer& pixelBuffer )
549 // Was the load successful?
550 if( pixelBuffer && ( pixelBuffer.GetWidth() != 0 ) && ( pixelBuffer.GetHeight() != 0 ) )
552 // No atlas support for now
553 textureInfo.useAtlas = NO_ATLAS;
555 if( textureInfo.storageType == UPLOAD_TO_TEXTURE )
557 // If there is a mask texture ID associated with this texture, then apply the mask
558 // if it's already loaded. If it hasn't, and the mask is still loading,
559 // wait for the mask to finish loading.
560 if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
562 LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
563 if( maskLoadState == LOADING )
565 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel buffer temporarily
566 textureInfo.loadState = WAITING_FOR_MASK;
568 else if( maskLoadState == LOAD_FINISHED )
570 ApplyMask( pixelBuffer, textureInfo.maskTextureId, textureInfo.scaleFactor, textureInfo.cropToMask );
571 UploadTexture( pixelBuffer, textureInfo );
572 NotifyObservers( textureInfo, true );
577 UploadTexture( pixelBuffer, textureInfo );
578 NotifyObservers( textureInfo, true );
583 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel data
584 textureInfo.loadState = LOAD_FINISHED;
586 // Check if there was another texture waiting for this load to complete
587 // (e.g. if this was an image mask, and its load is on a different thread)
588 CheckForWaitingTexture( textureInfo );
593 DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
594 // @todo If the load was unsuccessful, upload the broken image.
595 textureInfo.loadState = LOAD_FAILED;
596 CheckForWaitingTexture( textureInfo );
597 NotifyObservers( textureInfo, false );
601 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
603 // Search the cache, checking if any texture has this texture id as a
605 const unsigned int size = mTextureInfoContainer.size();
607 for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
609 if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
610 mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
612 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
613 Devel::PixelBuffer pixelBuffer = textureInfo.pixelBuffer;
614 textureInfo.pixelBuffer.Reset();
616 if( maskTextureInfo.loadState == LOAD_FINISHED )
618 ApplyMask( pixelBuffer, maskTextureInfo.textureId, textureInfo.scaleFactor, textureInfo.cropToMask );
619 UploadTexture( pixelBuffer, textureInfo );
620 NotifyObservers( textureInfo, true );
624 DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
625 textureInfo.loadState = LOAD_FAILED;
626 NotifyObservers( textureInfo, false );
632 void TextureManager::ApplyMask(
633 Devel::PixelBuffer& pixelBuffer, TextureId maskTextureId,
634 float contentScale, bool cropToMask )
636 int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
637 Devel::PixelBuffer maskPixelBuffer = mTextureInfoContainer[maskCacheIndex].pixelBuffer;
638 pixelBuffer.ApplyMask( maskPixelBuffer, contentScale, cropToMask );
641 void TextureManager::UploadTexture( Devel::PixelBuffer& pixelBuffer, TextureInfo& textureInfo )
643 if( textureInfo.useAtlas != USE_ATLAS )
645 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
647 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelBuffer.GetPixelFormat(), pixelBuffer.GetWidth(), pixelBuffer.GetHeight() );
648 PixelData pixelData = Devel::PixelBuffer::Convert( pixelBuffer );
649 texture.Upload( pixelData );
650 textureInfo.textureSet = TextureSet::New();
651 textureInfo.textureSet.SetTexture( 0u, texture );
654 // Update the load state.
655 // Note: This is regardless of success as we care about whether a
656 // load attempt is in progress or not. If unsuccessful, a broken
657 // image is still loaded.
658 textureInfo.loadState = UPLOADED;
661 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
663 TextureId textureId = textureInfo.textureId;
665 // If there is an observer: Notify the load is complete, whether successful or not,
666 // and erase it from the list
667 unsigned int observerCount = textureInfo.observerList.Count();
668 TextureInfo* info = &textureInfo;
670 while( observerCount )
672 TextureUploadObserver* observer = info->observerList[0];
674 // During UploadComplete() a Control ResourceReady() signal is emitted.
675 // During that signal the app may add remove /add Textures (e.g. via
676 // ImageViews). At this point no more observers can be added to the
677 // observerList, because textureInfo.loadState = UPLOADED. However it is
678 // possible for observers to be removed, hence we check the observer list
679 // count every iteration.
681 // The reference to the textureInfo struct can also become invalidated,
682 // because new load requests can modify the mTextureInfoContainer list
683 // (e.g. if more requests are pushed back it can cause the list to be
684 // resized invalidating the reference to the TextureInfo ).
685 observer->UploadComplete( success, info->textureId, info->textureSet, info->useAtlas, info->atlasRect );
686 observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
688 // Get the textureInfo from the container again as it may have been
691 int textureInfoIndex = GetCacheIndexFromId( textureId );
692 if( textureInfoIndex == INVALID_CACHE_INDEX)
694 return; // texture has been removed - can stop.
697 info = &mTextureInfoContainer[ textureInfoIndex ];
698 observerCount = info->observerList.Count();
699 if ( observerCount > 0 )
701 // remove the observer that was just triggered if it's still in the list
702 for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
706 info->observerList.Erase( j );
715 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
717 return mCurrentTextureId++;
720 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
722 const unsigned int size = mTextureInfoContainer.size();
724 for( unsigned int i = 0; i < size; ++i )
726 if( mTextureInfoContainer[i].textureId == textureId )
732 DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
733 return INVALID_CACHE_INDEX;
736 TextureManager::TextureHash TextureManager::GenerateHash(
737 const std::string& url,
738 const ImageDimensions size,
739 const FittingMode::Type fittingMode,
740 const Dali::SamplingMode::Type samplingMode,
741 const UseAtlas useAtlas,
742 TextureId maskTextureId )
744 std::string hashTarget( url );
745 const size_t urlLength = hashTarget.length();
746 const uint16_t width = size.GetWidth();
747 const uint16_t height = size.GetWidth();
749 // If either the width or height has been specified, include the resizing options in the hash
750 if( width != 0 || height != 0 )
752 // We are appending 5 bytes to the URL to form the hash input.
753 hashTarget.resize( urlLength + 5u );
754 char* hashTargetPtr = &( hashTarget[ urlLength ] );
756 // Pack the width and height (4 bytes total).
757 *hashTargetPtr++ = size.GetWidth() & 0xff;
758 *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
759 *hashTargetPtr++ = size.GetHeight() & 0xff;
760 *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
762 // Bit-pack the FittingMode, SamplingMode and atlasing.
763 // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
764 *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
768 // We are not including sizing information, but we still need an extra byte for atlasing.
769 hashTarget.resize( urlLength + 1u );
770 // Add the atlasing to the hash input.
771 hashTarget[ urlLength ] = useAtlas;
774 if( maskTextureId != INVALID_TEXTURE_ID )
776 hashTarget.resize( urlLength + sizeof( TextureId ) );
777 TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
779 // Append the hash target to the end of the URL byte by byte:
780 // (to avoid SIGBUS / alignment issues)
781 for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
783 *hashTargetPtr++ = maskTextureId & 0xff;
784 maskTextureId >>= 8u;
788 return Dali::CalculateHash( hashTarget );
791 int TextureManager::FindCachedTexture(
792 const TextureManager::TextureHash hash,
793 const std::string& url,
794 const ImageDimensions size,
795 const FittingMode::Type fittingMode,
796 const Dali::SamplingMode::Type samplingMode,
798 TextureId maskTextureId)
800 // Default to an invalid ID, in case we do not find a match.
801 int cacheIndex = INVALID_CACHE_INDEX;
803 // Iterate through our hashes to find a match.
804 const unsigned int count = mTextureInfoContainer.size();
805 for( unsigned int i = 0u; i < count; ++i )
807 if( mTextureInfoContainer[i].hash == hash )
809 // We have a match, now we check all the original parameters in case of a hash collision.
810 TextureInfo& textureInfo( mTextureInfoContainer[i] );
812 if( ( url == textureInfo.url.GetUrl() ) &&
813 ( useAtlas == textureInfo.useAtlas ) &&
814 ( maskTextureId == textureInfo.maskTextureId ) &&
815 ( size == textureInfo.desiredSize ) &&
816 ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
817 ( fittingMode == textureInfo.fittingMode &&
818 samplingMode == textureInfo.samplingMode ) ) )
820 // The found Texture is a match.
830 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
832 const unsigned int count = mTextureInfoContainer.size();
833 for( unsigned int i = 0; i < count; ++i )
835 TextureInfo& textureInfo( mTextureInfoContainer[i] );
836 for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); )
840 j = textureInfo.observerList.Erase( j );
850 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(TextureManager& textureManager)
851 : AsyncLoadingHelper(Toolkit::AsyncImageLoader::New(), textureManager,
852 AsyncLoadingInfoContainerType())
856 void TextureManager::AsyncLoadingHelper::Load(TextureId textureId,
857 const VisualUrl& url,
858 ImageDimensions desiredSize,
859 FittingMode::Type fittingMode,
860 SamplingMode::Type samplingMode,
861 bool orientationCorrection)
863 mLoadingInfoContainer.push_back(AsyncLoadingInfo(textureId));
864 auto id = mLoader.Load(url.GetUrl(), desiredSize, fittingMode, samplingMode, orientationCorrection);
865 mLoadingInfoContainer.back().loadId = id;
868 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(AsyncLoadingHelper&& rhs)
869 : AsyncLoadingHelper(rhs.mLoader, rhs.mTextureManager, std::move(rhs.mLoadingInfoContainer))
873 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(
874 Toolkit::AsyncImageLoader loader,
875 TextureManager& textureManager,
876 AsyncLoadingInfoContainerType&& loadingInfoContainer)
878 mTextureManager(textureManager),
879 mLoadingInfoContainer(std::move(loadingInfoContainer))
881 DevelAsyncImageLoader::PixelBufferLoadedSignal(mLoader).Connect(
882 this, &AsyncLoadingHelper::AsyncLoadComplete);
885 void TextureManager::AsyncLoadingHelper::AsyncLoadComplete(uint32_t id,
886 Devel::PixelBuffer pixelBuffer)
888 mTextureManager.AsyncLoadComplete(mLoadingInfoContainer, id, pixelBuffer);
891 } // namespace Internal
893 } // namespace Toolkit