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>
~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
}
samplerContainer.Clear();
frameBufferContainer.Clear();
vertexBufferContainer.Clear();
- geometryContainer.Clear();
+ geometryContainer.Clear(); // clear now before the pipeline cache is deleted
rendererContainer.Clear();
textureContainer.Clear();
/*
- * 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.
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));
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)
if(iter->level1nodes.empty())
{
- // Stop observer program lifecycle
+ // Stop observer lifecycle
iter->program->RemoveLifecycleObserver(*this);
+ iter->geometry->RemoveLifecycleObserver(*this);
iter = level0nodes.erase(iter);
}
else
{
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
#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;
/**
* Pipeline cache
*/
-class PipelineCache : public Program::LifecycleObserver
+class PipelineCache : public Program::LifecycleObserver, public Geometry::LifecycleObserver
{
public:
/**
*/
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.
}
} // 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)
{
}
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;
+ }
}
}
* 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>
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();
/**
*/
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;
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
mProgramCache = &programCache;
mUniformBufferManager = &uniformBufferManager;
mPipelineCache = &pipelineCache;
+
+ // Add Observer now
+ if(mGeometry)
+ {
+ mGeometry->AddLifecycleObserver(*this);
+ }
}
Renderer::~Renderer()
mCurrentProgram->RemoveLifecycleObserver(*this);
mCurrentProgram = nullptr;
}
+ if(mGeometry)
+ {
+ mGeometry->RemoveLifecycleObserver(*this);
+ mGeometry = nullptr;
+ }
}
void Renderer::operator delete(void* ptr)
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)
#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;
* 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:
/**
*/
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;