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