Skip to content

GLEmulation: divide texture coordinates by q when it can differ from 1 - #28063

Open
TheTomBritton wants to merge 1 commit into
emscripten-core:mainfrom
TheTomBritton:glemu-texcoord-proj
Open

TheTomBritton wants to merge 1 commit into
emscripten-core:mainfrom
TheTomBritton:glemu-texcoord-proj

Conversation

@TheTomBritton

Copy link
Copy Markdown

Fixes #27947.

Fixed-function GL samples at s/q, t/q; the generated samplers ignored q, so homogeneous texture coordinates (glTexCoordPointer(4, ...) with q = 1/w, the GL 1.x technique for perspective-correct texturing) drew wrong.

Runtime detection turned out to be modest, with one catch: the component count alone isn't enough, because a texture matrix whose last row isn't (0,0,0,1) also makes q != 1 with 2-component coordinates. Each draw now works out which units need q - a 4-component texcoord array, or a projective texture-matrix last row - and that goes into the renderer key and is re-checked per draw, since a matrix change alone doesn't trigger a renderer lookup. Only those units use texture2DProj; everything else keeps texture2D and its cost.

Adds test_gl_texcoord_q, covering 4-component perspective coordinates and a matrix-only change between two draws. Both cases fail before the change and pass after it on Chrome/ANGLE (Metal and SwiftShader); the other LEGACY_GL_EMULATION browser tests pass locally. If you would rather have the simpler unconditional texture2DProj with your performance note, it is a one-line change and I'm happy to switch.

Side finding, not fixed here: fixed-function textureCube gets a .xy coordinate, which isn't valid GLSL.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AFhyBALrPm8CSkyHzQizDH

GL divides the s and t texture coordinates by q before the lookup. The
generated fixed-function shaders sampled with texture2D(coord.xy) and
ignored q, which is wrong for four-component texture coordinates (for
example (s*q, t*q, 0, q) for perspective-correct texturing) and for
texture matrices whose last row is not (0, 0, 0, 1), even with two- or
three-component coordinates.

Decide per draw which texture units need the divide: those whose texture
coordinate array has four components, or whose texture matrix (when one
is in use) has a projective last row. Those units sample with
texture2DProj; everything else keeps the cheaper texture2D. The mask is
part of the renderer key, and getRenderer re-checks it on every draw,
since a texture matrix can become projective without any of the state
changes that make it look the renderer up again. User shader programs
are unaffected. Cube maps are left as they were.

Adds a browser test with three bands: an affine draw, the same draw after
only the texture matrix changed to double q, and four-component texture
coordinates whose q runs from 1 to 3 across the screen. The last two fail
before this change and all three pass after it.

Fixes emscripten-core#27947

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UR3bCLyLBQiGtevGJx2ARt

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request updates the fixed-function fragment shader generation in LEGACY_GL_EMULATION to correctly handle projective texture lookups by dividing texture coordinates by q when necessary. This is achieved by tracking projective texture units and updating the shader generation logic to use texture2DProj instead of texture2D when q can differ from 1. A regression test was added to verify the fix. The review feedback identified a potential runtime error and suggested more robust handling of matrix access and floating-point comparisons.

Comment thread src/lib/libglemu.js
Comment on lines +2091 to +2093
var m = GLImmediate.matrix[2/*t*/ + i];
if ((attr && attr.size == 4) ||
(GLImmediate.useTextureMatrix && (m[3] || m[7] || m[11] || m[15] != 1))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Directly accessing properties of m can cause a runtime TypeError if GLImmediate.matrix is not fully initialized or if m is undefined for texture unit i. Additionally, the expression (m[3] || m[7] || m[11] || m[15] != 1) relies on implicit truthy/falsy coercion of floating-point values, which is less clear and can be fragile.\n\nWe should safely retrieve m and perform explicit comparisons against 0 and 1.

        var m = GLImmediate.matrix && GLImmediate.matrix[2/*t*/ + i];\n        if ((attr && attr.size == 4) ||\n            (GLImmediate.useTextureMatrix && m && (m[3] !== 0 || m[7] !== 0 || m[11] !== 0 || m[15] !== 1))) {

@sbc100
sbc100 requested a review from juj October 23, 2026 19:40

This branch has not been deployed

No deployments
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.

LEGACY_GL_EMULATION: 4-component texture coordinates ignore q — no projective divide in generated samplers

1 participant