[dali_2.3.23] Merge branch 'devel/master'
[platform/core/uifw/dali-adaptor.git] / dali / internal / graphics / gles-impl / gles-graphics-reflection.cpp
1 /*
2  * Copyright (c) 2024 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 #if defined(DEBUG_ENABLED)
37 Debug::Filter* gGraphicsReflectionLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_GRAPHICS_REFLECTION");
38 #endif
39
40 struct StringSize
41 {
42   const char* const mString;
43   const uint32_t    mLength;
44
45   template<uint32_t kLength>
46   constexpr StringSize(const char (&string)[kLength])
47   : mString(string),
48     mLength(kLength - 1) // remove terminating null; N.B. there should be no other null.
49   {
50   }
51
52   operator const char*() const
53   {
54     return mString;
55   }
56 };
57
58 bool operator==(const StringSize& lhs, const char* rhs)
59 {
60   return strncmp(lhs.mString, rhs, lhs.mLength) == 0;
61 }
62
63 const char* const    DELIMITERS           = " \t\n";
64 const char* const    DELIMITERS_INC_INDEX = " \t\n[]";
65 constexpr StringSize UNIFORM{"uniform"};
66 constexpr StringSize SAMPLER_PREFIX{"sampler"};
67 constexpr StringSize SAMPLER_TYPES[]   = {"2D", "Cube", "ExternalOES"};
68 constexpr auto       END_SAMPLER_TYPES = SAMPLER_TYPES + std::extent<decltype(SAMPLER_TYPES)>::value;
69
70 Dali::Graphics::VertexInputAttributeFormat GetVertexAttributeTypeFormat(GLenum type)
71 {
72   switch(type)
73   {
74     case GL_FLOAT:
75       return Dali::Graphics::VertexInputAttributeFormat::FLOAT;
76     case GL_FLOAT_VEC2:
77       return Dali::Graphics::VertexInputAttributeFormat::VEC2;
78     case GL_FLOAT_VEC3:
79       return Dali::Graphics::VertexInputAttributeFormat::VEC3;
80     case GL_FLOAT_VEC4:
81       return Dali::Graphics::VertexInputAttributeFormat::VEC4;
82     case GL_INT:
83       return Dali::Graphics::VertexInputAttributeFormat::INTEGER;
84     default:
85       return Dali::Graphics::VertexInputAttributeFormat::UNDEFINED;
86   }
87 }
88
89 uint32_t GetGLDataTypeSize(GLenum type)
90 {
91   // There are many more types than what are covered here, but
92   // they are not supported in dali.
93   switch(type)
94   {
95     case GL_FLOAT: // "float", 1 float, 4 bytes
96       return 4;
97     case GL_FLOAT_VEC2: // "vec2", 2 floats, 8 bytes
98       return 8;
99     case GL_FLOAT_VEC3: // "vec3", 3 floats, 12 bytes
100       return 12;
101     case GL_FLOAT_VEC4: // "vec4", 4 floats, 16 bytes
102       return 16;
103     case GL_INT: // "int", 1 integer, 4 bytes
104       return 4;
105     case GL_FLOAT_MAT2: // "mat2", 4 floats, 16 bytes
106       return 16;
107     case GL_FLOAT_MAT3: // "mat3", 3 vec3, 36 bytes
108       return 36;
109     case GL_FLOAT_MAT4: // "mat4", 4 vec4, 64 bytes
110       return 64;
111     default:
112       return 0;
113   }
114 }
115
116 bool IsSampler(GLenum type)
117 {
118   return type == GL_SAMPLER_2D || type == GL_SAMPLER_3D || type == GL_SAMPLER_CUBE || type == GL_SAMPLER_EXTERNAL_OES;
119 }
120
121 std::string GetShaderSource(Dali::Graphics::ShaderState shaderState)
122 {
123   std::vector<uint8_t> data;
124   auto*                shader           = static_cast<const Dali::Graphics::GLES::Shader*>(shaderState.shader);
125   auto&                shaderCreateInfo = shader->GetCreateInfo();
126   data.resize(shaderCreateInfo.sourceSize + 1);
127   std::memcpy(&data[0], shaderCreateInfo.sourceData, shaderCreateInfo.sourceSize);
128   data[shaderCreateInfo.sourceSize] = 0;
129
130   return std::string(reinterpret_cast<char*>(&data[0]));
131 }
132
133 void ParseShaderSamplers(std::string shaderSource, std::vector<Dali::Graphics::UniformInfo>& uniformOpaques, int& samplerPosition, std::vector<int>& samplerPositions)
134 {
135   if(!shaderSource.empty())
136   {
137     char* shaderStr = strdup(shaderSource.c_str());
138     char* uniform   = strstr(shaderStr, UNIFORM);
139
140     while(uniform)
141     {
142       // From "uniform" to ";", not ignoring comments.
143       char* outerToken = strtok_r(uniform + UNIFORM.mLength, ";", &uniform);
144
145       char* nextPtr = nullptr;
146       char* token   = strtok_r(outerToken, DELIMITERS, &nextPtr);
147       while(token)
148       {
149         // Ignore any token up to "sampler"
150         if(SAMPLER_PREFIX == token)
151         {
152           token += SAMPLER_PREFIX.mLength;
153           if(std::find(SAMPLER_TYPES, END_SAMPLER_TYPES, token) != END_SAMPLER_TYPES)
154           {
155             bool found(false);
156             // We now are at next token after "samplerxxx" in outerToken token "stream"
157
158             // Does it use array notation?
159             int  arraySize = 0; // 0 = No array
160             auto iter      = std::string(token).find("[", 0);
161             if(iter != std::string::npos)
162             {
163               // Get Array size from source. (Warning, may be higher than GetActiveUniform suggests)
164               iter++;
165               arraySize = int(strtol(token + iter, nullptr, 0));
166             }
167
168             token = strtok_r(nullptr, DELIMITERS_INC_INDEX, &nextPtr); // " ", "\t", "\n", "[", "]"
169
170             for(uint32_t i = 0; i < static_cast<uint32_t>(uniformOpaques.size()); ++i)
171             {
172               if(samplerPositions[i] == -1 &&
173                  strncmp(token, uniformOpaques[i].name.c_str(), uniformOpaques[i].name.size()) == 0)
174               {
175                 // We have found a matching name.
176                 samplerPositions[i] = uniformOpaques[i].offset = samplerPosition;
177                 if(arraySize == 0)
178                 {
179                   ++samplerPosition;
180                 }
181                 else
182                 {
183                   samplerPosition += arraySize;
184                 }
185                 found = true;
186                 break;
187               }
188             }
189
190             if(!found)
191             {
192               DALI_LOG_INFO(gGraphicsReflectionLogFilter, Debug::General, "Sampler uniform %s declared but not used in the shader\n", token);
193             }
194             break;
195           }
196         }
197
198         token = strtok_r(nullptr, DELIMITERS, &nextPtr);
199       }
200
201       uniform = strstr(uniform, UNIFORM);
202     }
203     free(shaderStr);
204   }
205 }
206
207 } // anonymous namespace
208
209 namespace Dali::Graphics::GLES
210 {
211 Reflection::Reflection(GLES::ProgramImpl& program, Graphics::EglGraphicsController& controller)
212 : Graphics::Reflection(),
213   mController(controller),
214   mProgram(program)
215 {
216 }
217
218 Reflection::~Reflection() = default;
219
220 void Reflection::BuildVertexAttributeReflection()
221 {
222   auto glProgram = mProgram.GetGlProgram();
223
224   int    written, size, location, maxLength, nAttribs;
225   GLenum type;
226   char*  name;
227
228   auto gl = mController.GetGL();
229   if(!gl)
230   {
231     // Do nothing during shutdown
232     return;
233   }
234
235   DALI_LOG_INFO(gGraphicsReflectionLogFilter, Debug::General, "Build vertex attribute reflection for glProgram : %u\n", glProgram);
236
237   gl->GetProgramiv(glProgram, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxLength);
238   gl->GetProgramiv(glProgram, GL_ACTIVE_ATTRIBUTES, &nAttribs);
239
240   mVertexInputAttributes.clear();
241   mVertexInputAttributes.resize(nAttribs);
242
243   int maximumLocation = nAttribs - 1;
244
245   name = new GLchar[maxLength];
246   for(int i = 0; i < nAttribs; i++)
247   {
248     gl->GetActiveAttrib(glProgram, i, maxLength, &written, &size, &type, name);
249     location = gl->GetAttribLocation(glProgram, name);
250
251     if(location >= 0)
252     {
253       if(maximumLocation < location)
254       {
255         maximumLocation = location;
256         // Increate continer size s.t. we can use maximumLocation as index.
257         mVertexInputAttributes.resize(maximumLocation + 1u);
258       }
259
260       AttributeInfo attributeInfo;
261       attributeInfo.location           = location;
262       attributeInfo.name               = name;
263       attributeInfo.format             = GetVertexAttributeTypeFormat(type);
264       mVertexInputAttributes[location] = std::move(attributeInfo);
265     }
266   }
267
268   delete[] name;
269 }
270
271 void Reflection::BuildUniformBlockReflection()
272 {
273   auto  gl        = mController.GetGL();
274   auto  glProgram = mProgram.GetGlProgram();
275   char* name;
276   int   numUniformBlocks = 0;
277
278   if(!gl)
279   {
280     // Do nothing during shutdown
281     return;
282   }
283
284   DALI_LOG_INFO(gGraphicsReflectionLogFilter, Debug::General, "Build uniform block reflection for glProgram : %u\n", glProgram);
285
286   int   maxUniformNameLength;
287   GLint activeUniformCount = 0;
288   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORM_BLOCKS, &numUniformBlocks);
289   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORMS, &activeUniformCount);
290   gl->GetProgramiv(glProgram, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniformNameLength);
291
292   numUniformBlocks++; // add block 0 for standalone UBO block
293
294   mUniformBlocks.resize(numUniformBlocks);
295   mUniformOpaques.clear();
296
297   mStandaloneUniformExtraInfos.clear();
298
299   std::vector<GLuint> uniformIndices;
300   uniformIndices.reserve(activeUniformCount);
301   for(auto i = 0; i < activeUniformCount; ++i)
302   {
303     uniformIndices.emplace_back(i);
304   }
305
306   // Obtain all parameters for active uniforms
307   auto getActiveUniformParams = [gl, glProgram, uniformIndices](GLenum param) {
308     std::vector<GLint> params;
309     params.resize(uniformIndices.size());
310     gl->GetActiveUniformsiv(glProgram, uniformIndices.size(), uniformIndices.data(), param, params.data());
311     return params;
312   };
313
314   auto activeUniformType       = getActiveUniformParams(GL_UNIFORM_TYPE);
315   auto activeUniformSize       = getActiveUniformParams(GL_UNIFORM_SIZE);
316   auto activeUniformNameLength = getActiveUniformParams(GL_UNIFORM_NAME_LENGTH);
317   auto activeUniformBlockIndex = getActiveUniformParams(GL_UNIFORM_BLOCK_INDEX);
318   auto activeUniformOffset     = getActiveUniformParams(GL_UNIFORM_OFFSET);
319
320   // Extract only uniform blocks and collect data
321   // collect samplers into separate array
322   std::vector<UniformInfo> samplers;
323
324   name = new char[maxUniformNameLength + 1];
325
326   for(auto i = 0u; i < activeUniformBlockIndex.size(); ++i)
327   {
328     GLenum type;
329     GLint  elementCount;
330     GLint  written;
331     gl->GetActiveUniform(glProgram, i, maxUniformNameLength, &written, &elementCount, &type, name);
332
333     auto location = gl->GetUniformLocation(glProgram, name);
334
335     UniformInfo* uniformInfo{nullptr};
336     if(IsSampler(activeUniformType[i]))
337     {
338       samplers.emplace_back();
339       uniformInfo               = &samplers.back();
340       uniformInfo->uniformClass = UniformClass::COMBINED_IMAGE_SAMPLER;
341     }
342     else
343     {
344       auto  blockIndex = activeUniformBlockIndex[i] + 1;
345       auto& members    = mUniformBlocks[blockIndex].members;
346       members.emplace_back();
347       uniformInfo               = &members.back();
348       uniformInfo->uniformClass = UniformClass::UNIFORM;
349       uniformInfo->binding      = 0;
350       uniformInfo->bufferIndex  = blockIndex;
351       uniformInfo->binding      = blockIndex == 0 ? i : 0; // this will be reset later
352       uniformInfo->offset       = activeUniformOffset[i];
353     }
354
355     uniformInfo->location = location; // location must be set later and sorted by offset
356     uniformInfo->name     = name;
357
358     // Strip off array index from name, use element count instead
359     if(elementCount > 1)
360     {
361       std::string uniformName = name;
362       auto        iter        = uniformName.find('[', 0);
363       if(iter != std::string::npos)
364       {
365         uniformInfo->name         = uniformName.substr(0, iter);
366         uniformInfo->elementCount = elementCount;
367       }
368     }
369   }
370   delete[] name;
371
372   // Sort by offset
373   uint32_t blockIndex = 0;
374   for(auto& ubo : mUniformBlocks)
375   {
376     std::sort(ubo.members.begin(), ubo.members.end(), [](auto& lhs, auto& rhs) {
377       return lhs.offset < rhs.offset;
378     });
379
380     if(blockIndex > 0)
381     {
382       GLint uboSize;
383       GLint blockNameLength;
384       gl->GetActiveUniformBlockiv(glProgram, blockIndex - 1, GL_UNIFORM_BLOCK_DATA_SIZE, &uboSize);
385       gl->GetActiveUniformBlockiv(glProgram, blockIndex - 1, GL_UNIFORM_BLOCK_NAME_LENGTH, &blockNameLength);
386       char* blockName = new char[blockNameLength];
387       gl->GetActiveUniformBlockName(glProgram, blockIndex - 1, blockNameLength, nullptr, blockName);
388       ubo.name = blockName;
389       ubo.size = uboSize;
390       delete[] blockName;
391     }
392     else
393     {
394       ubo.name = "";
395       ubo.size = 0; // to compute later
396     }
397     ubo.binding       = 0;
398     ubo.descriptorSet = 0;
399     blockIndex++;
400   }
401
402   // count uniform size
403   auto& defaultUniformBlock = mUniformBlocks[0]; // Standalone block
404   defaultUniformBlock.size  = 0;
405
406   mStandaloneUniformExtraInfos.reserve(defaultUniformBlock.members.size());
407   for(auto& member : defaultUniformBlock.members)
408   {
409     auto& type         = activeUniformType[member.binding];
410     auto  dataTypeSize = GetGLDataTypeSize(type);
411     member.offset      = defaultUniformBlock.size;
412     defaultUniformBlock.size += (dataTypeSize * activeUniformSize[member.binding]);
413
414     UniformExtraInfo extraInfo{};
415     extraInfo.location  = member.location;
416     extraInfo.size      = (dataTypeSize);
417     extraInfo.offset    = member.offset;
418     extraInfo.arraySize = activeUniformSize[member.binding];
419     extraInfo.type      = activeUniformType[member.binding];
420     member.binding      = 0;
421     mStandaloneUniformExtraInfos.emplace_back(extraInfo);
422   }
423
424   mUniformOpaques = samplers;
425   SortOpaques();
426 }
427
428 uint32_t Reflection::GetVertexAttributeLocation(const std::string& name) const
429 {
430   DALI_LOG_INFO(gGraphicsReflectionLogFilter, Debug::Verbose, "name : %s\n", name.c_str());
431   for(auto&& attr : mVertexInputAttributes)
432   {
433     if(attr.name == name)
434     {
435       return attr.location;
436     }
437   }
438   return ERROR_ATTRIBUTE_NOT_FOUND;
439 }
440
441 Dali::Graphics::VertexInputAttributeFormat Reflection::GetVertexAttributeFormat(uint32_t location) const
442 {
443   DALI_LOG_INFO(gGraphicsReflectionLogFilter, Debug::Verbose, "location : %u\n", location);
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   DALI_LOG_INFO(gGraphicsReflectionLogFilter, Debug::Verbose, "location : %u\n", location);
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     out.members[i].elementCount = memberUniform.elementCount;
516   }
517
518   return true;
519 }
520
521 std::vector<uint32_t> Reflection::GetUniformBlockLocations() const
522 {
523   std::vector<uint32_t> retval{};
524   for(auto&& ubo : mUniformBlocks)
525   {
526     retval.emplace_back(ubo.binding);
527   }
528   return retval;
529 }
530
531 std::string Reflection::GetUniformBlockName(uint32_t blockIndex) const
532 {
533   if(blockIndex < mUniformBlocks.size())
534   {
535     return mUniformBlocks[blockIndex].name;
536   }
537   else
538   {
539     return std::string();
540   }
541 }
542
543 uint32_t Reflection::GetUniformBlockMemberCount(uint32_t blockIndex) const
544 {
545   if(blockIndex < mUniformBlocks.size())
546   {
547     return static_cast<uint32_t>(mUniformBlocks[blockIndex].members.size());
548   }
549   else
550   {
551     return 0u;
552   }
553 }
554
555 std::string Reflection::GetUniformBlockMemberName(uint32_t blockIndex, uint32_t memberLocation) const
556 {
557   if(blockIndex < mUniformBlocks.size() && memberLocation < mUniformBlocks[blockIndex].members.size())
558   {
559     return mUniformBlocks[blockIndex].members[memberLocation].name;
560   }
561   else
562   {
563     return std::string();
564   }
565 }
566
567 uint32_t Reflection::GetUniformBlockMemberOffset(uint32_t blockIndex, uint32_t memberLocation) const
568 {
569   if(blockIndex < mUniformBlocks.size() && memberLocation < mUniformBlocks[blockIndex].members.size())
570   {
571     return mUniformBlocks[blockIndex].members[memberLocation].offset;
572   }
573   else
574   {
575     return 0u;
576   }
577 }
578
579 bool Reflection::GetNamedUniform(const std::string& name, Dali::Graphics::UniformInfo& out) const
580 {
581   auto index = 0u;
582   for(auto&& ubo : mUniformBlocks)
583   {
584     for(auto&& member : ubo.members)
585     {
586       if(name == member.name || name == (ubo.name + "." + member.name))
587       {
588         out.name         = name;
589         out.location     = member.location;
590         out.binding      = ubo.binding;
591         out.bufferIndex  = index;
592         out.offset       = member.offset;
593         out.uniformClass = Graphics::UniformClass::UNIFORM;
594         return true;
595       }
596     }
597     ++index;
598   }
599
600   // check samplers
601   index = 0u;
602   for(auto&& uniform : mUniformOpaques)
603   {
604     if(uniform.name == name)
605     {
606       out.uniformClass = Graphics::UniformClass::COMBINED_IMAGE_SAMPLER;
607       out.binding      = 0;
608       out.name         = name;
609       out.offset       = index;            // lexical location in shader
610       out.location     = uniform.location; // uniform location mapping
611       return true;
612     }
613     ++index;
614   }
615
616   return false;
617 }
618
619 std::vector<GLenum> Reflection::GetStandaloneUniformTypes() const
620 {
621   std::vector<GLenum> retval{};
622   for(auto&& uniform : mStandaloneUniformExtraInfos)
623   {
624     retval.emplace_back(uniform.type);
625   }
626
627   return retval;
628 }
629
630 const std::vector<Reflection::UniformExtraInfo>& Reflection::GetStandaloneUniformExtraInfo() const
631 {
632   return mStandaloneUniformExtraInfos;
633 }
634
635 const std::vector<Dali::Graphics::UniformInfo>& Reflection::GetSamplers() const
636 {
637   return mUniformOpaques;
638 }
639
640 Graphics::ShaderLanguage Reflection::GetLanguage() const
641 {
642   auto version = Graphics::ShaderLanguage::GLSL_3_2;
643
644   auto gl = mController.GetGL();
645   if(!gl)
646   {
647     // Do nothing during shutdown
648     return version;
649   }
650
651   int majorVersion, minorVersion;
652   gl->GetIntegerv(GL_MAJOR_VERSION, &majorVersion);
653   gl->GetIntegerv(GL_MINOR_VERSION, &minorVersion);
654   DALI_LOG_RELEASE_INFO("GL Version (integer) : %d.%d\n", majorVersion, minorVersion);
655   DALI_LOG_RELEASE_INFO("GLSL Version : %s\n", gl->GetString(GL_SHADING_LANGUAGE_VERSION));
656
657   // TODO: the language version is hardcoded for now, but we may use what we get
658   // from GL_SHADING_LANGUAGE_VERSION?
659   return version;
660 }
661
662 void Reflection::SortOpaques()
663 {
664   //Determine declaration order of each sampler
665   auto& programCreateInfo = mProgram.GetCreateInfo();
666
667   std::vector<uint8_t> data;
668   std::string          vertShader;
669   std::string          fragShader;
670
671   for(auto& shaderState : *programCreateInfo.shaderState)
672   {
673     if(shaderState.pipelineStage == PipelineStage::VERTEX_SHADER)
674     {
675       vertShader = GetShaderSource(shaderState);
676     }
677     else if(shaderState.pipelineStage == PipelineStage::FRAGMENT_SHADER)
678     {
679       fragShader = GetShaderSource(shaderState);
680     }
681   }
682
683   int              samplerPosition = 0;
684   std::vector<int> samplerPositions(mUniformOpaques.size(), -1);
685
686   ParseShaderSamplers(vertShader, mUniformOpaques, samplerPosition, samplerPositions);
687   ParseShaderSamplers(fragShader, mUniformOpaques, samplerPosition, samplerPositions);
688
689   std::sort(mUniformOpaques.begin(), mUniformOpaques.end(), [](const UniformInfo& a, const UniformInfo& b) { return a.offset < b.offset; });
690 }
691
692 } // namespace Dali::Graphics::GLES