[dali_2.3.19] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / visuals / npatch-loader.cpp
index f23f1ca..fb844ce 100644 (file)
@@ -1,5 +1,5 @@
- /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+/*
+ * Copyright (c) 2023 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.
 // CLASS HEADER
 #include <dali-toolkit/internal/visuals/npatch-loader.h>
 
-// EXTERNAL HEADER
+// INTERNAL HEADERS
+#include <dali-toolkit/internal/visuals/rendering-addon.h>
+
+// EXTERNAL HEADERS
 #include <dali/devel-api/common/hash.h>
-#include <dali/devel-api/images/texture-set-image.h>
-#include <dali-toolkit/public-api/image-loader/sync-image-loader.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
+#include <dali/integration-api/debug.h>
+#include <dali/integration-api/trace.h>
 
 namespace Dali
 {
-
 namespace Toolkit
 {
-
 namespace Internal
 {
+namespace
+{
+constexpr auto INVALID_CACHE_INDEX = int32_t{-1}; ///< Invalid Cache index
+constexpr auto UNINITIALIZED_ID    = int32_t{0};  ///< uninitialised id, use to initialize ids
+
+DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_IMAGE_PERFORMANCE_MARKER, false);
+} // Anonymous namespace
 
 NPatchLoader::NPatchLoader()
+: mCurrentNPatchDataId(0),
+  mRemoveProcessorRegistered(false)
 {
 }
 
 NPatchLoader::~NPatchLoader()
 {
+  if(mRemoveProcessorRegistered && Adaptor::IsAvailable())
+  {
+    Adaptor::Get().UnregisterProcessor(*this, true);
+    mRemoveProcessorRegistered = false;
+  }
+}
+
+NPatchData::NPatchDataId NPatchLoader::GenerateUniqueNPatchDataId()
+{
+  // Skip invalid id generation.
+  if(DALI_UNLIKELY(mCurrentNPatchDataId == NPatchData::INVALID_NPATCH_DATA_ID))
+  {
+    mCurrentNPatchDataId = 0;
+  }
+  return mCurrentNPatchDataId++;
 }
 
-std::size_t NPatchLoader::Load( const std::string& url, const Rect< int >& border )
+NPatchData::NPatchDataId NPatchLoader::Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading)
 {
-  std::size_t hash = CalculateHash( url );
-  OwnerContainer< Data* >::SizeType index = UNINITIALIZED_ID;
-  const OwnerContainer< Data* >::SizeType count = mCache.Count();
-  int cachedIndex = -1;
+  NPatchDataPtr data = GetNPatchData(url, border, preMultiplyOnLoad);
 
-  for( ; index < count; ++index )
+  DALI_ASSERT_ALWAYS(data.Get() && "NPatchData creation failed!");
+
+  if(data->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
   {
-    if( mCache[ index ]->hash == hash )
+    if(!synchronousLoading)
     {
-      // hash match, check url as well in case of hash collision
-      if( mCache[ index ]->url == url )
+      // 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 ]->border == border )
-        {
-          return index+1u; // valid indices are from 1 onwards
-        }
-        else
-        {
-          cachedIndex = index;
-        }
+        return data->GetId();
       }
     }
+
+    data->SetLoadingState(NPatchData::LoadingState::LOADING);
+
+    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.Get(), true, preMultiplyOnLoading);
+
+    if(pixelBuffer)
+    {
+      preMultiplyOnLoad = (preMultiplyOnLoading == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD) ? true : false;
+      data->SetLoadedNPatchData(pixelBuffer, preMultiplyOnLoad);
+    }
+    else if(synchronousLoading)
+    {
+      data->SetLoadingState(NPatchData::LoadingState::LOAD_FAILED);
+    }
+  }
+  return data->GetId();
+}
+
+int32_t NPatchLoader::GetCacheIndexFromId(const NPatchData::NPatchDataId id)
+{
+  const unsigned int size = mCache.size();
+
+  for(unsigned int i = 0; i < size; ++i)
+  {
+    if(mCache[i].mData->GetId() == id)
+    {
+      return i;
+    }
+  }
+
+  return INVALID_CACHE_INDEX;
+}
+
+bool NPatchLoader::GetNPatchData(const NPatchData::NPatchDataId id, NPatchDataPtr& data)
+{
+  int32_t cacheIndex = GetCacheIndexFromId(id);
+  if(cacheIndex != INVALID_CACHE_INDEX)
+  {
+    data = mCache[cacheIndex].mData;
+    return true;
+  }
+  data = nullptr;
+  return false;
+}
+
+void NPatchLoader::RequestRemove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
+{
+  // Remove observer first
+  if(textureObserver)
+  {
+    int32_t cacheIndex = GetCacheIndexFromId(id);
+    if(cacheIndex != INVALID_CACHE_INDEX)
+    {
+      NPatchInfo& info(mCache[cacheIndex]);
+
+      info.mData->RemoveObserver(textureObserver);
+    }
+  }
+
+  mRemoveQueue.push_back({id, nullptr});
+
+  if(!mRemoveProcessorRegistered && Adaptor::IsAvailable())
+  {
+    mRemoveProcessorRegistered = true;
+    Adaptor::Get().RegisterProcessor(*this, true);
   }
+}
 
-  if( cachedIndex != -1 )
+void NPatchLoader::Remove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
+{
+  int32_t cacheIndex = GetCacheIndexFromId(id);
+  if(cacheIndex == INVALID_CACHE_INDEX)
   {
-    // Same url but border is different - use the existing texture
-    Data* data = new Data();
-    data->hash = hash;
-    data->url = url;
-    data->croppedWidth = mCache[ cachedIndex ]->croppedWidth;
-    data->croppedHeight = mCache[ cachedIndex ]->croppedHeight;
+    return;
+  }
+
+  NPatchInfo& info(mCache[cacheIndex]);
 
-    data->textureSet = mCache[ cachedIndex ]->textureSet;
+  info.mData->RemoveObserver(textureObserver);
 
-    NinePatchImage::StretchRanges stretchRangesX;
-    stretchRangesX.PushBack( Uint16Pair( border.left, data->croppedWidth - border.right ) );
+  if(--info.mReferenceCount <= 0)
+  {
+    mCache.erase(mCache.begin() + cacheIndex);
+  }
+}
 
-    NinePatchImage::StretchRanges stretchRangesY;
-    stretchRangesY.PushBack( Uint16Pair( border.top, data->croppedHeight - border.bottom ) );
+void NPatchLoader::Process(bool postProcessor)
+{
+  DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_NPATCH_LOADER_PROCESS_REMOVE_QUEUE", [&](std::ostringstream& oss) {
+    oss << "[" << mRemoveQueue.size() << "]";
+  });
 
-    data->stretchPixelsX = stretchRangesX;
-    data->stretchPixelsY = stretchRangesY;
-    data->border = border;
+  for(auto& iter : mRemoveQueue)
+  {
+    Remove(iter.first, iter.second);
+  }
 
-    mCache.PushBack( data );
+  mRemoveQueue.clear();
 
-    return mCache.Count(); // valid ids start from 1u
+  if(Adaptor::IsAvailable())
+  {
+    Adaptor::Get().UnregisterProcessor(*this, true);
+    mRemoveProcessorRegistered = false;
   }
 
-  // got to the end so no match, decode N patch and append new item to cache
-  if( border == Rect< int >( 0, 0, 0, 0 ) )
+  DALI_TRACE_END(gTraceFilter, "DALI_NPATCH_LOADER_PROCESS_REMOVE_QUEUE");
+}
+
+NPatchDataPtr 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)
   {
-    NinePatchImage ninePatch = NinePatchImage::New( url );
-    if( ninePatch )
+    if(mCache[index].mData->GetHash() == hash)
     {
-      BufferImage croppedImage = ninePatch.CreateCroppedBufferImage();
-      if( croppedImage )
+      // hash match, check url as well in case of hash collision
+      if(mCache[index].mData->GetUrl().GetUrl() == url.GetUrl())
       {
-        Data* data = new Data();
-        data->hash = hash;
-        data->url = url;
-        data->textureSet = TextureSet::New();
-        TextureSetImage( data->textureSet, 0u, croppedImage );
-        data->croppedWidth = croppedImage.GetWidth();
-        data->croppedHeight = croppedImage.GetHeight();
-        data->stretchPixelsX = ninePatch.GetStretchPixelsX();
-        data->stretchPixelsY = ninePatch.GetStretchPixelsY();
-        data->border = Rect< int >( 0, 0, 0, 0 );
-        mCache.PushBack( data );
-
-        return mCache.Count(); // valid ids start from 1u
+        // 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];
+            }
+          }
+        }
       }
     }
   }
-  else
+
+  // If this is new image loading, make new cache data
+  if(infoPtr == nullptr)
   {
-    // Load image from file
-    PixelData pixels = SyncImageLoader::Load( url );
-    if( pixels )
-    {
-      Data* data = new Data();
-      data->hash = hash;
-      data->url = url;
-      data->croppedWidth = pixels.GetWidth();
-      data->croppedHeight = pixels.GetHeight();
+    NPatchInfo info(new NPatchData());
+    info.mData->SetId(GenerateUniqueNPatchDataId());
+    info.mData->SetHash(hash);
+    info.mData->SetUrl(url);
+    info.mData->SetBorder(border);
+    info.mData->SetPreMultiplyOnLoad(preMultiplyOnLoad);
 
-      Texture texture = Texture::New( TextureType::TEXTURE_2D, pixels.GetPixelFormat(), pixels.GetWidth(), pixels.GetHeight() );
-      texture.Upload( pixels, 0, 0, 0, 0, pixels.GetWidth(), pixels.GetHeight() );
+    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());
 
-      data->textureSet = TextureSet::New();
-      data->textureSet.SetTexture( 0u, texture );
+    info.mData->SetId(GenerateUniqueNPatchDataId());
+    info.mData->SetHash(hash);
+    info.mData->SetUrl(url);
+    info.mData->SetCroppedWidth(infoPtr->mData->GetCroppedWidth());
+    info.mData->SetCroppedHeight(infoPtr->mData->GetCroppedHeight());
 
-      NinePatchImage::StretchRanges stretchRangesX;
-      stretchRangesX.PushBack( Uint16Pair( border.left, data->croppedWidth - border.right ) );
+    info.mData->SetTextures(infoPtr->mData->GetTextures());
 
-      NinePatchImage::StretchRanges stretchRangesY;
-      stretchRangesY.PushBack( Uint16Pair( border.top, data->croppedHeight - border.bottom ) );
+    NPatchUtility::StretchRanges stretchRangesX;
+    stretchRangesX.PushBack(Uint16Pair(border.left, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.right)) ? info.mData->GetCroppedHeight() - border.right : 0)));
 
-      data->stretchPixelsX = stretchRangesX;
-      data->stretchPixelsY = stretchRangesY;
-      data->border = border;
+    NPatchUtility::StretchRanges stretchRangesY;
+    stretchRangesY.PushBack(Uint16Pair(border.top, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.bottom)) ? info.mData->GetCroppedHeight() - border.bottom : 0)));
 
-      mCache.PushBack( data );
+    info.mData->SetStretchPixelsX(stretchRangesX);
+    info.mData->SetStretchPixelsY(stretchRangesY);
+    info.mData->SetBorder(border);
 
-      return mCache.Count(); // valid ids start from 1u
-    }
-  }
+    info.mData->SetPreMultiplyOnLoad(infoPtr->mData->IsPreMultiplied());
 
-  return 0u;
-}
+    info.mData->SetLoadingState(NPatchData::LoadingState::LOAD_COMPLETE);
 
-bool NPatchLoader::GetNPatchData( std::size_t id, const Data*& data )
-{
-  if( ( id > UNINITIALIZED_ID )&&( id <= mCache.Count() ) )
+    mCache.emplace_back(std::move(info));
+    infoPtr = &mCache.back();
+  }
+  // Else, LOAD_FAILED. just increase reference so we can reuse it.
+  else
   {
-    data = mCache[ id - 1u ]; // id's start from 1u
-    return true;
+    infoPtr->mReferenceCount++;
   }
-  data = NULL;
-  return false;
+
+  DALI_ASSERT_ALWAYS(infoPtr && "NPatchInfo creation failed!");
+
+  return infoPtr->mData;
 }
 
 } // namespace Internal