2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali/internal/render/renderers/pipeline-cache.h>
21 #include <dali/graphics-api/graphics-types.h>
22 #include <dali/integration-api/debug.h>
23 #include <dali/internal/render/common/render-instruction.h>
24 #include <dali/internal/render/renderers/render-renderer.h>
25 #include <dali/internal/render/renderers/render-vertex-buffer.h>
26 #include <dali/internal/render/shaders/program.h>
28 namespace Dali::Internal::Render
32 #if defined(DEBUG_ENABLED)
33 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_PIPELINE_CACHE");
36 constexpr uint32_t CACHE_CLEAN_FRAME_COUNT = 600; // 60fps * 10sec
38 // Helper to get the vertex input format
39 Dali::Graphics::VertexInputFormat GetPropertyVertexFormat(Property::Type propertyType)
41 Dali::Graphics::VertexInputFormat type{};
45 case Property::BOOLEAN:
47 type = Dali::Graphics::VertexInputFormat::UNDEFINED; // type = GL_BYTE; @todo new type for this?
50 case Property::INTEGER:
52 type = Dali::Graphics::VertexInputFormat::INTEGER; // (short)
57 type = Dali::Graphics::VertexInputFormat::FLOAT;
60 case Property::VECTOR2:
62 type = Dali::Graphics::VertexInputFormat::FVECTOR2;
65 case Property::VECTOR3:
67 type = Dali::Graphics::VertexInputFormat::FVECTOR3;
70 case Property::VECTOR4:
72 type = Dali::Graphics::VertexInputFormat::FVECTOR4;
77 type = Dali::Graphics::VertexInputFormat::UNDEFINED;
84 constexpr Graphics::CullMode ConvertCullFace(Dali::FaceCullingMode::Type mode)
88 case Dali::FaceCullingMode::NONE:
90 return Graphics::CullMode::NONE;
92 case Dali::FaceCullingMode::FRONT:
94 return Graphics::CullMode::FRONT;
96 case Dali::FaceCullingMode::BACK:
98 return Graphics::CullMode::BACK;
100 case Dali::FaceCullingMode::FRONT_AND_BACK:
102 return Graphics::CullMode::FRONT_AND_BACK;
106 return Graphics::CullMode::NONE;
111 constexpr Graphics::BlendFactor ConvertBlendFactor(BlendFactor::Type blendFactor)
115 case BlendFactor::ZERO:
116 return Graphics::BlendFactor::ZERO;
117 case BlendFactor::ONE:
118 return Graphics::BlendFactor::ONE;
119 case BlendFactor::SRC_COLOR:
120 return Graphics::BlendFactor::SRC_COLOR;
121 case BlendFactor::ONE_MINUS_SRC_COLOR:
122 return Graphics::BlendFactor::ONE_MINUS_SRC_COLOR;
123 case BlendFactor::SRC_ALPHA:
124 return Graphics::BlendFactor::SRC_ALPHA;
125 case BlendFactor::ONE_MINUS_SRC_ALPHA:
126 return Graphics::BlendFactor::ONE_MINUS_SRC_ALPHA;
127 case BlendFactor::DST_ALPHA:
128 return Graphics::BlendFactor::DST_ALPHA;
129 case BlendFactor::ONE_MINUS_DST_ALPHA:
130 return Graphics::BlendFactor::ONE_MINUS_DST_ALPHA;
131 case BlendFactor::DST_COLOR:
132 return Graphics::BlendFactor::DST_COLOR;
133 case BlendFactor::ONE_MINUS_DST_COLOR:
134 return Graphics::BlendFactor::ONE_MINUS_DST_COLOR;
135 case BlendFactor::SRC_ALPHA_SATURATE:
136 return Graphics::BlendFactor::SRC_ALPHA_SATURATE;
137 case BlendFactor::CONSTANT_COLOR:
138 return Graphics::BlendFactor::CONSTANT_COLOR;
139 case BlendFactor::ONE_MINUS_CONSTANT_COLOR:
140 return Graphics::BlendFactor::ONE_MINUS_CONSTANT_COLOR;
141 case BlendFactor::CONSTANT_ALPHA:
142 return Graphics::BlendFactor::CONSTANT_ALPHA;
143 case BlendFactor::ONE_MINUS_CONSTANT_ALPHA:
144 return Graphics::BlendFactor::ONE_MINUS_CONSTANT_ALPHA;
146 return Graphics::BlendFactor();
150 constexpr Graphics::BlendOp ConvertBlendEquation(DevelBlendEquation::Type blendEquation)
152 switch(blendEquation)
154 case DevelBlendEquation::ADD:
155 return Graphics::BlendOp::ADD;
156 case DevelBlendEquation::SUBTRACT:
157 return Graphics::BlendOp::SUBTRACT;
158 case DevelBlendEquation::REVERSE_SUBTRACT:
159 return Graphics::BlendOp::REVERSE_SUBTRACT;
160 case DevelBlendEquation::COLOR:
161 return Graphics::BlendOp::COLOR;
162 case DevelBlendEquation::COLOR_BURN:
163 return Graphics::BlendOp::COLOR_BURN;
164 case DevelBlendEquation::COLOR_DODGE:
165 return Graphics::BlendOp::COLOR_DODGE;
166 case DevelBlendEquation::DARKEN:
167 return Graphics::BlendOp::DARKEN;
168 case DevelBlendEquation::DIFFERENCE:
169 return Graphics::BlendOp::DIFFERENCE;
170 case DevelBlendEquation::EXCLUSION:
171 return Graphics::BlendOp::EXCLUSION;
172 case DevelBlendEquation::HARD_LIGHT:
173 return Graphics::BlendOp::HARD_LIGHT;
174 case DevelBlendEquation::HUE:
175 return Graphics::BlendOp::HUE;
176 case DevelBlendEquation::LIGHTEN:
177 return Graphics::BlendOp::LIGHTEN;
178 case DevelBlendEquation::LUMINOSITY:
179 return Graphics::BlendOp::LUMINOSITY;
180 case DevelBlendEquation::MAX:
181 return Graphics::BlendOp::MAX;
182 case DevelBlendEquation::MIN:
183 return Graphics::BlendOp::MIN;
184 case DevelBlendEquation::MULTIPLY:
185 return Graphics::BlendOp::MULTIPLY;
186 case DevelBlendEquation::OVERLAY:
187 return Graphics::BlendOp::OVERLAY;
188 case DevelBlendEquation::SATURATION:
189 return Graphics::BlendOp::SATURATION;
190 case DevelBlendEquation::SCREEN:
191 return Graphics::BlendOp::SCREEN;
192 case DevelBlendEquation::SOFT_LIGHT:
193 return Graphics::BlendOp::SOFT_LIGHT;
195 return Graphics::BlendOp{};
199 PipelineCacheL0Ptr PipelineCache::GetPipelineCacheL0(Program* program, Render::Geometry* geometry)
201 auto it = std::find_if(level0nodes.begin(), level0nodes.end(), [program, geometry](PipelineCacheL0& item) {
202 return ((item.program == program && item.geometry == geometry));
205 // Add new node to cache
206 if(it == level0nodes.end())
208 uint32_t bindingIndex{0u};
209 auto& reflection = graphicsController->GetProgramReflection(program->GetGraphicsProgram());
211 Graphics::VertexInputState vertexInputState{};
214 bool attrNotFound = false;
215 for(auto&& vertexBuffer : geometry->GetVertexBuffers())
217 const VertexBuffer::Format& vertexFormat = *vertexBuffer->GetFormat();
219 uint32_t divisor = vertexBuffer->GetDivisor();
220 Graphics::VertexInputRate vertexInputRate = (divisor == 0
221 ? Graphics::VertexInputRate::PER_VERTEX
222 : Graphics::VertexInputRate::PER_INSTANCE);
224 vertexInputState.bufferBindings.emplace_back(vertexFormat.size, // stride
226 //@todo Add the actual rate to the graphics struct
228 const uint32_t attributeCount = vertexBuffer->GetAttributeCount();
229 uint32_t lastBoundAttributeIndex = 0;
230 for(uint32_t i = 0; i < attributeCount; ++i)
232 auto attributeName = vertexBuffer->GetAttributeName(i);
233 int32_t pLocation = reflection.GetVertexAttributeLocation(std::string(attributeName.GetStringView()));
236 auto location = static_cast<uint32_t>(pLocation);
237 vertexInputState.attributes.emplace_back(location,
239 vertexFormat.components[i].offset,
240 GetPropertyVertexFormat(vertexFormat.components[i].type));
241 ++lastBoundAttributeIndex;
246 DALI_LOG_WARNING("Attribute not found in the shader: %s\n", attributeName.GetCString());
247 // Don't bind unused attributes.
250 base += lastBoundAttributeIndex;
253 PipelineCacheL0 level0;
254 level0.program = program;
255 level0.geometry = geometry;
256 level0.inputState = vertexInputState;
258 it = level0nodes.insert(level0nodes.end(), std::move(level0));
262 DALI_LOG_INFO(gLogFilter, Debug::General,
263 "!!!!!!! Attributes not found. !!!!!!!!\n"
264 "Shader src: VERT:\n%s\nFRAGMENT:\n%s\n",
265 program->GetShaderData()->GetVertexShader(),
266 program->GetShaderData()->GetFragmentShader());
273 PipelineCacheL1Ptr PipelineCacheL0::GetPipelineCacheL1(Render::Renderer* renderer, bool usingReflection)
275 // hash must be collision free
277 auto topo = (uint32_t(geometry->GetTopology()) & 0xffu);
278 auto cull = (uint32_t(renderer->GetFaceCullMode()) & 0xffu);
280 static const Graphics::PolygonMode polyTable[] = {
281 Graphics::PolygonMode::POINT,
282 Graphics::PolygonMode::LINE,
283 Graphics::PolygonMode::LINE,
284 Graphics::PolygonMode::LINE,
285 Graphics::PolygonMode::FILL,
286 Graphics::PolygonMode::FILL,
287 Graphics::PolygonMode::FILL};
289 auto poly = polyTable[topo];
291 static const FaceCullingMode::Type adjFaceCullingMode[4] =
293 FaceCullingMode::NONE,
294 FaceCullingMode::BACK,
295 FaceCullingMode::FRONT,
296 FaceCullingMode::FRONT_AND_BACK,
299 static const FaceCullingMode::Type normalFaceCullingMode[4] =
301 FaceCullingMode::NONE,
302 FaceCullingMode::FRONT,
303 FaceCullingMode::BACK,
304 FaceCullingMode::FRONT_AND_BACK,
307 static const FaceCullingMode::Type* cullModeTable[2] = {
308 normalFaceCullingMode,
311 // Retrieve cull mode
312 auto cullModeTableIndex = uint32_t(usingReflection) & 1u;
313 cull = cullModeTable[cullModeTableIndex][renderer->GetFaceCullMode()];
315 hash = (topo & 0xffu) | ((cull << 8u) & 0xff00u) | ((uint32_t(poly) << 16u) & 0xff0000u);
317 // If L1 not found by hash, create rasterization state describing pipeline and store it
318 auto it = std::find_if(level1nodes.begin(), level1nodes.end(), [hash](PipelineCacheL1& item) { return item.hashCode == hash; });
320 if(it == level1nodes.end())
322 PipelineCacheL1 item;
323 item.hashCode = hash;
324 item.rs.cullMode = ConvertCullFace(FaceCullingMode::Type(cull));
325 item.rs.frontFace = Graphics::FrontFace::COUNTER_CLOCKWISE;
326 item.rs.polygonMode = poly; // not in use
327 item.ia.topology = geometry->GetTopology();
329 it = level1nodes.insert(level1nodes.end(), std::move(item));
335 void PipelineCacheL0::ClearUnusedCache()
337 for(auto iter = level1nodes.begin(); iter != level1nodes.end();)
339 if(iter->ClearUnusedCache())
341 iter = level1nodes.erase(iter);
350 PipelineCacheL2Ptr PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions)
355 if(DALI_UNLIKELY(noBlends.empty()))
357 noBlends.emplace_back(PipelineCacheL2{});
360 auto& noBlend = *noBlends.begin();
362 if(noBlend.pipeline == nullptr)
364 // reset all before returning if pipeline has never been created for that case
366 memset(&noBlend.colorBlendState, 0, sizeof(Graphics::ColorBlendState));
368 return noBlends.begin();
371 auto bitmask = uint32_t(blendingOptions.GetBitmask());
373 // Find by bitmask (L2 entries must be sorted by bitmask)
374 auto it = std::find_if(level2nodes.begin(), level2nodes.end(), [bitmask](PipelineCacheL2& item) { return item.hash == bitmask; });
376 // TODO: find better way of blend constants lookup
377 PipelineCacheL2Ptr retval = level2nodes.end();
378 if(it != level2nodes.end())
380 bool hasBlendColor = blendingOptions.GetBlendColor();
381 while(hasBlendColor && it != level2nodes.end() && (*it).hash == bitmask)
383 Vector4 v(it->colorBlendState.blendConstants);
384 if(v == *blendingOptions.GetBlendColor())
396 if(retval == level2nodes.end())
398 // create new entry and return it with null pipeline
399 PipelineCacheL2 l2{};
400 l2.pipeline = nullptr;
401 auto& colorBlendState = l2.colorBlendState;
402 colorBlendState.SetBlendEnable(true);
403 Graphics::BlendOp rgbOp = ConvertBlendEquation(blendingOptions.GetBlendEquationRgb());
404 Graphics::BlendOp alphaOp = ConvertBlendEquation(blendingOptions.GetBlendEquationAlpha());
405 if(blendingOptions.IsAdvancedBlendEquationApplied() && premul)
409 DALI_LOG_ERROR("Advanced Blend Equation MUST be applied by using BlendEquation.\n");
415 .SetSrcColorBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendSrcFactorRgb()))
416 .SetSrcAlphaBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendSrcFactorAlpha()))
417 .SetDstColorBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendDestFactorRgb()))
418 .SetDstAlphaBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendDestFactorAlpha()))
419 .SetColorBlendOp(rgbOp)
420 .SetAlphaBlendOp(alphaOp);
422 // Blend color is optional and rarely used
423 auto* blendColor = const_cast<Vector4*>(blendingOptions.GetBlendColor());
426 colorBlendState.SetBlendConstants(blendColor->AsFloat());
429 l2.hash = blendingOptions.GetBitmask();
431 auto upperBound = std::upper_bound(level2nodes.begin(), level2nodes.end(), l2, [](const PipelineCacheL2& lhs, const PipelineCacheL2& rhs) { return lhs.hash < rhs.hash; });
433 level2nodes.insert(upperBound, std::move(l2));
435 // run same function to retrieve retval
436 retval = GetPipelineCacheL2(blend, premul, blendingOptions);
442 bool PipelineCacheL1::ClearUnusedCache()
444 for(auto iter = level2nodes.begin(); iter != level2nodes.end();)
446 if(iter->referenceCount == 0)
448 iter = level2nodes.erase(iter);
456 if(!noBlends.empty() && noBlends.begin()->referenceCount > 0)
461 return level2nodes.empty();
464 void PipelineCacheQueryInfo::GenerateHash()
466 // Lightweight hash value generation.
467 hash = (reinterpret_cast<std::size_t>(program) >> Dali::Log<sizeof(decltype(*program))>::value) ^
468 (reinterpret_cast<std::size_t>(geometry) >> Dali::Log<sizeof(decltype(*geometry))>::value) ^
469 ((blendingEnabled ? 1u : 0u) << 0u) ^
470 ((alphaPremultiplied ? 1u : 0u) << 1u) ^
471 (static_cast<std::size_t>(geometry->GetTopology()) << 2u) ^
472 (static_cast<std::size_t>(renderer->GetFaceCullMode()) << 5u) ^
473 ((cameraUsingReflection ? 1u : 0u) << 8u) ^
474 (blendingEnabled ? static_cast<std::size_t>(blendingOptions->GetBitmask()) : 0xDA11u);
477 bool PipelineCacheQueryInfo::Equal(const PipelineCacheQueryInfo& lhs, const PipelineCacheQueryInfo& rhs) noexcept
479 // Naive equal check.
480 const bool ret = (lhs.hash == rhs.hash) && // Check hash value first
481 (lhs.program == rhs.program) &&
482 (lhs.geometry == rhs.geometry) &&
483 (lhs.blendingEnabled == rhs.blendingEnabled) &&
484 (lhs.alphaPremultiplied == rhs.alphaPremultiplied) &&
485 (lhs.geometry->GetTopology() == rhs.geometry->GetTopology()) &&
486 (lhs.renderer->GetFaceCullMode() == rhs.renderer->GetFaceCullMode()) &&
487 (lhs.cameraUsingReflection == rhs.cameraUsingReflection) &&
488 (!lhs.blendingEnabled ||
489 (lhs.blendingOptions->GetBitmask() == rhs.blendingOptions->GetBitmask() &&
490 ((lhs.blendingOptions->GetBlendColor() == nullptr && rhs.blendingOptions->GetBlendColor() == nullptr) ||
491 (lhs.blendingOptions->GetBlendColor() &&
492 rhs.blendingOptions->GetBlendColor() &&
493 (*lhs.blendingOptions->GetBlendColor() == *rhs.blendingOptions->GetBlendColor())))));
498 PipelineCache::PipelineCache(Graphics::Controller& controller)
499 : graphicsController(&controller)
501 // Clean up cache first
502 CleanLatestUsedCache();
505 PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound)
507 // Seperate branch whether query use blending or not.
508 const int latestUsedCacheIndex = queryInfo.blendingEnabled ? 0 : 1;
510 // If we can reuse latest bound pipeline, Fast return.
511 if(ReuseLatestBoundPipeline(latestUsedCacheIndex, queryInfo))
513 mLatestResult[latestUsedCacheIndex].level2->referenceCount++;
514 return mLatestResult[latestUsedCacheIndex];
517 auto level0 = GetPipelineCacheL0(queryInfo.program, queryInfo.geometry);
518 auto level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
520 PipelineCachePtr level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
522 // Create new pipeline at level2 if requested
523 if(level2->pipeline == nullptr && createNewIfNotFound)
525 Graphics::ProgramState programState{};
526 programState.program = &queryInfo.program->GetGraphicsProgram();
527 // Create the pipeline
528 Graphics::PipelineCreateInfo createInfo;
530 .SetInputAssemblyState(&level1->ia)
531 .SetVertexInputState(&level0->inputState)
532 .SetRasterizationState(&level1->rs)
533 .SetColorBlendState(&level2->colorBlendState)
534 .SetProgramState(&programState);
536 // Store a pipeline per renderer per render (renderer can be owned by multiple nodes,
537 // and re-drawn in multiple instructions).
538 level2->pipeline = graphicsController->CreatePipeline(createInfo, nullptr);
541 PipelineResult result{};
543 result.pipeline = level2->pipeline.get();
544 result.level2 = level2;
546 level2->referenceCount++;
548 // Copy query and result
549 mLatestQuery[latestUsedCacheIndex] = queryInfo;
550 mLatestResult[latestUsedCacheIndex] = result;
555 bool PipelineCache::ReuseLatestBoundPipeline(const int latestUsedCacheIndex, const PipelineCacheQueryInfo& queryInfo) const
557 return mLatestResult[latestUsedCacheIndex].pipeline != nullptr && PipelineCacheQueryInfo::Equal(queryInfo, mLatestQuery[latestUsedCacheIndex]);
560 void PipelineCache::PreRender()
562 CleanLatestUsedCache();
564 // We don't need to check this every frame
565 if(++mFrameCount >= CACHE_CLEAN_FRAME_COUNT)
572 void PipelineCache::ClearUnusedCache()
574 for(auto iter = level0nodes.begin(); iter != level0nodes.end();)
576 iter->ClearUnusedCache();
578 if(iter->level1nodes.empty())
580 iter = level0nodes.erase(iter);
589 void PipelineCache::ResetPipeline(PipelineCachePtr pipelineCache)
591 // TODO : Can we always assume that pipelineCache input is valid iterator?
592 pipelineCache->referenceCount--;
595 } // namespace Dali::Internal::Render