Implement SVG image file cache system 49/313349/15
authorEunki, Hong <eunkiki.hong@samsung.com>
Mon, 24 Jun 2024 05:14:55 +0000 (14:14 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Tue, 23 Jul 2024 05:42:40 +0000 (14:42 +0900)
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>
20 files changed:
automated-tests/src/dali-toolkit-internal/CMakeLists.txt
automated-tests/src/dali-toolkit-internal/utc-Dali-SvgLoader.cpp [new file with mode: 0644]
automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp
automated-tests/src/dali-toolkit/utc-Dali-SvgVisual.cpp
dali-toolkit/internal/file.list
dali-toolkit/internal/visuals/image/image-visual.cpp
dali-toolkit/internal/visuals/image/image-visual.h
dali-toolkit/internal/visuals/npatch/npatch-loader.h
dali-toolkit/internal/visuals/svg/svg-loader-observer.cpp [new file with mode: 0644]
dali-toolkit/internal/visuals/svg/svg-loader-observer.h [new file with mode: 0644]
dali-toolkit/internal/visuals/svg/svg-loader.cpp [new file with mode: 0644]
dali-toolkit/internal/visuals/svg/svg-loader.h [new file with mode: 0644]
dali-toolkit/internal/visuals/svg/svg-task.cpp
dali-toolkit/internal/visuals/svg/svg-task.h
dali-toolkit/internal/visuals/svg/svg-visual.cpp
dali-toolkit/internal/visuals/svg/svg-visual.h
dali-toolkit/internal/visuals/visual-factory-cache.cpp
dali-toolkit/internal/visuals/visual-factory-cache.h
dali-toolkit/internal/visuals/visual-factory-impl.cpp
dali-toolkit/internal/visuals/visual-factory-impl.h

index 68bbf2f..5d7567b 100755 (executable)
@@ -20,6 +20,7 @@ SET(TC_SOURCES
  utc-Dali-LineHelperFunctions.cpp
  utc-Dali-LogicalModel.cpp
  utc-Dali-PropertyHelper.cpp
+ utc-Dali-SvgLoader.cpp
  utc-Dali-Text-AbstractStyleCharacterRun.cpp
  utc-Dali-Text-Characters.cpp
  utc-Dali-Text-CharacterSetConversion.cpp
diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-SvgLoader.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-SvgLoader.cpp
new file mode 100644 (file)
index 0000000..58a815c
--- /dev/null
@@ -0,0 +1,1100 @@
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <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
index ef92cad..c60d4ee 100644 (file)
@@ -1184,15 +1184,16 @@ int UtcDaliImageViewSetImageUrl(void)
   END_TEST;
 }
 
-bool    gResourceReadySignalFired = false;
-Vector3 gNaturalSize;
+namespace
+{
+bool gResourceReadySignalFired = false;
 
 void ResourceReadySignal(Control control)
 {
   gResourceReadySignalFired = true;
 }
 
-void OnResourceReadySignalSVG(Control control)
+void OnResourceReadySyncSVGLoading02(Control control)
 {
   // Check whether Image Visual transforms on ImageView::OnRelayout()
   Toolkit::Internal::Control& controlImpl = Toolkit::Internal::GetImplementation(control);
@@ -1205,10 +1206,14 @@ void OnResourceReadySignalSVG(Control control)
   Property::Map* retMap = transformValue->GetMap();
   DALI_TEST_CHECK(retMap);
 
+  auto size = retMap->Find(Visual::Transform::Property::SIZE)->Get<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;
@@ -1499,10 +1504,15 @@ int UtcDaliImageViewReloadAlphaMaskImage(void)
   END_TEST;
 }
 
+namespace
+{
+Vector3 gNaturalSize;
+
 void OnRelayoutOverride(Size size)
 {
   gNaturalSize = size; // Size Relayout is using
 }
+} // namespace
 
 int UtcDaliImageViewReplaceImageAndGetNaturalSize(void)
 {
@@ -2922,8 +2932,17 @@ int UtcDaliImageViewLoadRemoteSVG(void)
     application.Render();
 
     DALI_TEST_EQUALS(imageView.GetRendererCount(), 1u, TEST_LOCATION);
+
+    imageView.Unparent();
   }
 
+  // Insure to Remove svg cache.
+  application.SendNotification();
+  application.Render();
+  application.RunIdles();
+  application.SendNotification();
+  application.Render();
+
   // Without size set
   {
     Toolkit::ImageView imageView;
@@ -2948,6 +2967,8 @@ int UtcDaliImageViewLoadRemoteSVG(void)
     application.Render();
 
     DALI_TEST_EQUALS(imageView.GetRendererCount(), 1u, TEST_LOCATION);
+
+    imageView.Unparent();
   }
 
   END_TEST;
@@ -3015,6 +3036,7 @@ int UtcDaliImageViewSyncSVGLoading02(void)
 
   tet_infoline("ImageView Testing SVG image sync loading");
 
+  for(int testCase = 0; testCase < 3; ++testCase)
   {
     ImageView imageView = ImageView::New();
 
@@ -3025,7 +3047,28 @@ int UtcDaliImageViewSyncSVGLoading02(void)
     syncLoadingMap.Insert(Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING, true);
     syncLoadingMap.Insert(DevelVisual::Property::VISUAL_FITTING_MODE, Toolkit::DevelVisual::FIT_KEEP_ASPECT_RATIO);
     imageView.SetProperty(ImageView::Property::IMAGE, syncLoadingMap);
-    imageView.ResourceReadySignal().Connect(&OnResourceReadySignalSVG);
+    switch(testCase)
+    {
+      case 0:
+      default:
+      {
+        tet_printf("Case 0 : Do not set size (size is 0, 0)\n");
+        break;
+      }
+      case 1:
+      {
+        tet_printf("Case 1 : Width is bigger than height (size is 200, 100)\n");
+        imageView.SetProperty(Actor::Property::SIZE, Vector2(200.0f, 100.0f));
+        break;
+      }
+      case 2:
+      {
+        tet_printf("Case 2 : Height is bigger than width (size is 100, 200)\n");
+        imageView.SetProperty(Actor::Property::SIZE, Vector2(100.0f, 200.0f));
+        break;
+      }
+    }
+    imageView.ResourceReadySignal().Connect(&OnResourceReadySyncSVGLoading02);
 
     application.GetScene().Add(imageView);
     DALI_TEST_CHECK(imageView);
@@ -3050,6 +3093,14 @@ int UtcDaliImageViewSyncSVGLoading02(void)
     Vector3 naturalSize = imageView.GetNaturalSize();
     DALI_TEST_EQUALS(naturalSize.width, 100.0f, TEST_LOCATION);
     DALI_TEST_EQUALS(naturalSize.height, 100.0f, TEST_LOCATION);
+
+    imageView.Unparent();
+
+    // Ensure remove cache.
+    application.SendNotification();
+    application.Render();
+    application.SendNotification();
+    application.Render();
   }
   END_TEST;
 }
@@ -3087,6 +3138,57 @@ int UtcDaliImageViewAsyncSVGLoading(void)
   END_TEST;
 }
 
+int UtcDaliImageViewAsyncSVGLoading02(void)
+{
+  ToolkitTestApplication application;
+
+  tet_infoline("ImageView Testing SVG image async loading and the loaded result check cached");
+
+  {
+    ImageView imageView = ImageView::New();
+    DALI_TEST_CHECK(imageView);
+
+    // Async loading is used - default value of SYNCHRONOUS_LOADING is false.
+    Property::Map propertyMap;
+    propertyMap.Insert(Toolkit::Visual::Property::TYPE, Toolkit::Visual::IMAGE);
+    propertyMap.Insert(Toolkit::ImageVisual::Property::URL, TEST_RESOURCE_DIR "/svg1.svg");
+    imageView.SetProperty(ImageView::Property::IMAGE, propertyMap);
+
+    application.GetScene().Add(imageView);
+
+    // Check that natural size return invalid values now
+    // Note : This logic might be changed if we decide to decode the svg synchronously.
+    Vector3 naturalSize = imageView.GetNaturalSize();
+    DALI_TEST_NOT_EQUALS(naturalSize.width, 100.0f, 0.01f, TEST_LOCATION);
+    DALI_TEST_NOT_EQUALS(naturalSize.height, 100.0f, 0.01f, TEST_LOCATION);
+
+    application.SendNotification();
+
+    // Wait for loading
+    DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
+    application.SendNotification();
+    application.Render(16);
+
+    naturalSize = imageView.GetNaturalSize();
+    DALI_TEST_EQUALS(naturalSize.width, 100.0f, TEST_LOCATION);
+    DALI_TEST_EQUALS(naturalSize.height, 100.0f, TEST_LOCATION);
+
+    // Test that imageView2 use cached natrual size.
+    ImageView imageView2 = ImageView::New();
+    DALI_TEST_CHECK(imageView2);
+
+    // Use same property map with imageView
+    imageView2.SetProperty(ImageView::Property::IMAGE, propertyMap);
+
+    // Check whether natural size is same as cached image size.
+    naturalSize = imageView2.GetNaturalSize();
+    DALI_TEST_EQUALS(naturalSize.width, 100.0f, TEST_LOCATION);
+    DALI_TEST_EQUALS(naturalSize.height, 100.0f, TEST_LOCATION);
+  }
+  END_TEST;
+}
+
 int UtcDaliImageViewSVGLoadingSyncSetInvalidValue(void)
 {
   ToolkitTestApplication application;
@@ -3412,9 +3514,12 @@ int UtcDaliImageViewSvgAtlasing(void)
   propertyMap["url"]      = TEST_SVG_FILE_NAME;
   propertyMap["atlasing"] = true;
 
+  gResourceReadySignalFired = false;
+
   ImageView imageView = ImageView::New();
   imageView.SetProperty(ImageView::Property::IMAGE, propertyMap);
   imageView.SetProperty(Actor::Property::SIZE, Vector2(100.f, 100.f));
+  imageView.ResourceReadySignal().Connect(&ResourceReadySignal);
   application.GetScene().Add(imageView);
 
   application.SendNotification();
@@ -3422,6 +3527,8 @@ int UtcDaliImageViewSvgAtlasing(void)
   // Wait for loading & rasterization
   DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(2), true, TEST_LOCATION);
 
+  DALI_TEST_CHECK(gResourceReadySignalFired);
+
   application.SendNotification();
   application.Render(16);
 
@@ -3431,15 +3538,70 @@ int UtcDaliImageViewSvgAtlasing(void)
   params1["height"] << 100;
   DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexSubImage2D", params1), true, TEST_LOCATION);
 
-  imageView.SetProperty(Actor::Property::SIZE, Vector2(600.f, 600.f));
+  callStack.Reset();
+
+  gResourceReadySignalFired = false;
+
+  // Also use new image view with atlas.
+  ImageView imageView2 = ImageView::New();
+  imageView2.SetProperty(ImageView::Property::IMAGE, propertyMap);
+  imageView2.SetProperty(Actor::Property::SIZE, Vector2(100.f, 100.f));
+  imageView2.ResourceReadySignal().Connect(&ResourceReadySignal);
+  application.GetScene().Add(imageView2);
 
   application.SendNotification();
 
-  // Wait for rasterization
+  // Let we check that we use cached image, and cached texture.
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1, 0), false, TEST_LOCATION);
+
+  DALI_TEST_CHECK(gResourceReadySignalFired);
+
+  application.SendNotification();
+  application.Render(16);
+
+  // Check there is no newly generated texture
+  DALI_TEST_EQUALS(callStack.CountMethod("GenTextures"), 0, TEST_LOCATION);
+  DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexSubImage2D", params1), false, TEST_LOCATION);
+
+  callStack.Reset();
+
+  gResourceReadySignalFired = false;
+
+  // Also use new image view 'without'' atlas.
+  propertyMap["atlasing"] = false;
+  ImageView imageView3    = ImageView::New();
+  imageView3.SetProperty(ImageView::Property::IMAGE, propertyMap);
+  imageView3.SetProperty(Actor::Property::SIZE, Vector2(100.f, 100.f));
+  imageView3.ResourceReadySignal().Connect(&ResourceReadySignal);
+  application.GetScene().Add(imageView3);
+
+  application.SendNotification();
+
+  // Let we check that we use cached image, but not cached texture.
+  // Wait rasterize.
   DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
 
+  DALI_TEST_CHECK(gResourceReadySignalFired);
+
+  application.SendNotification();
+  application.Render(16);
+
+  // Check that we generate new texture
+  DALI_TEST_EQUALS(callStack.CountMethod("GenTextures"), 1, TEST_LOCATION);
+  DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexImage2D", params1), true, TEST_LOCATION);
+
   callStack.Reset();
 
+  gResourceReadySignalFired = false;
+
+  // Try to atlas over the size.
+  imageView.SetProperty(Actor::Property::SIZE, Vector2(600.f, 600.f));
+
+  application.SendNotification();
+
+  // Wait for rasterization
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+
   application.SendNotification();
   application.Render(16);
 
@@ -3449,6 +3611,24 @@ int UtcDaliImageViewSvgAtlasing(void)
   params2["height"] << 600;
   DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexImage2D", params2), true, TEST_LOCATION);
 
+  callStack.Reset();
+
+  // Try to load over the size.
+  // Note that imageView3's atlas attempt is false.
+  imageView3.SetProperty(Actor::Property::SIZE, Vector2(600.f, 600.f));
+
+  // Let we check atlas cached.
+  DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1, 0), false, TEST_LOCATION);
+
+  application.SendNotification();
+  application.Render(16);
+
+  // Check there is no newly generated texture
+  DALI_TEST_EQUALS(callStack.CountMethod("GenTextures"), 0, TEST_LOCATION);
+  DALI_TEST_EQUALS(callStack.FindMethodAndParams("TexSubImage2D", params2), false, TEST_LOCATION);
+
+  gResourceReadySignalFired = false;
+
   END_TEST;
 }
 
@@ -5927,4 +6107,222 @@ int UtcDaliImageViewImageLoadSuccessAndReload01(void)
   gResourceReadySignalFired = false;
 
   END_TEST;
+}
+
+namespace
+{
+int  gSvgReRasterizeDuringResourceReadyOrderType = 0;
+void OnResourceReadyReRasterize01(Control control)
+{
+  if(gResourceReadySignalCounter == 0u)
+  {
+    auto parent = gImageView1.GetParent();
+    DALI_TEST_CHECK(parent);
+
+    tet_printf("Request gImageView2 and gImageView3 as 300x300. gImageView4 as 400x400\n");
+
+    gImageView2 = ImageView::New(TEST_SVG_FILE_NAME);
+    gImageView2.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal);
+    gImageView2.SetProperty(Actor::Property::SIZE, Vector2(300.f, 300.f));
+    parent.Add(gImageView2);
+    gImageView3 = ImageView::New(TEST_SVG_FILE_NAME);
+    gImageView3.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal);
+    gImageView3.SetProperty(Actor::Property::SIZE, Vector2(300.f, 300.f));
+    parent.Add(gImageView3);
+    gImageView4 = ImageView::New(TEST_SVG_FILE_NAME);
+    gImageView4.ResourceReadySignal().Connect(&OnSimpleResourceReadySignal);
+    gImageView4.SetProperty(Actor::Property::SIZE, Vector2(400.f, 400.f));
+
+    Property::Map map;
+    map.Add(Visual::Property::TYPE, Visual::IMAGE);
+    map.Add(ImageVisual::Property::URL, TEST_SVG_FILE_NAME);
+    map.Add(ImageVisual::Property::SYNCHRONOUS_LOADING, true);
+
+    gImageView1.Unparent();
+
+    gImageView1.SetProperty(Actor::Property::SIZE, Vector2(400.f, 400.f));
+    gImageView1.SetProperty(ImageView::Property::IMAGE, map);
+
+    if((gSvgReRasterizeDuringResourceReadyOrderType & 2) == 0)
+    {
+      tet_printf("Make 400x400 image raterized as synchronously\n");
+      // Add gImageView1 now.
+      parent.Add(gImageView1);
+    }
+    else
+    {
+      tet_printf("Make 400x400 image raterized as synchronously, but after some async rasterization request first.\n");
+    }
+
+    if((gSvgReRasterizeDuringResourceReadyOrderType & 1) == 0)
+    {
+      // Add gImageView4 now
+      parent.Add(gImageView4);
+    }
+  }
+  else if(gResourceReadySignalCounter > 0 && ((gSvgReRasterizeDuringResourceReadyOrderType & 1) == 1))
+  {
+    auto parent = gImageView2.GetParent();
+    DALI_TEST_CHECK(parent);
+
+    // Add gImageView4 after second phase of gImageView1 load done.
+    parent.Add(gImageView4);
+  }
+  ++gResourceReadySignalCounter;
+}
+
+} // namespace
+
+int UtcDaliImageViewSvgReRasterizeDuringResourceReady01(void)
+{
+  for(int useInvalidSvg = 0; useInvalidSvg < 2; ++useInvalidSvg)
+  {
+    for(gSvgReRasterizeDuringResourceReadyOrderType = 0; gSvgReRasterizeDuringResourceReadyOrderType < 4; ++gSvgReRasterizeDuringResourceReadyOrderType)
+    {
+      ToolkitTestApplication application;
+
+      tet_infoline("Test SVG image rasterize and re-rasterize at ResourceReady callback.\n");
+      if(useInvalidSvg == 1)
+      {
+        tet_infoline("But in this case, we will use invalid svg file. So broken image used.\n");
+      }
+      tet_printf("order type : %d\n", gSvgReRasterizeDuringResourceReadyOrderType);
+
+      TestGlAbstraction& gl           = application.GetGlAbstraction();
+      TraceCallStack&    textureTrace = gl.GetTextureTrace();
+      textureTrace.Enable(true);
+
+      // Clear image view for clear test
+      if(gImageView1)
+      {
+        gImageView1.Reset();
+      }
+      if(gImageView2)
+      {
+        gImageView2.Reset();
+      }
+      if(gImageView3)
+      {
+        gImageView3.Reset();
+      }
+      if(gImageView4)
+      {
+        gImageView4.Reset();
+      }
+      gResourceReadySignalCounter = 0u;
+
+      gImageView1 = ImageView::New(useInvalidSvg == 1 ? TEST_RESOURCE_DIR "/invalid.svg" : TEST_SVG_FILE_NAME);
+      gImageView1.SetProperty(Actor::Property::SIZE, Vector2(200.f, 200.f));
+      gImageView1.ResourceReadySignal().Connect(&OnResourceReadyReRasterize01);
+      application.GetScene().Add(gImageView1);
+
+      DALI_TEST_EQUALS(gResourceReadySignalCounter, 0u, TEST_LOCATION);
+
+      application.SendNotification();
+      application.Render(16);
+
+      if(useInvalidSvg == 1)
+      {
+        // load invalid svg file. It will emit ResourceReady for gImageView1
+        DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+        DALI_TEST_EQUALS(gResourceReadySignalCounter, 1u, TEST_LOCATION);
+      }
+      else
+      {
+        // load svg file. It will not emit ResourceReady yet
+        DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+        DALI_TEST_EQUALS(gResourceReadySignalCounter, 0u, TEST_LOCATION);
+
+        application.SendNotification();
+        application.Render(16);
+        DALI_TEST_EQUALS(gResourceReadySignalCounter, 0u, TEST_LOCATION);
+
+        // rasterize svg 200x200. Now, ResourceReady signal will be emitted for gImageView1
+        DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(1), true, TEST_LOCATION);
+        DALI_TEST_EQUALS(gResourceReadySignalCounter, 1u, TEST_LOCATION);
+      }
+
+      application.SendNotification();
+      application.Render(16);
+      if((gSvgReRasterizeDuringResourceReadyOrderType & 2) == 2)
+      {
+        // Add gImageView1 now, to avoid sync load & rasterize request always before async.
+        application.GetScene().Add(gImageView1);
+
+        application.SendNotification();
+        application.Render(16);
+      }
+      // ResourceReady signal will be emitted for gImageView1 (sync load case), or gImageView4 (if gImageView1 rasterize first.)
+      // Note : We cannot assume that gImageView1 rasterize requested before gImageView4.
+      //        So, we cannot ensure that gResourceReadySignalCounter is which 2 or 3.
+      DALI_TEST_GREATER(gResourceReadySignalCounter, 1, TEST_LOCATION);
+      DALI_TEST_GREATER(4, gResourceReadySignalCounter, TEST_LOCATION);
+
+      application.SendNotification();
+      application.Render(16);
+
+      {
+        TraceCallStack::NamedParams params;
+        params["width"] << 200;
+        params["height"] << 200;
+        DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), useInvalidSvg == 0, TEST_LOCATION);
+        params.mParams.clear();
+        params["width"] << 300;
+        params["height"] << 300;
+        DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), false, TEST_LOCATION);
+        params.mParams.clear();
+        params["width"] << 400;
+        params["height"] << 400;
+        DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), true, TEST_LOCATION);
+      }
+      textureTrace.Reset();
+
+      // Total event trigger count is 2 or 1 : rasterize svg 300x300 + (rasterize svg 400x400 if required) + (load valid svg file if required)
+      // Now, ResourceReady signal will be emitted for gImageView2 and gImageView3 (and gImageView4 if required).
+      // Note : We cannot assume that which signal will be come first / rasterization 300x300 vs 400x400.
+      uint32_t eventTriggerRequiredCount = gResourceReadySignalCounter == 2 ? (gSvgReRasterizeDuringResourceReadyOrderType == 3 ? 3 : 2) : 1; // Check whether we need to wait 400x400 + 300x300, or only 300x300.
+
+      DALI_TEST_EQUALS(Test::WaitForEventThreadTrigger(eventTriggerRequiredCount), true, TEST_LOCATION);
+      DALI_TEST_EQUALS(gResourceReadySignalCounter, 5u, TEST_LOCATION);
+
+      application.SendNotification();
+      application.Render(16);
+
+      {
+        TraceCallStack::NamedParams params;
+        params["width"] << 200;
+        params["height"] << 200;
+        DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), false, TEST_LOCATION);
+        params.mParams.clear();
+        params["width"] << 300;
+        params["height"] << 300;
+        DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), true, TEST_LOCATION);
+        params.mParams.clear();
+        params["width"] << 400;
+        params["height"] << 400;
+        DALI_TEST_EQUALS(textureTrace.FindMethodAndParams("TexImage2D", params), false, TEST_LOCATION);
+      }
+
+      // Clear image view for clear test
+      if(gImageView1)
+      {
+        gImageView1.Reset();
+      }
+      if(gImageView2)
+      {
+        gImageView2.Reset();
+      }
+      if(gImageView3)
+      {
+        gImageView3.Reset();
+      }
+      if(gImageView4)
+      {
+        gImageView4.Reset();
+      }
+      gResourceReadySignalCounter = 0u;
+    }
+  }
+
+  END_TEST;
 }
\ No newline at end of file
index 48fd4ce..2bd9077 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -88,3 +88,93 @@ int UtcDaliSvgVisualChageSize(void)
 
   END_TEST;
 }
+
+int UtcDaliSvgVisualSvgCacheFileAndRasterizedTexture(void)
+{
+  tet_infoline("Test rasterized texture cached");
+
+  ToolkitTestApplication application;
+
+  TraceCallStack& textureTrace = application.GetGlAbstraction().GetTextureTrace();
+  textureTrace.Enable(true);
+
+  Visual::Base visual1 = VisualFactory::Get().CreateVisual(Property::Map().Add(ImageVisual::Property::URL, TEST_SVG_FILE_NAME));
+  DALI_TEST_CHECK(visual1);
+  Visual::Base visual2 = VisualFactory::Get().CreateVisual(Property::Map().Add(ImageVisual::Property::URL, TEST_SVG_FILE_NAME));
+  DALI_TEST_CHECK(visual2);
+  Visual::Base visual3 = VisualFactory::Get().CreateVisual(Property::Map().Add(ImageVisual::Property::URL, TEST_SVG_FILE_NAME));
+  DALI_TEST_CHECK(visual3);
+
+  DummyControl      control1   = DummyControl::New();
+  DummyControlImpl& dummyImpl1 = static_cast<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
index 661ccae..e4603d9 100644 (file)
@@ -51,6 +51,8 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/visuals/npatch/npatch-loader.cpp
    ${toolkit_src_dir}/visuals/npatch/npatch-visual.cpp
    ${toolkit_src_dir}/visuals/primitive/primitive-visual.cpp
+   ${toolkit_src_dir}/visuals/svg/svg-loader-observer.cpp
+   ${toolkit_src_dir}/visuals/svg/svg-loader.cpp
    ${toolkit_src_dir}/visuals/svg/svg-task.cpp
    ${toolkit_src_dir}/visuals/svg/svg-visual.cpp
    ${toolkit_src_dir}/visuals/text/text-visual-shader-factory.cpp
index 5205354..147e617 100644 (file)
@@ -737,7 +737,7 @@ void ImageVisual::LoadTexture(bool& atlasing, Vector4& atlasRect, TextureSet& te
   }
 }
 
-bool ImageVisual::AttemptAtlasing()
+bool ImageVisual::AttemptAtlasing() const
 {
   return (!mImpl->mCustomShader && (mImageUrl.IsLocalResource() || mImageUrl.IsBufferResource()) && mAttemptAtlasing);
 }
index 5680d61..445218b 100644 (file)
@@ -284,7 +284,7 @@ private:
    * @brief Checks if atlasing should be attempted
    * @return bool returns true if atlasing can be attempted.
    */
-  bool AttemptAtlasing();
+  bool AttemptAtlasing() const;
 
   /**
    * @brief Initializes the Dali::Renderer from the image url
index fd1a4fa..08948b0 100644 (file)
@@ -153,7 +153,7 @@ private:
     NPatchInfo& operator=(const NPatchInfo& info) = delete; // Do not use copy assign
 
     NPatchDataPtr mData;
-    std::int16_t  mReferenceCount; ///< The number of N-patch visuals that use this data.
+    int32_t       mReferenceCount; ///< The number of N-patch visuals that use this data.
   };
 
   /**
diff --git a/dali-toolkit/internal/visuals/svg/svg-loader-observer.cpp b/dali-toolkit/internal/visuals/svg/svg-loader-observer.cpp
new file mode 100644 (file)
index 0000000..71ceb1b
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <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
diff --git a/dali-toolkit/internal/visuals/svg/svg-loader-observer.h b/dali-toolkit/internal/visuals/svg/svg-loader-observer.h
new file mode 100644 (file)
index 0000000..869b4a3
--- /dev/null
@@ -0,0 +1,102 @@
+#ifndef DALI_TOOLKIT_SVG_UPLOAD_OBSERVER_H
+#define DALI_TOOLKIT_SVG_UPLOAD_OBSERVER_H
+
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// EXTERNAL INCLUDES
+#include <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
diff --git a/dali-toolkit/internal/visuals/svg/svg-loader.cpp b/dali-toolkit/internal/visuals/svg/svg-loader.cpp
new file mode 100644 (file)
index 0000000..4a3ea1c
--- /dev/null
@@ -0,0 +1,1242 @@
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <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
diff --git a/dali-toolkit/internal/visuals/svg/svg-loader.h b/dali-toolkit/internal/visuals/svg/svg-loader.h
new file mode 100644 (file)
index 0000000..56e31e6
--- /dev/null
@@ -0,0 +1,458 @@
+#ifndef DALI_TOOLKIT_SVG_LOADER_H
+#define DALI_TOOLKIT_SVG_LOADER_H
+
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// EXTERNAL INCLUDES
+#include <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
index 0a4ff89..dd9fd00 100644 (file)
@@ -55,9 +55,10 @@ uint64_t GetNanoseconds()
 #endif
 } // namespace
 
-SvgTask::SvgTask(VectorImageRenderer vectorRenderer, CallbackBase* callback, AsyncTask::PriorityType priorityType)
+SvgTask::SvgTask(VectorImageRenderer vectorRenderer, int32_t id, CallbackBase* callback, AsyncTask::PriorityType priorityType)
 : AsyncTask(callback, priorityType),
   mVectorRenderer(vectorRenderer),
+  mId(id),
   mHasSucceeded(false)
 {
 }
@@ -77,8 +78,8 @@ VectorImageRenderer SvgTask::GetRenderer()
   return mVectorRenderer;
 }
 
-SvgLoadingTask::SvgLoadingTask(VectorImageRenderer vectorRenderer, const VisualUrl& url, EncodedImageBuffer encodedImageBuffer, float dpi, CallbackBase* callback)
-: SvgTask(vectorRenderer, callback, url.GetProtocolType() == VisualUrl::ProtocolType::REMOTE ? AsyncTask::PriorityType::LOW : AsyncTask::PriorityType::HIGH),
+SvgLoadingTask::SvgLoadingTask(VectorImageRenderer vectorRenderer, int32_t id, const VisualUrl& url, EncodedImageBuffer encodedImageBuffer, float dpi, CallbackBase* callback)
+: SvgTask(vectorRenderer, id, callback, url.GetProtocolType() == VisualUrl::ProtocolType::REMOTE ? AsyncTask::PriorityType::LOW : AsyncTask::PriorityType::HIGH),
   mImageUrl(url),
   mEncodedImageBuffer(encodedImageBuffer),
   mDpi(dpi)
@@ -175,8 +176,8 @@ bool SvgLoadingTask::IsReady()
   return true;
 }
 
-SvgRasterizingTask::SvgRasterizingTask(VectorImageRenderer vectorRenderer, uint32_t width, uint32_t height, CallbackBase* callback)
-: SvgTask(vectorRenderer, callback),
+SvgRasterizingTask::SvgRasterizingTask(VectorImageRenderer vectorRenderer, int32_t id, uint32_t width, uint32_t height, CallbackBase* callback)
+: SvgTask(vectorRenderer, id, callback),
   mWidth(width),
   mHeight(height)
 {
index e23a8ae..5885ee7 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_SVG_TASK_H
 
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -55,10 +55,11 @@ public:
   /**
    * Constructor
    * @param[in] vectorRenderer The vector rasterizer.
+   * @param[in] id The id of this task.
    * @param[in] callback The callback that is called when the operation is completed.
    * @param[in] priorityType The priority of this task.
    */
-  SvgTask(VectorImageRenderer vectorRenderer, CallbackBase* callback, AsyncTask::PriorityType priorityType = AsyncTask::PriorityType::DEFAULT);
+  SvgTask(VectorImageRenderer vectorRenderer, int32_t id, CallbackBase* callback, AsyncTask::PriorityType priorityType = AsyncTask::PriorityType::DEFAULT);
 
   /**
    * Destructor.
@@ -72,6 +73,15 @@ public:
   bool HasSucceeded() const;
 
   /**
+   * Get the id of task.
+   * @return The Id of task what we added from constructor.
+   */
+  int32_t GetId() const
+  {
+    return mId;
+  }
+
+  /**
    * @brief Get the task's imageRenderer
    * @return VectorImageRenderer
    */
@@ -92,6 +102,7 @@ private:
 
 protected:
   VectorImageRenderer mVectorRenderer;
+  const int32_t       mId;
   bool                mHasSucceeded;
 };
 
@@ -101,12 +112,13 @@ public:
   /**
    * Constructor
    * @param[in] vectorRenderer The vector rasterizer.
+   * @param[in] id The id of this task.
    * @param[in] url The URL to svg resource to use.
    * @param[in] encodedImageBuffer The resource buffer if required.
    * @param[in] dpi The DPI of the screen.
    * @param[in] callback The callback that is called when the operation is completed.
    */
-  SvgLoadingTask(VectorImageRenderer vectorRenderer, const VisualUrl& url, EncodedImageBuffer encodedImageBuffer, float dpi, CallbackBase* callback);
+  SvgLoadingTask(VectorImageRenderer vectorRenderer, int32_t id, const VisualUrl& url, EncodedImageBuffer encodedImageBuffer, float dpi, CallbackBase* callback);
 
   /**
    * Destructor.
@@ -151,11 +163,12 @@ public:
   /**
    * Constructor
    * @param[in] vectorRenderer The vector rasterizer.
+   * @param[in] id The id of this task.
    * @param[in] width The rasterization width.
    * @param[in] height The rasterization height.
    * @param[in] callback The callback that is called when the operation is completed.
    */
-  SvgRasterizingTask(VectorImageRenderer vectorRenderer, uint32_t width, uint32_t height, CallbackBase* callback);
+  SvgRasterizingTask(VectorImageRenderer vectorRenderer, int32_t id, uint32_t width, uint32_t height, CallbackBase* callback);
 
   /**
    * Destructor.
index 85ff479..919cd52 100644 (file)
@@ -22,7 +22,7 @@
 #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>
@@ -42,8 +42,7 @@ namespace
 {
 const int CUSTOM_PROPERTY_COUNT(1); // atlas
 
-// property name
-const Dali::Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
+constexpr Dali::Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
 
 } // namespace
 
@@ -65,9 +64,12 @@ SvgVisualPtr SvgVisual::New(VisualFactoryCache& factoryCache, ImageVisualShaderF
 SvgVisual::SvgVisual(VisualFactoryCache& factoryCache, ImageVisualShaderFactory& shaderFactory, const VisualUrl& imageUrl, ImageDimensions size)
 : Visual::Base(factoryCache, Visual::FittingMode::DONT_CARE, Toolkit::Visual::SVG),
   mImageVisualShaderFactory(shaderFactory),
+  mSvgLoader(factoryCache.GetSvgLoader()),
+  mSvgLoadId(SvgLoader::INVALID_SVG_LOAD_ID),
+  mSvgRasterizeId(SvgLoader::INVALID_SVG_RASTERIZE_ID),
   mAtlasRect(FULL_TEXTURE_RECT),
+  mAtlasRectIndex(Property::INVALID_INDEX),
   mImageUrl(imageUrl),
-  mVectorRenderer(VectorImageRenderer::New()),
   mDefaultWidth(0),
   mDefaultHeight(0),
   mPlacementActor(),
@@ -84,13 +86,16 @@ SvgVisual::~SvgVisual()
 {
   if(Stage::IsInstalled())
   {
-    if(mLoadingTask)
+    if(mSvgLoadId != SvgLoader::INVALID_SVG_LOAD_ID)
     {
-      Dali::AsyncTaskManager::Get().RemoveTask(mLoadingTask);
+      mSvgLoader.RequestLoadRemove(mSvgLoadId, this);
+      mSvgLoadId = SvgLoader::INVALID_SVG_LOAD_ID;
     }
-    if(mRasterizingTask)
+    if(mSvgRasterizeId != SvgLoader::INVALID_SVG_LOAD_ID)
     {
-      Dali::AsyncTaskManager::Get().RemoveTask(mRasterizingTask);
+      // We don't need to remove task synchronously.
+      mSvgLoader.RequestRasterizeRemove(mSvgRasterizeId, this, false);
+      mSvgRasterizeId = SvgLoader::INVALID_SVG_RASTERIZE_ID;
     }
 
     if(mImageUrl.IsBufferResource())
@@ -111,34 +116,10 @@ void SvgVisual::OnInitialize()
   Vector2 dpi     = Stage::GetCurrent().GetDpi();
   float   meanDpi = (dpi.height + dpi.width) * 0.5f;
 
-  EncodedImageBuffer encodedImageBuffer;
+  const bool synchronousLoading = IsSynchronousLoadingRequired() && (mImageUrl.IsLocalResource() || mImageUrl.IsBufferResource());
 
-  if(mImageUrl.IsBufferResource())
-  {
-    // Increase reference count of External Resources :
-    // EncodedImageBuffer.
-    // Reference count will be decreased at destructor of the visual.
-    TextureManager& textureManager = mFactoryCache.GetTextureManager();
-    textureManager.UseExternalResource(mImageUrl.GetUrl());
-
-    encodedImageBuffer = textureManager.GetEncodedImageBuffer(mImageUrl.GetUrl());
-  }
-
-  mLoadingTask = new SvgLoadingTask(mVectorRenderer, mImageUrl, encodedImageBuffer, meanDpi, MakeCallback(this, &SvgVisual::ApplyRasterizedImage));
-
-  if(IsSynchronousLoadingRequired() && (mImageUrl.IsLocalResource() || mImageUrl.IsBufferResource()))
-  {
-    mLoadingTask->Process();
-    if(!mLoadingTask->HasSucceeded())
-    {
-      mLoadFailed = true;
-    }
-    mLoadingTask.Reset(); // We don't need it anymore.
-  }
-  else
-  {
-    Dali::AsyncTaskManager::Get().AddTask(mLoadingTask);
-  }
+  // It will call SvgVisual::LoadComplete() synchronously if it required, or we already loaded same svg before.
+  mSvgLoadId = mSvgLoader.Load(mImageUrl, meanDpi, this, synchronousLoading);
 }
 
 void SvgVisual::DoSetProperties(const Property::Map& propertyMap)
@@ -176,7 +157,11 @@ void SvgVisual::DoSetProperty(Property::Index index, const Property::Value& valu
   {
     case Toolkit::ImageVisual::Property::ATLASING:
     {
-      value.Get(mAttemptAtlasing);
+      bool atlasing = false;
+      if(value.Get(atlasing))
+      {
+        mAttemptAtlasing = atlasing;
+      }
       break;
     }
     case Toolkit::ImageVisual::Property::SYNCHRONOUS_LOADING:
@@ -255,17 +240,18 @@ void SvgVisual::DoSetOnScene(Actor& actor)
 void SvgVisual::DoSetOffScene(Actor& actor)
 {
   // Remove rasterizing task
-  if(mRasterizingTask)
+  if(mSvgRasterizeId != SvgLoader::INVALID_SVG_LOAD_ID)
   {
-    Dali::AsyncTaskManager::Get().RemoveTask(mRasterizingTask);
-    mRasterizingTask.Reset();
+    // We don't need to remove task synchronously.
+    mSvgLoader.RequestRasterizeRemove(mSvgRasterizeId, this, false);
+    mSvgRasterizeId = SvgLoader::INVALID_SVG_RASTERIZE_ID;
   }
 
   actor.RemoveRenderer(mImpl->mRenderer);
   mPlacementActor.Reset();
 
-  // Reset the visual size to zero so that when adding the actor back to stage the SVG rasterization is forced
-  mRasterizedSize = Vector2::ZERO;
+  // Reset the visual size so that when adding the actor back to stage the SVG rasterization is forced
+  mRasterizedSize = -Vector2::ONE; ///< Let we don't use zero since visual size could be zero after trasnform
 }
 
 void SvgVisual::GetNaturalSize(Vector2& naturalSize)
@@ -325,115 +311,134 @@ void SvgVisual::EnablePreMultipliedAlpha(bool preMultiplied)
   }
 }
 
+bool SvgVisual::AttemptAtlasing() const
+{
+  return (!mImpl->mCustomShader && (mImageUrl.IsLocalResource() || mImageUrl.IsBufferResource()) && mAttemptAtlasing);
+}
+
 void SvgVisual::AddRasterizationTask(const Vector2& size)
 {
   if(mImpl->mRenderer)
   {
     // Remove previous task
-    if(mRasterizingTask)
+    if(mSvgRasterizeId != SvgLoader::INVALID_SVG_LOAD_ID)
     {
-      Dali::AsyncTaskManager::Get().RemoveTask(mRasterizingTask);
-      mRasterizingTask.Reset();
+      mSvgLoader.RequestRasterizeRemove(mSvgRasterizeId, this, true);
+      mSvgRasterizeId = SvgLoader::INVALID_SVG_RASTERIZE_ID;
     }
 
     uint32_t width  = static_cast<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();
@@ -450,36 +455,37 @@ void SvgVisual::ApplyRasterizedImage(SvgTaskPtr task)
   }
   else if(!mLoadFailed)
   {
+    SvgVisualPtr self = this; // Keep reference until this API finished
+
     mLoadFailed = true;
 
-    // Remove rasterizing task if we requested before.
-    if(mRasterizingTask)
+    if(IsOnScene())
     {
-      Dali::AsyncTaskManager::Get().RemoveTask(mRasterizingTask);
-      mRasterizingTask.Reset();
-    }
+      Actor actor = mPlacementActor.GetHandle();
+      if(actor && mImpl->mRenderer)
+      {
+        Vector2 imageSize = Vector2::ZERO;
+        imageSize         = actor.GetProperty(Actor::Property::SIZE).Get<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;
@@ -498,17 +504,12 @@ void SvgVisual::OnSetTransform()
     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()
@@ -528,7 +529,7 @@ Shader SvgVisual::GenerateShader() const
     shader = mImageVisualShaderFactory.GetShader(
       mFactoryCache,
       ImageVisualShaderFeatureBuilder()
-        .EnableTextureAtlas(mAttemptAtlasing)
+        .EnableTextureAtlas(mImpl->mFlags & Visual::Base::Impl::IS_ATLASING_APPLIED)
         .EnableRoundedCorner(IsRoundedCornerRequired())
         .EnableBorderline(IsBorderlineRequired()));
   }
index faf8a22..29e10e9 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_INTERNAL_SVG_VISUAL_H
 
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #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
 {
@@ -47,7 +48,8 @@ typedef IntrusivePtr<SvgVisual> SvgVisualPtr;
  * | url                      | STRING           |
  *
  */
-class SvgVisual : public Visual::Base
+class SvgVisual : public Visual::Base,
+                  public SvgLoaderObserver
 {
 public:
   /**
@@ -150,13 +152,16 @@ protected:
    */
   Shader GenerateShader() const override;
 
-public:
+protected: // Implementation of  SvgLoaderObserver
   /**
-   * @bried Apply the rasterized image to the visual.
-   *
-   * @param[in] task SvgTaskPtr
+   * @copydoc Dali::Toolkit::Internal::SvgLoaderObserver::LoadComplete
+   */
+  void LoadComplete(int32_t loadId, Dali::VectorImageRenderer vectorImageRenderer) override;
+
+  /**
+   * @copydoc Dali::Toolkit::Internal::SvgLoaderObserver::RasterizeComplete
    */
-  void ApplyRasterizedImage(SvgTaskPtr task);
+  void RasterizeComplete(int32_t rasterizeId, Dali::TextureSet textureSet, Vector4 atlasRect) override;
 
 private:
   /**
@@ -173,6 +178,12 @@ private:
    */
   void DoSetProperty(Property::Index index, const Property::Value& value);
 
+  /**
+   * @brief Checks if atlasing should be attempted
+   * @return bool returns true if atlasing can be attempted.
+   */
+  bool AttemptAtlasing() const;
+
   // Undefined
   SvgVisual(const SvgVisual& svgRenderer);
 
@@ -181,18 +192,21 @@ private:
 
 private:
   ImageVisualShaderFactory& mImageVisualShaderFactory;
-  Vector4                   mAtlasRect;
-  VisualUrl                 mImageUrl;
-  VectorImageRenderer       mVectorRenderer;
-  uint32_t                  mDefaultWidth;
-  uint32_t                  mDefaultHeight;
-  WeakHandle<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
index afc3784..5228ce4 100644 (file)
@@ -65,6 +65,7 @@ VisualFactoryCache::VisualFactoryCache(bool preMultiplyOnLoad)
   mDefaultBrokenImageUrl(""),
   mUseDefaultBrokenImageOnly(true)
 {
+  mSvgLoader.SetVisualFactoryCache(*this);
 }
 
 VisualFactoryCache::~VisualFactoryCache()
@@ -147,6 +148,11 @@ NPatchLoader& VisualFactoryCache::GetNPatchLoader()
   return mNPatchLoader;
 }
 
+SvgLoader& VisualFactoryCache::GetSvgLoader()
+{
+  return mSvgLoader;
+}
+
 VectorAnimationManager& VisualFactoryCache::GetVectorAnimationManager()
 {
   if(!mVectorAnimationManager)
index 8a9f133..11a591d 100644 (file)
@@ -26,6 +26,7 @@
 // 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
@@ -38,6 +39,7 @@ namespace Internal
 {
 class ImageAtlasManager;
 class NPatchLoader;
+class SvgLoader;
 class TextureManager;
 class VectorAnimationManager;
 
@@ -258,6 +260,12 @@ public:
   NPatchLoader& GetNPatchLoader();
 
   /**
+   * Get the Svg texture cache.
+   * @return A reference to the Svg loader
+   */
+  SvgLoader& GetSvgLoader();
+
+  /**
    * Get the vector animation manager.
    * @return A reference to the vector animation manager.
    */
@@ -356,6 +364,7 @@ private:
   ImageAtlasManagerPtr mAtlasManager;
   TextureManager       mTextureManager;
   NPatchLoader         mNPatchLoader;
+  SvgLoader            mSvgLoader;
 
   std::unique_ptr<VectorAnimationManager> mVectorAnimationManager;
   bool                                    mPreMultiplyOnLoad;
index 1b0a44e..b6f6ac4 100644 (file)
@@ -450,6 +450,11 @@ Internal::TextureManager& VisualFactory::GetTextureManager()
   return GetFactoryCache().GetTextureManager();
 }
 
+Internal::SvgLoader& VisualFactory::GetSvgLoader()
+{
+  return GetFactoryCache().GetSvgLoader();
+}
+
 void VisualFactory::SetBrokenImageUrl(Toolkit::StyleManager& styleManager)
 {
   const std::string        imageDirPath   = AssetManager::GetDaliImagePath();
index daecbb0..b4cde6e 100644 (file)
@@ -121,6 +121,11 @@ public:
    */
   Internal::TextureManager& GetTextureManager();
 
+  /**
+   * @return the reference to svg loader
+   */
+  Internal::SvgLoader& GetSvgLoader();
+
 protected:
   /**
    * A reference counted object may only be deleted by calling Unreference()