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 <eunkiki.hong@samsung.com>
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
--- /dev/null
+/*
+ * 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 <iostream>
+
+#include <stdlib.h>
+
+#include <dali-toolkit-test-suite-utils.h>
+#include <toolkit-environment-variable.h>
+#include <toolkit-event-thread-callback.h>
+#include <toolkit-timer.h>
+
+#include <dali-toolkit/internal/visuals/svg/svg-loader.h>
+
+#include <dali-toolkit/internal/texture-manager/texture-manager-impl.h>
+#include <dali-toolkit/internal/visuals/image/image-atlas-manager.h>
+#include <dali-toolkit/internal/visuals/svg/svg-loader-observer.h>
+#include <dali-toolkit/internal/visuals/visual-factory-impl.h> ///< For VisualFactory's member SvgLoader.
+#include <dali-toolkit/public-api/image-loader/image-url.h>
+#include <dali-toolkit/public-api/image-loader/image.h>
+#include <dali/devel-api/adaptor-framework/pixel-buffer.h>
+
+#include <test-encoded-image-buffer.h>
+
+#if defined(ELDBUS_ENABLED)
+#include <automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/dbus-wrapper.h>
+#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<DBusWrapper>(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<void(void*)> signal)
+ {
+ mLoadSignals.push_back(signal);
+ }
+
+ void ConnectRasterizeFunction(std::function<void(void*)> signal)
+ {
+ mRasterizeSignals.push_back(signal);
+ }
+
+public:
+ std::vector<std::function<void(void*)>> mLoadSignals;
+ std::vector<std::function<void(void*)>> 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<std::pair<uint32_t, uint32_t>> 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<CustomData*>(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<CustomData*>(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<CustomData*>(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<CustomData*>(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<CustomData*>(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<CustomData*>(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
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);
Property::Map* retMap = transformValue->GetMap();
DALI_TEST_CHECK(retMap);
+ auto size = retMap->Find(Visual::Transform::Property::SIZE)->Get<Vector2>();
+
// Fitting mode is applied at this point. because we do FittingMode in control
- DALI_TEST_EQUALS(retMap->Find(Visual::Transform::Property::SIZE)->Get<Vector2>(), Vector2::ONE, TEST_LOCATION);
+ DALI_TEST_EQUALS(size, Vector2(100.0f, 100.0f), TEST_LOCATION);
}
+} // namespace
+
int UtcDaliImageViewCheckResourceReady(void)
{
ToolkitTestApplication application;
END_TEST;
}
+namespace
+{
+Vector3 gNaturalSize;
+
void OnRelayoutOverride(Size size)
{
gNaturalSize = size; // Size Relayout is using
}
+} // namespace
int UtcDaliImageViewReplaceImageAndGetNaturalSize(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;
application.Render();
DALI_TEST_EQUALS(imageView.GetRendererCount(), 1u, TEST_LOCATION);
+
+ imageView.Unparent();
}
END_TEST;
tet_infoline("ImageView Testing SVG image sync loading");
+ for(int testCase = 0; testCase < 3; ++testCase)
{
ImageView imageView = ImageView::New();
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);
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;
}
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;
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();
// Wait for loading & rasterization
DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
+ DALI_TEST_CHECK(gResourceReadySignalFired);
+
application.SendNotification();
application.Render(16);
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);
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;
}
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
/*
- * 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.
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<DummyControlImpl&>(control1.GetImplementation());
+ dummyImpl1.RegisterVisual(DummyControl::Property::TEST_VISUAL, visual1);
+
+ DummyControl control2 = DummyControl::New();
+ DummyControlImpl& dummyImpl2 = static_cast<DummyControlImpl&>(control2.GetImplementation());
+ dummyImpl2.RegisterVisual(DummyControl::Property::TEST_VISUAL, visual2);
+
+ DummyControl control3 = DummyControl::New();
+ DummyControlImpl& dummyImpl3 = static_cast<DummyControlImpl&>(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
${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
}
}
-bool ImageVisual::AttemptAtlasing()
+bool ImageVisual::AttemptAtlasing() const
{
return (!mImpl->mCustomShader && (mImageUrl.IsLocalResource() || mImageUrl.IsBufferResource()) && mAttemptAtlasing);
}
* @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
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.
};
/**
--- /dev/null
+/*
+ * 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 <dali-toolkit/internal/visuals/svg/svg-loader-observer.h>
+
+namespace Dali::Toolkit::Internal
+{
+SvgLoaderObserver::~SvgLoaderObserver()
+{
+ if(!mLoadDestructionSignal.Empty())
+ {
+ mLoadDestructionSignal.Emit(this);
+ }
+ if(!mRasterizeDestructionSignal.Empty())
+ {
+ mRasterizeDestructionSignal.Emit(this);
+ }
+}
+} // namespace Dali::Toolkit::Internal
--- /dev/null
+#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 <dali/devel-api/adaptor-framework/vector-image-renderer.h>
+#include <dali/public-api/rendering/texture-set.h>
+
+// 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<void(SvgLoaderObserver*)> 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
--- /dev/null
+/*
+ * 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 <dali-toolkit/internal/visuals/svg/svg-loader.h>
+
+// INTERNAL HEADERS
+#include <dali-toolkit/internal/texture-manager/texture-manager-impl.h> ///< for EncodedImageBuffer
+#include <dali-toolkit/internal/visuals/image/image-atlas-manager.h>
+#include <dali-toolkit/internal/visuals/svg/svg-task.h>
+#include <dali-toolkit/internal/visuals/svg/svg-visual.h>
+#include <dali-toolkit/internal/visuals/visual-factory-cache.h>
+
+// EXTERNAL HEADERS
+#include <dali/devel-api/common/hash.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
+#include <dali/integration-api/debug.h>
+#include <dali/integration-api/trace.h>
+#include <dali/public-api/adaptor-framework/encoded-image-buffer.h>
+
+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<SvgCacheIndex>(static_cast<uint32_t>(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<size_t>(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<int>(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<int>(mLoadCache[loadCacheIndex].mReferenceCount));
+ }
+
+ rasterizeId = GenerateUniqueSvgRasterizeId();
+ cacheIndex = static_cast<SvgCacheIndex>(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<size_t>(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<int>(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<size_t>(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<size_t>(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<size_t>(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<uint32_t>(mLoadCache.size());
+
+ for(uint32_t i = 0; i < size; ++i)
+ {
+ if(mLoadCache[i].mId == loadId)
+ {
+ return static_cast<SvgCacheIndex>(i);
+ }
+ }
+ return SvgLoader::INVALID_SVG_CACHE_INDEX;
+}
+
+SvgLoader::SvgCacheIndex SvgLoader::GetCacheIndexFromRasterizeCacheById(const SvgLoader::SvgRasterizeId rasterizeId) const
+{
+ const uint32_t size = static_cast<uint32_t>(mRasterizeCache.size());
+
+ for(uint32_t i = 0; i < size; ++i)
+ {
+ if(mRasterizeCache[i].mId == rasterizeId)
+ {
+ return static_cast<SvgCacheIndex>(i);
+ }
+ }
+ return SvgLoader::INVALID_SVG_CACHE_INDEX;
+}
+
+SvgLoader::SvgCacheIndex SvgLoader::FindCacheIndexFromLoadCache(const VisualUrl& imageUrl, float dpi) const
+{
+ const uint32_t size = static_cast<uint32_t>(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<SvgCacheIndex>(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<uint32_t>(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<SvgCacheIndex>(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<size_t>(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<int>(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<std::size_t>(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<size_t>(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<int>(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<std::size_t>(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<size_t>(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<size_t>(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
--- /dev/null
+#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 <dali/devel-api/adaptor-framework/vector-image-renderer.h>
+#include <dali/integration-api/processor-interface.h>
+#include <dali/public-api/adaptor-framework/async-task-manager.h>
+#include <dali/public-api/common/intrusive-ptr.h>
+#include <dali/public-api/rendering/texture-set.h>
+#include <string>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/visuals/svg/svg-loader-observer.h>
+#include <dali-toolkit/internal/visuals/svg/svg-task.h>
+#include <dali-toolkit/internal/visuals/visual-url.h>
+
+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<SvgCacheIndex>(std::numeric_limits<SvgCacheIndex>::max());
+
+ using ObserverContainer = Dali::Vector<SvgLoaderObserver*>;
+
+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<SvgLoader::SvgLoadInfo> mLoadCache{};
+ std::vector<SvgLoader::SvgRasterizeInfo> mRasterizeCache{};
+
+ using LoadQueueElement = std::pair<SvgLoadId, SvgLoaderObserver*>;
+ Dali::Vector<LoadQueueElement> 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<SvgRasterizeId, SvgLoaderObserver*>;
+ Dali::Vector<RasterizeQueueElement> mRasterizeQueue{}; ///< Queue of svg rasterze after NotifyRasterizeObservers
+ SvgRasterizeId mRasterizingQueueRasterizeId; ///< SvgRasterizeId when it is rasterizing. it causes Rasterize SVG to be queued.
+
+ std::vector<SvgLoadId> mLoadRemoveQueue{}; ///< Queue of SvgLoader::SvgLoadInfo to remove at PostProcess. It will be cleared after PostProcess.
+ std::vector<SvgRasterizeId> 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
#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)
{
}
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)
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)
{
#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.
/**
* 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.
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
*/
protected:
VectorImageRenderer mVectorRenderer;
+ const int32_t mId;
bool mHasSucceeded;
};
/**
* 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.
/**
* 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.
#include <dali-toolkit/internal/visuals/image/image-atlas-manager.h>
#include <dali-toolkit/internal/visuals/image/image-visual-shader-factory.h>
#include <dali-toolkit/internal/visuals/image/image-visual-shader-feature-builder.h>
-#include <dali-toolkit/internal/visuals/svg/svg-task.h>
+#include <dali-toolkit/internal/visuals/svg/svg-loader.h>
#include <dali-toolkit/internal/visuals/visual-base-data-impl.h>
#include <dali-toolkit/internal/visuals/visual-string-constants.h>
#include <dali-toolkit/public-api/visuals/image-visual-properties.h>
{
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
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(),
{
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())
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)
{
case Toolkit::ImageVisual::Property::ATLASING:
{
- value.Get(mAttemptAtlasing);
+ bool atlasing = false;
+ if(value.Get(atlasing))
+ {
+ mAttemptAtlasing = atlasing;
+ }
break;
}
case Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING:
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)
}
}
+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<uint32_t>(roundf(size.width));
uint32_t height = static_cast<uint32_t>(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<SvgRasterizingTask*>(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<Vector2>();
+ 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<float>(rasterizedPixelData.GetWidth());
- mRasterizedSize.y = static_cast<float>(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();
}
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<Vector2>();
+ 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<Vector2>();
- 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;
size.width = static_cast<uint32_t>(roundf(size.width));
size.height = static_cast<uint32_t>(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()
shader = mImageVisualShaderFactory.GetShader(
mFactoryCache,
ImageVisualShaderFeatureBuilder()
- .EnableTextureAtlas(mAttemptAtlasing)
+ .EnableTextureAtlas(mImpl->mFlags & Visual::Base::Impl::IS_ATLASING_APPLIED)
.EnableRoundedCorner(IsRoundedCornerRequired())
.EnableBorderline(IsBorderlineRequired()));
}
#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.
#include <dali/public-api/object/weak-handle.h>
// INTERNAL INCLUDES
+#include <dali-toolkit/internal/visuals/svg/svg-loader-observer.h>
+#include <dali-toolkit/internal/visuals/svg/svg-loader.h>
#include <dali-toolkit/internal/visuals/visual-base-impl.h>
#include <dali-toolkit/internal/visuals/visual-url.h>
-#include <dali-toolkit/internal/visuals/svg/svg-task.h>
namespace Dali
{
* | url | STRING |
*
*/
-class SvgVisual : public Visual::Base
+class SvgVisual : public Visual::Base,
+ public SvgLoaderObserver
{
public:
/**
*/
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:
/**
*/
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);
private:
ImageVisualShaderFactory& mImageVisualShaderFactory;
- Vector4 mAtlasRect;
- VisualUrl mImageUrl;
- VectorImageRenderer mVectorRenderer;
- uint32_t mDefaultWidth;
- uint32_t mDefaultHeight;
- WeakHandle<Actor> 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<Actor> mPlacementActor;
+ Vector2 mRasterizedSize;
+ Dali::ImageDimensions mDesiredSize{};
+ bool mLoadFailed : 1;
+ bool mAttemptAtlasing : 1; ///< If true will attempt atlasing, otherwise create unique texture
};
} // namespace Internal
mDefaultBrokenImageUrl(""),
mUseDefaultBrokenImageOnly(true)
{
+ mSvgLoader.SetVisualFactoryCache(*this);
}
VisualFactoryCache::~VisualFactoryCache()
return mNPatchLoader;
}
+SvgLoader& VisualFactoryCache::GetSvgLoader()
+{
+ return mSvgLoader;
+}
+
VectorAnimationManager& VisualFactoryCache::GetVectorAnimationManager()
{
if(!mVectorAnimationManager)
// INTERNAL INCLUDES
#include <dali-toolkit/internal/texture-manager/texture-manager-impl.h>
#include <dali-toolkit/internal/visuals/npatch/npatch-loader.h>
+#include <dali-toolkit/internal/visuals/svg/svg-loader.h>
#include <dali/devel-api/rendering/renderer-devel.h>
namespace Dali
{
class ImageAtlasManager;
class NPatchLoader;
+class SvgLoader;
class TextureManager;
class VectorAnimationManager;
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.
*/
ImageAtlasManagerPtr mAtlasManager;
TextureManager mTextureManager;
NPatchLoader mNPatchLoader;
+ SvgLoader mSvgLoader;
std::unique_ptr<VectorAnimationManager> mVectorAnimationManager;
bool mPreMultiplyOnLoad;
return GetFactoryCache().GetTextureManager();
}
+Internal::SvgLoader& VisualFactory::GetSvgLoader()
+{
+ return GetFactoryCache().GetSvgLoader();
+}
+
void VisualFactory::SetBrokenImageUrl(Toolkit::StyleManager& styleManager)
{
const std::string imageDirPath = AssetManager::GetDaliImagePath();
*/
Internal::TextureManager& GetTextureManager();
+ /**
+ * @return the reference to svg loader
+ */
+ Internal::SvgLoader& GetSvgLoader();
+
protected:
/**
* A reference counted object may only be deleted by calling Unreference()