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