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 "gles-graphics-pipeline-cache.h"
20 #include "egl-graphics-controller.h"
21 #include "gles-graphics-pipeline.h"
22 #include "gles-graphics-program.h"
26 constexpr uint32_t CACHE_CLEAN_FLUSH_COUNT = 3600u; // 60fps * 60sec / ~3 flushes per frame
29 namespace Dali::Graphics::GLES
32 * @brief custom delete function for cached object
35 struct CachedObjectDeleter
37 CachedObjectDeleter() = default;
39 void operator()(T* object)
41 // Discard resource (add it to discard queue)
42 object->DiscardResource();
47 struct CachedObjectDeleter<GLES::Program>
49 CachedObjectDeleter() = default;
51 void operator()(GLES::Program* object)
53 // Program deleter should skip discard queue if controller shutting down
54 if(!object->GetController().IsShuttingDown())
56 object->DiscardResource();
60 // delete object otherwise
67 * @brief The order of states being stored in the cache and mask
69 enum class StateLookupIndex : uint32_t
71 COLOR_BLEND_STATE_BIT = 0,
72 VIEWPORT_STATE_BIT = 1,
73 BASE_PIPELINE_STATE_BIT = 2,
74 DEPTH_STENCIL_STATE_BIT = 3,
75 RASTERIZATION_STATE_BIT = 4,
76 VERTEX_INPUT_STATE_BIT = 5,
77 INPUT_ASSEMBLY_STATE_BIT = 6,
82 * Helper float compare function
84 static bool cmpf(float A, float B, float epsilon = 0.005f)
86 return (fabs(A - B) < epsilon);
92 static bool operator==(const Graphics::Viewport& lhs, const Graphics::Viewport& rhs)
94 return cmpf(lhs.x, rhs.x) &&
96 cmpf(lhs.width, rhs.width) &&
97 cmpf(lhs.height, rhs.height) &&
98 cmpf(lhs.minDepth, rhs.minDepth) &&
99 cmpf(lhs.maxDepth, rhs.maxDepth);
102 static bool operator==(const Graphics::Rect2D& lhs, const Graphics::Rect2D& rhs)
104 return cmpf(lhs.x, rhs.x) &&
105 cmpf(lhs.y, rhs.y) &&
106 cmpf(lhs.width, rhs.width) &&
107 cmpf(lhs.height, rhs.height);
110 static bool operator==(const Graphics::StencilOpState& lhs, const Graphics::StencilOpState& rhs)
112 return lhs.failOp == rhs.failOp &&
113 lhs.passOp == rhs.passOp &&
114 lhs.depthFailOp == rhs.depthFailOp &&
115 lhs.compareOp == rhs.compareOp &&
116 lhs.compareMask == rhs.compareMask &&
117 lhs.writeMask == rhs.writeMask &&
118 lhs.reference == rhs.reference;
122 operator==(const Dali::Graphics::VertexInputState::Attribute& lhs,
123 const Dali::Graphics::VertexInputState::Attribute& rhs)
125 return lhs.location == rhs.location &&
126 lhs.binding == rhs.binding &&
127 lhs.offset == rhs.offset &&
128 lhs.format == rhs.format;
132 operator==(const Dali::Graphics::VertexInputState::Binding& lhs, const Dali::Graphics::VertexInputState::Binding& rhs)
134 return lhs.stride == rhs.stride &&
135 lhs.inputRate == rhs.inputRate;
138 using PipelineStateCompateFunctionType = bool(const Graphics::PipelineCreateInfo*,
139 const Graphics::PipelineCreateInfo*);
141 static std::vector<PipelineStateCompateFunctionType*>& GetStateCompareFuncTable()
143 static std::vector<PipelineStateCompateFunctionType*> stateCompareFuncTable{};
144 return stateCompareFuncTable;
148 * @brief Initialises compare function lookup table
150 void InitialiseStateCompareLookupTable()
152 GetStateCompareFuncTable().clear();
153 GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // colorBlendState
155 const auto& lcb = *lhs->colorBlendState;
156 const auto& rcb = *rhs->colorBlendState;
157 return lcb.logicOpEnable == rcb.logicOpEnable &&
158 lcb.logicOp == rcb.logicOp &&
159 cmpf(lcb.blendConstants[0], rcb.blendConstants[0]) &&
160 cmpf(lcb.blendConstants[1], rcb.blendConstants[1]) &&
161 cmpf(lcb.blendConstants[2], rcb.blendConstants[2]) &&
162 cmpf(lcb.blendConstants[3], rcb.blendConstants[3]) &&
163 lcb.blendEnable == rcb.blendEnable &&
164 lcb.srcColorBlendFactor == rcb.srcColorBlendFactor &&
165 lcb.dstColorBlendFactor == rcb.dstColorBlendFactor &&
166 lcb.colorBlendOp == rcb.colorBlendOp &&
167 lcb.srcAlphaBlendFactor == rcb.srcAlphaBlendFactor &&
168 lcb.dstAlphaBlendFactor == rcb.dstAlphaBlendFactor &&
169 lcb.alphaBlendOp == rcb.alphaBlendOp &&
170 lcb.colorComponentWriteBits == rcb.colorComponentWriteBits;
172 GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // viewport state
174 const auto& lvp = *lhs->viewportState;
175 const auto& rvp = *rhs->viewportState;
176 return lvp.viewport == rvp.viewport &&
177 lvp.scissor == rvp.scissor &&
178 lvp.scissorTestEnable == rvp.scissorTestEnable;
180 GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // basePipeline
182 return lhs->basePipeline == rhs->basePipeline;
184 GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // depthStencilState
186 const auto& lds = *lhs->depthStencilState;
187 const auto& rds = *rhs->depthStencilState;
188 return lds.depthTestEnable == rds.depthTestEnable &&
189 lds.depthWriteEnable == rds.depthWriteEnable &&
190 lds.depthCompareOp == rds.depthCompareOp &&
191 lds.stencilTestEnable == rds.stencilTestEnable &&
192 lds.front == rds.front &&
193 lds.back == rds.back;
195 GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // rasterizationState
197 const auto& lrs = *lhs->rasterizationState;
198 const auto& rrs = *rhs->rasterizationState;
199 return lrs.cullMode == rrs.cullMode &&
200 lrs.polygonMode == rrs.polygonMode &&
201 lrs.frontFace == rrs.frontFace;
203 GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // vertexInputState
205 const auto& lvi = *lhs->vertexInputState;
206 const auto& rvi = *rhs->vertexInputState;
207 return lvi.bufferBindings.size() == rvi.bufferBindings.size() &&
208 lvi.attributes.size() == rvi.attributes.size() &&
209 std::equal(lvi.bufferBindings.begin(), lvi.bufferBindings.end(), rvi.bufferBindings.begin(), [](const auto& lhs, const auto& rhs) {
210 return operator==(lhs, rhs);
212 std::equal(lvi.attributes.begin(), lvi.attributes.end(), rvi.attributes.begin(), [](const auto& lhs, const auto& rhs) {
213 return operator==(lhs, rhs);
216 GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // inputAssemblyState
218 const auto& lia = *lhs->inputAssemblyState;
219 const auto& ria = *rhs->inputAssemblyState;
220 return lia.topology == ria.topology &&
221 lia.primitiveRestartEnable == ria.primitiveRestartEnable;
226 * @brief Helper function calculating the bitmask of set states
228 * @param[in] info Valid PipelineCreateInfo structure
229 * @return bitmask of set states
231 inline uint32_t GetStateBitmask(const PipelineCreateInfo& info)
234 mask |= bool(info.colorBlendState) << int(StateLookupIndex::COLOR_BLEND_STATE_BIT);
235 mask |= bool(info.viewportState) << int(StateLookupIndex::VIEWPORT_STATE_BIT);
236 mask |= bool(info.basePipeline) << int(StateLookupIndex::BASE_PIPELINE_STATE_BIT);
237 mask |= bool(info.depthStencilState) << int(StateLookupIndex::DEPTH_STENCIL_STATE_BIT);
238 mask |= bool(info.rasterizationState) << int(StateLookupIndex::RASTERIZATION_STATE_BIT);
239 mask |= bool(info.vertexInputState) << int(StateLookupIndex::VERTEX_INPUT_STATE_BIT);
240 mask |= bool(info.inputAssemblyState) << int(StateLookupIndex::INPUT_ASSEMBLY_STATE_BIT);
245 * @brief Implementation of cache
247 struct PipelineCache::Impl
252 explicit Impl(EglGraphicsController& _controller)
253 : controller(_controller),
254 pipelineEntriesFlushRequired(false),
255 programEntriesFlushRequired(false),
256 shaderEntriesFlushRequired(false)
258 // Initialise lookup table
259 InitialiseStateCompareLookupTable();
267 // First destroy pipelines
271 programEntries.clear();
275 * @brief Structure describes a single cache entry
279 CacheEntry() = default;
281 CacheEntry(UniquePtr<PipelineImpl>&& _pipeline, uint32_t _bitmask)
282 : pipeline(std::move(_pipeline)),
283 stateBitmask(_bitmask)
287 ~CacheEntry() = default;
289 CacheEntry(CacheEntry&&) = default;
290 CacheEntry& operator=(CacheEntry&&) = default;
292 UniquePtr<PipelineImpl> pipeline{nullptr};
293 uint32_t stateBitmask{0u};
297 * @brief Sorted array of shaders used to create program
299 struct ProgramCacheEntry
301 // sorted array of shaders
302 std::vector<const Graphics::Shader*> shaders;
303 UniquePtr<ProgramImpl> program{nullptr};
306 struct ShaderCacheEntry
308 UniquePtr<ShaderImpl> shaderImpl{nullptr};
311 EglGraphicsController& controller;
312 std::vector<CacheEntry> entries;
313 std::vector<ProgramCacheEntry> programEntries;
314 std::vector<ShaderCacheEntry> shaderEntries;
316 bool pipelineEntriesFlushRequired : 1;
317 bool programEntriesFlushRequired : 1;
318 bool shaderEntriesFlushRequired : 1;
321 PipelineCache::PipelineCache(EglGraphicsController& controller)
323 mImpl = std::make_unique<Impl>(controller);
326 PipelineCache::~PipelineCache() = default;
328 PipelineImpl* PipelineCache::FindPipelineImpl(const PipelineCreateInfo& info)
330 auto bitmask = GetStateBitmask(info);
332 for(auto& entry : mImpl->entries)
334 auto& pipeline = entry.pipeline;
335 auto& cacheInfo = pipeline->GetCreateInfo();
336 if(!info.programState)
341 // Check whether the program is the same
342 if(info.programState->program)
344 const auto& lhsProgram = *static_cast<const GLES::Program*>(info.programState->program);
345 const auto& rhsProgram = *static_cast<const GLES::Program*>(cacheInfo.programState->program);
346 if(lhsProgram != rhsProgram)
351 // Test whether set states bitmask matches
352 if(entry.stateBitmask != bitmask)
357 // Now test only for states that are set
359 for(i = 0; i < int(StateLookupIndex::MAX_STATE); ++i)
361 // Test only set states
362 if((entry.stateBitmask & (1 << i)))
364 if(!(GetStateCompareFuncTable()[i](&info, &cacheInfo)))
371 // TODO: For now ignoring dynamic state mask and allocator
372 // Getting as far as here, we have found our pipeline impl
373 if(i == int(StateLookupIndex::MAX_STATE))
375 return pipeline.get();
382 ProgramImpl* PipelineCache::FindProgramImpl(const ProgramCreateInfo& info)
384 if(mImpl->programEntries.empty())
389 // assert if no shaders given
390 std::vector<const Graphics::Shader*> shaders;
391 shaders.reserve(info.shaderState->size());
393 for(auto& state : *info.shaderState)
395 shaders.push_back(state.shader);
399 std::sort(shaders.begin(), shaders.end());
401 for(auto& item : mImpl->programEntries)
403 if(item.shaders.size() != shaders.size())
408 int k = shaders.size();
409 while(--k >= 0 && item.shaders[k] == shaders[k])
414 return item.program.get();
420 Graphics::UniquePtr<Graphics::Pipeline> PipelineCache::GetPipeline(const PipelineCreateInfo& pipelineCreateInfo,
421 Graphics::UniquePtr<Graphics::Pipeline>&& oldPipeline)
423 auto cachedPipeline = FindPipelineImpl(pipelineCreateInfo);
425 // Return same pointer if nothing changed
426 if(oldPipeline && *static_cast<GLES::Pipeline*>(oldPipeline.get()) == cachedPipeline)
428 return std::move(oldPipeline);
433 // create new pipeline
434 auto pipeline = MakeUnique<GLES::PipelineImpl>(pipelineCreateInfo, mImpl->controller, *this);
436 cachedPipeline = pipeline.get();
439 mImpl->entries.emplace_back(std::move(pipeline), GetStateBitmask(pipelineCreateInfo));
442 auto wrapper = MakeUnique<GLES::Pipeline, CachedObjectDeleter<GLES::Pipeline>>(*cachedPipeline);
443 return std::move(wrapper);
446 Graphics::UniquePtr<Graphics::Program> PipelineCache::GetProgram(const ProgramCreateInfo& programCreateInfo,
447 Graphics::UniquePtr<Graphics::Program>&& oldProgram)
449 ProgramImpl* cachedProgram = FindProgramImpl(programCreateInfo);
451 // Return same pointer if nothing changed
452 if(oldProgram && *static_cast<GLES::Program*>(oldProgram.get()) == cachedProgram)
454 return std::move(oldProgram);
459 // create new pipeline
460 auto program = MakeUnique<GLES::ProgramImpl>(programCreateInfo, mImpl->controller);
462 program->Create(); // Don't currently handle failure.
464 cachedProgram = program.get();
467 mImpl->programEntries.emplace_back();
468 auto& item = mImpl->programEntries.back();
469 item.program = std::move(program);
470 for(auto& state : *programCreateInfo.shaderState)
472 item.shaders.push_back(state.shader);
475 std::sort(item.shaders.begin(), item.shaders.end());
478 auto wrapper = MakeUnique<GLES::Program, CachedObjectDeleter<GLES::Program>>(cachedProgram);
479 return std::move(wrapper);
482 ShaderImpl* PipelineCache::FindShaderImpl(const ShaderCreateInfo& shaderCreateInfo)
484 if(!mImpl->shaderEntries.empty())
486 for(auto& item : mImpl->shaderEntries)
488 auto& itemInfo = item.shaderImpl->GetCreateInfo();
489 if(itemInfo.pipelineStage != shaderCreateInfo.pipelineStage ||
490 itemInfo.shaderlanguage != shaderCreateInfo.shaderlanguage ||
491 itemInfo.sourceMode != shaderCreateInfo.sourceMode ||
492 itemInfo.sourceSize != shaderCreateInfo.sourceSize)
497 if(memcmp(itemInfo.sourceData, shaderCreateInfo.sourceData, itemInfo.sourceSize) == 0)
499 return item.shaderImpl.get();
506 Graphics::UniquePtr<Graphics::Shader> PipelineCache::GetShader(const ShaderCreateInfo& shaderCreateInfo,
507 Graphics::UniquePtr<Graphics::Shader>&& oldShader)
509 ShaderImpl* cachedShader = FindShaderImpl(shaderCreateInfo);
511 // Return same pointer if nothing changed
512 if(oldShader && *static_cast<GLES::Shader*>(oldShader.get()) == cachedShader)
514 return std::move(oldShader);
519 auto shader = MakeUnique<GLES::ShaderImpl>(shaderCreateInfo, mImpl->controller);
520 cachedShader = shader.get();
522 mImpl->shaderEntries.emplace_back();
523 mImpl->shaderEntries.back().shaderImpl = std::move(shader);
525 auto wrapper = MakeUnique<GLES::Shader, CachedObjectDeleter<GLES::Shader>>(cachedShader);
526 return std::move(wrapper);
529 void PipelineCache::FlushCache()
531 if(mImpl->pipelineEntriesFlushRequired)
533 decltype(mImpl->entries) newEntries;
534 newEntries.reserve(mImpl->entries.size());
536 for(auto& entry : mImpl->entries)
538 // Move items which are still in use into the new array
539 if(entry.pipeline->GetRefCount() != 0)
541 newEntries.emplace_back(std::move(entry));
545 // Move temporary array in place of stored cache
546 // Unused pipelines will be deleted automatically
547 mImpl->entries = std::move(newEntries);
549 mImpl->pipelineEntriesFlushRequired = false;
552 if(mImpl->programEntriesFlushRequired)
554 // Program cache require similar action.
555 decltype(mImpl->programEntries) newProgramEntries;
556 newProgramEntries.reserve(mImpl->programEntries.size());
558 for(auto& entry : mImpl->programEntries)
560 // Move items which are still in use into the new array
561 if(entry.program->GetRefCount() != 0)
563 newProgramEntries.emplace_back(std::move(entry));
567 mImpl->programEntries = std::move(newProgramEntries);
569 mImpl->programEntriesFlushRequired = false;
572 if(mImpl->shaderEntriesFlushRequired)
574 // There is at least 1 unused shader
575 mImpl->shaderEntriesFlushRequired = false;
576 bool deleteRequired{false};
577 for(auto& entry : mImpl->shaderEntries)
579 if(entry.shaderImpl->GetRefCount() == 0)
581 mImpl->shaderEntriesFlushRequired = true;
582 auto frameCount = entry.shaderImpl->IncreaseFlushCount();
583 if(frameCount > CACHE_CLEAN_FLUSH_COUNT)
585 deleteRequired = true;
591 decltype(mImpl->shaderEntries) newShaderEntries;
592 newShaderEntries.reserve(mImpl->shaderEntries.size());
593 for(auto& entry : mImpl->shaderEntries)
595 if(entry.shaderImpl->GetRefCount() > 0 ||
596 entry.shaderImpl->GetFlushCount() <= CACHE_CLEAN_FLUSH_COUNT)
598 newShaderEntries.emplace_back(std::move(entry));
601 mImpl->shaderEntries = std::move(newShaderEntries);
606 void PipelineCache::MarkPipelineCacheFlushRequired()
608 mImpl->pipelineEntriesFlushRequired = true;
611 void PipelineCache::MarkProgramCacheFlushRequired()
613 mImpl->programEntriesFlushRequired = true;
616 void PipelineCache::MarkShaderCacheFlushRequired()
618 mImpl->shaderEntriesFlushRequired = true;
621 } // namespace Dali::Graphics::GLES