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