VertexBuffer instancing
[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 // Helper to get the vertex input format
33 Dali::Graphics::VertexInputFormat GetPropertyVertexFormat(Property::Type propertyType)
34 {
35   Dali::Graphics::VertexInputFormat type{};
36
37   switch(propertyType)
38   {
39     case Property::BOOLEAN:
40     {
41       type = Dali::Graphics::VertexInputFormat::UNDEFINED; // type = GL_BYTE; @todo new type for this?
42       break;
43     }
44     case Property::INTEGER:
45     {
46       type = Dali::Graphics::VertexInputFormat::INTEGER; // (short)
47       break;
48     }
49     case Property::FLOAT:
50     {
51       type = Dali::Graphics::VertexInputFormat::FLOAT;
52       break;
53     }
54     case Property::VECTOR2:
55     {
56       type = Dali::Graphics::VertexInputFormat::FVECTOR2;
57       break;
58     }
59     case Property::VECTOR3:
60     {
61       type = Dali::Graphics::VertexInputFormat::FVECTOR3;
62       break;
63     }
64     case Property::VECTOR4:
65     {
66       type = Dali::Graphics::VertexInputFormat::FVECTOR4;
67       break;
68     }
69     default:
70     {
71       type = Dali::Graphics::VertexInputFormat::UNDEFINED;
72     }
73   }
74
75   return type;
76 }
77
78 constexpr Graphics::CullMode ConvertCullFace(Dali::FaceCullingMode::Type mode)
79 {
80   switch(mode)
81   {
82     case Dali::FaceCullingMode::NONE:
83     {
84       return Graphics::CullMode::NONE;
85     }
86     case Dali::FaceCullingMode::FRONT:
87     {
88       return Graphics::CullMode::FRONT;
89     }
90     case Dali::FaceCullingMode::BACK:
91     {
92       return Graphics::CullMode::BACK;
93     }
94     case Dali::FaceCullingMode::FRONT_AND_BACK:
95     {
96       return Graphics::CullMode::FRONT_AND_BACK;
97     }
98     default:
99     {
100       return Graphics::CullMode::NONE;
101     }
102   }
103 }
104
105 constexpr Graphics::BlendFactor ConvertBlendFactor(BlendFactor::Type blendFactor)
106 {
107   switch(blendFactor)
108   {
109     case BlendFactor::ZERO:
110       return Graphics::BlendFactor::ZERO;
111     case BlendFactor::ONE:
112       return Graphics::BlendFactor::ONE;
113     case BlendFactor::SRC_COLOR:
114       return Graphics::BlendFactor::SRC_COLOR;
115     case BlendFactor::ONE_MINUS_SRC_COLOR:
116       return Graphics::BlendFactor::ONE_MINUS_SRC_COLOR;
117     case BlendFactor::SRC_ALPHA:
118       return Graphics::BlendFactor::SRC_ALPHA;
119     case BlendFactor::ONE_MINUS_SRC_ALPHA:
120       return Graphics::BlendFactor::ONE_MINUS_SRC_ALPHA;
121     case BlendFactor::DST_ALPHA:
122       return Graphics::BlendFactor::DST_ALPHA;
123     case BlendFactor::ONE_MINUS_DST_ALPHA:
124       return Graphics::BlendFactor::ONE_MINUS_DST_ALPHA;
125     case BlendFactor::DST_COLOR:
126       return Graphics::BlendFactor::DST_COLOR;
127     case BlendFactor::ONE_MINUS_DST_COLOR:
128       return Graphics::BlendFactor::ONE_MINUS_DST_COLOR;
129     case BlendFactor::SRC_ALPHA_SATURATE:
130       return Graphics::BlendFactor::SRC_ALPHA_SATURATE;
131     case BlendFactor::CONSTANT_COLOR:
132       return Graphics::BlendFactor::CONSTANT_COLOR;
133     case BlendFactor::ONE_MINUS_CONSTANT_COLOR:
134       return Graphics::BlendFactor::ONE_MINUS_CONSTANT_COLOR;
135     case BlendFactor::CONSTANT_ALPHA:
136       return Graphics::BlendFactor::CONSTANT_ALPHA;
137     case BlendFactor::ONE_MINUS_CONSTANT_ALPHA:
138       return Graphics::BlendFactor::ONE_MINUS_CONSTANT_ALPHA;
139     default:
140       return Graphics::BlendFactor();
141   }
142 }
143
144 constexpr Graphics::BlendOp ConvertBlendEquation(DevelBlendEquation::Type blendEquation)
145 {
146   switch(blendEquation)
147   {
148     case DevelBlendEquation::ADD:
149       return Graphics::BlendOp::ADD;
150     case DevelBlendEquation::SUBTRACT:
151       return Graphics::BlendOp::SUBTRACT;
152     case DevelBlendEquation::REVERSE_SUBTRACT:
153       return Graphics::BlendOp::REVERSE_SUBTRACT;
154     case DevelBlendEquation::COLOR:
155       return Graphics::BlendOp::COLOR;
156     case DevelBlendEquation::COLOR_BURN:
157       return Graphics::BlendOp::COLOR_BURN;
158     case DevelBlendEquation::COLOR_DODGE:
159       return Graphics::BlendOp::COLOR_DODGE;
160     case DevelBlendEquation::DARKEN:
161       return Graphics::BlendOp::DARKEN;
162     case DevelBlendEquation::DIFFERENCE:
163       return Graphics::BlendOp::DIFFERENCE;
164     case DevelBlendEquation::EXCLUSION:
165       return Graphics::BlendOp::EXCLUSION;
166     case DevelBlendEquation::HARD_LIGHT:
167       return Graphics::BlendOp::HARD_LIGHT;
168     case DevelBlendEquation::HUE:
169       return Graphics::BlendOp::HUE;
170     case DevelBlendEquation::LIGHTEN:
171       return Graphics::BlendOp::LIGHTEN;
172     case DevelBlendEquation::LUMINOSITY:
173       return Graphics::BlendOp::LUMINOSITY;
174     case DevelBlendEquation::MAX:
175       return Graphics::BlendOp::MAX;
176     case DevelBlendEquation::MIN:
177       return Graphics::BlendOp::MIN;
178     case DevelBlendEquation::MULTIPLY:
179       return Graphics::BlendOp::MULTIPLY;
180     case DevelBlendEquation::OVERLAY:
181       return Graphics::BlendOp::OVERLAY;
182     case DevelBlendEquation::SATURATION:
183       return Graphics::BlendOp::SATURATION;
184     case DevelBlendEquation::SCREEN:
185       return Graphics::BlendOp::SCREEN;
186     case DevelBlendEquation::SOFT_LIGHT:
187       return Graphics::BlendOp::SOFT_LIGHT;
188   }
189   return Graphics::BlendOp{};
190 }
191 } // namespace
192
193 PipelineCacheL0* PipelineCache::GetPipelineCacheL0(std::size_t hash, Program* program, Render::Geometry* geometry)
194 {
195   auto it = std::find_if(level0nodes.begin(), level0nodes.end(), [hash, program, geometry](PipelineCacheL0& item) {
196     return ((item.hash == hash && item.program == program && item.geometry == geometry));
197   });
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     level0nodes.emplace_back(std::move(level0));
251     it = level0nodes.end() - 1;
252   }
253
254   return &*it;
255 }
256
257 PipelineCacheL1* 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) {
303     return item.hashCode == hash;
304   });
305
306   PipelineCacheL1* retval = nullptr;
307   if(it == level1nodes.end())
308   {
309     PipelineCacheL1 item;
310     item.hashCode       = hash;
311     item.rs.cullMode    = ConvertCullFace(FaceCullingMode::Type(cull));
312     item.rs.frontFace   = Graphics::FrontFace::COUNTER_CLOCKWISE;
313     item.rs.polygonMode = poly; // not in use
314     item.ia.topology    = geometry->GetTopology();
315     level1nodes.emplace_back(std::move(item));
316     retval = &level1nodes.back();
317   }
318   else
319   {
320     retval = &*it;
321   }
322   return retval;
323 }
324
325 PipelineCacheL2* PipelineCacheL1::GetPipelineCacheL2(bool blend, bool premul, BlendingOptions& blendingOptions)
326 {
327   // early out
328   if(!blend)
329   {
330     if(noBlend.pipeline == nullptr)
331     {
332       // reset all before returning if pipeline has never been created for that case
333       noBlend.hash = 0;
334       memset(&noBlend.colorBlendState, 0, sizeof(Graphics::ColorBlendState));
335     }
336     return &noBlend;
337   }
338
339   auto bitmask = uint32_t(blendingOptions.GetBitmask());
340
341   // Find by bitmask (L2 entries must be sorted by bitmask)
342   auto it = std::find_if(level2nodes.begin(), level2nodes.end(), [bitmask](PipelineCacheL2& item) {
343     return item.hash == bitmask;
344   });
345
346   // TODO: find better way of blend constants lookup
347   PipelineCacheL2* retval = nullptr;
348   if(it != level2nodes.end())
349   {
350     bool hasBlendColor = blendingOptions.GetBlendColor();
351     while(hasBlendColor && it != level2nodes.end() && (*it).hash == bitmask)
352     {
353       Vector4 v(it->colorBlendState.blendConstants);
354       if(v == *blendingOptions.GetBlendColor())
355       {
356         retval = &*it;
357       }
358       ++it;
359     }
360     if(!hasBlendColor)
361     {
362       retval = &*it;
363     }
364   }
365
366   if(!retval)
367   {
368     // create new entry and return it with null pipeline
369     PipelineCacheL2 l2;
370     l2.pipeline           = nullptr;
371     auto& colorBlendState = l2.colorBlendState;
372     colorBlendState.SetBlendEnable(true);
373     Graphics::BlendOp rgbOp   = ConvertBlendEquation(blendingOptions.GetBlendEquationRgb());
374     Graphics::BlendOp alphaOp = ConvertBlendEquation(blendingOptions.GetBlendEquationAlpha());
375     if(blendingOptions.IsAdvancedBlendEquationApplied() && premul)
376     {
377       if(rgbOp != alphaOp)
378       {
379         DALI_LOG_ERROR("Advanced Blend Equation MUST be applied by using BlendEquation.\n");
380         alphaOp = rgbOp;
381       }
382     }
383
384     colorBlendState
385       .SetSrcColorBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendSrcFactorRgb()))
386       .SetSrcAlphaBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendSrcFactorAlpha()))
387       .SetDstColorBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendDestFactorRgb()))
388       .SetDstAlphaBlendFactor(ConvertBlendFactor(blendingOptions.GetBlendDestFactorAlpha()))
389       .SetColorBlendOp(rgbOp)
390       .SetAlphaBlendOp(alphaOp);
391
392     // Blend color is optional and rarely used
393     auto* blendColor = const_cast<Vector4*>(blendingOptions.GetBlendColor());
394     if(blendColor)
395     {
396       colorBlendState.SetBlendConstants(blendColor->AsFloat());
397     }
398
399     l2.hash = blendingOptions.GetBitmask();
400     level2nodes.emplace_back(std::move(l2));
401
402     std::sort(level2nodes.begin(), level2nodes.end(), [](PipelineCacheL2& lhs, PipelineCacheL2& rhs) {
403       return lhs.hash < rhs.hash;
404     });
405
406     // run same function to retrieve retval
407     retval = GetPipelineCacheL2(blend, premul, blendingOptions);
408   }
409
410   return retval;
411 }
412
413 void PipelineCacheQueryInfo::GenerateHash()
414 {
415   // Lightweight hash value generation.
416   hash = (reinterpret_cast<std::size_t>(program) >> Dali::Log<sizeof(decltype(*program))>::value) ^
417          (reinterpret_cast<std::size_t>(geometry) >> Dali::Log<sizeof(decltype(*geometry))>::value) ^
418          ((blendingEnabled ? 1u : 0u) << 0u) ^
419          ((alphaPremultiplied ? 1u : 0u) << 1u) ^
420          (static_cast<std::size_t>(geometry->GetTopology()) << 2u) ^
421          (static_cast<std::size_t>(renderer->GetFaceCullMode()) << 5u) ^
422          ((cameraUsingReflection ? 1u : 0u) << 8u) ^
423          (blendingEnabled ? static_cast<std::size_t>(blendingOptions->GetBitmask()) : 0xDA11u);
424 }
425
426 bool PipelineCacheQueryInfo::Equal(const PipelineCacheQueryInfo& lhs, const PipelineCacheQueryInfo& rhs) noexcept
427 {
428   // Naive equal check.
429   const bool ret = (lhs.hash == rhs.hash) && // Check hash value first
430                    (lhs.program == rhs.program) &&
431                    (lhs.geometry == rhs.geometry) &&
432                    (lhs.blendingEnabled == rhs.blendingEnabled) &&
433                    (lhs.alphaPremultiplied == rhs.alphaPremultiplied) &&
434                    (lhs.geometry->GetTopology() == rhs.geometry->GetTopology()) &&
435                    (lhs.renderer->GetFaceCullMode() == rhs.renderer->GetFaceCullMode()) &&
436                    (lhs.cameraUsingReflection == rhs.cameraUsingReflection) &&
437                    (!lhs.blendingEnabled ||
438                     (lhs.blendingOptions->GetBitmask() == rhs.blendingOptions->GetBitmask() &&
439                      ((lhs.blendingOptions->GetBlendColor() == nullptr && rhs.blendingOptions->GetBlendColor() == nullptr) ||
440                       (lhs.blendingOptions->GetBlendColor() &&
441                        rhs.blendingOptions->GetBlendColor() &&
442                        (*lhs.blendingOptions->GetBlendColor() == *rhs.blendingOptions->GetBlendColor())))));
443
444   return ret;
445 }
446
447 PipelineCache::PipelineCache(Graphics::Controller& controller)
448 : graphicsController(&controller)
449 {
450   // Clean up cache first
451   CleanLatestUsedCache();
452 }
453
454 PipelineResult PipelineCache::GetPipeline(const PipelineCacheQueryInfo& queryInfo, bool createNewIfNotFound)
455 {
456   // Seperate branch whether query use blending or not.
457   const int latestUsedCacheIndex = queryInfo.blendingEnabled ? 0 : 1;
458
459   // If we can reuse latest bound pipeline, Fast return.
460   if(ReuseLatestBoundPipeline(latestUsedCacheIndex, queryInfo))
461   {
462     return mLatestResult[latestUsedCacheIndex];
463   }
464
465   auto* level0 = GetPipelineCacheL0(queryInfo.hash, queryInfo.program, queryInfo.geometry);
466   auto* level1 = level0->GetPipelineCacheL1(queryInfo.renderer, queryInfo.cameraUsingReflection);
467   auto* level2 = level1->GetPipelineCacheL2(queryInfo.blendingEnabled, queryInfo.alphaPremultiplied, *queryInfo.blendingOptions);
468
469   // Create new pipeline at level2 if requested
470   if(level2->pipeline == nullptr && createNewIfNotFound)
471   {
472     Graphics::ProgramState programState{};
473     programState.program = &queryInfo.program->GetGraphicsProgram();
474     // Create the pipeline
475     Graphics::PipelineCreateInfo createInfo;
476     createInfo
477       .SetInputAssemblyState(&level1->ia)
478       .SetVertexInputState(&level0->inputState)
479       .SetRasterizationState(&level1->rs)
480       .SetColorBlendState(&level2->colorBlendState)
481       .SetProgramState(&programState);
482
483     // Store a pipeline per renderer per render (renderer can be owned by multiple nodes,
484     // and re-drawn in multiple instructions).
485     level2->pipeline = graphicsController->CreatePipeline(createInfo, nullptr);
486   }
487
488   PipelineResult result{};
489
490   result.pipeline = level2->pipeline.get();
491   result.level0   = level0;
492   result.level1   = level1;
493   result.level2   = level2;
494
495   // Copy query and result
496   mLatestQuery[latestUsedCacheIndex]  = queryInfo;
497   mLatestResult[latestUsedCacheIndex] = result;
498
499   return result;
500 }
501
502 bool PipelineCache::ReuseLatestBoundPipeline(const int latestUsedCacheIndex, const PipelineCacheQueryInfo& queryInfo) const
503 {
504   return mLatestResult[latestUsedCacheIndex].pipeline != nullptr && PipelineCacheQueryInfo::Equal(queryInfo, mLatestQuery[latestUsedCacheIndex]);
505 }
506
507 } // namespace Dali::Internal::Render