Add support for Imagevisual to use External Texture as input through Url
[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)
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       // if sync loading is required, the loading should immediately when actor is on stage
139       Devel::PixelBuffer pixelBuffer = LoadImageFromFile( url.GetUrl(), desiredSize, fittingMode, samplingMode );
140       if( pixelBuffer )
141       {
142         data = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
143       }
144     }
145     if( !data )
146     {
147       // use broken image
148       textureSet = TextureSet::New();
149       Devel::PixelBuffer pixelBuffer = LoadImageFromFile( BROKEN_IMAGE_URL );
150       if( pixelBuffer )
151       {
152         data = Devel::PixelBuffer::Convert(pixelBuffer); // takes ownership of buffer
153       }
154       Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, data.GetPixelFormat(),
155                                       data.GetWidth(), data.GetHeight() );
156       texture.Upload( data );
157       textureSet = TextureSet::New();
158       textureSet.SetTexture( 0u, texture );
159     }
160     else
161     {
162       if( atlasingStatus ) // attempt atlasing
163       {
164         textureSet = imageAtlasManager->Add( textureRect, data );
165       }
166       if( !textureSet ) // big image, no atlasing or atlasing failed
167       {
168         atlasingStatus = false;
169         Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, data.GetPixelFormat(),
170                                         data.GetWidth(), data.GetHeight() );
171         texture.Upload( data );
172         textureSet = TextureSet::New();
173         textureSet.SetTexture( 0u, texture );
174       }
175     }
176   }
177   else
178   {
179     loadingStatus = true;
180     if( atlasingStatus )
181     {
182       textureSet = imageAtlasManager->Add( textureRect, url.GetUrl(), desiredSize, fittingMode, true, atlasObserver );
183     }
184     if( !textureSet ) // big image, no atlasing or atlasing failed
185     {
186       atlasingStatus = false;
187       if( !maskInfo )
188       {
189         textureId = RequestLoad( url, desiredSize, fittingMode, samplingMode, TextureManager::NO_ATLAS, textureObserver );
190       }
191       else
192       {
193         textureId = RequestLoad( url,
194                                  maskInfo->mAlphaMaskId,
195                                  maskInfo->mContentScaleFactor,
196                                  desiredSize,
197                                  fittingMode, samplingMode,
198                                  TextureManager::NO_ATLAS,
199                                  maskInfo->mCropToMask,
200                                  textureObserver );
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 {
232   return RequestLoadInternal( url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, useAtlas, false, UPLOAD_TO_TEXTURE, observer );
233 }
234
235 TextureManager::TextureId TextureManager::RequestLoad(
236   const VisualUrl&         url,
237   TextureId                maskTextureId,
238   float                    contentScale,
239   const ImageDimensions    desiredSize,
240   FittingMode::Type        fittingMode,
241   Dali::SamplingMode::Type samplingMode,
242   const UseAtlas           useAtlas,
243   bool                     cropToMask,
244   TextureUploadObserver*   observer )
245 {
246   return RequestLoadInternal( url, maskTextureId, contentScale, desiredSize, fittingMode, samplingMode, useAtlas, cropToMask, UPLOAD_TO_TEXTURE, observer );
247 }
248
249 TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl )
250 {
251   // Use the normal load procedure to get the alpha mask.
252   return RequestLoadInternal( maskUrl, INVALID_TEXTURE_ID, 1.0f, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::NO_FILTER, NO_ATLAS, false, KEEP_PIXEL_BUFFER, NULL );
253 }
254
255 TextureManager::TextureId TextureManager::RequestLoadInternal(
256   const VisualUrl&         url,
257   TextureId                maskTextureId,
258   float                    contentScale,
259   const ImageDimensions    desiredSize,
260   FittingMode::Type        fittingMode,
261   Dali::SamplingMode::Type samplingMode,
262   UseAtlas                 useAtlas,
263   bool                     cropToMask,
264   StorageType              storageType,
265   TextureUploadObserver*   observer )
266 {
267   // First check if the requested Texture is cached.
268   const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
269
270   TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
271
272   // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
273   int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
274
275   // Check if the requested Texture exists in the cache.
276   if( cacheIndex != INVALID_CACHE_INDEX )
277   {
278     // Mark this texture being used by another client resource.
279     ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
280     textureId = mTextureInfoContainer[ cacheIndex ].textureId;
281
282     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) Using cached texture @%d, textureId=%d\n", url.GetUrl().c_str(), observer, cacheIndex, textureId );
283   }
284
285   if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required
286   {
287     // We need a new Texture.
288     textureId = GenerateUniqueTextureId();
289     mTextureInfoContainer.push_back( TextureInfo( textureId, maskTextureId, url.GetUrl(),
290                                                   desiredSize, contentScale, fittingMode, samplingMode,
291                                                   false, cropToMask, useAtlas, textureHash ) );
292     cacheIndex = mTextureInfoContainer.size() - 1u;
293
294     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::RequestLoad( url=%s observer=%p ) New texture, cacheIndex:%d, textureId=%d\n", url.GetUrl().c_str(), observer, cacheIndex, textureId );
295   }
296
297   // The below code path is common whether we are using the cache or not.
298   // The textureInfoIndex now refers to either a pre-existing cached TextureInfo,
299   // or a new TextureInfo just created.
300   TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
301   textureInfo.maskTextureId = maskTextureId;
302   textureInfo.storageType = storageType;
303
304   DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
305                  textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
306                  textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
307                  textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
308                  textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
309
310   // Check if we should add the observer. Only do this if we have not loaded yet and it will not have loaded by the end of this method.
311   switch( textureInfo.loadState )
312   {
313     case TextureManager::NOT_STARTED:
314     {
315       LoadTexture( textureInfo );
316       ObserveTexture( textureInfo, observer );
317       break;
318     }
319     case TextureManager::LOADING:
320     {
321       ObserveTexture( textureInfo, observer );
322       break;
323     }
324     case TextureManager::UPLOADED:
325     {
326       if( observer )
327       {
328         // The Texture has already loaded. The other observers have already been notified.
329         // We need to send a "late" loaded notification for this observer.
330         observer->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
331                                   textureInfo.useAtlas, textureInfo.atlasRect );
332       }
333       break;
334     }
335     case TextureManager::CANCELLED:
336     {
337       // A cancelled texture hasn't finished loading yet. Treat as a loading texture
338       // (it's ref count has already been incremented, above)
339       textureInfo.loadState = TextureManager::LOADING;
340       ObserveTexture( textureInfo, observer );
341       break;
342     }
343     case TextureManager::LOAD_FINISHED:
344     case TextureManager::WAITING_FOR_MASK:
345     case TextureManager::LOAD_FAILED:
346       // Loading has already completed. Do nothing.
347       break;
348   }
349
350   // Return the TextureId for which this Texture can now be referenced by externally.
351   return textureId;
352 }
353
354 void TextureManager::Remove( const TextureManager::TextureId textureId )
355 {
356   int textureInfoIndex = GetCacheIndexFromId( textureId );
357   if( textureInfoIndex != INVALID_INDEX )
358   {
359     TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
360
361     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
362                    textureId, textureInfoIndex,
363                    textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
364                    textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
365                    textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
366                    textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
367
368     // Decrement the reference count and check if this is the last user of this Texture.
369     if( --textureInfo.referenceCount <= 0 )
370     {
371       // This is the last remove for this Texture.
372       textureInfo.referenceCount = 0;
373       bool removeTextureInfo = false;
374
375       // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
376       if( textureInfo.loadState == UPLOADED )
377       {
378         if( textureInfo.atlas )
379         {
380           textureInfo.atlas.Remove( textureInfo.atlasRect );
381         }
382         removeTextureInfo = true;
383       }
384       else if( textureInfo.loadState == LOADING )
385       {
386         // We mark the textureInfo for removal.
387         // Once the load has completed, this method will be called again.
388         textureInfo.loadState = CANCELLED;
389       }
390       else
391       {
392         // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
393         removeTextureInfo = true;
394       }
395
396       // If the state allows us to remove the TextureInfo data, we do so.
397       if( removeTextureInfo )
398       {
399         // Permanently remove the textureInfo struct.
400         mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
401       }
402     }
403   }
404 }
405
406 const VisualUrl& TextureManager::GetVisualUrl( TextureId textureId )
407 {
408   int cacheIndex = GetCacheIndexFromId( textureId );
409   DALI_ASSERT_DEBUG( cacheIndex != INVALID_CACHE_INDEX && "TextureId out of range");
410
411   TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
412   return cachedTextureInfo.url;
413 }
414
415 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
416 {
417   LoadState loadState = TextureManager::NOT_STARTED;
418
419   int cacheIndex = GetCacheIndexFromId( textureId );
420   if( cacheIndex != INVALID_CACHE_INDEX )
421   {
422     TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
423     loadState = cachedTextureInfo.loadState;
424   }
425   return loadState;
426 }
427
428 TextureSet TextureManager::GetTextureSet( TextureId textureId )
429 {
430   TextureSet textureSet;// empty handle
431
432   int cacheIndex = GetCacheIndexFromId( textureId );
433   if( cacheIndex != INVALID_CACHE_INDEX )
434   {
435     TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
436     textureSet = cachedTextureInfo.textureSet;
437   }
438   return textureSet;
439 }
440
441 std::string TextureManager::AddExternalTexture( TextureSet& textureSet )
442 {
443   TextureManager::ExternalTextureInfo info;
444   info.textureId = GenerateUniqueTextureId();
445   info.textureSet = textureSet;
446   mExternalTextures.emplace_back( info );
447   return VisualUrl::CreateTextureUrl( std::to_string( info.textureId ) );
448 }
449
450 TextureSet TextureManager::RemoveExternalTexture( const std::string& url )
451 {
452   if( url.size() > 0u )
453   {
454     // get the location from the Url
455     VisualUrl parseUrl( url );
456     if( VisualUrl::TEXTURE == parseUrl.GetProtocolType() )
457     {
458       std::string location = parseUrl.GetLocation();
459       if( location.size() > 0u )
460       {
461         TextureId id = std::stoi( location );
462         const auto end = mExternalTextures.end();
463         for( auto iter = mExternalTextures.begin(); iter != end; ++iter )
464         {
465           if( iter->textureId == id )
466           {
467             auto textureSet = iter->textureSet;
468             mExternalTextures.erase( iter );
469             return textureSet;
470           }
471         }
472       }
473     }
474   }
475   return TextureSet();
476 }
477
478 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
479 {
480   bool success = true;
481
482   if( textureInfo.loadState == NOT_STARTED )
483   {
484     textureInfo.loadState = LOADING;
485
486     if( !textureInfo.loadSynchronously )
487     {
488       auto& loadersContainer = textureInfo.url.IsLocalResource() ? mAsyncLocalLoaders : mAsyncRemoteLoaders;
489       auto loadingHelperIt = loadersContainer.GetNext();
490       DALI_ASSERT_ALWAYS(loadingHelperIt != loadersContainer.End());
491       loadingHelperIt->Load(textureInfo.textureId, textureInfo.url,
492                             textureInfo.desiredSize, textureInfo.fittingMode,
493                             textureInfo.samplingMode, true);
494     }
495   }
496
497   return success;
498 }
499
500 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
501                                      TextureUploadObserver* observer )
502 {
503   if( observer )
504   {
505     textureInfo.observerList.PushBack( observer );
506     observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
507   }
508 }
509
510 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, Devel::PixelBuffer pixelBuffer )
511 {
512   DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
513
514   if( loadingContainer.size() >= 1u )
515   {
516     AsyncLoadingInfo loadingInfo = loadingContainer.front();
517
518     if( loadingInfo.loadId == id )
519     {
520       int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
521       if( cacheIndex != INVALID_CACHE_INDEX )
522       {
523         TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
524
525         DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
526
527         if( textureInfo.loadState != CANCELLED )
528         {
529           // textureInfo can be invalidated after this call (as the mTextureInfoContainer may be modified)
530           PostLoad( textureInfo, pixelBuffer );
531         }
532         else
533         {
534           Remove( textureInfo.textureId );
535         }
536       }
537     }
538
539     loadingContainer.pop_front();
540   }
541 }
542
543 void TextureManager::PostLoad( TextureInfo& textureInfo, Devel::PixelBuffer& pixelBuffer )
544 {
545   // Was the load successful?
546   if( pixelBuffer && ( pixelBuffer.GetWidth() != 0 ) && ( pixelBuffer.GetHeight() != 0 ) )
547   {
548     // No atlas support for now
549     textureInfo.useAtlas = NO_ATLAS;
550
551     if( textureInfo.storageType == UPLOAD_TO_TEXTURE )
552     {
553       // If there is a mask texture ID associated with this texture, then apply the mask
554       // if it's already loaded. If it hasn't, and the mask is still loading,
555       // wait for the mask to finish loading.
556       if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
557       {
558         LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
559         if( maskLoadState == LOADING )
560         {
561           textureInfo.pixelBuffer = pixelBuffer; // Store the pixel buffer temporarily
562           textureInfo.loadState = WAITING_FOR_MASK;
563         }
564         else if( maskLoadState == LOAD_FINISHED )
565         {
566           ApplyMask( pixelBuffer, textureInfo.maskTextureId, textureInfo.scaleFactor, textureInfo.cropToMask );
567           UploadTexture( pixelBuffer, textureInfo );
568           NotifyObservers( textureInfo, true );
569         }
570       }
571       else
572       {
573         UploadTexture( pixelBuffer, textureInfo );
574         NotifyObservers( textureInfo, true );
575       }
576     }
577     else
578     {
579       textureInfo.pixelBuffer = pixelBuffer; // Store the pixel data
580       textureInfo.loadState = LOAD_FINISHED;
581
582       // Check if there was another texture waiting for this load to complete
583       // (e.g. if this was an image mask, and its load is on a different thread)
584       CheckForWaitingTexture( textureInfo );
585     }
586   }
587   else
588   {
589     DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
590     // @todo If the load was unsuccessful, upload the broken image.
591     textureInfo.loadState = LOAD_FAILED;
592     CheckForWaitingTexture( textureInfo );
593     NotifyObservers( textureInfo, false );
594   }
595 }
596
597 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
598 {
599   // Search the cache, checking if any texture has this texture id as a
600   // maskTextureId:
601   const unsigned int size = mTextureInfoContainer.size();
602
603   for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
604   {
605     if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
606         mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
607     {
608       TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
609       Devel::PixelBuffer pixelBuffer = textureInfo.pixelBuffer;
610       textureInfo.pixelBuffer.Reset();
611
612       if( maskTextureInfo.loadState == LOAD_FINISHED )
613       {
614         ApplyMask( pixelBuffer, maskTextureInfo.textureId, textureInfo.scaleFactor, textureInfo.cropToMask );
615         UploadTexture( pixelBuffer, textureInfo );
616         NotifyObservers( textureInfo, true );
617       }
618       else
619       {
620         DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
621         textureInfo.loadState = LOAD_FAILED;
622         NotifyObservers( textureInfo, false );
623       }
624     }
625   }
626 }
627
628 void TextureManager::ApplyMask(
629   Devel::PixelBuffer& pixelBuffer, TextureId maskTextureId,
630   float contentScale, bool cropToMask )
631 {
632   int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
633   Devel::PixelBuffer maskPixelBuffer = mTextureInfoContainer[maskCacheIndex].pixelBuffer;
634   pixelBuffer.ApplyMask( maskPixelBuffer, contentScale, cropToMask );
635 }
636
637 void TextureManager::UploadTexture( Devel::PixelBuffer& pixelBuffer, TextureInfo& textureInfo )
638 {
639   if( textureInfo.useAtlas != USE_ATLAS )
640   {
641     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
642
643     Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelBuffer.GetPixelFormat(), pixelBuffer.GetWidth(), pixelBuffer.GetHeight() );
644     PixelData pixelData = Devel::PixelBuffer::Convert( pixelBuffer );
645     texture.Upload( pixelData );
646     textureInfo.textureSet = TextureSet::New();
647     textureInfo.textureSet.SetTexture( 0u, texture );
648   }
649
650   // Update the load state.
651   // Note: This is regardless of success as we care about whether a
652   // load attempt is in progress or not.  If unsuccessful, a broken
653   // image is still loaded.
654   textureInfo.loadState = UPLOADED;
655 }
656
657 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
658 {
659   TextureId textureId = textureInfo.textureId;
660
661   // If there is an observer: Notify the load is complete, whether successful or not,
662   // and erase it from the list
663   unsigned int observerCount = textureInfo.observerList.Count();
664   TextureInfo* info = &textureInfo;
665
666   while( observerCount )
667   {
668     TextureUploadObserver* observer = info->observerList[0];
669
670     // During UploadComplete() a Control ResourceReady() signal is emitted.
671     // During that signal the app may add remove /add Textures (e.g. via
672     // ImageViews).  At this point no more observers can be added to the
673     // observerList, because textureInfo.loadState = UPLOADED. However it is
674     // possible for observers to be removed, hence we check the observer list
675     // count every iteration.
676
677     // The reference to the textureInfo struct can also become invalidated,
678     // because new load requests can modify the mTextureInfoContainer list
679     // (e.g. if more requests are pushed back it can cause the list to be
680     // resized invalidating the reference to the TextureInfo ).
681     observer->UploadComplete( success, info->textureId, info->textureSet, info->useAtlas, info->atlasRect );
682     observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
683
684     // Get the textureInfo from the container again as it may have been
685     // invalidated,
686
687     int textureInfoIndex = GetCacheIndexFromId( textureId );
688     if( textureInfoIndex == INVALID_CACHE_INDEX)
689     {
690       return; // texture has been removed - can stop.
691     }
692
693     info = &mTextureInfoContainer[ textureInfoIndex ];
694     observerCount = info->observerList.Count();
695     if ( observerCount > 0 )
696     {
697       // remove the observer that was just triggered if it's still in the list
698       for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
699       {
700         if( *j == observer )
701         {
702           info->observerList.Erase( j );
703           observerCount--;
704           break;
705         }
706       }
707     }
708   }
709 }
710
711 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
712 {
713   return mCurrentTextureId++;
714 }
715
716 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
717 {
718   const unsigned int size = mTextureInfoContainer.size();
719
720   for( unsigned int i = 0; i < size; ++i )
721   {
722     if( mTextureInfoContainer[i].textureId == textureId )
723     {
724       return i;
725     }
726   }
727
728   DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
729   return INVALID_CACHE_INDEX;
730 }
731
732 TextureManager::TextureHash TextureManager::GenerateHash(
733   const std::string&             url,
734   const ImageDimensions          size,
735   const FittingMode::Type        fittingMode,
736   const Dali::SamplingMode::Type samplingMode,
737   const UseAtlas                 useAtlas,
738   TextureId                      maskTextureId )
739 {
740   std::string hashTarget( url );
741   const size_t urlLength = hashTarget.length();
742   const uint16_t width = size.GetWidth();
743   const uint16_t height = size.GetWidth();
744
745   // If either the width or height has been specified, include the resizing options in the hash
746   if( width != 0 || height != 0 )
747   {
748     // We are appending 5 bytes to the URL to form the hash input.
749     hashTarget.resize( urlLength + 5u );
750     char* hashTargetPtr = &( hashTarget[ urlLength ] );
751
752     // Pack the width and height (4 bytes total).
753     *hashTargetPtr++ = size.GetWidth() & 0xff;
754     *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
755     *hashTargetPtr++ = size.GetHeight() & 0xff;
756     *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
757
758     // Bit-pack the FittingMode, SamplingMode and atlasing.
759     // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
760     *hashTargetPtr   = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
761   }
762   else
763   {
764     // We are not including sizing information, but we still need an extra byte for atlasing.
765     hashTarget.resize( urlLength + 1u );
766     // Add the atlasing to the hash input.
767     hashTarget[ urlLength ] = useAtlas;
768   }
769
770   if( maskTextureId != INVALID_TEXTURE_ID )
771   {
772     hashTarget.resize( urlLength + sizeof( TextureId ) );
773     TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
774
775     // Append the hash target to the end of the URL byte by byte:
776     // (to avoid SIGBUS / alignment issues)
777     for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
778     {
779       *hashTargetPtr++ = maskTextureId & 0xff;
780       maskTextureId >>= 8u;
781     }
782   }
783
784   return Dali::CalculateHash( hashTarget );
785 }
786
787 int TextureManager::FindCachedTexture(
788   const TextureManager::TextureHash hash,
789   const std::string&                url,
790   const ImageDimensions             size,
791   const FittingMode::Type           fittingMode,
792   const Dali::SamplingMode::Type    samplingMode,
793   const bool                        useAtlas,
794   TextureId                         maskTextureId)
795 {
796   // Default to an invalid ID, in case we do not find a match.
797   int cacheIndex = INVALID_CACHE_INDEX;
798
799   // Iterate through our hashes to find a match.
800   const unsigned int count = mTextureInfoContainer.size();
801   for( unsigned int i = 0u; i < count; ++i )
802   {
803     if( mTextureInfoContainer[i].hash == hash )
804     {
805       // We have a match, now we check all the original parameters in case of a hash collision.
806       TextureInfo& textureInfo( mTextureInfoContainer[i] );
807
808       if( ( url == textureInfo.url.GetUrl() ) &&
809           ( useAtlas == textureInfo.useAtlas ) &&
810           ( maskTextureId == textureInfo.maskTextureId ) &&
811           ( size == textureInfo.desiredSize ) &&
812           ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
813             ( fittingMode == textureInfo.fittingMode &&
814               samplingMode == textureInfo.samplingMode ) ) )
815       {
816         // The found Texture is a match.
817         cacheIndex = i;
818         break;
819       }
820     }
821   }
822
823   return cacheIndex;
824 }
825
826 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
827 {
828   const unsigned int count = mTextureInfoContainer.size();
829   for( unsigned int i = 0; i < count; ++i )
830   {
831     TextureInfo& textureInfo( mTextureInfoContainer[i] );
832     for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); )
833     {
834       if( *j == observer )
835       {
836         j = textureInfo.observerList.Erase( j );
837       }
838       else
839       {
840         ++j;
841       }
842     }
843   }
844 }
845
846 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(TextureManager& textureManager)
847 : AsyncLoadingHelper(Toolkit::AsyncImageLoader::New(), textureManager,
848                      AsyncLoadingInfoContainerType())
849 {
850 }
851
852 void TextureManager::AsyncLoadingHelper::Load(TextureId          textureId,
853                                               const VisualUrl&   url,
854                                               ImageDimensions    desiredSize,
855                                               FittingMode::Type  fittingMode,
856                                               SamplingMode::Type samplingMode,
857                                               bool               orientationCorrection)
858 {
859   mLoadingInfoContainer.push_back(AsyncLoadingInfo(textureId));
860   auto id = mLoader.Load(url.GetUrl(), desiredSize, fittingMode, samplingMode, orientationCorrection);
861   mLoadingInfoContainer.back().loadId = id;
862 }
863
864 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(AsyncLoadingHelper&& rhs)
865 : AsyncLoadingHelper(rhs.mLoader, rhs.mTextureManager, std::move(rhs.mLoadingInfoContainer))
866 {
867 }
868
869 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(
870     Toolkit::AsyncImageLoader loader,
871     TextureManager& textureManager,
872     AsyncLoadingInfoContainerType&& loadingInfoContainer)
873 : mLoader(loader),
874   mTextureManager(textureManager),
875   mLoadingInfoContainer(std::move(loadingInfoContainer))
876 {
877   DevelAsyncImageLoader::PixelBufferLoadedSignal(mLoader).Connect(
878       this, &AsyncLoadingHelper::AsyncLoadComplete);
879 }
880
881 void TextureManager::AsyncLoadingHelper::AsyncLoadComplete(uint32_t           id,
882                                                            Devel::PixelBuffer pixelBuffer)
883 {
884   mTextureManager.AsyncLoadComplete(mLoadingInfoContainer, id, pixelBuffer);
885 }
886
887 } // namespace Internal
888
889 } // namespace Toolkit
890
891 } // namespace Dali