}
}
}
+
+ DALI_TEST_EQUALS(gPipelineCache->level0nodes.size(), 3, TEST_LOCATION);
DALI_TEST_EQUALS(noBlendFoundCount, 1u, TEST_LOCATION);
+ // Remove renderer to test whether old pipeline is removed
+ application.GetScene().Remove(actor1);
+ actor1.RemoveRenderer(renderer1);
+ renderer1.Reset();
+
+ // Make the frame count of the pipeline cache large to clean cache
+ gPipelineCache->mFrameCount = 1000;
+
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_EQUALS(gPipelineCache->level0nodes.size(), 2, TEST_LOCATION);
+
END_TEST;
-}
\ No newline at end of file
+}
~Impl()
{
threadPool.reset(nullptr); // reset now to maintain correct destruction order
+ rendererContainer.Clear(); // clear now before the pipeline cache is deleted
}
void AddRenderTracker(Render::RenderTracker* renderTracker)
}
}
- // Clean latest used pipeline
- mImpl->pipelineCache->CleanLatestUsedCache();
+ // Reset pipeline cache before rendering
+ mImpl->pipelineCache->PreRender();
mImpl->commandBufferSubmitted = false;
}
{
namespace
{
+constexpr uint32_t CACHE_CLEAN_FRAME_COUNT = 600; // 60fps * 10sec
+
// Helper to get the vertex input format
Dali::Graphics::VertexInputFormat GetPropertyVertexFormat(Property::Type propertyType)
{
return retval;
}
+void PipelineCacheL0::ClearUnusedCache()
+{
+ for(auto iter = level1nodes.begin(); iter != level1nodes.end();)
+ {
+ if(iter->ClearUnusedCache())
+ {
+ iter = level1nodes.erase(iter);
+ }
+ else
+ {
+ iter++;
+ }
+ }
+}
+
PipelineCacheL2* PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions)
{
// early out
return retval;
}
+bool PipelineCacheL1::ClearUnusedCache()
+{
+ if(noBlend.referenceCount > 0)
+ {
+ return false;
+ }
+
+ for(auto iter = level2nodes.begin(); iter != level2nodes.end();)
+ {
+ if(iter->referenceCount == 0)
+ {
+ iter = level2nodes.erase(iter);
+ }
+ else
+ {
+ iter++;
+ }
+ }
+
+ return level2nodes.empty();
+}
+
void PipelineCacheQueryInfo::GenerateHash()
{
// Lightweight hash value generation.
// If we can reuse latest bound pipeline, Fast return.
if(ReuseLatestBoundPipeline(latestUsedCacheIndex, queryInfo))
{
+ mLatestResult[latestUsedCacheIndex].level2->referenceCount++;
return mLatestResult[latestUsedCacheIndex];
}
PipelineResult result{};
result.pipeline = level2->pipeline.get();
- result.level0 = level0;
- result.level1 = level1;
result.level2 = level2;
+ level2->referenceCount++;
+
// Copy query and result
mLatestQuery[latestUsedCacheIndex] = queryInfo;
mLatestResult[latestUsedCacheIndex] = result;
return mLatestResult[latestUsedCacheIndex].pipeline != nullptr && PipelineCacheQueryInfo::Equal(queryInfo, mLatestQuery[latestUsedCacheIndex]);
}
+void PipelineCache::PreRender()
+{
+ CleanLatestUsedCache();
+
+ // We don't need to check this every frame
+ if(++mFrameCount >= CACHE_CLEAN_FRAME_COUNT)
+ {
+ mFrameCount = 0u;
+ ClearUnusedCache();
+ }
+}
+
+void PipelineCache::ClearUnusedCache()
+{
+ for(auto iter = level0nodes.begin(); iter != level0nodes.end();)
+ {
+ iter->ClearUnusedCache();
+
+ if(iter->level1nodes.empty())
+ {
+ iter = level0nodes.erase(iter);
+ }
+ else
+ {
+ iter++;
+ }
+ }
+}
+
+void PipelineCache::ResetPipeline(PipelineCacheL2* pipelineCache)
+{
+ if(pipelineCache)
+ {
+ pipelineCache->referenceCount--;
+ }
+}
+
} // namespace Dali::Internal::Render
struct PipelineCacheL2
{
uint32_t hash{};
+ uint32_t referenceCount{0u};
Graphics::ColorBlendState colorBlendState;
Graphics::UniquePtr<Graphics::Pipeline> pipeline;
};
{
PipelineCacheL2* GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions);
+ /**
+ * @brief Clear unused caches.
+ */
+ bool ClearUnusedCache();
+
uint32_t hashCode{}; // 1byte cull, 1byte poly, 1byte frontface
Graphics::RasterizationState rs{};
Graphics::InputAssemblyState ia{};
{
PipelineCacheL1* GetPipelineCacheL1(Render::Renderer* renderer, bool usingReflection);
+ /**
+ * @brief Clear unused caches.
+ */
+ void ClearUnusedCache();
+
std::size_t hash{};
Geometry* geometry{};
Program* program{};
struct PipelineResult
{
Graphics::Pipeline* pipeline;
-
- PipelineCacheL0* level0;
- PipelineCacheL1* level1;
- PipelineCacheL2* level2;
+ PipelineCacheL2* level2;
};
/**
bool ReuseLatestBoundPipeline(const int latestUsedCacheIndex, const PipelineCacheQueryInfo& queryInfo) const;
/**
+ * @brief This is called before rendering every frame.
+ */
+ void PreRender();
+
+ /**
+ * @brief Decrease the reference count of the pipeline cache.
+ * @param pipelineCache The pipeline cache to decrease the reference count
+ */
+ void ResetPipeline(PipelineCacheL2* pipelineCache);
+
+private:
+ /**
* @brief Clear latest bound result.
*/
void CleanLatestUsedCache()
mLatestResult[1].pipeline = nullptr;
}
+ /**
+ * @brief Clear unused caches.
+ */
+ void ClearUnusedCache();
+
private:
Graphics::Controller* graphicsController{nullptr};
std::vector<PipelineCacheL0> level0nodes;
// (Since most UI case (like Text and Image) enable blend, and most 3D case disable blend.)
PipelineCacheQueryInfo mLatestQuery[2]; ///< Latest requested query info. It will be invalidate after query's renderer / geometry / blendingOptions value changed.
PipelineResult mLatestResult[2]; ///< Latest used result. It will be invalidate when we call CleanLatestUsedCache() or some cache changed.
+
+ uint32_t mFrameCount{0u};
};
} // namespace Render
static MemoryPoolObjectAllocator<Renderer> gRenderRendererMemoryPool;
return gRenderRendererMemoryPool;
}
-}
+} // namespace
void Renderer::PrepareCommandBuffer()
{
mPipelineCache = &pipelineCache;
}
-Renderer::~Renderer() = default;
+Renderer::~Renderer()
+{
+ // Reset old pipeline
+ mPipelineCache->ResetPipeline(mPipeline);
+}
void Renderer::operator delete(void* ptr)
{
queryInfo.GenerateHash();
+ // Reset old pipeline
+ mPipelineCache->ResetPipeline(mPipeline);
+
// Find or generate new pipeline.
auto pipelineResult = mPipelineCache->GetPipeline(queryInfo, true);
+ mPipeline = pipelineResult.level2;
+
// should be never null?
return *pipelineResult.pipeline;
}
{
struct ShaderCache;
class PipelineCache;
+class PipelineCacheL2;
class UniformBufferManager;
-class PipelineCache;
class Renderer;
using RendererKey = MemoryPoolKey<Render::Renderer>;
std::vector<Graphics::UniformBufferBinding> mUniformBufferBindings{};
Render::PipelineCache* mPipelineCache{nullptr};
+ PipelineCacheL2* mPipeline{nullptr};
using Hash = std::size_t;