Trigger Program cleaning by the size of cache container 11/299911/34
authorEunki, Hong <eunkiki.hong@samsung.com>
Thu, 12 Oct 2023 03:19:07 +0000 (12:19 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Thu, 25 Jul 2024 04:25:25 +0000 (13:25 +0900)
Current logic try to clean program cache every 10 seconds.
That mean, we need to re-create program.
It might make some unneccessary performance slow-down actions.

To avoid it, let we try to GC only if the number of program cache size
is bigger than threshold, not always.

And also, for every GC time, we delete only 5 programs incrementally per each frame.
So make we less-block the rendering time.

It will be useful for real world app who don't use custom shader.

Change-Id: Ie4e3bfb3486984673caa79750f55d813f256c0bf
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
automated-tests/src/dali/utc-Dali-RenderTask.cpp
automated-tests/src/dali/utc-Dali-Shader.cpp
dali/internal/render/common/render-manager.cpp
dali/internal/render/renderers/pipeline-cache.cpp
dali/internal/render/renderers/pipeline-cache.h
dali/internal/render/renderers/render-renderer.cpp
dali/internal/render/renderers/render-renderer.h
dali/internal/render/shaders/program-controller.cpp
dali/internal/render/shaders/program-controller.h
dali/internal/render/shaders/program.cpp
dali/internal/render/shaders/program.h

index 90156929b2c574b77a185e914a47b588e9c0c6b6..a88f2535330813cb69257edd4b0ec8f4d53f85d9 100644 (file)
@@ -4420,7 +4420,7 @@ int UtcDaliRenderTaskRenderPassTag(void)
   END_TEST;
 }
 
-int UtcDaliRenderTaskWithWrongShaderData(void)
+int UtcDaliRenderTaskWithWrongShaderData01(void)
 {
   TestApplication application;
   tet_infoline("Testing RenderTask with wrong shader data");
@@ -4457,6 +4457,54 @@ int UtcDaliRenderTaskWithWrongShaderData(void)
   END_TEST;
 }
 
+int UtcDaliRenderTaskWithWrongShaderData02(void)
+{
+  TestApplication application;
+  tet_infoline("Testing RenderTask with wrong shader data. This UTC is for line coverage");
+
+  Stage   stage = Stage::GetCurrent();
+  Vector2 stageSize(stage.GetSize());
+
+  Actor blue                                 = Actor::New();
+  blue[Dali::Actor::Property::NAME]          = "Blue";
+  blue[Dali::Actor::Property::ANCHOR_POINT]  = AnchorPoint::CENTER;
+  blue[Dali::Actor::Property::PARENT_ORIGIN] = ParentOrigin::CENTER;
+  blue[Dali::Actor::Property::SIZE]          = Vector2(300, 300);
+  blue[Dali::Actor::Property::POSITION]      = Vector2(0, 0);
+
+  Geometry geometry = Geometry::New();
+
+  Shader   validShader = Shader::New("vertexSrc", "fragmentSrc");
+  Renderer renderer    = Renderer::New(geometry, validShader);
+  blue.AddRenderer(renderer);
+
+  stage.Add(blue);
+
+  auto& gfx = application.GetGraphicsController();
+
+  RenderTaskList renderTaskList = stage.GetRenderTaskList();
+  DALI_TEST_EQUALS(0u, renderTaskList.GetTask(0u).GetRenderPassTag(), TEST_LOCATION);
+  // Render and notify
+  application.SendNotification();
+  application.Render(16);
+  DALI_TEST_CHECK(gfx.mCallStack.FindMethod("CreatePipeline"));
+  gfx.mCallStack.Reset();
+  DALI_TEST_EQUALS(0u, renderTaskList.GetTask(0u).GetRenderPassTag(), TEST_LOCATION);
+
+  // Change the shader as invalid.
+  Shader invalidShader = Shader::New(Property::Value(10.0f));
+  renderer.SetShader(invalidShader);
+
+  // Render and notify
+  application.SendNotification();
+  application.Render(16);
+  DALI_TEST_CHECK(!gfx.mCallStack.FindMethod("CreatePipeline"));
+  gfx.mCallStack.Reset();
+  DALI_TEST_EQUALS(0u, renderTaskList.GetTask(0u).GetRenderPassTag(), TEST_LOCATION);
+
+  END_TEST;
+}
+
 int UtcDaliRenderTaskOrderIndex01(void)
 {
   TestApplication application;
@@ -4649,4 +4697,4 @@ int UtcDaliRenderTaskDestructWorkerThreadN(void)
   DALI_TEST_CHECK(true);
 
   END_TEST;
-}
\ No newline at end of file
+}
index f419d749f165c47f394967054a5530545d0e9d1d..bed7fc30b6284fed8ba7569bd0ec9e3f17d5a809 100644 (file)
@@ -732,6 +732,169 @@ int UtcDaliShaderWrongData(void)
 
   END_TEST;
 }
+namespace
+{
+constexpr uint32_t PROGRAM_CACHE_CLEAN_FRAME_COUNT = 300u; // 60fps * 5sec
+
+constexpr uint32_t INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD = 64u; // Let we trigger program cache clean if the number of shader is bigger than this value
+constexpr uint32_t MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD = 1024u;
+
+constexpr uint32_t PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT = PROGRAM_CACHE_CLEAN_FRAME_COUNT + MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD;
+
+static_assert(PROGRAM_CACHE_CLEAN_FRAME_COUNT <= PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT);
+static_assert(MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD <= PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT);
+} // namespace
+
+int UtcDaliShaderStressTest01(void)
+{
+  TestApplication application;
+
+  tet_infoline("Test that use a lots of independence shaders, for line coverage of program cache removal");
+
+  const int testFrameCount = INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD /*Initial threshold*/ + 1u /*..And make over the threshold*/ + PROGRAM_CACHE_CLEAN_FRAME_COUNT /*Program GC frame count*/;
+
+  try
+  {
+    // We should keep some renderer that some Program should be 'Garbage Collected' when they are the latest program for some renderer.
+    const int referenceKeepedRendererCount = INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD;
+
+    std::vector<Renderer> rendererList; ///< To keep reference of Renderer
+    rendererList.reserve(referenceKeepedRendererCount);
+
+    for(int frameCount = 0; frameCount < testFrameCount; ++frameCount)
+    {
+      // Generate new shader code
+      std::ostringstream oss;
+      oss << VertexSource << "\n"
+          << frameCount << "\n";
+
+      Shader   shader   = Shader::New(oss.str(), FragmentSource);
+      Geometry geometry = CreateQuadGeometry();
+      Renderer renderer = Renderer::New(geometry, shader);
+
+      if(frameCount < referenceKeepedRendererCount)
+      {
+        rendererList.push_back(renderer);
+      }
+
+      Actor actor = Actor::New();
+      actor.AddRenderer(renderer);
+      actor.SetProperty(Actor::Property::SIZE, Vector2(400.0f, 400.0f));
+      application.GetScene().Add(actor);
+
+      tet_printf("start %d ~ ", frameCount);
+      application.SendNotification();
+      application.Render(0);
+      tet_printf(" ~ end %d\n", frameCount);
+
+      actor.Unparent();
+    }
+
+    tet_printf("Generate shader finished. Let's wait incremental GC");
+
+    for(uint32_t frameCount = 0u; frameCount < PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT; ++frameCount)
+    {
+      tet_printf("start %u ~ ", frameCount);
+      application.SendNotification();
+      application.Render(0);
+      tet_printf(" ~ end %u\n", frameCount);
+    }
+    DALI_TEST_CHECK(true);
+  }
+  catch(...)
+  {
+    DALI_TEST_CHECK(false);
+  }
+  END_TEST;
+}
+
+int UtcDaliShaderStressTest02(void)
+{
+  TestApplication application;
+
+  tet_infoline("Test that use a lots of independence shaders, for line coverage of program cache removal 02");
+  tet_infoline("For this UTC, we will test a renderer that has 2 programs A and B. Use A first, and use B, and use A again. But A become GC, B is not GC.");
+
+  const int testFrameCount = INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD /*Initial threshold*/ + 1u /*..And make over the threshold*/ + PROGRAM_CACHE_CLEAN_FRAME_COUNT /*Program GC frame count*/;
+
+  try
+  {
+    // Keep first frame reference for test.
+    Renderer firstFrameRenderer;
+    for(int frameCount = 0; frameCount < testFrameCount; ++frameCount)
+    {
+      // Generate new shader code
+      std::ostringstream oss;
+      oss << VertexSource << "\n"
+          << frameCount << "\n";
+
+      Shader   shader   = Shader::New(oss.str(), FragmentSource);
+      Geometry geometry = CreateQuadGeometry();
+      Renderer renderer = Renderer::New(geometry, shader);
+
+      if(frameCount == 0u)
+      {
+        firstFrameRenderer = renderer;
+      }
+
+      Actor actor = Actor::New();
+      actor.AddRenderer(renderer);
+      actor.SetProperty(Actor::Property::SIZE, Vector2(400.0f, 400.0f));
+      application.GetScene().Add(actor);
+
+      tet_printf("start %d ~ ", frameCount);
+      application.SendNotification();
+      application.Render(0);
+      tet_printf(" ~ end %d\n", frameCount);
+
+      if(frameCount == 0u)
+      {
+        // Generate new shader for future code
+        std::ostringstream oss2;
+        oss2 << VertexSource << "\n"
+             << (frameCount + INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD + 1) << "\n";
+
+        // Create new Program that will not be GC.
+        Shader futureShader = Shader::New(oss2.str(), FragmentSource);
+        renderer.SetShader(futureShader);
+
+        // And render 1 frame.
+        tet_printf("start %d ~ ", frameCount);
+        application.SendNotification();
+        application.Render(0);
+        tet_printf(" ~ end %d\n", frameCount);
+
+        // Change back the Program that will be GC.
+        renderer.SetShader(shader);
+
+        // And render 1 frame again.
+        tet_printf("start %d ~ ", frameCount);
+        application.SendNotification();
+        application.Render(0);
+        tet_printf(" ~ end %d\n", frameCount);
+      }
+
+      actor.Unparent();
+    }
+
+    tet_printf("Generate shader finished. Let's wait incremental GC");
+
+    for(uint32_t frameCount = 0u; frameCount < PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT; ++frameCount)
+    {
+      tet_printf("start %u ~ ", frameCount);
+      application.SendNotification();
+      application.Render(0);
+      tet_printf(" ~ end %u\n", frameCount);
+    }
+
+    DALI_TEST_CHECK(true);
+  }
+  catch(...)
+  {
+    DALI_TEST_CHECK(false);
+  }
+  END_TEST;
+}
 
 int UtcDaliShaderDestructWorkerThreadN(void)
 {
index 529d464f9da7f7e0cb4d4b13417e820b72087167..a815eb34bdadf1a358003353ff8b7897e205e43a 100644 (file)
@@ -63,8 +63,15 @@ Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_REN
 
 namespace
 {
-// TODO : Cache clean logic have some problem now. Just block it until bug resolved
-// constexpr uint32_t CACHE_CLEAN_FRAME_COUNT = 600u; // 60fps * 10sec
+constexpr uint32_t PROGRAM_CACHE_CLEAN_FRAME_COUNT = 300u; // 60fps * 5sec
+
+constexpr uint32_t INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD = 64u; // Let we trigger program cache clean if the number of shader is bigger than this value
+constexpr uint32_t MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD = 1024u;
+
+constexpr uint32_t PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT = PROGRAM_CACHE_CLEAN_FRAME_COUNT + MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD;
+
+static_assert(PROGRAM_CACHE_CLEAN_FRAME_COUNT <= PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT);
+static_assert(MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD <= PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT);
 
 #if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
 constexpr uint32_t SHRINK_TO_FIT_FRAME_COUNT = (1u << 8); ///< 256 frames. Make this value as power of 2.
@@ -177,7 +184,8 @@ struct RenderManager::Impl
 
   ~Impl()
   {
-    rendererContainer.Clear(); // clear now before the pipeline cache is deleted
+    rendererContainer.Clear(); // clear now before the program contoller and the pipeline cache are deleted
+    pipelineCache.reset();     // clear now before the program contoller is deleted
   }
 
   void AddRenderTracker(Render::RenderTracker* renderTracker)
@@ -199,6 +207,60 @@ struct RenderManager::Impl
     }
   }
 
+  /**
+   * @brief Prepare to check used count of shader and program cache.
+   * It will be used when the size of shader and program cache is too big, so we need to collect garbages.
+   * Currently, we collect and remove programs only if the number of program/shader is bigger than threshold.
+   *
+   * @note Should be called at PreRender
+   */
+  void RequestProgramCacheCleanIfNeed()
+  {
+    if(programCacheCleanRequestedFrame == 0u)
+    {
+      if(DALI_UNLIKELY(programController.GetCachedProgramCount() > programCacheCleanRequiredThreshold))
+      {
+        // Mark current frame count
+        programCacheCleanRequestedFrame = frameCount;
+
+        DALI_LOG_RELEASE_INFO("Trigger ProgramCache GC. program : [%u]\n", programController.GetCachedProgramCount());
+
+        // Prepare to collect program used flag.
+        programController.ResetUsedFlag();
+      }
+    }
+  }
+
+  /**
+   * @brief Cleanup unused program and shader cache if need.
+   *
+   * @note Should be called at PostRender
+   */
+  void ClearUnusedProgramCacheIfNeed()
+  {
+    // Remove unused shader and programs during we render PROGRAM_CACHE_CLEAN_FRAME_COUNT frames.
+    if(programCacheCleanRequestedFrame != 0u && programCacheCleanRequestedFrame + PROGRAM_CACHE_CLEAN_FRAME_COUNT - 1u <= frameCount)
+    {
+      // Clean cache incrementally, or force clean if we spend too much frames to collect them.
+      if(!programController.ClearUnusedCacheIncrementally(programCacheCleanRequestedFrame + PROGRAM_CACHE_FORCE_CLEAN_FRAME_COUNT - 1u <= frameCount))
+      {
+        // Reset current frame count.
+        programCacheCleanRequestedFrame = 0u;
+
+        DALI_LOG_RELEASE_INFO("ProgramCache GC finished. program : [%u]\n", programController.GetCachedProgramCount());
+
+        // Double up threshold
+        programCacheCleanRequiredThreshold <<= 1;
+        programCacheCleanRequiredThreshold = std::max(programCacheCleanRequiredThreshold, programController.GetCachedProgramCount());
+
+        if(programCacheCleanRequiredThreshold > MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD)
+        {
+          programCacheCleanRequiredThreshold = MAXIMUM_PROGRAM_CACHE_CLEAN_THRESHOLD;
+        }
+      }
+    }
+  }
+
   // the order is important for destruction,
   Graphics::Controller&           graphicsController;
   RenderQueue                     renderQueue;      ///< A message queue for receiving messages from the update-thread.
@@ -234,6 +296,12 @@ struct RenderManager::Impl
   uint32_t    frameCount{0u};                                                    ///< The current frame count
   BufferIndex renderBufferIndex{SceneGraphBuffers::INITIAL_UPDATE_BUFFER_INDEX}; ///< The index of the buffer to read from;
 
+  uint32_t programCacheCleanRequiredThreshold{INITIAL_PROGRAM_CACHE_CLEAN_THRESHOLD}; ///< The threshold to request program cache clean up operation.
+                                                                                      ///< It will be automatically increased after we request cache clean.
+
+  uint32_t programCacheCleanRequestedFrame{0u}; ///< 0 mean, we didn't request program cache clean. otherwise, we request cache clean.
+                                                ///< otherwise, we request program cache clean at that frame, and now we are checking reference.
+
   bool lastFrameWasRendered{false}; ///< Keeps track of the last frame being rendered due to having render instructions
   bool commandBufferSubmitted{false};
 };
@@ -551,14 +619,8 @@ void RenderManager::PreRender(Integration::RenderStatus& status, bool forceClear
   // Reset pipeline cache before rendering
   mImpl->pipelineCache->PreRender();
 
-  // Let we collect reference counts during CACHE_CLEAN_FRAME_COUNT frames.
-  // TODO : Cache clean logic have some problem now. Just block it until bug resolved
-  /*
-  if(mImpl->frameCount % CACHE_CLEAN_FRAME_COUNT == 1)
-  {
-    mImpl->programController.ResetReferenceCount();
-  }
-  */
+  // Check we need to clean up program cache
+  mImpl->RequestProgramCacheCleanIfNeed();
 
   mImpl->commandBufferSubmitted = false;
 }
@@ -1146,8 +1208,7 @@ void RenderManager::RenderScene(Integration::RenderStatus& status, Integration::
 
   if(targetsToPresent.size() > 0u)
   {
-    DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_RENDER_FINISHED", [&](std::ostringstream& oss)
-                                            { oss << "[" << targetsToPresent.size() << "]"; });
+    DALI_TRACE_BEGIN_WITH_MESSAGE_GENERATOR(gTraceFilter, "DALI_RENDER_FINISHED", [&](std::ostringstream& oss) { oss << "[" << targetsToPresent.size() << "]"; });
   }
 
   // Flush UBOs
@@ -1209,14 +1270,7 @@ void RenderManager::PostRender()
     count += scene->GetRenderInstructions().Count(mImpl->renderBufferIndex);
   }
 
-  // Remove unused shader and programs during CACHE_CLEAN_FRAME_COUNT frames.
-  // TODO : Cache clean logic have some problem now. Just block it until bug resolved
-  /*
-  if(mImpl->frameCount % CACHE_CLEAN_FRAME_COUNT == 0)
-  {
-    mImpl->programController.ClearUnusedCache();
-  }
-  */
+  mImpl->ClearUnusedProgramCacheIfNeed();
 
 #if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
   // Shrink relevant containers if required.
index e78b7f7dcb7be0da7848cec623aa378fd6e602f7..c73ab4e797db7269a779cb6aab65de9267e5d9dd 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.
@@ -255,6 +255,9 @@ PipelineCacheL0Ptr PipelineCache::GetPipelineCacheL0(Program* program, Render::G
     level0.geometry   = geometry;
     level0.inputState = vertexInputState;
 
+    // Observer program lifecycle
+    program->AddLifecycleObserver(*this);
+
     it = level0nodes.insert(level0nodes.end(), std::move(level0));
 
     if(attrNotFound)
@@ -502,6 +505,15 @@ PipelineCache::PipelineCache(Graphics::Controller& controller)
   CleanLatestUsedCache();
 }
 
+PipelineCache::~PipelineCache()
+{
+  // Stop observer program lifecycle
+  for(auto&& level0node : level0nodes)
+  {
+    level0node.program->RemoveLifecycleObserver(*this);
+  }
+}
+
 PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound)
 {
   // Seperate branch whether query use blending or not.
@@ -577,6 +589,8 @@ void PipelineCache::ClearUnusedCache()
 
     if(iter->level1nodes.empty())
     {
+      // Stop observer program lifecycle
+      iter->program->RemoveLifecycleObserver(*this);
       iter = level0nodes.erase(iter);
     }
     else
@@ -592,4 +606,23 @@ void PipelineCache::ResetPipeline(PipelineCachePtr pipelineCache)
   pipelineCache->referenceCount--;
 }
 
+void PipelineCache::ProgramDestroyed(const Program* program)
+{
+  // Remove latest used pipeline cache infomation.
+  CleanLatestUsedCache();
+
+  // Remove cached items what cache hold now.
+  for(auto iter = level0nodes.begin(); iter != level0nodes.end();)
+  {
+    if(iter->program == program)
+    {
+      iter = level0nodes.erase(iter);
+    }
+    else
+    {
+      iter++;
+    }
+  }
+}
+
 } // namespace Dali::Internal::Render
index 6d5caf45f292aca4ec73ea386a6f8a7f9af75b72..17bc6a030024eee10f8e2a7a58dedb15ff26dbaa 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_RENDER_PIPELINE_CACHE_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.
 #include <dali/graphics-api/graphics-controller.h>
 #include <dali/graphics-api/graphics-pipeline.h>
 #include <dali/graphics-api/graphics-types.h>
-#include <dali/internal/common/blending-options.h>
 #include <dali/public-api/common/list-wrapper.h>
 
+#include <dali/internal/common/blending-options.h>
+#include <dali/internal/render/shaders/program.h> ///< For Program::LifecycleObserver
+
 namespace Dali::Internal
 {
-class Program;
 namespace Render
 {
 class Renderer;
@@ -76,7 +77,7 @@ struct PipelineCacheL1
 };
 
 /**
- * Cache Level 0 : Stores hash, geometry, program amd vertex input state
+ * Cache Level 0 : Stores geometry, program amd vertex input state
  */
 struct PipelineCacheL0 // L0 cache
 {
@@ -130,7 +131,7 @@ struct PipelineResult
 /**
  * Pipeline cache
  */
-class PipelineCache
+class PipelineCache : public Program::LifecycleObserver
 {
 public:
   /**
@@ -139,6 +140,11 @@ public:
    */
   explicit PipelineCache(Graphics::Controller& controller);
 
+  /**
+   * Destructor
+   */
+  ~PipelineCache();
+
   /**
    * Retrieves next cache level
    */
@@ -173,6 +179,12 @@ public:
    */
   void ResetPipeline(PipelineCachePtr pipelineCache);
 
+public: // From Program::LifecycleObserver
+  /**
+   * @copydoc Dali::Internal::Program::LifecycleObserver::ProgramDestroyed()
+   */
+  void ProgramDestroyed(const Program* program);
+
 private:
   /**
    * @brief Clear latest bound result.
index 2b81a0cfec668ed1e25ba9a7fbe9ad5b0e3dd51c..2314229bd1301763df2ae11f470ad216eeee9174 100644 (file)
@@ -163,6 +163,13 @@ Renderer::~Renderer()
     mPipelineCache->ResetPipeline(mPipeline);
     mPipelineCached = false;
   }
+
+  // Stop observing
+  if(mCurrentProgram)
+  {
+    mCurrentProgram->RemoveLifecycleObserver(*this);
+    mCurrentProgram = nullptr;
+  }
 }
 
 void Renderer::operator delete(void* ptr)
@@ -398,6 +405,10 @@ Program* Renderer::PrepareProgram(const SceneGraph::RenderInstruction& instructi
   if(!shaderData)
   {
     DALI_LOG_ERROR("Failed to get shader data.\n");
+    if(mCurrentProgram)
+    {
+      mCurrentProgram->RemoveLifecycleObserver(*this);
+    }
     mCurrentProgram = nullptr;
     return nullptr;
   }
@@ -408,6 +419,10 @@ Program* Renderer::PrepareProgram(const SceneGraph::RenderInstruction& instructi
   if(!program)
   {
     DALI_LOG_ERROR("Failed to create program for shader at address %p.\n", reinterpret_cast<const void*>(&shader));
+    if(mCurrentProgram)
+    {
+      mCurrentProgram->RemoveLifecycleObserver(*this);
+    }
     mCurrentProgram = nullptr;
     return nullptr;
   }
@@ -449,7 +464,15 @@ Program* Renderer::PrepareProgram(const SceneGraph::RenderInstruction& instructi
   }
 
   // Set prefetched program to be used during rendering
-  mCurrentProgram = program;
+  if(mCurrentProgram != program)
+  {
+    if(mCurrentProgram)
+    {
+      mCurrentProgram->RemoveLifecycleObserver(*this);
+    }
+    mCurrentProgram = program;
+    mCurrentProgram->AddLifecycleObserver(*this);
+  }
   return mCurrentProgram;
 }
 
@@ -862,16 +885,16 @@ void Renderer::FillUniformBuffer(Program&
         continue;
       }
 
-      uniform.uniformOffset      = uniformInfo.offset;
-      uniform.uniformLocation    = int16_t(uniformInfo.location);
-      uniform.uniformBlockIndex  = uniformInfo.bufferIndex;
-      uniform.initialized        = true;
+      uniform.uniformOffset     = uniformInfo.offset;
+      uniform.uniformLocation   = int16_t(uniformInfo.location);
+      uniform.uniformBlockIndex = uniformInfo.bufferIndex;
+      uniform.initialized       = true;
 
-      auto       dst      = ubo->GetOffset() + uniformInfo.offset;
-      const auto typeSize = iter.propertyValue->GetValueSize();
+      auto       dst             = ubo->GetOffset() + uniformInfo.offset;
+      const auto typeSize        = iter.propertyValue->GetValueSize();
       uniform.arrayElementStride = uniformInfo.elementCount > 0 ? (uniformInfo.elementStride ? uniformInfo.elementStride : typeSize) : typeSize;
 
-      const auto dest     = dst + uniform.arrayElementStride * arrayIndex;
+      const auto dest = dst + uniform.arrayElementStride * arrayIndex;
 
       ubo->Write(iter.propertyValue->GetValueAddress(updateBufferIndex),
                  typeSize,
@@ -971,6 +994,28 @@ void Renderer::DetachFromNodeDataProvider(const SceneGraph::NodeDataProvider& no
   }
 }
 
+void Renderer::ProgramDestroyed(const Program* program)
+{
+  DALI_ASSERT_ALWAYS(mCurrentProgram == program && "Something wrong happend when Render::Renderer observed by program!");
+  mCurrentProgram = nullptr;
+
+  // The cached pipeline might be invalided after program destroyed.
+  // Remove current cached pipeline information.
+  // Note that reset flag is enought since mPipeline pointer will be invalidate now.
+  mPipelineCached = false;
+
+  // Destroy whole mNodeIndexMap and mUniformIndexMaps container.
+  // It will be re-created at next render time.
+  // Note : Destroy the program will be happened at RenderManager::PostRender().
+  //        We don't worry about the mNodeIndexMap and mUniformIndexMaps become invalidated after this call.
+  mNodeIndexMap.clear();
+  mUniformIndexMaps.clear();
+#if defined(LOW_SPEC_MEMORY_MANAGEMENT_ENABLED)
+  mNodeIndexMap.shrink_to_fit();
+  mUniformIndexMaps.shrink_to_fit();
+#endif
+}
+
 Vector4 Renderer::GetTextureUpdateArea() const noexcept
 {
   Vector4 result = Vector4::ZERO;
index c4aa33a889dcfd0af77ce6a2f896ddb6807cb8de..58f7128b9f8dd66cfc52ca3b26c3f480885caa7a 100644 (file)
@@ -88,7 +88,7 @@ namespace Render
  * These objects are used during RenderManager::Render(), so properties modified during
  * the Update must either be double-buffered, or set via a message added to the RenderQueue.
  */
-class Renderer
+class Renderer : public Program::LifecycleObserver
 {
 public:
   /**
@@ -531,6 +531,12 @@ public:
    */
   Vector4 GetTextureUpdateArea() const noexcept;
 
+public: // From Program::LifecycleObserver
+  /**
+   * @copydoc Dali::Internal::Program::LifecycleObserver::ProgramDestroyed()
+   */
+  void ProgramDestroyed(const Program* program);
+
 private:
   struct UniformIndexMap;
 
@@ -636,7 +642,7 @@ private:
     const PropertyInputImpl* propertyValue{nullptr}; ///< The property value
     Hash                     uniformNameHash{0u};
     Hash                     uniformNameHashNoArray{0u};
-    int32_t                  arrayIndex{-1}; ///< The array index
+    int32_t                  arrayIndex{-1};         ///< The array index
     uint32_t                 arrayElementStride{0u}; ///< The stride for element of an array (0 - tightly packed)
 
     int16_t  uniformLocation{0u};
index 133dc4359a673f38816eea457a944bb42d41ddd8..830421a4a0243dad49e3921ff5c45905ceaf7bc0 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.
@@ -26,35 +26,59 @@ namespace Dali
 {
 namespace Internal
 {
+namespace
+{
+constexpr uint32_t MAXIMUM_COLLECTING_ITEM_COUNTS_PER_GC_CALL = 5u;
+static_assert(1u <= MAXIMUM_COLLECTING_ITEM_COUNTS_PER_GC_CALL); /// Should delete at least 1 item.
+} // namespace
+
 ProgramController::ProgramController(Graphics::Controller& graphicsController)
-: mGraphicsController(graphicsController)
+: mGraphicsController(graphicsController),
+  mProgramCacheAdded(false)
 {
   mProgramCache.Reserve(32);
+
+  mClearCacheIterator = mProgramCache.Begin();
 }
 
 ProgramController::~ProgramController() = default;
 
-void ProgramController::ResetReferenceCount()
+void ProgramController::ResetUsedFlag()
 {
   for(auto&& item : mProgramCache)
   {
-    item->ClearReferenceCount();
+    item->ClearUsedFlag();
   }
+  mClearCacheIterator = mProgramCache.Begin();
 }
 
-void ProgramController::ClearUnusedCache()
+bool ProgramController::ClearUnusedCacheIncrementally(bool fullCollect)
 {
-  for(auto iter = mProgramCache.Begin(); iter != mProgramCache.End();)
+  if(mProgramCacheAdded)
   {
-    if((*iter)->GetReferenceCount() == 0u)
+    // Clear from begin again if cache container changed.
+    mClearCacheIterator = mProgramCache.Begin();
+
+    // Reset flag
+    mProgramCacheAdded = false;
+  }
+
+  uint32_t checkedCount = 0u;
+  // Check only limited items. (Since this loop give overhead for rendering.)
+  // TODO : Could we check running time here, instead of check counter?
+  for(; mClearCacheIterator != mProgramCache.End() && (fullCollect || ++checkedCount <= MAXIMUM_COLLECTING_ITEM_COUNTS_PER_GC_CALL);)
+  {
+    if(!((*mClearCacheIterator)->IsUsed()))
     {
-      iter = mProgramCache.Erase(iter);
+      mClearCacheIterator = mProgramCache.Erase(mClearCacheIterator);
     }
     else
     {
-      ++iter;
+      ++mClearCacheIterator;
     }
   }
+
+  return mClearCacheIterator != mProgramCache.End();
 }
 
 Program* ProgramController::GetProgram(size_t shaderHash)
@@ -66,7 +90,7 @@ Program* ProgramController::GetProgram(size_t shaderHash)
     size_t hash = (*iter)->GetHash();
     if(shaderHash == hash)
     {
-      (*iter)->Reference();
+      (*iter)->MarkAsUsed();
       program = (*iter)->GetProgram();
       break;
     }
@@ -79,6 +103,8 @@ void ProgramController::AddProgram(size_t shaderHash, Program* program)
   // we expect unique hash values so it is caller's job to guarantee that
   // AddProgram is only called after program checks that GetProgram returns NULL
   mProgramCache.PushBack(new ProgramPair(program, shaderHash));
+
+  mProgramCacheAdded = true;
 }
 
 } // namespace Internal
index 57e97ca81928ff7c20f2fd41f7b7d7be6d4ae8a9..ed21fdc079474644077aa1816cb724addd47f514 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_PROGRAM_CONTROLLER_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.
@@ -51,7 +51,7 @@ public:
     ProgramPair(Program* program, size_t shaderHash)
     : mProgram(program),
       mShaderHash(shaderHash),
-      mRefCount{1u}
+      mUsed{true} ///< Initialize as be used at construct time.
     {
     }
 
@@ -81,19 +81,19 @@ public:
       return mShaderHash;
     }
 
-    [[nodiscard]] inline uint16_t GetReferenceCount() const
+    [[nodiscard]] inline bool IsUsed() const
     {
-      return mRefCount;
+      return mUsed;
     }
 
-    void Reference()
+    void MarkAsUsed()
     {
-      ++mRefCount;
+      mUsed = true;
     }
 
-    void ClearReferenceCount()
+    void ClearUsedFlag()
     {
-      mRefCount = 0u;
+      mUsed = false;
     }
 
     ProgramPair(const ProgramPair&) = delete;
@@ -102,7 +102,7 @@ public:
   private: // Data
     Program* mProgram;
     size_t   mShaderHash;
-    uint16_t mRefCount;
+    bool     mUsed : 1;
   };
 
   /**
@@ -121,14 +121,28 @@ public:
 
 public: // API
   /**
-   * @brief Reset all program reference count as 0.
+   * @brief Reset all program used flags.
+   * @note The used flag of program will be marked when we call GetProgram() or AddProgram().
    */
-  void ResetReferenceCount();
+  void ResetUsedFlag();
 
   /**
-   * @brief Clear program who the reference count is 0.
+   * @brief Clear program incrementally who are not be used.
+   *
+   * @param[in] fullCollect True if we want to clear whole items.
+   * @return True if we need to iterate more item to check used count. False if we clear cache completely.
    */
-  void ClearUnusedCache();
+  bool ClearUnusedCacheIncrementally(bool fullCollect);
+
+  /**
+   * @brief Get the number of cached program
+   *
+   * @return The number of cached program.
+   */
+  uint32_t GetCachedProgramCount() const
+  {
+    return static_cast<uint32_t>(mProgramCache.Count());
+  }
 
 private: // From ProgramCache
   /**
@@ -147,6 +161,9 @@ private: // Data
   using ProgramContainer = OwnerContainer<ProgramPair*>;
   using ProgramIterator  = ProgramContainer::Iterator;
   ProgramContainer mProgramCache;
+
+  ProgramIterator mClearCacheIterator;
+  bool            mProgramCacheAdded : 1;
 };
 
 } // namespace Internal
index c2dcf148e642bba52e2d07f4affc82b8d9876a44..d4e271196d5bcf25732614748354bb68f2cd5bce 100644 (file)
@@ -95,13 +95,26 @@ Program* Program::New(ProgramCache& cache, const Internal::ShaderDataPtr& shader
 
 Program::Program(ProgramCache& cache, Internal::ShaderDataPtr shaderData, Graphics::Controller& controller)
 : mCache(cache),
-  mGfxProgram(nullptr),
+  mLifecycleObservers(),
   mGfxController(controller),
-  mProgramData(std::move(shaderData))
+  mProgramData(std::move(shaderData)),
+  mObserverNotifying(false)
 {
 }
 
-Program::~Program() = default;
+Program::~Program()
+{
+  mObserverNotifying = true;
+  for(auto&& iter : mLifecycleObservers)
+  {
+    auto* observer = iter.first;
+    observer->ProgramDestroyed(this);
+  }
+  mLifecycleObservers.clear();
+
+  // Note : We don't need to restore mObserverNotifying to false as we are in delete the object.
+  // If someone call AddObserver / RemoveObserver after this, assert.
+}
 
 void Program::BuildReflection(const Graphics::Reflection& graphicsReflection, Render::UniformBufferManager& uniformBufferManager)
 {
index c6e58b68a28c77a2e0dbcba051dbb4133c7dfd80..f738435277c29a733ff3e5ad41fd0036ecc27699 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_PROGRAM_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.
@@ -21,6 +21,7 @@
 // EXTERNAL INCLUDES
 #include <cstdint> // int32_t, uint32_t
 #include <string>
+#include <unordered_map>
 
 // INTERNAL INCLUDES
 #include <dali/internal/common/const-string.h>
@@ -46,7 +47,7 @@ class ProgramCache;
 namespace Render
 {
 class UniformBufferManager;
-}
+} // namespace Render
 
 /**
  * A program contains a vertex & fragment shader.
@@ -57,6 +58,24 @@ class Program
 public:
   using Hash = std::size_t;
 
+  /**
+   * Observer to determine when the program is no longer present
+   */
+  class LifecycleObserver
+  {
+  public:
+    /**
+     * Called shortly before the program is destroyed.
+     */
+    virtual void ProgramDestroyed(const Program* program) = 0;
+
+  protected:
+    /**
+     * Virtual destructor, no deletion through this interface
+     */
+    virtual ~LifecycleObserver() = default;
+  };
+
   /**
    * Indices of default uniforms
    */
@@ -128,6 +147,45 @@ public:
    */
   [[nodiscard]] const Graphics::UniformInfo* GetDefaultUniform(DefaultUniformIndex defaultUniformIndex) const;
 
+  /**
+   * Allows Program to track the life-cycle of this object.
+   * Note that we allow to observe lifecycle multiple times.
+   * But ProgramDestroyed callback will be called only one time.
+   * @param[in] observer The observer to add.
+   */
+  void AddLifecycleObserver(LifecycleObserver& observer)
+  {
+    DALI_ASSERT_ALWAYS(!mObserverNotifying && "Cannot add observer while notifying Program::LifecycleObservers");
+
+    auto iter = mLifecycleObservers.find(&observer);
+    if(iter != mLifecycleObservers.end())
+    {
+      // Increase the number of observer count
+      ++(iter->second);
+    }
+    else
+    {
+      mLifecycleObservers.insert({&observer, 1u});
+    }
+  }
+
+  /**
+   * The Program no longer needs to track the life-cycle of this object.
+   * @param[in] observer The observer that to remove.
+   */
+  void RemoveLifecycleObserver(LifecycleObserver& observer)
+  {
+    DALI_ASSERT_ALWAYS(!mObserverNotifying && "Cannot remove observer while notifying Program::LifecycleObservers");
+
+    auto iter = mLifecycleObservers.find(&observer);
+    DALI_ASSERT_ALWAYS(iter != mLifecycleObservers.end());
+
+    if(--(iter->second) == 0u)
+    {
+      mLifecycleObservers.erase(iter);
+    }
+  }
+
 private: // Implementation
   /**
    * Constructor, private so no direct instantiation
@@ -190,10 +248,15 @@ public:
     return mUniformBlockMemoryRequirements;
   }
 
-private:                                                 // Data
-  ProgramCache&                          mCache;         ///< The program cache
+private:                // Data
+  ProgramCache& mCache; ///< The program cache
+
+  using LifecycleObserverContainer = std::unordered_map<LifecycleObserver*, uint32_t>; ///< Lifecycle observers container. We allow to add same observer multiple times.
+                                                                                       ///< Key is a pointer to observer, value is the number of observer added.
+  LifecycleObserverContainer mLifecycleObservers;
+
   Graphics::UniquePtr<Graphics::Program> mGfxProgram;    ///< Gfx program
-  Graphics::Controller&                  mGfxController; /// < Gfx controller
+  Graphics::Controller&                  mGfxController; ///< Gfx controller
   Internal::ShaderDataPtr                mProgramData;   ///< Shader program source and binary (when compiled & linked or loaded)
 
   // uniform value caching
@@ -205,6 +268,8 @@ private:                                                 // Data
   UniformReflectionContainer mReflectionDefaultUniforms{}; ///< Contains default uniforms
 
   UniformBlockMemoryRequirements mUniformBlockMemoryRequirements; ///< Memory requirements per each block, block 0 = standalone/emulated
+
+  bool mObserverNotifying : 1; ///< Safety guard flag to ensure that the LifecycleObserver not be added or deleted while observing.
 };
 
 } // namespace Internal