/*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 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.
{
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)
{
}
} // namespace
-PipelineCacheL0* PipelineCache::GetPipelineCacheL0(Program* program, Render::Geometry* geometry)
+PipelineCacheL0* PipelineCache::GetPipelineCacheL0(std::size_t hash, Program* program, Render::Geometry* geometry)
{
- auto it = std::find_if(level0nodes.begin(), level0nodes.end(), [program, geometry](PipelineCacheL0& item) {
- return ((item.program == program && item.geometry == geometry));
+ auto it = std::find_if(level0nodes.begin(), level0nodes.end(), [hash, program, geometry](PipelineCacheL0& item) {
+ return ((item.hash == hash && item.program == program && item.geometry == geometry));
});
// Add new node to cache
{
const VertexBuffer::Format& vertexFormat = *vertexBuffer->GetFormat();
+ uint32_t divisor = vertexBuffer->GetDivisor();
+ Graphics::VertexInputRate vertexInputRate = (divisor == 0
+ ? Graphics::VertexInputRate::PER_VERTEX
+ : Graphics::VertexInputRate::PER_INSTANCE);
+
vertexInputState.bufferBindings.emplace_back(vertexFormat.size, // stride
- Graphics::VertexInputRate::PER_VERTEX);
+ vertexInputRate);
+ //@todo Add the actual rate to the graphics struct
const uint32_t attributeCount = vertexBuffer->GetAttributeCount();
uint32_t lastBoundAttributeIndex = 0;
++bindingIndex;
}
PipelineCacheL0 level0;
+ level0.hash = hash;
level0.program = program;
level0.geometry = geometry;
level0.inputState = vertexInputState;
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.
+ hash = (reinterpret_cast<std::size_t>(program) >> Dali::Log<sizeof(decltype(*program))>::value) ^
+ (reinterpret_cast<std::size_t>(geometry) >> Dali::Log<sizeof(decltype(*geometry))>::value) ^
+ ((blendingEnabled ? 1u : 0u) << 0u) ^
+ ((alphaPremultiplied ? 1u : 0u) << 1u) ^
+ (static_cast<std::size_t>(geometry->GetTopology()) << 2u) ^
+ (static_cast<std::size_t>(renderer->GetFaceCullMode()) << 5u) ^
+ ((cameraUsingReflection ? 1u : 0u) << 8u) ^
+ (blendingEnabled ? static_cast<std::size_t>(blendingOptions->GetBitmask()) : 0xDA11u);
+}
+
+bool PipelineCacheQueryInfo::Equal(const PipelineCacheQueryInfo& lhs, const PipelineCacheQueryInfo& rhs) noexcept
+{
+ // Naive equal check.
+ const bool ret = (lhs.hash == rhs.hash) && // Check hash value first
+ (lhs.program == rhs.program) &&
+ (lhs.geometry == rhs.geometry) &&
+ (lhs.blendingEnabled == rhs.blendingEnabled) &&
+ (lhs.alphaPremultiplied == rhs.alphaPremultiplied) &&
+ (lhs.geometry->GetTopology() == rhs.geometry->GetTopology()) &&
+ (lhs.renderer->GetFaceCullMode() == rhs.renderer->GetFaceCullMode()) &&
+ (lhs.cameraUsingReflection == rhs.cameraUsingReflection) &&
+ (!lhs.blendingEnabled ||
+ (lhs.blendingOptions->GetBitmask() == rhs.blendingOptions->GetBitmask() &&
+ ((lhs.blendingOptions->GetBlendColor() == nullptr && rhs.blendingOptions->GetBlendColor() == nullptr) ||
+ (lhs.blendingOptions->GetBlendColor() &&
+ rhs.blendingOptions->GetBlendColor() &&
+ (*lhs.blendingOptions->GetBlendColor() == *rhs.blendingOptions->GetBlendColor())))));
+
+ return ret;
+}
+
PipelineCache::PipelineCache(Graphics::Controller& controller)
: graphicsController(&controller)
{
+ // Clean up cache first
+ CleanLatestUsedCache();
}
PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound)
{
- auto* level0 = GetPipelineCacheL0(queryInfo.program, queryInfo.geometry);
+ // Seperate branch whether query use blending or not.
+ const int latestUsedCacheIndex = queryInfo.blendingEnabled ? 0 : 1;
+
+ // If we can reuse latest bound pipeline, Fast return.
+ if(ReuseLatestBoundPipeline(latestUsedCacheIndex, queryInfo))
+ {
+ mLatestResult[latestUsedCacheIndex].level2->referenceCount++;
+ return mLatestResult[latestUsedCacheIndex];
+ }
+
+ auto* level0 = GetPipelineCacheL0(queryInfo.hash, queryInfo.program, queryInfo.geometry);
auto* level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
auto* level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
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 result;
}
+bool PipelineCache::ReuseLatestBoundPipeline(const int latestUsedCacheIndex, const PipelineCacheQueryInfo& queryInfo) const
+{
+ 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