2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/visuals/npatch-loader.h>
22 #include <dali-toolkit/internal/visuals/rendering-addon.h>
25 #include <dali/devel-api/common/hash.h>
26 #include <dali/integration-api/adaptor-framework/adaptor.h>
27 #include <dali/integration-api/debug.h>
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
40 } // Anonymous namespace
42 NPatchLoader::NPatchLoader()
43 : mCurrentNPatchDataId(0),
44 mRemoveProcessorRegistered(false)
48 NPatchLoader::~NPatchLoader()
50 if(mRemoveProcessorRegistered && Adaptor::IsAvailable())
52 Adaptor::Get().UnregisterProcessor(*this, true);
53 mRemoveProcessorRegistered = false;
57 NPatchData::NPatchDataId NPatchLoader::GenerateUniqueNPatchDataId()
59 // Skip invalid id generation.
60 if(DALI_UNLIKELY(mCurrentNPatchDataId == NPatchData::INVALID_NPATCH_DATA_ID))
62 mCurrentNPatchDataId = 0;
64 return mCurrentNPatchDataId++;
67 NPatchData::NPatchDataId NPatchLoader::Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading)
69 NPatchDataPtr data = GetNPatchData(url, border, preMultiplyOnLoad);
71 DALI_ASSERT_ALWAYS(data.Get() && "NPatchData creation failed!");
73 if(data->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
75 if(!synchronousLoading)
77 // NotifyObserver already done, so
78 // data will not iterate observer list.
79 // We need to call LoadComplete directly.
80 data->NotifyObserver(textureObserver, true);
83 else // if NOT_STARTED or LOADING or LOAD_FAILED, try to reload.
85 if(!synchronousLoading)
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)
95 data->SetLoadingState(NPatchData::LoadingState::LOADING);
97 auto preMultiplyOnLoading = preMultiplyOnLoad ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD
98 : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
100 Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(url, Dali::ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, synchronousLoading, data.Get(), true, preMultiplyOnLoading);
104 preMultiplyOnLoad = (preMultiplyOnLoading == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD) ? true : false;
105 data->SetLoadedNPatchData(pixelBuffer, preMultiplyOnLoad);
107 else if(synchronousLoading)
109 data->SetLoadingState(NPatchData::LoadingState::LOAD_FAILED);
112 return data->GetId();
115 int32_t NPatchLoader::GetCacheIndexFromId(const NPatchData::NPatchDataId id)
117 const unsigned int size = mCache.size();
119 for(unsigned int i = 0; i < size; ++i)
121 if(mCache[i].mData->GetId() == id)
127 return INVALID_CACHE_INDEX;
130 bool NPatchLoader::GetNPatchData(const NPatchData::NPatchDataId id, NPatchDataPtr& data)
132 int32_t cacheIndex = GetCacheIndexFromId(id);
133 if(cacheIndex != INVALID_CACHE_INDEX)
135 data = mCache[cacheIndex].mData;
142 void NPatchLoader::RequestRemove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
144 // Remove observer first
147 int32_t cacheIndex = GetCacheIndexFromId(id);
148 if(cacheIndex != INVALID_CACHE_INDEX)
150 NPatchInfo& info(mCache[cacheIndex]);
152 info.mData->RemoveObserver(textureObserver);
156 mRemoveQueue.push_back({id, nullptr});
158 if(!mRemoveProcessorRegistered && Adaptor::IsAvailable())
160 mRemoveProcessorRegistered = true;
161 Adaptor::Get().RegisterProcessor(*this, true);
165 void NPatchLoader::Remove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
167 int32_t cacheIndex = GetCacheIndexFromId(id);
168 if(cacheIndex == INVALID_CACHE_INDEX)
173 NPatchInfo& info(mCache[cacheIndex]);
175 info.mData->RemoveObserver(textureObserver);
177 if(--info.mReferenceCount <= 0)
179 mCache.erase(mCache.begin() + cacheIndex);
183 void NPatchLoader::Process(bool postProcessor)
185 for(auto& iter : mRemoveQueue)
187 Remove(iter.first, iter.second);
189 mRemoveQueue.clear();
191 if(Adaptor::IsAvailable())
193 Adaptor::Get().UnregisterProcessor(*this, true);
194 mRemoveProcessorRegistered = false;
198 NPatchDataPtr NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad)
200 std::size_t hash = CalculateHash(url.GetUrl());
201 std::vector<NPatchInfo>::size_type index = UNINITIALIZED_ID;
202 const std::vector<NPatchInfo>::size_type count = mCache.size();
204 NPatchInfo* infoPtr = nullptr;
206 for(; index < count; ++index)
208 if(mCache[index].mData->GetHash() == hash)
210 // hash match, check url as well in case of hash collision
211 if(mCache[index].mData->GetUrl().GetUrl() == url.GetUrl())
213 // Use cached data. Need to fast-out return.
214 if(mCache[index].mData->GetBorder() == border)
216 mCache[index].mReferenceCount++;
217 return mCache[index].mData;
221 if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
223 // If we only found LOAD_FAILED case, replace current data. We can reuse texture
224 if(infoPtr == nullptr || infoPtr->mData->GetLoadingState() != NPatchData::LoadingState::LOAD_COMPLETE)
226 infoPtr = &mCache[index];
229 // Still loading pixel buffer. We cannot reuse cached texture yet. Skip checking
230 else if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOADING)
234 // if LOAD_FAILED, reuse this cached NPatchData, and try to load again.
237 if(infoPtr == nullptr)
239 infoPtr = &mCache[index];
247 // If this is new image loading, make new cache data
248 if(infoPtr == nullptr)
250 NPatchInfo info(new NPatchData());
251 info.mData->SetId(GenerateUniqueNPatchDataId());
252 info.mData->SetHash(hash);
253 info.mData->SetUrl(url);
254 info.mData->SetBorder(border);
255 info.mData->SetPreMultiplyOnLoad(preMultiplyOnLoad);
257 mCache.emplace_back(std::move(info));
258 infoPtr = &mCache.back();
260 // Else if LOAD_COMPLETE, Same url but border is different - use the existing texture
261 else if(infoPtr->mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
263 NPatchInfo info(new NPatchData());
265 info.mData->SetId(GenerateUniqueNPatchDataId());
266 info.mData->SetHash(hash);
267 info.mData->SetUrl(url);
268 info.mData->SetCroppedWidth(infoPtr->mData->GetCroppedWidth());
269 info.mData->SetCroppedHeight(infoPtr->mData->GetCroppedHeight());
271 info.mData->SetTextures(infoPtr->mData->GetTextures());
273 NPatchUtility::StretchRanges stretchRangesX;
274 stretchRangesX.PushBack(Uint16Pair(border.left, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.right)) ? info.mData->GetCroppedHeight() - border.right : 0)));
276 NPatchUtility::StretchRanges stretchRangesY;
277 stretchRangesY.PushBack(Uint16Pair(border.top, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.bottom)) ? info.mData->GetCroppedHeight() - border.bottom : 0)));
279 info.mData->SetStretchPixelsX(stretchRangesX);
280 info.mData->SetStretchPixelsY(stretchRangesY);
281 info.mData->SetBorder(border);
283 info.mData->SetPreMultiplyOnLoad(infoPtr->mData->IsPreMultiplied());
285 info.mData->SetLoadingState(NPatchData::LoadingState::LOAD_COMPLETE);
287 mCache.emplace_back(std::move(info));
288 infoPtr = &mCache.back();
290 // Else, LOAD_FAILED. just increase reference so we can reuse it.
293 infoPtr->mReferenceCount++;
296 DALI_ASSERT_ALWAYS(infoPtr && "NPatchInfo creation failed!");
298 return infoPtr->mData;
301 } // namespace Internal
303 } // namespace Toolkit