Adding Async remote loading to ImageVisual
[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 <dali/devel-api/common/hash.h>
23 #include <dali/devel-api/images/texture-set-image.h>
24 #include <dali/integration-api/debug.h>
25
26 // INTERNAL HEADERS
27 #include <dali/integration-api/debug.h>
28 #include <dali-toolkit/internal/image-loader/async-image-loader-impl.h>
29 #include <dali-toolkit/internal/image-loader/image-atlas-impl.h>
30 #include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
31
32 namespace Dali
33 {
34
35 namespace Toolkit
36 {
37
38 namespace Internal
39 {
40
41 namespace
42 {
43
44 #ifdef DEBUG_ENABLED
45 Debug::Filter* gTextureManagerLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_TEXTURE_MANAGER" );
46 #endif
47
48 const uint32_t      DEFAULT_ATLAS_SIZE( 1024u );                     ///< This size can fit 8 by 8 images of average size 128 * 128
49 const Vector4       FULL_ATLAS_RECT( 0.0f, 0.0f, 1.0f, 1.0f );       ///< UV Rectangle that covers the full Texture
50 const char * const  BROKEN_IMAGE_URL( DALI_IMAGE_DIR "broken.png" ); ///< URL For the broken image placeholder
51 const int           INVALID_INDEX( -1 );                             ///< Invalid index used to represent a non-existant TextureInfo struct
52 const int           INVALID_CACHE_INDEX( -1 ); ///< Invalid Cache index
53
54 } // Anonymous namespace
55
56
57 TextureManager::TextureManager()
58 : mAsyncLocalLoader( Toolkit::AsyncImageLoader::New() ),
59   mAsyncRemoteLoader( Toolkit::AsyncImageLoader::New() ),
60   mCurrentTextureId( 0 )
61 {
62   mAsyncLocalLoader.ImageLoadedSignal().Connect( this, &TextureManager::AsyncLocalLoadComplete );
63   mAsyncRemoteLoader.ImageLoadedSignal().Connect( this, &TextureManager::AsyncRemoteLoadComplete );
64 }
65
66 TextureManager::TextureId TextureManager::RequestLoad(
67   const VisualUrl&         url,
68   const ImageDimensions    desiredSize,
69   FittingMode::Type        fittingMode,
70   Dali::SamplingMode::Type samplingMode,
71   const UseAtlas           useAtlas,
72   TextureUploadObserver*   observer )
73 {
74   // First check if the requested Texture is cached.
75   const TextureHash textureHash = GenerateHash( url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas );
76
77   // Look up the texture by hash. Note: The extra parameters are used in case of a hash collision.
78   int cacheIndex = FindCachedTexture( textureHash, url.GetUrl(), desiredSize, fittingMode, samplingMode, useAtlas );
79   TextureManager::TextureId textureId = INVALID_TEXTURE_ID;
80
81   // Check if the requested Texture exists in the cache.
82   if( cacheIndex != INVALID_CACHE_INDEX )
83   {
84     // Mark this texture being used by another client resource.
85     ++( mTextureInfoContainer[ cacheIndex ].referenceCount );
86     textureId = mTextureInfoContainer[ cacheIndex ].textureId;
87
88     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 );
89   }
90   else
91   {
92     // We need a new Texture.
93     textureId = GenerateUniqueTextureId();
94     mTextureInfoContainer.push_back( TextureInfo( textureId, url.GetUrl(), desiredSize, fittingMode, samplingMode, false, useAtlas, textureHash ) );
95     cacheIndex = mTextureInfoContainer.size() - 1u;
96
97     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 );
98   }
99
100   // The below code path is common whether we are using the cache or not.
101   // The textureInfoIndex now refers to either a pre-existing cached TextureInfo, or a new TextureInfo just created.
102   TextureInfo& textureInfo( mTextureInfoContainer[ cacheIndex ] );
103
104   DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureInfo loadState:%s\n",
105                  textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
106                  textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
107                  textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
108                  textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
109
110   // 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.
111   switch( textureInfo.loadState )
112   {
113     case TextureManager::NOT_STARTED:
114     {
115       LoadTexture( textureInfo );
116       ObserveTexture( textureInfo, observer );
117       break;
118     }
119     case TextureManager::LOADING:
120     {
121       ObserveTexture( textureInfo, observer );
122       break;
123     }
124     case TextureManager::UPLOADED:
125     {
126       if( observer )
127       {
128         // The Texture has already loaded. The other observers have already been notified.
129         // We need to send a "late" loaded notification for this observer.
130         observer->UploadComplete( textureInfo.loadingSucceeded,
131                                   textureInfo.textureSet, textureInfo.useAtlas,
132                                   textureInfo.atlasRect );
133       }
134       break;
135     }
136     case TextureManager::CANCELLED:
137     {
138       // A cancelled texture hasn't finished loading yet. Treat as a loading texture
139       // (it's ref count has already been incremented, above)
140       textureInfo.loadState = TextureManager::LOADING;
141       ObserveTexture( textureInfo, observer );
142       break;
143     }
144   }
145
146   // Return the TextureId for which this Texture can now be referenced by externally.
147   return textureId;
148 }
149
150 void TextureManager::Remove( const TextureManager::TextureId textureId )
151 {
152   int textureInfoIndex = GetCacheIndexFromId( textureId );
153   if( textureInfoIndex != INVALID_INDEX )
154   {
155     TextureInfo& textureInfo( mTextureInfoContainer[ textureInfoIndex ] );
156
157
158     DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::Remove(%d) cacheIdx:%d loadState:%s\n",
159                    textureId, textureInfoIndex,
160                    textureInfo.loadState == TextureManager::NOT_STARTED ? "NOT_STARTED" :
161                    textureInfo.loadState == TextureManager::LOADING ? "LOADING" :
162                    textureInfo.loadState == TextureManager::UPLOADED ? "UPLOADED" :
163                    textureInfo.loadState == TextureManager::CANCELLED ? "CANCELLED" : "Unknown" );
164
165     // Decrement the reference count and check if this is the last user of this Texture.
166     if( --textureInfo.referenceCount <= 0 )
167     {
168       // This is the last remove for this Texture.
169       textureInfo.referenceCount = 0;
170       bool removeTextureInfo = false;
171
172       // If loaded, we can remove the TextureInfo and the Atlas (if atlased).
173       if( textureInfo.loadState == UPLOADED )
174       {
175         if( textureInfo.atlas )
176         {
177           textureInfo.atlas.Remove( textureInfo.atlasRect );
178         }
179         removeTextureInfo = true;
180       }
181       else if( textureInfo.loadState == LOADING )
182       {
183         // We mark the textureInfo for removal.
184         // Once the load has completed, this method will be called again.
185         textureInfo.loadState = CANCELLED;
186       }
187       else
188       {
189         // In other states, we are not waiting for a load so we are safe to remove the TextureInfo data.
190         removeTextureInfo = true;
191       }
192
193       // If the state allows us to remove the TextureInfo data, we do so.
194       if( removeTextureInfo )
195       {
196         // Permanently remove the textureInfo struct.
197         mTextureInfoContainer.erase( mTextureInfoContainer.begin() + textureInfoIndex );
198       }
199     }
200   }
201 }
202
203 TextureManager::LoadState TextureManager::GetTextureState( TextureId textureId )
204 {
205   LoadState loadState = TextureManager::NOT_STARTED;
206
207   int cacheIndex = GetCacheIndexFromId( textureId );
208   if( cacheIndex != INVALID_CACHE_INDEX )
209   {
210     TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
211     loadState = cachedTextureInfo.loadState;
212   }
213   return loadState;
214 }
215
216 TextureSet TextureManager::GetTextureSet( TextureId textureId )
217 {
218   TextureSet textureSet;// empty handle
219
220   int cacheIndex = GetCacheIndexFromId( textureId );
221   if( cacheIndex != INVALID_CACHE_INDEX )
222   {
223     TextureInfo& cachedTextureInfo( mTextureInfoContainer[ cacheIndex ] );
224     textureSet = cachedTextureInfo.textureSet;
225   }
226   return textureSet;
227 }
228
229
230
231 bool TextureManager::LoadTexture( TextureInfo& textureInfo )
232 {
233   bool success = true;
234
235   if( textureInfo.loadState == NOT_STARTED )
236   {
237     textureInfo.loadState = LOADING;
238
239     if( !textureInfo.loadSynchronously )
240     {
241       if( textureInfo.url.IsLocal() )
242       {
243         mAsyncLocalLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
244         mAsyncLocalLoadingInfoContainer.back().loadId = GetImplementation(mAsyncLocalLoader).Load(
245           textureInfo.url, textureInfo.desiredSize,
246           textureInfo.fittingMode, textureInfo.samplingMode, true );
247       }
248       else
249       {
250         mAsyncRemoteLoadingInfoContainer.push_back( AsyncLoadingInfo( textureInfo.textureId ) );
251         mAsyncRemoteLoadingInfoContainer.back().loadId = GetImplementation(mAsyncRemoteLoader).Load(
252           textureInfo.url, textureInfo.desiredSize,
253           textureInfo.fittingMode, textureInfo.samplingMode, true );
254       }
255     }
256   }
257
258   return success;
259 }
260
261 void TextureManager::ObserveTexture( TextureInfo& textureInfo,
262                                      TextureUploadObserver* observer )
263 {
264   if( observer )
265   {
266     textureInfo.observerList.PushBack( observer );
267     observer->DestructionSignal().Connect( this, &TextureManager::ObserverDestroyed );
268   }
269 }
270
271 void TextureManager::AsyncLocalLoadComplete( uint32_t id, PixelData pixelData )
272 {
273   AsyncLoadComplete( mAsyncLocalLoadingInfoContainer, id, pixelData );
274 }
275
276 void TextureManager::AsyncRemoteLoadComplete( uint32_t id, PixelData pixelData )
277 {
278   AsyncLoadComplete( mAsyncRemoteLoadingInfoContainer, id, pixelData );
279 }
280
281 void TextureManager::AsyncLoadComplete( AsyncLoadingInfoContainerType& loadingContainer, uint32_t id, PixelData pixelData )
282 {
283   DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "TextureManager::AsyncLoadComplete( id:%d )\n", id );
284
285   if( loadingContainer.size() >= 1u )
286   {
287     AsyncLoadingInfo loadingInfo = loadingContainer.front();
288
289     if( loadingInfo.loadId == id )
290     {
291       int cacheIndex = GetCacheIndexFromId( loadingInfo.textureId );
292       if( cacheIndex != INVALID_CACHE_INDEX )
293       {
294         // Once we have found the TextureInfo data, we call a common function used to process loaded data for both sync and async loads.
295         TextureInfo& textureInfo( mTextureInfoContainer[cacheIndex] );
296
297         DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  CacheIndex:%d LoadState: %d\n", cacheIndex, textureInfo.loadState );
298
299         // Only perform atlasing if the load has not been cancelled since the request.
300         if( textureInfo.loadState != CANCELLED )
301         {
302           // Perform atlasing and finalize the load.
303           PostLoad( textureInfo, pixelData );
304         }
305         else
306         {
307           Remove( textureInfo.textureId );
308         }
309       }
310     }
311
312     loadingContainer.pop_front();
313   }
314 }
315
316
317 bool TextureManager::PostLoad( TextureInfo& textureInfo, PixelData pixelData )
318 {
319   bool success = false;
320
321   // Was the load successful?
322   if( pixelData && ( pixelData.GetWidth() != 0 ) && ( pixelData.GetHeight() != 0 ) )
323   {
324     // Regardless of whether the atlasing succeeds or not, we have a valid image, so we mark it as successful.
325     success = true;
326
327     bool usingAtlas = false;
328
329     // No atlas support for now
330     textureInfo.useAtlas = NO_ATLAS;
331
332     if( ! usingAtlas )
333     {
334       DALI_LOG_INFO( gTextureManagerLogFilter, Debug::Concise, "  TextureManager::PostLoad() textureId:%d\n", textureInfo.textureId );
335
336       Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, pixelData.GetPixelFormat(), pixelData.GetWidth(), pixelData.GetHeight() );
337       texture.Upload( pixelData );
338       textureInfo.textureSet = TextureSet::New();
339       textureInfo.textureSet.SetTexture( 0u, texture );
340     }
341   }
342
343   if( ! success )
344   {
345     DALI_LOG_ERROR( "TextureManager::AsyncImageLoad(%s) failed\n", textureInfo.url.GetUrl().c_str() );
346     // @todo If the load was unsuccessful, upload the broken image.
347   }
348
349   // Update the load state.
350   // Note: This is regardless of success as we care about whether a
351   // load attempt is in progress or not.  If unsuccessful, a broken
352   // image is still loaded.
353   textureInfo.loadState = UPLOADED;
354
355   // We need to store the load succeeded state as if a future request to load this texture comes in,
356   // we need to re-broadcast the UploadComplete notification to that observer.
357   textureInfo.loadingSucceeded = success;
358
359   // If there is an observer: Notify the load is complete, whether successful or not:
360   const unsigned int observerCount = textureInfo.observerList.Count();
361   for( unsigned int i = 0; i < observerCount; ++i )
362   {
363     TextureUploadObserver* observer = textureInfo.observerList[i];
364     if( observer )
365     {
366       observer->UploadComplete( success, textureInfo.textureSet, textureInfo.useAtlas, textureInfo.atlasRect );
367       observer->DestructionSignal().Disconnect( this, &TextureManager::ObserverDestroyed );
368     }
369   }
370
371   textureInfo.observerList.Clear();
372
373   return success;
374 }
375
376 TextureManager::TextureId TextureManager::GenerateUniqueTextureId()
377 {
378   return mCurrentTextureId++;
379 }
380
381 int TextureManager::GetCacheIndexFromId( const TextureId textureId )
382 {
383   const unsigned int size = mTextureInfoContainer.size();
384
385   for( unsigned int i = 0; i < size; ++i )
386   {
387     if( mTextureInfoContainer[i].textureId == textureId )
388     {
389       return i;
390     }
391   }
392
393   DALI_LOG_WARNING( "Cannot locate TextureId: %d\n", textureId );
394   return INVALID_CACHE_INDEX;
395 }
396
397 TextureManager::TextureHash TextureManager::GenerateHash(
398   const std::string&             url,
399   const ImageDimensions          size,
400   const FittingMode::Type        fittingMode,
401   const Dali::SamplingMode::Type samplingMode,
402   const UseAtlas                 useAtlas )
403 {
404   std::string hashTarget( url );
405   const size_t urlLength = hashTarget.length();
406   const uint16_t width = size.GetWidth();
407   const uint16_t height = size.GetWidth();
408
409   // If either the width or height has been specified, include the resizing options in the hash
410   if( width != 0 || height != 0 )
411   {
412     // We are appending 5 bytes to the URL to form the hash input.
413     hashTarget.resize( urlLength + 5u );
414     char* hashTargetPtr = &( hashTarget[ urlLength ] );
415
416     // Pack the width and height (4 bytes total).
417     *hashTargetPtr++ = size.GetWidth() & 0xff;
418     *hashTargetPtr++ = ( size.GetWidth() >> 8u ) & 0xff;
419     *hashTargetPtr++ = size.GetHeight() & 0xff;
420     *hashTargetPtr++ = ( size.GetHeight() >> 8u ) & 0xff;
421
422     // Bit-pack the FittingMode, SamplingMode and atlasing.
423     // FittingMode=2bits, SamplingMode=3bits, useAtlas=1bit
424     *hashTargetPtr   = ( fittingMode << 4u ) | ( samplingMode << 1 ) | useAtlas;
425   }
426   else
427   {
428     // We are not including sizing information, but we still need an extra byte for atlasing.
429     hashTarget.resize( urlLength + 1u );
430     // Add the atlasing to the hash input.
431     hashTarget[ urlLength ] = useAtlas;
432   }
433
434   return Dali::CalculateHash( hashTarget );
435 }
436
437 int TextureManager::FindCachedTexture(
438   const TextureManager::TextureHash hash,
439   const std::string&                url,
440   const ImageDimensions             size,
441   const FittingMode::Type           fittingMode,
442   const Dali::SamplingMode::Type    samplingMode,
443   const bool                        useAtlas )
444 {
445   // Default to an invalid ID, in case we do not find a match.
446   int cacheIndex = INVALID_CACHE_INDEX;
447
448   // Iterate through our hashes to find a match.
449   const unsigned int count = mTextureInfoContainer.size();
450   for( unsigned int i = 0u; i < count; ++i )
451   {
452     if( mTextureInfoContainer[i].hash == hash )
453     {
454       // We have a match, now we check all the original parameters in case of a hash collision.
455       TextureInfo& textureInfo( mTextureInfoContainer[i] );
456
457       if( ( url == textureInfo.url.GetUrl() ) &&
458           ( useAtlas == textureInfo.useAtlas ) &&
459           ( size == textureInfo.desiredSize ) &&
460           ( ( size.GetWidth() == 0 && size.GetHeight() == 0 ) ||
461             ( fittingMode == textureInfo.fittingMode &&
462               samplingMode == textureInfo.samplingMode ) ) )
463       {
464         // The found Texture is a match.
465         cacheIndex = i;
466         break;
467       }
468     }
469   }
470
471   return cacheIndex;
472 }
473
474 void TextureManager::ObserverDestroyed( TextureUploadObserver* observer )
475 {
476   const unsigned int count = mTextureInfoContainer.size();
477   for( unsigned int i = 0; i < count; ++i )
478   {
479     TextureInfo& textureInfo( mTextureInfoContainer[i] );
480     for( TextureInfo::ObserverListType::Iterator j = textureInfo.observerList.Begin(); j != textureInfo.observerList.End(); ++j )
481     {
482       if( *j == observer )
483       {
484         textureInfo.observerList.Erase( j );
485         break;
486       }
487     }
488   }
489 }
490
491 TextureManager::~TextureManager()
492 {
493   mTextureInfoContainer.clear();
494   mAsyncLocalLoadingInfoContainer.clear();
495   mAsyncRemoteLoadingInfoContainer.clear();
496 }
497
498
499
500
501 } // namespace Internal
502
503 } // namespace Toolkit
504
505 } // namespace Dali