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