@forgeax/engine-render 0.1.28 → 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 (642) hide show
  1. package/README.md +627 -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 +21 -0
  5. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  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 +8 -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 +2 -2
  60. package/dist/{chunk-JAASCMXA.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-J52NMQKR.mjs → chunk-BQVQOQJH.mjs} +1086 -838
  65. package/dist/chunk-BQVQOQJH.mjs.map +1 -0
  66. package/dist/{chunk-IQMKJLNU.mjs → chunk-BTWH7BP4.mjs} +386 -17
  67. package/dist/chunk-BTWH7BP4.mjs.map +1 -0
  68. package/dist/{chunk-MBU4ZVE7.mjs → chunk-FUODTTIA.mjs} +3921 -671
  69. package/dist/chunk-FUODTTIA.mjs.map +1 -0
  70. package/dist/{chunk-MYJ5RPJO.mjs → chunk-JQTSEI3Z.mjs} +3 -3
  71. package/dist/{chunk-MYJ5RPJO.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
  72. package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
  73. package/dist/chunk-NICXSHAT.mjs.map +1 -0
  74. package/dist/{chunk-J6KACNOH.mjs → chunk-NRNXBLIJ.mjs} +577 -7
  75. package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
  76. package/dist/{chunk-RBDBZ5R7.mjs → chunk-PA5DRJNI.mjs} +2 -2
  77. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  78. package/dist/{chunk-VSL23LZE.mjs → chunk-S4DNKQYQ.mjs} +1117 -445
  79. package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
  80. package/dist/components/camera.d.ts +62 -2
  81. package/dist/components/camera.d.ts.map +1 -1
  82. package/dist/components/index.d.ts +1 -0
  83. package/dist/components/index.d.ts.map +1 -1
  84. package/dist/components/instances.d.ts +6 -20
  85. package/dist/components/instances.d.ts.map +1 -1
  86. package/dist/components/scene-instance.d.ts +2 -1
  87. package/dist/components/scene-instance.d.ts.map +1 -1
  88. package/dist/components/screen-space-reflection.d.ts +14 -0
  89. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  90. package/dist/construct-renderer.mjs +12888 -7025
  91. package/dist/construct-renderer.mjs.map +1 -1
  92. package/dist/device/gpu-residency.d.ts +1 -12
  93. package/dist/device/gpu-residency.d.ts.map +1 -1
  94. package/dist/environment/background.d.ts +6 -0
  95. package/dist/environment/background.d.ts.map +1 -0
  96. package/dist/errors/gpu-driven.d.ts +6 -3
  97. package/dist/errors/gpu-driven.d.ts.map +1 -1
  98. package/dist/errors/render.d.ts +7 -4
  99. package/dist/errors/render.d.ts.map +1 -1
  100. package/dist/extract/camera.d.ts.map +1 -1
  101. package/dist/extract/gpu-driven.d.ts +33 -3
  102. package/dist/extract/gpu-driven.d.ts.map +1 -1
  103. package/dist/features/host.d.ts +23 -1
  104. package/dist/features/host.d.ts.map +1 -1
  105. package/dist/features/noise-texture.d.ts +17 -0
  106. package/dist/features/noise-texture.d.ts.map +1 -0
  107. package/dist/features/plan.d.ts +83 -2
  108. package/dist/features/plan.d.ts.map +1 -1
  109. package/dist/features/prepared-gpu-work.d.ts +37 -4
  110. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  111. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  112. package/dist/features/prepared-graphics.d.ts +5 -0
  113. package/dist/features/prepared-graphics.d.ts.map +1 -1
  114. package/dist/features/render-graph-compute.d.ts +5 -3
  115. package/dist/features/render-graph-compute.d.ts.map +1 -1
  116. package/dist/features/render-graph-contribution.d.ts +11 -3
  117. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  118. package/dist/features/render-graph-raster.d.ts +19 -3
  119. package/dist/features/render-graph-raster.d.ts.map +1 -1
  120. package/dist/features/types.d.ts +11 -0
  121. package/dist/features/types.d.ts.map +1 -1
  122. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  123. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  124. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  125. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  126. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  127. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  128. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  129. package/dist/gpu-driven/production-raster.d.ts +144 -23
  130. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  131. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  132. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  133. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  134. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  135. package/dist/gpu-scene-schema.d.ts +2 -8
  136. package/dist/gpu-scene-schema.d.ts.map +1 -1
  137. package/dist/gpu-scene.d.ts +14 -0
  138. package/dist/gpu-scene.d.ts.map +1 -1
  139. package/dist/gpu-texture-usage.d.ts +1 -0
  140. package/dist/gpu-texture-usage.d.ts.map +1 -1
  141. package/dist/hdrp-buffers.d.ts +6 -0
  142. package/dist/hdrp-buffers.d.ts.map +1 -1
  143. package/dist/ibl/frame-resources.d.ts +8 -0
  144. package/dist/ibl/frame-resources.d.ts.map +1 -0
  145. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  146. package/dist/index.d.ts +18 -8
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.mjs +8 -8
  149. package/dist/inspection-types.d.ts +164 -0
  150. package/dist/inspection-types.d.ts.map +1 -1
  151. package/dist/instances-derived-bounds.d.ts +11 -0
  152. package/dist/instances-derived-bounds.d.ts.map +1 -1
  153. package/dist/instances.d.ts +89 -44
  154. package/dist/instances.d.ts.map +1 -1
  155. package/dist/internal.d.ts +3 -0
  156. package/dist/internal.d.ts.map +1 -1
  157. package/dist/internal.mjs +117 -9
  158. package/dist/internal.mjs.map +1 -1
  159. package/dist/lifecycle.d.ts +2 -1
  160. package/dist/lifecycle.d.ts.map +1 -1
  161. package/dist/material-row.d.ts +5 -0
  162. package/dist/material-row.d.ts.map +1 -0
  163. package/dist/materials.d.ts +15 -0
  164. package/dist/materials.d.ts.map +1 -1
  165. package/dist/pbr-pipeline.d.ts +21 -5
  166. package/dist/pbr-pipeline.d.ts.map +1 -1
  167. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  168. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  169. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  170. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  171. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  172. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  173. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  174. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  175. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  176. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  177. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  178. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  179. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  180. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  181. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  182. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  183. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  184. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  185. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  186. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  187. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  188. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  189. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  190. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  191. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  192. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  193. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  194. package/dist/pipeline/standard-output/types.d.ts +39 -0
  195. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  196. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  197. package/dist/pipeline/standard-post.d.ts +2 -2
  198. package/dist/pipeline/standard-post.d.ts.map +1 -1
  199. package/dist/pipeline-builder.d.ts +2 -1
  200. package/dist/pipeline-builder.d.ts.map +1 -1
  201. package/dist/pipeline-spec-types.d.ts +4 -0
  202. package/dist/pipeline-spec-types.d.ts.map +1 -1
  203. package/dist/pipeline-spec.d.ts +1 -9
  204. package/dist/pipeline-spec.d.ts.map +1 -1
  205. package/dist/points-lines/standard-owner.d.ts +1 -1
  206. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  207. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  208. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  209. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  210. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  211. package/dist/record/frame-lighting.d.ts +3 -3
  212. package/dist/record/frame-lighting.d.ts.map +1 -1
  213. package/dist/record/frame-snapshot.d.ts +106 -5
  214. package/dist/record/frame-snapshot.d.ts.map +1 -1
  215. package/dist/record/frame-targets.d.ts.map +1 -1
  216. package/dist/record/frame.d.ts +39 -4
  217. package/dist/record/frame.d.ts.map +1 -1
  218. package/dist/record/main-pass-geometry.d.ts +7 -8
  219. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  220. package/dist/record/main-pass-material.d.ts +38 -1
  221. package/dist/record/main-pass-material.d.ts.map +1 -1
  222. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  223. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  224. package/dist/record/main-pass.d.ts +12 -2
  225. package/dist/record/main-pass.d.ts.map +1 -1
  226. package/dist/record/mesh-ssbo.d.ts +15 -6
  227. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  228. package/dist/record/prepared-material-bindings.d.ts +19 -0
  229. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  230. package/dist/record/recovery-pipeline.d.ts +2 -9
  231. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  232. package/dist/record/render-context.d.ts +74 -26
  233. package/dist/record/render-context.d.ts.map +1 -1
  234. package/dist/record/shadow-pass.d.ts +62 -7
  235. package/dist/record/shadow-pass.d.ts.map +1 -1
  236. package/dist/record/skybox-post-pass.d.ts +1 -1
  237. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  238. package/dist/record/typed-frame-graph.d.ts +35 -7
  239. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  240. package/dist/record/view-ubo.d.ts +1 -0
  241. package/dist/record/view-ubo.d.ts.map +1 -1
  242. package/dist/recovery/render-system-candidate.d.ts +10 -2
  243. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  244. package/dist/recovery/render-system-roots.d.ts +11 -0
  245. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  246. package/dist/recovery/types.d.ts +76 -0
  247. package/dist/recovery/types.d.ts.map +1 -0
  248. package/dist/reflection/record-owner.d.ts +0 -2
  249. package/dist/reflection/record-owner.d.ts.map +1 -1
  250. package/dist/render-contract.d.ts +173 -30
  251. package/dist/render-contract.d.ts.map +1 -1
  252. package/dist/render-graph-primitives.d.ts +1 -0
  253. package/dist/render-graph-primitives.d.ts.map +1 -1
  254. package/dist/render-pipeline.d.ts +64 -5
  255. package/dist/render-pipeline.d.ts.map +1 -1
  256. package/dist/render-system-extract-tail.d.ts +7 -5
  257. package/dist/render-system-extract-tail.d.ts.map +1 -1
  258. package/dist/render-system-extract.d.ts +101 -41
  259. package/dist/render-system-extract.d.ts.map +1 -1
  260. package/dist/render-system-projections.d.ts +22 -0
  261. package/dist/render-system-projections.d.ts.map +1 -0
  262. package/dist/render-system.d.ts +27 -77
  263. package/dist/render-system.d.ts.map +1 -1
  264. package/dist/scene/render-scene-types.d.ts +14 -7
  265. package/dist/scene/render-scene-types.d.ts.map +1 -1
  266. package/dist/scene/render-scene.d.ts +29 -11
  267. package/dist/scene/render-scene.d.ts.map +1 -1
  268. package/dist/scene/shadow-visibility.d.ts +5 -0
  269. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  270. package/dist/ssr/admission.d.ts +70 -1
  271. package/dist/ssr/admission.d.ts.map +1 -1
  272. package/dist/ssr/compose.d.ts +13 -0
  273. package/dist/ssr/compose.d.ts.map +1 -0
  274. package/dist/ssr/errors.d.ts +35 -0
  275. package/dist/ssr/errors.d.ts.map +1 -1
  276. package/dist/ssr/graph.d.ts +60 -0
  277. package/dist/ssr/graph.d.ts.map +1 -0
  278. package/dist/ssr/history.d.ts +97 -0
  279. package/dist/ssr/history.d.ts.map +1 -0
  280. package/dist/ssr/hiz.d.ts +37 -0
  281. package/dist/ssr/hiz.d.ts.map +1 -0
  282. package/dist/ssr/inspection.d.ts +42 -0
  283. package/dist/ssr/inspection.d.ts.map +1 -0
  284. package/dist/ssr/resources.d.ts +58 -0
  285. package/dist/ssr/resources.d.ts.map +1 -0
  286. package/dist/ssr/temporal.d.ts +38 -0
  287. package/dist/ssr/temporal.d.ts.map +1 -0
  288. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  289. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  290. package/dist/systems/skin-palette-types.d.ts +15 -0
  291. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  292. package/dist/systems/transparent-dispatch.d.ts +9 -0
  293. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  294. package/dist/targets/material-source.d.ts +7 -0
  295. package/dist/targets/material-source.d.ts.map +1 -1
  296. package/dist/temporal/frame-coordinator.d.ts +7 -0
  297. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  298. package/dist/temporal/gpu.d.ts +8 -0
  299. package/dist/temporal/gpu.d.ts.map +1 -1
  300. package/dist/temporal/history.d.ts.map +1 -1
  301. package/dist/temporal/index.mjs +6 -6
  302. package/dist/temporal/inspection.d.ts.map +1 -1
  303. package/dist/temporal/standard-scene-data.d.ts +2 -1
  304. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  305. package/dist/temporal/temporal-view.d.ts +2 -0
  306. package/dist/temporal/temporal-view.d.ts.map +1 -1
  307. package/dist/typed-render-graph-primitives.d.ts +35 -1
  308. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  309. package/dist/typed-shadow-passes.d.ts +3 -1
  310. package/dist/typed-shadow-passes.d.ts.map +1 -1
  311. package/dist/volume/temporal.d.ts +6 -0
  312. package/dist/volume/temporal.d.ts.map +1 -1
  313. package/package.json +21 -20
  314. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  315. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  316. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  317. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  318. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  319. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  320. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  321. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  322. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  323. package/src/__tests__/factory-contract.integration.test.ts +67 -8
  324. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  325. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  326. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  327. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  328. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  329. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  330. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  331. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  332. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  333. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
  334. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  335. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  336. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
  337. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
  338. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
  339. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  340. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  341. package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
  342. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  343. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  344. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  345. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  346. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  347. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  348. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  349. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  350. package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
  351. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
  352. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  353. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  354. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  355. package/src/__tests__/gpu-scene.unit.test.ts +559 -33
  356. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  357. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  358. package/src/__tests__/instances-store.unit.test.ts +242 -39
  359. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  360. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  361. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  362. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  363. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  364. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  365. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  366. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  367. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  368. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  369. package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
  370. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  371. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  372. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  373. package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
  374. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  375. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  376. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  377. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  378. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
  379. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  380. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  381. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  382. package/src/__tests__/public-surface.test-d.ts +3 -0
  383. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  384. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
  385. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
  386. package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
  387. package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
  388. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  389. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  390. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  391. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  392. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  393. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  394. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  395. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  396. package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
  397. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  398. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  399. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  400. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  401. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +60 -0
  402. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  403. package/src/__tests__/shader-manifest-fixture.ts +39 -0
  404. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  405. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  406. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  407. package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
  408. package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
  409. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  410. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  411. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  412. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  413. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
  414. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
  415. package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
  416. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  417. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  418. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  419. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  420. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  421. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  422. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  423. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  424. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  425. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  426. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  427. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  428. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  429. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  430. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  431. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  432. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  433. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  434. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  435. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  436. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  437. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  438. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
  439. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  440. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  441. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  442. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  443. package/src/__tests__/standard-output.browser.test.ts +15 -0
  444. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  445. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
  446. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  447. package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
  448. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  449. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  450. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  451. package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
  452. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  453. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
  454. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  455. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  456. package/src/__tests__/taa-stability-feedback.ts +341 -0
  457. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  458. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  459. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  460. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  461. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  462. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  463. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  464. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  465. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  466. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  467. package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
  468. package/src/assembly/dynamic-geometry-host.ts +48 -0
  469. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  470. package/src/assembly/factory.ts +4 -0
  471. package/src/assembly/gpu-driven-owner.ts +29 -0
  472. package/src/assembly/host-contract.ts +10 -0
  473. package/src/assembly/material/assembly.ts +38 -0
  474. package/src/assembly/material-shader-policy.ts +71 -28
  475. package/src/assembly/recovery/failure-location.ts +62 -0
  476. package/src/assembly/recovery/renderer-recover.ts +19 -17
  477. package/src/assembly/renderer-facade.ts +12 -0
  478. package/src/assembly/renderer-feature-inspection.ts +13 -0
  479. package/src/assembly/renderer-frame-transaction.ts +63 -0
  480. package/src/assembly/renderer-helpers.ts +26 -0
  481. package/src/assembly/renderer-inspection.ts +6 -0
  482. package/src/assembly/shader-prewarm-policy.ts +32 -0
  483. package/src/assembly/skin-palette-owner.ts +21 -0
  484. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  485. package/src/assembly/webgpu-device-loss.ts +75 -0
  486. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  487. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  488. package/src/assembly/webgpu-ready.ts +142 -121
  489. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  490. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  491. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  492. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  493. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  494. package/src/assembly/webgpu-renderer.ts +666 -652
  495. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  496. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  497. package/src/components/camera.ts +287 -7
  498. package/src/components/index.ts +1 -0
  499. package/src/components/instances.ts +45 -18
  500. package/src/components/scene-instance.ts +2 -1
  501. package/src/components/screen-space-reflection.ts +21 -0
  502. package/src/components/sprite-animation.ts +2 -2
  503. package/src/components/sprite-instances.ts +1 -1
  504. package/src/device/gpu-residency.ts +34 -31
  505. package/src/environment/background.ts +263 -0
  506. package/src/errors/gpu-driven.ts +39 -5
  507. package/src/errors/render.ts +27 -3
  508. package/src/extract/camera.ts +26 -1
  509. package/src/extract/gpu-driven.ts +185 -10
  510. package/src/features/host.ts +237 -19
  511. package/src/features/noise-texture.ts +91 -0
  512. package/src/features/plan.ts +553 -24
  513. package/src/features/prepared-gpu-work.ts +631 -49
  514. package/src/features/prepared-graphics-store.ts +31 -0
  515. package/src/features/prepared-graphics.ts +5 -0
  516. package/src/features/render-graph-compute.ts +41 -8
  517. package/src/features/render-graph-contribution.ts +141 -3
  518. package/src/features/render-graph-raster.ts +143 -30
  519. package/src/features/types.ts +11 -0
  520. package/src/fullscreen-post-process-pass.ts +3 -0
  521. package/src/gpu-driven/batch-topology.ts +424 -102
  522. package/src/gpu-driven/pbr-program.ts +31 -0
  523. package/src/gpu-driven/prepared-draw.ts +29 -4
  524. package/src/gpu-driven/production-raster.ts +2267 -694
  525. package/src/gpu-driven/shadow-views.ts +539 -0
  526. package/src/gpu-driven/view-gpu.ts +188 -41
  527. package/src/gpu-scene-schema.ts +52 -11
  528. package/src/gpu-scene.ts +186 -99
  529. package/src/gpu-texture-usage.ts +1 -0
  530. package/src/hdrp-buffers.ts +72 -36
  531. package/src/ibl/frame-resources.ts +38 -0
  532. package/src/ibl/skylight-bind-group.ts +5 -4
  533. package/src/index.ts +109 -1
  534. package/src/inspection-types.ts +191 -0
  535. package/src/instances-derived-bounds.ts +258 -34
  536. package/src/instances.ts +459 -150
  537. package/src/internal.ts +22 -0
  538. package/src/lifecycle.ts +2 -0
  539. package/src/material-row.ts +145 -0
  540. package/src/materials.ts +44 -8
  541. package/src/pbr-pipeline.ts +159 -17
  542. package/src/pipeline/standard-forward-lane.ts +52 -24
  543. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  544. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  545. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  546. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  547. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  548. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  549. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  550. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  551. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  552. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  553. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  554. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  555. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  556. package/src/pipeline/standard-output/color-transform.ts +124 -0
  557. package/src/pipeline/standard-output/graph.ts +139 -0
  558. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  559. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  560. package/src/pipeline/standard-output/lut-state.ts +176 -0
  561. package/src/pipeline/standard-output/resources.ts +121 -0
  562. package/src/pipeline/standard-output/types.ts +58 -0
  563. package/src/pipeline/standard-pipeline.ts +192 -59
  564. package/src/pipeline/standard-post.ts +186 -27
  565. package/src/pipeline-builder.ts +16 -6
  566. package/src/pipeline-spec-types.ts +4 -0
  567. package/src/pipeline-spec.ts +37 -29
  568. package/src/points-lines/standard-owner.ts +6 -14
  569. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  570. package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
  571. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  572. package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
  573. package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
  574. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  575. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  576. package/src/record/dynamic-geometry-consumption.ts +59 -0
  577. package/src/record/frame-lighting.ts +12 -11
  578. package/src/record/frame-snapshot.ts +133 -23
  579. package/src/record/frame-targets.ts +9 -0
  580. package/src/record/frame.ts +725 -133
  581. package/src/record/main-pass-geometry.ts +149 -94
  582. package/src/record/main-pass-material.ts +115 -26
  583. package/src/record/main-pass-sprite-draws.ts +16 -107
  584. package/src/record/main-pass.ts +78 -8
  585. package/src/record/material-uniforms.ts +1 -1
  586. package/src/record/mesh-ssbo.ts +59 -15
  587. package/src/record/prepared-material-bindings.ts +234 -0
  588. package/src/record/recovery-pipeline.ts +59 -136
  589. package/src/record/render-context.ts +90 -22
  590. package/src/record/shadow-pass.ts +1231 -209
  591. package/src/record/skybox-post-pass.ts +14 -3
  592. package/src/record/typed-frame-graph.ts +1004 -44
  593. package/src/record/view-ubo.ts +11 -0
  594. package/src/recovery/render-system-candidate.ts +90 -66
  595. package/src/recovery/render-system-roots.ts +113 -0
  596. package/src/recovery/types.ts +77 -0
  597. package/src/reflection/record-owner.ts +26 -54
  598. package/src/render-contract.ts +195 -25
  599. package/src/render-graph-primitives.ts +6 -2
  600. package/src/render-pipeline.ts +70 -1
  601. package/src/render-system-extract-tail.ts +409 -160
  602. package/src/render-system-extract.ts +376 -102
  603. package/src/render-system-projections.ts +241 -0
  604. package/src/render-system.ts +681 -822
  605. package/src/scene/render-scene-types.ts +13 -4
  606. package/src/scene/render-scene.ts +569 -355
  607. package/src/scene/shadow-visibility.ts +21 -0
  608. package/src/ssr/admission.ts +380 -14
  609. package/src/ssr/compose.ts +144 -0
  610. package/src/ssr/errors.ts +48 -0
  611. package/src/ssr/graph.ts +777 -0
  612. package/src/ssr/history.ts +436 -0
  613. package/src/ssr/hiz.ts +124 -0
  614. package/src/ssr/inspection.ts +124 -0
  615. package/src/ssr/resources.ts +202 -0
  616. package/src/ssr/temporal.ts +164 -0
  617. package/src/systems/skin-palette-allocator.ts +495 -148
  618. package/src/systems/skin-palette-types.ts +17 -0
  619. package/src/systems/transparent-dispatch.ts +90 -0
  620. package/src/targets/material-source.ts +34 -0
  621. package/src/temporal/coverage.ts +1 -1
  622. package/src/temporal/frame-coordinator.ts +23 -2
  623. package/src/temporal/gpu.ts +74 -12
  624. package/src/temporal/history.ts +3 -1
  625. package/src/temporal/inspection.ts +2 -1
  626. package/src/temporal/standard-scene-data.ts +3 -1
  627. package/src/temporal/temporal-view.ts +3 -0
  628. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
  629. package/src/typed-render-graph-primitives.ts +266 -74
  630. package/src/typed-shadow-passes.ts +122 -12
  631. package/src/volume/temporal.ts +10 -0
  632. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  633. package/dist/chunk-GF523LHF.mjs.map +0 -1
  634. package/dist/chunk-IQMKJLNU.mjs.map +0 -1
  635. package/dist/chunk-J52NMQKR.mjs.map +0 -1
  636. package/dist/chunk-J6KACNOH.mjs.map +0 -1
  637. package/dist/chunk-JAASCMXA.mjs.map +0 -1
  638. package/dist/chunk-MBU4ZVE7.mjs.map +0 -1
  639. package/dist/chunk-RBDBZ5R7.mjs.map +0 -1
  640. package/dist/chunk-VSL23LZE.mjs.map +0 -1
  641. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  642. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
@@ -25,10 +25,19 @@ import { executeRendererFrameTransaction } from '../assembly/renderer-frame-tran
25
25
  import { type RenderError, RenderFeatureStageFailedError } from '../errors/render';
26
26
  import {
27
27
  getRenderFeaturePlanExecutionProjection,
28
+ hasSubmissionSensitiveFeatures,
28
29
  type RenderFeaturePlanExecution,
29
30
  } from '../features/host';
30
- import { type RenderFeaturePlannedFrame, renderFeaturePlanSignature } from '../features/plan';
31
- import { projectRenderFeaturePlans as projectPlanExecutions } from '../features/render-graph-contribution';
31
+ import {
32
+ type RenderFeaturePlannedFrame,
33
+ renderFeaturePlanSignature,
34
+ renderFeaturePlanSignatureEvidenceMatches,
35
+ } from '../features/plan';
36
+ import {
37
+ createRenderFeatureProjectionState,
38
+ projectRenderFeaturePlans as projectPlanExecutions,
39
+ projectRenderFeatureShadows,
40
+ } from '../features/render-graph-contribution';
32
41
  import type {
33
42
  RenderFeatureGraphBindingsResolution,
34
43
  RenderFeatureGraphTargetResolver,
@@ -46,12 +55,26 @@ import {
46
55
  GPU_TEXTURE_USAGE_COPY_DST,
47
56
  GPU_TEXTURE_USAGE_COPY_SRC,
48
57
  GPU_TEXTURE_USAGE_RENDER_ATTACHMENT,
58
+ GPU_TEXTURE_USAGE_STORAGE_BINDING,
49
59
  GPU_TEXTURE_USAGE_TEXTURE_BINDING,
50
60
  } from '../gpu-texture-usage';
61
+ import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from '../gpu-usage';
62
+ import { createStandardSurfaceLightingBindGroup } from '../hdrp-buffers';
63
+ import type { RenderFeatureGraphInspection } from '../inspection-types';
51
64
  import {
52
65
  type StandardTopologyInputValue,
53
66
  standardLightingTopologySignature,
54
67
  } from '../pipeline/standard-lighting/topology';
68
+ import {
69
+ importAutoExposureGraphResources,
70
+ retireAutoExposureGpuResources,
71
+ } from '../pipeline/standard-output/auto-exposure/gpu';
72
+ import { commitAutoExposureSubmission } from '../pipeline/standard-output/auto-exposure/state';
73
+ import { retireStandardLutGpuResources } from '../pipeline/standard-output/lut-gpu';
74
+ import {
75
+ commitStandardLutCandidate,
76
+ resetStandardLutState,
77
+ } from '../pipeline/standard-output/lut-state';
55
78
  import { resolveVolumetricFogProfile, STANDARD_PIPELINE_ID } from '../pipeline/standard-profile';
56
79
  import type { CameraSnapshot } from '../render-contract';
57
80
  import type {
@@ -61,7 +84,7 @@ import type {
61
84
  } from '../render-pipeline';
62
85
  import type { ExtractedLights, ExtractedVolumetricFog } from '../render-system-extract';
63
86
  import { SHADOW_ATLAS_DEFAULT_FACE_SIZE, SHADOW_ATLAS_DEFAULT_LAYERS } from '../shadow-atlas';
64
- import type { SsrAdmissionResult } from '../ssr/admission';
87
+ import type { SsrAdmissionResult, SsrSpatialAdmission } from '../ssr/admission';
65
88
  import type { RenderTarget } from '../targets/contracts';
66
89
  import type { RenderTargetPhysical } from '../targets/physical';
67
90
  import {
@@ -71,6 +94,7 @@ import {
71
94
  hasPendingTemporalGpuSubmit,
72
95
  retireTemporalGpuState,
73
96
  retireTemporalGpuStateAfterFence,
97
+ TEMPORAL_HISTORY_FORMATS,
74
98
  temporalReadIndex,
75
99
  temporalWriteIndex,
76
100
  } from '../temporal/gpu';
@@ -79,7 +103,12 @@ import type { TransmissionDemand } from '../transmission/projection';
79
103
  import { hasVolumetricFogCapability } from '../volume/capability';
80
104
  import { deriveVolumetricFogExtent, deriveVolumetricFogResolvedExtent } from '../volume/resources';
81
105
  import { buildPerFrameBindGroups } from './frame-lighting';
82
- import { getTextureIdentity, type RenderFrameState } from './frame-snapshot';
106
+ import {
107
+ type GraphTargetPassReplayPrefixReceipt,
108
+ type GraphTargetPassReplayReceipt,
109
+ getTextureIdentity,
110
+ type RenderFrameState,
111
+ } from './frame-snapshot';
83
112
  import type { GpuPassTimingPassIdentity } from './gpu-pass-timing/contract.js';
84
113
  import type { GpuPassTimingCapture, GpuPassTimingSession } from './gpu-pass-timing/session.js';
85
114
  import type { GpuTimingCapture } from './gpu-timing';
@@ -198,13 +227,14 @@ export function commitReflectionFallbackGraph(
198
227
  function importTemporalHistoryTarget(
199
228
  graph: RenderGraphBuilder<RenderPipelineFrame>,
200
229
  label: string,
201
- kind: 'color' | 'temporal',
230
+ kind: 'color' | 'temporal' | 'stability',
202
231
  role: 'read' | 'write',
203
232
  ): import('../render-pipeline').RenderPipelineTarget {
233
+ const format = TEMPORAL_HISTORY_FORMATS[kind];
204
234
  const texture = graph.importTexture(
205
235
  label,
206
236
  {
207
- format: 'rgba16float',
237
+ format,
208
238
  size: 'surface',
209
239
  usage:
210
240
  GPU_TEXTURE_USAGE_COPY_SRC |
@@ -222,7 +252,7 @@ function importTemporalHistoryTarget(
222
252
  frame.targetH,
223
253
  );
224
254
  const index = role === 'read' ? temporalReadIndex(state) : temporalWriteIndex(state);
225
- return (kind === 'color' ? state.color[index] : state.temporal[index]).texture;
255
+ return state[kind][index].texture;
226
256
  },
227
257
  );
228
258
  if (!texture.ok) throw texture.error;
@@ -239,11 +269,11 @@ function importTemporalHistoryTarget(
239
269
  frame.targetH,
240
270
  );
241
271
  const index = role === 'read' ? temporalReadIndex(state) : temporalWriteIndex(state);
242
- return (kind === 'color' ? state.color[index] : state.temporal[index]).view;
272
+ return state[kind][index].view;
243
273
  },
244
274
  );
245
275
  if (!view.ok) throw view.error;
246
- return { texture: texture.value, view: view.value, format: 'rgba16float', sampleCount: 1 };
276
+ return { texture: texture.value, view: view.value, format, sampleCount: 1 };
247
277
  }
248
278
 
249
279
  function importTemporalHistoryTargets(
@@ -253,6 +283,18 @@ function importTemporalHistoryTargets(
253
283
  > {
254
284
  return {
255
285
  currentColor: importTemporalHistoryTarget(graph, 'taa-history-current-color', 'color', 'write'),
286
+ currentStability: importTemporalHistoryTarget(
287
+ graph,
288
+ 'taa-history-current-stability',
289
+ 'stability',
290
+ 'write',
291
+ ),
292
+ previousStability: importTemporalHistoryTarget(
293
+ graph,
294
+ 'taa-history-previous-stability',
295
+ 'stability',
296
+ 'read',
297
+ ),
256
298
  previousColor: importTemporalHistoryTarget(
257
299
  graph,
258
300
  'taa-history-previous-color',
@@ -274,6 +316,110 @@ function importTemporalHistoryTargets(
274
316
  };
275
317
  }
276
318
 
319
+ function importSsrHistoryTarget(
320
+ graph: RenderGraphBuilder<RenderPipelineFrame>,
321
+ label: string,
322
+ role: 'read' | 'write',
323
+ surface = false,
324
+ ): import('../render-pipeline').RenderPipelineTarget {
325
+ const texture = graph.importTexture(
326
+ label,
327
+ {
328
+ format: surface ? 'rgba8unorm' : 'rgba16float',
329
+ size: 'half-surface',
330
+ usage:
331
+ GPU_TEXTURE_USAGE_COPY_SRC |
332
+ GPU_TEXTURE_USAGE_COPY_DST |
333
+ GPU_TEXTURE_USAGE_RENDER_ATTACHMENT |
334
+ GPU_TEXTURE_USAGE_STORAGE_BINDING |
335
+ GPU_TEXTURE_USAGE_TEXTURE_BINDING,
336
+ domain: 'linear-hdr',
337
+ },
338
+ (frame) => {
339
+ const internal = frame as import('./render-context')._InternalRenderPipelineContext;
340
+ const owner = internal.frameState.ssrHistoryOwner;
341
+ const candidate = internal.frameState.ssrHistoryCandidate;
342
+ if (owner === undefined || candidate === undefined) {
343
+ throw new RhiError({
344
+ code: 'rhi-not-available',
345
+ expected: 'SSR history candidate to be staged before graph execution',
346
+ hint: 'begin the SSR history candidate before executing the admitted graph',
347
+ });
348
+ }
349
+ const slot =
350
+ role === 'read'
351
+ ? (candidate.readSlot ?? (candidate.writeSlot === 0 ? 1 : 0))
352
+ : candidate.writeSlot;
353
+ return surface
354
+ ? owner.resources.slots[slot].surfaceTexture
355
+ : owner.resources.slots[slot].texture;
356
+ },
357
+ );
358
+ if (!texture.ok) throw texture.error;
359
+ const view = graph.importView(
360
+ texture.value,
361
+ { label: `${label}.view`, dimension: '2d' },
362
+ (frame) => {
363
+ const internal = frame as import('./render-context')._InternalRenderPipelineContext;
364
+ const owner = internal.frameState.ssrHistoryOwner;
365
+ const candidate = internal.frameState.ssrHistoryCandidate;
366
+ if (owner === undefined || candidate === undefined) {
367
+ throw new RhiError({
368
+ code: 'rhi-not-available',
369
+ expected: 'SSR history candidate to be staged before graph execution',
370
+ hint: 'begin the SSR history candidate before executing the admitted graph',
371
+ });
372
+ }
373
+ const slot =
374
+ role === 'read'
375
+ ? (candidate.readSlot ?? (candidate.writeSlot === 0 ? 1 : 0))
376
+ : candidate.writeSlot;
377
+ return surface ? owner.resources.slots[slot].surfaceView : owner.resources.slots[slot].view;
378
+ },
379
+ );
380
+ if (!view.ok) throw view.error;
381
+ return {
382
+ texture: texture.value,
383
+ view: view.value,
384
+ format: surface ? 'rgba8unorm' : 'rgba16float',
385
+ sampleCount: 1,
386
+ domain: 'linear-hdr',
387
+ };
388
+ }
389
+
390
+ function importSsrHistoryTargets(
391
+ graph: RenderGraphBuilder<RenderPipelineFrame>,
392
+ ): NonNullable<
393
+ import('../render-pipeline').RenderPipelineBuildContext<RenderPipelineFrame>['ssrHistory']
394
+ > {
395
+ return {
396
+ previous: importSsrHistoryTarget(graph, 'ssr-history-previous', 'read'),
397
+ output: importSsrHistoryTarget(graph, 'ssr-history-output', 'write'),
398
+ previousSurface: importSsrHistoryTarget(graph, 'ssr-history-previous-surface', 'read', true),
399
+ outputSurface: importSsrHistoryTarget(graph, 'ssr-history-output-surface', 'write', true),
400
+ params: (() => {
401
+ const imported = graph.importBuffer(
402
+ 'ssr-history-params',
403
+ { size: 32, usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST },
404
+ (frame) => {
405
+ const internal = frame as import('./render-context')._InternalRenderPipelineContext;
406
+ const owner = internal.frameState.ssrHistoryOwner;
407
+ if (owner === undefined) {
408
+ throw new RhiError({
409
+ code: 'rhi-not-available',
410
+ expected: 'SSR history owner to be staged before graph execution',
411
+ hint: 'create the renderer-owned SSR history before executing the admitted graph',
412
+ });
413
+ }
414
+ return owner.resources.paramsBuffer;
415
+ },
416
+ );
417
+ if (!imported.ok) throw imported.error;
418
+ return imported.value;
419
+ })(),
420
+ };
421
+ }
422
+
277
423
  export function shouldUseTemporalFrameTransaction(
278
424
  antialias: CameraSnapshot['antialias'],
279
425
  temporalDemand = false,
@@ -538,7 +684,6 @@ function addCubeCaptureGraphPasses(
538
684
  const captureResolveView = captureResolve?.ok ? captureResolve.value : null;
539
685
  const captureContext = {
540
686
  ...internal,
541
- gpuDrivenEntityKeys: new Set<number>(),
542
687
  camera: current().faceCamera,
543
688
  // Cube faces are raw scene captures, not display-camera HDR output.
544
689
  // Select the PSO against the actual target attachment so an LDR
@@ -787,6 +932,57 @@ export interface RenderFeatureGraphRuntimeState {
787
932
  >;
788
933
  }
789
934
 
935
+ /** The exact fields that define one validated feature-plan collection. */
936
+ export interface RenderFeaturePlanRevisionEntry {
937
+ readonly featureIdentity: string;
938
+ readonly generation: number;
939
+ readonly signature: string;
940
+ readonly order: number;
941
+ }
942
+
943
+ /** Compact graph-owner state for a completely validated feature-plan set. */
944
+ export interface RenderFeaturePlanRevisionState {
945
+ readonly revision: number;
946
+ readonly entries: readonly RenderFeaturePlanRevisionEntry[];
947
+ }
948
+
949
+ /**
950
+ * Advance the plan revision only when the complete canonical plan collection
951
+ * changes. Equality is an exact field comparison; the canonical signatures
952
+ * have already been recomputed by validation before this helper is called.
953
+ */
954
+ export function updateRenderFeaturePlanRevision(
955
+ previous: RenderFeaturePlanRevisionState,
956
+ plans: readonly RenderFeaturePlannedFrame[],
957
+ executions: readonly RenderFeaturePlanExecution[],
958
+ ): RenderFeaturePlanRevisionState {
959
+ if (
960
+ plans.length === previous.entries.length &&
961
+ plans.every((planned, index) => {
962
+ const prior = previous.entries[index];
963
+ return (
964
+ prior !== undefined &&
965
+ prior.featureIdentity === planned.featureIdentity &&
966
+ prior.generation === planned.generation &&
967
+ prior.signature === planned.signature &&
968
+ prior.order === (executions[index]?.order ?? -1)
969
+ );
970
+ })
971
+ ) {
972
+ return previous;
973
+ }
974
+ const entries = plans.map((planned, index) => ({
975
+ featureIdentity: planned.featureIdentity,
976
+ generation: planned.generation,
977
+ signature: planned.signature,
978
+ order: executions[index]?.order ?? -1,
979
+ }));
980
+ return {
981
+ revision: previous.revision + 1,
982
+ entries: Object.freeze(entries),
983
+ };
984
+ }
985
+
790
986
  /** A frame-local graph candidate that is committed only after compilation. */
791
987
  export interface RenderFeatureGraphCandidate {
792
988
  readonly plans: readonly RenderFeaturePlannedFrame[];
@@ -795,16 +991,22 @@ export interface RenderFeatureGraphCandidate {
795
991
  import('../fullscreen-post-process-pass').PostProcessShaderEntry
796
992
  >;
797
993
  /**
798
- * Physical prepared resources are imported into the compiled graph. A new
799
- * prepared batch therefore needs a new graph even when the declarative plan
800
- * signature is unchanged; otherwise queue retirement can destroy the buffer
801
- * still referenced by the memoized graph.
994
+ * Physical transient graphics resources are imported into the compiled
995
+ * graph. Their lease therefore needs a new graph even when the declarative
996
+ * plan signature is unchanged; otherwise queue retirement could destroy a
997
+ * buffer still referenced by the memoized graph. Retired GPU-work leases are
998
+ * kept in the completion batch but are not imported by this candidate and do
999
+ * not require a graph revision on their own.
802
1000
  */
803
1001
  readonly preparedResourceKey?: string;
804
1002
  /** Release candidate-owned prepared resources when graph promotion fails. */
805
1003
  readonly onRejected?: () => void;
806
1004
  /** Publish candidate-owned runtime declarations only after graph promotion. */
807
1005
  readonly onAccepted?: () => void;
1006
+ /** Consume producer frame state only after the command buffer is submitted. */
1007
+ readonly onSubmitted?: () => void;
1008
+ /** Discard producer frame state after graph execution/submission aborts. */
1009
+ readonly onAborted?: () => void;
808
1010
  /** Discard candidate-only resources when a base graph is kept as fallback. */
809
1011
  readonly onAbandoned?: () => void;
810
1012
  }
@@ -812,6 +1014,139 @@ export interface RenderFeatureGraphCandidate {
812
1014
  const featureGraphStates = new WeakMap<RenderSystemInternals, RenderFeatureGraphRuntimeState>();
813
1015
  const graphDeviceGenerations = new WeakMap<object, number>();
814
1016
 
1017
+ interface MutableRenderFeatureGraphInspection {
1018
+ planRevisionState: RenderFeaturePlanRevisionState;
1019
+ signatureValidationCalls: number;
1020
+ signatureValidationBytes: number;
1021
+ signatureValidationChars: number;
1022
+ signatureValidationCpuMs: number;
1023
+ signatureValidationCacheHits: number;
1024
+ signatureValidationCacheMisses: number;
1025
+ ensureCalls: number;
1026
+ reuseHits: number;
1027
+ rebuildAttempts: number;
1028
+ compileAttempts: number;
1029
+ compileSuccesses: number;
1030
+ compileFailures: number;
1031
+ validationFailures: number;
1032
+ buildFailures: number;
1033
+ compileCpuMs: number;
1034
+ candidatesWithPreparedBatches: number;
1035
+ preparedKeyChanges: number;
1036
+ accepted: number;
1037
+ rejected: number;
1038
+ abandoned: number;
1039
+ lastPlanSignature: string;
1040
+ lastTopologyKey: string | undefined;
1041
+ lastPreparedResourceKey: string | undefined;
1042
+ preparedKeyObserved: boolean;
1043
+ }
1044
+
1045
+ const featureGraphInspections = new WeakMap<
1046
+ RenderSystemInternals,
1047
+ MutableRenderFeatureGraphInspection
1048
+ >();
1049
+
1050
+ function mutableFeatureGraphInspection(
1051
+ internals: RenderSystemInternals,
1052
+ ): MutableRenderFeatureGraphInspection {
1053
+ const existing = featureGraphInspections.get(internals);
1054
+ if (existing !== undefined) return existing;
1055
+ const created: MutableRenderFeatureGraphInspection = {
1056
+ planRevisionState: { revision: 0, entries: [] },
1057
+ signatureValidationCalls: 0,
1058
+ signatureValidationBytes: 0,
1059
+ signatureValidationChars: 0,
1060
+ signatureValidationCpuMs: 0,
1061
+ signatureValidationCacheHits: 0,
1062
+ signatureValidationCacheMisses: 0,
1063
+ ensureCalls: 0,
1064
+ reuseHits: 0,
1065
+ rebuildAttempts: 0,
1066
+ compileAttempts: 0,
1067
+ compileSuccesses: 0,
1068
+ compileFailures: 0,
1069
+ validationFailures: 0,
1070
+ buildFailures: 0,
1071
+ compileCpuMs: 0,
1072
+ candidatesWithPreparedBatches: 0,
1073
+ preparedKeyChanges: 0,
1074
+ accepted: 0,
1075
+ rejected: 0,
1076
+ abandoned: 0,
1077
+ lastPlanSignature: '',
1078
+ lastTopologyKey: undefined,
1079
+ lastPreparedResourceKey: undefined,
1080
+ preparedKeyObserved: false,
1081
+ };
1082
+ featureGraphInspections.set(internals, created);
1083
+ return created;
1084
+ }
1085
+
1086
+ export function getRenderFeatureGraphInspection(
1087
+ internals: RenderSystemInternals,
1088
+ ): RenderFeatureGraphInspection {
1089
+ const metrics = mutableFeatureGraphInspection(internals);
1090
+ return Object.freeze({
1091
+ planRevision: metrics.planRevisionState.revision,
1092
+ signatureValidationCalls: metrics.signatureValidationCalls,
1093
+ signatureValidationBytes: metrics.signatureValidationBytes,
1094
+ signatureValidationChars: metrics.signatureValidationChars,
1095
+ signatureValidationCpuMs: metrics.signatureValidationCpuMs,
1096
+ signatureValidationCacheHits: metrics.signatureValidationCacheHits,
1097
+ signatureValidationCacheMisses: metrics.signatureValidationCacheMisses,
1098
+ ensureCalls: metrics.ensureCalls,
1099
+ reuseHits: metrics.reuseHits,
1100
+ rebuildAttempts: metrics.rebuildAttempts,
1101
+ compileAttempts: metrics.compileAttempts,
1102
+ compileSuccesses: metrics.compileSuccesses,
1103
+ compileFailures: metrics.compileFailures,
1104
+ validationFailures: metrics.validationFailures,
1105
+ buildFailures: metrics.buildFailures,
1106
+ compileCpuMs: metrics.compileCpuMs,
1107
+ candidatesWithPreparedBatches: metrics.candidatesWithPreparedBatches,
1108
+ preparedKeyChanges: metrics.preparedKeyChanges,
1109
+ accepted: metrics.accepted,
1110
+ rejected: metrics.rejected,
1111
+ abandoned: metrics.abandoned,
1112
+ last: Object.freeze({
1113
+ planSignature: metrics.lastPlanSignature,
1114
+ topologyKey: metrics.lastTopologyKey,
1115
+ preparedResourceKey: metrics.lastPreparedResourceKey,
1116
+ }),
1117
+ });
1118
+ }
1119
+
1120
+ export function recordRenderFeatureCandidate(
1121
+ internals: RenderSystemInternals,
1122
+ candidate: RenderFeatureGraphCandidate,
1123
+ ): void {
1124
+ const metrics = mutableFeatureGraphInspection(internals);
1125
+ if (
1126
+ metrics.preparedKeyObserved &&
1127
+ metrics.lastPreparedResourceKey !== candidate.preparedResourceKey
1128
+ ) {
1129
+ metrics.preparedKeyChanges += 1;
1130
+ }
1131
+ metrics.preparedKeyObserved = true;
1132
+ metrics.lastPreparedResourceKey = candidate.preparedResourceKey;
1133
+ if (candidate.preparedResourceKey !== undefined) {
1134
+ metrics.candidatesWithPreparedBatches += 1;
1135
+ }
1136
+ }
1137
+
1138
+ function recordRenderFeatureCandidateEvent(
1139
+ internals: RenderSystemInternals,
1140
+ candidate: RenderFeatureGraphCandidate,
1141
+ event: 'accepted' | 'rejected' | 'abandoned',
1142
+ ): void {
1143
+ const metrics = mutableFeatureGraphInspection(internals);
1144
+ metrics[event] += 1;
1145
+ if (event === 'accepted') candidate.onAccepted?.();
1146
+ else if (event === 'rejected') candidate.onRejected?.();
1147
+ else candidate.onAbandoned?.();
1148
+ }
1149
+
815
1150
  interface TimingInstrumentationState {
816
1151
  readonly instrumentation: RenderGraphPassInstrumentation<RenderPipelineFrame>;
817
1152
  setCapture(capture: GpuPassTimingCapture | undefined): void;
@@ -830,6 +1165,11 @@ type CompiledGraphTargetAccess = CompiledRenderGraph<RenderPipelineFrame> & {
830
1165
  readonly getColorTargetTexture: (name: string) => Texture | undefined;
831
1166
  };
832
1167
 
1168
+ type ResolvedDepthPassExecution =
1169
+ import('@forgeax/engine-render-graph').RenderGraphPassExecution & {
1170
+ readonly resolvedDepthStencilAttachmentView?: TextureView;
1171
+ };
1172
+
833
1173
  export function getRenderFeatureGraphState(
834
1174
  internals: RenderSystemInternals,
835
1175
  ): RenderFeatureGraphRuntimeState {
@@ -899,15 +1239,46 @@ export function resolvePostProcessChainAdmission(
899
1239
 
900
1240
  function validateRenderFeaturePlans(
901
1241
  plans: readonly RenderFeaturePlannedFrame[],
1242
+ metrics?: MutableRenderFeatureGraphInspection,
902
1243
  ): Result<readonly RenderFeaturePlanExecution[], RenderError> {
903
1244
  const identities = new Set<string>();
904
1245
  const projected: RenderFeaturePlanExecution[] = [];
905
1246
  let previousOrder = -1;
906
1247
  for (const [order, planned] of plans.entries()) {
907
1248
  const execution = getRenderFeaturePlanExecutionProjection(planned);
1249
+ const signatureMetrics =
1250
+ metrics === undefined ? undefined : { calls: 0, typedArrayBytes: 0, outputChars: 0 };
1251
+ const started =
1252
+ metrics === undefined
1253
+ ? 0
1254
+ : typeof performance === 'undefined'
1255
+ ? Date.now()
1256
+ : performance.now();
1257
+ // The host records detached structural evidence, but graph admission must
1258
+ // check it again so a mutation between the two synchronous stages cannot
1259
+ // smuggle a stale plan into the compiled graph. Only candidates without
1260
+ // evidence fall back to the canonical serializer.
1261
+ const signatureCacheHit = renderFeaturePlanSignatureEvidenceMatches(
1262
+ planned.plan,
1263
+ planned.signature,
1264
+ );
1265
+ if (metrics !== undefined) {
1266
+ if (signatureCacheHit) metrics.signatureValidationCacheHits += 1;
1267
+ else metrics.signatureValidationCacheMisses += 1;
1268
+ }
1269
+ const recomputedSignature = signatureCacheHit
1270
+ ? planned.signature
1271
+ : renderFeaturePlanSignature(planned.plan, signatureMetrics);
1272
+ if (metrics !== undefined) {
1273
+ const now = typeof performance === 'undefined' ? Date.now() : performance.now();
1274
+ metrics.signatureValidationCalls += signatureMetrics?.calls ?? 0;
1275
+ metrics.signatureValidationBytes += signatureMetrics?.typedArrayBytes ?? 0;
1276
+ metrics.signatureValidationChars += signatureMetrics?.outputChars ?? 0;
1277
+ metrics.signatureValidationCpuMs += now - started;
1278
+ }
908
1279
  if (
909
1280
  identities.has(planned.featureIdentity) ||
910
- planned.signature !== renderFeaturePlanSignature(planned.plan) ||
1281
+ planned.signature !== recomputedSignature ||
911
1282
  execution === undefined ||
912
1283
  execution.featureIdentity !== planned.featureIdentity ||
913
1284
  execution.order <= previousOrder
@@ -930,7 +1301,7 @@ function commitFeatureCandidate(
930
1301
  // Publish renderer-owned declarations only after their device resources have
931
1302
  // been admitted. If the acceptance hook throws, the previously accepted
932
1303
  // graph/state pair remains visible to the caller.
933
- candidate.onAccepted?.();
1304
+ recordRenderFeatureCandidateEvent(internals, candidate, 'accepted');
934
1305
  const state = getRenderFeatureGraphState(internals);
935
1306
  state.plans = candidate.plans;
936
1307
  state.fullscreenEffects = candidate.fullscreenEffects;
@@ -948,10 +1319,12 @@ function topologyOf(
948
1319
  shadowMapSize: number | undefined,
949
1320
  gpuDriven: PreparedGpuDrivenFrame | undefined,
950
1321
  featureGraphCandidate: RenderFeatureGraphCandidate | undefined,
1322
+ featurePlanRevision: number,
951
1323
  cubeCaptureState: CubeCaptureGraphState | undefined,
952
1324
  transmissionDemand: TransmissionDemand | undefined,
953
1325
  volumetricFog: ExtractedVolumetricFog | undefined,
954
1326
  standardLighting: StandardTopologyInputValue | undefined,
1327
+ ssr: SsrSpatialAdmission | undefined,
955
1328
  ): Result<RenderPipelineTopology, RhiError> {
956
1329
  const cascadeCount = Math.max(1, Math.min(4, Math.round(lights.cascadeCount ?? 1))) as
957
1330
  | 1
@@ -1007,12 +1380,15 @@ function topologyOf(
1007
1380
  : volumetricFog?.lightKind === undefined
1008
1381
  ? lights.directional
1009
1382
  : undefined;
1383
+ const preparedLut =
1384
+ frameState.pendingStandardLutGpuResources ?? frameState.standardLutGpuResources;
1010
1385
  return ok({
1011
1386
  pipelineId: STANDARD_PIPELINE_ID,
1012
1387
  standardProfile: internals.standardProfile,
1013
1388
  config,
1014
1389
  clearOnly,
1015
1390
  reflectionFallback: { enabled: frameState.reflectionFallbackDemand === true },
1391
+ ssr,
1016
1392
  surface: {
1017
1393
  width,
1018
1394
  height,
@@ -1025,6 +1401,27 @@ function topologyOf(
1025
1401
  antialias: camera.antialias,
1026
1402
  bloom: camera.bloom,
1027
1403
  },
1404
+ atmosphere: !clearOnly && frameState.environmentFrame?.source.kind === 'atmosphere',
1405
+ output: {
1406
+ autoExposure:
1407
+ !clearOnly &&
1408
+ camera.output?.exposure.kind === 'auto' &&
1409
+ (frameState.pendingAutoExposureGpuResources ?? frameState.autoExposureGpuResources) !==
1410
+ undefined,
1411
+ whiteBalance:
1412
+ !clearOnly &&
1413
+ camera.output !== undefined &&
1414
+ (camera.output.temperature !== 6504 || camera.output.tint !== 0),
1415
+ temperature: camera.output?.temperature ?? 6504,
1416
+ tint: camera.output?.tint ?? 0,
1417
+ colorLut:
1418
+ !clearOnly &&
1419
+ (camera.output?.colorLutStrength ?? 0) > 0 &&
1420
+ (frameState.pendingStandardLutGpuResources ?? frameState.standardLutGpuResources) !==
1421
+ undefined,
1422
+ colorLutStrength: camera.output?.colorLutStrength ?? 0,
1423
+ ...(preparedLut === undefined ? {} : { lutSourceKey: preparedLut.sourceKey }),
1424
+ },
1028
1425
  temporal: {
1029
1426
  taa: camera.antialias === 'taa',
1030
1427
  motionBlur: isMotionBlurTemporalDemand(camera.motionBlur),
@@ -1092,9 +1489,7 @@ function topologyOf(
1092
1489
  maxColorAttachments: internals.device.caps.maxColorAttachments,
1093
1490
  },
1094
1491
  featureTopologySignature: JSON.stringify({
1095
- plans: renderFeatureGraphPlanSignature(
1096
- featureGraphCandidate?.plans ?? getRenderFeatureGraphState(internals).plans,
1097
- ),
1492
+ planRevision: featurePlanRevision,
1098
1493
  fullscreenEffects: featurePostEffectDeclarations,
1099
1494
  preparedResourceKey: featureGraphCandidate?.preparedResourceKey ?? '',
1100
1495
  cubeCaptureSlots: cubeCaptureState?.work.length ?? 0,
@@ -1263,7 +1658,9 @@ function resolveTargetBindings(
1263
1658
  frame.runtime as RenderSystemInternals,
1264
1659
  frame.frameState,
1265
1660
  frame.pipelineState,
1266
- frame.validated.length > 0,
1661
+ // This callback is reached only for an actual feature draw. A scene
1662
+ // containing only particles still needs the pipeline's View/light ABI.
1663
+ true,
1267
1664
  frame.bindGroupCounts,
1268
1665
  {
1269
1666
  directionalShadow: frame.frameState.currentDirectionalShadowView ?? undefined,
@@ -1350,23 +1747,41 @@ export function ensureCompiledFrameGraph(
1350
1747
  volumeTopologyCandidate = false,
1351
1748
  standardLighting?: StandardTopologyInputValue,
1352
1749
  onRecoveryError?: (error: unknown) => void,
1750
+ ssr?: SsrSpatialAdmission,
1353
1751
  ): CompiledRenderGraph<RenderPipelineFrame> | null {
1752
+ const featureMetrics = mutableFeatureGraphInspection(internals);
1753
+ featureMetrics.ensureCalls += 1;
1354
1754
  const requestedStandardLightingSignature = standardLightingTopologySignature(standardLighting);
1355
1755
  const lastKnownGood = (): CompiledRenderGraph<RenderPipelineFrame> | null =>
1356
1756
  frameState.compiledFrameGraph !== null &&
1757
+ !hasSubmissionSensitiveFeatures(getRenderFeatureGraphState(internals).plans) &&
1357
1758
  frameState.standardLightingGraphSignature === requestedStandardLightingSignature
1358
1759
  ? frameState.compiledFrameGraph
1359
1760
  : null;
1360
1761
  const surfaceWidth = Math.max(1, width);
1361
1762
  const surfaceHeight = Math.max(1, height);
1362
1763
  const featurePlans = featureGraphCandidate?.plans ?? getRenderFeatureGraphState(internals).plans;
1363
- const projectedFeaturePlans = validateRenderFeaturePlans(featurePlans);
1764
+ const projectedFeaturePlans = validateRenderFeaturePlans(featurePlans, featureMetrics);
1364
1765
  if (!projectedFeaturePlans.ok) {
1766
+ featureMetrics.validationFailures += 1;
1365
1767
  onRecoveryError?.(projectedFeaturePlans.error);
1366
1768
  reportRenderFeatureGraphError(internals, projectedFeaturePlans.error);
1367
- featureGraphCandidate?.onRejected?.();
1769
+ if (featureGraphCandidate !== undefined) {
1770
+ recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
1771
+ }
1368
1772
  return volumeTopologyCandidate ? null : lastKnownGood();
1369
1773
  }
1774
+ const nextPlanRevision = updateRenderFeaturePlanRevision(
1775
+ featureMetrics.planRevisionState,
1776
+ featurePlans,
1777
+ projectedFeaturePlans.value,
1778
+ );
1779
+ if (nextPlanRevision !== featureMetrics.planRevisionState) {
1780
+ featureMetrics.lastPlanSignature = nextPlanRevision.entries
1781
+ .map((entry) => `${entry.featureIdentity}:${entry.signature}`)
1782
+ .join('|');
1783
+ featureMetrics.planRevisionState = nextPlanRevision;
1784
+ }
1370
1785
  const topologyResult = topologyOf(
1371
1786
  internals,
1372
1787
  frameState,
@@ -1379,15 +1794,20 @@ export function ensureCompiledFrameGraph(
1379
1794
  shadowMapSize,
1380
1795
  gpuDriven,
1381
1796
  featureGraphCandidate,
1797
+ featureMetrics.planRevisionState.revision,
1382
1798
  cubeCaptureState,
1383
1799
  transmissionDemand,
1384
1800
  volumetricFog,
1385
1801
  standardLighting,
1802
+ ssr,
1386
1803
  );
1387
1804
  if (!topologyResult.ok) {
1805
+ featureMetrics.buildFailures += 1;
1388
1806
  onRecoveryError?.(topologyResult.error);
1389
1807
  internals.errorRegistry.fire(topologyResult.error);
1390
- featureGraphCandidate?.onRejected?.();
1808
+ if (featureGraphCandidate !== undefined) {
1809
+ recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
1810
+ }
1391
1811
  return volumeTopologyCandidate ? null : lastKnownGood();
1392
1812
  }
1393
1813
  const requestedPostEffects = topologyResult.value.config?.postEffects ?? [];
@@ -1416,7 +1836,7 @@ export function ensureCompiledFrameGraph(
1416
1836
  getPipeline(
1417
1837
  identity,
1418
1838
  built.bindGroupLayout,
1419
- topologyResult.value.surface.storageFormat as GPUTextureFormat,
1839
+ [topologyResult.value.surface.storageFormat as GPUTextureFormat],
1420
1840
  entry,
1421
1841
  ) !== null
1422
1842
  );
@@ -1427,7 +1847,9 @@ export function ensureCompiledFrameGraph(
1427
1847
  // Do not publish a candidate whose fullscreen chain is only partially
1428
1848
  // ready. Keep the accepted graph (including its prior post-effect chain)
1429
1849
  // and retry the same feature candidate on the next frame.
1430
- featureGraphCandidate?.onRejected?.();
1850
+ if (featureGraphCandidate !== undefined) {
1851
+ recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
1852
+ }
1431
1853
  return acceptedLkg;
1432
1854
  }
1433
1855
  const topology = postProcessAdmission.pending
@@ -1441,6 +1863,12 @@ export function ensureCompiledFrameGraph(
1441
1863
  : topologyResult.value;
1442
1864
  const key = JSON.stringify({
1443
1865
  topology,
1866
+ // Observation domains are an explicit per-frame graph demand. They are
1867
+ // part of the cache identity so a request made after the ordinary graph
1868
+ // was compiled can admit the matching tone/output capture boundaries on
1869
+ // that receipt without forcing every unobserved frame onto the split
1870
+ // output path.
1871
+ observationCaptureDomains: [...(internals.observationCaptureDomains ?? [])].sort(),
1444
1872
  // GPU-driven residency can become available after the first record pass
1445
1873
  // (custom mesh handles are resolved into the per-frame projection). The
1446
1874
  // topology signature alone is intentionally stable across that lifecycle,
@@ -1461,11 +1889,13 @@ export function ensureCompiledFrameGraph(
1461
1889
  ]),
1462
1890
  });
1463
1891
  if (frameState.compiledFrameGraph !== null && frameState.compiledFrameGraphTopologyKey === key) {
1892
+ featureMetrics.reuseHits += 1;
1893
+ featureMetrics.lastTopologyKey = key;
1464
1894
  frameState.standardLightingGraphSignature = requestedStandardLightingSignature;
1465
1895
  if (featureGraphCandidate !== undefined && !postProcessAdmission.pending) {
1466
1896
  commitFeatureCandidate(internals, featureGraphCandidate);
1467
1897
  } else if (featureGraphCandidate !== undefined) {
1468
- featureGraphCandidate.onAbandoned?.();
1898
+ recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'abandoned');
1469
1899
  }
1470
1900
  // A candidate flag only describes a possible topology replacement. When
1471
1901
  // the derived key is unchanged there is no replacement to stage: reuse the
@@ -1474,17 +1904,109 @@ export function ensureCompiledFrameGraph(
1474
1904
  return frameState.compiledFrameGraph;
1475
1905
  }
1476
1906
 
1907
+ featureMetrics.rebuildAttempts += 1;
1908
+ featureMetrics.lastTopologyKey = key;
1909
+ const discardPendingStandardOutput = (): void => {
1910
+ if (frameState.pendingAutoExposureGpuResources !== undefined) {
1911
+ retireAutoExposureGpuResources(frameState.pendingAutoExposureGpuResources);
1912
+ frameState.pendingAutoExposureGpuResources = undefined;
1913
+ }
1914
+ if (frameState.pendingStandardLutGpuResources !== undefined) {
1915
+ retireStandardLutGpuResources(frameState.pendingStandardLutGpuResources);
1916
+ frameState.pendingStandardLutGpuResources = undefined;
1917
+ }
1918
+ };
1477
1919
  const builder = new RenderGraphBuilder<RenderPipelineFrame>();
1920
+ const autoExposureResources =
1921
+ topology.output?.autoExposure === true ? importAutoExposureGraphResources(builder) : undefined;
1922
+ if (autoExposureResources !== undefined && !autoExposureResources.ok) {
1923
+ internals.errorRegistry.fire(autoExposureResources.error);
1924
+ featureGraphCandidate?.onRejected?.();
1925
+ discardPendingStandardOutput();
1926
+ return lastKnownGood();
1927
+ }
1928
+ const lutResources =
1929
+ topology.output?.colorLut === true
1930
+ ? (() => {
1931
+ const prepared =
1932
+ frameState.pendingStandardLutGpuResources ?? frameState.standardLutGpuResources;
1933
+ if (prepared === undefined) return undefined;
1934
+ const texture = builder.importTexture(
1935
+ 'standard-color-lut-source',
1936
+ {
1937
+ format: 'rgba16float',
1938
+ size: {
1939
+ width: prepared.size,
1940
+ height: prepared.size,
1941
+ depthOrArrayLayers: prepared.size,
1942
+ },
1943
+ dimension: '3d',
1944
+ usage: GPU_TEXTURE_USAGE_TEXTURE_BINDING,
1945
+ },
1946
+ (frame) =>
1947
+ (
1948
+ (frame as _InternalRenderPipelineContext).frameState
1949
+ .pendingStandardLutGpuResources ??
1950
+ (frame as _InternalRenderPipelineContext).frameState.standardLutGpuResources
1951
+ )?.texture ?? prepared.texture,
1952
+ );
1953
+ if (!texture.ok) return texture;
1954
+ const view = builder.importView(
1955
+ texture.value,
1956
+ { label: 'standard-color-lut-source.view', dimension: '3d' },
1957
+ (frame) =>
1958
+ (
1959
+ (frame as _InternalRenderPipelineContext).frameState
1960
+ .pendingStandardLutGpuResources ??
1961
+ (frame as _InternalRenderPipelineContext).frameState.standardLutGpuResources
1962
+ )?.view ?? prepared.view,
1963
+ );
1964
+ if (!view.ok) return view;
1965
+ return ok({ texture: texture.value, view: view.value, prepared });
1966
+ })()
1967
+ : undefined;
1968
+ if (lutResources !== undefined && !lutResources.ok) {
1969
+ internals.errorRegistry.fire(lutResources.error);
1970
+ featureGraphCandidate?.onRejected?.();
1971
+ discardPendingStandardOutput();
1972
+ return lastKnownGood();
1973
+ }
1478
1974
  const taaHistory = camera.antialias === 'taa' ? importTemporalHistoryTargets(builder) : undefined;
1975
+ const ssrHistory = ssr?.status === 'admitted' ? importSsrHistoryTargets(builder) : undefined;
1479
1976
  let projectedFeatures = false;
1977
+ const featureProjection = createRenderFeatureProjectionState();
1978
+ let featureShadows:
1979
+ | ReturnType<typeof projectRenderFeatureShadows<RenderPipelineFrame>>
1980
+ | undefined;
1480
1981
  let projectedGpuDriven = false;
1481
1982
  const built = frameState.activePipeline.build(
1482
1983
  {
1483
1984
  graph: builder,
1985
+ observationCaptureDomains: internals.observationCaptureDomains,
1986
+ ...(autoExposureResources?.ok === true
1987
+ ? { standardOutput: { autoExposure: autoExposureResources.value } }
1988
+ : {}),
1989
+ ...(lutResources?.ok === true
1990
+ ? {
1991
+ standardOutput: {
1992
+ ...(autoExposureResources?.ok === true
1993
+ ? { autoExposure: autoExposureResources.value }
1994
+ : {}),
1995
+ colorLut: {
1996
+ view: lutResources.value.view,
1997
+ sampler: lutResources.value.prepared.sampler,
1998
+ strength: lutResources.value.prepared.strength,
1999
+ bindGroupLayout: lutResources.value.prepared.bindGroupLayout,
2000
+ bindGroup: lutResources.value.prepared.bindGroup,
2001
+ },
2002
+ },
2003
+ }
2004
+ : {}),
1484
2005
  ...(standardLighting === undefined ? {} : { standardLighting }),
1485
2006
  capabilities: { rgba16floatRenderable: internals.device.caps.rgba16floatRenderable },
1486
2007
  ...(occlusion === undefined ? {} : { occlusion }),
1487
2008
  ...(taaHistory === undefined ? {} : { taaHistory }),
2009
+ ...(ssrHistory === undefined ? {} : { ssrHistory }),
1488
2010
  encodeTransmissionMip: ({ pass, resources, source }) => {
1489
2011
  const sourceView = resources.textureView(source);
1490
2012
  if (!sourceView.ok) throw sourceView.error;
@@ -1496,14 +2018,53 @@ export function ensureCompiledFrameGraph(
1496
2018
  projectedGpuDriven = true;
1497
2019
  return gpuDriven.project(builder, target.format, target.sampleCount);
1498
2020
  },
1499
- contributeFeatures: (targets, semanticTargets = [], namedTargets = {}) => {
2021
+ ...(gpuDriven?.projectShadow === undefined
2022
+ ? {}
2023
+ : {
2024
+ projectGpuDrivenShadow: (identity) =>
2025
+ gpuDriven.projectShadow?.(builder, identity) ?? ok(undefined),
2026
+ }),
2027
+ contributeShadowFeatures: () =>
2028
+ (featureShadows ??= projectRenderFeatureShadows(
2029
+ builder,
2030
+ projectedFeaturePlans.value,
2031
+ featureProjection,
2032
+ )),
2033
+ contributeFeatures: (
2034
+ targets,
2035
+ semanticTargets = [],
2036
+ namedTargets = {},
2037
+ standardSurfaceAccesses = [],
2038
+ ) => {
1500
2039
  if (projectedFeatures) return ok(undefined);
1501
2040
  projectedFeatures = true;
1502
2041
  if (projectedFeaturePlans.value.length === 0) return ok(undefined);
1503
2042
  const resolveTarget = targetResolver(targets, semanticTargets, namedTargets);
1504
2043
  const projected = projectPlanExecutions(builder, projectedFeaturePlans.value, {
2044
+ state: featureProjection,
1505
2045
  resolveTarget,
2046
+ standardSurfaceAccesses,
1506
2047
  resolveBindings: (input) => resolveTargetBindings(input, featurePostProcessEntries),
2048
+ resolveStandardLighting: (frame, pipeline) => {
2049
+ const context = frame as import('./render-context')._InternalRenderPipelineContext;
2050
+ if (context.standardLighting?.kind !== 'clustered') return undefined;
2051
+ const layout = (
2052
+ pipeline as RenderPipeline & {
2053
+ getBindGroupLayout?: (index: number) => BindGroupLayout;
2054
+ }
2055
+ ).getBindGroupLayout?.(2);
2056
+ if (layout === undefined) return undefined;
2057
+ return (
2058
+ createStandardSurfaceLightingBindGroup(
2059
+ context.runtime,
2060
+ layout,
2061
+ context.standardLighting.prepared.layout.grid,
2062
+ context.hdrpSsaoBlurredView === undefined
2063
+ ? { enabled: false }
2064
+ : { enabled: true, ssaoBlurredView: context.hdrpSsaoBlurredView },
2065
+ ) ?? undefined
2066
+ );
2067
+ },
1507
2068
  reportError: (error) => reportRenderFeatureGraphError(internals, error),
1508
2069
  });
1509
2070
  return projected.ok ? ok(undefined) : projected;
@@ -1524,19 +2085,33 @@ export function ensureCompiledFrameGraph(
1524
2085
  topology,
1525
2086
  );
1526
2087
  if (!built.ok) {
2088
+ featureMetrics.buildFailures += 1;
1527
2089
  onRecoveryError?.(built.error);
1528
2090
  internals.errorRegistry.fire(built.error);
1529
2091
  featureGraphCandidate?.onRejected?.();
2092
+ discardPendingStandardOutput();
2093
+ if (featureGraphCandidate !== undefined) {
2094
+ recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
2095
+ }
1530
2096
  return volumeTopologyCandidate ? null : lastKnownGood();
1531
2097
  }
2098
+ featureMetrics.compileAttempts += 1;
2099
+ const compileStarted = typeof performance === 'undefined' ? Date.now() : performance.now();
1532
2100
  const compiled = builder.compile({
1533
2101
  device: internals.device,
1534
2102
  surfaceSize: { width: surfaceWidth, height: surfaceHeight },
1535
2103
  });
2104
+ featureMetrics.compileCpuMs +=
2105
+ (typeof performance === 'undefined' ? Date.now() : performance.now()) - compileStarted;
1536
2106
  if (!compiled.ok) {
2107
+ featureMetrics.compileFailures += 1;
1537
2108
  onRecoveryError?.(compiled.error);
1538
2109
  internals.errorRegistry.fire(compiled.error);
1539
2110
  featureGraphCandidate?.onRejected?.();
2111
+ discardPendingStandardOutput();
2112
+ if (featureGraphCandidate !== undefined) {
2113
+ recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
2114
+ }
1540
2115
  return lastKnownGood();
1541
2116
  }
1542
2117
  const previous = frameState.compiledFrameGraph;
@@ -1551,21 +2126,31 @@ export function ensureCompiledFrameGraph(
1551
2126
  frameState.compiledFrameGraphGeneration += 1;
1552
2127
  const compiledTargets = compiled.value as CompiledGraphTargetAccess;
1553
2128
  frameState.graphGeneration += 1;
2129
+ featureMetrics.compileSuccesses += 1;
1554
2130
  frameState.perFrameGraph = {
1555
2131
  getColorTargetDescriptor: (name) => compiledTargets.getColorTargetDescriptor(name),
1556
2132
  getColorTargetView: (name) => compiledTargets.getColorTargetView(name),
1557
2133
  getColorTargetTexture: (name) => compiledTargets.getColorTargetTexture(name),
1558
2134
  graphGeneration: frameState.graphGeneration,
1559
2135
  };
2136
+ // View buffer replacement is safe to retire once the compiled graph owns
2137
+ // the new imports. Shadow cache publication remains submit-transactional
2138
+ // and is committed only by executeCompiledFrameGraph's onSubmitted hook.
2139
+ if (gpuDriven?._commitGpuResourceReplacement !== undefined) {
2140
+ gpuDriven._commitGpuResourceReplacement();
2141
+ } else {
2142
+ // Synthetic/internal prepared projections predating the split own no
2143
+ // shadow cache; retain their original graph-promotion callback.
2144
+ gpuDriven?._commitResourceReplacement();
2145
+ }
1560
2146
  const deviceGeneration = internals.deviceScope?.generation;
1561
2147
  if (deviceGeneration !== undefined) {
1562
2148
  graphDeviceGenerations.set(compiled.value, deviceGeneration);
1563
2149
  }
1564
- gpuDriven?._commitResourceReplacement();
1565
2150
  if (featureGraphCandidate !== undefined && !postProcessAdmission.pending) {
1566
2151
  commitFeatureCandidate(internals, featureGraphCandidate);
1567
2152
  } else if (featureGraphCandidate !== undefined) {
1568
- featureGraphCandidate.onAbandoned?.();
2153
+ recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'abandoned');
1569
2154
  }
1570
2155
  if (!volumeTopologyCandidate && previous !== null) retire(frameState, previous);
1571
2156
  return compiled.value;
@@ -1577,15 +2162,21 @@ export function executeCompiledFrameGraph(
1577
2162
  frame: RenderPipelineFrame,
1578
2163
  encoder: RhiCommandEncoder,
1579
2164
  runPass?: import('@forgeax/engine-render-graph').RenderGraphPassRunner,
1580
- frameHooks?: {
1581
- readonly afterGraphExecute?: () => Result<void, RhiError>;
1582
- readonly onSubmitted?: () => void;
1583
- readonly onAborted?: () => void;
1584
- },
2165
+ frameHooks?:
2166
+ | {
2167
+ readonly afterGraphExecute?: () => Result<void, RhiError>;
2168
+ readonly onSubmitted?: () => void;
2169
+ readonly onAborted?: () => void;
2170
+ }
2171
+ // Keep the pre-submit-hook shape accepted for internal callers that have
2172
+ // not yet migrated to the typed frame hooks object.
2173
+ | (() => void),
1585
2174
  readbackFaces?: readonly number[],
1586
2175
  onSubmitted?: (completed: Promise<unknown>) => void,
1587
2176
  timingCapture?: GpuTimingCapture,
1588
2177
  ): boolean {
2178
+ const legacySubmitCommit = typeof frameHooks === 'function' ? frameHooks : undefined;
2179
+ const resolvedFrameHooks = typeof frameHooks === 'function' ? undefined : frameHooks;
1589
2180
  const graph = frameState.compiledFrameGraph;
1590
2181
  const rejectTemporalFrame = (): void => {
1591
2182
  if (frameState.temporalFrameInput === undefined) return;
@@ -1594,6 +2185,8 @@ export function executeCompiledFrameGraph(
1594
2185
  };
1595
2186
  if (graph === null) {
1596
2187
  timingCapture?.discard();
2188
+ frameState.pendingAutoExposureState = undefined;
2189
+ frameState.pendingStandardLutState = undefined;
1597
2190
  settleVolumetricFogGraphCandidate(frameState, false);
1598
2191
  rejectTemporalFrame();
1599
2192
  return false;
@@ -1664,14 +2257,327 @@ export function executeCompiledFrameGraph(
1664
2257
  const transaction = executeRendererFrameTransaction<PreparedCommand>({
1665
2258
  build: () => ({ ok: true, value: { command: undefined as never } }),
1666
2259
  execute: () => {
1667
- const executed = graph.execute(frame, runPass, graphInstrumentation);
1668
- timingInstrumentationState?.setCapture(undefined);
1669
- if (!executed.ok) {
1670
- passTimingCapture?.abort({ code: executed.error.code });
1671
- internals.errorRegistry.fire(executed.error);
1672
- return { ok: false, stage: 'execute' };
2260
+ const graphCapture = frameState.graphTargetCapture;
2261
+ const replayPassNames = graphCapture?.replayPassNames;
2262
+ const unsupportedCaptureKind =
2263
+ graphCapture !== undefined &&
2264
+ graphCapture.kind !== undefined &&
2265
+ graphCapture.kind !== 'target-copy' &&
2266
+ graphCapture.kind !== 'pass-replay';
2267
+ if (unsupportedCaptureKind || replayPassNames !== undefined) {
2268
+ if (graphCapture === undefined) {
2269
+ return { ok: false, stage: 'execute' };
2270
+ }
2271
+ const passNames = graph.inspect().passes.map((pass) => pass.name);
2272
+ const passNameCounts = new Map<string, number>();
2273
+ for (const passName of passNames) {
2274
+ passNameCounts.set(passName, (passNameCounts.get(passName) ?? 0) + 1);
2275
+ }
2276
+ const passNameSet = new Set(passNames);
2277
+ const selectedReplayPassNames = replayPassNames ?? [];
2278
+ const requestedPassNames = new Set(selectedReplayPassNames);
2279
+ const passReplayCapture = graphCapture.kind === 'pass-replay' ? graphCapture : undefined;
2280
+ const onPassEncoded = passReplayCapture?.onPassEncoded;
2281
+ const onPrefixEncoded = passReplayCapture?.onPrefixEncoded;
2282
+ const depthAttachmentPassName = passReplayCapture?.depthAttachmentPassName;
2283
+ const observeAfterPassName = passReplayCapture?.observeAfterPassName;
2284
+ let replayValidationFailed = false;
2285
+ const duplicatePassName = selectedReplayPassNames.find(
2286
+ (passName, index) => selectedReplayPassNames.indexOf(passName) !== index,
2287
+ );
2288
+ const invalidPassName = selectedReplayPassNames.find(
2289
+ (passName) => !passNameSet.has(passName),
2290
+ );
2291
+ const nonUniquePassName = selectedReplayPassNames.find(
2292
+ (passName) => passNameCounts.get(passName) !== 1,
2293
+ );
2294
+ const replayFailure = (reason: string): void => {
2295
+ replayValidationFailed = true;
2296
+ internals.errorRegistry.fire(
2297
+ new RhiError({
2298
+ code: 'webgpu-runtime-error',
2299
+ expected:
2300
+ 'the requested graph target replay pass selection is valid and executes once',
2301
+ hint: 'inspect replayPassNames against the compiled graph pass roster',
2302
+ detail: {
2303
+ error: {
2304
+ code: 'graph-target-capture-failed',
2305
+ message: JSON.stringify({
2306
+ target: graphCapture.kind === 'pass-replay' ? undefined : graphCapture.name,
2307
+ replayPassNames,
2308
+ availablePassNames: passNames,
2309
+ reason,
2310
+ }),
2311
+ },
2312
+ },
2313
+ }),
2314
+ );
2315
+ };
2316
+ if (unsupportedCaptureKind) {
2317
+ replayFailure('capture-kind-unknown');
2318
+ return { ok: false, stage: 'execute' };
2319
+ }
2320
+ if (replayPassNames === undefined) {
2321
+ replayFailure('replay-pass-selection-missing');
2322
+ return { ok: false, stage: 'execute' };
2323
+ }
2324
+ if (replayPassNames.length === 0) {
2325
+ replayFailure('replay-pass-selection-empty');
2326
+ return { ok: false, stage: 'execute' };
2327
+ }
2328
+ if (duplicatePassName !== undefined) {
2329
+ replayFailure(`replay-pass-selection-duplicate:${duplicatePassName}`);
2330
+ return { ok: false, stage: 'execute' };
2331
+ }
2332
+ if (invalidPassName !== undefined) {
2333
+ replayFailure(`replay-pass-selection-unknown:${invalidPassName}`);
2334
+ return { ok: false, stage: 'execute' };
2335
+ }
2336
+ if (nonUniquePassName !== undefined) {
2337
+ replayFailure(`replay-pass-selection-non-unique:${nonUniquePassName}`);
2338
+ return { ok: false, stage: 'execute' };
2339
+ }
2340
+
2341
+ if (onPrefixEncoded !== undefined) {
2342
+ if (onPassEncoded !== undefined) {
2343
+ replayFailure('pass-replay-prefix-callback-conflict');
2344
+ return { ok: false, stage: 'execute' };
2345
+ }
2346
+ if (depthAttachmentPassName === undefined) {
2347
+ replayFailure('pass-replay-depth-attachment-name-missing');
2348
+ return { ok: false, stage: 'execute' };
2349
+ }
2350
+ if (observeAfterPassName === undefined) {
2351
+ replayFailure('pass-replay-observe-boundary-name-missing');
2352
+ return { ok: false, stage: 'execute' };
2353
+ }
2354
+ const firstNonPrefixPass = selectedReplayPassNames.find(
2355
+ (passName, index) => passName !== passNames[index],
2356
+ );
2357
+ if (
2358
+ firstNonPrefixPass !== undefined ||
2359
+ selectedReplayPassNames.length > passNames.length
2360
+ ) {
2361
+ replayFailure('pass-replay-selection-not-prefix');
2362
+ return { ok: false, stage: 'execute' };
2363
+ }
2364
+ if (selectedReplayPassNames.at(-1) !== observeAfterPassName) {
2365
+ replayFailure('pass-replay-observe-boundary-not-last');
2366
+ return { ok: false, stage: 'execute' };
2367
+ }
2368
+ const anchorIndex = selectedReplayPassNames.indexOf(depthAttachmentPassName);
2369
+ if (anchorIndex < 0 || anchorIndex > selectedReplayPassNames.length - 1) {
2370
+ replayFailure('pass-replay-depth-attachment-not-in-prefix');
2371
+ return { ok: false, stage: 'execute' };
2372
+ }
2373
+ }
2374
+
2375
+ let replayTarget:
2376
+ | {
2377
+ readonly name: string;
2378
+ readonly view: TextureView;
2379
+ readonly texture: Texture;
2380
+ readonly textureIdentity: number;
2381
+ readonly graphGeneration: number;
2382
+ }
2383
+ | undefined;
2384
+ if (onPassEncoded !== undefined || onPrefixEncoded !== undefined) {
2385
+ const targetName = passReplayCapture?.targetName;
2386
+ const graphAccess = frameState.perFrameGraph;
2387
+ const targetView =
2388
+ targetName === undefined ? undefined : graphAccess?.getColorTargetView(targetName);
2389
+ const targetTexture =
2390
+ targetName === undefined ? undefined : graphAccess?.getColorTargetTexture(targetName);
2391
+ const targetDescriptor =
2392
+ targetName === undefined
2393
+ ? undefined
2394
+ : graphAccess?.getColorTargetDescriptor(targetName);
2395
+ if (targetName === undefined) {
2396
+ replayFailure('pass-replay-target-name-missing');
2397
+ return { ok: false, stage: 'execute' };
2398
+ }
2399
+ if (graphAccess === undefined || graphAccess === null) {
2400
+ replayFailure(`pass-replay-target-missing:${targetName}`);
2401
+ return { ok: false, stage: 'execute' };
2402
+ }
2403
+ if (targetView === undefined || targetTexture === undefined) {
2404
+ replayFailure(`pass-replay-target-missing:${targetName}`);
2405
+ return { ok: false, stage: 'execute' };
2406
+ }
2407
+ if (targetDescriptor !== undefined && targetDescriptor.texture !== targetTexture) {
2408
+ replayFailure(`pass-replay-target-identity-mismatch:${targetName}`);
2409
+ return { ok: false, stage: 'execute' };
2410
+ }
2411
+ replayTarget = {
2412
+ name: targetName,
2413
+ view: targetView,
2414
+ texture: targetTexture,
2415
+ textureIdentity: getTextureIdentity(targetTexture),
2416
+ graphGeneration: graphAccess.graphGeneration,
2417
+ };
2418
+ }
2419
+
2420
+ const executed = graph.execute(frame, runPass, graphInstrumentation);
2421
+ if (!executed.ok) {
2422
+ passTimingCapture?.abort({ code: executed.error.code });
2423
+ internals.errorRegistry.fire(executed.error);
2424
+ return { ok: false, stage: 'execute' };
2425
+ }
2426
+ const replayedPassNames = new Set<string>();
2427
+ let prefixCallbackCount = 0;
2428
+ let depthAnchorReceipt:
2429
+ | {
2430
+ readonly pass: { name: string; executionIndex: number };
2431
+ readonly view: TextureView;
2432
+ }
2433
+ | undefined;
2434
+ const replayRunPass: import('@forgeax/engine-render-graph').RenderGraphPassRunner = (
2435
+ pass,
2436
+ encode,
2437
+ ) => {
2438
+ if (!requestedPassNames.has(pass.name)) return;
2439
+ if (replayedPassNames.has(pass.name)) {
2440
+ replayFailure(`replay-pass-selection-multiple-hit:${pass.name}`);
2441
+ return;
2442
+ }
2443
+ replayedPassNames.add(pass.name);
2444
+ const depthView = (pass as ResolvedDepthPassExecution).resolvedDepthStencilAttachmentView;
2445
+ if (onPrefixEncoded !== undefined) {
2446
+ if (pass.name === depthAttachmentPassName) {
2447
+ if (depthView === undefined) {
2448
+ replayFailure(`pass-replay-depth-attachment-missing:${pass.name}`);
2449
+ return;
2450
+ }
2451
+ const target = replayTarget;
2452
+ if (target === undefined || depthView !== target.view) {
2453
+ replayFailure(`pass-replay-attachment-target-mismatch:${pass.name}`);
2454
+ return;
2455
+ }
2456
+ if (depthAnchorReceipt !== undefined) {
2457
+ replayFailure(`pass-replay-depth-attachment-multiple-hit:${pass.name}`);
2458
+ return;
2459
+ }
2460
+ depthAnchorReceipt = {
2461
+ pass: { name: pass.name, executionIndex: pass.executionIndex },
2462
+ view: depthView,
2463
+ };
2464
+ }
2465
+ let encoded = false;
2466
+ const encodePrefix = () => {
2467
+ if (encoded) {
2468
+ replayFailure(`pass-replay-encode-multiple-hit:${pass.name}`);
2469
+ return;
2470
+ }
2471
+ encode();
2472
+ encoded = true;
2473
+ if (replayValidationFailed || pass.name !== observeAfterPassName) return;
2474
+ const target = replayTarget;
2475
+ const anchor = depthAnchorReceipt;
2476
+ if (target === undefined || anchor === undefined) {
2477
+ replayFailure('pass-replay-depth-attachment-anchor-missing-at-boundary');
2478
+ return;
2479
+ }
2480
+ const receipt: GraphTargetPassReplayPrefixReceipt = {
2481
+ passName: pass.name,
2482
+ executionIndex: pass.executionIndex,
2483
+ resolvedDepthStencilAttachmentView: anchor.view,
2484
+ graphGeneration: target.graphGeneration,
2485
+ targetName: target.name,
2486
+ targetView: target.view,
2487
+ targetTexture: target.texture,
2488
+ targetTextureIdentity: target.textureIdentity,
2489
+ replayPassNames: [...selectedReplayPassNames],
2490
+ depthAttachmentPassName: anchor.pass.name,
2491
+ observeAfterPassName: pass.name,
2492
+ depthAttachmentExecutionIndex: anchor.pass.executionIndex,
2493
+ };
2494
+ try {
2495
+ onPrefixEncoded(receipt);
2496
+ prefixCallbackCount += 1;
2497
+ } catch (cause) {
2498
+ replayFailure(
2499
+ `pass-replay-prefix-callback-threw:${pass.name}:${cause instanceof Error ? cause.message : String(cause)}`,
2500
+ );
2501
+ }
2502
+ };
2503
+ if (runPass === undefined) encodePrefix();
2504
+ else runPass(pass, encodePrefix);
2505
+ if (!encoded && !replayValidationFailed) {
2506
+ replayFailure(`pass-replay-encode-not-called:${pass.name}`);
2507
+ }
2508
+ return;
2509
+ }
2510
+ if (onPassEncoded === undefined) {
2511
+ if (runPass === undefined) encode();
2512
+ else runPass(pass, encode);
2513
+ return;
2514
+ }
2515
+ const target = replayTarget;
2516
+ if (target === undefined || depthView === undefined) {
2517
+ replayFailure(`pass-replay-depth-attachment-missing:${pass.name}`);
2518
+ return;
2519
+ }
2520
+ if (depthView !== target.view) {
2521
+ replayFailure(`pass-replay-attachment-target-mismatch:${pass.name}`);
2522
+ return;
2523
+ }
2524
+ let encoded = false;
2525
+ const encodeAndObserve = () => {
2526
+ if (encoded) {
2527
+ replayFailure(`pass-replay-encode-multiple-hit:${pass.name}`);
2528
+ return;
2529
+ }
2530
+ encode();
2531
+ encoded = true;
2532
+ if (replayValidationFailed) return;
2533
+ const receipt: GraphTargetPassReplayReceipt = {
2534
+ passName: pass.name,
2535
+ executionIndex: pass.executionIndex,
2536
+ resolvedDepthStencilAttachmentView: depthView,
2537
+ graphGeneration: target.graphGeneration,
2538
+ targetName: target.name,
2539
+ targetView: target.view,
2540
+ targetTexture: target.texture,
2541
+ targetTextureIdentity: target.textureIdentity,
2542
+ };
2543
+ try {
2544
+ onPassEncoded(receipt);
2545
+ } catch (cause) {
2546
+ replayFailure(
2547
+ `pass-replay-callback-threw:${pass.name}:${cause instanceof Error ? cause.message : String(cause)}`,
2548
+ );
2549
+ }
2550
+ };
2551
+ if (runPass === undefined) encodeAndObserve();
2552
+ else runPass(pass, encodeAndObserve);
2553
+ if (!encoded && !replayValidationFailed) {
2554
+ replayFailure(`pass-replay-encode-not-called:${pass.name}`);
2555
+ }
2556
+ };
2557
+ const replayed = graph.execute(frame, replayRunPass);
2558
+ if (!replayed.ok) {
2559
+ internals.errorRegistry.fire(replayed.error);
2560
+ return { ok: false, stage: 'execute' };
2561
+ }
2562
+ if (replayValidationFailed) return { ok: false, stage: 'execute' };
2563
+ if (replayedPassNames.size !== requestedPassNames.size) {
2564
+ replayFailure('replay-pass-selection-zero-hit');
2565
+ return { ok: false, stage: 'execute' };
2566
+ }
2567
+ if (onPrefixEncoded !== undefined && prefixCallbackCount !== 1) {
2568
+ replayFailure('pass-replay-prefix-callback-count-invalid');
2569
+ return { ok: false, stage: 'execute' };
2570
+ }
2571
+ } else {
2572
+ const executed = graph.execute(frame, runPass, graphInstrumentation);
2573
+ if (!executed.ok) {
2574
+ passTimingCapture?.abort({ code: executed.error.code });
2575
+ internals.errorRegistry.fire(executed.error);
2576
+ return { ok: false, stage: 'execute' };
2577
+ }
1673
2578
  }
1674
- const afterGraphExecute = frameHooks?.afterGraphExecute;
2579
+ timingInstrumentationState?.setCapture(undefined);
2580
+ const afterGraphExecute = resolvedFrameHooks?.afterGraphExecute;
1675
2581
  if (afterGraphExecute !== undefined) {
1676
2582
  const recorded = afterGraphExecute();
1677
2583
  if (!recorded.ok) {
@@ -1684,7 +2590,7 @@ export function executeCompiledFrameGraph(
1684
2590
  finish: () => {
1685
2591
  const graphCapture = frameState.graphTargetCapture;
1686
2592
  frameState.graphTargetCapture = undefined;
1687
- if (graphCapture !== undefined) {
2593
+ if (graphCapture !== undefined && graphCapture.kind !== 'pass-replay') {
1688
2594
  const graphAccess = frameState.perFrameGraph;
1689
2595
  const graphTexture = graphAccess?.getColorTargetTexture(graphCapture.name);
1690
2596
  const descriptor = graphAccess?.getColorTargetDescriptor(graphCapture.name);
@@ -1903,7 +2809,8 @@ export function executeCompiledFrameGraph(
1903
2809
  },
1904
2810
  }),
1905
2811
  commit: () => {
1906
- frameHooks?.onSubmitted?.();
2812
+ resolvedFrameHooks?.onSubmitted?.();
2813
+ legacySubmitCommit?.();
1907
2814
  if (frameState.temporalFrameInput !== undefined) {
1908
2815
  const temporal = frameState.temporalFrameTransaction.commit({ accepted: true });
1909
2816
  if (temporal.ok) {
@@ -1931,6 +2838,46 @@ export function executeCompiledFrameGraph(
1931
2838
  ?.perPassResources.commitBloomFrameReceipts?.(frameState.bloomFrameReceipts);
1932
2839
  frameState.bloomFrameReceipts = undefined;
1933
2840
  }
2841
+ const pendingAuto = frameState.pendingAutoExposureGpuResources;
2842
+ if (pendingAuto !== undefined) {
2843
+ const previousAuto = frameState.autoExposureGpuResources;
2844
+ frameState.autoExposureGpuResources = pendingAuto;
2845
+ frameState.pendingAutoExposureGpuResources = undefined;
2846
+ if (previousAuto !== undefined && previousAuto !== pendingAuto) {
2847
+ internals.device.queue
2848
+ .onSubmittedWorkDone()
2849
+ .then(() => retireAutoExposureGpuResources(previousAuto))
2850
+ .catch(() => undefined);
2851
+ }
2852
+ }
2853
+ const pendingAutoState = frameState.pendingAutoExposureState;
2854
+ if (pendingAutoState !== undefined) {
2855
+ frameState.autoExposureState = commitAutoExposureSubmission(
2856
+ pendingAutoState.state,
2857
+ pendingAutoState,
2858
+ );
2859
+ frameState.pendingAutoExposureState = undefined;
2860
+ }
2861
+ const pendingLut = frameState.pendingStandardLutGpuResources;
2862
+ if (pendingLut !== undefined) {
2863
+ const previousLut = frameState.standardLutGpuResources;
2864
+ frameState.standardLutGpuResources = pendingLut;
2865
+ frameState.pendingStandardLutGpuResources = undefined;
2866
+ if (previousLut !== undefined && previousLut !== pendingLut) {
2867
+ retireStandardLutGpuResources(previousLut);
2868
+ }
2869
+ }
2870
+ const pendingLutState = frameState.pendingStandardLutState;
2871
+ if (pendingLutState !== undefined) {
2872
+ frameState.standardLutState =
2873
+ pendingLutState.remove || pendingLutState.candidate === undefined
2874
+ ? resetStandardLutState(pendingLutState.state, 'resource-removed', {
2875
+ targetGeneration: pendingLutState.targetGeneration,
2876
+ deviceEpoch: pendingLutState.deviceEpoch,
2877
+ })
2878
+ : commitStandardLutCandidate(pendingLutState.state, pendingLutState.candidate);
2879
+ frameState.pendingStandardLutState = undefined;
2880
+ }
1934
2881
  const stagedGpuState = frameState.temporalGpuState;
1935
2882
  if (stagedGpuState !== undefined) {
1936
2883
  if (commitTemporalGpuSubmit(stagedGpuState)) {
@@ -1993,15 +2940,28 @@ export function executeCompiledFrameGraph(
1993
2940
  frameState.environmentLifecycle?.publish(frameState.environmentGeneration);
1994
2941
  frameState.environmentGeneration = undefined;
1995
2942
  }
2943
+ frameState.pendingAtmospherePublish?.();
2944
+ frameState.pendingAtmospherePublish = undefined;
1996
2945
  onSubmitted?.(internals.device.queue.onSubmittedWorkDone());
1997
2946
  },
1998
2947
  abort: (failure) => {
1999
- frameHooks?.onAborted?.();
2948
+ frameState.pendingAtmospherePublish = undefined;
2949
+ resolvedFrameHooks?.onAborted?.();
2000
2950
  passTimingCapture?.abort({ code: failure.stage });
2001
2951
  timingHost.gpuPassTimingCapture = undefined;
2002
2952
  timingHost.gpuPassTimingSubmittedWork = undefined;
2003
2953
  timingCapture?.discard();
2004
2954
  frameState.bloomFrameReceipts = undefined;
2955
+ frameState.pendingAutoExposureState = undefined;
2956
+ frameState.pendingStandardLutState = undefined;
2957
+ if (frameState.pendingAutoExposureGpuResources !== undefined) {
2958
+ retireAutoExposureGpuResources(frameState.pendingAutoExposureGpuResources);
2959
+ frameState.pendingAutoExposureGpuResources = undefined;
2960
+ }
2961
+ if (frameState.pendingStandardLutGpuResources !== undefined) {
2962
+ retireStandardLutGpuResources(frameState.pendingStandardLutGpuResources);
2963
+ frameState.pendingStandardLutGpuResources = undefined;
2964
+ }
2005
2965
  if (frameState.environmentGeneration !== undefined) {
2006
2966
  frameState.environmentLifecycle?.recordStageFailure(
2007
2967
  failure.stage,