fix(gl): translate vector equality to GLSL equal - #2964
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Vector != (std.ne) still emits a scalar-bool GLSL comparison and should be translated (and tested) similarly to == to avoid the same type/semantic mismatch.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR fixes a GLSL backend semantic mismatch for std.eq on vectors: WGSL vector equality is component-wise and yields a boolean vector, while GLSL’s == yields a scalar bool, so GLSL must use equal(lhs, rhs) to preserve std.eq’s return type.
Changes:
- Route
std.eqcodegen through the active generator’semitBinaryOpinstead of emittinglhs == rhsdirectly. - Teach the GLSL generator to translate vector
==intoequal(lhs, rhs). - Add a GLSL regression test covering vector equality translation.
File summaries
| File | Description |
|---|---|
| packages/typegpu/src/std/boolean.ts | Changes std.eq codegen to delegate binary emission to the active generator. |
| packages/typegpu-gl/src/glslGenerator.ts | Adds a GLSL-specific emitBinaryOp translation for vector equality to equal(). |
| packages/typegpu-gl/tests/glslGenerator.test.ts | Adds a snapshot test to lock in the equal(lhs, rhs) emission for vector equality. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Important
The fix covers std.eq (==) but leaves std.ne (!=) and std.allEq on vectors emitting GLSL that fails to compile. Since #2832 is scoped as "component-wise vector comparison" and GLSL's notEqual is the direct analogue, please extend the fix (or explicitly narrow the issue scope) before merging.
Reviewed changes
std.eqdelegates to the active generator —boolean.tseq.codegenImplnow callsctx.gen.emitBinaryOp(lhs, '==', rhs)instead of the rawstitch==; WGSL output is unchanged (baseemitBinaryOpwraps==in the same parens).- GLSL
emitBinaryOpmaps vector==toequal— a new branch inGlslGenerator.emitBinaryOpemits the GLSLequal(lhs, rhs)builtin when the lhs is a vector, producing abvecNresult that matches WGSL's component-wise semantics; all other operators fall through to the base generator. - Regression test — a
glslGenerator.test.tscase assertingstd.eqonvec3fresolves toreturn equal(lhs, rhs);.
⚠️ std.ne and std.allEq on vectors still emit invalid GLSL
This PR translates vector equality to GLSL's equal, but the same bug class remains for the sibling comparison helpers, and #2832's title ("component-wise vector comparison") covers them too.
std.ne(lhs, rhs)on vectors resolves (verified) tobvec3 compare(...) { return (lhs != rhs); }. GLSL's!=on vector operands returns a scalarbool, not abvecN, so this is a type error — the fix mechanism inemitBinaryOphandles==but not!=(GLSLnotEqualis the analogue).std.allEq(lhs, rhs)resolves toreturn all(lhs == rhs);. GLSL'sall()requires abvecNargument, butlhs == rhson vectors is a scalarbool, so this also fails to compile.allEq's codegen uses a rawstitch==and does not route throughemitBinaryOp, so it is untouched by this change.
Technical details
# Extend vector-equality translation to ne / allEq
## Affected sites
- packages/typegpu/src/std/boolean.ts:109 — `ne.codegenImpl` still emits `stitch\`(${lhs} != ${rhs})\`` (no generator delegation).
- packages/typegpu/src/std/boolean.ts:67 — `allEq.codegenImpl` still emits `stitch\`all(${lhs} == ${rhs})\`` (no generator delegation).
- packages/typegpu-gl/src/glslGenerator.ts:826 — the new `==` branch does not handle `!=`.
## Required outcome
- `std.ne` on vectors must emit a `bvecN` result on GLSL (e.g. `notEqual(lhs, rhs)`) and unchanged `(${lhs} != ${rhs})` on WGSL.
- `std.allEq` on vectors must emit `all(equal(lhs, rhs))` on GLSL (WGSL `all(${lhs} == ${rhs})` unchanged).
## Suggested approach
- Extend the `emitBinaryOp` override's equality branch to also map `!=` → `notEqual` for vector operands, and delegate `ne.codegenImpl` to `emitBinaryOp(lhs, '!=', rhs)` (mirroring the `eq` change).
- For `allEq`, either route through `emitBinaryOp`/a generator hook or emit `all(equal(...))` conditionally on the GLSL generator; add GLSL regression tests for both.
## Verification
- Reproduced via a scratch GLSL resolve: `std.ne` → `(lhs != rhs)` and `std.allEq` → `all(lhs == rhs)` for `vec3f` inputs (both invalid GLSL for a `bvec3`/scalar-bool context).DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Important
The delta correctly fixes std.ne, but std.allEq on vectors — the other half of the prior finding — still emits GLSL that fails to compile. Please route it through the same mechanism (or explicitly narrow the issue scope) before merging.
Reviewed changes
Since the prior pullfrog review (9679612), commit 34edcac7 ("fix(gl): translate vector inequality"):
- Extended GLSL
emitBinaryOp's equality branch to also translate vector!=to GLSL'snotEqualbuiltin, now requiring both operands to carry knownvec*types. - Routed
std.necodegen throughctx.gen.emitBinaryOp(lhs, '!=', rhs); WGSL output stays byte-identical ((lhs != rhs)) while GLSL emitsnotEqual(lhs, rhs). - Added a GLSL regression snapshot pinning
std.neonvec3ftoreturn notEqual(lhs, rhs);.
⚠️ std.allEq on vectors still emits non-compiling GLSL
The delta addresses std.ne but not std.allEq, the other helper the prior review flagged. allEq's codegen (boolean.ts:67) still uses a raw stitch `all(${lhs} == ${rhs})`, so on GLSL it emits return all(lhs == rhs); — verified — where the vector == yields a scalar bool while GLSL's all() requires a bvecN. Any shader calling std.allEq on vectors fails to compile on the WebGL fallback; the WGSL path is unaffected (valid there).
Technical details
# std.allEq: route inner == through emitBinaryOp
## Affected sites
- packages/typegpu/src/std/boolean.ts:67 — `allEq.codegenImpl` emits raw `stitch` `` `all(${lhs} == ${rhs})` ``, bypassing the generator hook added for eq/ne. (This line is not in the no diff; it is the untouched sibling of the changed `eq`/`ne` lines 90/109.)
## Required outcome
- GLSL: `all(equal(lhs, rhs))` (compiles; `equal` returns `bvecN`).
- WGSL: unchanged semantics — `all(lhs == rhs)` stays valid (an extra paren via `emitBinaryOp` is harmless, but existing inline snapshots would need regenerating).
## Suggested approach
- `codegenImpl: (ctx, [lhs, rhs]) => stitch` `` `all(${ctx.gen.emitBinaryOp(lhs, '==', rhs)})` `` ` (or a generator hook), mirroring the eq/ne delegation. Add a GLSL snapshot test (e.g. `std.allEq(vec3f, vec3f)` → `all(equal(lhs, rhs))`).
## Open questions for the human
- `std.lt` / `std.le` / `std.gt` / `std.ge` on vectors have the same scalar-bool type error on GLSL (raw `stitch` `` `(a < b)` `` etc.); GLSL provides `lessThan` / `lessThanEqual` / `greaterThan` / `greaterThanEqual`. If the intent is full component-wise comparison parity they belong in this fix; otherwise explicitly narrow issue #2832's scope.DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

WGSL vector equality is component-wise and returns a boolean vector. GLSL’s
==operator instead returns a scalar boolean for vector operands, so the fallback currently emits an expression whose type does not matchstd.eq.std.eqnow delegates binary emission to the active generator. The GLSL generator recognizes vector equality and emits GLSL’sequal(lhs, rhs)builtin; WGSL output remains unchanged through the base generator. A GLSL regression test covers the generatedbvec3function.Verification:
@typegpu/gltype tests@typegpu/glpackage buildsCloses #2832.