Merge "Added shader support to pipeline cache" into devel/master
[platform/core/uifw/dali-adaptor.git] / dali / internal / graphics / gles-impl / gles-graphics-pipeline-cache.cpp
1 /*
2  * Copyright (c) 2023 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 #include "gles-graphics-pipeline-cache.h"
19 #include <algorithm>
20 #include "egl-graphics-controller.h"
21 #include "gles-graphics-pipeline.h"
22 #include "gles-graphics-program.h"
23
24 namespace
25 {
26 constexpr uint32_t CACHE_CLEAN_FLUSH_COUNT = 3600u; // 60fps * 60sec / ~3 flushes per frame
27 }
28
29 namespace Dali::Graphics::GLES
30 {
31 /**
32  * @brief custom delete function for cached object
33  */
34 template<class T>
35 struct CachedObjectDeleter
36 {
37   CachedObjectDeleter() = default;
38
39   void operator()(T* object)
40   {
41     // Discard resource (add it to discard queue)
42     object->DiscardResource();
43   }
44 };
45
46 template<>
47 struct CachedObjectDeleter<GLES::Program>
48 {
49   CachedObjectDeleter() = default;
50
51   void operator()(GLES::Program* object)
52   {
53     // Program deleter should skip discard queue if controller shutting down
54     if(!object->GetController().IsShuttingDown())
55     {
56       object->DiscardResource();
57     }
58     else
59     {
60       // delete object otherwise
61       delete object;
62     }
63   }
64 };
65
66 /**
67  * @brief The order of states being stored in the cache and mask
68  */
69 enum class StateLookupIndex : uint32_t
70 {
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,
78   MAX_STATE                = 7
79 };
80
81 /**
82  * Helper float compare function
83  */
84 static bool cmpf(float A, float B, float epsilon = 0.005f)
85 {
86   return (fabs(A - B) < epsilon);
87 }
88
89 /**
90  * Helper operators
91  */
92 static bool operator==(const Graphics::Viewport& lhs, const Graphics::Viewport& rhs)
93 {
94   return cmpf(lhs.x, rhs.x) &&
95          cmpf(lhs.y, rhs.y) &&
96          cmpf(lhs.width, rhs.width) &&
97          cmpf(lhs.height, rhs.height) &&
98          cmpf(lhs.minDepth, rhs.minDepth) &&
99          cmpf(lhs.maxDepth, rhs.maxDepth);
100 }
101
102 static bool operator==(const Graphics::Rect2D& lhs, const Graphics::Rect2D& rhs)
103 {
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);
108 }
109
110 static bool operator==(const Graphics::StencilOpState& lhs, const Graphics::StencilOpState& rhs)
111 {
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;
119 }
120
121 static bool
122 operator==(const Dali::Graphics::VertexInputState::Attribute& lhs,
123            const Dali::Graphics::VertexInputState::Attribute& rhs)
124 {
125   return lhs.location == rhs.location &&
126          lhs.binding == rhs.binding &&
127          lhs.offset == rhs.offset &&
128          lhs.format == rhs.format;
129 }
130
131 static bool
132 operator==(const Dali::Graphics::VertexInputState::Binding& lhs, const Dali::Graphics::VertexInputState::Binding& rhs)
133 {
134   return lhs.stride == rhs.stride &&
135          lhs.inputRate == rhs.inputRate;
136 }
137
138 using PipelineStateCompateFunctionType = bool(const Graphics::PipelineCreateInfo*,
139                                               const Graphics::PipelineCreateInfo*);
140
141 static std::vector<PipelineStateCompateFunctionType*>& GetStateCompareFuncTable()
142 {
143   static std::vector<PipelineStateCompateFunctionType*> stateCompareFuncTable{};
144   return stateCompareFuncTable;
145 }
146
147 /**
148  * @brief Initialises compare function lookup table
149  */
150 void InitialiseStateCompareLookupTable()
151 {
152   GetStateCompareFuncTable().clear();
153   GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // colorBlendState
154                                        {
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;
171                                        });
172   GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // viewport state
173                                        {
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;
179                                        });
180   GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // basePipeline
181                                        {
182                                          return lhs->basePipeline == rhs->basePipeline;
183                                        });
184   GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // depthStencilState
185                                        {
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;
194                                        });
195   GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // rasterizationState
196                                        {
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;
202                                        });
203   GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // vertexInputState
204                                        {
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);
211                                                 }) &&
212                                                 std::equal(lvi.attributes.begin(), lvi.attributes.end(), rvi.attributes.begin(), [](const auto& lhs, const auto& rhs) {
213                                                   return operator==(lhs, rhs);
214                                                 });
215                                        });
216   GetStateCompareFuncTable().push_back([](const auto* lhs, const auto* rhs) -> bool // inputAssemblyState
217                                        {
218                                          const auto& lia = *lhs->inputAssemblyState;
219                                          const auto& ria = *rhs->inputAssemblyState;
220                                          return lia.topology == ria.topology &&
221                                                 lia.primitiveRestartEnable == ria.primitiveRestartEnable;
222                                        });
223 }
224
225 /**
226  * @brief Helper function calculating the bitmask of set states
227  *
228  * @param[in] info Valid PipelineCreateInfo structure
229  * @return bitmask of set states
230  */
231 inline uint32_t GetStateBitmask(const PipelineCreateInfo& info)
232 {
233   uint32_t mask{0u};
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);
241   return mask;
242 }
243
244 /**
245  * @brief Implementation of cache
246  */
247 struct PipelineCache::Impl
248 {
249   /**
250    * @brief Constructor
251    */
252   explicit Impl(EglGraphicsController& _controller)
253   : controller(_controller),
254     pipelineEntriesFlushRequired(false),
255     programEntriesFlushRequired(false),
256     shaderEntriesFlushRequired(false)
257   {
258     // Initialise lookup table
259     InitialiseStateCompareLookupTable();
260   }
261
262   /**
263    * @brief destructor
264    */
265   ~Impl()
266   {
267     // First destroy pipelines
268     entries.clear();
269
270     // Now programs
271     programEntries.clear();
272   }
273
274   /**
275    * @brief Structure describes a single cache entry
276    */
277   struct CacheEntry
278   {
279     CacheEntry() = default;
280
281     CacheEntry(UniquePtr<PipelineImpl>&& _pipeline, uint32_t _bitmask)
282     : pipeline(std::move(_pipeline)),
283       stateBitmask(_bitmask)
284     {
285     }
286
287     ~CacheEntry() = default;
288
289     CacheEntry(CacheEntry&&) = default;
290     CacheEntry& operator=(CacheEntry&&) = default;
291
292     UniquePtr<PipelineImpl> pipeline{nullptr};
293     uint32_t                stateBitmask{0u};
294   };
295
296   /**
297    * @brief Sorted array of shaders used to create program
298    */
299   struct ProgramCacheEntry
300   {
301     // sorted array of shaders
302     std::vector<const Graphics::Shader*> shaders;
303     UniquePtr<ProgramImpl>               program{nullptr};
304   };
305
306   struct ShaderCacheEntry
307   {
308     UniquePtr<ShaderImpl> shaderImpl{nullptr};
309   };
310
311   EglGraphicsController&         controller;
312   std::vector<CacheEntry>        entries;
313   std::vector<ProgramCacheEntry> programEntries;
314   std::vector<ShaderCacheEntry>  shaderEntries;
315
316   bool pipelineEntriesFlushRequired : 1;
317   bool programEntriesFlushRequired : 1;
318   bool shaderEntriesFlushRequired : 1;
319 };
320
321 PipelineCache::PipelineCache(EglGraphicsController& controller)
322 {
323   mImpl = std::make_unique<Impl>(controller);
324 }
325
326 PipelineCache::~PipelineCache() = default;
327
328 PipelineImpl* PipelineCache::FindPipelineImpl(const PipelineCreateInfo& info)
329 {
330   auto bitmask = GetStateBitmask(info);
331
332   for(auto& entry : mImpl->entries)
333   {
334     auto& pipeline  = entry.pipeline;
335     auto& cacheInfo = pipeline->GetCreateInfo();
336     if(!info.programState)
337     {
338       continue;
339     }
340
341     // Check whether the program is the same
342     if(info.programState->program)
343     {
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)
347       {
348         continue;
349       }
350
351       // Test whether set states bitmask matches
352       if(entry.stateBitmask != bitmask)
353       {
354         continue;
355       }
356
357       // Now test only for states that are set
358       auto i = 0;
359       for(i = 0; i < int(StateLookupIndex::MAX_STATE); ++i)
360       {
361         // Test only set states
362         if((entry.stateBitmask & (1 << i)))
363         {
364           if(!(GetStateCompareFuncTable()[i](&info, &cacheInfo)))
365           {
366             break;
367           }
368         }
369       }
370
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))
374       {
375         return pipeline.get();
376       }
377     }
378   }
379   return nullptr;
380 }
381
382 ProgramImpl* PipelineCache::FindProgramImpl(const ProgramCreateInfo& info)
383 {
384   if(mImpl->programEntries.empty())
385   {
386     return nullptr;
387   }
388
389   // assert if no shaders given
390   std::vector<const Graphics::Shader*> shaders;
391   shaders.reserve(info.shaderState->size());
392
393   for(auto& state : *info.shaderState)
394   {
395     shaders.push_back(state.shader);
396   }
397
398   // sort
399   std::sort(shaders.begin(), shaders.end());
400
401   for(auto& item : mImpl->programEntries)
402   {
403     if(item.shaders.size() != shaders.size())
404     {
405       continue;
406     }
407
408     int k = shaders.size();
409     while(--k >= 0 && item.shaders[k] == shaders[k])
410       ;
411
412     if(k < 0)
413     {
414       return item.program.get();
415     }
416   }
417   return nullptr;
418 }
419
420 Graphics::UniquePtr<Graphics::Pipeline> PipelineCache::GetPipeline(const PipelineCreateInfo&                 pipelineCreateInfo,
421                                                                    Graphics::UniquePtr<Graphics::Pipeline>&& oldPipeline)
422 {
423   auto cachedPipeline = FindPipelineImpl(pipelineCreateInfo);
424
425   // Return same pointer if nothing changed
426   if(oldPipeline && *static_cast<GLES::Pipeline*>(oldPipeline.get()) == cachedPipeline)
427   {
428     return std::move(oldPipeline);
429   }
430
431   if(!cachedPipeline)
432   {
433     // create new pipeline
434     auto pipeline = MakeUnique<GLES::PipelineImpl>(pipelineCreateInfo, mImpl->controller, *this);
435
436     cachedPipeline = pipeline.get();
437
438     // add it to cache
439     mImpl->entries.emplace_back(std::move(pipeline), GetStateBitmask(pipelineCreateInfo));
440   }
441
442   auto wrapper = MakeUnique<GLES::Pipeline, CachedObjectDeleter<GLES::Pipeline>>(*cachedPipeline);
443   return std::move(wrapper);
444 }
445
446 Graphics::UniquePtr<Graphics::Program> PipelineCache::GetProgram(const ProgramCreateInfo&                 programCreateInfo,
447                                                                  Graphics::UniquePtr<Graphics::Program>&& oldProgram)
448 {
449   ProgramImpl* cachedProgram = FindProgramImpl(programCreateInfo);
450
451   // Return same pointer if nothing changed
452   if(oldProgram && *static_cast<GLES::Program*>(oldProgram.get()) == cachedProgram)
453   {
454     return std::move(oldProgram);
455   }
456
457   if(!cachedProgram)
458   {
459     // create new pipeline
460     auto program = MakeUnique<GLES::ProgramImpl>(programCreateInfo, mImpl->controller);
461
462     program->Create(); // Don't currently handle failure.
463
464     cachedProgram = program.get();
465
466     // add it to cache
467     mImpl->programEntries.emplace_back();
468     auto& item   = mImpl->programEntries.back();
469     item.program = std::move(program);
470     for(auto& state : *programCreateInfo.shaderState)
471     {
472       item.shaders.push_back(state.shader);
473     }
474
475     std::sort(item.shaders.begin(), item.shaders.end());
476   }
477
478   auto wrapper = MakeUnique<GLES::Program, CachedObjectDeleter<GLES::Program>>(cachedProgram);
479   return std::move(wrapper);
480 }
481
482 ShaderImpl* PipelineCache::FindShaderImpl(const ShaderCreateInfo& shaderCreateInfo)
483 {
484   if(!mImpl->shaderEntries.empty())
485   {
486     for(auto& item : mImpl->shaderEntries)
487     {
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)
493       {
494         continue;
495       }
496
497       if(memcmp(itemInfo.sourceData, shaderCreateInfo.sourceData, itemInfo.sourceSize) == 0)
498       {
499         return item.shaderImpl.get();
500       }
501     }
502   }
503   return nullptr;
504 }
505
506 Graphics::UniquePtr<Graphics::Shader> PipelineCache::GetShader(const ShaderCreateInfo&                 shaderCreateInfo,
507                                                                Graphics::UniquePtr<Graphics::Shader>&& oldShader)
508 {
509   ShaderImpl* cachedShader = FindShaderImpl(shaderCreateInfo);
510
511   // Return same pointer if nothing changed
512   if(oldShader && *static_cast<GLES::Shader*>(oldShader.get()) == cachedShader)
513   {
514     return std::move(oldShader);
515   }
516
517   if(!cachedShader)
518   {
519     auto shader  = MakeUnique<GLES::ShaderImpl>(shaderCreateInfo, mImpl->controller);
520     cachedShader = shader.get();
521
522     mImpl->shaderEntries.emplace_back();
523     mImpl->shaderEntries.back().shaderImpl = std::move(shader);
524   }
525   auto wrapper = MakeUnique<GLES::Shader, CachedObjectDeleter<GLES::Shader>>(cachedShader);
526   return std::move(wrapper);
527 }
528
529 void PipelineCache::FlushCache()
530 {
531   if(mImpl->pipelineEntriesFlushRequired)
532   {
533     decltype(mImpl->entries) newEntries;
534     newEntries.reserve(mImpl->entries.size());
535
536     for(auto& entry : mImpl->entries)
537     {
538       // Move items which are still in use into the new array
539       if(entry.pipeline->GetRefCount() != 0)
540       {
541         newEntries.emplace_back(std::move(entry));
542       }
543     }
544
545     // Move temporary array in place of stored cache
546     // Unused pipelines will be deleted automatically
547     mImpl->entries = std::move(newEntries);
548
549     mImpl->pipelineEntriesFlushRequired = false;
550   }
551
552   if(mImpl->programEntriesFlushRequired)
553   {
554     // Program cache require similar action.
555     decltype(mImpl->programEntries) newProgramEntries;
556     newProgramEntries.reserve(mImpl->programEntries.size());
557
558     for(auto& entry : mImpl->programEntries)
559     {
560       // Move items which are still in use into the new array
561       if(entry.program->GetRefCount() != 0)
562       {
563         newProgramEntries.emplace_back(std::move(entry));
564       }
565     }
566
567     mImpl->programEntries = std::move(newProgramEntries);
568
569     mImpl->programEntriesFlushRequired = false;
570   }
571
572   if(mImpl->shaderEntriesFlushRequired)
573   {
574     // There is at least 1 unused shader
575     mImpl->shaderEntriesFlushRequired = false;
576     bool deleteRequired{false};
577     for(auto& entry : mImpl->shaderEntries)
578     {
579       if(entry.shaderImpl->GetRefCount() == 0)
580       {
581         mImpl->shaderEntriesFlushRequired = true;
582         auto frameCount                   = entry.shaderImpl->IncreaseFlushCount();
583         if(frameCount > CACHE_CLEAN_FLUSH_COUNT)
584         {
585           deleteRequired = true;
586         }
587       }
588     }
589     if(deleteRequired)
590     {
591       decltype(mImpl->shaderEntries) newShaderEntries;
592       newShaderEntries.reserve(mImpl->shaderEntries.size());
593       for(auto& entry : mImpl->shaderEntries)
594       {
595         if(entry.shaderImpl->GetRefCount() > 0 ||
596            entry.shaderImpl->GetFlushCount() <= CACHE_CLEAN_FLUSH_COUNT)
597         {
598           newShaderEntries.emplace_back(std::move(entry));
599         }
600       }
601       mImpl->shaderEntries = std::move(newShaderEntries);
602     }
603   }
604 }
605
606 void PipelineCache::MarkPipelineCacheFlushRequired()
607 {
608   mImpl->pipelineEntriesFlushRequired = true;
609 }
610
611 void PipelineCache::MarkProgramCacheFlushRequired()
612 {
613   mImpl->programEntriesFlushRequired = true;
614 }
615
616 void PipelineCache::MarkShaderCacheFlushRequired()
617 {
618   mImpl->shaderEntriesFlushRequired = true;
619 }
620
621 } // namespace Dali::Graphics::GLES