Refactoring npatch-loader so it works well both sync-async loading 29/273429/15
authorEunki, Hong <eunkiki.hong@samsung.com>
Tue, 5 Apr 2022 14:00:04 +0000 (23:00 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Wed, 13 Apr 2022 07:53:03 +0000 (16:53 +0900)
Make async loading always call LoadComplete when we try to load n-patch image.
+
Releative with both sync-async case,
Fix minor reference count issue for NPatchData.
Previous logic only control refrence count as Observer. It was not good.
+
Fix minor caching issue with border.
Previous code logic have problem when we use same Url and different border.

Change-Id: Ic54dd522e44f5db64f3e9d08aa44db224ab4d506
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
automated-tests/src/dali-toolkit/utc-Dali-VisualFactory.cpp
dali-toolkit/internal/visuals/npatch-data.cpp
dali-toolkit/internal/visuals/npatch-data.h
dali-toolkit/internal/visuals/npatch-loader.cpp
dali-toolkit/internal/visuals/npatch-loader.h
dali-toolkit/internal/visuals/npatch/npatch-visual.cpp
dali-toolkit/internal/visuals/visual-factory-cache.cpp

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