9839d95db6ee4f8564bb9e68f29309db8de12db2
[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 PipelineCacheL0Ptr 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) { return ((item.hash == hash && item.program == program && item.geometry == geometry)); });
198
199   // Add new node to cache
200   if(it == level0nodes.end())
201   {
202     uint32_t bindingIndex{0u};
203     auto&    reflection = graphicsController->GetProgramReflection(program->GetGraphicsProgram());
204
205     Graphics::VertexInputState vertexInputState{};
206     uint32_t                   base = 0;
207
208     for(auto&& vertexBuffer : geometry->GetVertexBuffers())
209     {
210       const VertexBuffer::Format& vertexFormat = *vertexBuffer->GetFormat();
211
212       uint32_t                  divisor         = vertexBuffer->GetDivisor();
213       Graphics::VertexInputRate vertexInputRate = (divisor == 0
214                                                      ? Graphics::VertexInputRate::PER_VERTEX
215                                                      : Graphics::VertexInputRate::PER_INSTANCE);
216
217       vertexInputState.bufferBindings.emplace_back(vertexFormat.size, // stride
218                                                    vertexInputRate);
219       //@todo Add the actual rate to the graphics struct
220
221       const uint32_t attributeCount          = vertexBuffer->GetAttributeCount();
222       uint32_t       lastBoundAttributeIndex = 0;
223       for(uint32_t i = 0; i < attributeCount; ++i)
224       {
225         auto    attributeName = vertexBuffer->GetAttributeName(i);
226         int32_t pLocation     = reflection.GetVertexAttributeLocation(std::string(attributeName.GetStringView()));
227         if(-1 != pLocation)
228         {
229           auto location = static_cast<uint32_t>(pLocation);
230           vertexInputState.attributes.emplace_back(location,
231                                                    bindingIndex,
232                                                    vertexFormat.components[i].offset,
233                                                    GetPropertyVertexFormat(vertexFormat.components[i].type));
234           ++lastBoundAttributeIndex;
235         }
236         else
237         {
238           DALI_LOG_WARNING("Attribute not found in the shader: %s\n", attributeName.GetCString());
239           // Don't bind unused attributes.
240         }
241       }
242       base += lastBoundAttributeIndex;
243       ++bindingIndex;
244     }
245     PipelineCacheL0 level0;
246     level0.hash       = hash;
247     level0.program    = program;
248     level0.geometry   = geometry;
249     level0.inputState = vertexInputState;
250
251     it = level0nodes.insert(level0nodes.end(), std::move(level0));
252   }
253
254   return it;
255 }
256
257 PipelineCacheL1Ptr PipelineCacheL0::GetPipelineCacheL1(Render::Renderer* renderer, bool usingReflection)
258 {
259   // hash must be collision free
260   uint32_t hash = 0;
261   auto     topo = (uint32_t(geometry->GetTopology()) & 0xffu);
262   auto     cull = (uint32_t(renderer->GetFaceCullMode()) & 0xffu);
263
264   static const Graphics::PolygonMode polyTable[] = {
265     Graphics::PolygonMode::POINT,
266     Graphics::PolygonMode::LINE,
267     Graphics::PolygonMode::LINE,
268     Graphics::PolygonMode::LINE,
269     Graphics::PolygonMode::FILL,
270     Graphics::PolygonMode::FILL,
271     Graphics::PolygonMode::FILL};
272
273   auto poly = polyTable[topo];
274
275   static const FaceCullingMode::Type adjFaceCullingMode[4] =
276     {
277       FaceCullingMode::NONE,
278       FaceCullingMode::BACK,
279       FaceCullingMode::FRONT,
280       FaceCullingMode::FRONT_AND_BACK,
281     };
282
283   static const FaceCullingMode::Type normalFaceCullingMode[4] =
284     {
285       FaceCullingMode::NONE,
286       FaceCullingMode::FRONT,
287       FaceCullingMode::BACK,
288       FaceCullingMode::FRONT_AND_BACK,
289     };
290
291   static const FaceCullingMode::Type* cullModeTable[2] = {
292     normalFaceCullingMode,
293     adjFaceCullingMode};
294
295   // Retrieve cull mode
296   auto cullModeTableIndex = uint32_t(usingReflection) & 1u;
297   cull                    = cullModeTable[cullModeTableIndex][renderer->GetFaceCullMode()];
298
299   hash = (topo & 0xffu) | ((cull << 8u) & 0xff00u) | ((uint32_t(poly) << 16u) & 0xff0000u);
300
301   // If L1 not found by hash, create rasterization state describing pipeline and store it
302   auto it = std::find_if(level1nodes.begin(), level1nodes.end(), [hash](PipelineCacheL1& item) { return item.hashCode == hash; });
303
304   if(it == level1nodes.end())
305   {
306     PipelineCacheL1 item;
307     item.hashCode       = hash;
308     item.rs.cullMode    = ConvertCullFace(FaceCullingMode::Type(cull));
309     item.rs.frontFace   = Graphics::FrontFace::COUNTER_CLOCKWISE;
310     item.rs.polygonMode = poly; // not in use
311     item.ia.topology    = geometry->GetTopology();
312
313     it = level1nodes.insert(level1nodes.end(), std::move(item));
314   }
315
316   return it;
317 }
318
319 void PipelineCacheL0::ClearUnusedCache()
320 {
321   for(auto iter = level1nodes.begin(); iter != level1nodes.end();)
322   {
323     if(iter->ClearUnusedCache())
324     {
325       iter = level1nodes.erase(iter);
326     }
327     else
328     {
329       iter++;
330     }
331   }
332 }
333
334 PipelineCacheL2Ptr PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions)
335 {
336   // early out
337   if(!blend)
338   {
339     if(DALI_UNLIKELY(noBlends.empty()))
340     {
341       noBlends.emplace_back(PipelineCacheL2{});
342     }
343
344     auto& noBlend = *noBlends.begin();
345
346     if(noBlend.pipeline == nullptr)
347     {
348       // reset all before returning if pipeline has never been created for that case
349       noBlend.hash = 0;
350       memset(&noBlend.colorBlendState, 0, sizeof(Graphics::ColorBlendState));
351     }
352     return noBlends.begin();
353   }
354
355   auto bitmask = uint32_t(blendingOptions.GetBitmask());
356
357   // Find by bitmask (L2 entries must be sorted by bitmask)
358   auto it = std::find_if(level2nodes.begin(), level2nodes.end(), [bitmask](PipelineCacheL2& item) { return item.hash == bitmask; });
359
360   // TODO: find better way of blend constants lookup
361   PipelineCacheL2Ptr retval = level2nodes.end();
362   if(it != level2nodes.end())
363   {
364     bool hasBlendColor = blendingOptions.GetBlendColor();
365     while(hasBlendColor && it != level2nodes.end() && (*it).hash == bitmask)
366     {
367       Vector4 v(it->colorBlendState.blendConstants);
368       if(v == *blendingOptions.GetBlendColor())
369       {
370         retval = it;
371       }
372       ++it;
373     }
374     if(!hasBlendColor)
375     {
376       retval = it;
377     }
378   }
379
380   if(retval == level2nodes.end())
381   {
382     // create new entry and return it with null pipeline
383     PipelineCacheL2 l2{};
384     l2.pipeline           = nullptr;
385     auto& colorBlendState = l2.colorBlendState;
386     colorBlendState.SetBlendEnable(true);
387     Graphics::BlendOp rgbOp   = ConvertBlendEquation(blendingOptions.GetBlendEquationRgb());
388     Graphics::BlendOp alphaOp = ConvertBlendEquation(blendingOptions.GetBlendEquationAlpha());
389     if(blendingOptions.IsAdvancedBlendEquationApplied() && premul)
390     {
391       if(rgbOp != alphaOp)
392       {
393         DALI_LOG_ERROR("Advanced Blend Equation MUST be applied by using BlendEquation.\n");
394         alphaOp = rgbOp;
395       }
396     }
397
398     colorBlendState
399       .SetSrcColorBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendSrcFactorRgb()))
400       .SetSrcAlphaBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendSrcFactorAlpha()))
401       .SetDstColorBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendDestFactorRgb()))
402       .SetDstAlphaBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendDestFactorAlpha()))
403       .SetColorBlendOp(rgbOp)
404       .SetAlphaBlendOp(alphaOp);
405
406     // Blend color is optional and rarely used
407     auto* blendColor = const_cast<Vector4*>(blendingOptions.GetBlendColor());
408     if(blendColor)
409     {
410       colorBlendState.SetBlendConstants(blendColor->AsFloat());
411     }
412
413     l2.hash = blendingOptions.GetBitmask();
414
415     auto upperBound = std::upper_bound(level2nodes.begin(), level2nodes.end(), l2, [](const PipelineCacheL2& lhs, const PipelineCacheL2& rhs) { return lhs.hash < rhs.hash; });
416
417     level2nodes.insert(upperBound, std::move(l2));
418
419     // run same function to retrieve retval
420     retval = GetPipelineCacheL2(blend, premul, blendingOptions);
421   }
422
423   return retval;
424 }
425
426 bool PipelineCacheL1::ClearUnusedCache()
427 {
428   for(auto iter = level2nodes.begin(); iter != level2nodes.end();)
429   {
430     if(iter->referenceCount == 0)
431     {
432       iter = level2nodes.erase(iter);
433     }
434     else
435     {
436       iter++;
437     }
438   }
439
440   if(!noBlends.empty() && noBlends.begin()->referenceCount > 0)
441   {
442     return false;
443   }
444
445   return level2nodes.empty();
446 }
447
448 void PipelineCacheQueryInfo::GenerateHash()
449 {
450   // Lightweight hash value generation.
451   hash = (reinterpret_cast<std::size_t>(program) >> Dali::Log<sizeof(decltype(*program))>::value) ^
452          (reinterpret_cast<std::size_t>(geometry) >> Dali::Log<sizeof(decltype(*geometry))>::value) ^
453          ((blendingEnabled ? 1u : 0u) << 0u) ^
454          ((alphaPremultiplied ? 1u : 0u) << 1u) ^
455          (static_cast<std::size_t>(geometry->GetTopology()) << 2u) ^
456          (static_cast<std::size_t>(renderer->GetFaceCullMode()) << 5u) ^
457          ((cameraUsingReflection ? 1u : 0u) << 8u) ^
458          (blendingEnabled ? static_cast<std::size_t>(blendingOptions->GetBitmask()) : 0xDA11u);
459 }
460
461 bool PipelineCacheQueryInfo::Equal(const PipelineCacheQueryInfo& lhs, const PipelineCacheQueryInfo& rhs) noexcept
462 {
463   // Naive equal check.
464   const bool ret = (lhs.hash == rhs.hash) && // Check hash value first
465                    (lhs.program == rhs.program) &&
466                    (lhs.geometry == rhs.geometry) &&
467                    (lhs.blendingEnabled == rhs.blendingEnabled) &&
468                    (lhs.alphaPremultiplied == rhs.alphaPremultiplied) &&
469                    (lhs.geometry->GetTopology() == rhs.geometry->GetTopology()) &&
470                    (lhs.renderer->GetFaceCullMode() == rhs.renderer->GetFaceCullMode()) &&
471                    (lhs.cameraUsingReflection == rhs.cameraUsingReflection) &&
472                    (!lhs.blendingEnabled ||
473                     (lhs.blendingOptions->GetBitmask() == rhs.blendingOptions->GetBitmask() &&
474                      ((lhs.blendingOptions->GetBlendColor() == nullptr && rhs.blendingOptions->GetBlendColor() == nullptr) ||
475                       (lhs.blendingOptions->GetBlendColor() &&
476                        rhs.blendingOptions->GetBlendColor() &&
477                        (*lhs.blendingOptions->GetBlendColor() == *rhs.blendingOptions->GetBlendColor())))));
478
479   return ret;
480 }
481
482 PipelineCache::PipelineCache(Graphics::Controller& controller)
483 : graphicsController(&controller)
484 {
485   // Clean up cache first
486   CleanLatestUsedCache();
487 }
488
489 PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound)
490 {
491   // Seperate branch whether query use blending or not.
492   const int latestUsedCacheIndex = queryInfo.blendingEnabled ? 0 : 1;
493
494   // If we can reuse latest bound pipeline, Fast return.
495   if(ReuseLatestBoundPipeline(latestUsedCacheIndex, queryInfo))
496   {
497     mLatestResult[latestUsedCacheIndex].level2->referenceCount++;
498     return mLatestResult[latestUsedCacheIndex];
499   }
500
501   auto level0 = GetPipelineCacheL0(queryInfo.hash, queryInfo.program, queryInfo.geometry);
502   auto level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
503
504   PipelineCachePtr level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
505
506   // Create new pipeline at level2 if requested
507   if(level2->pipeline == nullptr && createNewIfNotFound)
508   {
509     Graphics::ProgramState programState{};
510     programState.program = &queryInfo.program->GetGraphicsProgram();
511     // Create the pipeline
512     Graphics::PipelineCreateInfo createInfo;
513     createInfo
514       .SetInputAssemblyState(&level1->ia)
515       .SetVertexInputState(&level0->inputState)
516       .SetRasterizationState(&level1->rs)
517       .SetColorBlendState(&level2->colorBlendState)
518       .SetProgramState(&programState);
519
520     // Store a pipeline per renderer per render (renderer can be owned by multiple nodes,
521     // and re-drawn in multiple instructions).
522     level2->pipeline = graphicsController->CreatePipeline(createInfo, nullptr);
523   }
524
525   PipelineResult result{};
526
527   result.pipeline = level2->pipeline.get();
528   result.level2   = level2;
529
530   level2->referenceCount++;
531
532   // Copy query and result
533   mLatestQuery[latestUsedCacheIndex]  = queryInfo;
534   mLatestResult[latestUsedCacheIndex] = result;
535
536   return result;
537 }
538
539 bool PipelineCache::ReuseLatestBoundPipeline(const int latestUsedCacheIndex, const PipelineCacheQueryInfo& queryInfo) const
540 {
541   return mLatestResult[latestUsedCacheIndex].pipeline != nullptr && PipelineCacheQueryInfo::Equal(queryInfo, mLatestQuery[latestUsedCacheIndex]);
542 }
543
544 void PipelineCache::PreRender()
545 {
546   CleanLatestUsedCache();
547
548   // We don't need to check this every frame
549   if(++mFrameCount >= CACHE_CLEAN_FRAME_COUNT)
550   {
551     mFrameCount = 0u;
552     ClearUnusedCache();
553   }
554 }
555
556 void PipelineCache::ClearUnusedCache()
557 {
558   for(auto iter = level0nodes.begin(); iter != level0nodes.end();)
559   {
560     iter->ClearUnusedCache();
561
562     if(iter->level1nodes.empty())
563     {
564       iter = level0nodes.erase(iter);
565     }
566     else
567     {
568       iter++;
569     }
570   }
571 }
572
573 void PipelineCache::ResetPipeline(PipelineCachePtr pipelineCache)
574 {
575   // TODO : Can we always assume that pipelineCache input is valid iterator?
576   pipelineCache->referenceCount--;
577 }
578
579 } // namespace Dali::Internal::Render