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::strtoul(numberString, nullptr, 10) : 0;
51 constexpr auto MAX_NUMBER_OF_THREADS = 100u;
52 DALI_ASSERT_DEBUG( numberOfThreads < MAX_NUMBER_OF_THREADS );
53 return ( numberOfThreads > 0 && numberOfThreads < MAX_NUMBER_OF_THREADS ) ? numberOfThreads : defaultValue;
56 size_t GetNumberOfLocalLoaderThreads()
58 return GetNumberOfThreads(NUMBER_OF_LOCAL_LOADER_THREADS_ENV, DEFAULT_NUMBER_OF_LOCAL_LOADER_THREADS);
61 size_t GetNumberOfRemoteLoaderThreads()
63 return GetNumberOfThreads(NUMBER_OF_REMOTE_LOADER_THREADS_ENV, DEFAULT_NUMBER_OF_REMOTE_LOADER_THREADS);
81 Debug::Filter* gTextureManagerLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_TEXTURE_MANAGER" );
84 const uint32_t DEFAULT_ATLAS_SIZE( 1024u ); ///< This size can fit 8 by 8 images of average size 128 * 128
85 const Vector4 FULL_ATLAS_RECT( 0.0f, 0.0f, 1.0f, 1.0f ); ///< UV Rectangle that covers the full Texture
86 const char * const BROKEN_IMAGE_URL( DALI_IMAGE_DIR "broken.png" ); ///< URL For the broken image placeholder
87 const int INVALID_INDEX( -1 ); ///< Invalid index used to represent a non-existant TextureInfo struct
88 const int INVALID_CACHE_INDEX( -1 ); ///< Invalid Cache index
90 } // Anonymous namespace
92 TextureManager::MaskingData::MaskingData()
94 mAlphaMaskId( INVALID_TEXTURE_ID ),
95 mContentScaleFactor( 1.0f ),
100 TextureManager::TextureManager()
101 : mAsyncLocalLoaders( GetNumberOfLocalLoaderThreads(), [&]() { return AsyncLoadingHelper(*this); } ),
102 mAsyncRemoteLoaders( GetNumberOfRemoteLoaderThreads(), [&]() { return AsyncLoadingHelper(*this); } ),
103 mCurrentTextureId( 0 )
107 TextureSet TextureManager::LoadTexture(
108 const VisualUrl& url, Dali::ImageDimensions desiredSize, Dali::FittingMode::Type fittingMode,
109 Dali::SamplingMode::Type samplingMode, const MaskingDataPointer& maskInfo,
110 bool synchronousLoading, TextureManager::TextureId& textureId, Vector4& textureRect,
111 bool& atlasingStatus, bool& loadingStatus, Dali::WrapMode::Type wrapModeU,
112 Dali::WrapMode::Type wrapModeV, TextureUploadObserver* textureObserver,
113 AtlasUploadObserver* atlasObserver, ImageAtlasManagerPtr imageAtlasManager, bool orientationCorrection,
114 TextureManager::ReloadPolicy reloadPolicy, TextureManager::MultiplyOnLoad preMultiplyOnLoad )
116 TextureSet textureSet;
118 loadingStatus = false;
119 textureRect = FULL_ATLAS_RECT;
121 if( VisualUrl::TEXTURE == url.GetProtocolType())
123 std::string location = url.GetLocation();
124 if( location.size() > 0u )
126 TextureId id = std::stoi( location );
127 for( auto&& elem : mExternalTextures )
129 if( elem.textureId == id )
131 textureId = elem.textureId;
132 return elem.textureSet;
137 else if( synchronousLoading )
142 Devel::PixelBuffer pixelBuffer = LoadImageFromFile( url.GetUrl(), desiredSize, fittingMode, samplingMode,
143 orientationCorrection );
146 if( preMultiplyOnLoad == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD )
148 pixelBuffer.MultiplyColorByAlpha();
151 data = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
157 textureSet = TextureSet::New();
158 Devel::PixelBuffer pixelBuffer = LoadImageFromFile( BROKEN_IMAGE_URL );
161 if( preMultiplyOnLoad == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD)
163 pixelBuffer.MultiplyColorByAlpha();
165 data = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
167 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, data.GetPixelFormat(),
168 data.GetWidth(), data.GetHeight() );
169 texture.Upload( data );
170 textureSet = TextureSet::New();
171 textureSet.SetTexture( 0u, texture );
175 if( atlasingStatus ) // attempt atlasing
177 textureSet = imageAtlasManager->Add( textureRect, data );
179 if( !textureSet ) // big image, no atlasing or atlasing failed
181 atlasingStatus = false;
182 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, data.GetPixelFormat(),
183 data.GetWidth(), data.GetHeight() );
184 texture.Upload( data );
185 textureSet = TextureSet::New();
186 textureSet.SetTexture( 0u, texture );
192 loadingStatus = true;
195 textureSet = imageAtlasManager->Add( textureRect, url.GetUrl(), desiredSize, fittingMode, true, atlasObserver );
197 if( !textureSet ) // big image, no atlasing or atlasing failed
199 atlasingStatus = false;
202 textureId = RequestLoad( url, desiredSize, fittingMode, samplingMode, TextureManager::NO_ATLAS,
203 textureObserver, orientationCorrection, reloadPolicy, preMultiplyOnLoad );
207 textureId = RequestLoad( url,
208 maskInfo->mAlphaMaskId,
209 maskInfo->mContentScaleFactor,
211 fittingMode, samplingMode,
212 TextureManager::NO_ATLAS,
213 maskInfo->mCropToMask,
215 orientationCorrection,
216 reloadPolicy, preMultiplyOnLoad );
219 TextureManager::LoadState loadState = GetTextureStateInternal( textureId );
220 loadingStatus = ( loadState == TextureManager::LOADING );
222 if( loadState == TextureManager::UPLOADED )
224 // UploadComplete has already been called - keep the same texture set
225 textureSet = GetTextureSet( textureId );
230 if( ! atlasingStatus && textureSet )
232 Sampler sampler = Sampler::New();
233 sampler.SetWrapMode( wrapModeU, wrapModeV );
234 textureSet.SetSampler( 0u, sampler );
240 TextureManager::TextureId TextureManager::RequestLoad(
241 const VisualUrl& url,
242 const ImageDimensions desiredSize,
243 FittingMode::Type fittingMode,
244 Dali::SamplingMode::Type samplingMode,
245 const UseAtlas useAtlas,
246 TextureUploadObserver* observer,
247 bool orientationCorrection,
248 TextureManager::ReloadPolicy reloadPolicy,
249 TextureManager::MultiplyOnLoad preMultiplyOnLoad )
251 return RequestLoadInternal( url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, useAtlas,
252 false, UPLOAD_TO_TEXTURE, observer, orientationCorrection, reloadPolicy, preMultiplyOnLoad );
255 TextureManager::TextureId TextureManager::RequestLoad(
256 const VisualUrl& url,
257 TextureId maskTextureId,
259 const ImageDimensions desiredSize,
260 FittingMode::Type fittingMode,
261 Dali::SamplingMode::Type samplingMode,
262 const UseAtlas useAtlas,
264 TextureUploadObserver* observer,
265 bool orientationCorrection,
266 TextureManager::ReloadPolicy reloadPolicy,
267 TextureManager::MultiplyOnLoad preMultiplyOnLoad )
269 return RequestLoadInternal( url, maskTextureId, contentScale, desiredSize, fittingMode, samplingMode, useAtlas,
270 cropToMask, UPLOAD_TO_TEXTURE, observer, orientationCorrection, reloadPolicy, preMultiplyOnLoad );
273 TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl )
275 // Use the normal load procedure to get the alpha mask.
276 return RequestLoadInternal( maskUrl, INVALID_TEXTURE_ID, 1.0f, ImageDimensions(), FittingMode::SCALE_TO_FILL,
277 SamplingMode::NO_FILTER, NO_ATLAS, false, KEEP_PIXEL_BUFFER, NULL, true,
278 TextureManager::ReloadPolicy::CACHED, TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY);
281 TextureManager::TextureId TextureManager::RequestLoadInternal(
282 const VisualUrl& url,
283 TextureId maskTextureId,
285 const ImageDimensions desiredSize,
286 FittingMode::Type fittingMode,
287 Dali::SamplingMode::Type samplingMode,
290 StorageType storageType,
291 TextureUploadObserver* observer,
292 bool orientationCorrection,
293 TextureManager::ReloadPolicy reloadPolicy,
294 TextureManager::MultiplyOnLoad preMultiplyOnLoad)
296 // First check if the requested Texture is cached.
297 const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas,
298 maskTextureId, preMultiplyOnLoad );
300 TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
302 // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
303 int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas,
304 maskTextureId, preMultiplyOnLoad );
306 // Check if the requested Texture exists in the cache.
307 if( cacheIndex != INVALID_CACHE_INDEX )
309 if ( TextureManager::ReloadPolicy::CACHED == reloadPolicy )
311 // Mark this texture being used by another client resource. Forced reload would replace the current texture
312 // without the need for incrementing the reference count.
313 ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
315 textureId = mTextureInfoContainer[ cacheIndex ].textureId;
316 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) Using cached texture id@%d, textureId=%d\n",
317 url.GetUrl().c_str(), observer, cacheIndex, textureId );
320 if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required
322 // We need a new Texture.
323 textureId = GenerateUniqueTextureId();
324 mTextureInfoContainer.push_back( TextureInfo( textureId, maskTextureId, url.GetUrl(),
325 desiredSize, contentScale, fittingMode, samplingMode,
326 false, cropToMask, useAtlas, textureHash, orientationCorrection, preMultiplyOnLoad ) );
327 cacheIndex = mTextureInfoContainer.size() - 1u;
329 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) New texture, cacheIndex:%d, textureId=%d\n",
330 url.GetUrl().c_str(), observer, cacheIndex, textureId );
333 // The below code path is common whether we are using the cache or not.
334 // The textureInfoIndex now refers to either a pre-existing cached TextureInfo,
335 // or a new TextureInfo just created.
336 TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
337 textureInfo.maskTextureId = maskTextureId;
338 textureInfo.storageType = storageType;
339 textureInfo.orientationCorrection = orientationCorrection;
341 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
342 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
343 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
344 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
345 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
347 // Force reloading of texture by setting loadState unless already loading or cancelled.
348 if ( TextureManager::ReloadPolicy::FORCED == reloadPolicy && TextureManager::LOADING != textureInfo.loadState &&
349 TextureManager::CANCELLED != textureInfo.loadState )
351 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Verbose, "TextureManager::RequestLoad( url=%s observer=%p ) ForcedReload cacheIndex:%d, textureId=%d\n",
352 url.GetUrl().c_str(), observer, cacheIndex, textureId );
353 textureInfo.loadState = TextureManager::NOT_STARTED;
356 // Check if we should add the observer.
357 // Only do this if we have not loaded yet and it will not have loaded by the end of this method.
358 switch( textureInfo.loadState )
360 case TextureManager::LOAD_FAILED: // Failed notifies observer which then stops observing.
361 case TextureManager::NOT_STARTED:
363 LoadTexture( textureInfo );
364 ObserveTexture( textureInfo, observer );
367 case TextureManager::LOADING:
369 ObserveTexture( textureInfo, observer );
372 case TextureManager::UPLOADED:
376 // The Texture has already loaded. The other observers have already been notified.
377 // We need to send a "late" loaded notification for this observer.
378 observer->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
379 textureInfo.useAtlas, textureInfo.atlasRect );
383 case TextureManager::CANCELLED:
385 // A cancelled texture hasn't finished loading yet. Treat as a loading texture
386 // (it's ref count has already been incremented, above)
387 textureInfo.loadState = TextureManager::LOADING;
388 ObserveTexture( textureInfo, observer );
391 case TextureManager::LOAD_FINISHED:
392 case TextureManager::WAITING_FOR_MASK:
393 // Loading has already completed. Do nothing.
397 // Return the TextureId for which this Texture can now be referenced by externally.
401 void TextureManager::Remove( const TextureManager::TextureId textureId )
403 int textureInfoIndex = GetCacheIndexFromId( textureId );
404 if( textureInfoIndex != INVALID_INDEX )
406 TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
408 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
409 textureId, textureInfoIndex,
410 textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
411 textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
412 textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
413 textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
415 // Decrement the reference count and check if this is the last user of this Texture.
416 if( --textureInfo.referenceCount <= 0 )
418 // This is the last remove for this Texture.
419 textureInfo.referenceCount = 0;
420 bool removeTextureInfo = false;
422 // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
423 if( textureInfo.loadState == UPLOADED )
425 if( textureInfo.atlas )
427 textureInfo.atlas.Remove( textureInfo.atlasRect );
429 removeTextureInfo = true;
431 else if( textureInfo.loadState == LOADING )
433 // We mark the textureInfo for removal.
434 // Once the load has completed, this method will be called again.
435 textureInfo.loadState = CANCELLED;
439 // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
440 removeTextureInfo = true;
443 // If the state allows us to remove the TextureInfo data, we do so.
444 if( removeTextureInfo )
446 // Permanently remove the textureInfo struct.
447 mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
453 const VisualUrl& TextureManager::GetVisualUrl( TextureId textureId )
455 int cacheIndex = GetCacheIndexFromId( textureId );
456 DALI_ASSERT_DEBUG( cacheIndex != INVALID_CACHE_INDEX && "TextureId out of range");
458 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
459 return cachedTextureInfo.url;
462 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
464 LoadState loadState = TextureManager::NOT_STARTED;
466 int cacheIndex = GetCacheIndexFromId( textureId );
467 if( cacheIndex != INVALID_CACHE_INDEX )
469 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
470 loadState = cachedTextureInfo.loadState;
474 for( auto&& elem : mExternalTextures )
476 if( elem.textureId == textureId )
478 loadState = LoadState::UPLOADED;
486 TextureManager::LoadState TextureManager::GetTextureStateInternal( TextureId textureId )
488 LoadState loadState = TextureManager::NOT_STARTED;
490 int cacheIndex = GetCacheIndexFromId( textureId );
491 if( cacheIndex != INVALID_CACHE_INDEX )
493 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
494 loadState = cachedTextureInfo.loadState;
500 TextureSet TextureManager::GetTextureSet( TextureId textureId )
502 TextureSet textureSet;// empty handle
504 int cacheIndex = GetCacheIndexFromId( textureId );
505 if( cacheIndex != INVALID_CACHE_INDEX )
507 TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
508 textureSet = cachedTextureInfo.textureSet;
512 for( auto&& elem : mExternalTextures )
514 if( elem.textureId == textureId )
516 textureSet = elem.textureSet;
524 std::string TextureManager::AddExternalTexture( TextureSet& textureSet )
526 TextureManager::ExternalTextureInfo info;
527 info.textureId = GenerateUniqueTextureId();
528 info.textureSet = textureSet;
529 mExternalTextures.emplace_back( info );
530 return VisualUrl::CreateTextureUrl( std::to_string( info.textureId ) );
533 TextureSet TextureManager::RemoveExternalTexture( const std::string& url )
535 if( url.size() > 0u )
537 // get the location from the Url
538 VisualUrl parseUrl( url );
539 if( VisualUrl::TEXTURE == parseUrl.GetProtocolType() )
541 std::string location = parseUrl.GetLocation();
542 if( location.size() > 0u )
544 TextureId id = std::stoi( location );
545 const auto end = mExternalTextures.end();
546 for( auto iter = mExternalTextures.begin(); iter != end; ++iter )
548 if( iter->textureId == id )
550 auto textureSet = iter->textureSet;
551 mExternalTextures.erase( iter );
561 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
565 if( textureInfo.loadState == NOT_STARTED )
567 textureInfo.loadState = LOADING;
569 if( !textureInfo.loadSynchronously )
571 auto& loadersContainer = textureInfo.url.IsLocalResource() ? mAsyncLocalLoaders : mAsyncRemoteLoaders;
572 auto loadingHelperIt = loadersContainer.GetNext();
573 DALI_ASSERT_ALWAYS(loadingHelperIt != loadersContainer.End());
574 loadingHelperIt->Load(textureInfo.textureId, textureInfo.url,
575 textureInfo.desiredSize, textureInfo.fittingMode,
576 textureInfo.samplingMode, textureInfo.orientationCorrection );
583 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
584 TextureUploadObserver* observer )
588 textureInfo.observerList.PushBack( observer );
589 observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
593 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id,
594 Devel::PixelBuffer pixelBuffer )
596 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
598 if( loadingContainer.size() >= 1u )
600 AsyncLoadingInfo loadingInfo = loadingContainer.front();
602 if( loadingInfo.loadId == id )
604 int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
605 if( cacheIndex != INVALID_CACHE_INDEX )
607 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
609 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
611 if( textureInfo.loadState != CANCELLED )
613 // textureInfo can be invalidated after this call (as the mTextureInfoContainer may be modified)
614 PostLoad( textureInfo, pixelBuffer );
618 Remove( textureInfo.textureId );
623 loadingContainer.pop_front();
627 void TextureManager::PostLoad( TextureInfo& textureInfo, Devel::PixelBuffer& pixelBuffer )
629 // Was the load successful?
630 if( pixelBuffer && ( pixelBuffer.GetWidth() != 0 ) && ( pixelBuffer.GetHeight() != 0 ) )
632 // No atlas support for now
633 textureInfo.useAtlas = NO_ATLAS;
635 if( textureInfo.storageType == UPLOAD_TO_TEXTURE )
637 // If there is a mask texture ID associated with this texture, then apply the mask
638 // if it's already loaded. If it hasn't, and the mask is still loading,
639 // wait for the mask to finish loading.
640 if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
642 LoadState maskLoadState = GetTextureStateInternal( textureInfo.maskTextureId );
643 if( maskLoadState == LOADING )
645 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel buffer temporarily
646 textureInfo.loadState = WAITING_FOR_MASK;
648 else if( maskLoadState == LOAD_FINISHED )
650 ApplyMask( pixelBuffer, textureInfo.maskTextureId, textureInfo.scaleFactor, textureInfo.cropToMask );
651 UploadTexture( pixelBuffer, textureInfo );
652 NotifyObservers( textureInfo, true );
657 UploadTexture( pixelBuffer, textureInfo );
658 NotifyObservers( textureInfo, true );
663 textureInfo.pixelBuffer = pixelBuffer; // Store the pixel data
664 textureInfo.loadState = LOAD_FINISHED;
666 // Check if there was another texture waiting for this load to complete
667 // (e.g. if this was an image mask, and its load is on a different thread)
668 CheckForWaitingTexture( textureInfo );
673 DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
674 // @todo If the load was unsuccessful, upload the broken image.
675 textureInfo.loadState = LOAD_FAILED;
676 CheckForWaitingTexture( textureInfo );
677 NotifyObservers( textureInfo, false );
681 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
683 // Search the cache, checking if any texture has this texture id as a
685 const unsigned int size = mTextureInfoContainer.size();
687 for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
689 if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
690 mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
692 TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
693 Devel::PixelBuffer pixelBuffer = textureInfo.pixelBuffer;
694 textureInfo.pixelBuffer.Reset();
696 if( maskTextureInfo.loadState == LOAD_FINISHED )
698 ApplyMask( pixelBuffer, maskTextureInfo.textureId, textureInfo.scaleFactor, textureInfo.cropToMask );
699 UploadTexture( pixelBuffer, textureInfo );
700 NotifyObservers( textureInfo, true );
704 DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
705 textureInfo.loadState = LOAD_FAILED;
706 NotifyObservers( textureInfo, false );
712 void TextureManager::ApplyMask(
713 Devel::PixelBuffer& pixelBuffer, TextureId maskTextureId,
714 float contentScale, bool cropToMask )
716 int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
717 Devel::PixelBuffer maskPixelBuffer = mTextureInfoContainer[maskCacheIndex].pixelBuffer;
718 pixelBuffer.ApplyMask( maskPixelBuffer, contentScale, cropToMask );
721 void TextureManager::UploadTexture( Devel::PixelBuffer& pixelBuffer, TextureInfo& textureInfo )
723 if( textureInfo.useAtlas != USE_ATLAS )
725 DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, " TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
727 Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelBuffer.GetPixelFormat(),
728 pixelBuffer.GetWidth(), pixelBuffer.GetHeight() );
729 if( textureInfo.preMultiplyOnLoad == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD )
731 pixelBuffer.MultiplyColorByAlpha();
734 PixelData pixelData = Devel::PixelBuffer::Convert( pixelBuffer );
735 texture.Upload( pixelData );
736 if ( ! textureInfo.textureSet )
738 textureInfo.textureSet = TextureSet::New();
740 textureInfo.textureSet.SetTexture( 0u, texture );
743 // Update the load state.
744 // Note: This is regardless of success as we care about whether a
745 // load attempt is in progress or not. If unsuccessful, a broken
746 // image is still loaded.
747 textureInfo.loadState = UPLOADED;
750 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
752 TextureId textureId = textureInfo.textureId;
754 // If there is an observer: Notify the load is complete, whether successful or not,
755 // and erase it from the list
756 unsigned int observerCount = textureInfo.observerList.Count();
757 TextureInfo* info = &textureInfo;
759 while( observerCount )
761 TextureUploadObserver* observer = info->observerList[0];
763 // During UploadComplete() a Control ResourceReady() signal is emitted.
764 // During that signal the app may add remove /add Textures (e.g. via
765 // ImageViews). At this point no more observers can be added to the
766 // observerList, because textureInfo.loadState = UPLOADED. However it is
767 // possible for observers to be removed, hence we check the observer list
768 // count every iteration.
770 // The reference to the textureInfo struct can also become invalidated,
771 // because new load requests can modify the mTextureInfoContainer list
772 // (e.g. if more requests are pushed back it can cause the list to be
773 // resized invalidating the reference to the TextureInfo ).
774 observer->UploadComplete( success, info->textureId, info->textureSet, info->useAtlas, info->atlasRect );
775 observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
777 // Get the textureInfo from the container again as it may have been
780 int textureInfoIndex = GetCacheIndexFromId( textureId );
781 if( textureInfoIndex == INVALID_CACHE_INDEX)
783 return; // texture has been removed - can stop.
786 info = &mTextureInfoContainer[ textureInfoIndex ];
787 observerCount = info->observerList.Count();
788 if ( observerCount > 0 )
790 // remove the observer that was just triggered if it's still in the list
791 for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
795 info->observerList.Erase( j );
804 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
806 return mCurrentTextureId++;
809 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
811 const unsigned int size = mTextureInfoContainer.size();
813 for( unsigned int i = 0; i < size; ++i )
815 if( mTextureInfoContainer[i].textureId == textureId )
821 return INVALID_CACHE_INDEX;
824 TextureManager::TextureHash TextureManager::GenerateHash(
825 const std::string& url,
826 const ImageDimensions size,
827 const FittingMode::Type fittingMode,
828 const Dali::SamplingMode::Type samplingMode,
829 const UseAtlas useAtlas,
830 TextureId maskTextureId,
831 TextureManager::MultiplyOnLoad preMultiplyOnLoad)
833 std::string hashTarget( url );
834 const size_t urlLength = hashTarget.length();
835 const uint16_t width = size.GetWidth();
836 const uint16_t height = size.GetWidth();
838 // If either the width or height has been specified, include the resizing options in the hash
839 if( width != 0 || height != 0 )
841 // We are appending 5 bytes to the URL to form the hash input.
842 hashTarget.resize( urlLength + 5u );
843 char* hashTargetPtr = &( hashTarget[ urlLength ] );
845 // Pack the width and height (4 bytes total).
846 *hashTargetPtr++ = size.GetWidth() & 0xff;
847 *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
848 *hashTargetPtr++ = size.GetHeight() & 0xff;
849 *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
851 // Bit-pack the FittingMode, SamplingMode and atlasing.
852 // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
853 *hashTargetPtr = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
857 // We are not including sizing information, but we still need an extra byte for atlasing.
858 hashTarget.resize( urlLength + 1u );
859 // Add the atlasing to the hash input.
860 hashTarget[ urlLength ] = useAtlas;
863 if( maskTextureId != INVALID_TEXTURE_ID )
865 auto textureIdIndex = hashTarget.length();
866 hashTarget.resize( hashTarget.length() + sizeof( TextureId ) );
867 unsigned char* hashTargetPtr = reinterpret_cast<unsigned char*>(&( hashTarget[ textureIdIndex ] ));
869 // Append the texture id to the end of the URL byte by byte:
870 // (to avoid SIGBUS / alignment issues)
871 for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
873 *hashTargetPtr++ = maskTextureId & 0xff;
874 maskTextureId >>= 8u;
878 auto premultipliedIndex = hashTarget.length();
879 hashTarget.resize( premultipliedIndex + 1 );
880 char* preMultPtr = &( hashTarget[ premultipliedIndex ] );
881 *preMultPtr = (preMultiplyOnLoad == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD) ? 't':'f';
883 return Dali::CalculateHash( hashTarget );
886 int TextureManager::FindCachedTexture(
887 const TextureManager::TextureHash hash,
888 const std::string& url,
889 const ImageDimensions size,
890 const FittingMode::Type fittingMode,
891 const Dali::SamplingMode::Type samplingMode,
893 TextureId maskTextureId,
894 TextureManager::MultiplyOnLoad preMultiplyOnLoad )
896 // Default to an invalid ID, in case we do not find a match.
897 int cacheIndex = INVALID_CACHE_INDEX;
899 // Iterate through our hashes to find a match.
900 const unsigned int count = mTextureInfoContainer.size();
901 for( unsigned int i = 0u; i < count; ++i )
903 if( mTextureInfoContainer[i].hash == hash )
905 // We have a match, now we check all the original parameters in case of a hash collision.
906 TextureInfo& textureInfo( mTextureInfoContainer[i] );
908 if( ( url == textureInfo.url.GetUrl() ) &&
909 ( useAtlas == textureInfo.useAtlas ) &&
910 ( maskTextureId == textureInfo.maskTextureId ) &&
911 ( size == textureInfo.desiredSize ) &&
912 ( preMultiplyOnLoad == textureInfo.preMultiplyOnLoad ) &&
913 ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
914 ( fittingMode == textureInfo.fittingMode &&
915 samplingMode == textureInfo.samplingMode ) ) )
917 // The found Texture is a match.
927 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
929 const unsigned int count = mTextureInfoContainer.size();
930 for( unsigned int i = 0; i < count; ++i )
932 TextureInfo& textureInfo( mTextureInfoContainer[i] );
933 for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin();
934 j != textureInfo.observerList.End(); )
938 j = textureInfo.observerList.Erase( j );
949 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(TextureManager& textureManager)
950 : AsyncLoadingHelper(Toolkit::AsyncImageLoader::New(), textureManager,
951 AsyncLoadingInfoContainerType())
955 void TextureManager::AsyncLoadingHelper::Load(TextureId textureId,
956 const VisualUrl& url,
957 ImageDimensions desiredSize,
958 FittingMode::Type fittingMode,
959 SamplingMode::Type samplingMode,
960 bool orientationCorrection)
962 mLoadingInfoContainer.push_back(AsyncLoadingInfo(textureId));
963 auto id = mLoader.Load(url.GetUrl(), desiredSize, fittingMode, samplingMode, orientationCorrection);
964 mLoadingInfoContainer.back().loadId = id;
967 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(AsyncLoadingHelper&& rhs)
968 : AsyncLoadingHelper(rhs.mLoader, rhs.mTextureManager, std::move(rhs.mLoadingInfoContainer))
972 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(
973 Toolkit::AsyncImageLoader loader,
974 TextureManager& textureManager,
975 AsyncLoadingInfoContainerType&& loadingInfoContainer)
977 mTextureManager(textureManager),
978 mLoadingInfoContainer(std::move(loadingInfoContainer))
980 DevelAsyncImageLoader::PixelBufferLoadedSignal(mLoader).Connect(
981 this, &AsyncLoadingHelper::AsyncLoadComplete);
984 void TextureManager::AsyncLoadingHelper::AsyncLoadComplete(uint32_t id,
985 Devel::PixelBuffer pixelBuffer)
987 mTextureManager.AsyncLoadComplete(mLoadingInfoContainer, id, pixelBuffer);
990 } // namespace Internal
992 } // namespace Toolkit