@forgeax/engine-render 0.1.27 → 0.1.29

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 (655) hide show
  1. package/README.md +842 -124
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
  4. package/dist/assembly/dynamic-geometry-host.d.ts +52 -0
  5. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
  6. package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
  7. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +3 -1
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/gpu-driven-owner.d.ts +10 -0
  11. package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
  12. package/dist/assembly/host-contract.d.ts +19 -0
  13. package/dist/assembly/host-contract.d.ts.map +1 -1
  14. package/dist/assembly/material/assembly.d.ts +7 -0
  15. package/dist/assembly/material/assembly.d.ts.map +1 -1
  16. package/dist/assembly/material-shader-policy.d.ts +3 -1
  17. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  18. package/dist/assembly/recovery/failure-location.d.ts +12 -0
  19. package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
  20. package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
  21. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  22. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  23. package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
  24. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
  25. package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
  26. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  27. package/dist/assembly/renderer-helpers.d.ts +3 -0
  28. package/dist/assembly/renderer-helpers.d.ts.map +1 -1
  29. package/dist/assembly/renderer-inspection.d.ts +4 -0
  30. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  31. package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
  32. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  33. package/dist/assembly/skin-palette-owner.d.ts +8 -0
  34. package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
  35. package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
  36. package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
  37. package/dist/assembly/webgpu-device-loss.d.ts +15 -0
  38. package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
  39. package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
  40. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
  41. package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
  42. package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
  43. package/dist/assembly/webgpu-ready.d.ts +7 -3
  44. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  45. package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
  46. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
  47. package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
  48. package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
  49. package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
  50. package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
  51. package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
  52. package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
  53. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
  54. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
  55. package/dist/assembly/webgpu-renderer.d.ts +7 -141
  56. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  57. package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
  58. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  59. package/dist/authoring.mjs +3 -3
  60. package/dist/{chunk-ADHHVYLW.mjs → chunk-33XZFTQU.mjs} +245 -39
  61. package/dist/chunk-33XZFTQU.mjs.map +1 -0
  62. package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
  63. package/dist/chunk-BLXGONR2.mjs.map +1 -0
  64. package/dist/{chunk-KM2NOX2I.mjs → chunk-BQVQOQJH.mjs} +958 -254
  65. package/dist/chunk-BQVQOQJH.mjs.map +1 -0
  66. package/dist/{chunk-HKXTW355.mjs → chunk-BTWH7BP4.mjs} +386 -17
  67. package/dist/chunk-BTWH7BP4.mjs.map +1 -0
  68. package/dist/{chunk-TMIASV2N.mjs → chunk-FUODTTIA.mjs} +3934 -676
  69. package/dist/chunk-FUODTTIA.mjs.map +1 -0
  70. package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
  71. package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
  72. package/dist/{chunk-E34VL5VI.mjs → chunk-JQTSEI3Z.mjs} +3 -3
  73. package/dist/{chunk-E34VL5VI.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
  74. package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
  75. package/dist/chunk-NICXSHAT.mjs.map +1 -0
  76. package/dist/{chunk-ZZ4YQ474.mjs → chunk-NRNXBLIJ.mjs} +577 -7
  77. package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
  78. package/dist/{chunk-JDWARUOI.mjs → chunk-PA5DRJNI.mjs} +17 -3
  79. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  80. package/dist/{chunk-XDP6XE5V.mjs → chunk-S4DNKQYQ.mjs} +1118 -446
  81. package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
  82. package/dist/components/camera.d.ts +62 -2
  83. package/dist/components/camera.d.ts.map +1 -1
  84. package/dist/components/index.d.ts +1 -0
  85. package/dist/components/index.d.ts.map +1 -1
  86. package/dist/components/instances.d.ts +6 -20
  87. package/dist/components/instances.d.ts.map +1 -1
  88. package/dist/components/scene-instance.d.ts +2 -1
  89. package/dist/components/scene-instance.d.ts.map +1 -1
  90. package/dist/components/screen-space-reflection.d.ts +14 -0
  91. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  92. package/dist/construct-renderer.mjs +13805 -7186
  93. package/dist/construct-renderer.mjs.map +1 -1
  94. package/dist/device/gpu-residency.d.ts +11 -17
  95. package/dist/device/gpu-residency.d.ts.map +1 -1
  96. package/dist/device/mesh-residency-lifetime.d.ts +21 -0
  97. package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
  98. package/dist/dynamic-geometry.d.ts +125 -0
  99. package/dist/dynamic-geometry.d.ts.map +1 -0
  100. package/dist/environment/background.d.ts +6 -0
  101. package/dist/environment/background.d.ts.map +1 -0
  102. package/dist/errors/gpu-driven.d.ts +6 -3
  103. package/dist/errors/gpu-driven.d.ts.map +1 -1
  104. package/dist/errors/render.d.ts +7 -4
  105. package/dist/errors/render.d.ts.map +1 -1
  106. package/dist/extract/camera.d.ts.map +1 -1
  107. package/dist/extract/gpu-driven.d.ts +33 -3
  108. package/dist/extract/gpu-driven.d.ts.map +1 -1
  109. package/dist/features/host.d.ts +23 -1
  110. package/dist/features/host.d.ts.map +1 -1
  111. package/dist/features/noise-texture.d.ts +17 -0
  112. package/dist/features/noise-texture.d.ts.map +1 -0
  113. package/dist/features/plan.d.ts +83 -2
  114. package/dist/features/plan.d.ts.map +1 -1
  115. package/dist/features/prepared-gpu-work.d.ts +37 -4
  116. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  117. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  118. package/dist/features/prepared-graphics.d.ts +5 -0
  119. package/dist/features/prepared-graphics.d.ts.map +1 -1
  120. package/dist/features/render-graph-compute.d.ts +5 -3
  121. package/dist/features/render-graph-compute.d.ts.map +1 -1
  122. package/dist/features/render-graph-contribution.d.ts +11 -3
  123. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  124. package/dist/features/render-graph-raster.d.ts +19 -3
  125. package/dist/features/render-graph-raster.d.ts.map +1 -1
  126. package/dist/features/types.d.ts +11 -0
  127. package/dist/features/types.d.ts.map +1 -1
  128. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  129. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  130. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  131. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  132. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  133. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  134. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  135. package/dist/gpu-driven/production-raster.d.ts +144 -23
  136. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  137. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  138. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  139. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  140. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  141. package/dist/gpu-scene-schema.d.ts +2 -8
  142. package/dist/gpu-scene-schema.d.ts.map +1 -1
  143. package/dist/gpu-scene.d.ts +14 -0
  144. package/dist/gpu-scene.d.ts.map +1 -1
  145. package/dist/gpu-texture-usage.d.ts +1 -0
  146. package/dist/gpu-texture-usage.d.ts.map +1 -1
  147. package/dist/hdrp-buffers.d.ts +6 -0
  148. package/dist/hdrp-buffers.d.ts.map +1 -1
  149. package/dist/ibl/frame-resources.d.ts +8 -0
  150. package/dist/ibl/frame-resources.d.ts.map +1 -0
  151. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  152. package/dist/index.d.ts +21 -8
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.mjs +8 -7
  155. package/dist/index.mjs.map +1 -1
  156. package/dist/inspection-types.d.ts +164 -0
  157. package/dist/inspection-types.d.ts.map +1 -1
  158. package/dist/instances-derived-bounds.d.ts +11 -0
  159. package/dist/instances-derived-bounds.d.ts.map +1 -1
  160. package/dist/instances.d.ts +89 -44
  161. package/dist/instances.d.ts.map +1 -1
  162. package/dist/internal.d.ts +3 -0
  163. package/dist/internal.d.ts.map +1 -1
  164. package/dist/internal.mjs +118 -10
  165. package/dist/internal.mjs.map +1 -1
  166. package/dist/lifecycle.d.ts +2 -1
  167. package/dist/lifecycle.d.ts.map +1 -1
  168. package/dist/material-row.d.ts +5 -0
  169. package/dist/material-row.d.ts.map +1 -0
  170. package/dist/materials.d.ts +15 -0
  171. package/dist/materials.d.ts.map +1 -1
  172. package/dist/pbr-pipeline.d.ts +21 -5
  173. package/dist/pbr-pipeline.d.ts.map +1 -1
  174. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  175. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  176. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  177. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  178. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  179. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  180. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  181. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  182. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  183. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  184. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  185. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  186. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  187. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  188. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  189. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  190. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  191. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  192. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  193. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  194. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  195. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  196. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  197. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  198. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  199. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  200. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  201. package/dist/pipeline/standard-output/types.d.ts +39 -0
  202. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  203. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  204. package/dist/pipeline/standard-post.d.ts +2 -2
  205. package/dist/pipeline/standard-post.d.ts.map +1 -1
  206. package/dist/pipeline-builder.d.ts +2 -1
  207. package/dist/pipeline-builder.d.ts.map +1 -1
  208. package/dist/pipeline-spec-types.d.ts +4 -0
  209. package/dist/pipeline-spec-types.d.ts.map +1 -1
  210. package/dist/pipeline-spec.d.ts +1 -9
  211. package/dist/pipeline-spec.d.ts.map +1 -1
  212. package/dist/points-lines/standard-owner.d.ts +1 -1
  213. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  214. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  215. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  216. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  217. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  218. package/dist/record/frame-lighting.d.ts +3 -3
  219. package/dist/record/frame-lighting.d.ts.map +1 -1
  220. package/dist/record/frame-snapshot.d.ts +106 -5
  221. package/dist/record/frame-snapshot.d.ts.map +1 -1
  222. package/dist/record/frame-targets.d.ts.map +1 -1
  223. package/dist/record/frame.d.ts +39 -4
  224. package/dist/record/frame.d.ts.map +1 -1
  225. package/dist/record/main-pass-geometry.d.ts +7 -8
  226. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  227. package/dist/record/main-pass-material.d.ts +38 -1
  228. package/dist/record/main-pass-material.d.ts.map +1 -1
  229. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  230. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  231. package/dist/record/main-pass.d.ts +12 -2
  232. package/dist/record/main-pass.d.ts.map +1 -1
  233. package/dist/record/mesh-ssbo.d.ts +15 -6
  234. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  235. package/dist/record/prepared-material-bindings.d.ts +19 -0
  236. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  237. package/dist/record/recovery-pipeline.d.ts +2 -9
  238. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  239. package/dist/record/render-context.d.ts +76 -26
  240. package/dist/record/render-context.d.ts.map +1 -1
  241. package/dist/record/shadow-pass.d.ts +62 -7
  242. package/dist/record/shadow-pass.d.ts.map +1 -1
  243. package/dist/record/skybox-post-pass.d.ts +1 -1
  244. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  245. package/dist/record/typed-frame-graph.d.ts +35 -7
  246. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  247. package/dist/record/view-ubo.d.ts +1 -0
  248. package/dist/record/view-ubo.d.ts.map +1 -1
  249. package/dist/recovery/render-system-candidate.d.ts +10 -2
  250. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  251. package/dist/recovery/render-system-roots.d.ts +11 -0
  252. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  253. package/dist/recovery/types.d.ts +76 -0
  254. package/dist/recovery/types.d.ts.map +1 -0
  255. package/dist/reflection/record-owner.d.ts +0 -2
  256. package/dist/reflection/record-owner.d.ts.map +1 -1
  257. package/dist/render-contract.d.ts +185 -30
  258. package/dist/render-contract.d.ts.map +1 -1
  259. package/dist/render-graph-primitives.d.ts +1 -0
  260. package/dist/render-graph-primitives.d.ts.map +1 -1
  261. package/dist/render-pipeline.d.ts +64 -5
  262. package/dist/render-pipeline.d.ts.map +1 -1
  263. package/dist/render-system-extract-tail.d.ts +7 -5
  264. package/dist/render-system-extract-tail.d.ts.map +1 -1
  265. package/dist/render-system-extract.d.ts +101 -41
  266. package/dist/render-system-extract.d.ts.map +1 -1
  267. package/dist/render-system-projections.d.ts +22 -0
  268. package/dist/render-system-projections.d.ts.map +1 -0
  269. package/dist/render-system.d.ts +34 -77
  270. package/dist/render-system.d.ts.map +1 -1
  271. package/dist/scene/render-scene-types.d.ts +14 -7
  272. package/dist/scene/render-scene-types.d.ts.map +1 -1
  273. package/dist/scene/render-scene.d.ts +29 -11
  274. package/dist/scene/render-scene.d.ts.map +1 -1
  275. package/dist/scene/shadow-visibility.d.ts +5 -0
  276. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  277. package/dist/ssr/admission.d.ts +70 -1
  278. package/dist/ssr/admission.d.ts.map +1 -1
  279. package/dist/ssr/compose.d.ts +13 -0
  280. package/dist/ssr/compose.d.ts.map +1 -0
  281. package/dist/ssr/errors.d.ts +35 -0
  282. package/dist/ssr/errors.d.ts.map +1 -1
  283. package/dist/ssr/graph.d.ts +60 -0
  284. package/dist/ssr/graph.d.ts.map +1 -0
  285. package/dist/ssr/history.d.ts +97 -0
  286. package/dist/ssr/history.d.ts.map +1 -0
  287. package/dist/ssr/hiz.d.ts +37 -0
  288. package/dist/ssr/hiz.d.ts.map +1 -0
  289. package/dist/ssr/inspection.d.ts +42 -0
  290. package/dist/ssr/inspection.d.ts.map +1 -0
  291. package/dist/ssr/resources.d.ts +58 -0
  292. package/dist/ssr/resources.d.ts.map +1 -0
  293. package/dist/ssr/temporal.d.ts +38 -0
  294. package/dist/ssr/temporal.d.ts.map +1 -0
  295. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  296. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  297. package/dist/systems/skin-palette-types.d.ts +15 -0
  298. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  299. package/dist/systems/transparent-dispatch.d.ts +9 -0
  300. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  301. package/dist/targets/material-source.d.ts +7 -0
  302. package/dist/targets/material-source.d.ts.map +1 -1
  303. package/dist/temporal/frame-coordinator.d.ts +7 -0
  304. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  305. package/dist/temporal/gpu.d.ts +8 -0
  306. package/dist/temporal/gpu.d.ts.map +1 -1
  307. package/dist/temporal/history.d.ts.map +1 -1
  308. package/dist/temporal/index.mjs +6 -6
  309. package/dist/temporal/inspection.d.ts.map +1 -1
  310. package/dist/temporal/standard-scene-data.d.ts +2 -1
  311. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  312. package/dist/temporal/temporal-view.d.ts +2 -0
  313. package/dist/temporal/temporal-view.d.ts.map +1 -1
  314. package/dist/typed-render-graph-primitives.d.ts +35 -1
  315. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  316. package/dist/typed-shadow-passes.d.ts +3 -1
  317. package/dist/typed-shadow-passes.d.ts.map +1 -1
  318. package/dist/volume/temporal.d.ts +6 -0
  319. package/dist/volume/temporal.d.ts.map +1 -1
  320. package/package.json +21 -20
  321. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  322. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  323. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  324. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  325. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  326. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  327. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  328. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  329. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  330. package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
  331. package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
  332. package/src/__tests__/factory-contract.integration.test.ts +73 -22
  333. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  334. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  335. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  336. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  337. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  338. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  339. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  340. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  341. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  342. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
  343. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  344. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  345. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
  346. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
  347. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
  348. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  349. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  350. package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
  351. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  352. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  353. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  354. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  355. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  356. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  357. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  358. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  359. package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
  360. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
  361. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  362. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  363. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  364. package/src/__tests__/gpu-scene.unit.test.ts +559 -33
  365. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  366. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  367. package/src/__tests__/instances-store.unit.test.ts +242 -39
  368. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  369. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  370. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  371. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  372. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  373. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  374. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  375. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  376. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  377. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  378. package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
  379. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  380. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
  381. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  382. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  383. package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
  384. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  385. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  386. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  387. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  388. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
  389. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  390. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  391. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  392. package/src/__tests__/public-surface.test-d.ts +3 -0
  393. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  394. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
  395. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
  396. package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
  397. package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
  398. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  399. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  400. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  401. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  402. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  403. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  404. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  405. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  406. package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
  407. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  408. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  409. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  410. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  411. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +61 -1
  412. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  413. package/src/__tests__/shader-manifest-fixture.ts +39 -0
  414. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  415. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  416. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  417. package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
  418. package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
  419. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  420. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  421. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  422. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  423. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
  424. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
  425. package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
  426. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  427. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  428. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  429. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  430. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  431. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  432. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  433. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  434. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  435. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  436. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  437. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  438. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  439. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  440. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  441. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  442. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  443. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  444. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  445. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  446. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  447. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  448. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
  449. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  450. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  451. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  452. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  453. package/src/__tests__/standard-output.browser.test.ts +15 -0
  454. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  455. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
  456. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  457. package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
  458. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  459. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  460. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  461. package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
  462. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  463. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
  464. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  465. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  466. package/src/__tests__/taa-stability-feedback.ts +341 -0
  467. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  468. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  469. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  470. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  471. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  472. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  473. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  474. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  475. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  476. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  477. package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
  478. package/src/assembly/dynamic-geometry-host.ts +887 -0
  479. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  480. package/src/assembly/factory.ts +4 -0
  481. package/src/assembly/gpu-driven-owner.ts +29 -0
  482. package/src/assembly/host-contract.ts +36 -0
  483. package/src/assembly/material/assembly.ts +38 -0
  484. package/src/assembly/material-shader-policy.ts +71 -28
  485. package/src/assembly/recovery/failure-location.ts +62 -0
  486. package/src/assembly/recovery/renderer-recover.ts +19 -17
  487. package/src/assembly/renderer-facade.ts +29 -0
  488. package/src/assembly/renderer-feature-inspection.ts +13 -0
  489. package/src/assembly/renderer-frame-transaction.ts +63 -0
  490. package/src/assembly/renderer-helpers.ts +26 -0
  491. package/src/assembly/renderer-inspection.ts +6 -0
  492. package/src/assembly/shader-prewarm-policy.ts +32 -0
  493. package/src/assembly/skin-palette-owner.ts +21 -0
  494. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  495. package/src/assembly/webgpu-device-loss.ts +75 -0
  496. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  497. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  498. package/src/assembly/webgpu-ready.ts +142 -121
  499. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  500. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  501. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  502. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  503. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  504. package/src/assembly/webgpu-renderer.ts +675 -614
  505. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  506. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  507. package/src/components/camera.ts +287 -7
  508. package/src/components/index.ts +1 -0
  509. package/src/components/instances.ts +45 -18
  510. package/src/components/scene-instance.ts +2 -1
  511. package/src/components/screen-space-reflection.ts +21 -0
  512. package/src/components/sprite-animation.ts +3 -3
  513. package/src/components/sprite-instances.ts +1 -1
  514. package/src/device/gpu-residency.ts +96 -93
  515. package/src/device/mesh-residency-lifetime.ts +73 -0
  516. package/src/dynamic-geometry.ts +736 -0
  517. package/src/environment/background.ts +263 -0
  518. package/src/errors/gpu-driven.ts +39 -5
  519. package/src/errors/render.ts +27 -3
  520. package/src/extract/camera.ts +26 -1
  521. package/src/extract/gpu-driven.ts +185 -10
  522. package/src/features/host.ts +237 -19
  523. package/src/features/noise-texture.ts +91 -0
  524. package/src/features/plan.ts +553 -24
  525. package/src/features/prepared-gpu-work.ts +631 -49
  526. package/src/features/prepared-graphics-store.ts +31 -0
  527. package/src/features/prepared-graphics.ts +5 -0
  528. package/src/features/render-graph-compute.ts +41 -8
  529. package/src/features/render-graph-contribution.ts +141 -3
  530. package/src/features/render-graph-raster.ts +143 -30
  531. package/src/features/types.ts +11 -0
  532. package/src/fullscreen-post-process-pass.ts +3 -0
  533. package/src/gpu-driven/batch-topology.ts +424 -102
  534. package/src/gpu-driven/pbr-program.ts +31 -0
  535. package/src/gpu-driven/prepared-draw.ts +29 -4
  536. package/src/gpu-driven/production-raster.ts +2267 -694
  537. package/src/gpu-driven/shadow-views.ts +539 -0
  538. package/src/gpu-driven/view-gpu.ts +188 -41
  539. package/src/gpu-scene-schema.ts +52 -11
  540. package/src/gpu-scene.ts +186 -99
  541. package/src/gpu-texture-usage.ts +1 -0
  542. package/src/hdrp-buffers.ts +72 -36
  543. package/src/ibl/frame-resources.ts +38 -0
  544. package/src/ibl/skylight-bind-group.ts +5 -4
  545. package/src/index.ts +125 -1
  546. package/src/inspection-types.ts +191 -0
  547. package/src/instances-derived-bounds.ts +258 -34
  548. package/src/instances.ts +459 -150
  549. package/src/internal.ts +22 -0
  550. package/src/lifecycle.ts +2 -0
  551. package/src/material-row.ts +145 -0
  552. package/src/materials.ts +44 -8
  553. package/src/pbr-pipeline.ts +159 -17
  554. package/src/pipeline/standard-forward-lane.ts +52 -24
  555. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  556. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  557. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  558. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  559. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  560. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  561. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  562. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  563. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  564. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  565. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  566. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  567. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  568. package/src/pipeline/standard-output/color-transform.ts +124 -0
  569. package/src/pipeline/standard-output/graph.ts +139 -0
  570. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  571. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  572. package/src/pipeline/standard-output/lut-state.ts +176 -0
  573. package/src/pipeline/standard-output/resources.ts +121 -0
  574. package/src/pipeline/standard-output/types.ts +58 -0
  575. package/src/pipeline/standard-pipeline.ts +192 -59
  576. package/src/pipeline/standard-post.ts +186 -27
  577. package/src/pipeline-builder.ts +16 -6
  578. package/src/pipeline-spec-types.ts +4 -0
  579. package/src/pipeline-spec.ts +37 -29
  580. package/src/points-lines/standard-owner.ts +6 -14
  581. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  582. package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
  583. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  584. package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
  585. package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
  586. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  587. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  588. package/src/record/dynamic-geometry-consumption.ts +59 -0
  589. package/src/record/frame-lighting.ts +12 -11
  590. package/src/record/frame-snapshot.ts +133 -23
  591. package/src/record/frame-targets.ts +9 -0
  592. package/src/record/frame.ts +727 -133
  593. package/src/record/main-pass-geometry.ts +162 -98
  594. package/src/record/main-pass-material.ts +115 -26
  595. package/src/record/main-pass-sprite-draws.ts +18 -107
  596. package/src/record/main-pass.ts +78 -8
  597. package/src/record/material-uniforms.ts +1 -1
  598. package/src/record/mesh-ssbo.ts +59 -15
  599. package/src/record/prepared-material-bindings.ts +234 -0
  600. package/src/record/recovery-pipeline.ts +59 -136
  601. package/src/record/render-context.ts +92 -22
  602. package/src/record/shadow-pass.ts +1231 -209
  603. package/src/record/skybox-post-pass.ts +14 -3
  604. package/src/record/typed-frame-graph.ts +1006 -53
  605. package/src/record/view-ubo.ts +11 -0
  606. package/src/recovery/render-system-candidate.ts +90 -66
  607. package/src/recovery/render-system-roots.ts +113 -0
  608. package/src/recovery/types.ts +77 -0
  609. package/src/reflection/record-owner.ts +26 -54
  610. package/src/render-contract.ts +219 -25
  611. package/src/render-graph-primitives.ts +6 -2
  612. package/src/render-pipeline.ts +70 -1
  613. package/src/render-system-extract-tail.ts +409 -160
  614. package/src/render-system-extract.ts +376 -102
  615. package/src/render-system-projections.ts +241 -0
  616. package/src/render-system.ts +692 -791
  617. package/src/scene/render-scene-types.ts +13 -4
  618. package/src/scene/render-scene.ts +569 -355
  619. package/src/scene/shadow-visibility.ts +21 -0
  620. package/src/ssr/admission.ts +380 -14
  621. package/src/ssr/compose.ts +144 -0
  622. package/src/ssr/errors.ts +48 -0
  623. package/src/ssr/graph.ts +777 -0
  624. package/src/ssr/history.ts +436 -0
  625. package/src/ssr/hiz.ts +124 -0
  626. package/src/ssr/inspection.ts +124 -0
  627. package/src/ssr/resources.ts +202 -0
  628. package/src/ssr/temporal.ts +164 -0
  629. package/src/systems/skin-palette-allocator.ts +495 -148
  630. package/src/systems/skin-palette-types.ts +17 -0
  631. package/src/systems/transparent-dispatch.ts +90 -0
  632. package/src/targets/material-source.ts +34 -0
  633. package/src/temporal/coverage.ts +1 -1
  634. package/src/temporal/frame-coordinator.ts +23 -2
  635. package/src/temporal/gpu.ts +74 -12
  636. package/src/temporal/history.ts +3 -1
  637. package/src/temporal/inspection.ts +2 -1
  638. package/src/temporal/standard-scene-data.ts +3 -1
  639. package/src/temporal/temporal-view.ts +3 -0
  640. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
  641. package/src/typed-render-graph-primitives.ts +266 -74
  642. package/src/typed-shadow-passes.ts +122 -12
  643. package/src/volume/temporal.ts +10 -0
  644. package/dist/chunk-ADHHVYLW.mjs.map +0 -1
  645. package/dist/chunk-FJ6P52EE.mjs.map +0 -1
  646. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  647. package/dist/chunk-GF523LHF.mjs.map +0 -1
  648. package/dist/chunk-HKXTW355.mjs.map +0 -1
  649. package/dist/chunk-JDWARUOI.mjs.map +0 -1
  650. package/dist/chunk-KM2NOX2I.mjs.map +0 -1
  651. package/dist/chunk-TMIASV2N.mjs.map +0 -1
  652. package/dist/chunk-XDP6XE5V.mjs.map +0 -1
  653. package/dist/chunk-ZZ4YQ474.mjs.map +0 -1
  654. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  655. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
@@ -0,0 +1,1417 @@
1
+ import { World } from '@forgeax/engine-ecs';
2
+ import { createRenderReadLease } from '@forgeax/engine-ecs/projection';
3
+ import {
4
+ deriveVertexBufferLayoutFromProjection,
5
+ deriveVertexLayoutProjection,
6
+ } from '@forgeax/engine-geometry';
7
+ import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
8
+ import type { RhiDevice, ShaderModule } from '@forgeax/engine-rhi';
9
+ import { createShaderModule, rhi } from '@forgeax/engine-rhi-webgpu';
10
+ import {
11
+ createStandardPbrArtifactReceipt,
12
+ type MaterialShaderArtifact,
13
+ } from '@forgeax/engine-shader';
14
+ import { ok } from '@forgeax/engine-types';
15
+ import { buildEngineShaderManifest } from '@forgeax/engine-vite-plugin-shader';
16
+ import { describe, expect, it } from 'vitest';
17
+ import { gpuDrivenDrawKey, gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
18
+ import { type GpuDrivenPbrProgram, standardPbrProgramKey } from '../gpu-driven/pbr-program';
19
+ import { GpuDrivenProduction } from '../gpu-driven/production-raster';
20
+ import { shadowViewRasterAccesses } from '../gpu-driven/shadow-views';
21
+ import { GPU_DRIVEN_VIEW_WGSL } from '../gpu-driven/view-gpu';
22
+ import { GpuBuffer } from '../gpu-resource';
23
+ import {
24
+ GPU_BUFFER_USAGE_COPY_DST,
25
+ GPU_BUFFER_USAGE_INDEX,
26
+ GPU_BUFFER_USAGE_MAP_READ,
27
+ GPU_BUFFER_USAGE_UNIFORM,
28
+ GPU_BUFFER_USAGE_VERTEX,
29
+ } from '../gpu-usage';
30
+ import {
31
+ buildGpuDrivenPbrInstancesBindGroupLayout,
32
+ buildGpuDrivenPbrPipelineLayout,
33
+ buildPbrPipelineLayouts,
34
+ } from '../pbr-pipeline';
35
+ import { makeZeroCameraFallbackSnapshot, worldEntityKey } from '../record/frame-snapshot';
36
+ import {
37
+ type GpuDrivenShadowBatch,
38
+ type GpuDrivenShadowSubmission,
39
+ recordGpuDrivenShadowIndirect,
40
+ } from '../record/shadow-pass';
41
+ import { writeViewUbo } from '../record/view-ubo';
42
+ import type { RenderPipelineFrame } from '../render-pipeline';
43
+ import type {
44
+ ExtractedFrame,
45
+ MaterialSnapshot,
46
+ RenderableSnapshot,
47
+ } from '../render-system-extract';
48
+ import { PersistentRenderScene } from '../scene/render-scene';
49
+
50
+ const STANDARD_PBR_RECEIPT = createStandardPbrArtifactReceipt();
51
+ const ENGINE_SHADER_MANIFEST = await buildEngineShaderManifest();
52
+ const STANDARD_PBR_PRODUCER = ENGINE_SHADER_MANIFEST.materialShaders.find(
53
+ ({ identifier }) => identifier === 'forgeax::default-standard-pbr',
54
+ );
55
+ const STANDARD_PBR_GPU_VARIANT = STANDARD_PBR_PRODUCER?.variants.find(
56
+ ({ defines }) =>
57
+ defines.STORAGE_BUFFER_AVAILABLE === true &&
58
+ defines.GPU_DRIVEN_SCENE_INDEX_AVAILABLE === true &&
59
+ defines.CLUSTER_FORWARD_AVAILABLE === false &&
60
+ defines.VERTEX_COLOR_AVAILABLE === false &&
61
+ defines.PROBE_BLEND_AVAILABLE === false &&
62
+ defines.EXTENDED_LIGHTING_AVAILABLE === false &&
63
+ defines.TRANSMISSION_AVAILABLE === false &&
64
+ defines.DIRECTIONAL_PCSS_AVAILABLE === false &&
65
+ defines.PROJECTOR_AVAILABLE === false,
66
+ );
67
+ const STANDARD_PBR_COLORED_GPU_VARIANT = STANDARD_PBR_PRODUCER?.variants.find(
68
+ ({ defines }) =>
69
+ defines.STORAGE_BUFFER_AVAILABLE === true &&
70
+ defines.GPU_DRIVEN_SCENE_INDEX_AVAILABLE === true &&
71
+ defines.CLUSTER_FORWARD_AVAILABLE === false &&
72
+ defines.VERTEX_COLOR_AVAILABLE === true &&
73
+ defines.PROBE_BLEND_AVAILABLE === false &&
74
+ defines.EXTENDED_LIGHTING_AVAILABLE === false &&
75
+ defines.TRANSMISSION_AVAILABLE === false &&
76
+ defines.DIRECTIONAL_PCSS_AVAILABLE === false &&
77
+ defines.PROJECTOR_AVAILABLE === false,
78
+ );
79
+ const STANDARD_PBR_COLORED_RECEIPT = createStandardPbrArtifactReceipt(false, true);
80
+ const SHADOW_CASTER_PRODUCER = ENGINE_SHADER_MANIFEST.materialShaders.find(
81
+ ({ identifier }) => identifier === 'forgeax::default-shadow-caster',
82
+ );
83
+ const SHADOW_CASTER_GPU_ALPHA_VARIANT = SHADOW_CASTER_PRODUCER?.variants.find(
84
+ ({ defines }) =>
85
+ defines.STORAGE_BUFFER_AVAILABLE === true &&
86
+ defines.SKINNING_DISABLED === true &&
87
+ defines.GPU_DRIVEN_SCENE_INDEX_AVAILABLE === true &&
88
+ defines.GPU_DRIVEN_SCENE_INDEX_EXPLICIT === false &&
89
+ defines.ALPHA_MASK === true,
90
+ );
91
+
92
+ // The local/native evidence window remains 300 frames. CI already sets the
93
+ // Dawn lightweight profile for this roster; keep the same assertions while
94
+ // bounding the repeated submit/readback loop so one fixture cannot dominate
95
+ // the shared runner's critical path.
96
+ const STABLE_FRAME_COUNT = process.env.FORGEAX_DAWN_LIGHTWEIGHT === '1' ? 12 : 300;
97
+
98
+ function alphaMaskSnapshot(
99
+ coordinateSet: 1 | 7 | undefined,
100
+ vertexColorAvailable = false,
101
+ ): RenderableSnapshot {
102
+ const receipt = vertexColorAvailable ? STANDARD_PBR_COLORED_RECEIPT : STANDARD_PBR_RECEIPT;
103
+ const material = {
104
+ baseColor: new Float32Array([1, 1, 1]),
105
+ metallic: 0,
106
+ roughness: 1,
107
+ materialHandle: 7,
108
+ materialShaderId: 'forgeax::default-standard-pbr',
109
+ // The fixture binds one white texture view across the reserved texture
110
+ // region. Keep the normal lane explicitly flat so the alpha-mask pixel
111
+ // assertion isolates scene-row material selection instead of sampling a
112
+ // white (1,1,1) normal map.
113
+ paramSnapshot: { alphaCutoff: 0.5, normalScale: 0 },
114
+ textureHandles: new Map([['baseColorTexture', 1]]) as never,
115
+ samplerHandles: new Map([['baseColorTexture', 2]]) as never,
116
+ ...(coordinateSet !== undefined
117
+ ? {
118
+ textureCoordinates: new Map([
119
+ [
120
+ 'baseColorTexture',
121
+ {
122
+ set: coordinateSet,
123
+ transform: { offset: [0.8, 0.1], scale: [0.5, 0.5], rotation: Math.PI / 2 },
124
+ },
125
+ ],
126
+ ]),
127
+ }
128
+ : {}),
129
+ } as unknown as MaterialSnapshot;
130
+ const world = new Float32Array(16);
131
+ world[0] = 1;
132
+ world[5] = 1;
133
+ world[10] = 1;
134
+ world[15] = 1;
135
+ return {
136
+ assetHandle: 3,
137
+ transform: { world },
138
+ localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
139
+ material,
140
+ materials: [material],
141
+ materialBindingSources: ['engine-default'],
142
+ worldId: 0,
143
+ entityKey: 1,
144
+ gpuDrivenDraws: [
145
+ {
146
+ kind: 'indexed',
147
+ first: 0,
148
+ count: 3,
149
+ baseVertex: 0,
150
+ materialSlot: 0,
151
+ topology: 'triangle-list',
152
+ pipelineClass: 'forgeax::default-standard-pbr|triangle-list|rigid',
153
+ materialResourceClass: 'standard-pbr-resources',
154
+ prepared: {
155
+ identity: {
156
+ material: 'forgeax::default-standard-pbr',
157
+ geometry: 'triangle',
158
+ deformation: 'rigid',
159
+ },
160
+ receiptGeneration: receipt.generation,
161
+ directEntry: receipt.directEntry,
162
+ sceneIndexEntry: receipt.sceneIndexEntry,
163
+ materialRow: receipt.materialRow,
164
+ resourceSlots: receipt.resourceSlots,
165
+ uvSets: receipt.uvSets,
166
+ vertexInputs: receipt.vertexInputs,
167
+ alphaMask: receipt.alphaMask,
168
+ skinPaletteAddress: receipt.skinPaletteAddress,
169
+ topology: 'triangle-list',
170
+ indexed: true,
171
+ first: 0,
172
+ count: 3,
173
+ baseVertex: 0,
174
+ },
175
+ },
176
+ ],
177
+ };
178
+ }
179
+
180
+ function mesh(device: RhiDevice, coordinateSet: 1 | 7 | undefined, vertexColorAvailable = false) {
181
+ const secondaryUv = coordinateSet === 7 ? 0.1 : 0.8;
182
+ const vertexStride = vertexColorAvailable ? 80 : 64;
183
+ const vertex = device
184
+ .createBuffer({
185
+ size: vertexStride * 3,
186
+ usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
187
+ })
188
+ .unwrap();
189
+ const index = device
190
+ .createBuffer({
191
+ size: 8,
192
+ usage: GPU_BUFFER_USAGE_INDEX | GPU_BUFFER_USAGE_COPY_DST,
193
+ })
194
+ .unwrap();
195
+ device.queue
196
+ .writeBuffer(
197
+ vertex,
198
+ 0,
199
+ new Float32Array([
200
+ -0.8,
201
+ -0.8,
202
+ -2,
203
+ 0,
204
+ 0,
205
+ 1,
206
+ 0.1,
207
+ 0.1,
208
+ 1,
209
+ 0,
210
+ 0,
211
+ 1,
212
+ secondaryUv,
213
+ secondaryUv,
214
+ 0.8,
215
+ 0.8,
216
+ ...(vertexColorAvailable ? [1, 0, 0, 1] : []),
217
+ 0.8,
218
+ -0.8,
219
+ -2,
220
+ 0,
221
+ 0,
222
+ 1,
223
+ 0.1,
224
+ 0.1,
225
+ 1,
226
+ 0,
227
+ 0,
228
+ 1,
229
+ secondaryUv,
230
+ secondaryUv,
231
+ 0.8,
232
+ 0.8,
233
+ ...(vertexColorAvailable ? [1, 0, 0, 1] : []),
234
+ 0,
235
+ 0.8,
236
+ -2,
237
+ 0,
238
+ 0,
239
+ 1,
240
+ 0.1,
241
+ 0.1,
242
+ 1,
243
+ 0,
244
+ 0,
245
+ 1,
246
+ secondaryUv,
247
+ secondaryUv,
248
+ 0.8,
249
+ 0.8,
250
+ ...(vertexColorAvailable ? [1, 0, 0, 1] : []),
251
+ ]),
252
+ )
253
+ .unwrap();
254
+ // Dawn requires queue.writeBuffer payloads to be four-byte aligned; the
255
+ // fourth index is padding because the indirect draw consumes only three.
256
+ device.queue.writeBuffer(index, 0, new Uint16Array([0, 1, 2, 0])).unwrap();
257
+ return {
258
+ vertexBuffer: new GpuBuffer(device, vertex),
259
+ indexBuffer: new GpuBuffer(device, index),
260
+ vboBytes: vertexStride * 3,
261
+ iboBytes: 8,
262
+ indexCount: 3,
263
+ indexFormat: 'uint16' as const,
264
+ layout: '12F' as const,
265
+ layoutProjection: deriveVertexLayoutProjection({
266
+ position: new Float32Array(9),
267
+ normal: new Float32Array(9),
268
+ uv: new Float32Array(6),
269
+ tangent: new Float32Array(12),
270
+ uv1: new Float32Array(6),
271
+ uv7: new Float32Array(6),
272
+ ...(vertexColorAvailable ? { color: new Float32Array(12) } : {}),
273
+ }),
274
+ uvSetCount: 8,
275
+ vertexCount: 3,
276
+ indexed: true,
277
+ topology: 'triangle-list' as const,
278
+ submeshes: [
279
+ {
280
+ indexOffset: 0,
281
+ indexCount: 3,
282
+ vertexCount: 3,
283
+ materialSlot: 0,
284
+ topology: 'triangle-list' as const,
285
+ },
286
+ ],
287
+ };
288
+ }
289
+
290
+ describe('GPU-driven production PBR Dawn regression', () => {
291
+ it.each([
292
+ { coordinateSet: undefined, vertexColorAvailable: false },
293
+ { coordinateSet: 1, vertexColorAvailable: false },
294
+ { coordinateSet: 7, vertexColorAvailable: true },
295
+ ] as const)('admits Alpha Mask into main and shadow production plans (transformed UV set=$coordinateSet, vertex color=$vertexColorAvailable)', async ({
296
+ coordinateSet,
297
+ vertexColorAvailable,
298
+ }) => {
299
+ const transformed = coordinateSet !== undefined;
300
+ const standardPbrGpuVariant = vertexColorAvailable
301
+ ? STANDARD_PBR_COLORED_GPU_VARIANT
302
+ : STANDARD_PBR_GPU_VARIANT;
303
+ const defaultGpuVariant = STANDARD_PBR_GPU_VARIANT;
304
+ const adapter = (await rhi.requestAdapter()).unwrap();
305
+ const device = (await adapter.requestDevice()).unwrap();
306
+ expect(STANDARD_PBR_PRODUCER).toBeDefined();
307
+ if (STANDARD_PBR_PRODUCER === undefined) return;
308
+ expect(standardPbrGpuVariant).toBeDefined();
309
+ if (standardPbrGpuVariant === undefined) return;
310
+ expect(standardPbrGpuVariant.defines.GPU_DRIVEN_SCENE_INDEX_AVAILABLE).toBe(true);
311
+ if (vertexColorAvailable) {
312
+ expect(defaultGpuVariant).toBeDefined();
313
+ if (defaultGpuVariant === undefined) return;
314
+ }
315
+ // Compile the exact cooked Standard PBR scene-index producer variant on
316
+ // native Dawn. This is the same manifest branch selected by the renderer
317
+ // for the storage-backed GPU-driven lane, including its production material
318
+ // ABI and Alpha Mask discard path.
319
+ const producerShader = await createShaderModule(device, {
320
+ code: standardPbrGpuVariant.composedWgsl,
321
+ label: `standard-pbr-gpu-driven-producer-alpha-mask${vertexColorAvailable ? '-vertex-color' : ''}`,
322
+ });
323
+ expect(producerShader.ok).toBe(true);
324
+ if (!producerShader.ok) return;
325
+ const defaultProducerShader = vertexColorAvailable
326
+ ? await createShaderModule(device, {
327
+ code: defaultGpuVariant?.composedWgsl ?? '',
328
+ label: 'standard-pbr-gpu-driven-producer-alpha-mask-default',
329
+ })
330
+ : producerShader;
331
+ expect(defaultProducerShader.ok).toBe(true);
332
+ if (!defaultProducerShader.ok) return;
333
+ expect(SHADOW_CASTER_PRODUCER).toBeDefined();
334
+ expect(SHADOW_CASTER_GPU_ALPHA_VARIANT).toBeDefined();
335
+ if (SHADOW_CASTER_GPU_ALPHA_VARIANT === undefined) return;
336
+ const shadowCasterShader = (
337
+ await createShaderModule(device, {
338
+ code: SHADOW_CASTER_GPU_ALPHA_VARIANT.composedWgsl,
339
+ label: 'shadow-caster-gpu-driven-producer-alpha-mask',
340
+ })
341
+ ).unwrap();
342
+ expect(STANDARD_PBR_RECEIPT.alphaMask).toEqual({
343
+ cutoff: 'alphaCutoff',
344
+ source: 'baseColor.a',
345
+ });
346
+ const viewShader = (
347
+ await createShaderModule(device, { code: GPU_DRIVEN_VIEW_WGSL, label: 'gpu-driven-view' })
348
+ ).unwrap();
349
+ const pbrLayouts = buildPbrPipelineLayouts(device, {
350
+ storageBuffer: true,
351
+ extendedLighting: false,
352
+ projectorAvailable: false,
353
+ });
354
+ const instancesLayout = buildGpuDrivenPbrInstancesBindGroupLayout(device, {
355
+ storageBuffer: true,
356
+ });
357
+ const pipelineLayout = buildGpuDrivenPbrPipelineLayout(device, pbrLayouts, instancesLayout);
358
+ const viewLayout = pbrLayouts.viewBgl;
359
+ const materialLayout = pbrLayouts.materialBgl;
360
+ const meshLayout = pbrLayouts.meshArrayBgl;
361
+ const viewUniform = device
362
+ .createBuffer({
363
+ size: 4096,
364
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
365
+ })
366
+ .unwrap();
367
+ const shadowViewUniform = device
368
+ .createBuffer({
369
+ size: 4096,
370
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
371
+ })
372
+ .unwrap();
373
+ const directionalCascadeUniform = device
374
+ .createBuffer({
375
+ size: 256,
376
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
377
+ })
378
+ .unwrap();
379
+ const pointCascadeUniform = device
380
+ .createBuffer({
381
+ size: 256,
382
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
383
+ })
384
+ .unwrap();
385
+ const spotCascadeUniform = device
386
+ .createBuffer({
387
+ size: 256,
388
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
389
+ })
390
+ .unwrap();
391
+ const viewDepthTexture = device
392
+ .createTexture({
393
+ label: 'gpu-driven-alpha-mask.view-depth',
394
+ format: 'depth32float',
395
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
396
+ usage: 0x14,
397
+ dimension: '2d',
398
+ textureBindingViewDimension: undefined,
399
+ })
400
+ .unwrap();
401
+ const pointShadowTexture = device
402
+ .createTexture({
403
+ label: 'gpu-driven-alpha-mask.point-shadow-depth',
404
+ format: 'depth32float',
405
+ size: { width: 1, height: 1, depthOrArrayLayers: 6 },
406
+ usage: 0x14,
407
+ dimension: '2d',
408
+ textureBindingViewDimension: 'cube-array',
409
+ })
410
+ .unwrap();
411
+ const viewDepth = device.createTextureView(viewDepthTexture, { dimension: '2d' }).unwrap();
412
+ const pointShadowDepth = device
413
+ .createTextureView(pointShadowTexture, { dimension: 'cube-array' })
414
+ .unwrap();
415
+ const comparisonSampler = device.createSampler({ compare: 'less' }).unwrap();
416
+ const viewBindGroup = device
417
+ .createBindGroup({
418
+ layout: viewLayout,
419
+ entries: [
420
+ { binding: 0, resource: { kind: 'buffer', value: { buffer: viewUniform } } },
421
+ { binding: 3, resource: { kind: 'textureView', value: viewDepth } },
422
+ { binding: 4, resource: { kind: 'sampler', value: comparisonSampler } },
423
+ { binding: 5, resource: { kind: 'textureView', value: pointShadowDepth } },
424
+ { binding: 6, resource: { kind: 'buffer', value: { buffer: viewUniform } } },
425
+ { binding: 7, resource: { kind: 'buffer', value: { buffer: viewUniform } } },
426
+ { binding: 8, resource: { kind: 'textureView', value: viewDepth } },
427
+ { binding: 10, resource: { kind: 'buffer', value: { buffer: viewUniform } } },
428
+ ],
429
+ })
430
+ .unwrap();
431
+ const createShadowViewBindGroup = (cascadeBuffer: typeof directionalCascadeUniform) =>
432
+ device
433
+ .createBindGroup({
434
+ layout: viewLayout,
435
+ entries: [
436
+ { binding: 0, resource: { kind: 'buffer', value: { buffer: shadowViewUniform } } },
437
+ { binding: 3, resource: { kind: 'textureView', value: viewDepth } },
438
+ { binding: 4, resource: { kind: 'sampler', value: comparisonSampler } },
439
+ { binding: 5, resource: { kind: 'textureView', value: pointShadowDepth } },
440
+ { binding: 6, resource: { kind: 'buffer', value: { buffer: shadowViewUniform } } },
441
+ { binding: 7, resource: { kind: 'buffer', value: { buffer: cascadeBuffer } } },
442
+ { binding: 8, resource: { kind: 'textureView', value: viewDepth } },
443
+ { binding: 10, resource: { kind: 'buffer', value: { buffer: shadowViewUniform } } },
444
+ ],
445
+ })
446
+ .unwrap();
447
+ const directionalShadowViewBindGroup = createShadowViewBindGroup(directionalCascadeUniform);
448
+ const pointShadowViewBindGroup = createShadowViewBindGroup(pointCascadeUniform);
449
+ const spotShadowViewBindGroup = createShadowViewBindGroup(spotCascadeUniform);
450
+ const camera = makeZeroCameraFallbackSnapshot();
451
+ const identityLightMatrix = new Float32Array(16);
452
+ identityLightMatrix[0] = 1;
453
+ identityLightMatrix[5] = 1;
454
+ identityLightMatrix[10] = 1;
455
+ identityLightMatrix[15] = 1;
456
+ writeViewUbo(
457
+ device.queue,
458
+ viewUniform,
459
+ camera,
460
+ {
461
+ kind: 'directional',
462
+ // The triangle faces +Z; choose a light direction whose incoming
463
+ // vector (`-view.lightDir`) illuminates that normal so the opaque
464
+ // readback proves the fragment path rather than a black fixture.
465
+ direction: new Float32Array([0, 0, -1]) as never,
466
+ color: new Float32Array([1, 1, 1]) as never,
467
+ intensity: 1,
468
+ },
469
+ { lightViewProj: [identityLightMatrix] } as never,
470
+ [],
471
+ );
472
+ const shadowLightMatrix = new Float32Array(identityLightMatrix);
473
+ shadowLightMatrix[14] = 2;
474
+ writeViewUbo(
475
+ device.queue,
476
+ shadowViewUniform,
477
+ camera,
478
+ {
479
+ kind: 'directional',
480
+ direction: new Float32Array([0, 0, -1]) as never,
481
+ color: new Float32Array([1, 1, 1]) as never,
482
+ intensity: 1,
483
+ },
484
+ { lightViewProj: [shadowLightMatrix] } as never,
485
+ [],
486
+ );
487
+ device.queue.writeBuffer(directionalCascadeUniform, 0, new Uint32Array([0, 0, 0, 0])).unwrap();
488
+ device.queue.writeBuffer(pointCascadeUniform, 0, new Uint32Array([0, 0, 0, 0])).unwrap();
489
+ device.queue.writeBuffer(spotCascadeUniform, 0, new Uint32Array([0, 1, 0, 0])).unwrap();
490
+ device.queue.writeBuffer(spotCascadeUniform, 16, shadowLightMatrix).unwrap();
491
+ const opaqueMaterialBuffer = device
492
+ .createBuffer({
493
+ size: 512,
494
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
495
+ })
496
+ .unwrap();
497
+ const discardedMaterialBuffer = device
498
+ .createBuffer({
499
+ size: 512,
500
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
501
+ })
502
+ .unwrap();
503
+ const opaqueBaseColorTexture = device
504
+ .createTexture({
505
+ label: 'gpu-driven-alpha-mask.opaque-base-color',
506
+ format: 'rgba8unorm',
507
+ size: { width: transformed ? 4 : 1, height: 1, depthOrArrayLayers: 1 },
508
+ usage: 0x06,
509
+ dimension: '2d',
510
+ textureBindingViewDimension: undefined,
511
+ })
512
+ .unwrap();
513
+ const discardedBaseColorTexture = device
514
+ .createTexture({
515
+ label: 'gpu-driven-alpha-mask.discarded-base-color',
516
+ format: 'rgba8unorm',
517
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
518
+ usage: 0x06,
519
+ dimension: '2d',
520
+ textureBindingViewDimension: undefined,
521
+ })
522
+ .unwrap();
523
+ const opaqueBaseColorView = device
524
+ .createTextureView(opaqueBaseColorTexture, {
525
+ dimension: '2d',
526
+ })
527
+ .unwrap();
528
+ const discardedBaseColorView = device
529
+ .createTextureView(discardedBaseColorTexture, {
530
+ dimension: '2d',
531
+ })
532
+ .unwrap();
533
+ const materialCubeTexture = device
534
+ .createTexture({
535
+ label: 'gpu-driven-alpha-mask.material-fallback-cube',
536
+ format: 'rgba8unorm',
537
+ size: { width: 1, height: 1, depthOrArrayLayers: 6 },
538
+ usage: 0x06,
539
+ dimension: '2d',
540
+ textureBindingViewDimension: 'cube',
541
+ })
542
+ .unwrap();
543
+ const materialCubeView = device
544
+ .createTextureView(materialCubeTexture, { dimension: 'cube' })
545
+ .unwrap();
546
+ const baseColorSampler = device
547
+ .createSampler({ magFilter: 'nearest', minFilter: 'nearest' })
548
+ .unwrap();
549
+ expect(
550
+ device.queue.writeTexture(
551
+ { texture: opaqueBaseColorTexture },
552
+ // Only transformed UV1 reaches the opaque texel. UV0, ignoring
553
+ // rotation, or ignoring the offset each reaches a transparent texel.
554
+ new Uint8Array(
555
+ transformed
556
+ ? [255, 255, 255, 0, 255, 255, 255, 255, 255, 255, 255, 0, 255, 255, 255, 0]
557
+ : [255, 255, 255, 255],
558
+ ),
559
+ { bytesPerRow: transformed ? 16 : 4, rowsPerImage: 1 },
560
+ { width: transformed ? 4 : 1, height: 1, depthOrArrayLayers: 1 },
561
+ ).ok,
562
+ ).toBe(true);
563
+ expect(
564
+ device.queue.writeTexture(
565
+ { texture: discardedBaseColorTexture },
566
+ new Uint8Array([255, 255, 255, 64]),
567
+ { bytesPerRow: 4, rowsPerImage: 1 },
568
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
569
+ ).ok,
570
+ ).toBe(true);
571
+ device.queue.writeBuffer(opaqueMaterialBuffer, 0, new Float32Array([1, 1, 1, 1, 0.5])).unwrap();
572
+ device.queue
573
+ .writeBuffer(discardedMaterialBuffer, 0, new Float32Array([1, 1, 1, 0.25, 0.5]))
574
+ .unwrap();
575
+ const source = alphaMaskSnapshot(coordinateSet, vertexColorAvailable);
576
+ const persistentWorld = new World();
577
+ const persistentLease = createRenderReadLease(persistentWorld);
578
+ const persistentScene = new PersistentRenderScene({ getDevice: () => device });
579
+ const persistentFrame = {
580
+ cameras: [makeZeroCameraFallbackSnapshot()],
581
+ auxiliaryCameras: [],
582
+ cubeCameras: [],
583
+ lights: { directional: undefined, directionalCount: 0, point: [], spot: [] },
584
+ environment: undefined,
585
+ environmentReady: true,
586
+ renderables: [source],
587
+ dispatch: [],
588
+ shadowCasterEntityKeys: new Set<number>(),
589
+ shadowCasterDrawKeys: new Set<string>(),
590
+ skylight: undefined,
591
+ skylightCount: 0,
592
+ skybox: undefined,
593
+ skyboxCount: 0,
594
+ fog: undefined,
595
+ frustumStats: { culled: 0, total: 1 },
596
+ visibilityStats: { explicitlyHidden: 0 },
597
+ postProcessParams: new Map(),
598
+ visibilitySnapshots: [],
599
+ featureVisibilitySnapshots: [],
600
+ hiddenEntityReports: [],
601
+ } as unknown as ExtractedFrame;
602
+ persistentScene.extractComposition(
603
+ [persistentWorld],
604
+ { cameraOwner: 0, resourceOwner: 0 },
605
+ 0,
606
+ () => persistentFrame,
607
+ [persistentLease],
608
+ );
609
+ const initialGpuState = persistentScene.compositionGpuDrivenState();
610
+ expect(initialGpuState).toBeDefined();
611
+ if (initialGpuState === undefined) return;
612
+ const projection = persistentScene.compositionSlots();
613
+ const scene = initialGpuState.scene;
614
+ const structureMetrics = initialGpuState.structureMetrics;
615
+ if (structureMetrics === undefined) return;
616
+ expect(structureMetrics).toEqual({
617
+ worldEntitiesScanned: 1,
618
+ sceneTableUploadBytes: expect.any(Number),
619
+ paletteUploadBytes: 0,
620
+ });
621
+ const createMaterialEntries = (
622
+ materialBuffer: typeof opaqueMaterialBuffer,
623
+ baseColorView: typeof opaqueBaseColorView,
624
+ ) => [
625
+ { binding: 0, resource: { kind: 'buffer' as const, value: { buffer: materialBuffer } } },
626
+ ...Array.from({ length: 7 }, (_, index) => [
627
+ {
628
+ binding: index * 2 + 1,
629
+ resource: { kind: 'sampler' as const, value: baseColorSampler },
630
+ },
631
+ {
632
+ binding: index * 2 + 2,
633
+ resource: { kind: 'textureView' as const, value: baseColorView },
634
+ },
635
+ ]).flat(),
636
+ { binding: 15, resource: { kind: 'textureView' as const, value: materialCubeView } },
637
+ { binding: 16, resource: { kind: 'sampler' as const, value: baseColorSampler } },
638
+ { binding: 17, resource: { kind: 'textureView' as const, value: materialCubeView } },
639
+ { binding: 18, resource: { kind: 'sampler' as const, value: baseColorSampler } },
640
+ { binding: 19, resource: { kind: 'textureView' as const, value: baseColorView } },
641
+ { binding: 20, resource: { kind: 'sampler' as const, value: baseColorSampler } },
642
+ { binding: 21, resource: { kind: 'buffer' as const, value: { buffer: viewUniform } } },
643
+ { binding: 22, resource: { kind: 'sampler' as const, value: baseColorSampler } },
644
+ { binding: 23, resource: { kind: 'textureView' as const, value: baseColorView } },
645
+ {
646
+ binding: 46,
647
+ resource: {
648
+ kind: 'buffer' as const,
649
+ value: { buffer: scene.materialBuffer },
650
+ },
651
+ },
652
+ ];
653
+ const opaqueMaterialBindGroup = device
654
+ .createBindGroup({
655
+ layout: materialLayout,
656
+ entries: createMaterialEntries(opaqueMaterialBuffer, opaqueBaseColorView),
657
+ })
658
+ .unwrap();
659
+ const discardedMaterialBindGroup = device
660
+ .createBindGroup({
661
+ layout: materialLayout,
662
+ entries: createMaterialEntries(discardedMaterialBuffer, discardedBaseColorView),
663
+ })
664
+ .unwrap();
665
+ expect(initialGpuState.plan).toMatchObject({
666
+ candidateCount: 1,
667
+ batches: [{ key: { admission: 'alpha-mask' } }],
668
+ });
669
+ const worldEntity = worldEntityKey(source.worldId, source.entityKey);
670
+ const shadowKey = gpuDrivenShadowDrawKey(worldEntity, 7, 0, 0);
671
+ const shadowIdentities = [
672
+ { kind: 'directional' as const, index: 0 },
673
+ { kind: 'point' as const, index: 0, face: 0 },
674
+ { kind: 'spot' as const, index: 0 },
675
+ ] as const;
676
+ const shadowViews = shadowIdentities.map((identity) => ({
677
+ identity,
678
+ planes: new Float32Array(24),
679
+ cacheToken: 'alpha-mask-dawn',
680
+ }));
681
+ const production = new GpuDrivenProduction(device, {
682
+ createShaderModule: () => ok(viewShader as ShaderModule),
683
+ });
684
+ const gpuMesh = mesh(device, coordinateSet, vertexColorAvailable);
685
+ const receipt = vertexColorAvailable ? STANDARD_PBR_COLORED_RECEIPT : STANDARD_PBR_RECEIPT;
686
+ const artifact = {
687
+ material: 'forgeax::default-standard-pbr',
688
+ pass: 'forward',
689
+ wgsl: standardPbrGpuVariant.composedWgsl,
690
+ layoutIdentity: receipt.reflection.layoutIdentity,
691
+ bindings: [],
692
+ deps: [],
693
+ vertexInputs: receipt.vertexInputs as unknown as readonly Readonly<Record<string, unknown>>[],
694
+ receipt,
695
+ } satisfies MaterialShaderArtifact;
696
+ const programKey = standardPbrProgramKey(artifact.material, vertexColorAvailable);
697
+ expect(programKey).toBeDefined();
698
+ if (programKey === undefined) return;
699
+ const defaultProgramKey = standardPbrProgramKey(artifact.material);
700
+ expect(defaultProgramKey).toBeDefined();
701
+ if (defaultProgramKey === undefined) return;
702
+ const defaultArtifact: MaterialShaderArtifact = vertexColorAvailable
703
+ ? {
704
+ ...artifact,
705
+ wgsl: defaultGpuVariant?.composedWgsl ?? '',
706
+ layoutIdentity: STANDARD_PBR_RECEIPT.reflection.layoutIdentity,
707
+ vertexInputs: STANDARD_PBR_RECEIPT.vertexInputs as unknown as readonly Readonly<
708
+ Record<string, unknown>
709
+ >[],
710
+ receipt: STANDARD_PBR_RECEIPT,
711
+ }
712
+ : artifact;
713
+ const gpuDrivenPbrPrograms = new Map<string, GpuDrivenPbrProgram>([
714
+ [programKey, { module: producerShader.value, artifact }],
715
+ ]);
716
+ if (defaultProgramKey !== programKey) {
717
+ gpuDrivenPbrPrograms.set(defaultProgramKey, {
718
+ module: defaultProducerShader.value,
719
+ artifact: defaultArtifact,
720
+ });
721
+ }
722
+ const prepareInput = {
723
+ scene: {
724
+ scene,
725
+ plan: initialGpuState.plan,
726
+ slots: projection,
727
+ },
728
+ camera: makeZeroCameraFallbackSnapshot(),
729
+ meshBySlot: new Map(projection.map((slot) => [slot.slot, gpuMesh])),
730
+ viewBindGroupLayout: viewLayout,
731
+ meshResidencyEpoch: 1,
732
+ hdrp: false,
733
+ standardPbrArtifact: artifact,
734
+ standardPbrPipelineState: {
735
+ pbrPipelineLayout: pipelineLayout,
736
+ gpuDrivenPbrPipelineLayout: pipelineLayout,
737
+ meshBindGroupLayout: meshLayout,
738
+ instancesBindGroupLayout: instancesLayout,
739
+ gpuDrivenInstancesBindGroupLayout: instancesLayout,
740
+ standardPbrShaderModule: producerShader.value,
741
+ gpuDrivenPbrPrograms,
742
+ // The composed producer keeps the canonical uv0..uv7 input ABI even
743
+ // when this material only authored uv0. The geometry owner aliases the
744
+ // missing sets to uv0 when the producer advertises the full ABI.
745
+ standardPbrShaderUvSetCount: 8,
746
+ } as never,
747
+ shadowCasterDrawKeys: new Set([shadowKey]),
748
+ structureMetrics: {
749
+ ...structureMetrics,
750
+ },
751
+ } as const;
752
+ const prepared = production.prepare(prepareInput);
753
+ expect(prepared.ok).toBe(true);
754
+ if (!prepared.ok || prepared.value === undefined) return;
755
+ expect(prepared.value.ownsAllDrawItems).toBe(true);
756
+ expect(prepared.value.shadowDrawKeys).toEqual(new Set([shadowKey]));
757
+ expect(prepared.value.standardPbrFrameResources.instancesBindGroup).toBeDefined();
758
+ expect(prepared.value.standardPbrFrameResources.sceneMaterialBuffer).toBe(scene.materialBuffer);
759
+ expect(prepared.value.standardPbrFrameResources.materialStride).toBe(512);
760
+ expect(artifact.receipt.alphaMask).toEqual({ cutoff: 'alphaCutoff', source: 'baseColor.a' });
761
+
762
+ const shadowVertexLayout = deriveVertexBufferLayoutFromProjection(gpuMesh.layoutProjection, {
763
+ shaderUvSetCount: 8,
764
+ })[0];
765
+ expect(shadowVertexLayout).toBeDefined();
766
+ if (shadowVertexLayout === undefined) return;
767
+ const shadowPipeline = device
768
+ .createRenderPipeline({
769
+ label: 'gpu-driven-alpha-mask-production-shadow-caster',
770
+ layout: pipelineLayout,
771
+ vertex: {
772
+ module: shadowCasterShader,
773
+ entryPoint: 'vs_scene_index',
774
+ buffers: [
775
+ {
776
+ arrayStride: shadowVertexLayout.arrayStride,
777
+ stepMode: 'vertex',
778
+ attributes: shadowVertexLayout.attributes.map(
779
+ ({ shaderLocation, offset, format }) => ({
780
+ shaderLocation,
781
+ offset,
782
+ format: format as GPUVertexFormat,
783
+ }),
784
+ ),
785
+ },
786
+ ],
787
+ },
788
+ fragment: {
789
+ module: shadowCasterShader,
790
+ entryPoint: 'fs_main',
791
+ targets: [],
792
+ },
793
+ primitive: { topology: 'triangle-list', cullMode: 'back', frontFace: 'ccw' },
794
+ depthStencil: {
795
+ format: 'depth32float',
796
+ depthWriteEnabled: true,
797
+ depthCompare: 'less',
798
+ },
799
+ })
800
+ .unwrap();
801
+
802
+ const shadowUpdated = prepared.value.updateShadowViews?.(shadowViews);
803
+ expect(shadowUpdated?.ok).toBe(true);
804
+ expect(prepared.value.shadowBatchProjections?.get('directional:0:')).toBeDefined();
805
+ expect(prepared.value.shadowBatchProjections?.get('point:0:0')).toBeDefined();
806
+ expect(prepared.value.shadowBatchProjections?.get('spot:0:')).toBeDefined();
807
+
808
+ const graph = new RenderGraphBuilder<RenderPipelineFrame>();
809
+ const main = prepared.value.project(graph, 'rgba8unorm', 1);
810
+ expect(main.ok).toBe(true);
811
+ if (!main.ok) return;
812
+ const shadow = prepared.value.projectShadow?.(graph, shadowIdentities[0]);
813
+ expect(shadow?.ok).toBe(true);
814
+ if (shadow === undefined || !shadow.ok || shadow.value === undefined) return;
815
+ if (shadow.value.graphResources === undefined) return;
816
+ const shadowProjection = shadow.value;
817
+ const shadowGraphResources = shadowProjection.graphResources;
818
+ if (shadowGraphResources === undefined) return;
819
+ const pointShadow = prepared.value.projectShadow?.(graph, shadowIdentities[1]);
820
+ const spotShadow = prepared.value.projectShadow?.(graph, shadowIdentities[2]);
821
+ expect(pointShadow?.ok).toBe(true);
822
+ expect(spotShadow?.ok).toBe(true);
823
+ if (
824
+ pointShadow === undefined ||
825
+ !pointShadow.ok ||
826
+ pointShadow.value === undefined ||
827
+ pointShadow.value.graphResources === undefined ||
828
+ spotShadow === undefined ||
829
+ !spotShadow.ok ||
830
+ spotShadow.value === undefined ||
831
+ spotShadow.value.graphResources === undefined
832
+ )
833
+ return;
834
+ const pointShadowGraphResources = pointShadow.value.graphResources;
835
+ const spotShadowGraphResources = spotShadow.value.graphResources;
836
+ const frameResources = prepared.value.standardPbrFrameResources;
837
+
838
+ const shadowDepthTargets = shadowIdentities.map((identity) => {
839
+ const texture = graph
840
+ .createTexture(`gpu-driven-alpha-mask.${identity.kind}-shadow-depth`, {
841
+ format: 'depth32float',
842
+ size: { width: 1, height: 1 },
843
+ })
844
+ .unwrap();
845
+ return { identity, texture, view: graph.view(texture).unwrap() };
846
+ });
847
+ const shadowDepthReadbackBuffers = shadowIdentities.map((identity) => ({
848
+ identity,
849
+ buffer: device
850
+ .createBuffer({
851
+ label: `gpu-driven-alpha-mask.${identity.kind}-shadow-depth-readback`,
852
+ size: 256,
853
+ usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST,
854
+ })
855
+ .unwrap(),
856
+ }));
857
+ const shadowDepthReadbackResources = shadowDepthReadbackBuffers.map(({ identity, buffer }) => ({
858
+ identity,
859
+ buffer,
860
+ resource: graph
861
+ .importBuffer(
862
+ `gpu-driven-alpha-mask.${identity.kind}-shadow-depth-readback`,
863
+ { size: 256, usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST },
864
+ () => buffer,
865
+ )
866
+ .unwrap(),
867
+ }));
868
+ const makeShadowSubmission = (
869
+ identity: (typeof shadowIdentities)[number],
870
+ ): GpuDrivenShadowSubmission | undefined => {
871
+ const preparedFrame = prepared.value;
872
+ if (preparedFrame === undefined) return undefined;
873
+ const submission = preparedFrame.shadowViewPool?.submission(identity);
874
+ const projections = preparedFrame.shadowBatchProjections?.get(
875
+ `${identity.kind}:${identity.index}:${identity.kind === 'point' ? identity.face : ''}`,
876
+ );
877
+ if (submission === undefined || projections === undefined) return undefined;
878
+ const batches = new Map<number, GpuDrivenShadowBatch>();
879
+ for (const batch of submission.plan.batches) {
880
+ const projection = projections.get(batch.batchId);
881
+ if (projection === undefined) return undefined;
882
+ const indexBuffer = projection.mesh.indexBuffer;
883
+ batches.set(batch.batchId, {
884
+ pipeline: shadowPipeline,
885
+ meshBindGroup: projection.meshBindGroup,
886
+ visibleBindGroup: projection.visibleBindGroup,
887
+ deformation: projection.deformation,
888
+ alphaMaskMaterialGroup: opaqueMaterialBindGroup,
889
+ vertexBuffer: projection.mesh.vertexBuffer.handle,
890
+ ...(indexBuffer === null
891
+ ? {}
892
+ : {
893
+ indexBuffer: indexBuffer.handle,
894
+ indexFormat: projection.mesh.indexFormat,
895
+ }),
896
+ });
897
+ }
898
+ return { view: submission.view, plan: submission.plan, batches };
899
+ };
900
+
901
+ const opaqueOutput = graph
902
+ .createTexture('gpu-driven-alpha-mask-opaque', {
903
+ format: 'rgba8unorm',
904
+ size: { width: 1, height: 1 },
905
+ })
906
+ .unwrap();
907
+ const discardedOutput = graph
908
+ .createTexture('gpu-driven-alpha-mask-discarded', {
909
+ format: 'rgba8unorm',
910
+ size: { width: 1, height: 1 },
911
+ })
912
+ .unwrap();
913
+ const opaqueOutputView = graph.view(opaqueOutput).unwrap();
914
+ const discardedOutputView = graph.view(discardedOutput).unwrap();
915
+ const depth = graph
916
+ .createTexture('gpu-driven-alpha-mask-depth', {
917
+ format: 'depth24plus-stencil8',
918
+ size: { width: 1, height: 1 },
919
+ })
920
+ .unwrap();
921
+ const depthView = graph.view(depth).unwrap();
922
+ const opaqueReadbackBuffer = device
923
+ .createBuffer({
924
+ size: 256,
925
+ usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST,
926
+ })
927
+ .unwrap();
928
+ const discardedReadbackBuffer = device
929
+ .createBuffer({
930
+ size: 256,
931
+ usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST,
932
+ })
933
+ .unwrap();
934
+ const shadowIndirectReadbackBuffer = device
935
+ .createBuffer({
936
+ size: 256,
937
+ usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST,
938
+ })
939
+ .unwrap();
940
+ const pointShadowIndirectReadbackBuffer = device
941
+ .createBuffer({
942
+ size: 256,
943
+ usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST,
944
+ })
945
+ .unwrap();
946
+ const spotShadowIndirectReadbackBuffer = device
947
+ .createBuffer({
948
+ size: 256,
949
+ usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST,
950
+ })
951
+ .unwrap();
952
+ const opaqueReadback = graph
953
+ .importBuffer(
954
+ 'gpu-driven-alpha-mask-opaque-readback',
955
+ { size: 256, usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST },
956
+ () => opaqueReadbackBuffer,
957
+ )
958
+ .unwrap();
959
+ const discardedReadback = graph
960
+ .importBuffer(
961
+ 'gpu-driven-alpha-mask-discarded-readback',
962
+ { size: 256, usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST },
963
+ () => discardedReadbackBuffer,
964
+ )
965
+ .unwrap();
966
+ const shadowIndirectReadback = graph
967
+ .importBuffer(
968
+ 'gpu-driven-alpha-mask-shadow-indirect-readback',
969
+ { size: 256, usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST },
970
+ () => shadowIndirectReadbackBuffer,
971
+ )
972
+ .unwrap();
973
+ const pointShadowIndirectReadback = graph
974
+ .importBuffer(
975
+ 'gpu-driven-alpha-mask-point-shadow-indirect-readback',
976
+ { size: 256, usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST },
977
+ () => pointShadowIndirectReadbackBuffer,
978
+ )
979
+ .unwrap();
980
+ const spotShadowIndirectReadback = graph
981
+ .importBuffer(
982
+ 'gpu-driven-alpha-mask-spot-shadow-indirect-readback',
983
+ { size: 256, usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST },
984
+ () => spotShadowIndirectReadbackBuffer,
985
+ )
986
+ .unwrap();
987
+ const addAlphaMaskRaster = (
988
+ name: string,
989
+ outputView: typeof opaqueOutputView,
990
+ materialBindGroup: typeof opaqueMaterialBindGroup,
991
+ ) =>
992
+ graph
993
+ .addRasterPass(name, {
994
+ accesses: [
995
+ ...main.value.accesses,
996
+ { resource: outputView, usage: 'color-attachment' },
997
+ { resource: depthView, usage: 'depth-stencil-write' },
998
+ ],
999
+ colorAttachments: [
1000
+ {
1001
+ view: outputView,
1002
+ loadOp: 'clear',
1003
+ storeOp: 'store',
1004
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
1005
+ },
1006
+ ],
1007
+ depthStencilAttachment: {
1008
+ view: depthView,
1009
+ depthClearValue: 1,
1010
+ depthLoadOp: 'clear',
1011
+ depthStoreOp: 'discard',
1012
+ stencilLoadOp: 'clear',
1013
+ stencilStoreOp: 'discard',
1014
+ },
1015
+ encode: ({ pass, resources }) =>
1016
+ main.value.encode(viewBindGroup, pass, resources, {
1017
+ ...frameResources,
1018
+ materialBindGroups: [materialBindGroup],
1019
+ }),
1020
+ })
1021
+ .unwrap();
1022
+ addAlphaMaskRaster('gpu-driven.alpha-mask.opaque', opaqueOutputView, opaqueMaterialBindGroup);
1023
+ addAlphaMaskRaster(
1024
+ 'gpu-driven.alpha-mask.discarded',
1025
+ discardedOutputView,
1026
+ discardedMaterialBindGroup,
1027
+ );
1028
+ const shadowViewBindGroups = new Map([
1029
+ ['directional', directionalShadowViewBindGroup],
1030
+ ['point', pointShadowViewBindGroup],
1031
+ ['spot', spotShadowViewBindGroup],
1032
+ ] as const);
1033
+ const shadowGraphResourcesByKind = new Map([
1034
+ ['directional', shadowGraphResources],
1035
+ ['point', pointShadowGraphResources],
1036
+ ['spot', spotShadowGraphResources],
1037
+ ] as const);
1038
+ for (const target of shadowDepthTargets) {
1039
+ const submission = makeShadowSubmission(target.identity);
1040
+ const resources = shadowGraphResourcesByKind.get(target.identity.kind);
1041
+ const shadowViewGroup = shadowViewBindGroups.get(target.identity.kind);
1042
+ const readback = shadowDepthReadbackResources.find(
1043
+ ({ identity }) => identity.kind === target.identity.kind,
1044
+ );
1045
+ expect(submission).toBeDefined();
1046
+ expect(resources).toBeDefined();
1047
+ expect(shadowViewGroup).toBeDefined();
1048
+ expect(readback).toBeDefined();
1049
+ if (
1050
+ submission === undefined ||
1051
+ resources === undefined ||
1052
+ shadowViewGroup === undefined ||
1053
+ readback === undefined
1054
+ )
1055
+ return;
1056
+ graph
1057
+ .addRasterPass(`gpu-driven.alpha-mask.${target.identity.kind}-shadow-depth-raster`, {
1058
+ accesses: [
1059
+ ...shadowViewRasterAccesses(resources),
1060
+ { resource: target.view, usage: 'depth-stencil-write' },
1061
+ ],
1062
+ colorAttachments: [],
1063
+ depthStencilAttachment: {
1064
+ view: target.view,
1065
+ depthClearValue: 1,
1066
+ depthLoadOp: 'clear',
1067
+ depthStoreOp: 'store',
1068
+ },
1069
+ encode: ({ pass }) => {
1070
+ pass.setBindGroup(0, shadowViewGroup, [0, 0]);
1071
+ recordGpuDrivenShadowIndirect(pass, submission);
1072
+ },
1073
+ })
1074
+ .unwrap();
1075
+ graph
1076
+ .addCopyPass(`gpu-driven.alpha-mask.${target.identity.kind}-shadow-depth-readback`, {
1077
+ accesses: [
1078
+ { resource: target.view, usage: 'copy-src' },
1079
+ { resource: readback.resource, usage: 'copy-dst' },
1080
+ ],
1081
+ encode: ({ encoder, resources: graphResources }) =>
1082
+ encoder.copyTextureToBuffer(
1083
+ { texture: graphResources.texture(target.texture).unwrap() as unknown as GPUTexture },
1084
+ {
1085
+ buffer: graphResources.buffer(readback.resource).unwrap() as unknown as GPUBuffer,
1086
+ bytesPerRow: 256,
1087
+ rowsPerImage: 1,
1088
+ },
1089
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
1090
+ ),
1091
+ })
1092
+ .unwrap();
1093
+ }
1094
+ graph
1095
+ .addCopyPass('gpu-driven.alpha-mask.opaque-readback', {
1096
+ accesses: [
1097
+ { resource: opaqueOutputView, usage: 'copy-src' },
1098
+ { resource: opaqueReadback, usage: 'copy-dst' },
1099
+ ],
1100
+ encode: ({ encoder, resources }) =>
1101
+ encoder.copyTextureToBuffer(
1102
+ { texture: resources.texture(opaqueOutput).unwrap() as unknown as GPUTexture },
1103
+ {
1104
+ buffer: resources.buffer(opaqueReadback).unwrap() as unknown as GPUBuffer,
1105
+ bytesPerRow: 256,
1106
+ rowsPerImage: 1,
1107
+ },
1108
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
1109
+ ),
1110
+ })
1111
+ .unwrap();
1112
+ graph
1113
+ .addCopyPass('gpu-driven.alpha-mask.discarded-readback', {
1114
+ accesses: [
1115
+ { resource: discardedOutputView, usage: 'copy-src' },
1116
+ { resource: discardedReadback, usage: 'copy-dst' },
1117
+ ],
1118
+ encode: ({ encoder, resources }) =>
1119
+ encoder.copyTextureToBuffer(
1120
+ { texture: resources.texture(discardedOutput).unwrap() as unknown as GPUTexture },
1121
+ {
1122
+ buffer: resources.buffer(discardedReadback).unwrap() as unknown as GPUBuffer,
1123
+ bytesPerRow: 256,
1124
+ rowsPerImage: 1,
1125
+ },
1126
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
1127
+ ),
1128
+ })
1129
+ .unwrap();
1130
+ graph
1131
+ .addCopyPass('gpu-driven.alpha-mask.shadow-indirect-readback', {
1132
+ accesses: [
1133
+ { resource: shadowGraphResources.indirect, usage: 'copy-src' },
1134
+ { resource: shadowIndirectReadback, usage: 'copy-dst' },
1135
+ ],
1136
+ encode: ({ encoder, resources }) =>
1137
+ encoder.copyBufferToBuffer(
1138
+ resources.buffer(shadowGraphResources.indirect).unwrap(),
1139
+ 0,
1140
+ resources.buffer(shadowIndirectReadback).unwrap(),
1141
+ 0,
1142
+ 20,
1143
+ ),
1144
+ })
1145
+ .unwrap();
1146
+ graph
1147
+ .addCopyPass('gpu-driven.alpha-mask.point-shadow-indirect-readback', {
1148
+ accesses: [
1149
+ { resource: pointShadowGraphResources.indirect, usage: 'copy-src' },
1150
+ { resource: pointShadowIndirectReadback, usage: 'copy-dst' },
1151
+ ],
1152
+ encode: ({ encoder, resources }) =>
1153
+ encoder.copyBufferToBuffer(
1154
+ resources.buffer(pointShadowGraphResources.indirect).unwrap(),
1155
+ 0,
1156
+ resources.buffer(pointShadowIndirectReadback).unwrap(),
1157
+ 0,
1158
+ 20,
1159
+ ),
1160
+ })
1161
+ .unwrap();
1162
+ graph
1163
+ .addCopyPass('gpu-driven.alpha-mask.spot-shadow-indirect-readback', {
1164
+ accesses: [
1165
+ { resource: spotShadowGraphResources.indirect, usage: 'copy-src' },
1166
+ { resource: spotShadowIndirectReadback, usage: 'copy-dst' },
1167
+ ],
1168
+ encode: ({ encoder, resources }) =>
1169
+ encoder.copyBufferToBuffer(
1170
+ resources.buffer(spotShadowGraphResources.indirect).unwrap(),
1171
+ 0,
1172
+ resources.buffer(spotShadowIndirectReadback).unwrap(),
1173
+ 0,
1174
+ 20,
1175
+ ),
1176
+ })
1177
+ .unwrap();
1178
+ const compiled = graph.compile({ device, surfaceSize: { width: 1, height: 1 } });
1179
+ expect(compiled.ok).toBe(true);
1180
+ if (compiled.ok) {
1181
+ const names = compiled.value.inspect().passes.map((pass) => pass.name);
1182
+ expect(names).toEqual(
1183
+ expect.arrayContaining([
1184
+ 'gpu-driven.view-reset',
1185
+ 'gpu-driven.frustum-compact',
1186
+ 'gpu-driven.finalize-indirect',
1187
+ 'gpu-driven.shadow.directional-cascade-0.view-reset',
1188
+ 'gpu-driven.shadow.directional-cascade-0.finalize-indirect',
1189
+ 'gpu-driven.shadow.point-cube-face-0-0.view-reset',
1190
+ 'gpu-driven.shadow.point-cube-face-0-0.finalize-indirect',
1191
+ 'gpu-driven.shadow.spot-atlas-0.view-reset',
1192
+ 'gpu-driven.shadow.spot-atlas-0.finalize-indirect',
1193
+ 'gpu-driven.alpha-mask.opaque',
1194
+ 'gpu-driven.alpha-mask.discarded',
1195
+ ]),
1196
+ );
1197
+ const encoder = device.createCommandEncoder({ label: 'gpu-driven-alpha-mask-dawn' }).unwrap();
1198
+ expect(compiled.value.execute({ encoder } as unknown as RenderPipelineFrame).ok).toBe(true);
1199
+ device.queue.submit([encoder.finish().unwrap()]).unwrap();
1200
+ await device.queue.onSubmittedWorkDone();
1201
+ prepared.value._commitResourceReplacement();
1202
+ expect(production.inspect().worldEntitiesScanned).toBe(1);
1203
+ expect(production.inspect().sceneTableUploadBytes).toBeGreaterThan(0);
1204
+ expect(production.inspect().paletteUploadBytes).toBe(0);
1205
+ persistentScene.extractComposition(
1206
+ [persistentWorld],
1207
+ { cameraOwner: 0, resourceOwner: 0 },
1208
+ 0,
1209
+ () => persistentFrame,
1210
+ [persistentLease],
1211
+ );
1212
+ const stableGpuState = persistentScene.compositionGpuDrivenState();
1213
+ expect(stableGpuState).toBeDefined();
1214
+ if (stableGpuState === undefined) return;
1215
+ const stableMetrics = stableGpuState.structureMetrics;
1216
+ if (stableMetrics === undefined) return;
1217
+ expect(stableMetrics).toEqual({
1218
+ worldEntitiesScanned: 0,
1219
+ sceneTableUploadBytes: 0,
1220
+ paletteUploadBytes: 0,
1221
+ });
1222
+ const stablePrepared = production.prepare({
1223
+ ...prepareInput,
1224
+ scene: stableGpuState,
1225
+ structureMetrics: stableMetrics,
1226
+ });
1227
+ expect(stablePrepared.ok).toBe(true);
1228
+ if (!stablePrepared.ok || stablePrepared.value === undefined) return;
1229
+ const stableShadowUpdated = stablePrepared.value.updateShadowViews?.(shadowViews);
1230
+ expect(stableShadowUpdated?.ok).toBe(true);
1231
+ stablePrepared.value._commitResourceReplacement();
1232
+ const stableValidationRows = [{ source }] as const;
1233
+ const stableGpuOwnedDrawKeys = new Set([gpuDrivenDrawKey(worldEntity, 7, 0)]);
1234
+ const stableValidationCacheKey = 'alpha-mask-dawn-stable';
1235
+ production.recordCpuValidation(
1236
+ stableValidationRows,
1237
+ stableGpuOwnedDrawKeys,
1238
+ stableValidationCacheKey,
1239
+ );
1240
+ const stableBaseline = production.inspect();
1241
+ const stableFrameInspections: Array<{
1242
+ readonly gpuOwnedSnapshotsMaterialized: number;
1243
+ readonly filteredPlanBuilds: number;
1244
+ readonly candidateUploadBytes: number;
1245
+ readonly batchUploadBytes: number;
1246
+ readonly batchBindGroupCreates: number;
1247
+ readonly validatedGpuOwnedRows: number;
1248
+ readonly cpuFallbackDrawItems: number;
1249
+ readonly cpuValidationScans: number;
1250
+ readonly cpuValidationCacheHits: number;
1251
+ readonly worldEntitiesScanned: number;
1252
+ readonly sceneTableUploadBytes: number;
1253
+ readonly paletteUploadBytes: number;
1254
+ readonly overflow: boolean;
1255
+ }> = [];
1256
+ const stableFrameMetrics: Array<NonNullable<typeof stableGpuState.structureMetrics>> = [];
1257
+ for (let frame = 0; frame < STABLE_FRAME_COUNT; frame += 1) {
1258
+ persistentScene.extractComposition(
1259
+ [persistentWorld],
1260
+ { cameraOwner: 0, resourceOwner: 0 },
1261
+ 0,
1262
+ () => persistentFrame,
1263
+ [persistentLease],
1264
+ );
1265
+ const frameGpuState = persistentScene.compositionGpuDrivenState();
1266
+ expect(frameGpuState).toBeDefined();
1267
+ if (frameGpuState === undefined || frameGpuState.structureMetrics === undefined) return;
1268
+ stableFrameMetrics.push(frameGpuState.structureMetrics);
1269
+ // Exercise preparation on every submitted frame. Replaying an already
1270
+ // compiled graph alone cannot detect a per-frame topology/upload leak.
1271
+ const framePrepared = production.prepare({
1272
+ ...prepareInput,
1273
+ scene: frameGpuState,
1274
+ structureMetrics: frameGpuState.structureMetrics,
1275
+ });
1276
+ expect(framePrepared.ok).toBe(true);
1277
+ if (!framePrepared.ok || framePrepared.value === undefined) return;
1278
+ expect(framePrepared.value.updateShadowViews?.(shadowViews)?.ok).toBe(true);
1279
+ framePrepared.value._commitResourceReplacement();
1280
+ production.recordCpuValidation(
1281
+ stableValidationRows,
1282
+ stableGpuOwnedDrawKeys,
1283
+ stableValidationCacheKey,
1284
+ );
1285
+ const stableEncoder = device
1286
+ .createCommandEncoder({ label: `gpu-driven-alpha-mask-stable-${frame}` })
1287
+ .unwrap();
1288
+ expect(
1289
+ compiled.value.execute({ encoder: stableEncoder } as unknown as RenderPipelineFrame).ok,
1290
+ ).toBe(true);
1291
+ device.queue.submit([stableEncoder.finish().unwrap()]).unwrap();
1292
+ await device.queue.onSubmittedWorkDone();
1293
+ const inspection = production.inspect();
1294
+ stableFrameInspections.push({
1295
+ gpuOwnedSnapshotsMaterialized: inspection.gpuOwnedSnapshotsMaterialized,
1296
+ filteredPlanBuilds: inspection.filteredPlanBuilds,
1297
+ candidateUploadBytes: inspection.candidateUploadBytes,
1298
+ batchUploadBytes: inspection.batchUploadBytes,
1299
+ batchBindGroupCreates: inspection.batchBindGroupCreates,
1300
+ validatedGpuOwnedRows: inspection.validatedGpuOwnedRows,
1301
+ cpuFallbackDrawItems: inspection.cpuFallbackDrawItems,
1302
+ cpuValidationScans: inspection.cpuValidationScans,
1303
+ cpuValidationCacheHits: inspection.cpuValidationCacheHits,
1304
+ worldEntitiesScanned: inspection.worldEntitiesScanned,
1305
+ sceneTableUploadBytes: inspection.sceneTableUploadBytes,
1306
+ paletteUploadBytes: inspection.paletteUploadBytes,
1307
+ overflow: inspection.overflow,
1308
+ });
1309
+ }
1310
+ expect(stableFrameInspections).toHaveLength(STABLE_FRAME_COUNT);
1311
+ expect(stableFrameMetrics).toHaveLength(STABLE_FRAME_COUNT);
1312
+ for (const metrics of stableFrameMetrics) {
1313
+ expect(metrics).toEqual({
1314
+ worldEntitiesScanned: 0,
1315
+ sceneTableUploadBytes: 0,
1316
+ paletteUploadBytes: 0,
1317
+ });
1318
+ }
1319
+ for (const inspection of stableFrameInspections) {
1320
+ expect(inspection).toEqual({
1321
+ gpuOwnedSnapshotsMaterialized: 0,
1322
+ filteredPlanBuilds: 0,
1323
+ candidateUploadBytes: 0,
1324
+ batchUploadBytes: 0,
1325
+ batchBindGroupCreates: 0,
1326
+ validatedGpuOwnedRows: 0,
1327
+ cpuFallbackDrawItems: 0,
1328
+ cpuValidationScans: stableBaseline.cpuValidationScans,
1329
+ cpuValidationCacheHits: expect.any(Number),
1330
+ worldEntitiesScanned: 0,
1331
+ sceneTableUploadBytes: 0,
1332
+ paletteUploadBytes: 0,
1333
+ overflow: false,
1334
+ });
1335
+ }
1336
+ expect(production.inspect().cpuValidationScans).toBe(stableBaseline.cpuValidationScans);
1337
+ expect(production.inspect().cpuValidationCacheHits).toBe(
1338
+ stableBaseline.cpuValidationCacheHits + STABLE_FRAME_COUNT,
1339
+ );
1340
+ const opaqueMapped = (
1341
+ await opaqueReadbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ)
1342
+ ).unwrap();
1343
+ const opaquePixel = [...new Uint8Array(opaqueMapped.getMappedRange().unwrap().slice(0, 4))];
1344
+ opaqueMapped.unmap();
1345
+ const discardedMapped = (
1346
+ await discardedReadbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ)
1347
+ ).unwrap();
1348
+ const discardedPixel = [
1349
+ ...new Uint8Array(discardedMapped.getMappedRange().unwrap().slice(0, 4)),
1350
+ ];
1351
+ discardedMapped.unmap();
1352
+ const shadowIndirectMapped = (
1353
+ await shadowIndirectReadbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ)
1354
+ ).unwrap();
1355
+ const shadowIndirect = new Uint32Array(
1356
+ shadowIndirectMapped.getMappedRange().unwrap().slice(0, 20),
1357
+ );
1358
+ shadowIndirectMapped.unmap();
1359
+ const pointShadowIndirectMapped = (
1360
+ await pointShadowIndirectReadbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ)
1361
+ ).unwrap();
1362
+ const pointShadowIndirect = new Uint32Array(
1363
+ pointShadowIndirectMapped.getMappedRange().unwrap().slice(0, 20),
1364
+ );
1365
+ pointShadowIndirectMapped.unmap();
1366
+ const spotShadowIndirectMapped = (
1367
+ await spotShadowIndirectReadbackBuffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ)
1368
+ ).unwrap();
1369
+ const spotShadowIndirect = new Uint32Array(
1370
+ spotShadowIndirectMapped.getMappedRange().unwrap().slice(0, 20),
1371
+ );
1372
+ spotShadowIndirectMapped.unmap();
1373
+ expect(opaquePixel[0]).toBeGreaterThan(0);
1374
+ if (vertexColorAvailable) {
1375
+ expect(opaquePixel[0]).toBeGreaterThan(opaquePixel[1] ?? Number.NaN);
1376
+ } else {
1377
+ expect(opaquePixel[1]).toBeGreaterThan(0);
1378
+ }
1379
+ expect(discardedPixel).toEqual([0, 0, 0, 0]);
1380
+ expect(Array.from(shadowIndirect.slice(0, 2))).toEqual([3, 1]);
1381
+ expect(Array.from(pointShadowIndirect.slice(0, 2))).toEqual([3, 1]);
1382
+ expect(Array.from(spotShadowIndirect.slice(0, 2))).toEqual([3, 1]);
1383
+ for (const { identity, buffer } of shadowDepthReadbackBuffers) {
1384
+ const mapped = (await buffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ)).unwrap();
1385
+ const depth = new Float32Array(mapped.getMappedRange().unwrap().slice(0, 4))[0] ?? 1;
1386
+ mapped.unmap();
1387
+ expect(depth, `${identity.kind} shadow depth raster readback`).toBeLessThan(1);
1388
+ }
1389
+ (await compiled.value.retire()).unwrap();
1390
+ }
1391
+ for (const buffer of [
1392
+ viewUniform,
1393
+ opaqueMaterialBuffer,
1394
+ discardedMaterialBuffer,
1395
+ opaqueReadbackBuffer,
1396
+ discardedReadbackBuffer,
1397
+ shadowIndirectReadbackBuffer,
1398
+ pointShadowIndirectReadbackBuffer,
1399
+ spotShadowIndirectReadbackBuffer,
1400
+ ...shadowDepthReadbackBuffers.map(({ buffer }) => buffer),
1401
+ ]) {
1402
+ device.destroyBuffer(buffer);
1403
+ }
1404
+ device.destroyTexture(opaqueBaseColorTexture);
1405
+ device.destroyTexture(discardedBaseColorTexture);
1406
+ device.destroyTexture(viewDepthTexture);
1407
+ device.destroyTexture(pointShadowTexture);
1408
+ device.destroyTexture(materialCubeTexture);
1409
+ device.destroyBuffer(shadowViewUniform);
1410
+ device.destroyBuffer(directionalCascadeUniform);
1411
+ device.destroyBuffer(pointCascadeUniform);
1412
+ device.destroyBuffer(spotCascadeUniform);
1413
+ production.dispose();
1414
+ persistentScene.dispose();
1415
+ persistentLease.dispose();
1416
+ });
1417
+ });