Skip to content

LEGACY_GL_EMULATION: duplicate-texture-load regex truncates sample expressions when a texture matrix is active (invalid GLSL for COMBINE shaders) #27946

Open

Description

@TheTomBritton

Version

emcc 6.0.12; verified present on main (src/lib/libglemu.js, CTexUnit.prototype.genPassLines). Flags: -sLEGACY_GL_EMULATION.

Summary

The duplicate-texture-load optimization in genPassLines extracts sample expressions with

var texLoadRegex = /(texture.*?\(.*?\))/g;

When a texture matrix is active, genTexUnitSampleExpr emits

texture2D(u_texUnit0, (u_textureMatrix0 * v_texCoord0).xy)

and the lazy .*? stops at the first ) — inside the matrix parentheses — capturing the truncated prefix texture2D(u_texUnit0, (u_textureMatrix0 * v_texCoord0). The following

lines = lines.split(texLoadExpr).join(texLoadVar);

replaces that prefix everywhere and leaves the dangling .xy) fragments behind, producing GLSL that fails to compile. Any GL_COMBINE environment that references the texture more than once (MODULATE, INTERPOLATE, …) combined with a non-identity texture matrix produces a broken fragment shader, so entire scenes render with the pink/unlinked-program fallback.

Also

The regex is re-created after a substitution as /(texture.*\(.*\))/g — a greedy variant inconsistent with the first, which over-captures when one line contains two loads.

Suggested fix

Extract the expression with balanced-parenthesis scanning, or skip the hoist when the match contains an unbalanced (. (Our ship workaround post-processes the generated JS to disable the cache entirely; the hoist is only an optimization.)

Activity

  1. juj commented on Oct 14, 2026

    @juj
    Collaborator

    Could a super-simple sentinel comment work here as a fix?

    diff --git a/src/lib/libglemu.js b/src/lib/libglemu.js
    index 3089630e6e..cce2345bf2 100644
    --- a/src/lib/libglemu.js
    +++ b/src/lib/libglemu.js
    @@ -1105,7 +1105,7 @@ var LibraryGLEmulation = {
             if (TEX_MATRIX_UNIFORM_PREFIX != null) {
               texCoordExpr = `(${TEX_MATRIX_UNIFORM_PREFIX}${texUnitID} * ${texCoordExpr})`;
             }
    -        return `${func}(${TEX_UNIT_UNIFORM_PREFIX}${texUnitID}, ${texCoordExpr}.xy)`;
    +        return `${func}(${TEX_UNIT_UNIFORM_PREFIX}${texUnitID}, ${texCoordExpr}.xy)/*endload*/`;
           }
    
           function getTypeFromCombineOp(op) {
    @@ -1314,7 +1314,7 @@ var LibraryGLEmulation = {
             var lines = this.env.genPassLines(passOutputVar, passInputVar, texUnitID).join('\n');
    
             var texLoadLines = '';
    -        var texLoadRegex = /(texture.*?\(.*?\))/g;
    +        var texLoadRegex = /(texture.*?\(.*?\))\/\*endload\*\//g;
             var loadCounter = 0;
             var load;
    
    @@ -1330,7 +1330,7 @@ var LibraryGLEmulation = {
                 texLoadLines += texLoadLine + '\n'; // Store the generated texture load statements in a temp string to not confuse regex search in progress.
                 lines = lines.split(texLoadExpr).join(texLoadVar);
                 // Reset regex search, since we modified the string.
    -            texLoadRegex = /(texture.*\(.*\))/g;
    +            texLoadRegex = /(texture.*?\(.*?\))\/\*endload\*\//g;
               }
             }
             return [texLoadLines + lines];
  2. TheTomBritton commented on Oct 23, 2026

    @TheTomBritton
    Author

    Yes, the sentinel works. I made a minimal repro: two GL_COMBINE setups that use the texture more than once, one with two loads on a single line and one with INTERPOLATE, drawn with a texture matrix set. Without the change, the generated shader fails to compile (texload0 = texture2D(u_texUnit0, (u_textureMatrix0 * v_texCoord0);). With your diff it compiles and draws the right colours on Chrome/ANGLE Metal and SwiftShader. The leftover /*endload*/ comments after merging are harmless. PR with your diff plus that test: #28061.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions