From 2580d75061499528ff05d00e0e1d5907998900ac Mon Sep 17 00:00:00 2001 From: "Eunki, Hong" Date: Tue, 5 Apr 2022 23:00:04 +0900 Subject: [PATCH] Refactoring npatch-loader so it works well both sync-async loading Make async loading always call LoadComplete when we try to load n-patch image. + Releative with both sync-async case, Fix minor reference count issue for NPatchData. Previous logic only control refrence count as Observer. It was not good. + Fix minor caching issue with border. Previous code logic have problem when we use same Url and different border. Change-Id: Ic54dd522e44f5db64f3e9d08aa44db224ab4d506 Signed-off-by: Eunki, Hong --- .../src/dali-toolkit/utc-Dali-VisualFactory.cpp | 245 ++++++++++++++++++++- dali-toolkit/internal/visuals/npatch-data.cpp | 37 +++- dali-toolkit/internal/visuals/npatch-data.h | 15 +- dali-toolkit/internal/visuals/npatch-loader.cpp | 221 ++++++++++++------- dali-toolkit/internal/visuals/npatch-loader.h | 64 +++++- .../internal/visuals/npatch/npatch-visual.cpp | 100 ++++----- .../internal/visuals/visual-factory-cache.cpp | 2 - 7 files changed, 536 insertions(+), 148 deletions(-) diff --git a/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp b/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp index 8b0adff..6291e50 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp @@ -110,6 +110,13 @@ void TestVisualAsynchronousRender(ToolkitTestApplication& application, DALI_TEST_EQUALS(actor.GetRendererCount(), 1u, TEST_LOCATION); } +int gResourceReadySignalCounter; + +void OnResourceReadySignal(Control control) +{ + gResourceReadySignalCounter++; +} + } // namespace void dali_visual_factory_startup(void) @@ -729,7 +736,7 @@ int UtcDaliVisualFactoryGetNPatchVisual2(void) propertyMap.Insert(ImageVisual::Property::URL, gImage_34_RGBA); propertyMap.Insert(ImageVisual::Property::BORDER, Rect(2, 2, 2, 2)); { - tet_infoline("whole grid"); + tet_infoline("whole grid (2,2,2,2) async"); Visual::Base visual = factory.CreateVisual(propertyMap); DALI_TEST_CHECK(visual); @@ -770,9 +777,80 @@ int UtcDaliVisualFactoryGetNPatchVisual2(void) propertyMap.Clear(); propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::N_PATCH); propertyMap.Insert(ImageVisual::Property::URL, gImage_34_RGBA); + propertyMap.Insert(ImageVisual::Property::BORDER, Rect(2, 2, 2, 2)); + propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, true); + { + tet_infoline("whole grid (2,2,2,2) sync"); + Visual::Base visual = factory.CreateVisual(propertyMap); + DALI_TEST_CHECK(visual); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + + DummyControl actor = DummyControl::New(true); + TestVisualRender(application, actor, visual); + + DALI_TEST_EQUALS(textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION); + + Vector2 naturalSize(0.0f, 0.0f); + visual.GetNaturalSize(naturalSize); + DALI_TEST_EQUALS(naturalSize, Vector2(imageSize.GetWidth(), imageSize.GetHeight()), TEST_LOCATION); + } + + propertyMap.Clear(); + propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::N_PATCH); + propertyMap.Insert(ImageVisual::Property::URL, gImage_34_RGBA); propertyMap.Insert(ImageVisual::Property::BORDER, Rect(1, 1, 1, 1)); + propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, true); { - tet_infoline("whole grid"); + tet_infoline("whole grid (1,1,1,1) sync : for testing same image, different border"); + Visual::Base visual = factory.CreateVisual(propertyMap); + DALI_TEST_CHECK(visual); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + + DummyControl actor = DummyControl::New(true); + TestVisualRender(application, actor, visual); + + DALI_TEST_EQUALS(textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION); + + Vector2 naturalSize(0.0f, 0.0f); + visual.GetNaturalSize(naturalSize); + DALI_TEST_EQUALS(naturalSize, Vector2(imageSize.GetWidth(), imageSize.GetHeight()), TEST_LOCATION); + } + + propertyMap.Clear(); + propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::N_PATCH); + propertyMap.Insert(ImageVisual::Property::URL, gImage_34_RGBA); + propertyMap.Insert(ImageVisual::Property::BORDER, Rect(1, 1, 1, 1)); + { + tet_infoline("whole grid (1,1,1,1) async"); + Visual::Base visual = factory.CreateVisual(propertyMap); + DALI_TEST_CHECK(visual); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + + DummyControl actor = DummyControl::New(true); + TestVisualRender(application, actor, visual); + + DALI_TEST_EQUALS(textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION); + + Vector2 naturalSize(0.0f, 0.0f); + visual.GetNaturalSize(naturalSize); + DALI_TEST_EQUALS(naturalSize, Vector2(imageSize.GetWidth(), imageSize.GetHeight()), TEST_LOCATION); + } + + propertyMap.Clear(); + propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::N_PATCH); + propertyMap.Insert(ImageVisual::Property::URL, gImage_34_RGBA); + propertyMap.Insert(ImageVisual::Property::BORDER, Rect(3, 3, 3, 3)); + { + tet_infoline("whole grid (3,3,3,3) async"); Visual::Base visual = factory.CreateVisual(propertyMap); DALI_TEST_CHECK(visual); @@ -1103,6 +1181,169 @@ int UtcDaliVisualFactoryGetNPatchVisual8(void) END_TEST; } +int UtcDaliVisualFactoryGetNPatchVisual9(void) +{ + ToolkitTestApplication application; + tet_infoline("UtcDaliVisualFactoryGetNPatchVisual9: Request n-patch visual sync during another n-patch visual load image asynchronously"); + + VisualFactory factory = VisualFactory::Get(); + DALI_TEST_CHECK(factory); + + Property::Map propertyMap; + propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::N_PATCH); + propertyMap.Insert(ImageVisual::Property::URL, TEST_NPATCH_FILE_NAME); + propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, false); + Visual::Base visual = factory.CreateVisual(propertyMap); + DALI_TEST_CHECK(visual); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + + DummyControl actor = DummyControl::New(true); + + DummyControlImpl& dummyImpl = static_cast(actor.GetImplementation()); + dummyImpl.RegisterVisual(DummyControl::Property::TEST_VISUAL, visual); + + actor.SetProperty(Actor::Property::SIZE, Vector2(200.f, 200.f)); + DALI_TEST_EQUALS(actor.GetRendererCount(), 0u, TEST_LOCATION); + + application.GetScene().Add(actor); + + propertyMap.Clear(); + propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::N_PATCH); + propertyMap.Insert(ImageVisual::Property::URL, TEST_NPATCH_FILE_NAME); + propertyMap.Insert(ImageVisual::Property::SYNCHRONOUS_LOADING, true); + Visual::Base visual2 = factory.CreateVisual(propertyMap); + DALI_TEST_CHECK(visual2); + + DummyControl actor2 = DummyControl::New(true); + + DummyControlImpl& dummyImpl2 = static_cast(actor2.GetImplementation()); + dummyImpl2.RegisterVisual(DummyControl::Property::TEST_VISUAL, visual2); + + actor2.SetProperty(Actor::Property::SIZE, Vector2(200.f, 200.f)); + DALI_TEST_EQUALS(actor2.GetRendererCount(), 0u, TEST_LOCATION); + + application.GetScene().Add(actor2); + + application.SendNotification(); + application.Render(); + + application.SendNotification(); + application.Render(); + + // Async loading is not finished yet. + { + DALI_TEST_EQUALS(actor.GetRendererCount(), 0u, TEST_LOCATION); + } + // Sync loading is finished. + { + Renderer renderer = actor2.GetRendererAt(0); + auto textures = renderer.GetTextures(); + + DALI_TEST_EQUALS(textures.GetTextureCount(), 1, TEST_LOCATION); + } + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + application.SendNotification(); + application.Render(); + // Async loading is finished. + { + Renderer renderer = actor.GetRendererAt(0); + auto textures = renderer.GetTextures(); + + DALI_TEST_EQUALS(textures.GetTextureCount(), 1, TEST_LOCATION); + } + + END_TEST; +} + +int UtcDaliVisualFactoryGetNPatchVisual10(void) +{ + ToolkitTestApplication application; + tet_infoline("UtcDaliVisualFactoryGetNPatchVisual10: Request same 9-patch visual with a different border"); + + VisualFactory factory = VisualFactory::Get(); + DALI_TEST_CHECK(factory); + + // Get actual size of test image + ImageDimensions imageSize = Dali::GetClosestImageSize(gImage_34_RGBA); + + Property::Map propertyMap; + propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::N_PATCH); + propertyMap.Insert(ImageVisual::Property::URL, gImage_34_RGBA); + propertyMap.Insert(ImageVisual::Property::BORDER, Rect(2, 2, 2, 2)); + { + tet_infoline("whole grid (2,2,2,2) async"); + Visual::Base visual = factory.CreateVisual(propertyMap); + DALI_TEST_CHECK(visual); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + + DummyControl actor = DummyControl::New(true); + TestVisualAsynchronousRender(application, actor, visual); + + DALI_TEST_EQUALS(textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION); + + Vector2 naturalSize(0.0f, 0.0f); + visual.GetNaturalSize(naturalSize); + DALI_TEST_EQUALS(naturalSize, Vector2(imageSize.GetWidth(), imageSize.GetHeight()), TEST_LOCATION); + } + + propertyMap.Clear(); + propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::N_PATCH); + propertyMap.Insert(ImageVisual::Property::URL, gImage_34_RGBA); + propertyMap.Insert(ImageVisual::Property::BORDER, Rect(1, 1, 1, 1)); + { + tet_infoline("whole grid (1,1,1,1) async. Check whether we use cached texture"); + // We don't use dummyControl here + + const int expectResourceReadySignalCounter = 10; + gResourceReadySignalCounter = 0; + + for(int i = 0; i < expectResourceReadySignalCounter; i++) + { + ImageView imageView = ImageView::New(); + imageView[Toolkit::ImageView::Property::IMAGE] = propertyMap; + imageView.ResourceReadySignal().Connect(&OnResourceReadySignal); + application.GetScene().Add(imageView); + } + + // Dont wait for loading. All border use cached texture. + + DALI_TEST_EQUALS(gResourceReadySignalCounter, expectResourceReadySignalCounter, TEST_LOCATION); + } + + propertyMap.Clear(); + propertyMap.Insert(Toolkit::Visual::Property::TYPE, Visual::N_PATCH); + propertyMap.Insert(ImageVisual::Property::URL, gImage_34_RGBA); + propertyMap.Insert(ImageVisual::Property::BORDER, Rect(1, 2, 1, 2)); + { + tet_infoline("whole grid (1,2,1,2) async. Check whether we use cached texture"); + // We don't use dummyControl here + + const int expectResourceReadySignalCounter = 10; + gResourceReadySignalCounter = 0; + + for(int i = 0; i < expectResourceReadySignalCounter; i++) + { + ImageView imageView = ImageView::New(); + imageView[Toolkit::ImageView::Property::IMAGE] = propertyMap; + imageView.ResourceReadySignal().Connect(&OnResourceReadySignal); + application.GetScene().Add(imageView); + } + + // Dont wait for loading. All border use cached texture. + + DALI_TEST_EQUALS(gResourceReadySignalCounter, expectResourceReadySignalCounter, TEST_LOCATION); + } + + END_TEST; +} + int UtcDaliNPatchVisualAuxiliaryImage01(void) { ToolkitTestApplication application; diff --git a/dali-toolkit/internal/visuals/npatch-data.cpp b/dali-toolkit/internal/visuals/npatch-data.cpp index e8a6042..cbc038a 100644 --- a/dali-toolkit/internal/visuals/npatch-data.cpp +++ b/dali-toolkit/internal/visuals/npatch-data.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * Copyright (c) 2022 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ NPatchData::NPatchData() mCroppedWidth(0), mCroppedHeight(0), mBorder(0, 0, 0, 0), - mLoadingState(LoadingState::LOADING), + mLoadingState(LoadingState::NOT_STARTED), mPreMultiplyOnLoad(false), mRenderingMap{nullptr} { @@ -224,22 +224,49 @@ void NPatchData::SetLoadedNPatchData(Devel::PixelBuffer& pixelBuffer, bool preMu mLoadingState = LoadingState::LOAD_COMPLETE; } +void NPatchData::NotifyObserver(TextureUploadObserver* observer, const bool& loadSuccess) +{ + observer->LoadComplete( + loadSuccess, + TextureUploadObserver::TextureInformation( + TextureUploadObserver::ReturnType::TEXTURE, + static_cast(mId), ///< Note : until end of NPatchLoader::Load, npatch-visual don't know the id of data. + mTextureSet, + false, // UseAtlas + Vector4(), // AtlasRect + mPreMultiplyOnLoad)); +} + void NPatchData::LoadComplete(bool loadSuccess, TextureInformation textureInformation) { if(loadSuccess) { - SetLoadedNPatchData(textureInformation.pixelBuffer, textureInformation.preMultiplied); + if(mLoadingState != LoadingState::LOAD_COMPLETE) + { + // If mLoadingState is LOAD_FAILED, just re-set (It can be happened when sync loading is failed, but async loading is succeeded). + SetLoadedNPatchData(textureInformation.pixelBuffer, textureInformation.preMultiplied); + } } else { - mLoadingState = LoadingState::LOAD_FAILED; + if(mLoadingState == LoadingState::LOADING) + { + mLoadingState = LoadingState::LOAD_FAILED; + } + // If mLoadingState is already LOAD_COMPLETE, we can use uploaded texture (It can be happened when sync loading is succeeded, but async loading is failed). + else if(mLoadingState == LoadingState::LOAD_COMPLETE) + { + loadSuccess = true; + } } for(uint32_t index = 0; index < mObserverList.Count(); ++index) { TextureUploadObserver* observer = mObserverList[index]; - observer->LoadComplete(loadSuccess, TextureUploadObserver::TextureInformation(TextureUploadObserver::ReturnType::TEXTURE, TextureManager::INVALID_TEXTURE_ID, mTextureSet, false, Vector4(), textureInformation.preMultiplied)); + NotifyObserver(observer, loadSuccess); } + + mObserverList.Clear(); } } // namespace Internal diff --git a/dali-toolkit/internal/visuals/npatch-data.h b/dali-toolkit/internal/visuals/npatch-data.h index f67e599..cc520d8 100644 --- a/dali-toolkit/internal/visuals/npatch-data.h +++ b/dali-toolkit/internal/visuals/npatch-data.h @@ -44,9 +44,10 @@ public: */ enum class LoadingState { - LOADING = 0, ///< NPatch is on loading. - LOAD_COMPLETE, ///< NPatch loading is completed successfully. - LOAD_FAILED ///< NPatch loading is failed. + NOT_STARTED = 0, ///< NPatch loading is not started yet. + LOADING, ///< NPatch is on loading. + LOAD_COMPLETE, ///< NPatch loading is completed successfully. + LOAD_FAILED ///< NPatch loading is failed. }; public: @@ -251,6 +252,14 @@ public: */ void SetLoadedNPatchData(Devel::PixelBuffer& pixelBuffer, bool preMultiplied); + /** + * @brief Send LoadComplete notify with current setuped NPatchData + * + * @param [in] observer observer who will be got LoadComplete notify + * @param [in] loadSuccess whether the image load success or not. + */ + void NotifyObserver(TextureUploadObserver* observer, const bool& loadSuccess); + private: /** * @copydoc TextureUploadObserver::LoadComplete diff --git a/dali-toolkit/internal/visuals/npatch-loader.cpp b/dali-toolkit/internal/visuals/npatch-loader.cpp index cb2850d..4c6471e 100644 --- a/dali-toolkit/internal/visuals/npatch-loader.cpp +++ b/dali-toolkit/internal/visuals/npatch-loader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * Copyright (c) 2022 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,94 +54,59 @@ NPatchData::NPatchDataId NPatchLoader::GenerateUniqueNPatchDataId() std::size_t NPatchLoader::Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect& border, bool& preMultiplyOnLoad, bool synchronousLoading) { - std::size_t hash = CalculateHash(url.GetUrl()); - OwnerContainer::SizeType index = UNINITIALIZED_ID; - const OwnerContainer::SizeType count = mCache.Count(); + NPatchData* data = GetNPatchData(url, border, preMultiplyOnLoad); - for(; index < count; ++index) + DALI_ASSERT_ALWAYS(data && "NPatchData creation failed!"); + + if(data->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE) { - if(mCache[index]->GetHash() == hash) + if(!synchronousLoading) { - // hash match, check url as well in case of hash collision - if(mCache[index]->GetUrl().GetUrl() == url.GetUrl()) + // NotifyObserver already done, so + // data will not iterate observer list. + // We need to call LoadComplete directly. + data->NotifyObserver(textureObserver, true); + } + } + else // if NOT_STARTED or LOADING or LOAD_FAILED, try to reload. + { + if(!synchronousLoading) + { + data->AddObserver(textureObserver); + // If still LOADING and async, don't need to request reload. Fast return. + if(data->GetLoadingState() == NPatchData::LoadingState::LOADING) { - // Use cached data - if(mCache[index]->GetBorder() == border) - { - if(mCache[index]->GetLoadingState() == NPatchData::LoadingState::LOADING) - { - mCache[index]->AddObserver(textureObserver); - } - return mCache[index]->GetId(); // valid indices are from 1 onwards - } - else - { - if(mCache[index]->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE) - { - // Same url but border is different - use the existing texture - NPatchData* newData = new NPatchData(); - newData->SetId(GenerateUniqueNPatchDataId()); - newData->SetHash(hash); - newData->SetUrl(url); - newData->SetCroppedWidth(mCache[index]->GetCroppedWidth()); - newData->SetCroppedHeight(mCache[index]->GetCroppedHeight()); - - newData->SetTextures(mCache[index]->GetTextures()); - - NPatchUtility::StretchRanges stretchRangesX; - stretchRangesX.PushBack(Uint16Pair(border.left, ((newData->GetCroppedWidth() >= static_cast(border.right)) ? newData->GetCroppedHeight() - border.right : 0))); - - NPatchUtility::StretchRanges stretchRangesY; - stretchRangesY.PushBack(Uint16Pair(border.top, ((newData->GetCroppedWidth() >= static_cast(border.bottom)) ? newData->GetCroppedHeight() - border.bottom : 0))); + return data->GetId(); + } + } - newData->SetStretchPixelsX(stretchRangesX); - newData->SetStretchPixelsY(stretchRangesY); - newData->SetBorder(border); + data->SetLoadingState(NPatchData::LoadingState::LOADING); - newData->SetPreMultiplyOnLoad(mCache[index]->IsPreMultiplied()); + auto preMultiplyOnLoading = preMultiplyOnLoad ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD + : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY; - newData->SetLoadingState(NPatchData::LoadingState::LOAD_COMPLETE); - newData->AddObserver(textureObserver); + Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(url, Dali::ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, synchronousLoading, data, true, preMultiplyOnLoading); - mCache.PushBack(newData); - return newData->GetId(); // valid ids start from 1u - } - } - } + if(pixelBuffer) + { + preMultiplyOnLoad = (preMultiplyOnLoading == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD) ? true : false; + data->SetLoadedNPatchData(pixelBuffer, preMultiplyOnLoad); + } + else if(synchronousLoading) + { + data->SetLoadingState(NPatchData::LoadingState::LOAD_FAILED); } - } - - // If this is new image loading, make new cache data - NPatchData* data; - data = new NPatchData(); - data->SetId(GenerateUniqueNPatchDataId()); - data->SetHash(hash); - data->SetUrl(url); - data->SetBorder(border); - data->SetPreMultiplyOnLoad(preMultiplyOnLoad); - data->AddObserver(textureObserver); - mCache.PushBack(data); - - auto preMultiplyOnLoading = preMultiplyOnLoad ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD - : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY; - - Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(url, Dali::ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, synchronousLoading, data, true, preMultiplyOnLoading); - - if(pixelBuffer) - { - preMultiplyOnLoad = (preMultiplyOnLoading == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD) ? true : false; - data->SetLoadedNPatchData(pixelBuffer, preMultiplyOnLoad); } return data->GetId(); } int32_t NPatchLoader::GetCacheIndexFromId(const NPatchData::NPatchDataId id) { - const unsigned int size = mCache.Count(); + const unsigned int size = mCache.size(); for(unsigned int i = 0; i < size; ++i) { - if(mCache[i]->GetId() == id) + if(mCache[i].mData->GetId() == id) { return i; } @@ -155,7 +120,7 @@ bool NPatchLoader::GetNPatchData(const NPatchData::NPatchDataId id, const NPatch int32_t cacheIndex = GetCacheIndexFromId(id); if(cacheIndex != INVALID_CACHE_INDEX) { - data = mCache[cacheIndex]; + data = mCache[cacheIndex].mData; return true; } data = nullptr; @@ -170,17 +135,119 @@ void NPatchLoader::Remove(std::size_t id, TextureUploadObserver* textureObserver return; } - NPatchData* data; - data = mCache[cacheIndex]; + NPatchInfo& info(mCache[cacheIndex]); - data->RemoveObserver(textureObserver); + info.mData->RemoveObserver(textureObserver); - if(data->GetObserverCount() == 0) + if(--info.mReferenceCount <= 0) { - mCache.Erase(mCache.Begin() + cacheIndex); + mCache.erase(mCache.begin() + cacheIndex); } } +NPatchData* NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect& border, bool& preMultiplyOnLoad) +{ + std::size_t hash = CalculateHash(url.GetUrl()); + std::vector::size_type index = UNINITIALIZED_ID; + const std::vector::size_type count = mCache.size(); + + NPatchInfo* infoPtr = nullptr; + + for(; index < count; ++index) + { + if(mCache[index].mData->GetHash() == hash) + { + // hash match, check url as well in case of hash collision + if(mCache[index].mData->GetUrl().GetUrl() == url.GetUrl()) + { + // Use cached data. Need to fast-out return. + if(mCache[index].mData->GetBorder() == border) + { + mCache[index].mReferenceCount++; + return mCache[index].mData; + } + else + { + if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE) + { + // If we only found LOAD_FAILED case, replace current data. We can reuse texture + if(infoPtr == nullptr || infoPtr->mData->GetLoadingState() != NPatchData::LoadingState::LOAD_COMPLETE) + { + infoPtr = &mCache[index]; + } + } + // Still loading pixel buffer. We cannot reuse cached texture yet. Skip checking + else if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOADING) + { + continue; + } + // if LOAD_FAILED, reuse this cached NPatchData, and try to load again. + else + { + if(infoPtr == nullptr) + { + infoPtr = &mCache[index]; + } + } + } + } + } + } + + // If this is new image loading, make new cache data + if(infoPtr == nullptr) + { + NPatchInfo info(new NPatchData()); + info.mData->SetId(GenerateUniqueNPatchDataId()); + info.mData->SetHash(hash); + info.mData->SetUrl(url); + info.mData->SetBorder(border); + info.mData->SetPreMultiplyOnLoad(preMultiplyOnLoad); + + mCache.emplace_back(std::move(info)); + infoPtr = &mCache.back(); + } + // Else if LOAD_COMPLETE, Same url but border is different - use the existing texture + else if(infoPtr->mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE) + { + NPatchInfo info(new NPatchData()); + + info.mData->SetId(GenerateUniqueNPatchDataId()); + info.mData->SetHash(hash); + info.mData->SetUrl(url); + info.mData->SetCroppedWidth(infoPtr->mData->GetCroppedWidth()); + info.mData->SetCroppedHeight(infoPtr->mData->GetCroppedHeight()); + + info.mData->SetTextures(infoPtr->mData->GetTextures()); + + NPatchUtility::StretchRanges stretchRangesX; + stretchRangesX.PushBack(Uint16Pair(border.left, ((info.mData->GetCroppedWidth() >= static_cast(border.right)) ? info.mData->GetCroppedHeight() - border.right : 0))); + + NPatchUtility::StretchRanges stretchRangesY; + stretchRangesY.PushBack(Uint16Pair(border.top, ((info.mData->GetCroppedWidth() >= static_cast(border.bottom)) ? info.mData->GetCroppedHeight() - border.bottom : 0))); + + info.mData->SetStretchPixelsX(stretchRangesX); + info.mData->SetStretchPixelsY(stretchRangesY); + info.mData->SetBorder(border); + + info.mData->SetPreMultiplyOnLoad(infoPtr->mData->IsPreMultiplied()); + + info.mData->SetLoadingState(NPatchData::LoadingState::LOAD_COMPLETE); + + mCache.emplace_back(std::move(info)); + infoPtr = &mCache.back(); + } + // Else, LOAD_FAILED. just increase reference so we can reuse it. + else + { + infoPtr->mReferenceCount++; + } + + DALI_ASSERT_ALWAYS(infoPtr && "NPatchInfo creation failed!"); + + return infoPtr->mData; +} + } // namespace Internal } // namespace Toolkit diff --git a/dali-toolkit/internal/visuals/npatch-loader.h b/dali-toolkit/internal/visuals/npatch-loader.h index 6475613..48301a6 100644 --- a/dali-toolkit/internal/visuals/npatch-loader.h +++ b/dali-toolkit/internal/visuals/npatch-loader.h @@ -19,7 +19,6 @@ // EXTERNAL INCLUDES #include -#include #include #include @@ -90,8 +89,9 @@ public: /** * @brief Remove a texture matching id. - * Erase the observer from the observer list of cache. - * If the observer list is empty, the textureSet will be reset. + * Erase the observer from the observer list of cache if we need. + * This API decrease cached NPatchInfo reference. + * If the NPatchInfo reference become 0, the textureSet will be reset. * * @param [in] id cache data id * @param [in] textureObserver The NPatchVisual that requested loading. @@ -103,6 +103,60 @@ private: int32_t GetCacheIndexFromId(const NPatchData::NPatchDataId id); +private: + /** + * @brief Information of NPatchData + * It also hold ownership of NPatchData memory. + */ + struct NPatchInfo + { + NPatchInfo(NPatchData* data) + : mData(data), + mReferenceCount(1u) + { + } + ~NPatchInfo() + { + if(mData) + { + delete mData; + } + } + NPatchInfo(NPatchInfo&& info) // move constructor + { + mData = std::move(info.mData); + mReferenceCount = info.mReferenceCount; + info.mData = nullptr; + info.mReferenceCount = 0u; + } + NPatchInfo& operator=(NPatchInfo&& info) // move operator + { + mData = std::move(info.mData); + mReferenceCount = info.mReferenceCount; + info.mData = nullptr; + info.mReferenceCount = 0u; + return *this; + } + + NPatchInfo() = delete; // Do not use default constructor + NPatchInfo(const NPatchInfo& info) = delete; // Do not use copy constructor + + NPatchData* mData; + std::int16_t mReferenceCount; ///< The number of N-patch visuals that use this data. + }; + + /** + * @brief Get cached NPatchData by inputed url and border. If there is no cached data, create new one. + * @note This API increase cached NPatchInfo reference. + * + * @param [in] url to retrieve + * @param [in] border The border size of the image + * @param [in,out] preMultiplyOnLoad True if the image color should be multiplied by it's alpha. Set to false if the + * image has no alpha channel + * @return NPatchData pointer that Load function will used. + */ + NPatchData* GetNPatchData(const VisualUrl& url, const Rect& border, bool& preMultiplyOnLoad); + protected: /** * Undefined copy constructor. @@ -115,8 +169,8 @@ protected: NPatchLoader& operator=(const NPatchLoader& rhs); private: - NPatchData::NPatchDataId mCurrentNPatchDataId; - OwnerContainer mCache; + NPatchData::NPatchDataId mCurrentNPatchDataId; + std::vector mCache; }; } // namespace Internal diff --git a/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp b/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp index 831d4b4..5fc5dd0 100644 --- a/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp +++ b/dali-toolkit/internal/visuals/npatch/npatch-visual.cpp @@ -198,40 +198,17 @@ void NPatchVisual::DoSetOnScene(Actor& actor) // load when first go on stage LoadImages(); + // Set mPlacementActor now, because some case, LoadImages can use this information in LoadComplete API. + // at this case, we try to SetResouce to mPlaceActor twice. so, we should avoid that case. + mPlacementActor = actor; + const NPatchData* data; - if(mLoader.GetNPatchData(mId, data)) + if(mImpl->mRenderer && mLoader.GetNPatchData(mId, data) && data->GetLoadingState() != NPatchData::LoadingState::LOADING) { - Geometry geometry = CreateGeometry(); - Shader shader = CreateShader(); - - mImpl->mRenderer.SetGeometry(geometry); - mImpl->mRenderer.SetShader(shader); - - mPlacementActor = actor; - // If all reasources are already loaded, apply textures and uniforms now - // else, will be completed uploaded at LoadComplete function asynchronously. - if(data->GetLoadingState() != NPatchData::LoadingState::LOADING && - (!mAuxiliaryUrl.IsValid() || mAuxiliaryResourceStatus != Toolkit::Visual::ResourceStatus::PREPARING)) + // If mAuxiliaryUrl need to be loaded, we should wait it until LoadComplete called. + if(!mAuxiliaryUrl.IsValid() || mAuxiliaryResourceStatus != Toolkit::Visual::ResourceStatus::PREPARING) { - if(RenderingAddOn::Get().IsValid()) - { - RenderingAddOn::Get().SubmitRenderTask(mImpl->mRenderer, data->GetRenderingMap()); - } - - ApplyTextureAndUniforms(); - actor.AddRenderer(mImpl->mRenderer); - mPlacementActor.Reset(); - - // npatch loaded and ready to display - if(data->GetLoadingState() != NPatchData::LoadingState::LOAD_COMPLETE || - (mAuxiliaryUrl.IsValid() && mAuxiliaryResourceStatus != Toolkit::Visual::ResourceStatus::READY)) - { - ResourceReady(Toolkit::Visual::ResourceStatus::FAILED); - } - else - { - ResourceReady(Toolkit::Visual::ResourceStatus::READY); - } + SetResource(); } } } @@ -513,8 +490,6 @@ void NPatchVisual::ApplyTextureAndUniforms() else { DALI_LOG_ERROR("The N patch image '%s' is not a valid N patch image\n", mImageUrl.GetUrl().c_str()); - textureSet = TextureSet::New(); - Actor actor = mPlacementActor.GetHandle(); Vector2 imageSize = Vector2::ZERO; if(actor) @@ -548,18 +523,36 @@ Geometry NPatchVisual::GetNinePatchGeometry(VisualFactoryCache::GeometryType sub void NPatchVisual::SetResource() { - Geometry geometry = CreateGeometry(); - Shader shader = CreateShader(); + const NPatchData* data; + if(mImpl->mRenderer && mLoader.GetNPatchData(mId, data)) + { + Geometry geometry = CreateGeometry(); + Shader shader = CreateShader(); - mImpl->mRenderer.SetGeometry(geometry); - mImpl->mRenderer.SetShader(shader); + mImpl->mRenderer.SetGeometry(geometry); + mImpl->mRenderer.SetShader(shader); - Actor actor = mPlacementActor.GetHandle(); - if(actor) - { - ApplyTextureAndUniforms(); - actor.AddRenderer(mImpl->mRenderer); - mPlacementActor.Reset(); + if(RenderingAddOn::Get().IsValid()) + { + RenderingAddOn::Get().SubmitRenderTask(mImpl->mRenderer, data->GetRenderingMap()); + } + Actor actor = mPlacementActor.GetHandle(); + if(actor) + { + ApplyTextureAndUniforms(); + actor.AddRenderer(mImpl->mRenderer); + mPlacementActor.Reset(); + } + + // npatch loaded and ready to display + if(data->GetLoadingState() != NPatchData::LoadingState::LOAD_COMPLETE) + { + ResourceReady(Toolkit::Visual::ResourceStatus::FAILED); + } + else + { + ResourceReady(Toolkit::Visual::ResourceStatus::READY); + } } } @@ -567,6 +560,15 @@ void NPatchVisual::LoadComplete(bool loadSuccess, TextureInformation textureInfo { if(textureInformation.returnType == TextureUploadObserver::ReturnType::TEXTURE) // For the Url. { + if(textureInformation.textureId != TextureManager::INVALID_TEXTURE_ID) + { + if(mId == NPatchData::INVALID_NPATCH_DATA_ID) + { + // Special case when mLoader.Load call LoadComplete function before mId setup. + // We can overwrite mId. + mId = static_cast(textureInformation.textureId); + } + } if(loadSuccess) { EnablePreMultipliedAlpha(textureInformation.preMultiplied); @@ -584,7 +586,7 @@ void NPatchVisual::LoadComplete(bool loadSuccess, TextureInformation textureInfo mAuxiliaryResourceStatus = Toolkit::Visual::ResourceStatus::FAILED; } } - // If auxiliaryUrl didn't set || auxiliaryUrl load done. + // If auxiliaryUrl didn't required OR auxiliaryUrl load done. if(!mAuxiliaryUrl.IsValid() || mAuxiliaryResourceStatus != Toolkit::Visual::ResourceStatus::PREPARING) { const NPatchData* data; @@ -592,16 +594,6 @@ void NPatchVisual::LoadComplete(bool loadSuccess, TextureInformation textureInfo if(mImpl->mRenderer && mLoader.GetNPatchData(mId, data) && data->GetLoadingState() != NPatchData::LoadingState::LOADING) { SetResource(); - // npatch loaded and ready to display - if(data->GetLoadingState() != NPatchData::LoadingState::LOAD_COMPLETE || - (mAuxiliaryUrl.IsValid() && mAuxiliaryResourceStatus != Toolkit::Visual::ResourceStatus::READY)) - { - ResourceReady(Toolkit::Visual::ResourceStatus::FAILED); - } - else - { - ResourceReady(Toolkit::Visual::ResourceStatus::READY); - } } } } diff --git a/dali-toolkit/internal/visuals/visual-factory-cache.cpp b/dali-toolkit/internal/visuals/visual-factory-cache.cpp index d7932d2..2f5d123 100644 --- a/dali-toolkit/internal/visuals/visual-factory-cache.cpp +++ b/dali-toolkit/internal/visuals/visual-factory-cache.cpp @@ -412,7 +412,6 @@ void VisualFactoryCache::UpdateBrokenImageRenderer(Renderer& renderer, const Vec int brokenIndex = GetProperBrokenImageIndex(size); if(GetBrokenImageVisualType(brokenIndex) == VisualUrl::N_PATCH) { - DALI_LOG_ERROR("Broken npatch?"); // Set geometry and shader for npatch Geometry geometry = GetNPatchGeometry(brokenIndex); Shader shader = GetNPatchShader(brokenIndex); @@ -422,7 +421,6 @@ void VisualFactoryCache::UpdateBrokenImageRenderer(Renderer& renderer, const Vec } else { - DALI_LOG_ERROR("Broken single image"); // Create single image renderer only if rederer is not use normal ImageShader. i.e. npatch visual. if(!rendererIsImage) { -- 2.7.4