@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
@@ -1,14 +1,26 @@
1
+ import { World } from '@forgeax/engine-ecs';
1
2
  import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
2
- import { type RhiCommandEncoder, type RhiDevice, RhiError } from '@forgeax/engine-rhi';
3
- import { RhiNullAdapter } from '@forgeax/engine-rhi-null';
4
- import { err } from '@forgeax/engine-types';
3
+ import { type Buffer, type RhiCommandEncoder, type RhiDevice, RhiError } from '@forgeax/engine-rhi';
4
+ import { RhiNullAdapter, rhi } from '@forgeax/engine-rhi-null';
5
+ import { err, ok } from '@forgeax/engine-types';
5
6
  import { describe, expect, it, vi } from 'vitest';
7
+ import {
8
+ createRenderer as constructRenderer,
9
+ disposeObservationCaptureSet,
10
+ exposeRenderer,
11
+ } from '../assembly/factory';
6
12
  import { executeRendererFrameTransaction } from '../assembly/renderer-frame-transaction';
7
13
  import { DeviceScope } from '../device/device-scope';
8
14
  import { selectEnvironment } from '../environment/frame';
9
15
  import { EnvironmentLifecycle } from '../environment/lifecycle';
16
+ import type { RendererContractFailureError } from '../errors/render';
17
+ import { GPU_TEXTURE_USAGE_COPY_SRC } from '../gpu-texture-usage';
10
18
  import { RhiErrorListenerRegistry } from '../lifecycle';
11
- import type { RenderFrameState } from '../record/frame-snapshot';
19
+ import type {
20
+ GraphTargetPassReplayPrefixReceipt,
21
+ GraphTargetPassReplayReceipt,
22
+ RenderFrameState,
23
+ } from '../record/frame-snapshot';
12
24
  import { executeCompiledFrameGraph } from '../record/typed-frame-graph';
13
25
  import { createRenderPipelineTarget, type RenderPipelineFrame } from '../render-pipeline';
14
26
  import {
@@ -88,11 +100,21 @@ function temporalResolveFrame(
88
100
 
89
101
  async function buildTemporalResolveGraph(device: RhiDevice) {
90
102
  const graph = new RenderGraphBuilder<RenderPipelineFrame>();
91
- const descriptor = (format: 'rgba8unorm' | 'rgba16float') => ({
103
+ const descriptor = (format: 'rgba8unorm' | 'rgba16float' | 'r8unorm') => ({
92
104
  format,
93
105
  size: { width: 8, height: 8 },
94
106
  });
95
107
  const targets = {
108
+ historyStability: createRenderPipelineTarget(
109
+ graph,
110
+ 'taa-history-stability',
111
+ descriptor('r8unorm'),
112
+ ).unwrap(),
113
+ writeStability: createRenderPipelineTarget(
114
+ graph,
115
+ 'taa-write-stability',
116
+ descriptor('r8unorm'),
117
+ ).unwrap(),
96
118
  scene: createRenderPipelineTarget(graph, 'taa-scene', descriptor('rgba16float')).unwrap(),
97
119
  currentTemporal: createRenderPipelineTarget(
98
120
  graph,
@@ -139,11 +161,861 @@ async function buildTemporalResolveGraph(device: RhiDevice) {
139
161
  encode: ({ pass }) => pass.draw(3, 1, 0, 0),
140
162
  }).ok,
141
163
  ).toBe(true);
164
+ // Four rgba16float attachments already consume the default 32-byte budget.
165
+ expect(
166
+ graph.addRasterPass('taa-seed-stability', {
167
+ accesses: [{ resource: targets.historyStability.view, usage: 'color-attachment' }],
168
+ colorAttachments: [
169
+ {
170
+ view: targets.historyStability.view,
171
+ loadOp: 'clear',
172
+ storeOp: 'store',
173
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
174
+ },
175
+ ],
176
+ encode: () => {},
177
+ }).ok,
178
+ ).toBe(true);
142
179
  expect(addTypedTemporalResolvePass(graph, targets).ok).toBe(true);
143
180
  return graph.compile({ device, surfaceSize: { width: 8, height: 8 } }).unwrap();
144
181
  }
145
182
 
183
+ type ReplayFailureStage = 'execute' | 'finish' | 'submit';
184
+ type ReplayCaptureKind = 'target-copy' | 'pass-replay';
185
+ type ReplayCallbackMode =
186
+ | 'valid'
187
+ | 'missing-target'
188
+ | 'missing-depth'
189
+ | 'mismatch-target'
190
+ | 'throws';
191
+
192
+ function executeReplayCapture(
193
+ replayPassNames: readonly string[],
194
+ failureStage?: ReplayFailureStage,
195
+ captureKind: ReplayCaptureKind = 'target-copy',
196
+ callbackMode?: ReplayCallbackMode,
197
+ prefixCapture?: {
198
+ readonly depthAttachmentPassName: string;
199
+ readonly observeAfterPassName: string;
200
+ readonly passNames?: readonly string[];
201
+ readonly depthPassNames?: readonly string[];
202
+ readonly noEncodePassNames?: readonly string[];
203
+ readonly repeatPassName?: string;
204
+ },
205
+ ) {
206
+ const errors: unknown[] = [];
207
+ const encodedPasses: string[] = [];
208
+ let graphExecutions = 0;
209
+ let submits = 0;
210
+ let commits = 0;
211
+ let copies = 0;
212
+ const targetTexture = {};
213
+ const targetView = {};
214
+ const callbackReceipts: GraphTargetPassReplayReceipt[] = [];
215
+ const prefixReceipts: GraphTargetPassReplayPrefixReceipt[] = [];
216
+ const passNames = prefixCapture?.passNames ?? ['scene', 'shadow-cascade-2', 'output'];
217
+ const depthPassNames = new Set(prefixCapture?.depthPassNames ?? ['shadow-cascade-2']);
218
+ const noEncodePassNames = new Set(prefixCapture?.noEncodePassNames ?? []);
219
+ const graph = {
220
+ inspect: () => ({
221
+ passes: passNames.map((name, executionIndex) => ({ name, executionIndex })),
222
+ }),
223
+ execute: (
224
+ _frame: unknown,
225
+ runPass?: (pass: { name: string; executionIndex: number }, encode: () => void) => void,
226
+ ) => {
227
+ graphExecutions += 1;
228
+ if (failureStage === 'execute' && graphExecutions === 2)
229
+ return err(
230
+ new RhiError({
231
+ code: 'webgpu-runtime-error',
232
+ expected: 'selected graph replay executes',
233
+ hint: 'repair the selected replay pass and retry',
234
+ }),
235
+ );
236
+ for (const [executionIndex, name] of passNames.entries()) {
237
+ const depthView = !depthPassNames.has(name)
238
+ ? undefined
239
+ : callbackMode === 'missing-depth'
240
+ ? undefined
241
+ : callbackMode === 'mismatch-target' || name === 'spot-shadow'
242
+ ? {}
243
+ : targetView;
244
+ const pass = {
245
+ name,
246
+ executionIndex,
247
+ ...(depthView === undefined ? {} : { resolvedDepthStencilAttachmentView: depthView }),
248
+ };
249
+ const encode = () => encodedPasses.push(name);
250
+ const executions = prefixCapture?.repeatPassName === name ? 2 : 1;
251
+ for (let repeat = 0; repeat < executions; repeat += 1) {
252
+ if (runPass === undefined) encode();
253
+ else if (!noEncodePassNames.has(name)) runPass(pass, encode);
254
+ }
255
+ }
256
+ return ok(undefined);
257
+ },
258
+ };
259
+ const frameState = {
260
+ compiledFrameGraph: graph,
261
+ frameNumber: 13,
262
+ graphTargetCapture:
263
+ captureKind === 'pass-replay'
264
+ ? {
265
+ kind: 'pass-replay' as const,
266
+ replayPassNames,
267
+ ...(callbackMode === undefined
268
+ ? {}
269
+ : {
270
+ targetName: 'shadow-target',
271
+ ...(prefixCapture === undefined
272
+ ? {
273
+ onPassEncoded: (receipt: GraphTargetPassReplayReceipt) => {
274
+ callbackReceipts.push(receipt);
275
+ if (callbackMode === 'throws') throw new Error('callback failed');
276
+ },
277
+ }
278
+ : {
279
+ depthAttachmentPassName: prefixCapture.depthAttachmentPassName,
280
+ observeAfterPassName: prefixCapture.observeAfterPassName,
281
+ onPrefixEncoded: (receipt: GraphTargetPassReplayPrefixReceipt) => {
282
+ prefixReceipts.push(receipt);
283
+ if (callbackMode === 'throws') throw new Error('callback failed');
284
+ },
285
+ }),
286
+ }),
287
+ }
288
+ : {
289
+ name: 'standard-output-color',
290
+ replayPassNames,
291
+ buffer: {} as never,
292
+ bytesPerRow: 256,
293
+ width: 4,
294
+ height: 2,
295
+ expected: {
296
+ format: 'rgba16float' as const,
297
+ width: 4,
298
+ height: 2,
299
+ usage: GPU_TEXTURE_USAGE_COPY_SRC,
300
+ },
301
+ },
302
+ perFrameGraph: {
303
+ graphGeneration: 7,
304
+ getColorTargetView: () =>
305
+ callbackMode === 'missing-target' ? undefined : (targetView as never),
306
+ getColorTargetTexture: () =>
307
+ callbackMode === 'missing-target' ? undefined : (targetTexture as never),
308
+ getColorTargetDescriptor: () => ({
309
+ format: 'rgba16float' as const,
310
+ size: { width: 4, height: 2 },
311
+ usage: GPU_TEXTURE_USAGE_COPY_SRC,
312
+ texture: targetTexture,
313
+ }),
314
+ },
315
+ } as never;
316
+ const encoder = {
317
+ copyTextureToBuffer: () => {
318
+ copies += 1;
319
+ },
320
+ finish: () =>
321
+ failureStage === 'finish'
322
+ ? err(
323
+ new RhiError({
324
+ code: 'webgpu-runtime-error',
325
+ expected: 'the selected graph replay command finishes',
326
+ hint: 'repair the command encoder and retry',
327
+ }),
328
+ )
329
+ : ok({} as never),
330
+ } as never;
331
+ const internals = {
332
+ errorRegistry: { fire: (error: unknown) => errors.push(error) },
333
+ device: {
334
+ queue: {
335
+ submit: () => {
336
+ submits += 1;
337
+ return failureStage === 'submit'
338
+ ? err(
339
+ new RhiError({
340
+ code: 'webgpu-runtime-error',
341
+ expected: 'the selected graph replay command submits',
342
+ hint: 'repair the queue submission and retry',
343
+ }),
344
+ )
345
+ : ok(undefined);
346
+ },
347
+ },
348
+ },
349
+ } as never;
350
+ const result = executeCompiledFrameGraph(
351
+ internals,
352
+ frameState,
353
+ {} as never,
354
+ encoder,
355
+ undefined,
356
+ () => {
357
+ commits += 1;
358
+ },
359
+ );
360
+ return {
361
+ result,
362
+ errors,
363
+ encodedPasses,
364
+ callbackReceipts,
365
+ prefixReceipts,
366
+ graphExecutions,
367
+ submits,
368
+ commits,
369
+ copies,
370
+ };
371
+ }
372
+
146
373
  describe('renderer successful-submit transaction', () => {
374
+ it('publishes the renderer-created receipt object only after submit', async () => {
375
+ const rendererHost = await constructRenderer(
376
+ { getContext: () => null },
377
+ { rhi },
378
+ {
379
+ shaderManifestUrl: `data:application/json,${encodeURIComponent(
380
+ JSON.stringify({ schemaVersion: '1.0.0', entries: [] }),
381
+ )}`,
382
+ },
383
+ );
384
+ const renderer = exposeRenderer(rendererHost);
385
+ const events: Array<
386
+ Extract<import('../render-contract').RendererEvent, { kind: 'frame-submitted' }>
387
+ > = [];
388
+ renderer.subscribe((event) => {
389
+ if (event.kind === 'frame-submitted') events.push(event);
390
+ });
391
+ const world = new World();
392
+ const attached = renderer.attach(world);
393
+ expect(attached.ok).toBe(true);
394
+ if (!attached.ok) return;
395
+ const frame = renderer.draw({
396
+ leases: [attached.value],
397
+ camera: { lease: attached.value },
398
+ environment: { lease: attached.value },
399
+ });
400
+ expect(frame.ok).toBe(true);
401
+ if (!frame.ok) return;
402
+ expect(events).toHaveLength(1);
403
+ expect(events[0]?.receipt).toBe(frame.value);
404
+ expect(rendererHost.inspect().observation.resourceStats).toEqual({
405
+ allocationCount: 0,
406
+ liveCount: 0,
407
+ peakLiveCount: 0,
408
+ mapCount: 0,
409
+ readbackCount: 0,
410
+ liveByteLength: 0,
411
+ });
412
+ await frame.value.completed;
413
+ await renderer.dispose();
414
+ });
415
+
416
+ it('fail-closes an all-domain request without an admitted LUT producer', async () => {
417
+ const renderer = await constructRenderer(
418
+ { getContext: () => null },
419
+ { rhi },
420
+ {
421
+ shaderManifestUrl: `data:application/json,${encodeURIComponent(
422
+ JSON.stringify({ schemaVersion: '1.0.0', entries: [] }),
423
+ )}`,
424
+ },
425
+ );
426
+ const world = new World();
427
+ const attached = renderer.attach(world);
428
+ expect(attached.ok).toBe(true);
429
+ if (!attached.ok) return;
430
+ const frame = renderer.draw({
431
+ leases: [attached.value],
432
+ camera: { lease: attached.value },
433
+ environment: { lease: attached.value },
434
+ });
435
+ expect(frame.ok).toBe(true);
436
+ if (!frame.ok || frame.value === undefined) return;
437
+ expect(frame.value.graphGeneration).toBeTypeOf('number');
438
+ expect(frame.value.backendId).toBe('null');
439
+ const forgedGraphReceipt = Object.freeze({
440
+ ...frame.value,
441
+ graphGeneration: (frame.value.graphGeneration ?? 0) + 1,
442
+ });
443
+ const forgedGraph = await renderer.observe(forgedGraphReceipt, { include: ['timings'] });
444
+ expect(forgedGraph.ok).toBe(false);
445
+ if (!forgedGraph.ok) expect(forgedGraph.error.code).toBe('frame-receipt-stale');
446
+ const duplicateDomain = await renderer.observe(frame.value, {
447
+ include: ['linear-hdr', 'linear-hdr'],
448
+ });
449
+ expect(duplicateDomain.ok).toBe(false);
450
+ if (!duplicateDomain.ok) expect(duplicateDomain.error.code).toBe('renderer-contract-failed');
451
+ const domainsAndTimings = await renderer.observe(frame.value, {
452
+ include: ['timings', 'linear-hdr'],
453
+ });
454
+ expect(domainsAndTimings.ok).toBe(false);
455
+ if (!domainsAndTimings.ok)
456
+ expect(domainsAndTimings.error.code).toBe('renderer-contract-failed');
457
+ const observed = await renderer.observe(frame.value, {
458
+ include: ['linear-hdr', 'linear-ldr', 'final-srgb'],
459
+ });
460
+ expect(observed.ok).toBe(false);
461
+ if (!observed.ok) expect(observed.error.code).toBe('renderer-contract-failed');
462
+ const duplicate = await renderer.observe(frame.value, {
463
+ include: ['linear-hdr', 'linear-ldr', 'final-srgb'],
464
+ });
465
+ expect(duplicate.ok).toBe(false);
466
+ if (!duplicate.ok) expect(duplicate.error.code).toBe('renderer-contract-failed');
467
+ await renderer.dispose();
468
+ const stale = await renderer.observe(frame.value, { include: ['linear-hdr'] });
469
+ expect(stale.ok).toBe(false);
470
+ if (!stale.ok) expect(stale.error.code).toBe('frame-receipt-stale');
471
+ });
472
+
473
+ it('rejects a receipt issued by another Renderer owner', async () => {
474
+ const options = {
475
+ shaderManifestUrl: `data:application/json,${encodeURIComponent(
476
+ JSON.stringify({ schemaVersion: '1.0.0', entries: [] }),
477
+ )}`,
478
+ };
479
+ const firstRenderer = await constructRenderer({ getContext: () => null }, { rhi }, options);
480
+ const secondRenderer = await constructRenderer({ getContext: () => null }, { rhi }, options);
481
+ const world = new World();
482
+ const attached = firstRenderer.attach(world);
483
+ expect(attached.ok).toBe(true);
484
+ if (!attached.ok) return;
485
+ const frame = firstRenderer.draw({
486
+ leases: [attached.value],
487
+ camera: { lease: attached.value },
488
+ environment: { lease: attached.value },
489
+ });
490
+ expect(frame.ok).toBe(true);
491
+ if (!frame.ok || frame.value === undefined) return;
492
+ expect(frame.value.backendId).toBe('null');
493
+ const crossRenderer = await secondRenderer.observe(frame.value, { include: ['timings'] });
494
+ expect(crossRenderer.ok).toBe(false);
495
+ if (!crossRenderer.ok) expect(crossRenderer.error.code).toBe('frame-receipt-stale');
496
+ await firstRenderer.dispose();
497
+ await secondRenderer.dispose();
498
+ });
499
+
500
+ it.each([
501
+ 'result',
502
+ 'throw',
503
+ ] as const)('retries a failed buffer destroy without duplicate ownership or errors (%s)', async (failureMode) => {
504
+ const device = (await new RhiNullAdapter().requestDevice()).unwrap();
505
+ const buffer = device
506
+ .createBuffer({ label: 'observation-retry', size: 256, usage: 0, mappedAtCreation: false })
507
+ .unwrap();
508
+ const texture = device
509
+ .createTexture({
510
+ label: 'observation-retry-source',
511
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
512
+ format: 'rgba8unorm',
513
+ usage: 0,
514
+ textureBindingViewDimension: undefined,
515
+ })
516
+ .unwrap();
517
+ const capture = {
518
+ domain: 'linear-hdr' as const,
519
+ format: 'rgba8unorm' as const,
520
+ device,
521
+ texture,
522
+ buffer,
523
+ frameNumber: 7,
524
+ deviceGeneration: 3,
525
+ graphGeneration: 11,
526
+ backendId: 'null',
527
+ width: 1,
528
+ height: 1,
529
+ bytesPerRow: 256,
530
+ };
531
+ const destroyed = new WeakSet<Buffer>();
532
+ const failures = new WeakMap<Buffer, RendererContractFailureError>();
533
+ const errors: RendererContractFailureError[] = [];
534
+ const destroyError = new RhiError({
535
+ code: 'webgpu-runtime-error',
536
+ expected: 'receipt-owned observation buffer destruction succeeds',
537
+ hint: 'retry against the original device',
538
+ });
539
+ let attempts = 0;
540
+ const originalDestroy = device.destroyBuffer.bind(device);
541
+ const destroyBuffer = vi.spyOn(device, 'destroyBuffer').mockImplementation((handle) => {
542
+ attempts += 1;
543
+ if (attempts === 1) {
544
+ if (failureMode === 'throw') throw destroyError;
545
+ return err(destroyError);
546
+ }
547
+ return originalDestroy(handle);
548
+ });
549
+ try {
550
+ const first = disposeObservationCaptureSet([capture], destroyed, failures, (failure) =>
551
+ errors.push(failure),
552
+ );
553
+ expect(first?.code).toBe('renderer-contract-failed');
554
+ expect(destroyed.has(buffer)).toBe(false);
555
+ expect(errors).toHaveLength(1);
556
+ const second = disposeObservationCaptureSet([capture], destroyed, failures, (failure) =>
557
+ errors.push(failure),
558
+ );
559
+ expect(second).toBeUndefined();
560
+ expect(destroyed.has(buffer)).toBe(true);
561
+ expect(attempts).toBe(2);
562
+ expect(errors).toHaveLength(1);
563
+ expect(disposeObservationCaptureSet([capture], destroyed, failures, () => undefined)).toBe(
564
+ undefined,
565
+ );
566
+ expect(attempts).toBe(2);
567
+ } finally {
568
+ destroyBuffer.mockRestore();
569
+ device.destroyTexture(texture);
570
+ }
571
+ });
572
+
573
+ it('keeps real Renderer observation requests isolated across receipts', async () => {
574
+ const renderer = await constructRenderer(
575
+ { getContext: () => null },
576
+ { rhi },
577
+ {
578
+ shaderManifestUrl: `data:application/json,${encodeURIComponent(
579
+ JSON.stringify({ schemaVersion: '1.0.0', entries: [] }),
580
+ )}`,
581
+ },
582
+ );
583
+ const world = new World();
584
+ const attached = renderer.attach(world);
585
+ expect(attached.ok).toBe(true);
586
+ if (!attached.ok) return;
587
+ const first = renderer.draw({
588
+ leases: [attached.value],
589
+ camera: { lease: attached.value },
590
+ environment: { lease: attached.value },
591
+ });
592
+ const second = renderer.draw({
593
+ leases: [attached.value],
594
+ camera: { lease: attached.value },
595
+ environment: { lease: attached.value },
596
+ });
597
+ expect(first.ok).toBe(true);
598
+ expect(second.ok).toBe(true);
599
+ if (!first.ok || !second.ok || first.value === undefined || second.value === undefined) return;
600
+ const firstPending = renderer.observe(first.value, {
601
+ include: ['timings', 'linear-hdr', 'linear-ldr', 'final-srgb'],
602
+ });
603
+ const secondResult = await renderer.observe(second.value, {
604
+ include: ['linear-hdr', 'linear-ldr', 'final-srgb'],
605
+ });
606
+ const firstResult = await firstPending;
607
+ expect(firstResult.ok).toBe(false);
608
+ expect(secondResult.ok).toBe(false);
609
+ if (!firstResult.ok) expect(firstResult.error.code).toBe('renderer-contract-failed');
610
+ if (!secondResult.ok) expect(secondResult.error.code).toBe('renderer-contract-failed');
611
+ const duplicate = await renderer.observe(first.value, { include: ['linear-hdr'] });
612
+ expect(duplicate.ok).toBe(false);
613
+ if (!duplicate.ok) expect(duplicate.error.code).toBe('renderer-contract-failed');
614
+ await renderer.dispose();
615
+ const stale = await renderer.observe(second.value, { include: ['linear-hdr'] });
616
+ expect(stale.ok).toBe(false);
617
+ if (!stale.ok) expect(stale.error.code).toBe('frame-receipt-stale');
618
+ });
619
+
620
+ it('keeps failed retries attached to their original buffer and frame', async () => {
621
+ const device = (await new RhiNullAdapter().requestDevice()).unwrap();
622
+ const buffer = device
623
+ .createBuffer({ label: 'observation-retry-a', size: 256, usage: 0, mappedAtCreation: false })
624
+ .unwrap();
625
+ const bufferB = device
626
+ .createBuffer({ label: 'observation-retry-b', size: 256, usage: 0, mappedAtCreation: false })
627
+ .unwrap();
628
+ const texture = device
629
+ .createTexture({
630
+ label: 'observation-retry-source-a',
631
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
632
+ format: 'rgba8unorm',
633
+ usage: 0,
634
+ textureBindingViewDimension: undefined,
635
+ })
636
+ .unwrap();
637
+ const textureB = device
638
+ .createTexture({
639
+ label: 'observation-retry-source-b',
640
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
641
+ format: 'rgba8unorm',
642
+ usage: 0,
643
+ textureBindingViewDimension: undefined,
644
+ })
645
+ .unwrap();
646
+ const capture = {
647
+ domain: 'linear-hdr' as const,
648
+ format: 'rgba8unorm' as const,
649
+ device,
650
+ texture,
651
+ buffer,
652
+ frameNumber: 7,
653
+ deviceGeneration: 3,
654
+ graphGeneration: 11,
655
+ backendId: 'null',
656
+ width: 1,
657
+ height: 1,
658
+ bytesPerRow: 256,
659
+ };
660
+ const captureB = {
661
+ ...capture,
662
+ domain: 'linear-ldr' as const,
663
+ texture: textureB,
664
+ buffer: bufferB,
665
+ frameNumber: 8,
666
+ };
667
+ const destroyed = new WeakSet<Buffer>();
668
+ const failures = new WeakMap<Buffer, RendererContractFailureError>();
669
+ const errors: RendererContractFailureError[] = [];
670
+ const destroyError = new RhiError({
671
+ code: 'webgpu-runtime-error',
672
+ expected: 'receipt-owned observation buffer destruction succeeds',
673
+ hint: 'retry against the original device',
674
+ });
675
+ const attempts = new Map<Buffer, number>();
676
+ const originalDestroy = device.destroyBuffer.bind(device);
677
+ const destroyBuffer = vi.spyOn(device, 'destroyBuffer').mockImplementation((handle) => {
678
+ const count = (attempts.get(handle) ?? 0) + 1;
679
+ attempts.set(handle, count);
680
+ if (handle === buffer && count === 1) return err(destroyError);
681
+ return originalDestroy(handle);
682
+ });
683
+ try {
684
+ expect(
685
+ disposeObservationCaptureSet([capture, captureB], destroyed, failures, (failure) =>
686
+ errors.push(failure),
687
+ ),
688
+ ).toBeDefined();
689
+ expect(destroyed.has(buffer)).toBe(false);
690
+ expect(destroyed.has(bufferB)).toBe(true);
691
+ expect(errors).toHaveLength(1);
692
+ expect(
693
+ disposeObservationCaptureSet([capture], destroyed, failures, (failure) =>
694
+ errors.push(failure),
695
+ ),
696
+ ).toBeUndefined();
697
+ expect(destroyed.has(buffer)).toBe(true);
698
+ expect(attempts.get(buffer)).toBe(2);
699
+ expect(attempts.get(bufferB)).toBe(1);
700
+ expect(errors).toHaveLength(1);
701
+ expect(disposeObservationCaptureSet([captureB], destroyed, failures, () => undefined)).toBe(
702
+ undefined,
703
+ );
704
+ expect(attempts.get(bufferB)).toBe(1);
705
+ } finally {
706
+ destroyBuffer.mockRestore();
707
+ device.destroyTexture(texture);
708
+ device.destroyTexture(textureB);
709
+ }
710
+ });
711
+
712
+ it('replays selected passes without target-copy payload', () => {
713
+ const replay = executeReplayCapture(['shadow-cascade-2'], undefined, 'pass-replay');
714
+
715
+ expect(replay.result).toBe(true);
716
+ expect(replay.graphExecutions).toBe(2);
717
+ expect(replay.encodedPasses).toEqual([
718
+ 'scene',
719
+ 'shadow-cascade-2',
720
+ 'output',
721
+ 'shadow-cascade-2',
722
+ ]);
723
+ expect(replay.errors).toEqual([]);
724
+ expect(replay.submits).toBe(1);
725
+ expect(replay.commits).toBe(1);
726
+ expect(replay.copies).toBe(0);
727
+ });
728
+
729
+ it('reports the resolved depth and target identities after selected pass encoding', () => {
730
+ const replay = executeReplayCapture(['shadow-cascade-2'], undefined, 'pass-replay', 'valid');
731
+
732
+ expect(replay.result).toBe(true);
733
+ expect(replay.callbackReceipts).toHaveLength(1);
734
+ const receipt = replay.callbackReceipts[0];
735
+ expect(receipt).toMatchObject({
736
+ passName: 'shadow-cascade-2',
737
+ executionIndex: 1,
738
+ graphGeneration: 7,
739
+ targetName: 'shadow-target',
740
+ });
741
+ expect(receipt?.resolvedDepthStencilAttachmentView).toBe(receipt?.targetView);
742
+ expect(receipt?.targetTextureIdentity).toBeGreaterThan(0);
743
+ expect(replay.submits).toBe(1);
744
+ expect(replay.commits).toBe(1);
745
+ });
746
+
747
+ it('observes a complete prefix once after a no-depth boundary pass', () => {
748
+ const replay = executeReplayCapture(
749
+ ['scene', 'shadow-cascade-2', 'output'],
750
+ undefined,
751
+ 'pass-replay',
752
+ 'valid',
753
+ {
754
+ depthAttachmentPassName: 'shadow-cascade-2',
755
+ observeAfterPassName: 'output',
756
+ },
757
+ );
758
+
759
+ expect(replay.result).toBe(true);
760
+ expect(replay.encodedPasses).toEqual([
761
+ 'scene',
762
+ 'shadow-cascade-2',
763
+ 'output',
764
+ 'scene',
765
+ 'shadow-cascade-2',
766
+ 'output',
767
+ ]);
768
+ expect(replay.callbackReceipts).toEqual([]);
769
+ expect(replay.prefixReceipts).toHaveLength(1);
770
+ expect(replay.prefixReceipts[0]).toMatchObject({
771
+ observeAfterPassName: 'output',
772
+ depthAttachmentPassName: 'shadow-cascade-2',
773
+ passName: 'output',
774
+ executionIndex: 2,
775
+ });
776
+ expect(replay.prefixReceipts[0]?.resolvedDepthStencilAttachmentView).toBe(
777
+ replay.prefixReceipts[0]?.targetView,
778
+ );
779
+ expect(replay.submits).toBe(1);
780
+ expect(replay.commits).toBe(1);
781
+ });
782
+
783
+ it.each([
784
+ ['non-prefix', ['shadow-cascade-2', 'output'] as const, 'shadow-cascade-2', 'output'],
785
+ ['anchor-after-boundary', ['scene', 'output'] as const, 'shadow-cascade-2', 'output'],
786
+ [
787
+ 'boundary-not-last',
788
+ ['scene', 'shadow-cascade-2', 'output'] as const,
789
+ 'shadow-cascade-2',
790
+ 'shadow-cascade-2',
791
+ ],
792
+ ] as const)('rejects invalid prefix observer selection: %s', (_label, replayPassNames, depthAttachmentPassName, observeAfterPassName) => {
793
+ const replay = executeReplayCapture(replayPassNames, undefined, 'pass-replay', 'valid', {
794
+ depthAttachmentPassName,
795
+ observeAfterPassName,
796
+ });
797
+
798
+ expect(replay.result).toBe(false);
799
+ expect(replay.graphExecutions).toBe(0);
800
+ expect(replay.submits).toBe(0);
801
+ expect(replay.commits).toBe(0);
802
+ expect(replay.prefixReceipts).toEqual([]);
803
+ expect(replay.errors[0]).toMatchObject({
804
+ code: 'webgpu-runtime-error',
805
+ detail: { error: { code: 'graph-target-capture-failed' } },
806
+ });
807
+ });
808
+
809
+ it('rejects a no-depth anchor and does not submit the replay', () => {
810
+ const replay = executeReplayCapture(['scene', 'output'], undefined, 'pass-replay', 'valid', {
811
+ depthAttachmentPassName: 'output',
812
+ observeAfterPassName: 'output',
813
+ });
814
+
815
+ expect(replay.result).toBe(false);
816
+ expect(replay.submits).toBe(0);
817
+ expect(replay.commits).toBe(0);
818
+ expect(replay.prefixReceipts).toEqual([]);
819
+ expect(replay.errors[0]).toMatchObject({
820
+ detail: {
821
+ error: {
822
+ code: 'graph-target-capture-failed',
823
+ },
824
+ },
825
+ });
826
+ });
827
+
828
+ it('does not submit when the prefix callback throws', () => {
829
+ const replay = executeReplayCapture(
830
+ ['scene', 'shadow-cascade-2', 'output'],
831
+ undefined,
832
+ 'pass-replay',
833
+ 'throws',
834
+ { depthAttachmentPassName: 'shadow-cascade-2', observeAfterPassName: 'output' },
835
+ );
836
+
837
+ expect(replay.result).toBe(false);
838
+ expect(replay.submits).toBe(0);
839
+ expect(replay.commits).toBe(0);
840
+ expect(replay.prefixReceipts).toHaveLength(1);
841
+ expect(replay.errors[0]).toMatchObject({
842
+ detail: {
843
+ error: {
844
+ code: 'graph-target-capture-failed',
845
+ },
846
+ },
847
+ });
848
+ });
849
+
850
+ it('accepts multiple depth passes and a no-depth boundary with one callback', () => {
851
+ const replay = executeReplayCapture(
852
+ ['shadow-cascade-0', 'shadow-cascade-1', 'spot-shadow', 'skybox'],
853
+ undefined,
854
+ 'pass-replay',
855
+ 'valid',
856
+ {
857
+ depthAttachmentPassName: 'shadow-cascade-1',
858
+ observeAfterPassName: 'skybox',
859
+ passNames: ['shadow-cascade-0', 'shadow-cascade-1', 'spot-shadow', 'skybox'],
860
+ depthPassNames: ['shadow-cascade-0', 'shadow-cascade-1', 'spot-shadow'],
861
+ },
862
+ );
863
+
864
+ expect(replay.result).toBe(true);
865
+ expect(replay.prefixReceipts).toHaveLength(1);
866
+ expect(replay.prefixReceipts[0]).toMatchObject({
867
+ passName: 'skybox',
868
+ depthAttachmentPassName: 'shadow-cascade-1',
869
+ observeAfterPassName: 'skybox',
870
+ });
871
+ expect(replay.submits).toBe(1);
872
+ expect(replay.commits).toBe(1);
873
+ });
874
+
875
+ it('rejects a spot anchor when the target is a directional depth view', () => {
876
+ const replay = executeReplayCapture(
877
+ ['shadow-cascade-0', 'spot-shadow', 'skybox'],
878
+ undefined,
879
+ 'pass-replay',
880
+ 'valid',
881
+ {
882
+ depthAttachmentPassName: 'spot-shadow',
883
+ observeAfterPassName: 'skybox',
884
+ passNames: ['shadow-cascade-0', 'spot-shadow', 'skybox'],
885
+ depthPassNames: ['shadow-cascade-0', 'spot-shadow'],
886
+ },
887
+ );
888
+
889
+ expect(replay.result).toBe(false);
890
+ expect(replay.submits).toBe(0);
891
+ expect(replay.commits).toBe(0);
892
+ expect(replay.errors[0]).toMatchObject({
893
+ detail: { error: { code: 'graph-target-capture-failed' } },
894
+ });
895
+ });
896
+
897
+ it('rejects an unexecuted or repeated anchor before submit', () => {
898
+ const unexecuted = executeReplayCapture(
899
+ ['scene', 'shadow-cascade-2', 'output'],
900
+ undefined,
901
+ 'pass-replay',
902
+ 'valid',
903
+ {
904
+ depthAttachmentPassName: 'shadow-cascade-2',
905
+ observeAfterPassName: 'output',
906
+ noEncodePassNames: ['shadow-cascade-2'],
907
+ },
908
+ );
909
+ expect(unexecuted.result).toBe(false);
910
+ expect(unexecuted.submits).toBe(0);
911
+ expect(unexecuted.commits).toBe(0);
912
+
913
+ const repeated = executeReplayCapture(
914
+ ['scene', 'shadow-cascade-2', 'output'],
915
+ undefined,
916
+ 'pass-replay',
917
+ 'valid',
918
+ {
919
+ depthAttachmentPassName: 'shadow-cascade-2',
920
+ observeAfterPassName: 'output',
921
+ repeatPassName: 'shadow-cascade-2',
922
+ },
923
+ );
924
+ expect(repeated.result).toBe(false);
925
+ expect(repeated.submits).toBe(0);
926
+ expect(repeated.commits).toBe(0);
927
+ expect(repeated.errors[0]).toMatchObject({
928
+ detail: { error: { code: 'graph-target-capture-failed' } },
929
+ });
930
+ });
931
+
932
+ it.each<ReplayCallbackMode>([
933
+ 'missing-target',
934
+ 'missing-depth',
935
+ 'mismatch-target',
936
+ 'throws',
937
+ ])('fails pass-replay callback validation for %s before submit', (callbackMode) => {
938
+ const replay = executeReplayCapture(
939
+ ['shadow-cascade-2'],
940
+ undefined,
941
+ 'pass-replay',
942
+ callbackMode,
943
+ );
944
+
945
+ expect(replay.result).toBe(false);
946
+ expect(replay.submits).toBe(0);
947
+ expect(replay.commits).toBe(0);
948
+ expect(replay.errors[0]).toMatchObject({
949
+ code: 'webgpu-runtime-error',
950
+ detail: { error: { code: 'graph-target-capture-failed' } },
951
+ });
952
+ });
953
+
954
+ it.each([
955
+ ['unknown', ['missing-pass']],
956
+ ['duplicate', ['shadow-cascade-2', 'shadow-cascade-2']],
957
+ ['empty', []],
958
+ ] as const)('rejects %s pass-replay selection without target-copy fields', (_reason, replayPassNames) => {
959
+ const replay = executeReplayCapture(replayPassNames, undefined, 'pass-replay');
960
+
961
+ expect(replay.result).toBe(false);
962
+ expect(replay.graphExecutions).toBe(0);
963
+ expect(replay.encodedPasses).toEqual([]);
964
+ expect(replay.submits).toBe(0);
965
+ expect(replay.commits).toBe(0);
966
+ expect(replay.copies).toBe(0);
967
+ expect(replay.errors[0]).toMatchObject({
968
+ code: 'webgpu-runtime-error',
969
+ detail: { error: { code: 'graph-target-capture-failed' } },
970
+ });
971
+ });
972
+
973
+ it('replays only the requested graph passes on the same submitted command', () => {
974
+ const replay = executeReplayCapture(['shadow-cascade-2']);
975
+
976
+ expect(replay.result).toBe(true);
977
+ expect(replay.graphExecutions).toBe(2);
978
+ expect(replay.encodedPasses).toEqual([
979
+ 'scene',
980
+ 'shadow-cascade-2',
981
+ 'output',
982
+ 'shadow-cascade-2',
983
+ ]);
984
+ expect(replay.errors).toEqual([]);
985
+ expect(replay.submits).toBe(1);
986
+ expect(replay.commits).toBe(1);
987
+ });
988
+
989
+ it.each<ReplayFailureStage>([
990
+ 'execute',
991
+ 'finish',
992
+ 'submit',
993
+ ])('does not commit when replay transaction fails at %s', (failureStage) => {
994
+ const replay = executeReplayCapture(['shadow-cascade-2'], failureStage);
995
+
996
+ expect(replay.result).toBe(false);
997
+ expect(replay.commits).toBe(0);
998
+ expect(replay.submits).toBe(failureStage === 'submit' ? 1 : 0);
999
+ });
1000
+
1001
+ it.each([
1002
+ ['unknown', ['missing-pass']],
1003
+ ['duplicate', ['shadow-cascade-2', 'shadow-cascade-2']],
1004
+ ['empty', []],
1005
+ ] as const)('rejects %s graph replay selection before submit', (_reason, replayPassNames) => {
1006
+ const replay = executeReplayCapture(replayPassNames);
1007
+
1008
+ expect(replay.result).toBe(false);
1009
+ expect(replay.graphExecutions).toBe(0);
1010
+ expect(replay.encodedPasses).toEqual([]);
1011
+ expect(replay.submits).toBe(0);
1012
+ expect(replay.commits).toBe(0);
1013
+ expect(replay.errors[0]).toMatchObject({
1014
+ code: 'webgpu-runtime-error',
1015
+ detail: { error: { code: 'graph-target-capture-failed' } },
1016
+ });
1017
+ });
1018
+
147
1019
  it.each<Stage>([
148
1020
  'build',
149
1021
  'execute',
@@ -326,6 +1198,7 @@ describe('renderer successful-submit transaction', () => {
326
1198
  if (failureAt === 'bind-group') createBindGroup.mockReturnValue(err(failure));
327
1199
  const errors = new RhiErrorListenerRegistry();
328
1200
  const observed: unknown[] = [];
1201
+ const gpuCommitAfterSubmit: number[] = [];
329
1202
  errors.add((error) => observed.push(error));
330
1203
  const submits = vi.spyOn(device.queue, 'submit');
331
1204
  const failedFrame = temporalResolveFrame(
@@ -339,9 +1212,12 @@ describe('renderer successful-submit transaction', () => {
339
1212
  frameState,
340
1213
  failedFrame,
341
1214
  failedFrame.encoder,
1215
+ undefined,
1216
+ () => gpuCommitAfterSubmit.push(submits.mock.calls.length),
342
1217
  );
343
1218
  expect(failed).toBe(false);
344
1219
  expect(submits).not.toHaveBeenCalled();
1220
+ expect(gpuCommitAfterSubmit).toEqual([]);
345
1221
  expect(observed[0]).toMatchObject({ code: 'pass-encode-failed' });
346
1222
  const cause = (observed[0] as { detail?: { cause?: unknown } }).detail?.cause;
347
1223
  expect(cause).toBeInstanceOf(RhiError);
@@ -389,9 +1265,12 @@ describe('renderer successful-submit transaction', () => {
389
1265
  frameState,
390
1266
  retryFrame,
391
1267
  retryFrame.encoder,
1268
+ undefined,
1269
+ () => gpuCommitAfterSubmit.push(submits.mock.calls.length),
392
1270
  );
393
1271
  expect(retried).toBe(true);
394
1272
  expect(submits).toHaveBeenCalledTimes(1);
1273
+ expect(gpuCommitAfterSubmit).toEqual([1]);
395
1274
  expect(frameState.activeTemporalGpuState).not.toBe(active);
396
1275
  expect(frameState.successfulTemporalFrameIndex).toBe(8);
397
1276
  expect(frameState.temporalGpuState).toBeUndefined();
@@ -403,3 +1282,42 @@ describe('renderer successful-submit transaction', () => {
403
1282
  scope.dispose();
404
1283
  });
405
1284
  });
1285
+
1286
+ import { executeAutoExposureFrameTransaction } from '../assembly/renderer-frame-transaction';
1287
+ import { createAutoExposureState } from '../pipeline/standard-output/auto-exposure/state';
1288
+
1289
+ describe('renderer frame transaction ownership', () => {
1290
+ it('does not publish a LUT generation when submit fails', () => {
1291
+ let published = 0;
1292
+ const result = executeRendererFrameTransaction({
1293
+ build: () => ({ ok: true, value: { generation: 4, resident: 'lut:candidate' } }),
1294
+ execute: () => ({ ok: true, value: undefined }),
1295
+ finish: () => ({ ok: true, value: undefined }),
1296
+ submit: () => ({ ok: false, stage: 'submit' as const }),
1297
+ commit: () => {
1298
+ published += 1;
1299
+ },
1300
+ });
1301
+ expect(result).toEqual({ ok: false, error: { stage: 'submit' } });
1302
+ expect(published).toBe(0);
1303
+ });
1304
+
1305
+ it('does not advance state when recording fails after graph construction', () => {
1306
+ const initial = createAutoExposureState({
1307
+ fallback: 1,
1308
+ targetGeneration: 7,
1309
+ deviceEpoch: 3,
1310
+ });
1311
+ if (!initial.ok) throw initial.error;
1312
+ const result = executeAutoExposureFrameTransaction(
1313
+ initial.value,
1314
+ { ev: 0.75, generation: 7, deviceEpoch: 3, frameId: 11 },
1315
+ { failAt: 'execute' },
1316
+ );
1317
+
1318
+ expect(result.ok).toBe(false);
1319
+ expect(result.state).toBe(initial.value);
1320
+ expect(result.state.targetGeneration).toBe(7);
1321
+ expect(result.state.receipt.committed).toBe(false);
1322
+ });
1323
+ });