@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.
Files changed (72) hide show
  1. package/dist/formats/bnk/CjsBnkFormat.js +12 -2
  2. package/dist/formats/bnk/CjsBnkFormat.js.map +1 -1
  3. package/dist/formats/bnk/core/graph.js +3 -6
  4. package/dist/formats/bnk/core/graph.js.map +1 -1
  5. package/dist/formats/bnk/core/helpers.js +53 -11
  6. package/dist/formats/bnk/core/helpers.js.map +1 -1
  7. package/dist/formats/bnk/core/sfxNodes.js +532 -0
  8. package/dist/formats/bnk/core/sfxNodes.js.map +1 -0
  9. package/dist/formats/bnk/core/soundbanksInfo.js +59 -51
  10. package/dist/formats/bnk/core/soundbanksInfo.js.map +1 -1
  11. package/dist/formats/gr2/CjsGr2Format.js +256 -13
  12. package/dist/formats/gr2/CjsGr2Format.js.map +1 -1
  13. package/dist/formats/gr2/core/helpers.js +15 -15
  14. package/dist/formats/gr2/core/helpers.js.map +1 -1
  15. package/dist/formats/gr2/core/json.js +1 -1
  16. package/dist/formats/gr2/core/json.js.map +1 -1
  17. package/dist/formats/gr2/core/targets.js +1 -1
  18. package/dist/formats/gr2/core/targets.js.map +1 -1
  19. package/dist/formats/gr2/index.js +0 -1
  20. package/dist/formats/gr2/index.js.map +1 -1
  21. package/dist/formats/hlsl/core/analysis.js +1 -1
  22. package/dist/formats/hlsl/index.js +1 -0
  23. package/dist/formats/hlsl/index.js.map +1 -1
  24. package/dist/formats/index.js +3 -6
  25. package/dist/formats/index.js.map +1 -1
  26. package/dist/formats/webgl/core/cewg/binary.js +3 -3
  27. package/dist/formats/webgl/core/cewg/binary.js.map +1 -1
  28. package/dist/formats/webgl/core/effectPackage.js +4 -1
  29. package/dist/formats/webgl/core/effectPackage.js.map +1 -1
  30. package/dist/formats/webgl/core/errors.js +3 -3
  31. package/dist/formats/webgl/core/errors.js.map +1 -1
  32. package/docs/formats/dxbc/README.md +68 -0
  33. package/docs/formats/dxbc/architecture.md +80 -0
  34. package/docs/formats/dxbc/reference/api.md +77 -0
  35. package/docs/formats/dxbc/reference/classes/README.md +76 -0
  36. package/docs/formats/dxbc/reference/decoded-output.md +122 -0
  37. package/docs/formats/gr2.md +3 -4
  38. package/docs/formats/hlsl/README.md +54 -0
  39. package/docs/formats/hlsl/architecture.md +67 -0
  40. package/docs/formats/hlsl/guides/hydrating-json-output.md +62 -0
  41. package/docs/formats/hlsl/guides/reading-effects.md +64 -0
  42. package/docs/formats/hlsl/reference/advanced-analysis.md +66 -0
  43. package/docs/formats/hlsl/reference/api.md +97 -0
  44. package/docs/formats/hlsl/reference/classes/README.md +16 -0
  45. package/docs/formats/hlsl/reference/classes/carbon-compatibility.md +66 -0
  46. package/docs/formats/hlsl/reference/classes/public-api.md +26 -0
  47. package/docs/formats/hlsl/reference/classes/tr2-effect-model.md +150 -0
  48. package/docs/formats/hlsl/reference/json-graph.md +100 -0
  49. package/docs/formats/hlsl/reference/portable-reflection.md +141 -0
  50. package/docs/formats/provenance.md +9 -4
  51. package/docs/formats/webgl/README.md +57 -0
  52. package/docs/formats/webgl/architecture.md +70 -0
  53. package/docs/formats/webgl/carbon-constant-layouts.md +326 -0
  54. package/docs/formats/webgl/decl-io.md +1234 -0
  55. package/docs/formats/webgl/effect-reflection.md +127 -0
  56. package/docs/formats/webgl/memory-structured.md +871 -0
  57. package/docs/formats/webgl/reference/classes/README.md +96 -0
  58. package/docs/formats/webgl/texture-sample.md +964 -0
  59. package/docs/formats/webgpu/README.md +81 -0
  60. package/docs/formats/webgpu/architecture.md +93 -0
  61. package/docs/formats/webgpu/formats/cewgpu.md +479 -0
  62. package/docs/formats/webgpu/guides/effect-packaging.md +199 -0
  63. package/docs/formats/webgpu/reference/api.md +197 -0
  64. package/docs/formats/webgpu/reference/classes/README.md +66 -0
  65. package/docs/formats/webgpu/reference/wgsl-compatibility.md +1442 -0
  66. package/docs/formats/wwise.md +16 -1
  67. package/docs/reference/classes/formats.md +10 -10
  68. package/format-notices/bnk/NOTICE +5 -4
  69. package/format-notices/webgl/NOTICE +1 -1
  70. package/package.json +1 -1
  71. package/dist/formats/gr2/core/CjsFormatGr2.js +0 -273
  72. package/dist/formats/gr2/core/CjsFormatGr2.js.map +0 -1
@@ -0,0 +1,871 @@
1
+ # DXBC → GLSL ES 3.00 Lowering Spec: Memory-Structured Family
2
+
3
+ Status: Evolving
4
+ Scope: `@carbonenginejs/runtime-resource/formats/webgl` structured-memory lowering
5
+ Audience: Shader translator maintainers and reviewers
6
+ Summary: Defines bounded WebGL2 adaptations for DXBC structured-memory operations.
7
+
8
+ Family key: `memory-structured`
9
+ Target: GLSL ES 3.00 (WebGL2), vertex + pixel stages only (no compute, no SSBOs).
10
+ Register model: every DXBC register is stored by the emitter as a `float` `vec4`; all
11
+ integer/unsigned reads and writes go through `floatBitsToInt` / `floatBitsToUint` /
12
+ `intBitsToFloat` / `uintBitsToFloat` at the use site, mirroring HLSLcc's own
13
+ `GetBitcastOp` (`vendor/HLSLcc/src/toGLSLOperand.cpp:327-353`) and
14
+ `AddOpAssignToDestWithMask` (`vendor/HLSLcc/src/toGLSLInstruction.cpp:28-153`)
15
+ machinery, which HLSLcc itself falls back to whenever static data-type analysis is
16
+ unavailable (exactly the reflection-stripped situation this fork runs in per
17
+ `vendor/HLSLcc/CARBONENGINEJS-FORK.md:34-52`).
18
+
19
+ Corpus counts (450k-instruction sweep, 1611 EVE Online DX11 effects):
20
+
21
+ | Opcode | Count | Stage reality |
22
+ |---|---:|---|
23
+ | `ld_structured` | 4014 | vs (BoneTransforms skinning) + ps (LightBuffer/LightIndexBuffer-style tbuffer reads) |
24
+ | `store_structured` | 930 | compute-only in D3D11 (requires a UAV write target); **not observed as reachable in vs/ps stages actually shipped to WebGL2** — see per-opcode note |
25
+ | `store_uav_typed` | 642 | compute-only in practice for this corpus (UAV write) |
26
+ | `sync` | 183 | compute-only (`sync` only has meaning with `dcl_thread_group`) |
27
+ | `ld_raw` | 54 | no confirmed vs/ps corpus example found (see per-opcode note); same SSBO problem as `ld_structured` if it does appear in vs/ps |
28
+ | `store_raw` | 54 | compute-only in practice (RWByteAddressBuffer write) |
29
+ | `atomic_iadd` | 27 | **confirmed present in a pixel shader** (`lensflareoccludert.sm_depth`, `stageName: "pixel"` in `dx11-instruction-coverage.json`'s `initialCandidateOpcodeExamples`) — not compute-only, see per-opcode note |
30
+ | `imm_atomic_iadd` | 24 | compute-only (UAV atomic with previous-value return) |
31
+ | `ld_uav_typed` | 18 | compute-only in this corpus — sampled instance is `measureexposure.sm_depth` (tone-mapping luminance compute pass), **not** `lensflareoccludert` (that file's confirmed opcode is `atomic_iadd`/`dcl_unordered_access_view_typed`, per `TRANSPILING-GAPS.md:100-114`); see per-opcode note |
32
+ | `imm_atomic_exch` | 9 | compute-only |
33
+ | `atomic_umax` | 6 | compute-only |
34
+ | `atomic_umin` | 3 | compute-only |
35
+ | `bufinfo` | 0 | not observed in this corpus; specified for completeness only |
36
+
37
+ **Central WebGL2 constraint** (governs every opcode below): GLSL ES 3.00 has **no
38
+ shader storage buffers** (`buffer` blocks require `#version 310 es` or GL 4.3+), **no
39
+ image load/store types** (`image2D`/`imageLoad`/`imageStore` require ES 3.10+), and
40
+ **no atomic-memory built-ins outside compute shaders** (`atomicAdd`/`imageAtomicAdd`
41
+ etc. and `barrier()`/`memoryBarrier()` are ES 3.10 compute-shader built-ins, not part
42
+ of the ES 3.00 vertex/fragment built-in set). Every HLSLcc GLSL template shown below
43
+ is therefore **reference material for what desktop/Vulkan/Metal HLSLcc emits**, not
44
+ directly compilable WebGL2 output. The one opcode with a proven, shipping WebGL2
45
+ lowering is `ld_structured` restricted to the `BoneTransforms` skinning case, via the
46
+ package-time `cb3` rewrite described in its section below.
47
+
48
+ ---
49
+
50
+ ## Shared machinery referenced by this family
51
+
52
+ - `HaveUnsignedTypes(eLang)` and `HaveBitEncodingOps(eLang)`
53
+ (`vendor/HLSLcc/src/internal_includes/languages.h:156-180`) both return `1` for
54
+ every target except `LANG_ES_100`/`LANG_120`. `LANG_ES_300` (this project's
55
+ target) has unsigned integer types and bit-encoding intrinsics
56
+ (`floatBitsToInt`/`floatBitsToUint`/`intBitsToFloat`/`uintBitsToFloat`) available,
57
+ so all the `TO_FLAG_UNSIGNED_INTEGER` / bitcast branches below are live for our
58
+ target.
59
+ - `AddAssignToDest` / `AddAssignPrologue`
60
+ (`vendor/HLSLcc/src/toGLSLInstruction.cpp:155-171`) write the destination operand,
61
+ the write mask, `= `, and the correct number of constructor/bitcast open-parens;
62
+ `AddAssignPrologue` closes them and appends `;\n`. This is the generic
63
+ "assign-with-implicit-bitcast" pattern the emitter must reproduce for `ld_structured`,
64
+ `ld_raw`, `ld_uav_typed`, and `bufinfo`.
65
+ - `_sat` (saturate) is applied **generically, after the main switch**, only to
66
+ operand 0 (`vendor/HLSLcc/src/toGLSLInstruction.cpp:4821-4844`): it re-clamps
67
+ `dest = clamp(dest, 0.0, 1.0)` (with an Adreno `min(max(dest,0.0),1.0)` workaround
68
+ path gated behind `#ifdef UNITY_ADRENO_ES3`, since `eTargetLanguage == LANG_ES_300`
69
+ triggers the workaround branch). This only matters for opcodes that write a
70
+ register destination — i.e. `ld_structured`, `ld_raw`, `ld_uav_typed`, `bufinfo`,
71
+ `imm_atomic_*` (previous-value destination). Store/`sync`/non-`imm_` atomics have
72
+ no float destination and DXBC does not encode `_sat` for them in practice.
73
+ - Structured/raw buffer declaration (`DeclareBufferVariable`,
74
+ `vendor/HLSLcc/src/toGLSLDeclaration.cpp:1027-1092`) is the HLSLcc reference
75
+ declaration these instructions index into:
76
+ ```glsl
77
+ struct t0_type { uint[<stride/4>] value; };
78
+ layout(std430, binding = N) readonly buffer t0 { t0_type t0_buf[]; };
79
+ ```
80
+ (raw buffers use `uint t0_buf[]` / `int t0_buf[]` directly, no wrapper struct,
81
+ selected by `HaveUnsignedTypes`). This declaration is unusable in GLSL ES 3.00 —
82
+ documented here only because the per-instruction `t0_buf[...]` indexing expression
83
+ it feeds is the exact text pattern the package-time `cb3` rewrite (see
84
+ `ld_structured`) pattern-matches against.
85
+
86
+ ---
87
+
88
+ ## `ld_structured` (4014 instances) — highest priority in this family
89
+
90
+ ### Semantics
91
+ Loads a structured-buffer element: given a structure index (operand 1) and a
92
+ byte offset within the structure (operand 2), reads one or more 32-bit components
93
+ from resource operand 3 (a `t#`/`u#` structured buffer) into the destination
94
+ register, per D3D11 `ld_structured` semantics (index-then-byte-offset addressing
95
+ into an array of fixed-stride structures).
96
+
97
+ ### GLSL lowering — A. HLSLcc reference (desktop/Vulkan/Metal SSBO path)
98
+ `TranslateShaderStorageLoad`, case `OPCODE_LD_STRUCTURED`
99
+ (`vendor/HLSLcc/src/toGLSLInstruction.cpp:1585-1683`, dispatched at
100
+ `toGLSLInstruction.cpp:4256-4264`). Operands: `psDest=asOperands[0]`,
101
+ `psSrcAddr=asOperands[1]` (struct index), `psSrcByteOff=asOperands[2]` (byte
102
+ offset), `psSrc=asOperands[3]` (resource).
103
+
104
+ For each destination component `c` present in the destination write mask:
105
+ ```glsl
106
+ <bitcast_open><resourceName>_buf[<structIndexExpr>].value[(<byteOffExpr> >> 2u) + <swz>u]<bitcast_close>
107
+ ```
108
+ where `<swz>` is `psSrc->aui32Swizzle[c]` if the resource operand carries an
109
+ explicit swizzle (`OPERAND_4_COMPONENT_SWIZZLE_MODE`), else `c` itself
110
+ (`toGLSLInstruction.cpp:1673`) — i.e. the *resource* operand's own swizzle can
111
+ remap which dword of the structure element component `c` reads from. All such
112
+ per-component expressions are joined by `AddAssignToDest`/constructor into:
113
+ ```glsl
114
+ dest.mask = <ctor>(comp0, comp1, ...);
115
+ ```
116
+ (`toGLSLInstruction.cpp:1623-1682`).
117
+
118
+ `<bitcast_open>`/`<bitcast_close>` depend on `destDataType = psDest->GetDataType()`
119
+ (`toGLSLInstruction.cpp:1642-1654`):
120
+ - `SVT_FLOAT` → `uintBitsToFloat(...)` (since `HaveBitEncodingOps` is true for ES
121
+ 3.00); the code falls back to `float(...)` only for pre-bit-encoding targets.
122
+ - `SVT_INT`/`SVT_INT16`/`SVT_INT12` → `int(...)`.
123
+ - `SVT_UINT` → no wrapper at all (`addedBitcast` stays `0`) because the backing
124
+ `t0_type.value` array element type is always `uint` — "always uint array atm"
125
+ (`toGLSLInstruction.cpp:1641`).
126
+
127
+ The struct-index operand is translated with **both** `TO_FLAG_UNSIGNED_INTEGER |
128
+ TO_FLAG_INTEGER` set simultaneously (`toGLSLInstruction.cpp:1664`) — reproduce
129
+ this by picking whichever the index operand's own declared type implies (uint by
130
+ default on ES 3.00). The byte-offset operand's flag (`srcOffFlag`) is
131
+ `TO_FLAG_UNSIGNED_INTEGER` unless the target lacks unsigned types or the operand's
132
+ own `SVT_INT`/`SVT_INT16`/`SVT_INT12` type forces `TO_FLAG_INTEGER`
133
+ (`toGLSLInstruction.cpp:1618-1621`); when unsigned, both the `>> 2` and the `+
134
+ <swz>` component addend get a trailing `u` suffix (`toGLSLInstruction.cpp:1669-1675`).
135
+
136
+ ### GLSL lowering — B. WebGL2 `cb3` joint-matrix rewrite contract (the shipping path)
137
+ This is the **only** `ld_structured` lowering this emitter must actually produce
138
+ runnable WebGL2 GLSL for, restricted to the `BoneTransforms` skinning case
139
+ (vertex stage). It is a two-stage pipeline:
140
+
141
+ 1. Emit HLSLcc's reference SSBO GLSL exactly as in section A (this project's
142
+ HLSLcc fork already tolerates missing `RDEF` bindings for
143
+ `dcl_resource_structured` by falling back to the DXBC declaration's encoded
144
+ stride — see `CARBONENGINEJS-FORK.md:41-44` and
145
+ `TRANSPILING-GAPS.md:51-54`), producing text of the exact shape:
146
+ ```glsl
147
+ struct t0_type { uint[1] value; };
148
+ layout(std430, binding = 0) readonly buffer t0 { t0_type t0_buf[]; };
149
+ ...
150
+ dest = vec4(uintBitsToFloat(t0_buf[idx].value[(0 >> 2) + 0]),
151
+ uintBitsToFloat(t0_buf[idx].value[(0 >> 2) + 1]),
152
+ uintBitsToFloat(t0_buf[idx].value[(0 >> 2) + 2]),
153
+ uintBitsToFloat(t0_buf[idx].value[(0 >> 2) + 3]));
154
+ ```
155
+ Verified against an actual generated fixture
156
+ (`../shaderdiscovery/artifacts/ab-shader-set/work/skinned_fxdirectionalv5/skinned_fxdirectionalv5.sm_hi.dxbc_29291f5662ed6781.vertex.es300.glsl:60-64`):
157
+ all four components of one row share the **same** byte offset (row 0 uses
158
+ `0`, row 1 uses `16`, row 2 uses `32` — i.e. the row's base byte offset, not
159
+ a per-component offset of `0/4/8/12`), differing only in the trailing
160
+ `+ 0/1/2/3` dword index, and **without** a `u` suffix on either the shift or
161
+ the addend. The missing `u` is not a formatting nit: in this corpus the
162
+ byte-offset operand is declared `SVT_INT` (not `SVT_UINT`), so
163
+ `srcOffFlag` resolves to `TO_FLAG_INTEGER`
164
+ (`toGLSLInstruction.cpp:1618-1621`), which suppresses the `u` suffix
165
+ entirely (see `printImmediate32`, `toGLSLOperand.cpp:371-387` vs. `388-395`).
166
+ This also matters functionally: `lowerStructuredBoneLoad`'s regex
167
+ (`packageTr2WebglEffect.js:875`) matches literal `(\d+)\s*>>\s*2\s*\)` with
168
+ no `u` tolerance, and its 4-component agreement check requires all four
169
+ loads to share one `byteOffset` — a per-component-varying offset of
170
+ `0/4/8/12` would both fail to match the regex *and* fail the "same
171
+ byteOffset" agreement check, silently no-opping the rewrite. The previous
172
+ worked example here was internally inconsistent with the very rewrite
173
+ contract it was illustrating.
174
+ 2. A package-time rewrite pass (`scripts/packageTr2WebglEffect.js`,
175
+ functions `lowerWebgl2SkinningAbi` at lines 826-864 and
176
+ `lowerStructuredBoneLoad` at lines 873-902) runs over that generated GLSL text
177
+ and performs, **in this exact order**:
178
+ 1. Strip the `#ifdef GL_ARB_shader_storage_buffer_object` /
179
+ `GL_ARB_shader_image_load_store` extension guard blocks (regexes at
180
+ `packageTr2WebglEffect.js:832-833`).
181
+ 2. Strip the `struct t0_type { uint[1] value; };` declaration (regex at
182
+ `packageTr2WebglEffect.js:834`, matches only the 1-word/4-byte stride
183
+ shape).
184
+ 3. Strip the `layout(std430, binding = 0) readonly buffer t0 { t0_type
185
+ t0_buf[]; };` declaration (regex at `packageTr2WebglEffect.js:835`).
186
+ 4. Grow the vertex stage's `ConstantBuffer3` (`cb3`) declaration to
187
+ `vec4 data[max(existingSize, 200)]` (`packageTr2WebglEffect.js:836-839`) —
188
+ this is where `JointMat` lives at `cb3.data[26..199]` per the ccpwgl runtime
189
+ ABI (`cb3[26..199]`, 58 joints × 12 floats = 696 floats,
190
+ `AGENT-FINDINGS/decisions/016-cewg-skinning-abi-lowering-for-ccpwgl-2026-06-30.md`
191
+ and `015-joint-matrix-jointmat-findings-ccpwgl-runtime-truth.md`).
192
+ 5. Convert `uvec4`/`ivec4` (or `uvec2/3`, `ivec2/3`) `in_BLENDINDICES<n>` vertex
193
+ inputs to plain `vec<N>` (regex at `packageTr2WebglEffect.js:840-843`) —
194
+ ccpwgl binds GR2 mesh blend indices as float attributes via
195
+ `vertexAttribPointer`, not `vertexAttribIPointer`
196
+ (`AGENT-FINDINGS/decisions/028-cewg-skinned-blend-index-abi-lowering.md`).
197
+ 6. Strip the DX11 global bone-offset add HLSLcc emits when combining
198
+ `in_BLENDINDICES0` with a `cb3.data[26]` offset uniform (two regex forms at
199
+ `packageTr2WebglEffect.js:845-852`, covering both the scalar `int(...) +
200
+ floatBitsToInt(cb3.data[26].x)` shape and the vector `(i)uvec/ivec(...) +
201
+ floatBitsToInt(cb3.data[26].<swz>)` shape) — ccpwgl's `cb3.data[26]` slot is
202
+ reused for `JointMat`, not the native bone-ring-buffer offset, so this add
203
+ must be removed entirely rather than merely rewired.
204
+ 7. Rewrite each 4-component `t0_buf[...]` row-load expression
205
+ (`vec4(t0_buf[idx].value[(byteOff>>2)+0], ...)`, all 4 components required,
206
+ `lowerStructuredBoneLoad`) into:
207
+ ```glsl
208
+ cb3.data[26 + (<idx>) * 3 + <row>]
209
+ ```
210
+ where `<row> = floor(byteOff / 16)` and must be in `{0,1,2}` (a `Float4x3`
211
+ bone matrix is 3 `vec4` rows); if any of the 4 component sub-expressions
212
+ disagree on `idx`/`byteOff`, or `row` falls outside `0..2`, or the swizzle
213
+ is anything but the identity `xyzw`/`.xyzw` is stripped when default,
214
+ **the rewrite silently no-ops and leaves the un-lowered `t0_buf` text in
215
+ place** (`packageTr2WebglEffect.js:886,890,895,898` all `return match`) —
216
+ this is a silent-failure edge case the implementing engineer must guard
217
+ against (add a diagnostic) rather than trust to fail loudly.
218
+ 8. If any rewrite happened, inject a `// CEWG: BoneTransforms lowered to cb3
219
+ JointMat rows.` marker comment after `#version 300 es`
220
+ (`packageTr2WebglEffect.js:859-861`).
221
+
222
+ ### GLSL lowering — C. `ld_structured` in pixel stage / non-skinning resources
223
+ The corpus also uses `ld_structured` on `t#` "packed tbuffer" resources unrelated
224
+ to skinning — e.g. `LightBuffer`/`LightIndexBuffer` reads in pixel shaders such as
225
+ `decalcylindricv5.sm_depth` (`AGENT-FINDINGS/decisions/005-structured-resource-only-opcodes-2026-06-26.md`).
226
+ **No `cb3`-style *functional* ABI rewrite exists for these** — they hit the same
227
+ SSBO-unavailability wall as section A. The emitter's default path lowers them to
228
+ pixel `usampler2D` data textures (see `DxbcGlslEmitter.js` `dcl_resource_structured`
229
+ pixel branch), which compiles but consumes a texture unit each; on real drivers
230
+ the `_depth` quad variants overflow `MAX_TEXTURE_IMAGE_UNITS`(16).
231
+
232
+ **Resolution (2026-07-08) — stub, not rewrite.** Since CEWG does not support this
233
+ tiled lighting, the packager can DROP it instead of lowering it. Run
234
+ `packageTr2WebglEffect.js --stub-light-resources`: it resolves the light resource
235
+ names (`LightBuffer`, `LightIndexBuffer`, `LightProfileArray`) to `t#` registers
236
+ from the Carbon `.sm` reflection (RDEF is stripped, so names live only there —
237
+ and the registers vary per permutation, so this is name-driven, not fixed to
238
+ sb11/sb12/s13) and passes them to `emitGlsl` as `stubResourceRegisters`. The
239
+ emitter then drops their decl+binding and lowers reads to `uintBitsToFloat(0u)`
240
+ (structured) / `vec4(0.0)` (sampled) — zeroing the per-tile light count makes the
241
+ light loop dead. The packager also strips those `resource` bindings from the
242
+ manifest JSON (`stripLightResourcesFromManifest`) so the CEWG runtime does not
243
+ synthesize a texture def (glType 0 → "Invalid shader texture definition") for the
244
+ now-undeclared light buffers. Opt-in, default off; every other package is
245
+ unchanged. A functional light constant-buffer path remains possible but was not
246
+ built.
247
+
248
+ Tested by `test/glsl-emitter.test.js` (synthetic pixel shaders with a structured
249
+ buffer / sampler2DArray: declared by default, dropped + no binding when the
250
+ register is in `stubResourceRegisters`, and only listed registers dropped) and
251
+ `test/stub-light-resources.test.js` (the packager's `resolveStubLightRegisters`
252
+ name→register resolution and `stripLightResourcesFromManifest` manifest filter,
253
+ in `scripts/stubLightResources.js`).
254
+
255
+ ### Type rules
256
+ - Struct index operand: read as int/uint (both flags set in HLSLcc; pick uint by
257
+ default for ES 3.00).
258
+ - Byte-offset operand: uint unless the operand's own declared type is signed int.
259
+ - Result component type follows the *destination* register's inferred type
260
+ (float → `uintBitsToFloat`, int → `int(...)`, uint → passthrough), **not** the
261
+ source resource's declared return type — this is purely dest-driven, matching
262
+ the "everything is `float vec4`, bitcast at use" register model this project
263
+ already commits to.
264
+ - This is a data-movement instruction, not a comparison — it does not produce a
265
+ 0xFFFFFFFF/0 mask.
266
+
267
+ ### Helpers needed
268
+ - `structuredLoadComponent(bufName, structIndex, byteOffset, component, destType)`
269
+ — reference-only (HLSLcc SSBO shape), needed if the emitter ever targets a
270
+ non-WebGL2 backend or documents the pre-rewrite intermediate form.
271
+ - `lowerBoneTransformsToCb3` (package-time text pass; port of
272
+ `lowerWebgl2SkinningAbi` + `lowerStructuredBoneLoad`).
273
+ - `lowerBlendIndicesToFloatAttribute` (package-time text pass, part of the same
274
+ rewrite; port of the `in_BLENDINDICES` regex).
275
+ - `refuseNonSkinningStructuredLoad` (detection helper for section C).
276
+
277
+ ### Edge cases
278
+ - NaN/inf: none introduced by the load itself; `uintBitsToFloat` is a pure
279
+ bit-reinterpret, so any NaN bit pattern already in the buffer round-trips as
280
+ NaN.
281
+ - The resource-operand swizzle indirection (`psSrc->aui32Swizzle[component]`)
282
+ means component `c` of the destination is not guaranteed to read structure
283
+ dword `c` — verify this against real corpus DXBC before assuming identity
284
+ swizzle always holds.
285
+ - The row/index-agreement check in `lowerStructuredBoneLoad` requires **all 4**
286
+ components to share the same `idx`/`byteOffset`; a destination write mask
287
+ narrower than `.xyzw` (e.g. `.xy`) will not match the 4-load regex and will
288
+ silently fail to rewrite — this is plausible for shaders that only need part of
289
+ a bone row and needs explicit test coverage.
290
+ - `bSaturate` on `ld_structured` is legal per the generic post-switch handling
291
+ but is not expected to appear in real bone/light-buffer loads; still must be
292
+ implemented for correctness if the corpus is ever re-scanned with `_sat`
293
+ detection.
294
+
295
+ ### WebGL2 notes
296
+ - SSBOs (`buffer` blocks) do not exist in GLSL ES 3.00 at all; section A's
297
+ output is fundamentally uncompilable in WebGL2 and must never reach the
298
+ final package unless rewritten by section B.
299
+ - `layout(std430, binding=N)` is likewise unavailable in ES 3.00 (`std430` and UBO
300
+ binding indices exist, but SSBO binding does not).
301
+
302
+ ### Confidence
303
+ **High** for the `BoneTransforms`/`cb3` path (validated end-to-end:
304
+ `336/336` and `240/240` WebGL2 program links per
305
+ `TRANSPILING-GAPS.md:60-63` and `016-cewg-skinning-abi-lowering-for-ccpwgl-2026-06-30.md`).
306
+ **Low** for non-skinning `ld_structured` in pixel stages — no validated WebGL2
307
+ lowering exists; treat as blocked pending a decision.
308
+
309
+ ---
310
+
311
+ ## `store_structured` (930 instances)
312
+
313
+ ### Semantics
314
+ Writes one or more 32-bit components of a structured-buffer element addressed by
315
+ a structure index and byte offset, per D3D11 `store_structured` (the inverse of
316
+ `ld_structured`; only valid against a UAV, since D3D11 `structured buffer`
317
+ read-only resources cannot be written).
318
+
319
+ ### GLSL lowering
320
+ `TranslateShaderStorageStore`, case `OPCODE_STORE_STRUCTURED`
321
+ (`vendor/HLSLcc/src/toGLSLInstruction.cpp:1500-1583`, dispatched at
322
+ `toGLSLInstruction.cpp:4348-4357`). Operands: `psDest=asOperands[0]` (the UAV
323
+ resource, carrying the write mask via `OPERAND_4_COMPONENT_MASK_MODE`),
324
+ `psDestAddr=asOperands[1]` (struct index), `psDestByteOff=asOperands[2]` (byte
325
+ offset), `psSrc=asOperands[3]` (value to store).
326
+
327
+ For each component present in `psDest->ui32CompMask`:
328
+ ```glsl
329
+ <name>_buf[<structIndexExpr>].value[(<byteOffExpr> >> 2u) + <comp>u] = <uint-or-int-cast>(src.<swz-or-x>);
330
+ ```
331
+ (`toGLSLInstruction.cpp:1534-1582`). `dstOffFlag` follows the same
332
+ unsigned-unless-signed-typed rule as `ld_structured`. The source cast (`srcFlag`)
333
+ is `TO_FLAG_UNSIGNED_INTEGER` by default, flipped to `TO_FLAG_INTEGER` only when
334
+ `DeclareRWStructuredBufferTemplateTypeAsInteger` reports the target buffer as a
335
+ single-`int`-typed `RWStructuredBuffer<int4>` (the "avoid calling the wrong
336
+ `AtomicMin` overload" special case, `toGLSLDeclaration.cpp:998-1025`) — otherwise
337
+ the destination array element type is always `uint` (`toGLSLInstruction.cpp:1569-1572`).
338
+ Source components are consumed left-to-right via an incrementing `srcComponent`
339
+ counter if the source operand has more than one swizzle element, else always
340
+ `.x` (`toGLSLInstruction.cpp:1575-1578`).
341
+
342
+ ### Type rules
343
+ - Destination index/byte-offset: same int/uint rule as `ld_structured`.
344
+ - Source value: bitcast to uint (default) or int (special RWStructuredBuffer<int>
345
+ case) before storing — never stored as float, consistent with "backing array is
346
+ always uint".
347
+
348
+ ### Helpers needed
349
+ - `structuredStoreComponent(bufName, structIndex, byteOffset, component, value,
350
+ srcType)` — reference-only; no WebGL2 target exists for this opcode (see below).
351
+
352
+ ### Edge cases
353
+ - No destination register write, so `_sat` never applies (DXBC does not attach
354
+ `_sat` to store instructions).
355
+ - Per-component write masking must exactly follow `psDest->ui32CompMask`
356
+ (`OPERAND_4_COMPONENT_MASK_MODE`), not the source operand's own mask.
357
+
358
+ ### WebGL2 notes
359
+ `store_structured` requires a writable UAV, which in turn requires an SSBO
360
+ (`buffer`, not `readonly buffer`) — completely unavailable in GLSL ES 3.00. No
361
+ package-time rewrite analogous to the `cb3` skinning path exists for writes
362
+ (there is no ccpwgl uniform target that plausibly receives a per-invocation
363
+ compute-style scatter write). **This opcode is out of scope for the WebGL2
364
+ emitter.** The 930 corpus instances should be treated as evidence this opcode
365
+ occurs in DX11 stages that are not shipped to the current WebGL2 vs/ps target
366
+ (the sampled corpus instance is `createhistograms.sm_depth`, tagged
367
+ `stageName: "geometry"` in `dx11-instruction-coverage.json` but much more
368
+ plausibly a mislabeled compute shader given the histogram-building workload
369
+ and its co-occurring `sync`/`dcl_thread_group`-shaped instruction in the same
370
+ file — see the `atomic_iadd` section for the one confirmed **non**-compute
371
+ counterexample in this family, `lensflareoccludert`, which is a pixel shader,
372
+ not compute); the emitter should detect and refuse rather than attempt
373
+ emission.
374
+
375
+ ### Confidence
376
+ **Medium** on the HLSLcc reference lowering itself (directly read from source);
377
+ **high** on the WebGL2-scope conclusion (no counter-evidence of a vs/ps
378
+ `store_structured` shipping shader was found in the decision corpus, and the
379
+ project's own draft transpiler already special-cases the sibling UAV write
380
+ opcode `store_uav_typed` as a target blocker — see that section).
381
+
382
+ ---
383
+
384
+ ## `store_uav_typed` (642 instances)
385
+
386
+ ### Semantics
387
+ Writes a full-precision (or format-converted) texel/element to a typed UAV
388
+ (`RWTexture*`/`RWBuffer`) at an integer address, per D3D11 `store_uav_typed`.
389
+
390
+ ### GLSL lowering
391
+ Case `OPCODE_STORE_UAV_TYPED` (`vendor/HLSLcc/src/toGLSLInstruction.cpp:4359-4415`):
392
+ ```glsl
393
+ imageStore(<uavName>, <addr-expr-by-dimension>, <value-cast-by-return-type>);
394
+ ```
395
+ The address component mask and any `TO_AUTO_EXPAND_TO_VEC{2,3,4}` flag are chosen
396
+ from the UAV's reflected `REFLECT_RESOURCE_DIMENSION_*`
397
+ (`toGLSLInstruction.cpp:4382-4407`): 1D/Buffer → `.x` only; 2D/1DArray/2DMS →
398
+ `.xy` with `TO_AUTO_EXPAND_TO_VEC2`; 2DArray/3D/2DMSArray/Cube → `.xyz` with
399
+ `TO_AUTO_EXPAND_TO_VEC3`; CubeArray → `TO_AUTO_EXPAND_TO_VEC4`. The stored value
400
+ is translated with `ResourceReturnTypeToFlag(psRes->ui32ReturnType)` — i.e. cast
401
+ to match the UAV's declared return type (float/int/uint).
402
+
403
+ ### Type rules
404
+ Value operand bitcast is driven entirely by the UAV's reflected return type
405
+ (`RETURN_TYPE_FLOAT`/`SINT`/`UINT`/`UNORM`/`SNORM`), not by any DXBC instruction
406
+ flag — this requires resource-binding reflection, which per
407
+ `CARBONENGINEJS-FORK.md:34-39` is frequently stripped in this project's shipped
408
+ DXBC and falls back to register-stable naming only (no return-type recovery
409
+ implied by that fallback).
410
+
411
+ ### Helpers needed
412
+ None for the WebGL2 emitter proper — see WebGL2 notes. Reference-only helper:
413
+ `imageStoreTyped(uav, addr, value, returnType)`.
414
+
415
+ ### Edge cases
416
+ - `imageStore` swizzle/expansion must match the UAV's declared dimensionality
417
+ exactly, or GLSL will reject a texel with the wrong component count.
418
+ - Missing `RDEF` reflection (this project's normal stripped-DXBC case) leaves the
419
+ return type unrecoverable, which independently blocks this opcode even before
420
+ the SSBO/image-type gap is considered.
421
+
422
+ ### WebGL2 notes
423
+ `image2D`/`imageBuffer` UAV types and `imageStore` are GLSL ES 3.10+ built-ins,
424
+ not part of ES 3.00. **Fully out of scope for the WebGL2 emitter.** This matches
425
+ the project's own draft-transpiler decision, which already lists
426
+ `store_uav_typed` as a hard `TARGET_BLOCKER_OPCODES` entry
427
+ (`../shaderdiscovery/src/core/transpiler/gles/Dx11GlesDraftTranspiler.js:130`) and
428
+ the `TRANSPILING-GAPS.md:100-114` "UAV and atomic path... blocked for current
429
+ WebGL2 target" decision. The emitter must detect `dcl_unordered_access_view_*`
430
+ declarations plus this opcode and refuse the stage (or the whole effect) with an
431
+ explicit diagnostic, never attempt best-effort emission.
432
+
433
+ ### Confidence
434
+ **High** — corroborated independently by HLSLcc source, the draft transpiler's
435
+ explicit blocker list, and the shaderdiscovery decision log.
436
+
437
+ ---
438
+
439
+ ## `sync` (183 instances)
440
+
441
+ ### Semantics
442
+ A compute-shader thread-group synchronization barrier. DXBC encodes which memory
443
+ domains/threads to synchronize via `ui32SyncFlags`: `SYNC_THREAD_GROUP_SHARED_MEMORY`,
444
+ `SYNC_UNORDERED_ACCESS_VIEW_MEMORY_GROUP`/`_GLOBAL`, and `SYNC_THREADS_IN_GROUP`.
445
+ It only has defined meaning inside a compute shader with a `dcl_thread_group` size.
446
+
447
+ ### GLSL lowering
448
+ Case `OPCODE_SYNC` (`vendor/HLSLcc/src/toGLSLInstruction.cpp:3958-3984`):
449
+ ```glsl
450
+ if (flags & SYNC_THREAD_GROUP_SHARED_MEMORY) memoryBarrierShared();
451
+ if (flags & (SYNC_UNORDERED_ACCESS_VIEW_MEMORY_GROUP|_GLOBAL)) memoryBarrier();
452
+ if (flags & SYNC_THREADS_IN_GROUP) barrier();
453
+ ```
454
+ Each condition independently emits its statement (not mutually exclusive; a
455
+ single `sync` instruction can emit all three lines).
456
+
457
+ ### Type rules
458
+ N/A — no operands, no data type.
459
+
460
+ ### Helpers needed
461
+ None — this opcode has no GLSL ES 3.00 equivalent to helper-wrap; see WebGL2
462
+ notes.
463
+
464
+ ### Edge cases
465
+ None beyond the flag decoding itself (a bitmask, not an enum — must check all
466
+ three bits independently, not `switch`/`else if`).
467
+
468
+ ### WebGL2 notes
469
+ `barrier()`, `memoryBarrier()`, and `memoryBarrierShared()` are **compute-shader-
470
+ only** built-ins in GLSL ES (introduced with ES 3.10 compute shaders); they do
471
+ not exist in the ES 3.00 vertex/fragment built-in set at all, and vertex/fragment
472
+ shaders have no thread-group concept regardless of GLSL version. **Fully out of
473
+ scope for the WebGL2 emitter.** Detect `sync` (and its precondition,
474
+ `dcl_thread_group`) and refuse the stage.
475
+
476
+ ### Confidence
477
+ **High** — the DXBC semantics and the compute-only nature of `barrier`/
478
+ `memoryBarrier` in GLSL ES are unambiguous.
479
+
480
+ ---
481
+
482
+ ## `ld_raw` (54 instances)
483
+
484
+ ### Semantics
485
+ Reads one or more 32-bit components from a raw (`ByteAddressBuffer`) resource at
486
+ a byte offset, per D3D11 `ld_raw` — same addressing model as `ld_structured` but
487
+ without a structure index (flat byte-addressed array).
488
+
489
+ ### GLSL lowering
490
+ Same function as `ld_structured`, `TranslateShaderStorageLoad`, case
491
+ `OPCODE_LD_RAW` (`vendor/HLSLcc/src/toGLSLInstruction.cpp:1602-1606`, dispatched
492
+ at `toGLSLInstruction.cpp:4416-4425`). Operands: `psDest=asOperands[0]`,
493
+ `psSrcByteOff=asOperands[1]`, `psSrc=asOperands[2]` — no `psSrcAddr`, so the
494
+ `TranslateShaderStorageLoad` body skips the `[<idx>].value` indirection
495
+ entirely (`toGLSLInstruction.cpp:1661-1666`, gated on `if (psSrcAddr)`), producing:
496
+ ```glsl
497
+ <bitcast_open><name>_buf[(<byteOffExpr> >> 2u) + <comp>u]<bitcast_close>
498
+ ```
499
+ against a flat `uint`/`int` array (`vendor/HLSLcc/src/toGLSLDeclaration.cpp:1078-1088`,
500
+ `isRaw` branch: `uint <name>_buf[];` or `int <name>_buf[];` chosen by
501
+ `HaveUnsignedTypes`), not a `_type` struct array.
502
+
503
+ ### Type rules
504
+ Identical bitcast-by-destination-type rule as `ld_structured` (float →
505
+ `uintBitsToFloat`, int → `int(...)`, uint → passthrough).
506
+
507
+ ### Helpers needed
508
+ - `rawLoadComponent(bufName, byteOffset, component, destType)` — reference-only;
509
+ see WebGL2 notes for scope.
510
+
511
+ ### Edge cases
512
+ Same swizzle-indirection caveat as `ld_structured` (component addressing follows
513
+ the resource operand's own swizzle if present).
514
+
515
+ ### WebGL2 notes
516
+ Same SSBO unavailability as `ld_structured` section A/C. No `cb3`-style rewrite
517
+ is known or defined for raw-buffer reads — the `BoneTransforms` rewrite is
518
+ specific to the structured-buffer `t0_buf[idx].value[...]` shape, not the flat
519
+ `t0_buf[...]` raw shape. **Out of scope for the WebGL2 emitter** unless/until a
520
+ specific raw-buffer resource is proven to need a package-time ABI rewrite
521
+ analogous to skinning.
522
+
523
+ The corpus table's "vs/ps" stage claim was overreach: the only sampled
524
+ instance in `dx11-instruction-coverage.json`'s `allOpcodeExamples` is
525
+ `graphics\effect.dx11\managed\space\specialfx\particles\gpu\emit.sm_depth`
526
+ tagged `stageName: "geometry"`, paired with the sibling `store_raw` opcode in
527
+ the exact same file/technique (also tagged `"geometry"`) — a GPU particle
528
+ emission pass, which is much more plausibly a compute-style workload than a
529
+ genuine vertex/pixel `ByteAddressBuffer` read. Do not assume `ld_raw` is
530
+ `vs/ps`-reachable without a concrete counter-example (unlike `atomic_iadd`,
531
+ which has one — see that section).
532
+
533
+ ### Confidence
534
+ **Medium** — HLSLcc source lowering is directly read and clear, but no shipping
535
+ corpus evidence of `ld_raw` reaching a WebGL2-validated vs/ps program was found
536
+ (only 54 instances total, none flagged in the skinning/JointMat decision
537
+ trail); the sole sampled corpus example is paired with `store_raw` in a
538
+ likely-compute GPU-particle-emission shader, not a vs/ps stage.
539
+
540
+ ---
541
+
542
+ ## `store_raw` (54 instances)
543
+
544
+ ### Semantics
545
+ Writes one or more 32-bit components to a raw UAV (`RWByteAddressBuffer`) at a
546
+ byte offset, per D3D11 `store_raw` — inverse of `ld_raw`.
547
+
548
+ ### GLSL lowering
549
+ Same function as `store_structured`, `TranslateShaderStorageStore`, case
550
+ `OPCODE_STORE_RAW` (`vendor/HLSLcc/src/toGLSLInstruction.cpp:1519-1523`,
551
+ dispatched at `toGLSLInstruction.cpp:4338-4346`). Operands: `psDest=asOperands[0]`,
552
+ `psDestByteOff=asOperands[1]`, `psSrc=asOperands[2]` — no `psDestAddr`, so no
553
+ `[<idx>].value` indirection (`toGLSLInstruction.cpp:1546-1551`, gated on `if
554
+ (psDestAddr)`):
555
+ ```glsl
556
+ <name>_buf[(<byteOffExpr> >> 2u) + <comp>u] = <uint-or-int-cast>(src.<swz-or-x>);
557
+ ```
558
+
559
+ ### Type rules
560
+ Same as `store_structured`: uint by default, int only for the special
561
+ single-int `RWStructuredBuffer<int4>`-shaped buffer detection (which does not
562
+ actually apply to raw buffers in practice, since that check is structured-buffer
563
+ specific, but the code path is shared).
564
+
565
+ ### Helpers needed
566
+ - `rawStoreComponent(bufName, byteOffset, component, value, srcType)` —
567
+ reference-only.
568
+
569
+ ### Edge cases
570
+ No destination register, so `_sat` never applies.
571
+
572
+ ### WebGL2 notes
573
+ Requires a writable UAV/SSBO — unavailable in ES 3.00. **Out of scope for the
574
+ WebGL2 emitter**; treat identically to `store_structured` (detect and refuse).
575
+
576
+ ### Confidence
577
+ **Medium** — same reasoning as `ld_raw`; low corpus volume (54) with no
578
+ known vs/ps shipping path.
579
+
580
+ ---
581
+
582
+ ## `atomic_iadd` (27) / `imm_atomic_iadd` (24) / `imm_atomic_exch` (9) / `atomic_umax` (6) / `atomic_umin` (3)
583
+
584
+ Grouped: all five are handled by the single `TranslateAtomicMemOp` function and
585
+ differ only in GLSL function name and whether a previous-value destination
586
+ exists.
587
+
588
+ ### Semantics
589
+ - `atomic_iadd` / `imm_atomic_iadd`: atomically add a value to a UAV or
590
+ groupshared (TGSM) memory location; the `imm_` form additionally returns the
591
+ pre-add value into a destination register, the non-`imm_` form discards it.
592
+ - `imm_atomic_exch`: atomically replace the memory location's value and return
593
+ the previous value (exchange has no non-`imm_` counterpart in DXBC).
594
+ - `atomic_umax` / `atomic_umin`: atomically store `max`/`min` of the current
595
+ value and the source value (unsigned comparison), discarding the previous
596
+ value (no `imm_` variants observed in this corpus, though DXBC defines
597
+ `imm_atomic_umax`/`umin` too).
598
+
599
+ ### GLSL lowering
600
+ `TranslateAtomicMemOp` (`vendor/HLSLcc/src/toGLSLInstruction.cpp:1685-2087`,
601
+ dispatched at `toGLSLInstruction.cpp:4428-4450`). Per-opcode operand layout and
602
+ GLSL function name selected at the top of the function
603
+ (`toGLSLInstruction.cpp:1700-1935`):
604
+
605
+ | Opcode | `func` | operands (dest addr order) |
606
+ |---|---|---|
607
+ | `imm_atomic_iadd` | `"Add"` | `[0]=previousValue, [1]=dest, [2]=destAddr, [3]=src` |
608
+ | `atomic_iadd` | `"Add"` | `[0]=dest, [1]=destAddr, [2]=src` (no previousValue) |
609
+ | `imm_atomic_exch` | `"Exchange"` | `[0]=previousValue, [1]=dest, [2]=destAddr, [3]=src` |
610
+ | `atomic_umin` | `"Min"` | `[0]=dest, [1]=destAddr, [2]=src` |
611
+ | `atomic_umax` | `"Max"` | `[0]=dest, [1]=destAddr, [2]=src` |
612
+
613
+ Resource-kind detection (`toGLSLInstruction.cpp:1968-2011`): if `dest` is not
614
+ `OPERAND_TYPE_THREAD_GROUP_SHARED_MEMORY`, look up its UAV binding.
615
+ `RTYPE_UAV_RWTYPED` → `isUint = (returnType == RETURN_TYPE_UINT)` and derive
616
+ `texDim` (1/2/3) from the UAV's resource dimension; `RTYPE_UAV_RWSTRUCTURED` →
617
+ `isUint=false, ui32DstDataTypeFlag |= TO_FLAG_INTEGER` only if
618
+ `DeclareRWStructuredBufferTemplateTypeAsInteger` says so; TGSM defaults to
619
+ `isUint=true, texDim=0`. Then:
620
+ ```glsl
621
+ ui32DataTypeFlag = isUint ? (TO_FLAG_UNSIGNED_INTEGER|TO_AUTO_BITCAST_TO_UINT)
622
+ : (TO_FLAG_INTEGER|TO_AUTO_BITCAST_TO_INT);
623
+ ```
624
+ (`toGLSLInstruction.cpp:2013-2016`). If `previousValue` exists, it's assigned via
625
+ `AddAssignToDest(previousValue, isUint?SVT_UINT:SVT_INT, 1, ...)` **before** the
626
+ atomic call text is emitted (`toGLSLInstruction.cpp:2018-2019`). Final emitted
627
+ shape (`toGLSLInstruction.cpp:2021-2086`):
628
+ - Typed-UAV (image) form (`texDim>0`):
629
+ ```glsl
630
+ [prev =] imageAtomic<Func>(<uavName>, <addr>.<xy|xyz|x>, <src-cast>);
631
+ ```
632
+ - Buffer/TGSM form (`texDim==0`):
633
+ ```glsl
634
+ [prev =] atomic<Func>(<name>[_buf][<addr> >> 2u], <src-cast>);
635
+ ```
636
+ where `<name>_buf[<addr>]` gains an extra `.value[<addrY> >> 2u]` indirection
637
+ if the destAddr operand carries **two** swizzle components (X and Y) — "structured
638
+ buf if we have both x & y swizzles" (`toGLSLInstruction.cpp:2055-2062`) — vs. a
639
+ raw buffer's single-component addressing.
640
+ - No trailing `;` is appended when `previousValue` is set (the assignment prologue
641
+ handles closing parens/semicolon via `AddAssignPrologue`); otherwise the line
642
+ is terminated with `;\n` directly (`toGLSLInstruction.cpp:2081-2086`).
643
+
644
+ ### Type rules
645
+ - Both `compare`/`src` (when present) are cast via the same
646
+ `ui32DataTypeFlag` (`TO_AUTO_BITCAST_TO_UINT` or `_INT`), i.e. bitcast from the
647
+ underlying float-vec4 register storage to whichever integer type the target
648
+ memory location actually holds.
649
+ - `previousValue` destination type is `SVT_UINT` or `SVT_INT` matching `isUint`,
650
+ never float directly (though the register itself is stored as float and
651
+ bitcast at the destination-write site per this project's register model).
652
+ - These do **not** produce DXBC comparison masks; `atomic_umin`/`umax` compare
653
+ internally to select which value to store, but the DXBC/GLSL result is the
654
+ stored/previous *value*, not a boolean or 0xFFFFFFFF/0 mask.
655
+
656
+ ### Helpers needed
657
+ None implementable for WebGL2 (see below). Reference-only helpers if ever
658
+ targeting ES 3.10+/desktop: `atomicUavOrTgsmOp(func, dest, addr, src,
659
+ [compare], [returnsPrevious])`.
660
+
661
+ ### Edge cases
662
+ - `atomic_umin`/`atomic_umax` are unsigned-only per DXBC (`imin`/`imax` variants
663
+ exist separately for signed); do not conflate with `atomic_imin`/`imax`.
664
+ - The 1-vs-2-swizzle-component destAddr distinction (structured vs. raw
665
+ addressing within the same atomic path) must be preserved if this is ever
666
+ ported.
667
+
668
+ ### WebGL2 notes
669
+ `atomicAdd`/`atomicMin`/`atomicMax`/`atomicExchange`/`atomicCompSwap` (buffer/
670
+ shared-memory atomics) and `imageAtomicAdd` etc. (image atomics) are GLSL ES
671
+ 3.10+ compute-shader-only built-ins; none exist in ES 3.00 vertex/fragment
672
+ shaders, regardless of which D3D11 stage the source instruction came from.
673
+ **Fully out of scope for the WebGL2 emitter.** Detect any `atomic_*`/
674
+ `imm_atomic_*` opcode and refuse the stage.
675
+
676
+ Do **not** rely on "these only occur in compute shaders" as the reason for
677
+ skipping this check in the pixel-stage emitter: `dx11-instruction-coverage.json`
678
+ (`initialCandidateOpcodeExamples`) records a concrete `atomic_iadd` instance in
679
+ `graphics\effect.dx11\managed\space\specialfx\lensflares\lensflareoccludert.sm_depth`
680
+ tagged `stageIndex: 1` / `stageName: "pixel"` — an actual D3D11 pixel shader
681
+ (D3D11.1 permits UAV access from pixel shaders; a lens-flare occlusion query
682
+ written into a UAV counter from the pixel stage is a plausible, ordinary
683
+ technique, not a mislabeled compute pass). The other four sampled corpus
684
+ instances of this opcode group (`atomic_iadd` again in `createhistograms.sm_depth`,
685
+ `imm_atomic_iadd` in `clear.sm_depth`, `imm_atomic_exch`/`atomic_umax`/
686
+ `atomic_umin` in `computelightlists.sm_depth`) are all tagged `stageName:
687
+ "geometry"` by the same tool, but those files' names (histogram building,
688
+ particle-buffer clear, light-list culling) are classic GPGPU compute-shader
689
+ workloads, and the coverage tool has no `"compute"` stage label at all in its
690
+ vocabulary (only `vertex`/`pixel`/`geometry`/`hull`) — those four are much
691
+ better explained as compute shaders whose slot the tool mislabels than as
692
+ genuine geometry shaders (a real DX11 geometry shader cannot declare
693
+ `dcl_thread_group`/`sync`, which the *sibling* `createhistograms.sm_depth`
694
+ example for the `sync` opcode does, and DXBC does not permit that in a
695
+ geometry stage). The `lensflareoccludert` pixel-stage instance is not
696
+ explained away by that reasoning and must be treated as real: the emitter's
697
+ **ps-stage translation path itself** needs the detect-and-refuse check, not
698
+ just a pre-filter that assumes this opcode class never survives into a vs/ps
699
+ compile.
700
+
701
+ `TRANSPILING-GAPS.md:100-114` ("UAV and atomic path... blocked for current
702
+ WebGL2 target... `atomic_iadd`: 6... Affects the `lensflareoccludert` path" —
703
+ note that count was measured on a narrower earlier corpus slice than this
704
+ family's 450k-instruction sweep, which shows 27) already names
705
+ `lensflareoccludert` as the blocked case; it does not itself claim the
706
+ instruction is compute-only, and the per-instruction corpus scan confirms it
707
+ is not.
708
+
709
+ ### Confidence
710
+ **High** for the DXBC/GLSL semantics (read directly from source) and for the
711
+ WebGL2-out-of-scope conclusion (ES 3.00 has no atomics in any stage, so the
712
+ `lensflareoccludert` pixel-shader counterexample does not change the outcome).
713
+ **Medium** on the blanket "compute-only" framing carried over from the
714
+ project's decision log — confirmed false for at least the `lensflareoccludert`
715
+ `atomic_iadd` instance, which is a pixel shader; the emitter must not assume
716
+ this opcode family is filtered out before reaching ps-stage translation.
717
+
718
+ ---
719
+
720
+ ## `ld_uav_typed` (18 instances)
721
+
722
+ ### Semantics
723
+ Reads a texel/element from a typed UAV (`RWTexture*`/`RWBuffer`) at an integer
724
+ address, per D3D11 `ld_uav_typed` — the read counterpart of `store_uav_typed`.
725
+
726
+ ### GLSL lowering
727
+ Case `OPCODE_LD_UAV_TYPED` (`vendor/HLSLcc/src/toGLSLInstruction.cpp:4266-4336`).
728
+ Operands: `psDest=asOperands[0]`, `psSrcAddr=asOperands[1]`,
729
+ `psSrc=asOperands[2]` (UAV resource). Address component mask derived from
730
+ `psInst->eResDim` the same way as `store_uav_typed` (1/2/3 components,
731
+ `toGLSLInstruction.cpp:4281-4300`). Source data type resolved from the UAV's
732
+ reflected return type (`RETURN_TYPE_FLOAT/SINT/UINT/SNORM/UNORM`,
733
+ `toGLSLInstruction.cpp:4302-4325`, with `UNORM`/`SNORM` both mapping to
734
+ `SVT_FLOAT`). Emitted form:
735
+ ```glsl
736
+ dest.mask = imageLoad(<uavName>, <addr-by-dimension>)<swizzle-with-mask>;
737
+ ```
738
+ using `AddAssignToDest`/`AddAssignPrologue` for the destination, and
739
+ `TranslateOperandSwizzleWithMask` to apply the resource operand's own swizzle to
740
+ the 4-component `imageLoad` result restricted to the destination write mask
741
+ (`toGLSLInstruction.cpp:4327-4335`).
742
+
743
+ ### Type rules
744
+ Destination component type follows the UAV's reflected return type, not a fixed
745
+ uint-array convention (unlike `ld_structured`/`ld_raw`) — `imageLoad` always
746
+ returns a 4-component vector of the sampler/image's declared base type
747
+ (`ivec4`/`uvec4`/`vec4`), then swizzled down to the requested mask.
748
+
749
+ ### Helpers needed
750
+ None for WebGL2 (see below). Reference-only: `imageLoadTyped(uav, addr,
751
+ returnType)`.
752
+
753
+ ### Edge cases
754
+ Requires resource-binding reflection (return type, dimension) exactly like
755
+ `store_uav_typed` — doubly blocked when `RDEF` is stripped, independent of the
756
+ image-type gap below.
757
+
758
+ ### WebGL2 notes
759
+ `imageLoad` and image sampler types (`image2D` etc.) are ES 3.10+-only.
760
+ **Fully out of scope for the WebGL2 emitter.** Matches the draft transpiler's
761
+ explicit blocker (`Dx11GlesDraftTranspiler.js:129`) and the
762
+ `TRANSPILING-GAPS.md` UAV decision. With only 18 corpus instances (vs.
763
+ `store_uav_typed`'s 642), this is very likely the read side of a small set of
764
+ compute/UAV effects rather than any vs/ps-reachable code path — but the
765
+ concrete sampled instance (`dx11-instruction-coverage.json`, `allOpcodeExamples`)
766
+ is `graphics\effect.dx11\managed\space\postprocess\measureexposure.sm_depth`
767
+ (a tone-mapping average-luminance compute pass), **not** `lensflareoccludert`.
768
+ `lensflareoccludert` is confirmed by `TRANSPILING-GAPS.md:100-114` to carry
769
+ `dcl_unordered_access_view_typed`/`atomic_iadd` (and per this family's own
770
+ corpus scan, `atomic_iadd` there is a **pixel**-stage instance — see that
771
+ section); do not conflate the two effects or assume `ld_uav_typed`'s
772
+ compute-only reasoning transfers to `atomic_iadd`.
773
+
774
+ ### Confidence
775
+ **High** — same corroboration as `store_uav_typed`, corrected to cite the
776
+ right sampled effect (`measureexposure.sm_depth`, not `lensflareoccludert`).
777
+
778
+ ---
779
+
780
+ ## `bufinfo` (0 instances — specified for completeness only)
781
+
782
+ ### Semantics
783
+ Returns the element count of a structured/raw/typed buffer resource, per D3D11
784
+ `bufinfo` (used for bounds-checking dynamic buffer accesses).
785
+
786
+ ### GLSL lowering
787
+ Case `OPCODE_BUFINFO` (`vendor/HLSLcc/src/toGLSLInstruction.cpp:4753-4766`):
788
+ ```glsl
789
+ dest = <ctor>(<resourceName>_buf.length());
790
+ ```
791
+ via `AddAssignToDest(dest, SVT_INT, 1, ...)` then
792
+ `<resourceName>_buf.length()` then `AddAssignPrologue`.
793
+
794
+ ### Type rules
795
+ Result is always `SVT_INT` (a signed count), regardless of the buffer's element
796
+ type.
797
+
798
+ ### Helpers needed
799
+ None — zero corpus instances; no helper is justified without evidence of use.
800
+
801
+ ### Edge cases
802
+ N/A — not observed in this corpus.
803
+
804
+ ### WebGL2 notes
805
+ `.length()` on a GLSL array returns a compile-time constant for fixed-size
806
+ arrays (legal in ES 3.00) but is a **run-time** query only for SSBO
807
+ runtime-sized arrays (`buffer T { ... x[]; }`), which do not exist in ES 3.00.
808
+ Since this opcode's only real use (bounds-checking a structured/raw buffer) is
809
+ tied to the same SSBO-only resource kinds as `ld_structured`/`ld_raw`, it would
810
+ be out of scope for the same reason if it appeared. Zero corpus instances means
811
+ no immediate action item, but the emitter should still recognize and refuse the
812
+ opcode defensively rather than silently mis-translate.
813
+
814
+ ### Confidence
815
+ **Low** — zero real-world evidence in this corpus; semantics and lowering are
816
+ read directly from HLSLcc source but entirely unvalidated against any actual
817
+ shader.
818
+
819
+ ---
820
+
821
+ ## Helpers summary
822
+
823
+ Helpers the memory-structured family needs the emitter to provide (grouped by
824
+ whether they produce runnable WebGL2 output or are reference/detection-only):
825
+
826
+ **Shipping (WebGL2-runnable) helpers:**
827
+ 1. `lowerBoneTransformsToCb3` — package-time text-rewrite pass that removes the
828
+ HLSLcc `t0` SSBO declaration, grows `cb3.data[]` to at least 200 `vec4`s,
829
+ strips the native bone-ring-buffer offset add, and rewrites each 4-component
830
+ `t0_buf[idx].value[...]` row load to `cb3.data[26 + idx*3 + row]` (optionally
831
+ swizzled). Port of `lowerWebgl2SkinningAbi` +
832
+ `lowerStructuredBoneLoad` in `scripts/packageTr2WebglEffect.js:826-902`.
833
+ 2. `lowerBlendIndicesToFloatAttribute` — package-time rewrite of
834
+ `uvec4`/`ivec4`/etc. `in_BLENDINDICES<n>` vertex inputs to `vec<N>`, part of
835
+ the same pass (`packageTr2WebglEffect.js:840-843`).
836
+
837
+ **Reference-only helpers** (mirror HLSLcc's non-WebGL2 SSBO/UAV/atomic/compute
838
+ lowering, useful for documentation, debugging, and any future non-WebGL2
839
+ backend, but must never be emitted as final WebGL2 output):
840
+ 3. `structuredLoadComponent(bufName, structIndex, byteOffset, component, destType)`
841
+ 4. `structuredStoreComponent(bufName, structIndex, byteOffset, component, value, srcType)`
842
+ 5. `rawLoadComponent(bufName, byteOffset, component, destType)`
843
+ 6. `rawStoreComponent(bufName, byteOffset, component, value, srcType)`
844
+ 7. `imageLoadTyped(uav, addr, returnType)`
845
+ 8. `imageStoreTyped(uav, addr, value, returnType)`
846
+ 9. `atomicUavOrTgsmOp(func, dest, addr, src, compare?, returnsPrevious?)`
847
+
848
+ **Detection/refusal helpers** (must run before emission; there is no GLSL ES
849
+ 3.00 lowering to fall back to):
850
+ 10. `refuseComputeOnlyOpcode(opcodeName)` — for `sync`, `store_structured`
851
+ (non-skinning), `store_raw`, `store_uav_typed`, `ld_uav_typed`,
852
+ `atomic_iadd`, `imm_atomic_iadd`, `imm_atomic_exch`, `atomic_umax`,
853
+ `atomic_umin`, and `bufinfo` should it ever appear.
854
+ 11. `refuseNonSkinningStructuredLoad(resourceName)` — for `ld_structured` /
855
+ `ld_raw` uses that are not the vertex-stage `BoneTransforms` skinning
856
+ pattern (e.g. `LightBuffer`/`LightIndexBuffer` pixel-stage reads).
857
+
858
+ ---
859
+
860
+ *Sources consulted: `vendor/HLSLcc/src/toGLSLInstruction.cpp`,
861
+ `toGLSLOperand.cpp`, `toGLSLDeclaration.cpp`, `HLSLccToolkit.cpp`,
862
+ `internal_includes/languages.h`; `vendor/HLSLcc/CARBONENGINEJS-FORK.md`;
863
+ `../shaderdiscovery/TRANSPILING-GAPS.md`; `../shaderdiscovery/AGENT-FINDINGS/decisions/005-`,
864
+ `016-`, `028-cewg-*`, `014-`/`015-`/`016-`/`017-joint-matrix-*`;
865
+ `../shaderdiscovery/src/core/transpiler/gles/Dx11GlesDraftTranspiler.js` (hints
866
+ only); `scripts/packageTr2WebglEffect.js`;
867
+ `../shaderdiscovery/artifacts/dx11-instruction-coverage.json` (per-instruction
868
+ stage ground truth, used to correct several "compute-only" stage-reality
869
+ claims); `../shaderdiscovery/artifacts/ab-shader-set/work/skinned_fxdirectionalv5/*.es300.glsl`
870
+ (actual generated fixture, used to correct the `ld_structured` cb3 worked
871
+ example).*