Added Auxiliary image to NPatch visual.
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / visuals / texture-manager-impl.cpp
1  /*
2  * Copyright (c) 2017 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/visuals/texture-manager-impl.h>
20
21 // EXTERNAL HEADERS
22 #include <cstdlib>
23 #include <string>
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>
31
32 // INTERNAL HEADERS
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>
36
37 namespace
38 {
39
40 constexpr auto DEFAULT_NUMBER_OF_LOCAL_LOADER_THREADS = size_t{4u};
41 constexpr auto DEFAULT_NUMBER_OF_REMOTE_LOADER_THREADS = size_t{8u};
42
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";
45
46 size_t GetNumberOfThreads(const char* environmentVariable, size_t defaultValue)
47 {
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;
52 }
53
54 size_t GetNumberOfLocalLoaderThreads()
55 {
56   return GetNumberOfThreads(NUMBER_OF_LOCAL_LOADER_THREADS_ENV, DEFAULT_NUMBER_OF_LOCAL_LOADER_THREADS);
57 }
58
59 size_t GetNumberOfRemoteLoaderThreads()
60 {
61   return GetNumberOfThreads(NUMBER_OF_REMOTE_LOADER_THREADS_ENV, DEFAULT_NUMBER_OF_REMOTE_LOADER_THREADS);
62 }
63
64 } // namespace
65
66 namespace Dali
67 {
68
69 namespace Toolkit
70 {
71
72 namespace Internal
73 {
74
75 namespace
76 {
77
78 #ifdef DEBUG_ENABLED
79 Debug::Filter* gTextureManagerLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_TEXTURE_MANAGER" );
80 #endif
81
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
87
88 } // Anonymous namespace
89
90 TextureManager::MaskingData::MaskingData()
91 : mAlphaMaskUrl(),
92   mAlphaMaskId( INVALID_TEXTURE_ID ),
93   mContentScaleFactor( 1.0f ),
94   mCropToMask( true )
95 {
96 }
97
98 TextureManager::TextureManager()
99 : mAsyncLocalLoaders( GetNumberOfLocalLoaderThreads(), [&]() { return AsyncLoadingHelper(*this); } ),
100   mAsyncRemoteLoaders( GetNumberOfRemoteLoaderThreads(), [&]() { return AsyncLoadingHelper(*this); } ),
101   mCurrentTextureId( 0 )
102 {
103 }
104
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 )
112 {
113   TextureSet textureSet;
114
115   loadingStatus = false;
116   textureRect = FULL_ATLAS_RECT;
117
118   if( VisualUrl::TEXTURE == url.GetProtocolType())
119   {
120     std::string location = url.GetLocation();
121     if( location.size() > 0u )
122     {
123       TextureId id = std::stoi( location );
124       for( auto&& elem : mExternalTextures )
125       {
126         if( elem.textureId == id )
127         {
128           return elem.textureSet;
129         }
130       }
131     }
132   }
133   else if( synchronousLoading )
134   {
135     PixelData data;
136     if( url.IsValid() )
137     {
138       Devel::PixelBuffer pixelBuffer = LoadImageFromFile( url.GetUrl(), desiredSize, fittingMode, samplingMode, orientationCorrection  );
139       if( pixelBuffer )
140       {
141         data = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
142       }
143     }
144     if( !data )
145     {
146       // use broken image
147       textureSet = TextureSet::New();
148       Devel::PixelBuffer pixelBuffer = LoadImageFromFile( BROKEN_IMAGE_URL );
149       if( pixelBuffer )
150       {
151         data = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
152       }
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 );
158     }
159     else
160     {
161       if( atlasingStatus ) // attempt atlasing
162       {
163         textureSet = imageAtlasManager->Add( textureRect, data );
164       }
165       if( !textureSet ) // big image, no atlasing or atlasing failed
166       {
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 );
173       }
174     }
175   }
176   else
177   {
178     loadingStatus = true;
179     if( atlasingStatus )
180     {
181       textureSet = imageAtlasManager->Add( textureRect, url.GetUrl(), desiredSize, fittingMode, true, atlasObserver );
182     }
183     if( !textureSet ) // big image, no atlasing or atlasing failed
184     {
185       atlasingStatus = false;
186       if( !maskInfo )
187       {
188         textureId = RequestLoad( url, desiredSize, fittingMode, samplingMode, TextureManager::NO_ATLAS, textureObserver, orientationCorrection );
189       }
190       else
191       {
192         textureId = RequestLoad( url,
193                                  maskInfo->mAlphaMaskId,
194                                  maskInfo->mContentScaleFactor,
195                                  desiredSize,
196                                  fittingMode, samplingMode,
197                                  TextureManager::NO_ATLAS,
198                                  maskInfo->mCropToMask,
199                                  textureObserver,
200                                  orientationCorrection);
201       }
202
203       TextureManager::LoadState loadState = GetTextureState( textureId );
204       loadingStatus = ( loadState == TextureManager::LOADING );
205
206       if( loadState == TextureManager::UPLOADED )
207       {
208         // UploadComplete has already been called - keep the same texture set
209         textureSet = GetTextureSet( textureId );
210       }
211     }
212   }
213
214   if( ! atlasingStatus && textureSet )
215   {
216     Sampler sampler = Sampler::New();
217     sampler.SetWrapMode(  wrapModeU, wrapModeV  );
218     textureSet.SetSampler( 0u, sampler );
219   }
220
221   return textureSet;
222 }
223
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 )
232 {
233   return RequestLoadInternal( url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, useAtlas, false, UPLOAD_TO_TEXTURE, observer, orientationCorrection );
234 }
235
236 TextureManager::TextureId TextureManager::RequestLoad(
237   const VisualUrl&         url,
238   TextureId                maskTextureId,
239   float                    contentScale,
240   const ImageDimensions    desiredSize,
241   FittingMode::Type        fittingMode,
242   Dali::SamplingMode::Type samplingMode,
243   const UseAtlas           useAtlas,
244   bool                     cropToMask,
245   TextureUploadObserver*   observer,
246   bool                     orientationCorrection )
247 {
248   return RequestLoadInternal( url, maskTextureId, contentScale, desiredSize, fittingMode, samplingMode, useAtlas, cropToMask, UPLOAD_TO_TEXTURE, observer, orientationCorrection );
249 }
250
251 TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl )
252 {
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 );
255 }
256
257 TextureManager::TextureId TextureManager::RequestLoadInternal(
258   const VisualUrl&         url,
259   TextureId                maskTextureId,
260   float                    contentScale,
261   const ImageDimensions    desiredSize,
262   FittingMode::Type        fittingMode,
263   Dali::SamplingMode::Type samplingMode,
264   UseAtlas                 useAtlas,
265   bool                     cropToMask,
266   StorageType              storageType,
267   TextureUploadObserver*   observer,
268   bool                     orientationCorrection )
269 {
270   // First check if the requested Texture is cached.
271   const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
272
273   TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
274
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 );
277
278   // Check if the requested Texture exists in the cache.
279   if( cacheIndex != INVALID_CACHE_INDEX )
280   {
281     // Mark this texture being used by another client resource.
282     ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
283     textureId = mTextureInfoContainer[ cacheIndex ].textureId;
284
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 );
286   }
287
288   if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required
289   {
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;
296
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 );
298   }
299
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;
307
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" );
313
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 )
316   {
317     case TextureManager::NOT_STARTED:
318     {
319       LoadTexture( textureInfo );
320       ObserveTexture( textureInfo, observer );
321       break;
322     }
323     case TextureManager::LOADING:
324     {
325       ObserveTexture( textureInfo, observer );
326       break;
327     }
328     case TextureManager::UPLOADED:
329     {
330       if( observer )
331       {
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 );
336       }
337       break;
338     }
339     case TextureManager::CANCELLED:
340     {
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 );
345       break;
346     }
347     case TextureManager::LOAD_FINISHED:
348     case TextureManager::WAITING_FOR_MASK:
349     case TextureManager::LOAD_FAILED:
350       // Loading has already completed. Do nothing.
351       break;
352   }
353
354   // Return the TextureId for which this Texture can now be referenced by externally.
355   return textureId;
356 }
357
358 void TextureManager::Remove( const TextureManager::TextureId textureId )
359 {
360   int textureInfoIndex = GetCacheIndexFromId( textureId );
361   if( textureInfoIndex != INVALID_INDEX )
362   {
363     TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
364
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" );
371
372     // Decrement the reference count and check if this is the last user of this Texture.
373     if( --textureInfo.referenceCount <= 0 )
374     {
375       // This is the last remove for this Texture.
376       textureInfo.referenceCount = 0;
377       bool removeTextureInfo = false;
378
379       // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
380       if( textureInfo.loadState == UPLOADED )
381       {
382         if( textureInfo.atlas )
383         {
384           textureInfo.atlas.Remove( textureInfo.atlasRect );
385         }
386         removeTextureInfo = true;
387       }
388       else if( textureInfo.loadState == LOADING )
389       {
390         // We mark the textureInfo for removal.
391         // Once the load has completed, this method will be called again.
392         textureInfo.loadState = CANCELLED;
393       }
394       else
395       {
396         // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
397         removeTextureInfo = true;
398       }
399
400       // If the state allows us to remove the TextureInfo data, we do so.
401       if( removeTextureInfo )
402       {
403         // Permanently remove the textureInfo struct.
404         mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
405       }
406     }
407   }
408 }
409
410 const VisualUrl& TextureManager::GetVisualUrl( TextureId textureId )
411 {
412   int cacheIndex = GetCacheIndexFromId( textureId );
413   DALI_ASSERT_DEBUG( cacheIndex != INVALID_CACHE_INDEX && "TextureId out of range");
414
415   TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
416   return cachedTextureInfo.url;
417 }
418
419 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
420 {
421   LoadState loadState = TextureManager::NOT_STARTED;
422
423   int cacheIndex = GetCacheIndexFromId( textureId );
424   if( cacheIndex != INVALID_CACHE_INDEX )
425   {
426     TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
427     loadState = cachedTextureInfo.loadState;
428   }
429   return loadState;
430 }
431
432 TextureSet TextureManager::GetTextureSet( TextureId textureId )
433 {
434   TextureSet textureSet;// empty handle
435
436   int cacheIndex = GetCacheIndexFromId( textureId );
437   if( cacheIndex != INVALID_CACHE_INDEX )
438   {
439     TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
440     textureSet = cachedTextureInfo.textureSet;
441   }
442   return textureSet;
443 }
444
445 std::string TextureManager::AddExternalTexture( TextureSet& textureSet )
446 {
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 ) );
452 }
453
454 TextureSet TextureManager::RemoveExternalTexture( const std::string& url )
455 {
456   if( url.size() > 0u )
457   {
458     // get the location from the Url
459     VisualUrl parseUrl( url );
460     if( VisualUrl::TEXTURE == parseUrl.GetProtocolType() )
461     {
462       std::string location = parseUrl.GetLocation();
463       if( location.size() > 0u )
464       {
465         TextureId id = std::stoi( location );
466         const auto end = mExternalTextures.end();
467         for( auto iter = mExternalTextures.begin(); iter != end; ++iter )
468         {
469           if( iter->textureId == id )
470           {
471             auto textureSet = iter->textureSet;
472             mExternalTextures.erase( iter );
473             return textureSet;
474           }
475         }
476       }
477     }
478   }
479   return TextureSet();
480 }
481
482 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
483 {
484   bool success = true;
485
486   if( textureInfo.loadState == NOT_STARTED )
487   {
488     textureInfo.loadState = LOADING;
489
490     if( !textureInfo.loadSynchronously )
491     {
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 );
498     }
499   }
500
501   return success;
502 }
503
504 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
505                                      TextureUploadObserver* observer )
506 {
507   if( observer )
508   {
509     textureInfo.observerList.PushBack( observer );
510     observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
511   }
512 }
513
514 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, Devel::PixelBuffer pixelBuffer )
515 {
516   DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
517
518   if( loadingContainer.size() >= 1u )
519   {
520     AsyncLoadingInfo loadingInfo = loadingContainer.front();
521
522     if( loadingInfo.loadId == id )
523     {
524       int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
525       if( cacheIndex != INVALID_CACHE_INDEX )
526       {
527         TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
528
529         DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
530
531         if( textureInfo.loadState != CANCELLED )
532         {
533           // textureInfo can be invalidated after this call (as the mTextureInfoContainer may be modified)
534           PostLoad( textureInfo, pixelBuffer );
535         }
536         else
537         {
538           Remove( textureInfo.textureId );
539         }
540       }
541     }
542
543     loadingContainer.pop_front();
544   }
545 }
546
547 void TextureManager::PostLoad( TextureInfo& textureInfo, Devel::PixelBuffer& pixelBuffer )
548 {
549   // Was the load successful?
550   if( pixelBuffer && ( pixelBuffer.GetWidth() != 0 ) && ( pixelBuffer.GetHeight() != 0 ) )
551   {
552     // No atlas support for now
553     textureInfo.useAtlas = NO_ATLAS;
554
555     if( textureInfo.storageType == UPLOAD_TO_TEXTURE )
556     {
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 )
561       {
562         LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
563         if( maskLoadState == LOADING )
564         {
565           textureInfo.pixelBuffer = pixelBuffer; // Store the pixel buffer temporarily
566           textureInfo.loadState = WAITING_FOR_MASK;
567         }
568         else if( maskLoadState == LOAD_FINISHED )
569         {
570           ApplyMask( pixelBuffer, textureInfo.maskTextureId, textureInfo.scaleFactor, textureInfo.cropToMask );
571           UploadTexture( pixelBuffer, textureInfo );
572           NotifyObservers( textureInfo, true );
573         }
574       }
575       else
576       {
577         UploadTexture( pixelBuffer, textureInfo );
578         NotifyObservers( textureInfo, true );
579       }
580     }
581     else
582     {
583       textureInfo.pixelBuffer = pixelBuffer; // Store the pixel data
584       textureInfo.loadState = LOAD_FINISHED;
585
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 );
589     }
590   }
591   else
592   {
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 );
598   }
599 }
600
601 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
602 {
603   // Search the cache, checking if any texture has this texture id as a
604   // maskTextureId:
605   const unsigned int size = mTextureInfoContainer.size();
606
607   for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
608   {
609     if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
610         mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
611     {
612       TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
613       Devel::PixelBuffer pixelBuffer = textureInfo.pixelBuffer;
614       textureInfo.pixelBuffer.Reset();
615
616       if( maskTextureInfo.loadState == LOAD_FINISHED )
617       {
618         ApplyMask( pixelBuffer, maskTextureInfo.textureId, textureInfo.scaleFactor, textureInfo.cropToMask );
619         UploadTexture( pixelBuffer, textureInfo );
620         NotifyObservers( textureInfo, true );
621       }
622       else
623       {
624         DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
625         textureInfo.loadState = LOAD_FAILED;
626         NotifyObservers( textureInfo, false );
627       }
628     }
629   }
630 }
631
632 void TextureManager::ApplyMask(
633   Devel::PixelBuffer& pixelBuffer, TextureId maskTextureId,
634   float contentScale, bool cropToMask )
635 {
636   int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
637   Devel::PixelBuffer maskPixelBuffer = mTextureInfoContainer[maskCacheIndex].pixelBuffer;
638   pixelBuffer.ApplyMask( maskPixelBuffer, contentScale, cropToMask );
639 }
640
641 void TextureManager::UploadTexture( Devel::PixelBuffer& pixelBuffer, TextureInfo& textureInfo )
642 {
643   if( textureInfo.useAtlas != USE_ATLAS )
644   {
645     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
646
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 );
652   }
653
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;
659 }
660
661 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
662 {
663   TextureId textureId = textureInfo.textureId;
664
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;
669
670   while( observerCount )
671   {
672     TextureUploadObserver* observer = info->observerList[0];
673
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.
680
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 );
687
688     // Get the textureInfo from the container again as it may have been
689     // invalidated,
690
691     int textureInfoIndex = GetCacheIndexFromId( textureId );
692     if( textureInfoIndex == INVALID_CACHE_INDEX)
693     {
694       return; // texture has been removed - can stop.
695     }
696
697     info = &mTextureInfoContainer[ textureInfoIndex ];
698     observerCount = info->observerList.Count();
699     if ( observerCount > 0 )
700     {
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 )
703       {
704         if( *j == observer )
705         {
706           info->observerList.Erase( j );
707           observerCount--;
708           break;
709         }
710       }
711     }
712   }
713 }
714
715 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
716 {
717   return mCurrentTextureId++;
718 }
719
720 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
721 {
722   const unsigned int size = mTextureInfoContainer.size();
723
724   for( unsigned int i = 0; i < size; ++i )
725   {
726     if( mTextureInfoContainer[i].textureId == textureId )
727     {
728       return i;
729     }
730   }
731
732   DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
733   return INVALID_CACHE_INDEX;
734 }
735
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 )
743 {
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();
748
749   // If either the width or height has been specified, include the resizing options in the hash
750   if( width != 0 || height != 0 )
751   {
752     // We are appending 5 bytes to the URL to form the hash input.
753     hashTarget.resize( urlLength + 5u );
754     char* hashTargetPtr = &( hashTarget[ urlLength ] );
755
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;
761
762     // Bit-pack the FittingMode, SamplingMode and atlasing.
763     // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
764     *hashTargetPtr   = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
765   }
766   else
767   {
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;
772   }
773
774   if( maskTextureId != INVALID_TEXTURE_ID )
775   {
776     hashTarget.resize( urlLength + sizeof( TextureId ) );
777     TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
778
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 )
782     {
783       *hashTargetPtr++ = maskTextureId & 0xff;
784       maskTextureId >>= 8u;
785     }
786   }
787
788   return Dali::CalculateHash( hashTarget );
789 }
790
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,
797   const bool                        useAtlas,
798   TextureId                         maskTextureId)
799 {
800   // Default to an invalid ID, in case we do not find a match.
801   int cacheIndex = INVALID_CACHE_INDEX;
802
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 )
806   {
807     if( mTextureInfoContainer[i].hash == hash )
808     {
809       // We have a match, now we check all the original parameters in case of a hash collision.
810       TextureInfo& textureInfo( mTextureInfoContainer[i] );
811
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 ) ) )
819       {
820         // The found Texture is a match.
821         cacheIndex = i;
822         break;
823       }
824     }
825   }
826
827   return cacheIndex;
828 }
829
830 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
831 {
832   const unsigned int count = mTextureInfoContainer.size();
833   for( unsigned int i = 0; i < count; ++i )
834   {
835     TextureInfo& textureInfo( mTextureInfoContainer[i] );
836     for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); )
837     {
838       if( *j == observer )
839       {
840         j = textureInfo.observerList.Erase( j );
841       }
842       else
843       {
844         ++j;
845       }
846     }
847   }
848 }
849
850 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(TextureManager& textureManager)
851 : AsyncLoadingHelper(Toolkit::AsyncImageLoader::New(), textureManager,
852                      AsyncLoadingInfoContainerType())
853 {
854 }
855
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)
862 {
863   mLoadingInfoContainer.push_back(AsyncLoadingInfo(textureId));
864   auto id = mLoader.Load(url.GetUrl(), desiredSize, fittingMode, samplingMode, orientationCorrection);
865   mLoadingInfoContainer.back().loadId = id;
866 }
867
868 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(AsyncLoadingHelper&& rhs)
869 : AsyncLoadingHelper(rhs.mLoader, rhs.mTextureManager, std::move(rhs.mLoadingInfoContainer))
870 {
871 }
872
873 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(
874     Toolkit::AsyncImageLoader loader,
875     TextureManager& textureManager,
876     AsyncLoadingInfoContainerType&& loadingInfoContainer)
877 : mLoader(loader),
878   mTextureManager(textureManager),
879   mLoadingInfoContainer(std::move(loadingInfoContainer))
880 {
881   DevelAsyncImageLoader::PixelBufferLoadedSignal(mLoader).Connect(
882       this, &AsyncLoadingHelper::AsyncLoadComplete);
883 }
884
885 void TextureManager::AsyncLoadingHelper::AsyncLoadComplete(uint32_t           id,
886                                                            Devel::PixelBuffer pixelBuffer)
887 {
888   mTextureManager.AsyncLoadComplete(mLoadingInfoContainer, id, pixelBuffer);
889 }
890
891 } // namespace Internal
892
893 } // namespace Toolkit
894
895 } // namespace Dali