Skip to content

Conversation

@juj
Copy link
Collaborator

@juj juj commented Nov 20, 2025

Fix preserving layout(std140) directives in WebGL shaders when using -sGL_EXPLICIT_UNIFORM_BINDING WebGL extension. Improve the existing test to catch the reported scenario.

Fixes #25828.

source = source.replace(/layout\s*\([^,]*?binding\s*=\s*([-\d]+)[^,]*?\)/g, ''); // "layout(binding = 3)" -> ""
source = source.replace(/(layout\s*\((.*?)),\s*binding\s*=\s*([-\d]+)\)/g, '$1)'); // "layout(std140, binding = 1)" -> "layout(std140)"
source = source.replace(/layout\s*\(\s*binding\s*=\s*([-\d]+)\s*,(.*?)\)/g, 'layout($2)'); // "layout(binding = 1, std140)" -> "layout(std140)"
source = source.replace(/layout\s*\(\s*binding\s*=\s*([-\d]+)\s*,\s*(.*?)\)/g, 'layout($2)'); // "layout(binding = 1, std140)" -> "layout(std140)"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This \s* addition is not part of the fix itself, but just to improve cleanliness of the string replace for the assert() in the test below. Without this, layout(binding = 1, std140) was converted to form layout( std140), so the \s* here swallows the stray space before std140.

// their way to the actual WebGL shader compiler. These regexes get quite
// hairy, check against https://regex101.com/ when working on these.
source = source.replace(/layout\s*\(.*?binding\s*=\s*([-\d]+).*?\)/g, ''); // "layout(binding = 3)" -> ""
source = source.replace(/layout\s*\([^,]*?binding\s*=\s*([-\d]+)[^,]*?\)/g, ''); // "layout(binding = 3)" -> ""
Copy link
Collaborator Author

@juj juj Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual fix here is to change the regex match to use [^,] (capture anything else than a , character), instead of a . (capture any character).

This way the first replace won't catch on layout(binding = 3, ...) or layout(..., binding = 3)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use \s* here? i.e. what chars are allowed between the opening paren and the word binding?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be layout(std140, binding=5) for example.

Copy link
Collaborator

@sbc100 sbc100 Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I you don't want to match those is that right?

Why doesn't \s* work then? Otherwise won't you end up matching things like layout(whatever binding = 0)?

I guess its not valid but I don't see why you would want to match anything except space?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, can you remove the ? that follow the * operators? Isn't it redundant to say [^,]*? since the * already includes matching zero things.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, yeah, I somehow read your first question as "why not use .* here?".

Now I see what you meant. Definitely using \s* is simpler than [^,]*?. Updated to that form. Thanks!

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm % question

juj added 2 commits November 20, 2025 20:44
…the Emscripten -sGL_EXPLICIT_UNIFORM_BINDING WebGL extension. Improve the existing test to catch the reported scenario. Fixes emscripten-core#25828.
@juj juj force-pushed the fix_layout_std140_preserve branch from 79e9661 to fbbd974 Compare November 20, 2025 18:44
Copy link
Collaborator

@kainino0x kainino0x left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW @juj I discussed the issue more with @SetoKaiba on chat and determined that the root issue, that exposed this bug, was actually in the non-browser runtime they're using, which doesn't implement one of the WebGL special cases: in WebGL (unlike GLES), uniform buffers always have std140 layout.

Because of that, for WebGL I don't think it really matters if the layout(std140) is preserved or not. I don't know if this changes what you want to do in Emscripten.

@SetoKaiba
Copy link

FWIW @juj I discussed the issue more with @SetoKaiba on chat and determined that the root issue, that exposed this bug, was actually in the non-browser runtime they're using, which doesn't implement one of the WebGL special cases: in WebGL (unlike GLES), uniform buffers always have std140 layout.

Because of that, for WebGL I don't think it really matters if the layout(std140) is preserved or not. I don't know if this changes what you want to do in Emscripten.

Yes.
After a discussion with @kainino0x, the issue is confirmed that the Kwai minigame runtime doesn't implement the spec strictly. https://registry.khronos.org/webgl/specs/latest/2.0/#5.25
I think there maybe two options here.

  1. Keep the layout, current PR.
  2. Keep only the first replacement. std140 is the only supported.

@juj
Copy link
Collaborator Author

juj commented Nov 20, 2025

Yeah, that makes sense. It is still good to land this, given that it can otherwise produce red herrings to developers (like happened in this case)

@juj juj merged commit e43e97f into emscripten-core:main Nov 20, 2025
13 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A bug of glShaderSource in libwebgl.js?

4 participants