@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,964 @@
1
+ # DXBC -> GLSL ES 3.00 Lowering Spec: texture-sample family
2
+
3
+ Status: Evolving
4
+ Scope: `@carbonenginejs/runtime-resource/formats/webgl` texture and sampling lowering
5
+ Audience: Shader translator maintainers and reviewers
6
+ Summary: Defines DXBC texture sampling behavior and WebGL2 target adaptations.
7
+
8
+ Target: GLSL ES 3.00 (WebGL2), vertex + pixel stages, no SSBO/compute.
9
+ Register storage model: every register is a float `vec4`; typed reads/writes bitcast at
10
+ the use site (`floatBitsToInt`, `floatBitsToUint`, `intBitsToFloat`, `uintBitsToFloat`),
11
+ mirroring HLSLcc's own behavior when reflected data-type analysis is unavailable
12
+ (`ShaderInfo::GetTextureDataType` returns `SVT_FLOAT` when no `RDEF` binding is found —
13
+ `vendor/HLSLcc/src/ShaderInfo.cpp:10-18`).
14
+
15
+ Authority order used below: (1) `toGLSLInstruction.cpp` / `toGLSLOperand.cpp` /
16
+ `toGLSLDeclaration.cpp` / `HLSLccToolkit.cpp`, (2) `CARBONENGINEJS-FORK.md`, (3)
17
+ `TRANSPILING-GAPS.md`, (4) `AGENT-FINDINGS/decisions/*.md`, (5) `Dx11GlesDraftTranspiler.js`
18
+ (hints only, cross-checked, never trusted standalone).
19
+
20
+ **Correction (verification pass, 2026-07-05)**: sources (3) and (4) above —
21
+ `TRANSPILING-GAPS.md`, `AGENT-FINDINGS/decisions/*.md` — and source (5),
22
+ `Dx11GlesDraftTranspiler.js`, do not exist anywhere in this repository: neither in the
23
+ working tree nor in `git log --all` history, under any path (checked repo-wide, not just
24
+ under `docs/` or `vendor/`). Only `CARBONENGINEJS-FORK.md`
25
+ (`vendor/HLSLcc/CARBONENGINEJS-FORK.md`) and the `toGLSLInstruction.cpp`/`toGLSLOperand.cpp`/
26
+ `toGLSLDeclaration.cpp`/`HLSLccToolkit.cpp` C++ sources actually exist and were readable for
27
+ this review. Every citation to `TRANSPILING-GAPS.md:*`, `decisions/*.md`, or
28
+ `Dx11GlesDraftTranspiler.js` elsewhere in this document is therefore **unverifiable** and
29
+ must not be treated as independent corroboration until those files are located or
30
+ reconstructed — treat any "Confidence" rating that leaned on them as resting on the C++
31
+ citations alone (which were independently re-verified line-by-line for this pass and, apart
32
+ from the corrections called out below, held up).
33
+
34
+ This project's own `hlsl2webgl` CLI tool is the concrete configuration this spec targets:
35
+ `LANG_ES_300` with `HLSLCC_FLAG_COMBINE_TEXTURE_SAMPLERS | HLSLCC_FLAG_UNIFORM_BUFFER_OBJECT |
36
+ HLSLCC_FLAG_INOUT_SEMANTIC_NAMES | HLSLCC_FLAG_INCLUDE_INSTRUCTIONS_COMMENTS |
37
+ HLSLCC_FLAG_KEEP_VARYING_LOCATIONS` (`vendor/HLSLcc/tools/hlsl2webgl/hlsl2webgl.cpp:38-44`).
38
+ So the combined-texture-sampler path (single opaque GLSL `samplerND` per t#/s# pair) is
39
+ this CLI tool's **default** configuration (both `hlsl2webgl.cpp`'s `--flags`/
40
+ `--no-default-flags` options and `scripts/packageTr2WebglEffect.js`'s `--flags` passthrough
41
+ can override it), not a hardcoded, unconditional invariant — but it is the configuration
42
+ this spec targets and the one assumed throughout.
43
+
44
+ ---
45
+
46
+ ## 0. Register-stable ABI: how t#/s# become GLSL names
47
+
48
+ Ground truth: `toGLSLOperand.cpp:1738-1882` (`ResourceName`, `TextureSamplerName`),
49
+ `toGLSLDeclaration.cpp:1597-1686` (`TranslateResourceTexture`), and
50
+ `CARBONENGINEJS-FORK.md`.
51
+
52
+ - **Plain resource name** (`ResourceName`, `toGLSLOperand.cpp:1738-1810`): if the register
53
+ has a resolved `RDEF`/reflection binding, the name is the reflected identifier (with `[`/`]`
54
+ array syntax rewritten to `_`, and an array-offset suffix appended if the register is not
55
+ the base of its binding). If no binding is found (stripped `RDEF` — the documented
56
+ `CARBONENGINEJS-FORK.md` scenario), the fallback is the literal register-stable symbol:
57
+ `"t" + regNo` for textures, `"s" + regNo` for samplers (prefixed with the literal string
58
+ `sampler` if it doesn't already start with it — so unresolved sampler `s3` becomes
59
+ `samplers3`), `"cb" + regNo` for constant buffers, `"u" + regNo` for UAVs
60
+ (`toGLSLOperand.cpp:1780-1799`).
61
+ - **Combined texture+sampler name** (`TextureSamplerName`, `toGLSLOperand.cpp:1819-1876`):
62
+ built independently of `ResourceName`. If either the texture or the sampler binding is
63
+ unresolved, the fallback is literally `"t" + texRegNo + "TEX_with_SMPs" + smpRegNo`
64
+ (e.g. `t0TEX_with_SMPs0`) — the `t#`/`s#` symbols survive intact inside the combined name.
65
+ If both bindings resolve, the name is `<textureName>[arrayOffset]TEX_with_SMP<samplerName>`.
66
+ A `bZCompare` flag prefixes `hlslcc_zcmp` onto either name for the shadow-sampler variant
67
+ used by depth-compare ops (see `sample_c` below).
68
+ - **Declaration duplication**: `TranslateResourceTexture` (`toGLSLDeclaration.cpp:1632-1666`)
69
+ emits **both** symbols as separate `uniform` declarations when
70
+ `HLSLCC_FLAG_COMBINE_TEXTURE_SAMPLERS` is set: one `uniform sampler2D <TextureSamplerName>`
71
+ per (texture, sampler) pair actually used together in the shader, **and** a plain
72
+ `uniform sampler2D <ResourceName>` (the bare `t#` symbol) that is declared but not
73
+ referenced by instruction bodies (instruction bodies call `TextureSamplerName`, not
74
+ `ResourceName`, when `useCombinedTextureSamplers` is true — `toGLSLInstruction.cpp:1332-1336`).
75
+ This is deliberate register-identity preservation per `CARBONENGINEJS-FORK.md`
76
+ ("emitting deterministic register-stable GLSL symbols such as `cb3`, `t0`, and `s0`" /
77
+ "preserving enough register identity for `Tr2EffectBindingManifest`... and eventual
78
+ trinityjs binding work"): the emitter must keep declaring the bare `t#` uniform even
79
+ though the sampling calls use the combined name, so downstream tooling can still find a
80
+ `t#`-named uniform to correlate against Carbon metadata.
81
+ - **Texture/sampler pairing is register-based, not name-based**
82
+ (`AGENT-FINDINGS/decisions/003-texture-sampler-translation-boundary-2026-06-26.md`):
83
+ do not infer texture ownership from a sampler's metadata label; the (t#, s#) pair observed
84
+ on the actual instruction operands is the only truth.
85
+ - **Samplerless resource access is real** (`decisions/005-samplerless-texture-access-evidence-2026-06-26.md`):
86
+ `ld` and `resinfo` carry no sampler operand at all. `TranslateTexelFetch` handles a
87
+ missing reflected binding by synthesizing a `fallbackBinding` from the `SHEX`-derived
88
+ resource dimension (`toGLSLInstruction.cpp:825-870`), defaulting
89
+ `ui32ReturnType = RETURN_TYPE_FLOAT`, matching the fork's stripped-`RDEF` fallback policy.
90
+
91
+ ---
92
+
93
+ ## 1. `sample` (3064 occurrences)
94
+
95
+ **Semantics**: Standard filtered texture sample using the sampler's declared filter/wrap
96
+ state and an implicitly-computed LOD (derivative-based, like a normal pixel-shader texture
97
+ fetch). D3D11 `SAMPLE` opcode: `dest, address, resource, sampler`.
98
+
99
+ **GLSL lowering**: `TranslateTextureSample(psInst, TEXSMP_FLAG_NONE)`
100
+ (`toGLSLInstruction.cpp:3161-3170`, dispatch into `toGLSLInstruction.cpp:1131-1459`).
101
+ Template (combined-sampler, ES 3.00, dimension = 2D):
102
+
103
+ ```glsl
104
+ dest = texture(<TextureSamplerName>, <coord>)<returnSwizzle>;
105
+ ```
106
+
107
+ - Function name defaults to `"texture"` (`iHaveOverloadedTexFuncs` is true for every
108
+ language except `LANG_ES_100`/`LANG_120`, `languages.h:37-44`, so ES 3.00 always uses the
109
+ overloaded `texture()` builtin, never legacy `texture2D`/`textureCube`/etc.).
110
+ `funcName`/`offset`/`gradSwizzle`/`ui32NumOffsets` are chosen per resource dimension at
111
+ `toGLSLInstruction.cpp:1172-1251`.
112
+ - Coordinate build is `TranslateTexCoord(eResDim, psDestAddr)` (`:985-1031`), which selects
113
+ and expands the address operand's leading components per dimension — see the coordinate
114
+ table in section "Coordinate component selection" below.
115
+ - If `psInst->bAddressOffset` (immediate texel offset present, see section on
116
+ `sample_controls` below), `offset = "Offset"` is appended to the function name and an
117
+ `ivec2`/`ivec3`/int offset literal argument is appended (`:1391-1411`).
118
+ - After the call closes, the **return-channel swizzle** is applied by re-reading the
119
+ swizzle encoded on the **texture (`t#`) operand itself** (not the destination): DXBC
120
+ texture instructions carry a component swizzle on the resource operand describing how the
121
+ hardware's raw fetched RGBA maps onto the instruction's result vector before the
122
+ destination write mask applies. HLSLcc re-enables the write mask on that operand
123
+ (`iWriteMaskEnabled = 1`) and calls `TranslateOperandSwizzleWithMask(psContext, psSrcTex,
124
+ psDest->GetAccessMask(), 0)` (`:1451-1457`) to append e.g. `.yzwx` intersected with the
125
+ destination's write mask.
126
+ - Saturate (`_sat`) is **not** applied inside `TranslateTextureSample`; it is a uniform
127
+ post-processing step applied by the general instruction-loop epilogue
128
+ (`toGLSLInstruction.cpp:4821-4844`): after any instruction with `bSaturate` set, the
129
+ destination register is re-emitted as `dst = clamp(dst, 0.0, 1.0);` (with an
130
+ `#ifdef UNITY_ADRENO_ES3` `min(max(x,0.0),1.0)` variant guard). This applies identically
131
+ to every opcode in this family.
132
+
133
+ **Type rules**: destination type comes from `ShaderInfo::GetTextureDataType(texRegNo)`
134
+ (`ShaderInfo.cpp:10-18`) — `SVT_FLOAT`, `SVT_INT`, or `SVT_UINT` from the reflected
135
+ `RESOURCE_RETURN_TYPE`, or `SVT_FLOAT` if unreflected. `AddAssignToDest`
136
+ (`toGLSLInstruction.cpp:1281`, `:155-171`) wraps the whole call in `floatBitsToInt(...)`/
137
+ `floatBitsToUint(...)`/`intBitsToFloat(...)`/`uintBitsToFloat(...)` only if the destination's
138
+ *declared register type* (from prior data-type analysis) disagrees with the texture's
139
+ return type — since this project stores everything as float vec4 and skips full data-type
140
+ analysis, the safe default is to always treat the texture-op result as float and defer any
141
+ int/uint reinterpretation to the consuming instruction's own bitcast (matches the "bitcast
142
+ at use site" policy stated in the task, and matches `GetSamplerType`'s int/uint sampler
143
+ selection below). Coordinates are read with `TO_AUTO_BITCAST_TO_FLOAT` (`TranslateTexCoord`
144
+ always sets this flag, `:989`) — i.e. address components are reinterpreted as float via
145
+ `intBitsToFloat`/`uintBitsToFloat` if the source register was produced as int/uint.
146
+ The **sampler variant type** (`sampler2D` vs `isampler2D` vs `usampler2D`) is chosen once,
147
+ at declaration time, from the reflected return type via `GetSamplerType`
148
+ (`toGLSLDeclaration.cpp:1388-1551`); if unreflected it falls back to `default: return
149
+ "sampler2D"` (float) at `:1550` and per-dimension `default:` cases, again matching the
150
+ stripped-`RDEF` fallback.
151
+
152
+ **Helpers needed**: none beyond the coordinate-assembly and bitcast machinery already
153
+ required by every opcode family (`floatBitsToInt`/`intBitsToFloat`/etc. are native GLSL ES
154
+ 3.00 builtins, not custom helpers — `HaveBitEncodingOps` is true for every language except
155
+ `LANG_ES_100`/`LANG_120`, `languages.h:169-180`).
156
+
157
+ **Edge cases**: Out-of-range coordinates follow the sampler's wrap mode (not a DXBC
158
+ concern — GLSL `texture()` handles it per the WebGL2 sampler state, which is set at the
159
+ JS/WebGL layer, outside this translator's scope). NaN/Inf in coordinates is
160
+ undefined/implementation-defined per GLSL ES spec, same as native GLSL; HLSLcc does not
161
+ special-case it.
162
+
163
+ **WebGL2 notes**: `texture()` (the "vec form" overloaded builtin) is core GLSL ES 3.00 —
164
+ no extension needed for 1D/2D/3D/Cube/array textures except cubemap arrays (see below).
165
+ Legacy `texture2D`/`textureCube`/etc. names are never emitted because `iHaveOverloadedTexFuncs`
166
+ is true for ES 3.00.
167
+
168
+ **Confidence**: high — this is the highest-volume opcode in the corpus and the core
169
+ `TranslateTextureSample` path is fully read and directly cited line-by-line.
170
+
171
+ ---
172
+
173
+ ## 2. `sample_l` (9124 occurrences — highest volume in family)
174
+
175
+ **Semantics**: Texture sample with an explicit, caller-supplied LOD (mip level), no
176
+ derivative computation. D3D11 `SAMPLE_L`: `dest, address, resource, sampler, LOD`. Used
177
+ heavily for skinned/environment/UI shaders that need deterministic mip selection outside a
178
+ derivative-having stage (this is also the *only* sampling form legal in a vertex shader,
179
+ which cannot compute screen-space derivatives).
180
+
181
+ **GLSL lowering**: `TranslateTextureSample(psInst, TEXSMP_FLAG_LOD)`
182
+ (`toGLSLInstruction.cpp:3171-3180`). LOD operand is `psInst->asOperands[4]`
183
+ (`:1144`). Template:
184
+
185
+ ```glsl
186
+ dest = textureLod(<TextureSamplerName>, <coord>, <lod>)<returnSwizzle>;
187
+ ```
188
+
189
+ or with an immediate texel offset: `textureLodOffset(<sampler>, <coord>, <lod>, <offsetVec>)`
190
+ (`:1308-1309`, offset suffix logic `:1163-1170`).
191
+
192
+ - LOD argument: `TranslateOperand(psSrcLOD, TO_AUTO_BITCAST_TO_FLOAT)` (`:1361-1372`); if
193
+ `psContext->psShader->ui32MajorVersion < 4` (SM1–3 legacy bytecode) a `.w` swizzle is
194
+ appended, but for DX11/SM5 bytecode (the corpus target) this branch does not apply — the
195
+ scalar LOD operand is used directly.
196
+ - **2D-array shadow-sampler LOD workaround** (`needsLodWorkaround`,
197
+ `toGLSLInstruction.cpp:1284,1297-1300,1357-1360`): GLSL has no `textureLod()` overload for
198
+ `sampler2DArrayShadow`. When `eResDim == RESOURCE_DIMENSION_TEXTURE2DARRAY` **and**
199
+ `TEXSMP_FLAG_DEPTHCOMPARE` is set (i.e. this is really a `sample_c`/`sample_c_lz` case on
200
+ a 2D-array depth resource, not plain `sample_l`), HLSLcc substitutes
201
+ `textureGrad(sampler, coord, vec2(0.0,0.0), vec2(0.0,0.0))` — zero gradients approximate
202
+ LOD 0. This is a correctness approximation, not exact LOD selection, and only fires for
203
+ the depth-compare + 2D-array combination.
204
+
205
+ **Type rules**: identical destination-type rule to `sample` (texture reflected return
206
+ type, `SVT_FLOAT` fallback). LOD operand is bitcast to float via `TO_AUTO_BITCAST_TO_FLOAT`
207
+ regardless of the DXBC register's nominal type, because DXBC LOD is always a float value
208
+ even when stored in an otherwise-integer temp register.
209
+
210
+ **Helpers needed**: none beyond core bitcast machinery.
211
+
212
+ **Edge cases**: LOD is clamped to `[0, textureQueryLevels-1]` by the GL implementation
213
+ itself, per GLSL spec — HLSLcc emits no explicit clamp. A negative or out-of-range LOD is
214
+ implementation-defined-clamped, not a translator concern.
215
+
216
+ **WebGL2 notes**: `textureLod` is core GLSL ES 3.00 for all sampler types except
217
+ `sampler2DArrayShadow`/`samplerCubeShadow` (no such overload exists at all — hence the
218
+ grad workaround above). `textureLodOffset` is also core ES 3.00 (`texelFetchOffset`-family
219
+ functions, GLSL ES 3.00 spec section 8.9) — no extension required.
220
+
221
+ **Confidence**: high — dominant opcode, full lowering path read directly, workaround logic
222
+ explicitly commented in source.
223
+
224
+ ---
225
+
226
+ ## 3. `sample_b` (3312 occurrences)
227
+
228
+ **Semantics**: Texture sample with a LOD **bias** added to the automatically computed
229
+ (derivative-based) LOD. D3D11 `SAMPLE_B`: `dest, address, resource, sampler, bias`.
230
+ Pixel-shader only (requires derivatives to compute the base LOD before biasing).
231
+
232
+ **GLSL lowering**: `TranslateTextureSample(psInst, TEXSMP_FLAG_BIAS)`
233
+ (`toGLSLInstruction.cpp:3211-3220`). Bias operand is `psInst->asOperands[4]` (`:1147`).
234
+ Template:
235
+
236
+ ```glsl
237
+ dest = texture(<TextureSamplerName>, <coord>, <bias>)<returnSwizzle>;
238
+ ```
239
+
240
+ The bias is appended as the GLSL `texture()` builtin's optional trailing `bias` parameter
241
+ (`:1426-1430`, appended after any offset/gather-component arguments) — **not** a separate
242
+ function name (`textureBias` does not exist; core `texture()` already accepts an optional
243
+ bias argument in both desktop GLSL and GLSL ES 3.00 for non-shadow, non-array-shadow
244
+ samplers in fragment shaders).
245
+
246
+ **Type rules**: same destination-type rule as `sample`. Bias is
247
+ `TranslateOperand(psSrcBias, TO_AUTO_BITCAST_TO_FLOAT)` — always read/bitcast as float.
248
+
249
+ **Helpers needed**: none.
250
+
251
+ **Edge cases**: per GLSL ES 3.00 spec, the optional bias parameter to `texture()` is **only
252
+ legal in fragment shaders**; DXBC `sample_b` cannot legally appear in a vertex shader
253
+ either (no derivatives), so this is a non-issue in practice, but the emitter should assert
254
+ stage == pixel for `sample_b` rather than silently emit invalid vertex-shader GLSL.
255
+
256
+ **WebGL2 notes**: the bias-argument overload of `texture()` is core GLSL ES 3.00 (unlike
257
+ GLSL ES 1.00/`LANG_ES_100`, which required the `GL_OES_shader_texture_lod`-style `EXT`
258
+ suffix handled by the `ext = "EXT"` branch at `:1303-1306` — that branch only triggers for
259
+ `LANG_ES_100`, never for ES 3.00, so no extension suffix is emitted for this target).
260
+
261
+ **Confidence**: high — straightforward, fully read lowering path.
262
+
263
+ ---
264
+
265
+ ## 4. `sample_d` (153 occurrences)
266
+
267
+ **Semantics**: Texture sample with explicit user-supplied screen-space gradients
268
+ (`ddx`, `ddy`) instead of automatically-computed derivatives — used to avoid derivative
269
+ discontinuities across non-uniform control flow, or to sample from a vertex/geometry-style
270
+ context with manually supplied gradients. D3D11 `SAMPLE_D`: `dest, address, resource,
271
+ sampler, xDerivatives, yDerivatives`.
272
+
273
+ **GLSL lowering**: `TranslateTextureSample(psInst, TEXSMP_FLAG_GRAD)`
274
+ (`toGLSLInstruction.cpp:3201-3210`). `psSrcDx = operands[4]`, `psSrcDy = operands[5]`
275
+ (`:1145-1146`). Template:
276
+
277
+ ```glsl
278
+ dest = textureGrad(<TextureSamplerName>, <coord>, vec4(<dx>)<gradSwizzle>, vec4(<dy>)<gradSwizzle>)<returnSwizzle>;
279
+ ```
280
+
281
+ - Gradient arguments are always wrapped `vec4(...)` then swizzled down to the dimension's
282
+ `gradSwizzle` (`.x`/`.xy`/`.xyz` per resource dimension, `:1177,1192,1207,1218,1229,1236,1242`)
283
+ — i.e. the DXBC gradient operand is always a full 4-component register, and only the
284
+ dimension-appropriate leading components are kept (`:1378-1388`).
285
+ - With an immediate offset, `textureGradOffset(sampler, coord, dx, dy, offsetVec)` is used
286
+ instead (`offset = "Offset"`, `:1311`).
287
+
288
+ **Type rules**: same destination-type rule as `sample`. Gradient operands are
289
+ `TranslateOperand(psSrcDx/Dy, TO_AUTO_BITCAST_TO_FLOAT)` — always bitcast to float.
290
+
291
+ **Helpers needed**: none.
292
+
293
+ **Edge cases**: the 2D-array-shadow LOD-workaround described under `sample_l` also applies
294
+ here in principle (`needsLodWorkaround` is dimension/depth-compare gated, not
295
+ opcode-gated) but `sample_d` is not itself a depth-compare opcode in this corpus (DXBC has
296
+ no `sample_d_c`), so it does not fire for plain `sample_d`.
297
+
298
+ **WebGL2 notes**: `textureGrad`/`textureGradOffset` are core GLSL ES 3.00 builtins for all
299
+ non-shadow sampler types plus `sampler2DShadow`/`samplerCubeShadow` (but not
300
+ `sampler2DArrayShadow`, consistent with the `sample_l` workaround note). Low corpus count
301
+ (153) means most usages likely target ordinary color textures, but if a Carbon effect ever
302
+ uses `sample_d` on a 2D-array depth resource, the same "no textureGrad-family overload"
303
+ class of gap could theoretically surface — unconfirmed in this corpus, flagged as a risk.
304
+
305
+ **Confidence**: medium — the lowering path is fully read and low-ambiguity, but the low
306
+ occurrence count (153) means it has had less indirect validation than `sample`/`sample_l`/
307
+ `sample_b` from the corpus-count-as-confidence-proxy perspective.
308
+
309
+ ---
310
+
311
+ ## 5. `gather4` (195 occurrences) — and `gather4_po` / `gather4_po_c` / `gather4_c`
312
+
313
+ **Semantics**: Fetches the same single component (selectable) from the 4 texels used in
314
+ bilinear filtering at the given coordinate, without applying the bilinear weights — one
315
+ `vec4` result where each component is that channel from one of the 4 neighboring texels
316
+ (D3D11 `GATHER4` family). `_PO` variants add a programmable integer texel offset operand;
317
+ `_C` variants add a depth-comparison reference (shadow gather).
318
+
319
+ **GLSL lowering**: dispatch at `toGLSLInstruction.cpp:3121-3160`:
320
+ - `gather4` -> `TranslateTextureSample(psInst, TEXSMP_FLAG_GATHER)`
321
+ - `gather4_po` -> `TEXSMP_FLAG_GATHER | TEXSMP_FLAG_PARAMOFFSET`
322
+ - `gather4_po_c` -> `TEXSMP_FLAG_GATHER | TEXSMP_FLAG_PARAMOFFSET | TEXSMP_FLAG_DEPTHCOMPARE`
323
+ - `gather4_c` -> `TEXSMP_FLAG_GATHER | TEXSMP_FLAG_DEPTHCOMPARE`
324
+
325
+ Inside `TranslateTextureSample`, `funcName` is forced to `"textureGather"`
326
+ (`:1253-1254`). Template:
327
+
328
+ ```glsl
329
+ dest = textureGather(<TextureSamplerName>, <coord>[, <refZ>][, <offsetVec>][, <component>])<returnSwizzle>;
330
+ ```
331
+
332
+ - Depth-compare reference (`gather4_c`/`gather4_po_c`): unlike ordinary depth-compare
333
+ sampling, the gather forms pass the reference as a **separate trailing argument**, never
334
+ embedded into the coordinate vector — the code explicitly special-cases
335
+ `!(ui32Flags & TEXSMP_FLAG_GATHER)` when deciding whether to build the `txVecN` embedded-
336
+ reference temp (`:1264-1265`, `:1341-1354`), matching real
337
+ `textureGather(sampler, coord, refZ)` GLSL signature for shadow samplers.
338
+ - Programmable offset (`gather4_po`/`gather4_po_c`): read via
339
+ `TranslateOperand(psSrcOff, TO_FLAG_INTEGER, mask)` and appended with a leading comma
340
+ (`:1412-1423`), mask width = `ui32NumOffsets` (1/2/3 per dimension).
341
+ Immediate (non-programmable) offsets on plain `gather4` follow the same
342
+ `bAddressOffset`/`iUAddrOffset` path as `sample`.
343
+ - **Gather component selection**: `gather4`/`gather4_po` may carry a 1-component swizzle on
344
+ the *sampler* (`s#`) operand selecting which of R/G/B/A to gather; if that swizzle is not
345
+ `X` (red, the GLSL default), the component index is appended as a trailing int argument
346
+ (`:1432-1447`). Component selection is explicitly **not supported** for the `_C` (depth
347
+ compare) gather variants — HLSLcc's comment states this outright (`:1442-1445`); shadow
348
+ gather always returns the comparison result, there is no channel to select.
349
+ - Switch-console-specific `GATHER4_PO` `Offset`-suffix quirk (`:1165-1170`) is not relevant
350
+ to this WebGL2-only target (`psContext->IsSwitch()` is always false here).
351
+
352
+ **Type rules**: destination type same rule as `sample` (reflected return type,
353
+ `SVT_FLOAT` fallback). Offset operand is read with `TO_FLAG_INTEGER` (never bitcast to
354
+ float) since GLSL's `ivec` offset parameters require true integer values, not a
355
+ reinterpreted float register.
356
+
357
+ **Helpers needed**: `hlslcc_textureGather4Emulated` (**mandatory for this target** — see
358
+ WebGL2 notes below). **Correction**: an earlier draft of this spec claimed "zero hits for
359
+ `HaveGather` outside `languages.h`" — that grep claim is false. `toGLSLInstruction.cpp`/
360
+ `toGLSLDeclaration.cpp`/`toGLSLOperand.cpp` (the instruction/declaration/operand lowering
361
+ proper) indeed never check `HaveGather`, but `toGLSL.cpp`'s `AddVersionDependentCode`
362
+ (`:163-172`) does: `if (!HaveGather(eLang)) { if (any gather4* opcode used)
363
+ EnableExtension("GL_ARB_texture_gather"); }`. `EnableExtension` (`HLSLCrossCompilerContext.cpp:157-167`)
364
+ emits a guarded pragma — `#ifdef GL_ARB_texture_gather` / `#extension GL_ARB_texture_gather
365
+ : enable` / `#endif` — so it never hard-fails on an unrecognized extension name; it is a
366
+ best-effort "enable it if the compiler happens to advertise it" pattern. For `LANG_ES_300`
367
+ this is a no-op in practice: `GL_ARB_texture_gather` is a **desktop**-only ARB extension
368
+ token with no ES/WebGL2-side branch (contrast the image-atomics case a few lines above at
369
+ `:154-160`, which does pick `GL_OES_shader_image_atomic` on `isES`) — no WebGL2 GLSL ES
370
+ preprocessor predefines that macro, so the `#ifdef` never fires, no `#extension` line is
371
+ emitted, and the instruction body's hardcoded `funcName = "textureGather"`
372
+ (`toGLSLInstruction.cpp:1253-1254`, still unconditional) is unaffected either way. So the
373
+ end conclusion is unchanged — this target still needs the emulation helper — but the
374
+ "HLSLcc never gates this at all" framing is wrong: it does attempt a gate, the gate is just
375
+ built for desktop GL and silently does nothing useful on `LANG_ES_300`. The JS emitter must
376
+ supply its own 4-tap emulation (four `textureOffset`/`texture` calls at the four
377
+ bilinear-neighbor texel centers, computed from `textureSize`) wherever the real
378
+ `textureGather` builtin is unavailable.
379
+
380
+ **Edge cases**: `textureGather` (where available) requires the four sampled texels to be
381
+ selected by hardware bilinear-neighbor rules that are implementation-defined at exact
382
+ texel boundaries; an emulated fallback needs to replicate the "texel below-left of the
383
+ sample point" neighbor-selection rule (typically via `floor(coord*size - 0.5)` and
384
+ `+ivec2(0/1,0/1)` taps) to match D3D `Gather4` semantics closely enough for typical
385
+ consumers (contact-hardening shadows, procedural blending). This emulation is
386
+ **not sourced from HLSLcc** (HLSLcc assumes `textureGather` exists) and is the single
387
+ highest-risk item in this spec.
388
+
389
+ **WebGL2 notes — critical gap**: `languages.h:220-227` defines
390
+ `HaveGather(eLang)` as true only for `eLang >= LANG_400` (desktop GL 4.0+) **or**
391
+ `eLang == LANG_ES_310`. **`LANG_ES_300` (this project's actual target) is excluded.**
392
+ `textureGather`/`textureGatherOffset` are GLSL ES 3.10 / desktop-GLSL-4.00 builtins; they
393
+ do not exist in GLSL ES 3.00 core and WebGL2 (which is GLSL ES 3.00-based) exposes no
394
+ extension that adds them. **Correction**: an earlier draft of this spec claimed "HLSLcc's
395
+ GLSL backend never checks `HaveGather` before emitting `textureGather(...)`" — that is not
396
+ quite right; see the "Helpers needed" correction above: `toGLSL.cpp` does check
397
+ `!HaveGather(eLang)` and attempts to `EnableExtension("GL_ARB_texture_gather")`, it's just
398
+ that the attempt is built for desktop GL and is a silent no-op on `LANG_ES_300`
399
+ (the `#ifdef`-guarded extension macro is never predefined by an ES/WebGL2 preprocessor, and
400
+ the instruction body's `funcName` is set unconditionally regardless of that check's
401
+ outcome). Net effect is unchanged: it will happily produce GLSL that fails to compile under
402
+ strict WebGL2/ES 3.00 validation. This spec's emitter must either (a) always emit the
403
+ `hlslcc_textureGather4Emulated` helper instead of raw `textureGather` for this family, or
404
+ (b) detect and reject `gather4*` shaders at translation time with an explicit diagnostic —
405
+ but silently trusting HLSLcc's literal `textureGather(...)` output will break at WebGL2
406
+ shader-compile time. This is the most important actionable finding in this spec for the
407
+ `gather4` opcodes.
408
+
409
+ **Confidence**: low for WebGL2 compilability, high for what HLSLcc *literally emits*
410
+ (fully read source, zero ambiguity in the C++, including the `toGLSL.cpp` extension-attempt
411
+ correction above) — the risk is entirely in the target capability mismatch documented
412
+ above, not in misreading HLSLcc's intent. An earlier draft cited `TRANSPILING-GAPS.md:188`
413
+ as corroboration for this being a known pending item; that file does not exist anywhere in
414
+ this repository (see the authority-order correction at the top of this document), so that
415
+ citation is withdrawn and this section's confidence rests solely on the C++ reading above.
416
+
417
+ ---
418
+
419
+ ## 6. `sample_c` (0 occurrences in corpus, spec required for depth paths)
420
+
421
+ **Semantics**: Depth-comparison ("shadow") sample: fetches from a depth-format resource
422
+ using a comparison sampler, compares the fetched depth against a supplied reference value,
423
+ and returns the hardware's percentage-closer-filtered (PCF) result — a **float in
424
+ `[0.0, 1.0]`**, not a 0/1 boolean and **not** the DXBC ALU integer comparison-mask
425
+ convention. D3D11 `SAMPLE_C`: `dest, address, resource, sampler, referenceValue`, implicit
426
+ (derivative-based) LOD like `sample`.
427
+
428
+ **Important — comparison-mask convention does NOT apply here**: DXBC's scalar/vector ALU
429
+ comparison opcodes (`eq`/`ne`/`lt`/`ge`, `ieq`/`ige`/etc., handled by `AddComparison`,
430
+ `toGLSLInstruction.cpp:173` and the `OPCODE_GE` comment "the result is a boolean but HLSL
431
+ asm returns 0xFFFFFFFF/0x0 instead", `:2689-2694`) produce a full-lane
432
+ `0xFFFFFFFF`/`0x00000000` integer mask per component. **`sample_c`/`sample_c_lz`/
433
+ `gather4_c` are unrelated to that convention** — hardware depth comparison sampling
434
+ returns a genuinely-filtered floating-point value (0.0, 1.0, or any PCF-blended value in
435
+ between when the sampler uses linear filtering across multiple depth texels), matching
436
+ standard D3D11 `SampleCmp`/`SampleCmpLevelZero` semantics that HLSLcc's GLSL backend
437
+ forwards unchanged (it applies no extra masking logic to the depth-compare result — the
438
+ value comes directly out of GLSL `texture(sampler2DShadow, ...)`, which itself returns a
439
+ filtered float per the GLSL spec).
440
+
441
+ **GLSL lowering**: `TranslateTextureSample(psInst, TEXSMP_FLAG_DEPTHCOMPARE)`
442
+ (`toGLSLInstruction.cpp:3181-3190`). `psSrcRef = operands[4 + hasParamOffset]` (`:1143`).
443
+
444
+ - For every resource dimension **except** `TEXTURECUBEARRAY` and non-gather ops, the
445
+ reference value is embedded as the **last component of the texture coordinate vector**
446
+ (this matches core GLSL's shadow-sampler convention, where `sampler2DShadow` takes a
447
+ `vec3(u, v, refZ)`): HLSLcc builds a temp,
448
+ ```glsl
449
+ vec3 txVec<N> = vec3(<coord>, <refZ>);
450
+ ```
451
+ (`:1264-1277`; `depthCmpCoordType` is `"vec2"`/`"vec3"`/`"vec4"` per dimension,
452
+ `:1176,1191,1206,1217,1228,1235`), then samples `texture(<sampler>, txVec<N>)`.
453
+ The temp exists "as Adrenos hate nonstandard swizzles in the texcoords" (source comment,
454
+ `:1269`).
455
+ - For `TEXTURECUBEARRAY` (no `depthCmpCoordType` case defined) the reference is passed as a
456
+ separate trailing argument instead (`:1349-1354`), matching GLSL's
457
+ `texture(samplerCubeArrayShadow, vec4(dir, arrayIdx), refZ)` signature, which has no room
458
+ in the coordinate vector for both array index and reference.
459
+ - Function name is plain `funcName` (`"texture"`, or `"textureLod"`/`"textureGrad"` variants
460
+ per other combined flags) — depth-compare does **not** change the function name, only the
461
+ sampler *type* (`sampler2DShadow` etc., chosen at declaration time from `ui32IsShadowTex`)
462
+ and the coordinate/argument shape.
463
+ - `ResourceName`/`TextureSamplerName` calls pass `bZCompare = 1` when
464
+ `TEXSMP_FLAG_DEPTHCOMPARE` is set (`:1333,1335`). **Correction**: because
465
+ `HLSLCC_FLAG_COMBINE_TEXTURE_SAMPLERS` is always set for this fork (see section 0),
466
+ `useCombinedTextureSamplers` is always true, so the instruction body always takes the
467
+ `:1335` branch — `TextureSamplerName(..., bZCompare=1)` — never the `:1333` bare-
468
+ `ResourceName` branch. The declaration that actually matches what the instruction body
469
+ calls is therefore the **combined**-sampler shadow uniform emitted at
470
+ `toGLSLDeclaration.cpp:1634-1646` (`uniform <samplerType>Shadow <TextureSamplerName(...,
471
+ bZCompare=1)>`), **not** `toGLSLDeclaration.cpp:1668-1686` as an earlier draft of this spec
472
+ claimed — that `1668-1686` block declares the *bare* `ResourceName`-based shadow uniform
473
+ (`hlslcc_zcmp`-prefixed plain `t#` symbol), which is emitted unconditionally alongside the
474
+ combined one (regardless of the combine flag) purely for register-identity duplication
475
+ (the same "declared but not referenced by instruction bodies" pattern section 0 already
476
+ documents for the non-shadow case) and is never the symbol the instruction body's call
477
+ resolves to. Concretely, a shader that both plain-samples and depth-compares the same
478
+ `t#` texture with this fork's always-on combine flag gets **four** declared GLSL sampler
479
+ uniforms for that texture (combined non-shadow `TEX_with_SMP` name, combined shadow
480
+ `hlslcc_zcmp...TEX_with_SMP` name, bare non-shadow `t#`/reflected name, bare shadow
481
+ `hlslcc_zcmp`+`t#`/reflected name — `toGLSLDeclaration.cpp:1632-1686`), of which only the
482
+ two combined ones are ever referenced by instruction bodies.
483
+
484
+ **Type rules**: destination is always float (D3D `SampleCmp` result type is float;
485
+ `ui32ReturnType`/`GetTextureDataType` is not consulted for the shadow path's numeric
486
+ result — it stays float because depth formats reflect as float). Reference value:
487
+ `TranslateOperand(psSrcRef, TO_AUTO_BITCAST_TO_FLOAT)` (`:1275,1353`) — always float.
488
+
489
+ **Helpers needed**: none beyond the `txVecN` embedding pattern (a per-call-site local
490
+ `vec2/vec3/vec4` temp, not a shared function — the JS emitter should replicate this
491
+ inline-temp pattern rather than centralizing it, to match HLSLcc's numbering scheme
492
+ `m_NextTexCoordTemp` used to keep temp names unique per shader phase, `:1267`).
493
+
494
+ **Edge cases**: see `sample_l`'s 2D-array-shadow `textureLod`-unavailable workaround —
495
+ that workaround is keyed off `TEXSMP_FLAG_DEPTHCOMPARE` and fires for `sample_c` combined
496
+ with an explicit-LOD or LOD-zero flag on a `TEXTURE2DARRAY` resource; plain `sample_c`
497
+ (implicit LOD) does not need it because ordinary `texture(sampler2DArrayShadow, ...)`
498
+ (no explicit LOD) is legal GLSL.
499
+
500
+ **WebGL2 notes**: `sampler2DShadow`/`sampler2DArrayShadow`/`samplerCubeShadow` and the
501
+ `texture()` shadow-comparison overloads are core GLSL ES 3.00. `samplerCubeArrayShadow`
502
+ requires cubemap-array support, which itself needs `GL_OES_texture_cube_map_array`/
503
+ `GL_EXT_texture_cube_map_array` on ES targets (`HaveCubemapArray` is false for
504
+ `LANG_ES_300`, `languages.h:75-80`; `TranslateResourceTexture` enables both OES and EXT
505
+ extension strings for ES languages when a cubemap array is declared,
506
+ `toGLSLDeclaration.cpp:1608-1619`) — so a `sample_c` against a `TextureCubeArray` shadow
507
+ resource depends on a WebGL2 extension actually being available at runtime, which is not
508
+ guaranteed.
509
+
510
+ **Confidence**: medium — the lowering path itself is fully and unambiguously read from
511
+ source; the medium (not high) rating is solely because the corpus has 0 real-world
512
+ instances to cross-check the reading against, per the task's own instruction to still
513
+ document it for depth-path completeness.
514
+
515
+ ---
516
+
517
+ ## 7. `sample_c_lz` (12,640 Frontier occurrences; 0 in the EVE corpus)
518
+
519
+ **Semantics**: Identical to `sample_c` except the LOD is forced to `0` (no derivative
520
+ computation) — D3D11 `SampleCmpLevelZero`. Used for shadow-map lookups from a
521
+ non-derivative-having context (frequently a loop-unrolled PCF kernel where the compiler
522
+ wants to guarantee LOD-0 regardless of control flow uniformity).
523
+
524
+ **GLSL lowering**: `TranslateTextureSample(psInst, TEXSMP_FLAG_DEPTHCOMPARE |
525
+ TEXSMP_FLAG_FIRSTLOD)` (`toGLSLInstruction.cpp:3191-3200`). `TEXSMP_FLAG_FIRSTLOD` alone
526
+ (without `TEXSMP_FLAG_LOD`) selects the `...Lod...` function-name branch
527
+ (`:1308`, condition includes `TEXSMP_FLAG_FIRSTLOD`) but supplies a **literal `0.0`**
528
+ argument instead of reading an LOD operand (`:1373-1377`, `bcatcstr(glsl, ", 0.0")`) —
529
+ there is no LOD source operand for this opcode at all (DXBC `sample_c_lz` has no LOD field
530
+ in its operand list; zero is implied by the opcode itself).
531
+ Template (non-2D-array-shadow case):
532
+
533
+ ```glsl
534
+ dest = textureLod(<sampler2DShadow>, vec3(<coord>, <refZ>), 0.0);
535
+ ```
536
+
537
+ - **2D-array shadow workaround applies here directly and unconditionally** for
538
+ `RESOURCE_DIMENSION_TEXTURE2DARRAY` (`needsLodWorkaround` is true whenever
539
+ dimension is 2D-array **and** depth-compare is set — true for both `sample_c` with an
540
+ explicit/zero LOD and, notably, always true for `sample_c_lz` combined with a
541
+ `TEXTURE2DARRAY` resource, which is an extremely common real-world case: cascaded shadow
542
+ maps stored as a `Texture2DArray`). In that case the emitted call becomes:
543
+ ```glsl
544
+ dest = textureGrad(<sampler2DArrayShadow>, txVecN, vec2(0.0,0.0), vec2(0.0,0.0));
545
+ ```
546
+ (`:1297-1300,1357-1360`) — the literal `0.0` LOD argument is dropped entirely in this
547
+ branch (`!needsLodWorkaroundES2` gate at `:1375-1377` only affects the ES-1.00 pixel-
548
+ shader case, irrelevant here).
549
+ - A Nintendo-Switch-specific override exists (`:1287-1295`) that reverts this workaround
550
+ back to a plain fetch for `sample_c_lz` specifically, because `textureGrad` on shadow
551
+ samplers is software-emulated (slow) on that platform; this branch is gated by
552
+ `psContext->IsSwitch()` and never applies to this WebGL2-only target.
553
+
554
+ **Type rules**: same as `sample_c` (float result, float reference operand).
555
+
556
+ **Helpers needed**: none (same `txVecN` inline-temp pattern as `sample_c`).
557
+
558
+ **Edge cases**: because the LOD-zero constant is baked in at translation time (not read
559
+ from a register), there is no NaN/Inf risk from the LOD argument itself. The 2D-array
560
+ `textureGrad`-with-zero-gradients substitution is an *approximation* — it does not
561
+ guarantee literal mip level 0 selection the way a true `textureLod(..., 0.0)` would if
562
+ that overload existed; zero gradients make the implementation compute the coarsest
563
+ (or a very fine) LOD depending on its `dFdx`/`dFdy`-from-zero handling, which is
564
+ implementation-defined at the edges but converges to LOD 0 in practice on all GLES/ANGLE
565
+ implementations observed by upstream HLSLcc (this is HLSLcc's own accepted tradeoff;
566
+ Frontier's observed comparison samples are all 2D and therefore do not exercise it).
567
+
568
+ **WebGL2 notes**: identical to `sample_c`'s notes; the `textureGrad`-for-shadow-2D-array
569
+ substitution is specifically a WebGL2/GLSL-ES-3.00 accommodation (GLSL ES 3.00 core simply
570
+ has no `textureLod` overload for `sampler2DArrayShadow` — same restriction exists in
571
+ desktop GLSL, this is not an ES-only gap).
572
+
573
+ **Corpus evidence**: the cached Frontier Stillness high-tier catalog from 2026-07-10
574
+ contains 12,640 instructions across 784 unique pixel shaders and 99 effects. Every
575
+ observed instruction targets `texture2d`; no affected shader samples the same resource
576
+ through both comparison and ordinary filtered operations. The EVE build-3430261
577
+ high-tier corpus contains no `sample_c` or `sample_c_lz` instructions.
578
+
579
+ **Confidence**: high for the observed `texture2d` path — the full Frontier corpus emits,
580
+ and representative StandardPBR and QuadV5 programs compile/link in Chromium WebGL2.
581
+ Synthetic 2D, cube, and 2D-array shadow variants also compile/link; cube/array remain
582
+ lower-confidence semantically because Frontier does not exercise those dimensions.
583
+
584
+ ---
585
+
586
+ ## 8. `ld` (667 occurrences)
587
+
588
+ **Semantics**: Direct, unfiltered texel fetch by **integer** coordinates plus an explicit
589
+ integer mip level — no sampler object involved at all (samplerless access, confirmed by
590
+ `decisions/005-samplerless-texture-access-evidence-2026-06-26.md`). D3D11 `LD`: `dest,
591
+ address(int), resource[, swizzle]`, where `address.w` (or `.a`) holds the mip level for
592
+ non-multisample resources.
593
+
594
+ **GLSL lowering**: dispatches through the shared `OPCODE_LD`/`OPCODE_LD_MS` case
595
+ (`toGLSLInstruction.cpp:4092-4109`) into `TranslateTexelFetch`
596
+ (`:819-980`). Per-dimension templates (`:896-976`):
597
+
598
+ | Resource dimension | Template |
599
+ |---|---|
600
+ | `TEXTURE1D` / `BUFFER` | `texelFetch(tex, int(coord.x), int(coord.w))` (buffer has no LOD arg) |
601
+ | `TEXTURE2D` / `TEXTURE1DARRAY` | `texelFetch(tex, ivec2(coord.xy), int(coord.w))` |
602
+ | `TEXTURE2DARRAY` / `TEXTURE3D` | `texelFetch(tex, ivec3(coord.xyz), int(coord.w))` |
603
+ | `TEXTURE2DMS` (`ld_ms` only) | `texelFetch(tex, ivec2(coord.xy), int(sampleIndexOperand.x))` — 3rd instruction operand, not `.w` |
604
+ | `TEXTURE2DMSARRAY` (`ld_ms`) | `texelFetch(tex, ivec3(coord.xyz), int(sampleIndexOperand.x))` |
605
+ | Cube / CubeArray / BufferEx | not possible in HLSL or GLSL — `ASSERT(0)` (`:970-974`) |
606
+
607
+ - `hasOffset` (`psInst->bAddressOffset`) swaps in `texelFetchOffset(...)` with a trailing
608
+ `ivec2`/`ivec3`/int offset literal (`:875,891-894,909-911,923-926,938-941`).
609
+ - **Source bug — malformed offset literal for `TEXTURE2D`/`TEXTURE2DARRAY`**: the
610
+ `TEXTURE1D` (`:909-910`, plain `int`) and `TEXTURE3D` (`:923-924`, `ivec3(%d, %d, %d)`,
611
+ 3 args) offset literals are well-formed, but the `TEXTURE2DARRAY` (`:925-926`) and
612
+ `TEXTURE2D` (`:940-941`) cases both emit the literal text `ivec3(%d, %d)` —
613
+ labeled `ivec3` but with only **two** `%d` substitutions
614
+ (`bformata(glsl, ", ivec3(%d, %d)", psInst->iUAddrOffset, psInst->iVAddrOffset)`).
615
+ `ivec3(3, 5)` is not a legal GLSL constructor call (an `ivec3` needs 3 components, or a
616
+ single scalar to splat, not 2), so this is invalid GLSL that will fail to compile — this
617
+ looks like a copy-paste typo in the vendor source (should read `ivec2`, matching the
618
+ correct 2-arg, 2D offset shape used elsewhere, e.g. `sample`'s own offset-building code at
619
+ `toGLSLInstruction.cpp:1398-1402` which correctly emits `ivec2(%d, %d)` for 2 offsets).
620
+ Any Carbon effect that uses an immediate texel offset (`sample_controls`) on a `ld`/`ld_ms`
621
+ against a `Texture2D` or `Texture2DArray` resource will hit this bug and get
622
+ non-compiling GLSL; `Texture1D`/`Texture3D`/`Texture1DArray` immediate-offset `ld` is
623
+ unaffected. This is a real upstream HLSLcc defect (ground truth wins per this review's
624
+ brief), not a misreading — the JS emitter must special-case these two dimensions to emit
625
+ `ivec2` instead of replicating the literal `ivec3` text.
626
+ - Coordinates are read `TO_FLAG_INTEGER | TO_AUTO_EXPAND_TO_VEC2/VEC3` (`:903,920,935,950,961`)
627
+ — **integer**, not the `TO_AUTO_BITCAST_TO_FLOAT` used by filtered sampling; the mip/LOD
628
+ (or sample index for MS) is separately read with plain `TO_FLAG_INTEGER` on the `.w`/`.a`
629
+ mask or the dedicated 4th operand.
630
+ - Return-channel swizzle is applied the same way as `sample` (via the texture operand's
631
+ own swizzle re-enabled and masked by the destination access mask, `:978`).
632
+ - On Vulkan the texture name is wrapped `<samplerType>(<tex>, <dummySampler>)` because
633
+ SPIR-V requires every texel fetch to go through a combined-image-sampler even when HLSL
634
+ had none (`:872-886`) — **not applicable** to this non-Vulkan WebGL2 target; plain
635
+ `texelFetch(tex, ...)` is used directly with no dummy sampler wrapper.
636
+
637
+ **Type rules**: destination type comes from the (possibly synthesized-fallback) resource
638
+ binding's `ui32ReturnType` via `ResourceReturnTypeToFlag`/`TypeFlagsToSVTType`
639
+ (`:889`), defaulting to `RETURN_TYPE_FLOAT` when unreflected
640
+ (`fallbackBinding.ui32ReturnType = RETURN_TYPE_FLOAT`, `:830`) — this is the exact
641
+ `CARBONENGINEJS-FORK.md` "samplerless texture fetches synthesize a fallback resource
642
+ binding from `SHEX` declaration data" behavior. Coordinate and mip/sample-index operands
643
+ are always read as true integers (`TO_FLAG_INTEGER`), never bitcast-to-float — this is the
644
+ one texture opcode family where address components are genuinely integer, not
645
+ float-reinterpreted-as-address.
646
+
647
+ **Helpers needed**: none — `texelFetch`/`texelFetchOffset` are core GLSL ES 3.00 builtins
648
+ for all the dimensions DXBC `ld` legally targets.
649
+
650
+ **Edge cases**: out-of-range integer coordinates or mip level return `vec4(0)` per GLSL ES
651
+ 3.00 spec (well-defined, unlike desktop GL's implementation-defined behavior in some
652
+ older versions) — no clamp needed from the translator. Buffer resources have no mip/LOD
653
+ argument at all (`:904-911` conditionally omits it).
654
+
655
+ **WebGL2 notes**: fully native, no gaps. This is one of the safest opcodes in the family
656
+ for WebGL2 portability.
657
+
658
+ **Confidence**: high — `TranslateTexelFetch` fully read, dimension table directly
659
+ transcribed from source, and its `SHEX`-fallback path is independently corroborated by
660
+ `CARBONENGINEJS-FORK.md`.
661
+
662
+ ---
663
+
664
+ ## 9. `resinfo` (247 occurrences)
665
+
666
+ **Semantics**: Queries a resource's dimensions (width/height/depth-or-array-size) at a
667
+ given mip level, plus the resource's total mip-chain length, with a caller-selectable
668
+ return-type encoding (float / reciprocal-float / uint) — D3D11 `RESINFO`, decoded
669
+ return-type control per `decisions/016...018-dxbc-instruction-controls...md`: bits 11-12 of
670
+ the opcode token, `0` = float, `1` = reciprocal float, `2` = uint
671
+ (`RESINFO_INSTRUCTION_RETURN_{FLOAT,RCPFLOAT,UINT}`).
672
+
673
+ **GLSL lowering**: dispatch loop iterates the destination write mask, calling
674
+ `GetResInfoData(psInst, swizzledComponentIndex, destElem)` once per live destination
675
+ component (`toGLSLInstruction.cpp:4734-4752`), implementation at `:1033-1129`:
676
+
677
+ - For `index` (post-swizzle component index) `< 3` — width/height/depth-or-arraysize:
678
+ ```glsl
679
+ dest.<comp> = <returnCast>( <maybe 1.0/> textureSize(<tex>[, int(mipOperand)]) [.x|.y|.z] );
680
+ ```
681
+ - `dim = GetNumTextureDimensions(eResDim)` (`HLSLccToolkit.cpp:437-458`: 1 for
682
+ `TEXTURE1D`; 2 for `TEXTURE2D`/`TEXTURE2DMS`/`TEXTURE1DARRAY`/`TEXTURECUBE`; 3 for
683
+ `TEXTURE3D`/`TEXTURE2DARRAY`/`TEXTURE2DMSARRAY`/`TEXTURECUBEARRAY`).
684
+ - If the requested `index` exceeds `dim` (e.g. asking for `.z` on a 2D texture), the
685
+ literal constant `0`/`0.0`/`uint(0)` is emitted instead of calling `textureSize`
686
+ (`:1064-1067`) — the source comment notes `0u` is mistreated as a const-int by "old
687
+ ES3.0 Adrenos", hence `uint(0)` is spelled out rather than a bare `0u`.
688
+ - Return-type wrapping: `RESINFO_INSTRUCTION_RETURN_UINT` → `uvec<dim>(...)` (or
689
+ `ivec<dim>` if `HaveUnsignedTypes` is false, not applicable to ES 3.00);
690
+ `RESINFO_INSTRUCTION_RETURN_RCPFLOAT` → `vec<dim>(1.0) / vec<dim>(textureSize(...))`;
691
+ else → `vec<dim>(textureSize(...))` (`:1070-1080`).
692
+ - MS resources (`isMS`) and UAVs (`isUAV`) omit the mip-level argument to
693
+ `textureSize`/`imageSize` entirely (`:1089-1093`; UAV uses `imageSize` instead of
694
+ `textureSize`, `:1082-1085` — not reachable via the pure `t#` `resinfo` path this family
695
+ documents, but present in the same function since `resinfo` can also target a UAV).
696
+ - For `index >= 3` (total mip-level count):
697
+ ```glsl
698
+ dest.w = <int|uint|float>(textureQueryLevels(<tex>));
699
+ ```
700
+ (`:1112-1127`) — **unconditionally emitted with no target-language capability check**
701
+ (see WebGL2 notes below).
702
+
703
+ **Type rules**: each live destination component is assigned independently via
704
+ `AddOpAssignToDestWithMask(..., eResInfoReturnType == RESINFO_INSTRUCTION_RETURN_UINT ?
705
+ SVT_UINT : SVT_FLOAT, 1, ..., 1 << destElem)` (`:1057`) — i.e. `resinfo`'s destination type
706
+ per-component is uint only for the UINT return-type control, float for both FLOAT and
707
+ RCPFLOAT controls (RCPFLOAT is still a float result, just the reciprocal).
708
+
709
+ **Helpers needed**: `hlslcc_textureQueryLevels` (**mandatory for the 4th/mip-count
710
+ component on this target** — see WebGL2 notes). No helper needed for the width/height/
711
+ depth components (`textureSize` is fully core).
712
+
713
+ **Edge cases**: `textureSize` with an out-of-range `lod` argument returns `0` per GLSL ES
714
+ 3.00 spec (well-defined). Buffer/`BUFFEX` resources are excluded from the `dim==0` default
715
+ path implicitly by never appearing in the `resinfo`-legal dimension set.
716
+
717
+ **WebGL2 notes — gap on the mip-count component**: `languages.h:247-254` defines
718
+ `HaveQueryLevels(eLang)` true only for `eLang >= LANG_430`. `LANG_ES_300` does **not**
719
+ qualify, and `GetResInfoData`'s `index >= 3` branch (`:1112-1127`) itself calls
720
+ `textureQueryLevels(...)` unconditionally, with no local `HaveQueryLevels` gate. **Correction**:
721
+ an earlier draft of this spec said this was "no gate at all" and contrasted it with `lod`'s
722
+ `HaveQueryLod` as the supposed sole gated case in the family — that contrast overstates the
723
+ difference. `toGLSL.cpp`'s `AddVersionDependentCode` **does** check `HaveQueryLevels`
724
+ (`:234-241`): `if (!HaveQueryLevels(eLang)) { if (OPCODE_RESINFO used)
725
+ { EnableExtension("GL_ARB_texture_query_levels"); EnableExtension("GL_ARB_shader_image_size"); } }`.
726
+ As with `gather4`'s analogous `GL_ARB_texture_gather` attempt above, both are desktop-only
727
+ ARB extension tokens behind an `EnableExtension` `#ifdef` guard that is never true under a
728
+ GLSL ES/WebGL2 preprocessor, so on `LANG_ES_300` this is a no-op in practice: no
729
+ `#extension` line is emitted and `GetResInfoData`'s call site is unaffected. The practical
730
+ conclusion is therefore unchanged — `textureQueryLevels` does not exist in GLSL ES 3.00
731
+ core, has no WebGL2-exposed extension equivalent, and any Carbon effect that reads
732
+ `resinfo`'s 4th (`.w`, "total mip count") component on this target will receive GLSL that
733
+ fails to compile. Given 247 corpus occurrences of `resinfo`, this is a second actionable,
734
+ high-value gap for this spec (alongside `gather4`'s `textureGather` gap) — the emitter must
735
+ substitute `hlslcc_textureQueryLevels` (fallback strategy: accept the mip count as an
736
+ out-of-band uniform per texture, since there is no in-shader WebGL2-legal way to query it;
737
+ or hard-fail translation if this exact component is read). The first-3-components path
738
+ (`textureSize`) has no such gap.
739
+
740
+ **Confidence**: high on what HLSLcc emits (fully read, unambiguous, including the
741
+ `toGLSL.cpp` extension-attempt correction above), but explicitly flagged as a real WebGL2
742
+ compile-time risk for the mip-count component. The general opcode was previously described
743
+ as "corroborated by `TRANSPILING-GAPS.md:186,337`" — that file does not exist in this
744
+ repository (see the authority-order correction at the top of this document), so that
745
+ corroboration claim is withdrawn; this section's confidence now rests solely on the C++
746
+ reading above.
747
+
748
+ ---
749
+
750
+ ## 10. `deriv_rtx_coarse` (209) and `deriv_rty_coarse` (215)
751
+ ### (plus `deriv_rtx`/`deriv_rtx_fine`/`deriv_rty`/`deriv_rty_fine`, same lowering)
752
+
753
+ **Semantics**: Screen-space partial derivative of the source value with respect to
754
+ window-space X (`rtx`) or Y (`rty`). D3D11 distinguishes `_coarse` (may share a derivative
755
+ across a 2x2 quad, cheaper) from `_fine` (per-pixel) and from the plain (compiler's choice)
756
+ form, but **GLSL only exposes one derivative pair** (`dFdx`/`dFdy`), with precision
757
+ controlled by an optional `GL_OES_standard_derivatives`-style hint, not a distinct
758
+ coarse/fine builtin.
759
+
760
+ **GLSL lowering**: all six coarse/fine/plain DERIV opcodes collapse onto the same two
761
+ `case` blocks (`toGLSLInstruction.cpp:4579-4602`):
762
+ ```glsl
763
+ dest = dFdx(src)<destSwizzleSubset>; // DERIV_RTX_COARSE / DERIV_RTX_FINE / DERIV_RTX
764
+ dest = dFdy(src)<destSwizzleSubset>; // DERIV_RTY_COARSE / DERIV_RTY_FINE / DERIV_RTY
765
+ ```
766
+ via `CallHelper1("dFdx", psInst, 0, 1, 1)` / `CallHelper1("dFdy", psInst, 0, 1, 1)`.
767
+ `CallHelper1` (`:745-762`): destination is assigned `SVT_FLOAT` with the destination's own
768
+ swizzle-element count (`AddAssignToDest(dest, SVT_FLOAT, dstSwizCount, ...)`), the call is
769
+ `name(TranslateOperand(src0, TO_AUTO_BITCAST_TO_FLOAT, destMask))` where `destMask` is the
770
+ destination's own access mask (the 4th `CallHelper1` argument, `paramsShouldFollowWriteMask
771
+ = 1`, restricts the source read to the same components being written).
772
+
773
+ **Type rules**: always float in and float out; source is
774
+ `TO_AUTO_BITCAST_TO_FLOAT` (reinterpret as float if the register was produced as int/uint).
775
+ No int/uint derivative form exists in DXBC or GLSL.
776
+
777
+ **Helpers needed**: none — `dFdx`/`dFdy` are core GLSL ES 3.00 builtins (fragment-shader
778
+ only).
779
+
780
+ **Edge cases**: derivatives are **fragment-shader only** — DXBC guarantees `deriv_*` never
781
+ appears in a vertex shader (no rasterization quad exists there), so no stage guard is
782
+ needed beyond what DXBC itself enforces. Derivatives across non-uniform control flow
783
+ (diverging discard/branch within a 2x2 quad) are undefined-ish in both D3D and GLES —
784
+ HLSLcc adds no special handling; this is an inherent GPU behavior difference the
785
+ translator cannot paper over.
786
+
787
+ **WebGL2 notes**: `dFdx`/`dFdy` (and `fwidth`) are **core, unconditional** in GLSL ES 3.00
788
+ (unlike GLSL ES 1.00/WebGL1, where they required the `GL_OES_standard_derivatives`
789
+ extension — irrelevant here since target is ES 3.00). The coarse/fine distinction is
790
+ simply lost/unified; there is no GLSL ES 3.00 way to request coarse-only derivatives, so
791
+ `_fine` and `_coarse` and plain forms are indistinguishable in the output, matching
792
+ upstream HLSLcc behavior exactly (not a gap this project introduces).
793
+
794
+ **Confidence**: high — trivial, fully read, single-line-per-opcode lowering, high corpus
795
+ count (209+215 combined for the `_coarse` variants alone).
796
+
797
+ ---
798
+
799
+ ## 11. `lod` (0 occurrences in corpus, spec required per task)
800
+
801
+ **Semantics**: Computes the LOD the hardware *would* select for a given sample (both the
802
+ "clamped" and "unclamped" values), without actually sampling — D3D11 `LOD`: result is
803
+ `(ClampedLOD, NonClampedLOD, 0, 0)`. Distinct from every `sample_*` opcode: this one never
804
+ fetches texels.
805
+
806
+ **GLSL lowering**: `toGLSLInstruction.cpp:4161-4200`.
807
+ ```glsl
808
+ dest = textureQueryLod(<tex>, <coord>)<returnSwizzle>; // core-language name, LANG>=400
809
+ // or
810
+ dest = textureQueryLOD(<tex>, <coord>)<returnSwizzle>; // extension name, otherwise
811
+ ```
812
+ Function-name casing is chosen by `HaveQueryLod(eLang)` (`languages.h:238-245`, true only
813
+ for `eLang >= LANG_400`) — this is the one check in this family that gates a **function
814
+ name** directly inside the instruction-lowering switch itself (`gather4`'s `HaveGather` and
815
+ `resinfo`'s `HaveQueryLevels` are instead checked separately in `toGLSL.cpp`'s
816
+ `AddVersionDependentCode`, purely to attempt an `EnableExtension` pragma — see the
817
+ corrections in those sections above; `lod` gets an analogous, equally ES-3.00-ineffective
818
+ `EnableExtension("GL_ARB_texture_query_lod")` attempt there too, gated on the same
819
+ `!HaveQueryLod` check, at `toGLSL.cpp:226-231`).
820
+
821
+ **Correction — resource-name resolution bypasses the combined-sampler ABI**: unlike every
822
+ other opcode in this family, the texture operand here (`psInst->asOperands[2]`) is emitted
823
+ via a plain `TranslateOperand(&psInst->asOperands[2], TO_FLAG_NONE)` call (`:4185`), which
824
+ for an `OPERAND_TYPE_RESOURCE` operand routes into `toGLSLOperand.cpp:1271-1275`'s
825
+ `case OPERAND_TYPE_RESOURCE: ResourceName(glsl, psContext, RGROUP_TEXTURE,
826
+ psOperand->ui32RegisterNumber, 0);` — the **bare** `ResourceName` path, not
827
+ `TextureSamplerName`. `lod` never touches its sampler operand
828
+ (`psInst->asOperands[3]`) at all. So even though `HLSLCC_FLAG_COMBINE_TEXTURE_SAMPLERS` is
829
+ always on for this fork, `lod` always references the bare, non-combined `t#`/reflected-name
830
+ sampler uniform (the one declared unconditionally at `toGLSLDeclaration.cpp:1659-1666`) —
831
+ a *different* GLSL uniform than the combined `TEX_with_SMP...` name that `sample`/
832
+ `sample_l`/etc. use to read the same DXBC texture register. This is architecturally
833
+ consistent (`textureQueryLod` only needs one sampler object, and a real one is always
834
+ declared), but it means `lod` is the one opcode in this family that does not follow
835
+ section 0's combined-sampler naming rule, which the earlier draft of this section did not
836
+ call out at all. Coordinates use the same `TranslateTexCoord` per-dimension selection as
837
+ `sample` (`:4187-4189`). Return-channel swizzle applied the same way as other texture ops
838
+ (`:4194-4197`).
839
+
840
+ **Type rules**: destination is always `SVT_FLOAT`, 4 components (`AddAssignToDest(dest,
841
+ SVT_FLOAT, 4, ...)`, `:4171`) — DXBC `lod` always produces float regardless of the
842
+ resource's reflected return type (this is a query result, not a texel fetch).
843
+
844
+ **Helpers needed**: `hlslcc_textureQueryLod` fallback (see WebGL2 notes — `LANG_ES_300`
845
+ falls into the `textureQueryLOD` extension-name branch, but that extension is not part of
846
+ WebGL2's guaranteed baseline).
847
+
848
+ **Edge cases**: none beyond standard coordinate range handling; no fetch occurs so no
849
+ wrap/border-color interaction applies.
850
+
851
+ **WebGL2 notes — gap**: `HaveQueryLod(LANG_ES_300)` is false, so HLSLcc emits the
852
+ extension-style name `textureQueryLOD(...)`. That name corresponds to
853
+ `GL_ARB_texture_query_lod` (desktop) / `GL_EXT_texture_query_lod`-equivalent — **not** a
854
+ function that exists in unextended WebGL2/GLSL ES 3.00, and no such extension is part of
855
+ the WebGL2 core extension set exposed by browsers. Since the corpus shows 0 real usages,
856
+ this is a low-priority gap in practice, but the emitter should either detect `lod`
857
+ opcode usage and fail translation explicitly, or provide a CPU-side/uniform-supplied LOD
858
+ approximation, rather than emit a GLSL call that will not link on any real WebGL2
859
+ implementation.
860
+
861
+ **Confidence**: low — zero corpus occurrences means this reading has no cross-check
862
+ against real Carbon effect output; the source reading itself (line-cited above) is
863
+ unambiguous, but "will this ever actually appear" is unverified.
864
+
865
+ ---
866
+
867
+ ## Coordinate component selection per resource dimension
868
+
869
+ Ground truth: `TranslateTexCoord` (`toGLSLInstruction.cpp:985-1031`), used by every
870
+ filtered-sample opcode (`sample`, `sample_l`, `sample_b`, `sample_d`, `sample_c`,
871
+ `sample_c_lz`, `gather4*`, `lod`) — **not** used by `ld`/`ld_ms` (integer path, see
872
+ `TranslateTexelFetch` table above) or `resinfo` (no coordinate operand for width/height
873
+ query; only an optional mip-level scalar).
874
+
875
+ | `eResDim` | Access mask kept | Auto-expand | Meaning of DXBC address components |
876
+ |---|---|---|---|
877
+ | `TEXTURE1D` | `.x` only | none (scalar) | `x` = u |
878
+ | `TEXTURE2D` | `.xy` | `vec2` | `xy` = (u, v) |
879
+ | `TEXTURE1DARRAY` | `.xy` | `vec2` | `x` = u, `y` = array slice |
880
+ | `TEXTURECUBE` | `.xyz` | `vec3` | `xyz` = direction vector |
881
+ | `TEXTURE3D` | `.xyz` | `vec3` | `xyz` = (u, v, w) |
882
+ | `TEXTURE2DARRAY` | `.xyz` | `vec3` | `xy` = (u, v), `z` = array slice |
883
+ | `TEXTURECUBEARRAY` | all 4 (`.xyzw`) | `vec4` | `xyz` = direction, `w` = array slice |
884
+
885
+ All texcoord operands are read with `TO_AUTO_BITCAST_TO_FLOAT` (`:989`) regardless of
886
+ dimension — the address register is always treated as float data (reinterpreted via
887
+ `intBitsToFloat`/`uintBitsToFloat` if it was produced by an integer-typed instruction),
888
+ consistent with DXBC's convention that sample-instruction addresses are always float even
889
+ though the same register file backs int/uint temps.
890
+
891
+ The depth-compare embedded-coordinate type (`depthCmpCoordType`, one dimension wider than
892
+ the plain coordinate type to make room for the reference value) is: `TEXTURE1D` → `vec2`,
893
+ `TEXTURE2D`/`TEXTURE1DARRAY` → `vec3`, `TEXTURECUBE`/`TEXTURE3D`/`TEXTURE2DARRAY` → `vec4`,
894
+ `TEXTURECUBEARRAY` → none (reference passed as a separate trailing argument instead, see
895
+ `sample_c` above).
896
+
897
+ ---
898
+
899
+ ## `sample_controls` extension: immediate texel offsets
900
+
901
+ Ground truth: `decisions/016-018-dxbc-instruction-controls-used-by-the-gles-draft-transpiler.md`
902
+ plus `toGLSLInstruction.cpp:1161-1170,1391-1423`.
903
+
904
+ - The extended-opcode-token field `sample_controls` (bits 0-5 of an extended token,
905
+ decoded per Microsoft DXC's `d3d12TokenizedProgramFormat.hpp`) carries three signed
906
+ immediate texel-offset values (U/V/W, each roughly -8..+7) attached to a `sample*`/`ld*`/
907
+ `gather4*` instruction. HLSLcc surfaces this as `psInst->bAddressOffset` +
908
+ `iUAddrOffset`/`iVAddrOffset`/`iWAddrOffset` on the `Instruction` struct.
909
+ - When present, every texture-op template in this family appends `"Offset"` to the GLSL
910
+ function name (`texture` → `textureOffset`, `textureLod` → `textureLodOffset`,
911
+ `textureGrad` → `textureGradOffset`, `texelFetch` → `texelFetchOffset`,
912
+ `textureGather` → `textureGatherOffset`) and appends a trailing integer/`ivecN` literal
913
+ argument built directly from the decoded immediate offset values — **not** a runtime
914
+ register read, since D3D11 requires these offsets to be compile-time immediates.
915
+ - `gather4_po`/`gather4_po_c` instead carry a **programmable** (runtime, register-valued)
916
+ offset via a distinct extra source operand (`psSrcOff`), read with
917
+ `TO_FLAG_INTEGER` and appended as a comma-separated argument rather than a function-name
918
+ suffix — this is the one offset form in the family that is not a literal.
919
+ - `TRANSPILING-GAPS.md:338` ("Already handled"): "DXBC sample offsets lower to WebGL2
920
+ offset texture calls where resource dimension supports it" — corroborates the
921
+ above from the draft-transpiler side.
922
+ - WebGL2 note: `textureOffset`/`textureLodOffset`/`textureGradOffset`/`texelFetchOffset`
923
+ are all core GLSL ES 3.00 (§8.9 of the spec) and impose a **compile-time-constant**
924
+ offset requirement — the offset argument must be a constant expression, matching DXBC's
925
+ own immediate-only restriction, so no runtime-variable-offset gap exists for the literal
926
+ (`bAddressOffset`) path. `textureGatherOffset` shares the `HaveGather`-gap noted for
927
+ `gather4` above (ES 3.00 does not have it at all, regardless of constant-offset support).
928
+
929
+ ---
930
+
931
+ ## Helpers summary
932
+
933
+ Helpers this family requires the JS emitter to define (native GLSL ES 3.00 builtins used
934
+ directly — `texture`, `textureLod`, `textureGrad`, `texelFetch`, `textureSize`,
935
+ `textureOffset`/`textureLodOffset`/`textureGradOffset`/`texelFetchOffset`, `dFdx`, `dFdy`,
936
+ `floatBitsToInt`/`floatBitsToUint`/`intBitsToFloat`/`uintBitsToFloat` — are **not** listed
937
+ here since they need no custom implementation):
938
+
939
+ 1. **`hlslcc_textureGather4Emulated`** — mandatory. `textureGather`/`textureGatherOffset`
940
+ do not exist in GLSL ES 3.00 (`HaveGather` excludes `LANG_ES_300`,
941
+ `languages.h:220-227`), yet HLSLcc's GLSL backend emits them unconditionally for
942
+ `gather4`/`gather4_po`/`gather4_po_c`/`gather4_c`. Must emulate via four texel taps
943
+ (computed from `textureSize`) replicating D3D `Gather4`'s neighbor-selection order.
944
+ Needed for both the plain-channel-gather and (separately, since component selection is
945
+ unsupported there per source) the depth-compare gather variants.
946
+ 2. **`hlslcc_textureQueryLevels`** — mandatory for `resinfo`'s 4th (mip-count) destination
947
+ component only. `textureQueryLevels` requires `LANG >= 430`
948
+ (`languages.h:247-254`) but `GetResInfoData` calls it with **no gate at all**
949
+ (`toGLSLInstruction.cpp:1112-1127`). No WebGL2/ES-3.00 extension provides an equivalent;
950
+ fallback strategy (out-of-band uniform, or explicit translation failure on this specific
951
+ component) must be decided by the emitter.
952
+ 3. **`hlslcc_textureQueryLod`** — needed only if the (zero-occurrence-so-far) `lod` opcode
953
+ is ever exercised. `HaveQueryLod` correctly excludes `LANG_ES_300`
954
+ (`languages.h:238-245`), and the resulting `textureQueryLOD(...)` extension name has no
955
+ WebGL2-guaranteed equivalent either. Lowest priority of the three, but same class of gap.
956
+ 4. **Depth-compare inline coordinate temp (`txVec<N>`)** — not a shared function, but a
957
+ per-call-site codegen pattern (`vecK txVecN = vecK(coord, refZ);`) the emitter must
958
+ replicate for `sample_c`/`sample_c_lz`/`gather4_po_c` on non-cube-array dimensions,
959
+ using a monotonically increasing per-phase counter matching HLSLcc's
960
+ `m_NextTexCoordTemp` (`toGLSLInstruction.cpp:1267`) to keep temp names collision-free.
961
+
962
+ No helper is needed for `sample`, `sample_l`, `sample_b`, `sample_d`, `ld`, or the
963
+ `deriv_rtx*`/`deriv_rty*` family — all of those map directly onto unconditional GLSL ES
964
+ 3.00 core builtins.