[dali_2.3.21] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-scene3d / internal / common / image-resource-loader.cpp
1 /*
2  * Copyright (c) 2024 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-scene3d/internal/common/image-resource-loader.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/adaptor-framework/image-loading.h>
23 #include <dali/devel-api/adaptor-framework/lifecycle-controller.h>
24 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
25 #include <dali/devel-api/common/hash.h>
26 #include <dali/devel-api/common/map-wrapper.h>
27 #include <dali/devel-api/threading/mutex.h>
28 #include <dali/integration-api/adaptor-framework/adaptor.h>
29 #include <dali/integration-api/debug.h>
30 #include <dali/integration-api/pixel-data-integ.h>
31 #include <dali/public-api/adaptor-framework/timer.h>
32 #include <dali/public-api/common/vector-wrapper.h>
33 #include <dali/public-api/object/base-object.h>
34 #include <dali/public-api/signals/connection-tracker.h>
35
36 #include <functional> ///< for std::function
37 #include <memory>     ///< for std::shared_ptr
38 #include <mutex>
39 #include <sstream>
40 #include <string>
41 #include <utility> ///< for std::pair
42
43 // INTERNAL INCLUDES
44
45 namespace
46 {
47 constexpr uint32_t MAXIMUM_COLLECTING_ITEM_COUNTS_PER_GC_CALL = 5u;
48 constexpr uint32_t GC_PERIOD_MILLISECONDS                     = 1000u;
49
50 #ifdef DEBUG_ENABLED
51 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_IMAGE_RESOURCE_LOADER");
52 #endif
53
54 bool IsDefaultPixelData(const Dali::PixelData& pixelData)
55 {
56   if(pixelData == Dali::Scene3D::Internal::ImageResourceLoader::GetEmptyPixelDataWhiteRGB() ||
57      pixelData == Dali::Scene3D::Internal::ImageResourceLoader::GetEmptyPixelDataWhiteRGBA() ||
58      pixelData == Dali::Scene3D::Internal::ImageResourceLoader::GetEmptyPixelDataZAxisRGB() ||
59      pixelData == Dali::Scene3D::Internal::ImageResourceLoader::GetEmptyPixelDataZAxisAndAlphaRGBA())
60   {
61     return true;
62   }
63   return false;
64 }
65
66 bool SupportPixelDataCache(const Dali::PixelData& pixelData)
67 {
68   // Check given pixelData support to release data after upload.
69   // This is cause we need to reduce CPU memory usage.
70   if(Dali::Integration::IsPixelDataReleaseAfterUpload(pixelData))
71   {
72     return true;
73   }
74
75   // Check given pixelData is default pixelData.
76   if(IsDefaultPixelData(pixelData))
77   {
78     return true;
79   }
80   return false;
81 }
82
83 struct ImageInformation
84 {
85   ImageInformation(const std::string           url,
86                    const Dali::ImageDimensions dimensions,
87                    Dali::FittingMode::Type     fittingMode,
88                    Dali::SamplingMode::Type    samplingMode,
89                    bool                        orientationCorrection)
90   : mUrl(url),
91     mDimensions(dimensions),
92     mFittingMode(fittingMode),
93     mSamplingMode(samplingMode),
94     mOrientationCorrection(orientationCorrection)
95   {
96   }
97
98   bool operator==(const ImageInformation& rhs) const
99   {
100     // Check url and orientation correction is enough.
101     return (mUrl == rhs.mUrl) && (mOrientationCorrection == rhs.mOrientationCorrection);
102   }
103
104   std::string              mUrl;
105   Dali::ImageDimensions    mDimensions;
106   Dali::FittingMode::Type  mFittingMode;
107   Dali::SamplingMode::Type mSamplingMode;
108   bool                     mOrientationCorrection;
109 };
110
111 // Hash functor list
112 std::size_t GenerateHash(const ImageInformation& info)
113 {
114   std::vector<std::uint8_t> hashTarget;
115   const uint16_t            width  = info.mDimensions.GetWidth();
116   const uint16_t            height = info.mDimensions.GetHeight();
117
118   // If either the width or height has been specified, include the resizing options in the hash
119   if(width != 0 || height != 0)
120   {
121     // We are appending 5 bytes to the URL to form the hash input.
122     hashTarget.resize(5u);
123     std::uint8_t* hashTargetPtr = &(hashTarget[0u]);
124
125     // Pack the width and height (4 bytes total).
126     *hashTargetPtr++ = info.mDimensions.GetWidth() & 0xff;
127     *hashTargetPtr++ = (info.mDimensions.GetWidth() >> 8u) & 0xff;
128     *hashTargetPtr++ = info.mDimensions.GetHeight() & 0xff;
129     *hashTargetPtr++ = (info.mDimensions.GetHeight() >> 8u) & 0xff;
130
131     // Bit-pack the FittingMode, SamplingMode and orientation correction.
132     // FittingMode=2bits, SamplingMode=3bits, orientationCorrection=1bit
133     *hashTargetPtr = (info.mFittingMode << 4u) | (info.mSamplingMode << 1) | (info.mOrientationCorrection ? 1 : 0);
134   }
135   else
136   {
137     // We are not including sizing information, but we still need an extra byte for orientationCorrection.
138     hashTarget.resize(1u);
139     hashTarget[0u] = info.mOrientationCorrection ? 't' : 'f';
140   }
141
142   return Dali::CalculateHash(info.mUrl) ^ Dali::CalculateHash(hashTarget);
143 }
144
145 std::size_t GenerateHash(const Dali::PixelData& pixelData, bool mipmapRequired)
146 {
147   return reinterpret_cast<std::size_t>(static_cast<void*>(pixelData.GetObjectPtr())) ^ (static_cast<std::size_t>(mipmapRequired) << (sizeof(std::size_t) * 4));
148 }
149
150 // Item Creation functor list
151
152 Dali::PixelData CreatePixelDataFromImageInfo(const ImageInformation& info, bool releasePixelData)
153 {
154   Dali::PixelData pixelData;
155
156   // Load the image synchronously (block the thread here).
157   Dali::Devel::PixelBuffer pixelBuffer = Dali::LoadImageFromFile(info.mUrl, info.mDimensions, info.mFittingMode, info.mSamplingMode, info.mOrientationCorrection);
158   if(pixelBuffer)
159   {
160     pixelData = Dali::Devel::PixelBuffer::Convert(pixelBuffer, releasePixelData);
161   }
162   return pixelData;
163 }
164
165 Dali::Texture CreateTextureFromPixelData(const Dali::PixelData& pixelData, bool mipmapRequired)
166 {
167   Dali::Texture texture;
168   if(pixelData)
169   {
170     texture = Dali::Texture::New(Dali::TextureType::TEXTURE_2D, pixelData.GetPixelFormat(), pixelData.GetWidth(), pixelData.GetHeight());
171     texture.Upload(pixelData, 0, 0, 0, 0, pixelData.GetWidth(), pixelData.GetHeight());
172     if(mipmapRequired)
173     {
174       texture.GenerateMipmaps();
175     }
176   }
177   return texture;
178 }
179
180 // Check function whether we can collect given data as garbage, or not.
181 bool PixelDataCacheCollectable(const ImageInformation& info, const Dali::PixelData& pixelData)
182 {
183   return pixelData.GetBaseObject().ReferenceCount() <= 1;
184 }
185
186 bool TextureCacheCollectable(const Dali::PixelData& pixelData, const Dali::Texture& texture)
187 {
188   return !IsDefaultPixelData(pixelData) &&                  ///< If key is not default pixelData
189          pixelData.GetBaseObject().ReferenceCount() <= 2 && ///< And it have reference count as 2 (1 is for the key of this container, and other is PixelData cache.)
190          texture.GetBaseObject().ReferenceCount() <= 1;     ///< And nobody use this texture, except this contianer.
191 }
192
193 // Forward declare, for signal connection.
194 void DestroyCacheImpl();
195
196 class CacheImpl : public Dali::ConnectionTracker
197 {
198 public:
199   /**
200    * @brief Constructor
201    */
202   CacheImpl()
203   : mPixelDataCache{},
204     mTextureCache{},
205     mTimer{},
206     mLatestCollectedPixelDataIter{mPixelDataCache.begin()},
207     mLatestCollectedTextureIter{mTextureCache.begin()},
208     mDataMutex{},
209     mPixelDataContainerUpdated{false},
210     mTextureContainerUpdated{false},
211     mDestroyed{false},
212     mFullCollectRequested{false}
213   {
214     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Create CacheImpl\n");
215
216     // We should create CacheImpl at main thread, To ensure delete this cache impl
217     Dali::LifecycleController::Get().TerminateSignal().Connect(DestroyCacheImpl);
218   }
219
220   /**
221    * @brief Destructor
222    */
223   ~CacheImpl()
224   {
225     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Destroy CacheImpl\n");
226     {
227       mDataMutex.lock();
228
229       mDestroyed                    = true;
230       mPixelDataContainerUpdated    = false;
231       mTextureContainerUpdated      = false;
232       mLatestCollectedPixelDataIter = decltype(mLatestCollectedPixelDataIter)(); // Invalidate iterator
233       mLatestCollectedTextureIter   = decltype(mLatestCollectedTextureIter)();   // Invalidate iterator
234
235       mPixelDataCache.clear();
236       mTextureCache.clear();
237
238       mDataMutex.unlock();
239     }
240
241     if(mTimer)
242     {
243       if(Dali::Adaptor::IsAvailable())
244       {
245         mTimer.Stop();
246       }
247     }
248   }
249
250 private: // Unified API for this class
251   // Let compare with hash first. And then, check detail keys after.
252   using PixelDataCacheContainer = std::map<std::size_t, std::vector<std::pair<ImageInformation, Dali::PixelData>>>;
253   using TextureCacheContainer   = std::map<std::size_t, std::vector<std::pair<Dali::PixelData, Dali::Texture>>>;
254
255   /**
256    * @brief Try to get cached item, or create new handle if there is no item.
257    *
258    * @tparam needMutex Whether we need to lock the mutex during this operation, or not.
259    * @param[in] cacheContainer The container of key / item pair.
260    * @param[in] hashValue The hash value of key.
261    * @param[in] key The key of cache item.
262    * @param[in] keyFlag The additional flags when we need to create new item.
263    * @param[out] containerUpdate True whether container changed or not.
264    * @return Item that has been cached. Or newly created.
265    */
266   template<bool needMutex, typename KeyType, typename ItemType, ItemType (*ItemCreationFunction)(const KeyType&, bool), typename ContainerType>
267   ItemType GetOrCreateCachedItem(ContainerType& cacheContainer, std::size_t hashValue, const KeyType& key, bool keyFlag, bool& containerUpdated)
268   {
269     if constexpr(needMutex)
270     {
271       mDataMutex.lock();
272     }
273     ItemType returnItem;
274
275     if(DALI_LIKELY(!mDestroyed))
276     {
277       bool found = false;
278
279       auto iter = cacheContainer.lower_bound(hashValue);
280       DALI_LOG_INFO(gLogFilter, Debug::Verbose, "HashValue : %zu\n", hashValue);
281       if((iter == cacheContainer.end()) || (hashValue != iter->first))
282       {
283         containerUpdated = true;
284
285         returnItem = ItemCreationFunction(key, keyFlag);
286         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Create new item\n");
287         cacheContainer.insert(iter, {hashValue, {{key, returnItem}}});
288       }
289       else
290       {
291         auto& cachePairList = iter->second;
292         for(auto jter = cachePairList.begin(), jterEnd = cachePairList.end(); jter != jterEnd; ++jter)
293         {
294           if(jter->first == key)
295           {
296             // We found that input pixelData already cached.
297             returnItem = jter->second;
298             DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Get cached item\n");
299             found = true;
300             break;
301           }
302         }
303
304         // If we fail to found same list, just append.
305         if(!found)
306         {
307           containerUpdated = true;
308
309           returnItem = ItemCreationFunction(key, keyFlag);
310           DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Create new item\n");
311           cachePairList.emplace_back(key, returnItem);
312         }
313       }
314     }
315
316     if constexpr(needMutex)
317     {
318       mDataMutex.unlock();
319     }
320
321     return returnItem;
322   }
323
324   /**
325    * @brief Try to collect garbages, which reference counts are 1.
326    *
327    * @tparam needMutex Whether we need to lock the mutex during this operation, or not.
328    * @param[in] cacheContainer The container of key / item pair.
329    * @param[in] fullCollect True if we need to collect whole container.
330    * @param[in, out] containerUpdated True if container information changed. lastIterator will be begin of container when we start collect garbages.
331    * @param[in, out] lastIterator The last iterator of container.
332    * @oaram[in, out] checkedCount The number of iteration checked total.
333    * @return True if we iterate whole container, so we don't need to check anymore. False otherwise
334    */
335   template<typename KeyType, typename ValueType, bool (*Collectable)(const KeyType&, const ValueType&), typename ContainerType, typename Iterator = typename ContainerType::iterator>
336   bool CollectGarbages(ContainerType& cacheContainer, bool fullCollect, bool& containerUpdated, Iterator& lastIterator, uint32_t& checkedCount, uint32_t& collectedCount)
337   {
338     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Collect Garbages : %zu (checkedCount : %d, fullCollect? %d)\n", cacheContainer.size(), checkedCount, fullCollect);
339     // Container changed. We should re-collect garbage from begin again.
340     if(fullCollect || containerUpdated)
341     {
342       lastIterator     = cacheContainer.begin();
343       containerUpdated = false;
344     }
345
346     for(; lastIterator != cacheContainer.end() && (fullCollect || ++checkedCount <= MAXIMUM_COLLECTING_ITEM_COUNTS_PER_GC_CALL);)
347     {
348       auto& cachePairList = lastIterator->second;
349
350       for(auto jter = cachePairList.begin(); jter != cachePairList.end();)
351       {
352         auto& item = jter->second;
353         DALI_LOG_INFO(gLogFilter, Debug::Verbose, "item : %p, ref count : %u\n", item.GetObjectPtr(), (item ? item.GetBaseObject().ReferenceCount() : 0u));
354         if(!item || Collectable(jter->first, item))
355         {
356           DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GC!!!\n");
357           // This item is garbage! just remove it.
358           ++collectedCount;
359           jter = cachePairList.erase(jter);
360         }
361         else
362         {
363           ++jter;
364         }
365       }
366
367       if(cachePairList.empty())
368       {
369         lastIterator = cacheContainer.erase(lastIterator);
370       }
371       else
372       {
373         ++lastIterator;
374       }
375     }
376
377     return (lastIterator != cacheContainer.end());
378   }
379
380 public: // Called by main thread.
381   /**
382    * @brief Try to get cached texture, or newly create if there is no texture that already cached.
383    *
384    * @param[in] pixelData The pixelData of image.
385    * @param[in] mipmapRequired True if result texture need to generate mipmap.
386    * @return Texture that has been cached. Or empty handle if we fail to found cached item.
387    */
388   Dali::Texture GetOrCreateCachedTexture(const Dali::PixelData& pixelData, bool mipmapRequired)
389   {
390     auto hashValue = GenerateHash(pixelData, mipmapRequired);
391     return GetOrCreateCachedItem<false, Dali::PixelData, Dali::Texture, CreateTextureFromPixelData>(mTextureCache, hashValue, pixelData, mipmapRequired, mTextureContainerUpdated);
392   }
393
394   /**
395    * @brief Request incremental gargabe collect.
396    *
397    * @param[in] fullCollect True if we will collect whole items, or incrementally.
398    */
399   void RequestGarbageCollect(bool fullCollect)
400   {
401     if(DALI_LIKELY(Dali::Adaptor::IsAvailable()))
402     {
403       if(!mTimer)
404       {
405         mTimer = Dali::Timer::New(GC_PERIOD_MILLISECONDS);
406         mTimer.TickSignal().Connect(this, &CacheImpl::OnTick);
407       }
408
409       mFullCollectRequested |= fullCollect;
410
411       if(!mTimer.IsRunning())
412       {
413         // Restart container interating.
414         mDataMutex.lock();
415         mPixelDataContainerUpdated = true;
416         mDataMutex.unlock();
417         mTextureContainerUpdated = true;
418         mTimer.Start();
419       }
420     }
421   }
422
423 public: // Can be called by worker thread
424   /**
425    * @brief Try to get cached pixel data, or newly create if there is no pixel data that already cached.
426    *
427    * @param[in] info The informations of image to load.
428    * @param[in] releasePixelData Whether we need to release pixel data after upload, or not.
429    * @return Texture that has been cached. Or empty handle if we fail to found cached item.
430    */
431   Dali::PixelData GetOrCreateCachedPixelData(const ImageInformation& info, bool releasePixelData)
432   {
433     auto hashValue = GenerateHash(info);
434     return GetOrCreateCachedItem<true, ImageInformation, Dali::PixelData, CreatePixelDataFromImageInfo>(mPixelDataCache, hashValue, info, releasePixelData, mPixelDataContainerUpdated);
435   }
436
437 private: // Called by main thread
438   bool OnTick()
439   {
440     // Clear full GC flag
441     const bool fullCollect = mFullCollectRequested;
442     mFullCollectRequested  = false;
443
444     return IncrementalGarbageCollect(fullCollect);
445   }
446
447   /**
448    * @brief Remove unused cache item incrementally.
449    *
450    * @param[in] fullCollect True if we will collect whole items, or incrementally.
451    * @return True if there still exist what we need to check clean. False when whole cached items are using now.
452    */
453   bool IncrementalGarbageCollect(bool fullCollect)
454   {
455     bool continueTimer = false;
456     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GC start\n");
457
458     // Try to collect Texture GC first, due to the reference count of pixelData who become key of textures.
459     // After all texture GC finished, then check PixelData cache.
460     uint32_t checkedCount   = 0u;
461     uint32_t collectedCount = 0u;
462
463     // We should lock mutex during GC pixelData.
464     mDataMutex.lock();
465
466     // GC Texture
467     continueTimer |= CollectGarbages<Dali::PixelData, Dali::Texture, TextureCacheCollectable>(mTextureCache, fullCollect, mTextureContainerUpdated, mLatestCollectedTextureIter, checkedCount, collectedCount);
468
469     // GC PixelData last. If there are some collected Texture before, we should full-collect.
470     // (Since most of PixelData use 'ReleaseAfterUpload' flags).
471     continueTimer |= CollectGarbages<ImageInformation, Dali::PixelData, PixelDataCacheCollectable>(mPixelDataCache, fullCollect || (collectedCount > 0u), mPixelDataContainerUpdated, mLatestCollectedPixelDataIter, checkedCount, collectedCount);
472
473     mDataMutex.unlock();
474
475     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GC finished. checkedCount : %u, continueTimer : %d\n", checkedCount, continueTimer);
476
477     return continueTimer;
478   }
479
480 private:
481   PixelDataCacheContainer mPixelDataCache;
482   TextureCacheContainer   mTextureCache;
483
484   Dali::Timer mTimer;
485
486   // Be used when we garbage collection.
487   PixelDataCacheContainer::iterator mLatestCollectedPixelDataIter;
488   TextureCacheContainer::iterator   mLatestCollectedTextureIter;
489
490   std::mutex mDataMutex;
491
492   bool mPixelDataContainerUpdated;
493   bool mTextureContainerUpdated;
494
495   bool mDestroyed : 1;
496   bool mFullCollectRequested : 1;
497 };
498
499 static std::shared_ptr<CacheImpl> gCacheImpl{nullptr};
500 static Dali::Texture              gEmptyTextureWhiteRGB{};
501 static Dali::Texture              gEmptyCubeTextureWhiteRGB{};
502
503 std::shared_ptr<CacheImpl> GetCacheImpl()
504 {
505   if(DALI_UNLIKELY(!gCacheImpl))
506   {
507     gCacheImpl = std::make_shared<CacheImpl>();
508   }
509   return gCacheImpl;
510 }
511
512 void DestroyCacheImpl()
513 {
514   gCacheImpl.reset();
515
516   // Remove texture object when application stopped.
517   gEmptyTextureWhiteRGB.Reset();
518   gEmptyCubeTextureWhiteRGB.Reset();
519 }
520
521 } // namespace
522
523 namespace Dali::Scene3D::Internal
524 {
525 namespace ImageResourceLoader
526 {
527 // Called by main thread..
528 Dali::Texture GetEmptyTextureWhiteRGB()
529 {
530   if(!gEmptyTextureWhiteRGB)
531   {
532     Dali::PixelData emptyPixelData = GetEmptyPixelDataWhiteRGB();
533     gEmptyTextureWhiteRGB          = Texture::New(TextureType::TEXTURE_2D, emptyPixelData.GetPixelFormat(), emptyPixelData.GetWidth(), emptyPixelData.GetHeight());
534     gEmptyTextureWhiteRGB.Upload(emptyPixelData, 0, 0, 0, 0, emptyPixelData.GetWidth(), emptyPixelData.GetHeight());
535   }
536   return gEmptyTextureWhiteRGB;
537 }
538
539 Dali::Texture GetEmptyCubeTextureWhiteRGB()
540 {
541   if(!gEmptyCubeTextureWhiteRGB)
542   {
543     Dali::PixelData emptyPixelData = GetEmptyPixelDataWhiteRGB();
544     gEmptyCubeTextureWhiteRGB      = Texture::New(TextureType::TEXTURE_CUBE, emptyPixelData.GetPixelFormat(), emptyPixelData.GetWidth(), emptyPixelData.GetHeight());
545     for(size_t iSide = 0u; iSide < 6; ++iSide)
546     {
547       gEmptyCubeTextureWhiteRGB.Upload(emptyPixelData, CubeMapLayer::POSITIVE_X + iSide, 0u, 0u, 0u, emptyPixelData.GetWidth(), emptyPixelData.GetHeight());
548     }
549   }
550   return gEmptyCubeTextureWhiteRGB;
551 }
552
553 Dali::Texture GetCachedTexture(Dali::PixelData pixelData, bool mipmapRequired)
554 {
555   if(Dali::Adaptor::IsAvailable() && SupportPixelDataCache(pixelData))
556   {
557     return GetCacheImpl()->GetOrCreateCachedTexture(pixelData, mipmapRequired);
558   }
559   else
560   {
561     return CreateTextureFromPixelData(pixelData, mipmapRequired);
562   }
563 }
564
565 void RequestGarbageCollect(bool fullCollect)
566 {
567   if(DALI_LIKELY(Dali::Adaptor::IsAvailable()))
568   {
569     GetCacheImpl()->RequestGarbageCollect(fullCollect);
570   }
571 }
572
573 void EnsureResourceLoaderCreated()
574 {
575   if(DALI_LIKELY(Dali::Adaptor::IsAvailable()))
576   {
577     GetCacheImpl();
578   }
579 }
580
581 // Can be called by worker thread.
582 Dali::PixelData GetEmptyPixelDataWhiteRGB()
583 {
584   static Dali::PixelData emptyPixelData = PixelData::New(new uint8_t[3]{0xff, 0xff, 0xff}, 3, 1, 1, Pixel::RGB888, PixelData::DELETE_ARRAY);
585   return emptyPixelData;
586 }
587
588 Dali::PixelData GetEmptyPixelDataWhiteRGBA()
589 {
590   static Dali::PixelData emptyPixelData = PixelData::New(new uint8_t[4]{0xff, 0xff, 0xff, 0xff}, 4, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY);
591   return emptyPixelData;
592 }
593
594 Dali::PixelData GetEmptyPixelDataZAxisRGB()
595 {
596   static Dali::PixelData emptyPixelData = PixelData::New(new uint8_t[3]{0x7f, 0x7f, 0xff}, 3, 1, 1, Pixel::RGB888, PixelData::DELETE_ARRAY);
597   return emptyPixelData;
598 }
599
600 Dali::PixelData GetEmptyPixelDataZAxisAndAlphaRGBA()
601 {
602   static Dali::PixelData emptyPixelData = PixelData::New(new uint8_t[4]{0x7f, 0x7f, 0xff, 0xff}, 4, 1, 1, Pixel::RGBA8888, PixelData::DELETE_ARRAY);
603   return emptyPixelData;
604 }
605
606 Dali::PixelData GetCachedPixelData(const std::string& url)
607 {
608   return GetCachedPixelData(url, ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, true);
609 }
610
611 Dali::PixelData GetCachedPixelData(const std::string& url,
612                                    ImageDimensions    dimensions,
613                                    FittingMode::Type  fittingMode,
614                                    SamplingMode::Type samplingMode,
615                                    bool               orientationCorrection)
616 {
617   ImageInformation info(url, dimensions, fittingMode, samplingMode, orientationCorrection);
618   if(gCacheImpl == nullptr)
619   {
620     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "CacheImpl not prepared! load PixelData without cache.\n");
621     return CreatePixelDataFromImageInfo(info, false);
622   }
623   else
624   {
625     return GetCacheImpl()->GetOrCreateCachedPixelData(info, true);
626   }
627 }
628 } // namespace ImageResourceLoader
629 } // namespace Dali::Scene3D::Internal