@forgeax/engine-render 0.1.29 → 0.1.31

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 (350) hide show
  1. package/README.md +248 -21
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +1 -1
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts +4 -3
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/dynamic-geometry-host.d.ts +6 -0
  7. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  8. package/dist/assembly/host-contract.d.ts +6 -0
  9. package/dist/assembly/host-contract.d.ts.map +1 -1
  10. package/dist/assembly/material/assembly.d.ts +26 -3
  11. package/dist/assembly/material/assembly.d.ts.map +1 -1
  12. package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
  13. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
  14. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  15. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  16. package/dist/assembly/recovery/recovery-attempt.d.ts +1 -6
  17. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
  18. package/dist/assembly/recovery/renderer-recover.d.ts +1 -0
  19. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  20. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  21. package/dist/assembly/shader-prewarm-policy.d.ts +1 -1
  22. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  23. package/dist/assembly/webgpu-ready.d.ts +1 -1
  24. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  25. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  26. package/dist/capture/scene-projection.d.ts +41 -0
  27. package/dist/capture/scene-projection.d.ts.map +1 -0
  28. package/dist/{chunk-BQVQOQJH.mjs → chunk-7MQ3XBSP.mjs} +20 -14
  29. package/dist/chunk-7MQ3XBSP.mjs.map +1 -0
  30. package/dist/{chunk-S4DNKQYQ.mjs → chunk-C3DDSXYK.mjs} +86 -33
  31. package/dist/chunk-C3DDSXYK.mjs.map +1 -0
  32. package/dist/{chunk-BTWH7BP4.mjs → chunk-FUUO4UND.mjs} +73 -29
  33. package/dist/chunk-FUUO4UND.mjs.map +1 -0
  34. package/dist/{chunk-NRNXBLIJ.mjs → chunk-JRBDO2KB.mjs} +46 -6
  35. package/dist/chunk-JRBDO2KB.mjs.map +1 -0
  36. package/dist/{chunk-NICXSHAT.mjs → chunk-MDQJPY3T.mjs} +8 -6
  37. package/dist/chunk-MDQJPY3T.mjs.map +1 -0
  38. package/dist/{chunk-JQTSEI3Z.mjs → chunk-NO4WZCAH.mjs} +3 -3
  39. package/dist/chunk-NO4WZCAH.mjs.map +1 -0
  40. package/dist/{chunk-FUODTTIA.mjs → chunk-PSEQ6QPH.mjs} +621 -108
  41. package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
  42. package/dist/{chunk-33XZFTQU.mjs → chunk-S5UE2T5P.mjs} +48 -6
  43. package/dist/chunk-S5UE2T5P.mjs.map +1 -0
  44. package/dist/components/directional-light.d.ts +2 -2
  45. package/dist/components/dynamic-resolution.d.ts +15 -0
  46. package/dist/components/dynamic-resolution.d.ts.map +1 -0
  47. package/dist/components/index.d.ts +1 -0
  48. package/dist/components/index.d.ts.map +1 -1
  49. package/dist/components/light-helpers.d.ts.map +1 -1
  50. package/dist/components/reflection-probe.d.ts +1 -0
  51. package/dist/components/reflection-probe.d.ts.map +1 -1
  52. package/dist/components/skylight.d.ts +4 -1
  53. package/dist/components/skylight.d.ts.map +1 -1
  54. package/dist/construct-renderer.mjs +2172 -796
  55. package/dist/construct-renderer.mjs.map +1 -1
  56. package/dist/device/device-scope.d.ts +2 -0
  57. package/dist/device/device-scope.d.ts.map +1 -1
  58. package/dist/device/gpu-residency.d.ts +2 -2
  59. package/dist/device/gpu-residency.d.ts.map +1 -1
  60. package/dist/dynamic-geometry.d.ts.map +1 -1
  61. package/dist/environment/background.d.ts +3 -2
  62. package/dist/environment/background.d.ts.map +1 -1
  63. package/dist/environment/ibl.d.ts +11 -0
  64. package/dist/environment/ibl.d.ts.map +1 -0
  65. package/dist/environment/storage.d.ts +14 -0
  66. package/dist/environment/storage.d.ts.map +1 -0
  67. package/dist/errors/gpu-driven.d.ts +2 -2
  68. package/dist/errors/gpu-driven.d.ts.map +1 -1
  69. package/dist/errors/render.d.ts +35 -1
  70. package/dist/errors/render.d.ts.map +1 -1
  71. package/dist/extract/camera.d.ts.map +1 -1
  72. package/dist/extract/contracts.d.ts +7 -0
  73. package/dist/extract/contracts.d.ts.map +1 -1
  74. package/dist/extract/gpu-driven.d.ts +18 -1
  75. package/dist/extract/gpu-driven.d.ts.map +1 -1
  76. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  77. package/dist/gpu-driven/material-artifacts.d.ts +24 -0
  78. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
  79. package/dist/gpu-driven/material-bindings.d.ts +7 -0
  80. package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
  81. package/dist/gpu-driven/prepared-draw.d.ts +2 -0
  82. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  83. package/dist/gpu-driven/production-raster.d.ts +27 -6
  84. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  85. package/dist/gpu-resource.d.ts +5 -2
  86. package/dist/gpu-resource.d.ts.map +1 -1
  87. package/dist/gpu-scene-schema.d.ts.map +1 -1
  88. package/dist/gpu-scene.d.ts.map +1 -1
  89. package/dist/hdrp-buffers.d.ts +3 -0
  90. package/dist/hdrp-buffers.d.ts.map +1 -1
  91. package/dist/ibl/IblPipelineCache.d.ts +2 -0
  92. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  93. package/dist/ibl/skylight-bind-group.d.ts +3 -1
  94. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  95. package/dist/index.d.ts +83 -81
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.mjs +7 -5
  98. package/dist/index.mjs.map +1 -1
  99. package/dist/inspection-types.d.ts +16 -1
  100. package/dist/inspection-types.d.ts.map +1 -1
  101. package/dist/internal.mjs +6 -6
  102. package/dist/material-row.d.ts +10 -0
  103. package/dist/material-row.d.ts.map +1 -1
  104. package/dist/pbr-pipeline.d.ts +3 -1
  105. package/dist/pbr-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/render-extent.d.ts +51 -0
  107. package/dist/pipeline/render-extent.d.ts.map +1 -0
  108. package/dist/pipeline/standard-build-context.d.ts +7 -0
  109. package/dist/pipeline/standard-build-context.d.ts.map +1 -0
  110. package/dist/pipeline/standard-forward-lane.d.ts +3 -2
  111. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  112. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  113. package/dist/pipeline/standard-post.d.ts +1 -1
  114. package/dist/pipeline/standard-post.d.ts.map +1 -1
  115. package/dist/pipeline/standard-profile.d.ts +2 -1
  116. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  117. package/dist/pipeline-builder.d.ts +3 -1
  118. package/dist/pipeline-builder.d.ts.map +1 -1
  119. package/dist/plugin.d.ts.map +1 -1
  120. package/dist/post-process-errors.d.ts +9 -2
  121. package/dist/post-process-errors.d.ts.map +1 -1
  122. package/dist/prepare/extended-lighting/resources.d.ts +3 -3
  123. package/dist/record/frame-lighting.d.ts.map +1 -1
  124. package/dist/record/frame-snapshot.d.ts +3 -8
  125. package/dist/record/frame-snapshot.d.ts.map +1 -1
  126. package/dist/record/frame.d.ts +3 -2
  127. package/dist/record/frame.d.ts.map +1 -1
  128. package/dist/record/main-pass-geometry.d.ts +7 -2
  129. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  130. package/dist/record/main-pass-material.d.ts +6 -3
  131. package/dist/record/main-pass-material.d.ts.map +1 -1
  132. package/dist/record/main-pass.d.ts +2 -0
  133. package/dist/record/main-pass.d.ts.map +1 -1
  134. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  135. package/dist/record/recovery-pipeline.d.ts +9 -1
  136. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  137. package/dist/record/render-context.d.ts +34 -3
  138. package/dist/record/render-context.d.ts.map +1 -1
  139. package/dist/record/shadow-pass.d.ts +2 -2
  140. package/dist/record/shadow-pass.d.ts.map +1 -1
  141. package/dist/record/typed-frame-graph.d.ts +5 -2
  142. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  143. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  144. package/dist/reflection/background.d.ts +14 -0
  145. package/dist/reflection/background.d.ts.map +1 -0
  146. package/dist/reflection/filter.d.ts.map +1 -1
  147. package/dist/reflection/projection.d.ts +1 -0
  148. package/dist/reflection/projection.d.ts.map +1 -1
  149. package/dist/reflection/record-owner.d.ts +4 -2
  150. package/dist/reflection/record-owner.d.ts.map +1 -1
  151. package/dist/render-contract.d.ts +47 -32
  152. package/dist/render-contract.d.ts.map +1 -1
  153. package/dist/render-graph-primitives.d.ts.map +1 -1
  154. package/dist/render-pipeline.d.ts +17 -1
  155. package/dist/render-pipeline.d.ts.map +1 -1
  156. package/dist/render-system-extract-tail.d.ts +9 -2
  157. package/dist/render-system-extract-tail.d.ts.map +1 -1
  158. package/dist/render-system-extract.d.ts +29 -2
  159. package/dist/render-system-extract.d.ts.map +1 -1
  160. package/dist/render-system.d.ts.map +1 -1
  161. package/dist/scene/render-scene.d.ts +2 -0
  162. package/dist/scene/render-scene.d.ts.map +1 -1
  163. package/dist/ssao-config.d.ts +8 -6
  164. package/dist/ssao-config.d.ts.map +1 -1
  165. package/dist/ssr/graph.d.ts.map +1 -1
  166. package/dist/temporal/gpu.d.ts.map +1 -1
  167. package/dist/temporal/index.mjs +5 -5
  168. package/dist/temporal/standard-scene-data.d.ts +2 -1
  169. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  170. package/dist/temporal/target-coverage-attachment.d.ts +15 -0
  171. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
  172. package/dist/temporal/temporal-view.d.ts +6 -2
  173. package/dist/temporal/temporal-view.d.ts.map +1 -1
  174. package/dist/temporal/view.d.ts +6 -0
  175. package/dist/temporal/view.d.ts.map +1 -1
  176. package/dist/typed-render-graph-primitives.d.ts +6 -0
  177. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  178. package/dist/volume/component.d.ts +1 -0
  179. package/dist/volume/component.d.ts.map +1 -1
  180. package/package.json +21 -21
  181. package/src/__tests__/capture-scene-projection.unit.test.ts +24 -0
  182. package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
  183. package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
  184. package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
  185. package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
  186. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
  187. package/src/__tests__/factory-contract.integration.test.ts +164 -1
  188. package/src/__tests__/frame-observation.contract.test.ts +21 -0
  189. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +1 -1
  190. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +20 -1
  191. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +0 -3
  192. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +0 -3
  193. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +11 -6
  194. package/src/__tests__/gpu-driven-production.integration.test.ts +13 -25
  195. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -0
  196. package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
  197. package/src/__tests__/gpu-pass-timing.browser.test.ts +14 -1
  198. package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
  199. package/src/__tests__/gpu-scene.unit.test.ts +55 -1
  200. package/src/__tests__/materials-standard-contract.unit.test.ts +5 -1
  201. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
  202. package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -0
  203. package/src/__tests__/physical-clearcoat.integration.test.ts +6 -1
  204. package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -5
  205. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +216 -5
  206. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +68 -1
  207. package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
  208. package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
  209. package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
  210. package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
  211. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -15
  212. package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
  213. package/src/__tests__/render-error-code-owner.test-d.ts +3 -0
  214. package/src/__tests__/render-error-detail-ssot.test-d.ts +5 -1
  215. package/src/__tests__/render-extent.unit.test.ts +63 -0
  216. package/src/__tests__/render-scene-projection.unit.test.ts +3 -2
  217. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +173 -9
  218. package/src/__tests__/shader-manifest-fixture.ts +6 -0
  219. package/src/__tests__/skinned-shadow-caster.test.ts +8 -1
  220. package/src/__tests__/specular-environment-scale.dawn.test.ts +4 -2
  221. package/src/__tests__/ssao-config.unit.test.ts +7 -0
  222. package/src/__tests__/ssao-surface.dawn.test.ts +252 -0
  223. package/src/__tests__/ssr-cube-silhouette.dawn.test.ts +270 -0
  224. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +51 -43
  225. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +4 -0
  226. package/src/__tests__/ssr-gpu-dispatch.ts +10 -5
  227. package/src/__tests__/ssr-graph-topology.unit.test.ts +2 -2
  228. package/src/__tests__/ssr-hiz.browser.test.ts +1 -1
  229. package/src/__tests__/ssr-hiz.dawn.test.ts +1 -1
  230. package/src/__tests__/ssr-spatial.integration.test.ts +1 -1
  231. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +40 -0
  232. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +238 -1
  233. package/src/__tests__/standard-profile-oracle.unit.test.ts +27 -0
  234. package/src/__tests__/taa-coverage-edge.dawn.test.ts +6 -0
  235. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +5 -1
  236. package/src/__tests__/taa-stability-feedback.ts +1 -0
  237. package/src/__tests__/temporal-reset-matrix.unit.test.ts +2 -2
  238. package/src/__tests__/temporal-view.unit.test.ts +17 -0
  239. package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
  240. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
  241. package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
  242. package/src/assembly/atmosphere-shader-prewarm.ts +3 -2
  243. package/src/assembly/device-feature-admission.ts +9 -4
  244. package/src/assembly/dynamic-geometry-host.ts +236 -163
  245. package/src/assembly/host-contract.ts +9 -0
  246. package/src/assembly/material/assembly.ts +350 -6
  247. package/src/assembly/material/pipeline-helpers.ts +100 -0
  248. package/src/assembly/material-shader-policy.ts +39 -18
  249. package/src/assembly/recovery/device-loss-fanout.ts +5 -0
  250. package/src/assembly/recovery/recovery-attempt.ts +2 -29
  251. package/src/assembly/recovery/renderer-recover.ts +6 -9
  252. package/src/assembly/renderer-facade.ts +22 -1
  253. package/src/assembly/shader-prewarm-policy.ts +14 -1
  254. package/src/assembly/webgpu-ready.ts +28 -19
  255. package/src/assembly/webgpu-renderer.ts +95 -95
  256. package/src/capture/scene-projection.ts +37 -0
  257. package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
  258. package/src/components/__tests__/light-radius.integration.test.ts +59 -0
  259. package/src/components/directional-light.ts +3 -3
  260. package/src/components/dynamic-resolution.ts +67 -0
  261. package/src/components/index.ts +1 -0
  262. package/src/components/light-helpers.ts +6 -1
  263. package/src/components/reflection-probe.ts +1 -0
  264. package/src/components/skylight.ts +4 -1
  265. package/src/device/device-scope.ts +13 -7
  266. package/src/device/gpu-residency.ts +11 -11
  267. package/src/dynamic-geometry.ts +13 -8
  268. package/src/environment/background.ts +30 -12
  269. package/src/environment/ibl.ts +211 -0
  270. package/src/environment/storage.ts +63 -0
  271. package/src/errors/gpu-driven.ts +10 -3
  272. package/src/errors/render.ts +64 -0
  273. package/src/extract/camera.ts +12 -1
  274. package/src/extract/contracts.ts +10 -0
  275. package/src/extract/gpu-driven.ts +72 -17
  276. package/src/gpu-driven/batch-topology.ts +33 -11
  277. package/src/gpu-driven/material-artifacts.ts +179 -0
  278. package/src/gpu-driven/material-bindings.ts +41 -0
  279. package/src/gpu-driven/prepared-draw.ts +13 -2
  280. package/src/gpu-driven/production-raster.ts +681 -164
  281. package/src/gpu-resource.ts +21 -2
  282. package/src/gpu-scene-schema.ts +11 -2
  283. package/src/gpu-scene.ts +14 -3
  284. package/src/hdrp-buffers.ts +69 -0
  285. package/src/ibl/IblPipelineCache.ts +5 -0
  286. package/src/ibl/skylight-bind-group.ts +27 -17
  287. package/src/index.ts +92 -81
  288. package/src/inspection-types.ts +16 -1
  289. package/src/material-row.ts +60 -11
  290. package/src/pbr-pipeline.ts +13 -2
  291. package/src/pipeline/render-extent.ts +111 -0
  292. package/src/pipeline/standard-build-context.ts +7 -0
  293. package/src/pipeline/standard-forward-lane.ts +41 -24
  294. package/src/pipeline/standard-pipeline.ts +47 -18
  295. package/src/pipeline/standard-post.ts +4 -0
  296. package/src/pipeline/standard-profile.ts +2 -1
  297. package/src/pipeline-builder.ts +12 -3
  298. package/src/plugin.ts +4 -0
  299. package/src/post-process-errors.ts +26 -18
  300. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
  301. package/src/prepare/extended-lighting/resources.ts +3 -3
  302. package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
  303. package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
  304. package/src/record/__tests__/material-slot-plan.unit.test.ts +9 -0
  305. package/src/record/__tests__/recovery-pipeline.unit.test.ts +47 -1
  306. package/src/record/__tests__/sprite-material-abi.unit.test.ts +7 -3
  307. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
  308. package/src/record/frame-lighting.ts +3 -1
  309. package/src/record/frame-snapshot.ts +13 -11
  310. package/src/record/frame.ts +150 -74
  311. package/src/record/main-pass-geometry.ts +60 -9
  312. package/src/record/main-pass-material.ts +47 -19
  313. package/src/record/main-pass.ts +63 -4
  314. package/src/record/prepared-material-bindings.ts +4 -0
  315. package/src/record/recovery-pipeline.ts +56 -0
  316. package/src/record/render-context.ts +34 -3
  317. package/src/record/shadow-pass.ts +187 -50
  318. package/src/record/typed-frame-graph.ts +60 -3
  319. package/src/record/view-ubo.ts +1 -1
  320. package/src/recovery/render-system-candidate.ts +54 -11
  321. package/src/reflection/background.ts +151 -0
  322. package/src/reflection/filter.ts +3 -0
  323. package/src/reflection/projection.ts +16 -6
  324. package/src/reflection/record-owner.ts +282 -86
  325. package/src/render-contract.ts +50 -32
  326. package/src/render-graph-primitives.ts +28 -3
  327. package/src/render-pipeline.ts +20 -1
  328. package/src/render-system-extract-tail.ts +23 -3
  329. package/src/render-system-extract.ts +127 -42
  330. package/src/render-system.ts +35 -7
  331. package/src/scene/render-scene.ts +8 -0
  332. package/src/ssao-config.ts +25 -8
  333. package/src/ssr/graph.ts +78 -58
  334. package/src/temporal/gpu.ts +5 -0
  335. package/src/temporal/standard-scene-data.ts +7 -1
  336. package/src/temporal/target-coverage-attachment.ts +72 -0
  337. package/src/temporal/temporal-view.ts +10 -4
  338. package/src/temporal/view.ts +12 -2
  339. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +24 -9
  340. package/src/typed-render-graph-primitives.ts +38 -0
  341. package/src/volume/component.ts +1 -0
  342. package/src/volume/passes.ts +3 -3
  343. package/dist/chunk-33XZFTQU.mjs.map +0 -1
  344. package/dist/chunk-BQVQOQJH.mjs.map +0 -1
  345. package/dist/chunk-BTWH7BP4.mjs.map +0 -1
  346. package/dist/chunk-FUODTTIA.mjs.map +0 -1
  347. package/dist/chunk-JQTSEI3Z.mjs.map +0 -1
  348. package/dist/chunk-NICXSHAT.mjs.map +0 -1
  349. package/dist/chunk-NRNXBLIJ.mjs.map +0 -1
  350. package/dist/chunk-S4DNKQYQ.mjs.map +0 -1
@@ -0,0 +1,270 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { mat4, vec3 } from '@forgeax/engine-math';
4
+ import { expect, it } from 'vitest';
5
+ import {
6
+ GPU_TEXTURE_USAGE_COPY_SRC,
7
+ GPU_TEXTURE_USAGE_RENDER_ATTACHMENT,
8
+ GPU_TEXTURE_USAGE_STORAGE_BINDING,
9
+ GPU_TEXTURE_USAGE_TEXTURE_BINDING,
10
+ } from '../gpu-texture-usage';
11
+ import {
12
+ GPU_BUFFER_USAGE_COPY_DST,
13
+ GPU_BUFFER_USAGE_MAP_READ,
14
+ GPU_BUFFER_USAGE_UNIFORM,
15
+ } from '../gpu-usage';
16
+ import { VIEW_UNIFORM_BYTES } from '../record/view-ubo';
17
+
18
+ // Independent analytic raster inputs and mirror-box oracle reproduce the ordinary
19
+ // plane + cube example. No captured GPU handles, pixels or shader outputs are fixtures.
20
+ it.each([
21
+ 0, 0.07, -0.07,
22
+ ])('keeps the reflected cube silhouette connected without exterior rays (camera offset %f)', async (offset) => {
23
+ const compiler = await import(
24
+ /* @vite-ignore */ new URL('../../../shader-compiler/dist/index.mjs', import.meta.url).href
25
+ );
26
+ const source = (name: string) =>
27
+ readFileSync(resolve(`packages/shader/src/${name}.wgsl`), 'utf8');
28
+ const compile = async (name: string, id: string) => {
29
+ const result = await compiler.compileShader(source(name), {
30
+ id,
31
+ imports: { 'forgeax_view::common': source('common') },
32
+ });
33
+ if (!result.ok) throw result.error;
34
+ return result.value.wgsl;
35
+ };
36
+ const adapter = await navigator.gpu.requestAdapter();
37
+ if (!adapter) throw new Error('Dawn adapter unavailable');
38
+ const device = await adapter.requestDevice();
39
+ device.pushErrorScope('validation');
40
+ try {
41
+ const size = 512,
42
+ half = size / 2;
43
+ const projection = mat4.create(),
44
+ camera = mat4.create(),
45
+ vp = mat4.create(),
46
+ inverse = mat4.create();
47
+ mat4.perspective(projection, Math.PI / 3, 1, 0.1, 20);
48
+ mat4.lookAt(
49
+ camera,
50
+ vec3.create(6 + offset, 3, 6 - offset),
51
+ vec3.create(offset, 0, -offset),
52
+ vec3.create(0, 1, 0),
53
+ );
54
+ mat4.multiply(vp, projection, camera);
55
+ mat4.invert(inverse, vp);
56
+ const values = new Float32Array(VIEW_UNIFORM_BYTES / 4);
57
+ values.set(vp);
58
+ values.set([6 + offset, 3, 6 - offset], 24);
59
+ values.set(inverse, 44);
60
+ values.set([0.1, 20, 0, 0], 228);
61
+ values.set([12, 0.2, 0.65, 1], 236);
62
+ const view = device.createBuffer({
63
+ size: VIEW_UNIFORM_BYTES,
64
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
65
+ });
66
+ device.queue.writeBuffer(view, 0, values);
67
+ const sampled = GPU_TEXTURE_USAGE_TEXTURE_BINDING | GPU_TEXTURE_USAGE_RENDER_ATTACHMENT;
68
+ const depth = device.createTexture({
69
+ size: [size, size],
70
+ format: 'depth32float',
71
+ usage: sampled,
72
+ });
73
+ const color = () =>
74
+ device.createTexture({ size: [size, size], format: 'rgba16float', usage: sampled });
75
+ const normal = color(),
76
+ radiance = color(),
77
+ fallback = color(),
78
+ temporal = color();
79
+ const hiz = device.createTexture({
80
+ size: [half, half],
81
+ mipLevelCount: 9,
82
+ format: 'r32float',
83
+ usage: GPU_TEXTURE_USAGE_TEXTURE_BINDING | GPU_TEXTURE_USAGE_STORAGE_BINDING,
84
+ });
85
+ const traced = device.createTexture({
86
+ size: [half, half],
87
+ format: 'rgba16float',
88
+ usage: GPU_TEXTURE_USAGE_STORAGE_BINDING | GPU_TEXTURE_USAGE_COPY_SRC,
89
+ });
90
+ const reactive = device.createTexture({
91
+ size: [half, half],
92
+ format: 'r32float',
93
+ usage: GPU_TEXTURE_USAGE_STORAGE_BINDING,
94
+ });
95
+ const seed = device.createShaderModule({
96
+ code: `
97
+ @group(0) @binding(0) var<uniform> vp: mat4x4<f32>;
98
+ @vertex fn vs(@builtin(vertex_index) i:u32)->@builtin(position) vec4<f32> {
99
+ let p=array<vec2<f32>,3>(vec2<f32>(-1.0),vec2<f32>(3.0,-1.0),vec2<f32>(-1.0,3.0));return vec4<f32>(p[i],0.0,1.0);
100
+ }
101
+ struct Out { @builtin(frag_depth) depth:f32, @location(0) normal:vec4<f32>, @location(1) color:vec4<f32>, @location(2) fallback:vec4<f32>, @location(3) temporal:vec4<f32> };
102
+ @fragment fn fs(@builtin(position) p:vec4<f32>)->Out {
103
+ let screen=vec2<f32>(p.x/${size}.0*2.0-1.0,1.0-p.y/${size}.0*2.0)/sqrt(3.0);
104
+ let eye=vec3<f32>(${6 + offset},3.0,${6 - offset});
105
+ let d=vec3<f32>(-2.0/3.0,-1.0/3.0,-2.0/3.0)+screen.x*vec3<f32>(sqrt(0.5),0.0,-sqrt(0.5))+screen.y*vec3<f32>(-1.0/sqrt(18.0),sqrt(8.0/9.0),-1.0/sqrt(18.0));
106
+ let lower=min((vec3<f32>(-1.0)-eye)/d,(vec3<f32>(1.0)-eye)/d);
107
+ let upper=max((vec3<f32>(-1.0)-eye)/d,(vec3<f32>(1.0)-eye)/d);
108
+ let enter=max(max(lower.x,lower.y),lower.z);let leave=min(min(upper.x,upper.y),upper.z);
109
+ var t=select(1e6,(-1.0-eye.y)/d.y,d.y<0.0);var n=vec3<f32>(0.0,1.0,0.0);var c=vec3<f32>(0.1,0.2,0.4);var roughness=0.08;
110
+ if (enter>0.0 && enter<leave && enter<t) {
111
+ t=enter;let hit=eye+d*t;let a=abs(hit);n=vec3<f32>(0.0);
112
+ if(a.x>=a.y && a.x>=a.z){n.x=sign(hit.x);}else if(a.y>=a.z){n.y=sign(hit.y);}else{n.z=sign(hit.z);}
113
+ c=vec3<f32>(1.0,0.05,0.02);roughness=0.4;
114
+ }
115
+ let clip=vp*vec4<f32>(eye+d*t,1.0);var out:Out;
116
+ out.depth=clamp(clip.z/clip.w,0.0,1.0);out.normal=vec4<f32>(n*0.5+0.5,roughness);out.color=vec4<f32>(c,1.0);out.fallback=vec4<f32>(0.1,0.1,0.1,1.0);out.temporal=vec4<f32>(0.0);return out;
117
+ }`,
118
+ });
119
+ const seedPipeline = await device.createRenderPipelineAsync({
120
+ layout: 'auto',
121
+ vertex: { module: seed, entryPoint: 'vs' },
122
+ fragment: {
123
+ module: seed,
124
+ entryPoint: 'fs',
125
+ targets: Array.from({ length: 4 }, () => ({ format: 'rgba16float' as const })),
126
+ },
127
+ depthStencil: { format: 'depth32float', depthWriteEnabled: true, depthCompare: 'always' },
128
+ });
129
+ const compute = async (name: string, id: string, entryPoint: string) =>
130
+ device.createComputePipelineAsync({
131
+ layout: 'auto',
132
+ compute: {
133
+ module: device.createShaderModule({ code: await compile(name, id) }),
134
+ entryPoint,
135
+ },
136
+ });
137
+ const seedHiz = await compute('ssr-hiz', 'forgeax_ssr::hiz', 'ssr_hiz_seed');
138
+ const reduceHiz = await compute('ssr-hiz-reduce', 'forgeax_ssr::hiz_reduce', 'ssr_hiz_reduce');
139
+ const trace = await compute('ssr-trace', 'forgeax_ssr::trace', 'ssr_trace');
140
+ const encoder = device.createCommandEncoder();
141
+ const raster = encoder.beginRenderPass({
142
+ colorAttachments: [normal, radiance, fallback, temporal].map((t) => ({
143
+ view: t.createView(),
144
+ loadOp: 'clear' as const,
145
+ storeOp: 'store' as const,
146
+ clearValue: [0, 0, 0, 0],
147
+ })),
148
+ depthStencilAttachment: {
149
+ view: depth.createView(),
150
+ depthClearValue: 1,
151
+ depthLoadOp: 'clear',
152
+ depthStoreOp: 'store',
153
+ },
154
+ });
155
+ raster.setPipeline(seedPipeline);
156
+ raster.setBindGroup(
157
+ 0,
158
+ device.createBindGroup({
159
+ layout: seedPipeline.getBindGroupLayout(0),
160
+ entries: [{ binding: 0, resource: { buffer: view } }],
161
+ }),
162
+ );
163
+ raster.draw(3);
164
+ raster.end();
165
+ const dispatch = (
166
+ pipeline: GPUComputePipeline,
167
+ entries: GPUBindGroupEntry[],
168
+ extent: number,
169
+ ) => {
170
+ const pass = encoder.beginComputePass();
171
+ pass.setPipeline(pipeline);
172
+ pass.setBindGroup(
173
+ 0,
174
+ device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries }),
175
+ );
176
+ pass.dispatchWorkgroups(Math.ceil(extent / 8), Math.ceil(extent / 8));
177
+ pass.end();
178
+ };
179
+ for (let level = 0; level < 9; level++) {
180
+ dispatch(
181
+ level === 0 ? seedHiz : reduceHiz,
182
+ [
183
+ {
184
+ binding: 0,
185
+ resource:
186
+ level === 0
187
+ ? depth.createView()
188
+ : hiz.createView({ baseMipLevel: level - 1, mipLevelCount: 1 }),
189
+ },
190
+ { binding: 1, resource: hiz.createView({ baseMipLevel: level, mipLevelCount: 1 }) },
191
+ ...(level === 0 ? [{ binding: 2, resource: { buffer: view } }] : []),
192
+ ],
193
+ half >> level,
194
+ );
195
+ }
196
+ dispatch(
197
+ trace,
198
+ [depth, normal, radiance, hiz, traced]
199
+ .map<GPUBindGroupEntry>((t, binding) => ({ binding, resource: t.createView() }))
200
+ .concat([
201
+ { binding: 5, resource: { buffer: view } },
202
+ { binding: 6, resource: fallback.createView() },
203
+ { binding: 7, resource: temporal.createView() },
204
+ { binding: 8, resource: reactive.createView() },
205
+ ]),
206
+ half,
207
+ );
208
+ const readback = device.createBuffer({
209
+ size: half * half * 8,
210
+ usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST,
211
+ });
212
+ encoder.copyTextureToBuffer({ texture: traced }, { buffer: readback, bytesPerRow: half * 8 }, [
213
+ half,
214
+ half,
215
+ ]);
216
+ device.queue.submit([encoder.finish()]);
217
+ await readback.mapAsync(GPU_BUFFER_USAGE_MAP_READ);
218
+ const words = new Uint16Array(readback.getMappedRange().slice(0));
219
+ readback.unmap();
220
+ const hits = new Uint8Array(half * half);
221
+ // Mirror-box slab intersections are independent of the GPU march and Hi-Z.
222
+ for (let y = 0; y < half; y++)
223
+ for (let x = 0; x < half; x++) {
224
+ const sx = (((2 * x + 0.5) / size) * 2 - 1) / Math.sqrt(3),
225
+ sy = (1 - ((2 * y + 0.5) / size) * 2) / Math.sqrt(3);
226
+ const d = [
227
+ -2 / 3 + sx * Math.SQRT1_2 - sy / Math.sqrt(18),
228
+ -1 / 3 + sy * Math.sqrt(8 / 9),
229
+ -2 / 3 - sx * Math.SQRT1_2 - sy / Math.sqrt(18),
230
+ ] as const;
231
+ let enter = -Infinity,
232
+ leave = Infinity;
233
+ const bounds = [
234
+ [-1, 1],
235
+ [-3, -1],
236
+ [-1, 1],
237
+ ] as const;
238
+ const eye = [6 + offset, 3, 6 - offset] as const;
239
+ for (const axis of [0, 1, 2] as const) {
240
+ const [lo, hi] = bounds[axis];
241
+ const a = (lo - eye[axis]) / d[axis],
242
+ b = (hi - eye[axis]) / d[axis];
243
+ enter = Math.max(enter, Math.min(a, b));
244
+ leave = Math.min(leave, Math.max(a, b));
245
+ }
246
+ hits[y * half + x] = Number(enter > 0 && enter < leave);
247
+ }
248
+ let interior = 0,
249
+ misses = 0,
250
+ exteriorHits = 0;
251
+ for (let y = 180; y < half - 1; y++)
252
+ for (let x = 1; x < half - 1; x++) {
253
+ const footprint = [-1, 0, 1].flatMap((dy) =>
254
+ [-1, 0, 1].map((dx) => hits[(y + dy) * half + x + dx] === 1),
255
+ );
256
+ const offset = (y * half + x) * 4;
257
+ const confident = (words[offset + 3] ?? 0) >= 0x3800; // positive half >= 0.5
258
+ if (footprint.every(Boolean)) {
259
+ interior++;
260
+ if (!confident) misses++;
261
+ }
262
+ if (!footprint.some(Boolean) && confident && (words[offset] ?? 0) >= 0x3800) exteriorHits++;
263
+ }
264
+ expect(await device.popErrorScope()).toBeNull();
265
+ expect(interior).toBeGreaterThan(1000);
266
+ expect({ misses, exteriorHits }).toEqual({ misses: 0, exteriorHits: 0 });
267
+ } finally {
268
+ device.destroy();
269
+ }
270
+ }, 30_000);
@@ -1,5 +1,6 @@
1
+ import type { RhiDevice } from '@forgeax/engine-rhi';
1
2
  import { rhi } from '@forgeax/engine-rhi-webgpu';
2
- import { describe, expect, it } from 'vitest';
3
+ import { beforeAll, describe, expect, it } from 'vitest';
3
4
  import {
4
5
  admittedSsrSpatial,
5
6
  runSsrGpuDispatch,
@@ -10,31 +11,43 @@ import {
10
11
  } from './ssr-gpu-dispatch';
11
12
  import { runSsrReprojectionProbe } from './ssr-reprojection-probe';
12
13
 
14
+ const confidence = (values: readonly number[]) => values.filter((_, i) => i % 4 === 3);
15
+
13
16
  describe('SSR GPU dispatch Browser WebGPU probe', () => {
14
- it('executes the admitted Hi-Z and trace shaders and reads a finite result', async () => {
17
+ let device: RhiDevice;
18
+ let sources: ReturnType<typeof sourcesFromManifest>;
19
+ beforeAll(async () => {
15
20
  expect(admittedSsrSpatial().status).toBe('admitted');
16
21
  const response = await fetch('/shaders/manifest.json');
17
22
  expect(response.ok).toBe(true);
18
- const manifest = (await response.json()) as SsrShaderManifest;
23
+ sources = sourcesFromManifest((await response.json()) as SsrShaderManifest);
19
24
  const adapter = (await rhi.requestAdapter()).unwrap();
20
- const device = (await adapter.requestDevice()).unwrap();
21
- expect(await runSsrReprojectionProbe(device, sourcesFromManifest(manifest).temporal)).toEqual([
25
+ device = (await adapter.requestDevice()).unwrap();
26
+ });
27
+
28
+ // Independent cases identify the failing invariant. The 300-frame case
29
+ // also includes cold software-driver compilation and completed readbacks;
30
+ // its bounded 30-second execution allowance is not a frame-performance gate.
31
+ it('reprojects history and reconstructs perspective and orthographic view distance', async () => {
32
+ expect(await runSsrReprojectionProbe(device, sources.temporal)).toEqual([
22
33
  [0.5, 0.5, 0.375, 0.5625],
23
34
  [0.5, 0.5, 0.375, 0.5625],
24
35
  [0.5, 0.5, 0.375, 0.5625],
25
36
  ]);
26
- for (const distances of await runSsrViewDistanceProbe(
27
- device,
28
- sourcesFromManifest(manifest).trace,
29
- )) {
37
+ for (const distances of await runSsrViewDistanceProbe(device, sources.trace)) {
30
38
  expect(distances[0]).toBeCloseTo(6.58, 4);
31
39
  expect(distances[1]).toBeCloseTo(12, 4);
32
40
  }
33
- const hit = await runSsrGpuDispatch(device, sourcesFromManifest(manifest), 300);
41
+ });
42
+
43
+ it('executes 300 stable Hi-Z, trace and temporal frames with finite visible hits', async () => {
44
+ const hit = await runSsrGpuDispatch(device, sources, 300);
45
+ expect(hit.passNames.filter((name) => name.startsWith('ssr-hiz-reduce'))).toHaveLength(1);
46
+ expect(hit.passNames.filter((name) => name.startsWith('ssr-reflection-mip'))).toHaveLength(1);
34
47
  expect(hit.passNames).toContain('ssr-hiz-seed');
35
48
  expect(hit.passNames).toContain('ssr-trace');
36
49
  expect(hit.passNames).toContain('ssr-temporal');
37
- expect(hit.passNames).toContain('ssr-trace');
50
+ expect(hit.passNames.filter((name) => name === 'ssr-compose')).toHaveLength(1);
38
51
  expect(hit.resourceNames).toContain('ssr-resolved');
39
52
  expect(hit.pixels).toHaveLength(300);
40
53
  expect(new Set(hit.pixels.map((pixel) => pixel.join(','))).size).toBe(1);
@@ -45,47 +58,42 @@ describe('SSR GPU dispatch Browser WebGPU probe', () => {
45
58
  const dx = (((0.5 / 64) * 2 - 1) * 2) / Math.sqrt(3);
46
59
  expect(hiz[0]).toBeCloseTo(6 / (1 - dx), 2);
47
60
  const trace = hit.tracePixels[0] ?? [];
48
- const confidence = (values: readonly number[]) => values.filter((_, i) => i % 4 === 3);
49
61
  expect(confidence(trace).some((value) => value > 0)).toBe(true);
50
62
  expect(confidence(trace).filter((value) => value >= 0x3400).length).toBeGreaterThan(40);
51
63
  expect(confidence(trace).every((value) => value <= 0x3c00)).toBe(true);
52
- const backFace = await runSsrGpuDispatch(device, sourcesFromManifest(manifest), 1, {
53
- sourceBackFacing: true,
54
- });
55
- expect(confidence(backFace.tracePixels[0] ?? []).every((value) => value === 0)).toBe(true);
64
+ }, 30_000);
65
+
66
+ it('rejects back-facing sources', async () => {
67
+ const result = await runSsrGpuDispatch(device, sources, 1, { sourceBackFacing: true });
68
+ expect(confidence(result.tracePixels[0] ?? []).every((value) => value === 0)).toBe(true);
69
+ });
56
70
 
57
- const coordinates = await runSsrGpuDispatch(device, sourcesFromManifest(manifest), 1, {
71
+ it.each([
72
+ false,
73
+ true,
74
+ ])('preserves reflected coordinates (recessed source: %s)', async (recessedSource) => {
75
+ const result = await runSsrGpuDispatch(device, sources, 1, {
58
76
  coordinateSource: true,
77
+ recessedSource,
59
78
  });
60
- const errors = ssrCoordinateErrors(coordinates.tracePixels[0] ?? []);
79
+ const errors = ssrCoordinateErrors(result.tracePixels[0] ?? [], recessedSource);
61
80
  expect(errors.length).toBeGreaterThan(10);
62
81
  expect(errors.filter((error) => error > 0.002).length / errors.length).toBeLessThan(0.05);
82
+ });
63
83
 
64
- const grout = await runSsrGpuDispatch(device, sourcesFromManifest(manifest), 1, {
65
- coordinateSource: true,
66
- recessedSource: true,
67
- });
68
- const groutErrors = ssrCoordinateErrors(grout.tracePixels[0] ?? [], true);
69
- expect(groutErrors.length).toBeGreaterThan(10);
70
- expect(groutErrors.filter((error) => error > 0.002).length / groutErrors.length).toBeLessThan(
71
- 0.05,
72
- );
73
-
74
- for (const options of [{ excludedSource: true }, { excludedReceiver: true }]) {
75
- const excluded = await runSsrGpuDispatch(device, sourcesFromManifest(manifest), 1, options);
76
- expect(confidence(excluded.tracePixels[0] ?? []).every((value) => value === 0)).toBe(true);
77
- }
78
-
79
- const miss = await runSsrGpuDispatch(device, sourcesFromManifest(manifest), 1, {
80
- maxDistance: 0.01,
81
- });
82
- expect(miss.tracePixels[0]?.[3]).toBe(0);
83
- expect(miss.tracePixels[0]).not.toEqual(hit.tracePixels[0]);
84
+ it.each([
85
+ { excludedSource: true },
86
+ { excludedReceiver: true },
87
+ ])('rejects excluded material coverage (%j)', async (options) => {
88
+ const result = await runSsrGpuDispatch(device, sources, 1, options);
89
+ expect(confidence(result.tracePixels[0] ?? []).every((value) => value === 0)).toBe(true);
90
+ });
84
91
 
85
- const disabled = await runSsrGpuDispatch(device, sourcesFromManifest(manifest), 1, {
86
- enabled: false,
87
- });
88
- expect(disabled.tracePixels[0]?.[3]).toBe(0);
89
- expect(disabled.tracePixels[0]).not.toEqual(hit.tracePixels[0]);
92
+ it.each([
93
+ { maxDistance: 0.01 },
94
+ { enabled: false },
95
+ ])('produces zero confidence for an unavailable ray (%j)', async (options) => {
96
+ const result = await runSsrGpuDispatch(device, sources, 1, options);
97
+ expect(confidence(result.tracePixels[0] ?? []).every((value) => value === 0)).toBe(true);
90
98
  });
91
99
  });
@@ -39,6 +39,10 @@ describe('SSR GPU dispatch Dawn probe', () => {
39
39
  expect(distances[1]).toBeCloseTo(12, 4);
40
40
  }
41
41
  const hit = await runSsrGpuDispatch(device, sourcesFromManifest(manifest), 300);
42
+ // Mip count must not grow CPU pass recording: ordered dispatches share
43
+ // one compute pass per pyramid, with every level still produced below.
44
+ expect(hit.passNames.filter((name) => name.startsWith('ssr-hiz-reduce'))).toHaveLength(1);
45
+ expect(hit.passNames.filter((name) => name.startsWith('ssr-reflection-mip'))).toHaveLength(1);
42
46
  expect(hit.passNames).toContain('ssr-hiz-seed');
43
47
  expect(hit.passNames).toContain('ssr-trace');
44
48
  expect(hit.passNames).toContain('ssr-temporal');
@@ -887,15 +887,22 @@ export async function runSsrGpuDispatch(
887
887
  const submitted = device.queue.submit([finished.value]);
888
888
  if (!submitted.ok) throw submitted.error;
889
889
  await device.queue.onSubmittedWorkDone();
890
- const mapped = await readbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ);
890
+ // These independent copies share one completed submission. Request all
891
+ // mappings together instead of paying three browser/driver round trips
892
+ // for each of the 300 frames; every buffer is still read and validated.
893
+ const [mapped, traceMapped, hizMapped] = await Promise.all([
894
+ readbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ),
895
+ traceReadbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ),
896
+ hizReadbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ),
897
+ ]);
891
898
  if (!mapped.ok) throw mapped.error;
899
+ if (!traceMapped.ok) throw traceMapped.error;
900
+ if (!hizMapped.ok) throw hizMapped.error;
892
901
  const range = mapped.value.getMappedRange();
893
902
  if (!range.ok) throw range.error;
894
903
  const readbackWords = new Uint16Array(range.value.slice(0, bytesPerRow * SSR_GPU_PROBE_HEIGHT));
895
904
  pixels.push([...readbackWords.slice(0, 4)]);
896
905
  mapped.value.unmap();
897
- const traceMapped = await traceReadbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ);
898
- if (!traceMapped.ok) throw traceMapped.error;
899
906
  const traceRange = traceMapped.value.getMappedRange();
900
907
  if (!traceRange.ok) throw traceRange.error;
901
908
  const traceWords = new Uint16Array(
@@ -903,8 +910,6 @@ export async function runSsrGpuDispatch(
903
910
  );
904
911
  tracePixels.push([...traceWords]);
905
912
  traceMapped.value.unmap();
906
- const hizMapped = await hizReadbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ);
907
- if (!hizMapped.ok) throw hizMapped.error;
908
913
  const hizRange = hizMapped.value.getMappedRange();
909
914
  if (!hizRange.ok) throw hizRange.error;
910
915
  const hizWords = new Float32Array(hizRange.value.slice(0, bytesPerRow * SSR_GPU_PROBE_HEIGHT));
@@ -78,7 +78,7 @@ describe('SSR typed spatial graph', () => {
78
78
  const compiled = graph.compile({ device, surfaceSize: { width: 5, height: 3 } }).unwrap();
79
79
  const info = compiled.inspect();
80
80
  const names = info.passes.map((pass) => pass.name);
81
- expect(names).toEqual(['seed-inputs', 'ssr-hiz-seed', 'ssr-hiz-reduce-1', 'ssr-trace']);
81
+ expect(names).toEqual(['seed-inputs', 'ssr-hiz-seed', 'ssr-hiz-reduce-chain', 'ssr-trace']);
82
82
  expect(info.resources.map((resource) => resource.label)).toEqual(
83
83
  expect.arrayContaining(['ssr-hiz', 'ssr-trace', 'ssr-hit-reactivity']),
84
84
  );
@@ -88,7 +88,7 @@ describe('SSR typed spatial graph', () => {
88
88
  { resource: 'ssr-hit-reactivity', usage: 'storage-write' },
89
89
  ]),
90
90
  );
91
- expect(info.passes.find((pass) => pass.name === 'ssr-hiz-reduce-1')?.accesses).toEqual([
91
+ expect(info.passes.find((pass) => pass.name === 'ssr-hiz-reduce-chain')?.accesses).toEqual([
92
92
  { resource: 'ssr-hiz', usage: 'sampled-read' },
93
93
  { resource: 'ssr-hiz', usage: 'storage-write' },
94
94
  ]);
@@ -125,7 +125,7 @@ describe('SSR Hi-Z spatial Browser WebGPU gate', () => {
125
125
  expect(info.passes.map((pass) => pass.name)).toEqual([
126
126
  'seed-inputs',
127
127
  'ssr-hiz-seed',
128
- 'ssr-hiz-reduce-1',
128
+ 'ssr-hiz-reduce-chain',
129
129
  'ssr-trace',
130
130
  ]);
131
131
  expect(
@@ -125,7 +125,7 @@ describe('SSR Hi-Z spatial Dawn WebGPU gate', () => {
125
125
  expect(info.passes.map((pass) => pass.name)).toEqual([
126
126
  'seed-inputs',
127
127
  'ssr-hiz-seed',
128
- 'ssr-hiz-reduce-1',
128
+ 'ssr-hiz-reduce-chain',
129
129
  'ssr-trace',
130
130
  ]);
131
131
  expect(
@@ -122,7 +122,7 @@ describe('SSR spatial RhiNull integration', () => {
122
122
  expect(device.framePassNames).toEqual([
123
123
  'seed-inputs',
124
124
  'ssr-hiz-seed',
125
- 'ssr-hiz-reduce-1',
125
+ 'ssr-hiz-reduce-chain',
126
126
  'ssr-trace',
127
127
  ]);
128
128
  expect(compiled.value.inspect().resources).toEqual(
@@ -44,6 +44,46 @@ const noColorVariants = [
44
44
  ] as never;
45
45
  const noColorManifest = { variants: noColorVariants } as never;
46
46
  describe('Standard clustered variant resolution', () => {
47
+ it.each([
48
+ false,
49
+ true,
50
+ ])('retains colored clustered geometry when reflection fallback is %s', (fallback) => {
51
+ const projection = deriveVertexLayoutProjection({
52
+ position: new Float32Array(9),
53
+ color: new Float32Array(12),
54
+ });
55
+ const projected = variantSetFromVertexLayoutProjection(
56
+ projection,
57
+ standardTopologyVariantSet({ kind: 'clustered' }, true, true),
58
+ );
59
+ if (!projected.ok) throw projected.error;
60
+ const geometry = projected.value;
61
+ const request =
62
+ geometry === '' && fallback
63
+ ? ''
64
+ : `${geometry === '' ? '' : `${geometry}+`}REFLECTION_FALLBACK_AVAILABLE=${fallback}`;
65
+ const resolved = resolveMaterialShaderVariantSet(
66
+ request,
67
+ [
68
+ {
69
+ defines: {
70
+ CLUSTER_FORWARD_AVAILABLE: true,
71
+ STORAGE_BUFFER_AVAILABLE: true,
72
+ VERTEX_COLOR_AVAILABLE: true,
73
+ REFLECTION_FALLBACK_AVAILABLE: false,
74
+ },
75
+ },
76
+ ],
77
+ 'webgpu',
78
+ true,
79
+ );
80
+ if (fallback) expect(resolved).toBe('');
81
+ else
82
+ expect(resolved).toBe(
83
+ 'CLUSTER_FORWARD_AVAILABLE=true+REFLECTION_FALLBACK_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true+VERTEX_COLOR_AVAILABLE=true',
84
+ );
85
+ });
86
+
47
87
  it('derives the canonical capability key for every storage/cluster pair', () => {
48
88
  expect(standardCapabilityVariantSet(true, true, true)).toBe(
49
89
  'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true+VERTEX_COLOR_AVAILABLE=true',