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 9015692..a88f253 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 f419d74..bed7fc3 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 529d464..a815eb3 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 e78b7f7..c73ab4e 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 6d5caf4..17bc6a0 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:
   /**
@@ -140,6 +141,11 @@ public:
   explicit PipelineCache(Graphics::Controller& controller);
 
   /**
+   * Destructor
+   */
+  ~PipelineCache();
+
+  /**
    * Retrieves next cache level
    */
   PipelineCacheL0Ptr GetPipelineCacheL0(Program* program, Render::Geometry* geometry);
@@ -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 2b81a0c..2314229 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 c4aa33a..58f7128 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 133dc43..830421a 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 57e97ca..ed21fdc 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 c2dcf14..d4e2711 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 c6e58b6..f738435 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.
@@ -58,6 +59,24 @@ 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
    */
   enum class DefaultUniformIndex
@@ -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