Shader processor 63/313763/3
authorAdam Bialogonski <adam.b@samsung.com>
Wed, 3 Jul 2024 13:44:09 +0000 (14:44 +0100)
committerAdam Bialogonski <adam.b@samsung.com>
Wed, 3 Jul 2024 17:07:01 +0000 (18:07 +0100)
Shader process that uses DALi own shader syntax and
translates it into GLES2, GLES3 or Vulkan compatible
shaders.

Change-Id: I83f7370e5ced6f85f50fa3937f196f10ecdb3323

22 files changed:
automated-tests/resources/shaders/canvas-view-with-output.frag [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view-with-output.frag.gles2 [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view-with-output.frag.gles3 [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view-with-output.frag.glsl-spirv [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view.frag [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view.frag.gles2 [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view.frag.gles3 [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view.frag.glsl-spirv [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view.vert [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view.vert.gles2 [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view.vert.gles3 [new file with mode: 0644]
automated-tests/resources/shaders/canvas-view.vert.glsl-spirv [new file with mode: 0644]
automated-tests/src/dali-graphics/CMakeLists.txt
automated-tests/src/dali-graphics/utc-Dali-GraphicsShader.cpp
automated-tests/src/dali-graphics/utc-Dali-GraphicsShaderParser.cpp [new file with mode: 0644]
dali/internal/graphics/common/shader-parser.cpp [new file with mode: 0644]
dali/internal/graphics/common/shader-parser.h [new file with mode: 0644]
dali/internal/graphics/file.list
dali/internal/graphics/gles-impl/gles-graphics-program.cpp
dali/internal/graphics/gles-impl/gles-graphics-program.h
dali/internal/graphics/gles-impl/gles-graphics-shader.cpp
dali/internal/graphics/gles-impl/gles-graphics-shader.h

diff --git a/automated-tests/resources/shaders/canvas-view-with-output.frag b/automated-tests/resources/shaders/canvas-view-with-output.frag
new file mode 100644 (file)
index 0000000..d7b51ec
--- /dev/null
@@ -0,0 +1,22 @@
+//@ignore:on
+#define UNIFORM_BLOCK uniform
+#define UNIFORM uniform
+#define INPUT in
+#define OUTPUT out
+#define OUT_COLOR gl_FragColor
+//@ignore:off
+
+UNIFORM_BLOCK FragBlock
+{
+  UNIFORM lowp vec4 uColor;
+};
+
+INPUT mediump vec2 vTexCoord;
+uniform sampler2D sTexture;
+
+OUTPUT mediump vec4 fragColor;
+
+void main()
+{
+  fragColor = TEXTURE(sTexture, vTexCoord) * uColor;
+}
diff --git a/automated-tests/resources/shaders/canvas-view-with-output.frag.gles2 b/automated-tests/resources/shaders/canvas-view-with-output.frag.gles2
new file mode 100644 (file)
index 0000000..9e471ea
--- /dev/null
@@ -0,0 +1,15 @@
+#version 100
+
+
+#define TEXTURE texture2D
+uniform  lowp vec4 uColor;
+
+varying mediump vec2 vTexCoord;
+uniform sampler2D sTexture;
+
+#define fragColor gl_FragColor
+
+void main()
+{
+  fragColor = TEXTURE(sTexture, vTexCoord) * uColor;
+}
diff --git a/automated-tests/resources/shaders/canvas-view-with-output.frag.gles3 b/automated-tests/resources/shaders/canvas-view-with-output.frag.gles3
new file mode 100644 (file)
index 0000000..91b5774
--- /dev/null
@@ -0,0 +1,18 @@
+#version 320 es
+
+
+#define TEXTURE texture
+layout(std140) uniform FragBlock
+{
+ lowp vec4 uColor;
+};
+
+in mediump vec2 vTexCoord;
+uniform sampler2D sTexture;
+
+out mediump vec4 fragColor;
+
+void main()
+{
+  fragColor = TEXTURE(sTexture, vTexCoord) * uColor;
+}
diff --git a/automated-tests/resources/shaders/canvas-view-with-output.frag.glsl-spirv b/automated-tests/resources/shaders/canvas-view-with-output.frag.glsl-spirv
new file mode 100644 (file)
index 0000000..ffa5d20
--- /dev/null
@@ -0,0 +1,18 @@
+#version 430
+
+
+#define TEXTURE texture
+layout(set=0, binding=1, std140) uniform FragBlock
+{
+ lowp vec4 uColor;
+};
+
+layout(location = 0) in mediump vec2 vTexCoord;
+uniform sampler2D sTexture;
+
+out mediump vec4 fragColor;
+
+void main()
+{
+  fragColor = TEXTURE(sTexture, vTexCoord) * uColor;
+}
diff --git a/automated-tests/resources/shaders/canvas-view.frag b/automated-tests/resources/shaders/canvas-view.frag
new file mode 100644 (file)
index 0000000..b3a4d06
--- /dev/null
@@ -0,0 +1,20 @@
+//@ignore:on
+#define UNIFORM_BLOCK uniform
+#define UNIFORM uniform
+#define INPUT in
+#define OUTPUT out
+#define OUT_COLOR gl_FragColor
+//@ignore:off
+
+UNIFORM_BLOCK FragBlock
+{
+  UNIFORM lowp vec4 uColor;
+};
+
+INPUT mediump vec2 vTexCoord;
+uniform sampler2D sTexture;
+
+void main()
+{
+  gl_FragColor = TEXTURE(sTexture, vTexCoord) * uColor;
+}
diff --git a/automated-tests/resources/shaders/canvas-view.frag.gles2 b/automated-tests/resources/shaders/canvas-view.frag.gles2
new file mode 100644 (file)
index 0000000..a53d664
--- /dev/null
@@ -0,0 +1,13 @@
+#version 100
+
+
+#define TEXTURE texture2D
+uniform  lowp vec4 uColor;
+
+varying mediump vec2 vTexCoord;
+uniform sampler2D sTexture;
+
+void main()
+{
+  gl_FragColor = TEXTURE(sTexture, vTexCoord) * uColor;
+}
diff --git a/automated-tests/resources/shaders/canvas-view.frag.gles3 b/automated-tests/resources/shaders/canvas-view.frag.gles3
new file mode 100644 (file)
index 0000000..0f9435f
--- /dev/null
@@ -0,0 +1,18 @@
+#version 320 es
+
+
+#define TEXTURE texture
+layout(std140) uniform FragBlock
+{
+ lowp vec4 uColor;
+};
+
+in mediump vec2 vTexCoord;
+uniform sampler2D sTexture;
+
+#define gl_FragColor _glFragColor
+out mediump vec4 _glFragColor;
+void main()
+{
+  gl_FragColor = TEXTURE(sTexture, vTexCoord) * uColor;
+}
diff --git a/automated-tests/resources/shaders/canvas-view.frag.glsl-spirv b/automated-tests/resources/shaders/canvas-view.frag.glsl-spirv
new file mode 100644 (file)
index 0000000..57f5256
--- /dev/null
@@ -0,0 +1,18 @@
+#version 430
+
+
+#define TEXTURE texture
+layout(set=0, binding=1, std140) uniform FragBlock
+{
+ lowp vec4 uColor;
+};
+
+layout(location = 0) in mediump vec2 vTexCoord;
+uniform sampler2D sTexture;
+
+#define gl_FragColor _glFragColor
+out mediump vec4 _glFragColor;
+void main()
+{
+  gl_FragColor = TEXTURE(sTexture, vTexCoord) * uColor;
+}
diff --git a/automated-tests/resources/shaders/canvas-view.vert b/automated-tests/resources/shaders/canvas-view.vert
new file mode 100644 (file)
index 0000000..c663ed5
--- /dev/null
@@ -0,0 +1,20 @@
+//@ignore:on
+#define UNIFORM_BLOCK uniform
+#define UNIFORM uniform
+#define INPUT in
+#define OUTPUT out
+#define OUT_COLOR gl_FragColor
+//@ignore:off
+
+INPUT mediump vec2 aPosition;
+OUTPUT mediump vec2 vTexCoord;
+UNIFORM_BLOCK VertBlock
+{
+  UNIFORM highp mat4 uMvpMatrix;
+  UNIFORM highp vec3 uSize;
+};
+void main()
+{
+  gl_Position = uMvpMatrix * vec4(aPosition * uSize.xy, 0.0, 1.0);
+  vTexCoord = aPosition + vec2(0.5);
+}
diff --git a/automated-tests/resources/shaders/canvas-view.vert.gles2 b/automated-tests/resources/shaders/canvas-view.vert.gles2
new file mode 100644 (file)
index 0000000..563f2c0
--- /dev/null
@@ -0,0 +1,13 @@
+#version 100
+
+
+#define TEXTURE texture2D
+attribute mediump vec2 aPosition;
+varying mediump vec2 vTexCoord;
+uniform  highp mat4 uMvpMatrix;
+uniform  highp vec3 uSize;
+void main()
+{
+  gl_Position = uMvpMatrix * vec4(aPosition * uSize.xy, 0.0, 1.0);
+  vTexCoord = aPosition + vec2(0.5);
+}
diff --git a/automated-tests/resources/shaders/canvas-view.vert.gles3 b/automated-tests/resources/shaders/canvas-view.vert.gles3
new file mode 100644 (file)
index 0000000..9566ded
--- /dev/null
@@ -0,0 +1,16 @@
+#version 320 es
+
+
+#define TEXTURE texture
+in mediump vec2 aPosition;
+out mediump vec2 vTexCoord;
+layout(std140) uniform VertBlock
+{
+ highp mat4 uMvpMatrix;
+ highp vec3 uSize;
+};
+void main()
+{
+  gl_Position = uMvpMatrix * vec4(aPosition * uSize.xy, 0.0, 1.0);
+  vTexCoord = aPosition + vec2(0.5);
+}
diff --git a/automated-tests/resources/shaders/canvas-view.vert.glsl-spirv b/automated-tests/resources/shaders/canvas-view.vert.glsl-spirv
new file mode 100644 (file)
index 0000000..3663488
--- /dev/null
@@ -0,0 +1,16 @@
+#version 430
+
+
+#define TEXTURE texture
+layout(location = 0) in mediump vec2 aPosition;
+layout(location=0) out mediump vec2 vTexCoord;
+layout(set=0, binding=0, std140) uniform VertBlock
+{
+ highp mat4 uMvpMatrix;
+ highp vec3 uSize;
+};
+void main()
+{
+  gl_Position = uMvpMatrix * vec4(aPosition * uSize.xy, 0.0, 1.0);
+  vTexCoord = aPosition + vec2(0.5);
+}
index 8ec9991ea25ce1750789af04fcefa14fb97d8ec3..39a4494a6babdd3d7acb5a1b9904a62f7d1747ab 100644 (file)
@@ -15,6 +15,7 @@ SET(TC_SOURCES
     utc-Dali-GraphicsProgram.cpp
     utc-Dali-GraphicsSampler.cpp
     utc-Dali-GraphicsShader.cpp
+    utc-Dali-GraphicsShaderParser.cpp
     utc-Dali-GraphicsTexture.cpp
 )
 
index 6b9bad3060b759d5c1a8a2b78a255eec2dee30a0..51c67bdf04ce9c3702ce5d6ca7cd9d224f9a298b 100644 (file)
@@ -37,9 +37,10 @@ int UtcDaliGlesStripLegacyCodeIfNeededTest1(void)
     info.SetSourceSize(vertexShader.size());
     info.SetSourceMode(Dali::Graphics::ShaderSourceMode::TEXT);
 
-    size_t dataSize  = 0;
-    size_t dataIndex = 0;
-    Graphics::GLES::ShaderImpl::StripLegacyCodeIfNeeded(info, dataIndex, dataSize);
+    size_t   dataSize  = 0;
+    size_t   dataIndex = 0;
+    uint32_t version;
+    Graphics::GLES::ShaderImpl::StripLegacyCodeIfNeeded(info, dataIndex, version, dataSize);
 
     DALI_TEST_EQUALS(dataIndex, 0, TEST_LOCATION);
     DALI_TEST_EQUALS(dataSize, vertexShader.size(), TEST_LOCATION);
@@ -71,9 +72,10 @@ int UtcDaliGlesStripLegacyCodeTestDifferentPrefix(void)
     info.SetSourceSize(prefixedVertexShader.size());
     info.SetSourceMode(Dali::Graphics::ShaderSourceMode::TEXT);
 
-    size_t dataSize  = 0;
-    size_t dataIndex = 0;
-    Graphics::GLES::ShaderImpl::StripLegacyCodeIfNeeded(info, dataIndex, dataSize);
+    size_t   dataSize  = 0;
+    size_t   dataIndex = 0;
+    uint32_t version;
+    Graphics::GLES::ShaderImpl::StripLegacyCodeIfNeeded(info, dataIndex, version, dataSize);
 
     auto index = prefixedVertexShader.find("//@version");
 
@@ -107,9 +109,10 @@ int UtcDaliGlesStripLegacyCodeIfNeededTest2(void)
     info.SetSourceSize(prefixedVertexShader.size());
     info.SetSourceMode(Dali::Graphics::ShaderSourceMode::TEXT);
 
-    size_t dataSize  = 0;
-    size_t dataIndex = 0;
-    Graphics::GLES::ShaderImpl::StripLegacyCodeIfNeeded(info, dataIndex, dataSize);
+    size_t   dataSize  = 0;
+    size_t   dataIndex = 0;
+    uint32_t version;
+    Graphics::GLES::ShaderImpl::StripLegacyCodeIfNeeded(info, dataIndex, version, dataSize);
 
     DALI_TEST_EQUALS(dataIndex, vertexPrefix.length(), TEST_LOCATION);
 
@@ -143,9 +146,10 @@ int UtcDaliGlesLegacyCodeTest(void)
     info.SetSourceSize(prefixedVertexShader.size());
     info.SetSourceMode(Dali::Graphics::ShaderSourceMode::TEXT);
 
-    size_t dataSize  = 0;
-    size_t dataIndex = 0;
-    Graphics::GLES::ShaderImpl::StripLegacyCodeIfNeeded(info, dataIndex, dataSize);
+    size_t   dataSize  = 0;
+    size_t   dataIndex = 0;
+    uint32_t version;
+    Graphics::GLES::ShaderImpl::StripLegacyCodeIfNeeded(info, dataIndex, version, dataSize);
 
     auto index = prefixedVertexShader.find("#version");
 
diff --git a/automated-tests/src/dali-graphics/utc-Dali-GraphicsShaderParser.cpp b/automated-tests/src/dali-graphics/utc-Dali-GraphicsShaderParser.cpp
new file mode 100644 (file)
index 0000000..cd26743
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dali/dali.h>
+
+#include <dali-test-suite-utils.h>
+#include <dali/internal/graphics/common/shader-parser.h>
+#include <fstream>
+
+#ifndef TEST_RESOURCE_DIR
+#define TEST_RESOURCE_DIR ""
+#endif
+
+using namespace Dali::Internal::ShaderParser;
+
+static std::string LoadTextFile(std::string filename)
+{
+  std::ifstream t(filename);
+  std::string   str((std::istreambuf_iterator<char>(t)),
+                  std::istreambuf_iterator<char>());
+  return str;
+}
+
+static bool CompareFileWithString(std::string file1, std::string stringToCompare)
+{
+  std::ifstream infile1(file1);
+
+  std::ostringstream sstr;
+  sstr << infile1.rdbuf();
+  auto s = sstr.str();
+
+  auto result = (s == stringToCompare);
+  if(!result)
+  {
+    tet_printf("%s\n", s.c_str());
+    tet_printf("---\n%s\n", stringToCompare.c_str());
+  }
+  return result;
+}
+
+int UtcParseGLES2Shader(void)
+{
+  tet_infoline("UtcParseGLES2Shader - Tests parser output for generating GLES2");
+
+  auto vertexShader   = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view.vert");
+  auto fragmentShader = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view.frag");
+
+  std::vector<std::string> outStrings;
+
+  Internal::ShaderParser::ShaderParserInfo parseInfo{};
+  parseInfo.vertexShaderCode            = &vertexShader;
+  parseInfo.fragmentShaderCode          = &fragmentShader;
+  parseInfo.vertexShaderLegacyVersion   = 0;
+  parseInfo.fragmentShaderLegacyVersion = 0;
+  parseInfo.language                    = Internal::ShaderParser::OutputLanguage::GLSL2; // We default to GLSL3
+  parseInfo.outputVersion               = 0;
+
+  Parse(parseInfo, outStrings);
+  auto& outVertexShader   = outStrings[0];
+  auto& outFragmentShader = outStrings[1];
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view.vert.gles2", outVertexShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view.frag.gles2", outFragmentShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  END_TEST;
+}
+
+int UtcParseGLES2ShaderWithOutput(void)
+{
+  tet_infoline("UtcParseGLES2ShaderWithOutput - Tests parser output for generating GLES2");
+
+  // Load fragment shader with gl_FragColor
+  auto vertexShader   = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view.vert");
+  auto fragmentShader = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view-with-output.frag");
+
+  std::vector<std::string> outStrings;
+
+  Internal::ShaderParser::ShaderParserInfo parseInfo{};
+  parseInfo.vertexShaderCode            = &vertexShader;
+  parseInfo.fragmentShaderCode          = &fragmentShader;
+  parseInfo.vertexShaderLegacyVersion   = 0;
+  parseInfo.fragmentShaderLegacyVersion = 0;
+  parseInfo.language                    = Internal::ShaderParser::OutputLanguage::GLSL2; // We default to GLSL3
+  parseInfo.outputVersion               = 0;
+  Parse(parseInfo, outStrings);
+
+  auto& outVertexShader   = outStrings[0];
+  auto& outFragmentShader = outStrings[1];
+
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view.vert.gles2", outVertexShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view-with-output.frag.gles2", outFragmentShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  END_TEST;
+}
+
+int UtcParseGLES3Shader(void)
+{
+  tet_infoline("UtcParseGLES3Shader - Tests parser output for generating GLES3");
+
+  auto vertexShader   = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view.vert");
+  auto fragmentShader = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view.frag");
+
+  std::vector<std::string> outStrings;
+
+  Internal::ShaderParser::ShaderParserInfo parseInfo{};
+  parseInfo.vertexShaderCode            = &vertexShader;
+  parseInfo.fragmentShaderCode          = &fragmentShader;
+  parseInfo.vertexShaderLegacyVersion   = 0;
+  parseInfo.fragmentShaderLegacyVersion = 0;
+  parseInfo.language                    = Internal::ShaderParser::OutputLanguage::GLSL3;
+  parseInfo.outputVersion               = 0;
+  Parse(parseInfo, outStrings);
+  auto& outVertexShader   = outStrings[0];
+  auto& outFragmentShader = outStrings[1];
+
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view.vert.gles3", outVertexShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view.frag.gles3", outFragmentShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  END_TEST;
+}
+
+int UtcParseGLES3ShaderWithOutput(void)
+{
+  tet_infoline("UtcParseGLES3ShaderWithOutput - Tests parser output for generating GLES3 with OUTPUT in fragment shader");
+
+  auto vertexShader   = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view.vert");
+  auto fragmentShader = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view-with-output.frag");
+
+  std::vector<std::string> outStrings;
+
+  Internal::ShaderParser::ShaderParserInfo parseInfo{};
+  parseInfo.vertexShaderCode            = &vertexShader;
+  parseInfo.fragmentShaderCode          = &fragmentShader;
+  parseInfo.vertexShaderLegacyVersion   = 0;
+  parseInfo.fragmentShaderLegacyVersion = 0;
+  parseInfo.language                    = Internal::ShaderParser::OutputLanguage::GLSL3;
+  parseInfo.outputVersion               = 0;
+  Parse(parseInfo, outStrings);
+
+  // save to compare
+
+  {
+    //std::ofstream outv(TEST_RESOURCE_DIR "/shaders/canvas-view.vert.gles3");
+    //std::ofstream outf(TEST_RESOURCE_DIR "/shaders/canvas-view-with-output.frag.gles3");
+    //outv << outVertexShader;
+    //outf << outFragmentShader;
+  }
+  auto& outVertexShader   = outStrings[0];
+  auto& outFragmentShader = outStrings[1];
+
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view.vert.gles3", outVertexShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view-with-output.frag.gles3", outFragmentShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  END_TEST;
+}
+
+int UtcParseSPIRVShader(void)
+{
+  tet_infoline("UtcParseSPIRVShader - Tests parser output for generating GLES3");
+
+  // TODO: this test should fail in future after modifying sampler keywords!
+
+  auto vertexShader   = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view.vert");
+  auto fragmentShader = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view.frag");
+
+  std::vector<std::string> outStrings;
+
+  Internal::ShaderParser::ShaderParserInfo parseInfo{};
+  parseInfo.vertexShaderCode            = &vertexShader;
+  parseInfo.fragmentShaderCode          = &fragmentShader;
+  parseInfo.vertexShaderLegacyVersion   = 0;
+  parseInfo.fragmentShaderLegacyVersion = 0;
+  parseInfo.language                    = Internal::ShaderParser::OutputLanguage::SPIRV_GLSL;
+  parseInfo.outputVersion               = 0;
+  Parse(parseInfo, outStrings);
+
+  // save to compare
+
+  {
+    //std::ofstream outv(TEST_RESOURCE_DIR "/shaders/canvas-view.vert.glsl-spirv");
+    //std::ofstream outf(TEST_RESOURCE_DIR "/shaders/canvas-view.frag.glsl-spirv");
+    //outv << outVertexShader;
+    //outf << outFragmentShader;
+  }
+  auto& outVertexShader   = outStrings[0];
+  auto& outFragmentShader = outStrings[1];
+
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view.vert.glsl-spirv", outVertexShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view.frag.glsl-spirv", outFragmentShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  END_TEST;
+}
+
+int UtcParseSPIRVShaderWithOutput(void)
+{
+  tet_infoline("UtcParseSPIRVShaderWithOutput - Tests parser output for generating GLES3");
+
+  // TODO: this test should fail in future after modifying sampler keywords!
+
+  auto vertexShader   = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view.vert");
+  auto fragmentShader = LoadTextFile(TEST_RESOURCE_DIR "/shaders/canvas-view-with-output.frag");
+
+  std::vector<std::string> outStrings;
+
+  Internal::ShaderParser::ShaderParserInfo parseInfo{};
+  parseInfo.vertexShaderCode            = &vertexShader;
+  parseInfo.fragmentShaderCode          = &fragmentShader;
+  parseInfo.vertexShaderLegacyVersion   = 0;
+  parseInfo.fragmentShaderLegacyVersion = 0;
+  parseInfo.language                    = Internal::ShaderParser::OutputLanguage::SPIRV_GLSL;
+  parseInfo.outputVersion               = 0;
+  Parse(parseInfo, outStrings);
+
+  // save to compare
+
+  {
+    //std::ofstream outv(TEST_RESOURCE_DIR "/shaders/canvas-view.vert.glsl-spirv");
+    //std::ofstream outf(TEST_RESOURCE_DIR "/shaders/canvas-view-with-output.frag.glsl-spirv");
+    //outv << outVertexShader;
+    //outf << outFragmentShader;
+  }
+  auto& outVertexShader   = outStrings[0];
+  auto& outFragmentShader = outStrings[1];
+
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view.vert.glsl-spirv", outVertexShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  {
+    bool cmp = CompareFileWithString(TEST_RESOURCE_DIR "/shaders/canvas-view-with-output.frag.glsl-spirv", outFragmentShader);
+    DALI_TEST_EQUALS(cmp, true, TEST_LOCATION);
+  }
+  END_TEST;
+}
diff --git a/dali/internal/graphics/common/shader-parser.cpp b/dali/internal/graphics/common/shader-parser.cpp
new file mode 100644 (file)
index 0000000..0447c8e
--- /dev/null
@@ -0,0 +1,539 @@
+/*
+* Copyright (c) 2024 Samsung Electronics Co., Ltd.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+*/
+
+#include <dali/internal/graphics/common/shader-parser.h>
+#include <sstream>
+
+namespace Dali::Internal::ShaderParser
+{
+CodeLine TokenizeLine(std::string line)
+{
+  std::regex word_regex("(\\w+)");
+  auto       words_begin =
+    std::sregex_iterator(line.begin(), line.end(), word_regex);
+  auto words_end = std::sregex_iterator();
+
+  CodeLine lineOfCode;
+  lineOfCode.line = line;
+
+  for(auto it = words_begin; it != words_end; ++it)
+  {
+    const std::smatch& match = *it;
+    lineOfCode.tokens.emplace_back(match.position(), match.length());
+  }
+  return lineOfCode;
+}
+
+std::string GetToken(CodeLine& line, int i)
+{
+  // Function allows retrieving a token from start and from the
+  // end of line. Negative 'i' retrieves token from the end. For example:
+  // GetToken( line, -1 ) - retrieves last token
+  // GetToken( line, 0 ) - retrieves first token
+  // GetToken( line, 1 ) - retrieves second (counting from 0) token
+  if(abs(i) >= line.tokens.size())
+  {
+    return "";
+  }
+  if(i < 0)
+  {
+    i = int(line.tokens.size()) + i;
+  }
+  return std::string(std::string_view(&line.line[line.tokens[i].first], line.tokens[i].second));
+}
+
+void TokenizeSource(Program& program, ShaderStage stage, std::istream& ss)
+{
+  Shader* output{nullptr};
+  if(stage == ShaderStage::VERTEX)
+  {
+    output = &program.vertexShader;
+  }
+  else if(stage == ShaderStage::FRAGMENT)
+  {
+    output = &program.fragmentShader;
+  }
+
+  // Invalid shader stage
+  if(output == nullptr)
+  {
+    return;
+  }
+
+  std::string line;
+  bool        ignoreLines       = false;
+  int         lineNumber        = 0;
+  output->customOutputLineIndex = -1; // Assume using gl_FragColor in fragment shader, no index for custom output
+  output->mainLine              = -1;
+  while(std::getline(ss, line))
+  {
+    // turn ignoring on
+    if(line.substr(0, 12) == "//@ignore:on")
+    {
+      ignoreLines = true;
+      continue;
+    }
+
+    // turn ignoring off
+    if(ignoreLines)
+    {
+      if(line.substr(0, 13) == "//@ignore:off")
+      {
+        ignoreLines = false;
+      }
+      continue;
+    }
+
+    CodeLine lineOfCode = TokenizeLine(line);
+
+    // find out whether fragment shader contains OUTPUT
+    if(!lineOfCode.tokens.empty() && stage == ShaderStage::FRAGMENT)
+    {
+      // Look for at least one OUTPUT int the fragment shader if written
+      // for GLSL3. If there is no OUTPUT we assume programmers used GLSL2
+      // gl_FragColor.
+      if(output->customOutputLineIndex < 0)
+      {
+        if(GetToken(lineOfCode, 0) == "OUTPUT")
+        {
+          output->customOutputLineIndex = output->codeLines.size();
+        }
+      }
+      // find main function
+      if(output->mainLine < 0 && GetToken(lineOfCode, 0) == "void" && GetToken(lineOfCode, 1) == "main")
+      {
+        output->mainLine = lineNumber;
+      }
+    }
+
+    output->codeLines.emplace_back(std::move(lineOfCode));
+    lineNumber++;
+  }
+}
+
+void TokenizeSource(Program& program, ShaderStage stage, const std::string& sourceCodeString)
+{
+  std::stringstream ss(sourceCodeString);
+  TokenizeSource(program, stage, ss);
+}
+
+template<class IT>
+bool ProcessTokenINPUT(IT& it, Program& program, OutputLanguage lang, ShaderStage stage)
+{
+  int               attributeLocation = 0;
+  auto&             l                 = *it;
+  std::string&      outString         = ((stage == ShaderStage::VERTEX) ? program.vertexShader.output : program.fragmentShader.output);
+  std::stringstream ss;
+  if(l.tokens.size())
+  {
+    auto token = GetToken(l, 0);
+    if(token == "INPUT")
+    {
+      auto varName = GetToken(l, -1);
+      if(lang == OutputLanguage::SPIRV_GLSL)
+      {
+        // For vertex stage input locations are incremental
+        int location = 0;
+        if(stage == ShaderStage::VERTEX)
+        {
+          location = attributeLocation++;
+        }
+        else
+        {
+          auto iter = program.varyings.find(varName);
+          if(iter != program.varyings.end())
+          {
+            location = (*iter).second;
+          }
+        }
+
+        ss << "layout(location = " << location << ") in" << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+        outString += ss.str();
+        return true;
+      }
+      else if(lang == OutputLanguage::GLSL3)
+      {
+        ss << "in" << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+        outString += ss.str();
+        return true;
+      }
+      else if(lang == OutputLanguage::GLSL2)
+      {
+        if(stage == ShaderStage::VERTEX)
+        {
+          ss << "attribute" << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+        }
+        else
+        {
+          ss << "varying" << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+        }
+        outString += ss.str();
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+template<class IT>
+bool ProcessTokenOUTPUT(IT& it, Program& program, OutputLanguage lang, ShaderStage stage)
+{
+  std::string& outString = ((stage == ShaderStage::VERTEX) ? program.vertexShader.output : program.fragmentShader.output);
+  auto&        l         = *it;
+  if(l.tokens.size())
+  {
+    auto token = GetToken(l, 0);
+    if(token == "OUTPUT")
+    {
+      if(lang == OutputLanguage::SPIRV_GLSL)
+      {
+        int               location = -1; // invalid location
+        std::stringstream ss;
+        if(stage == ShaderStage::VERTEX)
+        {
+          auto varName = GetToken(l, -1);
+          // compare varyings map
+          auto iter = program.varyings.find(varName);
+          if(iter != program.varyings.end())
+          {
+            location = (*iter).second;
+          }
+          // SPIRV requires storing locations
+          ss << "layout(location=" << location << ") out" << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+          outString += ss.str();
+        }
+        else
+        {
+          // for fragment shader the gl_FragColor is our output
+          // we will use OUT_COLOR
+          auto varName = GetToken(l, -1);
+          ss << "out" << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+          outString += ss.str();
+        }
+        return true;
+      }
+      else if(lang == OutputLanguage::GLSL3)
+      {
+        std::stringstream ss;
+        ss << "out"
+           << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+        outString += ss.str();
+        return true;
+      }
+      else if(lang == OutputLanguage::GLSL2)
+      {
+        std::stringstream ss;
+        if(stage == ShaderStage::VERTEX)
+        {
+          ss << "varying" << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+          outString += ss.str();
+        }
+        else
+        {
+          // get output variable name
+          auto& cl      = program.fragmentShader.codeLines[program.fragmentShader.customOutputLineIndex];
+          auto  varname = GetToken(cl, -1);
+          ss << "#define " << varname << " gl_FragColor\n";
+          outString += ss.str();
+        }
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+template<class IT>
+bool ProcessTokenUNIFORM_BLOCK(IT& it, Program& program, OutputLanguage lang, ShaderStage stage)
+{
+  auto&             l       = *it;
+  int&              binding = program.uboBinding;
+  std::string&      outStr  = (stage == ShaderStage::VERTEX) ? program.vertexShader.output : program.fragmentShader.output;
+  std::stringstream ss;
+  if(l.tokens.size())
+  {
+    auto token = GetToken(l, 0);
+    if(token == "UNIFORM_BLOCK")
+    {
+      bool gles3plus = false;
+      if(lang == OutputLanguage::SPIRV_GLSL)
+      {
+        ss << "layout(set=0, binding=" << binding << ", std140) uniform" << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+        binding++;
+        gles3plus = true;
+      }
+      else if(lang == OutputLanguage::GLSL3)
+      {
+        ss << "layout(std140) uniform" << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+        gles3plus = true;
+      }
+      if(gles3plus) // remove word UNIFORM for gles3+/spirv
+      {
+        // iterate block
+        l = (*++it);
+        while(l.line.find('}') == std::string::npos)
+        {
+          auto isUniform = (GetToken(l, 0) == "UNIFORM");
+          if(isUniform)
+          {
+            ss << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+          }
+          else
+          {
+            ss << l.line << "\n";
+          }
+          l = *(++it);
+        }
+        ss << "};\n";
+      }
+      else if(lang == OutputLanguage::GLSL2)
+      {
+        while(l.line.find('{') == std::string::npos)
+        {
+          l = *(++it);
+        }
+        l = *(++it);
+        while(l.line.find('}') == std::string::npos)
+        {
+          auto isUniform = (GetToken(l, 0) == "UNIFORM");
+          if(isUniform)
+          {
+            ss << "uniform " << l.line.substr(l.tokens[0].first + l.tokens[0].second).c_str() << "\n";
+          }
+          else
+          {
+            ss << l.line << "\n";
+          }
+          l = *(++it);
+        }
+      }
+    }
+    auto str = ss.str();
+    if(str.empty())
+    {
+      return false;
+    }
+    outStr += str;
+    return true;
+  }
+  return false;
+}
+
+// Links inputs and outputs of two stages and assigns
+// location
+void LinkProgram(Program& program)
+{
+  int location = 0;
+  for(auto& line : program.vertexShader.codeLines)
+  {
+    auto token = GetToken(line, 0);
+    if(token == std::string("OUTPUT"))
+    {
+      auto varname              = GetToken(line, -1);
+      program.varyings[varname] = location++;
+    }
+  }
+  // Verify
+  for(auto& line : program.fragmentShader.codeLines)
+  {
+    auto token = GetToken(line, 0);
+    if(token == std::string("INPUT"))
+    {
+      auto varname = GetToken(line, -1);
+    }
+  }
+}
+
+void ProcessStage(Program& program, ShaderStage stage, OutputLanguage language)
+{
+  auto& codeLines = ((stage == ShaderStage::VERTEX) ? program.vertexShader.codeLines : program.fragmentShader.codeLines);
+  auto& outString = ((stage == ShaderStage::VERTEX) ? program.vertexShader.output : program.fragmentShader.output);
+
+  int  lineNum     = 0;
+  bool textureDone = false;
+
+  // add OUTPUT to the fragment shader if it's not defined (then we assume gl_FragColor has been used)
+  if(stage == ShaderStage::FRAGMENT &&
+     program.fragmentShader.customOutputLineIndex < 0 &&
+     program.fragmentShader.mainLine >= 0 &&
+     language != OutputLanguage::GLSL2)
+  {
+    // Push tokenized extra line into the code that defines the output
+    // we add output as _glFragColor and define
+    std::string line1 = "OUTPUT mediump vec4 _glFragColor;";
+    program.fragmentShader.codeLines.insert(program.fragmentShader.codeLines.begin() + program.fragmentShader.mainLine, TokenizeLine(line1));
+    line1 = "#define gl_FragColor _glFragColor";
+    program.fragmentShader.codeLines.insert(program.fragmentShader.codeLines.begin() + program.fragmentShader.mainLine, TokenizeLine(line1));
+  }
+
+  for(auto it = codeLines.begin(); it != codeLines.end(); ++it)
+  {
+    auto& line = *it;
+    if(lineNum > 0 && !textureDone)
+    {
+      textureDone = true;
+      // Add texture macro
+      if(language == OutputLanguage::GLSL2)
+      {
+        outString += "\n#define TEXTURE texture2D\n";
+      }
+      else
+      {
+        outString += "\n#define TEXTURE texture\n";
+      }
+    }
+    lineNum++;
+    // no tokens (shouldn't happen?)
+    if(line.tokens.empty())
+    {
+      outString += line.line + "\n";
+      continue;
+    }
+
+    auto res = ProcessTokenINPUT(it, program, language, stage);
+    if(!res)
+    {
+      res = ProcessTokenOUTPUT(it, program, language, stage);
+    }
+    if(!res)
+    {
+      res = ProcessTokenUNIFORM_BLOCK(it, program, language, stage);
+    }
+    if(!res)
+    {
+      outString += line.line + "\n";
+    }
+  }
+}
+
+void Parse(const ShaderParserInfo& parseInfo, std::vector<std::string>& output)
+{
+  auto vs = std::istringstream(*parseInfo.vertexShaderCode);
+  auto fs = std::istringstream(*parseInfo.fragmentShaderCode);
+
+  output.resize(2);
+
+  // Create program
+  Program program;
+
+  if(parseInfo.vertexShaderLegacyVersion)
+  {
+    output[0] = *parseInfo.vertexShaderCode;
+  }
+  else
+  {
+    TokenizeSource(program, ShaderStage::VERTEX, vs);
+  }
+
+  if(parseInfo.fragmentShaderLegacyVersion)
+  {
+    output[1] = *parseInfo.fragmentShaderCode;
+  }
+  else
+  {
+    TokenizeSource(program, ShaderStage::FRAGMENT, fs);
+  }
+
+  // Pick the right GLSL dialect and version based on provided shaders
+  if(parseInfo.vertexShaderLegacyVersion && parseInfo.fragmentShaderLegacyVersion)
+  {
+    // Not touching any shaders, return current code as output
+    return;
+  }
+  else if(!parseInfo.vertexShaderLegacyVersion && !parseInfo.fragmentShaderLegacyVersion)
+  {
+    // Both shaders need processing and linking
+    // Assign the shader version. Since both stages are being converted
+    // the version can be assumed.
+    if(parseInfo.language == OutputLanguage::GLSL3)
+    {
+      program.vertexShader.output += "#version 320 es\n";
+      program.fragmentShader.output += "#version 320 es\n";
+    }
+    else if(parseInfo.language == OutputLanguage::GLSL2)
+    {
+      program.vertexShader.output += "#version 100\n";
+      program.fragmentShader.output += "#version 100\n";
+    }
+    else if(parseInfo.language == OutputLanguage::SPIRV_GLSL)
+    {
+      program.vertexShader.output += "#version 430\n";
+      program.fragmentShader.output += "#version 430\n";
+    }
+
+    // link inputs and outputs between vertex and fragment shader
+    LinkProgram(program);
+
+    ProcessStage(program, ShaderStage::VERTEX, parseInfo.language);
+    ProcessStage(program, ShaderStage::FRAGMENT, parseInfo.language);
+
+    output[0] = std::move(program.vertexShader.output);
+    output[1] = std::move(program.fragmentShader.output);
+  }
+  else
+  {
+    // Case: only vertex shader is modern
+    if(!parseInfo.vertexShaderLegacyVersion)
+    {
+      // update #version
+      std::string suffix(parseInfo.outputVersion < 200 ? std::string("\n") : std::string(" es\n"));
+      program.vertexShader.output += std::string("#version ") + std::to_string(parseInfo.outputVersion) + suffix;
+      program.fragmentShader.output += std::string("#version ") + std::to_string(parseInfo.outputVersion) + suffix;
+
+      auto language = parseInfo.language;
+      if(parseInfo.language != OutputLanguage::SPIRV_GLSL)
+      {
+        if(parseInfo.outputVersion < 200)
+        {
+          language = OutputLanguage::GLSL2;
+        }
+        else
+        {
+          language = OutputLanguage::GLSL3;
+        }
+      }
+      ProcessStage(program, ShaderStage::VERTEX, language);
+      output[0] = std::move(program.vertexShader.output);
+    }
+    // Case: only fragment shader is modern
+    else
+    {
+      // update #version
+      std::string suffix(parseInfo.outputVersion < 200 ? std::string("\n") : std::string(" es\n"));
+      program.vertexShader.output += std::string("#version ") + std::to_string(parseInfo.outputVersion) + suffix;
+      program.fragmentShader.output += std::string("#version ") + std::to_string(parseInfo.outputVersion) + suffix;
+
+      auto language = parseInfo.language;
+      if(parseInfo.language != OutputLanguage::SPIRV_GLSL)
+      {
+        if(parseInfo.outputVersion < 200)
+        {
+          language = OutputLanguage::GLSL2;
+        }
+        else
+        {
+          language = OutputLanguage::GLSL3;
+        }
+      }
+
+      ProcessStage(program, ShaderStage::FRAGMENT, language);
+      output[1] = std::move(program.fragmentShader.output);
+    }
+  }
+}
+
+} // namespace Dali::Internal::ShaderParser
\ No newline at end of file
diff --git a/dali/internal/graphics/common/shader-parser.h b/dali/internal/graphics/common/shader-parser.h
new file mode 100644 (file)
index 0000000..376fb71
--- /dev/null
@@ -0,0 +1,94 @@
+#ifndef DALI_INTERNAL_GRAPHICS_SHADER_PARSER_H
+#define DALI_INTERNAL_GRAPHICS_SHADER_PARSER_H
+
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <map>
+#include <regex>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace Dali::Internal::ShaderParser
+{
+/**
+ * Defines parser shader stages
+ */
+enum class ShaderStage
+{
+  VERTEX,
+  FRAGMENT
+};
+
+/**
+ * Defines output shader GLSL dialects
+ */
+enum class OutputLanguage
+{
+  GLSL2,
+  GLSL3,
+  SPIRV_GLSL
+};
+
+using CodeTokenPair = std::pair<int, int>;
+struct CodeLine
+{
+  std::vector<CodeTokenPair> tokens;
+  std::vector<std::string>   replacement;
+  std::string                line;
+};
+
+struct Shader
+{
+  std::vector<CodeLine> codeLines;
+  std::string           output;
+  int                   customOutputLineIndex;
+  int                   mainLine;
+};
+
+struct Program
+{
+  Shader                     vertexShader;
+  Shader                     fragmentShader;
+  std::map<std::string, int> varyings;
+  int                        uboBinding{0};
+};
+
+struct ShaderParserInfo
+{
+  const std::string* vertexShaderCode;
+  const std::string* fragmentShaderCode;
+
+  uint32_t vertexShaderLegacyVersion;
+  uint32_t fragmentShaderLegacyVersion;
+
+  OutputLanguage language;
+  uint32_t       outputVersion;
+};
+
+/**
+ * Parses given source code and returns requested variant of shader
+ * @param[in] parseInfo Valid ShaderParserInfo structure
+ * @param[out] output Output strings
+ */
+void Parse(const ShaderParserInfo& parseInfo, std::vector<std::string>& output);
+
+} // namespace Dali::Internal::ShaderParser
+
+#endif
\ No newline at end of file
index 3f8b5dd45faba4b6cefe08284f3742992776e1e2..006e744f405d27cc6a08b0e77eca2b3ac34e080e 100644 (file)
@@ -9,6 +9,7 @@ SET( adaptor_graphics_gles_src_files
     ${adaptor_graphics_dir}/gles/gl-proxy-implementation.cpp
     ${adaptor_graphics_dir}/gles/egl-graphics-factory.cpp
     ${adaptor_graphics_dir}/gles/egl-graphics.cpp
+    ${adaptor_graphics_dir}/common/shader-parser.cpp
 )
 
 INCLUDE( ${adaptor_graphics_dir}/gles-impl/file.list )
index dcd09669fc94300a407f6ec7d36635bbfd3b1d12..d0636fc763284c2a0e2f10b00a88aa1590765d7e 100644 (file)
 #include "gles-graphics-program.h"
 
 // INTERNAL HEADERS
+#include <dali/internal/graphics/common/shader-parser.h>
 #include "egl-graphics-controller.h"
 #include "gles-graphics-reflection.h"
 #include "gles-graphics-shader.h"
 
 // EXTERNAL HEADERS
-#include <dali/integration-api/gl-abstraction.h>
-#include <dali/integration-api/gl-defines.h>
+#include <iostream>
 
 #if defined(DEBUG_ENABLED)
 Debug::Filter* gGraphicsProgramLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_GRAPHICS_PROGRAM");
@@ -135,6 +135,80 @@ bool ProgramImpl::Destroy()
   return false;
 }
 
+void ProgramImpl::Preprocess()
+{
+  // For now only Vertex and Fragment shader stages supported
+  // and one per stage
+  std::string  vertexString;
+  std::string  fragmentString;
+  std::string* currentString = nullptr;
+
+  const GLES::Shader* vsh = nullptr;
+  const GLES::Shader* fsh = nullptr;
+
+  const auto& info = mImpl->createInfo;
+
+  for(const auto& state : *info.shaderState)
+  {
+    const auto* shader = static_cast<const GLES::Shader*>(state.shader);
+    if(state.pipelineStage == PipelineStage::VERTEX_SHADER)
+    {
+      // Only TEXT source mode can be processed
+      currentString = &vertexString;
+      vsh           = shader;
+    }
+    else if(state.pipelineStage == PipelineStage::FRAGMENT_SHADER)
+    {
+      // Only TEXT source mode can be processed
+      currentString = &fragmentString;
+      fsh           = shader;
+    }
+    else
+    {
+      // no valid stream to push
+      currentString = nullptr;
+      DALI_LOG_ERROR("Shader state contains invalid shader source (most likely binary)! Can't process!");
+    }
+
+    // Check if stream valid
+    if(currentString && currentString->empty() && shader->GetCreateInfo().sourceMode == ShaderSourceMode::TEXT)
+    {
+      *currentString = std::string(reinterpret_cast<const char*>(shader->GetCreateInfo().sourceData),
+                                   shader->GetCreateInfo().sourceSize);
+    }
+    else
+    {
+      DALI_LOG_ERROR("Preprocessing of binary shaders isn't allowed!");
+    }
+  }
+
+  // if we have both streams ready
+  if(!vertexString.empty() && !fragmentString.empty())
+  {
+    // In case we have one modern shader and one legacy counterpart we need to enforce
+    // output language.
+    Internal::ShaderParser::ShaderParserInfo parseInfo{};
+    parseInfo.vertexShaderCode            = &vertexString;
+    parseInfo.fragmentShaderCode          = &fragmentString;
+    parseInfo.vertexShaderLegacyVersion   = vsh->GetGLSLVersion();
+    parseInfo.fragmentShaderLegacyVersion = fsh->GetGLSLVersion();
+    parseInfo.language                    = Internal::ShaderParser::OutputLanguage::GLSL3; // We default to GLSL3
+    parseInfo.outputVersion               = std::max(vsh->GetGLSLVersion(), fsh->GetGLSLVersion());
+
+    std::vector<std::string> newShaders;
+
+    Internal::ShaderParser::Parse(parseInfo, newShaders);
+
+    // substitute shader code
+    vsh->GetImplementation()->SetPreprocessedCode(newShaders[0].data(), newShaders[0].size());
+    fsh->GetImplementation()->SetPreprocessedCode(newShaders[1].data(), newShaders[1].size());
+  }
+  else
+  {
+    DALI_LOG_ERROR("Preprocessing shader code failed!");
+  }
+}
+
 bool ProgramImpl::Create()
 {
   // Create and link new program
@@ -150,6 +224,9 @@ bool ProgramImpl::Create()
   DALI_LOG_DEBUG_INFO("Program[%s] create program id : %u\n", mImpl->name.c_str(), program);
 
   const auto& info = mImpl->createInfo;
+
+  Preprocess();
+
   for(const auto& state : *info.shaderState)
   {
     const auto* shader = static_cast<const GLES::Shader*>(state.shader);
@@ -175,7 +252,7 @@ bool ProgramImpl::Create()
     gl->GetProgramInfoLog(program, 4096, &size, output);
 
     // log on error
-    DALI_LOG_ERROR("glLinkProgam[%s] failed:\n%s\n", mImpl->name.c_str(), output);
+    DALI_LOG_ERROR("glLinkProgram[%s] failed:\n%s\n", mImpl->name.c_str(), output);
     gl->DeleteProgram(program);
     return false;
   }
index ee8a888b91ab7af862300297d72e135ad3756d56..cc89b71b0f7645497ffee140e30132ba82fe5511 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_GRAPHICS_GLES_PROGRAM_H
 
 /*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -65,6 +65,11 @@ public:
    */
   bool Create();
 
+  /**
+   * @brief Preprocesses shaders
+   */
+  void Preprocess();
+
   /**
    * @brief Returns GL program id
    *
index 36ffd27ce31f1579bd0812ed58fb5339adb4702e..2ee3020fd36a31ebe3ccfec2d2512ab9323f64d4 100644 (file)
@@ -39,7 +39,7 @@ struct ShaderImpl::Impl
     size_t dataStartIndex = 0;
     size_t dataSize;
 
-    ShaderImpl::StripLegacyCodeIfNeeded( _createInfo, dataStartIndex, dataSize );
+    ShaderImpl::StripLegacyCodeIfNeeded(_createInfo, dataStartIndex, glslVersion, dataSize);
 
     source.resize(dataSize);
     std::copy(reinterpret_cast<const uint8_t*>(_createInfo.sourceData) + dataStartIndex,
@@ -106,8 +106,8 @@ struct ShaderImpl::Impl
       if(pipelineStage)
       {
         auto       shader = gl->CreateShader(pipelineStage);
-        const auto src    = reinterpret_cast<const char*>(createInfo.sourceData);
-        GLint      size   = createInfo.sourceSize;
+        const auto src    = !sourcePreprocessed.empty() ? reinterpret_cast<const char*>(sourcePreprocessed.data()) : reinterpret_cast<const char*>(createInfo.sourceData);
+        GLint      size   = !sourcePreprocessed.empty() ? GLint(sourcePreprocessed.size()) : createInfo.sourceSize;
         gl->ShaderSource(shader, 1, const_cast<const char**>(&src), &size);
         gl->CompileShader(shader);
 
@@ -118,7 +118,7 @@ struct ShaderImpl::Impl
           char    output[4096];
           GLsizei outputSize{0u};
           gl->GetShaderInfoLog(shader, 4096, &outputSize, output);
-          DALI_LOG_ERROR("Code: %.*s\n", size, reinterpret_cast<const char*>(createInfo.sourceData));
+          DALI_LOG_ERROR("Code: %.*s\n", size, reinterpret_cast<const char*>(src));
           DALI_LOG_ERROR("glCompileShader() failed: \n%s\n", output);
           gl->DeleteShader(shader);
           return false;
@@ -141,13 +141,24 @@ struct ShaderImpl::Impl
     }
   }
 
+  void SetPreprocessedCode(void* data, uint32_t size)
+  {
+    sourcePreprocessed.resize(size);
+
+    std::copy(reinterpret_cast<const uint8_t*>(data),
+              reinterpret_cast<const uint8_t*>(data) + size,
+              sourcePreprocessed.data());
+  }
+
   EglGraphicsController& controller;
   ShaderCreateInfo       createInfo;
   std::vector<uint8_t>   source{};
+  std::vector<uint8_t>   sourcePreprocessed{};
 
   uint32_t glShader{};
   uint32_t refCount{0u};
-  uint32_t flushCount{0u}; ///< Number of frames at refCount=0
+  uint32_t flushCount{0u};  ///< Number of frames at refCount=0
+  uint32_t glslVersion{0u}; ///< 0 - unknown, otherwise valid #version like 130, 300, etc.
 };
 
 ShaderImpl::ShaderImpl(const Graphics::ShaderCreateInfo& createInfo, Graphics::EglGraphicsController& controller)
@@ -191,6 +202,11 @@ uint32_t ShaderImpl::Release()
   return mImpl->flushCount;
 }
 
+[[nodiscard]] uint32_t ShaderImpl::GetGLSLVersion() const
+{
+  return mImpl->glslVersion;
+}
+
 /**
  * @brief Compiles shader
  *
@@ -216,12 +232,13 @@ const ShaderCreateInfo& ShaderImpl::GetCreateInfo() const
   return mImpl->controller;
 }
 
-void ShaderImpl::StripLegacyCodeIfNeeded(const ShaderCreateInfo& info, size_t& startIndex, size_t& finalDataSize)
+void ShaderImpl::StripLegacyCodeIfNeeded(const ShaderCreateInfo& info, size_t& startIndex, uint32_t& glslVersion, size_t& finalDataSize)
 {
   // Make a copy of source code. if code is meant to be used
   // by modern parser, skip the prefix part
-  auto text = reinterpret_cast<const char*>(info.sourceData);
+  auto text   = reinterpret_cast<const char*>(info.sourceData);
   auto result = std::string_view(text).find("//@legacy-prefix-end");
+  glslVersion = 0u;
   if(info.shaderVersion != 0)
   {
     if(result != 0 && result != std::string::npos)
@@ -238,23 +255,30 @@ void ShaderImpl::StripLegacyCodeIfNeeded(const ShaderCreateInfo& info, size_t& s
   {
     // For legacy shaders we need to make sure that the #version is a very first line
     // so need to strip //@legacy-prefix-end tag
-    if(result != std::string::npos)
+    auto versionPos = std::string_view(text).find("#version", 0);
+    if(versionPos == std::string::npos)
     {
-      auto versionPos = std::string_view(text).find("#version", result);
-      if(versionPos == std::string::npos)
-      {
-        DALI_LOG_ERROR("Shader processing: new-line missing after @legacy-prefix-end!\n");
-        startIndex = 0; // not trimming anything
-      }
-      else
-      {
-        startIndex = versionPos;
-      }
+      startIndex = 0; // not trimming anything
+
+      // if there's no version yet it's a legacy shader we assign 100
+      glslVersion = 100;
+    }
+    else
+    {
+      // save version of legacy shader
+      char* end;
+      glslVersion = uint32_t(std::strtol(std::string_view(text).data() + versionPos + 9, &end, 10));
+      startIndex  = versionPos;
     }
   }
   finalDataSize = info.sourceSize - startIndex;
 }
 
+void ShaderImpl::SetPreprocessedCode(void* data, uint32_t size)
+{
+  mImpl->SetPreprocessedCode(data, size);
+}
+
 Shader::~Shader()
 {
   if(!mShader->Release())
@@ -277,4 +301,9 @@ void Shader::DiscardResource()
   }
 }
 
+uint32_t Shader::GetGLSLVersion() const
+{
+  return GetImplementation()->GetGLSLVersion();
+}
+
 } // namespace Dali::Graphics::GLES
index 45fce49c5dfa9b81e881947993a37981187128ac..ac85822e0c908c557e1370e2bca5c8120585fbda 100644 (file)
@@ -36,12 +36,28 @@ public:
    * @param[in] controller Reference to the controller
    */
   ShaderImpl(const Graphics::ShaderCreateInfo& createInfo, Graphics::EglGraphicsController& controller);
+
+  /**
+   * @brief destructor
+   */
   ~ShaderImpl();
 
+  /**
+   * @brief Increases ref count
+   * @return ref count after increment
+   */
   uint32_t Retain();
 
+  /**
+   * @brief Decreases refcount
+   * @return ref count after decrement
+   */
   uint32_t Release();
 
+  /**
+   * @brief returns current ref count
+   * @return current ref count
+   */
   [[nodiscard]] uint32_t GetRefCount() const;
 
   /**
@@ -70,19 +86,45 @@ public:
    */
   void Destroy();
 
+  /**
+   * @brief Returns GL resource
+   * @return Valid GL shader resource
+   */
   uint32_t GetGLShader() const;
 
+  /**
+   * @brief Returns create info structure
+   * @return Returns valid create info structure
+   */
   [[nodiscard]] const ShaderCreateInfo& GetCreateInfo() const;
 
+  /**
+   * @brief Returns reference to the graphics controller
+   * @return Valid reference to the graphics controller
+   */
   [[nodiscard]] EglGraphicsController& GetController() const;
 
   /**
    * Strips legacy prefix fromt he GLSL source code if necessary
    * @param info valid ShaderCreateInfo strucutre
    * @param[out] startIndex Start index of the source code
+   * @param[out] glslVersion Detected GLSL version of legacy shader
    * @param[out] finalDataSize Size of trimmed data
    */
-  static void StripLegacyCodeIfNeeded(const ShaderCreateInfo& info, size_t& startIndex, size_t& finalDataSize);
+  static void StripLegacyCodeIfNeeded(const ShaderCreateInfo& info, size_t& startIndex, uint32_t& glslVersion, size_t& finalDataSize);
+
+  /**
+   * @brief Sets preprocess code
+   * @param[in] data Valid pointer to the new source code
+   * @param[in] size Size of the source code
+   */
+  void SetPreprocessedCode(void* data, uint32_t size);
+
+  /**
+   * @brief Returns GLSL version
+   * @return Returns valid GLSL version or 0 if undefined
+   */
+  [[nodiscard]] uint32_t GetGLSLVersion() const;
 
 private:
   friend class Shader;
@@ -147,6 +189,8 @@ public:
     // nothing to do here
   }
 
+  [[nodiscard]] uint32_t GetGLSLVersion() const;
+
 private:
   ShaderImpl* mShader{nullptr};
 };