Expand description
Type-aware post-translator passes for the HLSL→WGSL pipeline.
After the regex rewrites in lib.rs produce something WGSL-shaped, the
remaining failures cluster around HLSL semantics WGSL doesn’t honour:
- Implicit scalar→vector broadcasts in calls like
clamp(<vec3>, 0, 1). HLSL broadcasts the scalars0and1tovec3(0)andvec3(1)automatically; WGSL refuses withinconsistent type passed as argument #2 to clamp. - Implicit scalar←vector truncation in declarations like
float lum = GetPixel(uv) * c.x + …;. HLSL takes the first component of the vec result; WGSL refuses withthe type of lum is expected to be f32; but got vec3<f32>.
This module fixes both with two passes that share a small symbol
table built from the translated source’s var NAME: TYPE and
let NAME: TYPE declarations. Type inference for arbitrary
expressions is intentionally heuristic: it returns a confident answer
for the dominant MD2 patterns (named locals, numeric literals, vec/
mat constructors, calls to a small set of known helpers) and
Unknown otherwise. Unknown short-circuits both passes — the
translator only injects fixes it’s sure about.
Structs§
- Symbol
Table - Maps locally-declared identifier → its WGSL type. Pre-seeded with the
uniforms and helper-function bindings the codegen wrapper exposes
inside
fs_main(so thattexsize,q1..q32,aspect, etc. resolve to known types).
Enums§
- ArgWrap 🔒
- Wgsl
Type - WGSL types we care about for MD2 user shaders. Anything outside this
set falls through as
WgslType::Unknownand skips both passes.
Constants§
- BROADCAST_
BUILTINS 🔒 - Built-in functions that require all arguments to share a type and don’t accept HLSL-style scalar↔vector broadcasts in WGSL. For each call to one of these, if any arg has a known vec type and another is a scalar, the scalar is wrapped in the matching vec constructor.
- POLY_
BUILTINS 🔒 - Builtins whose return type matches their arg type (HLSL “polymorphic”
element-typed functions). Used by
SymbolTable::infer_expr_type— the same set asBROADCAST_BUILTINSplus a few more pure-passthrough math functions we don’t need to broadcast-rewrite but whose return type is “same as arg”. - WRAPPER_
PRELUDE_ 🔒LOCALS - Wrapper preamble locals exposed to user shaders. Mirrors the
letaliases inonedrop-codegen::wrap_user_comp_shader::USER_COMP_FRAGMENT_PREFIX.
Functions§
- all_
unique 🔒 - arg_
wrap 🔒 - Decide how to coerce an arg of
arg_tytotarget(the smallest vec among the call’s args). Scalars get broadcast; larger vecs get truncated; equal vecs and unknowns mostly pass through (except for literal scalars, which we still broadcast even when type inference returned Unknown). - constructor_
type 🔒 - inject_
assignment_ coercions - Walk the source for
<bare_ident> <op>= <expr>;statements (where<op>is empty for plain assignment or one of+ - * /for compound assignment). When the inferred type of<expr>doesn’t match the declared type of<bare_ident>, inject the HLSL implicit conversion (broadcast or truncate). - inject_
broadcasts - Walk
srclooking for calls to broadcast-prone builtins; rewrite scalar arguments to vec constructors where the dominant arg is a vec. - inject_
swizzle_ assignments - Rewrite
target.<swizzle> <op>= <rhs>;into a full-vector reconstruction. WGSL refuses multi-component swizzles on the LHS of an assignment (invalid left-hand side of assignment); HLSL allows them freely. The dominant cases intest-presets-200/: - inject_
truncations - Walk
var X: TYPE = <expr>;(orlet) declarations and fix HLSL- implicit-conversion mismatches between LHS type and RHS type: - is_
identifier 🔒 - is_
numeric_ 🔒literal - is_
swizzle_ 🔒components - keyword_
at 🔒 - known_
call_ 🔒return_ type - narrower 🔒
- Reverse of
widen— pick the smaller vec when both args are vecs. Used by the broadcast pass so amax(vec3, vec4)call truncates the vec4 to vec3 (HLSL semantics), not the other way round. - normalise_
swizzle 🔒 - Map
r/g/b/atox/y/z/wwhile preserving identity forx/y/z/w. MD2 user shaders mix the two conventions freely; the codegen wrapper always usesxyzwfor its locals, so canonicalising on emit keeps downstream lane lookups stable. - split_
binop_ 🔒operands - Split an expression on top-level binary ops
+ - * /and return the operands. ReturnsNoneif no top-level op found (i.e. the expression is a single term). - split_
call 🔒 - Split
<head>(<args>)into (head, args). ReturnsNoneif the expression isn’t a bare call (e.g. has a trailing.foo). - split_
last_ 🔒swizzle - Return
(prefix, components)where<expr>.<components>is the swizzle. Splits on the last.at depth 0. - split_
top_ 🔒level_ commas - strip_
comments 🔒 - Replace
/* ... */and// ...comments with spaces in-place so the resulting string has the same length and column positions as the original — crucial becauseSymbolTable::infer_expr_typedoesn’t remap offsets when reasoning about an arg’s text. - strip_
outer_ 🔒parens - swizzle_
target_ 🔒type - vec_
of_ 🔒size - Returns the WgslType for a vec of the given component count.
1maps to scalar f32,2..4to the matching vec type, anything else toUnknown. Convenience for AST-driven rewrites that need to construct a target type from an inferred component count. - vec_
size 🔒 - Component count for vec types, 0 for everything else. Used by the
truncation pass to size the trailing
.xy/.xyzswizzle when narrowing a wider vec into a smaller one. - widen 🔒
- Pick the “widest” of two types — vec wins over scalar; among vecs keep the larger; on conflict default to the first non-unknown.