Remove old pipeline caches
[platform/core/uifw/dali-core.git] / dali / internal / render / renderers / 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 // CLASS HEADER
18 #include <dali/internal/render/renderers/pipeline-cache.h>
19
20 // INTERNAL INCLUDES
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>
27
28 namespace Dali::Internal::Render
29 {
30 namespace
31 {
32 constexpr uint32_t CACHE_CLEAN_FRAME_COUNT = 600; // 60fps * 10sec
33
34 // Helper to get the vertex input format
35 Dali::Graphics::VertexInputFormat GetPropertyVertexFormat(Property::Type propertyType)
36 {
37   Dali::Graphics::VertexInputFormat type{};
38
39   switch(propertyType)
40   {
41     case Property::BOOLEAN:
42     {
43       type = Dali::Graphics::VertexInputFormat::UNDEFINED; // type = GL_BYTE; @todo new type for this?
44       break;
45     }
46     case Property::INTEGER:
47     {
48       type = Dali::Graphics::VertexInputFormat::INTEGER; // (short)
49       break;
50     }
51     case Property::FLOAT:
52     {
53       type = Dali::Graphics::VertexInputFormat::FLOAT;
54       break;
55     }
56     case Property::VECTOR2:
57     {
58       type = Dali::Graphics::VertexInputFormat::FVECTOR2;
59       break;
60     }
61     case Property::VECTOR3:
62     {
63       type = Dali::Graphics::VertexInputFormat::FVECTOR3;
64       break;
65     }
66     case Property::VECTOR4:
67     {
68       type = Dali::Graphics::VertexInputFormat::FVECTOR4;
69       break;
70     }
71     default:
72     {
73       type = Dali::Graphics::VertexInputFormat::UNDEFINED;
74     }
75   }
76
77   return type;
78 }
79
80 constexpr Graphics::CullMode ConvertCullFace(Dali::FaceCullingMode::Type mode)
81 {
82   switch(mode)
83   {
84     case Dali::FaceCullingMode::NONE:
85     {
86       return Graphics::CullMode::NONE;
87     }
88     case Dali::FaceCullingMode::FRONT:
89     {
90       return Graphics::CullMode::FRONT;
91     }
92     case Dali::FaceCullingMode::BACK:
93     {
94       return Graphics::CullMode::BACK;
95     }
96     case Dali::FaceCullingMode::FRONT_AND_BACK:
97     {
98       return Graphics::CullMode::FRONT_AND_BACK;
99     }
100     default:
101     {
102       return Graphics::CullMode::NONE;
103     }
104   }
105 }
106
107 constexpr Graphics::BlendFactor ConvertBlendFactor(BlendFactor::Type blendFactor)
108 {
109   switch(blendFactor)
110   {
111     case BlendFactor::ZERO:
112       return Graphics::BlendFactor::ZERO;
113     case BlendFactor::ONE:
114       return Graphics::BlendFactor::ONE;
115     case BlendFactor::SRC_COLOR:
116       return Graphics::BlendFactor::SRC_COLOR;
117     case BlendFactor::ONE_MINUS_SRC_COLOR:
118       return Graphics::BlendFactor::ONE_MINUS_SRC_COLOR;
119     case BlendFactor::SRC_ALPHA:
120       return Graphics::BlendFactor::SRC_ALPHA;
121     case BlendFactor::ONE_MINUS_SRC_ALPHA:
122       return Graphics::BlendFactor::ONE_MINUS_SRC_ALPHA;
123     case BlendFactor::DST_ALPHA:
124       return Graphics::BlendFactor::DST_ALPHA;
125     case BlendFactor::ONE_MINUS_DST_ALPHA:
126       return Graphics::BlendFactor::ONE_MINUS_DST_ALPHA;
127     case BlendFactor::DST_COLOR:
128       return Graphics::BlendFactor::DST_COLOR;
129     case BlendFactor::ONE_MINUS_DST_COLOR:
130       return Graphics::BlendFactor::ONE_MINUS_DST_COLOR;
131     case BlendFactor::SRC_ALPHA_SATURATE:
132       return Graphics::BlendFactor::SRC_ALPHA_SATURATE;
133     case BlendFactor::CONSTANT_COLOR:
134       return Graphics::BlendFactor::CONSTANT_COLOR;
135     case BlendFactor::ONE_MINUS_CONSTANT_COLOR:
136       return Graphics::BlendFactor::ONE_MINUS_CONSTANT_COLOR;
137     case BlendFactor::CONSTANT_ALPHA:
138       return Graphics::BlendFactor::CONSTANT_ALPHA;
139     case BlendFactor::ONE_MINUS_CONSTANT_ALPHA:
140       return Graphics::BlendFactor::ONE_MINUS_CONSTANT_ALPHA;
141     default:
142       return Graphics::BlendFactor();
143   }
144 }
145
146 constexpr Graphics::BlendOp ConvertBlendEquation(DevelBlendEquation::Type blendEquation)
147 {
148   switch(blendEquation)
149   {
150     case DevelBlendEquation::ADD:
151       return Graphics::BlendOp::ADD;
152     case DevelBlendEquation::SUBTRACT:
153       return Graphics::BlendOp::SUBTRACT;
154     case DevelBlendEquation::REVERSE_SUBTRACT:
155       return Graphics::BlendOp::REVERSE_SUBTRACT;
156     case DevelBlendEquation::COLOR:
157       return Graphics::BlendOp::COLOR;
158     case DevelBlendEquation::COLOR_BURN:
159       return Graphics::BlendOp::COLOR_BURN;
160     case DevelBlendEquation::COLOR_DODGE:
161       return Graphics::BlendOp::COLOR_DODGE;
162     case DevelBlendEquation::DARKEN:
163       return Graphics::BlendOp::DARKEN;
164     case DevelBlendEquation::DIFFERENCE:
165       return Graphics::BlendOp::DIFFERENCE;
166     case DevelBlendEquation::EXCLUSION:
167       return Graphics::BlendOp::EXCLUSION;
168     case DevelBlendEquation::HARD_LIGHT:
169       return Graphics::BlendOp::HARD_LIGHT;
170     case DevelBlendEquation::HUE:
171       return Graphics::BlendOp::HUE;
172     case DevelBlendEquation::LIGHTEN:
173       return Graphics::BlendOp::LIGHTEN;
174     case DevelBlendEquation::LUMINOSITY:
175       return Graphics::BlendOp::LUMINOSITY;
176     case DevelBlendEquation::MAX:
177       return Graphics::BlendOp::MAX;
178     case DevelBlendEquation::MIN:
179       return Graphics::BlendOp::MIN;
180     case DevelBlendEquation::MULTIPLY:
181       return Graphics::BlendOp::MULTIPLY;
182     case DevelBlendEquation::OVERLAY:
183       return Graphics::BlendOp::OVERLAY;
184     case DevelBlendEquation::SATURATION:
185       return Graphics::BlendOp::SATURATION;
186     case DevelBlendEquation::SCREEN:
187       return Graphics::BlendOp::SCREEN;
188     case DevelBlendEquation::SOFT_LIGHT:
189       return Graphics::BlendOp::SOFT_LIGHT;
190   }
191   return Graphics::BlendOp{};
192 }
193 } // namespace
194
195 PipelineCacheL0* PipelineCache::GetPipelineCacheL0(std::size_t hash, Program* program, Render::Geometry* geometry)
196 {
197   auto it = std::find_if(level0nodes.begin(), level0nodes.end(), [hash, program, geometry](PipelineCacheL0& item) {
198     return ((item.hash == hash && item.program == program && item.geometry == geometry));
199   });
200
201   // Add new node to cache
202   if(it == level0nodes.end())
203   {
204     uint32_t bindingIndex{0u};
205     auto&    reflection = graphicsController->GetProgramReflection(program->GetGraphicsProgram());
206
207     Graphics::VertexInputState vertexInputState{};
208     uint32_t                   base = 0;
209
210     for(auto&& vertexBuffer : geometry->GetVertexBuffers())
211     {
212       const VertexBuffer::Format& vertexFormat = *vertexBuffer->GetFormat();
213
214       uint32_t                  divisor         = vertexBuffer->GetDivisor();
215       Graphics::VertexInputRate vertexInputRate = (divisor == 0
216                                                      ? Graphics::VertexInputRate::PER_VERTEX
217                                                      : Graphics::VertexInputRate::PER_INSTANCE);
218
219       vertexInputState.bufferBindings.emplace_back(vertexFormat.size, // stride
220                                                    vertexInputRate);
221       //@todo Add the actual rate to the graphics struct
222
223       const uint32_t attributeCount          = vertexBuffer->GetAttributeCount();
224       uint32_t       lastBoundAttributeIndex = 0;
225       for(uint32_t i = 0; i < attributeCount; ++i)
226       {
227         auto    attributeName = vertexBuffer->GetAttributeName(i);
228         int32_t pLocation     = reflection.GetVertexAttributeLocation(std::string(attributeName.GetStringView()));
229         if(-1 != pLocation)
230         {
231           auto location = static_cast<uint32_t>(pLocation);
232           vertexInputState.attributes.emplace_back(location,
233                                                    bindingIndex,
234                                                    vertexFormat.components[i].offset,
235                                                    GetPropertyVertexFormat(vertexFormat.components[i].type));
236           ++lastBoundAttributeIndex;
237         }
238         else
239         {
240           DALI_LOG_WARNING("Attribute not found in the shader: %s\n", attributeName.GetCString());
241           // Don't bind unused attributes.
242         }
243       }
244       base += lastBoundAttributeIndex;
245       ++bindingIndex;
246     }
247     PipelineCacheL0 level0;
248     level0.hash       = hash;
249     level0.program    = program;
250     level0.geometry   = geometry;
251     level0.inputState = vertexInputState;
252     level0nodes.emplace_back(std::move(level0));
253     it = level0nodes.end() - 1;
254   }
255
256   return &*it;
257 }
258
259 PipelineCacheL1* PipelineCacheL0::GetPipelineCacheL1(Render::Renderer* renderer, bool usingReflection)
260 {
261   // hash must be collision free
262   uint32_t hash = 0;
263   auto     topo = (uint32_t(geometry->GetTopology()) & 0xffu);
264   auto     cull = (uint32_t(renderer->GetFaceCullMode()) & 0xffu);
265
266   static const Graphics::PolygonMode polyTable[] = {
267     Graphics::PolygonMode::POINT,
268     Graphics::PolygonMode::LINE,
269     Graphics::PolygonMode::LINE,
270     Graphics::PolygonMode::LINE,
271     Graphics::PolygonMode::FILL,
272     Graphics::PolygonMode::FILL,
273     Graphics::PolygonMode::FILL};
274
275   auto poly = polyTable[topo];
276
277   static const FaceCullingMode::Type adjFaceCullingMode[4] =
278     {
279       FaceCullingMode::NONE,
280       FaceCullingMode::BACK,
281       FaceCullingMode::FRONT,
282       FaceCullingMode::FRONT_AND_BACK,
283     };
284
285   static const FaceCullingMode::Type normalFaceCullingMode[4] =
286     {
287       FaceCullingMode::NONE,
288       FaceCullingMode::FRONT,
289       FaceCullingMode::BACK,
290       FaceCullingMode::FRONT_AND_BACK,
291     };
292
293   static const FaceCullingMode::Type* cullModeTable[2] = {
294     normalFaceCullingMode,
295     adjFaceCullingMode};
296
297   // Retrieve cull mode
298   auto cullModeTableIndex = uint32_t(usingReflection) & 1u;
299   cull                    = cullModeTable[cullModeTableIndex][renderer->GetFaceCullMode()];
300
301   hash = (topo & 0xffu) | ((cull << 8u) & 0xff00u) | ((uint32_t(poly) << 16u) & 0xff0000u);
302
303   // If L1 not found by hash, create rasterization state describing pipeline and store it
304   auto it = std::find_if(level1nodes.begin(), level1nodes.end(), [hash](PipelineCacheL1& item) {
305     return item.hashCode == hash;
306   });
307
308   PipelineCacheL1* retval = nullptr;
309   if(it == level1nodes.end())
310   {
311     PipelineCacheL1 item;
312     item.hashCode       = hash;
313     item.rs.cullMode    = ConvertCullFace(FaceCullingMode::Type(cull));
314     item.rs.frontFace   = Graphics::FrontFace::COUNTER_CLOCKWISE;
315     item.rs.polygonMode = poly; // not in use
316     item.ia.topology    = geometry->GetTopology();
317     level1nodes.emplace_back(std::move(item));
318     retval = &level1nodes.back();
319   }
320   else
321   {
322     retval = &*it;
323   }
324   return retval;
325 }
326
327 void PipelineCacheL0::ClearUnusedCache()
328 {
329   for(auto iter = level1nodes.begin(); iter != level1nodes.end();)
330   {
331     if(iter->ClearUnusedCache())
332     {
333       iter = level1nodes.erase(iter);
334     }
335     else
336     {
337       iter++;
338     }
339   }
340 }
341
342 PipelineCacheL2* PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions)
343 {
344   // early out
345   if(!blend)
346   {
347     if(noBlend.pipeline == nullptr)
348     {
349       // reset all before returning if pipeline has never been created for that case
350       noBlend.hash = 0;
351       memset(&noBlend.colorBlendState, 0, sizeof(Graphics::ColorBlendState));
352     }
353     return &noBlend;
354   }
355
356   auto bitmask = uint32_t(blendingOptions.GetBitmask());
357
358   // Find by bitmask (L2 entries must be sorted by bitmask)
359   auto it = std::find_if(level2nodes.begin(), level2nodes.end(), [bitmask](PipelineCacheL2& item) {
360     return item.hash == bitmask;
361   });
362
363   // TODO: find better way of blend constants lookup
364   PipelineCacheL2* retval = nullptr;
365   if(it != level2nodes.end())
366   {
367     bool hasBlendColor = blendingOptions.GetBlendColor();
368     while(hasBlendColor && it != level2nodes.end() && (*it).hash == bitmask)
369     {
370       Vector4 v(it->colorBlendState.blendConstants);
371       if(v == *blendingOptions.GetBlendColor())
372       {
373         retval = &*it;
374       }
375       ++it;
376     }
377     if(!hasBlendColor)
378     {
379       retval = &*it;
380     }
381   }
382
383   if(!retval)
384   {
385     // create new entry and return it with null pipeline
386     PipelineCacheL2 l2;
387     l2.pipeline           = nullptr;
388     auto& colorBlendState = l2.colorBlendState;
389     colorBlendState.SetBlendEnable(true);
390     Graphics::BlendOp rgbOp   = ConvertBlendEquation(blendingOptions.GetBlendEquationRgb());
391     Graphics::BlendOp alphaOp = ConvertBlendEquation(blendingOptions.GetBlendEquationAlpha());
392     if(blendingOptions.IsAdvancedBlendEquationApplied() && premul)
393     {
394       if(rgbOp != alphaOp)
395       {
396         DALI_LOG_ERROR("Advanced Blend Equation MUST be applied by using BlendEquation.\n");
397         alphaOp = rgbOp;
398       }
399     }
400
401     colorBlendState
402       .SetSrcColorBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendSrcFactorRgb()))
403       .SetSrcAlphaBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendSrcFactorAlpha()))
404       .SetDstColorBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendDestFactorRgb()))
405       .SetDstAlphaBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendDestFactorAlpha()))
406       .SetColorBlendOp(rgbOp)
407       .SetAlphaBlendOp(alphaOp);
408
409     // Blend color is optional and rarely used
410     auto* blendColor = const_cast<Vector4*>(blendingOptions.GetBlendColor());
411     if(blendColor)
412     {
413       colorBlendState.SetBlendConstants(blendColor->AsFloat());
414     }
415
416     l2.hash = blendingOptions.GetBitmask();
417     level2nodes.emplace_back(std::move(l2));
418
419     std::sort(level2nodes.begin(), level2nodes.end(), [](PipelineCacheL2& lhs, PipelineCacheL2& rhs) {
420       return lhs.hash < rhs.hash;
421     });
422
423     // run same function to retrieve retval
424     retval = GetPipelineCacheL2(blend, premul, blendingOptions);
425   }
426
427   return retval;
428 }
429
430 bool PipelineCacheL1::ClearUnusedCache()
431 {
432   if(noBlend.referenceCount > 0)
433   {
434     return false;
435   }
436
437   for(auto iter = level2nodes.begin(); iter != level2nodes.end();)
438   {
439     if(iter->referenceCount == 0)
440     {
441       iter = level2nodes.erase(iter);
442     }
443     else
444     {
445       iter++;
446     }
447   }
448
449   return level2nodes.empty();
450 }
451
452 void PipelineCacheQueryInfo::GenerateHash()
453 {
454   // Lightweight hash value generation.
455   hash = (reinterpret_cast<std::size_t>(program) >> Dali::Log<sizeof(decltype(*program))>::value) ^
456          (reinterpret_cast<std::size_t>(geometry) >> Dali::Log<sizeof(decltype(*geometry))>::value) ^
457          ((blendingEnabled ? 1u : 0u) << 0u) ^
458          ((alphaPremultiplied ? 1u : 0u) << 1u) ^
459          (static_cast<std::size_t>(geometry->GetTopology()) << 2u) ^
460          (static_cast<std::size_t>(renderer->GetFaceCullMode()) << 5u) ^
461          ((cameraUsingReflection ? 1u : 0u) << 8u) ^
462          (blendingEnabled ? static_cast<std::size_t>(blendingOptions->GetBitmask()) : 0xDA11u);
463 }
464
465 bool PipelineCacheQueryInfo::Equal(const PipelineCacheQueryInfo& lhs, const PipelineCacheQueryInfo& rhs) noexcept
466 {
467   // Naive equal check.
468   const bool ret = (lhs.hash == rhs.hash) && // Check hash value first
469                    (lhs.program == rhs.program) &&
470                    (lhs.geometry == rhs.geometry) &&
471                    (lhs.blendingEnabled == rhs.blendingEnabled) &&
472                    (lhs.alphaPremultiplied == rhs.alphaPremultiplied) &&
473                    (lhs.geometry->GetTopology() == rhs.geometry->GetTopology()) &&
474                    (lhs.renderer->GetFaceCullMode() == rhs.renderer->GetFaceCullMode()) &&
475                    (lhs.cameraUsingReflection == rhs.cameraUsingReflection) &&
476                    (!lhs.blendingEnabled ||
477                     (lhs.blendingOptions->GetBitmask() == rhs.blendingOptions->GetBitmask() &&
478                      ((lhs.blendingOptions->GetBlendColor() == nullptr && rhs.blendingOptions->GetBlendColor() == nullptr) ||
479                       (lhs.blendingOptions->GetBlendColor() &&
480                        rhs.blendingOptions->GetBlendColor() &&
481                        (*lhs.blendingOptions->GetBlendColor() == *rhs.blendingOptions->GetBlendColor())))));
482
483   return ret;
484 }
485
486 PipelineCache::PipelineCache(Graphics::Controller& controller)
487 : graphicsController(&controller)
488 {
489   // Clean up cache first
490   CleanLatestUsedCache();
491 }
492
493 PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound)
494 {
495   // Seperate branch whether query use blending or not.
496   const int latestUsedCacheIndex = queryInfo.blendingEnabled ? 0 : 1;
497
498   // If we can reuse latest bound pipeline, Fast return.
499   if(ReuseLatestBoundPipeline(latestUsedCacheIndex, queryInfo))
500   {
501     mLatestResult[latestUsedCacheIndex].level2->referenceCount++;
502     return mLatestResult[latestUsedCacheIndex];
503   }
504
505   auto* level0 = GetPipelineCacheL0(queryInfo.hash, queryInfo.program, queryInfo.geometry);
506   auto* level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
507   auto* level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
508
509   // Create new pipeline at level2 if requested
510   if(level2->pipeline == nullptr && createNewIfNotFound)
511   {
512     Graphics::ProgramState programState{};
513     programState.program = &queryInfo.program->GetGraphicsProgram();
514     // Create the pipeline
515     Graphics::PipelineCreateInfo createInfo;
516     createInfo
517       .SetInputAssemblyState(&level1->ia)
518       .SetVertexInputState(&level0->inputState)
519       .SetRasterizationState(&level1->rs)
520       .SetColorBlendState(&level2->colorBlendState)
521       .SetProgramState(&programState);
522
523     // Store a pipeline per renderer per render (renderer can be owned by multiple nodes,
524     // and re-drawn in multiple instructions).
525     level2->pipeline = graphicsController->CreatePipeline(createInfo, nullptr);
526   }
527
528   PipelineResult result{};
529
530   result.pipeline = level2->pipeline.get();
531   result.level2   = level2;
532
533   level2->referenceCount++;
534
535   // Copy query and result
536   mLatestQuery[latestUsedCacheIndex]  = queryInfo;
537   mLatestResult[latestUsedCacheIndex] = result;
538
539   return result;
540 }
541
542 bool PipelineCache::ReuseLatestBoundPipeline(const int latestUsedCacheIndex, const PipelineCacheQueryInfo& queryInfo) const
543 {
544   return mLatestResult[latestUsedCacheIndex].pipeline != nullptr && PipelineCacheQueryInfo::Equal(queryInfo, mLatestQuery[latestUsedCacheIndex]);
545 }
546
547 void PipelineCache::PreRender()
548 {
549   CleanLatestUsedCache();
550
551   // We don't need to check this every frame
552   if(++mFrameCount >= CACHE_CLEAN_FRAME_COUNT)
553   {
554     mFrameCount = 0u;
555     ClearUnusedCache();
556   }
557 }
558
559 void PipelineCache::ClearUnusedCache()
560 {
561   for(auto iter = level0nodes.begin(); iter != level0nodes.end();)
562   {
563     iter->ClearUnusedCache();
564
565     if(iter->level1nodes.empty())
566     {
567       iter = level0nodes.erase(iter);
568     }
569     else
570     {
571       iter++;
572     }
573   }
574 }
575
576 void PipelineCache::ResetPipeline(PipelineCacheL2* pipelineCache)
577 {
578   if(pipelineCache)
579   {
580     pipelineCache->referenceCount--;
581   }
582 }
583
584 } // namespace Dali::Internal::Render