[dali_2.3.23] Merge branch 'devel/master'
[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 #if defined(DEBUG_ENABLED)
33 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_PIPELINE_CACHE");
34 #endif
35
36 constexpr uint32_t CACHE_CLEAN_FRAME_COUNT = 600; // 60fps * 10sec
37
38 // Helper to get the vertex input format
39 Dali::Graphics::VertexInputFormat GetPropertyVertexFormat(Property::Type propertyType)
40 {
41   Dali::Graphics::VertexInputFormat type{};
42
43   switch(propertyType)
44   {
45     case Property::BOOLEAN:
46     {
47       type = Dali::Graphics::VertexInputFormat::UNDEFINED; // type = GL_BYTE; @todo new type for this?
48       break;
49     }
50     case Property::INTEGER:
51     {
52       type = Dali::Graphics::VertexInputFormat::INTEGER; // (short)
53       break;
54     }
55     case Property::FLOAT:
56     {
57       type = Dali::Graphics::VertexInputFormat::FLOAT;
58       break;
59     }
60     case Property::VECTOR2:
61     {
62       type = Dali::Graphics::VertexInputFormat::FVECTOR2;
63       break;
64     }
65     case Property::VECTOR3:
66     {
67       type = Dali::Graphics::VertexInputFormat::FVECTOR3;
68       break;
69     }
70     case Property::VECTOR4:
71     {
72       type = Dali::Graphics::VertexInputFormat::FVECTOR4;
73       break;
74     }
75     default:
76     {
77       type = Dali::Graphics::VertexInputFormat::UNDEFINED;
78     }
79   }
80
81   return type;
82 }
83
84 constexpr Graphics::CullMode ConvertCullFace(Dali::FaceCullingMode::Type mode)
85 {
86   switch(mode)
87   {
88     case Dali::FaceCullingMode::NONE:
89     {
90       return Graphics::CullMode::NONE;
91     }
92     case Dali::FaceCullingMode::FRONT:
93     {
94       return Graphics::CullMode::FRONT;
95     }
96     case Dali::FaceCullingMode::BACK:
97     {
98       return Graphics::CullMode::BACK;
99     }
100     case Dali::FaceCullingMode::FRONT_AND_BACK:
101     {
102       return Graphics::CullMode::FRONT_AND_BACK;
103     }
104     default:
105     {
106       return Graphics::CullMode::NONE;
107     }
108   }
109 }
110
111 constexpr Graphics::BlendFactor ConvertBlendFactor(BlendFactor::Type blendFactor)
112 {
113   switch(blendFactor)
114   {
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;
145     default:
146       return Graphics::BlendFactor();
147   }
148 }
149
150 constexpr Graphics::BlendOp ConvertBlendEquation(DevelBlendEquation::Type blendEquation)
151 {
152   switch(blendEquation)
153   {
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;
194   }
195   return Graphics::BlendOp{};
196 }
197 } // namespace
198
199 PipelineCacheL0Ptr PipelineCache::GetPipelineCacheL0(Program* program, Render::Geometry* geometry)
200 {
201   auto it = std::find_if(level0nodes.begin(), level0nodes.end(), [program, geometry](PipelineCacheL0& item) {
202     return ((item.program == program && item.geometry == geometry));
203   });
204
205   // Add new node to cache
206   if(it == level0nodes.end())
207   {
208     uint32_t bindingIndex{0u};
209     auto&    reflection = graphicsController->GetProgramReflection(program->GetGraphicsProgram());
210
211     Graphics::VertexInputState vertexInputState{};
212     uint32_t                   base = 0;
213
214     bool attrNotFound = false;
215     for(auto&& vertexBuffer : geometry->GetVertexBuffers())
216     {
217       const VertexBuffer::Format& vertexFormat = *vertexBuffer->GetFormat();
218
219       uint32_t                  divisor         = vertexBuffer->GetDivisor();
220       Graphics::VertexInputRate vertexInputRate = (divisor == 0
221                                                      ? Graphics::VertexInputRate::PER_VERTEX
222                                                      : Graphics::VertexInputRate::PER_INSTANCE);
223
224       vertexInputState.bufferBindings.emplace_back(vertexFormat.size, // stride
225                                                    vertexInputRate);
226       //@todo Add the actual rate to the graphics struct
227
228       const uint32_t attributeCount          = vertexBuffer->GetAttributeCount();
229       uint32_t       lastBoundAttributeIndex = 0;
230       for(uint32_t i = 0; i < attributeCount; ++i)
231       {
232         auto    attributeName = vertexBuffer->GetAttributeName(i);
233         int32_t pLocation     = reflection.GetVertexAttributeLocation(std::string(attributeName.GetStringView()));
234         if(-1 != pLocation)
235         {
236           auto location = static_cast<uint32_t>(pLocation);
237           vertexInputState.attributes.emplace_back(location,
238                                                    bindingIndex,
239                                                    vertexFormat.components[i].offset,
240                                                    GetPropertyVertexFormat(vertexFormat.components[i].type));
241           ++lastBoundAttributeIndex;
242         }
243         else
244         {
245           attrNotFound = true;
246           DALI_LOG_WARNING("Attribute not found in the shader: %s\n", attributeName.GetCString());
247           // Don't bind unused attributes.
248         }
249       }
250       base += lastBoundAttributeIndex;
251       ++bindingIndex;
252     }
253     PipelineCacheL0 level0;
254     level0.program    = program;
255     level0.geometry   = geometry;
256     level0.inputState = vertexInputState;
257
258     it = level0nodes.insert(level0nodes.end(), std::move(level0));
259
260     if(attrNotFound)
261     {
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());
267     }
268   }
269
270   return it;
271 }
272
273 PipelineCacheL1Ptr PipelineCacheL0::GetPipelineCacheL1(Render::Renderer* renderer, bool usingReflection)
274 {
275   // hash must be collision free
276   uint32_t hash = 0;
277   auto     topo = (uint32_t(geometry->GetTopology()) & 0xffu);
278   auto     cull = (uint32_t(renderer->GetFaceCullMode()) & 0xffu);
279
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};
288
289   auto poly = polyTable[topo];
290
291   static const FaceCullingMode::Type adjFaceCullingMode[4] =
292     {
293       FaceCullingMode::NONE,
294       FaceCullingMode::BACK,
295       FaceCullingMode::FRONT,
296       FaceCullingMode::FRONT_AND_BACK,
297     };
298
299   static const FaceCullingMode::Type normalFaceCullingMode[4] =
300     {
301       FaceCullingMode::NONE,
302       FaceCullingMode::FRONT,
303       FaceCullingMode::BACK,
304       FaceCullingMode::FRONT_AND_BACK,
305     };
306
307   static const FaceCullingMode::Type* cullModeTable[2] = {
308     normalFaceCullingMode,
309     adjFaceCullingMode};
310
311   // Retrieve cull mode
312   auto cullModeTableIndex = uint32_t(usingReflection) & 1u;
313   cull                    = cullModeTable[cullModeTableIndex][renderer->GetFaceCullMode()];
314
315   hash = (topo & 0xffu) | ((cull << 8u) & 0xff00u) | ((uint32_t(poly) << 16u) & 0xff0000u);
316
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; });
319
320   if(it == level1nodes.end())
321   {
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();
328
329     it = level1nodes.insert(level1nodes.end(), std::move(item));
330   }
331
332   return it;
333 }
334
335 void PipelineCacheL0::ClearUnusedCache()
336 {
337   for(auto iter = level1nodes.begin(); iter != level1nodes.end();)
338   {
339     if(iter->ClearUnusedCache())
340     {
341       iter = level1nodes.erase(iter);
342     }
343     else
344     {
345       iter++;
346     }
347   }
348 }
349
350 PipelineCacheL2Ptr PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions)
351 {
352   // early out
353   if(!blend)
354   {
355     if(DALI_UNLIKELY(noBlends.empty()))
356     {
357       noBlends.emplace_back(PipelineCacheL2{});
358     }
359
360     auto& noBlend = *noBlends.begin();
361
362     if(noBlend.pipeline == nullptr)
363     {
364       // reset all before returning if pipeline has never been created for that case
365       noBlend.hash = 0;
366       memset(&noBlend.colorBlendState, 0, sizeof(Graphics::ColorBlendState));
367     }
368     return noBlends.begin();
369   }
370
371   auto bitmask = uint32_t(blendingOptions.GetBitmask());
372
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; });
375
376   // TODO: find better way of blend constants lookup
377   PipelineCacheL2Ptr retval = level2nodes.end();
378   if(it != level2nodes.end())
379   {
380     bool hasBlendColor = blendingOptions.GetBlendColor();
381     while(hasBlendColor && it != level2nodes.end() && (*it).hash == bitmask)
382     {
383       Vector4 v(it->colorBlendState.blendConstants);
384       if(v == *blendingOptions.GetBlendColor())
385       {
386         retval = it;
387       }
388       ++it;
389     }
390     if(!hasBlendColor)
391     {
392       retval = it;
393     }
394   }
395
396   if(retval == level2nodes.end())
397   {
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)
406     {
407       if(rgbOp != alphaOp)
408       {
409         DALI_LOG_ERROR("Advanced Blend Equation MUST be applied by using BlendEquation.\n");
410         alphaOp = rgbOp;
411       }
412     }
413
414     colorBlendState
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);
421
422     // Blend color is optional and rarely used
423     auto* blendColor = const_cast<Vector4*>(blendingOptions.GetBlendColor());
424     if(blendColor)
425     {
426       colorBlendState.SetBlendConstants(blendColor->AsFloat());
427     }
428
429     l2.hash = blendingOptions.GetBitmask();
430
431     auto upperBound = std::upper_bound(level2nodes.begin(), level2nodes.end(), l2, [](const PipelineCacheL2& lhs, const PipelineCacheL2& rhs) { return lhs.hash < rhs.hash; });
432
433     level2nodes.insert(upperBound, std::move(l2));
434
435     // run same function to retrieve retval
436     retval = GetPipelineCacheL2(blend, premul, blendingOptions);
437   }
438
439   return retval;
440 }
441
442 bool PipelineCacheL1::ClearUnusedCache()
443 {
444   for(auto iter = level2nodes.begin(); iter != level2nodes.end();)
445   {
446     if(iter->referenceCount == 0)
447     {
448       iter = level2nodes.erase(iter);
449     }
450     else
451     {
452       iter++;
453     }
454   }
455
456   if(!noBlends.empty() && noBlends.begin()->referenceCount > 0)
457   {
458     return false;
459   }
460
461   return level2nodes.empty();
462 }
463
464 void PipelineCacheQueryInfo::GenerateHash()
465 {
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);
475 }
476
477 bool PipelineCacheQueryInfo::Equal(const PipelineCacheQueryInfo& lhs, const PipelineCacheQueryInfo& rhs) noexcept
478 {
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())))));
494
495   return ret;
496 }
497
498 PipelineCache::PipelineCache(Graphics::Controller& controller)
499 : graphicsController(&controller)
500 {
501   // Clean up cache first
502   CleanLatestUsedCache();
503 }
504
505 PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound)
506 {
507   // Seperate branch whether query use blending or not.
508   const int latestUsedCacheIndex = queryInfo.blendingEnabled ? 0 : 1;
509
510   // If we can reuse latest bound pipeline, Fast return.
511   if(ReuseLatestBoundPipeline(latestUsedCacheIndex, queryInfo))
512   {
513     mLatestResult[latestUsedCacheIndex].level2->referenceCount++;
514     return mLatestResult[latestUsedCacheIndex];
515   }
516
517   auto level0 = GetPipelineCacheL0(queryInfo.program, queryInfo.geometry);
518   auto level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
519
520   PipelineCachePtr level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
521
522   // Create new pipeline at level2 if requested
523   if(level2->pipeline == nullptr && createNewIfNotFound)
524   {
525     Graphics::ProgramState programState{};
526     programState.program = &queryInfo.program->GetGraphicsProgram();
527     // Create the pipeline
528     Graphics::PipelineCreateInfo createInfo;
529     createInfo
530       .SetInputAssemblyState(&level1->ia)
531       .SetVertexInputState(&level0->inputState)
532       .SetRasterizationState(&level1->rs)
533       .SetColorBlendState(&level2->colorBlendState)
534       .SetProgramState(&programState);
535
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);
539   }
540
541   PipelineResult result{};
542
543   result.pipeline = level2->pipeline.get();
544   result.level2   = level2;
545
546   level2->referenceCount++;
547
548   // Copy query and result
549   mLatestQuery[latestUsedCacheIndex]  = queryInfo;
550   mLatestResult[latestUsedCacheIndex] = result;
551
552   return result;
553 }
554
555 bool PipelineCache::ReuseLatestBoundPipeline(const int latestUsedCacheIndex, const PipelineCacheQueryInfo& queryInfo) const
556 {
557   return mLatestResult[latestUsedCacheIndex].pipeline != nullptr && PipelineCacheQueryInfo::Equal(queryInfo, mLatestQuery[latestUsedCacheIndex]);
558 }
559
560 void PipelineCache::PreRender()
561 {
562   CleanLatestUsedCache();
563
564   // We don't need to check this every frame
565   if(++mFrameCount >= CACHE_CLEAN_FRAME_COUNT)
566   {
567     mFrameCount = 0u;
568     ClearUnusedCache();
569   }
570 }
571
572 void PipelineCache::ClearUnusedCache()
573 {
574   for(auto iter = level0nodes.begin(); iter != level0nodes.end();)
575   {
576     iter->ClearUnusedCache();
577
578     if(iter->level1nodes.empty())
579     {
580       iter = level0nodes.erase(iter);
581     }
582     else
583     {
584       iter++;
585     }
586   }
587 }
588
589 void PipelineCache::ResetPipeline(PipelineCachePtr pipelineCache)
590 {
591   // TODO : Can we always assume that pipelineCache input is valid iterator?
592   pipelineCache->referenceCount--;
593 }
594
595 } // namespace Dali::Internal::Render