2 * Copyright (c) 2021 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"
24 namespace Dali::Graphics::GLES
27 * @brief custom delete function for cached object
30 struct CachedObjectDeleter
32 CachedObjectDeleter() = default;
34 void operator()(T* object)
36 // Discard resource (add it to discard queue)
37 object->DiscardResource();
42 struct CachedObjectDeleter<GLES::Program>
44 CachedObjectDeleter() = default;
46 void operator()(GLES::Program* object)
48 // Program deleter should skip discard queue if controller shutting down
49 if(!object->GetController().IsShuttingDown())
51 object->DiscardResource();
55 // delete object otherwise
62 * @brief The order of states being stored in the cache and mask
64 enum class StateLookupIndex : uint32_t
66 COLOR_BLEND_STATE_BIT = 0,
67 VIEWPORT_STATE_BIT = 1,
68 FRAMEBUFFER_STATE_BIT = 2,
69 BASE_PIPELINE_STATE_BIT = 3,
70 DEPTH_STENCIL_STATE_BIT = 4,
71 RASTERIZATION_STATE_BIT = 5,
72 VERTEX_INPUT_STATE_BIT = 6,
73 INPUT_ASSEMBLY_STATE_BIT = 7,
78 * Helper float compare function
80 static bool cmpf(float A, float B, float epsilon = 0.005f)
82 return (fabs(A - B) < epsilon);
88 static bool operator==(const Graphics::Viewport& lhs, const Graphics::Viewport& rhs)
90 return cmpf(lhs.x, rhs.x) &&
92 cmpf(lhs.width, rhs.width) &&
93 cmpf(lhs.height, rhs.height) &&
94 cmpf(lhs.minDepth, rhs.minDepth) &&
95 cmpf(lhs.maxDepth, rhs.maxDepth);
98 static bool operator==(const Graphics::Rect2D& lhs, const Graphics::Rect2D& rhs)
100 return cmpf(lhs.x, rhs.x) &&
101 cmpf(lhs.y, rhs.y) &&
102 cmpf(lhs.width, rhs.width) &&
103 cmpf(lhs.height, rhs.height);
106 static bool operator==(const Graphics::StencilOpState& lhs, const Graphics::StencilOpState& rhs)
108 return lhs.failOp == rhs.failOp &&
109 lhs.passOp == rhs.passOp &&
110 lhs.depthFailOp == rhs.depthFailOp &&
111 lhs.compareOp == rhs.compareOp &&
112 lhs.compareMask == rhs.compareMask &&
113 lhs.writeMask == rhs.writeMask &&
114 lhs.reference == rhs.reference;
118 operator==(const Dali::Graphics::VertexInputState::Attribute& lhs,
119 const Dali::Graphics::VertexInputState::Attribute& rhs)
121 return lhs.location == rhs.location &&
122 lhs.binding == rhs.binding &&
123 lhs.offset == rhs.offset &&
124 lhs.format == rhs.format;
128 operator==(const Dali::Graphics::VertexInputState::Binding& lhs, const Dali::Graphics::VertexInputState::Binding& rhs)
130 return lhs.stride == rhs.stride &&
131 lhs.inputRate == rhs.inputRate;
134 using PipelineStateCompateFunctionType = bool(const Graphics::PipelineCreateInfo*,
135 const Graphics::PipelineCreateInfo*);
137 static std::vector<PipelineStateCompateFunctionType*> STATE_COMPARE_FUNC_TABLE{};
140 * @brief Initialises compare function lookup table
142 void InitialiseStateCompareLookupTable()
144 STATE_COMPARE_FUNC_TABLE = {
145 [](const auto* lhs, const auto* rhs) -> bool // colorBlendState
147 const auto& lcb = *lhs->colorBlendState;
148 const auto& rcb = *rhs->colorBlendState;
149 return lcb.logicOpEnable == rcb.logicOpEnable &&
150 lcb.logicOp == rcb.logicOp &&
151 cmpf(lcb.blendConstants[0], rcb.blendConstants[0]) &&
152 cmpf(lcb.blendConstants[1], rcb.blendConstants[1]) &&
153 cmpf(lcb.blendConstants[2], rcb.blendConstants[2]) &&
154 cmpf(lcb.blendConstants[3], rcb.blendConstants[3]) &&
155 lcb.blendEnable == rcb.blendEnable &&
156 lcb.srcColorBlendFactor == rcb.srcColorBlendFactor &&
157 lcb.dstColorBlendFactor == rcb.dstColorBlendFactor &&
158 lcb.colorBlendOp == rcb.colorBlendOp &&
159 lcb.srcAlphaBlendFactor == rcb.srcAlphaBlendFactor &&
160 lcb.dstAlphaBlendFactor == rcb.dstAlphaBlendFactor &&
161 lcb.alphaBlendOp == rcb.alphaBlendOp &&
162 lcb.colorComponentWriteBits == rcb.colorComponentWriteBits;
164 [](const auto* lhs, const auto* rhs) -> bool // viewport state
166 const auto& lvp = *lhs->viewportState;
167 const auto& rvp = *rhs->viewportState;
168 return lvp.viewport == rvp.viewport &&
169 lvp.scissor == rvp.scissor &&
170 lvp.scissorTestEnable == rvp.scissorTestEnable;
172 [](const auto* lhs, const auto* rhs) -> bool // framebufferState
174 const auto& lfb = *lhs->framebufferState;
175 const auto& rfb = *rhs->framebufferState;
176 return lfb.framebuffer == rfb.framebuffer;
178 [](const auto* lhs, const auto* rhs) -> bool // basePipeline
180 return lhs->basePipeline == rhs->basePipeline;
182 [](const auto* lhs, const auto* rhs) -> bool // depthStencilState
184 const auto& lds = *lhs->depthStencilState;
185 const auto& rds = *rhs->depthStencilState;
186 return lds.depthTestEnable == rds.depthTestEnable &&
187 lds.depthWriteEnable == rds.depthWriteEnable &&
188 lds.depthCompareOp == rds.depthCompareOp &&
189 lds.stencilTestEnable == rds.stencilTestEnable &&
190 lds.front == rds.front &&
191 lds.back == rds.back;
193 [](const auto* lhs, const auto* rhs) -> bool // rasterizationState
195 const auto& lrs = *lhs->rasterizationState;
196 const auto& rrs = *rhs->rasterizationState;
197 return lrs.cullMode == rrs.cullMode &&
198 lrs.polygonMode == rrs.polygonMode &&
199 lrs.frontFace == rrs.frontFace;
201 [](const auto* lhs, const auto* rhs) -> bool // vertexInputState
203 const auto& lvi = *lhs->vertexInputState;
204 const auto& rvi = *rhs->vertexInputState;
205 return lvi.bufferBindings.size() == rvi.bufferBindings.size() &&
206 lvi.attributes.size() == rvi.attributes.size() &&
207 std::equal(lvi.bufferBindings.begin(), lvi.bufferBindings.end(), rvi.bufferBindings.begin(), [](const auto& lhs, const auto& rhs) {
208 return operator==(lhs, rhs);
210 std::equal(lvi.attributes.begin(), lvi.attributes.end(), rvi.attributes.begin(), [](const auto& lhs, const auto& rhs) {
211 return operator==(lhs, rhs);
214 [](const auto* lhs, const auto* rhs) -> bool // inputAssemblyState
216 const auto& lia = *lhs->inputAssemblyState;
217 const auto& ria = *rhs->inputAssemblyState;
218 return lia.topology == ria.topology &&
219 lia.primitiveRestartEnable == ria.primitiveRestartEnable;
225 * @brief Helper function calculating the bitmask of set states
227 * @param[in] info Valid PipelineCreateInfo structure
228 * @return bitmask of set states
230 inline uint32_t GetStateBitmask(const PipelineCreateInfo& info)
233 mask |= bool(info.colorBlendState) << int(StateLookupIndex::COLOR_BLEND_STATE_BIT);
234 mask |= bool(info.viewportState) << int(StateLookupIndex::VIEWPORT_STATE_BIT);
235 mask |= bool(info.framebufferState) << int(StateLookupIndex::FRAMEBUFFER_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)
255 // Initialise lookup table
256 InitialiseStateCompareLookupTable();
264 // First destroy pipelines
268 programEntries.clear();
272 * @brief Structure describes a single cache entry
276 CacheEntry() = default;
278 CacheEntry(UniquePtr<PipelineImpl>&& _pipeline, uint32_t _bitmask)
279 : pipeline(std::move(_pipeline)),
280 stateBitmask(_bitmask)
284 ~CacheEntry() = default;
286 CacheEntry(CacheEntry&&) = default;
287 CacheEntry& operator=(CacheEntry&&) = default;
289 UniquePtr<PipelineImpl> pipeline{nullptr};
290 uint32_t stateBitmask{0u};
293 EglGraphicsController& controller;
294 std::vector<CacheEntry> entries;
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 std::vector<ProgramCacheEntry> programEntries;
309 PipelineCache::PipelineCache(EglGraphicsController& controller)
311 mImpl = std::make_unique<Impl>(controller);
314 PipelineCache::~PipelineCache() = default;
316 PipelineImpl* PipelineCache::FindPipelineImpl(const PipelineCreateInfo& info)
318 auto bitmask = GetStateBitmask(info);
320 for(auto& entry : mImpl->entries)
322 auto& pipeline = entry.pipeline;
323 auto& cacheInfo = pipeline->GetCreateInfo();
324 if(!info.programState)
329 // Check whether the program is the same
330 if(info.programState->program)
332 const auto& lhsProgram = *static_cast<const GLES::Program*>(info.programState->program);
333 const auto& rhsProgram = *static_cast<const GLES::Program*>(cacheInfo.programState->program);
334 if(lhsProgram != rhsProgram)
339 // Test whether set states bitmask matches
340 if(entry.stateBitmask != bitmask)
345 // Now test only for states that are set
347 for(i = 0; i < int(StateLookupIndex::MAX_STATE); ++i)
349 // Test only set states
350 if((entry.stateBitmask & (1 << i)))
352 if(!STATE_COMPARE_FUNC_TABLE[i](&info, &cacheInfo))
359 // TODO: For now ignoring dynamic state mask and allocator
360 // Getting as far as here, we have found our pipeline impl
361 if(i == int(StateLookupIndex::MAX_STATE))
363 return pipeline.get();
370 ProgramImpl* PipelineCache::FindProgramImpl(const ProgramCreateInfo& info)
372 if(mImpl->programEntries.empty())
377 // assert if no shaders given
378 std::vector<const Graphics::Shader*> shaders;
379 shaders.reserve(info.shaderState->size());
381 for(auto& state : *info.shaderState)
383 shaders.push_back(state.shader);
387 std::sort(shaders.begin(), shaders.end());
389 for(auto& item : mImpl->programEntries)
391 if(item.shaders.size() != shaders.size())
396 int k = shaders.size();
397 while(--k >= 0 && item.shaders[k] == shaders[k])
402 return item.program.get();
408 Graphics::UniquePtr<Graphics::Pipeline> PipelineCache::GetPipeline(const PipelineCreateInfo& pipelineCreateInfo,
409 Graphics::UniquePtr<Graphics::Pipeline>&& oldPipeline)
411 auto cachedPipeline = FindPipelineImpl(pipelineCreateInfo);
413 // Return same pointer if nothing changed
414 if(oldPipeline && *static_cast<GLES::Pipeline*>(oldPipeline.get()) == cachedPipeline)
416 return std::move(oldPipeline);
421 // create new pipeline
422 auto pipeline = MakeUnique<GLES::PipelineImpl>(pipelineCreateInfo, mImpl->controller, *this);
424 cachedPipeline = pipeline.get();
427 mImpl->entries.emplace_back(std::move(pipeline), GetStateBitmask(pipelineCreateInfo));
430 auto wrapper = MakeUnique<GLES::Pipeline, CachedObjectDeleter<GLES::Pipeline>>(*cachedPipeline);
431 return std::move(wrapper);
434 Graphics::UniquePtr<Graphics::Program> PipelineCache::GetProgram(const ProgramCreateInfo& programCreateInfo,
435 Graphics::UniquePtr<Graphics::Program>&& oldProgram)
437 ProgramImpl* cachedProgram = FindProgramImpl(programCreateInfo);
439 // Return same pointer if nothing changed
440 if(oldProgram && *static_cast<GLES::Program*>(oldProgram.get()) == cachedProgram)
442 return std::move(oldProgram);
447 // create new pipeline
448 auto program = MakeUnique<GLES::ProgramImpl>(programCreateInfo, mImpl->controller);
452 cachedProgram = program.get();
455 mImpl->programEntries.emplace_back();
456 auto& item = mImpl->programEntries.back();
457 item.program = std::move(program);
458 for(auto& state : *programCreateInfo.shaderState)
460 item.shaders.push_back(state.shader);
463 std::sort(item.shaders.begin(), item.shaders.end());
466 auto wrapper = MakeUnique<GLES::Program, CachedObjectDeleter<GLES::Program>>(cachedProgram);
467 return std::move(wrapper);
470 void PipelineCache::FlushCache()
472 decltype(mImpl->entries) newEntries;
473 newEntries.reserve(mImpl->entries.size());
475 for(auto& entry : mImpl->entries)
477 // Move items which are still in use into the new array
478 if(entry.pipeline->GetRefCount() != 0)
480 newEntries.emplace_back(std::move(entry));
484 // Move temporary array in place of stored cache
485 // Unused pipelines will be deleted automatically
486 mImpl->entries = std::move(newEntries);
488 // TODO: program cache may require similar action. However,
489 // since there is always one wrapper for Program object
490 // kept in the pipeline, then death of pipeline will result
491 // killing the program (if program isn't in use anymore)
494 } // namespace Dali::Graphics::GLES