/*
- * Copyright (c) 2021 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)
+PipelineCacheL0Ptr PipelineCache::GetPipelineCacheL0(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));
{
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;
level0.program = program;
level0.geometry = geometry;
level0.inputState = vertexInputState;
- level0nodes.emplace_back(std::move(level0));
- it = level0nodes.end() - 1;
+
+ it = level0nodes.insert(level0nodes.end(), std::move(level0));
}
- return &*it;
+ return it;
}
-PipelineCacheL1* PipelineCacheL0::GetPipelineCacheL1(Render::Renderer* renderer, bool usingReflection)
+PipelineCacheL1Ptr PipelineCacheL0::GetPipelineCacheL1(Render::Renderer* renderer, bool usingReflection)
{
// hash must be collision free
uint32_t hash = 0;
hash = (topo & 0xffu) | ((cull << 8u) & 0xff00u) | ((uint32_t(poly) << 16u) & 0xff0000u);
// If L1 not found by hash, create rasterization state describing pipeline and store it
- auto it = std::find_if(level1nodes.begin(), level1nodes.end(), [hash](PipelineCacheL1& item) {
- return item.hashCode == hash;
- });
+ auto it = std::find_if(level1nodes.begin(), level1nodes.end(), [hash](PipelineCacheL1& item) { return item.hashCode == hash; });
- PipelineCacheL1* retval = nullptr;
if(it == level1nodes.end())
{
PipelineCacheL1 item;
item.rs.frontFace = Graphics::FrontFace::COUNTER_CLOCKWISE;
item.rs.polygonMode = poly; // not in use
item.ia.topology = geometry->GetTopology();
- level1nodes.emplace_back(std::move(item));
- retval = &level1nodes.back();
+
+ it = level1nodes.insert(level1nodes.end(), std::move(item));
}
- else
+
+ return it;
+}
+
+void PipelineCacheL0::ClearUnusedCache()
+{
+ for(auto iter = level1nodes.begin(); iter != level1nodes.end();)
{
- retval = &*it;
+ if(iter->ClearUnusedCache())
+ {
+ iter = level1nodes.erase(iter);
+ }
+ else
+ {
+ iter++;
+ }
}
- return retval;
}
-PipelineCacheL2* PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions)
+PipelineCacheL2Ptr PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions)
{
// early out
if(!blend)
{
+ if(DALI_UNLIKELY(noBlends.empty()))
+ {
+ noBlends.emplace_back(PipelineCacheL2{});
+ }
+
+ auto& noBlend = *noBlends.begin();
+
if(noBlend.pipeline == nullptr)
{
// reset all before returning if pipeline has never been created for that case
noBlend.hash = 0;
memset(&noBlend.colorBlendState, 0, sizeof(Graphics::ColorBlendState));
}
- return &noBlend;
+ return noBlends.begin();
}
auto bitmask = uint32_t(blendingOptions.GetBitmask());
// Find by bitmask (L2 entries must be sorted by bitmask)
- auto it = std::find_if(level2nodes.begin(), level2nodes.end(), [bitmask](PipelineCacheL2& item) {
- return item.hash == bitmask;
- });
+ auto it = std::find_if(level2nodes.begin(), level2nodes.end(), [bitmask](PipelineCacheL2& item) { return item.hash == bitmask; });
// TODO: find better way of blend constants lookup
- PipelineCacheL2* retval = nullptr;
+ PipelineCacheL2Ptr retval = level2nodes.end();
if(it != level2nodes.end())
{
bool hasBlendColor = blendingOptions.GetBlendColor();
Vector4 v(it->colorBlendState.blendConstants);
if(v == *blendingOptions.GetBlendColor())
{
- retval = &*it;
+ retval = it;
}
++it;
}
if(!hasBlendColor)
{
- retval = &*it;
+ retval = it;
}
}
- if(!retval)
+ if(retval == level2nodes.end())
{
// create new entry and return it with null pipeline
- PipelineCacheL2 l2;
+ PipelineCacheL2 l2{};
l2.pipeline = nullptr;
auto& colorBlendState = l2.colorBlendState;
colorBlendState.SetBlendEnable(true);
Graphics::BlendOp rgbOp = ConvertBlendEquation(blendingOptions.GetBlendEquationRgb());
- Graphics::BlendOp alphaOp = ConvertBlendEquation(blendingOptions.GetBlendEquationRgb());
+ Graphics::BlendOp alphaOp = ConvertBlendEquation(blendingOptions.GetBlendEquationAlpha());
if(blendingOptions.IsAdvancedBlendEquationApplied() && premul)
{
if(rgbOp != alphaOp)
}
l2.hash = blendingOptions.GetBitmask();
- level2nodes.emplace_back(std::move(l2));
- std::sort(level2nodes.begin(), level2nodes.end(), [](PipelineCacheL2& lhs, PipelineCacheL2& rhs) {
- return lhs.hash < rhs.hash;
- });
+ auto upperBound = std::upper_bound(level2nodes.begin(), level2nodes.end(), l2, [](const PipelineCacheL2& lhs, const PipelineCacheL2& rhs) { return lhs.hash < rhs.hash; });
+
+ level2nodes.insert(upperBound, std::move(l2));
// run same function to retrieve retval
retval = GetPipelineCacheL2(blend, premul, blendingOptions);
return retval;
}
+bool PipelineCacheL1::ClearUnusedCache()
+{
+ for(auto iter = level2nodes.begin(); iter != level2nodes.end();)
+ {
+ if(iter->referenceCount == 0)
+ {
+ iter = level2nodes.erase(iter);
+ }
+ else
+ {
+ iter++;
+ }
+ }
+
+ if(!noBlends.empty() && noBlends.begin()->referenceCount > 0)
+ {
+ return false;
+ }
+
+ 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);
- auto* level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
- auto* level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
+ // 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.program, queryInfo.geometry);
+ auto level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
+
+ PipelineCachePtr level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
// Create new pipeline at level2 if requested
if(level2->pipeline == nullptr && createNewIfNotFound)
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(PipelineCachePtr pipelineCache)
+{
+ // TODO : Can we always assume that pipelineCache input is valid iterator?
+ pipelineCache->referenceCount--;
+}
+
} // namespace Dali::Internal::Render