f13a9c209928041ac26db9591e19251612b7d586
[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 #ifdef TRACE_ENABLED
188   if(gTraceFilter && gTraceFilter->IsTraceEnabled())
189   {
190     if(mRemoveQueue.size() > 0u)
191     {
192       std::ostringstream oss;
193       oss << "[" << mRemoveQueue.size() << "]";
194       DALI_TRACE_BEGIN_WITH_MESSAGE(gTraceFilter, "DALI_NPATCH_LOADER_PROCESS_REMOVE_QUEUE", oss.str().c_str());
195     }
196   }
197 #endif
198
199   for(auto& iter : mRemoveQueue)
200   {
201     Remove(iter.first, iter.second);
202   }
203
204 #ifdef TRACE_ENABLED
205   if(gTraceFilter && gTraceFilter->IsTraceEnabled())
206   {
207     if(mRemoveQueue.size() > 0u)
208     {
209       std::ostringstream oss;
210       oss << "[" << mRemoveQueue.size() << "]";
211       DALI_TRACE_END_WITH_MESSAGE(gTraceFilter, "DALI_NPATCH_LOADER_PROCESS_REMOVE_QUEUE", oss.str().c_str());
212     }
213   }
214 #endif
215
216   mRemoveQueue.clear();
217
218   if(Adaptor::IsAvailable())
219   {
220     Adaptor::Get().UnregisterProcessor(*this, true);
221     mRemoveProcessorRegistered = false;
222   }
223 }
224
225 NPatchDataPtr NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad)
226 {
227   std::size_t                              hash  = CalculateHash(url.GetUrl());
228   std::vector<NPatchInfo>::size_type       index = UNINITIALIZED_ID;
229   const std::vector<NPatchInfo>::size_type count = mCache.size();
230
231   NPatchInfo* infoPtr = nullptr;
232
233   for(; index < count; ++index)
234   {
235     if(mCache[index].mData->GetHash() == hash)
236     {
237       // hash match, check url as well in case of hash collision
238       if(mCache[index].mData->GetUrl().GetUrl() == url.GetUrl())
239       {
240         // Use cached data. Need to fast-out return.
241         if(mCache[index].mData->GetBorder() == border)
242         {
243           mCache[index].mReferenceCount++;
244           return mCache[index].mData;
245         }
246         else
247         {
248           if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
249           {
250             // If we only found LOAD_FAILED case, replace current data. We can reuse texture
251             if(infoPtr == nullptr || infoPtr->mData->GetLoadingState() != NPatchData::LoadingState::LOAD_COMPLETE)
252             {
253               infoPtr = &mCache[index];
254             }
255           }
256           // Still loading pixel buffer. We cannot reuse cached texture yet. Skip checking
257           else if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOADING)
258           {
259             continue;
260           }
261           // if LOAD_FAILED, reuse this cached NPatchData, and try to load again.
262           else
263           {
264             if(infoPtr == nullptr)
265             {
266               infoPtr = &mCache[index];
267             }
268           }
269         }
270       }
271     }
272   }
273
274   // If this is new image loading, make new cache data
275   if(infoPtr == nullptr)
276   {
277     NPatchInfo info(new NPatchData());
278     info.mData->SetId(GenerateUniqueNPatchDataId());
279     info.mData->SetHash(hash);
280     info.mData->SetUrl(url);
281     info.mData->SetBorder(border);
282     info.mData->SetPreMultiplyOnLoad(preMultiplyOnLoad);
283
284     mCache.emplace_back(std::move(info));
285     infoPtr = &mCache.back();
286   }
287   // Else if LOAD_COMPLETE, Same url but border is different - use the existing texture
288   else if(infoPtr->mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
289   {
290     NPatchInfo info(new NPatchData());
291
292     info.mData->SetId(GenerateUniqueNPatchDataId());
293     info.mData->SetHash(hash);
294     info.mData->SetUrl(url);
295     info.mData->SetCroppedWidth(infoPtr->mData->GetCroppedWidth());
296     info.mData->SetCroppedHeight(infoPtr->mData->GetCroppedHeight());
297
298     info.mData->SetTextures(infoPtr->mData->GetTextures());
299
300     NPatchUtility::StretchRanges stretchRangesX;
301     stretchRangesX.PushBack(Uint16Pair(border.left, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.right)) ? info.mData->GetCroppedHeight() - border.right : 0)));
302
303     NPatchUtility::StretchRanges stretchRangesY;
304     stretchRangesY.PushBack(Uint16Pair(border.top, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.bottom)) ? info.mData->GetCroppedHeight() - border.bottom : 0)));
305
306     info.mData->SetStretchPixelsX(stretchRangesX);
307     info.mData->SetStretchPixelsY(stretchRangesY);
308     info.mData->SetBorder(border);
309
310     info.mData->SetPreMultiplyOnLoad(infoPtr->mData->IsPreMultiplied());
311
312     info.mData->SetLoadingState(NPatchData::LoadingState::LOAD_COMPLETE);
313
314     mCache.emplace_back(std::move(info));
315     infoPtr = &mCache.back();
316   }
317   // Else, LOAD_FAILED. just increase reference so we can reuse it.
318   else
319   {
320     infoPtr->mReferenceCount++;
321   }
322
323   DALI_ASSERT_ALWAYS(infoPtr && "NPatchInfo creation failed!");
324
325   return infoPtr->mData;
326 }
327
328 } // namespace Internal
329
330 } // namespace Toolkit
331
332 } // namespace Dali