@carbonenginejs/runtime-resource 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/formats/bnk/CjsBnkFormat.js +12 -2
- package/dist/formats/bnk/CjsBnkFormat.js.map +1 -1
- package/dist/formats/bnk/core/graph.js +3 -6
- package/dist/formats/bnk/core/graph.js.map +1 -1
- package/dist/formats/bnk/core/helpers.js +53 -11
- package/dist/formats/bnk/core/helpers.js.map +1 -1
- package/dist/formats/bnk/core/sfxNodes.js +532 -0
- package/dist/formats/bnk/core/sfxNodes.js.map +1 -0
- package/dist/formats/bnk/core/soundbanksInfo.js +59 -51
- package/dist/formats/bnk/core/soundbanksInfo.js.map +1 -1
- package/dist/formats/gr2/CjsGr2Format.js +256 -13
- package/dist/formats/gr2/CjsGr2Format.js.map +1 -1
- package/dist/formats/gr2/core/helpers.js +15 -15
- package/dist/formats/gr2/core/helpers.js.map +1 -1
- package/dist/formats/gr2/core/json.js +1 -1
- package/dist/formats/gr2/core/json.js.map +1 -1
- package/dist/formats/gr2/core/targets.js +1 -1
- package/dist/formats/gr2/core/targets.js.map +1 -1
- package/dist/formats/gr2/index.js +0 -1
- package/dist/formats/gr2/index.js.map +1 -1
- package/dist/formats/hlsl/core/analysis.js +1 -1
- package/dist/formats/hlsl/index.js +1 -0
- package/dist/formats/hlsl/index.js.map +1 -1
- package/dist/formats/index.js +3 -6
- package/dist/formats/index.js.map +1 -1
- package/dist/formats/webgl/core/cewg/binary.js +3 -3
- package/dist/formats/webgl/core/cewg/binary.js.map +1 -1
- package/dist/formats/webgl/core/effectPackage.js +4 -1
- package/dist/formats/webgl/core/effectPackage.js.map +1 -1
- package/dist/formats/webgl/core/errors.js +3 -3
- package/dist/formats/webgl/core/errors.js.map +1 -1
- package/docs/formats/dxbc/README.md +68 -0
- package/docs/formats/dxbc/architecture.md +80 -0
- package/docs/formats/dxbc/reference/api.md +77 -0
- package/docs/formats/dxbc/reference/classes/README.md +76 -0
- package/docs/formats/dxbc/reference/decoded-output.md +122 -0
- package/docs/formats/gr2.md +3 -4
- package/docs/formats/hlsl/README.md +54 -0
- package/docs/formats/hlsl/architecture.md +67 -0
- package/docs/formats/hlsl/guides/hydrating-json-output.md +62 -0
- package/docs/formats/hlsl/guides/reading-effects.md +64 -0
- package/docs/formats/hlsl/reference/advanced-analysis.md +66 -0
- package/docs/formats/hlsl/reference/api.md +97 -0
- package/docs/formats/hlsl/reference/classes/README.md +16 -0
- package/docs/formats/hlsl/reference/classes/carbon-compatibility.md +66 -0
- package/docs/formats/hlsl/reference/classes/public-api.md +26 -0
- package/docs/formats/hlsl/reference/classes/tr2-effect-model.md +150 -0
- package/docs/formats/hlsl/reference/json-graph.md +100 -0
- package/docs/formats/hlsl/reference/portable-reflection.md +141 -0
- package/docs/formats/provenance.md +9 -4
- package/docs/formats/webgl/README.md +57 -0
- package/docs/formats/webgl/architecture.md +70 -0
- package/docs/formats/webgl/carbon-constant-layouts.md +326 -0
- package/docs/formats/webgl/decl-io.md +1234 -0
- package/docs/formats/webgl/effect-reflection.md +127 -0
- package/docs/formats/webgl/memory-structured.md +871 -0
- package/docs/formats/webgl/reference/classes/README.md +96 -0
- package/docs/formats/webgl/texture-sample.md +964 -0
- package/docs/formats/webgpu/README.md +81 -0
- package/docs/formats/webgpu/architecture.md +93 -0
- package/docs/formats/webgpu/formats/cewgpu.md +479 -0
- package/docs/formats/webgpu/guides/effect-packaging.md +199 -0
- package/docs/formats/webgpu/reference/api.md +197 -0
- package/docs/formats/webgpu/reference/classes/README.md +66 -0
- package/docs/formats/webgpu/reference/wgsl-compatibility.md +1442 -0
- package/docs/formats/wwise.md +16 -1
- package/docs/reference/classes/formats.md +10 -10
- package/format-notices/bnk/NOTICE +5 -4
- package/format-notices/webgl/NOTICE +1 -1
- package/package.json +1 -1
- package/dist/formats/gr2/core/CjsFormatGr2.js +0 -273
- package/dist/formats/gr2/core/CjsFormatGr2.js.map +0 -1
|
@@ -0,0 +1,1442 @@
|
|
|
1
|
+
# WGSL compatibility
|
|
2
|
+
|
|
3
|
+
Status: Evolving
|
|
4
|
+
Scope: `@carbonenginejs/runtime-resource/formats/webgpu` DXBC-to-WGSL lowering
|
|
5
|
+
Audience: Shader-tool authors, engine integrators, and maintainers
|
|
6
|
+
Summary: Records deliberate semantic adaptations, unsupported inputs, and bounded compiler behavior.
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
This page records every deliberate divergence between the DXBC contract and
|
|
11
|
+
emitted WGSL, every fail-closed boundary, and every bounded support decision.
|
|
12
|
+
Consult and update it whenever compiler behavior changes;
|
|
13
|
+
each entry says why it exists and what revisiting it would take. Categories:
|
|
14
|
+
|
|
15
|
+
- **Adapted** — accepted input whose WGSL semantics deliberately differ from
|
|
16
|
+
the exact D3D contract. These are the entries to re-read first when hunting
|
|
17
|
+
a rendering difference against the native client.
|
|
18
|
+
- **Not supported (fail closed)** — inputs the compiler rejects with an
|
|
19
|
+
explicit diagnostic rather than guessing.
|
|
20
|
+
- **Bounded / temporary** — supported within stated limits; the limits are the
|
|
21
|
+
first thing to widen when a shader trips them.
|
|
22
|
+
|
|
23
|
+
## Adapted
|
|
24
|
+
|
|
25
|
+
### `precise` floating-point operations → ordinary math + `@invariant` position
|
|
26
|
+
|
|
27
|
+
Current package policy adapts DXBC `precise`, which forbids
|
|
28
|
+
reassociation/fusion so multi-pass position math is bit-identical.
|
|
29
|
+
WGSL has no general no-contraction control, so instead of rejecting these
|
|
30
|
+
operations (a previous compiler boundary for higher-quality shader profiles):
|
|
31
|
+
|
|
32
|
+
- `precise`-marked operations lower as ordinary IEEE float math;
|
|
33
|
+
- every vertex `SV_Position` output is emitted `@invariant @builtin(position)`
|
|
34
|
+
(unconditionally, all vertex shaders), which guarantees identical position
|
|
35
|
+
results across pipelines built from the same emitted WGSL — the multi-pass
|
|
36
|
+
crack/z-fight artifact `precise` protects against;
|
|
37
|
+
- precise-mask metadata is still validated structurally (well-formed mask,
|
|
38
|
+
lanes covered by a destination write) in
|
|
39
|
+
`src/core/wgsl/precisionControls.js`.
|
|
40
|
+
|
|
41
|
+
NOT promised: bit-exact arithmetic parity with native D3D11; differential
|
|
42
|
+
tests against native output may differ in final ulps. Globally-non-refactorable
|
|
43
|
+
shaders (missing `REFACTORING_ALLOWED`) remain rejected — that contract is
|
|
44
|
+
stronger and genuinely unrepresentable. Revisit: if WGSL ever gains a
|
|
45
|
+
no-contraction control, restore exact lowering and drop this entry.
|
|
46
|
+
|
|
47
|
+
### DXBC comparison masks → `select` masks
|
|
48
|
+
|
|
49
|
+
DXBC comparisons produce 0xFFFFFFFF/0 integer masks; WGSL comparisons produce
|
|
50
|
+
`bool`. All comparison opcodes (`lt/ge/eq/ne`, `ilt/ige/ieq/ine`, `ult/uge`)
|
|
51
|
+
lower as `select(0u, 0xffffffffu, a OP b)` so downstream mask arithmetic
|
|
52
|
+
(`and`/`movc` chains) stays bit-faithful.
|
|
53
|
+
|
|
54
|
+
### Typeless registers → per-lane storage types with explicit bitcasts
|
|
55
|
+
|
|
56
|
+
DXBC registers are typeless 32-bit lanes. The type-inference union assigns one
|
|
57
|
+
storage type per SSA lane (conflicts become `bitpattern32` = `u32`) and every
|
|
58
|
+
crossing emits an explicit `bitcast`. Mixed-component writes (one instruction
|
|
59
|
+
writing lanes of different resolved types) split into per-component `let`s
|
|
60
|
+
(`valueN_x`, …) for immediate movs, structured loads, packed intrinsic
|
|
61
|
+
projections, and per-lane `movc` selects — in BOTH stages.
|
|
62
|
+
|
|
63
|
+
### SSA-legal cross-scope reads → hoisted function-top `var`s
|
|
64
|
+
|
|
65
|
+
SSA may resolve a post-join read to one arm's definition (the other path
|
|
66
|
+
diverges via return/discard); structured WGSL scoping cannot express that
|
|
67
|
+
directly. `src/core/wgsl/hoistEscapingValues.js` hoists escaping declarations
|
|
68
|
+
to uninitialized function-top `var`s (WGSL zero-initializes) plus in-place
|
|
69
|
+
assignments. The zero is unobservable — SSA proves the value is only read on
|
|
70
|
+
assigning paths.
|
|
71
|
+
|
|
72
|
+
### Switch clauses without a `default` → empty WGSL `default`
|
|
73
|
+
|
|
74
|
+
WGSL requires a `default` clause; DXBC switches without one fall through to
|
|
75
|
+
`endswitch`. The emitter appends an empty `default: {}`. Switches carrying
|
|
76
|
+
live merges DO require a real DXBC default (fail closed otherwise).
|
|
77
|
+
|
|
78
|
+
### `SV_IsFrontFace` → `front_facing` mask projection
|
|
79
|
+
|
|
80
|
+
DXBC reads the front-face flag as a 0xFFFFFFFF/0 mask; WGSL's builtin is
|
|
81
|
+
`bool`. Consumers receive `select(0u, 0xffffffffu, front_facing)` (or the
|
|
82
|
+
signed variant).
|
|
83
|
+
|
|
84
|
+
### Selection arms may write outputs alongside a live merge
|
|
85
|
+
|
|
86
|
+
A vertex `if`/`else` whose arms write shader outputs (typically `SV_Position`
|
|
87
|
+
in a Picking/stretch pass — one arm computes the real transform, the other
|
|
88
|
+
writes a constant/off-screen position) AND also carry a scalar phi merge
|
|
89
|
+
(e.g. a `TEXCOORD` lane read after the join) used to be rejected outright.
|
|
90
|
+
Output writes inside a branch compose correctly with the merge machinery: the
|
|
91
|
+
merge `var` is pre-declared before the `if` and assigned at each arm's end,
|
|
92
|
+
while output completeness is enforced by the post-branch component
|
|
93
|
+
intersection and the reachable-`ret` coverage check. The guard is therefore
|
|
94
|
+
removed for selections; the genuine "terminates before merge assignments"
|
|
95
|
+
(return inside an arm ahead of the appended merge write) guard stays.
|
|
96
|
+
This shape is covered by browser validation across vertex selections and live
|
|
97
|
+
merges.
|
|
98
|
+
|
|
99
|
+
The fragment stage kept this guard longer than the vertex stage even though its
|
|
100
|
+
surrounding machinery (per-arm written-component cloning, post-branch
|
|
101
|
+
intersection, merge-var appends) is identical; the fragment guard is now
|
|
102
|
+
removed too, browser-validated across fragment selections with live merges.
|
|
103
|
+
|
|
104
|
+
### Scalar merge inputs inherited through an arm tail
|
|
105
|
+
|
|
106
|
+
A two-armed selection merge's inputs are matched to arms by
|
|
107
|
+
`incoming.blockId === trueBlockId/falseBlockId`; canonical IR records the CFG
|
|
108
|
+
predecessor there even when its value ref resolves to an upstream definition.
|
|
109
|
+
For accepted prebuilt IR where exactly one edge identity is unavailable, the
|
|
110
|
+
remaining input is assigned to the remaining arm by elimination — a two-armed
|
|
111
|
+
join has exactly two edges and the phi exactly two inputs. The referenced input
|
|
112
|
+
may still be inherited through an arm tail, which requires the scope handling
|
|
113
|
+
described below.
|
|
114
|
+
|
|
115
|
+
The inherited input frequently does not lexically dominate its arm-tail merge
|
|
116
|
+
assignment. That is safe for the two arms whose assignment is emitted *inside* a
|
|
117
|
+
branch body (the true arm, and the else arm of an if/else): a selection region
|
|
118
|
+
is acyclic, so on the path reaching the arm tail the value was already assigned
|
|
119
|
+
before the merge write, and `hoistEscapingValues` lifts its declaration to a
|
|
120
|
+
function-top `var` (the zero initializer is unobservable on paths that skip it).
|
|
121
|
+
The **no-else false input is excluded** from this relaxation — it pre-initializes
|
|
122
|
+
the merge `var` *before* the `if`, so it must genuinely dominate the header;
|
|
123
|
+
hoisting cannot rescue a value that may be unassigned on a path reaching the
|
|
124
|
+
pre-init. Inputs that neither dominate nor are hoistable (and undefined-register
|
|
125
|
+
inputs on the true edge) still fail closed. Browser-validated on avatar tattoo
|
|
126
|
+
picking selections whose merges inherit a true-arm value through an inner join.
|
|
127
|
+
|
|
128
|
+
### Source modifiers (`neg`/`abs`/`absneg`) → per-consumer-type lowering
|
|
129
|
+
|
|
130
|
+
DXBC source-modifier semantics depend on the consuming instruction's type, and
|
|
131
|
+
the supported cases lower according to that consumer:
|
|
132
|
+
|
|
133
|
+
- float consumers: IEEE negate/abs (`-(x)`, `abs(x)`, `-(abs(x))`);
|
|
134
|
+
- signed-integer consumers: `neg` is two's-complement negation (`-(x)` on
|
|
135
|
+
`i32`);
|
|
136
|
+
- unsigned-integer consumers: `neg` is two's-complement negation, emitted as
|
|
137
|
+
the wrapping `(0u - x)` (WGSL has no unary minus on `u32`);
|
|
138
|
+
- integer consumers: `abs`/`absneg` fail closed because the absolute modifier
|
|
139
|
+
is defined only for floating-point instructions;
|
|
140
|
+
- bit-preserving movers (`mov`/`movc` with unknown or conflicting lane types):
|
|
141
|
+
the modifier applies FLOAT semantics to the raw lane bits, and IEEE
|
|
142
|
+
negate/abs/absneg are pure sign-bit operations, so they lower to
|
|
143
|
+
`^ 0x80000000u` / `& 0x7fffffffu` / `| 0x80000000u` on the `u32` storage
|
|
144
|
+
(with `bitcast` in/out for `i32`-stored lanes).
|
|
145
|
+
|
|
146
|
+
The ordinary WGSL float operators match finite non-zero inputs; signed-zero
|
|
147
|
+
and non-finite behavior inherits WGSL's floating-point latitude. The
|
|
148
|
+
bit-preserving mover path uses explicit sign-bit arithmetic.
|
|
149
|
+
|
|
150
|
+
Previously the modifier was applied as a type-blind `-(x)`/`abs(x)`, which was
|
|
151
|
+
invalid WGSL on `u32` lanes (caught by the browser gate) and a silent
|
|
152
|
+
miscompile on integer-stored mover lanes (two's-complement where the contract
|
|
153
|
+
is a sign-bit flip). The corpus-wide rebuild confirmed every previously
|
|
154
|
+
qualified package is byte-identical under the typed lowering: no already
|
|
155
|
+
qualified shader used the changed paths. Both stages; per-lane (mixed-type
|
|
156
|
+
`movc`) reads share the same storage-typed rules.
|
|
157
|
+
|
|
158
|
+
*Confirmed against vkd3d-shader:* `vsir_program_lower_modifiers` (ir.c) lowers
|
|
159
|
+
`NEG` as `data_type_is_integer(src) ? INEG : NEG` — integer vs float negate
|
|
160
|
+
dispatched on the operand's data type, the same per-consumer typing — with
|
|
161
|
+
`ABS` as float abs and `ABSNEG` as abs-then-neg. (vkd3d resolves the type before
|
|
162
|
+
lowering, so it has no separate bit-mover case; our sign-bit-on-raw-bits path is
|
|
163
|
+
the WGSL-specific equivalent for lanes whose type is still `bitpattern32`.)
|
|
164
|
+
|
|
165
|
+
When a `movc` writes lanes whose inferred storage types differ, both stages
|
|
166
|
+
emit one scalar `select` per lane instead of an unrepresentable mixed-type WGSL
|
|
167
|
+
vector. Each condition and value source is selected with that destination
|
|
168
|
+
lane's original swizzle, modifier, and storage reinterpretation. This path is
|
|
169
|
+
bounded to unsaturated temporary results and register, immediate, or constant-
|
|
170
|
+
buffer lane sources; other mixed mover shapes remain fail-closed. Condition
|
|
171
|
+
modifiers follow the `u32` consumer rules (two's-complement `neg`, with
|
|
172
|
+
`abs`/`absneg` rejected), while the two value operands retain the raw float-
|
|
173
|
+
data mover rules above.
|
|
174
|
+
|
|
175
|
+
### `continue`/`continuec` in loops → WGSL `continuing {}` latch
|
|
176
|
+
|
|
177
|
+
Loop phi-latch updates are emitted in a WGSL `continuing {}` block (which runs
|
|
178
|
+
on both fall-through and `continue` paths) instead of being appended to the loop
|
|
179
|
+
body. `continue` lowers to `continue;` and `continuec` to `if (cond) { continue;
|
|
180
|
+
}`. Behavior is unchanged for loops without `continue` (the continuing block
|
|
181
|
+
still runs the latch each iteration); it simply makes body `continue` correct
|
|
182
|
+
rather than skipping the latch. Both stages.
|
|
183
|
+
|
|
184
|
+
### Declared-but-unwritten location outputs → zero-filled (vertex only)
|
|
185
|
+
|
|
186
|
+
A **vertex** output signature may declare a `location` varying (COLOR/TEXCOORD)
|
|
187
|
+
that a given permutation never writes. D3D leaves such
|
|
188
|
+
lanes undefined; WGSL zero-initializes `var output`, so the unwritten lanes read
|
|
189
|
+
as 0 — a safe, valid choice. Completeness is still enforced for **builtin**
|
|
190
|
+
outputs (`SV_Position` must be fully written; zero is not a meaningful position).
|
|
191
|
+
|
|
192
|
+
The **fragment** stage does NOT relax this: an unwritten `SV_Target` lane still
|
|
193
|
+
fails closed (an undefined render-target lane is not a safe zero — it feeds
|
|
194
|
+
blending). Only the vertex `ret` completeness check was relaxed.
|
|
195
|
+
|
|
196
|
+
### Dead untyped temp writes → skipped
|
|
197
|
+
|
|
198
|
+
Compiler-emitted dead stores whose values nothing reads (and whose types are
|
|
199
|
+
therefore unresolvable) are dropped instead of failing the module.
|
|
200
|
+
|
|
201
|
+
### Terminal control flow → dead tail dropped
|
|
202
|
+
|
|
203
|
+
An `if`/`else` whose both arms return, or a `switch` with a default whose every
|
|
204
|
+
clause returns, terminates all paths; instructions after it (a trailing
|
|
205
|
+
unreachable `ret`, common after fully-branched Picking/depth outputs) are dead
|
|
206
|
+
and not lowered. Output-completeness is validated only on reachable `ret`s.
|
|
207
|
+
|
|
208
|
+
### `immediate_constant_buffer` (DXBC icb) → module `const` array
|
|
209
|
+
|
|
210
|
+
DXBC's inline constant table (`customdata`, dataClass 3) is emitted as a
|
|
211
|
+
module-scope `const icb = array<vec4<f32>, N>(vec4<f32>(...), ...)`. Finite
|
|
212
|
+
non-zero lanes use a shortest f32 decimal; non-finite lanes and negative zero
|
|
213
|
+
use `bitcast<f32>(0x..u)` so the exceptional or sign bit pattern is preserved.
|
|
214
|
+
WGSL does not fix the rounding direction for an inexact decimal-to-f32
|
|
215
|
+
conversion, so readable finite decimal emission is not a normative raw-bit
|
|
216
|
+
guarantee; guaranteed preservation would require raw-bit literals for every
|
|
217
|
+
lane. The same literal emitter is used for immutable indexable-temp tables.
|
|
218
|
+
`immediate_constant_buffer` operands lower as
|
|
219
|
+
`icb[<index>].<comp>` reusing the dynamic constant-buffer index machinery
|
|
220
|
+
(pure-relative and base+relative indices both supported), with int/uint
|
|
221
|
+
consumers bitcast exactly like uniform cbuffers. Out-of-bounds dynamic indices
|
|
222
|
+
are an adaptation: D3D constant/ICB reads return zero, while the emitted
|
|
223
|
+
unchecked WGSL array access has implementation-chosen out-of-bounds behavior;
|
|
224
|
+
qualified corpus shaders stay in range.
|
|
225
|
+
|
|
226
|
+
### Fixed-slot indexable temps → scalarized SSA locals
|
|
227
|
+
|
|
228
|
+
A declared width-four `indexable_temp` addressed as an exact
|
|
229
|
+
`x#[immediate-slot]` is treated as a bounded family of ordinary register
|
|
230
|
+
locals. The declaration must be unique, both index dimensions and the register
|
|
231
|
+
identity must be exact, the slot must be in range, and source selectors,
|
|
232
|
+
destination masks, and direct-IR SSA metadata must agree with the referenced
|
|
233
|
+
lanes. Each slot/lane then participates independently in the existing SSA,
|
|
234
|
+
masked-write, and structured merge machinery in both vertex and fragment
|
|
235
|
+
stages. No mutable WGSL array or dynamic array write is emitted.
|
|
236
|
+
|
|
237
|
+
Relative/dynamic addressing and narrower mutable declarations remain
|
|
238
|
+
fail-closed. Fixed reads of a register recognized as the immutable table shape
|
|
239
|
+
below are routed through that table rather than through mutable SSA. The
|
|
240
|
+
`cloud` and `cloudsimple` browser gates cover fixed-slot writes and reads with
|
|
241
|
+
zero WGSL warnings. The full corpus transition moved from 502 qualified / 35
|
|
242
|
+
unsupported / 0 failed to 504 / 33 / 0: exactly those two packages became
|
|
243
|
+
qualified, and SHA-256 comparison confirmed all 502 previously qualified
|
|
244
|
+
package bytes remained identical.
|
|
245
|
+
|
|
246
|
+
### Relative indexable temps → module `const` tables (immutable shape only)
|
|
247
|
+
|
|
248
|
+
An indexable temp (`x#`) accessed with relative addressing is recognized when
|
|
249
|
+
it is an immutable constant table: every write is a straight-line
|
|
250
|
+
pre-control-flow `mov x#[slot].mask, l(...)` immediate, all initializers precede
|
|
251
|
+
every read, one write mask is shared, every declared slot is fully written for
|
|
252
|
+
that mask, and reads select only written lanes. Operand roles (including
|
|
253
|
+
dual-destination instructions), register identities, index widths, modifiers,
|
|
254
|
+
and precision metadata are validated exactly. Initializer source
|
|
255
|
+
selection/swizzles are applied before storage, and scalar immediates replicate
|
|
256
|
+
across every written lane. Such registers lower exactly like the icb — a
|
|
257
|
+
module-scope
|
|
258
|
+
`const xt# = array<vec4<f32>, N>(...)` with reads through the shared dynamic
|
|
259
|
+
index machinery (`xt#[base + i32(index)].comp`) — so the dynamic read needs no
|
|
260
|
+
mutable-register SSA and inherits index-driven uniformity. This is the
|
|
261
|
+
compiler-generated shape for small lookup tables (e.g. the six quad-corner
|
|
262
|
+
UVs in `particles/gpu/quads`). Any other relative indexable-temp use —
|
|
263
|
+
mutable writes, non-immediate initializers, initializers under control flow,
|
|
264
|
+
partial slots — fails closed with a per-reason diagnostic. Out-of-bounds
|
|
265
|
+
dynamic indices retain WGSL's implementation-chosen array-access behavior
|
|
266
|
+
rather than D3D's out-of-bounds register semantics; no qualified corpus shader
|
|
267
|
+
indexes out of bounds.
|
|
268
|
+
|
|
269
|
+
### Component-packed varyings → one merged interface field per register
|
|
270
|
+
|
|
271
|
+
DXBC signature tables can emit several rows for a single interpolant register
|
|
272
|
+
when distinct semantics occupy different lanes (e.g. three `TEXCOORD`s packed
|
|
273
|
+
into `x`/`y`/`z` of output register 2, as in `starsprites`). Each row carries a
|
|
274
|
+
non-prefix mask (`y`-only, `z`-only) that would individually be rejected as a
|
|
275
|
+
gap in the WGSL location layout. Both stages now group signature rows by
|
|
276
|
+
`registerIndex`, union their masks, and emit ONE interface field per register
|
|
277
|
+
(validated prefix, single component type across the group). This is a
|
|
278
|
+
faithfulness fix, not a divergence — the merged field reproduces the register's
|
|
279
|
+
true lane occupancy.
|
|
280
|
+
|
|
281
|
+
### `linear_noperspective` varyings → `@interpolate(linear)` on both stages
|
|
282
|
+
|
|
283
|
+
DXBC `linear` interpolation is perspective-correct — the WGSL default — and
|
|
284
|
+
needs no attribute. DXBC `linear_noperspective` maps exactly to WGSL
|
|
285
|
+
`@interpolate(linear)` (center sampling on both sides). Because WebGPU
|
|
286
|
+
requires the vertex output and fragment input attributes at one location to
|
|
287
|
+
MATCH at pipeline creation, and DXBC declares interpolation only on the
|
|
288
|
+
fragment side (`dcl_input_ps`), the pass-global binding plan records the
|
|
289
|
+
non-default modes (`varyingInterpolation`) and the vertex module mirrors them
|
|
290
|
+
onto its paired outputs. Mixed modes on one packed register, centroid and
|
|
291
|
+
sample variants, and `constant` fail closed.
|
|
292
|
+
|
|
293
|
+
### Non-float `saturate` on movers → float clamp on the raw bits
|
|
294
|
+
|
|
295
|
+
D3D `saturate` assumes float data (like source modifiers). When a
|
|
296
|
+
bit-preserving `mov`/`movc` result's lanes resolve to integer storage, the
|
|
297
|
+
saturate lowers as `bitcast<T>(clamp(bitcast<f32-vec>(bits), 0.0, 1.0))` —
|
|
298
|
+
the direct WGSL float clamp on the raw lanes, keeping the storage type. Finite
|
|
299
|
+
values match the D3D clamp; non-finite inputs do not have portable
|
|
300
|
+
D3D-equivalent results in WGSL. Saturate on
|
|
301
|
+
genuinely integer arithmetic results still fails closed.
|
|
302
|
+
|
|
303
|
+
*Confirmed against vkd3d-shader:* `spirv_compiler_emit_sat` (spirv.c) is
|
|
304
|
+
`nclamp(x, 0.0, 1.0)` with float constants for floating-point data and a
|
|
305
|
+
`FIXME("Unhandled data type")` for non-float — saturate is a float clamp and
|
|
306
|
+
integer saturate is unhandled, matching "assumes float". Our bitcast-clamp on
|
|
307
|
+
`bitpattern32` mover lanes is the WGSL-specific handling for the float-data-in-
|
|
308
|
+
integer-storage case vkd3d left as a FIXME.
|
|
309
|
+
|
|
310
|
+
### `rcp` (both stages) → ordinary f32 division
|
|
311
|
+
|
|
312
|
+
DXBC `rcp` is a reduced-precision component-wise reciprocal; its maximum
|
|
313
|
+
relative error is 2^-21. It lowers to `1.0 / x`. For a finite, normal,
|
|
314
|
+
non-zero f32 with `abs(x)` in `[2^-126, 2^126]`, WGSL gives f32 division a
|
|
315
|
+
maximum error of 2.5 ULP, which satisfies that DXBC accuracy allowance.
|
|
316
|
+
|
|
317
|
+
The special-value contract is adapted. D3D specifies signed infinities for
|
|
318
|
+
signed-zero and subnormal inputs, signed zero for infinities, and NaN for NaN.
|
|
319
|
+
WGSL permits zero signs to be ignored and makes a runtime result that is
|
|
320
|
+
infinite or NaN indeterminate under its finite-math assumption. Exact behavior
|
|
321
|
+
for those inputs is therefore not portable. Finite normal denominators outside
|
|
322
|
+
the stated magnitude range can produce a subnormal reciprocal that D3D flushes
|
|
323
|
+
to signed zero but WGSL may preserve, so only the stated range has the claimed
|
|
324
|
+
accuracy match. Immediate operands are a fail-closed portability boundary:
|
|
325
|
+
each consumed lane whose raw f32 exponent is zero (signed zero or subnormal) or
|
|
326
|
+
255 (infinity or NaN) is rejected before modifiers and result saturation.
|
|
327
|
+
Unused immediate lanes are ignored, one-word immediates replicate normally,
|
|
328
|
+
and finite normal lanes remain accepted. Dynamic operands remain supported
|
|
329
|
+
with the signed-zero, subnormal, and non-finite caveats above. The same
|
|
330
|
+
signed-zero and non-finite caveats apply to the supported `div` opcode in both
|
|
331
|
+
stages.
|
|
332
|
+
|
|
333
|
+
*Confirmed against vkd3d-shader within the finite-normal scope above:* its
|
|
334
|
+
IR applies float source modifiers before `rcp` and destination saturation
|
|
335
|
+
afterward, while its SPIR-V backend emits floating division with a `1.0`
|
|
336
|
+
numerator using the active destination-mask/source-swizzle lanes. Scalar
|
|
337
|
+
immediates replicate across active lanes. Its tests also record D3D's
|
|
338
|
+
sign-sensitive zero/infinity results. This confirmation does not widen the
|
|
339
|
+
portable WGSL claim: signed zero, subnormal, infinity, NaN, overflow, and a
|
|
340
|
+
subnormal reciprocal retain the caveats above, and the GLSL/MSL backends do
|
|
341
|
+
not independently corroborate `rcp`.
|
|
342
|
+
|
|
343
|
+
### Vertex-stage texture sampling → explicit LOD/gradient only
|
|
344
|
+
|
|
345
|
+
The vertex binding restriction now admits texture and sampler bindings, and the
|
|
346
|
+
vertex stage lowers `sample_l` (`textureSampleLevel`) and `sample_d`
|
|
347
|
+
(`textureSampleGrad`). Implicit-LOD `sample`/`sample_b` stay fragment-only —
|
|
348
|
+
WGSL forbids implicit derivatives in a vertex entry point.
|
|
349
|
+
|
|
350
|
+
### Typed uint buffer UAVs + atomic operations → guarded storage atomics
|
|
351
|
+
|
|
352
|
+
A `dcl_unordered_access_view_typed` buffer with a uniform uint return type
|
|
353
|
+
lowers to `var<storage, read_write> uN: array<atomic<u32>>`. In fragment
|
|
354
|
+
programs, `atomic_iadd` becomes a bounds-guarded statement:
|
|
355
|
+
`if (i < arrayLength(&uN)) { atomicAdd(&uN[i], v); }`. The guard reproduces
|
|
356
|
+
D3D's defined behavior — out-of-bounds typed-UAV atomics are dropped — where
|
|
357
|
+
an unguarded WGSL access could target a live element or otherwise raise a
|
|
358
|
+
dynamic error. The result-returning form (`imm_atomic_iadd`), other atomic
|
|
359
|
+
opcodes, and non-uint or non-buffer UAV shapes fail closed. Vertex writable
|
|
360
|
+
storage remains outside the current portability contract. The bounded compute
|
|
361
|
+
profile below uses the same representation and `atomicStore` for ordinary
|
|
362
|
+
typed stores because WGSL requires every access to an atomic-typed element to
|
|
363
|
+
use an atomic builtin. The engine must bind either form as storage containing
|
|
364
|
+
raw 4-byte u32 words (`minBindingSize: 4`); the admitted typed-atomic contract
|
|
365
|
+
is one scalar word per element. No DXGI view-format conversion is reproduced.
|
|
366
|
+
*Confirmed against vkd3d-shader for operation shape and the
|
|
367
|
+
robustness-dependent OOB mechanism:* its backend emits the corresponding
|
|
368
|
+
atomic through a directly computed buffer/image pointer and inserts no
|
|
369
|
+
explicit bounds guard. A zero/drop result therefore depends on the applicable
|
|
370
|
+
target and runtime robustness guarantees; vkd3d-shader alone does not prove
|
|
371
|
+
that result for every target. This compiler independently implements D3D's
|
|
372
|
+
dropped-write result for the supported non-result atomic with an explicit
|
|
373
|
+
statement-level guard. A future result-returning OOB atomic must additionally
|
|
374
|
+
synthesize a zero old-value result.
|
|
375
|
+
|
|
376
|
+
### Bounded 1×1×1 compute programs → native WebGPU compute pipelines
|
|
377
|
+
|
|
378
|
+
Compute lowering is admitted only for an exact, whole-program-validated SM5.0
|
|
379
|
+
structural profile currently exercised by `particles/gpu/setdrawparameters` and
|
|
380
|
+
`particles/gpu/setsortargs`. It requires canonical global/SRV/UAV/temp/thread-
|
|
381
|
+
group declarations, one temporary register, `dcl_thread_group 1,1,1`, one
|
|
382
|
+
reachable straight-line block ending in `ret`, and exactly one typed scalar
|
|
383
|
+
sint buffer SRV plus one typed scalar uint buffer UAV. The supported body
|
|
384
|
+
opcodes are `ld`, low-half `imul`, `umax`, `iadd`, `ushr`,
|
|
385
|
+
`store_uav_typed`, and `ret`; every operand, selector, immediate, binding,
|
|
386
|
+
type-flow fact, and SSA edge is revalidated before emission.
|
|
387
|
+
|
|
388
|
+
The SRV is exposed as `var<storage, read> tN: array<i32>` and an out-of-bounds
|
|
389
|
+
`ld` returns zero through a clamped load plus `select`. The UAV is
|
|
390
|
+
`var<storage, read_write> uN: array<atomic<u32>>`; an in-range typed store
|
|
391
|
+
uses `atomicStore`, while an out-of-bounds store is dropped by an explicit
|
|
392
|
+
branch. These scalar-word layouts deliberately do not reproduce DXGI typed-view
|
|
393
|
+
conversion, so the engine binding contract is one raw 4-byte word per element.
|
|
394
|
+
Restricting the profile to scalar `x` loads and replicated full-mask stores
|
|
395
|
+
also avoids guessing the width of a general DXBC typed-buffer view.
|
|
396
|
+
|
|
397
|
+
The package carries the declared thread-group size as `[1, 1, 1]`. Trinity
|
|
398
|
+
effect metadata identifies compute as stage type `2`, while the decoded DXBC
|
|
399
|
+
program type remains `5`; these two enums are intentionally kept separate.
|
|
400
|
+
The browser gate creates and validates native shader modules, compute bind-group
|
|
401
|
+
layouts, pipeline layouts, and compute pipelines. It does not dispatch work or
|
|
402
|
+
expand the public render-only device API.
|
|
403
|
+
|
|
404
|
+
The full corpus transition moved from 504 qualified / 33 unsupported / 0
|
|
405
|
+
failed to 506 / 31 / 0: exactly the two programs named above became qualified,
|
|
406
|
+
and SHA-256 comparison confirmed all 504 previously qualified package bytes
|
|
407
|
+
remained identical.
|
|
408
|
+
|
|
409
|
+
### Bounded 64×1×1 structured skinning compute
|
|
410
|
+
|
|
411
|
+
A second, separate SM5.0 compute profile is currently exercised by
|
|
412
|
+
`system/raytracing/skinvertices`. Profile selection is structural rather than
|
|
413
|
+
path- or byte-hash-based, and malformed members of the selected declaration
|
|
414
|
+
family fail there instead of falling through to the scalar-word profile. The
|
|
415
|
+
declaration envelope is exactly one immediate `cb3` with three vec4 rows,
|
|
416
|
+
structured SRVs `t0`/`t1` with 48-/4-byte strides, one non-coherent structured
|
|
417
|
+
UAV `u0` with a 4-byte stride, `input_thread_id.x`, ten temporary registers,
|
|
418
|
+
and `dcl_thread_group 64,1,1`. Its bounded body has two nested selections and
|
|
419
|
+
no loops, barriers, atomics, textures, or samplers. CFG, SSA, scalar types,
|
|
420
|
+
bitcasts, live merges, operands, and resource identities are replayed and
|
|
421
|
+
compared before emission.
|
|
422
|
+
|
|
423
|
+
`input_thread_id.x` maps to
|
|
424
|
+
`@builtin(global_invocation_id) dispatch_thread_id: vec3<u32>` and uses only
|
|
425
|
+
the x component; the workgroup size is not multiplied into that already-global
|
|
426
|
+
identifier. The supported packed-index path treats `ubfe` as unsigned and
|
|
427
|
+
extracts only its observed eight-bit fields. Matrix rows and input words use
|
|
428
|
+
flat `array<u32>` storage so typeless f32/u32 bits survive unchanged.
|
|
429
|
+
Structured loads compute `arrayLength / strideWords`, clamp the eagerly
|
|
430
|
+
evaluated physical word access, and select zero for an out-of-range structure
|
|
431
|
+
index. Each scalar structured store has its own
|
|
432
|
+
`address < arrayLength(&u0)` branch and is dropped when out of range.
|
|
433
|
+
Offset-plus-swizzle accesses beyond a declared stride fail closed.
|
|
434
|
+
|
|
435
|
+
The DX12 SM5.1 comparison shader remains comparison-only: its unbounded
|
|
436
|
+
space1/space2 descriptor ranges do not provide the fixed WebGPU binding
|
|
437
|
+
contract used by the DX11 profile. The native browser gate validates the
|
|
438
|
+
64×1×1 module, four-entry compute bind-group layout, pipeline layout, and
|
|
439
|
+
compute pipeline without widening the public render-only device API or
|
|
440
|
+
dispatching work.
|
|
441
|
+
|
|
442
|
+
*Checked against vkd3d-shader:* its compute builtin maps to the global
|
|
443
|
+
invocation identifier, its raw/structured buffers flatten to scalar words, and
|
|
444
|
+
its structured loads/stores use direct backend accesses. Its Vulkan path
|
|
445
|
+
relies on runtime robustness for physical out-of-bounds behavior; the explicit
|
|
446
|
+
WGSL load-zero/store-drop guards above independently preserve the D3D result.
|
|
447
|
+
|
|
448
|
+
The full corpus transition moved from 506 qualified / 31 unsupported / 0
|
|
449
|
+
failed to 507 / 30 / 0: only `system/raytracing/skinvertices` became
|
|
450
|
+
qualified, and direct byte comparison confirmed all 506 previously qualified
|
|
451
|
+
packages remained identical.
|
|
452
|
+
|
|
453
|
+
### Bounded 256×1×1 two-word particle sort step
|
|
454
|
+
|
|
455
|
+
An isolated SM5.0/SM5.1 compute profile covers
|
|
456
|
+
`particles/gpu/sortstep`. Both backends declare the same finite space-zero
|
|
457
|
+
resources: immediate `cb3` with one vec4 row, typed uint buffer `t0`,
|
|
458
|
+
non-coherent structured UAV `u0` with an 8-byte stride,
|
|
459
|
+
`input_thread_group_id.x`, `input_thread_id_in_group.x`, two temporary
|
|
460
|
+
registers, and `dcl_thread_group 256,1,1`. The SM5.1 form additionally requires
|
|
461
|
+
canonical finite range-zero encodings; range-relative `cb0[3]` is normalized
|
|
462
|
+
back to physical `cb3`, not treated as a different binding. The exact 20-opcode
|
|
463
|
+
body has two nested selections and no loops, barriers, atomics, workgroup
|
|
464
|
+
memory, textures, samplers, or live register merges. Declaration, operand,
|
|
465
|
+
modifier, extension, binding-range, CFG, SSA, and type metadata are replayed
|
|
466
|
+
and compared before emission.
|
|
467
|
+
|
|
468
|
+
The two compute builtins map, in fixed order, to
|
|
469
|
+
`@builtin(workgroup_id) workgroup_id` and
|
|
470
|
+
`@builtin(local_invocation_id) local_invocation_id`. Their x components form
|
|
471
|
+
the global scalar lane used by the source; neither y nor z is read. Integer
|
|
472
|
+
arithmetic remains raw wrapping u32, including the profile's sole source
|
|
473
|
+
modifier: integer `NEG` is emitted as two's-complement negation rather than
|
|
474
|
+
floating negation. The pair comparison bitcasts the second word of each record
|
|
475
|
+
to f32, so ordinary WGSL `<` also preserves the source's false result for NaN.
|
|
476
|
+
|
|
477
|
+
The fixed `t0[3]` typed-buffer read uses a clamped physical word access and
|
|
478
|
+
selects zero when index 3 is out of range. The scalar-word view is not inferred
|
|
479
|
+
from the DXBC return tuple alone: the already-qualified `setsortargs` producer
|
|
480
|
+
publishes the same `SortParameters` binding as a typed scalar uint UAV and
|
|
481
|
+
writes its four words individually. The consumer therefore uses
|
|
482
|
+
`var<storage, read> t0: array<u32>` with `minBindingSize: 4`.
|
|
483
|
+
|
|
484
|
+
The UAV is `var<storage, read_write> u0: array<u32>` with
|
|
485
|
+
`minBindingSize: 8`. Structured loads divide `arrayLength` by two to obtain the
|
|
486
|
+
complete-record count, clamp both eagerly evaluated physical word accesses,
|
|
487
|
+
and select two zero words for an out-of-range record. Each two-word structured
|
|
488
|
+
store has its own complete-record bounds branch and is dropped as one source
|
|
489
|
+
instruction when out of range.
|
|
490
|
+
|
|
491
|
+
DX11 and DX12 are both substantive comparison inputs for this profile and emit
|
|
492
|
+
the same WGSL and portable binding layout after finite-range normalization.
|
|
493
|
+
The native browser gate validates the two-builtin 256×1×1 module, its compute
|
|
494
|
+
bind-group and pipeline layouts, and the compute pipeline with zero WGSL
|
|
495
|
+
warnings.
|
|
496
|
+
|
|
497
|
+
The full corpus transition moved from 507 qualified / 30 unsupported / 0
|
|
498
|
+
failed to 508 / 29 / 0: only `particles/gpu/sortstep` became qualified, and
|
|
499
|
+
direct byte comparison confirmed all 507 previously qualified packages
|
|
500
|
+
remained identical.
|
|
501
|
+
|
|
502
|
+
### Bounded 256×1×1 shared-memory particle bitonic merge
|
|
503
|
+
|
|
504
|
+
An isolated SM5.0/finite-SM5.1 profile covers
|
|
505
|
+
`particles/gpu/sortinner`. Both forms declare a typed uint Buffer `t0`, a
|
|
506
|
+
non-coherent structured UAV `u0` with an 8-byte stride, flattened and vector
|
|
507
|
+
local-thread identifiers, `workgroup_id.x`, three temporary registers,
|
|
508
|
+
structured thread-group memory `g0` with 512 8-byte records, and
|
|
509
|
+
`dcl_thread_group 256,1,1`. The flattened local identifier and
|
|
510
|
+
`input_thread_id_in_group.x` both map to `local_invocation_id.x` because the
|
|
511
|
+
admitted group shape is exactly 256×1×1.
|
|
512
|
+
|
|
513
|
+
The exact 61-opcode body has eight no-else selections and one uniform
|
|
514
|
+
nine-iteration loop. Its two `sync` instructions must carry exactly
|
|
515
|
+
`threads_in_group | thread_group_shared_memory`; the second executes once per
|
|
516
|
+
loop iteration, so each invocation dynamically reaches ten workgroup
|
|
517
|
+
barriers. The profile replays CFG, SSA, and scalar types, requires the exact
|
|
518
|
+
loop-carried signed stride merge and both integer `NEG` source modifiers, and
|
|
519
|
+
rejects declaration or executable tail words. A dedicated uniform WGSL
|
|
520
|
+
`stride` variable and immutable loop-exit condition avoid carrying the earlier
|
|
521
|
+
varying `r0.w` value into barrier control flow.
|
|
522
|
+
|
|
523
|
+
`g0` lowers to `var<workgroup> g0: array<u32, 1024>`, exactly 4 KiB. `t0`
|
|
524
|
+
and `u0` reuse the scalar-word and complete two-word-record contracts proven
|
|
525
|
+
for SetSortArgs and SortStep. External structured loads return a complete zero
|
|
526
|
+
record when physically out of range, and external stores drop the complete
|
|
527
|
+
record. The logical active count is not clamped to the physical `u0` length:
|
|
528
|
+
an out-of-range zero record can participate in the network and move into a
|
|
529
|
+
physically present slot, so pre-clamping would change defined robust-buffer
|
|
530
|
+
behavior.
|
|
531
|
+
|
|
532
|
+
The loop is a nine-stage compare/exchange (bitonic-merge) network, not a
|
|
533
|
+
standalone general sort of arbitrary input. For each stride it partitions the
|
|
534
|
+
512 workgroup records into disjoint pairs, compares the f32 key in word one,
|
|
535
|
+
and swaps both words when the high key is less than the low key. NaN therefore
|
|
536
|
+
keeps the source's false comparison result. The surrounding particle-sort
|
|
537
|
+
schedule supplies the bitonic input relationship; replacing this body with a
|
|
538
|
+
library sort would implement a stronger and different operation.
|
|
539
|
+
|
|
540
|
+
Shared-memory initialization has an explicit runtime orchestration premise.
|
|
541
|
+
SetSortArgs must complete first, the same `SortParameters` buffer range must be
|
|
542
|
+
usable as storage and indirect-dispatch data, its first three words must
|
|
543
|
+
dispatch exactly `D = ceil(max(N, 1) / 512)` groups, and SortInner must read
|
|
544
|
+
the fourth word, `t0[3]`, as the same `N`. A valid D3D/WebGPU x-dispatch
|
|
545
|
+
dimension has `D <= 65535`, hence `N <= 33,553,920 < 2^31`; for every
|
|
546
|
+
dispatched group and every logical record below the clamped remainder, the
|
|
547
|
+
source's signed outer guard then proves that record was initialized before the
|
|
548
|
+
network reads it.
|
|
549
|
+
The current WebGPU runtime prepares compute pipelines but does not dispatch
|
|
550
|
+
them, so same-buffer usage, ordering, and indirect dispatch remain integration
|
|
551
|
+
obligations rather than locally enforced runtime facts. Outside this premise
|
|
552
|
+
the original DXBC can read uninitialized thread-group memory (for example,
|
|
553
|
+
`N = 0x80000000`, group 1), and this profile makes no equivalence claim for
|
|
554
|
+
that source-undefined path.
|
|
555
|
+
|
|
556
|
+
The native browser gate validates one shared-memory module and both paired
|
|
557
|
+
compute pipelines with zero WGSL warnings. The full corpus transition moved
|
|
558
|
+
from 508 qualified / 29 unsupported / 0 failed to 509 / 28 / 0: only
|
|
559
|
+
`particles/gpu/sortinner` became qualified, and the hardened direct-byte
|
|
560
|
+
comparison confirmed all 508 previously qualified package outputs remained
|
|
561
|
+
identical.
|
|
562
|
+
|
|
563
|
+
### Exact 256×1×1 shared-memory particle chunk sort
|
|
564
|
+
|
|
565
|
+
An isolated SM5.0/finite-SM5.1 profile covers the full per-workgroup particle
|
|
566
|
+
sort that precedes the later merge passes. It has the same typed uint Buffer
|
|
567
|
+
`t0`, non-coherent stride-8 structured UAV `u0`, three thread identifiers,
|
|
568
|
+
512 two-word `g0` records, and 256×1×1 group shape as SortInner, but requires
|
|
569
|
+
exactly four temporary registers and its own exact 69-opcode body. Routing
|
|
570
|
+
checks this longer family before SortInner; near siblings still fail closed.
|
|
571
|
+
Declaration tails, body tails, operand selectors, the three integer `NEG`
|
|
572
|
+
sites, load extensions, synchronization flags, finite SM5.1 ranges and
|
|
573
|
+
references, CFG, SSA, scalar types, and both loop-carried merges are all
|
|
574
|
+
replayed or compared before emission.
|
|
575
|
+
|
|
576
|
+
The source begins with a storage-data-dependent `if (N == 0) return`
|
|
577
|
+
immediately before group barriers, which WGSL uniformity analysis cannot prove
|
|
578
|
+
uniform. That return is observably redundant: when `N` is zero the clamped
|
|
579
|
+
logical count is zero, so no external load, shared-memory read, or external
|
|
580
|
+
store executes. The profile validates but omits that three-opcode selection.
|
|
581
|
+
Every invocation can therefore reach the barriers uniformly with the same
|
|
582
|
+
result as the source's all-invocation return.
|
|
583
|
+
|
|
584
|
+
The two source loops are emitted with dedicated uniform state:
|
|
585
|
+
`merge_width`, `merge_done`, `half_width`, `stride`, and `stride_done`.
|
|
586
|
+
`merge_width` visits powers of two from 2 through 512; for each width, `stride`
|
|
587
|
+
visits descending powers of two from half the width through 1. This produces
|
|
588
|
+
45 compare/exchange stages. The two static `sync` sites require exactly
|
|
589
|
+
`threads_in_group | thread_group_shared_memory`: one follows initialization
|
|
590
|
+
and the other executes after every stage, for 46 dynamic barriers in a
|
|
591
|
+
complete invocation, including the normalized zero-count case.
|
|
592
|
+
|
|
593
|
+
For each stage, every local lane selects one disjoint pair. The first stride
|
|
594
|
+
of a merge width mirrors the high index; later strides use the ordinary merge
|
|
595
|
+
partner. Both words move together, and the second word is bitcast to f32 for
|
|
596
|
+
the `<` comparison. Finite keys are consequently sorted ascending within each
|
|
597
|
+
logical chunk of at most 512 records. Equal keys and signed zero preserve the
|
|
598
|
+
source's false comparison result, as does any comparison involving NaN; no
|
|
599
|
+
stronger total ordering is introduced.
|
|
600
|
+
|
|
601
|
+
`g0` remains the exact 4 KiB `array<u32, 1024>`. The signed difference between
|
|
602
|
+
`N` and the wrapping `workgroup_id.x * 512` base is clamped to `[0, 512]`.
|
|
603
|
+
Every shared record below that count is initialized before the first barrier,
|
|
604
|
+
and the guarded high member of every admitted pair implies that both pair
|
|
605
|
+
members are below the count. All physical shared indices remain in
|
|
606
|
+
`[0, 511]`, and each stage's pairs are disjoint. Shared-memory safety therefore
|
|
607
|
+
does not depend on a dispatch premise.
|
|
608
|
+
|
|
609
|
+
External accesses retain D3D robust-buffer behavior independently. `t0[3]`
|
|
610
|
+
returns zero when absent. A structured `u0` load returns one complete zero
|
|
611
|
+
record when either physical word is absent, while a structured store writes
|
|
612
|
+
both words only when the complete record exists. The logical count is not
|
|
613
|
+
clamped to physical `u0` length, because a robust zero record is allowed to
|
|
614
|
+
participate in the sort and move into a present slot.
|
|
615
|
+
|
|
616
|
+
Runtime orchestration is still responsible for the intended global result:
|
|
617
|
+
the producer must publish the same `N` in `t0[3]`, and the application must
|
|
618
|
+
dispatch the chunk groups that cover that logical range before scheduling
|
|
619
|
+
the subsequent merge profiles. This is a result-coverage obligation, not a
|
|
620
|
+
shared-memory-safety precondition of this profile.
|
|
621
|
+
|
|
622
|
+
The native browser gate validates the shared-memory module and both paired
|
|
623
|
+
compute pipelines with zero WGSL warnings.
|
|
624
|
+
|
|
625
|
+
The full corpus transition moved from 509 qualified / 28 unsupported / 0
|
|
626
|
+
failed to 510 / 27 / 0: only `particles/gpu/sort` became qualified. The
|
|
627
|
+
hardened direct-byte comparator cached and compared all 509 previously
|
|
628
|
+
qualified package outputs, with zero byte changes and zero regressions.
|
|
629
|
+
|
|
630
|
+
### `float_16` minimum precision → full-precision f32
|
|
631
|
+
|
|
632
|
+
D3D minimum precision is a floor, not a format: an implementation that computes
|
|
633
|
+
`min16float` operands at full 32-bit precision is conforming, and the registers
|
|
634
|
+
are 32-bit regardless of the hint. Numeric/value operands tagged `float_16`
|
|
635
|
+
therefore lower as ordinary f32 lanes — the hint is dropped, which changes
|
|
636
|
+
nothing observable versus a conforming D3D driver running at full precision.
|
|
637
|
+
Resource, sampler, and UAV handles are not value lanes and require default
|
|
638
|
+
precision. The other operand minimum-precision kinds (`float_2_8`, `sint_16`,
|
|
639
|
+
`uint_16`) stay fail-closed until a shader needs them.
|
|
640
|
+
|
|
641
|
+
*Confirmed against vkd3d-shader:* its SPIR-V backend (`spirv.c`) never reads the
|
|
642
|
+
decoded `min_precision` field — arithmetic lowers at full 32-bit width, the same
|
|
643
|
+
promotion. I/O-signature precision is a separate field. This compiler ignores
|
|
644
|
+
it and emits the signature's base 32-bit component type, so valid 10/16-bit
|
|
645
|
+
float or integer minima are conformingly widened; reserved or unknown
|
|
646
|
+
signature-precision values are not yet rejected.
|
|
647
|
+
|
|
648
|
+
### Exact 256×1×1 atomic histogram merge
|
|
649
|
+
|
|
650
|
+
An isolated SM5.0/finite-SM5.1 profile covers
|
|
651
|
+
`postprocess/mergehistograms`. Both forms declare immediate one-row `cb0`,
|
|
652
|
+
typed uint Buffer `t0`, a non-coherent typed uint UAV `u0`,
|
|
653
|
+
`input_thread_id_in_group.x`, `input_thread_id.x`, four temporary registers,
|
|
654
|
+
64 stride-4 thread-group records in `g0`, and
|
|
655
|
+
`dcl_thread_group 256,1,1`. The SM5.1 declarations and body references must
|
|
656
|
+
use canonical finite singleton range-zero metadata. The exact 35-opcode body,
|
|
657
|
+
operand selectors and immediates, SM5.0 typed-load extensions, both barrier
|
|
658
|
+
payloads, CFG, SSA, scalar types, and the loop-carried bin index are replayed
|
|
659
|
+
or compared before emission. Selection occurs before the generic compute
|
|
660
|
+
lowerer, so malformed near siblings fail closed.
|
|
661
|
+
|
|
662
|
+
`g0` lowers to
|
|
663
|
+
`var<workgroup> g0: array<atomic<u32>, 64>`. Local lanes 0 through 63
|
|
664
|
+
atomically initialize one bin each, all 256 invocations execute an
|
|
665
|
+
unconditional `workgroupBarrier`, active global invocations accumulate 16
|
|
666
|
+
`uint4` input records with four `atomicAdd` operations per loop iteration,
|
|
667
|
+
and every invocation executes the second unconditional barrier. Local lanes
|
|
668
|
+
0 through 63 then atomically add the shared totals to `u0`. The source
|
|
669
|
+
barriers must carry exactly
|
|
670
|
+
`threads_in_group | thread_group_shared_memory`; moving either barrier under
|
|
671
|
+
one of the surrounding varying conditions is rejected.
|
|
672
|
+
|
|
673
|
+
The typed Buffer is represented as `var<storage, read> t0: array<u32>`.
|
|
674
|
+
The source record address is preserved as the wrapping sequence
|
|
675
|
+
`(global_invocation_id.x << 6) >> 2`, and each logical `uint4` load is admitted
|
|
676
|
+
only when all four physical words exist. A missing or partial record therefore
|
|
677
|
+
produces four zero lanes, matching typed-buffer OOB load semantics without an
|
|
678
|
+
eager WGSL access outside the scalar backing array. The typed UAV is
|
|
679
|
+
`var<storage, read_write> u0: array<atomic<u32>>`; its final atomic executes
|
|
680
|
+
only when the local bin index is below `arrayLength(&u0)`, so an OOB source
|
|
681
|
+
atomic is dropped.
|
|
682
|
+
|
|
683
|
+
The intended histogram result has explicit runtime premises. `cb0.x` and
|
|
684
|
+
`cb0.y` must be finite non-negative integer-valued f32 dimensions representable
|
|
685
|
+
as u32, because the profile's `ftou` adaptation claims equivalence only on
|
|
686
|
+
that domain. Their low-u32 product is the active global-invocation count.
|
|
687
|
+
Dispatch must cover that count, `t0` must provide the intended 64 bins for
|
|
688
|
+
each active invocation, and `u0` must provide at least 64 elements (normally
|
|
689
|
+
initialized to zero for a fresh result). The explicit robust guards remain
|
|
690
|
+
memory-safe outside the physical `t0`/`u0` size premises, but undersized
|
|
691
|
+
bindings intentionally produce the source's zero-load/drop-atomic behavior
|
|
692
|
+
rather than the complete intended histogram.
|
|
693
|
+
|
|
694
|
+
The substantive DX11 and DX12 shader pair passes the real comparison matrix
|
|
695
|
+
and emits byte-identical WGSL after finite-range normalization. The native
|
|
696
|
+
browser gate validates both paired compute pipelines with zero WGSL warnings.
|
|
697
|
+
|
|
698
|
+
The full corpus transition moved from 510 qualified / 27 unsupported / 0
|
|
699
|
+
failed to 511 / 26 / 0: only `postprocess/mergehistograms` became qualified.
|
|
700
|
+
The hardened direct-byte comparator cached and compared all 510 previously
|
|
701
|
+
qualified package outputs, with zero byte changes and zero regressions.
|
|
702
|
+
|
|
703
|
+
### Exact 16×16×1 atomic histogram creation
|
|
704
|
+
|
|
705
|
+
An isolated dual-validator profile covers `postprocess/createhistograms`.
|
|
706
|
+
Both backends declare immediate one-row `cb0`, float `texture2d` `t0`, a
|
|
707
|
+
non-coherent typed uint Buffer UAV `u0`, two-component workgroup, local, and
|
|
708
|
+
global invocation identifiers, 64 stride-4 thread-group records in `g0`, and
|
|
709
|
+
`dcl_thread_group 16,16,1`. The SM5.0 form has three temporary registers and
|
|
710
|
+
converts `cb0.z` inside its final selection. The finite-range SM5.1 form has
|
|
711
|
+
four temps and hoists that conversion ahead of the first selection. Each
|
|
712
|
+
literal 49-instruction schedule has its own exact opcode, operand, modifier,
|
|
713
|
+
extension, range/reference, CFG, SSA, and type validator; both feed one
|
|
714
|
+
canonical emitter only after validation.
|
|
715
|
+
|
|
716
|
+
Canonical emission hoists the pure uniform `ftou(cb0.z)` conversion. This is
|
|
717
|
+
equivalent on the admitted runtime domain: `ScreenTilesX` must be finite,
|
|
718
|
+
non-negative, integer-valued, and representable as u32. The conversion has no
|
|
719
|
+
side effect, changes no branch or barrier participation, and its value is used
|
|
720
|
+
only by the final output address. Evaluating it for all 256 lanes instead of
|
|
721
|
+
the 16 output lanes is therefore unobservable on that domain. NaN and
|
|
722
|
+
out-of-range conversion remain outside this claim.
|
|
723
|
+
|
|
724
|
+
The entry point preserves the ordered source identities as
|
|
725
|
+
`workgroup_id`, `local_invocation_id`, and `global_invocation_id`.
|
|
726
|
+
`g0` is `var<workgroup> array<atomic<u32>, 64>`. The first 64 flattened
|
|
727
|
+
local lanes initialize it with `atomicStore`, all lanes execute the first
|
|
728
|
+
unconditional `workgroupBarrier`, and in-range pixels atomically increment one
|
|
729
|
+
bin. All lanes then execute the second unconditional barrier before the first
|
|
730
|
+
16 lanes atomically load four bins each. Both source barriers must carry
|
|
731
|
+
exactly `threads_in_group | thread_group_shared_memory`; moved, conditional,
|
|
732
|
+
or differently flagged barriers fail closed.
|
|
733
|
+
|
|
734
|
+
Mip-zero `resinfo_uint` becomes `textureDimensions(t0, 0)`. The explicit
|
|
735
|
+
global-coordinate test surrounds the source pixel path, while the load itself
|
|
736
|
+
retains the texture contract's safe coordinate and zero-result selection so
|
|
737
|
+
no eager out-of-range `textureLoad` can be formed. The RGB transfer curve,
|
|
738
|
+
luminance dot product, base-two logarithms/exponent, natural-log conversion,
|
|
739
|
+
and source `div_sat` remain in their original f32 order and bit-exact
|
|
740
|
+
constants. The resulting signed bin is additionally guarded by
|
|
741
|
+
`0 <= bin && bin < 64` before `atomicAdd`. That guard is redundant for the
|
|
742
|
+
admitted finite path but contains WGSL memory access on adapted numeric edges;
|
|
743
|
+
D3D otherwise makes all TGSM undefined for an out-of-range shared atomic.
|
|
744
|
+
|
|
745
|
+
Each workgroup emits 16 typed `uint4` records. The wrapping address is
|
|
746
|
+
`((workgroup_id.y * ScreenTilesX + workgroup_id.x) << 4) + local_index`.
|
|
747
|
+
The atomic-word `u0` representation tests the complete typed element against
|
|
748
|
+
`arrayLength(&u0) / 4` before issuing all four `atomicStore` calls. An
|
|
749
|
+
out-of-range or physically partial element therefore writes nothing, never a
|
|
750
|
+
partial record. This is the exact 64-bin layout consumed by
|
|
751
|
+
`postprocess/mergehistograms`.
|
|
752
|
+
|
|
753
|
+
The intended numeric result additionally requires finite `MinLuminance` and
|
|
754
|
+
`MaxLuminance`, `MaxLuminance > MinLuminance`, and finite intermediate
|
|
755
|
+
normalization arithmetic. In particular, every executed pixel path admitted
|
|
756
|
+
by this claim must produce a finite positive `luminance`, a finite
|
|
757
|
+
`log_luminance`, and a finite `normalized_luminance` before the multiply and
|
|
758
|
+
`ftoi`; the value presented to `ftoi` is consequently in `[0, 64]` and within
|
|
759
|
+
the i32 conversion range. This explicitly excludes zero/negative luminance,
|
|
760
|
+
NaN, infinity, and any overflow or invalid intermediate from the equivalence
|
|
761
|
+
claim. Ordinary finite `_sat` maps exactly to WGSL
|
|
762
|
+
`clamp(..., 0.0, 1.0)`; D3D's special NaN-to-zero saturation result remains
|
|
763
|
+
the existing documented non-finite adaptation.
|
|
764
|
+
|
|
765
|
+
For every non-empty dispatch, `ScreenTilesX` must equal the dispatched
|
|
766
|
+
x workgroup count as well as being the output row stride, and the y workgroup
|
|
767
|
+
count must cover the intended source texture rows. The equality prevents an
|
|
768
|
+
x workgroup beyond the declared row width from aliasing a later row's output
|
|
769
|
+
records; a dispatch/stride mismatch and its resulting output collision are
|
|
770
|
+
outside the equivalence claim. `u0` must contain the intended complete output
|
|
771
|
+
records. Physical texture and UAV undersizing remains memory-safe through the
|
|
772
|
+
explicit guards, but cannot produce the complete intended histogram.
|
|
773
|
+
|
|
774
|
+
The substantive DX11 and DX12 pair passes the real comparison matrix and
|
|
775
|
+
emits byte-identical WGSL after schedule and finite-range normalization. The
|
|
776
|
+
native browser gate validates the shared module and both paired compute
|
|
777
|
+
pipelines with zero WGSL warnings.
|
|
778
|
+
|
|
779
|
+
The full corpus transition moved from 511 qualified / 26 unsupported / 0
|
|
780
|
+
failed to 512 / 25 / 0: only `postprocess/createhistograms` became qualified.
|
|
781
|
+
The hardened direct-byte comparator confirmed all 511 common previously
|
|
782
|
+
qualified package outputs remained byte-identical.
|
|
783
|
+
|
|
784
|
+
### Exact two-pass particle clear with effect-proven signed counter
|
|
785
|
+
|
|
786
|
+
An effect-level profile covers `managed/space/specialfx/particles/gpu/clear`.
|
|
787
|
+
It requires exactly `Main.pass0.compute` and `Main.pass1.compute`, each as the
|
|
788
|
+
only active stage in its pass. Reflection must identify pass 0 `u0` and pass 1
|
|
789
|
+
`u1` as the same one-element `ParticleCounters` UAV with Carbon type 10,
|
|
790
|
+
alongside pass 1 stride-4 `DeadBuffer` `u0` and stride-32 `ParticleBuffer`
|
|
791
|
+
`u2`. Both companion IR programs are validated before an opaque,
|
|
792
|
+
program-identity-bound proof is minted. A standalone pass-0 shader can never
|
|
793
|
+
select this profile: its signed typed-store declaration alone does not prove
|
|
794
|
+
the bound view is `R32_SINT`, and a missing, forged, or differently decoded
|
|
795
|
+
program proof fails closed. Selecting only pass 0 for package output remains
|
|
796
|
+
safe because preflight still examines the complete resolved effect.
|
|
797
|
+
|
|
798
|
+
Pass 0 has exact SM5.0 and finite-range SM5.1 validators for its signed typed
|
|
799
|
+
`u0`, `1x1x1` group, and two-instruction body. Under the effect proof it emits
|
|
800
|
+
`array<atomic<i32>>` with a four-byte minimum binding and
|
|
801
|
+
`atomicStore(&u0[0u], 0i)`. The same opaque policy gates binding-plan
|
|
802
|
+
construction and final lowering, so signed typed UAV layout is not admitted
|
|
803
|
+
as a general store feature.
|
|
804
|
+
|
|
805
|
+
Pass 1 independently proves the signed 32-bit view through its exact returned
|
|
806
|
+
`imm_atomic_iadd` on signed typed `u1[0]`. Its separate SM5.0 and finite-range
|
|
807
|
+
SM5.1 validators require the literal 26-opcode schedule, immediate `cb3`,
|
|
808
|
+
three UAV identities and strides, scalar flattened local index, two temps,
|
|
809
|
+
`16x16x1` group, both structured loops, lane-zero tail selection, operand
|
|
810
|
+
selectors and immediates, ranges/references, CFG, SSA, scalar types, and the
|
|
811
|
+
two loop-carried merges. `cb3[0].x` is read as raw bits with `bitcast<u32>`.
|
|
812
|
+
The complete-block index stays source-shaped as
|
|
813
|
+
`insertBits(local_invocation_index, block_index, 8u, 24u)`.
|
|
814
|
+
|
|
815
|
+
Together the 256 lanes visit every index in `[0, count)` exactly once: all
|
|
816
|
+
complete 256-record blocks run in the first loop, then lane zero visits the
|
|
817
|
+
remainder. Each visit first attempts both source-ordered `ParticleBuffer`
|
|
818
|
+
stores under a complete stride-32 record guard, then executes
|
|
819
|
+
`atomicAdd(&u1[0u], 1i)`, bitcasts the returned old signed value to the
|
|
820
|
+
dead-list `u32` index, and independently guards the stride-4 `DeadBuffer`
|
|
821
|
+
store. A short particle buffer therefore does not suppress the counter
|
|
822
|
+
increment or dead-list attempt, and a short dead-list buffer drops only its
|
|
823
|
+
own store. No barrier is introduced.
|
|
824
|
+
|
|
825
|
+
The intended dispatch uses one `1x1x1` pass-0 workgroup, requires its reset to
|
|
826
|
+
complete and become visible before pass 1, then uses exactly one `16x16x1`
|
|
827
|
+
pass-1 workgroup with no concurrent counter users and at least `count`
|
|
828
|
+
complete records in both structured buffers. Extra pass-0 workgroups only
|
|
829
|
+
repeat the same zero store, but extra pass-1 workgroups repeat the entire
|
|
830
|
+
traversal and append duplicates. External consumers must wait for pass 1 to
|
|
831
|
+
complete. Practical counts must also fit the application's watchdog budget.
|
|
832
|
+
Explicit store guards remain memory-safe for undersized buffers, but the final
|
|
833
|
+
counter still reaches `count`, matching the source's per-operation
|
|
834
|
+
dropped-write behavior rather than claiming a complete result.
|
|
835
|
+
|
|
836
|
+
The substantive DX11 and DX12 effect pair passes the real effect-level matrix:
|
|
837
|
+
both passes are ready and emit byte-identical WGSL after range normalization.
|
|
838
|
+
The intentionally standalone matrix view keeps pass 0 unsupported while pass
|
|
839
|
+
1 is independently emitted. The engine's fail-closed matrix validator
|
|
840
|
+
reconciles the exact two-pass body, stage digests, occurrence counts, per-key
|
|
841
|
+
coverage, reset WGSL, and signed atomic layout before admitting that contextual
|
|
842
|
+
pass. Its required native WebGPU gate compiled one unique independently
|
|
843
|
+
emitted module and prepared four compute pipelines for the paired backends
|
|
844
|
+
with zero WGSL warnings.
|
|
845
|
+
|
|
846
|
+
The full corpus transition moved from 512 qualified / 25 unsupported / 0
|
|
847
|
+
failed to 513 / 24 / 0: only
|
|
848
|
+
`managed/space/specialfx/particles/gpu/clear` became qualified. The hardened
|
|
849
|
+
direct-byte comparator confirmed all 512 common previously qualified package
|
|
850
|
+
outputs remained byte-identical, with zero regressions.
|
|
851
|
+
|
|
852
|
+
### Exact 16×16×1 shared-memory particle emit
|
|
853
|
+
|
|
854
|
+
A dedicated SM5.0 profile covers
|
|
855
|
+
`managed/space/specialfx/particles/gpu/emit`. Admission is two-layered:
|
|
856
|
+
the exact declaration family (immediate `cb3` of 4096 vec4s, stride-32
|
|
857
|
+
`ParticleBuffer` UAV, signed typed counter buffer, stride-4 `DeadBuffer`,
|
|
858
|
+
flattened/vector local identifiers, `workgroup_id`, 112-byte raw TGSM, and a
|
|
859
|
+
`16x16x1` group) plus a browser-safe SHA-256 semantic digest of the complete
|
|
860
|
+
normalized program. The digest implementation is proven against `node:crypto`
|
|
861
|
+
on known and varied vectors and hardened against property aliasing, sparse
|
|
862
|
+
arrays, accessors, prototypes, and unknown semantic fields. SM5.1 is
|
|
863
|
+
recognized by the same digest family and then deliberately rejected as
|
|
864
|
+
comparison-only; only the literal SM5.0 schedule may emit WGSL.
|
|
865
|
+
|
|
866
|
+
The lowered body keeps the audited execution shape: lane-zero TGSM
|
|
867
|
+
initialization is followed by a uniform `workgroupBarrier()`; the returned
|
|
868
|
+
signed `atomicAdd(u1[0u], -1i)` stays ordered before its `old - 1` signed
|
|
869
|
+
success test; DeadBuffer reads use a clamped physical load with an explicit
|
|
870
|
+
zero fallback; cbuffer rows use clamped physical access with zero selection;
|
|
871
|
+
raw TGSM accesses stay within the initialized words; and the final
|
|
872
|
+
`ParticleBuffer` write is guarded as one complete eight-word record. Source
|
|
873
|
+
mapping covers the executable schedule, omitting only structural closure
|
|
874
|
+
instructions.
|
|
875
|
+
|
|
876
|
+
The signed typed-UAV layout is owned by one self-proving policy
|
|
877
|
+
(`particleEmitSignedAtomicLayoutPolicy`): candidacy itself — the declaration
|
|
878
|
+
family and semantic digest — is the whole proof, and binding-plan
|
|
879
|
+
construction and the lowerer consume the same function, so no identity list
|
|
880
|
+
is duplicated or admitted generally. Trusted admission constants may only be
|
|
881
|
+
regenerated by the fixture generator from the audited effect bytes; it
|
|
882
|
+
verifies the effect and internal DXBC SHA-256 hashes and fails closed on any
|
|
883
|
+
other input.
|
|
884
|
+
|
|
885
|
+
The genuine paired effects are exercised through the optional
|
|
886
|
+
`CJS_PARTICLE_EMIT_DX11_EFFECT` / `CJS_PARTICLE_EMIT_DX12_EFFECT` test
|
|
887
|
+
inputs. With both fixtures present the suite runs with zero skips: the DX11
|
|
888
|
+
package qualifies with its `Main.pass0` ready, the DX12 counterpart reports
|
|
889
|
+
the comparison-only boundary, and the required native WebGPU gate compiled
|
|
890
|
+
the one unique emitted module and prepared its compute pipeline with zero
|
|
891
|
+
WGSL warnings.
|
|
892
|
+
|
|
893
|
+
The full corpus transition moved from 506 qualified / 31 unsupported / 0
|
|
894
|
+
failed to 507 / 30 / 0: only
|
|
895
|
+
`managed/space/specialfx/particles/gpu/emit` became qualified, and direct
|
|
896
|
+
comparison confirmed all 506 common previously qualified package outputs
|
|
897
|
+
remained byte-identical.
|
|
898
|
+
|
|
899
|
+
### The exact compute-profile set is complete and frozen
|
|
900
|
+
|
|
901
|
+
The emit profile completes the audited GPU particle pipeline
|
|
902
|
+
(`setdrawparameters`, `setsortargs`, `sortstep`, `sort`, `sortinner`,
|
|
903
|
+
`createhistograms`, `mergehistograms`, `clear`, `emit`) alongside structured
|
|
904
|
+
skinning. No further package-specific exact compute profiles should be added:
|
|
905
|
+
remaining or future compute coverage (for example `particles/gpu/update` or
|
|
906
|
+
`computelightlists`) must instead extend the general typed IR path with
|
|
907
|
+
reusable thread-group memory, barrier, atomic, and loop lowering, retiring
|
|
908
|
+
profile-by-profile growth. Exact profiles remain pinned to their audited
|
|
909
|
+
bytecode, so any game-build shader recompile demotes the affected package to
|
|
910
|
+
unsupported until it is re-audited — a corpus rebuild after a build bump is
|
|
911
|
+
the standing re-qualification gate.
|
|
912
|
+
|
|
913
|
+
### Fail-closed intentional nontermination: `system/crash`
|
|
914
|
+
|
|
915
|
+
`system/crash` is not a bounded compute candidate. Without the required
|
|
916
|
+
sentinel its loop deliberately traverses all `2^32` indices and never
|
|
917
|
+
terminates; adding an iteration cap would change observable semantics.
|
|
918
|
+
Multiple workgroups also race rather than preserving the intended
|
|
919
|
+
exactly-one-group behavior. Because the package proves neither the sentinel
|
|
920
|
+
nor the exact-one-group runtime contract, the shader remains permanently
|
|
921
|
+
fail-closed.
|
|
922
|
+
|
|
923
|
+
### Resource handles → fixed, unmodified identities
|
|
924
|
+
|
|
925
|
+
Every supported resource, sampler, or UAV role requires the declared handle
|
|
926
|
+
type, default minimum precision, no source modifier, and a fixed descriptor
|
|
927
|
+
identity within the admitted singleton binding range. Relative identities fail
|
|
928
|
+
closed before binding lookup; a present fixed absolute identity is checked
|
|
929
|
+
against the resolved singleton binding. Legal resource-result swizzles remain
|
|
930
|
+
supported. *Confirmed against vkd3d-shader:* its register and descriptor
|
|
931
|
+
validation likewise restricts modifier types and verifies descriptor indices
|
|
932
|
+
against their declared ranges. This compiler is stricter about relative member
|
|
933
|
+
indices because its binding layout deliberately supports singleton ranges only.
|
|
934
|
+
|
|
935
|
+
### Typed `Buffer` SRVs require bound-view metadata
|
|
936
|
+
|
|
937
|
+
A render-stage `dcl_resource` with dimension `buffer` declares the component
|
|
938
|
+
class returned by `ld`, but it does not encode the width or conversion rules of
|
|
939
|
+
the bound DXGI view. The same uniform uint declaration can be paired with an
|
|
940
|
+
`R32_UINT` view or an `R32G32B32A32_UINT` view; lowering both to one WGSL
|
|
941
|
+
storage-array element type changes indexing for at least one valid binding.
|
|
942
|
+
|
|
943
|
+
Render typed-buffer SRVs therefore fail closed until trusted bound-view format
|
|
944
|
+
metadata is part of the binding policy, manifest, and compatibility
|
|
945
|
+
fingerprint. A future lowering must derive the physical WGSL element type,
|
|
946
|
+
element stride, D3D missing-channel values, format conversion, and
|
|
947
|
+
`minBindingSize` from that metadata rather than from the declaration return
|
|
948
|
+
token. The bounded compute profiles described above use separately validated
|
|
949
|
+
scalar-word contracts and are not widened by this restriction.
|
|
950
|
+
|
|
951
|
+
The corrective corpus transition moved from 513 qualified / 24 unsupported /
|
|
952
|
+
0 failed to 506 / 31 / 0. Exactly `exposuredebug`, `highpassfilter`, `taa`,
|
|
953
|
+
`taacopy`, `tonemapping`, `lensflare`, and `lensgrime` were retracted; direct
|
|
954
|
+
comparison confirmed all 506 remaining qualified packages are byte-identical.
|
|
955
|
+
The paired DX11/DX12 matrices retained matching axes and active topology with
|
|
956
|
+
zero front-end failures while moving the affected 111 stage occurrences (72
|
|
957
|
+
DX11 and 39 DX12) from emitted to unsupported.
|
|
958
|
+
|
|
959
|
+
## Not supported (fail closed)
|
|
960
|
+
|
|
961
|
+
- **Globally non-refactorable shaders** (`dcl_global_flags` without
|
|
962
|
+
`REFACTORING_ALLOWED`) — every operation would be precise; see the Adapted
|
|
963
|
+
entry for why per-op precise is representable but this is not.
|
|
964
|
+
- **DX12 bindless sampled-resource ranges** (`space1` arrays/unbounded
|
|
965
|
+
ranges) — comparison-only limitation under the current DX11 translation target
|
|
966
|
+
(DX11 is the target; DX12 exists to confirm equal results). Needs its own
|
|
967
|
+
audited design if it ever becomes target work.
|
|
968
|
+
- **`imul`/`umul` high-half results** — WGSL has no 32×32→64 multiply
|
|
969
|
+
builtin; only the low-half destination is supported.
|
|
970
|
+
- **Dynamic constant-buffer register selection** (`cbX[dynamic][…]` selecting
|
|
971
|
+
the *buffer*) — only the vector index may be dynamic.
|
|
972
|
+
- **Non-immediate mip levels in `resinfo`**; texture `ld` accepts a dynamic
|
|
973
|
+
address/mip but remains bounded to the resource shapes listed below.
|
|
974
|
+
- **Unknown texture dimensions** (`texturecubearray`, MSAA kinds, …) in
|
|
975
|
+
sampled layouts.
|
|
976
|
+
- **Render typed `Buffer` SRVs without explicit bound-view format metadata** —
|
|
977
|
+
the DXBC declaration identifies a return component class but not whether the
|
|
978
|
+
runtime view is scalar, vector, normalized, integer, or floating point.
|
|
979
|
+
- **Immediate texture offsets** (`sample_controls` / `_aoffimmi`) outside the
|
|
980
|
+
bounded 2D sample family below. In particular, offset texture `ld` and
|
|
981
|
+
non-2D sampling fail closed.
|
|
982
|
+
- **Mutable relative `indexable_temp` registers** (fixed, declared, bounded
|
|
983
|
+
width-four slots are scalarized as described above; any relative shape outside
|
|
984
|
+
the immutable constant-table form still fails closed), and subroutine control flow
|
|
985
|
+
(`call`/`callc`/`label`/`interface_call`) — front-end rejections.
|
|
986
|
+
- **Geometry, hull, and domain stage kinds, plus compute programs outside the
|
|
987
|
+
bounded profile above** — WGSL has no geometry/hull/domain stage. General
|
|
988
|
+
compute resource shapes, thread-group sizes, builtins, control flow, and
|
|
989
|
+
instruction families are not yet lowered. These fail closed per stage kind
|
|
990
|
+
or bounded-profile reason instead of being misreported as malformed records.
|
|
991
|
+
- **Sampler modes other than `default`**, fragment input interpolation modes
|
|
992
|
+
other than `linear` and `linear_noperspective`, minimum-precision operand
|
|
993
|
+
kinds other than `float_16` (which promotes; see Adapted), and vertex system semantics
|
|
994
|
+
outside `SV_Position`/`SV_VertexID`/`SV_InstanceID` (fragment:
|
|
995
|
+
`SV_Position`/`SV_IsFrontFace`, output `SV_Target`).
|
|
996
|
+
|
|
997
|
+
## Supported mappings
|
|
998
|
+
|
|
999
|
+
### `sample_d` gradient sampling and integer/rounding opcodes
|
|
1000
|
+
|
|
1001
|
+
`sample_d` lowers to `textureSampleGrad(t, s, coord, ddx, ddy)` (2/3-component
|
|
1002
|
+
spatial gradients by dimension). A 2D-array address consumes three source
|
|
1003
|
+
lanes (xy coordinate plus array layer) but its gradients consume only xy;
|
|
1004
|
+
3D/cube addresses and gradients consume xyz. Added `imax/imin/umax/umin` (WGSL overloaded
|
|
1005
|
+
`max`/`min`), `ishl`/`ishr` (`<< u32(...)` / `>> u32(...)` — DXBC shift counts
|
|
1006
|
+
cast to the WGSL-required u32), `ineg` (signed negation), `round_ne`
|
|
1007
|
+
(`round`, ties to even), `round_pi` (`ceil`), and the previously handler-only
|
|
1008
|
+
`ult`/`uge` to the applicable stage support sets.
|
|
1009
|
+
|
|
1010
|
+
### 2D-array sample layers → round-to-nearest-even
|
|
1011
|
+
|
|
1012
|
+
DXBC sampling rounds a floating Texture2DArray layer coordinate to the nearest
|
|
1013
|
+
integer with ties to even, then clamps it to the available layer range. The
|
|
1014
|
+
layer argument therefore lowers as `i32(round(layer))`; WGSL `round` has the
|
|
1015
|
+
same tie rule and WGSL sampling clamps the resulting array index. The spatial
|
|
1016
|
+
xy coordinate stays separate from that layer argument in every supported
|
|
1017
|
+
sample form and in both stages.
|
|
1018
|
+
|
|
1019
|
+
## Bounded / temporary
|
|
1020
|
+
|
|
1021
|
+
- **Carbon Detail maps → one physical 2D-array texture (fragment stage).**
|
|
1022
|
+
The late resource-transform planner recognizes exactly two logical
|
|
1023
|
+
`Detail1Map`/`Detail2Map` inputs or exactly three consecutive
|
|
1024
|
+
`Detail1Map`/`Detail2Map`/`Detail3Map` inputs. Each must be a scalar,
|
|
1025
|
+
non-sRGB, float4 Texture2D in the same register space, and every shader use
|
|
1026
|
+
must be an unmodified, fixed-register `sample_b` using the same sampler and
|
|
1027
|
+
bias operand. Relative/non-uniform handles, sample offsets, other opcodes,
|
|
1028
|
+
incompatible metadata, incomplete names, missing samples, or ambiguous
|
|
1029
|
+
bindings reject the transform.
|
|
1030
|
+
|
|
1031
|
+
The source IR and semantic parameter names remain unchanged. Physical
|
|
1032
|
+
lowering replaces the inputs with one `texture_2d_array<f32>`, reuses the
|
|
1033
|
+
first input identity, removes the later bindings only from the owning pass,
|
|
1034
|
+
and emits fixed layers 0/1 or 0/1/2. The WGSL set becomes version 3 and
|
|
1035
|
+
carries the complete realization recipe. Every named layer is required; the
|
|
1036
|
+
runtime may use a compatible native array representation or decode all
|
|
1037
|
+
layers to RGBA8, but it may not silently supply a missing layer.
|
|
1038
|
+
|
|
1039
|
+
Exact high (`.sm_depth`) and medium (`.sm_hi`) exhaustive matrices qualify
|
|
1040
|
+
all 160 DX11 bodies of `unpackedskinned_quaddetailv5` and all 32 DX11 bodies
|
|
1041
|
+
of `unpackedskinned_quadheatdetailv5`, with zero failed bodies. Their DX12
|
|
1042
|
+
matrices have the same axes/topology and zero failures; bindless bodies
|
|
1043
|
+
retain the pre-existing comparison-only unbounded-range boundary. The
|
|
1044
|
+
representative non-bindless, PPT-on, unclipped, opaque, debug-off body is
|
|
1045
|
+
body 4 (overlay blend for Detail): Detail falls from 17 source textures to
|
|
1046
|
+
15 physical textures through a three-layer recipe, while HeatDetail falls
|
|
1047
|
+
from 17 to 16 through a two-layer recipe. All four DX11 and four DX12
|
|
1048
|
+
representative fragment modules across high and medium compile in the
|
|
1049
|
+
browser with zero WGSL warnings.
|
|
1050
|
+
|
|
1051
|
+
The exact-build corpus remains 507 qualified, 30 unsupported, and 0 failed.
|
|
1052
|
+
Twelve package hashes change, all within the static/skinned,
|
|
1053
|
+
packed/unpacked Quad Detail, HeatDetail, and Environment families; the other
|
|
1054
|
+
495 qualified packages are byte-identical. Every changed package carries
|
|
1055
|
+
exactly one two- or three-layer Detail transform.
|
|
1056
|
+
|
|
1057
|
+
This closes the compiler-side sampled-texture binding limit only. WGSL-set
|
|
1058
|
+
version 3 is not yet accepted by the committed engine reader, and raw module
|
|
1059
|
+
compilation does not prove resource realization or rendering. Runtime
|
|
1060
|
+
support must consume the recipe explicitly before these packages are
|
|
1061
|
+
render-ready.
|
|
1062
|
+
- **Immediate 2D sample offsets** — `sample`, `sample_b`, `sample_d`, and
|
|
1063
|
+
`sample_l` lower their signed `_aoffimmi(u,v,w)` record to WGSL's final
|
|
1064
|
+
constant `vec2<i32>(u, v)` sampling argument. Both APIs apply that
|
|
1065
|
+
texel-space offset before sampler address modes, and both require components
|
|
1066
|
+
in `[-8, 7]`. D3D ignores `w` for a Texture2D, so only `u` and `v` are
|
|
1067
|
+
emitted. Fragment supports all four opcodes; vertex supports the
|
|
1068
|
+
explicit-gradient/LOD pair already legal there. Duplicate or malformed
|
|
1069
|
+
records, offsets on other opcodes, and non-2D resource shapes fail closed.
|
|
1070
|
+
The completed corpus transition kept 497 shaders qualified and intentionally
|
|
1071
|
+
changed exactly seven prior packages: `downsample`, `taa`, the tactical
|
|
1072
|
+
overlay `anchor`, `connector`, `ubershader`, and `velocity` shaders, and
|
|
1073
|
+
`ui/glowtransform`.
|
|
1074
|
+
*Confirmed against vkd3d-shader:* its IR preserves the signed immediate
|
|
1075
|
+
offset on sample instructions, and its SPIR-V, GLSL, and MSL backends pass
|
|
1076
|
+
those constants through as the target sampling operation's constant offset.
|
|
1077
|
+
- **`resinfo` (fragment stage)** — 2D and 3D textures, scalar immediate mip, components x/y
|
|
1078
|
+
(dimensions), z (depth, 3D only), and w (`textureNumLevels`); z rejected
|
|
1079
|
+
for 2D. A non-zero mip is queried through an in-range clamped level and its
|
|
1080
|
+
dimensions are selected to zero when the requested level is out of range,
|
|
1081
|
+
reproducing D3D instead of exposing WGSL's indeterminate out-of-range
|
|
1082
|
+
`textureDimensions` result. `_rcpFloat` reciprocates only dimensions, never
|
|
1083
|
+
the mip count; its specified infinity for zero dimensions shares the
|
|
1084
|
+
non-finite WGSL limitation documented for `rcp` above. Unknown return-type
|
|
1085
|
+
encodings fail closed. Ordinary float saturation is valid in D3D but
|
|
1086
|
+
currently unsupported here; saturation on the uint return mode is invalid
|
|
1087
|
+
because saturation requires a floating-point destination. D3D's zero result
|
|
1088
|
+
for an unbound resource is outside
|
|
1089
|
+
this shader mapping: WebGPU requires every declared binding, and the engine
|
|
1090
|
+
rejects a missing caller resource. A fallback texture cannot reproduce the
|
|
1091
|
+
exact result because WebGPU textures cannot have zero dimensions (and
|
|
1092
|
+
`_rcpFloat` requires infinity for applicable zero dimensions); exact
|
|
1093
|
+
emulation would need explicit bound-state metadata and a selected result.
|
|
1094
|
+
Widen per dimension when a shader needs it.
|
|
1095
|
+
*Confirmed against vkd3d-shader:* `spirv_compiler_emit_resinfo` (spirv.c)
|
|
1096
|
+
emits image-size and mip-level-count queries, pads missing dimension
|
|
1097
|
+
components with zero, applies the resource swizzle, and converts the uint
|
|
1098
|
+
vector to float for the ordinary float form. It also accepts ordinary float
|
|
1099
|
+
saturation after forming that result. It explicitly rejects
|
|
1100
|
+
`VKD3DSI_RESINFO_RCP_FLOAT`; that form here follows the D3D contract
|
|
1101
|
+
independently. vkd3d also issues the size query directly, so our explicit
|
|
1102
|
+
clamped-query/zero-select is the WGSL-specific guard needed to preserve
|
|
1103
|
+
D3D's defined out-of-range result.
|
|
1104
|
+
- **`ld`** — 2D textures (fragment only; original address lanes xy=texel and
|
|
1105
|
+
w=mip, packed into a three-lane u32 WGSL address). Typed-buffer loads are
|
|
1106
|
+
admitted only by bounded compute profiles with a separately validated
|
|
1107
|
+
scalar-word contract.
|
|
1108
|
+
Texture coordinates and mip are clamped to a valid texel for the eagerly
|
|
1109
|
+
evaluated `textureLoad`, then the result is selected to zero unless the
|
|
1110
|
+
original address was fully in range. This excludes WGSL's otherwise
|
|
1111
|
+
permitted live in-bounds texel result for an invalid logical texel address.
|
|
1112
|
+
The zero vector is exact under the current engine contract that these
|
|
1113
|
+
bindings use four-component views (`rgba8unorm` or `rgba8unorm-srgb` today).
|
|
1114
|
+
A future one- or two-component view would require view-channel metadata so
|
|
1115
|
+
the explicit out-of-bounds replacement can reproduce D3D's missing-component
|
|
1116
|
+
defaults (normally alpha one).
|
|
1117
|
+
- **`ld_structured`** — fixed immediate DWORD byte offsets, one scalar
|
|
1118
|
+
address, fixed (non-relative) resource operands. Every word fetch is clamped
|
|
1119
|
+
to valid storage-buffer memory and selected to zero when the structure index
|
|
1120
|
+
is outside `arrayLength / stride`. Offset-plus-swizzle accesses beyond the
|
|
1121
|
+
declared stride fail closed, so D3D's undefined byte-offset-overrun case is
|
|
1122
|
+
never emitted.
|
|
1123
|
+
*Confirmed against vkd3d-shader for address formation and the
|
|
1124
|
+
robustness-dependent OOB mechanism:* its texture `ld` takes coordinates from
|
|
1125
|
+
the resource-dimensional coordinate mask and LOD separately from source lane
|
|
1126
|
+
`w`; texture and raw/structured buffer loads then use direct backend accesses
|
|
1127
|
+
with no compiler-inserted bounds guard. A zero result therefore depends on
|
|
1128
|
+
the applicable target and runtime robustness guarantees (buffer robustness
|
|
1129
|
+
must not be generalized to every image access), and vkd3d-shader alone does
|
|
1130
|
+
not prove exact zero on every target. The explicit WGSL clamps and logical
|
|
1131
|
+
in-range selects above independently implement D3D's zero result without
|
|
1132
|
+
executing an invalid logical access.
|
|
1133
|
+
- **`f16tof32`/`f32tof16`** — per-lane `unpack2x16float`/`pack2x16float`.
|
|
1134
|
+
`f16tof32` is exact for finite normal inputs, but WGSL may flush binary16
|
|
1135
|
+
subnormals and ignore zero sign. `f32tof16` keeps only the low 16 result bits
|
|
1136
|
+
and is exact for finite non-zero inputs representable as normal binary16.
|
|
1137
|
+
Subnormal and zero-sign behavior shares the preceding caveat. For other
|
|
1138
|
+
finite normal-range values D3D requires round-toward-zero while WGSL does not
|
|
1139
|
+
fix a rounding direction; on finite overflow D3D yields signed max-f16 while
|
|
1140
|
+
WGSL permits an indeterminate result. Those inputs are an adapted boundary.
|
|
1141
|
+
- **`udiv` (both stages)** — quotient and remainder lower to WGSL `u32`
|
|
1142
|
+
division and remainder. Immediate divisors whose lanes are all non-zero keep
|
|
1143
|
+
the direct byte-stable `/` or `%` form. Dynamic or possibly-zero divisors use
|
|
1144
|
+
`select(0xffffffffu, a / max(b, 1u), b != 0u)` (and the corresponding `%`
|
|
1145
|
+
form); clamping the eagerly evaluated operation is necessary because WGSL
|
|
1146
|
+
evaluates both `select` alternatives. Both destinations may be written by
|
|
1147
|
+
one instruction when their masks match; mismatched live masks fail closed.
|
|
1148
|
+
A `null` destination does not contribute active source lanes. That shared
|
|
1149
|
+
multi-destination rule also corrects partial-mask `sincos` source lanes:
|
|
1150
|
+
the full-corpus rebuild intentionally changes only the affected WGSL lines
|
|
1151
|
+
in `beaconfx`, `raymarcher`, and `scannerbackground`; the other 494
|
|
1152
|
+
previously qualified packages remain byte-identical.
|
|
1153
|
+
*Confirmed against vkd3d-shader:* its
|
|
1154
|
+
`vsir_program_lower_udiv` comments that "division by zero is well-defined for
|
|
1155
|
+
… UDIV, and returns UINT_MAX", and it emits a `MOVC` selecting `0xffffffff`
|
|
1156
|
+
for both quotient and remainder when the divisor is zero — the same semantic
|
|
1157
|
+
reproduced by the eager-safe WGSL guard.
|
|
1158
|
+
- **Loop merges** — scalar header phis with exactly one entry and one
|
|
1159
|
+
backedge incoming. The entry and backedge use their actual reaching
|
|
1160
|
+
references, including an inherited preheader value. Multi-exit loops resolve
|
|
1161
|
+
and validate one assignment for every live scalar exit phi at every reachable
|
|
1162
|
+
`break` edge.
|
|
1163
|
+
- **Loop-exit (break-join) and header-backedge merges — cross-plan reaching
|
|
1164
|
+
values.** A loop exited only through `break` edges yields phis at the after-
|
|
1165
|
+
`endloop` join; a header phi likewise takes a value back along the latch edge.
|
|
1166
|
+
In both cases the per-edge value is resolved by `reachingRef` — a walk up the
|
|
1167
|
+
dominator chain from the edge's predecessor to the nearest block whose
|
|
1168
|
+
`outputValues` actually define the register. This is necessary because a break
|
|
1169
|
+
predecessor (or latch block) commonly only *inherits* the register: it has no
|
|
1170
|
+
matching entry in its own `outputValues`, while the canonical phi incoming
|
|
1171
|
+
retains the predecessor `blockId` but may reference an upstream definition.
|
|
1172
|
+
The resolved value is accepted when it is (a) an instruction result / program
|
|
1173
|
+
input that dominates the edge; (b) this loop's own header phi (a `var` before
|
|
1174
|
+
the loop / a no-op
|
|
1175
|
+
self-latch); or (c) any other **live** merge phi — an enclosing selection/
|
|
1176
|
+
switch/loop plan declares it as a `var` and `hoistEscapingValues` lifts that
|
|
1177
|
+
declaration to function scope, so the cross-plan read resolves. A non-live phi
|
|
1178
|
+
is never declared and fails closed.
|
|
1179
|
+
- **Switch merges** — break-terminated clauses; at most ONE pass-through
|
|
1180
|
+
incoming (a clause that keeps the prior value); a shared-join planner exists
|
|
1181
|
+
for `if { switch } endif` joins.
|
|
1182
|
+
- **Selection merges** — scalar phis; two-armed regions identify arm tails by
|
|
1183
|
+
edge kind; guaranteed-output tracking intersects arms.
|
|
1184
|
+
- **Observable undefined merge paths** — validation follows the exact
|
|
1185
|
+
references emitted by ordinary selections, switch clauses, shared
|
|
1186
|
+
`if { switch }` joins, loop header entry/backedge assignments, and loop-exit
|
|
1187
|
+
break assignments. Correlation keys include both SSA value identity and
|
|
1188
|
+
component, so two lanes written by one vector comparison are not conflated.
|
|
1189
|
+
Conditions are preserved through acyclic selection paths but cleared across
|
|
1190
|
+
loop backedges/exits, where they may change between iterations. Switch
|
|
1191
|
+
selector correlations are not modeled. Direct instruction uses fail closed
|
|
1192
|
+
except for one lane-exact rule: an undefined carrier consumed by raw bitwise
|
|
1193
|
+
`and` is safe when the sibling lane is the exact SSA condition proven zero on
|
|
1194
|
+
that path (`0 & unknown` is deterministically zero). The proof is repeated
|
|
1195
|
+
independently for every use and lane, requires the canonical unmodified
|
|
1196
|
+
default-precision `and` shape, and is cleared across loop boundaries; other
|
|
1197
|
+
operations, sibling identities, components, modifiers, index reads, and
|
|
1198
|
+
additional uses remain unsupported.
|
|
1199
|
+
- **`gather4`** — front-end lanes reserved, WGSL emission not yet built.
|
|
1200
|
+
|
|
1201
|
+
Unless a mapping states otherwise, ordinary WGSL floating-point operations
|
|
1202
|
+
inherit WGSL's permitted rounding, denormal, and zero-sign behavior plus its
|
|
1203
|
+
finite-math assumption. D3D's prescribed NaN/infinity tables are therefore not
|
|
1204
|
+
portable on those edge inputs.
|
|
1205
|
+
|
|
1206
|
+
## Adapted — numeric conversion edges
|
|
1207
|
+
|
|
1208
|
+
`ftoi`/`ftou` lower to WGSL `i32(x)`/`u32(x)`. Finite inputs within the target
|
|
1209
|
+
integer range match D3D's truncation toward zero. NaN and positive overflow do
|
|
1210
|
+
not: D3D specifies zero for NaN and the full integer maximum for overflow,
|
|
1211
|
+
whereas WGSL makes the NaN conversion indeterminate and clamps positive
|
|
1212
|
+
overflow to the largest target integer exactly representable by f32
|
|
1213
|
+
(`2147483520` for i32 and `4294967040` for u32). These inputs are an adapted
|
|
1214
|
+
boundary.
|
|
1215
|
+
|
|
1216
|
+
## Adapted — uniformity
|
|
1217
|
+
|
|
1218
|
+
### Derivatives / implicit-LOD samples in non-uniform control flow → `diagnostic(off, derivative_uniformity)`
|
|
1219
|
+
|
|
1220
|
+
WGSL forbids screen-space derivatives — the `dpdx*`/`dpdy*` family and the
|
|
1221
|
+
implicit-LOD samples that derive internally (`textureSample` /
|
|
1222
|
+
`textureSampleBias`) — inside **non-uniform** control flow (a branch whose
|
|
1223
|
+
condition can differ between the pixels of a 2x2 quad), because the derivative
|
|
1224
|
+
compares neighbor pixels that may not all be present. `src/core/wgsl/
|
|
1225
|
+
uniformity.js` tags each SSA value uniform or varying; when the fragment lowerer
|
|
1226
|
+
finds one of these operations under a varying-conditioned branch it records
|
|
1227
|
+
`requiresDerivativeUniformityOptOut` on the program, and `emitWgsl` prepends the
|
|
1228
|
+
module-level filter `diagnostic(off, derivative_uniformity);` (a standard WGSL
|
|
1229
|
+
opt-out that Dawn/Tint and Naga both honor — browser-gate confirmed) rather than
|
|
1230
|
+
rejecting the shader.
|
|
1231
|
+
|
|
1232
|
+
Why the directive and not gradient hoisting: the DXBC came from HLSL that relied
|
|
1233
|
+
on **D3D11's permissive divergent-derivative behavior** (non-participating quad
|
|
1234
|
+
lanes yield undefined derivatives). The directive keeps the operation at its
|
|
1235
|
+
original source-level control-flow point; both APIs leave the divergent result
|
|
1236
|
+
nonportable or undefined, and WGSL does not guarantee a particular hardware
|
|
1237
|
+
evaluation strategy. Converting to `textureSampleGrad` with a gradient computed in
|
|
1238
|
+
uniform control flow (hoisting) would substitute a *different* gradient than the
|
|
1239
|
+
one D3D11 used, i.e. be less faithful. The directive is emitted only when the
|
|
1240
|
+
analysis actually detects a non-uniform derivative/sample, and it is visible in
|
|
1241
|
+
the WGSL (with an explanatory comment) plus flagged on the typed program, so the
|
|
1242
|
+
reliance on the opt-out is never silent.
|
|
1243
|
+
|
|
1244
|
+
Soundness of the trigger: constant-buffer and immediate operands are not SSA
|
|
1245
|
+
values. Varying seeds are interpolated fragment inputs (`input[N]`, including
|
|
1246
|
+
`SV_Position`) and, conservatively, all texture sampling/loading and derivative
|
|
1247
|
+
results. This avoids known false negatives but may add the opt-out for a branch
|
|
1248
|
+
whose producer happens to be dynamically uniform; that only broadens where the
|
|
1249
|
+
diagnostic is disabled.
|
|
1250
|
+
|
|
1251
|
+
Loop-exit uniformity **is** modelled: `loopHasNonUniformExit` flags a loop whose
|
|
1252
|
+
exit is non-uniform — a `breakc`/`continuec` with a varying condition, or an
|
|
1253
|
+
unconditional `break`/`continue` guarded by a varying `if`/`switch` (nested loops
|
|
1254
|
+
skipped, as their breaks belong to the inner loop). Per the WGSL uniformity rules
|
|
1255
|
+
such a break taints both the loop body **and every statement after the loop** (the
|
|
1256
|
+
break edges carry non-uniformity to the merge), so the lowerer folds it into a
|
|
1257
|
+
running per-range flow flag: a requires-uniform op inside or below such a loop
|
|
1258
|
+
picks up the opt-out directive. This is what qualifies `system/shadowdepth`,
|
|
1259
|
+
whose top-level `textureSample` follows a loop with a varying-guarded `break` —
|
|
1260
|
+
top-level in the emitted WGSL, but non-uniform per the spec, and rejected by Dawn
|
|
1261
|
+
without the directive.
|
|
1262
|
+
|
|
1263
|
+
Representative implicit-LOD and derivative cases are browser-gated with the
|
|
1264
|
+
directive enabled, while uniform control-flow cases verify that the directive
|
|
1265
|
+
is not emitted unnecessarily.
|
|
1266
|
+
|
|
1267
|
+
## Selected-effect package provenance
|
|
1268
|
+
|
|
1269
|
+
`BuildEffect` emits selected-effect INFO schema version 2 while the binary
|
|
1270
|
+
CEWGPU container remains version 1. INFO v2 records the WebGPU target,
|
|
1271
|
+
backend-package and translator semantic versions, and a lower-case SHA-256
|
|
1272
|
+
digest computed over the exact compiled-effect input byte view. A conflicting
|
|
1273
|
+
caller digest fails closed. The reader retains legacy selected-effect INFO v1
|
|
1274
|
+
support and rejects unknown INFO schema versions.
|
|
1275
|
+
|
|
1276
|
+
The 0.4.2 strong-provenance checkpoint was metadata/container-only. Its
|
|
1277
|
+
exact-build corpus retained 507 qualified and 30 unsupported packages with no
|
|
1278
|
+
failures; all 537 status/error results were unchanged. Every qualified package
|
|
1279
|
+
changed only in INFO, while all 507 `META`, `ANLS`, and `WGSL` payloads remained
|
|
1280
|
+
byte-identical.
|
|
1281
|
+
|
|
1282
|
+
The 0.4.3 PGRF checkpoint additionally records the complete builder-derived
|
|
1283
|
+
source permutation topology and identity-only unique-body table. Exact build
|
|
1284
|
+
3444265 again retains 507 qualified and 30 unsupported packages with no
|
|
1285
|
+
failures and all 537 status/error results unchanged. Across the 507 qualified
|
|
1286
|
+
packages:
|
|
1287
|
+
|
|
1288
|
+
- all 1,521 `META`, `ANLS`, and `WGSL` chunks are byte-identical to 0.4.2;
|
|
1289
|
+
- all INFO documents equal 0.4.2 after removing only the PGRF pointer and
|
|
1290
|
+
normalizing the producer/translator versions;
|
|
1291
|
+
- an independent source-byte comparison matches all 507 PGRF documents,
|
|
1292
|
+
covering 8,257 permutation variants and 3,331 unique body identities; and
|
|
1293
|
+
- a full 537-source header audit finds no anomalies across 8,722 permutations,
|
|
1294
|
+
3,567 unique body records, and 5,155 exact aliases (maximum 972
|
|
1295
|
+
permutations in one source).
|
|
1296
|
+
|
|
1297
|
+
The final DX11/DX12 quads matrix remains qualified with matching active
|
|
1298
|
+
topology and both stages emitted. A new browser gate is not required for this
|
|
1299
|
+
checkpoint because emitted WGSL, analysis, metadata, layouts, and transforms
|
|
1300
|
+
are unchanged; old readers tolerate the additive chunk.
|
|
1301
|
+
|
|
1302
|
+
The 0.4.4 selected-reflection checkpoint adds complete version-15 reflection
|
|
1303
|
+
for the selected body in `RFLX`, with every exact byte vector externalized into
|
|
1304
|
+
canonical deduplicated `RBLB` records. The upstream
|
|
1305
|
+
`@carbonenginejs/runtime-resource/formats/hlsl/portable` v1 validator is rerun after
|
|
1306
|
+
reconstruction; package validation also reconciles INFO source identity,
|
|
1307
|
+
META/PGRF body identity, and every ANLS pass/stage source record. Earlier
|
|
1308
|
+
effect versions retain the legacy package surface. All-body reflection remains
|
|
1309
|
+
unsupported, so source/backend/runtime completeness remain false.
|
|
1310
|
+
|
|
1311
|
+
The exact-build 3444265 corpus remains 507 qualified, 30 unsupported, and zero
|
|
1312
|
+
failed/unqualified. All 537 statuses and errors match 0.4.3. Across the 507
|
|
1313
|
+
qualified packages:
|
|
1314
|
+
|
|
1315
|
+
- all `META`, `PGRF`, `ANLS`, and `WGSL` payloads are byte-identical;
|
|
1316
|
+
- INFO differs only by the 0.4.4 producer/translator versions and its RFLX/RBLB
|
|
1317
|
+
pointer;
|
|
1318
|
+
- all 507 packages contain deterministic, structurally valid RFLX/RBLB;
|
|
1319
|
+
- reconstructing every selected portable document from RFLX/RBLB deep-equals
|
|
1320
|
+
a fresh source reflection, including 16 non-default/non-first bodies; and
|
|
1321
|
+
- the reflected corpus covers 885 techniques, 937 passes, 1,863 stage
|
|
1322
|
+
programs, 4,366 constants, 2,281 resources, 1,027 samplers, 16 UAVs, 1,589
|
|
1323
|
+
render states, and 74,912 exact constant-default bytes.
|
|
1324
|
+
|
|
1325
|
+
A packed-package Chrome smoke builds and reads a real version-15 effect in the
|
|
1326
|
+
browser, resolves all reflected byte references, structured-clones the JSON
|
|
1327
|
+
view, and fails closed after raw blob corruption with no console errors. The
|
|
1328
|
+
engine prepares a newly generated real `quads.sm_hi` CEWGPU package with 10
|
|
1329
|
+
canonical bindings and zero WGSL warnings. The high-tier
|
|
1330
|
+
`unpacked_quadv5.sm_depth` DX11 self-pair remains exhaustively qualified across
|
|
1331
|
+
480 bodies, 4,480 emitted stage occurrences, and 2,240 ready pass occurrences;
|
|
1332
|
+
the envelope slice changes none of its WGSL.
|
|
1333
|
+
|
|
1334
|
+
The 0.5.0 all-source-reflection checkpoint replaces selected-only RFLX v1
|
|
1335
|
+
output with INFO v3 plus all-unique RFLX v2/RBLB. It records complete portable
|
|
1336
|
+
reflection for every unique version-15 source body while ANLS/WGSL remain
|
|
1337
|
+
selected-backend data. `GetPortableEffectReflection(permutationIndex)` exposes
|
|
1338
|
+
any package permutation as a fresh, format-hlsl-validated document with owned
|
|
1339
|
+
byte payloads. `runtime-resource` owns `Tr2EffectRes` selection, canonical
|
|
1340
|
+
`Tr2Shader` hydration, and the per-index cache; renderer-owned handles remain
|
|
1341
|
+
an engine concern.
|
|
1342
|
+
|
|
1343
|
+
An exhaustive build-3444265 oracle retains 507 qualified, 30 unsupported, and
|
|
1344
|
+
zero failed/unqualified results. For all 507 emitted packages, `META`, `PGRF`,
|
|
1345
|
+
`ANLS`, and `WGSL` are byte-identical to 0.4.4 after the expected INFO/RFLX
|
|
1346
|
+
schema change. Reconstructing all 8,257 permutations deep-equals fresh source
|
|
1347
|
+
reflection across 3,331 unique bodies, 23,949 source programs, 11,549
|
|
1348
|
+
techniques, 11,963 passes, 68,367 constants, 38,335 resources, 10,858
|
|
1349
|
+
samplers, 16 UAVs, and 1,241,456 exact constant-default bytes. The shared
|
|
1350
|
+
RBLB stores 5,901 deduplicated payloads / 20,625,492 bytes. Candidate packages
|
|
1351
|
+
total 132,688,092 bytes versus 39,643,003 bytes at 0.4.4; the median package
|
|
1352
|
+
ratio is 1.008 and p95 is 8.51. A second fresh build using format-hlsl 0.1.8
|
|
1353
|
+
produces all 507 packages byte-for-byte identically.
|
|
1354
|
+
|
|
1355
|
+
The format-hlsl 0.1.8 prerequisite preserves Carbon's authored zero-count
|
|
1356
|
+
bindless descriptor ranges and requires every resource/UAV map entry to match
|
|
1357
|
+
exactly one signature record. The real DX12 High unpacked-Quad source validates
|
|
1358
|
+
all 288 unique bodies, including its zero/unbounded SRV, UAV, and sampler
|
|
1359
|
+
ranges. The exact DX11/DX12
|
|
1360
|
+
`managed/space/spaceobject/v5/quad/unpacked_quadv5.sm_depth` matrix remains
|
|
1361
|
+
qualified across 480 bodies per backend; DX11 emits all 4,480 stage
|
|
1362
|
+
occurrences and 2,240 passes, while DX12's expected unbounded-range backend
|
|
1363
|
+
boundary remains comparison-only for 480 pass occurrences.
|
|
1364
|
+
|
|
1365
|
+
Explicit `SPACE_OBJECT_PPT_ENABLED=SOPPT_ENABLED` body-4 packages were then
|
|
1366
|
+
prepared through the unchanged engine-webgpu reader on a real WebGPU adapter.
|
|
1367
|
+
The DX11 package contains 144 reflected bodies and the DX12 package 288; each
|
|
1368
|
+
selected Main pass exposes two modules and 25 canonical bindings. Both browser
|
|
1369
|
+
gates compile with zero WGSL warnings.
|
|
1370
|
+
|
|
1371
|
+
## All-body backend packaging
|
|
1372
|
+
|
|
1373
|
+
`mode: "all"` translates every unique source body and stores the result in a
|
|
1374
|
+
`WGSB` `CJS_WGSL_BODY_SET` chunk. Selected mode remains the default and is
|
|
1375
|
+
unchanged.
|
|
1376
|
+
|
|
1377
|
+
The translation unit is deliberately one pass of one body rather than one
|
|
1378
|
+
stage. A pass owns its binding plan and resource-transform plan, so identical
|
|
1379
|
+
stage bytecode can legitimately translate differently when its pass-mates
|
|
1380
|
+
differ; sharing at stage granularity would be unsound. Bodies whose pass
|
|
1381
|
+
carries byte-identical stage bytecode, semantic bindings, and render states
|
|
1382
|
+
therefore share exactly one stored unit. On real uber-shader ship families this
|
|
1383
|
+
is the difference between storing every body's programs and storing a small
|
|
1384
|
+
fraction of them, because most permutation axes change only one stage of one
|
|
1385
|
+
pass.
|
|
1386
|
+
|
|
1387
|
+
A body the compiler cannot lower is retained as an explicitly unsupported
|
|
1388
|
+
record carrying its reason, and coverage degrades to `partial`. Its complete
|
|
1389
|
+
source reflection is untouched, so a partial backend never silently reduces
|
|
1390
|
+
source truth. This is the fail-closed alternative to dropping a body or
|
|
1391
|
+
shrinking the reported shader count.
|
|
1392
|
+
|
|
1393
|
+
Translating every body is deliberately **not** treated as backend completeness.
|
|
1394
|
+
`backendComplete` and `runtimeComplete` remain false, matching the sibling
|
|
1395
|
+
WebGL package, because neither the engine's realization of these layouts and
|
|
1396
|
+
resource transforms nor an exact draw of them has been gated. `INFO` carries
|
|
1397
|
+
the honest scope in `backendBodyCoverage` instead.
|
|
1398
|
+
|
|
1399
|
+
Evidence for the introducing change: the every-permutation reader join resolves
|
|
1400
|
+
every permutation of a real Quad family package to translated programs and
|
|
1401
|
+
reaches every unique body; the selected body's shared units are byte-identical
|
|
1402
|
+
to that package's own `WGSL` chunk; a full corpus rebuild leaves every
|
|
1403
|
+
selected-mode package byte-identical with unchanged statuses; every effect in
|
|
1404
|
+
the corpus builds in all-body mode with no build failures, the small number of
|
|
1405
|
+
partial results losing bodies only to already-documented compiler boundaries;
|
|
1406
|
+
and 202 translated modules from bodies that selected-mode packaging never
|
|
1407
|
+
emitted were compiled on a real WebGPU adapter with zero warnings, including a
|
|
1408
|
+
High `.sm_depth` explicit PPT-on Detail-family package with version-3
|
|
1409
|
+
resource-transform units and the exact bounded particle-clear compute profile.
|
|
1410
|
+
That is compiler and module evidence for the newly packaged bodies. It is not a
|
|
1411
|
+
prepared-pipeline or rendered claim for them.
|
|
1412
|
+
|
|
1413
|
+
Two defects in the first implementation were found only by building the whole
|
|
1414
|
+
corpus in all-body mode, not by the package suite: stage selection outside the
|
|
1415
|
+
per-body guard turned one geometry-stage body into a whole-package failure, and
|
|
1416
|
+
the all-body path initially omitted the particle-clear effect-profile
|
|
1417
|
+
preflight. The synthetic fixtures cannot express either condition, so an
|
|
1418
|
+
all-body corpus sweep is part of this feature's verification, not an optional
|
|
1419
|
+
extra.
|
|
1420
|
+
|
|
1421
|
+
## Verification contract
|
|
1422
|
+
|
|
1423
|
+
Every shader-emission, layout, or transform compatibility change requires the
|
|
1424
|
+
package suite and a representative `engine-webgpu` browser gate on a real
|
|
1425
|
+
WebGPU device with zero WGSL warnings. Format-level qualification cannot detect
|
|
1426
|
+
every WGSL scoping or validator failure, so browser validation remains part of
|
|
1427
|
+
the compiler contract. A reviewed envelope-only schema change may omit a new
|
|
1428
|
+
browser run when corpus comparison proves unchanged status/errors and
|
|
1429
|
+
byte-identical runtime-consumed shader chunks, and downstream reader tolerance
|
|
1430
|
+
is separately confirmed.
|
|
1431
|
+
|
|
1432
|
+
The browser gate proves the emitted WGSL is *valid and runs*; it does not by
|
|
1433
|
+
itself prove the translation is *semantically equivalent to D3D*. Semantic
|
|
1434
|
+
decisions (out-of-bounds behavior, source-modifier typing, minimum-precision,
|
|
1435
|
+
division-by-zero, atomics) are therefore taken from the Direct3D 11 functional
|
|
1436
|
+
specification and independently cross-referenced against
|
|
1437
|
+
[vkd3d-shader](https://gitlab.winehq.org/wine/vkd3d), Wine's DXBC→SPIR-V/GLSL
|
|
1438
|
+
translator, which is the closest independent implementation of the same
|
|
1439
|
+
input. vkd3d is used strictly as a **behavioral reference for verification** —
|
|
1440
|
+
no code is derived from it; this compiler is implemented independently from the
|
|
1441
|
+
D3D specification. (The reference checkout is kept quarantined outside every
|
|
1442
|
+
package, never bundled or published.)
|