@carbonenginejs/runtime-resource 0.10.0 → 0.11.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 (254) hide show
  1. package/dist/format/effect/effectPermutationGraph.js +257 -0
  2. package/dist/format/effect/effectPermutationGraph.js.map +1 -0
  3. package/dist/format/effect/effectReflectionPackage.js +636 -0
  4. package/dist/format/effect/effectReflectionPackage.js.map +1 -0
  5. package/dist/format/effect/sha256.js +114 -0
  6. package/dist/format/effect/sha256.js.map +1 -0
  7. package/dist/formats/dxbc/CjsDxbcFormat.js +142 -0
  8. package/dist/formats/dxbc/CjsDxbcFormat.js.map +1 -0
  9. package/dist/formats/dxbc/core/DxbcReader.js +266 -0
  10. package/dist/formats/dxbc/core/DxbcReader.js.map +1 -0
  11. package/dist/formats/dxbc/core/container.js +169 -0
  12. package/dist/formats/dxbc/core/container.js.map +1 -0
  13. package/dist/formats/dxbc/core/decoder.js +789 -0
  14. package/dist/formats/dxbc/core/decoder.js.map +1 -0
  15. package/dist/formats/dxbc/core/errors.js +19 -0
  16. package/dist/formats/dxbc/core/errors.js.map +1 -0
  17. package/dist/formats/dxbc/core/helpers.js +220 -0
  18. package/dist/formats/dxbc/core/helpers.js.map +1 -0
  19. package/dist/formats/dxbc/core/opcodes.js +45 -0
  20. package/dist/formats/dxbc/core/opcodes.js.map +1 -0
  21. package/dist/formats/dxbc/core/program.js +91 -0
  22. package/dist/formats/dxbc/core/program.js.map +1 -0
  23. package/dist/formats/dxbc/core/signature.js +172 -0
  24. package/dist/formats/dxbc/core/signature.js.map +1 -0
  25. package/dist/formats/dxbc/index.js +2 -0
  26. package/dist/formats/dxbc/index.js.map +1 -0
  27. package/dist/formats/hlsl/CjsHlslFormat.js +254 -0
  28. package/dist/formats/hlsl/CjsHlslFormat.js.map +1 -0
  29. package/dist/formats/hlsl/core/HlslBinaryUtils.js +15 -0
  30. package/dist/formats/hlsl/core/HlslBinaryUtils.js.map +1 -0
  31. package/dist/formats/hlsl/core/HlslEffectReadError.js +19 -0
  32. package/dist/formats/hlsl/core/HlslEffectReadError.js.map +1 -0
  33. package/dist/formats/hlsl/core/HlslEffectStateManager.js +130 -0
  34. package/dist/formats/hlsl/core/HlslEffectStateManager.js.map +1 -0
  35. package/dist/formats/hlsl/core/HlslReader.js +266 -0
  36. package/dist/formats/hlsl/core/HlslReader.js.map +1 -0
  37. package/dist/formats/hlsl/core/HlslRenderStateSetup.js +37 -0
  38. package/dist/formats/hlsl/core/HlslRenderStateSetup.js.map +1 -0
  39. package/dist/formats/hlsl/core/HlslResourceSetDescription.js +94 -0
  40. package/dist/formats/hlsl/core/HlslResourceSetDescription.js.map +1 -0
  41. package/dist/formats/hlsl/core/HlslShaderBytecode.js +44 -0
  42. package/dist/formats/hlsl/core/HlslShaderBytecode.js.map +1 -0
  43. package/dist/formats/hlsl/core/analysis.js +51 -0
  44. package/dist/formats/hlsl/core/analysis.js.map +1 -0
  45. package/dist/formats/hlsl/core/helpers.js +270 -0
  46. package/dist/formats/hlsl/core/helpers.js.map +1 -0
  47. package/dist/formats/hlsl/core/json.js +284 -0
  48. package/dist/formats/hlsl/core/json.js.map +1 -0
  49. package/dist/formats/hlsl/core/metadata.js +327 -0
  50. package/dist/formats/hlsl/core/metadata.js.map +1 -0
  51. package/dist/formats/hlsl/core/portableReflection.js +848 -0
  52. package/dist/formats/hlsl/core/portableReflection.js.map +1 -0
  53. package/dist/formats/hlsl/core/render-states.js +280 -0
  54. package/dist/formats/hlsl/core/render-states.js.map +1 -0
  55. package/dist/formats/hlsl/core/tr2/HlslRenderContextEnum.js +43 -0
  56. package/dist/formats/hlsl/core/tr2/HlslRenderContextEnum.js.map +1 -0
  57. package/dist/formats/hlsl/core/tr2/resources/HlslEffectRes.js +294 -0
  58. package/dist/formats/hlsl/core/tr2/resources/HlslEffectRes.js.map +1 -0
  59. package/dist/formats/hlsl/core/tr2/resources/HlslShaderPermutation.js +33 -0
  60. package/dist/formats/hlsl/core/tr2/resources/HlslShaderPermutation.js.map +1 -0
  61. package/dist/formats/hlsl/core/tr2/shader/HlslEffectBindingManifest.js +416 -0
  62. package/dist/formats/hlsl/core/tr2/shader/HlslEffectBindingManifest.js.map +1 -0
  63. package/dist/formats/hlsl/core/tr2/shader/HlslEffectConstant.js +40 -0
  64. package/dist/formats/hlsl/core/tr2/shader/HlslEffectConstant.js.map +1 -0
  65. package/dist/formats/hlsl/core/tr2/shader/HlslEffectDescription.js +742 -0
  66. package/dist/formats/hlsl/core/tr2/shader/HlslEffectDescription.js.map +1 -0
  67. package/dist/formats/hlsl/core/tr2/shader/HlslEffectLibrary.js +52 -0
  68. package/dist/formats/hlsl/core/tr2/shader/HlslEffectLibrary.js.map +1 -0
  69. package/dist/formats/hlsl/core/tr2/shader/HlslEffectParameterAnnotation.js +38 -0
  70. package/dist/formats/hlsl/core/tr2/shader/HlslEffectParameterAnnotation.js.map +1 -0
  71. package/dist/formats/hlsl/core/tr2/shader/HlslEffectResource.js +50 -0
  72. package/dist/formats/hlsl/core/tr2/shader/HlslEffectResource.js.map +1 -0
  73. package/dist/formats/hlsl/core/tr2/shader/HlslEffectStageInput.js +86 -0
  74. package/dist/formats/hlsl/core/tr2/shader/HlslEffectStageInput.js.map +1 -0
  75. package/dist/formats/hlsl/core/tr2/shader/HlslEffectTechnique.js +31 -0
  76. package/dist/formats/hlsl/core/tr2/shader/HlslEffectTechnique.js.map +1 -0
  77. package/dist/formats/hlsl/core/tr2/shader/HlslPass.js +42 -0
  78. package/dist/formats/hlsl/core/tr2/shader/HlslPass.js.map +1 -0
  79. package/dist/formats/hlsl/core/tr2/shader/HlslSamplerDescription.js +55 -0
  80. package/dist/formats/hlsl/core/tr2/shader/HlslSamplerDescription.js.map +1 -0
  81. package/dist/formats/hlsl/core/tr2/shader/HlslSamplerSetup.js +29 -0
  82. package/dist/formats/hlsl/core/tr2/shader/HlslSamplerSetup.js.map +1 -0
  83. package/dist/formats/hlsl/core/tr2/shader/HlslShader.js +220 -0
  84. package/dist/formats/hlsl/core/tr2/shader/HlslShader.js.map +1 -0
  85. package/dist/formats/hlsl/core/tr2/shader/HlslShaderOption.js +30 -0
  86. package/dist/formats/hlsl/core/tr2/shader/HlslShaderOption.js.map +1 -0
  87. package/dist/formats/hlsl/index.js +2 -0
  88. package/dist/formats/hlsl/index.js.map +1 -0
  89. package/dist/formats/hlsl/portable.js +2 -0
  90. package/dist/formats/hlsl/portable.js.map +1 -0
  91. package/dist/formats/index.js +4 -0
  92. package/dist/formats/index.js.map +1 -1
  93. package/dist/formats/webgl/CjsWebglFormat.js +221 -0
  94. package/dist/formats/webgl/CjsWebglFormat.js.map +1 -0
  95. package/dist/formats/webgl/core/cewg/CewgPackage.js +339 -0
  96. package/dist/formats/webgl/core/cewg/CewgPackage.js.map +1 -0
  97. package/dist/formats/webgl/core/cewg/CewgPackageBuilder.js +104 -0
  98. package/dist/formats/webgl/core/cewg/CewgPackageBuilder.js.map +1 -0
  99. package/dist/formats/webgl/core/cewg/binary.js +101 -0
  100. package/dist/formats/webgl/core/cewg/binary.js.map +1 -0
  101. package/dist/formats/webgl/core/cewgCompleteness.js +81 -0
  102. package/dist/formats/webgl/core/cewgCompleteness.js.map +1 -0
  103. package/dist/formats/webgl/core/effectPackage.js +789 -0
  104. package/dist/formats/webgl/core/effectPackage.js.map +1 -0
  105. package/dist/formats/webgl/core/effectPackageValidation.js +331 -0
  106. package/dist/formats/webgl/core/effectPackageValidation.js.map +1 -0
  107. package/dist/formats/webgl/core/errors.js +27 -0
  108. package/dist/formats/webgl/core/errors.js.map +1 -0
  109. package/dist/formats/webgl/core/glsl/DxbcGlslEmitter.js +2678 -0
  110. package/dist/formats/webgl/core/glsl/DxbcGlslEmitter.js.map +1 -0
  111. package/dist/formats/webgl/core/glsl/DxbcGlslHelpers.js +89 -0
  112. package/dist/formats/webgl/core/glsl/DxbcGlslHelpers.js.map +1 -0
  113. package/dist/formats/webgl/core/glsl/DxbcGlslOperandFormatter.js +428 -0
  114. package/dist/formats/webgl/core/glsl/DxbcGlslOperandFormatter.js.map +1 -0
  115. package/dist/formats/webgl/core/helpers.js +306 -0
  116. package/dist/formats/webgl/core/helpers.js.map +1 -0
  117. package/dist/formats/webgl/index.js +2 -0
  118. package/dist/formats/webgl/index.js.map +1 -0
  119. package/dist/formats/webgpu/CjsWebgpuFormat.js +376 -0
  120. package/dist/formats/webgpu/CjsWebgpuFormat.js.map +1 -0
  121. package/dist/formats/webgpu/core/cewgpu/CewgpuPackage.js +415 -0
  122. package/dist/formats/webgpu/core/cewgpu/CewgpuPackage.js.map +1 -0
  123. package/dist/formats/webgpu/core/cewgpu/CewgpuPackageBuilder.js +100 -0
  124. package/dist/formats/webgpu/core/cewgpu/CewgpuPackageBuilder.js.map +1 -0
  125. package/dist/formats/webgpu/core/cewgpu/binary.js +93 -0
  126. package/dist/formats/webgpu/core/cewgpu/binary.js.map +1 -0
  127. package/dist/formats/webgpu/core/cewgpu/tags.js +17 -0
  128. package/dist/formats/webgpu/core/cewgpu/tags.js.map +1 -0
  129. package/dist/formats/webgpu/core/effectAnalysis.js +82 -0
  130. package/dist/formats/webgpu/core/effectAnalysis.js.map +1 -0
  131. package/dist/formats/webgpu/core/effectBackendBodySet.js +305 -0
  132. package/dist/formats/webgpu/core/effectBackendBodySet.js.map +1 -0
  133. package/dist/formats/webgpu/core/effectPackageValidation.js +1078 -0
  134. package/dist/formats/webgpu/core/effectPackageValidation.js.map +1 -0
  135. package/dist/formats/webgpu/core/errors.js +20 -0
  136. package/dist/formats/webgpu/core/errors.js.map +1 -0
  137. package/dist/formats/webgpu/core/helpers.js +448 -0
  138. package/dist/formats/webgpu/core/helpers.js.map +1 -0
  139. package/dist/formats/webgpu/core/ir/analyzeRegisterValues.js +223 -0
  140. package/dist/formats/webgpu/core/ir/analyzeRegisterValues.js.map +1 -0
  141. package/dist/formats/webgpu/core/ir/buildControlFlow.js +220 -0
  142. package/dist/formats/webgpu/core/ir/buildControlFlow.js.map +1 -0
  143. package/dist/formats/webgpu/core/ir/indexableTemps.js +137 -0
  144. package/dist/formats/webgpu/core/ir/indexableTemps.js.map +1 -0
  145. package/dist/formats/webgpu/core/ir/inferValueTypes.js +454 -0
  146. package/dist/formats/webgpu/core/ir/inferValueTypes.js.map +1 -0
  147. package/dist/formats/webgpu/core/ir/lowerDxbcToIr.js +494 -0
  148. package/dist/formats/webgpu/core/ir/lowerDxbcToIr.js.map +1 -0
  149. package/dist/formats/webgpu/core/ir/resolveRegisterFlow.js +177 -0
  150. package/dist/formats/webgpu/core/ir/resolveRegisterFlow.js.map +1 -0
  151. package/dist/formats/webgpu/core/ir/sourceLanes.js +61 -0
  152. package/dist/formats/webgpu/core/ir/sourceLanes.js.map +1 -0
  153. package/dist/formats/webgpu/core/packageEffect.js +300 -0
  154. package/dist/formats/webgpu/core/packageEffect.js.map +1 -0
  155. package/dist/formats/webgpu/core/packageEffectSelection.js +164 -0
  156. package/dist/formats/webgpu/core/packageEffectSelection.js.map +1 -0
  157. package/dist/formats/webgpu/core/packageMetadata.js +8 -0
  158. package/dist/formats/webgpu/core/packageMetadata.js.map +1 -0
  159. package/dist/formats/webgpu/core/schema.js +4 -0
  160. package/dist/formats/webgpu/core/schema.js.map +1 -0
  161. package/dist/formats/webgpu/core/wgsl/buildResourceTransformPlan.js +275 -0
  162. package/dist/formats/webgpu/core/wgsl/buildResourceTransformPlan.js.map +1 -0
  163. package/dist/formats/webgpu/core/wgsl/buildWgslBindingPlan.js +182 -0
  164. package/dist/formats/webgpu/core/wgsl/buildWgslBindingPlan.js.map +1 -0
  165. package/dist/formats/webgpu/core/wgsl/buildWgslSet.js +324 -0
  166. package/dist/formats/webgpu/core/wgsl/buildWgslSet.js.map +1 -0
  167. package/dist/formats/webgpu/core/wgsl/emitWgsl.js +356 -0
  168. package/dist/formats/webgpu/core/wgsl/emitWgsl.js.map +1 -0
  169. package/dist/formats/webgpu/core/wgsl/hoistEscapingValues.js +77 -0
  170. package/dist/formats/webgpu/core/wgsl/hoistEscapingValues.js.map +1 -0
  171. package/dist/formats/webgpu/core/wgsl/lowerBindingLayout.js +451 -0
  172. package/dist/formats/webgpu/core/wgsl/lowerBindingLayout.js.map +1 -0
  173. package/dist/formats/webgpu/core/wgsl/lowerComputeProgram.js +754 -0
  174. package/dist/formats/webgpu/core/wgsl/lowerComputeProgram.js.map +1 -0
  175. package/dist/formats/webgpu/core/wgsl/lowerCreateHistogramsComputeProgram.js +457 -0
  176. package/dist/formats/webgpu/core/wgsl/lowerCreateHistogramsComputeProgram.js.map +1 -0
  177. package/dist/formats/webgpu/core/wgsl/lowerFragmentProgram.js +1572 -0
  178. package/dist/formats/webgpu/core/wgsl/lowerFragmentProgram.js.map +1 -0
  179. package/dist/formats/webgpu/core/wgsl/lowerMergeHistogramsComputeProgram.js +659 -0
  180. package/dist/formats/webgpu/core/wgsl/lowerMergeHistogramsComputeProgram.js.map +1 -0
  181. package/dist/formats/webgpu/core/wgsl/lowerParticleClearComputePrograms.js +734 -0
  182. package/dist/formats/webgpu/core/wgsl/lowerParticleClearComputePrograms.js.map +1 -0
  183. package/dist/formats/webgpu/core/wgsl/lowerParticleEmitComputeProgram.js +583 -0
  184. package/dist/formats/webgpu/core/wgsl/lowerParticleEmitComputeProgram.js.map +1 -0
  185. package/dist/formats/webgpu/core/wgsl/lowerSkinVerticesComputeProgram.js +621 -0
  186. package/dist/formats/webgpu/core/wgsl/lowerSkinVerticesComputeProgram.js.map +1 -0
  187. package/dist/formats/webgpu/core/wgsl/lowerSortComputeProgram.js +824 -0
  188. package/dist/formats/webgpu/core/wgsl/lowerSortComputeProgram.js.map +1 -0
  189. package/dist/formats/webgpu/core/wgsl/lowerSortInnerComputeProgram.js +697 -0
  190. package/dist/formats/webgpu/core/wgsl/lowerSortInnerComputeProgram.js.map +1 -0
  191. package/dist/formats/webgpu/core/wgsl/lowerSortStepComputeProgram.js +559 -0
  192. package/dist/formats/webgpu/core/wgsl/lowerSortStepComputeProgram.js.map +1 -0
  193. package/dist/formats/webgpu/core/wgsl/lowerVertexProgram.js +1328 -0
  194. package/dist/formats/webgpu/core/wgsl/lowerVertexProgram.js.map +1 -0
  195. package/dist/formats/webgpu/core/wgsl/particleEmitSemanticDigest.js +110 -0
  196. package/dist/formats/webgpu/core/wgsl/particleEmitSemanticDigest.js.map +1 -0
  197. package/dist/formats/webgpu/core/wgsl/precisionControls.js +55 -0
  198. package/dist/formats/webgpu/core/wgsl/precisionControls.js.map +1 -0
  199. package/dist/formats/webgpu/core/wgsl/selectionPlans.js +758 -0
  200. package/dist/formats/webgpu/core/wgsl/selectionPlans.js.map +1 -0
  201. package/dist/formats/webgpu/core/wgsl/uniformity.js +78 -0
  202. package/dist/formats/webgpu/core/wgsl/uniformity.js.map +1 -0
  203. package/dist/formats/webgpu/core/wgsl/validateExactComputeIr.js +196 -0
  204. package/dist/formats/webgpu/core/wgsl/validateExactComputeIr.js.map +1 -0
  205. package/dist/formats/webgpu/core/wgsl/validateHandleOperand.js +37 -0
  206. package/dist/formats/webgpu/core/wgsl/validateHandleOperand.js.map +1 -0
  207. package/dist/formats/webgpu/index.js +2 -0
  208. package/dist/formats/webgpu/index.js.map +1 -0
  209. package/dist/resource/shader/Tr2EffectRes.js +159 -150
  210. package/dist/resource/shader/Tr2EffectRes.js.map +1 -1
  211. package/dist/resource/shader/Tr2MaterialArea.js +23 -28
  212. package/dist/resource/shader/Tr2MaterialArea.js.map +1 -1
  213. package/dist/resource/shader/Tr2MaterialMesh.js +21 -27
  214. package/dist/resource/shader/Tr2MaterialMesh.js.map +1 -1
  215. package/dist/resource/shader/Tr2MaterialRes.js +24 -29
  216. package/dist/resource/shader/Tr2MaterialRes.js.map +1 -1
  217. package/dist/resource/shader/Tr2Shader.js +45 -39
  218. package/dist/resource/shader/Tr2Shader.js.map +1 -1
  219. package/dist/resource/shader/Tr2ShaderPermutation.js +30 -32
  220. package/dist/resource/shader/Tr2ShaderPermutation.js.map +1 -1
  221. package/dist/resource/shader/reflection/Tr2EffectConstant.js +113 -78
  222. package/dist/resource/shader/reflection/Tr2EffectConstant.js.map +1 -1
  223. package/dist/resource/shader/reflection/Tr2EffectDefine.js +17 -23
  224. package/dist/resource/shader/reflection/Tr2EffectDefine.js.map +1 -1
  225. package/dist/resource/shader/reflection/Tr2EffectDescription.js +23 -29
  226. package/dist/resource/shader/reflection/Tr2EffectDescription.js.map +1 -1
  227. package/dist/resource/shader/reflection/Tr2EffectLibrary.js +44 -40
  228. package/dist/resource/shader/reflection/Tr2EffectLibrary.js.map +1 -1
  229. package/dist/resource/shader/reflection/Tr2EffectParameterAnnotation.js +82 -86
  230. package/dist/resource/shader/reflection/Tr2EffectParameterAnnotation.js.map +1 -1
  231. package/dist/resource/shader/reflection/Tr2EffectResource.js +55 -61
  232. package/dist/resource/shader/reflection/Tr2EffectResource.js.map +1 -1
  233. package/dist/resource/shader/reflection/Tr2EffectStageInput.js +49 -45
  234. package/dist/resource/shader/reflection/Tr2EffectStageInput.js.map +1 -1
  235. package/dist/resource/shader/reflection/Tr2EffectTechnique.js +25 -30
  236. package/dist/resource/shader/reflection/Tr2EffectTechnique.js.map +1 -1
  237. package/dist/resource/shader/reflection/Tr2Pass.js +30 -32
  238. package/dist/resource/shader/reflection/Tr2Pass.js.map +1 -1
  239. package/dist/resource/shader/sampler/Tr2SamplerSetup.js +21 -26
  240. package/dist/resource/shader/sampler/Tr2SamplerSetup.js.map +1 -1
  241. package/docs/formats/README.md +4 -0
  242. package/docs/formats/provenance.md +11 -3
  243. package/docs/reference/classes/formats.md +440 -0
  244. package/format-notices/dxbc/LICENSE +21 -0
  245. package/format-notices/dxbc/NOTICE +20 -0
  246. package/format-notices/hlsl/LICENSE +21 -0
  247. package/format-notices/hlsl/NOTICE +25 -0
  248. package/format-notices/webgl/LICENSE +21 -0
  249. package/format-notices/webgl/NOTICE +35 -0
  250. package/format-notices/webgpu/LICENSE +21 -0
  251. package/format-notices/webgpu/NOTICE +31 -0
  252. package/package.json +6 -1
  253. package/dist/resource/shader/portableValidation.js +0 -461
  254. package/dist/resource/shader/portableValidation.js.map +0 -1
@@ -0,0 +1,2678 @@
1
+ import { CjsDxbcFormat } from '../../../dxbc/CjsDxbcFormat.js';
2
+ import { WebglReadError } from '../errors.js';
3
+ import { DxbcGlslOperandFormatter } from './DxbcGlslOperandFormatter.js';
4
+ import { DxbcGlslHelperRegistry } from './DxbcGlslHelpers.js';
5
+
6
+ const COMPONENTS = ["x", "y", "z", "w"];
7
+
8
+ /**
9
+ * Map-style compute lowering caps simultaneous UAV writes to this many
10
+ * fragment color outputs. WebGL2 only guarantees `MAX_DRAW_BUFFERS >= 4`
11
+ * (real implementations commonly expose up to 8); a compute kernel that
12
+ * writes more UAVs than fit in one draw call's attachment set (e.g. a
13
+ * hierarchical mip-chain "Pack" pass writing 16 mip levels per dispatch)
14
+ * cannot become a single map-style fragment pass at all.
15
+ */
16
+ const MAX_MAP_STYLE_UAV_OUTPUTS = 8;
17
+
18
+ /**
19
+ * GLSL sampler type per DXBC resource-dimension id (float return types; the
20
+ * stripped-RDEF corpus reflects everything as float).
21
+ */
22
+ const SAMPLER_TYPE_BY_DIMENSION = {
23
+ 2: "sampler2D",
24
+ 3: "sampler2D",
25
+ 5: "sampler3D",
26
+ 6: "samplerCube",
27
+ 8: "sampler2DArray"
28
+ };
29
+
30
+ /**
31
+ * Shadow-sampler type per WebGL2-supported DXBC resource dimension.
32
+ * Comparison sampling changes both the call signature and the uniform type;
33
+ * keeping this separate from the ordinary sampler table guarantees shaders
34
+ * without comparison opcodes retain their existing declarations verbatim.
35
+ */
36
+ const SHADOW_SAMPLER_TYPE_BY_DIMENSION = {
37
+ 3: "sampler2DShadow",
38
+ 6: "samplerCubeShadow",
39
+ 8: "sampler2DArrayShadow"
40
+ };
41
+ const COMPARISON_SAMPLE_OPCODES = new Set(["sample_c", "sample_c_lz"]);
42
+ const NON_COMPARISON_TEXTURE_OPCODES = new Set(["sample", "sample_l", "sample_b", "sample_d", "gather4", "gather4_po", "ld", "ld_ms", "lod"]);
43
+
44
+ /**
45
+ * Filtered-sample coordinate mask per resource dimension
46
+ * (HLSLcc `TranslateTexCoord`, toGLSLInstruction.cpp:985-1031).
47
+ */
48
+ const COORD_MASK_BY_DIMENSION = {
49
+ 2: "x",
50
+ 3: "xy",
51
+ 5: "xyz",
52
+ 6: "xyz",
53
+ 7: "xy",
54
+ 8: "xyz",
55
+ 10: "xyzw"
56
+ };
57
+
58
+ /**
59
+ * DXBC -> GLSL ES 3.00 emitter for vertex and pixel stages.
60
+ *
61
+ * Implements the lowering rules in `docs/dxbc-lowering/*.md`, which pin every
62
+ * template to HLSLcc's `toGLSLInstruction.cpp`/`toGLSLDeclaration.cpp`.
63
+ * Registers are stored as float vec4s with bitcasts at use sites, comparison
64
+ * results are 0xFFFFFFFF/0 masks (never bools), and register-stable symbols
65
+ * (`cb#.data[]`, `t#`, `in_SEMANTIC#`, `vs_SEMANTIC#`) form the ABI surface.
66
+ *
67
+ * Emitter-wide policy: Adreno-3xx driver hygiene from HLSLcc (per-component
68
+ * bitwise splits, `op_not`, `uint(0)` literals, `uvecN()` ctor wraps) is NOT
69
+ * ported; the target is ANGLE-backed WebGL2 and the plain spec-legal forms
70
+ * are emitted instead.
71
+ */
72
+ class DxbcGlslEmitter {
73
+ /**
74
+ * @param {object} [options] Emitter options.
75
+ * @param {object} [options.profile] Target-runtime profile overrides.
76
+ * @param {"array"|"std140"} [options.profile.constantBufferStyle] `uniform vec4 cbN[]`
77
+ * arrays for ccpwgl's uniform4fv path, or std140 blocks.
78
+ * @param {Object.<number,number>} [options.profile.pixelConstantBufferRemap] Pixel-stage
79
+ * cb slot renames (ccpwgl keeps PS effect constants at cb7).
80
+ * @param {function(number,string):string} [options.profile.samplerName] Texture uniform
81
+ * naming per register and stage.
82
+ * @param {number} [options.profile.vertexStructuredCapacity] Element capacity for
83
+ * vertex-stage structured-buffer UBOs (bones; Carbon max is 69 joints).
84
+ */
85
+ constructor(options = {}) {
86
+ this.helpers = new DxbcGlslHelperRegistry();
87
+ this._defineHelpers();
88
+ this.profile = {
89
+ constantBufferStyle: "array",
90
+ pixelConstantBufferRemap: {
91
+ 0: 7
92
+ },
93
+ samplerName: (register, stageName) => stageName === "vertex" ? `vs${register}` : `s${register}`,
94
+ vertexStructuredCapacity: 69,
95
+ dataTextureWidth: 2048,
96
+ // Resource (t#) registers to lower to a compile-time zero instead of
97
+ // declaring a sampler / structured-buffer texture for them. Empty by
98
+ // default (no behavior change); the packager fills this with the
99
+ // tiled-lighting resources (LightBuffer/LightIndexBuffer/
100
+ // LightProfileArray) when `--stub-light-resources` is set, so the
101
+ // fragment sampler count drops under MAX_TEXTURE_IMAGE_UNITS.
102
+ stubResourceRegisters: [],
103
+ lightConstantBuffer: null,
104
+ lightPackedTexture: null,
105
+ ...options.profile
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Registers the GLSL helper functions this emitter's lowerings can require.
111
+ * GLSL ES 3.00 has no `bitfieldExtract`/`bitfieldInsert`/`bitCount`/
112
+ * `textureGather` (all ES 3.10+), so `ibfe`/`ubfe`/`bfi`/`countbits`/
113
+ * `gather4` lower to these hand-written equivalents instead.
114
+ */
115
+ _defineHelpers() {
116
+ // D3D11 IBFE pseudocode: width = src0 & 0x1f, offset = src1 & 0x1f;
117
+ // width == 0 -> 0; width + offset < 32 -> a left/right arithmetic-shift
118
+ // pair that sign-extends from bit (width-1); else an arithmetic shift by
119
+ // offset alone. `w`/`o` are masked to [0,31] first so no shift amount
120
+ // here can ever reach 32 (GLSL shift-by-bit-width is undefined).
121
+ this.helpers.define("hlslcc_ibfe", {
122
+ source: ["int hlslcc_ibfe(int width, int offset, int value) {", " int w = width & 31;", " int o = offset & 31;", " if (w == 0) return 0;", " if (w + o < 32) {", " return (value << (32 - w - o)) >> (32 - w);", " }", " return value >> o;", "}"].join("\n")
123
+ });
124
+ // Same shape as `hlslcc_ibfe` with logical (unsigned) shifts.
125
+ this.helpers.define("hlslcc_ubfe", {
126
+ source: ["uint hlslcc_ubfe(uint width, uint offset, uint value) {", " uint w = width & 31u;", " uint o = offset & 31u;", " if (w == 0u) return 0u;", " if (w + o < 32u) {", " return (value << (32u - w - o)) >> (32u - w);", " }", " return value >> o;", "}"].join("\n")
127
+ });
128
+ // D3D11 BFI pseudocode: bits = (((1 << width) - 1) << offset);
129
+ // ((insert << offset) & bits) | (base & ~bits). `w` is masked to [0,31]
130
+ // (per-spec "width&31") so `1u << w` never shifts by 32.
131
+ this.helpers.define("hlslcc_bfi", {
132
+ source: ["uint hlslcc_bfi(uint width, uint offset, uint insert, uint base) {", " uint w = width & 31u;", " uint o = offset & 31u;", " uint bits = ((1u << w) - 1u) << o;", " return ((insert << o) & bits) | (base & ~bits);", "}"].join("\n")
133
+ });
134
+ // Parallel (SWAR) popcount over a 32-bit uint.
135
+ this.helpers.define("hlslcc_countbits", {
136
+ source: ["uint hlslcc_countbits(uint value) {", " uint v = value;", " v = v - ((v >> 1u) & 0x55555555u);", " v = (v & 0x33333333u) + ((v >> 2u) & 0x33333333u);", " v = (v + (v >> 4u)) & 0x0F0F0F0Fu;", " return (v * 0x01010101u) >> 24u;", "}"].join("\n")
137
+ });
138
+ // GLSL ES 3.00 has no `textureGather` (ES 3.10+/desktop 4.0+ only, see
139
+ // docs/dxbc-lowering/texture-sample.md section 5). Emulated with four
140
+ // `texelFetch` taps at the bilinear neighborhood, matching D3D Gather4 /
141
+ // GL textureGather's neighbor order (top-left, top-right, bottom-right,
142
+ // bottom-left -> .x/.y/.z/.w), clamped to the texture's edge.
143
+ this.helpers.define("hlslcc_textureGather4Emulated", {
144
+ source: ["vec4 hlslcc_textureGather4Emulated(sampler2D samp, vec2 uv, int channel) {", " ivec2 size = textureSize(samp, 0);", " ivec2 maxCoord = size - ivec2(1);", " vec2 texel = uv * vec2(size) - vec2(0.5);", " ivec2 base = ivec2(floor(texel));", " ivec2 c0 = clamp(base + ivec2(0, 1), ivec2(0), maxCoord);", " ivec2 c1 = clamp(base + ivec2(1, 1), ivec2(0), maxCoord);", " ivec2 c2 = clamp(base + ivec2(1, 0), ivec2(0), maxCoord);", " ivec2 c3 = clamp(base + ivec2(0, 0), ivec2(0), maxCoord);", " return vec4(", " texelFetch(samp, c0, 0)[channel],", " texelFetch(samp, c1, 0)[channel],", " texelFetch(samp, c2, 0)[channel],", " texelFetch(samp, c3, 0)[channel]", " );", "}"].join("\n")
145
+ });
146
+ // texture2darray variant of the gather4 emulation (ASSAO-style passes
147
+ // gather from the deinterleaved depth/AO slice arrays constantly).
148
+ // Same four-tap bilinear neighborhood and neighbor order as the 2D
149
+ // helper; the layer index rides in uvw.z and rounds to the nearest
150
+ // slice, matching D3D's array-coordinate rounding for Gather4. Unlike
151
+ // sampler2D (default lowp), sampler2DArray has NO default precision
152
+ // in GLSL ES 3.00, so the parameter must be qualified explicitly.
153
+ this.helpers.define("hlslcc_textureGather4ArrayEmulated", {
154
+ source: ["vec4 hlslcc_textureGather4ArrayEmulated(mediump sampler2DArray samp, vec3 uvw, int channel) {", " ivec2 size = textureSize(samp, 0).xy;", " ivec2 maxCoord = size - ivec2(1);", " int layer = int(uvw.z + 0.5);", " vec2 texel = uvw.xy * vec2(size) - vec2(0.5);", " ivec2 base = ivec2(floor(texel));", " ivec2 c0 = clamp(base + ivec2(0, 1), ivec2(0), maxCoord);", " ivec2 c1 = clamp(base + ivec2(1, 1), ivec2(0), maxCoord);", " ivec2 c2 = clamp(base + ivec2(1, 0), ivec2(0), maxCoord);", " ivec2 c3 = clamp(base + ivec2(0, 0), ivec2(0), maxCoord);", " return vec4(", " texelFetch(samp, ivec3(c0, layer), 0)[channel],", " texelFetch(samp, ivec3(c1, layer), 0)[channel],", " texelFetch(samp, ivec3(c2, layer), 0)[channel],", " texelFetch(samp, ivec3(c3, layer), 0)[channel]", " );", "}"].join("\n")
155
+ });
156
+ }
157
+
158
+ /**
159
+ * Translates one DXBC stage into GLSL ES 3.00 source.
160
+ *
161
+ * @param {ArrayBuffer|ArrayBufferView|Uint8Array} bytes DXBC container bytes.
162
+ * @param {object} [options] Emit options.
163
+ * @param {string} [options.source] Source name used in error details.
164
+ * @returns {{source:string,stageName:string,inputs:object[],outputs:object[],bindings:object[],warnings:string[],computeFragment:(object|undefined)}}
165
+ * GLSL text plus the IO contract the packaging layer records; compute
166
+ * stages add a `computeFragment` host contract (thread group,
167
+ * dispatch-origin uniform, per-output UAV slice routing).
168
+ */
169
+ Emit(bytes, options = {}) {
170
+ const sourceName = options.source || "memory";
171
+ const raw = CjsDxbcFormat.read(bytes, {
172
+ source: sourceName,
173
+ emit: CjsDxbcFormat.OUTPUT_RAW
174
+ });
175
+ if (!raw.program) {
176
+ throw new WebglReadError("DXBC container has no shader program chunk", {
177
+ source: sourceName
178
+ });
179
+ }
180
+ const program = raw.program;
181
+ if (program.programTypeName !== "vertex" && program.programTypeName !== "pixel" && program.programTypeName !== "compute") {
182
+ throw new WebglReadError("Only vertex, pixel, and compute stages target WebGL2", {
183
+ source: sourceName,
184
+ programTypeName: program.programTypeName
185
+ });
186
+ }
187
+ const state = {
188
+ sourceName,
189
+ stageName: program.programTypeName,
190
+ isPixel: program.programTypeName === "pixel",
191
+ isCompute: program.programTypeName === "compute",
192
+ decoder: raw.decoder,
193
+ isgn: raw.inputSignature,
194
+ osgn: raw.outputSignature,
195
+ inputNames: new Map(),
196
+ outputNames: new Map(),
197
+ resourceDimensions: new Map(),
198
+ resourceNames: new Map(),
199
+ comparisonResources: this._analyzeComparisonResources(raw.decoder, sourceName),
200
+ constantBufferNames: new Map(),
201
+ structuredBuffers: new Map(),
202
+ lightConstantBuffer: this._normalizeLightConstantBufferProfile(),
203
+ lightConstantBufferDeclared: false,
204
+ lightPackedTexture: this._normalizeLightPackedTextureProfile(),
205
+ lightPackedTextureDeclared: false,
206
+ // Resource (t#) registers stubbed to a compile-time zero: their
207
+ // declarations/bindings are dropped and every read of them lowers
208
+ // to `uintBitsToFloat(0u)` (structured) / `vec4(0.0)` (sampled).
209
+ stubbedResources: new Set(this.profile.stubResourceRegisters || []),
210
+ inputMasks: new Map(),
211
+ outputMasks: new Map(),
212
+ bindings: [],
213
+ declarationLines: [],
214
+ earlyMainLines: [],
215
+ bodyLines: [],
216
+ indent: 1,
217
+ warnings: [],
218
+ inputs: [],
219
+ outputs: [],
220
+ icbName: "ImmCB_0",
221
+ // Compute (map-style) state: workgroup size, UAV register->name map,
222
+ // UAV declared-return-types (for the non-float-format warning),
223
+ // the `_analyzeUavStores` pre-pass results (register->dimension,
224
+ // register->slice plan, register->per-slice output names, running
225
+ // output-location count), the dispatch-origin uniform name once
226
+ // declared, and which of the four thread-id pseudo-inputs this
227
+ // shader declares.
228
+ threadGroup: null,
229
+ uavNames: new Map(),
230
+ uavReturnTypes: new Map(),
231
+ uavDimensions: new Map(),
232
+ uavSlicePlan: new Map(),
233
+ uavSliceNames: new Map(),
234
+ uavOutputCount: 0,
235
+ dispatchOriginUniform: null,
236
+ computeInputsUsed: {
237
+ threadId: false,
238
+ groupId: false,
239
+ tig: false,
240
+ flattened: false
241
+ }
242
+ };
243
+ this.helpers.reset();
244
+ if (state.isCompute) {
245
+ this._assertMapStyleCompute(state);
246
+ this._analyzeUavStores(state);
247
+ }
248
+ state.integerVertexInputs = new Map();
249
+ state.formatter = new DxbcGlslOperandFormatter({
250
+ // Populated during vertex-input declaration (the declaration loop
251
+ // runs before any body emission, so the map is complete before the
252
+ // first operand is formatted). Integer reads of these registers
253
+ // value-convert instead of bitcast - see _declareVertexInput.
254
+ integerInputs: state.integerVertexInputs,
255
+ componentMap: operand => {
256
+ if (operand.type === 1) return state.inputMasks.get(operand.registerIndex) || null;
257
+ if (operand.type === 2) return state.outputMasks.get(operand.registerIndex) || null;
258
+ return null;
259
+ },
260
+ names: {
261
+ input: index => state.inputNames.get(index) || `v${index}`,
262
+ output: index => state.outputNames.get(index) || `o${index}`,
263
+ constantBuffer: slot => state.constantBufferNames.get(slot) || `cb${slot}`,
264
+ constantBufferMember: this.profile.constantBufferStyle === "std140" ? "data" : "",
265
+ resource: index => state.resourceNames.get(index) || `t${index}`,
266
+ uav: index => state.uavNames.get(index) || `cewgUav${index}`,
267
+ immediateConstantBuffer: state.icbName
268
+ }
269
+ });
270
+ for (const instruction of state.decoder.instructions) {
271
+ if (instruction.isDeclaration) {
272
+ this._emitDeclaration(state, instruction);
273
+ }
274
+ }
275
+ if (state.isCompute) {
276
+ this._emitComputeInputPrelude(state);
277
+ }
278
+ // Pair-aware varyings: declare and zero-fill any varying register the
279
+ // paired pixel stage reads but this vertex stage never declares (DX11
280
+ // tolerates such signature mismatches; GLSL linking does not).
281
+ if (!state.isPixel && !state.isCompute && Array.isArray(options.pairVaryings)) {
282
+ for (const register of options.pairVaryings) {
283
+ if (state.outputNames.has(register)) continue;
284
+ const name = `vs_r${register}`;
285
+ state.outputNames.set(register, name);
286
+ state.declarationLines.push(`out vec4 ${name};`);
287
+ state.outputs.push({
288
+ register,
289
+ name,
290
+ width: 4,
291
+ semanticName: null,
292
+ semanticIndex: 0
293
+ });
294
+ state.warnings.push(`varying ${name} is read by the paired pixel stage but never produced; zero-filled`);
295
+ }
296
+ }
297
+ this._zeroFillUnwrittenOutputs(state);
298
+ for (const instruction of state.decoder.instructions) {
299
+ if (!instruction.isDeclaration) {
300
+ this._emitInstruction(state, instruction);
301
+ }
302
+ }
303
+ if (state.indent !== 1) {
304
+ throw new WebglReadError("Unbalanced control-flow blocks", {
305
+ source: sourceName,
306
+ indent: state.indent
307
+ });
308
+ }
309
+ const result = {
310
+ source: this._assemble(state),
311
+ stageName: state.stageName,
312
+ inputs: state.inputs,
313
+ outputs: state.outputs,
314
+ bindings: state.bindings,
315
+ warnings: state.warnings
316
+ };
317
+ if (state.isCompute) {
318
+ // Host contract for one map-style draw: viewport = target size,
319
+ // `uavOutputs[i].location` = color attachment index, and `slice`
320
+ // (when non-null) = the texture2darray layer to attach there via
321
+ // framebufferTextureLayer. `threadGroup` documents the original
322
+ // dispatch granularity (fragment execution does not need it).
323
+ result.computeFragment = {
324
+ threadGroup: state.threadGroup ? [...state.threadGroup] : null,
325
+ dispatchOriginUniform: state.dispatchOriginUniform,
326
+ uavOutputs: state.bindings.filter(binding => binding.kind === "uavTexture").map(binding => ({
327
+ register: binding.registerIndex,
328
+ slice: binding.slice,
329
+ location: binding.location,
330
+ glslName: binding.name
331
+ }))
332
+ };
333
+ }
334
+ return result;
335
+ }
336
+
337
+ /**
338
+ * Normalizes the optional local-light constant-buffer lowering profile.
339
+ *
340
+ * @returns {object|null} Normalized profile, or null when disabled.
341
+ */
342
+ _normalizeLightConstantBufferProfile() {
343
+ const profile = this.profile.lightConstantBuffer;
344
+ if (!profile || typeof profile !== "object") return null;
345
+ const indexRegister = Number(profile.indexRegister);
346
+ const dataRegister = Number(profile.dataRegister);
347
+ if (!Number.isInteger(indexRegister) || !Number.isInteger(dataRegister)) return null;
348
+ return {
349
+ indexRegister,
350
+ dataRegister,
351
+ profileRegister: Number.isInteger(Number(profile.profileRegister)) ? Number(profile.profileRegister) : null,
352
+ registerIndex: Number.isInteger(Number(profile.registerIndex)) ? Number(profile.registerIndex) : 6,
353
+ name: profile.name || `cb${Number.isInteger(Number(profile.registerIndex)) ? Number(profile.registerIndex) : 6}`,
354
+ capacity: Number.isInteger(Number(profile.capacity)) ? Number(profile.capacity) : 40,
355
+ listBase: Number.isInteger(Number(profile.listBase)) ? Number(profile.listBase) : 65536
356
+ };
357
+ }
358
+
359
+ /**
360
+ * Finds resources used by comparison-sample instructions before resource
361
+ * declarations are emitted, so those declarations can use shadow sampler
362
+ * types. A single GLSL uniform cannot be both a shadow and ordinary sampler;
363
+ * reject that shape explicitly instead of producing invalid GLSL.
364
+ *
365
+ * @param {object} decoder Decoded DXBC instruction stream.
366
+ * @param {string} sourceName Source name used in error details.
367
+ * @returns {Map<number,Set<number>>} Resource register -> sampler registers.
368
+ */
369
+ _analyzeComparisonResources(decoder, sourceName) {
370
+ const comparisonResources = new Map();
371
+ const ordinaryResources = new Set();
372
+ for (const instruction of decoder.instructions) {
373
+ const resourceOperand = instruction.operands?.[2];
374
+ if (!resourceOperand || !Number.isInteger(resourceOperand.registerIndex)) continue;
375
+ if (COMPARISON_SAMPLE_OPCODES.has(instruction.opcodeName)) {
376
+ const register = resourceOperand.registerIndex;
377
+ const samplerRegister = instruction.operands?.[3]?.registerIndex;
378
+ const samplers = comparisonResources.get(register) || new Set();
379
+ if (Number.isInteger(samplerRegister)) samplers.add(samplerRegister);
380
+ comparisonResources.set(register, samplers);
381
+ } else if (NON_COMPARISON_TEXTURE_OPCODES.has(instruction.opcodeName)) {
382
+ ordinaryResources.add(resourceOperand.registerIndex);
383
+ }
384
+ }
385
+ for (const register of comparisonResources.keys()) {
386
+ if (ordinaryResources.has(register)) {
387
+ throw new WebglReadError("A resource sampled with comparison and non-comparison operations requires separate combined samplers", {
388
+ source: sourceName,
389
+ registerIndex: register
390
+ });
391
+ }
392
+ }
393
+ return comparisonResources;
394
+ }
395
+
396
+ /**
397
+ * Normalizes the optional packed local-light texture lowering profile.
398
+ *
399
+ * @returns {object|null} Normalized profile, or null when disabled.
400
+ */
401
+ _normalizeLightPackedTextureProfile() {
402
+ const profile = this.profile.lightPackedTexture;
403
+ if (!profile || typeof profile !== "object") return null;
404
+ const indexRegister = Number(profile.indexRegister);
405
+ const dataRegister = Number(profile.dataRegister);
406
+ if (!Number.isInteger(indexRegister) || !Number.isInteger(dataRegister)) return null;
407
+ return {
408
+ indexRegister,
409
+ dataRegister,
410
+ profileRegister: Number.isInteger(Number(profile.profileRegister)) ? Number(profile.profileRegister) : null,
411
+ registerIndex: Number.isInteger(Number(profile.registerIndex)) ? Number(profile.registerIndex) : indexRegister,
412
+ name: profile.name || "cewgLocalLightTexture",
413
+ dataTexelBase: Number.isInteger(Number(profile.dataTexelBase)) ? Number(profile.dataTexelBase) : 131072
414
+ };
415
+ }
416
+
417
+ /**
418
+ * Declares the packed local-light texture ABI used to replace the
419
+ * LightIndexBuffer/LightBuffer/LightProfileArray trio with one RGBA32UI
420
+ * data texture.
421
+ *
422
+ * @param {object} state Emit state.
423
+ */
424
+ _ensureLightPackedTexture(state) {
425
+ const profile = state.lightPackedTexture;
426
+ if (!profile || state.lightPackedTextureDeclared) return;
427
+ state.lightPackedTextureDeclared = true;
428
+ state.declarationLines.push(`uniform highp usampler2D ${profile.name};`);
429
+ state.bindings.push({
430
+ kind: "structuredTexture",
431
+ registerIndex: profile.registerIndex,
432
+ name: profile.name,
433
+ strideBytes: 0,
434
+ format: "RGBA32UI",
435
+ width: this.profile.dataTextureWidth,
436
+ cewgSemantic: "packedLocalLights",
437
+ lightIndexRegister: profile.indexRegister,
438
+ lightDataRegister: profile.dataRegister,
439
+ lightProfileRegister: profile.profileRegister,
440
+ dataTexelBase: profile.dataTexelBase
441
+ });
442
+ }
443
+
444
+ /**
445
+ * Declares the local-light constant-buffer ABI used to replace the two
446
+ * tiled-light structured buffers without consuming sampler units.
447
+ *
448
+ * @param {object} state Emit state.
449
+ */
450
+ _ensureLightConstantBuffer(state) {
451
+ const profile = state.lightConstantBuffer;
452
+ if (!profile || state.lightConstantBufferDeclared) return;
453
+ state.lightConstantBufferDeclared = true;
454
+ const rows = 1 + profile.capacity * 3;
455
+ state.constantBufferNames.set(profile.registerIndex, profile.name);
456
+ state.declarationLines.push(`uniform vec4 ${profile.name}[${rows}];`, `uint cewgLocalLightCount() { return floatBitsToUint(${profile.name}[0].x); }`, "uint cewgLocalLightIndexLoad(int element) {", " uint count = cewgLocalLightCount();", " if (count == 0u) return 0u;", ` const int listBase = ${profile.listBase};`, " if (element < listBase) return uint(listBase);", " int local = element - listBase;", " int node = local >> 1;", " if (node < 0 || uint(node) >= count) return 0u;", " if ((local & 1) == 0) return uint(node + 1);", " uint next = uint(node + 1);", " return next < count ? uint(listBase + int(next) * 2) : 0u;", "}", "vec4 cewgLocalLightRow(int lightIndex, int row) {", " int i = lightIndex - 1;", " if (i < 0 || uint(i) >= cewgLocalLightCount() || row < 0 || row >= 3) return vec4(0.0);", ` return ${profile.name}[1 + i * 3 + row];`, "}");
457
+ state.bindings.push({
458
+ kind: "constantBuffer",
459
+ registerIndex: profile.registerIndex,
460
+ name: profile.name,
461
+ sizeInVec4: rows,
462
+ style: "array",
463
+ cewgSemantic: "localLights",
464
+ capacityLights: profile.capacity,
465
+ lightIndexRegister: profile.indexRegister,
466
+ lightDataRegister: profile.dataRegister,
467
+ lightProfileRegister: profile.profileRegister
468
+ });
469
+ }
470
+
471
+ /**
472
+ * Rejects compute shaders that need real compute-pipeline features (shared
473
+ * memory, barriers, atomics, raw/structured/typed UAV reads) instead of the
474
+ * "map-style" thread-per-fragment shape this emitter lowers. The message
475
+ * intentionally contains "not supported" so packaging's kill-list
476
+ * classifier regex (`/not supported|No GLSL lowering|unimplementable/i`)
477
+ * routes these to the exclusion list rather than the failure list.
478
+ *
479
+ * @param {object} state Emit state.
480
+ */
481
+ _assertMapStyleCompute(state) {
482
+ for (const instruction of state.decoder.instructions) {
483
+ const name = instruction.opcodeName;
484
+ const blocked = name.startsWith("dcl_thread_group_shared_memory") || name === "sync" || name.startsWith("atomic_") || name.startsWith("imm_atomic_") || name === "ld_uav_typed" || name === "store_raw" || name === "store_structured";
485
+ if (blocked) {
486
+ throw new WebglReadError("compute stage is not map-style; WebGL2 fragment lowering is not supported for this instruction", {
487
+ source: state.sourceName,
488
+ opcodeName: name,
489
+ offset: instruction.offset
490
+ });
491
+ }
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Pre-scans a map-style compute shader's `store_uav_typed` instructions and
497
+ * builds a per-UAV slice plan before any declaration is emitted.
498
+ *
499
+ * Straight-line constant propagation only: the address registers ASSAO-style
500
+ * Prepare passes build are `mov rX.<c>, l(k)` immediates, so the tracker
501
+ * records the last constant written to each temp component and treats every
502
+ * other write as dynamic. Each stored-to UAV register resolves to either
503
+ * `{ kind: "single" }` (at most one store; the array-slice coordinate, when
504
+ * present, is dropped — the host attaches the target layer via
505
+ * `framebufferTextureLayer`) or `{ kind: "multiSlice", slices, sliceByOffset }`
506
+ * (several stores at statically distinct texture2darray slices, each routed
507
+ * to its own fragment output — the host attaches those layers as sequential
508
+ * color attachments).
509
+ *
510
+ * Anything outside those two shapes cannot become one fragment pass, so the
511
+ * throw messages deliberately contain "not supported" for packaging's
512
+ * kill-list classifier (see `_assertMapStyleCompute`).
513
+ *
514
+ * @param {object} state Emit state.
515
+ */
516
+ _analyzeUavStores(state) {
517
+ for (const instruction of state.decoder.instructions) {
518
+ if (instruction.isDeclaration && instruction.opcodeName === "dcl_unordered_access_view_typed") {
519
+ state.uavDimensions.set(instruction.declaration.registerIndex, instruction.declaration.resourceDimension);
520
+ }
521
+ }
522
+ const constantTemps = new Map();
523
+ const storesByUav = new Map();
524
+ let controlFlowDepth = 0;
525
+ for (const instruction of state.decoder.instructions) {
526
+ if (instruction.isDeclaration) continue;
527
+ const name = instruction.opcodeName;
528
+ if (name === "if" || name === "loop" || name === "switch") {
529
+ controlFlowDepth += 1;
530
+ continue;
531
+ }
532
+ if (name === "endif" || name === "endloop" || name === "endswitch") {
533
+ controlFlowDepth -= 1;
534
+ continue;
535
+ }
536
+ if (name === "store_uav_typed") {
537
+ const [uavOperand, addressOperand] = instruction.operands;
538
+ const register = uavOperand.registerIndex;
539
+ const isArray = state.uavDimensions.get(register) === 8;
540
+ const stores = storesByUav.get(register) || [];
541
+ stores.push({
542
+ offset: instruction.offset,
543
+ slice: isArray ? this._resolveSliceConstant(addressOperand, constantTemps) : null,
544
+ isArray,
545
+ controlFlowDepth
546
+ });
547
+ storesByUav.set(register, stores);
548
+ continue;
549
+ }
550
+ this._trackTempConstants(instruction, constantTemps);
551
+ }
552
+ for (const [register, stores] of storesByUav) {
553
+ if (stores.length <= 1) {
554
+ state.uavSlicePlan.set(register, {
555
+ kind: "single"
556
+ });
557
+ continue;
558
+ }
559
+ const throwNotMapStyle = shape => {
560
+ throw new WebglReadError(`compute stage is not map-style; ${shape} are not supported by the WebGL2 emitter's fragment-output lowering`, {
561
+ source: state.sourceName,
562
+ registerIndex: register,
563
+ storeCount: stores.length
564
+ });
565
+ };
566
+ const slices = stores.map(store => store.slice);
567
+ if (!stores[0].isArray) {
568
+ throwNotMapStyle("multiple stores to one non-array UAV");
569
+ }
570
+ if (slices.some(slice => slice === null)) {
571
+ throwNotMapStyle("multiple UAV stores with a dynamic array slice");
572
+ }
573
+ if (new Set(slices).size !== slices.length) {
574
+ throwNotMapStyle("multiple UAV stores to the same array slice");
575
+ }
576
+ if (stores.some(store => store.controlFlowDepth > 0)) {
577
+ throwNotMapStyle("multi-slice UAV stores inside divergent control flow");
578
+ }
579
+ state.uavSlicePlan.set(register, {
580
+ kind: "multiSlice",
581
+ slices: [...slices].sort((a, b) => a - b),
582
+ sliceByOffset: new Map(stores.map(store => [store.offset, store.slice]))
583
+ });
584
+ }
585
+ }
586
+
587
+ /**
588
+ * Updates the per-temp-component constant tracker for one instruction:
589
+ * `mov rX.<mask>, l(...)` records each written component's immediate dword,
590
+ * and any other write to a temp component invalidates it. Destination
591
+ * operands are exactly the mask-selection temp operands (DXBC sources
592
+ * always read through swizzle/select1), so this also handles multi-
593
+ * destination opcodes like `sincos` and `udiv` conservatively.
594
+ *
595
+ * @param {object} instruction Decoded non-declaration instruction.
596
+ * @param {Map<string, number>} constantTemps Constant tracker keyed `<register>.<component>`.
597
+ */
598
+ _trackTempConstants(instruction, constantTemps) {
599
+ const source = instruction.operands[1];
600
+ const isConstantMov = instruction.opcodeName === "mov" && !instruction.saturate && source?.type === 4 && source.modifierName === "none";
601
+ for (const operand of instruction.operands) {
602
+ if (operand.type !== 0 || operand.selectionModeName !== "mask" || operand.registerIndex === null) continue;
603
+ for (const component of operand.mask || "xyzw") {
604
+ const key = `${operand.registerIndex}.${component}`;
605
+ if (isConstantMov && operand.mask) {
606
+ const value = source.componentCount === 1 ? source.immediateValues[0] : source.immediateValues["xyzw".indexOf(component)];
607
+ constantTemps.set(key, value.uint32 | 0);
608
+ } else {
609
+ constantTemps.delete(key);
610
+ }
611
+ }
612
+ }
613
+ }
614
+
615
+ /**
616
+ * Resolves the array-slice (z) component of a `store_uav_typed` address
617
+ * operand to a compile-time constant, or null when it is dynamic.
618
+ *
619
+ * @param {object} addressOperand Decoded address operand.
620
+ * @param {Map<string, number>} constantTemps Constant tracker keyed `<register>.<component>`.
621
+ * @returns {number|null} Constant slice index, or null.
622
+ */
623
+ _resolveSliceConstant(addressOperand, constantTemps) {
624
+ if (addressOperand.type === 4) {
625
+ const value = addressOperand.componentCount === 1 ? addressOperand.immediateValues[0] : addressOperand.immediateValues[2];
626
+ return value ? value.uint32 | 0 : null;
627
+ }
628
+ if (addressOperand.type !== 0) {
629
+ return null;
630
+ }
631
+ const component = addressOperand.selectionModeName === "swizzle" && addressOperand.swizzle ? addressOperand.swizzle[2] : "z";
632
+ const key = `${addressOperand.registerIndex}.${component}`;
633
+ return constantTemps.has(key) ? constantTemps.get(key) : null;
634
+ }
635
+
636
+ /**
637
+ * Allocates the next map-style UAV fragment-output location, enforcing the
638
+ * `MAX_MAP_STYLE_UAV_OUTPUTS` attachment budget across single-output UAVs
639
+ * and per-slice multi-output UAVs alike.
640
+ *
641
+ * @param {object} state Emit state.
642
+ * @param {number} register UAV register index (error detail only).
643
+ * @returns {number} Allocated output location.
644
+ */
645
+ _allocateUavOutputLocation(state, register) {
646
+ const location = state.uavOutputCount;
647
+ if (location >= MAX_MAP_STYLE_UAV_OUTPUTS) {
648
+ throw new WebglReadError("compute stage is not map-style; too many simultaneous UAV writes are not supported by the WebGL2 emitter's fragment-output lowering", {
649
+ source: state.sourceName,
650
+ registerIndex: register,
651
+ uavCount: location + 1
652
+ });
653
+ }
654
+ state.uavOutputCount += 1;
655
+ return location;
656
+ }
657
+
658
+ // ------------------------------------------------------------------
659
+ // Declarations
660
+ // ------------------------------------------------------------------
661
+
662
+ /**
663
+ * Emits one decoded DXBC declaration into the GLSL interface and manifest.
664
+ *
665
+ * @param {object} state Mutable emission state.
666
+ * @param {object} instruction Decoded DXBC declaration instruction.
667
+ * @private
668
+ */
669
+ _emitDeclaration(state, instruction) {
670
+ const declaration = instruction.declaration;
671
+ switch (instruction.opcodeName) {
672
+ case "dcl_global_flags":
673
+ // layout(early_fragment_tests) is not GLSL ES 3.00; suppressed by design.
674
+ break;
675
+ case "dcl_temps":
676
+ for (let index = 0; index < declaration.tempCount; index += 1) {
677
+ state.declarationLines.push(`vec4 r${index};`);
678
+ }
679
+ break;
680
+ case "dcl_indexable_temp":
681
+ state.declarationLines.push(`vec${declaration.componentCount} x${declaration.registerIndex}[${declaration.registerCount}];`);
682
+ break;
683
+ case "dcl_constant_buffer":
684
+ {
685
+ const slot = declaration.registerIndex;
686
+ const mapped = state.isPixel ? this.profile.pixelConstantBufferRemap[slot] ?? slot : slot;
687
+ const name = `cb${mapped}`;
688
+ state.constantBufferNames.set(slot, name);
689
+ if (this.profile.constantBufferStyle === "std140") {
690
+ state.declarationLines.push(`layout(std140) uniform ConstantBuffer${mapped} {`, ` vec4 data[${declaration.sizeInVec4}];`, `} ${name};`);
691
+ } else {
692
+ state.declarationLines.push(`uniform vec4 ${name}[${declaration.sizeInVec4}];`);
693
+ }
694
+ state.bindings.push({
695
+ kind: "constantBuffer",
696
+ registerIndex: slot,
697
+ name,
698
+ sizeInVec4: declaration.sizeInVec4,
699
+ style: this.profile.constantBufferStyle
700
+ });
701
+ break;
702
+ }
703
+ case "dcl_sampler":
704
+ break;
705
+ case "dcl_resource":
706
+ {
707
+ const register = declaration.registerIndex;
708
+ if (state.lightPackedTexture?.profileRegister === register) {
709
+ this._ensureLightPackedTexture(state);
710
+ state.resourceDimensions.set(register, declaration.resourceDimension);
711
+ state.resourceNames.set(register, `s${register}`);
712
+ break;
713
+ }
714
+ if (state.lightConstantBuffer?.profileRegister === register) {
715
+ this._ensureLightConstantBuffer(state);
716
+ state.resourceDimensions.set(register, declaration.resourceDimension);
717
+ state.resourceNames.set(register, `s${register}`);
718
+ break;
719
+ }
720
+ if (state.stubbedResources.has(register)) {
721
+ // Stubbed tiled-lighting sampler (e.g. LightProfileArray):
722
+ // drop the declaration + binding entirely; every sample of
723
+ // this register lowers to vec4(0.0) in _textureSample.
724
+ break;
725
+ }
726
+ if (declaration.resourceDimension === 1) {
727
+ // Buffer<> texel buffers (post-processing) become float data textures
728
+ // read via width-wrapped texelFetch; no samplerBuffer in WebGL2.
729
+ const name = `bt${register}`;
730
+ state.resourceDimensions.set(register, 1);
731
+ state.resourceNames.set(register, name);
732
+ state.declarationLines.push(`uniform highp sampler2D ${name};`);
733
+ state.bindings.push({
734
+ kind: "bufferTexture",
735
+ registerIndex: register,
736
+ name,
737
+ format: "RGBA32F",
738
+ width: this.profile.dataTextureWidth,
739
+ returnTypes: declaration.returnType?.returnTypeNames || null
740
+ });
741
+ break;
742
+ }
743
+ const comparisonSamplers = state.comparisonResources.get(register) || null;
744
+ const samplerType = comparisonSamplers ? SHADOW_SAMPLER_TYPE_BY_DIMENSION[declaration.resourceDimension] : SAMPLER_TYPE_BY_DIMENSION[declaration.resourceDimension];
745
+ if (!samplerType) {
746
+ throw new WebglReadError(comparisonSamplers ? "Comparison sampling is not supported for this resource dimension by the WebGL2 emitter" : "Resource dimension is not supported by the WebGL2 emitter", {
747
+ source: state.sourceName,
748
+ dimensionName: declaration.resourceDimensionName
749
+ });
750
+ }
751
+ const name = this.profile.samplerName(register, state.stageName);
752
+ state.resourceDimensions.set(register, declaration.resourceDimension);
753
+ state.resourceNames.set(register, name);
754
+ state.declarationLines.push(`uniform mediump ${samplerType} ${name};`);
755
+ state.bindings.push({
756
+ kind: "resource",
757
+ registerIndex: register,
758
+ name,
759
+ samplerType,
760
+ dimensionName: declaration.resourceDimensionName,
761
+ ...(comparisonSamplers ? {
762
+ comparison: true,
763
+ samplerRegisterIndices: [...comparisonSamplers].sort((a, b) => a - b)
764
+ } : {})
765
+ });
766
+ break;
767
+ }
768
+ case "dcl_resource_structured":
769
+ {
770
+ const register = declaration.registerIndex;
771
+ const strideBytes = declaration.structureStride || 4;
772
+ const strideDwords = Math.max(1, strideBytes >> 2);
773
+ if (state.lightPackedTexture?.indexRegister === register) {
774
+ this._ensureLightPackedTexture(state);
775
+ state.structuredBuffers.set(register, {
776
+ kind: "lightIndexPacked",
777
+ name: state.lightPackedTexture.name,
778
+ strideDwords
779
+ });
780
+ break;
781
+ }
782
+ if (state.lightPackedTexture?.dataRegister === register) {
783
+ this._ensureLightPackedTexture(state);
784
+ state.structuredBuffers.set(register, {
785
+ kind: "lightDataPacked",
786
+ name: state.lightPackedTexture.name,
787
+ strideDwords,
788
+ dataTexelBase: state.lightPackedTexture.dataTexelBase
789
+ });
790
+ break;
791
+ }
792
+ if (state.lightConstantBuffer?.indexRegister === register) {
793
+ this._ensureLightConstantBuffer(state);
794
+ state.structuredBuffers.set(register, {
795
+ kind: "lightIndexCb",
796
+ name: state.lightConstantBuffer.name,
797
+ strideDwords
798
+ });
799
+ break;
800
+ }
801
+ if (state.lightConstantBuffer?.dataRegister === register) {
802
+ this._ensureLightConstantBuffer(state);
803
+ state.structuredBuffers.set(register, {
804
+ kind: "lightDataCb",
805
+ name: state.lightConstantBuffer.name,
806
+ strideDwords
807
+ });
808
+ break;
809
+ }
810
+ if (state.stubbedResources.has(register)) {
811
+ // Stubbed tiled-lighting structured buffer (LightBuffer /
812
+ // LightIndexBuffer): drop the usampler2D declaration + binding;
813
+ // ld_structured reads of it lower to uintBitsToFloat(0u), which
814
+ // zeroes the per-tile light count and makes the light loop dead.
815
+ state.structuredBuffers.set(register, {
816
+ kind: "stub",
817
+ name: `sb${register}`,
818
+ strideDwords
819
+ });
820
+ break;
821
+ }
822
+ if (state.isPixel) {
823
+ // Pixel-stage structured buffers (light lists) become RGBA32UI data
824
+ // textures: exact bits, no SSBO, resolution-scalable tile tables.
825
+ const name = `sb${register}`;
826
+ state.structuredBuffers.set(register, {
827
+ kind: "texture",
828
+ name,
829
+ strideDwords
830
+ });
831
+ state.declarationLines.push(`uniform highp usampler2D ${name};`);
832
+ state.bindings.push({
833
+ kind: "structuredTexture",
834
+ registerIndex: register,
835
+ name,
836
+ strideBytes,
837
+ format: "RGBA32UI",
838
+ width: this.profile.dataTextureWidth
839
+ });
840
+ } else {
841
+ // Vertex-stage structured buffers (BoneTransforms) become dedicated
842
+ // std140 UBOs sized to the profile capacity (Carbon max 69 joints).
843
+ const name = `CewgSb${register}`;
844
+ const capacity = this.profile.vertexStructuredCapacity;
845
+ const rows = capacity * Math.ceil(strideDwords / 4);
846
+ state.structuredBuffers.set(register, {
847
+ kind: "ubo",
848
+ name,
849
+ strideDwords
850
+ });
851
+ state.declarationLines.push(`layout(std140) uniform ${name}Block {`, ` vec4 data[${rows}];`, `} ${name};`);
852
+ state.bindings.push({
853
+ kind: "structuredUbo",
854
+ registerIndex: register,
855
+ name,
856
+ strideBytes,
857
+ capacityElements: capacity
858
+ });
859
+ }
860
+ break;
861
+ }
862
+ case "dcl_input":
863
+ if (state.isCompute) {
864
+ this._declareComputeInput(state, instruction);
865
+ } else {
866
+ this._declareVertexInput(state, instruction);
867
+ }
868
+ break;
869
+ case "dcl_thread_group":
870
+ state.threadGroup = [declaration.threadGroupX, declaration.threadGroupY, declaration.threadGroupZ];
871
+ break;
872
+ case "dcl_unordered_access_view_typed":
873
+ {
874
+ const register = declaration.registerIndex;
875
+ const returnTypes = declaration.returnType?.returnTypeNames || null;
876
+ const plan = state.uavSlicePlan.get(register);
877
+ state.uavReturnTypes.set(register, returnTypes);
878
+ if (plan?.kind === "multiSlice") {
879
+ // Statically distinct texture2darray slices each become their
880
+ // own fragment output; the host attaches the matching array
881
+ // layers as sequential color attachments (slice order) via
882
+ // framebufferTextureLayer.
883
+ const sliceNames = new Map();
884
+ for (const slice of plan.slices) {
885
+ const name = `cewgUav${register}_s${slice}`;
886
+ const location = this._allocateUavOutputLocation(state, register);
887
+ sliceNames.set(slice, name);
888
+ state.declarationLines.push(`layout(location = ${location}) out highp vec4 ${name};`);
889
+ state.bindings.push({
890
+ kind: "uavTexture",
891
+ registerIndex: register,
892
+ name,
893
+ slice,
894
+ location,
895
+ returnTypes
896
+ });
897
+ }
898
+ state.uavSliceNames.set(register, sliceNames);
899
+ break;
900
+ }
901
+ const name = `cewgUav${register}`;
902
+ const location = this._allocateUavOutputLocation(state, register);
903
+ state.uavNames.set(register, name);
904
+ state.declarationLines.push(`layout(location = ${location}) out highp vec4 ${name};`);
905
+ state.bindings.push({
906
+ kind: "uavTexture",
907
+ registerIndex: register,
908
+ name,
909
+ slice: null,
910
+ location,
911
+ returnTypes
912
+ });
913
+ break;
914
+ }
915
+ case "dcl_input_ps":
916
+ case "dcl_input_ps_siv":
917
+ case "dcl_input_ps_sgv":
918
+ this._declarePixelInput(state, instruction);
919
+ break;
920
+ case "dcl_input_sgv":
921
+ this._declareSystemInput(state, instruction);
922
+ break;
923
+ case "dcl_output":
924
+ case "dcl_output_siv":
925
+ this._declareOutput(state, instruction);
926
+ break;
927
+ case "customdata":
928
+ this._declareImmediateConstantBuffer(state, instruction);
929
+ break;
930
+ default:
931
+ throw new WebglReadError("Declaration is not supported by the WebGL2 emitter", {
932
+ source: state.sourceName,
933
+ opcodeName: instruction.opcodeName
934
+ });
935
+ }
936
+ }
937
+
938
+ /**
939
+ * Finds the signature element assigned to a register.
940
+ *
941
+ * @param {object|null} signature Decoded DXBC signature.
942
+ * @param {number} registerIndex Register index.
943
+ * @returns {object|null} Matching signature element.
944
+ * @private
945
+ */
946
+ _signatureElement(signature, registerIndex) {
947
+ return signature?.elements.find(element => element.registerIndex === registerIndex) || null;
948
+ }
949
+
950
+ /**
951
+ * Declares one vertex input using its Carbon semantic identity.
952
+ *
953
+ * @param {object} state Mutable emission state.
954
+ * @param {object} instruction Decoded input declaration.
955
+ * @private
956
+ */
957
+ _declareVertexInput(state, instruction) {
958
+ const register = instruction.declaration.registerIndex;
959
+ if (state.isPixel) {
960
+ return this._declarePixelInput(state, instruction);
961
+ }
962
+ const element = this._signatureElement(state.isgn, register);
963
+ if (!element) {
964
+ throw new WebglReadError("Vertex input register has no ISGN row", {
965
+ source: state.sourceName,
966
+ register
967
+ });
968
+ }
969
+ if (state.inputNames.has(register)) {
970
+ return;
971
+ }
972
+ const name = `in_${element.semanticName}${element.semanticIndex}`;
973
+ const width = this._maskWidth(element.mask);
974
+ // Integer semantics are float-lowered: ccpwgl binds all attributes with
975
+ // vertexAttribPointer, so uvec/ivec declarations would break linking.
976
+ // Because they're uploaded as plain float VALUES (e.g. bone index 5.0,
977
+ // not the bit pattern of int 5 - confirmed by the legacy skinned VS
978
+ // which does `3.0 * attr.x`), an integer read of one must VALUE-convert
979
+ // (`int(attr)`) rather than bitcast (`floatBitsToInt(attr)`, which would
980
+ // yield garbage). Record integer-typed inputs so the formatter knows.
981
+ if (element.componentTypeName === "uint32" || element.componentTypeName === "int32") {
982
+ state.integerVertexInputs.set(register, element.componentTypeName === "uint32" ? "uint" : "int");
983
+ }
984
+ state.inputNames.set(register, name);
985
+ state.inputMasks.set(register, this._maskChars(element.mask));
986
+ state.declarationLines.push(`in highp ${this._vecType(width)} ${name};`);
987
+ state.inputs.push({
988
+ register,
989
+ name,
990
+ semanticName: element.semanticName,
991
+ semanticIndex: element.semanticIndex,
992
+ componentTypeName: element.componentTypeName,
993
+ mask: element.mask
994
+ });
995
+ }
996
+
997
+ /**
998
+ * Declares one pixel-stage varying or system input.
999
+ *
1000
+ * @param {object} state Mutable emission state.
1001
+ * @param {object} instruction Decoded input declaration.
1002
+ * @private
1003
+ */
1004
+ _declarePixelInput(state, instruction) {
1005
+ const register = instruction.declaration.registerIndex;
1006
+ const declaration = instruction.declaration;
1007
+ if (instruction.opcodeName === "dcl_input_ps_siv" && declaration.systemValueName === "position") {
1008
+ state.inputNames.set(register, "hlslcc_FragCoord");
1009
+ state.earlyMainLines.push("vec4 hlslcc_FragCoord = vec4(gl_FragCoord.xyz, 1.0/gl_FragCoord.w);");
1010
+ return;
1011
+ }
1012
+ if (instruction.opcodeName === "dcl_input_ps_sgv" || instruction.opcodeName === "dcl_input_sgv") {
1013
+ return this._declareSystemInput(state, instruction);
1014
+ }
1015
+ if (state.inputNames.has(register)) {
1016
+ return;
1017
+ }
1018
+ const element = this._signatureElement(state.isgn, register);
1019
+ if (!element) {
1020
+ throw new WebglReadError("Pixel input register has no ISGN row", {
1021
+ source: state.sourceName,
1022
+ register
1023
+ });
1024
+ }
1025
+ // Varyings are internal VS<->PS wires: named by register and always vec4,
1026
+ // so packed registers (two semantics sharing one register) and partial
1027
+ // reads can never produce cross-stage name/type mismatches.
1028
+ const name = `vs_r${register}`;
1029
+ const flat = element.componentType === 1 || element.componentType === 2 || declaration.interpolationModeName === "constant";
1030
+ state.inputNames.set(register, name);
1031
+ state.declarationLines.push(`${flat ? "flat " : ""}in vec4 ${name};`);
1032
+ state.inputs.push({
1033
+ register,
1034
+ name,
1035
+ semanticName: element.semanticName,
1036
+ semanticIndex: element.semanticIndex,
1037
+ componentTypeName: element.componentTypeName,
1038
+ mask: element.mask
1039
+ });
1040
+ }
1041
+
1042
+ /**
1043
+ * Maps a supported system-generated input to its GLSL expression.
1044
+ *
1045
+ * @param {object} state Mutable emission state.
1046
+ * @param {object} instruction Decoded system-input declaration.
1047
+ * @private
1048
+ */
1049
+ _declareSystemInput(state, instruction) {
1050
+ const declaration = instruction.declaration;
1051
+ const register = declaration.registerIndex;
1052
+ switch (declaration.systemValueName) {
1053
+ case "is_front_face":
1054
+ state.inputNames.set(register, "vec4(uintBitsToFloat(gl_FrontFacing ? 0xffffffffu : 0u))");
1055
+ break;
1056
+ case "vertex_id":
1057
+ state.inputNames.set(register, "vec4(intBitsToFloat(gl_VertexID))");
1058
+ break;
1059
+ case "instance_id":
1060
+ state.inputNames.set(register, "vec4(intBitsToFloat(gl_InstanceID))");
1061
+ break;
1062
+ default:
1063
+ throw new WebglReadError("System-generated input is not supported by the WebGL2 emitter", {
1064
+ source: state.sourceName,
1065
+ systemValueName: declaration.systemValueName
1066
+ });
1067
+ }
1068
+ }
1069
+
1070
+ /**
1071
+ * Records which compute pseudo-input(s) a `dcl_input` declaration exposes.
1072
+ * The actual GLSL (uniform + derived locals) is emitted once, after every
1073
+ * declaration has been seen, by `_emitComputeInputPrelude` — `dcl_input`
1074
+ * for a thread-id kind can appear before or after `dcl_thread_group` in the
1075
+ * bytecode stream, and the flattened/group-id forms need the workgroup
1076
+ * size regardless of declaration order.
1077
+ *
1078
+ * @param {object} state Emit state.
1079
+ * @param {object} instruction `dcl_input` instruction with a thread-id-kind operand.
1080
+ */
1081
+ _declareComputeInput(state, instruction) {
1082
+ const kind = instruction.declaration.operandTypeName;
1083
+ switch (kind) {
1084
+ case "input_thread_id":
1085
+ state.computeInputsUsed.threadId = true;
1086
+ break;
1087
+ case "input_thread_group_id":
1088
+ state.computeInputsUsed.groupId = true;
1089
+ break;
1090
+ case "input_thread_id_in_group":
1091
+ state.computeInputsUsed.tig = true;
1092
+ break;
1093
+ case "input_thread_id_in_group_flattened":
1094
+ state.computeInputsUsed.flattened = true;
1095
+ break;
1096
+ default:
1097
+ throw new WebglReadError("Compute input is not supported by the WebGL2 emitter", {
1098
+ source: state.sourceName,
1099
+ operandTypeName: kind
1100
+ });
1101
+ }
1102
+ }
1103
+
1104
+ /**
1105
+ * Emits the shared `gl_FragCoord`-derived thread-id prelude for map-style
1106
+ * compute shaders: a `cewgDispatchOrigin` uniform (so a dispatch can be
1107
+ * tiled across more than one draw/viewport), then only the intermediate
1108
+ * and final locals this shader actually declared inputs for.
1109
+ *
1110
+ * @param {object} state Emit state.
1111
+ */
1112
+ _emitComputeInputPrelude(state) {
1113
+ const used = state.computeInputsUsed;
1114
+ if (!used.threadId && !used.groupId && !used.tig && !used.flattened) {
1115
+ return;
1116
+ }
1117
+ state.dispatchOriginUniform = "cewgDispatchOrigin";
1118
+ state.declarationLines.push("uniform ivec3 cewgDispatchOrigin;");
1119
+ state.bindings.push({
1120
+ kind: "dispatchUniform",
1121
+ name: "cewgDispatchOrigin"
1122
+ });
1123
+ state.earlyMainLines.push("ivec3 cewgThreadId = ivec3(ivec2(gl_FragCoord.xy), 0) + cewgDispatchOrigin;");
1124
+ if (used.threadId) {
1125
+ state.earlyMainLines.push("vec4 vThreadID = intBitsToFloat(ivec4(cewgThreadId, 0));");
1126
+ }
1127
+ if (used.groupId || used.tig || used.flattened) {
1128
+ const [nx, ny, nz] = this._threadGroupSizeOrThrow(state);
1129
+ state.earlyMainLines.push(`ivec3 cewgGroupId = cewgThreadId / ivec3(${nx}, ${ny}, ${nz});`);
1130
+ if (used.groupId) {
1131
+ state.earlyMainLines.push("vec4 vThreadGroupID = intBitsToFloat(ivec4(cewgGroupId, 0));");
1132
+ }
1133
+ if (used.tig || used.flattened) {
1134
+ state.earlyMainLines.push(`ivec3 cewgTig = cewgThreadId - cewgGroupId * ivec3(${nx}, ${ny}, ${nz});`);
1135
+ if (used.tig) {
1136
+ state.earlyMainLines.push("vec4 vThreadIDInGroup = intBitsToFloat(ivec4(cewgTig, 0));");
1137
+ }
1138
+ if (used.flattened) {
1139
+ state.earlyMainLines.push(`vec4 vThreadIDInGroupFlattened = intBitsToFloat(ivec4(cewgTig.z*${nx}*${ny} + cewgTig.y*${nx} + cewgTig.x, 0, 0, 0));`);
1140
+ }
1141
+ }
1142
+ }
1143
+ }
1144
+
1145
+ /**
1146
+ * Reads the workgroup size recorded by `dcl_thread_group`, required to
1147
+ * split the flat thread id back into group id / id-in-group components.
1148
+ *
1149
+ * @param {object} state Emit state.
1150
+ * @returns {[number,number,number]} Workgroup size.
1151
+ */
1152
+ _threadGroupSizeOrThrow(state) {
1153
+ if (!Array.isArray(state.threadGroup)) {
1154
+ throw new WebglReadError("Compute shader reads a thread-group-relative input with no dcl_thread_group", {
1155
+ source: state.sourceName
1156
+ });
1157
+ }
1158
+ return state.threadGroup;
1159
+ }
1160
+
1161
+ /**
1162
+ * Declares one stage output and records its backend identity.
1163
+ *
1164
+ * @param {object} state Mutable emission state.
1165
+ * @param {object} instruction Decoded output declaration.
1166
+ * @private
1167
+ */
1168
+ _declareOutput(state, instruction) {
1169
+ const declaration = instruction.declaration;
1170
+ const register = declaration.registerIndex;
1171
+ if (instruction.opcodeName === "dcl_output_siv") {
1172
+ if (declaration.systemValueName === "position") {
1173
+ state.outputNames.set(register, "gl_Position");
1174
+ state.outputs.push({
1175
+ register,
1176
+ name: "gl_Position",
1177
+ semanticName: "SV_Position",
1178
+ semanticIndex: 0
1179
+ });
1180
+ return;
1181
+ }
1182
+ throw new WebglReadError("Output system value is not supported by the WebGL2 emitter", {
1183
+ source: state.sourceName,
1184
+ systemValueName: declaration.systemValueName
1185
+ });
1186
+ }
1187
+ if (state.outputNames.has(register)) {
1188
+ return;
1189
+ }
1190
+ const element = this._signatureElement(state.osgn, register);
1191
+ if (!element) {
1192
+ throw new WebglReadError("Output register has no OSGN row", {
1193
+ source: state.sourceName,
1194
+ register
1195
+ });
1196
+ }
1197
+ const width = this._maskWidth(element.mask);
1198
+ if (state.isPixel) {
1199
+ const name = `${element.semanticName}${element.semanticIndex}`;
1200
+ state.outputNames.set(register, name);
1201
+ state.outputMasks.set(register, this._maskChars(element.mask));
1202
+ state.declarationLines.push(`layout(location = ${element.semanticIndex}) out ${this._vecType(width)} ${name};`);
1203
+ state.outputs.push({
1204
+ register,
1205
+ name,
1206
+ width,
1207
+ semanticName: element.semanticName,
1208
+ semanticIndex: element.semanticIndex
1209
+ });
1210
+ } else {
1211
+ const name = `vs_r${register}`;
1212
+ state.outputNames.set(register, name);
1213
+ state.declarationLines.push(`out vec4 ${name};`);
1214
+ state.outputs.push({
1215
+ register,
1216
+ name,
1217
+ width: 4,
1218
+ semanticName: element.semanticName,
1219
+ semanticIndex: element.semanticIndex
1220
+ });
1221
+ }
1222
+ }
1223
+
1224
+ /**
1225
+ * Materializes an immediate constant-buffer declaration.
1226
+ *
1227
+ * @param {object} state Mutable emission state.
1228
+ * @param {object} instruction Decoded custom-data declaration.
1229
+ * @private
1230
+ */
1231
+ _declareImmediateConstantBuffer(state, instruction) {
1232
+ const rows = instruction.customData?.immediateConstantBuffer;
1233
+ if (!rows) {
1234
+ return;
1235
+ }
1236
+ const literals = rows.map(row => `vec4(${row.map(value => `uintBitsToFloat(0x${(value.uint32 >>> 0).toString(16)}u)`).join(", ")})`);
1237
+ state.declarationLines.push(`const vec4 ${state.icbName}[${rows.length}] = vec4[${rows.length}](`, ...literals.map((literal, index) => ` ${literal}${index < literals.length - 1 ? "," : ""}`), ");");
1238
+ }
1239
+
1240
+ /**
1241
+ * Initializes paired vertex outputs that the source shader does not write.
1242
+ *
1243
+ * @param {object} state Mutable emission state.
1244
+ * @private
1245
+ */
1246
+ _zeroFillUnwrittenOutputs(state) {
1247
+ const written = new Set();
1248
+ for (const instruction of state.decoder.instructions) {
1249
+ if (instruction.isDeclaration) continue;
1250
+ for (const operand of instruction.operands) {
1251
+ if (operand.type === 2 && operand.registerIndex !== null) {
1252
+ written.add(operand.registerIndex);
1253
+ }
1254
+ }
1255
+ }
1256
+ for (const [register, name] of state.outputNames) {
1257
+ if (!written.has(register) && name !== "gl_Position") {
1258
+ const width = state.outputs.find(output => output.register === register)?.width || 4;
1259
+ state.earlyMainLines.push(`${name} = ${this._vecType(width)}(0.0);`);
1260
+ state.warnings.push(`output ${name} is declared but never written; zero-filled`);
1261
+ }
1262
+ }
1263
+ }
1264
+
1265
+ // ------------------------------------------------------------------
1266
+ // Instructions
1267
+ // ------------------------------------------------------------------
1268
+
1269
+ /**
1270
+ * Dispatches one decoded instruction to its GLSL lowering rule.
1271
+ *
1272
+ * @param {object} state Mutable emission state.
1273
+ * @param {object} instruction Decoded DXBC instruction.
1274
+ * @private
1275
+ */
1276
+ _emitInstruction(state, instruction) {
1277
+ const lower = DxbcGlslEmitter.LOWERINGS[instruction.opcodeName];
1278
+ if (!lower) {
1279
+ throw new WebglReadError("No GLSL lowering for opcode", {
1280
+ source: state.sourceName,
1281
+ opcodeName: instruction.opcodeName,
1282
+ offset: instruction.offset
1283
+ });
1284
+ }
1285
+ lower.call(this, state, instruction);
1286
+ }
1287
+
1288
+ /**
1289
+ * Appends one correctly indented line to the emitted function body.
1290
+ *
1291
+ * @param {object} state Mutable emission state.
1292
+ * @param {string} text GLSL source line.
1293
+ * @private
1294
+ */
1295
+ _line(state, text) {
1296
+ state.bodyLines.push(`${" ".repeat(state.indent)}${text}`);
1297
+ }
1298
+
1299
+ /**
1300
+ * Counts the active components in a DXBC mask.
1301
+ *
1302
+ * @param {number} mask DXBC component bit mask.
1303
+ * @returns {number} Active component count.
1304
+ * @private
1305
+ */
1306
+ _maskWidth(mask) {
1307
+ let width = 0;
1308
+ for (let bit = 0; bit < 4; bit += 1) {
1309
+ if (mask & 1 << bit) width += 1;
1310
+ }
1311
+ return width || 4;
1312
+ }
1313
+
1314
+ /**
1315
+ * Converts a DXBC component bit mask to GLSL swizzle characters.
1316
+ *
1317
+ * @param {number} mask DXBC component bit mask.
1318
+ * @returns {string} GLSL component string.
1319
+ * @private
1320
+ */
1321
+ _maskChars(mask) {
1322
+ return COMPONENTS.filter((_, bit) => mask & 1 << bit).join("") || "xyzw";
1323
+ }
1324
+
1325
+ /**
1326
+ * Builds the lvalue text for one register-space destination component,
1327
+ * remapped into the target variable's declared component space.
1328
+ */
1329
+ _destComponentRef(state, destOperand, target, component) {
1330
+ const remapped = state.formatter.remapComponents(destOperand, component);
1331
+ return `${target.ref}${remapped ? `.${remapped}` : ""}`;
1332
+ }
1333
+
1334
+ /**
1335
+ * Formats a resolved destination reference and optional mask.
1336
+ *
1337
+ * @param {object} target Resolved destination.
1338
+ * @returns {string} GLSL lvalue text.
1339
+ * @private
1340
+ */
1341
+ _destText(target) {
1342
+ return `${target.ref}${target.mask ? `.${target.mask}` : ""}`;
1343
+ }
1344
+
1345
+ /**
1346
+ * Selects the GLSL floating scalar or vector type for a width.
1347
+ *
1348
+ * @param {number} width Component width.
1349
+ * @returns {string} GLSL type name.
1350
+ * @private
1351
+ */
1352
+ _vecType(width) {
1353
+ return width === 1 ? "float" : `vec${width}`;
1354
+ }
1355
+
1356
+ /**
1357
+ * Reads a source at the destination mask's width, wrapping in a matching
1358
+ * constructor so intrinsic arguments always type-check exactly.
1359
+ */
1360
+ _vecArg(state, operand, destMask, as = "float") {
1361
+ const expression = state.formatter.sourceExpression(operand, {
1362
+ destMask,
1363
+ as
1364
+ });
1365
+ if (destMask.length === 1) {
1366
+ return expression;
1367
+ }
1368
+ const ctor = {
1369
+ float: "vec",
1370
+ int: "ivec",
1371
+ uint: "uvec"
1372
+ }[as] + destMask.length;
1373
+ return `${ctor}(${expression})`;
1374
+ }
1375
+
1376
+ /**
1377
+ * Resolves a destination operand and its register-space component mask.
1378
+ *
1379
+ * @param {object} state Mutable emission state.
1380
+ * @param {object} instruction Decoded DXBC instruction.
1381
+ * @param {number} [operandIndex=0] Destination operand index.
1382
+ * @returns {{target: object|null, mask: string}} Destination and mask.
1383
+ * @private
1384
+ */
1385
+ _destMask(state, instruction, operandIndex = 0) {
1386
+ const target = state.formatter.destination(instruction.operands[operandIndex]);
1387
+ // `mask` stays in register space: source-swizzle pairing is positional
1388
+ // against the register components; `target.mask` carries the declared-space
1389
+ // display form used for lvalue text.
1390
+ return {
1391
+ target,
1392
+ mask: target?.registerMask || "xyzw"
1393
+ };
1394
+ }
1395
+
1396
+ /**
1397
+ * Tests whether an operand produces one scalar component.
1398
+ *
1399
+ * @param {object} operand Decoded DXBC operand.
1400
+ * @returns {boolean} True when the source is scalar.
1401
+ * @private
1402
+ */
1403
+ _isScalarSource(operand) {
1404
+ return operand.selectionModeName === "select1" || operand.type === 4 && operand.componentCount === 1 || operand.componentCount === 1;
1405
+ }
1406
+
1407
+ /**
1408
+ * Emits an assignment and applies instruction saturation when required.
1409
+ *
1410
+ * @param {object} state Mutable emission state.
1411
+ * @param {object} instruction Decoded DXBC instruction.
1412
+ * @param {string} valueExpression GLSL right-hand expression.
1413
+ * @param {object} [options={}] Assignment formatting options.
1414
+ * @private
1415
+ */
1416
+ _assign(state, instruction, valueExpression, options = {}) {
1417
+ const statement = state.formatter.assignment(instruction.operands[0], valueExpression, options);
1418
+ if (statement) {
1419
+ this._line(state, statement);
1420
+ if (options.saturate === undefined && instruction.saturate) {
1421
+ const {
1422
+ target
1423
+ } = this._destMask(state, instruction);
1424
+ const text = this._destText(target);
1425
+ this._line(state, `${text} = clamp(${text}, 0.0, 1.0);`);
1426
+ }
1427
+ }
1428
+ }
1429
+
1430
+ /**
1431
+ * Emits a width-correct floating binary infix operation.
1432
+ *
1433
+ * @param {object} state Mutable emission state.
1434
+ * @param {object} instruction Decoded DXBC instruction.
1435
+ * @param {string} operator GLSL infix operator.
1436
+ * @private
1437
+ */
1438
+ _infixBinary(state, instruction, operator) {
1439
+ const {
1440
+ mask
1441
+ } = this._destMask(state, instruction);
1442
+ const a = state.formatter.sourceExpression(instruction.operands[1], {
1443
+ destMask: mask
1444
+ });
1445
+ const b = state.formatter.sourceExpression(instruction.operands[2], {
1446
+ destMask: mask
1447
+ });
1448
+ const width = Math.max(state.formatter.expressionWidth(instruction.operands[1], mask), state.formatter.expressionWidth(instruction.operands[2], mask));
1449
+ this._assignWidened(state, instruction, mask, `${a} ${operator} ${b}`, width === 1);
1450
+ }
1451
+
1452
+ /**
1453
+ * Widens a scalar result when assigning it to a vector destination.
1454
+ *
1455
+ * @param {object} state Mutable emission state.
1456
+ * @param {object} instruction Decoded DXBC instruction.
1457
+ * @param {string} mask Destination component mask.
1458
+ * @param {string} expression GLSL expression.
1459
+ * @param {boolean} rhsIsScalar Whether the expression is scalar.
1460
+ * @private
1461
+ */
1462
+ _assignWidened(state, instruction, mask, expression, rhsIsScalar) {
1463
+ const value = rhsIsScalar && mask.length > 1 ? `vec${mask.length}(${expression})` : expression;
1464
+ this._assign(state, instruction, value, {
1465
+ saturate: instruction.saturate
1466
+ });
1467
+ }
1468
+
1469
+ /**
1470
+ * Emits a unary GLSL intrinsic with destination-width handling.
1471
+ *
1472
+ * @param {object} state Mutable emission state.
1473
+ * @param {object} instruction Decoded DXBC instruction.
1474
+ * @param {string} glslName GLSL intrinsic name.
1475
+ * @private
1476
+ */
1477
+ _helperUnary(state, instruction, glslName) {
1478
+ const {
1479
+ mask
1480
+ } = this._destMask(state, instruction);
1481
+ const src = state.formatter.sourceExpression(instruction.operands[1], {
1482
+ destMask: mask
1483
+ });
1484
+ this._assignWidened(state, instruction, mask, `${glslName}(${src})`, state.formatter.expressionWidth(instruction.operands[1], mask) === 1);
1485
+ }
1486
+
1487
+ /**
1488
+ * Emits a binary GLSL intrinsic with width-matched arguments.
1489
+ *
1490
+ * @param {object} state Mutable emission state.
1491
+ * @param {object} instruction Decoded DXBC instruction.
1492
+ * @param {string} glslName GLSL intrinsic name.
1493
+ * @private
1494
+ */
1495
+ _helperBinary(state, instruction, glslName) {
1496
+ const {
1497
+ mask
1498
+ } = this._destMask(state, instruction);
1499
+ const a = this._vecArg(state, instruction.operands[1], mask);
1500
+ const b = this._vecArg(state, instruction.operands[2], mask);
1501
+ this._assign(state, instruction, `${glslName}(${a}, ${b})`, {
1502
+ saturate: instruction.saturate
1503
+ });
1504
+ }
1505
+
1506
+ /**
1507
+ * Emits a dot product and replicates its scalar result when necessary.
1508
+ *
1509
+ * @param {object} state Mutable emission state.
1510
+ * @param {object} instruction Decoded DXBC instruction.
1511
+ * @param {string} sourceMask Source component mask.
1512
+ * @private
1513
+ */
1514
+ _dotProduct(state, instruction, sourceMask) {
1515
+ const {
1516
+ mask
1517
+ } = this._destMask(state, instruction);
1518
+ const a = this._vecArg(state, instruction.operands[1], sourceMask);
1519
+ const b = this._vecArg(state, instruction.operands[2], sourceMask);
1520
+ const dot = `dot(${a}, ${b})`;
1521
+ this._assign(state, instruction, mask.length > 1 ? `vec${mask.length}(${dot})` : dot, {
1522
+ saturate: instruction.saturate
1523
+ });
1524
+ }
1525
+
1526
+ /**
1527
+ * Emits scalar or vector comparison bits in the float register model.
1528
+ *
1529
+ * @param {object} state Mutable emission state.
1530
+ * @param {object} instruction Decoded DXBC instruction.
1531
+ * @param {object} options Comparison options.
1532
+ * @param {string} options.intrinsic GLSL vector comparison intrinsic.
1533
+ * @param {string} options.operator GLSL scalar comparison operator.
1534
+ * @param {"float"|"int"|"uint"} options.as Operand interpretation.
1535
+ * @private
1536
+ */
1537
+ _comparison(state, instruction, {
1538
+ intrinsic,
1539
+ operator,
1540
+ as
1541
+ }) {
1542
+ const {
1543
+ mask
1544
+ } = this._destMask(state, instruction);
1545
+ if (mask.length === 1) {
1546
+ const a = state.formatter.sourceExpression(instruction.operands[1], {
1547
+ destMask: mask,
1548
+ as
1549
+ });
1550
+ const b = state.formatter.sourceExpression(instruction.operands[2], {
1551
+ destMask: mask,
1552
+ as
1553
+ });
1554
+ this._assign(state, instruction, `uintBitsToFloat((${a} ${operator} ${b}) ? 0xFFFFFFFFu : 0u)`);
1555
+ return;
1556
+ }
1557
+ const a = this._vecArg(state, instruction.operands[1], mask, as);
1558
+ const b = this._vecArg(state, instruction.operands[2], mask, as);
1559
+ this._assign(state, instruction, `uintBitsToFloat(uvec${mask.length}(${intrinsic}(${a}, ${b})) * 0xFFFFFFFFu)`);
1560
+ }
1561
+
1562
+ /**
1563
+ * Emits an unsigned bitwise operation through register bitcasts.
1564
+ *
1565
+ * @param {object} state Mutable emission state.
1566
+ * @param {object} instruction Decoded DXBC instruction.
1567
+ * @param {string} operator GLSL bitwise operator.
1568
+ * @private
1569
+ */
1570
+ _bitwiseBinary(state, instruction, operator) {
1571
+ const {
1572
+ mask
1573
+ } = this._destMask(state, instruction);
1574
+ const a = this._vecArg(state, instruction.operands[1], mask, "uint");
1575
+ const b = this._vecArg(state, instruction.operands[2], mask, "uint");
1576
+ this._assign(state, instruction, `uintBitsToFloat(${a} ${operator} ${b})`);
1577
+ }
1578
+
1579
+ /**
1580
+ * Emits a signed or unsigned integer binary operation through bitcasts.
1581
+ *
1582
+ * @param {object} state Mutable emission state.
1583
+ * @param {object} instruction Decoded DXBC instruction.
1584
+ * @param {string} operator GLSL integer operator.
1585
+ * @param {"int"|"uint"} [as="int"] Integer interpretation.
1586
+ * @private
1587
+ */
1588
+ _intBinary(state, instruction, operator, as = "int") {
1589
+ const {
1590
+ mask
1591
+ } = this._destMask(state, instruction);
1592
+ const a = this._vecArg(state, instruction.operands[1], mask, as);
1593
+ const b = this._vecArg(state, instruction.operands[2], mask, as);
1594
+ const wrap = as === "uint" ? "uintBitsToFloat" : "intBitsToFloat";
1595
+ this._assign(state, instruction, `${wrap}(${a} ${operator} ${b})`);
1596
+ }
1597
+
1598
+ /**
1599
+ * Formats the DXBC zero or nonzero control-flow condition.
1600
+ *
1601
+ * @param {object} state Mutable emission state.
1602
+ * @param {object} instruction Decoded DXBC instruction.
1603
+ * @returns {string} GLSL boolean expression.
1604
+ * @private
1605
+ */
1606
+ _condition(state, instruction) {
1607
+ const scalar = state.formatter.sourceExpression(instruction.operands[0], {
1608
+ destMask: "x"
1609
+ });
1610
+ const test = instruction.testBoolean === "nonzero" ? "!=" : "==";
1611
+ return `floatBitsToUint(${scalar}) ${test} 0u`;
1612
+ }
1613
+
1614
+ /**
1615
+ * Emits component-wise conditional moves without aliasing destinations.
1616
+ *
1617
+ * @param {object} state Mutable emission state.
1618
+ * @param {object} instruction Decoded DXBC instruction.
1619
+ * @private
1620
+ */
1621
+ _movc(state, instruction) {
1622
+ const destOperand = instruction.operands[0];
1623
+ const {
1624
+ target,
1625
+ mask
1626
+ } = this._destMask(state, instruction);
1627
+ if (!target) return;
1628
+ const aliases = instruction.operands.slice(1).some(operand => operand.type === destOperand.type && operand.registerIndex === destOperand.registerIndex);
1629
+ if (aliases) {
1630
+ this._line(state, "{");
1631
+ state.indent += 1;
1632
+ this._line(state, `vec4 hlslcc_movcTemp = ${target.ref};`);
1633
+ }
1634
+ for (const component of mask) {
1635
+ const condition = state.formatter.sourceExpression(instruction.operands[1], {
1636
+ destMask: component,
1637
+ as: "int"
1638
+ });
1639
+ const ifTrue = state.formatter.sourceExpression(instruction.operands[2], {
1640
+ destMask: component
1641
+ });
1642
+ const ifFalse = state.formatter.sourceExpression(instruction.operands[3], {
1643
+ destMask: component
1644
+ });
1645
+ const lvalue = aliases ? `hlslcc_movcTemp.${component}` : this._destComponentRef(state, destOperand, target, component);
1646
+ this._line(state, `${lvalue} = (${condition} != 0) ? ${ifTrue} : ${ifFalse};`);
1647
+ }
1648
+ if (aliases) {
1649
+ this._line(state, `${target.ref} = hlslcc_movcTemp;`);
1650
+ state.indent -= 1;
1651
+ this._line(state, "}");
1652
+ }
1653
+ if (instruction.saturate) {
1654
+ const text = this._destText(target);
1655
+ this._line(state, `${text} = clamp(${text}, 0.0, 1.0);`);
1656
+ }
1657
+ }
1658
+
1659
+ /**
1660
+ * Emits the paired sine and cosine destinations in alias-safe order.
1661
+ *
1662
+ * @param {object} state Mutable emission state.
1663
+ * @param {object} instruction Decoded DXBC instruction.
1664
+ * @private
1665
+ */
1666
+ _sincos(state, instruction) {
1667
+ const [sinDest, cosDest, angle] = instruction.operands;
1668
+ const emitOne = (destOperand, fn) => {
1669
+ if (destOperand.type === 13) return;
1670
+ const target = state.formatter.destination(destOperand);
1671
+ const mask = target.registerMask || "xyzw";
1672
+ const src = state.formatter.sourceExpression(angle, {
1673
+ destMask: mask
1674
+ });
1675
+ const call = state.formatter.expressionWidth(angle, mask) === 1 && mask.length > 1 ? `vec${mask.length}(${fn}(${src}))` : `${fn}(${src})`;
1676
+ const text = this._destText(target);
1677
+ this._line(state, `${text} = ${call};`);
1678
+ if (instruction.saturate) {
1679
+ this._line(state, `${text} = clamp(${text}, 0.0, 1.0);`);
1680
+ }
1681
+ };
1682
+ const sinAliasesAngle = sinDest.type === angle.type && sinDest.registerIndex === angle.registerIndex;
1683
+ if (sinAliasesAngle) {
1684
+ emitOne(cosDest, "cos");
1685
+ emitOne(sinDest, "sin");
1686
+ } else {
1687
+ emitOne(sinDest, "sin");
1688
+ emitOne(cosDest, "cos");
1689
+ }
1690
+ }
1691
+
1692
+ /**
1693
+ * Resolves and validates the declared dimension of a texture operand.
1694
+ *
1695
+ * @param {object} state Mutable emission state.
1696
+ * @param {object} instruction Decoded DXBC instruction.
1697
+ * @param {object} texOperand Texture resource operand.
1698
+ * @returns {number} DXBC resource-dimension code.
1699
+ * @private
1700
+ */
1701
+ _resourceDimension(state, instruction, texOperand) {
1702
+ const dimension = state.resourceDimensions.get(texOperand.registerIndex);
1703
+ if (!dimension) {
1704
+ throw new WebglReadError("Texture instruction references an undeclared resource", {
1705
+ source: state.sourceName,
1706
+ register: texOperand.registerIndex,
1707
+ opcodeName: instruction.opcodeName
1708
+ });
1709
+ }
1710
+ return dimension;
1711
+ }
1712
+
1713
+ /**
1714
+ * Emits a regular, LOD, bias, gradient, or comparison texture sample.
1715
+ *
1716
+ * @param {object} state Mutable emission state.
1717
+ * @param {object} instruction Decoded DXBC texture instruction.
1718
+ * @param {object} [options={}] Sampling-mode operand indexes.
1719
+ * @param {number|null} [options.lodOperandIndex=null] Explicit LOD operand.
1720
+ * @param {number|null} [options.biasOperandIndex=null] LOD bias operand.
1721
+ * @param {number[]|null} [options.gradOperandIndexes=null] Gradient operands.
1722
+ * @param {number|null} [options.comparisonRefOperandIndex=null] Comparison reference operand.
1723
+ * @param {boolean} [options.forceLodZero=false] Whether to force the LOD-zero adaptation.
1724
+ * @private
1725
+ */
1726
+ _textureSample(state, instruction, {
1727
+ lodOperandIndex = null,
1728
+ biasOperandIndex = null,
1729
+ gradOperandIndexes = null,
1730
+ comparisonRefOperandIndex = null,
1731
+ forceLodZero = false
1732
+ } = {}) {
1733
+ const {
1734
+ mask
1735
+ } = this._destMask(state, instruction);
1736
+ const coordOperand = instruction.operands[1];
1737
+ const texOperand = instruction.operands[2];
1738
+ if (state.lightPackedTexture?.profileRegister === texOperand.registerIndex) {
1739
+ const oneSwizzle = [...mask].map(component => texOperand.swizzle ? texOperand.swizzle["xyzw".indexOf(component)] : component).join("");
1740
+ this._assign(state, instruction, `vec4(1.0).${oneSwizzle}`, {
1741
+ saturate: instruction.saturate
1742
+ });
1743
+ return;
1744
+ }
1745
+ if (state.lightConstantBuffer?.profileRegister === texOperand.registerIndex) {
1746
+ // The constant-buffer local-light path carries the light rows but
1747
+ // not the texture profile array. Treat profile lookups as neutral
1748
+ // attenuation so profile-enabled lights degrade to unprofiled lights
1749
+ // instead of costing another sampler unit.
1750
+ const oneSwizzle = [...mask].map(component => texOperand.swizzle ? texOperand.swizzle["xyzw".indexOf(component)] : component).join("");
1751
+ this._assign(state, instruction, `vec4(1.0).${oneSwizzle}`, {
1752
+ saturate: instruction.saturate
1753
+ });
1754
+ return;
1755
+ }
1756
+ if (state.stubbedResources.has(texOperand.registerIndex)) {
1757
+ // Stubbed tiled-lighting sampler (LightProfileArray): its declaration
1758
+ // was dropped, so lower the sample to a compile-time zero of the
1759
+ // destination width instead of referencing the missing sampler.
1760
+ const zeroSwizzle = [...mask].map(component => texOperand.swizzle ? texOperand.swizzle["xyzw".indexOf(component)] : component).join("");
1761
+ this._assign(state, instruction, `vec4(0.0).${zeroSwizzle}`, {
1762
+ saturate: instruction.saturate
1763
+ });
1764
+ return;
1765
+ }
1766
+ const dimension = this._resourceDimension(state, instruction, texOperand);
1767
+ const coordMask = COORD_MASK_BY_DIMENSION[dimension];
1768
+ const coord = this._vecArg(state, coordOperand, coordMask);
1769
+ const texName = state.formatter.registerReference(texOperand);
1770
+ const returnSwizzle = [...mask].map(component => texOperand.swizzle ? texOperand.swizzle["xyzw".indexOf(component)] : component).join("");
1771
+ if (comparisonRefOperandIndex !== null) {
1772
+ const reference = state.formatter.sourceExpression(instruction.operands[comparisonRefOperandIndex], {
1773
+ destMask: "x"
1774
+ });
1775
+ const comparisonCoord = `vec${coordMask.length + 1}(${coord}, ${reference})`;
1776
+ let comparisonCall;
1777
+ if (!forceLodZero) {
1778
+ comparisonCall = `texture(${texName}, ${comparisonCoord})`;
1779
+ } else if (dimension === 3) {
1780
+ comparisonCall = `textureLod(${texName}, ${comparisonCoord}, 0.0)`;
1781
+ } else if (dimension === 6) {
1782
+ comparisonCall = `textureGrad(${texName}, ${comparisonCoord}, vec3(0.0), vec3(0.0))`;
1783
+ } else if (dimension === 8) {
1784
+ comparisonCall = `textureGrad(${texName}, ${comparisonCoord}, vec2(0.0), vec2(0.0))`;
1785
+ } else {
1786
+ throw new WebglReadError("Comparison LOD-zero sampling is not supported for this resource dimension", {
1787
+ source: state.sourceName,
1788
+ opcodeName: instruction.opcodeName,
1789
+ dimension
1790
+ });
1791
+ }
1792
+ const value = mask.length === 1 ? comparisonCall : `vec${mask.length}(${comparisonCall})`;
1793
+ this._assign(state, instruction, value, {
1794
+ saturate: instruction.saturate
1795
+ });
1796
+ return;
1797
+ }
1798
+ let call;
1799
+ if (gradOperandIndexes !== null) {
1800
+ const gradMask = coordMask === "xyzw" ? "xyz" : coordMask;
1801
+ const gradX = this._vecArg(state, instruction.operands[gradOperandIndexes[0]], gradMask);
1802
+ const gradY = this._vecArg(state, instruction.operands[gradOperandIndexes[1]], gradMask);
1803
+ call = `textureGrad(${texName}, ${coord}, ${gradX}, ${gradY})`;
1804
+ } else if (lodOperandIndex !== null) {
1805
+ const lod = state.formatter.sourceExpression(instruction.operands[lodOperandIndex], {
1806
+ destMask: "x"
1807
+ });
1808
+ call = `textureLod(${texName}, ${coord}, ${lod})`;
1809
+ } else if (biasOperandIndex !== null) {
1810
+ const bias = state.formatter.sourceExpression(instruction.operands[biasOperandIndex], {
1811
+ destMask: "x"
1812
+ });
1813
+ call = `texture(${texName}, ${coord}, ${bias})`;
1814
+ } else {
1815
+ call = `texture(${texName}, ${coord})`;
1816
+ }
1817
+ this._assign(state, instruction, `${call}.${returnSwizzle}`, {
1818
+ saturate: instruction.saturate
1819
+ });
1820
+ }
1821
+
1822
+ /**
1823
+ * Emits an integer-coordinate texel fetch for a declared resource.
1824
+ *
1825
+ * @param {object} state Mutable emission state.
1826
+ * @param {object} instruction Decoded DXBC load instruction.
1827
+ * @private
1828
+ */
1829
+ _texelFetch(state, instruction) {
1830
+ const {
1831
+ mask
1832
+ } = this._destMask(state, instruction);
1833
+ const coordOperand = instruction.operands[1];
1834
+ const texOperand = instruction.operands[2];
1835
+ const dimension = this._resourceDimension(state, instruction, texOperand);
1836
+ const texName = state.formatter.registerReference(texOperand);
1837
+ const returnSwizzle = [...mask].map(component => texOperand.swizzle ? texOperand.swizzle["xyzw".indexOf(component)] : component).join("");
1838
+ let call;
1839
+ if (dimension === 1) {
1840
+ const index = state.formatter.sourceExpression(coordOperand, {
1841
+ destMask: "x",
1842
+ as: "int"
1843
+ });
1844
+ const widthMask = this.profile.dataTextureWidth - 1;
1845
+ const widthShift = Math.log2(this.profile.dataTextureWidth);
1846
+ call = `texelFetch(${texName}, ivec2((${index}) & ${widthMask}, (${index}) >> ${widthShift}), 0)`;
1847
+ } else {
1848
+ const coordMask = dimension === 5 || dimension === 8 ? "xyz" : "xy";
1849
+ const coord = this._vecArg(state, coordOperand, coordMask, "int");
1850
+ const mip = state.formatter.sourceExpression(coordOperand, {
1851
+ destMask: "w",
1852
+ as: "int"
1853
+ });
1854
+ call = `texelFetch(${texName}, ${coord}, ${mip})`;
1855
+ }
1856
+ this._assign(state, instruction, `${call}.${returnSwizzle}`, {
1857
+ saturate: instruction.saturate
1858
+ });
1859
+ }
1860
+ }
1861
+
1862
+ /**
1863
+ * Per-opcode lowering table. Every rule cites its family spec in
1864
+ * `docs/dxbc-lowering/`; templates are HLSLcc-derived, see each doc.
1865
+ */
1866
+ DxbcGlslEmitter.LOWERINGS = {
1867
+ // --- float-alu ---
1868
+ mov(state, instruction) {
1869
+ const {
1870
+ mask
1871
+ } = this._destMask(state, instruction);
1872
+ const src = state.formatter.sourceExpression(instruction.operands[1], {
1873
+ destMask: mask
1874
+ });
1875
+ this._assignWidened(state, instruction, mask, src, state.formatter.expressionWidth(instruction.operands[1], mask) === 1);
1876
+ },
1877
+ mad(state, instruction) {
1878
+ const {
1879
+ mask
1880
+ } = this._destMask(state, instruction);
1881
+ const a = state.formatter.sourceExpression(instruction.operands[1], {
1882
+ destMask: mask
1883
+ });
1884
+ const b = state.formatter.sourceExpression(instruction.operands[2], {
1885
+ destMask: mask
1886
+ });
1887
+ const c = state.formatter.sourceExpression(instruction.operands[3], {
1888
+ destMask: mask
1889
+ });
1890
+ const width = Math.max(...[1, 2, 3].map(index => state.formatter.expressionWidth(instruction.operands[index], mask)));
1891
+ this._assignWidened(state, instruction, mask, `${a} * ${b} + ${c}`, width === 1);
1892
+ },
1893
+ mul(state, instruction) {
1894
+ this._infixBinary(state, instruction, "*");
1895
+ },
1896
+ add(state, instruction) {
1897
+ this._infixBinary(state, instruction, "+");
1898
+ },
1899
+ div(state, instruction) {
1900
+ this._infixBinary(state, instruction, "/");
1901
+ },
1902
+ dp2(state, instruction) {
1903
+ this._dotProduct(state, instruction, "xy");
1904
+ },
1905
+ dp3(state, instruction) {
1906
+ this._dotProduct(state, instruction, "xyz");
1907
+ },
1908
+ dp4(state, instruction) {
1909
+ this._dotProduct(state, instruction, "xyzw");
1910
+ },
1911
+ max(state, instruction) {
1912
+ this._helperBinary(state, instruction, "max");
1913
+ },
1914
+ min(state, instruction) {
1915
+ this._helperBinary(state, instruction, "min");
1916
+ },
1917
+ sqrt(state, instruction) {
1918
+ this._helperUnary(state, instruction, "sqrt");
1919
+ },
1920
+ rsq(state, instruction) {
1921
+ this._helperUnary(state, instruction, "inversesqrt");
1922
+ },
1923
+ exp(state, instruction) {
1924
+ this._helperUnary(state, instruction, "exp2");
1925
+ },
1926
+ log(state, instruction) {
1927
+ this._helperUnary(state, instruction, "log2");
1928
+ },
1929
+ frc(state, instruction) {
1930
+ this._helperUnary(state, instruction, "fract");
1931
+ },
1932
+ round_ne(state, instruction) {
1933
+ this._helperUnary(state, instruction, "roundEven");
1934
+ },
1935
+ round_ni(state, instruction) {
1936
+ this._helperUnary(state, instruction, "floor");
1937
+ },
1938
+ round_pi(state, instruction) {
1939
+ this._helperUnary(state, instruction, "ceil");
1940
+ },
1941
+ round_z(state, instruction) {
1942
+ this._helperUnary(state, instruction, "trunc");
1943
+ },
1944
+ rcp(state, instruction) {
1945
+ const {
1946
+ mask
1947
+ } = this._destMask(state, instruction);
1948
+ const src = this._vecArg(state, instruction.operands[1], mask);
1949
+ const one = mask.length > 1 ? `vec${mask.length}(1.0)` : "1.0";
1950
+ this._assign(state, instruction, `${one} / ${src}`, {
1951
+ saturate: instruction.saturate
1952
+ });
1953
+ },
1954
+ sincos(state, instruction) {
1955
+ this._sincos(state, instruction);
1956
+ },
1957
+ // --- cmp-controlflow ---
1958
+ lt(state, instruction) {
1959
+ this._comparison(state, instruction, {
1960
+ intrinsic: "lessThan",
1961
+ operator: "<",
1962
+ as: "float"
1963
+ });
1964
+ },
1965
+ ge(state, instruction) {
1966
+ this._comparison(state, instruction, {
1967
+ intrinsic: "greaterThanEqual",
1968
+ operator: ">=",
1969
+ as: "float"
1970
+ });
1971
+ },
1972
+ eq(state, instruction) {
1973
+ this._comparison(state, instruction, {
1974
+ intrinsic: "equal",
1975
+ operator: "==",
1976
+ as: "float"
1977
+ });
1978
+ },
1979
+ ne(state, instruction) {
1980
+ this._comparison(state, instruction, {
1981
+ intrinsic: "notEqual",
1982
+ operator: "!=",
1983
+ as: "float"
1984
+ });
1985
+ },
1986
+ ilt(state, instruction) {
1987
+ this._comparison(state, instruction, {
1988
+ intrinsic: "lessThan",
1989
+ operator: "<",
1990
+ as: "int"
1991
+ });
1992
+ },
1993
+ ige(state, instruction) {
1994
+ this._comparison(state, instruction, {
1995
+ intrinsic: "greaterThanEqual",
1996
+ operator: ">=",
1997
+ as: "int"
1998
+ });
1999
+ },
2000
+ ieq(state, instruction) {
2001
+ this._comparison(state, instruction, {
2002
+ intrinsic: "equal",
2003
+ operator: "==",
2004
+ as: "int"
2005
+ });
2006
+ },
2007
+ ine(state, instruction) {
2008
+ this._comparison(state, instruction, {
2009
+ intrinsic: "notEqual",
2010
+ operator: "!=",
2011
+ as: "int"
2012
+ });
2013
+ },
2014
+ ult(state, instruction) {
2015
+ this._comparison(state, instruction, {
2016
+ intrinsic: "lessThan",
2017
+ operator: "<",
2018
+ as: "uint"
2019
+ });
2020
+ },
2021
+ uge(state, instruction) {
2022
+ this._comparison(state, instruction, {
2023
+ intrinsic: "greaterThanEqual",
2024
+ operator: ">=",
2025
+ as: "uint"
2026
+ });
2027
+ },
2028
+ and(state, instruction) {
2029
+ this._bitwiseBinary(state, instruction, "&");
2030
+ },
2031
+ or(state, instruction) {
2032
+ this._bitwiseBinary(state, instruction, "|");
2033
+ },
2034
+ xor(state, instruction) {
2035
+ this._bitwiseBinary(state, instruction, "^");
2036
+ },
2037
+ not(state, instruction) {
2038
+ const {
2039
+ mask
2040
+ } = this._destMask(state, instruction);
2041
+ const src = this._vecArg(state, instruction.operands[1], mask, "int");
2042
+ this._assign(state, instruction, `intBitsToFloat(~${src})`);
2043
+ },
2044
+ movc(state, instruction) {
2045
+ this._movc(state, instruction);
2046
+ },
2047
+ if(state, instruction) {
2048
+ this._line(state, `if (${this._condition(state, instruction)}) {`);
2049
+ state.indent += 1;
2050
+ },
2051
+ else(state) {
2052
+ state.indent -= 1;
2053
+ this._line(state, "} else {");
2054
+ state.indent += 1;
2055
+ },
2056
+ endif(state) {
2057
+ state.indent -= 1;
2058
+ this._line(state, "}");
2059
+ },
2060
+ loop(state) {
2061
+ this._line(state, "while(true){");
2062
+ state.indent += 1;
2063
+ },
2064
+ endloop(state) {
2065
+ state.indent -= 1;
2066
+ this._line(state, "}");
2067
+ },
2068
+ break(state) {
2069
+ this._line(state, "break;");
2070
+ },
2071
+ breakc(state, instruction) {
2072
+ this._line(state, `if (${this._condition(state, instruction)}) {break;}`);
2073
+ },
2074
+ continue(state) {
2075
+ this._line(state, "continue;");
2076
+ },
2077
+ continuec(state, instruction) {
2078
+ this._line(state, `if (${this._condition(state, instruction)}) {continue;}`);
2079
+ },
2080
+ switch(state, instruction) {
2081
+ const selector = state.formatter.sourceExpression(instruction.operands[0], {
2082
+ destMask: "x",
2083
+ as: "int"
2084
+ });
2085
+ this._line(state, `switch(${selector}){`);
2086
+ state.indent += 2;
2087
+ },
2088
+ case(state, instruction) {
2089
+ const label = instruction.operands[0].immediateValues[0].uint32 | 0;
2090
+ state.indent -= 1;
2091
+ this._line(state, `case ${label}:`);
2092
+ state.indent += 1;
2093
+ },
2094
+ default(state) {
2095
+ state.indent -= 1;
2096
+ this._line(state, "default:");
2097
+ state.indent += 1;
2098
+ },
2099
+ endswitch(state) {
2100
+ state.indent -= 2;
2101
+ this._line(state, "}");
2102
+ },
2103
+ ret(state) {
2104
+ this._line(state, "return;");
2105
+ },
2106
+ retc(state, instruction) {
2107
+ this._line(state, `if (${this._condition(state, instruction)}) {return;}`);
2108
+ },
2109
+ discard(state, instruction) {
2110
+ const scalar = state.formatter.sourceExpression(instruction.operands[0], {
2111
+ destMask: "x",
2112
+ as: "int"
2113
+ });
2114
+ const test = instruction.testBoolean === "nonzero" ? "!=" : "==";
2115
+ this._line(state, `if((${scalar})${test}0){discard;}`);
2116
+ },
2117
+ // --- integer-conv (corpus-common subset) ---
2118
+ iadd(state, instruction) {
2119
+ this._intBinary(state, instruction, "+");
2120
+ },
2121
+ imad(state, instruction) {
2122
+ const {
2123
+ mask
2124
+ } = this._destMask(state, instruction);
2125
+ const a = this._vecArg(state, instruction.operands[1], mask, "int");
2126
+ const b = this._vecArg(state, instruction.operands[2], mask, "int");
2127
+ const c = this._vecArg(state, instruction.operands[3], mask, "int");
2128
+ this._assign(state, instruction, `intBitsToFloat(${a} * ${b} + ${c})`);
2129
+ },
2130
+ imul(state, instruction) {
2131
+ if (instruction.operands[0].type !== 13) {
2132
+ throw new WebglReadError("imul with a live high-half destination is unimplementable in GLSL ES 3.00", {
2133
+ source: state.sourceName
2134
+ });
2135
+ }
2136
+ const destLow = instruction.operands[1];
2137
+ const mask = state.formatter.destination(destLow)?.registerMask || "xyzw";
2138
+ const a = this._vecArg(state, instruction.operands[2], mask, "int");
2139
+ const b = this._vecArg(state, instruction.operands[3], mask, "int");
2140
+ const statement = state.formatter.assignment(destLow, `intBitsToFloat(${a} * ${b})`);
2141
+ if (statement) this._line(state, statement);
2142
+ },
2143
+ ineg(state, instruction) {
2144
+ const {
2145
+ mask
2146
+ } = this._destMask(state, instruction);
2147
+ const src = this._vecArg(state, instruction.operands[1], mask, "int");
2148
+ this._assign(state, instruction, `intBitsToFloat(-${src})`);
2149
+ },
2150
+ imax(state, instruction) {
2151
+ this._intMinMax(state, instruction, "max", "int");
2152
+ },
2153
+ imin(state, instruction) {
2154
+ this._intMinMax(state, instruction, "min", "int");
2155
+ },
2156
+ umax(state, instruction) {
2157
+ this._intMinMax(state, instruction, "max", "uint");
2158
+ },
2159
+ umin(state, instruction) {
2160
+ this._intMinMax(state, instruction, "min", "uint");
2161
+ },
2162
+ ishl(state, instruction) {
2163
+ this._intBinary(state, instruction, "<<");
2164
+ },
2165
+ ishr(state, instruction) {
2166
+ this._intBinary(state, instruction, ">>");
2167
+ },
2168
+ ushr(state, instruction) {
2169
+ this._intBinary(state, instruction, ">>", "uint");
2170
+ },
2171
+ ftoi(state, instruction) {
2172
+ const {
2173
+ mask
2174
+ } = this._destMask(state, instruction);
2175
+ const src = this._vecArg(state, instruction.operands[1], mask);
2176
+ const type = mask.length > 1 ? `ivec${mask.length}` : "int";
2177
+ this._assign(state, instruction, `intBitsToFloat(${type}(${src}))`);
2178
+ },
2179
+ ftou(state, instruction) {
2180
+ const {
2181
+ mask
2182
+ } = this._destMask(state, instruction);
2183
+ const src = this._vecArg(state, instruction.operands[1], mask);
2184
+ const type = mask.length > 1 ? `uvec${mask.length}` : "uint";
2185
+ this._assign(state, instruction, `uintBitsToFloat(${type}(${src}))`);
2186
+ },
2187
+ itof(state, instruction) {
2188
+ const {
2189
+ mask
2190
+ } = this._destMask(state, instruction);
2191
+ const src = this._vecArg(state, instruction.operands[1], mask, "int");
2192
+ const type = mask.length > 1 ? `vec${mask.length}` : "float";
2193
+ this._assign(state, instruction, `${type}(${src})`, {
2194
+ saturate: instruction.saturate
2195
+ });
2196
+ },
2197
+ utof(state, instruction) {
2198
+ const {
2199
+ mask
2200
+ } = this._destMask(state, instruction);
2201
+ const src = this._vecArg(state, instruction.operands[1], mask, "uint");
2202
+ const type = mask.length > 1 ? `vec${mask.length}` : "float";
2203
+ this._assign(state, instruction, `${type}(${src})`, {
2204
+ saturate: instruction.saturate
2205
+ });
2206
+ },
2207
+ // --- texture-sample ---
2208
+ sample(state, instruction) {
2209
+ this._textureSample(state, instruction);
2210
+ },
2211
+ sample_c(state, instruction) {
2212
+ this._textureSample(state, instruction, {
2213
+ comparisonRefOperandIndex: 4
2214
+ });
2215
+ },
2216
+ sample_c_lz(state, instruction) {
2217
+ this._textureSample(state, instruction, {
2218
+ comparisonRefOperandIndex: 4,
2219
+ forceLodZero: true
2220
+ });
2221
+ },
2222
+ sample_l(state, instruction) {
2223
+ this._textureSample(state, instruction, {
2224
+ lodOperandIndex: 4
2225
+ });
2226
+ },
2227
+ sample_b(state, instruction) {
2228
+ this._textureSample(state, instruction, {
2229
+ biasOperandIndex: 4
2230
+ });
2231
+ },
2232
+ sample_d(state, instruction) {
2233
+ this._textureSample(state, instruction, {
2234
+ gradOperandIndexes: [4, 5]
2235
+ });
2236
+ },
2237
+ udiv(state, instruction) {
2238
+ // Quotient into operand 0, remainder into operand 1; either may be null.
2239
+ // Raw `/` and `%` match HLSLcc; divide-by-zero is GLSL-undefined (spec risk noted).
2240
+ const [quotient, remainder, a, b] = instruction.operands;
2241
+ for (const [destOperand, operator] of [[quotient, "/"], [remainder, "%"]]) {
2242
+ if (destOperand.type === 13) continue;
2243
+ const mask = state.formatter.destination(destOperand)?.registerMask || "xyzw";
2244
+ const left = this._vecArg(state, a, mask, "uint");
2245
+ const right = this._vecArg(state, b, mask, "uint");
2246
+ const statement = state.formatter.assignment(destOperand, `uintBitsToFloat(${left} ${operator} ${right})`);
2247
+ if (statement) this._line(state, statement);
2248
+ }
2249
+ },
2250
+ ld(state, instruction) {
2251
+ this._texelFetch(state, instruction);
2252
+ },
2253
+ deriv_rtx(state, instruction) {
2254
+ this._helperUnary(state, instruction, "dFdx");
2255
+ },
2256
+ deriv_rtx_coarse(state, instruction) {
2257
+ this._helperUnary(state, instruction, "dFdx");
2258
+ },
2259
+ deriv_rtx_fine(state, instruction) {
2260
+ this._helperUnary(state, instruction, "dFdx");
2261
+ },
2262
+ deriv_rty(state, instruction) {
2263
+ this._helperUnary(state, instruction, "dFdy");
2264
+ },
2265
+ deriv_rty_coarse(state, instruction) {
2266
+ this._helperUnary(state, instruction, "dFdy");
2267
+ },
2268
+ deriv_rty_fine(state, instruction) {
2269
+ this._helperUnary(state, instruction, "dFdy");
2270
+ },
2271
+ resinfo(state, instruction) {
2272
+ this._resinfo(state, instruction);
2273
+ },
2274
+ // --- memory-structured ---
2275
+ ld_structured(state, instruction) {
2276
+ this._ldStructured(state, instruction);
2277
+ },
2278
+ // --- integer-conv: half-float packing (skinned vertex data unpacking) ---
2279
+ f16tof32(state, instruction) {
2280
+ const {
2281
+ target,
2282
+ mask
2283
+ } = this._destMask(state, instruction);
2284
+ if (!target) return;
2285
+ for (const component of mask) {
2286
+ const src = state.formatter.sourceExpression(instruction.operands[1], {
2287
+ destMask: component
2288
+ });
2289
+ const lvalue = this._destComponentRef(state, instruction.operands[0], target, component);
2290
+ this._line(state, `${lvalue} = unpackHalf2x16(floatBitsToUint(${src}) & 0xffffu).x;`);
2291
+ }
2292
+ },
2293
+ f32tof16(state, instruction) {
2294
+ const {
2295
+ target,
2296
+ mask
2297
+ } = this._destMask(state, instruction);
2298
+ if (!target) return;
2299
+ for (const component of mask) {
2300
+ const src = state.formatter.sourceExpression(instruction.operands[1], {
2301
+ destMask: component
2302
+ });
2303
+ const lvalue = this._destComponentRef(state, instruction.operands[0], target, component);
2304
+ this._line(state, `${lvalue} = uintBitsToFloat(packHalf2x16(vec2(${src}, 0.0)) & 0xffffu);`);
2305
+ }
2306
+ },
2307
+ nop() {},
2308
+ // --- bitfield (GLSL ES 3.00 has no bitfieldExtract/Insert/bitCount; ES 3.10+ only) ---
2309
+ ibfe(state, instruction) {
2310
+ const helper = this.helpers.require("hlslcc_ibfe");
2311
+ const {
2312
+ target,
2313
+ mask
2314
+ } = this._destMask(state, instruction);
2315
+ if (!target) return;
2316
+ for (const component of mask) {
2317
+ const width = state.formatter.sourceExpression(instruction.operands[1], {
2318
+ destMask: component,
2319
+ as: "int"
2320
+ });
2321
+ const offset = state.formatter.sourceExpression(instruction.operands[2], {
2322
+ destMask: component,
2323
+ as: "int"
2324
+ });
2325
+ const src = state.formatter.sourceExpression(instruction.operands[3], {
2326
+ destMask: component,
2327
+ as: "int"
2328
+ });
2329
+ const lvalue = this._destComponentRef(state, instruction.operands[0], target, component);
2330
+ this._line(state, `${lvalue} = intBitsToFloat(${helper}(${width}, ${offset}, ${src}));`);
2331
+ }
2332
+ },
2333
+ ubfe(state, instruction) {
2334
+ const helper = this.helpers.require("hlslcc_ubfe");
2335
+ const {
2336
+ target,
2337
+ mask
2338
+ } = this._destMask(state, instruction);
2339
+ if (!target) return;
2340
+ for (const component of mask) {
2341
+ const width = state.formatter.sourceExpression(instruction.operands[1], {
2342
+ destMask: component,
2343
+ as: "uint"
2344
+ });
2345
+ const offset = state.formatter.sourceExpression(instruction.operands[2], {
2346
+ destMask: component,
2347
+ as: "uint"
2348
+ });
2349
+ const src = state.formatter.sourceExpression(instruction.operands[3], {
2350
+ destMask: component,
2351
+ as: "uint"
2352
+ });
2353
+ const lvalue = this._destComponentRef(state, instruction.operands[0], target, component);
2354
+ this._line(state, `${lvalue} = uintBitsToFloat(${helper}(${width}, ${offset}, ${src}));`);
2355
+ }
2356
+ },
2357
+ bfi(state, instruction) {
2358
+ const helper = this.helpers.require("hlslcc_bfi");
2359
+ const {
2360
+ target,
2361
+ mask
2362
+ } = this._destMask(state, instruction);
2363
+ if (!target) return;
2364
+ for (const component of mask) {
2365
+ const width = state.formatter.sourceExpression(instruction.operands[1], {
2366
+ destMask: component,
2367
+ as: "uint"
2368
+ });
2369
+ const offset = state.formatter.sourceExpression(instruction.operands[2], {
2370
+ destMask: component,
2371
+ as: "uint"
2372
+ });
2373
+ const insert = state.formatter.sourceExpression(instruction.operands[3], {
2374
+ destMask: component,
2375
+ as: "uint"
2376
+ });
2377
+ const base = state.formatter.sourceExpression(instruction.operands[4], {
2378
+ destMask: component,
2379
+ as: "uint"
2380
+ });
2381
+ const lvalue = this._destComponentRef(state, instruction.operands[0], target, component);
2382
+ this._line(state, `${lvalue} = uintBitsToFloat(${helper}(${width}, ${offset}, ${insert}, ${base}));`);
2383
+ }
2384
+ },
2385
+ countbits(state, instruction) {
2386
+ const helper = this.helpers.require("hlslcc_countbits");
2387
+ const {
2388
+ target,
2389
+ mask
2390
+ } = this._destMask(state, instruction);
2391
+ if (!target) return;
2392
+ for (const component of mask) {
2393
+ const src = state.formatter.sourceExpression(instruction.operands[1], {
2394
+ destMask: component,
2395
+ as: "uint"
2396
+ });
2397
+ const lvalue = this._destComponentRef(state, instruction.operands[0], target, component);
2398
+ this._line(state, `${lvalue} = uintBitsToFloat(${helper}(${src}));`);
2399
+ }
2400
+ },
2401
+ // --- texture-sample: gather4 (GLSL ES 3.00 has no textureGather; emulated via 4 texelFetch taps) ---
2402
+ gather4(state, instruction) {
2403
+ this._gather4(state, instruction);
2404
+ },
2405
+ // --- compute map-style: UAV write ---
2406
+ store_uav_typed(state, instruction) {
2407
+ // Map-style contract: the address operand's xy are assumed to equal the
2408
+ // current fragment's coordinate (the packaging pipeline only routes
2409
+ // map-style compute shaders through this lowering, where every UAV
2410
+ // write targets the invoking thread's own output texel) — they are
2411
+ // deliberately unread here. The z (array-slice) coordinate was resolved
2412
+ // by the `_analyzeUavStores` pre-pass: a multi-slice UAV routes each
2413
+ // store to its own per-slice fragment output, a single-store UAV drops
2414
+ // the slice entirely (the host attaches the target layer).
2415
+ const [uavOperand,, valueOperand] = instruction.operands;
2416
+ const target = state.formatter.destination(uavOperand);
2417
+ if (!target) return;
2418
+ const mask = target.registerMask || "xyzw";
2419
+ const value = this._vecArg(state, valueOperand, mask);
2420
+ // RGBA float render targets are assumed for every map-style UAV output;
2421
+ // uint/sint DXBC UAV formats still pass their bit patterns through as
2422
+ // float here (consistent with this emitter's float-vec4 register model),
2423
+ // so flag it as a warning rather than a hard failure.
2424
+ const returnTypes = state.uavReturnTypes.get(uavOperand.registerIndex);
2425
+ if (returnTypes && returnTypes.some(name => name === "uint" || name === "sint")) {
2426
+ state.warnings.push(`store_uav_typed at offset ${instruction.offset} targets a non-float UAV (${returnTypes.join(",")}); output is treated as an RGBA float render target`);
2427
+ }
2428
+ const plan = state.uavSlicePlan.get(uavOperand.registerIndex);
2429
+ if (plan?.kind === "multiSlice") {
2430
+ const slice = plan.sliceByOffset.get(instruction.offset);
2431
+ const sliceName = state.uavSliceNames.get(uavOperand.registerIndex).get(slice);
2432
+ this._line(state, `${sliceName}${target.mask ? `.${target.mask}` : ""} = ${value};`);
2433
+ return;
2434
+ }
2435
+ this._line(state, `${this._destText(target)} = ${value};`);
2436
+ }
2437
+ };
2438
+
2439
+ /**
2440
+ * `ld_structured` lowering. Vertex UBO path reads `name.data[row]` vec4 rows;
2441
+ * pixel data-texture path reads RGBA32UI texels (4 dwords per texel, fixed
2442
+ * 2048-texel row width). 16-byte-aligned reads use one static channel per
2443
+ * component; unaligned reads fall back to per-dword dynamic addressing.
2444
+ */
2445
+ DxbcGlslEmitter.prototype._ldStructured = function _ldStructured(state, instruction) {
2446
+ const target = state.formatter.destination(instruction.operands[0]);
2447
+ if (!target) return;
2448
+ const mask = target.registerMask || "xyzw";
2449
+ const resourceOperand = instruction.operands[3];
2450
+ const info = state.structuredBuffers.get(resourceOperand.registerIndex);
2451
+ if (!info) {
2452
+ throw new WebglReadError("ld_structured references an undeclared structured buffer", {
2453
+ source: state.sourceName,
2454
+ register: resourceOperand.registerIndex
2455
+ });
2456
+ }
2457
+ if (info.kind === "stub") {
2458
+ // Stubbed tiled-lighting buffer: every component reads a compile-time
2459
+ // zero. floatBitsToUint of this is 0u, so the per-tile light count is 0
2460
+ // and the surrounding light loop is dead (no live sb#/s# references).
2461
+ for (const component of mask) {
2462
+ this._line(state, `${this._destComponentRef(state, instruction.operands[0], target, component)} = uintBitsToFloat(0u);`);
2463
+ }
2464
+ return;
2465
+ }
2466
+ const offsetOperand = instruction.operands[2];
2467
+ if (offsetOperand.type !== 4) {
2468
+ throw new WebglReadError("ld_structured with a non-immediate byte offset is not supported", {
2469
+ source: state.sourceName,
2470
+ offset: instruction.offset
2471
+ });
2472
+ }
2473
+ const offsetDwords = offsetOperand.immediateValues[0].uint32 >> 2;
2474
+ const element = state.formatter.sourceExpression(instruction.operands[1], {
2475
+ destMask: "x",
2476
+ as: "int"
2477
+ });
2478
+ const swizzle = resourceOperand.swizzle || "xyzw";
2479
+ const widthMask = this.profile.dataTextureWidth - 1;
2480
+ const widthShift = Math.log2(this.profile.dataTextureWidth);
2481
+ const aligned = info.strideDwords % 4 === 0 && offsetDwords % 4 === 0;
2482
+ for (const component of mask) {
2483
+ const channel = swizzle["xyzw".indexOf(component)];
2484
+ const channelIndex = "xyzw".indexOf(channel);
2485
+ let value;
2486
+ if (info.kind === "lightIndexPacked") {
2487
+ const dword = `(${element}) * ${info.strideDwords} + ${offsetDwords + channelIndex}`;
2488
+ value = `uintBitsToFloat(texelFetch(${info.name}, ivec2(((${dword}) >> 2) & ${widthMask}, (${dword}) >> ${widthShift + 2}), 0)[(${dword}) & 3])`;
2489
+ } else if (info.kind === "lightDataPacked") {
2490
+ const dword = `(${element}) * ${info.strideDwords} + ${offsetDwords + channelIndex}`;
2491
+ const texel = `(${info.dataTexelBase} + ((${dword}) >> 2))`;
2492
+ value = `uintBitsToFloat(texelFetch(${info.name}, ivec2((${texel}) & ${widthMask}, (${texel}) >> ${widthShift}), 0)[(${dword}) & 3])`;
2493
+ } else if (info.kind === "lightIndexCb") {
2494
+ value = `uintBitsToFloat(cewgLocalLightIndexLoad(${element}))`;
2495
+ } else if (info.kind === "lightDataCb") {
2496
+ if (!aligned) {
2497
+ throw new WebglReadError("local-light constant-buffer lowering requires aligned light rows", {
2498
+ source: state.sourceName,
2499
+ offset: instruction.offset
2500
+ });
2501
+ }
2502
+ value = `cewgLocalLightRow(${element}, ${offsetDwords / 4}).${channel}`;
2503
+ } else if (info.kind === "ubo") {
2504
+ if (aligned) {
2505
+ const row = `(${element}) * ${info.strideDwords / 4}${offsetDwords ? ` + ${offsetDwords / 4}` : ""}`;
2506
+ value = `${info.name}.data[${row}].${channel}`;
2507
+ } else {
2508
+ const dword = `(${element}) * ${info.strideDwords} + ${offsetDwords + channelIndex}`;
2509
+ value = `${info.name}.data[(${dword}) >> 2][(${dword}) & 3]`;
2510
+ }
2511
+ } else if (aligned) {
2512
+ const texel = `(${element}) * ${info.strideDwords / 4}${offsetDwords ? ` + ${offsetDwords / 4}` : ""}`;
2513
+ value = `uintBitsToFloat(texelFetch(${info.name}, ivec2((${texel}) & ${widthMask}, (${texel}) >> ${widthShift}), 0).${channel})`;
2514
+ } else {
2515
+ const dword = `(${element}) * ${info.strideDwords} + ${offsetDwords + channelIndex}`;
2516
+ value = `uintBitsToFloat(texelFetch(${info.name}, ivec2(((${dword}) >> 2) & ${widthMask}, (${dword}) >> ${widthShift + 2}), 0)[(${dword}) & 3])`;
2517
+ }
2518
+ this._line(state, `${this._destComponentRef(state, instruction.operands[0], target, component)} = ${value};`);
2519
+ }
2520
+ };
2521
+
2522
+ /**
2523
+ * `resinfo` lowering: width/height/depth via textureSize per live destination
2524
+ * component; the 4th (mip-count) channel has no WebGL2 equivalent and throws
2525
+ * only when a shader actually reads it.
2526
+ */
2527
+ DxbcGlslEmitter.prototype._resinfo = function _resinfo(state, instruction) {
2528
+ const {
2529
+ target,
2530
+ mask
2531
+ } = this._destMask(state, instruction);
2532
+ if (!target) return;
2533
+ const mipExpression = state.formatter.sourceExpression(instruction.operands[1], {
2534
+ destMask: "x",
2535
+ as: "int"
2536
+ });
2537
+ const texOperand = instruction.operands[2];
2538
+ const dimension = this._resourceDimension(state, instruction, texOperand);
2539
+ const axisCount = dimension === 5 || dimension === 8 ? 3 : 2;
2540
+ const returnType = instruction.resinfoReturnTypeName || "float";
2541
+ const sizeExpression = `textureSize(${state.formatter.registerReference(texOperand)}, ${mipExpression})`;
2542
+ for (const component of mask) {
2543
+ const destElem = "xyzw".indexOf(component);
2544
+ const channelChar = texOperand.swizzle ? texOperand.swizzle[destElem] : component;
2545
+ const channel = "xyzw".indexOf(channelChar);
2546
+ let value;
2547
+ if (channel >= 3) {
2548
+ throw new WebglReadError("resinfo mip-count component has no WebGL2 lowering", {
2549
+ source: state.sourceName,
2550
+ offset: instruction.offset
2551
+ });
2552
+ } else if (channel >= axisCount) {
2553
+ value = returnType === "uint" ? "uintBitsToFloat(0u)" : "0.0";
2554
+ } else {
2555
+ const axis = COMPONENTS[channel];
2556
+ if (returnType === "uint") {
2557
+ value = `uintBitsToFloat(uint(${sizeExpression}.${axis}))`;
2558
+ } else if (returnType === "rcpfloat") {
2559
+ value = `1.0 / float(${sizeExpression}.${axis})`;
2560
+ } else {
2561
+ value = `float(${sizeExpression}.${axis})`;
2562
+ }
2563
+ }
2564
+ this._line(state, `${this._destComponentRef(state, instruction.operands[0], target, component)} = ${value};`);
2565
+ }
2566
+ };
2567
+
2568
+ /**
2569
+ * Shared int/uint min-max lowering used by imax/imin/umax/umin.
2570
+ */
2571
+ DxbcGlslEmitter.prototype._intMinMax = function _intMinMax(state, instruction, fn, as) {
2572
+ const {
2573
+ mask
2574
+ } = this._destMask(state, instruction);
2575
+ const a = this._vecArg(state, instruction.operands[1], mask, as);
2576
+ const b = this._vecArg(state, instruction.operands[2], mask, as);
2577
+ const wrap = as === "uint" ? "uintBitsToFloat" : "intBitsToFloat";
2578
+ this._assign(state, instruction, `${wrap}(${fn}(${a}, ${b}))`);
2579
+ };
2580
+
2581
+ /**
2582
+ * `gather4` lowering: fetches one channel from each of the four texels a
2583
+ * bilinear sample would blend, via the `hlslcc_textureGather4Emulated` /
2584
+ * `hlslcc_textureGather4ArrayEmulated` helpers (GLSL ES 3.00 has no
2585
+ * `textureGather`). Only 2D and 2D-array resources are supported — the
2586
+ * dimensions the AO/blur/FSR/CAS map-style corpus this lowering targets is
2587
+ * known to use (ASSAO gathers from its deinterleaved texture2darray slices).
2588
+ *
2589
+ * @param {object} state Emit state.
2590
+ * @param {object} instruction `gather4` instruction.
2591
+ */
2592
+ DxbcGlslEmitter.prototype._gather4 = function _gather4(state, instruction) {
2593
+ const {
2594
+ mask
2595
+ } = this._destMask(state, instruction);
2596
+ const coordOperand = instruction.operands[1];
2597
+ const texOperand = instruction.operands[2];
2598
+ const samplerOperand = instruction.operands[3];
2599
+ const dimension = this._resourceDimension(state, instruction, texOperand);
2600
+ if (dimension !== 3 && dimension !== 8) {
2601
+ throw new WebglReadError("gather4 against this resource dimension is not supported by the WebGL2 emitter", {
2602
+ source: state.sourceName,
2603
+ dimensionName: texOperand.typeName,
2604
+ offset: instruction.offset
2605
+ });
2606
+ }
2607
+ const helper = this.helpers.require(dimension === 8 ? "hlslcc_textureGather4ArrayEmulated" : "hlslcc_textureGather4Emulated");
2608
+ const coord = this._vecArg(state, coordOperand, dimension === 8 ? "xyz" : "xy");
2609
+ const texName = state.formatter.registerReference(texOperand);
2610
+ const channelLetter = this._gather4Channel(samplerOperand);
2611
+ const channelIndex = "xyzw".indexOf(channelLetter);
2612
+ const call = `${helper}(${texName}, ${coord}, ${channelIndex})`;
2613
+ this._assign(state, instruction, mask === "xyzw" ? call : `${call}.${mask}`, {
2614
+ saturate: instruction.saturate
2615
+ });
2616
+ };
2617
+
2618
+ /**
2619
+ * Reads the gathered-channel selector `gather4`/`gather4_po` carry on their
2620
+ * sampler operand (a 1-component swizzle; red/`x` is the default when no
2621
+ * selection is encoded).
2622
+ *
2623
+ * @param {object} operand Decoded sampler operand.
2624
+ * @returns {string} Channel letter (`x`/`y`/`z`/`w`).
2625
+ */
2626
+ DxbcGlslEmitter.prototype._gather4Channel = function _gather4Channel(operand) {
2627
+ if (operand.selectionModeName === "select1" && operand.selected) {
2628
+ return operand.selected;
2629
+ }
2630
+ if (operand.selectionModeName === "swizzle" && operand.swizzle) {
2631
+ return operand.swizzle[0];
2632
+ }
2633
+ return "x";
2634
+ };
2635
+
2636
+ /**
2637
+ * Assembles the final GLSL text.
2638
+ */
2639
+ DxbcGlslEmitter.prototype._assemble = function _assemble(state) {
2640
+ const lines = ["#version 300 es"];
2641
+ if (state.isPixel || state.isCompute) {
2642
+ lines.push("precision highp float;", "precision highp int;");
2643
+ }
2644
+ lines.push("", ...state.declarationLines);
2645
+ const helperSource = this.helpers.emit();
2646
+ if (helperSource) {
2647
+ lines.push("", helperSource);
2648
+ }
2649
+ // Vertex stages get the standard DX->GL depth-range fixup: EVE's
2650
+ // projection matrices are D3D-style (z_clip in [0, w]), so the raw
2651
+ // DXBC position would land in GL NDC [0, 1] instead of [-1, 1] —
2652
+ // halving depth precision and breaking every pixel-stage read of
2653
+ // SV_Position.z (gl_FragCoord.z) and depth-encoding technique against
2654
+ // the DX-convention values the shaders were compiled for. Remapping
2655
+ // z after the translated body runs makes depth output, clipping and
2656
+ // gl_FragCoord.z all match D3D semantics exactly. The body is wrapped
2657
+ // in a helper so early `ret` (return) statements cannot skip the
2658
+ // fixup.
2659
+ const writesPosition = [...state.outputNames.values()].includes("gl_Position");
2660
+ if (!state.isPixel && !state.isCompute && writesPosition) {
2661
+ lines.push("", "void dxbc_main() {");
2662
+ for (const early of state.earlyMainLines) {
2663
+ lines.push(` ${early}`);
2664
+ }
2665
+ lines.push(...state.bodyLines, "}");
2666
+ lines.push("", "void main() {", " dxbc_main();", " gl_Position.z = 2.0 * gl_Position.z - gl_Position.w;", "}");
2667
+ return `${lines.join("\n")}\n`;
2668
+ }
2669
+ lines.push("", "void main() {");
2670
+ for (const early of state.earlyMainLines) {
2671
+ lines.push(` ${early}`);
2672
+ }
2673
+ lines.push(...state.bodyLines, "}");
2674
+ return `${lines.join("\n")}\n`;
2675
+ };
2676
+
2677
+ export { DxbcGlslEmitter };
2678
+ //# sourceMappingURL=DxbcGlslEmitter.js.map