Fix attribute cache bug
[platform/core/uifw/dali-adaptor.git] / dali / internal / graphics / gles-impl / gles-graphics-reflection.cpp
1 /*
2  * Copyright (c) 2021 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
18 #include "gles-graphics-reflection.h"
19
20 #include <dali/integration-api/debug.h>
21 #include <dali/integration-api/gl-abstraction.h>
22 #include <dali/integration-api/gl-defines.h>
23
24 #include <vector>
25 #include "egl-graphics-controller.h"
26
27 #include <GLES3/gl3.h>
28 #include <GLES3/gl31.h>
29
30 #include "gles-graphics-program.h"
31
32 #include <iostream>
33
34 namespace
35 {
36 struct StringSize
37 {
38   const char* const mString;
39   const uint32_t    mLength;
40
41   template<uint32_t kLength>
42   constexpr StringSize(const char (&string)[kLength])
43   : mString(string),
44     mLength(kLength - 1) // remove terminating null; N.B. there should be no other null.
45   {
46   }
47
48   operator const char*() const
49   {
50     return mString;
51   }
52 };
53
54 bool operator==(const StringSize& lhs, const char* rhs)
55 {
56   return strncmp(lhs.mString, rhs, lhs.mLength) == 0;
57 }
58
59 const char* const    DELIMITERS = " \t\n";
60 constexpr StringSize UNIFORM{"uniform"};
61 constexpr StringSize SAMPLER_PREFIX{"sampler"};
62 constexpr StringSize SAMPLER_TYPES[]   = {"2D", "Cube", "ExternalOES"};
63 constexpr auto       END_SAMPLER_TYPES = SAMPLER_TYPES + std::extent<decltype(SAMPLER_TYPES)>::value;
64
65 Dali::Graphics::VertexInputAttributeFormat GetVertexAttributeTypeFormat(GLenum type)
66 {
67   switch(type)
68   {
69     case GL_FLOAT:
70       return Dali::Graphics::VertexInputAttributeFormat::FLOAT;
71     case GL_FLOAT_VEC2:
72       return Dali::Graphics::VertexInputAttributeFormat::VEC2;
73     case GL_FLOAT_VEC3:
74       return Dali::Graphics::VertexInputAttributeFormat::VEC3;
75     case GL_FLOAT_VEC4:
76       return Dali::Graphics::VertexInputAttributeFormat::VEC4;
77     case GL_INT:
78       return Dali::Graphics::VertexInputAttributeFormat::INTEGER;
79     default:
80       return Dali::Graphics::VertexInputAttributeFormat::UNDEFINED;
81   }
82 }
83
84 uint32_t GetGLDataTypeSize(GLenum type)
85 {
86   // There are many more types than what are covered here, but
87   // they are not supported in dali.
88   switch(type)
89   {
90     case GL_FLOAT: // "float", 1 float, 4 bytes
91       return 4;
92     case GL_FLOAT_VEC2: // "vec2", 2 floats, 8 bytes
93       return 8;
94     case GL_FLOAT_VEC3: // "vec3", 3 floats, 12 bytes
95       return 12;
96     case GL_FLOAT_VEC4: // "vec4", 4 floats, 16 bytes
97       return 16;
98     case GL_INT: // "int", 1 integer, 4 bytes
99       return 4;
100     case GL_FLOAT_MAT2: // "mat2", 4 floats, 16 bytes
101       return 16;
102     case GL_FLOAT_MAT3: // "mat3", 3 vec3, 36 bytes
103       return 36;
104     case GL_FLOAT_MAT4: // "mat4", 4 vec4, 64 bytes
105       return 64;
106     default:
107       return 0;
108   }
109 }
110
111 bool IsSampler(GLenum type)
112 {
113   return type == GL_SAMPLER_2D || type == GL_SAMPLER_3D || type == GL_SAMPLER_CUBE || type == GL_SAMPLER_EXTERNAL_OES;
114 }
115
116 bool SortUniformInfoByLocation(Dali::Graphics::UniformInfo a, Dali::Graphics::UniformInfo b)
117 {
118   return a.location < b.location;
119 }
120
121 bool SortUniformExtraInfoByLocation(Dali::Graphics::GLES::Reflection::UniformExtraInfo a, Dali::Graphics::GLES::Reflection::UniformExtraInfo b)
122 {
123   return a.location < b.location;
124 }
125
126 std::string GetShaderSource(Dali::Graphics::ShaderState shaderState)
127 {
128   std::vector<uint8_t> data;
129   auto*                shader           = static_cast<const Dali::Graphics::GLES::Shader*>(shaderState.shader);
130   auto&                shaderCreateInfo = shader->GetCreateInfo();
131   data.resize(shaderCreateInfo.sourceSize + 1);
132   std::memcpy(&data[0], shaderCreateInfo.sourceData, shaderCreateInfo.sourceSize);
133   data[shaderCreateInfo.sourceSize] = 0;
134
135   return std::string(reinterpret_cast<char*>(&data[0]));
136 }
137
138 void ParseShaderSamplers(std::string shaderSource, std::vector<Dali::Graphics::UniformInfo>& uniformOpaques, int& samplerPosition, std::vector<int>& samplerPositions)
139 {
140   if(!shaderSource.empty())
141   {
142     char* shaderStr = strdup(shaderSource.c_str());
143     char* uniform   = strstr(shaderStr, UNIFORM);
144
145     while(uniform)
146     {
147       char* outerToken = strtok_r(uniform + UNIFORM.mLength, ";", &uniform);
148
149       char* nextPtr = nullptr;
150       char* token   = strtok_r(outerToken, DELIMITERS, &nextPtr);
151       while(token)
152       {
153         if(SAMPLER_PREFIX == token)
154         {
155           token += SAMPLER_PREFIX.mLength;
156           if(std::find(SAMPLER_TYPES, END_SAMPLER_TYPES, token) != END_SAMPLER_TYPES)
157           {
158             bool found(false);
159             token = strtok_r(nullptr, DELIMITERS, &nextPtr);
160
161             for(uint32_t i = 0; i < static_cast<uint32_t>(uniformOpaques.size()); ++i)
162             {
163               if(samplerPositions[i] == -1 &&
164                  strncmp(token, uniformOpaques[i].name.c_str(), uniformOpaques[i].name.size()) == 0)
165               {
166                 samplerPositions[i] = uniformOpaques[i].offset = samplerPosition++;
167                 found                                          = true;
168                 break;
169               }
170             }
171
172             if(!found)
173             {
174               DALI_LOG_ERROR("Sampler uniform %s declared but not used in the shader\n", token);
175             }
176             break;
177           }
178         }
179
180         token = strtok_r(nullptr, DELIMITERS, &nextPtr);
181       }
182
183       uniform = strstr(uniform, UNIFORM);
184     }
185     free(shaderStr);
186   }
187 }
188
189 } // anonymous namespace
190
191 namespace Dali::Graphics::GLES
192 {
193 Reflection::Reflection(GLES::ProgramImpl& program, Graphics::EglGraphicsController& controller)
194 : Graphics::Reflection(),
195   mController(controller),
196   mProgram(program)
197 {
198 }
199
200 Reflection::~Reflection() = default;
201
202 void Reflection::BuildVertexAttributeReflection()
203 {
204   auto glProgram = mProgram.GetGlProgram();
205
206   int    written, size, location, maxLength, nAttribs;
207   GLenum type;
208   char*  name;
209
210   auto gl = mController.GetGL();
211   if(!gl)
212   {
213     // Do nothing during shutdown
214     return;
215   }
216
217   gl->GetProgramiv(glProgram, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxLength);
218   gl->GetProgramiv(glProgram, GL_ACTIVE_ATTRIBUTES, &nAttribs);
219
220   mVertexInputAttributes.clear();
221   mVertexInputAttributes.resize(nAttribs);
222
223   int maximumLocation = nAttribs - 1;
224
225   name = new GLchar[maxLength];
226   for(int i = 0; i < nAttribs; i++)
227   {
228     gl->GetActiveAttrib(glProgram, i, maxLength, &written, &size, &type, name);
229     location = gl->GetAttribLocation(glProgram, name);
230
231     if(location >= 0)
232     {
233       if(maximumLocation < location)
234       {
235         maximumLocation = location;
236         // Increate continer size s.t. we can use maximumLocation as index.
237         mVertexInputAttributes.resize(maximumLocation + 1u);
238       }
239
240       AttributeInfo attributeInfo;
241       attributeInfo.location = location;
242       attributeInfo.name     = name;
243       attributeInfo.format   = GetVertexAttributeTypeFormat(type);
244       mVertexInputAttributes[location] = std::move(attributeInfo);
245     }
246   }
247
248   delete[] name;
249 }
250
251 void Reflection::BuildUniformReflection()
252 {
253   auto glProgram = mProgram.GetGlProgram();
254
255   int   maxLen;
256   char* name;
257
258   int numUniforms = 0;
259
260   auto gl = mController.GetGL();
261   if(!gl)
262   {
263     // Do nothing during shutdown
264     return;
265   }
266
267   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLen);
268   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORMS, &numUniforms);
269
270   mUniformBlocks.clear();
271   mDefaultUniformBlock.members.clear();
272   mUniformOpaques.clear();
273
274   name = new char[maxLen];
275
276   mStandaloneUniformExtraInfos.clear();
277
278   for(int i = 0; i < numUniforms; ++i)
279   {
280     int    elementCount;
281     GLenum type;
282     int    written;
283     gl->GetActiveUniform(glProgram, i, maxLen, &written, &elementCount, &type, name);
284     int location = gl->GetUniformLocation(glProgram, name);
285
286     Dali::Graphics::UniformInfo uniformInfo;
287
288     uniformInfo.name = name;
289     if(elementCount > 1)
290     {
291       auto iter = std::string(uniformInfo.name).find("[", 0);
292       if(iter != std::string::npos)
293       {
294         uniformInfo.name = std::string(name).substr(0, iter);
295       }
296     }
297
298     uniformInfo.uniformClass = IsSampler(type) ? Dali::Graphics::UniformClass::COMBINED_IMAGE_SAMPLER : Dali::Graphics::UniformClass::UNIFORM;
299     uniformInfo.location     = location; //IsSampler(type) ? 0 : location;
300     uniformInfo.binding      = 0;        // IsSampler(type) ? location : 0;
301     uniformInfo.bufferIndex  = 0;
302     uniformInfo.offset       = 0;
303
304     if(IsSampler(type))
305     {
306       mUniformOpaques.push_back(uniformInfo);
307     }
308     else
309     {
310       mDefaultUniformBlock.members.push_back(uniformInfo);
311       mStandaloneUniformExtraInfos.emplace_back(location, GetGLDataTypeSize(type), uniformInfo.offset, elementCount, type);
312     }
313   }
314
315   // Re-order according to uniform locations.
316
317   if(mDefaultUniformBlock.members.size() > 1)
318   {
319     std::sort(mDefaultUniformBlock.members.begin(), mDefaultUniformBlock.members.end(), SortUniformInfoByLocation);
320     std::sort(mStandaloneUniformExtraInfos.begin(), mStandaloneUniformExtraInfos.end(), SortUniformExtraInfoByLocation);
321   }
322
323   if(mUniformOpaques.size() > 1)
324   {
325     SortOpaques();
326   }
327
328   // Calculate the uniform offset
329   for(unsigned int i = 0; i < mDefaultUniformBlock.members.size(); ++i)
330   {
331     if(i == 0)
332     {
333       mDefaultUniformBlock.members[i].offset = 0;
334     }
335     else
336     {
337       uint32_t previousUniformLocation = mDefaultUniformBlock.members[i - 1].location;
338       auto     previousUniform         = std::find_if(mStandaloneUniformExtraInfos.begin(), mStandaloneUniformExtraInfos.end(), [&previousUniformLocation](const UniformExtraInfo& iter) { return iter.location == previousUniformLocation; });
339       if(previousUniform != mStandaloneUniformExtraInfos.end())
340       {
341         mDefaultUniformBlock.members[i].offset = mDefaultUniformBlock.members[i - 1].offset + (previousUniform->size * previousUniform->arraySize);
342         mStandaloneUniformExtraInfos[i].offset = mDefaultUniformBlock.members[i].offset;
343       }
344     }
345   }
346
347   if(mDefaultUniformBlock.members.size() > 0)
348   {
349     uint32_t lastUniformLocation = mDefaultUniformBlock.members.back().location;
350     auto     lastUniform         = std::find_if(mStandaloneUniformExtraInfos.begin(), mStandaloneUniformExtraInfos.end(), [&lastUniformLocation](const UniformExtraInfo& iter) { return iter.location == lastUniformLocation; });
351     if(lastUniform != mStandaloneUniformExtraInfos.end())
352     {
353       mDefaultUniformBlock.size = mDefaultUniformBlock.members.back().offset + (lastUniform->size * lastUniform->arraySize);
354       mUniformBlocks.push_back(mDefaultUniformBlock);
355     }
356   }
357   else
358   {
359     mDefaultUniformBlock.size = 0;
360   }
361
362   delete[] name;
363 }
364
365 // TODO: Maybe this is not needed if uniform block is not support by dali shaders?
366 void Reflection::BuildUniformBlockReflection()
367 {
368   auto gl               = mController.GetGL();
369   auto glProgram        = mProgram.GetGlProgram();
370   int  numUniformBlocks = 0;
371
372   if(!gl)
373   {
374     // Do nothing during shutdown
375     return;
376   }
377
378   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks);
379
380   mUniformBlocks.clear();
381   mUniformBlocks.resize(numUniformBlocks);
382
383   int uniformBlockMaxLength = 0;
384   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, &uniformBlockMaxLength);
385
386   char* uniformBlockName = new char[uniformBlockMaxLength];
387   for(int i = 0; i < numUniformBlocks; i++)
388   {
389     int length;
390     int blockBinding;
391     int blockDataSize;
392     gl->GetActiveUniformBlockName(glProgram, i, uniformBlockMaxLength, &length, uniformBlockName);
393     gl->GetActiveUniformBlockiv(glProgram, i, GL_UNIFORM_BLOCK_BINDING, &blockBinding);
394     gl->GetActiveUniformBlockiv(glProgram, i, GL_UNIFORM_BLOCK_DATA_SIZE, &blockDataSize);
395
396     Dali::Graphics::UniformBlockInfo uniformBlockInfo;
397     uniformBlockInfo.name    = uniformBlockName;
398     uniformBlockInfo.size    = blockDataSize;
399     uniformBlockInfo.binding = blockBinding;
400
401     int nUnis;
402     gl->GetActiveUniformBlockiv(glProgram, i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &nUnis);
403     int* unifIndexes = new GLint[nUnis];
404     gl->GetActiveUniformBlockiv(glProgram, i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, unifIndexes);
405     char* uniformName{};
406     int   maxUniLen;
407     gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniLen);
408
409     for(int unif = 0; unif < nUnis; ++unif)
410     {
411       int    uniIndex = unifIndexes[unif];
412       int    size;
413       GLenum type;
414
415       gl->GetActiveUniform(glProgram, uniIndex, maxUniLen, &length, &size, &type, uniformName);
416       int location = gl->GetUniformLocation(glProgram, uniformName);
417
418       Dali::Graphics::UniformInfo uniform;
419       uniform.name     = uniformName;
420       uniform.location = location;
421       uniformBlockInfo.members.push_back(uniform);
422     }
423
424     delete[] unifIndexes;
425
426     mUniformBlocks.push_back(uniformBlockInfo);
427   }
428   delete[] uniformBlockName;
429 }
430
431 uint32_t Reflection::GetVertexAttributeLocation(const std::string& name) const
432 {
433   for(auto&& attr : mVertexInputAttributes)
434   {
435     if(attr.name == name)
436     {
437       return attr.location;
438     }
439   }
440   return ERROR_ATTRIBUTE_NOT_FOUND;
441 }
442
443 Dali::Graphics::VertexInputAttributeFormat Reflection::GetVertexAttributeFormat(uint32_t location) const
444 {
445   if(location >= mVertexInputAttributes.size())
446   {
447     return Dali::Graphics::VertexInputAttributeFormat::UNDEFINED;
448   }
449
450   return mVertexInputAttributes[location].format;
451 }
452
453 std::string Reflection::GetVertexAttributeName(uint32_t location) const
454 {
455   if(location >= mVertexInputAttributes.size())
456   {
457     return std::string();
458   }
459
460   return mVertexInputAttributes[location].name;
461 }
462
463 std::vector<uint32_t> Reflection::GetVertexAttributeLocations() const
464 {
465   std::vector<uint32_t> locations;
466   for(auto&& attr : mVertexInputAttributes)
467   {
468     if(attr.format != Dali::Graphics::VertexInputAttributeFormat::UNDEFINED)
469     {
470       locations.push_back(attr.location);
471     }
472   }
473
474   return locations;
475 }
476
477 uint32_t Reflection::GetUniformBlockCount() const
478 {
479   return mUniformBlocks.size();
480 }
481
482 uint32_t Reflection::GetUniformBlockBinding(uint32_t index) const
483 {
484   return index < mUniformBlocks.size() ? mUniformBlocks[index].binding : 0u;
485 }
486
487 uint32_t Reflection::GetUniformBlockSize(uint32_t index) const
488 {
489   return index < mUniformBlocks.size() ? mUniformBlocks[index].size : 0u;
490 }
491
492 bool Reflection::GetUniformBlock(uint32_t index, Dali::Graphics::UniformBlockInfo& out) const
493 {
494   if(index >= mUniformBlocks.size())
495   {
496     return false;
497   }
498
499   const auto& block = mUniformBlocks[index];
500
501   out.name          = block.name;
502   out.binding       = block.binding;
503   out.descriptorSet = block.descriptorSet;
504   auto membersSize  = block.members.size();
505   out.members.resize(membersSize);
506   out.size = block.size;
507   for(auto i = 0u; i < out.members.size(); ++i)
508   {
509     const auto& memberUniform   = block.members[i];
510     out.members[i].name         = memberUniform.name;
511     out.members[i].binding      = block.binding;
512     out.members[i].uniformClass = Graphics::UniformClass::UNIFORM;
513     out.members[i].offset       = memberUniform.offset;
514     out.members[i].location     = memberUniform.location;
515   }
516
517   return true;
518 }
519
520 std::vector<uint32_t> Reflection::GetUniformBlockLocations() const
521 {
522   std::vector<uint32_t> retval{};
523   for(auto&& ubo : mUniformBlocks)
524   {
525     retval.emplace_back(ubo.binding);
526   }
527   return retval;
528 }
529
530 std::string Reflection::GetUniformBlockName(uint32_t blockIndex) const
531 {
532   if(blockIndex < mUniformBlocks.size())
533   {
534     return mUniformBlocks[blockIndex].name;
535   }
536   else
537   {
538     return std::string();
539   }
540 }
541
542 uint32_t Reflection::GetUniformBlockMemberCount(uint32_t blockIndex) const
543 {
544   if(blockIndex < mUniformBlocks.size())
545   {
546     return static_cast<uint32_t>(mUniformBlocks[blockIndex].members.size());
547   }
548   else
549   {
550     return 0u;
551   }
552 }
553
554 std::string Reflection::GetUniformBlockMemberName(uint32_t blockIndex, uint32_t memberLocation) const
555 {
556   if(blockIndex < mUniformBlocks.size() && memberLocation < mUniformBlocks[blockIndex].members.size())
557   {
558     return mUniformBlocks[blockIndex].members[memberLocation].name;
559   }
560   else
561   {
562     return std::string();
563   }
564 }
565
566 uint32_t Reflection::GetUniformBlockMemberOffset(uint32_t blockIndex, uint32_t memberLocation) const
567 {
568   if(blockIndex < mUniformBlocks.size() && memberLocation < mUniformBlocks[blockIndex].members.size())
569   {
570     return mUniformBlocks[blockIndex].members[memberLocation].offset;
571   }
572   else
573   {
574     return 0u;
575   }
576 }
577
578 bool Reflection::GetNamedUniform(const std::string& name, Dali::Graphics::UniformInfo& out) const
579 {
580   auto index = 0u;
581   for(auto&& ubo : mUniformBlocks)
582   {
583     for(auto&& member : ubo.members)
584     {
585       if(name == member.name || name == (ubo.name + "." + member.name))
586       {
587         out.name         = name;
588         out.location     = member.location;
589         out.binding      = ubo.binding;
590         out.bufferIndex  = index;
591         out.offset       = member.offset;
592         out.uniformClass = Graphics::UniformClass::UNIFORM;
593         return true;
594       }
595     }
596     ++index;
597   }
598
599   // check samplers
600   index = 0u;
601   for(auto&& uniform : mUniformOpaques)
602   {
603     if(uniform.name == name)
604     {
605       out.uniformClass = Graphics::UniformClass::COMBINED_IMAGE_SAMPLER;
606       out.binding      = 0;
607       out.name         = name;
608       out.offset       = index;            // lexical location in shader
609       out.location     = uniform.location; // uniform location mapping
610       return true;
611     }
612     ++index;
613   }
614
615   return false;
616 }
617
618 std::vector<GLenum> Reflection::GetStandaloneUniformTypes() const
619 {
620   std::vector<GLenum> retval{};
621   for(auto&& uniform : mStandaloneUniformExtraInfos)
622   {
623     retval.emplace_back(uniform.type);
624   }
625
626   return retval;
627 }
628
629 const std::vector<Reflection::UniformExtraInfo>& Reflection::GetStandaloneUniformExtraInfo() const
630 {
631   return mStandaloneUniformExtraInfos;
632 }
633
634 const std::vector<Dali::Graphics::UniformInfo>& Reflection::GetSamplers() const
635 {
636   return mUniformOpaques;
637 }
638
639 Graphics::ShaderLanguage Reflection::GetLanguage() const
640 {
641   auto version = Graphics::ShaderLanguage::GLSL_3_2;
642
643   auto gl = mController.GetGL();
644   if(!gl)
645   {
646     // Do nothing during shutdown
647     return version;
648   }
649
650   int majorVersion, minorVersion;
651   gl->GetIntegerv(GL_MAJOR_VERSION, &majorVersion);
652   gl->GetIntegerv(GL_MINOR_VERSION, &minorVersion);
653   DALI_LOG_RELEASE_INFO("GL Version (integer) : %d.%d\n", majorVersion, minorVersion);
654   DALI_LOG_RELEASE_INFO("GLSL Version : %s\n", gl->GetString(GL_SHADING_LANGUAGE_VERSION));
655
656   // TODO: the language version is hardcoded for now, but we may use what we get
657   // from GL_SHADING_LANGUAGE_VERSION?
658   return version;
659 }
660
661 void Reflection::SortOpaques()
662 {
663   //Determine declaration order of each sampler
664   auto& programCreateInfo = mProgram.GetCreateInfo();
665
666   std::vector<uint8_t> data;
667   std::string          vertShader;
668   std::string          fragShader;
669
670   for(auto& shaderState : *programCreateInfo.shaderState)
671   {
672     if(shaderState.pipelineStage == PipelineStage::VERTEX_SHADER)
673     {
674       vertShader = GetShaderSource(shaderState);
675     }
676     else if(shaderState.pipelineStage == PipelineStage::FRAGMENT_SHADER)
677     {
678       fragShader = GetShaderSource(shaderState);
679     }
680   }
681
682   int              samplerPosition = 0;
683   std::vector<int> samplerPositions(mUniformOpaques.size(), -1);
684
685   ParseShaderSamplers(vertShader, mUniformOpaques, samplerPosition, samplerPositions);
686   ParseShaderSamplers(fragShader, mUniformOpaques, samplerPosition, samplerPositions);
687
688   std::sort(mUniformOpaques.begin(), mUniformOpaques.end(), [](const UniformInfo& a, const UniformInfo& b) { return a.offset < b.offset; });
689 }
690
691 } // namespace Dali::Graphics::GLES