From ac397867b79af162604fa38193f10d778cebe9fe Mon Sep 17 00:00:00 2001 From: "Eunki, Hong" Date: Mon, 24 Jun 2024 14:14:55 +0900 Subject: [PATCH] Implement SVG image file cache system Let we cached svg image file load result, and rasterized texture as specific size. For image url, let we cache Dali::VectorImageRenderer, which connect with vector rasterize engine. For each rasterize request, we will use that cached id of loader, and Rasterize width/height. If we use same size for rasterizing, we can share same Texture. Change-Id: I9684557d2edabee0a749a39f428014c5a0cd27ff Signed-off-by: Eunki, Hong --- .../src/dali-toolkit-internal/CMakeLists.txt | 1 + .../dali-toolkit-internal/utc-Dali-SvgLoader.cpp | 1100 +++++++++++++++++ .../src/dali-toolkit/utc-Dali-ImageView.cpp | 412 ++++++- .../src/dali-toolkit/utc-Dali-SvgVisual.cpp | 92 +- dali-toolkit/internal/file.list | 2 + .../internal/visuals/image/image-visual.cpp | 2 +- dali-toolkit/internal/visuals/image/image-visual.h | 2 +- .../internal/visuals/npatch/npatch-loader.h | 2 +- .../internal/visuals/svg/svg-loader-observer.cpp | 34 + .../internal/visuals/svg/svg-loader-observer.h | 102 ++ dali-toolkit/internal/visuals/svg/svg-loader.cpp | 1242 ++++++++++++++++++++ dali-toolkit/internal/visuals/svg/svg-loader.h | 458 ++++++++ dali-toolkit/internal/visuals/svg/svg-task.cpp | 11 +- dali-toolkit/internal/visuals/svg/svg-task.h | 21 +- dali-toolkit/internal/visuals/svg/svg-visual.cpp | 273 ++--- dali-toolkit/internal/visuals/svg/svg-visual.h | 54 +- .../internal/visuals/visual-factory-cache.cpp | 6 + .../internal/visuals/visual-factory-cache.h | 9 + .../internal/visuals/visual-factory-impl.cpp | 5 + .../internal/visuals/visual-factory-impl.h | 5 + 20 files changed, 3657 insertions(+), 176 deletions(-) create mode 100644 automated-tests/src/dali-toolkit-internal/utc-Dali-SvgLoader.cpp create mode 100644 dali-toolkit/internal/visuals/svg/svg-loader-observer.cpp create mode 100644 dali-toolkit/internal/visuals/svg/svg-loader-observer.h create mode 100644 dali-toolkit/internal/visuals/svg/svg-loader.cpp create mode 100644 dali-toolkit/internal/visuals/svg/svg-loader.h diff --git a/automated-tests/src/dali-toolkit-internal/CMakeLists.txt b/automated-tests/src/dali-toolkit-internal/CMakeLists.txt index 68bbf2f..5d7567b 100755 --- a/automated-tests/src/dali-toolkit-internal/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit-internal/CMakeLists.txt @@ -20,6 +20,7 @@ SET(TC_SOURCES utc-Dali-LineHelperFunctions.cpp utc-Dali-LogicalModel.cpp utc-Dali-PropertyHelper.cpp + utc-Dali-SvgLoader.cpp utc-Dali-Text-AbstractStyleCharacterRun.cpp utc-Dali-Text-Characters.cpp utc-Dali-Text-CharacterSetConversion.cpp diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-SvgLoader.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-SvgLoader.cpp new file mode 100644 index 0000000..58a815c --- /dev/null +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-SvgLoader.cpp @@ -0,0 +1,1100 @@ +/* + * Copyright (c) 2024 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include ///< For VisualFactory's member SvgLoader. +#include +#include +#include + +#include + +#if defined(ELDBUS_ENABLED) +#include +#endif + +using namespace Dali::Toolkit::Internal; + +void utc_dali_toolkit_internal_svg_loader_startup(void) +{ + setenv("LOG_SVG_LOADER", "3", 1); + test_return_value = TET_UNDEF; +#if defined(ELDBUS_ENABLED) + DBusWrapper::Install(std::unique_ptr(new TestDBusWrapper)); +#endif +} + +void utc_dali_toolkit_internal_svg_loader_cleanup(void) +{ + test_return_value = TET_PASS; +} + +namespace +{ +const char* TEST_SVG_FILE_NAME = TEST_RESOURCE_DIR "/svg1.svg"; +const char* TEST_SVG_INVALID_RASTERIZE_FILE_NAME = TEST_RESOURCE_DIR "/invalid1.svg"; ///< Load succes but rasterize fail. + +constexpr Dali::Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f); +constexpr float DEFAULT_DPI(218.5f); + +class TestObserver : public Dali::Toolkit::Internal::SvgLoaderObserver +{ +public: + TestObserver() + : mLoadCalled(false), + mLoadSuccess(false), + mRasterizeCalled(false), + mRasterizeSuccess(false), + mVectorImageRenderer(), + mTextureSet(), + mAtlasRect(FULL_TEXTURE_RECT) + { + } + +public: ///< Implement of SvgLoaderObserver + void LoadComplete(int32_t loadId, Dali::VectorImageRenderer vectorImageRenderer) override + { + mLoadCalled = true; + mVectorImageRenderer = vectorImageRenderer; + + mLoadSuccess = !!mVectorImageRenderer; + } + + void RasterizeComplete(int32_t rasterizeId, Dali::TextureSet textureSet, Vector4 atlasRect) override + { + mRasterizeCalled = true; + mTextureSet = textureSet; + mAtlasRect = atlasRect; + + mRasterizeSuccess = !!mTextureSet; + } + +public: /// Check for test + void CheckTest(bool loadCalled, bool loadSuccess, bool rasterizeCalled, bool rasterizeSuccess, const char* location) + { + DALI_TEST_EQUALS(mLoadCalled, loadCalled, location); + DALI_TEST_EQUALS(mLoadSuccess, loadSuccess, location); + DALI_TEST_EQUALS(mRasterizeCalled, rasterizeCalled, location); + DALI_TEST_EQUALS(mRasterizeSuccess, rasterizeSuccess, location); + } + void CheckLoadTest(bool loadCalled, bool loadSuccess, const char* location) + { + DALI_TEST_EQUALS(mLoadCalled, loadCalled, location); + DALI_TEST_EQUALS(mLoadSuccess, loadSuccess, location); + } + void CheckRasterizeTest(bool rasterizeCalled, bool rasterizeSuccess, const char* location) + { + DALI_TEST_EQUALS(mRasterizeCalled, rasterizeCalled, location); + DALI_TEST_EQUALS(mRasterizeSuccess, rasterizeSuccess, location); + } + +public: + bool mLoadCalled; + bool mLoadSuccess; + bool mRasterizeCalled; + bool mRasterizeSuccess; + + Dali::VectorImageRenderer mVectorImageRenderer; + + TextureSet mTextureSet; + Vector4 mAtlasRect; +}; + +class TestObserverWithCustomFunction : public TestObserver +{ +public: + TestObserverWithCustomFunction() + : TestObserver(), + mLoadSignals{}, + mRasterizeSignals{}, + mLoadData{nullptr}, + mRasterizeData{nullptr}, + mKeepLoadSignal{false}, + mKeepRasterizeSignal{false} + { + } + +public: ///< Implement of SvgLoaderObserver + void LoadComplete(int32_t loadId, Dali::VectorImageRenderer vectorImageRenderer) override + { + TestObserver::LoadComplete(loadId, vectorImageRenderer); + + // Execute signals. + for(size_t i = 0; i < mLoadSignals.size(); i++) + { + mLoadSignals[i](mLoadData); + } + + // Clear signals. + if(!mKeepLoadSignal) + { + mLoadSignals.clear(); + } + } + + void RasterizeComplete(int32_t rasterizeId, Dali::TextureSet textureSet, Vector4 atlasRect) override + { + TestObserver::RasterizeComplete(rasterizeId, textureSet, atlasRect); + + // Execute signals. + for(size_t i = 0; i < mRasterizeSignals.size(); i++) + { + mRasterizeSignals[i](mRasterizeData); + } + + // Clear signals. + if(!mKeepRasterizeSignal) + { + mRasterizeSignals.clear(); + } + } + +public: + void ConnectLoadFunction(std::function signal) + { + mLoadSignals.push_back(signal); + } + + void ConnectRasterizeFunction(std::function signal) + { + mRasterizeSignals.push_back(signal); + } + +public: + std::vector> mLoadSignals; + std::vector> mRasterizeSignals; + void* mLoadData; + void* mRasterizeData; + bool mKeepLoadSignal; + bool mKeepRasterizeSignal; +}; + +} // namespace + +int UtcSvgLoaderBasicLoadAndRasterize(void) +{ + tet_infoline("Test various cases basic behavior\n"); + + ToolkitTestApplication application; + + EncodedImageBuffer svgBuffer = Dali::ConvertFileToEncodedImageBuffer(TEST_SVG_FILE_NAME, EncodedImageBuffer::ImageType::VECTOR_IMAGE); + + auto visualFactory = Toolkit::VisualFactory::Get(); + SvgLoader& svgLoader = GetImplementation(visualFactory).GetSvgLoader(); // Use VisualFactory's svg loader, for use atlas and EncodedImageBuffer. + + Dali::Toolkit::ImageUrl imageUrl = Dali::Toolkit::Image::GenerateUrl(svgBuffer); + svgBuffer.Reset(); + + const std::string fileNames[] = { + TEST_SVG_FILE_NAME, + TEST_SVG_INVALID_RASTERIZE_FILE_NAME, + imageUrl.GetUrl(), + "invalid.svg", + }; + const int fileNamesCount = sizeof(fileNames) / sizeof(fileNames[0]); + + const std::vector> rasterizeSizes = { + {0u, 0u}, + {100u, 100u}, + {600u, 600u}, ///< To big so atlas attempt failed. + }; + const int rasterizeSizesCount = rasterizeSizes.size(); + + for(int fileType = 0; fileType < fileNamesCount; ++fileType) + { + const bool loadSuccess = (fileType == 0 || fileType == 1 || fileType == 2); + const bool rasterizeSuccess = loadSuccess && (fileType == 0 || fileType == 2); + for(int synchronousLoading = 0; synchronousLoading < 2; ++synchronousLoading) + { + for(int attemptAtlasing = 0; attemptAtlasing < 2; ++attemptAtlasing) + { + for(int sizeType = 0; sizeType < rasterizeSizesCount; ++sizeType) + { + const bool atlasAttempted = (attemptAtlasing == 1) && (sizeType == 0 || sizeType == 1); + + tet_printf("\n\nTesting fileType %d, synchronousLoading %d, attemptAtlasing %d, sizeType %d\n\n", fileType, synchronousLoading, attemptAtlasing, sizeType); + + TestObserver observer; + std::string filename(fileNames[fileType]); + + SvgLoader::SvgLoadId loadId = svgLoader.Load(filename, DEFAULT_DPI, &observer, synchronousLoading == 1); + SvgLoader::SvgRasterizeId rasterizeId = svgLoader.Rasterize(loadId, rasterizeSizes[sizeType].first, rasterizeSizes[sizeType].second, attemptAtlasing == 1, &observer, synchronousLoading == 1); + DALI_TEST_CHECK(loadId != SvgLoader::INVALID_SVG_LOAD_ID); + DALI_TEST_CHECK(rasterizeId != SvgLoader::INVALID_SVG_RASTERIZE_ID); + + if(synchronousLoading == 1) + { + observer.CheckTest(true, loadSuccess, true, rasterizeSuccess, TEST_LOCATION); + } + else + { + observer.CheckTest(false, false, false, false, TEST_LOCATION); + + // Wait async load complete + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + observer.CheckTest(true, loadSuccess, false, false, TEST_LOCATION); + + if(loadSuccess) + { + // Wait async rasterize complete + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + // TODO : We don't notify rasterize failed even if load failed. Should we notify it? + observer.CheckTest(true, loadSuccess, true, rasterizeSuccess, TEST_LOCATION); + } + } + + DALI_TEST_EQUALS(!!observer.mVectorImageRenderer, loadSuccess, TEST_LOCATION); + DALI_TEST_EQUALS(!!observer.mTextureSet, rasterizeSuccess, TEST_LOCATION); + if(rasterizeSuccess) + { + if(atlasAttempted) + { + DALI_TEST_NOT_EQUALS(observer.mAtlasRect, FULL_TEXTURE_RECT, 0.01f, TEST_LOCATION); + } + else + { + DALI_TEST_EQUALS(observer.mAtlasRect, FULL_TEXTURE_RECT, TEST_LOCATION); + } + } + + // Remove cache + svgLoader.RequestLoadRemove(loadId, &observer); + svgLoader.RequestRasterizeRemove(rasterizeId, &observer, false); + + // Ensure svg loader cache removed. + application.SendNotification(); + application.Render(); + application.SendNotification(); + application.Render(); + } + } + } + } + + END_TEST; +} + +int UtcSvgLoaderCacheLoadAndRasterize01(void) +{ + tet_infoline("Test Load and Rsterize cached well\n"); + + ToolkitTestApplication application; + + SvgLoader svgLoader; ///Create svg loader without visual factory cache. + + TestObserver observer1; + TestObserver observer2; + TestObserver observer3; + TestObserver observer4; + TestObserver observer5; + + auto loadId1 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer1, false); + auto loadId2 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer2, false); + + tet_printf("Test Load cached well\n"); + DALI_TEST_EQUALS(loadId1, loadId2, TEST_LOCATION); + + observer1.CheckLoadTest(false, false, TEST_LOCATION); + observer2.CheckLoadTest(false, false, TEST_LOCATION); + + // Wait async load complete 1 time : loadId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + // Check both observer1 and observer2 loaded. + observer1.CheckLoadTest(true, true, TEST_LOCATION); + observer2.CheckLoadTest(true, true, TEST_LOCATION); + + tet_printf("Test difference url and dpi return not equal id\n"); + auto loadId3 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI + 2.0f, &observer3, false); + auto loadId4 = svgLoader.Load(std::string(TEST_SVG_INVALID_RASTERIZE_FILE_NAME), DEFAULT_DPI, &observer4, false); + DALI_TEST_CHECK(loadId1 != loadId3); + DALI_TEST_CHECK(loadId1 != loadId4); + DALI_TEST_CHECK(loadId3 != loadId4); + + observer3.CheckLoadTest(false, false, TEST_LOCATION); + observer4.CheckLoadTest(false, false, TEST_LOCATION); + + // Wait async load complete 2 times : loadId3 loadId4 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION); + + observer3.CheckLoadTest(true, true, TEST_LOCATION); + observer4.CheckLoadTest(true, true, TEST_LOCATION); + + tet_printf("Test Load cached well even after load completed\n"); + auto loadId5 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer5, false); + + DALI_TEST_EQUALS(loadId1, loadId5, TEST_LOCATION); + // Check observer5 loaded. + observer5.CheckLoadTest(true, true, TEST_LOCATION); + + tet_printf("Request Rasterize\n"); + auto rasterizeId1 = svgLoader.Rasterize(loadId1, 100u, 100u, false, &observer1, false); + auto rasterizeId2 = svgLoader.Rasterize(loadId1, 100u, 100u, false, &observer2, false); + + tet_printf("Test Rasterize cached well\n"); + DALI_TEST_EQUALS(rasterizeId1, rasterizeId2, TEST_LOCATION); + + observer1.CheckRasterizeTest(false, false, TEST_LOCATION); + observer2.CheckRasterizeTest(false, false, TEST_LOCATION); + + // Wait async rasterize complete 1 time : rasterizeId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + // Check both observer1 and observer2 rasterized. + observer1.CheckRasterizeTest(true, true, TEST_LOCATION); + observer2.CheckRasterizeTest(true, true, TEST_LOCATION); + + tet_printf("Test difference loadId and size return not equal id\n"); + auto rasterizeId3 = svgLoader.Rasterize(loadId1, 200u, 200u, false, &observer3, false); + auto rasterizeId4 = svgLoader.Rasterize(loadId3, 100u, 100u, false, &observer4, false); + DALI_TEST_CHECK(rasterizeId1 != rasterizeId3); + DALI_TEST_CHECK(rasterizeId1 != rasterizeId4); + DALI_TEST_CHECK(rasterizeId3 != rasterizeId4); + + observer3.CheckRasterizeTest(false, false, TEST_LOCATION); + observer4.CheckRasterizeTest(false, false, TEST_LOCATION); + + // Wait async rasterize complete 2 times : rasterizeId3 and rasterizeId4 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION); + observer3.CheckRasterizeTest(true, true, TEST_LOCATION); + observer4.CheckRasterizeTest(true, true, TEST_LOCATION); + + tet_printf("Test Rasterize cached well even after rasterize completed\n"); + auto rasterizeId5 = svgLoader.Rasterize(loadId1, 100u, 100u, false, &observer5, false); + + DALI_TEST_EQUALS(rasterizeId1, rasterizeId5, TEST_LOCATION); + // Check observer5 loaded. + observer5.CheckRasterizeTest(true, true, TEST_LOCATION); + + END_TEST; +} + +int UtcSvgLoaderCacheLoadAndRasterize02(void) +{ + tet_infoline("Test Load removed during rasterize\n"); + + ToolkitTestApplication application; + + SvgLoader svgLoader; ///Create svg loader without visual factory cache. + + TestObserver observer1; + TestObserver observer2; + TestObserver observer3; + + tet_printf("load request for loadId1\n"); + auto loadId1 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer1, false); + + // Wait async load complete 1 time : loadId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + observer1.CheckLoadTest(true, true, TEST_LOCATION); + + tet_printf("Request Rasterize\n"); + auto rasterizeId1 = svgLoader.Rasterize(loadId1, 100u, 100u, false, &observer1, false); + + tet_printf("Remove loadId1 during rasterize execute\n"); + svgLoader.RequestLoadRemove(loadId1, &observer1); + + application.SendNotification(); + application.Render(); + + // Wait async rasterize complete 1 time : rasterizeId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + application.SendNotification(); + application.Render(); + + observer1.CheckRasterizeTest(true, true, TEST_LOCATION); + + auto loadId2 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer2, false); + + tet_printf("Test rasterize request increase the reference count of loadId1\n"); + + DALI_TEST_EQUALS(loadId1, loadId2, TEST_LOCATION); + // Check observer2 loaded. + observer2.CheckLoadTest(true, true, TEST_LOCATION); + + tet_printf("Remove loadId2 and rasterizeId1 synchronously\n"); + svgLoader.RequestLoadRemove(loadId2, &observer2); + + application.SendNotification(); + application.Render(); + + svgLoader.RequestRasterizeRemove(rasterizeId1, &observer1, true); + + tet_printf("Test loadId3 is not cached.\n"); + [[maybe_unused]] auto loadId3 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer3, false); + observer3.CheckLoadTest(false, false, TEST_LOCATION); + + // Wait async load complete 1 time : loadId3 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + observer3.CheckLoadTest(true, true, TEST_LOCATION); + + END_TEST; +} + +int UtcSvgLoaderCacheLoadAndRasterize03(void) +{ + tet_infoline("Test Load and Rsterize call synchronously during async cached call\n"); + + ToolkitTestApplication application; + + SvgLoader svgLoader; ///Create svg loader without visual factory cache. + + TestObserver observer1; + TestObserver observer2; + TestObserver observer3; + + tet_printf("Load request async / sync / and async again\n"); + auto loadId1 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer1, false); + auto loadId2 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer2, true); + auto loadId3 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer3, false); + + tet_printf("Test Load cached well\n"); + DALI_TEST_EQUALS(loadId1, loadId2, TEST_LOCATION); + DALI_TEST_EQUALS(loadId1, loadId3, TEST_LOCATION); + DALI_TEST_EQUALS(loadId2, loadId3, TEST_LOCATION); + + tet_printf("Test async observer didn't notify. (Sync load didn't notify other observers)\n"); + observer1.CheckLoadTest(false, false, TEST_LOCATION); + observer2.CheckLoadTest(true, true, TEST_LOCATION); + observer3.CheckLoadTest(true, true, TEST_LOCATION); + + // Wait async load complete 1 time : loadId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + // Check all observers loaded well. + observer1.CheckLoadTest(true, true, TEST_LOCATION); + observer2.CheckLoadTest(true, true, TEST_LOCATION); + observer3.CheckLoadTest(true, true, TEST_LOCATION); + + tet_printf("Request Rasterize async / sync / and async again\n"); + auto rasterizeId1 = svgLoader.Rasterize(loadId1, 100u, 100u, false, &observer1, false); + auto rasterizeId2 = svgLoader.Rasterize(loadId2, 100u, 100u, false, &observer2, true); + auto rasterizeId3 = svgLoader.Rasterize(loadId3, 100u, 100u, false, &observer3, false); + + tet_printf("Test Rasterize cached well\n"); + DALI_TEST_EQUALS(rasterizeId1, rasterizeId2, TEST_LOCATION); + DALI_TEST_EQUALS(rasterizeId1, rasterizeId3, TEST_LOCATION); + DALI_TEST_EQUALS(rasterizeId2, rasterizeId3, TEST_LOCATION); + + tet_printf("Test async observer didn't notify. (Sync load didn't notify other observers)\n"); + observer1.CheckRasterizeTest(false, false, TEST_LOCATION); + observer2.CheckRasterizeTest(true, true, TEST_LOCATION); + observer3.CheckRasterizeTest(true, true, TEST_LOCATION); + + // Wait async rasterize complete 1 time : rasterizeId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + // Check all observers rasterized well. + observer1.CheckRasterizeTest(true, true, TEST_LOCATION); + observer2.CheckRasterizeTest(true, true, TEST_LOCATION); + observer3.CheckRasterizeTest(true, true, TEST_LOCATION); + + END_TEST; +} + +int UtcSvgLoaderLoadCancel(void) +{ + tet_infoline("Test Load cancel well\n"); + + ToolkitTestApplication application; + + SvgLoader svgLoader; ///Create svg loader without visual factory cache. + + TestObserver observer1; + TestObserver observer2; + TestObserver observer3; + + auto loadId1 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer1, false); + + observer1.CheckLoadTest(false, false, TEST_LOCATION); + + svgLoader.RequestLoadRemove(loadId1, &observer1); + + application.SendNotification(); + application.Render(); + + // load task is not finished yet. + + // Wait async load complete 1 time : loadId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + tet_printf("load request for loadId1 not notify\n"); + observer1.CheckLoadTest(false, false, TEST_LOCATION); + + auto loadId2 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer2, false); + + observer2.CheckLoadTest(false, false, TEST_LOCATION); + + svgLoader.RequestLoadRemove(loadId2, &observer2); + + application.SendNotification(); + application.Render(); + + // load task is not finished yet. + // But during loading task running, request same item again + auto loadId3 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer3, false); + DALI_TEST_EQUALS(loadId2, loadId3, TEST_LOCATION); + + // Wait async load complete 1 time : loadId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + tet_printf("load request for loadId2 not notify, but loadId3 notify\n"); + observer2.CheckLoadTest(false, false, TEST_LOCATION); + observer3.CheckLoadTest(true, true, TEST_LOCATION); + + END_TEST; +} + +int UtcSvgLoaderDestructDuringObserver01(void) +{ + tet_infoline("Test destroy observer during load observer\n"); + + ToolkitTestApplication application; + + SvgLoader svgLoader; ///Create svg loader without visual factory cache. + + TestObserverWithCustomFunction* observer1 = new TestObserverWithCustomFunction(); + TestObserverWithCustomFunction* observer2 = new TestObserverWithCustomFunction(); + + struct CustomData + { + TestObserver* self{nullptr}; + TestObserver* other{nullptr}; + + bool loadCalled{false}; + bool loadSuccess{false}; + } mData; + + mData.self = observer1; + mData.other = observer2; + + observer1->mLoadData = &mData; + observer1->ConnectLoadFunction([](void* data) { + DALI_TEST_CHECK(data); + CustomData* customData = static_cast(data); + TestObserver* observer1 = customData->self; + TestObserver* observer2 = customData->other; + DALI_TEST_CHECK(observer1); + DALI_TEST_CHECK(observer2); + tet_printf("Destroy observer1 and observer2 (self)n"); + + customData->loadCalled = observer1->mLoadCalled; + customData->loadSuccess = observer1->mLoadSuccess; + + delete observer1; + delete observer2; + }); + + observer2->ConnectLoadFunction([](void* data) { + tet_printf("observer2 Should be destroyed by observer1. Test failed\n"); + + tet_result(TET_FAIL); + }); + tet_printf("load request for loadId1 and loadId2. observer1 should be called first.\n"); + auto loadId1 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, observer1, false); + auto loadId2 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, observer2, false); + DALI_TEST_EQUALS(loadId1, loadId2, TEST_LOCATION); + + observer1->CheckLoadTest(false, false, TEST_LOCATION); + observer2->CheckLoadTest(false, false, TEST_LOCATION); + + // Wait async load complete 1 time : loadId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + DALI_TEST_EQUALS(mData.loadCalled, true, TEST_LOCATION); + DALI_TEST_EQUALS(mData.loadSuccess, true, TEST_LOCATION); + + END_TEST; +} + +int UtcSvgLoaderDestructDuringObserver02(void) +{ + tet_infoline("Test destroy observer during rasterize observer\n"); + + ToolkitTestApplication application; + + SvgLoader svgLoader; ///Create svg loader without visual factory cache. + + TestObserverWithCustomFunction* observer1 = new TestObserverWithCustomFunction(); + TestObserverWithCustomFunction* observer2 = new TestObserverWithCustomFunction(); + + struct CustomData + { + TestObserver* self{nullptr}; + TestObserver* other{nullptr}; + + bool rasterizeCalled{false}; + bool rasterizeSuccess{false}; + } mData; + + mData.self = observer1; + mData.other = observer2; + + observer1->mRasterizeData = &mData; + observer1->ConnectRasterizeFunction([](void* data) { + DALI_TEST_CHECK(data); + CustomData* customData = static_cast(data); + TestObserver* observer1 = customData->self; + TestObserver* observer2 = customData->other; + DALI_TEST_CHECK(observer1); + DALI_TEST_CHECK(observer2); + tet_printf("Destroy observer1(self) and observer2\n"); + + customData->rasterizeCalled = observer1->mRasterizeCalled; + customData->rasterizeSuccess = observer1->mRasterizeSuccess; + + delete observer1; + delete observer2; + }); + + observer2->ConnectRasterizeFunction([](void* data) { + tet_printf("observer2 Should be destroyed by observer1. Test failed\n"); + + tet_result(TET_FAIL); + }); + tet_printf("load request for loadId1 and loadId2. observer1 should be called first.\n"); + auto loadId1 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, observer1, false); + auto loadId2 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, observer2, false); + DALI_TEST_EQUALS(loadId1, loadId2, TEST_LOCATION); + + observer1->CheckLoadTest(false, false, TEST_LOCATION); + observer2->CheckLoadTest(false, false, TEST_LOCATION); + + // Wait async load complete 1 time : loadId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + observer1->CheckLoadTest(true, true, TEST_LOCATION); + observer2->CheckLoadTest(true, true, TEST_LOCATION); + + auto rasterizeId1 = svgLoader.Rasterize(loadId1, 100u, 100u, false, observer1, false); + auto rasterizeId2 = svgLoader.Rasterize(loadId2, 100u, 100u, false, observer2, false); + DALI_TEST_EQUALS(rasterizeId1, rasterizeId2, TEST_LOCATION); + + observer1->CheckRasterizeTest(false, false, TEST_LOCATION); + observer2->CheckRasterizeTest(false, false, TEST_LOCATION); + + // Wait async rasterize complete 1 time : rasterizeId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + DALI_TEST_EQUALS(mData.rasterizeCalled, true, TEST_LOCATION); + DALI_TEST_EQUALS(mData.rasterizeSuccess, true, TEST_LOCATION); + + END_TEST; +} + +int UtcSvgLoaderReqestDuringObserver01(void) +{ + tet_infoline("Test request load observer during load observer\n"); + + ToolkitTestApplication application; + + SvgLoader svgLoader; ///Create svg loader without visual factory cache. + + TestObserverWithCustomFunction observer1; + TestObserver observer2; + TestObserver observer3; + TestObserver observer4; + TestObserver observer5; + TestObserver* observer6 = new TestObserver(); + + struct CustomData + { + TestObserver* self{nullptr}; + TestObserver* loadCached{nullptr}; + TestObserver* loadNonCached1{nullptr}; + TestObserver* loadNonCached2{nullptr}; + + TestObserver* loadAndRemove{nullptr}; + TestObserver* loadAndDestruct{nullptr}; + + SvgLoader::SvgLoadId cachedId{SvgLoader::INVALID_SVG_LOAD_ID}; + SvgLoader::SvgLoadId nonCachedId1{SvgLoader::INVALID_SVG_LOAD_ID}; + SvgLoader::SvgLoadId nonCachedId2{SvgLoader::INVALID_SVG_LOAD_ID}; + } mData; + + mData.self = &observer1; + mData.loadCached = &observer2; + mData.loadNonCached1 = &observer3; + mData.loadNonCached2 = &observer4; + mData.loadAndRemove = &observer5; + mData.loadAndDestruct = observer6; + + observer1.mLoadData = &mData; + observer1.ConnectLoadFunction([&svgLoader](void* data) { + DALI_TEST_CHECK(data); + CustomData* customData = static_cast(data); + TestObserver* observer1 = customData->self; + TestObserver* observer2 = customData->loadCached; + TestObserver* observer3 = customData->loadNonCached1; + TestObserver* observer4 = customData->loadNonCached2; + TestObserver* observer5 = customData->loadAndRemove; + TestObserver* observer6 = customData->loadAndDestruct; + DALI_TEST_CHECK(observer1); + DALI_TEST_CHECK(observer2); + DALI_TEST_CHECK(observer3); + DALI_TEST_CHECK(observer4); + DALI_TEST_CHECK(observer5); + DALI_TEST_CHECK(observer6); + + tet_printf("Request for observer2(cached) and observer3, observer4(non-cached)\n"); + customData->cachedId = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, observer2, false); + customData->nonCachedId1 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI + 2.0f, observer3, false); + customData->nonCachedId2 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI + 2.0f, observer4, false); + + tet_printf("Test observer2 still not notify yet even if it is cached\n"); + observer2->CheckLoadTest(false, false, TEST_LOCATION); + observer3->CheckLoadTest(false, false, TEST_LOCATION); + observer4->CheckLoadTest(false, false, TEST_LOCATION); + + tet_printf("Test observer5 load request and cancel\n"); + auto loadId = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI + 2.0f, observer5, false); + svgLoader.RequestLoadRemove(loadId, observer5); + + tet_printf("Test observer6 load request and destruct\n"); + loadId = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI + 2.0f, observer6, false); + delete observer6; + }); + + tet_printf("load request for loadId1.\n"); + auto loadId1 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer1, false); + + observer1.CheckLoadTest(false, false, TEST_LOCATION); + observer2.CheckLoadTest(false, false, TEST_LOCATION); + observer3.CheckLoadTest(false, false, TEST_LOCATION); + observer4.CheckLoadTest(false, false, TEST_LOCATION); + observer5.CheckLoadTest(false, false, TEST_LOCATION); + observer6->CheckLoadTest(false, false, TEST_LOCATION); + + // Wait async load complete 1 time : loadId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + observer1.CheckLoadTest(true, true, TEST_LOCATION); + + tet_printf("Test observer2 notify after observer1 notify finished\n"); + DALI_TEST_EQUALS(loadId1, mData.cachedId, TEST_LOCATION); + DALI_TEST_CHECK(loadId1 != mData.nonCachedId1); + DALI_TEST_EQUALS(mData.nonCachedId1, mData.nonCachedId2, TEST_LOCATION); + observer2.CheckLoadTest(true, true, TEST_LOCATION); + observer3.CheckLoadTest(false, false, TEST_LOCATION); + observer4.CheckLoadTest(false, false, TEST_LOCATION); + observer5.CheckLoadTest(false, false, TEST_LOCATION); + + // Wait async load complete 1 time : mData.nonCachedId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + tet_printf("Test observer5 not notify\n"); + observer3.CheckLoadTest(true, true, TEST_LOCATION); + observer4.CheckLoadTest(true, true, TEST_LOCATION); + observer5.CheckLoadTest(false, false, TEST_LOCATION); + + END_TEST; +} + +int UtcSvgLoaderReqestDuringObserver02(void) +{ + tet_infoline("Test request load observer during load observer\n"); + + ToolkitTestApplication application; + + SvgLoader svgLoader; ///Create svg loader without visual factory cache. + + TestObserverWithCustomFunction observer1; + TestObserver observer2; + TestObserver observer3; + TestObserver observer4; + + struct CustomData + { + TestObserver* self{nullptr}; + TestObserver* loadCached{nullptr}; + TestObserver* loadNonCached1{nullptr}; + TestObserver* loadNonCached2{nullptr}; + + SvgLoader::SvgLoadId cachedId{SvgLoader::INVALID_SVG_LOAD_ID}; + SvgLoader::SvgLoadId nonCachedId1{SvgLoader::INVALID_SVG_LOAD_ID}; + SvgLoader::SvgLoadId nonCachedId2{SvgLoader::INVALID_SVG_LOAD_ID}; + } mData; + + mData.self = &observer1; + mData.loadCached = &observer2; + mData.loadNonCached1 = &observer3; + mData.loadNonCached2 = &observer4; + + observer1.mLoadData = &mData; + observer1.ConnectLoadFunction([&svgLoader](void* data) { + DALI_TEST_CHECK(data); + CustomData* customData = static_cast(data); + TestObserver* observer1 = customData->self; + TestObserver* observer2 = customData->loadCached; + TestObserver* observer3 = customData->loadNonCached1; + TestObserver* observer4 = customData->loadNonCached2; + DALI_TEST_CHECK(observer1); + DALI_TEST_CHECK(observer2); + DALI_TEST_CHECK(observer3); + DALI_TEST_CHECK(observer4); + + tet_printf("Request for observer2(cached) and observer3, observer4(non-cached)\n"); + tet_printf("For here, let we request observer4 as sync!\n"); + customData->cachedId = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, observer2, false); + customData->nonCachedId1 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI + 2.0f, observer3, false); + customData->nonCachedId2 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI + 2.0f, observer4, true); + + tet_printf("Test observer2 still not notify yet even if it is cached\n"); + observer2->CheckLoadTest(false, false, TEST_LOCATION); + + tet_printf("Test observer4 notify, but observer3 yet\n"); + observer3->CheckLoadTest(false, false, TEST_LOCATION); + observer4->CheckLoadTest(true, true, TEST_LOCATION); + }); + + tet_printf("load request for loadId1.\n"); + auto loadId1 = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, &observer1, false); + + observer1.CheckLoadTest(false, false, TEST_LOCATION); + observer2.CheckLoadTest(false, false, TEST_LOCATION); + observer3.CheckLoadTest(false, false, TEST_LOCATION); + observer4.CheckLoadTest(false, false, TEST_LOCATION); + + // Wait async load complete 1 time : loadId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + observer1.CheckLoadTest(true, true, TEST_LOCATION); + + tet_printf("Test observer2 notify after observer1 notify finished\n"); + DALI_TEST_EQUALS(loadId1, mData.cachedId, TEST_LOCATION); + DALI_TEST_CHECK(loadId1 != mData.nonCachedId1); + DALI_TEST_EQUALS(mData.nonCachedId1, mData.nonCachedId2, TEST_LOCATION); + observer2.CheckLoadTest(true, true, TEST_LOCATION); + + tet_printf("Test observer3 notify due to we load it synchronously already\n"); + observer3.CheckLoadTest(true, true, TEST_LOCATION); + observer4.CheckLoadTest(true, true, TEST_LOCATION); + + END_TEST; +} + +int UtcSvgLoaderReqestDuringObserver03(void) +{ + tet_infoline("Test request rasterize observer during rasterize observer\n"); + + ToolkitTestApplication application; + + SvgLoader svgLoader; ///Create svg loader without visual factory cache. + + TestObserverWithCustomFunction observer1; + TestObserver observer2; + TestObserver observer3; + TestObserver observer4; + TestObserver observer5; + TestObserver* observer6 = new TestObserver(); + + struct CustomData + { + TestObserver* self{nullptr}; + TestObserver* rasterizeCached{nullptr}; + TestObserver* rasterizeNonCached1{nullptr}; + TestObserver* rasterizeNonCached2{nullptr}; + + TestObserver* rasterizeAndRemove{nullptr}; + TestObserver* rasterizeAndDestruct{nullptr}; + + SvgLoader::SvgRasterizeId cachedId{SvgLoader::INVALID_SVG_RASTERIZE_ID}; + SvgLoader::SvgRasterizeId nonCachedId1{SvgLoader::INVALID_SVG_RASTERIZE_ID}; + SvgLoader::SvgRasterizeId nonCachedId2{SvgLoader::INVALID_SVG_RASTERIZE_ID}; + } mData; + + mData.self = &observer1; + mData.rasterizeCached = &observer2; + mData.rasterizeNonCached1 = &observer3; + mData.rasterizeNonCached2 = &observer4; + mData.rasterizeAndRemove = &observer5; + mData.rasterizeAndDestruct = observer6; + + // Sync load and cache it. + auto loadId = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, nullptr, true); + + observer1.mRasterizeData = &mData; + observer1.ConnectRasterizeFunction([&svgLoader, &loadId](void* data) { + DALI_TEST_CHECK(data); + CustomData* customData = static_cast(data); + TestObserver* observer1 = customData->self; + TestObserver* observer2 = customData->rasterizeCached; + TestObserver* observer3 = customData->rasterizeNonCached1; + TestObserver* observer4 = customData->rasterizeNonCached2; + TestObserver* observer5 = customData->rasterizeAndRemove; + TestObserver* observer6 = customData->rasterizeAndDestruct; + DALI_TEST_CHECK(observer1); + DALI_TEST_CHECK(observer2); + DALI_TEST_CHECK(observer3); + DALI_TEST_CHECK(observer4); + DALI_TEST_CHECK(observer5); + DALI_TEST_CHECK(observer6); + + tet_printf("Request for observer2(cached) and observer3, observer4(non-cached)\n"); + customData->cachedId = svgLoader.Rasterize(loadId, 100u, 100u, false, observer2, false); + customData->nonCachedId1 = svgLoader.Rasterize(loadId, 200u, 200u, false, observer3, false); + customData->nonCachedId2 = svgLoader.Rasterize(loadId, 200u, 200u, false, observer4, false); + + tet_printf("Test observer2 still not notify yet even if it is cached\n"); + observer2->CheckRasterizeTest(false, false, TEST_LOCATION); + observer3->CheckRasterizeTest(false, false, TEST_LOCATION); + observer4->CheckRasterizeTest(false, false, TEST_LOCATION); + + tet_printf("Test observer5 rasterize request and cancel\n"); + auto rasterizeId = svgLoader.Rasterize(loadId, 200u, 200u, false, observer5, false); + svgLoader.RequestRasterizeRemove(rasterizeId, observer5, true); + + tet_printf("Test observer6 rasterize request and destruct\n"); + rasterizeId = svgLoader.Rasterize(loadId, 200u, 200u, false, observer6, false); + delete observer6; + }); + + tet_printf("rasterize request for rasterizeId1.\n"); + auto rasterizeId1 = svgLoader.Rasterize(loadId, 100u, 100u, false, &observer1, false); + + observer1.CheckRasterizeTest(false, false, TEST_LOCATION); + observer2.CheckRasterizeTest(false, false, TEST_LOCATION); + observer3.CheckRasterizeTest(false, false, TEST_LOCATION); + observer4.CheckRasterizeTest(false, false, TEST_LOCATION); + observer5.CheckRasterizeTest(false, false, TEST_LOCATION); + observer6->CheckRasterizeTest(false, false, TEST_LOCATION); + + // Wait async rasterize complete 1 time : rasterizeId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + observer1.CheckRasterizeTest(true, true, TEST_LOCATION); + + tet_printf("Test observer2 notify after observer1 notify finished\n"); + DALI_TEST_EQUALS(rasterizeId1, mData.cachedId, TEST_LOCATION); + DALI_TEST_CHECK(rasterizeId1 != mData.nonCachedId1); + DALI_TEST_EQUALS(mData.nonCachedId1, mData.nonCachedId2, TEST_LOCATION); + observer2.CheckRasterizeTest(true, true, TEST_LOCATION); + observer3.CheckRasterizeTest(false, false, TEST_LOCATION); + observer4.CheckRasterizeTest(false, false, TEST_LOCATION); + observer5.CheckRasterizeTest(false, false, TEST_LOCATION); + + // Wait async rasterize complete 1 time : mData.nonCachedId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + tet_printf("Test observer5 not notify\n"); + observer3.CheckRasterizeTest(true, true, TEST_LOCATION); + observer4.CheckRasterizeTest(true, true, TEST_LOCATION); + observer5.CheckRasterizeTest(false, false, TEST_LOCATION); + + END_TEST; +} + +int UtcSvgLoaderReqestDuringObserver04(void) +{ + tet_infoline("Test request rasterize observer during rasterize observer\n"); + + ToolkitTestApplication application; + + SvgLoader svgLoader; ///Create svg loader without visual factory cache. + + TestObserverWithCustomFunction observer1; + TestObserver observer2; + TestObserver observer3; + TestObserver observer4; + + struct CustomData + { + TestObserver* self{nullptr}; + TestObserver* rasterizeCached{nullptr}; + TestObserver* rasterizeNonCached1{nullptr}; + TestObserver* rasterizeNonCached2{nullptr}; + + SvgLoader::SvgRasterizeId cachedId{SvgLoader::INVALID_SVG_RASTERIZE_ID}; + SvgLoader::SvgRasterizeId nonCachedId1{SvgLoader::INVALID_SVG_RASTERIZE_ID}; + SvgLoader::SvgRasterizeId nonCachedId2{SvgLoader::INVALID_SVG_RASTERIZE_ID}; + } mData; + + mData.self = &observer1; + mData.rasterizeCached = &observer2; + mData.rasterizeNonCached1 = &observer3; + mData.rasterizeNonCached2 = &observer4; + + // Sync load and cache it. + auto loadId = svgLoader.Load(std::string(TEST_SVG_FILE_NAME), DEFAULT_DPI, nullptr, true); + + observer1.mRasterizeData = &mData; + observer1.ConnectRasterizeFunction([&svgLoader, &loadId](void* data) { + DALI_TEST_CHECK(data); + CustomData* customData = static_cast(data); + TestObserver* observer1 = customData->self; + TestObserver* observer2 = customData->rasterizeCached; + TestObserver* observer3 = customData->rasterizeNonCached1; + TestObserver* observer4 = customData->rasterizeNonCached2; + DALI_TEST_CHECK(observer1); + DALI_TEST_CHECK(observer2); + DALI_TEST_CHECK(observer3); + DALI_TEST_CHECK(observer4); + + tet_printf("Request for observer2(cached) and observer3, observer4(non-cached)\n"); + tet_printf("For here, let we request observer4 as sync!\n"); + customData->cachedId = svgLoader.Rasterize(loadId, 100u, 100u, false, observer2, false); + customData->nonCachedId1 = svgLoader.Rasterize(loadId, 200u, 200u, false, observer3, false); + customData->nonCachedId2 = svgLoader.Rasterize(loadId, 200u, 200u, false, observer4, true); + + tet_printf("Test observer2 still not notify yet even if it is cached\n"); + observer2->CheckRasterizeTest(false, false, TEST_LOCATION); + + tet_printf("Test observer4 notify, but observer3 yet\n"); + observer3->CheckRasterizeTest(false, false, TEST_LOCATION); + observer4->CheckRasterizeTest(true, true, TEST_LOCATION); + }); + + tet_printf("rasterize request for rasterizeId1.\n"); + auto rasterizeId1 = svgLoader.Rasterize(loadId, 100u, 100u, false, &observer1, false); + + observer1.CheckRasterizeTest(false, false, TEST_LOCATION); + observer2.CheckRasterizeTest(false, false, TEST_LOCATION); + observer3.CheckRasterizeTest(false, false, TEST_LOCATION); + observer4.CheckRasterizeTest(false, false, TEST_LOCATION); + + // Wait async rasterize complete 1 time : rasterizeId1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + observer1.CheckRasterizeTest(true, true, TEST_LOCATION); + tet_printf("Test observer2 notify after observer1 notify finished\n"); + DALI_TEST_EQUALS(rasterizeId1, mData.cachedId, TEST_LOCATION); + DALI_TEST_CHECK(rasterizeId1 != mData.nonCachedId1); + DALI_TEST_EQUALS(mData.nonCachedId1, mData.nonCachedId2, TEST_LOCATION); + observer2.CheckRasterizeTest(true, true, TEST_LOCATION); + + tet_printf("Test observer3 notify due to we load it synchronously already\n"); + observer3.CheckRasterizeTest(true, true, TEST_LOCATION); + observer4.CheckRasterizeTest(true, true, TEST_LOCATION); + + END_TEST; +} \ No newline at end of file diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp index ef92cad..c60d4ee 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp @@ -1184,15 +1184,16 @@ int UtcDaliImageViewSetImageUrl(void) END_TEST; } -bool gResourceReadySignalFired = false; -Vector3 gNaturalSize; +namespace +{ +bool gResourceReadySignalFired = false; void ResourceReadySignal(Control control) { gResourceReadySignalFired = true; } -void OnResourceReadySignalSVG(Control control) +void OnResourceReadySyncSVGLoading02(Control control) { // Check whether Image Visual transforms on ImageView::OnRelayout() Toolkit::Internal::Control& controlImpl = Toolkit::Internal::GetImplementation(control); @@ -1205,10 +1206,14 @@ void OnResourceReadySignalSVG(Control control) Property::Map* retMap = transformValue->GetMap(); DALI_TEST_CHECK(retMap); + auto size = retMap->Find(Visual::Transform::Property::SIZE)->Get(); + // Fitting mode is applied at this point. because we do FittingMode in control - DALI_TEST_EQUALS(retMap->Find(Visual::Transform::Property::SIZE)->Get(), Vector2::ONE, TEST_LOCATION); + DALI_TEST_EQUALS(size, Vector2(100.0f, 100.0f), TEST_LOCATION); } +} // namespace + int UtcDaliImageViewCheckResourceReady(void) { ToolkitTestApplication application; @@ -1499,10 +1504,15 @@ int UtcDaliImageViewReloadAlphaMaskImage(void) END_TEST; } +namespace +{ +Vector3 gNaturalSize; + void OnRelayoutOverride(Size size) { gNaturalSize = size; // Size Relayout is using } +} // namespace int UtcDaliImageViewReplaceImageAndGetNaturalSize(void) { @@ -2922,8 +2932,17 @@ int UtcDaliImageViewLoadRemoteSVG(void) application.Render(); DALI_TEST_EQUALS(imageView.GetRendererCount(), 1u, TEST_LOCATION); + + imageView.Unparent(); } + // Insure to Remove svg cache. + application.SendNotification(); + application.Render(); + application.RunIdles(); + application.SendNotification(); + application.Render(); + // Without size set { Toolkit::ImageView imageView; @@ -2948,6 +2967,8 @@ int UtcDaliImageViewLoadRemoteSVG(void) application.Render(); DALI_TEST_EQUALS(imageView.GetRendererCount(), 1u, TEST_LOCATION); + + imageView.Unparent(); } END_TEST; @@ -3015,6 +3036,7 @@ int UtcDaliImageViewSyncSVGLoading02(void) tet_infoline("ImageView Testing SVG image sync loading"); + for(int testCase = 0; testCase < 3; ++testCase) { ImageView imageView = ImageView::New(); @@ -3025,7 +3047,28 @@ int UtcDaliImageViewSyncSVGLoading02(void) syncLoadingMap.Insert(Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING, true); syncLoadingMap.Insert(DevelVisual::Property::VISUAL_FITTING_MODE, Toolkit::DevelVisual::FIT_KEEP_ASPECT_RATIO); imageView.SetProperty(ImageView::Property::IMAGE, syncLoadingMap); - imageView.ResourceReadySignal().Connect(&OnResourceReadySignalSVG); + switch(testCase) + { + case 0: + default: + { + tet_printf("Case 0 : Do not set size (size is 0, 0)\n"); + break; + } + case 1: + { + tet_printf("Case 1 : Width is bigger than height (size is 200, 100)\n"); + imageView.SetProperty(Actor::Property::SIZE, Vector2(200.0f, 100.0f)); + break; + } + case 2: + { + tet_printf("Case 2 : Height is bigger than width (size is 100, 200)\n"); + imageView.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 200.0f)); + break; + } + } + imageView.ResourceReadySignal().Connect(&OnResourceReadySyncSVGLoading02); application.GetScene().Add(imageView); DALI_TEST_CHECK(imageView); @@ -3050,6 +3093,14 @@ int UtcDaliImageViewSyncSVGLoading02(void) Vector3 naturalSize = imageView.GetNaturalSize(); DALI_TEST_EQUALS(naturalSize.width, 100.0f, TEST_LOCATION); DALI_TEST_EQUALS(naturalSize.height, 100.0f, TEST_LOCATION); + + imageView.Unparent(); + + // Ensure remove cache. + application.SendNotification(); + application.Render(); + application.SendNotification(); + application.Render(); } END_TEST; } @@ -3087,6 +3138,57 @@ int UtcDaliImageViewAsyncSVGLoading(void) END_TEST; } +int UtcDaliImageViewAsyncSVGLoading02(void) +{ + ToolkitTestApplication application; + + tet_infoline("ImageView Testing SVG image async loading and the loaded result check cached"); + + { + ImageView imageView = ImageView::New(); + DALI_TEST_CHECK(imageView); + + // Async loading is used - default value of SYNCHRONOUS_LOADING is false. + Property::Map propertyMap; + propertyMap.Insert(Toolkit::Visual::Property::TYPE, Toolkit::Visual::IMAGE); + propertyMap.Insert(Toolkit::ImageVisual::Property::URL, TEST_RESOURCE_DIR "/svg1.svg"); + imageView.SetProperty(ImageView::Property::IMAGE, propertyMap); + + application.GetScene().Add(imageView); + + // Check that natural size return invalid values now + // Note : This logic might be changed if we decide to decode the svg synchronously. + Vector3 naturalSize = imageView.GetNaturalSize(); + DALI_TEST_NOT_EQUALS(naturalSize.width, 100.0f, 0.01f, TEST_LOCATION); + DALI_TEST_NOT_EQUALS(naturalSize.height, 100.0f, 0.01f, TEST_LOCATION); + + application.SendNotification(); + + // Wait for loading + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + application.SendNotification(); + application.Render(16); + + naturalSize = imageView.GetNaturalSize(); + DALI_TEST_EQUALS(naturalSize.width, 100.0f, TEST_LOCATION); + DALI_TEST_EQUALS(naturalSize.height, 100.0f, TEST_LOCATION); + + // Test that imageView2 use cached natrual size. + ImageView imageView2 = ImageView::New(); + DALI_TEST_CHECK(imageView2); + + // Use same property map with imageView + imageView2.SetProperty(ImageView::Property::IMAGE, propertyMap); + + // Check whether natural size is same as cached image size. + naturalSize = imageView2.GetNaturalSize(); + DALI_TEST_EQUALS(naturalSize.width, 100.0f, TEST_LOCATION); + DALI_TEST_EQUALS(naturalSize.height, 100.0f, TEST_LOCATION); + } + END_TEST; +} + int UtcDaliImageViewSVGLoadingSyncSetInvalidValue(void) { ToolkitTestApplication application; @@ -3412,9 +3514,12 @@ int UtcDaliImageViewSvgAtlasing(void) propertyMap["url"] = TEST_SVG_FILE_NAME; propertyMap["atlasing"] = true; + gResourceReadySignalFired = false; + ImageView imageView = ImageView::New(); imageView.SetProperty(ImageView::Property::IMAGE, propertyMap); imageView.SetProperty(Actor::Property::SIZE, Vector2(100.f, 100.f)); + imageView.ResourceReadySignal().Connect(&ResourceReadySignal); application.GetScene().Add(imageView); application.SendNotification(); @@ -3422,6 +3527,8 @@ int UtcDaliImageViewSvgAtlasing(void) // Wait for loading & rasterization DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION); + DALI_TEST_CHECK(gResourceReadySignalFired); + application.SendNotification(); application.Render(16); @@ -3431,15 +3538,70 @@ int UtcDaliImageViewSvgAtlasing(void) params1["height"] << 100; DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexSubImage2D", params1), true, TEST_LOCATION); - imageView.SetProperty(Actor::Property::SIZE, Vector2(600.f, 600.f)); + callStack.Reset(); + + gResourceReadySignalFired = false; + + // Also use new image view with atlas. + ImageView imageView2 = ImageView::New(); + imageView2.SetProperty(ImageView::Property::IMAGE, propertyMap); + imageView2.SetProperty(Actor::Property::SIZE, Vector2(100.f, 100.f)); + imageView2.ResourceReadySignal().Connect(&ResourceReadySignal); + application.GetScene().Add(imageView2); application.SendNotification(); - // Wait for rasterization + // Let we check that we use cached image, and cached texture. + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1, 0), false, TEST_LOCATION); + + DALI_TEST_CHECK(gResourceReadySignalFired); + + application.SendNotification(); + application.Render(16); + + // Check there is no newly generated texture + DALI_TEST_EQUALS(callStack.CountMethod("GenTextures"), 0, TEST_LOCATION); + DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexSubImage2D", params1), false, TEST_LOCATION); + + callStack.Reset(); + + gResourceReadySignalFired = false; + + // Also use new image view 'without'' atlas. + propertyMap["atlasing"] = false; + ImageView imageView3 = ImageView::New(); + imageView3.SetProperty(ImageView::Property::IMAGE, propertyMap); + imageView3.SetProperty(Actor::Property::SIZE, Vector2(100.f, 100.f)); + imageView3.ResourceReadySignal().Connect(&ResourceReadySignal); + application.GetScene().Add(imageView3); + + application.SendNotification(); + + // Let we check that we use cached image, but not cached texture. + // Wait rasterize. DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + DALI_TEST_CHECK(gResourceReadySignalFired); + + application.SendNotification(); + application.Render(16); + + // Check that we generate new texture + DALI_TEST_EQUALS(callStack.CountMethod("GenTextures"), 1, TEST_LOCATION); + DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexImage2D", params1), true, TEST_LOCATION); + callStack.Reset(); + gResourceReadySignalFired = false; + + // Try to atlas over the size. + imageView.SetProperty(Actor::Property::SIZE, Vector2(600.f, 600.f)); + + application.SendNotification(); + + // Wait for rasterization + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + application.SendNotification(); application.Render(16); @@ -3449,6 +3611,24 @@ int UtcDaliImageViewSvgAtlasing(void) params2["height"] << 600; DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexImage2D", params2), true, TEST_LOCATION); + callStack.Reset(); + + // Try to load over the size. + // Note that imageView3's atlas attempt is false. + imageView3.SetProperty(Actor::Property::SIZE, Vector2(600.f, 600.f)); + + // Let we check atlas cached. + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1, 0), false, TEST_LOCATION); + + application.SendNotification(); + application.Render(16); + + // Check there is no newly generated texture + DALI_TEST_EQUALS(callStack.CountMethod("GenTextures"), 0, TEST_LOCATION); + DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexSubImage2D", params2), false, TEST_LOCATION); + + gResourceReadySignalFired = false; + END_TEST; } @@ -5927,4 +6107,222 @@ int UtcDaliImageViewImageLoadSuccessAndReload01(void) gResourceReadySignalFired = false; END_TEST; +} + +namespace +{ +int gSvgReRasterizeDuringResourceReadyOrderType = 0; +void OnResourceReadyReRasterize01(Control control) +{ + if(gResourceReadySignalCounter == 0u) + { + auto parent = gImageView1.GetParent(); + DALI_TEST_CHECK(parent); + + tet_printf("Request gImageView2 and gImageView3 as 300x300. gImageView4 as 400x400\n"); + + gImageView2 = ImageView::New(TEST_SVG_FILE_NAME); + gImageView2.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal); + gImageView2.SetProperty(Actor::Property::SIZE, Vector2(300.f, 300.f)); + parent.Add(gImageView2); + gImageView3 = ImageView::New(TEST_SVG_FILE_NAME); + gImageView3.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal); + gImageView3.SetProperty(Actor::Property::SIZE, Vector2(300.f, 300.f)); + parent.Add(gImageView3); + gImageView4 = ImageView::New(TEST_SVG_FILE_NAME); + gImageView4.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal); + gImageView4.SetProperty(Actor::Property::SIZE, Vector2(400.f, 400.f)); + + Property::Map map; + map.Add(Visual::Property::TYPE, Visual::IMAGE); + map.Add(ImageVisual::Property::URL, TEST_SVG_FILE_NAME); + map.Add(ImageVisual::Property::SYNCHRONOUS_LOADING, true); + + gImageView1.Unparent(); + + gImageView1.SetProperty(Actor::Property::SIZE, Vector2(400.f, 400.f)); + gImageView1.SetProperty(ImageView::Property::IMAGE, map); + + if((gSvgReRasterizeDuringResourceReadyOrderType & 2) == 0) + { + tet_printf("Make 400x400 image raterized as synchronously\n"); + // Add gImageView1 now. + parent.Add(gImageView1); + } + else + { + tet_printf("Make 400x400 image raterized as synchronously, but after some async rasterization request first.\n"); + } + + if((gSvgReRasterizeDuringResourceReadyOrderType & 1) == 0) + { + // Add gImageView4 now + parent.Add(gImageView4); + } + } + else if(gResourceReadySignalCounter > 0 && ((gSvgReRasterizeDuringResourceReadyOrderType & 1) == 1)) + { + auto parent = gImageView2.GetParent(); + DALI_TEST_CHECK(parent); + + // Add gImageView4 after second phase of gImageView1 load done. + parent.Add(gImageView4); + } + ++gResourceReadySignalCounter; +} + +} // namespace + +int UtcDaliImageViewSvgReRasterizeDuringResourceReady01(void) +{ + for(int useInvalidSvg = 0; useInvalidSvg < 2; ++useInvalidSvg) + { + for(gSvgReRasterizeDuringResourceReadyOrderType = 0; gSvgReRasterizeDuringResourceReadyOrderType < 4; ++gSvgReRasterizeDuringResourceReadyOrderType) + { + ToolkitTestApplication application; + + tet_infoline("Test SVG image rasterize and re-rasterize at ResourceReady callback.\n"); + if(useInvalidSvg == 1) + { + tet_infoline("But in this case, we will use invalid svg file. So broken image used.\n"); + } + tet_printf("order type : %d\n", gSvgReRasterizeDuringResourceReadyOrderType); + + TestGlAbstraction& gl = application.GetGlAbstraction(); + TraceCallStack& textureTrace = gl.GetTextureTrace(); + textureTrace.Enable(true); + + // Clear image view for clear test + if(gImageView1) + { + gImageView1.Reset(); + } + if(gImageView2) + { + gImageView2.Reset(); + } + if(gImageView3) + { + gImageView3.Reset(); + } + if(gImageView4) + { + gImageView4.Reset(); + } + gResourceReadySignalCounter = 0u; + + gImageView1 = ImageView::New(useInvalidSvg == 1 ? TEST_RESOURCE_DIR "/invalid.svg" : TEST_SVG_FILE_NAME); + gImageView1.SetProperty(Actor::Property::SIZE, Vector2(200.f, 200.f)); + gImageView1.ResourceReadySignal().Connect(&OnResourceReadyReRasterize01); + application.GetScene().Add(gImageView1); + + DALI_TEST_EQUALS(gResourceReadySignalCounter, 0u, TEST_LOCATION); + + application.SendNotification(); + application.Render(16); + + if(useInvalidSvg == 1) + { + // load invalid svg file. It will emit ResourceReady for gImageView1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + DALI_TEST_EQUALS(gResourceReadySignalCounter, 1u, TEST_LOCATION); + } + else + { + // load svg file. It will not emit ResourceReady yet + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + DALI_TEST_EQUALS(gResourceReadySignalCounter, 0u, TEST_LOCATION); + + application.SendNotification(); + application.Render(16); + DALI_TEST_EQUALS(gResourceReadySignalCounter, 0u, TEST_LOCATION); + + // rasterize svg 200x200. Now, ResourceReady signal will be emitted for gImageView1 + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + DALI_TEST_EQUALS(gResourceReadySignalCounter, 1u, TEST_LOCATION); + } + + application.SendNotification(); + application.Render(16); + if((gSvgReRasterizeDuringResourceReadyOrderType & 2) == 2) + { + // Add gImageView1 now, to avoid sync load & rasterize request always before async. + application.GetScene().Add(gImageView1); + + application.SendNotification(); + application.Render(16); + } + // ResourceReady signal will be emitted for gImageView1 (sync load case), or gImageView4 (if gImageView1 rasterize first.) + // Note : We cannot assume that gImageView1 rasterize requested before gImageView4. + // So, we cannot ensure that gResourceReadySignalCounter is which 2 or 3. + DALI_TEST_GREATER(gResourceReadySignalCounter, 1, TEST_LOCATION); + DALI_TEST_GREATER(4, gResourceReadySignalCounter, TEST_LOCATION); + + application.SendNotification(); + application.Render(16); + + { + TraceCallStack::NamedParams params; + params["width"] << 200; + params["height"] << 200; + DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), useInvalidSvg == 0, TEST_LOCATION); + params.mParams.clear(); + params["width"] << 300; + params["height"] << 300; + DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), false, TEST_LOCATION); + params.mParams.clear(); + params["width"] << 400; + params["height"] << 400; + DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), true, TEST_LOCATION); + } + textureTrace.Reset(); + + // Total event trigger count is 2 or 1 : rasterize svg 300x300 + (rasterize svg 400x400 if required) + (load valid svg file if required) + // Now, ResourceReady signal will be emitted for gImageView2 and gImageView3 (and gImageView4 if required). + // Note : We cannot assume that which signal will be come first / rasterization 300x300 vs 400x400. + uint32_t eventTriggerRequiredCount = gResourceReadySignalCounter == 2 ? (gSvgReRasterizeDuringResourceReadyOrderType == 3 ? 3 : 2) : 1; // Check whether we need to wait 400x400 + 300x300, or only 300x300. + + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(eventTriggerRequiredCount), true, TEST_LOCATION); + DALI_TEST_EQUALS(gResourceReadySignalCounter, 5u, TEST_LOCATION); + + application.SendNotification(); + application.Render(16); + + { + TraceCallStack::NamedParams params; + params["width"] << 200; + params["height"] << 200; + DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), false, TEST_LOCATION); + params.mParams.clear(); + params["width"] << 300; + params["height"] << 300; + DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), true, TEST_LOCATION); + params.mParams.clear(); + params["width"] << 400; + params["height"] << 400; + DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), false, TEST_LOCATION); + } + + // Clear image view for clear test + if(gImageView1) + { + gImageView1.Reset(); + } + if(gImageView2) + { + gImageView2.Reset(); + } + if(gImageView3) + { + gImageView3.Reset(); + } + if(gImageView4) + { + gImageView4.Reset(); + } + gResourceReadySignalCounter = 0u; + } + } + + END_TEST; } \ No newline at end of file diff --git a/automated-tests/src/dali-toolkit/utc-Dali-SvgVisual.cpp b/automated-tests/src/dali-toolkit/utc-Dali-SvgVisual.cpp index 48fd4ce..2bd9077 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-SvgVisual.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-SvgVisual.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 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. @@ -88,3 +88,93 @@ int UtcDaliSvgVisualChageSize(void) END_TEST; } + +int UtcDaliSvgVisualSvgCacheFileAndRasterizedTexture(void) +{ + tet_infoline("Test rasterized texture cached"); + + ToolkitTestApplication application; + + TraceCallStack& textureTrace = application.GetGlAbstraction().GetTextureTrace(); + textureTrace.Enable(true); + + Visual::Base visual1 = VisualFactory::Get().CreateVisual(Property::Map().Add(ImageVisual::Property::URL, TEST_SVG_FILE_NAME)); + DALI_TEST_CHECK(visual1); + Visual::Base visual2 = VisualFactory::Get().CreateVisual(Property::Map().Add(ImageVisual::Property::URL, TEST_SVG_FILE_NAME)); + DALI_TEST_CHECK(visual2); + Visual::Base visual3 = VisualFactory::Get().CreateVisual(Property::Map().Add(ImageVisual::Property::URL, TEST_SVG_FILE_NAME)); + DALI_TEST_CHECK(visual3); + + DummyControl control1 = DummyControl::New(); + DummyControlImpl& dummyImpl1 = static_cast(control1.GetImplementation()); + dummyImpl1.RegisterVisual(DummyControl::Property::TEST_VISUAL, visual1); + + DummyControl control2 = DummyControl::New(); + DummyControlImpl& dummyImpl2 = static_cast(control2.GetImplementation()); + dummyImpl2.RegisterVisual(DummyControl::Property::TEST_VISUAL, visual2); + + DummyControl control3 = DummyControl::New(); + DummyControlImpl& dummyImpl3 = static_cast(control3.GetImplementation()); + dummyImpl3.RegisterVisual(DummyControl::Property::TEST_VISUAL, visual3); + + application.SendNotification(); + + // Wait for loading only one time + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION); + + Vector2 size1(100.0f, 100.0f); + Vector2 size2(300.0f, 300.0f); + + tet_printf("Rasterize control1 and control3 as 100x100, control2 as 300x300\n"); + + control1.SetProperty(Actor::Property::SIZE, size1); + application.GetScene().Add(control1); + control2.SetProperty(Actor::Property::SIZE, size2); + application.GetScene().Add(control2); + control3.SetProperty(Actor::Property::SIZE, size1); + application.GetScene().Add(control3); + + visual1.SetTransformAndSize(Property::Map(), size1); + visual2.SetTransformAndSize(Property::Map(), size2); + visual3.SetTransformAndSize(Property::Map(), size1); + + // Wait for rasterization + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION); + + application.SendNotification(); + application.Render(); + + // Check we upload only 2 textures. + TraceCallStack::NamedParams params; + params["width"] << 100; + params["height"] << 100; + DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), true, TEST_LOCATION); + params.mParams.clear(); + params["width"] << 300; + params["height"] << 300; + DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), true, TEST_LOCATION); + + application.SendNotification(); + application.Render(); + + textureTrace.Reset(); + + tet_printf("Change control3 size from 100x100, to 300x300\n"); + + control3.SetProperty(Actor::Property::SIZE, size2); + visual3.SetTransformAndSize(Property::Map(), size2); + + application.SendNotification(); + application.Render(); + + // Check we don't doing any additional rasterization + DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1, 0), false, TEST_LOCATION); + + application.SendNotification(); + application.Render(); + + // Check we don't upload any additional texture upload + DALI_TEST_EQUALS(textureTrace.CountMethod("GenTextures"), 0, TEST_LOCATION); + + END_TEST; +} \ No newline at end of file diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 661ccae..e4603d9 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -51,6 +51,8 @@ SET( toolkit_src_files ${toolkit_src_dir}/visuals/npatch/npatch-loader.cpp ${toolkit_src_dir}/visuals/npatch/npatch-visual.cpp ${toolkit_src_dir}/visuals/primitive/primitive-visual.cpp + ${toolkit_src_dir}/visuals/svg/svg-loader-observer.cpp + ${toolkit_src_dir}/visuals/svg/svg-loader.cpp ${toolkit_src_dir}/visuals/svg/svg-task.cpp ${toolkit_src_dir}/visuals/svg/svg-visual.cpp ${toolkit_src_dir}/visuals/text/text-visual-shader-factory.cpp diff --git a/dali-toolkit/internal/visuals/image/image-visual.cpp b/dali-toolkit/internal/visuals/image/image-visual.cpp index 5205354..147e617 100644 --- a/dali-toolkit/internal/visuals/image/image-visual.cpp +++ b/dali-toolkit/internal/visuals/image/image-visual.cpp @@ -737,7 +737,7 @@ void ImageVisual::LoadTexture(bool& atlasing, Vector4& atlasRect, TextureSet& te } } -bool ImageVisual::AttemptAtlasing() +bool ImageVisual::AttemptAtlasing() const { return (!mImpl->mCustomShader && (mImageUrl.IsLocalResource() || mImageUrl.IsBufferResource()) && mAttemptAtlasing); } diff --git a/dali-toolkit/internal/visuals/image/image-visual.h b/dali-toolkit/internal/visuals/image/image-visual.h index 5680d61..445218b 100644 --- a/dali-toolkit/internal/visuals/image/image-visual.h +++ b/dali-toolkit/internal/visuals/image/image-visual.h @@ -284,7 +284,7 @@ private: * @brief Checks if atlasing should be attempted * @return bool returns true if atlasing can be attempted. */ - bool AttemptAtlasing(); + bool AttemptAtlasing() const; /** * @brief Initializes the Dali::Renderer from the image url diff --git a/dali-toolkit/internal/visuals/npatch/npatch-loader.h b/dali-toolkit/internal/visuals/npatch/npatch-loader.h index fd1a4fa..08948b0 100644 --- a/dali-toolkit/internal/visuals/npatch/npatch-loader.h +++ b/dali-toolkit/internal/visuals/npatch/npatch-loader.h @@ -153,7 +153,7 @@ private: NPatchInfo& operator=(const NPatchInfo& info) = delete; // Do not use copy assign NPatchDataPtr mData; - std::int16_t mReferenceCount; ///< The number of N-patch visuals that use this data. + int32_t mReferenceCount; ///< The number of N-patch visuals that use this data. }; /** diff --git a/dali-toolkit/internal/visuals/svg/svg-loader-observer.cpp b/dali-toolkit/internal/visuals/svg/svg-loader-observer.cpp new file mode 100644 index 0000000..71ceb1b --- /dev/null +++ b/dali-toolkit/internal/visuals/svg/svg-loader-observer.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include + +namespace Dali::Toolkit::Internal +{ +SvgLoaderObserver::~SvgLoaderObserver() +{ + if(!mLoadDestructionSignal.Empty()) + { + mLoadDestructionSignal.Emit(this); + } + if(!mRasterizeDestructionSignal.Empty()) + { + mRasterizeDestructionSignal.Emit(this); + } +} +} // namespace Dali::Toolkit::Internal diff --git a/dali-toolkit/internal/visuals/svg/svg-loader-observer.h b/dali-toolkit/internal/visuals/svg/svg-loader-observer.h new file mode 100644 index 0000000..869b4a3 --- /dev/null +++ b/dali-toolkit/internal/visuals/svg/svg-loader-observer.h @@ -0,0 +1,102 @@ +#ifndef DALI_TOOLKIT_SVG_UPLOAD_OBSERVER_H +#define DALI_TOOLKIT_SVG_UPLOAD_OBSERVER_H + +/* + * Copyright (c) 2024 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// EXTERNAL INCLUDES +#include +#include + +// INTERNAL INCLUDES + +namespace Dali +{ +namespace Toolkit +{ +namespace Internal +{ +/** + * @brief Base class used to observe the load / rasterize status of a svg. + */ +class SvgLoaderObserver +{ +public: + typedef Signal DestructionSignalType; ///< Signal prototype for the Destruction Signal. + +public: + /** + * @brief Constructor. + */ + SvgLoaderObserver() = default; + + /** + * @brief Virtual destructor. + */ + virtual ~SvgLoaderObserver(); + + /** + * @brief Returns the destruction signal for load. + * This is emitted when the observer is destroyed. + * This is used by the observer notifier to mark this observer as destroyed (IE. It no longer needs notifying). + */ + DestructionSignalType& LoadDestructionSignal() + { + return mLoadDestructionSignal; + } + + /** + * @brief Returns the destruction signal for rasterize. + * This is emitted when the observer is destroyed. + * This is used by the observer notifier to mark this observer as destroyed (IE. It no longer needs notifying). + */ + DestructionSignalType& RasterizeDestructionSignal() + { + return mRasterizeDestructionSignal; + } + +public: + /** + * The action to be taken once the async load has finished. + * This should be overridden by the deriving class. + * + * @param[in] loadId Id of load request. + * @param[in] vectorImageRenderer Renderer class for svg image. It could be empty handle if rasterize failed. + */ + virtual void LoadComplete(int32_t loadId, Dali::VectorImageRenderer vectorImageRenderer) = 0; + + /** + * The action to be taken once the async rasterize has finished. + * This should be overridden by the deriving class. + * + * @param[in] rasterizeId Id of rasterize request. + * @param[in] textureSet Rasterize texture set. It could be empty handle if rasterize failed. + * @param[in] atlasRect The atlas rect of the rasterized image. + */ + virtual void RasterizeComplete(int32_t rasterizeId, Dali::TextureSet textureSet, Vector4 atlasRect) = 0; + +private: + DestructionSignalType mLoadDestructionSignal; ///< The destruction signal emitted when the observer is destroyed. + DestructionSignalType mRasterizeDestructionSignal; ///< The destruction signal emitted when the observer is destroyed. +}; + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_SVG_UPLOAD_OBSERVER_H diff --git a/dali-toolkit/internal/visuals/svg/svg-loader.cpp b/dali-toolkit/internal/visuals/svg/svg-loader.cpp new file mode 100644 index 0000000..4a3ea1c --- /dev/null +++ b/dali-toolkit/internal/visuals/svg/svg-loader.cpp @@ -0,0 +1,1242 @@ +/* + * Copyright (c) 2024 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include + +// INTERNAL HEADERS +#include ///< for EncodedImageBuffer +#include +#include +#include +#include + +// EXTERNAL HEADERS +#include +#include +#include +#include +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace Internal +{ +namespace +{ +DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_IMAGE_PERFORMANCE_MARKER, false); + +#ifdef DEBUG_ENABLED +Debug::Filter* gSvgLoaderLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_SVG_LOADER"); + +// clang-format off +#define GET_LOAD_STATE_STRING(loadState) \ + loadState == SvgLoader::LoadState::NOT_STARTED ? "NOT_STARTED" : \ + loadState == SvgLoader::LoadState::LOADING ? "LOADING" : \ + loadState == SvgLoader::LoadState::LOAD_FINISHED ? "LOAD_FINISHED" : \ + loadState == SvgLoader::LoadState::CANCELLED ? "CANCELLED" : \ + loadState == SvgLoader::LoadState::LOAD_FAILED ? "LOAD_FAILED" : \ + "Unknown" + +#define GET_RASTERIZE_STATE_STRING(rasterizeState) \ + rasterizeState == SvgLoader::RasterizeState::NOT_STARTED ? "NOT_STARTED" : \ + rasterizeState == SvgLoader::RasterizeState::RASTERIZING ? "RASTERIZING" : \ + rasterizeState == SvgLoader::RasterizeState::UPLOADED ? "UPLOADED" : \ + rasterizeState == SvgLoader::RasterizeState::UPLOAD_FAILED ? "UPLOAD_FAILED" : \ + "Unknown" +// clang-format on +#endif + +constexpr Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f); + +/** + * @brief Helper function to set rasterize info from PixelData. + * + * @param[in] visualFactoryCache The visual factory cache. It will be used to get atlas manager faster. + * @param[in] pixelData The rasterized pixel data. + * @param[out] rasterizeInfo The rasterize info. + */ +void SetTextureSetToRasterizeInfo(VisualFactoryCache* visualFactoryCache, const Dali::PixelData rasterizedPixelData, SvgLoader::SvgRasterizeInfo& rasterizeInfo) +{ + rasterizeInfo.mAtlasAttempted = false; + if(rasterizeInfo.mAttemptAtlasing) + { + // Try to use atlas attempt + if(DALI_LIKELY(Dali::Adaptor::IsAvailable() && visualFactoryCache)) + { + auto atlasManager = visualFactoryCache->GetAtlasManager(); + if(atlasManager) + { + Vector4 atlasRect; + auto textureSet = atlasManager->Add(atlasRect, rasterizedPixelData); + if(textureSet) // atlasing + { + rasterizeInfo.mTextureSet = textureSet; + rasterizeInfo.mAtlasRect = atlasRect; + rasterizeInfo.mAtlasAttempted = true; + + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, " rasterizeId:%d atlasAttempted:%d atlasRect:(%f %f %f %f)\n", rasterizeInfo.mId, rasterizeInfo.mAtlasAttempted, rasterizeInfo.mAtlasRect.x, rasterizeInfo.mAtlasRect.y, rasterizeInfo.mAtlasRect.z, rasterizeInfo.mAtlasRect.w); + } + } + } + } + + if(!rasterizeInfo.mAtlasAttempted) + { + // Atlas failed. Convert pixelData to texture. + Dali::Texture texture = Texture::New(Dali::TextureType::TEXTURE_2D, Pixel::RGBA8888, rasterizedPixelData.GetWidth(), rasterizedPixelData.GetHeight()); + texture.Upload(rasterizedPixelData); + + rasterizeInfo.mTextureSet = TextureSet::New(); + rasterizeInfo.mTextureSet.SetTexture(0u, texture); + + rasterizeInfo.mAtlasRect = FULL_TEXTURE_RECT; + + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, " rasterizeId:%d atlasAttempted:%d rasterizedPixelSize:(%ux%u)\n", rasterizeInfo.mId, rasterizeInfo.mAtlasAttempted, rasterizedPixelData.GetWidth(), rasterizedPixelData.GetHeight()); + } + + rasterizeInfo.mRasterizeState = SvgLoader::RasterizeState::UPLOADED; +} + +/** + * @brief Helper function to create a textureSet and atlasRect from rasterize info. + * + * @param[in] rasterizeInfo The rasterize info. + * @param[out] textureSet The textureSet. + * @param[out] atlasRect The atlasRect. + */ +void GetTextureSetFromRasterizeInfo(const SvgLoader::SvgRasterizeInfo& rasterizeInfo, Dali::TextureSet& textureSet, Vector4& atlasRect) +{ + if(!rasterizeInfo.mAtlasAttempted) + { + atlasRect = FULL_TEXTURE_RECT; + if(DALI_LIKELY(rasterizeInfo.mTextureSet && rasterizeInfo.mTextureSet.GetTextureCount() > 0u)) + { + auto texture = rasterizeInfo.mTextureSet.GetTexture(0u); + if(DALI_LIKELY(texture)) + { + // Always create new TextureSet here, so we don't share same TextureSets for multiple visuals. + textureSet = Dali::TextureSet::New(); + textureSet.SetTexture(0u, texture); + } + } + } + else + { + textureSet = rasterizeInfo.mTextureSet; + atlasRect = rasterizeInfo.mAtlasRect; + } +} + +} // Anonymous namespace + +SvgLoader::SvgLoader() +: mFactoryCache(nullptr), + mCurrentSvgLoadId(0), + mCurrentSvgRasterizeId(0), + mLoadingQueueLoadId(SvgLoader::INVALID_SVG_LOAD_ID), + mRasterizingQueueRasterizeId(SvgLoader::INVALID_SVG_RASTERIZE_ID), + mRemoveProcessorRegistered(false) +{ +} + +SvgLoader::~SvgLoader() +{ + if(mRemoveProcessorRegistered && Adaptor::IsAvailable()) + { + Adaptor::Get().UnregisterProcessorOnce(*this, true); + mRemoveProcessorRegistered = false; + } +} + +SvgLoader::SvgLoadId SvgLoader::GenerateUniqueSvgLoadId() +{ + // Skip invalid id generation. + if(DALI_UNLIKELY(mCurrentSvgLoadId == SvgLoader::INVALID_SVG_LOAD_ID)) + { + mCurrentSvgLoadId = 0; + } + return mCurrentSvgLoadId++; +} + +SvgLoader::SvgRasterizeId SvgLoader::GenerateUniqueSvgRasterizeId() +{ + // Skip invalid id generation. + if(DALI_UNLIKELY(mCurrentSvgRasterizeId == SvgLoader::INVALID_SVG_RASTERIZE_ID)) + { + mCurrentSvgRasterizeId = 0; + } + return mCurrentSvgRasterizeId++; +} + +SvgLoader::SvgLoadId SvgLoader::Load(const VisualUrl& url, float dpi, SvgLoaderObserver* svgObserver, bool synchronousLoading) +{ + SvgLoadId loadId = SvgLoader::INVALID_SVG_LOAD_ID; + auto cacheIndex = FindCacheIndexFromLoadCache(url, dpi); + + // Newly append cache now. + if(cacheIndex == SvgLoader::INVALID_SVG_CACHE_INDEX) + { + loadId = GenerateUniqueSvgLoadId(); + cacheIndex = static_cast(static_cast(mLoadCache.size())); + mLoadCache.push_back(SvgLoadInfo(loadId, url, dpi)); + + if(url.IsBufferResource()) + { + // Make encoded image buffer url valid until this SvgLoadInfo alive. + if(DALI_LIKELY(Dali::Adaptor::IsAvailable() && mFactoryCache)) + { + auto& textureManager = mFactoryCache->GetTextureManager(); + textureManager.UseExternalResource(url); + } + } + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::Load( url=%s dpi=%f observer=%p ) New cached index:%d loadId@%d\n", url.GetUrl().c_str(), dpi, svgObserver, cacheIndex, loadId); + } + else + { + DALI_ASSERT_ALWAYS(static_cast(cacheIndex) < mLoadCache.size() && "Invalid cache index"); + loadId = mLoadCache[cacheIndex].mId; + ++mLoadCache[cacheIndex].mReferenceCount; + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::Load( url=%s dpi=%f observer=%p ) Using cached index:%d loadId@%d\n", url.GetUrl().c_str(), dpi, svgObserver, cacheIndex, loadId); + } + + auto& loadInfo = mLoadCache[cacheIndex]; + + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::Load info id:%d, state:%s, refCount=%d\n", loadInfo.mId, GET_LOAD_STATE_STRING(loadInfo.mLoadState), static_cast(loadInfo.mReferenceCount)); + + switch(loadInfo.mLoadState) + { + case LoadState::LOAD_FAILED: // Failed notifies observer which then stops observing. + case LoadState::NOT_STARTED: + { + if(synchronousLoading) + { + // Do not add observer for sync load case. + LoadSynchronously(loadInfo, svgObserver); + } + else + { + LoadOrQueue(loadInfo, svgObserver); + } + break; + } + case LoadState::LOAD_FINISHED: + { + if(synchronousLoading || (mLoadingQueueLoadId == SvgLoader::INVALID_SVG_LOAD_ID && mRasterizingQueueRasterizeId == SvgLoader::INVALID_SVG_RASTERIZE_ID)) + { + // Already load finished. Notify observer. + if(svgObserver) + { + svgObserver->LoadComplete(loadId, loadInfo.mLoadState == LoadState::LOAD_FINISHED ? loadInfo.mVectorImageRenderer : Dali::VectorImageRenderer()); + } + } + else + { + // We should not notify observer yet. Queue it. + if(svgObserver) + { + LoadOrQueue(loadInfo, svgObserver); + } + } + break; + } + case LoadState::CANCELLED: + { + // A cancelled svg hasn't finished loading yet. Treat as a loading svg + // (it's ref count has already been incremented, above) + loadInfo.mLoadState = LoadState::LOADING; + DALI_FALLTHROUGH; + } + case LoadState::LOADING: + { + if(synchronousLoading) + { + // Do not add observer for sync load case. + // And also, we should not notify another observers even if we have already loaded. + LoadSynchronously(loadInfo, svgObserver); + } + else + { + AddLoadObserver(loadInfo, svgObserver); + } + break; + } + } + + return loadId; +} + +SvgLoader::SvgRasterizeId SvgLoader::Rasterize(SvgLoadId loadId, uint32_t width, uint32_t height, bool attemptAtlasing, SvgLoaderObserver* svgObserver, bool synchronousLoading) +{ + if(loadId == SvgLoader::INVALID_SVG_LOAD_ID) + { + return SvgLoader::INVALID_SVG_RASTERIZE_ID; + } + + SvgRasterizeId rasterizeId = SvgLoader::INVALID_SVG_RASTERIZE_ID; + auto cacheIndex = FindCacheIndexFromRasterizeCache(loadId, width, height, attemptAtlasing); + + // Newly append cache now. + if(cacheIndex == SvgLoader::INVALID_SVG_CACHE_INDEX) + { + // Increase loadId reference first + // It would be decreased at rasterizate removal. + { + auto loadCacheIndex = GetCacheIndexFromLoadCacheById(loadId); + DALI_ASSERT_ALWAYS(loadCacheIndex != SvgLoader::INVALID_SVG_CACHE_INDEX && "Invalid cache index"); + ++mLoadCache[loadCacheIndex].mReferenceCount; + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::Rasterize( loadId=%d Size=%ux%u atlas=%d observer=%p ) Increase loadId loadState:%s, refCount=%d\n", loadId, width, height, attemptAtlasing, svgObserver, GET_LOAD_STATE_STRING(mLoadCache[loadCacheIndex].mLoadState), static_cast(mLoadCache[loadCacheIndex].mReferenceCount)); + } + + rasterizeId = GenerateUniqueSvgRasterizeId(); + cacheIndex = static_cast(mRasterizeCache.size()); + mRasterizeCache.push_back(SvgRasterizeInfo(rasterizeId, loadId, width, height, attemptAtlasing)); + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::Rasterize( loadId=%d Size=%ux%u atlas=%d observer=%p ) New cached index:%d rasterizeId@%d\n", loadId, width, height, attemptAtlasing, svgObserver, cacheIndex, rasterizeId); + } + else + { + DALI_ASSERT_ALWAYS(static_cast(cacheIndex) < mRasterizeCache.size() && "Invalid cache index"); + rasterizeId = mRasterizeCache[cacheIndex].mId; + ++mRasterizeCache[cacheIndex].mReferenceCount; + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::Rasterize( loadId=%d Size=%ux%u atlas=%d observer=%p ) Use cached index:%d rasterizeId@%d\n", loadId, width, height, attemptAtlasing, svgObserver, cacheIndex, rasterizeId); + } + + auto& rasterizeInfo = mRasterizeCache[cacheIndex]; + + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::Rasterize info id:%d, state:%s, refCount=%d\n", rasterizeInfo.mId, GET_RASTERIZE_STATE_STRING(rasterizeInfo.mRasterizeState), static_cast(rasterizeInfo.mReferenceCount)); + + switch(rasterizeInfo.mRasterizeState) + { + case RasterizeState::UPLOAD_FAILED: // Failed notifies observer which then stops observing. + case RasterizeState::NOT_STARTED: + { + if(synchronousLoading) + { + // Do not add observer for sync load case. + RasterizeSynchronously(rasterizeInfo, svgObserver); + } + else + { + RasterizeOrQueue(rasterizeInfo, svgObserver); + } + break; + } + case RasterizeState::UPLOADED: + { + if(synchronousLoading || (mLoadingQueueLoadId == SvgLoader::INVALID_SVG_LOAD_ID && mRasterizingQueueRasterizeId == SvgLoader::INVALID_SVG_RASTERIZE_ID)) + { + // Already upload finished. Notify observer. + if(svgObserver) + { + Dali::TextureSet textureSet; + Vector4 atlasRect = FULL_TEXTURE_RECT; + if(rasterizeInfo.mRasterizeState == RasterizeState::UPLOADED) + { + GetTextureSetFromRasterizeInfo(rasterizeInfo, textureSet, atlasRect); + } + svgObserver->RasterizeComplete(rasterizeId, textureSet, atlasRect); + } + } + else + { + // We should not notify observer yet. Queue it. + if(svgObserver) + { + RasterizeOrQueue(rasterizeInfo, svgObserver); + } + } + break; + } + case RasterizeState::RASTERIZING: + { + if(synchronousLoading) + { + // Do not add observer for sync load case. + // And also, we should not notify another observers even if we have already loaded. + RasterizeSynchronously(rasterizeInfo, svgObserver); + } + else + { + AddRasterizeObserver(rasterizeInfo, svgObserver); + } + break; + } + } + + return rasterizeId; +} + +void SvgLoader::RequestLoadRemove(SvgLoader::SvgLoadId loadId, SvgLoaderObserver* svgObserver) +{ + // Remove observer first + auto cacheIndex = GetCacheIndexFromLoadCacheById(loadId); + if(cacheIndex == SvgLoader::INVALID_SVG_CACHE_INDEX) + { + return; + } + DALI_ASSERT_ALWAYS(static_cast(cacheIndex) < mLoadCache.size() && "Invalid cache index"); + + auto& loadInfo = mLoadCache[cacheIndex]; + RemoveLoadObserver(loadInfo, svgObserver); + mLoadRemoveQueue.push_back(loadId); + + if(!mRemoveProcessorRegistered && Adaptor::IsAvailable()) + { + mRemoveProcessorRegistered = true; + Adaptor::Get().RegisterProcessorOnce(*this, true); + } +} + +void SvgLoader::RequestRasterizeRemove(SvgLoader::SvgRasterizeId rasterizeId, SvgLoaderObserver* svgObserver, bool removalSynchronously) +{ + // Remove observer first + auto cacheIndex = GetCacheIndexFromRasterizeCacheById(rasterizeId); + if(cacheIndex == SvgLoader::INVALID_SVG_CACHE_INDEX) + { + return; + } + DALI_ASSERT_ALWAYS(static_cast(cacheIndex) < mRasterizeCache.size() && "Invalid cache index"); + + auto& rasterizeInfo = mRasterizeCache[cacheIndex]; + RemoveRasterizeObserver(rasterizeInfo, svgObserver); + + // Should not remove rasterize info if we are in notify observing. + if(removalSynchronously && (mLoadingQueueLoadId == SvgLoader::INVALID_SVG_LOAD_ID && mRasterizingQueueRasterizeId == SvgLoader::INVALID_SVG_RASTERIZE_ID)) + { + RemoveRasterize(rasterizeId); + } + else + { + mRasterizeRemoveQueue.push_back(rasterizeId); + + if(!mRemoveProcessorRegistered && Adaptor::IsAvailable()) + { + mRemoveProcessorRegistered = true; + Adaptor::Get().RegisterProcessorOnce(*this, true); + } + } +} + +Dali::VectorImageRenderer SvgLoader::GetVectorImageRenderer(SvgLoader::SvgLoadId loadId) const +{ + auto cacheIndex = GetCacheIndexFromLoadCacheById(loadId); + if(cacheIndex != SvgLoader::INVALID_SVG_CACHE_INDEX) + { + DALI_ASSERT_ALWAYS(static_cast(cacheIndex) < mLoadCache.size() && "Invalid cache index"); + return mLoadCache[cacheIndex].mVectorImageRenderer; + } + return Dali::VectorImageRenderer(); +} + +void SvgLoader::Process(bool postProcessor) +{ + DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_SVG_LOADER_PROCESS_REMOVE_QUEUE", [&](std::ostringstream& oss) { + oss << "[r:" << mRasterizeRemoveQueue.size() << ", l:" << mLoadRemoveQueue.size() << "]"; + }); + + mRemoveProcessorRegistered = false; + + for(auto& iter : mRasterizeRemoveQueue) + { + RemoveRasterize(iter); + } + mRasterizeRemoveQueue.clear(); + + for(auto& iter : mLoadRemoveQueue) + { + RemoveLoad(iter); + } + mLoadRemoveQueue.clear(); + + DALI_TRACE_END(gTraceFilter, "DALI_SVG_LOADER_PROCESS_REMOVE_QUEUE"); +} + +SvgLoader::SvgCacheIndex SvgLoader::GetCacheIndexFromLoadCacheById(const SvgLoader::SvgLoadId loadId) const +{ + const uint32_t size = static_cast(mLoadCache.size()); + + for(uint32_t i = 0; i < size; ++i) + { + if(mLoadCache[i].mId == loadId) + { + return static_cast(i); + } + } + return SvgLoader::INVALID_SVG_CACHE_INDEX; +} + +SvgLoader::SvgCacheIndex SvgLoader::GetCacheIndexFromRasterizeCacheById(const SvgLoader::SvgRasterizeId rasterizeId) const +{ + const uint32_t size = static_cast(mRasterizeCache.size()); + + for(uint32_t i = 0; i < size; ++i) + { + if(mRasterizeCache[i].mId == rasterizeId) + { + return static_cast(i); + } + } + return SvgLoader::INVALID_SVG_CACHE_INDEX; +} + +SvgLoader::SvgCacheIndex SvgLoader::FindCacheIndexFromLoadCache(const VisualUrl& imageUrl, float dpi) const +{ + const uint32_t size = static_cast(mLoadCache.size()); + + // TODO : Let we use hash in future. For now, just PoC + + for(uint32_t i = 0; i < size; ++i) + { + if(mLoadCache[i].mImageUrl.GetUrl() == imageUrl.GetUrl() && + Dali::Equals(mLoadCache[i].mDpi, dpi)) + { + return static_cast(i); + } + } + return SvgLoader::INVALID_SVG_CACHE_INDEX; +} + +SvgLoader::SvgCacheIndex SvgLoader::FindCacheIndexFromRasterizeCache(const SvgLoadId loadId, uint32_t width, uint32_t height, bool attemptAtlasing) const +{ + const uint32_t size = static_cast(mRasterizeCache.size()); + + // TODO : Let we use hash in future. For now, just PoC + + for(uint32_t i = 0; i < size; ++i) + { + if(mRasterizeCache[i].mLoadId == loadId && + mRasterizeCache[i].mWidth == width && + mRasterizeCache[i].mHeight == height) + { + // 1. If attemptAtlasing is true, then rasterizeInfo.mAttemptAtlasing should be true. The atlas rect result can be different. + // 2. If attemptAtlasing is false, then rasterizeInfo.mAtlasAttempted should be false. (We can use attempt failed result even if mAttemptAtlasing is true.) + if((attemptAtlasing && mRasterizeCache[i].mAttemptAtlasing) || + (!attemptAtlasing && !mRasterizeCache[i].mAtlasAttempted)) + { + return static_cast(i); + } + } + } + return SvgLoader::INVALID_SVG_CACHE_INDEX; +} + +void SvgLoader::RemoveLoad(SvgLoader::SvgLoadId loadId) +{ + auto cacheIndex = GetCacheIndexFromLoadCacheById(loadId); + if(cacheIndex != SvgLoader::INVALID_SVG_CACHE_INDEX) + { + DALI_ASSERT_ALWAYS(static_cast(cacheIndex) < mLoadCache.size() && "Invalid cache index"); + + auto& loadInfo(mLoadCache[cacheIndex]); + + --loadInfo.mReferenceCount; + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::RemoveLoad( url=%s dpi=%f ) cached index:%d loadId@%d, state:%s, refCount=%d\n", loadInfo.mImageUrl.GetUrl().c_str(), loadInfo.mDpi, cacheIndex, loadId, GET_LOAD_STATE_STRING(loadInfo.mLoadState), static_cast(loadInfo.mReferenceCount)); + + if(loadInfo.mReferenceCount <= 0) + { + if(loadInfo.mLoadState == LoadState::LOADING) + { + // Keep the load info in the cache, but mark it as cancelled. + // It will be removed when async load completed. + loadInfo.mLoadState = LoadState::CANCELLED; + } + else + { + if(loadInfo.mImageUrl.IsBufferResource()) + { + // Unreference image buffer url from texture manager. + if(DALI_LIKELY(Dali::Adaptor::IsAvailable() && mFactoryCache)) + { + auto& textureManager = mFactoryCache->GetTextureManager(); + textureManager.RemoveEncodedImageBuffer(loadInfo.mImageUrl.GetUrl()); + } + } + + // Remove the load info from the cache. + // Swap last data of cacheContainer. + if(static_cast(cacheIndex + 1) < mLoadCache.size()) + { + // Swap the value between current data and last data. + std::swap(mLoadCache[cacheIndex], mLoadCache.back()); + } + + // Now we can assume that latest data should be removed. pop_back. + mLoadCache.pop_back(); + + // Now, loadInfo is invalid + } + } + } +} + +void SvgLoader::RemoveRasterize(SvgLoader::SvgRasterizeId rasterizeId) +{ + auto cacheIndex = GetCacheIndexFromRasterizeCacheById(rasterizeId); + if(cacheIndex != SvgLoader::INVALID_SVG_CACHE_INDEX) + { + DALI_ASSERT_ALWAYS(static_cast(cacheIndex) < mRasterizeCache.size() && "Invalid cache index"); + + auto& rasterizeInfo(mRasterizeCache[cacheIndex]); + + --rasterizeInfo.mReferenceCount; + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::RemoveRasterize( loadId=%d Size=%ux%u ) cached index:%d rasterizeId@%d, state:%s, refCount=%d\n", rasterizeInfo.mLoadId, rasterizeInfo.mWidth, rasterizeInfo.mHeight, cacheIndex, rasterizeId, GET_RASTERIZE_STATE_STRING(rasterizeInfo.mRasterizeState), static_cast(rasterizeInfo.mReferenceCount)); + + if(rasterizeInfo.mReferenceCount <= 0) + { + // Reduce the reference count of LoadId first. + RemoveLoad(rasterizeInfo.mLoadId); + + if(rasterizeInfo.mRasterizeState == RasterizeState::RASTERIZING && rasterizeInfo.mTask) + { + // Cancel rasterize task immediatly! + Dali::AsyncTaskManager::Get().RemoveTask(rasterizeInfo.mTask); + } + + if(Dali::Adaptor::IsAvailable() && rasterizeInfo.mAtlasAttempted && mFactoryCache) + { + // Remove the atlas from the atlas manager. + auto atlasManager = mFactoryCache->GetAtlasManager(); + if(atlasManager) + { + atlasManager->Remove(rasterizeInfo.mTextureSet, rasterizeInfo.mAtlasRect); + } + } + + // Remove the rasterize info from the cache. + // Swap last data of cacheContainer. + if(static_cast(cacheIndex + 1) < mRasterizeCache.size()) + { + // Swap the value between current data and last data. + std::swap(mRasterizeCache[cacheIndex], mRasterizeCache.back()); + } + + // Now we can assume that latest data should be removed. pop_back. + mRasterizeCache.pop_back(); + + // Now, rasterize is invalid + } + } +} + +// Internal Methods for Load + +void SvgLoader::LoadOrQueue(SvgLoader::SvgLoadInfo& loadInfo, SvgLoaderObserver* svgObserver) +{ + if(mLoadingQueueLoadId != SvgLoader::INVALID_SVG_LOAD_ID || mRasterizingQueueRasterizeId != SvgLoader::INVALID_SVG_RASTERIZE_ID) + { + mLoadQueue.PushBack(LoadQueueElement(loadInfo.mId, svgObserver)); + if(svgObserver) + { + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Load)Connect DestructionSignal to observer:%p\n", svgObserver); + svgObserver->LoadDestructionSignal().Connect(this, &SvgLoader::LoadObserverDestroyed); + } + } + else + { + LoadRequest(loadInfo, svgObserver); + } +} + +void SvgLoader::LoadRequest(SvgLoader::SvgLoadInfo& loadInfo, SvgLoaderObserver* svgObserver) +{ + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::LoadRequest(): id:%d observer:%p\n", loadInfo.mId, svgObserver); + AddLoadObserver(loadInfo, svgObserver); + loadInfo.mLoadState = LoadState::LOADING; + + EncodedImageBuffer encodedImageBuffer; + if(loadInfo.mImageUrl.IsBufferResource()) + { + // Make encoded image buffer url valid until this SvgLoadInfo alive. + if(DALI_LIKELY(Dali::Adaptor::IsAvailable() && mFactoryCache)) + { + auto& textureManager = mFactoryCache->GetTextureManager(); + encodedImageBuffer = textureManager.GetEncodedImageBuffer(loadInfo.mImageUrl.GetUrl()); + } + } + + loadInfo.mTask = new SvgLoadingTask(loadInfo.mVectorImageRenderer, loadInfo.mId, loadInfo.mImageUrl, encodedImageBuffer, loadInfo.mDpi, MakeCallback(this, &SvgLoader::AsyncLoadComplete)); + + Dali::AsyncTaskManager::Get().AddTask(loadInfo.mTask); +} + +void SvgLoader::LoadSynchronously(SvgLoader::SvgLoadInfo& loadInfo, SvgLoaderObserver* svgObserver) +{ + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::LoadSynchronously(): id:%d observer:%p\n", loadInfo.mId, svgObserver); + + EncodedImageBuffer encodedImageBuffer; + if(loadInfo.mImageUrl.IsBufferResource()) + { + // Make encoded image buffer url valid until this SvgLoadInfo alive. + if(DALI_LIKELY(Dali::Adaptor::IsAvailable() && mFactoryCache)) + { + auto& textureManager = mFactoryCache->GetTextureManager(); + encodedImageBuffer = textureManager.GetEncodedImageBuffer(loadInfo.mImageUrl.GetUrl()); + } + } + + // Note, we will not store this task after this API called. + auto loadingTask = new SvgLoadingTask(loadInfo.mVectorImageRenderer, loadInfo.mId, loadInfo.mImageUrl, encodedImageBuffer, loadInfo.mDpi, nullptr); + loadingTask->Process(); + if(!loadingTask->HasSucceeded()) + { + loadInfo.mLoadState = LoadState::LOAD_FAILED; + } + else + { + loadInfo.mLoadState = LoadState::LOAD_FINISHED; + } + + if(svgObserver) + { + svgObserver->LoadComplete(loadInfo.mId, loadInfo.mLoadState == LoadState::LOAD_FINISHED ? loadInfo.mVectorImageRenderer : Dali::VectorImageRenderer()); + } +} + +void SvgLoader::AddLoadObserver(SvgLoader::SvgLoadInfo& loadInfo, SvgLoaderObserver* svgObserver) +{ + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::AddLoadObserver(): id:%d observer:%p\n", loadInfo.mId, svgObserver); + + if(svgObserver) + { + loadInfo.mObservers.PushBack(svgObserver); + + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Load)Connect DestructionSignal to observer:%p\n", svgObserver); + svgObserver->LoadDestructionSignal().Connect(this, &SvgLoader::LoadObserverDestroyed); + } +} + +void SvgLoader::RemoveLoadObserver(SvgLoader::SvgLoadInfo& loadInfo, SvgLoaderObserver* svgObserver) +{ + if(svgObserver) + { + const auto iterEnd = loadInfo.mObservers.End(); + const auto iter = std::find(loadInfo.mObservers.Begin(), iterEnd, svgObserver); + if(iter != iterEnd) + { + // Disconnect and remove the observer. + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Load)Disconnect DestructionSignal to observer:%p\n", svgObserver); + svgObserver->LoadDestructionSignal().Disconnect(this, &SvgLoader::LoadObserverDestroyed); + loadInfo.mObservers.Erase(iter); + } + else + { + // Given loadId might exist at load queue. + // Remove observer from the LoadQueue + for(auto&& element : mLoadQueue) + { + if(element.first == loadInfo.mId && element.second == svgObserver) + { + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, "Remove observer from load queue (loadId:%d, observer:%p)\n", element.first, element.second); + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Load)Disconnect DestructionSignal to observer:%p\n", svgObserver); + svgObserver->LoadDestructionSignal().Disconnect(this, &SvgLoader::LoadObserverDestroyed); + element.second = nullptr; + break; + } + } + } + } +} + +void SvgLoader::ProcessLoadQueue() +{ + for(auto&& element : mLoadQueue) + { + if(element.first == SvgLoader::INVALID_SVG_LOAD_ID) + { + continue; + } + + auto cacheIndex = GetCacheIndexFromLoadCacheById(element.first); + if(cacheIndex != SvgLoader::INVALID_SVG_CACHE_INDEX) + { + DALI_ASSERT_ALWAYS(static_cast(cacheIndex) < mLoadCache.size() && "Invalid cache index"); + + SvgLoadInfo& loadInfo(mLoadCache[cacheIndex]); + const auto loadId = element.first; + auto* svgObserver = element.second; + + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::ProcessLoadQueue() loadId=%d, observer=%p, cacheIndex=@%d, loadState:%s\n", loadId, svgObserver, cacheIndex, GET_LOAD_STATE_STRING(loadInfo.mLoadState)); + + if((loadInfo.mLoadState == LoadState::LOAD_FINISHED) || + (loadInfo.mLoadState == LoadState::LOAD_FAILED)) + { + if(svgObserver) + { + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Load)Disconnect DestructionSignal to observer:%p\n", svgObserver); + svgObserver->LoadDestructionSignal().Disconnect(this, &SvgLoader::LoadObserverDestroyed); + + svgObserver->LoadComplete(loadId, loadInfo.mLoadState == LoadState::LOAD_FINISHED ? loadInfo.mVectorImageRenderer : Dali::VectorImageRenderer()); + } + } + else if(loadInfo.mLoadState == LoadState::LOADING) + { + // Note : LOADING state texture cannot be queue. + // This case be occured when same load id are queue in mLoadQueue. + AddLoadObserver(loadInfo, svgObserver); + } + else + { + LoadRequest(loadInfo, svgObserver); + } + } + } + + mLoadQueue.Clear(); +} + +void SvgLoader::NotifyLoadObservers(SvgLoader::SvgLoadInfo& loadInfo) +{ + SvgLoadId loadId = loadInfo.mId; + + // Empty handle if load failed + Dali::VectorImageRenderer vectorImageRenderer = loadInfo.mLoadState == LoadState::LOAD_FINISHED ? loadInfo.mVectorImageRenderer : Dali::VectorImageRenderer(); + + // If there is an observer: Notify the load is complete, whether successful or not, + // and erase it from the list + SvgLoadInfo* info = &loadInfo; + + mLoadingQueueLoadId = loadInfo.mId; + + // Reverse observer list that we can pop_back the observer. + std::reverse(info->mObservers.Begin(), info->mObservers.End()); + + while(info->mObservers.Count()) + { + SvgLoaderObserver* observer = *(info->mObservers.End() - 1u); + + // During LoadComplete() a Control ResourceReady() signal is emitted. + // During that signal the app may add remove /add SvgLoad (e.g. via + // ImageViews). + // It is possible for observers to be removed from the observer list, + // and it is also possible for the mLoadCache to be modified, + // invalidating the reference to the SvgLoadInfo struct. + // Texture load requests for the same URL are deferred until the end of this + // method. + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::NotifyLoadObservers() observer:%p loadId:%d url:%s loadState:%s\n", observer, loadId, info->mImageUrl.GetUrl().c_str(), GET_LOAD_STATE_STRING(info->mLoadState)); + + // It is possible for the observer to be deleted. + // Disconnect and remove the observer first. + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Load)Disconnect DestructionSignal to observer:%p\n", observer); + observer->LoadDestructionSignal().Disconnect(this, &SvgLoader::LoadObserverDestroyed); + + info->mObservers.Erase(info->mObservers.End() - 1u); + + observer->LoadComplete(loadId, vectorImageRenderer); + + // Get the textureInfo from the container again as it may have been invalidated. + auto cacheIndex = GetCacheIndexFromLoadCacheById(loadId); + if(cacheIndex == SvgLoader::INVALID_SVG_CACHE_INDEX) + { + break; // load info has been removed - can stop. + } + DALI_ASSERT_ALWAYS(cacheIndex < mLoadCache.size() && "Invalid cache index"); + info = &mLoadCache[cacheIndex]; + } + + mLoadingQueueLoadId = SvgLoader::INVALID_SVG_LOAD_ID; + + ProcessLoadQueue(); + ProcessRasterizeQueue(); +} + +// Internal Methods for Rasterize + +void SvgLoader::RasterizeOrQueue(SvgLoader::SvgRasterizeInfo& rasterizeInfo, SvgLoaderObserver* svgObserver) +{ + if(mLoadingQueueLoadId != SvgLoader::INVALID_SVG_LOAD_ID || mRasterizingQueueRasterizeId != SvgLoader::INVALID_SVG_RASTERIZE_ID) + { + mRasterizeQueue.PushBack(RasterizeQueueElement(rasterizeInfo.mId, svgObserver)); + if(svgObserver) + { + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Rasterize)Connect DestructionSignal to observer:%p\n", svgObserver); + svgObserver->RasterizeDestructionSignal().Connect(this, &SvgLoader::RasterizeObserverDestroyed); + } + } + else + { + RasterizeRequest(rasterizeInfo, svgObserver); + } +} + +void SvgLoader::RasterizeRequest(SvgLoader::SvgRasterizeInfo& rasterizeInfo, SvgLoaderObserver* svgObserver) +{ + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::RasterizeRequest(): id:%d observer:%p\n", rasterizeInfo.mId, svgObserver); + AddRasterizeObserver(rasterizeInfo, svgObserver); + rasterizeInfo.mRasterizeState = RasterizeState::RASTERIZING; + + auto vectorImageRenderer = GetVectorImageRenderer(rasterizeInfo.mLoadId); + + auto rasterizingTask = new SvgRasterizingTask(vectorImageRenderer, rasterizeInfo.mId, rasterizeInfo.mWidth, rasterizeInfo.mHeight, MakeCallback(this, &SvgLoader::AsyncRasterizeComplete)); +#ifdef TRACE_ENABLED + { + auto loadCacheIndex = GetCacheIndexFromLoadCacheById(rasterizeInfo.mLoadId); + rasterizingTask->SetUrl(mLoadCache[loadCacheIndex].mImageUrl); + } +#endif + + // Keep SvgTask at info. + rasterizeInfo.mTask = std::move(rasterizingTask); + + Dali::AsyncTaskManager::Get().AddTask(rasterizeInfo.mTask); +} + +void SvgLoader::RasterizeSynchronously(SvgLoader::SvgRasterizeInfo& rasterizeInfo, SvgLoaderObserver* svgObserver) +{ + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::RasterizeSynchronously(): id:%d observer:%p\n", rasterizeInfo.mId, svgObserver); + + auto vectorImageRenderer = GetVectorImageRenderer(rasterizeInfo.mLoadId); + + // Note, we will not store this task after this API called. + auto rasterizingTask = new SvgRasterizingTask(vectorImageRenderer, rasterizeInfo.mId, rasterizeInfo.mWidth, rasterizeInfo.mHeight, nullptr); +#ifdef TRACE_ENABLED + { + auto loadCacheIndex = GetCacheIndexFromLoadCacheById(rasterizeInfo.mLoadId); + rasterizingTask->SetUrl(mLoadCache[loadCacheIndex].mImageUrl); + } +#endif + rasterizingTask->Process(); + + PixelData rasterizedPixelData = rasterizingTask->GetPixelData(); + + if(!rasterizingTask->HasSucceeded() || !rasterizedPixelData) + { + rasterizeInfo.mRasterizeState = RasterizeState::UPLOAD_FAILED; + } + else if(rasterizeInfo.mRasterizeState != RasterizeState::UPLOADED) ///< Check it to avoid duplicate Upload call. + { + SetTextureSetToRasterizeInfo(mFactoryCache, rasterizedPixelData, rasterizeInfo); + } + + if(svgObserver) + { + Dali::TextureSet textureSet; + Vector4 atlasRect = FULL_TEXTURE_RECT; + if(rasterizeInfo.mRasterizeState == RasterizeState::UPLOADED) + { + GetTextureSetFromRasterizeInfo(rasterizeInfo, textureSet, atlasRect); + } + svgObserver->RasterizeComplete(rasterizeInfo.mId, textureSet, atlasRect); + } +} + +void SvgLoader::AddRasterizeObserver(SvgLoader::SvgRasterizeInfo& rasterizeInfo, SvgLoaderObserver* svgObserver) +{ + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::AddRasterizeObserver(): id:%d observer:%p\n", rasterizeInfo.mId, svgObserver); + + if(svgObserver) + { + rasterizeInfo.mObservers.PushBack(svgObserver); + + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Rasterize)Connect DestructionSignal to observer:%p\n", svgObserver); + svgObserver->RasterizeDestructionSignal().Connect(this, &SvgLoader::RasterizeObserverDestroyed); + } +} + +void SvgLoader::RemoveRasterizeObserver(SvgLoader::SvgRasterizeInfo& rasterizeInfo, SvgLoaderObserver* svgObserver) +{ + if(svgObserver) + { + const auto iterEnd = rasterizeInfo.mObservers.End(); + const auto iter = std::find(rasterizeInfo.mObservers.Begin(), iterEnd, svgObserver); + if(iter != iterEnd) + { + // Disconnect and remove the observer. + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Rasterize)Disconnect DestructionSignal to observer:%p\n", svgObserver); + svgObserver->RasterizeDestructionSignal().Disconnect(this, &SvgLoader::RasterizeObserverDestroyed); + rasterizeInfo.mObservers.Erase(iter); + } + else + { + // Given rasterizeId might exist at rasterize queue. + // Remove observer from the RasterizeQueue + for(auto&& element : mRasterizeQueue) + { + if(element.first == rasterizeInfo.mId && element.second == svgObserver) + { + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, "Remove observer from rasterize queue (rasterizeId:%d, observer:%p)\n", element.first, element.second); + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Rasterize)Disconnect DestructionSignal to observer:%p\n", svgObserver); + svgObserver->RasterizeDestructionSignal().Disconnect(this, &SvgLoader::RasterizeObserverDestroyed); + element.second = nullptr; + break; + } + } + } + } +} + +void SvgLoader::ProcessRasterizeQueue() +{ + for(auto&& element : mRasterizeQueue) + { + if(element.first == SvgLoader::INVALID_SVG_RASTERIZE_ID) + { + continue; + } + + auto cacheIndex = GetCacheIndexFromRasterizeCacheById(element.first); + if(cacheIndex != SvgLoader::INVALID_SVG_CACHE_INDEX) + { + DALI_ASSERT_ALWAYS(static_cast(cacheIndex) < mRasterizeCache.size() && "Invalid cache index"); + + SvgRasterizeInfo& rasterizeInfo(mRasterizeCache[cacheIndex]); + const auto rasterizeId = element.first; + auto* svgObserver = element.second; + + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::General, "SvgLoader::ProcessRasterizeQueue() rasterizeId=%d, observer=%p, cacheIndex=@%d, rasterizeState:%s\n", rasterizeId, svgObserver, cacheIndex, GET_RASTERIZE_STATE_STRING(rasterizeInfo.mRasterizeState)); + + if((rasterizeInfo.mRasterizeState == RasterizeState::UPLOADED) || + (rasterizeInfo.mRasterizeState == RasterizeState::UPLOAD_FAILED)) + { + if(svgObserver) + { + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Rasterize)Disconnect DestructionSignal to observer:%p\n", svgObserver); + svgObserver->RasterizeDestructionSignal().Disconnect(this, &SvgLoader::RasterizeObserverDestroyed); + + Dali::TextureSet textureSet; + Vector4 atlasRect = FULL_TEXTURE_RECT; + if(rasterizeInfo.mRasterizeState == RasterizeState::UPLOADED) + { + GetTextureSetFromRasterizeInfo(rasterizeInfo, textureSet, atlasRect); + } + svgObserver->RasterizeComplete(rasterizeId, textureSet, atlasRect); + } + } + else if(rasterizeInfo.mRasterizeState == RasterizeState::RASTERIZING) + { + // Note : RASTERIZING state texture cannot be queue. + // This case be occured when same load id are queue in mRasterizeQueue. + AddRasterizeObserver(rasterizeInfo, svgObserver); + } + else + { + RasterizeRequest(rasterizeInfo, svgObserver); + } + } + } + + mRasterizeQueue.Clear(); +} + +void SvgLoader::NotifyRasterizeObservers(SvgLoader::SvgRasterizeInfo& rasterizeInfo) +{ + SvgRasterizeId rasterizeId = rasterizeInfo.mId; + + const bool rasterizationSuccess = rasterizeInfo.mRasterizeState == RasterizeState::UPLOADED; + + // If there is an observer: Notify the load is complete, whether successful or not, + // and erase it from the list + SvgRasterizeInfo* info = &rasterizeInfo; + + mRasterizingQueueRasterizeId = rasterizeInfo.mId; + + // Reverse observer list that we can pop_back the observer. + std::reverse(info->mObservers.Begin(), info->mObservers.End()); + + while(info->mObservers.Count()) + { + SvgLoaderObserver* observer = *(info->mObservers.End() - 1u); + + // During LoadComplete() a Control ResourceReady() signal is emitted. + // During that signal the app may add remove /add SvgLoad (e.g. via + // ImageViews). + // It is possible for observers to be removed from the observer list, + // and it is also possible for the mLoadCache to be modified, + // invalidating the reference to the SvgRasterizeInfo struct. + // Texture load requests for the same URL are deferred until the end of this + // method. + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::NotifyRasterizeObservers() observer:%p rasterizeId:%d loaderId:%d atlasAttempted:%d Size:%ux%u rasterizestate:%s\n", observer, rasterizeId, info->mLoadId, info->mAtlasAttempted, info->mWidth, info->mHeight, GET_RASTERIZE_STATE_STRING(info->mRasterizeState)); + + // It is possible for the observer to be deleted. + // Disconnect and remove the observer first. + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, " (Rasterize)Disconnect DestructionSignal to observer:%p\n", observer); + observer->RasterizeDestructionSignal().Disconnect(this, &SvgLoader::RasterizeObserverDestroyed); + + info->mObservers.Erase(info->mObservers.End() - 1u); + + Dali::TextureSet textureSet; + Vector4 atlasRect = FULL_TEXTURE_RECT; + if(rasterizationSuccess) + { + GetTextureSetFromRasterizeInfo(rasterizeInfo, textureSet, atlasRect); + } + + observer->RasterizeComplete(rasterizeId, textureSet, atlasRect); + + // Get the textureInfo from the container again as it may have been invalidated. + auto cacheIndex = GetCacheIndexFromRasterizeCacheById(rasterizeId); + if(cacheIndex == SvgLoader::INVALID_SVG_CACHE_INDEX) + { + break; // load info has been removed - can stop. + } + DALI_ASSERT_ALWAYS(cacheIndex < mRasterizeCache.size() && "Invalid cache index"); + info = &mRasterizeCache[cacheIndex]; + } + + mRasterizingQueueRasterizeId = SvgLoader::INVALID_SVG_RASTERIZE_ID; + + ProcessLoadQueue(); + ProcessRasterizeQueue(); +} + +/// From SvgLoadingTask +void SvgLoader::AsyncLoadComplete(SvgTaskPtr task) +{ + auto loadId = task->GetId(); + auto cacheIndex = GetCacheIndexFromLoadCacheById(loadId); + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::AsyncLoadComplete( loadId:%d CacheIndex:%u )\n", loadId, cacheIndex); + if(cacheIndex != SvgLoader::INVALID_SVG_CACHE_INDEX) + { + DALI_ASSERT_ALWAYS(cacheIndex < mLoadCache.size() && "Invalid cache index"); + + SvgLoadInfo& loadInfo(mLoadCache[cacheIndex]); + + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, " loadId:%d Url:%s CacheIndex:%d LoadState: %s\n", loadInfo.mId, loadInfo.mImageUrl.GetUrl().c_str(), cacheIndex, GET_LOAD_STATE_STRING(loadInfo.mLoadState)); + + if(loadInfo.mTask == task) + { + loadInfo.mTask.Reset(); + } + + if(loadInfo.mLoadState == LoadState::CANCELLED) + { + // Note : loadInfo can be invalidated after this call (as the mLoadCache may be modified) + RemoveLoad(loadId); + } + else + { + if(!task->HasSucceeded()) + { + loadInfo.mLoadState = LoadState::LOAD_FAILED; + } + else + { + loadInfo.mLoadState = LoadState::LOAD_FINISHED; + } + + // Note : loadInfo can be invalidated after this call (as the mLoadCache may be modified) + NotifyLoadObservers(loadInfo); + } + } +} + +/// From SvgRasterizingTask +void SvgLoader::AsyncRasterizeComplete(SvgTaskPtr task) +{ + auto rasterizeId = task->GetId(); + auto cacheIndex = GetCacheIndexFromRasterizeCacheById(rasterizeId); + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::AsyncRasterizeComplete( rasterizedId:%d CacheIndex:%u )\n", rasterizeId, cacheIndex); + if(cacheIndex != SvgLoader::INVALID_SVG_CACHE_INDEX) + { + DALI_ASSERT_ALWAYS(cacheIndex < mRasterizeCache.size() && "Invalid cache index"); + + SvgRasterizeInfo& rasterizeInfo(mRasterizeCache[cacheIndex]); + + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, " rasterizeId:%d loadId:%d attemptAtlasing:%d Size:%ux%u CacheIndex:%d RasterizeState: %s\n", rasterizeInfo.mId, rasterizeInfo.mLoadId, rasterizeInfo.mAttemptAtlasing, rasterizeInfo.mWidth, rasterizeInfo.mHeight, cacheIndex, GET_RASTERIZE_STATE_STRING(rasterizeInfo.mRasterizeState)); + + if(rasterizeInfo.mTask == task) + { + rasterizeInfo.mTask.Reset(); + } + + PixelData rasterizedPixelData = task->GetPixelData(); + + if(!task->HasSucceeded() || !rasterizedPixelData) + { + rasterizeInfo.mRasterizeState = RasterizeState::UPLOAD_FAILED; + } + else if(rasterizeInfo.mRasterizeState != RasterizeState::UPLOADED) ///< Check it to avoid duplicate Upload call. (e.g. sync rasterize) + { + SetTextureSetToRasterizeInfo(mFactoryCache, rasterizedPixelData, rasterizeInfo); + } + + // Note : rasterizeInfo can be invalidated after this call (as the mRasterizeCache may be modified) + NotifyRasterizeObservers(rasterizeInfo); + } +} + +/// From ~SvgVisual +void SvgLoader::LoadObserverDestroyed(SvgLoaderObserver* observer) +{ + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::LoadObserverDestroyed(): observer:%p\n", observer); + + for(auto&& loadInfo : mLoadCache) + { + for(auto iter = (loadInfo.mObservers.Begin()); iter != (loadInfo.mObservers.End());) + { + if(*iter == observer) + { + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, " (Load)Remove observer from LoadCache (id:%d, observer:%p)\n", loadInfo.mId, observer); + iter = loadInfo.mObservers.Erase(iter); + } + else + { + ++iter; + } + } + } + + // Remove element from the LoadQueue + for(auto&& element : mLoadQueue) + { + if(element.second == observer) + { + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, "Remove observer from load queue (loadId:%d, observer:%p)\n", element.first, element.second); + element.first = SvgLoader::INVALID_SVG_LOAD_ID; + element.second = nullptr; + } + } +} + +void SvgLoader::RasterizeObserverDestroyed(SvgLoaderObserver* observer) +{ + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, "SvgLoader::RasterizeObserverDestroyed(): observer:%p\n", observer); + + for(auto&& rasterizeInfo : mRasterizeCache) + { + for(auto iter = (rasterizeInfo.mObservers.Begin()); iter != (rasterizeInfo.mObservers.End());) + { + if(*iter == observer) + { + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Concise, " (Rasterize)Remove observer from RasterizeCache (id:%d, observer:%p)\n", rasterizeInfo.mId, observer); + iter = rasterizeInfo.mObservers.Erase(iter); + } + else + { + ++iter; + } + } + } + + // Remove element from the RasterizeQueue + for(auto&& element : mRasterizeQueue) + { + if(element.second == observer) + { + DALI_LOG_INFO(gSvgLoaderLogFilter, Debug::Verbose, "Remove observer from rasterize queue (rasterizeId:%d, observer:%p)\n", element.first, element.second); + element.first = SvgLoader::INVALID_SVG_RASTERIZE_ID; + element.second = nullptr; + } + } +} + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/visuals/svg/svg-loader.h b/dali-toolkit/internal/visuals/svg/svg-loader.h new file mode 100644 index 0000000..56e31e6 --- /dev/null +++ b/dali-toolkit/internal/visuals/svg/svg-loader.h @@ -0,0 +1,458 @@ +#ifndef DALI_TOOLKIT_SVG_LOADER_H +#define DALI_TOOLKIT_SVG_LOADER_H + +/* + * Copyright (c) 2024 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// EXTERNAL INCLUDES +#include +#include +#include +#include +#include +#include + +// INTERNAL INCLUDES +#include +#include +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace Internal +{ +class VisualFactoryCache; + +/** + * The manager for loading Svg textures. + * It caches them internally for better performance; i.e. to avoid loading and + * parsing the files over and over. + * + * @note To use EncodedImageBuffer and AtlasManager, we need to set VisualFactoryCache. + */ +class SvgLoader : public ConnectionTracker, public Integration::Processor +{ +public: + typedef int32_t SvgLoadId; ///< The SvgLoadId type. This is used as a handle to refer to a particular SvgLoader Data. + typedef int32_t SvgRasterizeId; ///< The SvgRasterizeId type. This is used as a handle to refer to a particular SvgLoader Data. + + static constexpr SvgLoadId INVALID_SVG_LOAD_ID = -1; ///< Used to represent a null SvgLoadId or error + static constexpr SvgRasterizeId INVALID_SVG_RASTERIZE_ID = -1; ///< Used to represent a null SvgRasterizeId or error + + /** + * @brief The LoadState Enumeration represents the current state of a particular Svg data's life-cycle. + */ + enum class LoadState + { + NOT_STARTED, ///< Default + LOADING, ///< Loading has been started, but not finished. + LOAD_FINISHED, ///< Loading has finished. + CANCELLED, ///< Removed before loading completed + LOAD_FAILED ///< Async loading failed, e.g. connection problem + }; + + /** + * @brief The RasterizeState Enumeration represents the current state of a particular Svg data's life-cycle. + */ + enum class RasterizeState + { + NOT_STARTED, ///< Default + RASTERIZING, ///< Rasterizing has been started, but not finished. + UPLOADED, ///< Upload has finished. + UPLOAD_FAILED ///< Async rasterizing failed, e.g. connection problem + }; + +private: + typedef uint32_t SvgCacheIndex; ///< The Cache index type. Only be used internally + static constexpr SvgCacheIndex INVALID_SVG_CACHE_INDEX = static_cast(std::numeric_limits::max()); + + using ObserverContainer = Dali::Vector; + +public: + /** + * Constructor + */ + SvgLoader(); + + /** + * Destructor, non-virtual as not a base class + */ + ~SvgLoader(); + + /** + * @brief Request to load an SVG file. + * It will notify SvgLoaderObserver->LoadComplete before we call RequestLoadRemove. + * @note Even if synchronousLoading is true, we notify observer always. + * @note If we meat cached svg load, notify LoadComplete first, and then API function finished. + * + * @param[in] url to retrieve + * @param[in] dpi The DPI of the screen. + * @param[in] svgObserver The SvgVisual that requested loading. + * @param[in] synchronousLoading True if the image will be loaded in synchronous time. + * @return id of the load request. + */ + SvgLoadId Load(const VisualUrl& url, float dpi, SvgLoaderObserver* svgObserver, bool synchronousLoading); + + /** + * @brief Request to rasterize an SVG file. + * It will notify SvgLoaderObserver->RasterizeComplete before we call RequestRasterizeRemove. + * @note Even if synchronousLoading is true, we notify observer always. + * @note If we meat cached svg rasterize, notify RasterizeComplete first, and then API function finished. + * + * @todo RasterizeComplete will not be called forever if loading failed. Should we need to support it? + * + * @param[in] loaderId to retrieve + * @param[in] width The rasterization width. + * @param[in] height The rasterization height. + * @param[in] attemptAtlasing True if we need to attempt atlas the image. + * @param[in] svgObserver The SvgVisual that requested loading. + * @param[in] synchronousLoading True if the image will be loaded in synchronous time. + * @return id of the rasterize request. + */ + SvgRasterizeId Rasterize(SvgLoadId loaderId, uint32_t width, uint32_t height, bool attemptAtlasing, SvgLoaderObserver* svgObserver, bool synchronousLoading); + + /** + * @brief Request remove a texture matching id. + * Erase the observer from the observer list of cache if we need. + * + * @param[in] loadId cache data id + * @param[in] svgObserver The SvgVisual that requested loading. + */ + void RequestLoadRemove(SvgLoadId loadId, SvgLoaderObserver* svgObserver); + + /** + * @brief Request remove a texture matching id. + * Erase the observer from the observer list of cache if we need. + * + * @param[in] rasterizeId cache data id + * @param[in] svgObserver The SvgVisual that requested loading. + * @param[in] removalSynchronously Whether the removal should be done synchronously, or not. + * It will be true when we want to ignore unused rasterize task. + */ + void RequestRasterizeRemove(SvgRasterizeId rasterizeId, SvgLoaderObserver* svgObserver, bool removalSynchronously); + + /** + * @brief Get the VectorImageRenderer of matching loadId. + * + * @param[in] loadId cache data id + * @return The vector image renderer of matching loadId. + */ + VectorImageRenderer GetVectorImageRenderer(SvgLoadId loadId) const; + +protected: // Implementation of Processor + /** + * @copydoc Dali::Integration::Processor::Process() + */ + void Process(bool postProcessor) override; + + /** + * @copydoc Dali::Integration::Processor::GetProcessorName() + */ + std::string_view GetProcessorName() const override + { + return "SvgLoader"; + } + +private: + SvgLoadId GenerateUniqueSvgLoadId(); + + SvgRasterizeId GenerateUniqueSvgRasterizeId(); + + SvgCacheIndex GetCacheIndexFromLoadCacheById(const SvgLoadId loadId) const; + + SvgCacheIndex GetCacheIndexFromRasterizeCacheById(const SvgRasterizeId rasterizeId) const; + + SvgCacheIndex FindCacheIndexFromLoadCache(const VisualUrl& imageUrl, float dpi) const; + + SvgCacheIndex FindCacheIndexFromRasterizeCache(const SvgLoadId loadId, uint32_t width, uint32_t height, bool attemptAtlasing) const; + + /** + * @brief Remove a texture matching id. + * Erase the observer from the observer list of cache if we need. + * This API decrease cached SvgLoadInfo reference. + * If the SvgLoadInfo reference become 0, the VectorImageRenderer will be reset. + * + * @param[in] loadId cache data id + */ + void RemoveLoad(SvgLoadId loadId); + + /** + * @brief Remove a texture matching id. + * Erase the observer from the observer list of cache if we need. + * This API decrease cached SvgRasterizeInfo reference. + * If the SvgRasterizeInfo reference become 0, the textureSet will be reset. + * + * @param[in] rasterizeId cache data id + */ + void RemoveRasterize(SvgRasterizeId rasterizeId); + +public: + /** + * @brief Information of Svg image load data + */ + struct SvgLoadInfo + { + SvgLoadInfo(SvgLoadId loadId, const VisualUrl& url, float dpi) + : mId(loadId), + mImageUrl(url), + mDpi(dpi), + mLoadState(LoadState::NOT_STARTED), + mVectorImageRenderer(Dali::VectorImageRenderer::New()), + mObservers(), + mReferenceCount(1u) + { + } + ~SvgLoadInfo() + { + } + + SvgLoadInfo(SvgLoadInfo&& info) noexcept // move constructor + { + // Reuse move operator. + *this = std::move(info); + } + SvgLoadInfo& operator=(SvgLoadInfo&& info) noexcept // move operator + { + if(this != &info) + { + mId = info.mId; + mTask = std::move(info.mTask); + + mImageUrl = std::move(info.mImageUrl); + + mDpi = info.mDpi; + + mLoadState = info.mLoadState; + mVectorImageRenderer = std::move(info.mVectorImageRenderer); + + mObservers = std::move(info.mObservers); + + mReferenceCount = info.mReferenceCount; + + info.mTask.Reset(); + info.mVectorImageRenderer.Reset(); + info.mObservers.Clear(); + info.mReferenceCount = 0; + } + return *this; + } + + private: + SvgLoadInfo() = delete; // Do not use empty constructor + SvgLoadInfo(const SvgLoadInfo& info) = delete; // Do not use copy constructor + SvgLoadInfo& operator=(const SvgLoadInfo& info) = delete; // Do not use copy assign + + public: + SvgLoadId mId; + SvgTaskPtr mTask; ///< Async task. It would be deleted when loading completed. + + VisualUrl mImageUrl; + float mDpi; + + LoadState mLoadState; + VectorImageRenderer mVectorImageRenderer; + + ObserverContainer mObservers; + + int32_t mReferenceCount; ///< The number of Svg visuals that use this data. + }; + + /** + * @brief Information of Svg image rasterize data + */ + struct SvgRasterizeInfo + { + SvgRasterizeInfo(SvgRasterizeId rasterizeId, SvgLoadId loadId, uint32_t width, uint32_t height, bool attemptAtlasing) + : mId(rasterizeId), + mLoadId(loadId), + mWidth(width), + mHeight(height), + mAttemptAtlasing(attemptAtlasing), + mRasterizeState(RasterizeState::NOT_STARTED), + mTextureSet(), + mAtlasRect(Vector4(0.0f, 0.0f, 1.0f, 1.0f)), + mAtlasAttempted(attemptAtlasing), ///< Let we assume that atlas attempted until rasterize completed. + mObservers(), + mReferenceCount(1u) + { + } + ~SvgRasterizeInfo() + { + } + SvgRasterizeInfo(SvgRasterizeInfo&& info) noexcept // move constructor + { + // Reuse move operator + *this = std::move(info); + } + SvgRasterizeInfo& operator=(SvgRasterizeInfo&& info) noexcept // move operator + { + if(this != &info) + { + mId = info.mId; + mTask = std::move(info.mTask); + + mLoadId = info.mLoadId; + mWidth = info.mWidth; + mHeight = info.mHeight; + mAttemptAtlasing = info.mAttemptAtlasing; + + mRasterizeState = info.mRasterizeState; + mTextureSet = std::move(info.mTextureSet); + mAtlasRect = std::move(info.mAtlasRect); + mAtlasAttempted = info.mAtlasAttempted; + + mObservers = std::move(info.mObservers); + + mReferenceCount = info.mReferenceCount; + + info.mTask.Reset(); + info.mTextureSet.Reset(); + info.mObservers.Clear(); + info.mReferenceCount = 0; + } + return *this; + } + + private: + SvgRasterizeInfo() = delete; // Do not use empty constructor + SvgRasterizeInfo(const SvgRasterizeInfo& info) = delete; // Do not use copy constructor + SvgRasterizeInfo& operator=(const SvgRasterizeInfo& info) = delete; // Do not use copy assign + + public: + SvgRasterizeId mId; + SvgTaskPtr mTask; ///< Async task. It would be deleted when rasterizing completed. + + SvgLoadId mLoadId; + uint32_t mWidth; + uint32_t mHeight; + bool mAttemptAtlasing; ///< True if atlas requested. + + RasterizeState mRasterizeState; + Dali::TextureSet mTextureSet; ///< The texture set from atlas manager, or rasterized result at index 0. + Vector4 mAtlasRect; + bool mAtlasAttempted; ///< True if atlasing attempted. False if atlasing failed + + ObserverContainer mObservers; + + int32_t mReferenceCount; ///< The number of Svg visuals that use this data. + }; + +private: ///< Internal Methods for load + void LoadOrQueue(SvgLoader::SvgLoadInfo& loadInfo, SvgLoaderObserver* svgObserver); + void LoadRequest(SvgLoader::SvgLoadInfo& loadInfo, SvgLoaderObserver* svgObserver); + void LoadSynchronously(SvgLoader::SvgLoadInfo& loadInfo, SvgLoaderObserver* svgObserver); + void AddLoadObserver(SvgLoader::SvgLoadInfo& loadInfo, SvgLoaderObserver* svgObserver); + void RemoveLoadObserver(SvgLoader::SvgLoadInfo& loadInfo, SvgLoaderObserver* svgObserver); + void ProcessLoadQueue(); + + /** + * Notify the current observers that the svg load is complete, + * then remove the observers from the list. + * @param[in] loadInfo The load info. + */ + void NotifyLoadObservers(SvgLoader::SvgLoadInfo& loadInfo); + +private: ///< Internal Methods for rasterize + void RasterizeOrQueue(SvgLoader::SvgRasterizeInfo& rasterizeInfo, SvgLoaderObserver* svgObserver); + void RasterizeRequest(SvgLoader::SvgRasterizeInfo& rasterizeInfo, SvgLoaderObserver* svgObserver); + void RasterizeSynchronously(SvgLoader::SvgRasterizeInfo& rasterizeInfo, SvgLoaderObserver* svgObserver); + void AddRasterizeObserver(SvgLoader::SvgRasterizeInfo& rasterizeInfo, SvgLoaderObserver* svgObserver); + void RemoveRasterizeObserver(SvgLoader::SvgRasterizeInfo& rasterizeInfo, SvgLoaderObserver* svgObserver); + void ProcessRasterizeQueue(); + + /** + * Notify the current observers that the svg rasterize is complete, + * then remove the observers from the list. + * @param[in] rasterizeInfo The rasterize info. + */ + void NotifyRasterizeObservers(SvgLoader::SvgRasterizeInfo& rasterizeInfo); + +public: ///< Methods for the VisualFactoryCache. + void SetVisualFactoryCache(VisualFactoryCache& factoryCache) + { + mFactoryCache = &factoryCache; + } + +private: + /** + * @brief Common method to handle loading completion. + * @param[in] task The task that has completed. + */ + void AsyncLoadComplete(SvgTaskPtr task); + + /** + * @brief Common method to handle rasterizing completion. + * @param[in] task The task that has completed. + */ + void AsyncRasterizeComplete(SvgTaskPtr task); + + /** + * This is called by the SvgLoaderObserver when an observer is destroyed during loading. + * We use the callback to know when to remove an observer from our notify list. + * @param[in] observer The observer that generated the callback + */ + void LoadObserverDestroyed(SvgLoaderObserver* svgObserver); + + /** + * This is called by the SvgLoaderObserver when an observer is destroyed during rasterizing. + * We use the callback to know when to remove an observer from our notify list. + * @param[in] observer The observer that generated the callback + */ + void RasterizeObserverDestroyed(SvgLoaderObserver* svgObserver); + +protected: + /** + * Undefined copy constructor. + */ + SvgLoader(const SvgLoader&) = delete; + + /** + * Undefined assignment operator. + */ + SvgLoader& operator=(const SvgLoader& rhs) = delete; + +private: + VisualFactoryCache* mFactoryCache; ///< The holder of visual factory cache. It will be used when we want to get atlas manager; + + SvgLoadId mCurrentSvgLoadId; + SvgRasterizeId mCurrentSvgRasterizeId; + + std::vector mLoadCache{}; + std::vector mRasterizeCache{}; + + using LoadQueueElement = std::pair; + Dali::Vector mLoadQueue{}; ///< Queue of svg load after NotifyLoadObservers + SvgLoadId mLoadingQueueLoadId; ///< SvgLoadId when it is loading. it causes Load SVG to be queued. + + using RasterizeQueueElement = std::pair; + Dali::Vector mRasterizeQueue{}; ///< Queue of svg rasterze after NotifyRasterizeObservers + SvgRasterizeId mRasterizingQueueRasterizeId; ///< SvgRasterizeId when it is rasterizing. it causes Rasterize SVG to be queued. + + std::vector mLoadRemoveQueue{}; ///< Queue of SvgLoader::SvgLoadInfo to remove at PostProcess. It will be cleared after PostProcess. + std::vector mRasterizeRemoveQueue{}; ///< Queue of SvgLoader::SvgRasterizeInfo to remove at PostProcess. It will be cleared after PostProcess. + + bool mRemoveProcessorRegistered : 1; ///< Flag if remove processor registered or not. +}; + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_SVG_LOADER_H diff --git a/dali-toolkit/internal/visuals/svg/svg-task.cpp b/dali-toolkit/internal/visuals/svg/svg-task.cpp index 0a4ff89..dd9fd00 100644 --- a/dali-toolkit/internal/visuals/svg/svg-task.cpp +++ b/dali-toolkit/internal/visuals/svg/svg-task.cpp @@ -55,9 +55,10 @@ uint64_t GetNanoseconds() #endif } // namespace -SvgTask::SvgTask(VectorImageRenderer vectorRenderer, CallbackBase* callback, AsyncTask::PriorityType priorityType) +SvgTask::SvgTask(VectorImageRenderer vectorRenderer, int32_t id, CallbackBase* callback, AsyncTask::PriorityType priorityType) : AsyncTask(callback, priorityType), mVectorRenderer(vectorRenderer), + mId(id), mHasSucceeded(false) { } @@ -77,8 +78,8 @@ VectorImageRenderer SvgTask::GetRenderer() return mVectorRenderer; } -SvgLoadingTask::SvgLoadingTask(VectorImageRenderer vectorRenderer, const VisualUrl& url, EncodedImageBuffer encodedImageBuffer, float dpi, CallbackBase* callback) -: SvgTask(vectorRenderer, callback, url.GetProtocolType() == VisualUrl::ProtocolType::REMOTE ? AsyncTask::PriorityType::LOW : AsyncTask::PriorityType::HIGH), +SvgLoadingTask::SvgLoadingTask(VectorImageRenderer vectorRenderer, int32_t id, const VisualUrl& url, EncodedImageBuffer encodedImageBuffer, float dpi, CallbackBase* callback) +: SvgTask(vectorRenderer, id, callback, url.GetProtocolType() == VisualUrl::ProtocolType::REMOTE ? AsyncTask::PriorityType::LOW : AsyncTask::PriorityType::HIGH), mImageUrl(url), mEncodedImageBuffer(encodedImageBuffer), mDpi(dpi) @@ -175,8 +176,8 @@ bool SvgLoadingTask::IsReady() return true; } -SvgRasterizingTask::SvgRasterizingTask(VectorImageRenderer vectorRenderer, uint32_t width, uint32_t height, CallbackBase* callback) -: SvgTask(vectorRenderer, callback), +SvgRasterizingTask::SvgRasterizingTask(VectorImageRenderer vectorRenderer, int32_t id, uint32_t width, uint32_t height, CallbackBase* callback) +: SvgTask(vectorRenderer, id, callback), mWidth(width), mHeight(height) { diff --git a/dali-toolkit/internal/visuals/svg/svg-task.h b/dali-toolkit/internal/visuals/svg/svg-task.h index e23a8ae..5885ee7 100644 --- a/dali-toolkit/internal/visuals/svg/svg-task.h +++ b/dali-toolkit/internal/visuals/svg/svg-task.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_SVG_TASK_H /* - * Copyright (c) 2023 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 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. @@ -55,10 +55,11 @@ public: /** * Constructor * @param[in] vectorRenderer The vector rasterizer. + * @param[in] id The id of this task. * @param[in] callback The callback that is called when the operation is completed. * @param[in] priorityType The priority of this task. */ - SvgTask(VectorImageRenderer vectorRenderer, CallbackBase* callback, AsyncTask::PriorityType priorityType = AsyncTask::PriorityType::DEFAULT); + SvgTask(VectorImageRenderer vectorRenderer, int32_t id, CallbackBase* callback, AsyncTask::PriorityType priorityType = AsyncTask::PriorityType::DEFAULT); /** * Destructor. @@ -72,6 +73,15 @@ public: bool HasSucceeded() const; /** + * Get the id of task. + * @return The Id of task what we added from constructor. + */ + int32_t GetId() const + { + return mId; + } + + /** * @brief Get the task's imageRenderer * @return VectorImageRenderer */ @@ -92,6 +102,7 @@ private: protected: VectorImageRenderer mVectorRenderer; + const int32_t mId; bool mHasSucceeded; }; @@ -101,12 +112,13 @@ public: /** * Constructor * @param[in] vectorRenderer The vector rasterizer. + * @param[in] id The id of this task. * @param[in] url The URL to svg resource to use. * @param[in] encodedImageBuffer The resource buffer if required. * @param[in] dpi The DPI of the screen. * @param[in] callback The callback that is called when the operation is completed. */ - SvgLoadingTask(VectorImageRenderer vectorRenderer, const VisualUrl& url, EncodedImageBuffer encodedImageBuffer, float dpi, CallbackBase* callback); + SvgLoadingTask(VectorImageRenderer vectorRenderer, int32_t id, const VisualUrl& url, EncodedImageBuffer encodedImageBuffer, float dpi, CallbackBase* callback); /** * Destructor. @@ -151,11 +163,12 @@ public: /** * Constructor * @param[in] vectorRenderer The vector rasterizer. + * @param[in] id The id of this task. * @param[in] width The rasterization width. * @param[in] height The rasterization height. * @param[in] callback The callback that is called when the operation is completed. */ - SvgRasterizingTask(VectorImageRenderer vectorRenderer, uint32_t width, uint32_t height, CallbackBase* callback); + SvgRasterizingTask(VectorImageRenderer vectorRenderer, int32_t id, uint32_t width, uint32_t height, CallbackBase* callback); /** * Destructor. diff --git a/dali-toolkit/internal/visuals/svg/svg-visual.cpp b/dali-toolkit/internal/visuals/svg/svg-visual.cpp index 85ff479..919cd52 100644 --- a/dali-toolkit/internal/visuals/svg/svg-visual.cpp +++ b/dali-toolkit/internal/visuals/svg/svg-visual.cpp @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include #include @@ -42,8 +42,7 @@ namespace { const int CUSTOM_PROPERTY_COUNT(1); // atlas -// property name -const Dali::Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f); +constexpr Dali::Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f); } // namespace @@ -65,9 +64,12 @@ SvgVisualPtr SvgVisual::New(VisualFactoryCache& factoryCache, ImageVisualShaderF SvgVisual::SvgVisual(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory, const VisualUrl& imageUrl, ImageDimensions size) : Visual::Base(factoryCache, Visual::FittingMode::DONT_CARE, Toolkit::Visual::SVG), mImageVisualShaderFactory(shaderFactory), + mSvgLoader(factoryCache.GetSvgLoader()), + mSvgLoadId(SvgLoader::INVALID_SVG_LOAD_ID), + mSvgRasterizeId(SvgLoader::INVALID_SVG_RASTERIZE_ID), mAtlasRect(FULL_TEXTURE_RECT), + mAtlasRectIndex(Property::INVALID_INDEX), mImageUrl(imageUrl), - mVectorRenderer(VectorImageRenderer::New()), mDefaultWidth(0), mDefaultHeight(0), mPlacementActor(), @@ -84,13 +86,16 @@ SvgVisual::~SvgVisual() { if(Stage::IsInstalled()) { - if(mLoadingTask) + if(mSvgLoadId != SvgLoader::INVALID_SVG_LOAD_ID) { - Dali::AsyncTaskManager::Get().RemoveTask(mLoadingTask); + mSvgLoader.RequestLoadRemove(mSvgLoadId, this); + mSvgLoadId = SvgLoader::INVALID_SVG_LOAD_ID; } - if(mRasterizingTask) + if(mSvgRasterizeId != SvgLoader::INVALID_SVG_LOAD_ID) { - Dali::AsyncTaskManager::Get().RemoveTask(mRasterizingTask); + // We don't need to remove task synchronously. + mSvgLoader.RequestRasterizeRemove(mSvgRasterizeId, this, false); + mSvgRasterizeId = SvgLoader::INVALID_SVG_RASTERIZE_ID; } if(mImageUrl.IsBufferResource()) @@ -111,34 +116,10 @@ void SvgVisual::OnInitialize() Vector2 dpi = Stage::GetCurrent().GetDpi(); float meanDpi = (dpi.height + dpi.width) * 0.5f; - EncodedImageBuffer encodedImageBuffer; + const bool synchronousLoading = IsSynchronousLoadingRequired() && (mImageUrl.IsLocalResource() || mImageUrl.IsBufferResource()); - if(mImageUrl.IsBufferResource()) - { - // Increase reference count of External Resources : - // EncodedImageBuffer. - // Reference count will be decreased at destructor of the visual. - TextureManager& textureManager = mFactoryCache.GetTextureManager(); - textureManager.UseExternalResource(mImageUrl.GetUrl()); - - encodedImageBuffer = textureManager.GetEncodedImageBuffer(mImageUrl.GetUrl()); - } - - mLoadingTask = new SvgLoadingTask(mVectorRenderer, mImageUrl, encodedImageBuffer, meanDpi, MakeCallback(this, &SvgVisual::ApplyRasterizedImage)); - - if(IsSynchronousLoadingRequired() && (mImageUrl.IsLocalResource() || mImageUrl.IsBufferResource())) - { - mLoadingTask->Process(); - if(!mLoadingTask->HasSucceeded()) - { - mLoadFailed = true; - } - mLoadingTask.Reset(); // We don't need it anymore. - } - else - { - Dali::AsyncTaskManager::Get().AddTask(mLoadingTask); - } + // It will call SvgVisual::LoadComplete() synchronously if it required, or we already loaded same svg before. + mSvgLoadId = mSvgLoader.Load(mImageUrl, meanDpi, this, synchronousLoading); } void SvgVisual::DoSetProperties(const Property::Map& propertyMap) @@ -176,7 +157,11 @@ void SvgVisual::DoSetProperty(Property::Index index, const Property::Value& valu { case Toolkit::ImageVisual::Property::ATLASING: { - value.Get(mAttemptAtlasing); + bool atlasing = false; + if(value.Get(atlasing)) + { + mAttemptAtlasing = atlasing; + } break; } case Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING: @@ -255,17 +240,18 @@ void SvgVisual::DoSetOnScene(Actor& actor) void SvgVisual::DoSetOffScene(Actor& actor) { // Remove rasterizing task - if(mRasterizingTask) + if(mSvgRasterizeId != SvgLoader::INVALID_SVG_LOAD_ID) { - Dali::AsyncTaskManager::Get().RemoveTask(mRasterizingTask); - mRasterizingTask.Reset(); + // We don't need to remove task synchronously. + mSvgLoader.RequestRasterizeRemove(mSvgRasterizeId, this, false); + mSvgRasterizeId = SvgLoader::INVALID_SVG_RASTERIZE_ID; } actor.RemoveRenderer(mImpl->mRenderer); mPlacementActor.Reset(); - // Reset the visual size to zero so that when adding the actor back to stage the SVG rasterization is forced - mRasterizedSize = Vector2::ZERO; + // Reset the visual size so that when adding the actor back to stage the SVG rasterization is forced + mRasterizedSize = -Vector2::ONE; ///< Let we don't use zero since visual size could be zero after trasnform } void SvgVisual::GetNaturalSize(Vector2& naturalSize) @@ -325,115 +311,134 @@ void SvgVisual::EnablePreMultipliedAlpha(bool preMultiplied) } } +bool SvgVisual::AttemptAtlasing() const +{ + return (!mImpl->mCustomShader && (mImageUrl.IsLocalResource() || mImageUrl.IsBufferResource()) && mAttemptAtlasing); +} + void SvgVisual::AddRasterizationTask(const Vector2& size) { if(mImpl->mRenderer) { // Remove previous task - if(mRasterizingTask) + if(mSvgRasterizeId != SvgLoader::INVALID_SVG_LOAD_ID) { - Dali::AsyncTaskManager::Get().RemoveTask(mRasterizingTask); - mRasterizingTask.Reset(); + mSvgLoader.RequestRasterizeRemove(mSvgRasterizeId, this, true); + mSvgRasterizeId = SvgLoader::INVALID_SVG_RASTERIZE_ID; } uint32_t width = static_cast(roundf(size.width)); uint32_t height = static_cast(roundf(size.height)); - mRasterizingTask = new SvgRasterizingTask(mVectorRenderer, width, height, MakeCallback(this, &SvgVisual::ApplyRasterizedImage)); + const bool synchronousRasterize = IsSynchronousLoadingRequired() && (mImageUrl.IsLocalResource() || mImageUrl.IsBufferResource()); + const bool attemtAtlasing = AttemptAtlasing(); -#ifdef TRACE_ENABLED - reinterpret_cast(mRasterizingTask.Get())->SetUrl(mImageUrl); -#endif + mSvgRasterizeId = mSvgLoader.Rasterize(mSvgLoadId, width, height, attemtAtlasing, this, synchronousRasterize); + } +} + +/// Called when SvgLoader::Load is completed. +void SvgVisual::LoadComplete(int32_t loadId, Dali::VectorImageRenderer vectorImageRenderer) +{ + // mSvgLoadId might not be updated if svg file is cached. Update now. + mSvgLoadId = loadId; - if(IsSynchronousLoadingRequired() && mImageUrl.IsLocalResource()) + if(DALI_LIKELY(vectorImageRenderer)) + { + vectorImageRenderer.GetDefaultSize(mDefaultWidth, mDefaultHeight); + } + else if(!mLoadFailed) + { + SvgVisualPtr self = this; // Keep reference until this API finished + + mLoadFailed = true; + + // Remove rasterizing task if we requested before. + if(mSvgRasterizeId != SvgLoader::INVALID_SVG_LOAD_ID) { - mRasterizingTask->Process(); - ApplyRasterizedImage(mRasterizingTask); - mRasterizingTask.Reset(); // We don't need it anymore. + mSvgLoader.RequestRasterizeRemove(mSvgRasterizeId, this, true); + mSvgRasterizeId = SvgLoader::INVALID_SVG_RASTERIZE_ID; } - else + + if(IsOnScene()) { - Dali::AsyncTaskManager::Get().AddTask(mRasterizingTask); + Actor actor = mPlacementActor.GetHandle(); + if(actor && mImpl->mRenderer) + { + Vector2 imageSize = Vector2::ZERO; + imageSize = actor.GetProperty(Actor::Property::SIZE).Get(); + mFactoryCache.UpdateBrokenImageRenderer(mImpl->mRenderer, imageSize); + actor.AddRenderer(mImpl->mRenderer); + // reset the weak handle so that the renderer only get added to actor once + mPlacementActor.Reset(); + } + + // Emit failed signal only if this visual is scene-on + ResourceReady(Toolkit::Visual::ResourceStatus::FAILED); } } } -void SvgVisual::ApplyRasterizedImage(SvgTaskPtr task) +/// Called when SvgLoader::Rasterize is completed. +void SvgVisual::RasterizeComplete(int32_t rasterizeId, Dali::TextureSet textureSet, Vector4 atlasRect) { - SvgVisualPtr self = this; // Keep reference until this API finished + // rasterize id might not be updated if rasterize is cached. + mSvgRasterizeId = rasterizeId; - if(task->HasSucceeded()) + if(DALI_LIKELY(textureSet)) { - PixelData rasterizedPixelData = task->GetPixelData(); - if(mDefaultWidth == 0 || mDefaultHeight == 0) - { - task->GetRenderer().GetDefaultSize(mDefaultWidth, mDefaultHeight); - } + bool updateShader = false; - // We don't need to keep tasks anymore. reset now. - if(task == mLoadingTask) + if(AttemptAtlasing() && atlasRect != FULL_TEXTURE_RECT) { - mLoadingTask.Reset(); + mAtlasRect = atlasRect; + if(DALI_UNLIKELY(!(mImpl->mFlags & Impl::IS_ATLASING_APPLIED))) + { + updateShader = true; + } + mImpl->mFlags |= Impl::IS_ATLASING_APPLIED; } - if(task == mRasterizingTask) + else { - mRasterizingTask.Reset(); + mAtlasRect = FULL_TEXTURE_RECT; + if(DALI_UNLIKELY(mImpl->mFlags & Impl::IS_ATLASING_APPLIED)) + { + updateShader = true; + } + mImpl->mFlags &= ~Impl::IS_ATLASING_APPLIED; } - // Rasterization success - if(rasterizedPixelData && IsOnScene()) + if(DALI_LIKELY(mImpl->mRenderer)) { - mRasterizedSize.x = static_cast(rasterizedPixelData.GetWidth()); - mRasterizedSize.y = static_cast(rasterizedPixelData.GetHeight()); - TextureSet currentTextureSet = mImpl->mRenderer.GetTextures(); - if(mImpl->mFlags & Impl::IS_ATLASING_APPLIED) - { - mFactoryCache.GetAtlasManager()->Remove(currentTextureSet, mAtlasRect); - } - TextureSet textureSet; - - if(mAttemptAtlasing && !mImpl->mCustomShader) + if(mAtlasRectIndex == Property::INVALID_INDEX) { - Vector4 atlasRect; - textureSet = mFactoryCache.GetAtlasManager()->Add(atlasRect, rasterizedPixelData); - if(textureSet) // atlasing + if(DALI_UNLIKELY(mAtlasRect != FULL_TEXTURE_RECT)) { - if(textureSet != currentTextureSet) - { - mImpl->mRenderer.SetTextures(textureSet); - } - mImpl->mRenderer.RegisterProperty(ATLAS_RECT_UNIFORM_NAME, atlasRect); - mAtlasRect = atlasRect; - mImpl->mFlags |= Impl::IS_ATLASING_APPLIED; + // Register atlas rect property only if it's not full texture rect. + mAtlasRectIndex = mImpl->mRenderer.RegisterUniqueProperty(mAtlasRectIndex, ATLAS_RECT_UNIFORM_NAME, mAtlasRect); } } - - if(!textureSet) // no atlasing - mAttemptAtlasing is false or adding to atlas is failed + else { - Texture texture = Texture::New(Dali::TextureType::TEXTURE_2D, Pixel::RGBA8888, rasterizedPixelData.GetWidth(), rasterizedPixelData.GetHeight()); - texture.Upload(rasterizedPixelData); - mImpl->mFlags &= ~Impl::IS_ATLASING_APPLIED; - - if(mAtlasRect == FULL_TEXTURE_RECT) - { - textureSet = currentTextureSet; - } - else - { - textureSet = TextureSet::New(); - mImpl->mRenderer.SetTextures(textureSet); + mImpl->mRenderer.SetProperty(mAtlasRectIndex, mAtlasRect); + } - mImpl->mRenderer.RegisterProperty(ATLAS_RECT_UNIFORM_NAME, FULL_TEXTURE_RECT); - mAtlasRect = FULL_TEXTURE_RECT; - } + if(textureSet != currentTextureSet) + { + mImpl->mRenderer.SetTextures(textureSet); + } - if(textureSet) - { - textureSet.SetTexture(0, texture); - } + if(DALI_UNLIKELY(updateShader)) + { + UpdateShader(); } + } + + if(IsOnScene()) + { + SvgVisualPtr self = this; // Keep reference until this API finished // Rasterized pixels are uploaded to texture. If weak handle is holding a placement actor, it is the time to add the renderer to actor. Actor actor = mPlacementActor.GetHandle(); @@ -450,36 +455,37 @@ void SvgVisual::ApplyRasterizedImage(SvgTaskPtr task) } else if(!mLoadFailed) { + SvgVisualPtr self = this; // Keep reference until this API finished + mLoadFailed = true; - // Remove rasterizing task if we requested before. - if(mRasterizingTask) + if(IsOnScene()) { - Dali::AsyncTaskManager::Get().RemoveTask(mRasterizingTask); - mRasterizingTask.Reset(); - } + Actor actor = mPlacementActor.GetHandle(); + if(actor && mImpl->mRenderer) + { + Vector2 imageSize = Vector2::ZERO; + imageSize = actor.GetProperty(Actor::Property::SIZE).Get(); + mFactoryCache.UpdateBrokenImageRenderer(mImpl->mRenderer, imageSize); + actor.AddRenderer(mImpl->mRenderer); - // We don't need to keep tasks anymore. reset now. - if(task == mLoadingTask) - { - mLoadingTask.Reset(); - } + // reset the weak handle so that the renderer only get added to actor once + mPlacementActor.Reset(); + } - Actor actor = mPlacementActor.GetHandle(); - if(actor) - { - Vector2 imageSize = Vector2::ZERO; - imageSize = actor.GetProperty(Actor::Property::SIZE).Get(); - mFactoryCache.UpdateBrokenImageRenderer(mImpl->mRenderer, imageSize); - actor.AddRenderer(mImpl->mRenderer); + // Emit failed signal only if this visual is scene-on + ResourceReady(Toolkit::Visual::ResourceStatus::FAILED); } - - ResourceReady(Toolkit::Visual::ResourceStatus::FAILED); } } void SvgVisual::OnSetTransform() { + if(mImpl->mRenderer) + { + mImpl->mTransform.SetUniforms(mImpl->mRenderer, Direction::LEFT_TO_RIGHT); + } + if(IsOnScene() && !mLoadFailed) { Vector2 size; @@ -498,17 +504,12 @@ void SvgVisual::OnSetTransform() size.width = static_cast(roundf(size.width)); size.height = static_cast(roundf(size.height)); - if(size != mRasterizedSize || mDefaultWidth == 0 || mDefaultHeight == 0) + if(size != mRasterizedSize) { mRasterizedSize = size; AddRasterizationTask(size); } } - - if(mImpl->mRenderer) - { - mImpl->mTransform.SetUniforms(mImpl->mRenderer, Direction::LEFT_TO_RIGHT); - } } void SvgVisual::UpdateShader() @@ -528,7 +529,7 @@ Shader SvgVisual::GenerateShader() const shader = mImageVisualShaderFactory.GetShader( mFactoryCache, ImageVisualShaderFeatureBuilder() - .EnableTextureAtlas(mAttemptAtlasing) + .EnableTextureAtlas(mImpl->mFlags & Visual::Base::Impl::IS_ATLASING_APPLIED) .EnableRoundedCorner(IsRoundedCornerRequired()) .EnableBorderline(IsBorderlineRequired())); } diff --git a/dali-toolkit/internal/visuals/svg/svg-visual.h b/dali-toolkit/internal/visuals/svg/svg-visual.h index faf8a22..29e10e9 100644 --- a/dali-toolkit/internal/visuals/svg/svg-visual.h +++ b/dali-toolkit/internal/visuals/svg/svg-visual.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_INTERNAL_SVG_VISUAL_H /* - * Copyright (c) 2022 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 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. @@ -23,9 +23,10 @@ #include // INTERNAL INCLUDES +#include +#include #include #include -#include namespace Dali { @@ -47,7 +48,8 @@ typedef IntrusivePtr SvgVisualPtr; * | url | STRING | * */ -class SvgVisual : public Visual::Base +class SvgVisual : public Visual::Base, + public SvgLoaderObserver { public: /** @@ -150,13 +152,16 @@ protected: */ Shader GenerateShader() const override; -public: +protected: // Implementation of SvgLoaderObserver /** - * @bried Apply the rasterized image to the visual. - * - * @param[in] task SvgTaskPtr + * @copydoc Dali::Toolkit::Internal::SvgLoaderObserver::LoadComplete + */ + void LoadComplete(int32_t loadId, Dali::VectorImageRenderer vectorImageRenderer) override; + + /** + * @copydoc Dali::Toolkit::Internal::SvgLoaderObserver::RasterizeComplete */ - void ApplyRasterizedImage(SvgTaskPtr task); + void RasterizeComplete(int32_t rasterizeId, Dali::TextureSet textureSet, Vector4 atlasRect) override; private: /** @@ -173,6 +178,12 @@ private: */ void DoSetProperty(Property::Index index, const Property::Value& value); + /** + * @brief Checks if atlasing should be attempted + * @return bool returns true if atlasing can be attempted. + */ + bool AttemptAtlasing() const; + // Undefined SvgVisual(const SvgVisual& svgRenderer); @@ -181,18 +192,21 @@ private: private: ImageVisualShaderFactory& mImageVisualShaderFactory; - Vector4 mAtlasRect; - VisualUrl mImageUrl; - VectorImageRenderer mVectorRenderer; - uint32_t mDefaultWidth; - uint32_t mDefaultHeight; - WeakHandle mPlacementActor; - Vector2 mRasterizedSize; - Dali::ImageDimensions mDesiredSize{}; - SvgTaskPtr mLoadingTask; - SvgTaskPtr mRasterizingTask; - bool mLoadFailed; - bool mAttemptAtlasing; ///< If true will attempt atlasing, otherwise create unique texture + SvgLoader& mSvgLoader; ///< reference to Svg loader for fast access + + SvgLoader::SvgLoadId mSvgLoadId; + SvgLoader::SvgRasterizeId mSvgRasterizeId; + + Vector4 mAtlasRect; + Property::Index mAtlasRectIndex; + VisualUrl mImageUrl; + uint32_t mDefaultWidth; + uint32_t mDefaultHeight; + WeakHandle mPlacementActor; + Vector2 mRasterizedSize; + Dali::ImageDimensions mDesiredSize{}; + bool mLoadFailed : 1; + bool mAttemptAtlasing : 1; ///< If true will attempt atlasing, otherwise create unique texture }; } // namespace Internal diff --git a/dali-toolkit/internal/visuals/visual-factory-cache.cpp b/dali-toolkit/internal/visuals/visual-factory-cache.cpp index afc3784..5228ce4 100644 --- a/dali-toolkit/internal/visuals/visual-factory-cache.cpp +++ b/dali-toolkit/internal/visuals/visual-factory-cache.cpp @@ -65,6 +65,7 @@ VisualFactoryCache::VisualFactoryCache(bool preMultiplyOnLoad) mDefaultBrokenImageUrl(""), mUseDefaultBrokenImageOnly(true) { + mSvgLoader.SetVisualFactoryCache(*this); } VisualFactoryCache::~VisualFactoryCache() @@ -147,6 +148,11 @@ NPatchLoader& VisualFactoryCache::GetNPatchLoader() return mNPatchLoader; } +SvgLoader& VisualFactoryCache::GetSvgLoader() +{ + return mSvgLoader; +} + VectorAnimationManager& VisualFactoryCache::GetVectorAnimationManager() { if(!mVectorAnimationManager) diff --git a/dali-toolkit/internal/visuals/visual-factory-cache.h b/dali-toolkit/internal/visuals/visual-factory-cache.h index 8a9f133..11a591d 100644 --- a/dali-toolkit/internal/visuals/visual-factory-cache.h +++ b/dali-toolkit/internal/visuals/visual-factory-cache.h @@ -26,6 +26,7 @@ // INTERNAL INCLUDES #include #include +#include #include namespace Dali @@ -38,6 +39,7 @@ namespace Internal { class ImageAtlasManager; class NPatchLoader; +class SvgLoader; class TextureManager; class VectorAnimationManager; @@ -258,6 +260,12 @@ public: NPatchLoader& GetNPatchLoader(); /** + * Get the Svg texture cache. + * @return A reference to the Svg loader + */ + SvgLoader& GetSvgLoader(); + + /** * Get the vector animation manager. * @return A reference to the vector animation manager. */ @@ -356,6 +364,7 @@ private: ImageAtlasManagerPtr mAtlasManager; TextureManager mTextureManager; NPatchLoader mNPatchLoader; + SvgLoader mSvgLoader; std::unique_ptr mVectorAnimationManager; bool mPreMultiplyOnLoad; diff --git a/dali-toolkit/internal/visuals/visual-factory-impl.cpp b/dali-toolkit/internal/visuals/visual-factory-impl.cpp index 1b0a44e..b6f6ac4 100644 --- a/dali-toolkit/internal/visuals/visual-factory-impl.cpp +++ b/dali-toolkit/internal/visuals/visual-factory-impl.cpp @@ -450,6 +450,11 @@ Internal::TextureManager& VisualFactory::GetTextureManager() return GetFactoryCache().GetTextureManager(); } +Internal::SvgLoader& VisualFactory::GetSvgLoader() +{ + return GetFactoryCache().GetSvgLoader(); +} + void VisualFactory::SetBrokenImageUrl(Toolkit::StyleManager& styleManager) { const std::string imageDirPath = AssetManager::GetDaliImagePath(); diff --git a/dali-toolkit/internal/visuals/visual-factory-impl.h b/dali-toolkit/internal/visuals/visual-factory-impl.h index daecbb0..b4cde6e 100644 --- a/dali-toolkit/internal/visuals/visual-factory-impl.h +++ b/dali-toolkit/internal/visuals/visual-factory-impl.h @@ -121,6 +121,11 @@ public: */ Internal::TextureManager& GetTextureManager(); + /** + * @return the reference to svg loader + */ + Internal::SvgLoader& GetSvgLoader(); + protected: /** * A reference counted object may only be deleted by calling Unreference() -- 2.7.4