Cache string based shader if required 45/314345/10
authorEunki, Hong <eunkiki.hong@samsung.com>
Wed, 10 Jul 2024 08:12:26 +0000 (17:12 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Wed, 24 Jul 2024 12:04:11 +0000 (21:04 +0900)
Let we cache string based shader's ShaderData.
It will be useful to avoid use multiple ShaderData creation
even if we use same source code.

Change-Id: Ifeaa9c2fa47b402690dab9b650b01ce874002069
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
automated-tests/src/dali-internal/utc-Dali-Internal-Shader.cpp
automated-tests/src/dali/utc-Dali-Shader.cpp
dali/internal/event/effects/shader-factory.cpp
dali/internal/event/effects/shader-factory.h

index e89c289..64fcd73 100644 (file)
@@ -97,7 +97,7 @@ int UtcDaliShaderWithPrefixTestVersion(void)
   END_TEST;
 }
 
-int UtcDaliInternalShaderSaveAndLoad(void)
+int UtcDaliInternalShaderSaveAndLoad01(void)
 {
   TestApplication application;
 
@@ -202,3 +202,137 @@ int UtcDaliInternalShaderSaveAndLoad(void)
 
   END_TEST;
 }
+
+int UtcDaliInternalShaderSaveAndLoad02(void)
+{
+  TestApplication application;
+
+  std::string vertexShader1   = "some vertex code\n";
+  std::string fragmentShader1 = "some fragment code\n";
+
+  std::string vertexShader2   = "some another vertex code\n";
+  std::string fragmentShader2 = "some another fragment code\n";
+
+  Dali::Vector<uint8_t> dummyBuffer(5);
+  for(size_t i = 0; i < 5; i++)
+  {
+    dummyBuffer[i] = static_cast<uint8_t>(i + 21);
+  }
+
+  // Make save and load failed successful
+  auto& platformAbstraction = application.GetPlatform();
+  platformAbstraction.SetLoadFileResult(false, dummyBuffer);
+
+  // Test version number in the shader data
+  Dali::Internal::ThreadLocalStorage& tls           = Dali::Internal::ThreadLocalStorage::Get();
+  Dali::Internal::ShaderFactory&      shaderFactory = tls.GetShaderFactory();
+
+  tet_printf("Load shader1. It should be cached at string container\n");
+  size_t                  shaderHash1 = 0u;
+  Internal::ShaderDataPtr shaderData1 = shaderFactory.Load(vertexShader1, fragmentShader1, Shader::Hint::NONE, 0u, "", shaderHash1);
+  DALI_TEST_CHECK(shaderHash1 != 0u);
+
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), true, TEST_LOCATION);
+
+  // Reset trace callstack
+  platformAbstraction.GetTrace().Reset();
+
+  tet_printf("Load shader2. It also should be cached at string container\n");
+  size_t                  shaderHash2 = 0u;
+  Internal::ShaderDataPtr shaderData2 = shaderFactory.Load(vertexShader2, fragmentShader2, Shader::Hint::NONE, 0u, "", shaderHash2);
+  DALI_TEST_CHECK(shaderHash2 != 0u);
+
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), true, TEST_LOCATION);
+
+  // Reset trace callstack
+  platformAbstraction.GetTrace().Reset();
+
+  tet_printf("Both shader 1 and 2 don't have binary now.\n");
+  DALI_TEST_EQUALS(shaderData1.Get()->HasBinary(), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(shaderData2.Get()->HasBinary(), false, TEST_LOCATION);
+
+  // Copy the memory of dummy
+  shaderData1.Get()->AllocateBuffer(5);
+  for(size_t i = 0; i < 5; i++)
+  {
+    shaderData1.Get()->GetBuffer()[i] = static_cast<uint8_t>(i + 1);
+  }
+
+  DALI_TEST_EQUALS(shaderData1.Get()->HasBinary(), true, TEST_LOCATION);
+
+  tet_printf("Save shaderData1 as binary, but failed.\n");
+  shaderFactory.SaveBinary(shaderData1);
+
+  tet_printf("Check shader saved\n");
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), false, TEST_LOCATION);
+
+  // Reset trace callstack
+  platformAbstraction.GetTrace().Reset();
+
+  tet_printf("Save shaderData1 as binary, and success now.\n");
+  platformAbstraction.SetSaveFileResult(true);
+  shaderFactory.SaveBinary(shaderData1);
+
+  tet_printf("Check shader saved\n");
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), false, TEST_LOCATION);
+
+  // Reset trace callstack
+  platformAbstraction.GetTrace().Reset();
+
+  tet_printf("Load shader with same code as shaderData1\n");
+  size_t                  shaderHash3 = 0u;
+  Internal::ShaderDataPtr shaderData3 = shaderFactory.Load(vertexShader1, fragmentShader1, Shader::Hint::NONE, 0u, "", shaderHash3);
+
+  tet_printf("Check shaderData1 cached\n");
+  DALI_TEST_EQUALS(shaderHash3, shaderHash1, TEST_LOCATION);
+  DALI_TEST_EQUALS(shaderData3.Get()->HasBinary(), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(shaderData3.Get(), shaderData1.Get(), TEST_LOCATION);
+
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), false, TEST_LOCATION);
+
+  // Reset trace callstack
+  platformAbstraction.GetTrace().Reset();
+
+  tet_printf("Load shader with same code as shaderData2\n");
+  size_t                  shaderHash4 = 0u;
+  Internal::ShaderDataPtr shaderData4 = shaderFactory.Load(vertexShader2, fragmentShader2, Shader::Hint::NONE, 0u, "", shaderHash4);
+
+  tet_printf("Check shaderData2 cached\n");
+  DALI_TEST_EQUALS(shaderHash4, shaderHash2, TEST_LOCATION);
+  DALI_TEST_EQUALS(shaderData4.Get(), shaderData2.Get(), TEST_LOCATION);
+  DALI_TEST_EQUALS(shaderData4.Get()->HasBinary(), false, TEST_LOCATION);
+
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), false, TEST_LOCATION);
+
+  // Reset trace callstack
+  platformAbstraction.GetTrace().Reset();
+
+  tet_printf("Allow to load shader binary\n");
+  platformAbstraction.SetLoadFileResult(true, dummyBuffer);
+
+  tet_printf("Load shader same as shaderData1, but difference render pass tag\n");
+  size_t                  shaderHash5 = 0u;
+  Internal::ShaderDataPtr shaderData5 = shaderFactory.Load(vertexShader1, fragmentShader1, Shader::Hint::NONE, 1u, "", shaderHash5);
+
+  tet_printf("Check shaderData1 and shaderData5 have same hash, but deferent buffer\n");
+  DALI_TEST_EQUALS(shaderHash5, shaderHash1, TEST_LOCATION);
+  DALI_TEST_CHECK(shaderData5.Get() != shaderData1.Get());
+  DALI_TEST_EQUALS(shaderData5.Get()->HasBinary(), true, TEST_LOCATION);
+
+  tet_printf("Check shaderData5 binary same as dummy buffer\n");
+  DALI_TEST_EQUALS(shaderData5.Get()->GetBufferSize(), dummyBuffer.Count(), TEST_LOCATION);
+  for(size_t i = 0; i < 5; ++i)
+  {
+    DALI_TEST_EQUALS(shaderData5.Get()->GetBuffer()[i], dummyBuffer[i], TEST_LOCATION);
+  }
+
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::SaveShaderBinaryFileFunc), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(platformAbstraction.WasCalled(TestPlatformAbstraction::LoadShaderBinaryFileFunc), true, TEST_LOCATION);
+  END_TEST;
+}
index f419d74..310f233 100644 (file)
@@ -23,6 +23,8 @@
 
 #include <iostream>
 
+#include <test-platform-abstraction.h>
+
 using namespace Dali;
 
 void utc_dali_shader_startup(void)
index 8f2ad5e..678ae49 100644 (file)
@@ -62,11 +62,18 @@ ShaderFactory::ShaderFactory() = default;
 ShaderFactory::~ShaderFactory()
 {
   // Let all the cached objects destroy themselves:
-  for(std::size_t i = 0, cacheSize = mShaderBinaryCache.Size(); i < cacheSize; ++i)
+  for(auto&& listPair : mShaderBinaryCache)
   {
-    if(mShaderBinaryCache[i])
+    for(auto&& shaderData : listPair.second)
     {
-      mShaderBinaryCache[i]->Unreference();
+      shaderData->Unreference();
+    }
+  }
+  for(auto&& listPair : mShaderStringCache)
+  {
+    for(auto&& shaderData : listPair.second)
+    {
+      shaderData->Unreference();
     }
   }
 }
@@ -75,26 +82,57 @@ ShaderDataPtr ShaderFactory::Load(std::string_view vertexSource, std::string_vie
 {
   // Work out the filename for the binary that the glsl source will be compiled and linked to:
   shaderHash = CalculateHash(vertexSource, fragmentSource);
-  std::string binaryShaderFilename;
-  shaderBinaryFilename(shaderHash, binaryShaderFilename);
 
   ShaderDataPtr shaderData;
 
   /// Check a cache of previously loaded shaders:
-  for(std::size_t i = 0, cacheSize = mShaderBinaryCache.Size(); i < cacheSize; ++i)
+  if(mShaderBinaryCache.count(shaderHash) > 0u)
   {
-    if(mShaderBinaryCache[i]->GetHashValue() == shaderHash)
+    const auto& cacheList = mShaderBinaryCache[shaderHash];
+    for(std::size_t i = 0, cacheSize = cacheList.Count(); i < cacheSize; ++i)
     {
-      shaderData = mShaderBinaryCache[i];
+      if(cacheList[i]->GetHints() == hints &&
+         cacheList[i]->GetRenderPassTag() == renderPassTag)
+      {
+        shaderData = cacheList[i];
+
+#ifdef DEBUG_ENABLED
+        if(Debug::Filter::gShader->IsEnabledFor(Debug::General))
+        {
+          std::string binaryShaderFilename;
+          shaderBinaryFilename(shaderHash, binaryShaderFilename);
+
+          DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "Mem cache hit on path: \"%s\", Hint : %d, Tag : %u\n", binaryShaderFilename.c_str(), static_cast<int>(hints), renderPassTag);
+        }
+#endif
+        break;
+      }
+    }
+  }
 
-      DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "Mem cache hit on path: \"%s\"\n", binaryShaderFilename.c_str());
-      break;
+  /// Check a cache of previously loaded shaders as normal string:
+  if(shaderData.Get() == nullptr && mShaderStringCache.count(shaderHash) > 0u)
+  {
+    const auto& cacheList = mShaderStringCache[shaderHash];
+    for(std::size_t i = 0, cacheSize = cacheList.Size(); i < cacheSize; ++i)
+    {
+      if(cacheList[i]->GetHints() == hints &&
+         cacheList[i]->GetRenderPassTag() == renderPassTag)
+      {
+        shaderData = cacheList[i];
+
+        DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "Mem cache hit on string shader. Hash : \"%zu\", Hint : %d, Tag : %u\n", shaderHash, static_cast<int>(hints), renderPassTag);
+        break;
+      }
     }
   }
 
   // If memory cache failed check the file system for a binary or return a source-only ShaderData:
   if(shaderData.Get() == nullptr)
   {
+    std::string binaryShaderFilename;
+    shaderBinaryFilename(shaderHash, binaryShaderFilename);
+
     // Allocate the structure that returns the loaded shader:
     shaderData = new ShaderData(vertexSource, fragmentSource, hints, renderPassTag, name);
     shaderData->SetHashValue(shaderHash);
@@ -105,10 +143,7 @@ ShaderDataPtr ShaderFactory::Load(std::string_view vertexSource, std::string_vie
     Integration::PlatformAbstraction& platformAbstraction = tls.GetPlatformAbstraction();
     const bool                        loaded              = platformAbstraction.LoadShaderBinaryFile(binaryShaderFilename, shaderData->GetBuffer());
 
-    if(loaded)
-    {
-      MemoryCacheInsert(*shaderData);
-    }
+    MemoryCacheInsert(*shaderData, loaded);
 
     DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, loaded ? "loaded on path: \"%s\"\n" : "failed to load on path: \"%s\"\n", binaryShaderFilename.c_str());
   }
@@ -127,25 +162,73 @@ void ShaderFactory::SaveBinary(Internal::ShaderDataPtr shaderData)
   const bool                        saved               = platformAbstraction.SaveShaderBinaryFile(binaryShaderFilename, &shaderData->GetBuffer()[0], static_cast<unsigned int>(shaderData->GetBufferSize())); // don't expect buffer larger than unsigned int
 
   // Save the binary into to memory cache:
-  MemoryCacheInsert(*shaderData);
+  MemoryCacheInsert(*shaderData, saved);
 
   DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, saved ? "Saved to file: %s\n" : "Save to file failed: %s\n", binaryShaderFilename.c_str());
-  if(saved)
-  {
-  } // Avoid unused variable warning in release builds
 }
 
-void ShaderFactory::MemoryCacheInsert(ShaderData& shaderData)
+void ShaderFactory::MemoryCacheInsert(ShaderData& shaderData, const bool isBinaryCached)
 {
-  DALI_ASSERT_DEBUG(shaderData.GetBufferSize() > 0);
+  const size_t shaderHash = shaderData.GetHashValue();
 
   // Save the binary into to memory cache:
-  if(shaderData.GetBufferSize() > 0)
+  if(isBinaryCached)
+  {
+    DALI_ASSERT_DEBUG(shaderData.GetBufferSize() > 0);
+
+    // Remove shaderdata from string cache if it exists:
+    RemoveStringShaderData(shaderData);
+
+    auto& cacheList = mShaderBinaryCache[shaderHash]; ///< Get or create a new cache list.
+
+    cacheList.Reserve(cacheList.Size() + 1); // Make sure the push won't throw after we inc the ref count.
+    shaderData.Reference();
+    cacheList.PushBack(&shaderData);
+    DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "CACHED BINARY FOR HASH: %u, HINT: %d, TAG: %u\n", shaderHash, static_cast<int>(shaderData.GetHints()), shaderData.GetRenderPassTag());
+  }
+  else
   {
-    mShaderBinaryCache.Reserve(mShaderBinaryCache.Size() + 1); // Make sure the push won't throw after we inc the ref count.
+    auto& cacheList = mShaderStringCache[shaderHash]; ///< Get or create a new cache list.
+
+    // Ignore shaderdata with string if it already exists:
+    for(auto iter = cacheList.Begin(); iter != cacheList.End(); ++iter)
+    {
+      if((*iter)->GetHints() == shaderData.GetHints() &&
+         (*iter)->GetRenderPassTag() == shaderData.GetRenderPassTag())
+      {
+        DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "ALREADY CACHED NON-BINARY CACHE FOR HASH: %u, HINT: %d, TAG: %u\n", shaderHash, static_cast<int>(shaderData.GetHints()), shaderData.GetRenderPassTag());
+        return;
+      }
+    }
     shaderData.Reference();
-    mShaderBinaryCache.PushBack(&shaderData);
-    DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "CACHED BINARY FOR HASH: %u\n", shaderData.GetHashValue());
+    cacheList.PushBack(&shaderData);
+    DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "CACHED NON-BINARY SHADER FOR HASH: %u, HINT: %d, TAG: %u\n", shaderHash, static_cast<int>(shaderData.GetHints()), shaderData.GetRenderPassTag());
+  }
+}
+
+void ShaderFactory::RemoveStringShaderData(ShaderData& shaderData)
+{
+  const size_t shaderHash = shaderData.GetHashValue();
+
+  if(mShaderStringCache.count(shaderHash) > 0u)
+  {
+    auto& cacheList = mShaderStringCache[shaderHash];
+    for(auto iter = cacheList.Begin(); iter != cacheList.End(); ++iter)
+    {
+      if((*iter)->GetHints() == shaderData.GetHints() &&
+         (*iter)->GetRenderPassTag() == shaderData.GetRenderPassTag())
+      {
+        DALI_LOG_INFO(Debug::Filter::gShader, Debug::General, "REMOVE NON-BINARY CACHE FOR HASH: %u, HINT: %d, TAG: %u\n", shaderHash, static_cast<int>(shaderData.GetHints()), shaderData.GetRenderPassTag());
+        // Reduce reference before erase
+        (*iter)->Unreference();
+        cacheList.Erase(iter);
+        break;
+      }
+    }
+    if(cacheList.Count() == 0)
+    {
+      mShaderStringCache.erase(shaderHash);
+    }
   }
 }
 
index 2171466..0662c31 100644 (file)
@@ -18,6 +18,9 @@
  *
  */
 
+// EXTERNAL INCLUDES
+#include <dali/devel-api/common/map-wrapper.h>
+
 // INTERNAL INCLUDES
 #include <dali/internal/common/message.h>
 #include <dali/internal/common/shader-data.h>
@@ -79,16 +82,25 @@ public:
   void SaveBinary(Internal::ShaderDataPtr shader) override;
 
 private:
-  void MemoryCacheInsert(Internal::ShaderData& shaderData);
+  void MemoryCacheInsert(Internal::ShaderData& shaderData, const bool isBinaryCached);
+
+  /**
+   * @brief Removes the string shader data from the cache.
+   * @param[in] shaderData The shader data to remove.
+   */
+  void RemoveStringShaderData(Internal::ShaderData& shaderData);
 
   // Undefined
-  ShaderFactory(const ShaderFactory&);
+  ShaderFactory(const ShaderFactory&) = delete;
 
   // Undefined
-  ShaderFactory& operator=(const ShaderFactory& rhs);
+  ShaderFactory& operator=(const ShaderFactory& rhs) = delete;
 
 private:
-  Dali::Vector<Internal::ShaderData*> mShaderBinaryCache; ///< Cache of pre-compiled shaders.
+  using ShaderCacheContainer = std::map<size_t, Dali::Vector<Internal::ShaderData*>>; ///< Container for the shader cache. Key is hash of shader code.
+
+  ShaderCacheContainer mShaderBinaryCache; ///< Cache of pre-compiled shaders.
+  ShaderCacheContainer mShaderStringCache; ///< Cache of non-pre-compiled shaders. (TODO : Could we clean up this cache by user management?)
 
 }; // class ShaderFactory