b7ad58cd220620bb79f4e56c3d311f4a4003c650
[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 maxLocationValue = 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(maxLocationValue < location)
234       {
235         maxLocationValue = location;
236         mVertexInputAttributes.resize(maxLocationValue + 1u);
237       }
238
239       AttributeInfo attributeInfo;
240       attributeInfo.location = location;
241       attributeInfo.name     = name;
242       attributeInfo.format   = GetVertexAttributeTypeFormat(type);
243       mVertexInputAttributes[location] = std::move(attributeInfo);
244     }
245   }
246
247   delete[] name;
248 }
249
250 void Reflection::BuildUniformReflection()
251 {
252   auto glProgram = mProgram.GetGlProgram();
253
254   int   maxLen;
255   char* name;
256
257   int numUniforms = 0;
258
259   auto gl = mController.GetGL();
260   if(!gl)
261   {
262     // Do nothing during shutdown
263     return;
264   }
265
266   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLen);
267   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORMS, &numUniforms);
268
269   mUniformBlocks.clear();
270   mDefaultUniformBlock.members.clear();
271   mUniformOpaques.clear();
272
273   name = new char[maxLen];
274
275   mStandaloneUniformExtraInfos.clear();
276
277   for(int i = 0; i < numUniforms; ++i)
278   {
279     int    elementCount;
280     GLenum type;
281     int    written;
282     gl->GetActiveUniform(glProgram, i, maxLen, &written, &elementCount, &type, name);
283     int location = gl->GetUniformLocation(glProgram, name);
284
285     Dali::Graphics::UniformInfo uniformInfo;
286
287     uniformInfo.name = name;
288     if(elementCount > 1)
289     {
290       auto iter = std::string(uniformInfo.name).find("[", 0);
291       if(iter != std::string::npos)
292       {
293         uniformInfo.name = std::string(name).substr(0, iter);
294       }
295     }
296
297     uniformInfo.uniformClass = IsSampler(type) ? Dali::Graphics::UniformClass::COMBINED_IMAGE_SAMPLER : Dali::Graphics::UniformClass::UNIFORM;
298     uniformInfo.location     = location; //IsSampler(type) ? 0 : location;
299     uniformInfo.binding      = 0;        // IsSampler(type) ? location : 0;
300     uniformInfo.bufferIndex  = 0;
301     uniformInfo.offset       = 0;
302
303     if(IsSampler(type))
304     {
305       mUniformOpaques.push_back(uniformInfo);
306     }
307     else
308     {
309       mDefaultUniformBlock.members.push_back(uniformInfo);
310       mStandaloneUniformExtraInfos.emplace_back(location, GetGLDataTypeSize(type), uniformInfo.offset, elementCount, type);
311     }
312   }
313
314   // Re-order according to uniform locations.
315
316   if(mDefaultUniformBlock.members.size() > 1)
317   {
318     std::sort(mDefaultUniformBlock.members.begin(), mDefaultUniformBlock.members.end(), SortUniformInfoByLocation);
319     std::sort(mStandaloneUniformExtraInfos.begin(), mStandaloneUniformExtraInfos.end(), SortUniformExtraInfoByLocation);
320   }
321
322   if(mUniformOpaques.size() > 1)
323   {
324     SortOpaques();
325   }
326
327   // Calculate the uniform offset
328   for(unsigned int i = 0; i < mDefaultUniformBlock.members.size(); ++i)
329   {
330     if(i == 0)
331     {
332       mDefaultUniformBlock.members[i].offset = 0;
333     }
334     else
335     {
336       uint32_t previousUniformLocation = mDefaultUniformBlock.members[i - 1].location;
337       auto     previousUniform         = std::find_if(mStandaloneUniformExtraInfos.begin(), mStandaloneUniformExtraInfos.end(), [&previousUniformLocation](const UniformExtraInfo& iter) { return iter.location == previousUniformLocation; });
338       if(previousUniform != mStandaloneUniformExtraInfos.end())
339       {
340         mDefaultUniformBlock.members[i].offset = mDefaultUniformBlock.members[i - 1].offset + (previousUniform->size * previousUniform->arraySize);
341         mStandaloneUniformExtraInfos[i].offset = mDefaultUniformBlock.members[i].offset;
342       }
343     }
344   }
345
346   if(mDefaultUniformBlock.members.size() > 0)
347   {
348     uint32_t lastUniformLocation = mDefaultUniformBlock.members.back().location;
349     auto     lastUniform         = std::find_if(mStandaloneUniformExtraInfos.begin(), mStandaloneUniformExtraInfos.end(), [&lastUniformLocation](const UniformExtraInfo& iter) { return iter.location == lastUniformLocation; });
350     if(lastUniform != mStandaloneUniformExtraInfos.end())
351     {
352       mDefaultUniformBlock.size = mDefaultUniformBlock.members.back().offset + (lastUniform->size * lastUniform->arraySize);
353       mUniformBlocks.push_back(mDefaultUniformBlock);
354     }
355   }
356   else
357   {
358     mDefaultUniformBlock.size = 0;
359   }
360
361   delete[] name;
362 }
363
364 // TODO: Maybe this is not needed if uniform block is not support by dali shaders?
365 void Reflection::BuildUniformBlockReflection()
366 {
367   auto gl               = mController.GetGL();
368   auto glProgram        = mProgram.GetGlProgram();
369   int  numUniformBlocks = 0;
370
371   if(!gl)
372   {
373     // Do nothing during shutdown
374     return;
375   }
376
377   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks);
378
379   mUniformBlocks.clear();
380   mUniformBlocks.resize(numUniformBlocks);
381
382   int uniformBlockMaxLength = 0;
383   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, &uniformBlockMaxLength);
384
385   char* uniformBlockName = new char[uniformBlockMaxLength];
386   for(int i = 0; i < numUniformBlocks; i++)
387   {
388     int length;
389     int blockBinding;
390     int blockDataSize;
391     gl->GetActiveUniformBlockName(glProgram, i, uniformBlockMaxLength, &length, uniformBlockName);
392     gl->GetActiveUniformBlockiv(glProgram, i, GL_UNIFORM_BLOCK_BINDING, &blockBinding);
393     gl->GetActiveUniformBlockiv(glProgram, i, GL_UNIFORM_BLOCK_DATA_SIZE, &blockDataSize);
394
395     Dali::Graphics::UniformBlockInfo uniformBlockInfo;
396     uniformBlockInfo.name    = uniformBlockName;
397     uniformBlockInfo.size    = blockDataSize;
398     uniformBlockInfo.binding = blockBinding;
399
400     int nUnis;
401     gl->GetActiveUniformBlockiv(glProgram, i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &nUnis);
402     int* unifIndexes = new GLint[nUnis];
403     gl->GetActiveUniformBlockiv(glProgram, i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, unifIndexes);
404     char* uniformName{};
405     int   maxUniLen;
406     gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniLen);
407
408     for(int unif = 0; unif < nUnis; ++unif)
409     {
410       int    uniIndex = unifIndexes[unif];
411       int    size;
412       GLenum type;
413
414       gl->GetActiveUniform(glProgram, uniIndex, maxUniLen, &length, &size, &type, uniformName);
415       int location = gl->GetUniformLocation(glProgram, uniformName);
416
417       Dali::Graphics::UniformInfo uniform;
418       uniform.name     = uniformName;
419       uniform.location = location;
420       uniformBlockInfo.members.push_back(uniform);
421     }
422
423     delete[] unifIndexes;
424
425     mUniformBlocks.push_back(uniformBlockInfo);
426   }
427   delete[] uniformBlockName;
428 }
429
430 uint32_t Reflection::GetVertexAttributeLocation(const std::string& name) const
431 {
432   for(auto&& attr : mVertexInputAttributes)
433   {
434     if(attr.name == name)
435     {
436       return attr.location;
437     }
438   }
439   return ERROR_ATTRIBUTE_NOT_FOUND;
440 }
441
442 Dali::Graphics::VertexInputAttributeFormat Reflection::GetVertexAttributeFormat(uint32_t location) const
443 {
444   if(location >= mVertexInputAttributes.size())
445   {
446     return Dali::Graphics::VertexInputAttributeFormat::UNDEFINED;
447   }
448
449   return mVertexInputAttributes[location].format;
450 }
451
452 std::string Reflection::GetVertexAttributeName(uint32_t location) const
453 {
454   if(location >= mVertexInputAttributes.size())
455   {
456     return std::string();
457   }
458
459   return mVertexInputAttributes[location].name;
460 }
461
462 std::vector<uint32_t> Reflection::GetVertexAttributeLocations() const
463 {
464   std::vector<uint32_t> locations;
465   for(auto&& attr : mVertexInputAttributes)
466   {
467     if(attr.format != Dali::Graphics::VertexInputAttributeFormat::UNDEFINED)
468     {
469       locations.push_back(attr.location);
470     }
471   }
472
473   return locations;
474 }
475
476 uint32_t Reflection::GetUniformBlockCount() const
477 {
478   return mUniformBlocks.size();
479 }
480
481 uint32_t Reflection::GetUniformBlockBinding(uint32_t index) const
482 {
483   return index < mUniformBlocks.size() ? mUniformBlocks[index].binding : 0u;
484 }
485
486 uint32_t Reflection::GetUniformBlockSize(uint32_t index) const
487 {
488   return index < mUniformBlocks.size() ? mUniformBlocks[index].size : 0u;
489 }
490
491 bool Reflection::GetUniformBlock(uint32_t index, Dali::Graphics::UniformBlockInfo& out) const
492 {
493   if(index >= mUniformBlocks.size())
494   {
495     return false;
496   }
497
498   const auto& block = mUniformBlocks[index];
499
500   out.name          = block.name;
501   out.binding       = block.binding;
502   out.descriptorSet = block.descriptorSet;
503   auto membersSize  = block.members.size();
504   out.members.resize(membersSize);
505   out.size = block.size;
506   for(auto i = 0u; i < out.members.size(); ++i)
507   {
508     const auto& memberUniform   = block.members[i];
509     out.members[i].name         = memberUniform.name;
510     out.members[i].binding      = block.binding;
511     out.members[i].uniformClass = Graphics::UniformClass::UNIFORM;
512     out.members[i].offset       = memberUniform.offset;
513     out.members[i].location     = memberUniform.location;
514   }
515
516   return true;
517 }
518
519 std::vector<uint32_t> Reflection::GetUniformBlockLocations() const
520 {
521   std::vector<uint32_t> retval{};
522   for(auto&& ubo : mUniformBlocks)
523   {
524     retval.emplace_back(ubo.binding);
525   }
526   return retval;
527 }
528
529 std::string Reflection::GetUniformBlockName(uint32_t blockIndex) const
530 {
531   if(blockIndex < mUniformBlocks.size())
532   {
533     return mUniformBlocks[blockIndex].name;
534   }
535   else
536   {
537     return std::string();
538   }
539 }
540
541 uint32_t Reflection::GetUniformBlockMemberCount(uint32_t blockIndex) const
542 {
543   if(blockIndex < mUniformBlocks.size())
544   {
545     return static_cast<uint32_t>(mUniformBlocks[blockIndex].members.size());
546   }
547   else
548   {
549     return 0u;
550   }
551 }
552
553 std::string Reflection::GetUniformBlockMemberName(uint32_t blockIndex, uint32_t memberLocation) const
554 {
555   if(blockIndex < mUniformBlocks.size() && memberLocation < mUniformBlocks[blockIndex].members.size())
556   {
557     return mUniformBlocks[blockIndex].members[memberLocation].name;
558   }
559   else
560   {
561     return std::string();
562   }
563 }
564
565 uint32_t Reflection::GetUniformBlockMemberOffset(uint32_t blockIndex, uint32_t memberLocation) const
566 {
567   if(blockIndex < mUniformBlocks.size() && memberLocation < mUniformBlocks[blockIndex].members.size())
568   {
569     return mUniformBlocks[blockIndex].members[memberLocation].offset;
570   }
571   else
572   {
573     return 0u;
574   }
575 }
576
577 bool Reflection::GetNamedUniform(const std::string& name, Dali::Graphics::UniformInfo& out) const
578 {
579   auto index = 0u;
580   for(auto&& ubo : mUniformBlocks)
581   {
582     for(auto&& member : ubo.members)
583     {
584       if(name == member.name || name == (ubo.name + "." + member.name))
585       {
586         out.name         = name;
587         out.location     = member.location;
588         out.binding      = ubo.binding;
589         out.bufferIndex  = index;
590         out.offset       = member.offset;
591         out.uniformClass = Graphics::UniformClass::UNIFORM;
592         return true;
593       }
594     }
595     ++index;
596   }
597
598   // check samplers
599   index = 0u;
600   for(auto&& uniform : mUniformOpaques)
601   {
602     if(uniform.name == name)
603     {
604       out.uniformClass = Graphics::UniformClass::COMBINED_IMAGE_SAMPLER;
605       out.binding      = 0;
606       out.name         = name;
607       out.offset       = index;            // lexical location in shader
608       out.location     = uniform.location; // uniform location mapping
609       return true;
610     }
611     ++index;
612   }
613
614   return false;
615 }
616
617 std::vector<GLenum> Reflection::GetStandaloneUniformTypes() const
618 {
619   std::vector<GLenum> retval{};
620   for(auto&& uniform : mStandaloneUniformExtraInfos)
621   {
622     retval.emplace_back(uniform.type);
623   }
624
625   return retval;
626 }
627
628 const std::vector<Reflection::UniformExtraInfo>& Reflection::GetStandaloneUniformExtraInfo() const
629 {
630   return mStandaloneUniformExtraInfos;
631 }
632
633 const std::vector<Dali::Graphics::UniformInfo>& Reflection::GetSamplers() const
634 {
635   return mUniformOpaques;
636 }
637
638 Graphics::ShaderLanguage Reflection::GetLanguage() const
639 {
640   auto version = Graphics::ShaderLanguage::GLSL_3_2;
641
642   auto gl = mController.GetGL();
643   if(!gl)
644   {
645     // Do nothing during shutdown
646     return version;
647   }
648
649   int majorVersion, minorVersion;
650   gl->GetIntegerv(GL_MAJOR_VERSION, &majorVersion);
651   gl->GetIntegerv(GL_MINOR_VERSION, &minorVersion);
652   DALI_LOG_RELEASE_INFO("GL Version (integer) : %d.%d\n", majorVersion, minorVersion);
653   DALI_LOG_RELEASE_INFO("GLSL Version : %s\n", gl->GetString(GL_SHADING_LANGUAGE_VERSION));
654
655   // TODO: the language version is hardcoded for now, but we may use what we get
656   // from GL_SHADING_LANGUAGE_VERSION?
657   return version;
658 }
659
660 void Reflection::SortOpaques()
661 {
662   //Determine declaration order of each sampler
663   auto& programCreateInfo = mProgram.GetCreateInfo();
664
665   std::vector<uint8_t> data;
666   std::string          vertShader;
667   std::string          fragShader;
668
669   for(auto& shaderState : *programCreateInfo.shaderState)
670   {
671     if(shaderState.pipelineStage == PipelineStage::VERTEX_SHADER)
672     {
673       vertShader = GetShaderSource(shaderState);
674     }
675     else if(shaderState.pipelineStage == PipelineStage::FRAGMENT_SHADER)
676     {
677       fragShader = GetShaderSource(shaderState);
678     }
679   }
680
681   int              samplerPosition = 0;
682   std::vector<int> samplerPositions(mUniformOpaques.size(), -1);
683
684   ParseShaderSamplers(vertShader, mUniformOpaques, samplerPosition, samplerPositions);
685   ParseShaderSamplers(fragShader, mUniformOpaques, samplerPosition, samplerPositions);
686
687   std::sort(mUniformOpaques.begin(), mUniformOpaques.end(), [](const UniformInfo& a, const UniformInfo& b) { return a.offset < b.offset; });
688 }
689
690 } // namespace Dali::Graphics::GLES