Merge "Guard NPatchData removal case during signal emit" into 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
29 namespace Dali
30 {
31 namespace Toolkit
32 {
33 namespace Internal
34 {
35 namespace
36 {
37 constexpr auto INVALID_CACHE_INDEX = int32_t{-1}; ///< Invalid Cache index
38 constexpr auto UNINITIALIZED_ID    = int32_t{0};  ///< uninitialised id, use to initialize ids
39
40 } // Anonymous namespace
41
42 NPatchLoader::NPatchLoader()
43 : mCurrentNPatchDataId(0),
44   mRemoveProcessorRegistered(false)
45 {
46 }
47
48 NPatchLoader::~NPatchLoader()
49 {
50   if(mRemoveProcessorRegistered && Adaptor::IsAvailable())
51   {
52     Adaptor::Get().UnregisterProcessor(*this, true);
53     mRemoveProcessorRegistered = false;
54   }
55 }
56
57 NPatchData::NPatchDataId NPatchLoader::GenerateUniqueNPatchDataId()
58 {
59   // Skip invalid id generation.
60   if(DALI_UNLIKELY(mCurrentNPatchDataId == NPatchData::INVALID_NPATCH_DATA_ID))
61   {
62     mCurrentNPatchDataId = 0;
63   }
64   return mCurrentNPatchDataId++;
65 }
66
67 NPatchData::NPatchDataId NPatchLoader::Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading)
68 {
69   std::shared_ptr<NPatchData> data = GetNPatchData(url, border, preMultiplyOnLoad);
70
71   DALI_ASSERT_ALWAYS(data.get() && "NPatchData creation failed!");
72
73   if(data->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
74   {
75     if(!synchronousLoading)
76     {
77       // NotifyObserver already done, so
78       // data will not iterate observer list.
79       // We need to call LoadComplete directly.
80       data->NotifyObserver(textureObserver, true);
81     }
82   }
83   else // if NOT_STARTED or LOADING or LOAD_FAILED, try to reload.
84   {
85     if(!synchronousLoading)
86     {
87       data->AddObserver(textureObserver);
88       // If still LOADING and async, don't need to request reload. Fast return.
89       if(data->GetLoadingState() == NPatchData::LoadingState::LOADING)
90       {
91         return data->GetId();
92       }
93     }
94
95     data->SetLoadingState(NPatchData::LoadingState::LOADING);
96
97     auto preMultiplyOnLoading = preMultiplyOnLoad ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD
98                                                   : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
99
100     Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(url, Dali::ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, synchronousLoading, data.get(), true, preMultiplyOnLoading);
101
102     if(pixelBuffer)
103     {
104       preMultiplyOnLoad = (preMultiplyOnLoading == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD) ? true : false;
105       data->SetLoadedNPatchData(pixelBuffer, preMultiplyOnLoad);
106     }
107     else if(synchronousLoading)
108     {
109       data->SetLoadingState(NPatchData::LoadingState::LOAD_FAILED);
110     }
111   }
112   return data->GetId();
113 }
114
115 int32_t NPatchLoader::GetCacheIndexFromId(const NPatchData::NPatchDataId id)
116 {
117   const unsigned int size = mCache.size();
118
119   for(unsigned int i = 0; i < size; ++i)
120   {
121     if(mCache[i].mData->GetId() == id)
122     {
123       return i;
124     }
125   }
126
127   return INVALID_CACHE_INDEX;
128 }
129
130 bool NPatchLoader::GetNPatchData(const NPatchData::NPatchDataId id, const NPatchData*& data)
131 {
132   int32_t cacheIndex = GetCacheIndexFromId(id);
133   if(cacheIndex != INVALID_CACHE_INDEX)
134   {
135     data = mCache[cacheIndex].mData.get();
136     return true;
137   }
138   data = nullptr;
139   return false;
140 }
141
142 void NPatchLoader::RequestRemove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
143 {
144   mRemoveQueue.push_back({id, textureObserver});
145
146   if(!mRemoveProcessorRegistered && Adaptor::IsAvailable())
147   {
148     mRemoveProcessorRegistered = true;
149     Adaptor::Get().RegisterProcessor(*this, true);
150   }
151 }
152
153 void NPatchLoader::Remove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
154 {
155   int32_t cacheIndex = GetCacheIndexFromId(id);
156   if(cacheIndex == INVALID_CACHE_INDEX)
157   {
158     return;
159   }
160
161   NPatchInfo& info(mCache[cacheIndex]);
162
163   info.mData->RemoveObserver(textureObserver);
164
165   if(--info.mReferenceCount <= 0)
166   {
167     mCache.erase(mCache.begin() + cacheIndex);
168   }
169 }
170
171 void NPatchLoader::Process(bool postProcessor)
172 {
173   for(auto& iter : mRemoveQueue)
174   {
175     Remove(iter.first, iter.second);
176   }
177   mRemoveQueue.clear();
178
179   if(Adaptor::IsAvailable())
180   {
181     Adaptor::Get().UnregisterProcessor(*this, true);
182     mRemoveProcessorRegistered = false;
183   }
184 }
185
186 std::shared_ptr<NPatchData> NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad)
187 {
188   std::size_t                              hash  = CalculateHash(url.GetUrl());
189   std::vector<NPatchInfo>::size_type       index = UNINITIALIZED_ID;
190   const std::vector<NPatchInfo>::size_type count = mCache.size();
191
192   NPatchInfo* infoPtr = nullptr;
193
194   for(; index < count; ++index)
195   {
196     if(mCache[index].mData->GetHash() == hash)
197     {
198       // hash match, check url as well in case of hash collision
199       if(mCache[index].mData->GetUrl().GetUrl() == url.GetUrl())
200       {
201         // Use cached data. Need to fast-out return.
202         if(mCache[index].mData->GetBorder() == border)
203         {
204           mCache[index].mReferenceCount++;
205           return mCache[index].mData;
206         }
207         else
208         {
209           if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
210           {
211             // If we only found LOAD_FAILED case, replace current data. We can reuse texture
212             if(infoPtr == nullptr || infoPtr->mData->GetLoadingState() != NPatchData::LoadingState::LOAD_COMPLETE)
213             {
214               infoPtr = &mCache[index];
215             }
216           }
217           // Still loading pixel buffer. We cannot reuse cached texture yet. Skip checking
218           else if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOADING)
219           {
220             continue;
221           }
222           // if LOAD_FAILED, reuse this cached NPatchData, and try to load again.
223           else
224           {
225             if(infoPtr == nullptr)
226             {
227               infoPtr = &mCache[index];
228             }
229           }
230         }
231       }
232     }
233   }
234
235   // If this is new image loading, make new cache data
236   if(infoPtr == nullptr)
237   {
238     NPatchInfo info(std::make_shared<NPatchData>());
239     info.mData->SetId(GenerateUniqueNPatchDataId());
240     info.mData->SetHash(hash);
241     info.mData->SetUrl(url);
242     info.mData->SetBorder(border);
243     info.mData->SetPreMultiplyOnLoad(preMultiplyOnLoad);
244
245     mCache.emplace_back(std::move(info));
246     infoPtr = &mCache.back();
247   }
248   // Else if LOAD_COMPLETE, Same url but border is different - use the existing texture
249   else if(infoPtr->mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
250   {
251     NPatchInfo info(std::make_shared<NPatchData>());
252
253     info.mData->SetId(GenerateUniqueNPatchDataId());
254     info.mData->SetHash(hash);
255     info.mData->SetUrl(url);
256     info.mData->SetCroppedWidth(infoPtr->mData->GetCroppedWidth());
257     info.mData->SetCroppedHeight(infoPtr->mData->GetCroppedHeight());
258
259     info.mData->SetTextures(infoPtr->mData->GetTextures());
260
261     NPatchUtility::StretchRanges stretchRangesX;
262     stretchRangesX.PushBack(Uint16Pair(border.left, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.right)) ? info.mData->GetCroppedHeight() - border.right : 0)));
263
264     NPatchUtility::StretchRanges stretchRangesY;
265     stretchRangesY.PushBack(Uint16Pair(border.top, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.bottom)) ? info.mData->GetCroppedHeight() - border.bottom : 0)));
266
267     info.mData->SetStretchPixelsX(stretchRangesX);
268     info.mData->SetStretchPixelsY(stretchRangesY);
269     info.mData->SetBorder(border);
270
271     info.mData->SetPreMultiplyOnLoad(infoPtr->mData->IsPreMultiplied());
272
273     info.mData->SetLoadingState(NPatchData::LoadingState::LOAD_COMPLETE);
274
275     mCache.emplace_back(std::move(info));
276     infoPtr = &mCache.back();
277   }
278   // Else, LOAD_FAILED. just increase reference so we can reuse it.
279   else
280   {
281     infoPtr->mReferenceCount++;
282   }
283
284   DALI_ASSERT_ALWAYS(infoPtr && "NPatchInfo creation failed!");
285
286   return infoPtr->mData;
287 }
288
289 } // namespace Internal
290
291 } // namespace Toolkit
292
293 } // namespace Dali