f0740874b39f7e7a3547b32f91449c6516fe525e
[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/devel-api/adaptor-framework/environment-variable.h>
25 #include <dali/devel-api/common/hash.h>
26 #include <dali/devel-api/images/texture-set-image.h>
27 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
28 #include <dali/integration-api/debug.h>
29
30 // INTERNAL HEADERS
31 #include <dali-toolkit/internal/image-loader/image-atlas-impl.h>
32 #include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
33
34 namespace
35 {
36
37 constexpr auto DEFAULT_NUMBER_OF_LOCAL_LOADER_THREADS = size_t{4u};
38 constexpr auto DEFAULT_NUMBER_OF_REMOTE_LOADER_THREADS = size_t{8u};
39
40 constexpr auto NUMBER_OF_LOCAL_LOADER_THREADS_ENV = "DALI_TEXTURE_LOCAL_THREADS";
41 constexpr auto NUMBER_OF_REMOTE_LOADER_THREADS_ENV = "DALI_TEXTURE_REMOTE_THREADS";
42
43 size_t GetNumberOfThreads(const char* environmentVariable, size_t defaultValue)
44 {
45   using Dali::EnvironmentVariable::GetEnvironmentVariable;
46   auto numberString = GetEnvironmentVariable(environmentVariable);
47   auto numberOfThreads = numberString ? std::strtol(numberString, nullptr, 10) : 0;
48   return (numberOfThreads > 0) ? numberOfThreads : defaultValue;
49 }
50
51 size_t GetNumberOfLocalLoaderThreads()
52 {
53   return GetNumberOfThreads(NUMBER_OF_LOCAL_LOADER_THREADS_ENV, DEFAULT_NUMBER_OF_LOCAL_LOADER_THREADS);
54 }
55
56 size_t GetNumberOfRemoteLoaderThreads()
57 {
58   return GetNumberOfThreads(NUMBER_OF_REMOTE_LOADER_THREADS_ENV, DEFAULT_NUMBER_OF_REMOTE_LOADER_THREADS);
59 }
60
61 } // namespace
62
63 namespace Dali
64 {
65
66 namespace Toolkit
67 {
68
69 namespace Internal
70 {
71
72 namespace
73 {
74
75 #ifdef DEBUG_ENABLED
76 Debug::Filter* gTextureManagerLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_TEXTURE_MANAGER" );
77 #endif
78
79 const uint32_t      DEFAULT_ATLAS_SIZE( 1024u );                     ///< This size can fit 8 by 8 images of average size 128 * 128
80 const Vector4       FULL_ATLAS_RECT( 0.0f, 0.0f, 1.0f, 1.0f );       ///< UV Rectangle that covers the full Texture
81 const char * const  BROKEN_IMAGE_URL( DALI_IMAGE_DIR "broken.png" ); ///< URL For the broken image placeholder
82 const int           INVALID_INDEX( -1 );                             ///< Invalid index used to represent a non-existant TextureInfo struct
83 const int           INVALID_CACHE_INDEX( -1 ); ///< Invalid Cache index
84
85 } // Anonymous namespace
86
87
88 TextureManager::TextureManager()
89 : mAsyncLocalLoaders( GetNumberOfLocalLoaderThreads(), [&]() { return AsyncLoadingHelper(*this); } ),
90   mAsyncRemoteLoaders( GetNumberOfRemoteLoaderThreads(), [&]() { return AsyncLoadingHelper(*this); } ),
91   mCurrentTextureId( 0 )
92 {
93 }
94
95 TextureManager::TextureId TextureManager::RequestLoad(
96   const VisualUrl&         url,
97   const ImageDimensions    desiredSize,
98   FittingMode::Type        fittingMode,
99   Dali::SamplingMode::Type samplingMode,
100   const UseAtlas           useAtlas,
101   TextureUploadObserver*   observer )
102 {
103   return RequestLoadInternal( url, INVALID_TEXTURE_ID, 1.0f, desiredSize, fittingMode, samplingMode, useAtlas, false, UPLOAD_TO_TEXTURE, observer );
104 }
105
106 TextureManager::TextureId TextureManager::RequestLoad(
107   const VisualUrl&         url,
108   TextureId                maskTextureId,
109   float                    contentScale,
110   const ImageDimensions    desiredSize,
111   FittingMode::Type        fittingMode,
112   Dali::SamplingMode::Type samplingMode,
113   const UseAtlas           useAtlas,
114   bool                     cropToMask,
115   TextureUploadObserver*   observer )
116 {
117   return RequestLoadInternal( url, maskTextureId, contentScale, desiredSize, fittingMode, samplingMode, useAtlas, cropToMask, UPLOAD_TO_TEXTURE, observer );
118 }
119
120 TextureManager::TextureId TextureManager::RequestMaskLoad( const VisualUrl& maskUrl )
121 {
122   // Use the normal load procedure to get the alpha mask.
123   return RequestLoadInternal( maskUrl, INVALID_TEXTURE_ID, 1.0f, ImageDimensions(), FittingMode::SCALE_TO_FILL, SamplingMode::NO_FILTER, NO_ATLAS, false, KEEP_PIXEL_BUFFER, NULL );
124 }
125
126
127 TextureManager::TextureId TextureManager::RequestLoadInternal(
128   const VisualUrl&         url,
129   TextureId                maskTextureId,
130   float                    contentScale,
131   const ImageDimensions    desiredSize,
132   FittingMode::Type        fittingMode,
133   Dali::SamplingMode::Type samplingMode,
134   UseAtlas                 useAtlas,
135   bool                     cropToMask,
136   StorageType              storageType,
137   TextureUploadObserver*   observer )
138 {
139   // First check if the requested Texture is cached.
140   const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
141
142   TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
143
144   // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
145   int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas, maskTextureId );
146
147   // Check if the requested Texture exists in the cache.
148   if( cacheIndex != INVALID_CACHE_INDEX )
149   {
150     // Mark this texture being used by another client resource.
151     ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
152     textureId = mTextureInfoContainer[ cacheIndex ].textureId;
153
154     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 );
155   }
156
157   if( textureId == INVALID_TEXTURE_ID ) // There was no caching, or caching not required
158   {
159     // We need a new Texture.
160     textureId = GenerateUniqueTextureId();
161     mTextureInfoContainer.push_back( TextureInfo( textureId, maskTextureId, url.GetUrl(),
162                                                   desiredSize, contentScale, fittingMode, samplingMode,
163                                                   false, cropToMask, useAtlas, textureHash ) );
164     cacheIndex = mTextureInfoContainer.size() - 1u;
165
166     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 );
167   }
168
169   // The below code path is common whether we are using the cache or not.
170   // The textureInfoIndex now refers to either a pre-existing cached TextureInfo,
171   // or a new TextureInfo just created.
172   TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
173   textureInfo.maskTextureId = maskTextureId;
174   textureInfo.storageType = storageType;
175
176   DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
177                  textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
178                  textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
179                  textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
180                  textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
181
182   // 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.
183   switch( textureInfo.loadState )
184   {
185     case TextureManager::NOT_STARTED:
186     {
187       LoadTexture( textureInfo );
188       ObserveTexture( textureInfo, observer );
189       break;
190     }
191     case TextureManager::LOADING:
192     {
193       ObserveTexture( textureInfo, observer );
194       break;
195     }
196     case TextureManager::UPLOADED:
197     {
198       if( observer )
199       {
200         // The Texture has already loaded. The other observers have already been notified.
201         // We need to send a "late" loaded notification for this observer.
202         observer->UploadComplete( true, textureInfo.textureId, textureInfo.textureSet,
203                                   textureInfo.useAtlas, textureInfo.atlasRect );
204       }
205       break;
206     }
207     case TextureManager::CANCELLED:
208     {
209       // A cancelled texture hasn't finished loading yet. Treat as a loading texture
210       // (it's ref count has already been incremented, above)
211       textureInfo.loadState = TextureManager::LOADING;
212       ObserveTexture( textureInfo, observer );
213       break;
214     }
215     case TextureManager::LOAD_FINISHED:
216     case TextureManager::WAITING_FOR_MASK:
217     case TextureManager::LOAD_FAILED:
218       // Loading has already completed. Do nothing.
219       break;
220   }
221
222   // Return the TextureId for which this Texture can now be referenced by externally.
223   return textureId;
224 }
225
226 void TextureManager::Remove( const TextureManager::TextureId textureId )
227 {
228   int textureInfoIndex = GetCacheIndexFromId( textureId );
229   if( textureInfoIndex != INVALID_INDEX )
230   {
231     TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
232
233     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
234                    textureId, textureInfoIndex,
235                    textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
236                    textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
237                    textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
238                    textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
239
240     // Decrement the reference count and check if this is the last user of this Texture.
241     if( --textureInfo.referenceCount <= 0 )
242     {
243       // This is the last remove for this Texture.
244       textureInfo.referenceCount = 0;
245       bool removeTextureInfo = false;
246
247       // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
248       if( textureInfo.loadState == UPLOADED )
249       {
250         if( textureInfo.atlas )
251         {
252           textureInfo.atlas.Remove( textureInfo.atlasRect );
253         }
254         removeTextureInfo = true;
255       }
256       else if( textureInfo.loadState == LOADING )
257       {
258         // We mark the textureInfo for removal.
259         // Once the load has completed, this method will be called again.
260         textureInfo.loadState = CANCELLED;
261       }
262       else
263       {
264         // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
265         removeTextureInfo = true;
266       }
267
268       // If the state allows us to remove the TextureInfo data, we do so.
269       if( removeTextureInfo )
270       {
271         // Permanently remove the textureInfo struct.
272         mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
273       }
274     }
275   }
276 }
277
278 const VisualUrl& TextureManager::GetVisualUrl( TextureId textureId )
279 {
280   int cacheIndex = GetCacheIndexFromId( textureId );
281   DALI_ASSERT_DEBUG( cacheIndex != INVALID_CACHE_INDEX && "TextureId out of range");
282
283   TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
284   return cachedTextureInfo.url;
285 }
286
287 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
288 {
289   LoadState loadState = TextureManager::NOT_STARTED;
290
291   int cacheIndex = GetCacheIndexFromId( textureId );
292   if( cacheIndex != INVALID_CACHE_INDEX )
293   {
294     TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
295     loadState = cachedTextureInfo.loadState;
296   }
297   return loadState;
298 }
299
300 TextureSet TextureManager::GetTextureSet( TextureId textureId )
301 {
302   TextureSet textureSet;// empty handle
303
304   int cacheIndex = GetCacheIndexFromId( textureId );
305   if( cacheIndex != INVALID_CACHE_INDEX )
306   {
307     TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
308     textureSet = cachedTextureInfo.textureSet;
309   }
310   return textureSet;
311 }
312
313 std::string TextureManager::AddExternalTexture( TextureSet& textureSet )
314 {
315   TextureManager::ExternalTextureInfo info;
316   info.textureId = GenerateUniqueTextureId();
317   info.textureSet = textureSet;
318   mExternalTextures.emplace_back( info );
319   return VisualUrl::CreateTextureUrl( std::to_string( info.textureId ) );
320 }
321
322 TextureSet TextureManager::RemoveExternalTexture( const std::string& url )
323 {
324   if( url.size() > 0u )
325   {
326     // get the location from the Url
327     VisualUrl parseUrl( url );
328     if( VisualUrl::TEXTURE == parseUrl.GetProtocolType() )
329     {
330       std::string location = parseUrl.GetLocation();
331       if( location.size() > 0u )
332       {
333         TextureId id = std::stoi( location );
334         const auto end = mExternalTextures.end();
335         for( auto iter = mExternalTextures.begin(); iter != end; ++iter )
336         {
337           if( iter->textureId == id )
338           {
339             auto textureSet = iter->textureSet;
340             mExternalTextures.erase( iter );
341             return textureSet;
342           }
343         }
344       }
345     }
346   }
347   return TextureSet();
348 }
349
350 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
351 {
352   bool success = true;
353
354   if( textureInfo.loadState == NOT_STARTED )
355   {
356     textureInfo.loadState = LOADING;
357
358     if( !textureInfo.loadSynchronously )
359     {
360       auto& loadersContainer = textureInfo.url.IsLocalResource() ? mAsyncLocalLoaders : mAsyncRemoteLoaders;
361       auto loadingHelperIt = loadersContainer.GetNext();
362       DALI_ASSERT_ALWAYS(loadingHelperIt != loadersContainer.End());
363       loadingHelperIt->Load(textureInfo.textureId, textureInfo.url,
364                             textureInfo.desiredSize, textureInfo.fittingMode,
365                             textureInfo.samplingMode, true);
366     }
367   }
368
369   return success;
370 }
371
372 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
373                                      TextureUploadObserver* observer )
374 {
375   if( observer )
376   {
377     textureInfo.observerList.PushBack( observer );
378     observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
379   }
380 }
381
382 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, Devel::PixelBuffer pixelBuffer )
383 {
384   DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
385
386   if( loadingContainer.size() >= 1u )
387   {
388     AsyncLoadingInfo loadingInfo = loadingContainer.front();
389
390     if( loadingInfo.loadId == id )
391     {
392       int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
393       if( cacheIndex != INVALID_CACHE_INDEX )
394       {
395         TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
396
397         DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
398
399         if( textureInfo.loadState != CANCELLED )
400         {
401           // textureInfo can be invalidated after this call (as the mTextureInfoContainer may be modified)
402           PostLoad( textureInfo, pixelBuffer );
403         }
404         else
405         {
406           Remove( textureInfo.textureId );
407         }
408       }
409     }
410
411     loadingContainer.pop_front();
412   }
413 }
414
415 void TextureManager::PostLoad( TextureInfo& textureInfo, Devel::PixelBuffer& pixelBuffer )
416 {
417   // Was the load successful?
418   if( pixelBuffer && ( pixelBuffer.GetWidth() != 0 ) && ( pixelBuffer.GetHeight() != 0 ) )
419   {
420     // No atlas support for now
421     textureInfo.useAtlas = NO_ATLAS;
422
423     if( textureInfo.storageType == UPLOAD_TO_TEXTURE )
424     {
425       // If there is a mask texture ID associated with this texture, then apply the mask
426       // if it's already loaded. If it hasn't, and the mask is still loading,
427       // wait for the mask to finish loading.
428       if( textureInfo.maskTextureId != INVALID_TEXTURE_ID )
429       {
430         LoadState maskLoadState = GetTextureState( textureInfo.maskTextureId );
431         if( maskLoadState == LOADING )
432         {
433           textureInfo.pixelBuffer = pixelBuffer; // Store the pixel buffer temporarily
434           textureInfo.loadState = WAITING_FOR_MASK;
435         }
436         else if( maskLoadState == LOAD_FINISHED )
437         {
438           ApplyMask( pixelBuffer, textureInfo.maskTextureId, textureInfo.scaleFactor, textureInfo.cropToMask );
439           UploadTexture( pixelBuffer, textureInfo );
440           NotifyObservers( textureInfo, true );
441         }
442       }
443       else
444       {
445         UploadTexture( pixelBuffer, textureInfo );
446         NotifyObservers( textureInfo, true );
447       }
448     }
449     else
450     {
451       textureInfo.pixelBuffer = pixelBuffer; // Store the pixel data
452       textureInfo.loadState = LOAD_FINISHED;
453
454       // Check if there was another texture waiting for this load to complete
455       // (e.g. if this was an image mask, and its load is on a different thread)
456       CheckForWaitingTexture( textureInfo );
457     }
458   }
459   else
460   {
461     DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
462     // @todo If the load was unsuccessful, upload the broken image.
463     textureInfo.loadState = LOAD_FAILED;
464     CheckForWaitingTexture( textureInfo );
465     NotifyObservers( textureInfo, false );
466   }
467 }
468
469 void TextureManager::CheckForWaitingTexture( TextureInfo& maskTextureInfo )
470 {
471   // Search the cache, checking if any texture has this texture id as a
472   // maskTextureId:
473   const unsigned int size = mTextureInfoContainer.size();
474
475   for( unsigned int cacheIndex = 0; cacheIndex < size; ++cacheIndex )
476   {
477     if( mTextureInfoContainer[cacheIndex].maskTextureId == maskTextureInfo.textureId &&
478         mTextureInfoContainer[cacheIndex].loadState == WAITING_FOR_MASK )
479     {
480       TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
481       Devel::PixelBuffer pixelBuffer = textureInfo.pixelBuffer;
482       textureInfo.pixelBuffer.Reset();
483
484       if( maskTextureInfo.loadState == LOAD_FINISHED )
485       {
486         ApplyMask( pixelBuffer, maskTextureInfo.textureId, textureInfo.scaleFactor, textureInfo.cropToMask );
487         UploadTexture( pixelBuffer, textureInfo );
488         NotifyObservers( textureInfo, true );
489       }
490       else
491       {
492         DALI_LOG_ERROR( "TextureManager::ApplyMask to %s failed\n", textureInfo.url.GetUrl().c_str() );
493         textureInfo.loadState = LOAD_FAILED;
494         NotifyObservers( textureInfo, false );
495       }
496     }
497   }
498 }
499
500 void TextureManager::ApplyMask(
501   Devel::PixelBuffer& pixelBuffer, TextureId maskTextureId,
502   float contentScale, bool cropToMask )
503 {
504   int maskCacheIndex = GetCacheIndexFromId( maskTextureId );
505   Devel::PixelBuffer maskPixelBuffer = mTextureInfoContainer[maskCacheIndex].pixelBuffer;
506   pixelBuffer.ApplyMask( maskPixelBuffer, contentScale, cropToMask );
507 }
508
509 void TextureManager::UploadTexture( Devel::PixelBuffer& pixelBuffer, TextureInfo& textureInfo )
510 {
511   if( textureInfo.useAtlas != USE_ATLAS )
512   {
513     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  TextureManager::UploadTexture() New Texture for textureId:%d\n", textureInfo.textureId );
514
515     Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelBuffer.GetPixelFormat(), pixelBuffer.GetWidth(), pixelBuffer.GetHeight() );
516     PixelData pixelData = Devel::PixelBuffer::Convert( pixelBuffer );
517     texture.Upload( pixelData );
518     textureInfo.textureSet = TextureSet::New();
519     textureInfo.textureSet.SetTexture( 0u, texture );
520   }
521
522   // Update the load state.
523   // Note: This is regardless of success as we care about whether a
524   // load attempt is in progress or not.  If unsuccessful, a broken
525   // image is still loaded.
526   textureInfo.loadState = UPLOADED;
527 }
528
529 void TextureManager::NotifyObservers( TextureInfo& textureInfo, bool success )
530 {
531   TextureId textureId = textureInfo.textureId;
532
533   // If there is an observer: Notify the load is complete, whether successful or not,
534   // and erase it from the list
535   unsigned int observerCount = textureInfo.observerList.Count();
536   TextureInfo* info = &textureInfo;
537
538   while( observerCount )
539   {
540     TextureUploadObserver* observer = info->observerList[0];
541
542     // During UploadComplete() a Control ResourceReady() signal is emitted.
543     // During that signal the app may add remove /add Textures (e.g. via
544     // ImageViews).  At this point no more observers can be added to the
545     // observerList, because textureInfo.loadState = UPLOADED. However it is
546     // possible for observers to be removed, hence we check the observer list
547     // count every iteration.
548
549     // The reference to the textureInfo struct can also become invalidated,
550     // because new load requests can modify the mTextureInfoContainer list
551     // (e.g. if more requests are pushed back it can cause the list to be
552     // resized invalidating the reference to the TextureInfo ).
553     observer->UploadComplete( success, info->textureId, info->textureSet, info->useAtlas, info->atlasRect );
554     observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
555
556     // Get the textureInfo from the container again as it may have been
557     // invalidated,
558
559     int textureInfoIndex = GetCacheIndexFromId( textureId );
560     if( textureInfoIndex == INVALID_CACHE_INDEX)
561     {
562       return; // texture has been removed - can stop.
563     }
564
565     info = &mTextureInfoContainer[ textureInfoIndex ];
566     observerCount = info->observerList.Count();
567     if ( observerCount > 0 )
568     {
569       // remove the observer that was just triggered if it's still in the list
570       for( TextureInfo::ObserverListType::Iterator j = info->observerList.Begin(); j != info->observerList.End(); ++j )
571       {
572         if( *j == observer )
573         {
574           info->observerList.Erase( j );
575           observerCount--;
576           break;
577         }
578       }
579     }
580   }
581 }
582
583 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
584 {
585   return mCurrentTextureId++;
586 }
587
588 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
589 {
590   const unsigned int size = mTextureInfoContainer.size();
591
592   for( unsigned int i = 0; i < size; ++i )
593   {
594     if( mTextureInfoContainer[i].textureId == textureId )
595     {
596       return i;
597     }
598   }
599
600   DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
601   return INVALID_CACHE_INDEX;
602 }
603
604 TextureManager::TextureHash TextureManager::GenerateHash(
605   const std::string&             url,
606   const ImageDimensions          size,
607   const FittingMode::Type        fittingMode,
608   const Dali::SamplingMode::Type samplingMode,
609   const UseAtlas                 useAtlas,
610   TextureId                      maskTextureId )
611 {
612   std::string hashTarget( url );
613   const size_t urlLength = hashTarget.length();
614   const uint16_t width = size.GetWidth();
615   const uint16_t height = size.GetWidth();
616
617   // If either the width or height has been specified, include the resizing options in the hash
618   if( width != 0 || height != 0 )
619   {
620     // We are appending 5 bytes to the URL to form the hash input.
621     hashTarget.resize( urlLength + 5u );
622     char* hashTargetPtr = &( hashTarget[ urlLength ] );
623
624     // Pack the width and height (4 bytes total).
625     *hashTargetPtr++ = size.GetWidth() & 0xff;
626     *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
627     *hashTargetPtr++ = size.GetHeight() & 0xff;
628     *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
629
630     // Bit-pack the FittingMode, SamplingMode and atlasing.
631     // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
632     *hashTargetPtr   = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
633   }
634   else
635   {
636     // We are not including sizing information, but we still need an extra byte for atlasing.
637     hashTarget.resize( urlLength + 1u );
638     // Add the atlasing to the hash input.
639     hashTarget[ urlLength ] = useAtlas;
640   }
641
642   if( maskTextureId != INVALID_TEXTURE_ID )
643   {
644     hashTarget.resize( urlLength + sizeof( TextureId ) );
645     TextureId* hashTargetPtr = reinterpret_cast<TextureId*>(&( hashTarget[ urlLength ] ));
646
647     // Append the hash target to the end of the URL byte by byte:
648     // (to avoid SIGBUS / alignment issues)
649     for( size_t byteIter = 0; byteIter < sizeof( TextureId ); ++byteIter )
650     {
651       *hashTargetPtr++ = maskTextureId & 0xff;
652       maskTextureId >>= 8u;
653     }
654   }
655
656   return Dali::CalculateHash( hashTarget );
657 }
658
659 int TextureManager::FindCachedTexture(
660   const TextureManager::TextureHash hash,
661   const std::string&                url,
662   const ImageDimensions             size,
663   const FittingMode::Type           fittingMode,
664   const Dali::SamplingMode::Type    samplingMode,
665   const bool                        useAtlas,
666   TextureId                         maskTextureId)
667 {
668   // Default to an invalid ID, in case we do not find a match.
669   int cacheIndex = INVALID_CACHE_INDEX;
670
671   // Iterate through our hashes to find a match.
672   const unsigned int count = mTextureInfoContainer.size();
673   for( unsigned int i = 0u; i < count; ++i )
674   {
675     if( mTextureInfoContainer[i].hash == hash )
676     {
677       // We have a match, now we check all the original parameters in case of a hash collision.
678       TextureInfo& textureInfo( mTextureInfoContainer[i] );
679
680       if( ( url == textureInfo.url.GetUrl() ) &&
681           ( useAtlas == textureInfo.useAtlas ) &&
682           ( maskTextureId == textureInfo.maskTextureId ) &&
683           ( size == textureInfo.desiredSize ) &&
684           ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
685             ( fittingMode == textureInfo.fittingMode &&
686               samplingMode == textureInfo.samplingMode ) ) )
687       {
688         // The found Texture is a match.
689         cacheIndex = i;
690         break;
691       }
692     }
693   }
694
695   return cacheIndex;
696 }
697
698 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
699 {
700   const unsigned int count = mTextureInfoContainer.size();
701   for( unsigned int i = 0; i < count; ++i )
702   {
703     TextureInfo& textureInfo( mTextureInfoContainer[i] );
704     for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); )
705     {
706       if( *j == observer )
707       {
708         j = textureInfo.observerList.Erase( j );
709       }
710       else
711       {
712         ++j;
713       }
714     }
715   }
716 }
717
718 TextureManager::~TextureManager()
719 {
720 }
721
722 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(TextureManager& textureManager)
723 : AsyncLoadingHelper(Toolkit::AsyncImageLoader::New(), textureManager,
724                      AsyncLoadingInfoContainerType())
725 {
726 }
727
728 void TextureManager::AsyncLoadingHelper::Load(TextureId          textureId,
729                                               const VisualUrl&   url,
730                                               ImageDimensions    desiredSize,
731                                               FittingMode::Type  fittingMode,
732                                               SamplingMode::Type samplingMode,
733                                               bool               orientationCorrection)
734 {
735   mLoadingInfoContainer.push_back(AsyncLoadingInfo(textureId));
736   auto id = mLoader.Load(url.GetUrl(), desiredSize, fittingMode, samplingMode, orientationCorrection);
737   mLoadingInfoContainer.back().loadId = id;
738 }
739
740 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(AsyncLoadingHelper&& rhs)
741 : AsyncLoadingHelper(rhs.mLoader, rhs.mTextureManager, std::move(rhs.mLoadingInfoContainer))
742 {
743 }
744
745 TextureManager::AsyncLoadingHelper::AsyncLoadingHelper(
746     Toolkit::AsyncImageLoader loader,
747     TextureManager& textureManager,
748     AsyncLoadingInfoContainerType&& loadingInfoContainer)
749 : mLoader(loader),
750   mTextureManager(textureManager),
751   mLoadingInfoContainer(std::move(loadingInfoContainer))
752 {
753   DevelAsyncImageLoader::PixelBufferLoadedSignal(mLoader).Connect(
754       this, &AsyncLoadingHelper::AsyncLoadComplete);
755 }
756
757 void TextureManager::AsyncLoadingHelper::AsyncLoadComplete(uint32_t           id,
758                                                            Devel::PixelBuffer pixelBuffer)
759 {
760   mTextureManager.AsyncLoadComplete(mLoadingInfoContainer, id, pixelBuffer);
761 }
762
763 } // namespace Internal
764
765 } // namespace Toolkit
766
767 } // namespace Dali