[dali_2.3.20] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / visuals / npatch-loader.cpp
1 /*
2  * Copyright (c) 2023 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/visuals/npatch-loader.h>
20
21 // INTERNAL HEADERS
22 #include <dali-toolkit/internal/visuals/rendering-addon.h>
23
24 // EXTERNAL HEADERS
25 #include <dali/devel-api/common/hash.h>
26 #include <dali/integration-api/adaptor-framework/adaptor.h>
27 #include <dali/integration-api/debug.h>
28 #include <dali/integration-api/trace.h>
29
30 namespace Dali
31 {
32 namespace Toolkit
33 {
34 namespace Internal
35 {
36 namespace
37 {
38 constexpr auto INVALID_CACHE_INDEX = int32_t{-1}; ///< Invalid Cache index
39 constexpr auto UNINITIALIZED_ID    = int32_t{0};  ///< uninitialised id, use to initialize ids
40
41 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_IMAGE_PERFORMANCE_MARKER, false);
42 } // Anonymous namespace
43
44 NPatchLoader::NPatchLoader()
45 : mCurrentNPatchDataId(0),
46   mRemoveProcessorRegistered(false)
47 {
48 }
49
50 NPatchLoader::~NPatchLoader()
51 {
52   if(mRemoveProcessorRegistered && Adaptor::IsAvailable())
53   {
54     Adaptor::Get().UnregisterProcessor(*this, true);
55     mRemoveProcessorRegistered = false;
56   }
57 }
58
59 NPatchData::NPatchDataId NPatchLoader::GenerateUniqueNPatchDataId()
60 {
61   // Skip invalid id generation.
62   if(DALI_UNLIKELY(mCurrentNPatchDataId == NPatchData::INVALID_NPATCH_DATA_ID))
63   {
64     mCurrentNPatchDataId = 0;
65   }
66   return mCurrentNPatchDataId++;
67 }
68
69 NPatchData::NPatchDataId NPatchLoader::Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading)
70 {
71   NPatchDataPtr data = GetNPatchData(url, border, preMultiplyOnLoad);
72
73   DALI_ASSERT_ALWAYS(data.Get() && "NPatchData creation failed!");
74
75   if(data->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
76   {
77     if(!synchronousLoading)
78     {
79       // NotifyObserver already done, so
80       // data will not iterate observer list.
81       // We need to call LoadComplete directly.
82       data->NotifyObserver(textureObserver, true);
83     }
84   }
85   else // if NOT_STARTED or LOADING or LOAD_FAILED, try to reload.
86   {
87     if(!synchronousLoading)
88     {
89       data->AddObserver(textureObserver);
90       // If still LOADING and async, don't need to request reload. Fast return.
91       if(data->GetLoadingState() == NPatchData::LoadingState::LOADING)
92       {
93         return data->GetId();
94       }
95     }
96
97     data->SetLoadingState(NPatchData::LoadingState::LOADING);
98
99     auto preMultiplyOnLoading = preMultiplyOnLoad ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD
100                                                   : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
101
102     Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(url, Dali::ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, synchronousLoading, data.Get(), true, preMultiplyOnLoading);
103
104     if(pixelBuffer)
105     {
106       preMultiplyOnLoad = (preMultiplyOnLoading == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD) ? true : false;
107       data->SetLoadedNPatchData(pixelBuffer, preMultiplyOnLoad);
108     }
109     else if(synchronousLoading)
110     {
111       data->SetLoadingState(NPatchData::LoadingState::LOAD_FAILED);
112     }
113   }
114   return data->GetId();
115 }
116
117 int32_t NPatchLoader::GetCacheIndexFromId(const NPatchData::NPatchDataId id)
118 {
119   const unsigned int size = mCache.size();
120
121   for(unsigned int i = 0; i < size; ++i)
122   {
123     if(mCache[i].mData->GetId() == id)
124     {
125       return i;
126     }
127   }
128
129   return INVALID_CACHE_INDEX;
130 }
131
132 bool NPatchLoader::GetNPatchData(const NPatchData::NPatchDataId id, NPatchDataPtr& data)
133 {
134   int32_t cacheIndex = GetCacheIndexFromId(id);
135   if(cacheIndex != INVALID_CACHE_INDEX)
136   {
137     data = mCache[cacheIndex].mData;
138     return true;
139   }
140   data = nullptr;
141   return false;
142 }
143
144 void NPatchLoader::RequestRemove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
145 {
146   // Remove observer first
147   if(textureObserver)
148   {
149     int32_t cacheIndex = GetCacheIndexFromId(id);
150     if(cacheIndex != INVALID_CACHE_INDEX)
151     {
152       NPatchInfo& info(mCache[cacheIndex]);
153
154       info.mData->RemoveObserver(textureObserver);
155     }
156   }
157
158   mRemoveQueue.push_back({id, nullptr});
159
160   if(!mRemoveProcessorRegistered && Adaptor::IsAvailable())
161   {
162     mRemoveProcessorRegistered = true;
163     Adaptor::Get().RegisterProcessor(*this, true);
164   }
165 }
166
167 void NPatchLoader::Remove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
168 {
169   int32_t cacheIndex = GetCacheIndexFromId(id);
170   if(cacheIndex == INVALID_CACHE_INDEX)
171   {
172     return;
173   }
174
175   NPatchInfo& info(mCache[cacheIndex]);
176
177   info.mData->RemoveObserver(textureObserver);
178
179   if(--info.mReferenceCount <= 0)
180   {
181     mCache.erase(mCache.begin() + cacheIndex);
182   }
183 }
184
185 void NPatchLoader::Process(bool postProcessor)
186 {
187   DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_NPATCH_LOADER_PROCESS_REMOVE_QUEUE", [&](std::ostringstream& oss) {
188     oss << "[" << mRemoveQueue.size() << "]";
189   });
190
191   for(auto& iter : mRemoveQueue)
192   {
193     Remove(iter.first, iter.second);
194   }
195
196   mRemoveQueue.clear();
197
198   if(Adaptor::IsAvailable())
199   {
200     Adaptor::Get().UnregisterProcessor(*this, true);
201     mRemoveProcessorRegistered = false;
202   }
203
204   DALI_TRACE_END(gTraceFilter, "DALI_NPATCH_LOADER_PROCESS_REMOVE_QUEUE");
205 }
206
207 NPatchDataPtr NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad)
208 {
209   std::size_t                              hash  = CalculateHash(url.GetUrl());
210   std::vector<NPatchInfo>::size_type       index = UNINITIALIZED_ID;
211   const std::vector<NPatchInfo>::size_type count = mCache.size();
212
213   NPatchInfo* infoPtr = nullptr;
214
215   for(; index < count; ++index)
216   {
217     if(mCache[index].mData->GetHash() == hash)
218     {
219       // hash match, check url as well in case of hash collision
220       if(mCache[index].mData->GetUrl().GetUrl() == url.GetUrl())
221       {
222         // Use cached data. Need to fast-out return.
223         if(mCache[index].mData->GetBorder() == border)
224         {
225           mCache[index].mReferenceCount++;
226           return mCache[index].mData;
227         }
228         else
229         {
230           if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
231           {
232             // If we only found LOAD_FAILED case, replace current data. We can reuse texture
233             if(infoPtr == nullptr || infoPtr->mData->GetLoadingState() != NPatchData::LoadingState::LOAD_COMPLETE)
234             {
235               infoPtr = &mCache[index];
236             }
237           }
238           // Still loading pixel buffer. We cannot reuse cached texture yet. Skip checking
239           else if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOADING)
240           {
241             continue;
242           }
243           // if LOAD_FAILED, reuse this cached NPatchData, and try to load again.
244           else
245           {
246             if(infoPtr == nullptr)
247             {
248               infoPtr = &mCache[index];
249             }
250           }
251         }
252       }
253     }
254   }
255
256   // If this is new image loading, make new cache data
257   if(infoPtr == nullptr)
258   {
259     NPatchInfo info(new NPatchData());
260     info.mData->SetId(GenerateUniqueNPatchDataId());
261     info.mData->SetHash(hash);
262     info.mData->SetUrl(url);
263     info.mData->SetBorder(border);
264     info.mData->SetPreMultiplyOnLoad(preMultiplyOnLoad);
265
266     mCache.emplace_back(std::move(info));
267     infoPtr = &mCache.back();
268   }
269   // Else if LOAD_COMPLETE, Same url but border is different - use the existing texture
270   else if(infoPtr->mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
271   {
272     NPatchInfo info(new NPatchData());
273
274     info.mData->SetId(GenerateUniqueNPatchDataId());
275     info.mData->SetHash(hash);
276     info.mData->SetUrl(url);
277     info.mData->SetCroppedWidth(infoPtr->mData->GetCroppedWidth());
278     info.mData->SetCroppedHeight(infoPtr->mData->GetCroppedHeight());
279
280     info.mData->SetTextures(infoPtr->mData->GetTextures());
281
282     NPatchUtility::StretchRanges stretchRangesX;
283     stretchRangesX.PushBack(Uint16Pair(border.left, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.right)) ? info.mData->GetCroppedHeight() - border.right : 0)));
284
285     NPatchUtility::StretchRanges stretchRangesY;
286     stretchRangesY.PushBack(Uint16Pair(border.top, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.bottom)) ? info.mData->GetCroppedHeight() - border.bottom : 0)));
287
288     info.mData->SetStretchPixelsX(stretchRangesX);
289     info.mData->SetStretchPixelsY(stretchRangesY);
290     info.mData->SetBorder(border);
291
292     info.mData->SetPreMultiplyOnLoad(infoPtr->mData->IsPreMultiplied());
293
294     info.mData->SetLoadingState(NPatchData::LoadingState::LOAD_COMPLETE);
295
296     mCache.emplace_back(std::move(info));
297     infoPtr = &mCache.back();
298   }
299   // Else, LOAD_FAILED. just increase reference so we can reuse it.
300   else
301   {
302     infoPtr->mReferenceCount++;
303   }
304
305   DALI_ASSERT_ALWAYS(infoPtr && "NPatchInfo creation failed!");
306
307   return infoPtr->mData;
308 }
309
310 } // namespace Internal
311
312 } // namespace Toolkit
313
314 } // namespace Dali