From 6049f8ce7360409842fe8a5f8fcb74940c732f42 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 26 Sep 2025 03:06:03 +0000
Subject: [PATCH 01/15] Initial plan
From b54d9c819e3e3392b8fad4877e9a4c0ca96e575d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 26 Sep 2025 03:20:57 +0000
Subject: [PATCH 02/15] Fix GLSL shader compilation by injecting missing
#version directive
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
crates/jsbindings/webgl.rs | 83 +++++++++++++++++--
test-shader-version-fix.html | 156 +++++++++++++++++++++++++++++++++++
2 files changed, 234 insertions(+), 5 deletions(-)
create mode 100644 test-shader-version-fix.html
diff --git a/crates/jsbindings/webgl.rs b/crates/jsbindings/webgl.rs
index b19796942..64644e0fe 100644
--- a/crates/jsbindings/webgl.rs
+++ b/crates/jsbindings/webgl.rs
@@ -67,16 +67,28 @@ fn patch_glsl_source_from_str(s: &str) -> String {
ast::TranslationUnit, lexer::full::fs::PreprocessorExt, parse::IntoParseBuilderExt,
};
+ // Check if version directive is missing and inject appropriate version
+ let source_to_parse = if !s.contains("#version") {
+ let version_directive = if detect_webgl2_syntax(s) {
+ "#version 300 es\n"
+ } else {
+ "#version 100\n"
+ };
+ format!("{}{}", version_directive, s)
+ } else {
+ s.to_string()
+ };
+
let mut processor = glsl_lang_pp::processor::fs::StdProcessor::new();
let mut tu: TranslationUnit = processor
- .open_source(s, Path::new("."))
+ .open_source(&source_to_parse, Path::new("."))
.builder()
.parse()
.map(|(mut tu, _, iter)| {
iter.into_directives().inject(&mut tu);
tu
})
- .expect(format!("Failed to parse GLSL source: \n{}\n", s).as_str());
+ .expect(format!("Failed to parse GLSL source: \n{}\n", &source_to_parse).as_str());
let mut my_glsl_patcher = MyGLSLPatcher {};
tu.visit_mut(&mut my_glsl_patcher);
@@ -108,14 +120,37 @@ fn patch_glsl_source_from_str(s: &str) -> String {
tu.0.splice(0..0, versions_list);
}
- let mut s = String::new();
+ let mut result = String::new();
glsl_transpiler::glsl::show_translation_unit(
- &mut s,
+ &mut result,
&tu,
glsl_transpiler::glsl::FormattingState::default(),
)
.expect("Failed to show GLSL");
- s
+ result
+}
+
+/// Detect if shader source uses WebGL 2.0 syntax
+fn detect_webgl2_syntax(source: &str) -> bool {
+ // WebGL 2.0 indicators
+ if source.contains("out vec4") ||
+ source.contains("in ") ||
+ source.contains("layout(") ||
+ source.contains("uniform") && source.contains("buffer") ||
+ source.contains("texture(") {
+ return true;
+ }
+
+ // WebGL 1.0 indicators
+ if source.contains("gl_FragColor") ||
+ source.contains("attribute ") ||
+ source.contains("varying ") ||
+ source.contains("texture2D(") {
+ return false;
+ }
+
+ // Default to WebGL 1.0 if uncertain
+ false
}
#[cxx::bridge(namespace = "holocron::webgl")]
@@ -204,6 +239,44 @@ void main() {
)
}
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl1() {
+ // Test case for the issue: shader without #version directive should get #version 100
+ let source_str = r#"void main() {
+ gl_FragColor = vec4(0., 1., 0., 1.);
+}"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"#version 100
+void main() {
+ gl_FragColor = vec4(0., 1., 0., 1.);
+}
+"#
+ );
+ }
+
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl2() {
+ // Test case for WebGL 2.0 shader without #version directive
+ let source_str = r#"precision mediump float;
+out vec4 fragColor;
+void main() {
+ fragColor = vec4(0., 1., 0., 1.);
+}"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"#version 300 es
+precision mediump float;
+out vec4 fragColor;
+void main() {
+ fragColor = vec4(0., 1., 0., 1.);
+}
+"#
+ );
+ }
+
#[test]
#[ignore]
fn test_patch_glsl_source_elif_expand() {
diff --git a/test-shader-version-fix.html b/test-shader-version-fix.html
new file mode 100644
index 000000000..310cdc4db
--- /dev/null
+++ b/test-shader-version-fix.html
@@ -0,0 +1,156 @@
+
+
+
+
+
+ GLSL Version Directive Fix Test
+
+
+
+ GLSL Version Directive Fix Test
+ This test validates that shaders without #version directives are properly handled by the JSAR Runtime GLSL patcher.
+
+
+
+
+
+
\ No newline at end of file
From 9db99f4b8cfffb1693d9e8a4d394135c5e451521 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 26 Sep 2025 03:24:20 +0000
Subject: [PATCH 03/15] Refine GLSL version detection and add comprehensive
test coverage
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
crates/jsbindings/webgl.rs | 97 +++++++++++++++++--
.../test-shader-version-fix.html | 0
2 files changed, 89 insertions(+), 8 deletions(-)
rename test-shader-version-fix.html => fixtures/html/webgl-conformance/test-shader-version-fix.html (100%)
diff --git a/crates/jsbindings/webgl.rs b/crates/jsbindings/webgl.rs
index 64644e0fe..4cfc4504f 100644
--- a/crates/jsbindings/webgl.rs
+++ b/crates/jsbindings/webgl.rs
@@ -132,24 +132,37 @@ fn patch_glsl_source_from_str(s: &str) -> String {
/// Detect if shader source uses WebGL 2.0 syntax
fn detect_webgl2_syntax(source: &str) -> bool {
- // WebGL 2.0 indicators
+ // WebGL 2.0 strong indicators (these only exist in WebGL 2.0)
if source.contains("out vec4") ||
- source.contains("in ") ||
- source.contains("layout(") ||
- source.contains("uniform") && source.contains("buffer") ||
- source.contains("texture(") {
+ source.contains("out mediump") ||
+ source.contains("out lowp") ||
+ source.contains("out highp") ||
+ source.contains("layout(location") {
return true;
}
- // WebGL 1.0 indicators
+ // Check for WebGL 2.0 built-ins
+ if source.contains("texture(") && !source.contains("texture2D(") {
+ return true;
+ }
+
+ // WebGL 1.0 strong indicators (these are deprecated/removed in WebGL 2.0)
if source.contains("gl_FragColor") ||
+ source.contains("gl_FragData") ||
source.contains("attribute ") ||
source.contains("varying ") ||
- source.contains("texture2D(") {
+ source.contains("texture2D(") ||
+ source.contains("textureCube(") {
return false;
}
- // Default to WebGL 1.0 if uncertain
+ // Check for 'in ' keyword (could be WebGL 2.0, but be more specific)
+ if source.contains("in vec") || source.contains("in mediump") ||
+ source.contains("in lowp") || source.contains("in highp") {
+ return true;
+ }
+
+ // Default to WebGL 1.0 if uncertain (safer for compatibility)
false
}
@@ -277,6 +290,74 @@ void main() {
);
}
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl1_vertex() {
+ // Test WebGL 1.0 vertex shader without version directive
+ let source_str = r#"attribute vec4 position;
+varying vec2 vTexCoord;
+void main() {
+ gl_Position = position;
+ vTexCoord = position.xy;
+}"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"#version 100
+attribute vec4 position;
+varying vec2 vTexCoord;
+void main() {
+ gl_Position = position;
+ vTexCoord = position.xy;
+}
+"#
+ );
+ }
+
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl2_vertex() {
+ // Test WebGL 2.0 vertex shader without version directive
+ let source_str = r#"in vec4 position;
+in vec2 texCoord;
+out vec2 vTexCoord;
+void main() {
+ gl_Position = position;
+ vTexCoord = texCoord;
+}"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"#version 300 es
+in vec4 position;
+in vec2 texCoord;
+out vec2 vTexCoord;
+void main() {
+ gl_Position = position;
+ vTexCoord = texCoord;
+}
+"#
+ );
+ }
+
+ #[test]
+ fn test_patch_glsl_source_existing_version_unchanged() {
+ // Test that existing version directives are preserved and reordered
+ let source_str = r#"precision mediump float;
+#version 300 es
+void main() {
+ gl_FragColor = vec4(0., 1., 0., 1.);
+}"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"#version 300 es
+precision mediump float;
+void main() {
+ gl_FragColor = vec4(0., 1., 0., 1.);
+}
+"#
+ );
+ }
+
#[test]
#[ignore]
fn test_patch_glsl_source_elif_expand() {
diff --git a/test-shader-version-fix.html b/fixtures/html/webgl-conformance/test-shader-version-fix.html
similarity index 100%
rename from test-shader-version-fix.html
rename to fixtures/html/webgl-conformance/test-shader-version-fix.html
From caf493ca7dc3bb8f28a622ebfc2c9d7e6b2590cf Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 28 Sep 2025 09:23:56 +0000
Subject: [PATCH 04/15] Make GLSL version handling WebGL standards-compliant
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
crates/jsbindings/webgl.rs | 29 +++++++++----------
.../test-shader-version-fix.html | 18 ++++++++----
src/renderer/render_api_opengles.cpp | 9 ++++++
3 files changed, 34 insertions(+), 22 deletions(-)
diff --git a/crates/jsbindings/webgl.rs b/crates/jsbindings/webgl.rs
index 4cfc4504f..b2d360cd9 100644
--- a/crates/jsbindings/webgl.rs
+++ b/crates/jsbindings/webgl.rs
@@ -67,14 +67,11 @@ fn patch_glsl_source_from_str(s: &str) -> String {
ast::TranslationUnit, lexer::full::fs::PreprocessorExt, parse::IntoParseBuilderExt,
};
- // Check if version directive is missing and inject appropriate version
- let source_to_parse = if !s.contains("#version") {
- let version_directive = if detect_webgl2_syntax(s) {
- "#version 300 es\n"
- } else {
- "#version 100\n"
- };
- format!("{}{}", version_directive, s)
+ // Only inject version directive for WebGL 2.0 shaders where it's required by spec
+ // WebGL 1.0 shaders should work without version directives per WebGL standard
+ let source_to_parse = if !s.contains("#version") && detect_webgl2_syntax(s) {
+ // WebGL 2.0 requires #version 300 es per specification
+ format!("#version 300 es\n{}", s)
} else {
s.to_string()
};
@@ -254,15 +251,15 @@ void main() {
#[test]
fn test_patch_glsl_source_missing_version_webgl1() {
- // Test case for the issue: shader without #version directive should get #version 100
+ // WebGL 1.0 shaders should NOT get version directive injected (per WebGL spec)
let source_str = r#"void main() {
gl_FragColor = vec4(0., 1., 0., 1.);
}"#;
let patched_source_str = patch_glsl_source_from_str(source_str);
+ // Should remain unchanged - WebGL 1.0 doesn't require #version
assert_eq!(
patched_source_str,
- r#"#version 100
-void main() {
+ r#"void main() {
gl_FragColor = vec4(0., 1., 0., 1.);
}
"#
@@ -271,7 +268,7 @@ void main() {
#[test]
fn test_patch_glsl_source_missing_version_webgl2() {
- // Test case for WebGL 2.0 shader without #version directive
+ // WebGL 2.0 shaders SHOULD get #version 300 es injected (required by spec)
let source_str = r#"precision mediump float;
out vec4 fragColor;
void main() {
@@ -292,7 +289,7 @@ void main() {
#[test]
fn test_patch_glsl_source_missing_version_webgl1_vertex() {
- // Test WebGL 1.0 vertex shader without version directive
+ // WebGL 1.0 vertex shader should NOT get version directive injected
let source_str = r#"attribute vec4 position;
varying vec2 vTexCoord;
void main() {
@@ -300,10 +297,10 @@ void main() {
vTexCoord = position.xy;
}"#;
let patched_source_str = patch_glsl_source_from_str(source_str);
+ // Should remain unchanged - WebGL 1.0 doesn't require #version
assert_eq!(
patched_source_str,
- r#"#version 100
-attribute vec4 position;
+ r#"attribute vec4 position;
varying vec2 vTexCoord;
void main() {
gl_Position = position;
@@ -315,7 +312,7 @@ void main() {
#[test]
fn test_patch_glsl_source_missing_version_webgl2_vertex() {
- // Test WebGL 2.0 vertex shader without version directive
+ // WebGL 2.0 vertex shader SHOULD get #version 300 es injected
let source_str = r#"in vec4 position;
in vec2 texCoord;
out vec2 vTexCoord;
diff --git a/fixtures/html/webgl-conformance/test-shader-version-fix.html b/fixtures/html/webgl-conformance/test-shader-version-fix.html
index 310cdc4db..5a0f1ff3a 100644
--- a/fixtures/html/webgl-conformance/test-shader-version-fix.html
+++ b/fixtures/html/webgl-conformance/test-shader-version-fix.html
@@ -3,7 +3,7 @@
- GLSL Version Directive Fix Test
+ WebGL Standards-Compliant GLSL Version Handling Test
- GLSL Version Directive Fix Test
- This test validates that shaders without #version directives are properly handled by the JSAR Runtime GLSL patcher.
+ WebGL Standards-Compliant GLSL Version Handling Test
+ This test validates WebGL standards-compliant GLSL version handling:
+
+ - WebGL 1.0: Version directives are optional (should default to GLSL ES 1.00)
+ - WebGL 2.0: Version directives are required (#version 300 es)
+
@@ -84,8 +88,9 @@ GLSL Version Directive Fix Test
addResult(`WebGL context available: ${gl.constructor.name}`, true);
addResult(`WebGL version: ${gl.getParameter(gl.VERSION)}`, true);
- // Test 1: WebGL 1.0 shaders without version directive
- const webgl1Section = addSection('WebGL 1.0 Shaders (should get #version 100)');
+ // Test 1: WebGL 1.0 shaders without version directive
+ // Per WebGL spec, these should work WITHOUT version directives
+ const webgl1Section = addSection('WebGL 1.0 Shaders (should work without #version per WebGL spec)');
// The exact failing case from the issue
const fragmentShaderNoVersion = `
@@ -105,8 +110,9 @@ GLSL Version Directive Fix Test
'Fragment shader without #version (using gl_FragColor)', webgl1Section);
// Test 2: WebGL 2.0 style shaders without version directive
+ // Per WebGL spec, these REQUIRE #version 300 es
if (gl.constructor.name.includes('WebGL2')) {
- const webgl2Section = addSection('WebGL 2.0 Shaders (should get #version 300 es)');
+ const webgl2Section = addSection('WebGL 2.0 Shaders (MUST have #version 300 es per WebGL spec)');
const vertexShaderWebGL2 = `
in vec4 position;
diff --git a/src/renderer/render_api_opengles.cpp b/src/renderer/render_api_opengles.cpp
index 606c09cbf..e88b4d9f5 100644
--- a/src/renderer/render_api_opengles.cpp
+++ b/src/renderer/render_api_opengles.cpp
@@ -740,6 +740,15 @@ class RHI_OpenGL : public TrRenderHardwareInterface
{
string line;
size_t pos = 0;
+ bool hasVersionDirective = source.find("#version") != string::npos;
+
+#ifdef __APPLE__
+ // On macOS, we need to ensure all shaders have a version directive for compatibility
+ if (!hasVersionDirective)
+ {
+ fixedSource += "#version 410 core\n";
+ }
+#endif
while (pos < source.size())
{
From e7ca3f79b535c65620962a354b516884ea8976d3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 28 Sep 2025 11:26:30 +0000
Subject: [PATCH 05/15] Implement ANGLE-style WebGL 1.0 to desktop OpenGL
translation
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
src/renderer/render_api_opengles.cpp | 115 +++++++++++++++++++++++++--
1 file changed, 107 insertions(+), 8 deletions(-)
diff --git a/src/renderer/render_api_opengles.cpp b/src/renderer/render_api_opengles.cpp
index e88b4d9f5..0e31b05a1 100644
--- a/src/renderer/render_api_opengles.cpp
+++ b/src/renderer/render_api_opengles.cpp
@@ -50,6 +50,108 @@ void getline(const string &input, string &line, size_t &pos, char delim = '\n')
}
}
+/**
+ * Patch WebGL/GLSL ES shader source to work with desktop OpenGL Core Profile.
+ * This implements the same kind of translation that browsers like Chrome do via ANGLE.
+ *
+ * Translates:
+ * - attribute -> in (vertex shaders)
+ * - varying -> out (vertex shaders) / in (fragment shaders)
+ * - gl_FragColor -> custom out vec4 fragColor
+ * - removes precision qualifiers (not valid in desktop GL)
+ * - adds appropriate #version directive
+ */
+string PatchWebGLShaderForDesktopGL(const string &source, GLuint shaderHandle)
+{
+ // Query the shader type from OpenGL
+ GLint shaderType;
+ glGetShaderiv(shaderHandle, GL_SHADER_TYPE, &shaderType);
+
+ istringstream ss(source);
+ string line, patched;
+ bool hasFragColor = false;
+ bool hasVersionDirective = source.find("#version") != string::npos;
+
+ // Add version directive if not present
+ if (!hasVersionDirective)
+ {
+ patched += "#version 410 core\n";
+ }
+
+ while (getline(ss, line))
+ {
+ string processedLine = line;
+
+ // Replace existing #version with desktop GL version
+ if (processedLine.find("#version") != string::npos)
+ {
+ processedLine = "#version 410 core";
+ }
+
+ // Remove precision qualifiers (not valid in desktop GL core)
+ if (processedLine.find("precision ") != string::npos)
+ {
+ continue; // Skip precision lines entirely
+ }
+
+ // Replace WebGL 1.0 keywords with desktop GL equivalents
+ size_t pos;
+
+ // attribute -> in (vertex shaders only)
+ if (shaderType == GL_VERTEX_SHADER && (pos = processedLine.find("attribute ")) != string::npos)
+ {
+ processedLine.replace(pos, 10, "in ");
+ }
+
+ // varying -> out (vertex) / in (fragment)
+ if ((pos = processedLine.find("varying ")) != string::npos)
+ {
+ if (shaderType == GL_VERTEX_SHADER)
+ {
+ processedLine.replace(pos, 8, "out ");
+ }
+ else
+ {
+ processedLine.replace(pos, 8, "in ");
+ }
+ }
+
+ // gl_FragColor -> fragColor (fragment shaders only)
+ if (shaderType == GL_FRAGMENT_SHADER && (pos = processedLine.find("gl_FragColor")) != string::npos)
+ {
+ hasFragColor = true;
+ processedLine.replace(pos, 12, "fragColor");
+ }
+
+ // gl_FragData -> fragColor (fragment shaders only)
+ if (shaderType == GL_FRAGMENT_SHADER && (pos = processedLine.find("gl_FragData")) != string::npos)
+ {
+ // This is more complex as it involves array access, but for now replace with fragColor
+ processedLine.replace(pos, 11, "fragColor");
+ }
+
+ patched += processedLine + "\n";
+ }
+
+ // Add output declaration for fragment color if gl_FragColor was used
+ if (shaderType == GL_FRAGMENT_SHADER && hasFragColor)
+ {
+ // Insert after version directive but before other content
+ size_t versionPos = patched.find("#version 410 core\n");
+ if (versionPos != string::npos)
+ {
+ size_t insertPos = versionPos + strlen("#version 410 core\n");
+ patched.insert(insertPos, "out vec4 fragColor;\n");
+ }
+ else
+ {
+ patched = "out vec4 fragColor;\n" + patched;
+ }
+ }
+
+ return patched;
+}
+
class RHI_OpenGL : public TrRenderHardwareInterface
{
private:
@@ -743,27 +845,24 @@ class RHI_OpenGL : public TrRenderHardwareInterface
bool hasVersionDirective = source.find("#version") != string::npos;
#ifdef __APPLE__
- // On macOS, we need to ensure all shaders have a version directive for compatibility
+ // On macOS, we need to translate WebGL/GLSL ES shaders to desktop OpenGL Core Profile
+ fixedSource = PatchWebGLShaderForDesktopGL(source, shader);
+#else
+ // On other platforms, use the original approach
if (!hasVersionDirective)
{
fixedSource += "#version 410 core\n";
}
-#endif
while (pos < source.size())
{
getline(source, line, pos);
string newLine = line;
-#ifdef __APPLE__
- /**
- * FIXME(Yorkie): This is a workaround for the shader source on macOS, we need to replace the version to 410 core
- * directly, a better solution is to use the shader preprocessor like google/angle to handle this.
- */
if (line.find("#version") != string::npos)
newLine = "#version 410 core";
-#endif
fixedSource += newLine + "\n";
}
+#endif
}
const char *sourceStr = fixedSource.c_str();
From 84dae549570a7d0087eaa6bb38e66263687a9303 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 28 Sep 2025 12:02:36 +0000
Subject: [PATCH 06/15] Fix gl_FragData array access translation for desktop
OpenGL compatibility
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
src/renderer/render_api_opengles.cpp | 52 +++++++++++++++++++++++++---
1 file changed, 48 insertions(+), 4 deletions(-)
diff --git a/src/renderer/render_api_opengles.cpp b/src/renderer/render_api_opengles.cpp
index 0e31b05a1..224db65ac 100644
--- a/src/renderer/render_api_opengles.cpp
+++ b/src/renderer/render_api_opengles.cpp
@@ -58,6 +58,8 @@ void getline(const string &input, string &line, size_t &pos, char delim = '\n')
* - attribute -> in (vertex shaders)
* - varying -> out (vertex shaders) / in (fragment shaders)
* - gl_FragColor -> custom out vec4 fragColor
+ * - gl_FragData[index] -> fragColor (single output, no MRT support yet)
+ * - fragColor[index] -> fragColor (fix array access patterns)
* - removes precision qualifiers (not valid in desktop GL)
* - adds appropriate #version directive
*/
@@ -123,11 +125,53 @@ string PatchWebGLShaderForDesktopGL(const string &source, GLuint shaderHandle)
processedLine.replace(pos, 12, "fragColor");
}
- // gl_FragData -> fragColor (fragment shaders only)
- if (shaderType == GL_FRAGMENT_SHADER && (pos = processedLine.find("gl_FragData")) != string::npos)
+ // gl_FragData[0] -> fragColor (fragment shaders only)
+ // Handle the common case of gl_FragData[0] which should become fragColor (single output)
+ if (shaderType == GL_FRAGMENT_SHADER && (pos = processedLine.find("gl_FragData[0]")) != string::npos)
{
- // This is more complex as it involves array access, but for now replace with fragColor
- processedLine.replace(pos, 11, "fragColor");
+ hasFragColor = true;
+ processedLine.replace(pos, 14, "fragColor");
+ }
+ // Handle general gl_FragData array access (convert to fragColor for single output)
+ else if (shaderType == GL_FRAGMENT_SHADER && (pos = processedLine.find("gl_FragData")) != string::npos)
+ {
+ hasFragColor = true;
+ // Look for array access pattern like gl_FragData[index]
+ size_t arrayStart = processedLine.find("[", pos);
+ if (arrayStart != string::npos)
+ {
+ size_t arrayEnd = processedLine.find("]", arrayStart);
+ if (arrayEnd != string::npos)
+ {
+ // Replace gl_FragData[index] with fragColor (assume single output for now)
+ processedLine.replace(pos, arrayEnd - pos + 1, "fragColor");
+ }
+ }
+ else
+ {
+ // No array access, just replace gl_FragData with fragColor
+ processedLine.replace(pos, 11, "fragColor");
+ }
+ }
+
+ // Handle any remaining fragColor[0] patterns that might have been missed
+ // This covers cases where gl_FragData[0] might have been partially processed
+ if (shaderType == GL_FRAGMENT_SHADER && (pos = processedLine.find("fragColor[0]")) != string::npos)
+ {
+ hasFragColor = true;
+ processedLine.replace(pos, 12, "fragColor");
+ }
+ // Handle other fragColor array access patterns (for single output case)
+ else if (shaderType == GL_FRAGMENT_SHADER && processedLine.find("fragColor[") != string::npos)
+ {
+ hasFragColor = true;
+ size_t fragPos = processedLine.find("fragColor[");
+ size_t arrayEnd = processedLine.find("]", fragPos);
+ if (arrayEnd != string::npos)
+ {
+ // Replace fragColor[index] with fragColor (assume single output)
+ processedLine.replace(fragPos, arrayEnd - fragPos + 1, "fragColor");
+ }
}
patched += processedLine + "\n";
From f97f4715665a37664e48dc0cb13e5b3645c1bf84 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 28 Sep 2025 12:12:59 +0000
Subject: [PATCH 07/15] Move version patch logic from Rust to C++ as requested
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
crates/jsbindings/webgl.rs | 161 +--------------------------
src/renderer/render_api_opengles.cpp | 64 ++++++++++-
2 files changed, 66 insertions(+), 159 deletions(-)
diff --git a/crates/jsbindings/webgl.rs b/crates/jsbindings/webgl.rs
index b2d360cd9..b19796942 100644
--- a/crates/jsbindings/webgl.rs
+++ b/crates/jsbindings/webgl.rs
@@ -67,25 +67,16 @@ fn patch_glsl_source_from_str(s: &str) -> String {
ast::TranslationUnit, lexer::full::fs::PreprocessorExt, parse::IntoParseBuilderExt,
};
- // Only inject version directive for WebGL 2.0 shaders where it's required by spec
- // WebGL 1.0 shaders should work without version directives per WebGL standard
- let source_to_parse = if !s.contains("#version") && detect_webgl2_syntax(s) {
- // WebGL 2.0 requires #version 300 es per specification
- format!("#version 300 es\n{}", s)
- } else {
- s.to_string()
- };
-
let mut processor = glsl_lang_pp::processor::fs::StdProcessor::new();
let mut tu: TranslationUnit = processor
- .open_source(&source_to_parse, Path::new("."))
+ .open_source(s, Path::new("."))
.builder()
.parse()
.map(|(mut tu, _, iter)| {
iter.into_directives().inject(&mut tu);
tu
})
- .expect(format!("Failed to parse GLSL source: \n{}\n", &source_to_parse).as_str());
+ .expect(format!("Failed to parse GLSL source: \n{}\n", s).as_str());
let mut my_glsl_patcher = MyGLSLPatcher {};
tu.visit_mut(&mut my_glsl_patcher);
@@ -117,50 +108,14 @@ fn patch_glsl_source_from_str(s: &str) -> String {
tu.0.splice(0..0, versions_list);
}
- let mut result = String::new();
+ let mut s = String::new();
glsl_transpiler::glsl::show_translation_unit(
- &mut result,
+ &mut s,
&tu,
glsl_transpiler::glsl::FormattingState::default(),
)
.expect("Failed to show GLSL");
- result
-}
-
-/// Detect if shader source uses WebGL 2.0 syntax
-fn detect_webgl2_syntax(source: &str) -> bool {
- // WebGL 2.0 strong indicators (these only exist in WebGL 2.0)
- if source.contains("out vec4") ||
- source.contains("out mediump") ||
- source.contains("out lowp") ||
- source.contains("out highp") ||
- source.contains("layout(location") {
- return true;
- }
-
- // Check for WebGL 2.0 built-ins
- if source.contains("texture(") && !source.contains("texture2D(") {
- return true;
- }
-
- // WebGL 1.0 strong indicators (these are deprecated/removed in WebGL 2.0)
- if source.contains("gl_FragColor") ||
- source.contains("gl_FragData") ||
- source.contains("attribute ") ||
- source.contains("varying ") ||
- source.contains("texture2D(") ||
- source.contains("textureCube(") {
- return false;
- }
-
- // Check for 'in ' keyword (could be WebGL 2.0, but be more specific)
- if source.contains("in vec") || source.contains("in mediump") ||
- source.contains("in lowp") || source.contains("in highp") {
- return true;
- }
-
- // Default to WebGL 1.0 if uncertain (safer for compatibility)
- false
+ s
}
#[cxx::bridge(namespace = "holocron::webgl")]
@@ -249,112 +204,6 @@ void main() {
)
}
- #[test]
- fn test_patch_glsl_source_missing_version_webgl1() {
- // WebGL 1.0 shaders should NOT get version directive injected (per WebGL spec)
- let source_str = r#"void main() {
- gl_FragColor = vec4(0., 1., 0., 1.);
-}"#;
- let patched_source_str = patch_glsl_source_from_str(source_str);
- // Should remain unchanged - WebGL 1.0 doesn't require #version
- assert_eq!(
- patched_source_str,
- r#"void main() {
- gl_FragColor = vec4(0., 1., 0., 1.);
-}
-"#
- );
- }
-
- #[test]
- fn test_patch_glsl_source_missing_version_webgl2() {
- // WebGL 2.0 shaders SHOULD get #version 300 es injected (required by spec)
- let source_str = r#"precision mediump float;
-out vec4 fragColor;
-void main() {
- fragColor = vec4(0., 1., 0., 1.);
-}"#;
- let patched_source_str = patch_glsl_source_from_str(source_str);
- assert_eq!(
- patched_source_str,
- r#"#version 300 es
-precision mediump float;
-out vec4 fragColor;
-void main() {
- fragColor = vec4(0., 1., 0., 1.);
-}
-"#
- );
- }
-
- #[test]
- fn test_patch_glsl_source_missing_version_webgl1_vertex() {
- // WebGL 1.0 vertex shader should NOT get version directive injected
- let source_str = r#"attribute vec4 position;
-varying vec2 vTexCoord;
-void main() {
- gl_Position = position;
- vTexCoord = position.xy;
-}"#;
- let patched_source_str = patch_glsl_source_from_str(source_str);
- // Should remain unchanged - WebGL 1.0 doesn't require #version
- assert_eq!(
- patched_source_str,
- r#"attribute vec4 position;
-varying vec2 vTexCoord;
-void main() {
- gl_Position = position;
- vTexCoord = position.xy;
-}
-"#
- );
- }
-
- #[test]
- fn test_patch_glsl_source_missing_version_webgl2_vertex() {
- // WebGL 2.0 vertex shader SHOULD get #version 300 es injected
- let source_str = r#"in vec4 position;
-in vec2 texCoord;
-out vec2 vTexCoord;
-void main() {
- gl_Position = position;
- vTexCoord = texCoord;
-}"#;
- let patched_source_str = patch_glsl_source_from_str(source_str);
- assert_eq!(
- patched_source_str,
- r#"#version 300 es
-in vec4 position;
-in vec2 texCoord;
-out vec2 vTexCoord;
-void main() {
- gl_Position = position;
- vTexCoord = texCoord;
-}
-"#
- );
- }
-
- #[test]
- fn test_patch_glsl_source_existing_version_unchanged() {
- // Test that existing version directives are preserved and reordered
- let source_str = r#"precision mediump float;
-#version 300 es
-void main() {
- gl_FragColor = vec4(0., 1., 0., 1.);
-}"#;
- let patched_source_str = patch_glsl_source_from_str(source_str);
- assert_eq!(
- patched_source_str,
- r#"#version 300 es
-precision mediump float;
-void main() {
- gl_FragColor = vec4(0., 1., 0., 1.);
-}
-"#
- );
- }
-
#[test]
#[ignore]
fn test_patch_glsl_source_elif_expand() {
diff --git a/src/renderer/render_api_opengles.cpp b/src/renderer/render_api_opengles.cpp
index 224db65ac..b2de2e694 100644
--- a/src/renderer/render_api_opengles.cpp
+++ b/src/renderer/render_api_opengles.cpp
@@ -50,6 +50,52 @@ void getline(const string &input, string &line, size_t &pos, char delim = '\n')
}
}
+/**
+ * Detect if shader source uses WebGL 2.0 syntax
+ * WebGL 2.0 requires #version 300 es per specification, WebGL 1.0 doesn't require version
+ */
+bool DetectWebGL2Syntax(const string &source)
+{
+ // WebGL 2.0 strong indicators (these only exist in WebGL 2.0)
+ if (source.find("out vec4") != string::npos ||
+ source.find("out mediump") != string::npos ||
+ source.find("out lowp") != string::npos ||
+ source.find("out highp") != string::npos ||
+ source.find("layout(location") != string::npos)
+ {
+ return true;
+ }
+
+ // Check for WebGL 2.0 built-ins
+ if (source.find("texture(") != string::npos && source.find("texture2D(") == string::npos)
+ {
+ return true;
+ }
+
+ // WebGL 1.0 strong indicators (these are deprecated/removed in WebGL 2.0)
+ if (source.find("gl_FragColor") != string::npos ||
+ source.find("gl_FragData") != string::npos ||
+ source.find("attribute ") != string::npos ||
+ source.find("varying ") != string::npos ||
+ source.find("texture2D(") != string::npos ||
+ source.find("textureCube(") != string::npos)
+ {
+ return false;
+ }
+
+ // Check for 'in ' keyword (could be WebGL 2.0, but be more specific)
+ if (source.find("in vec") != string::npos ||
+ source.find("in mediump") != string::npos ||
+ source.find("in lowp") != string::npos ||
+ source.find("in highp") != string::npos)
+ {
+ return true;
+ }
+
+ // Default to WebGL 1.0 for safety
+ return false;
+}
+
/**
* Patch WebGL/GLSL ES shader source to work with desktop OpenGL Core Profile.
* This implements the same kind of translation that browsers like Chrome do via ANGLE.
@@ -892,18 +938,30 @@ class RHI_OpenGL : public TrRenderHardwareInterface
// On macOS, we need to translate WebGL/GLSL ES shaders to desktop OpenGL Core Profile
fixedSource = PatchWebGLShaderForDesktopGL(source, shader);
#else
- // On other platforms, use the original approach
+ // On other platforms, implement WebGL standards-compliant version handling
if (!hasVersionDirective)
{
- fixedSource += "#version 410 core\n";
+ if (DetectWebGL2Syntax(source))
+ {
+ // WebGL 2.0 requires #version 300 es per specification
+ fixedSource += "#version 300 es\n";
+ }
+ // WebGL 1.0 shaders work without version directives per WebGL standard
+ // No version injection needed for WebGL 1.0
}
while (pos < source.size())
{
getline(source, line, pos);
string newLine = line;
+ // Replace existing #version with appropriate version for backend
if (line.find("#version") != string::npos)
- newLine = "#version 410 core";
+ {
+ if (DetectWebGL2Syntax(source))
+ newLine = "#version 300 es";
+ else
+ newLine = "#version 100"; // WebGL 1.0 default
+ }
fixedSource += newLine + "\n";
}
#endif
From 77bba30d69616f21f631b73ee27dec332b51e601 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 29 Sep 2025 03:00:56 +0000
Subject: [PATCH 08/15] Implement client-side WebGL version handling in
MyGLSLPatcher
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
crates/jsbindings/webgl.rs | 167 ++++++++++++++++++++++++++-
src/renderer/render_api_opengles.cpp | 78 +------------
2 files changed, 164 insertions(+), 81 deletions(-)
diff --git a/crates/jsbindings/webgl.rs b/crates/jsbindings/webgl.rs
index b19796942..46892bf82 100644
--- a/crates/jsbindings/webgl.rs
+++ b/crates/jsbindings/webgl.rs
@@ -67,16 +67,27 @@ fn patch_glsl_source_from_str(s: &str) -> String {
ast::TranslationUnit, lexer::full::fs::PreprocessorExt, parse::IntoParseBuilderExt,
};
+ // WebGL standards-compliant version handling
+ // WebGL 1.0: Version directives are optional (defaults to GLSL ES 1.00)
+ // WebGL 2.0: Version directives are required (#version 300 es)
+ let source_to_parse = if !s.contains("#version") && detect_webgl2_syntax(s) {
+ // WebGL 2.0 requires #version 300 es per specification
+ format!("#version 300 es\n{}", s)
+ } else {
+ // WebGL 1.0 shaders work without version directives per WebGL standard
+ s.to_string()
+ };
+
let mut processor = glsl_lang_pp::processor::fs::StdProcessor::new();
let mut tu: TranslationUnit = processor
- .open_source(s, Path::new("."))
+ .open_source(&source_to_parse, Path::new("."))
.builder()
.parse()
.map(|(mut tu, _, iter)| {
iter.into_directives().inject(&mut tu);
tu
})
- .expect(format!("Failed to parse GLSL source: \n{}\n", s).as_str());
+ .expect(format!("Failed to parse GLSL source: \n{}\n", &source_to_parse).as_str());
let mut my_glsl_patcher = MyGLSLPatcher {};
tu.visit_mut(&mut my_glsl_patcher);
@@ -108,14 +119,51 @@ fn patch_glsl_source_from_str(s: &str) -> String {
tu.0.splice(0..0, versions_list);
}
- let mut s = String::new();
+ let mut result = String::new();
glsl_transpiler::glsl::show_translation_unit(
- &mut s,
+ &mut result,
&tu,
glsl_transpiler::glsl::FormattingState::default(),
)
.expect("Failed to show GLSL");
- s
+ result
+}
+
+/// Detect if shader source uses WebGL 2.0 syntax
+/// WebGL 2.0 requires #version 300 es per specification, WebGL 1.0 doesn't require version
+fn detect_webgl2_syntax(source: &str) -> bool {
+ // WebGL 2.0 strong indicators (these only exist in WebGL 2.0)
+ if source.contains("out vec4") ||
+ source.contains("out mediump") ||
+ source.contains("out lowp") ||
+ source.contains("out highp") ||
+ source.contains("layout(location") {
+ return true;
+ }
+
+ // Check for WebGL 2.0 built-ins
+ if source.contains("texture(") && !source.contains("texture2D(") {
+ return true;
+ }
+
+ // WebGL 1.0 strong indicators (these are deprecated/removed in WebGL 2.0)
+ if source.contains("gl_FragColor") ||
+ source.contains("gl_FragData") ||
+ source.contains("attribute ") ||
+ source.contains("varying ") ||
+ source.contains("texture2D(") ||
+ source.contains("textureCube(") {
+ return false;
+ }
+
+ // Check for 'in ' keyword (could be WebGL 2.0, but be more specific)
+ if source.contains("in vec") || source.contains("in mediump") ||
+ source.contains("in lowp") || source.contains("in highp") {
+ return true;
+ }
+
+ // Default to WebGL 1.0 for safety
+ false
}
#[cxx::bridge(namespace = "holocron::webgl")]
@@ -232,6 +280,115 @@ vec3 test() {
vec3 test() {
return vec3(1., 0., 0.);
}
+"#
+ )
+ }
+
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl1() {
+ // WebGL 1.0 fragment shader without version - should remain unchanged per spec
+ let source_str = r#"
+precision mediump float;
+void main() {
+ gl_FragColor = vec4(0., 1., 0., 1.);
+}"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"precision mediump float;
+void main() {
+ gl_FragColor = vec4(0., 1., 0., 1.);
+}
+"#
+ )
+ }
+
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl2() {
+ // WebGL 2.0 fragment shader without version - should get #version 300 es
+ let source_str = r#"
+precision mediump float;
+out vec4 fragColor;
+void main() {
+ fragColor = vec4(1.0, 0.0, 0.0, 1.0);
+}"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"#version 300 es
+precision mediump float;
+out vec4 fragColor;
+void main() {
+ fragColor = vec4(1., 0., 0., 1.);
+}
+"#
+ )
+ }
+
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl1_vertex() {
+ // WebGL 1.0 vertex shader without version - should remain unchanged
+ let source_str = r#"
+attribute vec4 a_position;
+varying vec2 v_texCoord;
+void main() {
+ gl_Position = a_position;
+ v_texCoord = a_position.xy;
+}"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"attribute vec4 a_position;
+varying vec2 v_texCoord;
+void main() {
+ gl_Position = a_position;
+ v_texCoord = a_position.xy;
+}
+"#
+ )
+ }
+
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl2_vertex() {
+ // WebGL 2.0 vertex shader without version - should get #version 300 es
+ let source_str = r#"
+layout(location = 0) in vec4 a_position;
+out vec2 v_texCoord;
+void main() {
+ gl_Position = a_position;
+ v_texCoord = a_position.xy;
+}"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"#version 300 es
+layout(location = 0) in vec4 a_position;
+out vec2 v_texCoord;
+void main() {
+ gl_Position = a_position;
+ v_texCoord = a_position.xy;
+}
+"#
+ )
+ }
+
+ #[test]
+ fn test_patch_glsl_source_existing_version_unchanged() {
+ // Shader with existing version directive should remain unchanged
+ let source_str = r#"
+#version 100
+precision mediump float;
+void main() {
+ gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);
+}"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"#version 100
+precision mediump float;
+void main() {
+ gl_FragColor = vec4(.5, .5, .5, 1.);
+}
"#
)
}
diff --git a/src/renderer/render_api_opengles.cpp b/src/renderer/render_api_opengles.cpp
index b2de2e694..3f6a00bee 100644
--- a/src/renderer/render_api_opengles.cpp
+++ b/src/renderer/render_api_opengles.cpp
@@ -50,52 +50,6 @@ void getline(const string &input, string &line, size_t &pos, char delim = '\n')
}
}
-/**
- * Detect if shader source uses WebGL 2.0 syntax
- * WebGL 2.0 requires #version 300 es per specification, WebGL 1.0 doesn't require version
- */
-bool DetectWebGL2Syntax(const string &source)
-{
- // WebGL 2.0 strong indicators (these only exist in WebGL 2.0)
- if (source.find("out vec4") != string::npos ||
- source.find("out mediump") != string::npos ||
- source.find("out lowp") != string::npos ||
- source.find("out highp") != string::npos ||
- source.find("layout(location") != string::npos)
- {
- return true;
- }
-
- // Check for WebGL 2.0 built-ins
- if (source.find("texture(") != string::npos && source.find("texture2D(") == string::npos)
- {
- return true;
- }
-
- // WebGL 1.0 strong indicators (these are deprecated/removed in WebGL 2.0)
- if (source.find("gl_FragColor") != string::npos ||
- source.find("gl_FragData") != string::npos ||
- source.find("attribute ") != string::npos ||
- source.find("varying ") != string::npos ||
- source.find("texture2D(") != string::npos ||
- source.find("textureCube(") != string::npos)
- {
- return false;
- }
-
- // Check for 'in ' keyword (could be WebGL 2.0, but be more specific)
- if (source.find("in vec") != string::npos ||
- source.find("in mediump") != string::npos ||
- source.find("in lowp") != string::npos ||
- source.find("in highp") != string::npos)
- {
- return true;
- }
-
- // Default to WebGL 1.0 for safety
- return false;
-}
-
/**
* Patch WebGL/GLSL ES shader source to work with desktop OpenGL Core Profile.
* This implements the same kind of translation that browsers like Chrome do via ANGLE.
@@ -930,40 +884,12 @@ class RHI_OpenGL : public TrRenderHardwareInterface
string fixedSource;
{
- string line;
- size_t pos = 0;
- bool hasVersionDirective = source.find("#version") != string::npos;
-
#ifdef __APPLE__
// On macOS, we need to translate WebGL/GLSL ES shaders to desktop OpenGL Core Profile
fixedSource = PatchWebGLShaderForDesktopGL(source, shader);
#else
- // On other platforms, implement WebGL standards-compliant version handling
- if (!hasVersionDirective)
- {
- if (DetectWebGL2Syntax(source))
- {
- // WebGL 2.0 requires #version 300 es per specification
- fixedSource += "#version 300 es\n";
- }
- // WebGL 1.0 shaders work without version directives per WebGL standard
- // No version injection needed for WebGL 1.0
- }
-
- while (pos < source.size())
- {
- getline(source, line, pos);
- string newLine = line;
- // Replace existing #version with appropriate version for backend
- if (line.find("#version") != string::npos)
- {
- if (DetectWebGL2Syntax(source))
- newLine = "#version 300 es";
- else
- newLine = "#version 100"; // WebGL 1.0 default
- }
- fixedSource += newLine + "\n";
- }
+ // On other platforms, pass through the source as-is since client-side handles version logic
+ fixedSource = source;
#endif
}
From a347c64910aff909f8537ac302a463cd27c86e63 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 29 Sep 2025 03:33:50 +0000
Subject: [PATCH 09/15] Move WebGL version handling to client-side WebGLContext
as requested
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
crates/jsbindings/webgl.rs | 167 +-------------------------
src/client/graphics/webgl_context.cpp | 71 ++++++++++-
src/renderer/render_api_opengles.cpp | 166 +++----------------------
3 files changed, 90 insertions(+), 314 deletions(-)
diff --git a/crates/jsbindings/webgl.rs b/crates/jsbindings/webgl.rs
index 46892bf82..b19796942 100644
--- a/crates/jsbindings/webgl.rs
+++ b/crates/jsbindings/webgl.rs
@@ -67,27 +67,16 @@ fn patch_glsl_source_from_str(s: &str) -> String {
ast::TranslationUnit, lexer::full::fs::PreprocessorExt, parse::IntoParseBuilderExt,
};
- // WebGL standards-compliant version handling
- // WebGL 1.0: Version directives are optional (defaults to GLSL ES 1.00)
- // WebGL 2.0: Version directives are required (#version 300 es)
- let source_to_parse = if !s.contains("#version") && detect_webgl2_syntax(s) {
- // WebGL 2.0 requires #version 300 es per specification
- format!("#version 300 es\n{}", s)
- } else {
- // WebGL 1.0 shaders work without version directives per WebGL standard
- s.to_string()
- };
-
let mut processor = glsl_lang_pp::processor::fs::StdProcessor::new();
let mut tu: TranslationUnit = processor
- .open_source(&source_to_parse, Path::new("."))
+ .open_source(s, Path::new("."))
.builder()
.parse()
.map(|(mut tu, _, iter)| {
iter.into_directives().inject(&mut tu);
tu
})
- .expect(format!("Failed to parse GLSL source: \n{}\n", &source_to_parse).as_str());
+ .expect(format!("Failed to parse GLSL source: \n{}\n", s).as_str());
let mut my_glsl_patcher = MyGLSLPatcher {};
tu.visit_mut(&mut my_glsl_patcher);
@@ -119,51 +108,14 @@ fn patch_glsl_source_from_str(s: &str) -> String {
tu.0.splice(0..0, versions_list);
}
- let mut result = String::new();
+ let mut s = String::new();
glsl_transpiler::glsl::show_translation_unit(
- &mut result,
+ &mut s,
&tu,
glsl_transpiler::glsl::FormattingState::default(),
)
.expect("Failed to show GLSL");
- result
-}
-
-/// Detect if shader source uses WebGL 2.0 syntax
-/// WebGL 2.0 requires #version 300 es per specification, WebGL 1.0 doesn't require version
-fn detect_webgl2_syntax(source: &str) -> bool {
- // WebGL 2.0 strong indicators (these only exist in WebGL 2.0)
- if source.contains("out vec4") ||
- source.contains("out mediump") ||
- source.contains("out lowp") ||
- source.contains("out highp") ||
- source.contains("layout(location") {
- return true;
- }
-
- // Check for WebGL 2.0 built-ins
- if source.contains("texture(") && !source.contains("texture2D(") {
- return true;
- }
-
- // WebGL 1.0 strong indicators (these are deprecated/removed in WebGL 2.0)
- if source.contains("gl_FragColor") ||
- source.contains("gl_FragData") ||
- source.contains("attribute ") ||
- source.contains("varying ") ||
- source.contains("texture2D(") ||
- source.contains("textureCube(") {
- return false;
- }
-
- // Check for 'in ' keyword (could be WebGL 2.0, but be more specific)
- if source.contains("in vec") || source.contains("in mediump") ||
- source.contains("in lowp") || source.contains("in highp") {
- return true;
- }
-
- // Default to WebGL 1.0 for safety
- false
+ s
}
#[cxx::bridge(namespace = "holocron::webgl")]
@@ -280,115 +232,6 @@ vec3 test() {
vec3 test() {
return vec3(1., 0., 0.);
}
-"#
- )
- }
-
- #[test]
- fn test_patch_glsl_source_missing_version_webgl1() {
- // WebGL 1.0 fragment shader without version - should remain unchanged per spec
- let source_str = r#"
-precision mediump float;
-void main() {
- gl_FragColor = vec4(0., 1., 0., 1.);
-}"#;
- let patched_source_str = patch_glsl_source_from_str(source_str);
- assert_eq!(
- patched_source_str,
- r#"precision mediump float;
-void main() {
- gl_FragColor = vec4(0., 1., 0., 1.);
-}
-"#
- )
- }
-
- #[test]
- fn test_patch_glsl_source_missing_version_webgl2() {
- // WebGL 2.0 fragment shader without version - should get #version 300 es
- let source_str = r#"
-precision mediump float;
-out vec4 fragColor;
-void main() {
- fragColor = vec4(1.0, 0.0, 0.0, 1.0);
-}"#;
- let patched_source_str = patch_glsl_source_from_str(source_str);
- assert_eq!(
- patched_source_str,
- r#"#version 300 es
-precision mediump float;
-out vec4 fragColor;
-void main() {
- fragColor = vec4(1., 0., 0., 1.);
-}
-"#
- )
- }
-
- #[test]
- fn test_patch_glsl_source_missing_version_webgl1_vertex() {
- // WebGL 1.0 vertex shader without version - should remain unchanged
- let source_str = r#"
-attribute vec4 a_position;
-varying vec2 v_texCoord;
-void main() {
- gl_Position = a_position;
- v_texCoord = a_position.xy;
-}"#;
- let patched_source_str = patch_glsl_source_from_str(source_str);
- assert_eq!(
- patched_source_str,
- r#"attribute vec4 a_position;
-varying vec2 v_texCoord;
-void main() {
- gl_Position = a_position;
- v_texCoord = a_position.xy;
-}
-"#
- )
- }
-
- #[test]
- fn test_patch_glsl_source_missing_version_webgl2_vertex() {
- // WebGL 2.0 vertex shader without version - should get #version 300 es
- let source_str = r#"
-layout(location = 0) in vec4 a_position;
-out vec2 v_texCoord;
-void main() {
- gl_Position = a_position;
- v_texCoord = a_position.xy;
-}"#;
- let patched_source_str = patch_glsl_source_from_str(source_str);
- assert_eq!(
- patched_source_str,
- r#"#version 300 es
-layout(location = 0) in vec4 a_position;
-out vec2 v_texCoord;
-void main() {
- gl_Position = a_position;
- v_texCoord = a_position.xy;
-}
-"#
- )
- }
-
- #[test]
- fn test_patch_glsl_source_existing_version_unchanged() {
- // Shader with existing version directive should remain unchanged
- let source_str = r#"
-#version 100
-precision mediump float;
-void main() {
- gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);
-}"#;
- let patched_source_str = patch_glsl_source_from_str(source_str);
- assert_eq!(
- patched_source_str,
- r#"#version 100
-precision mediump float;
-void main() {
- gl_FragColor = vec4(.5, .5, .5, 1.);
-}
"#
)
}
diff --git a/src/client/graphics/webgl_context.cpp b/src/client/graphics/webgl_context.cpp
index 07ab5957e..9f23a0b55 100644
--- a/src/client/graphics/webgl_context.cpp
+++ b/src/client/graphics/webgl_context.cpp
@@ -28,6 +28,71 @@ namespace client_graphics
throw runtime_error(msg); \
}
+ /// Detect if shader source uses WebGL 2.0 syntax
+ /// WebGL 2.0 requires #version 300 es per specification, WebGL 1.0 doesn't require version
+ bool detectWebGL2Syntax(const string &source)
+ {
+ // WebGL 2.0 strong indicators (these only exist in WebGL 2.0)
+ if (source.find("out vec4") != string::npos ||
+ source.find("out mediump") != string::npos ||
+ source.find("out lowp") != string::npos ||
+ source.find("out highp") != string::npos ||
+ source.find("layout(location") != string::npos)
+ {
+ return true;
+ }
+
+ // Check for WebGL 2.0 built-ins - texture() without texture2D()
+ if (source.find("texture(") != string::npos && source.find("texture2D(") == string::npos)
+ {
+ return true;
+ }
+
+ // WebGL 1.0 strong indicators (these are deprecated/removed in WebGL 2.0)
+ if (source.find("gl_FragColor") != string::npos ||
+ source.find("gl_FragData") != string::npos ||
+ source.find("attribute ") != string::npos ||
+ source.find("varying ") != string::npos ||
+ source.find("texture2D(") != string::npos ||
+ source.find("textureCube(") != string::npos)
+ {
+ return false;
+ }
+
+ // Check for 'in ' keyword (could be WebGL 2.0, but be more specific)
+ if (source.find("in vec") != string::npos ||
+ source.find("in mediump") != string::npos ||
+ source.find("in lowp") != string::npos ||
+ source.find("in highp") != string::npos)
+ {
+ return true;
+ }
+
+ // Default to WebGL 1.0 for safety
+ return false;
+ }
+
+ /// Apply WebGL standards-compliant version handling
+ /// WebGL 1.0: Version directives are optional (defaults to GLSL ES 1.00)
+ /// WebGL 2.0: Version directives are required (#version 300 es)
+ string applyWebGLVersionHandling(const string &source)
+ {
+ // If shader already has a version directive, leave it unchanged
+ if (source.find("#version") != string::npos)
+ {
+ return source;
+ }
+
+ // WebGL 2.0 requires #version 300 es per specification
+ if (detectWebGL2Syntax(source))
+ {
+ return "#version 300 es\n" + source;
+ }
+
+ // WebGL 1.0 shaders work without version directives per WebGL standard
+ return source;
+ }
+
void WebGLState::Restore(WebGLState &state, shared_ptr context)
{
context->useProgram(state.program.value_or(nullptr));
@@ -350,7 +415,11 @@ namespace client_graphics
void WebGLContext::shaderSource(shared_ptr shader, const string &source)
{
- shader->source = GLSLSourcePatcher2::GetPatchedSource(source);
+ // Apply WebGL standards-compliant version handling first
+ string processedSource = applyWebGLVersionHandling(source);
+
+ // Then apply GLSL patching (reordering of directives, etc.)
+ shader->source = GLSLSourcePatcher2::GetPatchedSource(processedSource);
auto req = ShaderSourceCommandBufferRequest(shader->id, shader->source);
sendCommandBufferRequest(req);
}
diff --git a/src/renderer/render_api_opengles.cpp b/src/renderer/render_api_opengles.cpp
index 3f6a00bee..606c09cbf 100644
--- a/src/renderer/render_api_opengles.cpp
+++ b/src/renderer/render_api_opengles.cpp
@@ -50,152 +50,6 @@ void getline(const string &input, string &line, size_t &pos, char delim = '\n')
}
}
-/**
- * Patch WebGL/GLSL ES shader source to work with desktop OpenGL Core Profile.
- * This implements the same kind of translation that browsers like Chrome do via ANGLE.
- *
- * Translates:
- * - attribute -> in (vertex shaders)
- * - varying -> out (vertex shaders) / in (fragment shaders)
- * - gl_FragColor -> custom out vec4 fragColor
- * - gl_FragData[index] -> fragColor (single output, no MRT support yet)
- * - fragColor[index] -> fragColor (fix array access patterns)
- * - removes precision qualifiers (not valid in desktop GL)
- * - adds appropriate #version directive
- */
-string PatchWebGLShaderForDesktopGL(const string &source, GLuint shaderHandle)
-{
- // Query the shader type from OpenGL
- GLint shaderType;
- glGetShaderiv(shaderHandle, GL_SHADER_TYPE, &shaderType);
-
- istringstream ss(source);
- string line, patched;
- bool hasFragColor = false;
- bool hasVersionDirective = source.find("#version") != string::npos;
-
- // Add version directive if not present
- if (!hasVersionDirective)
- {
- patched += "#version 410 core\n";
- }
-
- while (getline(ss, line))
- {
- string processedLine = line;
-
- // Replace existing #version with desktop GL version
- if (processedLine.find("#version") != string::npos)
- {
- processedLine = "#version 410 core";
- }
-
- // Remove precision qualifiers (not valid in desktop GL core)
- if (processedLine.find("precision ") != string::npos)
- {
- continue; // Skip precision lines entirely
- }
-
- // Replace WebGL 1.0 keywords with desktop GL equivalents
- size_t pos;
-
- // attribute -> in (vertex shaders only)
- if (shaderType == GL_VERTEX_SHADER && (pos = processedLine.find("attribute ")) != string::npos)
- {
- processedLine.replace(pos, 10, "in ");
- }
-
- // varying -> out (vertex) / in (fragment)
- if ((pos = processedLine.find("varying ")) != string::npos)
- {
- if (shaderType == GL_VERTEX_SHADER)
- {
- processedLine.replace(pos, 8, "out ");
- }
- else
- {
- processedLine.replace(pos, 8, "in ");
- }
- }
-
- // gl_FragColor -> fragColor (fragment shaders only)
- if (shaderType == GL_FRAGMENT_SHADER && (pos = processedLine.find("gl_FragColor")) != string::npos)
- {
- hasFragColor = true;
- processedLine.replace(pos, 12, "fragColor");
- }
-
- // gl_FragData[0] -> fragColor (fragment shaders only)
- // Handle the common case of gl_FragData[0] which should become fragColor (single output)
- if (shaderType == GL_FRAGMENT_SHADER && (pos = processedLine.find("gl_FragData[0]")) != string::npos)
- {
- hasFragColor = true;
- processedLine.replace(pos, 14, "fragColor");
- }
- // Handle general gl_FragData array access (convert to fragColor for single output)
- else if (shaderType == GL_FRAGMENT_SHADER && (pos = processedLine.find("gl_FragData")) != string::npos)
- {
- hasFragColor = true;
- // Look for array access pattern like gl_FragData[index]
- size_t arrayStart = processedLine.find("[", pos);
- if (arrayStart != string::npos)
- {
- size_t arrayEnd = processedLine.find("]", arrayStart);
- if (arrayEnd != string::npos)
- {
- // Replace gl_FragData[index] with fragColor (assume single output for now)
- processedLine.replace(pos, arrayEnd - pos + 1, "fragColor");
- }
- }
- else
- {
- // No array access, just replace gl_FragData with fragColor
- processedLine.replace(pos, 11, "fragColor");
- }
- }
-
- // Handle any remaining fragColor[0] patterns that might have been missed
- // This covers cases where gl_FragData[0] might have been partially processed
- if (shaderType == GL_FRAGMENT_SHADER && (pos = processedLine.find("fragColor[0]")) != string::npos)
- {
- hasFragColor = true;
- processedLine.replace(pos, 12, "fragColor");
- }
- // Handle other fragColor array access patterns (for single output case)
- else if (shaderType == GL_FRAGMENT_SHADER && processedLine.find("fragColor[") != string::npos)
- {
- hasFragColor = true;
- size_t fragPos = processedLine.find("fragColor[");
- size_t arrayEnd = processedLine.find("]", fragPos);
- if (arrayEnd != string::npos)
- {
- // Replace fragColor[index] with fragColor (assume single output)
- processedLine.replace(fragPos, arrayEnd - fragPos + 1, "fragColor");
- }
- }
-
- patched += processedLine + "\n";
- }
-
- // Add output declaration for fragment color if gl_FragColor was used
- if (shaderType == GL_FRAGMENT_SHADER && hasFragColor)
- {
- // Insert after version directive but before other content
- size_t versionPos = patched.find("#version 410 core\n");
- if (versionPos != string::npos)
- {
- size_t insertPos = versionPos + strlen("#version 410 core\n");
- patched.insert(insertPos, "out vec4 fragColor;\n");
- }
- else
- {
- patched = "out vec4 fragColor;\n" + patched;
- }
- }
-
- return patched;
-}
-
class RHI_OpenGL : public TrRenderHardwareInterface
{
private:
@@ -884,13 +738,23 @@ class RHI_OpenGL : public TrRenderHardwareInterface
string fixedSource;
{
+ string line;
+ size_t pos = 0;
+
+ while (pos < source.size())
+ {
+ getline(source, line, pos);
+ string newLine = line;
#ifdef __APPLE__
- // On macOS, we need to translate WebGL/GLSL ES shaders to desktop OpenGL Core Profile
- fixedSource = PatchWebGLShaderForDesktopGL(source, shader);
-#else
- // On other platforms, pass through the source as-is since client-side handles version logic
- fixedSource = source;
+ /**
+ * FIXME(Yorkie): This is a workaround for the shader source on macOS, we need to replace the version to 410 core
+ * directly, a better solution is to use the shader preprocessor like google/angle to handle this.
+ */
+ if (line.find("#version") != string::npos)
+ newLine = "#version 410 core";
#endif
+ fixedSource += newLine + "\n";
+ }
}
const char *sourceStr = fixedSource.c_str();
From a223e7c922b6e1697422c4d0ad896d7f739bce82 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 29 Sep 2025 04:09:43 +0000
Subject: [PATCH 10/15] Move WebGL version handling to MyGLSLPatcher in
webgl.rs as requested
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
crates/jsbindings/css/mod.rs | 2 +-
.../jsbindings/css/stylesheets/style_rule.rs | 2 +-
crates/jsbindings/webgl.rs | 168 +++++++++++++++++-
src/client/graphics/webgl_context.cpp | 71 +-------
4 files changed, 169 insertions(+), 74 deletions(-)
diff --git a/crates/jsbindings/css/mod.rs b/crates/jsbindings/css/mod.rs
index db12d37bf..e7bd1bfbb 100644
--- a/crates/jsbindings/css/mod.rs
+++ b/crates/jsbindings/css/mod.rs
@@ -1,4 +1,4 @@
pub(crate) mod properties;
pub(crate) mod selectors;
-pub(crate) mod values;
pub(crate) mod stylesheets;
+pub(crate) mod values;
diff --git a/crates/jsbindings/css/stylesheets/style_rule.rs b/crates/jsbindings/css/stylesheets/style_rule.rs
index a27b1dc38..f0afe79e5 100644
--- a/crates/jsbindings/css/stylesheets/style_rule.rs
+++ b/crates/jsbindings/css/stylesheets/style_rule.rs
@@ -25,7 +25,7 @@ impl StyleRule {
.borrow()
.to_css(&mut selectors_text)
.expect("Failed to convert selectors to CSS");
-
+
Self {
selectors: SelectorList::new(&handle.selectors),
selectors_text,
diff --git a/crates/jsbindings/webgl.rs b/crates/jsbindings/webgl.rs
index b19796942..88c741b71 100644
--- a/crates/jsbindings/webgl.rs
+++ b/crates/jsbindings/webgl.rs
@@ -62,21 +62,72 @@ impl VisitorMut for MyGLSLPatcher {
}
}
+/// Detect if shader source uses WebGL 2.0 syntax
+/// WebGL 2.0 requires #version 300 es per specification, WebGL 1.0 doesn't require version
+fn detect_webgl2_syntax(s: &str) -> bool {
+ // WebGL 2.0 strong indicators (these only exist in WebGL 2.0)
+ if s.contains("out vec4")
+ || s.contains("out mediump")
+ || s.contains("out lowp")
+ || s.contains("out highp")
+ || s.contains("layout(location")
+ {
+ return true;
+ }
+
+ // Check for WebGL 2.0 built-ins - texture() without texture2D()
+ if s.contains("texture(") && !s.contains("texture2D(") {
+ return true;
+ }
+
+ // WebGL 1.0 strong indicators (these are deprecated/removed in WebGL 2.0)
+ if s.contains("gl_FragColor")
+ || s.contains("gl_FragData")
+ || s.contains("attribute ")
+ || s.contains("varying ")
+ || s.contains("texture2D(")
+ || s.contains("textureCube(")
+ {
+ return false;
+ }
+
+ // Check for 'in ' keyword (could be WebGL 2.0, but be more specific)
+ if s.contains("in vec")
+ || s.contains("in mediump")
+ || s.contains("in lowp")
+ || s.contains("in highp")
+ {
+ return true;
+ }
+
+ // Default to WebGL 1.0 for safety
+ false
+}
+
fn patch_glsl_source_from_str(s: &str) -> String {
use glsl_lang::{
ast::TranslationUnit, lexer::full::fs::PreprocessorExt, parse::IntoParseBuilderExt,
};
+ // Apply WebGL standards-compliant version handling first
+ let source_to_parse = if !s.contains("#version") && detect_webgl2_syntax(s) {
+ // WebGL 2.0 requires #version 300 es per specification
+ format!("#version 300 es\n{}", s)
+ } else {
+ // WebGL 1.0 shaders work without version directives per WebGL standard
+ s.to_string()
+ };
+
let mut processor = glsl_lang_pp::processor::fs::StdProcessor::new();
let mut tu: TranslationUnit = processor
- .open_source(s, Path::new("."))
+ .open_source(&source_to_parse, Path::new("."))
.builder()
.parse()
.map(|(mut tu, _, iter)| {
iter.into_directives().inject(&mut tu);
tu
})
- .expect(format!("Failed to parse GLSL source: \n{}\n", s).as_str());
+ .expect(format!("Failed to parse GLSL source: \n{}\n", source_to_parse).as_str());
let mut my_glsl_patcher = MyGLSLPatcher {};
tu.visit_mut(&mut my_glsl_patcher);
@@ -235,4 +286,117 @@ vec3 test() {
"#
)
}
+
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl1_fragment() {
+ let source_str = r#"
+precision mediump float;
+void main() {
+ gl_FragColor = vec4(0., 1., 0., 1.);
+}
+"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ // WebGL 1.0 shaders should remain unchanged (no version injection)
+ assert_eq!(
+ patched_source_str,
+ r#"precision mediump float;
+void main() {
+ gl_FragColor = vec4(0., 1., 0., 1.);
+}
+"#
+ );
+ }
+
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl2_fragment() {
+ let source_str = r#"
+precision mediump float;
+out vec4 fragColor;
+void main() {
+ fragColor = vec4(1.0, 0.0, 0.0, 1.0);
+}
+"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ // WebGL 2.0 shaders should get #version 300 es injected
+ assert_eq!(
+ patched_source_str,
+ r#"#version 300 es
+precision mediump float;
+out vec4 fragColor;
+void main() {
+ fragColor = vec4(1., 0., 0., 1.);
+}
+"#
+ );
+ }
+
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl1_vertex() {
+ let source_str = r#"
+attribute vec4 a_position;
+varying vec2 v_texcoord;
+void main() {
+ gl_Position = a_position;
+ v_texcoord = a_position.xy;
+}
+"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ // WebGL 1.0 vertex shader should remain unchanged
+ assert_eq!(
+ patched_source_str,
+ r#"attribute vec4 a_position;
+varying vec2 v_texcoord;
+void main() {
+ gl_Position = a_position;
+ v_texcoord = a_position.xy;
+}
+"#
+ );
+ }
+
+ #[test]
+ fn test_patch_glsl_source_missing_version_webgl2_vertex() {
+ let source_str = r#"
+layout(location = 0) in vec4 a_position;
+out vec2 v_texcoord;
+void main() {
+ gl_Position = a_position;
+ v_texcoord = a_position.xy;
+}
+"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ // WebGL 2.0 vertex shader should get #version 300 es injected
+ assert_eq!(
+ patched_source_str,
+ r#"#version 300 es
+layout(location = 0) in vec4 a_position;
+out vec2 v_texcoord;
+void main() {
+ gl_Position = a_position;
+ v_texcoord = a_position.xy;
+}
+"#
+ );
+ }
+
+ #[test]
+ fn test_patch_glsl_source_existing_version_unchanged() {
+ let source_str = r#"#version 100
+precision mediump float;
+void main() {
+ gl_FragColor = vec4(0., 1., 0., 1.);
+}
+"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ // Existing version should remain unchanged
+ assert_eq!(
+ patched_source_str,
+ r#"#version 100
+precision mediump float;
+void main() {
+ gl_FragColor = vec4(0., 1., 0., 1.);
+}
+"#
+ );
+ }
}
diff --git a/src/client/graphics/webgl_context.cpp b/src/client/graphics/webgl_context.cpp
index 9f23a0b55..07ab5957e 100644
--- a/src/client/graphics/webgl_context.cpp
+++ b/src/client/graphics/webgl_context.cpp
@@ -28,71 +28,6 @@ namespace client_graphics
throw runtime_error(msg); \
}
- /// Detect if shader source uses WebGL 2.0 syntax
- /// WebGL 2.0 requires #version 300 es per specification, WebGL 1.0 doesn't require version
- bool detectWebGL2Syntax(const string &source)
- {
- // WebGL 2.0 strong indicators (these only exist in WebGL 2.0)
- if (source.find("out vec4") != string::npos ||
- source.find("out mediump") != string::npos ||
- source.find("out lowp") != string::npos ||
- source.find("out highp") != string::npos ||
- source.find("layout(location") != string::npos)
- {
- return true;
- }
-
- // Check for WebGL 2.0 built-ins - texture() without texture2D()
- if (source.find("texture(") != string::npos && source.find("texture2D(") == string::npos)
- {
- return true;
- }
-
- // WebGL 1.0 strong indicators (these are deprecated/removed in WebGL 2.0)
- if (source.find("gl_FragColor") != string::npos ||
- source.find("gl_FragData") != string::npos ||
- source.find("attribute ") != string::npos ||
- source.find("varying ") != string::npos ||
- source.find("texture2D(") != string::npos ||
- source.find("textureCube(") != string::npos)
- {
- return false;
- }
-
- // Check for 'in ' keyword (could be WebGL 2.0, but be more specific)
- if (source.find("in vec") != string::npos ||
- source.find("in mediump") != string::npos ||
- source.find("in lowp") != string::npos ||
- source.find("in highp") != string::npos)
- {
- return true;
- }
-
- // Default to WebGL 1.0 for safety
- return false;
- }
-
- /// Apply WebGL standards-compliant version handling
- /// WebGL 1.0: Version directives are optional (defaults to GLSL ES 1.00)
- /// WebGL 2.0: Version directives are required (#version 300 es)
- string applyWebGLVersionHandling(const string &source)
- {
- // If shader already has a version directive, leave it unchanged
- if (source.find("#version") != string::npos)
- {
- return source;
- }
-
- // WebGL 2.0 requires #version 300 es per specification
- if (detectWebGL2Syntax(source))
- {
- return "#version 300 es\n" + source;
- }
-
- // WebGL 1.0 shaders work without version directives per WebGL standard
- return source;
- }
-
void WebGLState::Restore(WebGLState &state, shared_ptr context)
{
context->useProgram(state.program.value_or(nullptr));
@@ -415,11 +350,7 @@ namespace client_graphics
void WebGLContext::shaderSource(shared_ptr shader, const string &source)
{
- // Apply WebGL standards-compliant version handling first
- string processedSource = applyWebGLVersionHandling(source);
-
- // Then apply GLSL patching (reordering of directives, etc.)
- shader->source = GLSLSourcePatcher2::GetPatchedSource(processedSource);
+ shader->source = GLSLSourcePatcher2::GetPatchedSource(source);
auto req = ShaderSourceCommandBufferRequest(shader->id, shader->source);
sendCommandBufferRequest(req);
}
From 944794cdafb14605af3c42a5e9bb2a5bf3dc897f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 29 Sep 2025 04:15:01 +0000
Subject: [PATCH 11/15] Revert style_rule.rs file to original state as
requested
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
crates/jsbindings/css/stylesheets/style_rule.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/jsbindings/css/stylesheets/style_rule.rs b/crates/jsbindings/css/stylesheets/style_rule.rs
index f0afe79e5..a27b1dc38 100644
--- a/crates/jsbindings/css/stylesheets/style_rule.rs
+++ b/crates/jsbindings/css/stylesheets/style_rule.rs
@@ -25,7 +25,7 @@ impl StyleRule {
.borrow()
.to_css(&mut selectors_text)
.expect("Failed to convert selectors to CSS");
-
+
Self {
selectors: SelectorList::new(&handle.selectors),
selectors_text,
From 9b998fdce0dcdcaa7d51996dd67b719e9a6207f5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 29 Sep 2025 05:07:26 +0000
Subject: [PATCH 12/15] Revert css/mod.rs file to original state as requested
Co-authored-by: EndlessJour9527 <155411404+EndlessJour9527@users.noreply.github.com>
---
crates/jsbindings/css/mod.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/crates/jsbindings/css/mod.rs b/crates/jsbindings/css/mod.rs
index e7bd1bfbb..db12d37bf 100644
--- a/crates/jsbindings/css/mod.rs
+++ b/crates/jsbindings/css/mod.rs
@@ -1,4 +1,4 @@
pub(crate) mod properties;
pub(crate) mod selectors;
-pub(crate) mod stylesheets;
pub(crate) mod values;
+pub(crate) mod stylesheets;
From 1c263db4a94b1fc1958c565f4b5a9b680b84f461 Mon Sep 17 00:00:00 2001
From: yjh
Date: Thu, 9 Oct 2025 14:19:40 +0800
Subject: [PATCH 13/15] Refactor MyGLSLPatcher to enhance fragment output
handling and shader stage detection
---
crates/jsbindings/webgl.rs | 573 +++++++++++++++++++++++++++++--------
1 file changed, 455 insertions(+), 118 deletions(-)
diff --git a/crates/jsbindings/webgl.rs b/crates/jsbindings/webgl.rs
index 88c741b71..614c14db1 100644
--- a/crates/jsbindings/webgl.rs
+++ b/crates/jsbindings/webgl.rs
@@ -1,45 +1,394 @@
use glsl_lang::ast;
-use glsl_lang::visitor::{HostMut, Visit, VisitorMut};
+use glsl_lang::visitor::{Host, HostMut, Visit, Visitor, VisitorMut};
+use std::collections::BTreeSet;
use std::path::Path;
use crate::glsl_transpiler;
-struct MyGLSLPatcher {}
+const FRAGMENT_OUTPUT_NAME: &str = "fragColor";
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum ShaderStage {
+ Vertex,
+ Fragment,
+}
+
+#[derive(Default)]
+struct StageDetector {
+ has_fragment_builtin: bool,
+}
+
+impl StageDetector {
+ fn stage(&self) -> ShaderStage {
+ if self.has_fragment_builtin {
+ ShaderStage::Fragment
+ } else {
+ ShaderStage::Vertex
+ }
+ }
+}
+
+impl Visitor for StageDetector {
+ fn visit_identifier(&mut self, ident: &ast::Identifier) -> Visit {
+ let name = ident.content.as_str();
+ match name {
+ "gl_FragColor" | "gl_FragData" | "gl_FragDepth" | "gl_FragCoord" | "gl_SampleMask" => {
+ self.has_fragment_builtin = true;
+ }
+ _ => {}
+ }
+ Visit::Children
+ }
+}
+
+struct MyGLSLPatcher {
+ stage: ShaderStage,
+ saw_gl_fragcolor: bool,
+ fragment_output_declared: bool,
+ fragdata_indices: BTreeSet,
+}
impl MyGLSLPatcher {
+ fn fragment_output_name(index: u32) -> String {
+ if index == 0 {
+ FRAGMENT_OUTPUT_NAME.to_string()
+ } else {
+ format!("{}{}", FRAGMENT_OUTPUT_NAME, index)
+ }
+ }
+
+ fn fragment_output_exists(tu: &ast::TranslationUnit, name: &str) -> bool {
+ tu.0.iter().any(|decl| {
+ if let ast::ExternalDeclarationData::Declaration(decl_node) = &decl.content {
+ if let ast::DeclarationData::InitDeclaratorList(list) = &decl_node.content {
+ let head = &list.content.head.content;
+ if let Some(identifier) = &head.name {
+ return identifier.content.as_str() == name;
+ }
+ }
+ }
+ false
+ })
+ }
+
+ fn fragment_output_insertion_index(tu: &ast::TranslationUnit) -> usize {
+ let mut insertion_index = 0;
+ for (idx, decl) in tu.0.iter().enumerate() {
+ match &decl.content {
+ ast::ExternalDeclarationData::Preprocessor(_) => {
+ insertion_index = idx + 1;
+ }
+ ast::ExternalDeclarationData::Declaration(_) => {
+ insertion_index = idx + 1;
+ }
+ ast::ExternalDeclarationData::FunctionDefinition(_) => {
+ break;
+ }
+ }
+ }
+ insertion_index
+ }
+
+ fn extract_constant_u32(expr: &ast::Expr) -> Option {
+ match &expr.content {
+ ast::ExprData::IntConst(value) => value.to_string().parse().ok(),
+ ast::ExprData::UIntConst(value) => value
+ .to_string()
+ .trim_end_matches('u')
+ .parse()
+ .ok(),
+ _ => None,
+ }
+ }
+
+ fn ensure_additional_fragdata_declarations(&mut self, tu: &mut ast::TranslationUnit) {
+ let need_fragcolor = self.saw_gl_fragcolor || self.fragdata_indices.contains(&0);
+ if need_fragcolor && !self.fragment_output_declared {
+ self.ensure_fragment_output_declaration(tu);
+ }
+
+ let extra_indices: Vec = self
+ .fragdata_indices
+ .iter()
+ .copied()
+ .filter(|&idx| idx != 0)
+ .collect();
+
+ for index in extra_indices {
+ let name = Self::fragment_output_name(index);
+ if Self::fragment_output_exists(tu, &name) {
+ continue;
+ }
+
+ let insertion_index = Self::fragment_output_insertion_index(tu);
+ tu.0
+ .insert(insertion_index, self.build_fragment_output_declaration(&name));
+ }
+ }
+
+ fn new(stage: ShaderStage) -> Self {
+ Self {
+ stage,
+ saw_gl_fragcolor: false,
+ fragment_output_declared: false,
+ fragdata_indices: BTreeSet::new(),
+ }
+ }
+
+ fn apply(&mut self, tu: &mut ast::TranslationUnit) {
+ self.remove_precision_declarations(tu);
+ tu.visit_mut(self);
+ if self.stage == ShaderStage::Fragment {
+ if self.saw_gl_fragcolor {
+ self.ensure_fragment_output_declaration(tu);
+ }
+ self.ensure_additional_fragdata_declarations(tu);
+ }
+ self.ensure_version_and_extension_order(tu);
+ }
+
+ fn remove_precision_declarations(&self, tu: &mut ast::TranslationUnit) {
+ tu.0.retain(|decl| {
+ !matches!(
+ &decl.content,
+ ast::ExternalDeclarationData::Declaration(node)
+ if matches!(node.content, ast::DeclarationData::Precision(_, _))
+ )
+ });
+ }
+
+ fn sanitize_type_qualifier(&self, qualifier: &mut ast::TypeQualifier) {
+ use ast::{StorageQualifierData, TypeQualifierSpecData};
+
+ let mut seen_storage: Option = None;
+ let mut sanitized = Vec::with_capacity(qualifier.content.qualifiers.len());
+
+ for mut spec in qualifier.content.qualifiers.drain(..) {
+ let keep = match &mut spec.content {
+ TypeQualifierSpecData::Storage(storage) => {
+ match storage.content {
+ StorageQualifierData::Attribute => {
+ storage.content = StorageQualifierData::In;
+ }
+ StorageQualifierData::Varying => {
+ storage.content = match self.stage {
+ ShaderStage::Vertex => StorageQualifierData::Out,
+ ShaderStage::Fragment => StorageQualifierData::In,
+ };
+ }
+ _ => {}
+ }
+
+ if let Some(existing) = &seen_storage {
+ if existing == &storage.content {
+ false
+ } else {
+ seen_storage = Some(storage.content.clone());
+ true
+ }
+ } else {
+ seen_storage = Some(storage.content.clone());
+ true
+ }
+ }
+ TypeQualifierSpecData::Precision(_) => false,
+ _ => true,
+ };
+
+ if keep {
+ sanitized.push(spec);
+ }
+ }
+
+ qualifier.content.qualifiers = sanitized;
+ }
+
fn create_model_view_matrix_expr(&self) -> ast::Expr {
let new_lhs: ast::Expr =
- ast::ExprData::Variable(ast::IdentifierData(ast::SmolStr::new_inline("viewMatrix")).into())
- .into();
+ ast::ExprData::Variable(ast::IdentifierData::from("viewMatrix").into()).into();
let new_rhs: ast::Expr =
- ast::ExprData::Variable(ast::IdentifierData(ast::SmolStr::new_inline("modelMatrix")).into())
- .into();
- let new_binary_expr: ast::Expr = ast::ExprData::Binary(
+ ast::ExprData::Variable(ast::IdentifierData::from("modelMatrix").into()).into();
+ ast::ExprData::Binary(
ast::BinaryOpData::Mult.into(),
Box::new(new_lhs),
Box::new(new_rhs),
)
+ .into()
+ }
+
+ fn ensure_fragment_output_declaration(&mut self, tu: &mut ast::TranslationUnit) {
+ if self.fragment_output_declared {
+ return;
+ }
+
+ for decl in &mut tu.0 {
+ if let ast::ExternalDeclarationData::Declaration(decl_node) = &mut decl.content {
+ if let ast::DeclarationData::InitDeclaratorList(list) = &mut decl_node.content {
+ let head = &mut list.content.head.content;
+ if let Some(name) = &mut head.name {
+ let ident = name.content.as_str();
+ if ident == FRAGMENT_OUTPUT_NAME || ident == "glFragColor" {
+ name.content = ast::IdentifierData::from(FRAGMENT_OUTPUT_NAME);
+ self.prepare_fragment_output_type(&mut head.ty);
+ list.content.tail.clear();
+ self.fragment_output_declared = true;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ let insertion_index = Self::fragment_output_insertion_index(tu);
+ tu.0
+ .insert(insertion_index, self.build_fragment_output_declaration(FRAGMENT_OUTPUT_NAME));
+ self.fragment_output_declared = true;
+ }
+
+ fn prepare_fragment_output_type(&self, ty: &mut ast::FullySpecifiedType) {
+ ty.content.ty = ast::TypeSpecifierData {
+ ty: ast::TypeSpecifierNonArrayData::Vec4.into(),
+ array_specifier: None,
+ }
+ .into();
+
+ let qualifier = ty.content.qualifier.get_or_insert_with(|| {
+ ast::TypeQualifierData {
+ qualifiers: Vec::new(),
+ }
+ .into()
+ });
+
+ self.sanitize_type_qualifier(qualifier);
+
+ let has_out = qualifier.content.qualifiers.iter().any(|spec| {
+ matches!(
+ &spec.content,
+ ast::TypeQualifierSpecData::Storage(storage)
+ if matches!(storage.content, ast::StorageQualifierData::Out)
+ )
+ });
+
+ if !has_out {
+ qualifier
+ .content
+ .qualifiers
+ .push(ast::TypeQualifierSpecData::Storage(ast::StorageQualifierData::Out.into()).into());
+ }
+
+ if qualifier.content.qualifiers.is_empty() {
+ ty.content.qualifier = None;
+ }
+ }
+
+ fn build_fragment_output_declaration(&self, name: &str) -> ast::ExternalDeclaration {
+ let qualifier: ast::TypeQualifier = ast::TypeQualifierData {
+ qualifiers: vec![
+ ast::TypeQualifierSpecData::Storage(ast::StorageQualifierData::Out.into()).into(),
+ ],
+ }
+ .into();
+
+ let ty: ast::FullySpecifiedType = ast::FullySpecifiedTypeData {
+ qualifier: Some(qualifier),
+ ty: ast::TypeSpecifierData {
+ ty: ast::TypeSpecifierNonArrayData::Vec4.into(),
+ array_specifier: None,
+ }
+ .into(),
+ }
+ .into();
+
+ let single_decl: ast::SingleDeclaration = ast::SingleDeclarationData {
+ ty,
+ name: Some(ast::IdentifierData::from(name).into()),
+ array_specifier: None,
+ initializer: None,
+ }
.into();
- new_binary_expr
+
+ let init_list: ast::InitDeclaratorList = ast::InitDeclaratorListData {
+ head: single_decl,
+ tail: Vec::new(),
+ }
+ .into();
+
+ ast::ExternalDeclarationData::Declaration(
+ ast::DeclarationData::InitDeclaratorList(init_list).into(),
+ )
+ .into()
}
- fn handle_expr(&self, expr: &mut ast::Expr) -> bool {
+ fn ensure_version_and_extension_order(&self, tu: &mut ast::TranslationUnit) {
+ let mut versions = Vec::new();
+ let mut extensions = Vec::new();
+
+ tu.0.retain(|decl| {
+ if let ast::ExternalDeclarationData::Preprocessor(preprocessor) = &decl.content {
+ match &preprocessor.content {
+ ast::PreprocessorData::Version(_) => {
+ let mut updated = decl.clone();
+ if let ast::ExternalDeclarationData::Preprocessor(pre) = &mut updated.content {
+ if let ast::PreprocessorData::Version(version) = &mut pre.content {
+ version.content.version = 300;
+ version.content.profile = Some(ast::PreprocessorVersionProfileData::Es.into());
+ }
+ }
+ versions.push(updated);
+ return false;
+ }
+ ast::PreprocessorData::Extension(_) => {
+ extensions.push(decl.clone());
+ return false;
+ }
+ _ => {}
+ }
+ }
+ true
+ });
+
+ if versions.is_empty() {
+ let version = ast::PreprocessorVersionData {
+ version: 300,
+ profile: Some(ast::PreprocessorVersionProfileData::Es.into()),
+ }
+ .into();
+ versions.push(
+ ast::ExternalDeclarationData::Preprocessor(ast::PreprocessorData::Version(version).into())
+ .into(),
+ );
+ }
+
+ tu.0.splice(0..0, extensions);
+ tu.0.splice(0..0, versions);
+ }
+
+ fn handle_expr(&mut self, expr: &mut ast::Expr) -> bool {
match &mut expr.content {
ast::ExprData::Variable(identifier) => {
- if identifier.content.0 == "modelViewMatrix" {
+ if identifier.content.as_str() == "modelViewMatrix" {
*expr = self.create_model_view_matrix_expr();
- true
- } else {
- false
+ return true;
+ }
+ if self.stage == ShaderStage::Fragment && identifier.content.as_str() == "gl_FragColor" {
+ print!("Rewriting gl_FragColor to {}", FRAGMENT_OUTPUT_NAME);
+ identifier.content = ast::IdentifierData::from(FRAGMENT_OUTPUT_NAME);
+ self.saw_gl_fragcolor = true;
+ return true;
}
+ false
}
ast::ExprData::Unary(_, operand) => self.handle_expr(operand),
ast::ExprData::Binary(_, lhs, rhs) => {
- let r1 = self.handle_expr(lhs);
- let r2 = self.handle_expr(rhs);
- r1 || r2
+ let l = self.handle_expr(lhs);
+ let r = self.handle_expr(rhs);
+ l || r
+ }
+ ast::ExprData::Assignment(lhs, _, rhs) => {
+ let l = self.handle_expr(lhs);
+ let r = self.handle_expr(rhs);
+ l || r
}
- ast::ExprData::Assignment(_, _, rhs) => self.handle_expr(rhs),
ast::ExprData::FunCall(_, args) => {
let mut changed = false;
for arg in args {
@@ -47,6 +396,33 @@ impl MyGLSLPatcher {
}
changed
}
+ ast::ExprData::Ternary(cond, then_branch, else_branch) => {
+ let r1 = self.handle_expr(cond);
+ let r2 = self.handle_expr(then_branch);
+ let r3 = self.handle_expr(else_branch);
+ r1 || r2 || r3
+ }
+ ast::ExprData::Comma(lhs, rhs) => self.handle_expr(lhs) || self.handle_expr(rhs),
+ ast::ExprData::Bracket(inner, index) => {
+ if self.stage == ShaderStage::Fragment {
+ if let ast::ExprData::Variable(identifier) = &inner.content {
+ if identifier.content.as_str() == "gl_FragData" {
+ if let Some(idx) = Self::extract_constant_u32(index) {
+ let name = Self::fragment_output_name(idx);
+ *expr = ast::ExprData::Variable(ast::IdentifierData::from(name.as_str()).into()).into();
+ self.fragdata_indices.insert(idx);
+ if idx == 0 {
+ self.saw_gl_fragcolor = true;
+ }
+ return true;
+ }
+ }
+ }
+ }
+ self.handle_expr(inner) || self.handle_expr(index)
+ }
+ ast::ExprData::Dot(inner, _) => self.handle_expr(inner),
+ ast::ExprData::PostInc(inner) | ast::ExprData::PostDec(inner) => self.handle_expr(inner),
_ => false,
}
}
@@ -60,48 +436,31 @@ impl VisitorMut for MyGLSLPatcher {
Visit::Children
}
}
-}
-/// Detect if shader source uses WebGL 2.0 syntax
-/// WebGL 2.0 requires #version 300 es per specification, WebGL 1.0 doesn't require version
-fn detect_webgl2_syntax(s: &str) -> bool {
- // WebGL 2.0 strong indicators (these only exist in WebGL 2.0)
- if s.contains("out vec4")
- || s.contains("out mediump")
- || s.contains("out lowp")
- || s.contains("out highp")
- || s.contains("layout(location")
- {
- return true;
- }
-
- // Check for WebGL 2.0 built-ins - texture() without texture2D()
- if s.contains("texture(") && !s.contains("texture2D(") {
- return true;
+ fn visit_full_specified_type(&mut self, ty: &mut ast::FullySpecifiedType) -> Visit {
+ if let Some(qualifier) = &mut ty.content.qualifier {
+ self.sanitize_type_qualifier(qualifier);
+ if qualifier.content.qualifiers.is_empty() {
+ ty.content.qualifier = None;
+ }
+ }
+ Visit::Children
}
- // WebGL 1.0 strong indicators (these are deprecated/removed in WebGL 2.0)
- if s.contains("gl_FragColor")
- || s.contains("gl_FragData")
- || s.contains("attribute ")
- || s.contains("varying ")
- || s.contains("texture2D(")
- || s.contains("textureCube(")
- {
- return false;
+ fn visit_struct_field_specifier(&mut self, field: &mut ast::StructFieldSpecifier) -> Visit {
+ if let Some(qualifier) = &mut field.content.qualifier {
+ self.sanitize_type_qualifier(qualifier);
+ if qualifier.content.qualifiers.is_empty() {
+ field.content.qualifier = None;
+ }
+ }
+ Visit::Children
}
- // Check for 'in ' keyword (could be WebGL 2.0, but be more specific)
- if s.contains("in vec")
- || s.contains("in mediump")
- || s.contains("in lowp")
- || s.contains("in highp")
- {
- return true;
+ fn visit_type_qualifier(&mut self, qualifier: &mut ast::TypeQualifier) -> Visit {
+ self.sanitize_type_qualifier(qualifier);
+ Visit::Children
}
-
- // Default to WebGL 1.0 for safety
- false
}
fn patch_glsl_source_from_str(s: &str) -> String {
@@ -109,55 +468,22 @@ fn patch_glsl_source_from_str(s: &str) -> String {
ast::TranslationUnit, lexer::full::fs::PreprocessorExt, parse::IntoParseBuilderExt,
};
- // Apply WebGL standards-compliant version handling first
- let source_to_parse = if !s.contains("#version") && detect_webgl2_syntax(s) {
- // WebGL 2.0 requires #version 300 es per specification
- format!("#version 300 es\n{}", s)
- } else {
- // WebGL 1.0 shaders work without version directives per WebGL standard
- s.to_string()
- };
-
let mut processor = glsl_lang_pp::processor::fs::StdProcessor::new();
let mut tu: TranslationUnit = processor
- .open_source(&source_to_parse, Path::new("."))
+ .open_source(s, Path::new("."))
.builder()
.parse()
.map(|(mut tu, _, iter)| {
iter.into_directives().inject(&mut tu);
tu
})
- .expect(format!("Failed to parse GLSL source: \n{}\n", source_to_parse).as_str());
-
- let mut my_glsl_patcher = MyGLSLPatcher {};
- tu.visit_mut(&mut my_glsl_patcher);
-
- {
- /*
- * This reorders the preprocessor directives in the GLSL source code.
- *
- * 1. Move the #version directive to the top.
- * 2. Move the #extension directives to the top after the #version directive if exists.
- */
- let mut versions_list = Vec::new();
- let mut extensions_list = Vec::new();
- tu.0.retain(|decl| match &decl.content {
- ast::ExternalDeclarationData::Preprocessor(processor) => match processor.content {
- ast::PreprocessorData::Version(_) => {
- versions_list.push(decl.clone());
- false
- }
- ast::PreprocessorData::Extension(_) => {
- extensions_list.push(decl.clone());
- false
- }
- _ => true,
- },
- _ => true,
- });
- tu.0.splice(0..0, extensions_list);
- tu.0.splice(0..0, versions_list);
- }
+ .expect(format!("Failed to parse GLSL source: \n{}\n", s).as_str());
+
+ let mut detector = StageDetector::default();
+ tu.visit(&mut detector);
+ let stage = detector.stage();
+ let mut patcher = MyGLSLPatcher::new(stage);
+ patcher.apply(&mut tu);
let mut s = String::new();
glsl_transpiler::glsl::show_translation_unit(
@@ -180,39 +506,50 @@ mod ffi {
#[cfg(test)]
mod tests {
use super::*;
- use std::ffi::CString;
#[test]
- fn test_patch_glsl_source() {
+ fn test_fragment_shader_rewrites_gl_frag_color() {
let source_str = r#"
-#extension GL_OVR_multiview2 : enable
-layout(num_views = 2) in;
+precision mediump float;
+varying highp vec2 vUv;
-#version 300 es
-precision highp float;
-highp float a = 1.0;
-layout (location = 0) in vec3 aPos;
-layout (location = 1) in vec3 aNormal;
-layout (location = 0) out highp vec4 glFragColor;
-#extension GL_OES_standard_derivatives : enable
-
-void main() {
- gl_FragColor = vec4(1, 1, 1, 1);
-}"#;
+void main() {
+ gl_FragColor = vec4(vUv, 0.0, 1.0);
+}
+"#;
let patched_source_str = patch_glsl_source_from_str(source_str);
assert_eq!(
patched_source_str,
r#"#version 300 es
-#extension GL_OVR_multiview2 : enable
-#extension GL_OES_standard_derivatives : enable
-layout(num_views = 2) in;
-precision highp float;
-highp float a = 1.;
-layout(location = 0) in vec3 aPos;
-layout(location = 1) in vec3 aNormal;
-layout(location = 0) out highp vec4 glFragColor;
+in vec2 vUv;
+out vec4 fragColor;
+void main() {
+ fragColor = vec4(vUv, 0., 1.);
+}
+"#
+ )
+ }
+
+ #[test]
+ fn test_vertex_attribute_and_varying_transforms() {
+ let source_str = r#"
+attribute vec3 position;
+attribute vec3 normal;
+varying mediump vec2 vUv;
+
+void main() {
+ gl_Position = vec4(position, 1.0);
+}
+"#;
+ let patched_source_str = patch_glsl_source_from_str(source_str);
+ assert_eq!(
+ patched_source_str,
+ r#"#version 300 es
+in vec3 position;
+in vec3 normal;
+out vec2 vUv;
void main() {
- gl_FragColor = vec4(1, 1, 1, 1);
+ gl_Position = vec4(position, 1.);
}
"#
)
From 31fa5e811b7fc545f24db5db3792a153ac98b5eb Mon Sep 17 00:00:00 2001
From: yjh
Date: Thu, 9 Oct 2025 15:48:05 +0800
Subject: [PATCH 14/15] Refactor test-shader-version-fix.html for improved
readability and structure
---
.../test-shader-version-fix.html | 272 ++++++++++--------
1 file changed, 152 insertions(+), 120 deletions(-)
diff --git a/fixtures/html/webgl-conformance/test-shader-version-fix.html b/fixtures/html/webgl-conformance/test-shader-version-fix.html
index 5a0f1ff3a..cb3464bc9 100644
--- a/fixtures/html/webgl-conformance/test-shader-version-fix.html
+++ b/fixtures/html/webgl-conformance/test-shader-version-fix.html
@@ -1,162 +1,194 @@
-
-
- WebGL Standards-Compliant GLSL Version Handling Test
-
+
+
+ WebGL Standards-Compliant GLSL Version Handling Test
+
-
- WebGL Standards-Compliant GLSL Version Handling Test
- This test validates WebGL standards-compliant GLSL version handling:
-
- - WebGL 1.0: Version directives are optional (should default to GLSL ES 1.00)
- - WebGL 2.0: Version directives are required (#version 300 es)
-
-
-
-
-
+ // Run tests when page loads
+ document.addEventListener('DOMContentLoaded', runTests);
+
+
\ No newline at end of file
From 377515ca4d4ac0c9c91dcab8c1a2e412a7e1e0de Mon Sep 17 00:00:00 2001
From: yjh
Date: Thu, 9 Oct 2025 19:21:41 +0800
Subject: [PATCH 15/15] Enhance fragment output handling in MyGLSLPatcher by
ensuring proper declaration and updating existing variables
---
crates/jsbindings/webgl.rs | 65 +++++++++++++++++++++++++++++---------
1 file changed, 50 insertions(+), 15 deletions(-)
diff --git a/crates/jsbindings/webgl.rs b/crates/jsbindings/webgl.rs
index 614c14db1..7367366db 100644
--- a/crates/jsbindings/webgl.rs
+++ b/crates/jsbindings/webgl.rs
@@ -216,32 +216,68 @@ impl MyGLSLPatcher {
.into()
}
+ /// Ensures that the fragment shader has a proper output declaration.
+ /// If an existing declaration is found (either "fragColor" or "glFragColor"),
+ /// it will be updated to the correct type and name. Otherwise, a new declaration is inserted.
fn ensure_fragment_output_declaration(&mut self, tu: &mut ast::TranslationUnit) {
if self.fragment_output_declared {
return;
}
+ // Try to find and update existing fragment output declaration
+ if self.try_update_existing_fragment_output(tu) {
+ return;
+ }
+
+ // No existing declaration found, create a new one
+ self.insert_new_fragment_output_declaration(tu);
+ }
+
+ /// Attempts to find and update an existing fragment output declaration.
+ /// Returns true if an existing declaration was found and updated.
+ fn try_update_existing_fragment_output(&mut self, tu: &mut ast::TranslationUnit) -> bool {
for decl in &mut tu.0 {
- if let ast::ExternalDeclarationData::Declaration(decl_node) = &mut decl.content {
- if let ast::DeclarationData::InitDeclaratorList(list) = &mut decl_node.content {
- let head = &mut list.content.head.content;
- if let Some(name) = &mut head.name {
- let ident = name.content.as_str();
- if ident == FRAGMENT_OUTPUT_NAME || ident == "glFragColor" {
- name.content = ast::IdentifierData::from(FRAGMENT_OUTPUT_NAME);
- self.prepare_fragment_output_type(&mut head.ty);
- list.content.tail.clear();
- self.fragment_output_declared = true;
- return;
- }
+ if let Some(declaration_list) = self.extract_declaration_list(decl) {
+ let head = &mut declaration_list.content.head.content;
+
+ if let Some(variable_name) = &mut head.name {
+ let identifier = variable_name.content.as_str();
+
+ if self.is_fragment_output_variable(identifier) {
+ // Update the existing declaration
+ variable_name.content = ast::IdentifierData::from(FRAGMENT_OUTPUT_NAME);
+ self.prepare_fragment_output_type(&mut head.ty);
+ declaration_list.content.tail.clear();
+ self.fragment_output_declared = true;
+ return true;
}
}
}
}
+ false
+ }
+
+ /// Extracts the InitDeclaratorList from a declaration if it exists.
+ fn extract_declaration_list<'a>(&self, decl: &'a mut ast::ExternalDeclaration)
+ -> Option<&'a mut ast::InitDeclaratorList> {
+ if let ast::ExternalDeclarationData::Declaration(decl_node) = &mut decl.content {
+ if let ast::DeclarationData::InitDeclaratorList(list) = &mut decl_node.content {
+ return Some(list);
+ }
+ }
+ None
+ }
+
+ /// Checks if the given identifier represents a fragment output variable.
+ fn is_fragment_output_variable(&self, identifier: &str) -> bool {
+ identifier == FRAGMENT_OUTPUT_NAME || identifier == "glFragColor"
+ }
+ /// Inserts a new fragment output declaration at the appropriate position.
+ fn insert_new_fragment_output_declaration(&mut self, tu: &mut ast::TranslationUnit) {
let insertion_index = Self::fragment_output_insertion_index(tu);
- tu.0
- .insert(insertion_index, self.build_fragment_output_declaration(FRAGMENT_OUTPUT_NAME));
+ let new_declaration = self.build_fragment_output_declaration(FRAGMENT_OUTPUT_NAME);
+ tu.0.insert(insertion_index, new_declaration);
self.fragment_output_declared = true;
}
@@ -371,7 +407,6 @@ impl MyGLSLPatcher {
return true;
}
if self.stage == ShaderStage::Fragment && identifier.content.as_str() == "gl_FragColor" {
- print!("Rewriting gl_FragColor to {}", FRAGMENT_OUTPUT_NAME);
identifier.content = ast::IdentifierData::from(FRAGMENT_OUTPUT_NAME);
self.saw_gl_fragcolor = true;
return true;