Skip to content

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

Open

Description

@TheTomBritton

Version

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

Summary

Fixed-function OpenGL interpolates all four texture coordinates per fragment and samples at s/q, t/q. Supplying homogeneous coordinates — glTexCoord4f(s*q, t*q, 0, q) or glTexCoordPointer(4, ...) with q = 1/w — is the canonical GL 1.x technique for perspective-correct texturing, and real fixed-function games rely on it.

The generated sampler ignores q entirely:

return `${func}(${TEX_UNIT_UNIFORM_PREFIX}${texUnitID}, ${texCoordExpr}.xy)`;

so any content using projective texcoords samples at s*q, t*q instead of s, t and is wildly mis-textured.

Suggested fix

Sample with the projective form:

texture2DProj(u_texUnitN, vec4(coord.xy, 0.0, coord.w))

This is exactly equivalent to the current .xy when q == 1 — which is what the attribute's default w component yields for size-2/3 client arrays and glTexCoord2f — and correct otherwise, so it is safe to emit unconditionally. When a texture matrix is active the divide must use the post-matrix w (the expression above already does, since coord is the matrix-transformed vector). This is the patch we ship in production.

Activity

  1. juj commented on Oct 14, 2026

    @juj
    Collaborator

    I imagine there should be a way to detect per-draw how many components the texture coordinates have, and then call texture2DProj() if 4, and texture2D() otherwise?

    How complex would such runtime detection be?

    If that is complex to implement, then happy to land code that does the unconditional texture2DProj(), with a note reminding // N.b. if projective texture sampling shows up as a performance bottleneck, consider optimizing via texture2D() when texture coordinates have at most three components. or something along those lines.

  2. TheTomBritton commented on Oct 23, 2026

    @TheTomBritton
    Author

    Runtime detection turned out to be modest, with one catch: the component count alone isn't enough. A texture matrix whose last row isn't (0,0,0,1) also makes q != 1 with 2-component coordinates. In #28063 each draw works out which units need q: a 4-component texcoord array, or a projective texture-matrix last row. 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. The test covers a matrix-only change between two draws and 4-component perspective coordinates. Both fail before the change and pass after it on Chrome/ANGLE Metal and SwiftShader. If you'd rather have the simpler unconditional texture2DProj with your performance note, it's a one-line change and I'm happy to switch.

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

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