Clear pipelie cache if geometry destroyed or buffer changed 57/320657/1
authorEunki, Hong <eunkiki.hong@samsung.com>
Mon, 24 Feb 2025 13:39:07 +0000 (22:39 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Wed, 5 Mar 2025 08:58:39 +0000 (17:58 +0900)
We also cache pipeline cache the Render::Geometry by the raw pointer.
If someone use duplicated pointer, it might return invalid pipeline
with unmatched vertexInputState.

To avoid this issue, let we erase cache if Render::Geometry destroyed,
same as Render::Program

 - This is the commit message #2:

Reset cached pipeline if geometry buffer changed

Until now, we don't re-cache the geometry
if the vertex buffer added/removed, or data changed.

Since the vertex attribute might be changed if we try to use
same geometry, the rendering result
show some non-common results.

To fix this cache issue, let we ensure to reset the cached infomations
if the vertex buffer / indices buffer changed.

Change-Id: I0dc5b4fb6b0645d4b7763d7aa890d6ad946d54c6
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
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-geometry.cpp
dali/internal/render/renderers/render-geometry.h
dali/internal/render/renderers/render-renderer.cpp
dali/internal/render/renderers/render-renderer.h

index 0708d68982e5c12a6ec8e2a490d6d25d04519819..4650e18ef0b69a7d22181f11818b7085a2925d7b 100644 (file)
@@ -183,6 +183,7 @@ struct RenderManager::Impl
 
   ~Impl()
   {
+    geometryContainer.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
   }
@@ -273,7 +274,7 @@ struct RenderManager::Impl
     samplerContainer.Clear();
     frameBufferContainer.Clear();
     vertexBufferContainer.Clear();
-    geometryContainer.Clear();
+    geometryContainer.Clear(); // clear now before the pipeline cache is deleted
     rendererContainer.Clear();
     textureContainer.Clear();
 
index dfbd1ab3ba7a83581d1b8825b796591398f921c0..68e17d6bd7cc66c536bf5f890fce1bcf76e9bdec 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2025 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,8 +255,9 @@ PipelineCacheL0Ptr PipelineCache::GetPipelineCacheL0(Program* program, Render::G
     level0.geometry   = geometry;
     level0.inputState = vertexInputState;
 
-    // Observer program lifecycle
+    // Observer program and geometry lifecycle
     program->AddLifecycleObserver(*this);
+    geometry->AddLifecycleObserver(*this);
 
     it = level0nodes.insert(level0nodes.end(), std::move(level0));
 
@@ -507,11 +508,13 @@ PipelineCache::PipelineCache(Graphics::Controller& controller)
 
 PipelineCache::~PipelineCache()
 {
-  // Stop observer program lifecycle
+  // Stop observer lifecycle
   for(auto&& level0node : level0nodes)
   {
     level0node.program->RemoveLifecycleObserver(*this);
+    level0node.geometry->RemoveLifecycleObserver(*this);
   }
+  level0nodes.clear();
 }
 
 PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound)
@@ -590,8 +593,9 @@ void PipelineCache::ClearUnusedCache()
 
     if(iter->level1nodes.empty())
     {
-      // Stop observer program lifecycle
+      // Stop observer lifecycle
       iter->program->RemoveLifecycleObserver(*this);
+      iter->geometry->RemoveLifecycleObserver(*this);
       iter = level0nodes.erase(iter);
     }
     else
@@ -617,6 +621,35 @@ void PipelineCache::ProgramDestroyed(const Program* program)
   {
     if(iter->program == program)
     {
+      iter->geometry->RemoveLifecycleObserver(*this);
+      iter = level0nodes.erase(iter);
+    }
+    else
+    {
+      iter++;
+    }
+  }
+}
+
+Geometry::LifecycleObserver::NotifyReturnType PipelineCache::GeometryBufferChanged(const Geometry* geometry)
+{
+  // Let just run the same logic with geometry destroyed cases.
+  GeometryDestroyed(geometry);
+
+  return Geometry::LifecycleObserver::NotifyReturnType::STOP_OBSERVING;
+}
+
+void PipelineCache::GeometryDestroyed(const Geometry* geometry)
+{
+  // Remove latest used pipeline cache infomation.
+  CleanLatestUsedCache();
+
+  // Remove cached items what cache hold now.
+  for(auto iter = level0nodes.begin(); iter != level0nodes.end();)
+  {
+    if(iter->geometry == geometry)
+    {
+      iter->program->RemoveLifecycleObserver(*this);
       iter = level0nodes.erase(iter);
     }
     else
index 0f2f5ad86534aaa2fbd64d1d17922dab1742f298..3bc6a31cd057befc6865d63c0543fb7ded9f328a 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_INTERNAL_RENDER_PIPELINE_CACHE_H
 
 /*
- * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2025 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/public-api/common/list-wrapper.h>
 
 #include <dali/internal/common/blending-options.h>
-#include <dali/internal/render/shaders/program.h> ///< For Program::LifecycleObserver
+#include <dali/internal/render/renderers/render-geometry.h> ///< For Geometry::LifecycleObserver
+#include <dali/internal/render/shaders/program.h>           ///< For Program::LifecycleObserver
 
 namespace Dali::Internal
 {
 namespace Render
 {
 class Renderer;
-class Geometry;
 
 struct PipelineCacheL2;
 struct PipelineCacheL1;
@@ -133,7 +133,7 @@ struct PipelineResult
 /**
  * Pipeline cache
  */
-class PipelineCache : public Program::LifecycleObserver
+class PipelineCache : public Program::LifecycleObserver, public Geometry::LifecycleObserver
 {
 public:
   /**
@@ -187,6 +187,17 @@ public: // From Program::LifecycleObserver
    */
   void ProgramDestroyed(const Program* program);
 
+public: // From Geometry::LifecycleObserver
+  /**
+   * @copydoc Dali::Internal::Geometry::LifecycleObserver::GeometryBufferChanged()
+   */
+  Geometry::LifecycleObserver::NotifyReturnType GeometryBufferChanged(const Geometry* geometry);
+
+  /**
+   * @copydoc Dali::Internal::Geometry::LifecycleObserver::GeometryDestroyed()
+   */
+  void GeometryDestroyed(const Geometry* geometry);
+
 private:
   /**
    * @brief Clear latest bound result.
index a8b711552028c58c96a23ff2406fdfbb23070ed8..6c3f0ec2011c228b06cf468dff4b6d2550ca20e4 100644 (file)
@@ -51,17 +51,31 @@ inline constexpr size_t GetSizeOfIndexFromIndexType(Dali::Graphics::Format graph
 }
 } // unnamed namespace
 Geometry::Geometry()
-: mIndices(),
+: mLifecycleObservers(),
+  mIndices(),
   mIndexBuffer(nullptr),
   mIndexType(Dali::Graphics::Format::R16_UINT),
   mGeometryType(Dali::Geometry::TRIANGLES),
   mIndicesChanged(false),
   mHasBeenUploaded(false),
-  mUpdated(true)
+  mUpdated(true),
+  mObserverNotifying(false)
 {
 }
 
-Geometry::~Geometry() = default;
+Geometry::~Geometry()
+{
+  mObserverNotifying = true;
+  for(auto&& iter : mLifecycleObservers)
+  {
+    auto* observer = iter.first;
+    observer->GeometryDestroyed(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 Geometry::AddVertexBuffer(Render::VertexBuffer* vertexBuffer)
 {
@@ -151,6 +165,25 @@ void Geometry::Upload(Graphics::Controller& graphicsController)
     }
 
     mHasBeenUploaded = true;
+
+    // Notify to observers that geometry informations are changed
+    if(mUpdated)
+    {
+      mObserverNotifying = true;
+      for(auto iter = mLifecycleObservers.begin(); iter != mLifecycleObservers.end();)
+      {
+        auto returnValue = (*iter).first->GeometryBufferChanged(this);
+        if(returnValue == LifecycleObserver::KEEP_OBSERVING)
+        {
+          ++iter;
+        }
+        else
+        {
+          iter = mLifecycleObservers.erase(iter);
+        }
+      }
+      mObserverNotifying = false;
+    }
   }
 }
 
index 1443d59f5f2b6627e00dae81d8a6a6708c4c0b41..804c15f50025e656de1f06f9d99d10c713c1b13f 100644 (file)
@@ -17,6 +17,9 @@
  * limitations under the License.
  */
 
+// EXTERNAL INCLUDES
+#include <unordered_map>
+
 // INTERNAL INCLUDES
 #include <dali/devel-api/common/owner-container.h>
 #include <dali/graphics-api/graphics-controller.h>
@@ -51,6 +54,38 @@ public:
   using Uint16ContainerType = Dali::Vector<uint16_t>;
   using Uint32ContainerType = Dali::Vector<uint32_t>;
 
+  /**
+   * Observer to determine when the geometry is no longer present
+   */
+  class LifecycleObserver
+  {
+  public:
+    enum NotifyReturnType
+    {
+      STOP_OBSERVING,
+      KEEP_OBSERVING,
+    };
+
+  public:
+    /**
+     * Called shortly if the geometry indices or vertex buffers are changed.
+     * @return NotifyReturnType::STOP_OBSERVING if we will not observe this object after this called
+     *         NotifyReturnType::KEEP_OBSERVING if we will observe this object after this called.
+     */
+    virtual NotifyReturnType GeometryBufferChanged(const Geometry* geometry) = 0;
+
+    /**
+     * Called shortly before the geometry is destroyed.
+     */
+    virtual void GeometryDestroyed(const Geometry* geometry) = 0;
+
+  protected:
+    /**
+     * Virtual destructor, no deletion through this interface
+     */
+    virtual ~LifecycleObserver() = default;
+  };
+
   Geometry();
 
   /**
@@ -147,7 +182,50 @@ public:
    */
   bool BindVertexAttributes(Graphics::CommandBuffer& commandBuffer);
 
+  /**
+   * Allows Geometry to track the life-cycle of this object.
+   * Note that we allow to observe lifecycle multiple times.
+   * But GeometryDestroyed 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 Geometry::LifecycleObservers");
+
+    auto iter = mLifecycleObservers.find(&observer);
+    if(iter != mLifecycleObservers.end())
+    {
+      // Increase the number of observer count
+      ++(iter->second);
+    }
+    else
+    {
+      mLifecycleObservers.insert({&observer, 1u});
+    }
+  }
+
+  /**
+   * The Geometry 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 Geometry::LifecycleObservers");
+
+    auto iter = mLifecycleObservers.find(&observer);
+    DALI_ASSERT_ALWAYS(iter != mLifecycleObservers.end());
+
+    if(--(iter->second) == 0u)
+    {
+      mLifecycleObservers.erase(iter);
+    }
+  }
+
 private:
+  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;
+
   // VertexBuffers
   Vector<Render::VertexBuffer*> mVertexBuffers;
 
@@ -160,6 +238,8 @@ private:
   bool mIndicesChanged : 1;
   bool mHasBeenUploaded : 1;
   bool mUpdated : 1; ///< Flag to know if data has changed in a frame. Value fixed after Upload() call, and reset as false after OnRenderFinished
+
+  bool mObserverNotifying : 1; ///< Safety guard flag to ensure that the LifecycleObserver not be added or deleted while observing.
 };
 
 } // namespace Render
index 2483452aed6546feee86a1cb23a27fca9d7b62a2..e6a752aaa6cc7ba0c0d6b9006e2190baae9d6fe0 100644 (file)
@@ -158,6 +158,12 @@ void Renderer::Initialize(Graphics::Controller& graphicsController, ProgramCache
   mProgramCache         = &programCache;
   mUniformBufferManager = &uniformBufferManager;
   mPipelineCache        = &pipelineCache;
+
+  // Add Observer now
+  if(mGeometry)
+  {
+    mGeometry->AddLifecycleObserver(*this);
+  }
 }
 
 Renderer::~Renderer()
@@ -175,6 +181,11 @@ Renderer::~Renderer()
     mCurrentProgram->RemoveLifecycleObserver(*this);
     mCurrentProgram = nullptr;
   }
+  if(mGeometry)
+  {
+    mGeometry->RemoveLifecycleObserver(*this);
+    mGeometry = nullptr;
+  }
 }
 
 void Renderer::operator delete(void* ptr)
@@ -189,7 +200,27 @@ Renderer* Renderer::Get(RendererKey::KeyType rendererKey)
 
 void Renderer::SetGeometry(Render::Geometry* geometry)
 {
-  mGeometry = geometry;
+  if(mGeometry != geometry)
+  {
+    if(mGeometry)
+    {
+      mGeometry->RemoveLifecycleObserver(*this);
+    }
+
+    mGeometry = geometry;
+
+    if(mGeometry)
+    {
+      mGeometry->AddLifecycleObserver(*this);
+    }
+
+    // Reset old pipeline
+    if(DALI_LIKELY(mPipelineCached))
+    {
+      mPipelineCache->ResetPipeline(mPipeline);
+      mPipelineCached = false;
+    }
+  }
 }
 
 void Renderer::SetDrawCommands(Dali::DevelRenderer::DrawCommand* pDrawCommands, uint32_t size)
@@ -1051,6 +1082,27 @@ void Renderer::ProgramDestroyed(const Program* program)
 #endif
 }
 
+Geometry::LifecycleObserver::NotifyReturnType Renderer::GeometryBufferChanged(const Geometry* geometry)
+{
+  DALI_ASSERT_ALWAYS(mGeometry == geometry && "Something wrong happend when Render::Renderer observed by geometry!");
+
+  // Reset old pipeline
+  if(DALI_LIKELY(mPipelineCached))
+  {
+    mPipelineCache->ResetPipeline(mPipeline);
+    mPipelineCached = false;
+  }
+
+  return Geometry::LifecycleObserver::NotifyReturnType::KEEP_OBSERVING;
+}
+
+void Renderer::GeometryDestroyed(const Geometry* geometry)
+{
+  // Let just run the same logic with geometry buffer changed cases.
+  [[maybe_unused]] auto ret = GeometryBufferChanged(geometry);
+  mGeometry                 = nullptr;
+}
+
 Vector4 Renderer::GetTextureUpdateArea() const noexcept
 {
   Vector4 result = Vector4::ZERO;
index d793b87a108ec1a29e44061724fe144294e97245..d994c2c66e8e33175757e95ca22633e34ebfba77 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 : public Program::LifecycleObserver
+class Renderer : public Program::LifecycleObserver, public Geometry::LifecycleObserver
 {
 public:
   /**
@@ -550,6 +550,17 @@ public: // From Program::LifecycleObserver
    */
   void ProgramDestroyed(const Program* program);
 
+public: // From Geometry::LifecycleObserver
+  /**
+   * @copydoc Dali::Internal::Geometry::LifecycleObserver::GeometryBufferChanged()
+   */
+  Geometry::LifecycleObserver::NotifyReturnType GeometryBufferChanged(const Geometry* geometry);
+
+  /**
+   * @copydoc Dali::Internal::Geometry::LifecycleObserver::GeometryDestroyed()
+   */
+  void GeometryDestroyed(const Geometry* geometry);
+
 private:
   struct UniformIndexMap;