@forgeax/engine-render 0.1.33 → 0.1.34

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 (518) hide show
  1. package/README.md +1010 -124
  2. package/dist/assembly/backend-contract.d.ts +2 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
  5. package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
  6. package/dist/assembly/canvas-draw-guard.d.ts +3 -0
  7. package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
  8. package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
  9. package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
  10. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  11. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  12. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  13. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  14. package/dist/assembly/factory.d.ts +1 -1
  15. package/dist/assembly/factory.d.ts.map +1 -1
  16. package/dist/assembly/host-contract.d.ts +3 -3
  17. package/dist/assembly/host-contract.d.ts.map +1 -1
  18. package/dist/assembly/material/assembly.d.ts.map +1 -1
  19. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  20. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  21. package/dist/assembly/material-shader-policy.d.ts +16 -2
  22. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  23. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  24. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  25. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  26. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  27. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  28. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  29. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  30. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  31. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  32. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  33. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  34. package/dist/assembly/renderer-inspection.d.ts +11 -0
  35. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  36. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  37. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  38. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  39. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  40. package/dist/assembly/webgpu-ready.d.ts +1 -1
  41. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  42. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  43. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  44. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  45. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  46. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  47. package/dist/authoring.mjs +1 -1
  48. package/dist/barrel-distortion.d.ts +47 -0
  49. package/dist/barrel-distortion.d.ts.map +1 -0
  50. package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
  51. package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
  52. package/dist/{chunk-ZDQWGQW7.mjs → chunk-HEB564WF.mjs} +4 -4
  53. package/dist/{chunk-ZDQWGQW7.mjs.map → chunk-HEB564WF.mjs.map} +1 -1
  54. package/dist/{chunk-VOZWDM3L.mjs → chunk-MKDIT2EM.mjs} +1307 -188
  55. package/dist/chunk-MKDIT2EM.mjs.map +1 -0
  56. package/dist/{chunk-LEAXOMYJ.mjs → chunk-OCDLEI6I.mjs} +3 -3
  57. package/dist/{chunk-LEAXOMYJ.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
  58. package/dist/chunk-ODZWR6JQ.mjs +4927 -0
  59. package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
  60. package/dist/{chunk-3RTCJTH4.mjs → chunk-SHFUFJ5N.mjs} +184 -13
  61. package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
  62. package/dist/{chunk-OO3DRYK7.mjs → chunk-UI5SFODX.mjs} +176 -96
  63. package/dist/chunk-UI5SFODX.mjs.map +1 -0
  64. package/dist/{chunk-PLAXOOX5.mjs → chunk-UINHLCZA.mjs} +561 -79
  65. package/dist/chunk-UINHLCZA.mjs.map +1 -0
  66. package/dist/{chunk-NB2S7ZRX.mjs → chunk-WEX45HKH.mjs} +127 -386
  67. package/dist/chunk-WEX45HKH.mjs.map +1 -0
  68. package/dist/cloud/density.d.ts +55 -0
  69. package/dist/cloud/density.d.ts.map +1 -0
  70. package/dist/cloud/extract.d.ts +25 -0
  71. package/dist/cloud/extract.d.ts.map +1 -0
  72. package/dist/cloud/feature.d.ts +57 -0
  73. package/dist/cloud/feature.d.ts.map +1 -0
  74. package/dist/cloud/frame-context.d.ts +8 -0
  75. package/dist/cloud/frame-context.d.ts.map +1 -0
  76. package/dist/cloud/inspection.d.ts +59 -0
  77. package/dist/cloud/inspection.d.ts.map +1 -0
  78. package/dist/cloud/optics.d.ts +56 -0
  79. package/dist/cloud/optics.d.ts.map +1 -0
  80. package/dist/cloud/parameters.d.ts +73 -0
  81. package/dist/cloud/parameters.d.ts.map +1 -0
  82. package/dist/cloud/resources.d.ts +60 -0
  83. package/dist/cloud/resources.d.ts.map +1 -0
  84. package/dist/cloud/shadow.d.ts +42 -0
  85. package/dist/cloud/shadow.d.ts.map +1 -0
  86. package/dist/cloud/temporal.d.ts +74 -0
  87. package/dist/cloud/temporal.d.ts.map +1 -0
  88. package/dist/components/barrel-distortion.d.ts +13 -0
  89. package/dist/components/barrel-distortion.d.ts.map +1 -0
  90. package/dist/components/cloud-layer.d.ts +29 -0
  91. package/dist/components/cloud-layer.d.ts.map +1 -0
  92. package/dist/components/index.d.ts +2 -0
  93. package/dist/components/index.d.ts.map +1 -1
  94. package/dist/components/instances.d.ts +20 -6
  95. package/dist/components/instances.d.ts.map +1 -1
  96. package/dist/construct-renderer.d.ts +1 -1
  97. package/dist/construct-renderer.d.ts.map +1 -1
  98. package/dist/construct-renderer.mjs +6759 -2875
  99. package/dist/construct-renderer.mjs.map +1 -1
  100. package/dist/dynamic-geometry.d.ts +5 -1
  101. package/dist/dynamic-geometry.d.ts.map +1 -1
  102. package/dist/errors/cloud.d.ts +59 -0
  103. package/dist/errors/cloud.d.ts.map +1 -0
  104. package/dist/errors/render.d.ts +18 -4
  105. package/dist/errors/render.d.ts.map +1 -1
  106. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  107. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  108. package/dist/extract/camera.d.ts.map +1 -1
  109. package/dist/extract/contracts.d.ts +5 -1
  110. package/dist/extract/contracts.d.ts.map +1 -1
  111. package/dist/features/barrel-distortion.d.ts +29 -0
  112. package/dist/features/barrel-distortion.d.ts.map +1 -0
  113. package/dist/features/error-reporting.d.ts +6 -2
  114. package/dist/features/error-reporting.d.ts.map +1 -1
  115. package/dist/features/host.d.ts +10 -0
  116. package/dist/features/host.d.ts.map +1 -1
  117. package/dist/features/plan.d.ts +8 -0
  118. package/dist/features/plan.d.ts.map +1 -1
  119. package/dist/features/render-graph-contribution.d.ts +5 -0
  120. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  121. package/dist/features/render-graph-raster.d.ts +1 -0
  122. package/dist/features/render-graph-raster.d.ts.map +1 -1
  123. package/dist/features/targets.d.ts +2 -0
  124. package/dist/features/targets.d.ts.map +1 -1
  125. package/dist/features/types.d.ts +44 -0
  126. package/dist/features/types.d.ts.map +1 -1
  127. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  128. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  129. package/dist/gpu-dirty-ranges.d.ts +11 -0
  130. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  131. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  132. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  133. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  134. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  135. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  136. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  137. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  138. package/dist/gpu-driven/production-raster.d.ts +47 -3
  139. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  140. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  141. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  142. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  143. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  144. package/dist/gpu-driven/view-gpu.d.ts +26 -2
  145. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  146. package/dist/gpu-scene.d.ts +5 -0
  147. package/dist/gpu-scene.d.ts.map +1 -1
  148. package/dist/hdrp-buffers.d.ts +2 -2
  149. package/dist/hdrp-buffers.d.ts.map +1 -1
  150. package/dist/ibl/skylight-bind-group.d.ts +10 -1
  151. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  152. package/dist/index.d.ts +27 -7
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.mjs +258 -8
  155. package/dist/index.mjs.map +1 -1
  156. package/dist/inspection-types.d.ts +135 -1
  157. package/dist/inspection-types.d.ts.map +1 -1
  158. package/dist/instance-buffer-cache.d.ts +4 -4
  159. package/dist/instance-buffer-cache.d.ts.map +1 -1
  160. package/dist/instances.d.ts +48 -100
  161. package/dist/instances.d.ts.map +1 -1
  162. package/dist/internal.mjs +7 -7
  163. package/dist/pbr-pipeline.d.ts +19 -1
  164. package/dist/pbr-pipeline.d.ts.map +1 -1
  165. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  166. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  167. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  168. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  169. package/dist/pipeline/standard-output/types.d.ts +4 -2
  170. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  171. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  172. package/dist/pipeline/standard-post.d.ts.map +1 -1
  173. package/dist/pipeline-builder.d.ts +1 -1
  174. package/dist/pipeline-builder.d.ts.map +1 -1
  175. package/dist/plugin.d.ts.map +1 -1
  176. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  177. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  178. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  179. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  180. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  181. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  182. package/dist/record/fold-buckets.d.ts +95 -0
  183. package/dist/record/fold-buckets.d.ts.map +1 -0
  184. package/dist/record/fold-instance-buffer.d.ts +6 -0
  185. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  186. package/dist/record/frame-lighting.d.ts +1 -0
  187. package/dist/record/frame-lighting.d.ts.map +1 -1
  188. package/dist/record/frame-snapshot.d.ts +37 -3
  189. package/dist/record/frame-snapshot.d.ts.map +1 -1
  190. package/dist/record/frame.d.ts +10 -61
  191. package/dist/record/frame.d.ts.map +1 -1
  192. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  193. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  194. package/dist/record/gpu-timing.d.ts +8 -0
  195. package/dist/record/gpu-timing.d.ts.map +1 -1
  196. package/dist/record/main-pass-geometry.d.ts +21 -5
  197. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  198. package/dist/record/main-pass-material.d.ts +7 -1
  199. package/dist/record/main-pass-material.d.ts.map +1 -1
  200. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  201. package/dist/record/main-pass.d.ts +13 -2
  202. package/dist/record/main-pass.d.ts.map +1 -1
  203. package/dist/record/motion-blur-receipt.d.ts +11 -0
  204. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  205. package/dist/record/probe-blend-buffer.d.ts +16 -0
  206. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  207. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  208. package/dist/record/render-context.d.ts +22 -3
  209. package/dist/record/render-context.d.ts.map +1 -1
  210. package/dist/record/shadow-pass.d.ts +2 -0
  211. package/dist/record/shadow-pass.d.ts.map +1 -1
  212. package/dist/record/skybox-post-pass.d.ts +1 -1
  213. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  214. package/dist/record/typed-frame-graph.d.ts +22 -2
  215. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  216. package/dist/record/view-ubo.d.ts +11 -3
  217. package/dist/record/view-ubo.d.ts.map +1 -1
  218. package/dist/record/volume-params.d.ts +9 -0
  219. package/dist/record/volume-params.d.ts.map +1 -0
  220. package/dist/recovery/render-system-candidate.d.ts +10 -0
  221. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  222. package/dist/reflection/record-owner.d.ts.map +1 -1
  223. package/dist/render-contract.d.ts +42 -4
  224. package/dist/render-contract.d.ts.map +1 -1
  225. package/dist/render-graph-primitives.d.ts.map +1 -1
  226. package/dist/render-pipeline.d.ts +103 -2
  227. package/dist/render-pipeline.d.ts.map +1 -1
  228. package/dist/render-system-extract-tail.d.ts +3 -2
  229. package/dist/render-system-extract-tail.d.ts.map +1 -1
  230. package/dist/render-system-extract.d.ts +16 -9
  231. package/dist/render-system-extract.d.ts.map +1 -1
  232. package/dist/render-system-presentation.d.ts +23 -0
  233. package/dist/render-system-presentation.d.ts.map +1 -0
  234. package/dist/render-system-producer-roots.d.ts +10 -0
  235. package/dist/render-system-producer-roots.d.ts.map +1 -0
  236. package/dist/render-system-residency.d.ts +12 -0
  237. package/dist/render-system-residency.d.ts.map +1 -0
  238. package/dist/render-system.d.ts +19 -14
  239. package/dist/render-system.d.ts.map +1 -1
  240. package/dist/scene/probe-blend.d.ts +27 -0
  241. package/dist/scene/probe-blend.d.ts.map +1 -1
  242. package/dist/scene/render-scene.d.ts +12 -5
  243. package/dist/scene/render-scene.d.ts.map +1 -1
  244. package/dist/ssao-buffers.d.ts +9 -0
  245. package/dist/ssao-buffers.d.ts.map +1 -1
  246. package/dist/surface/dynamic-input.d.ts +133 -0
  247. package/dist/surface/dynamic-input.d.ts.map +1 -0
  248. package/dist/surface/gpu-driven.d.ts +41 -0
  249. package/dist/surface/gpu-driven.d.ts.map +1 -0
  250. package/dist/surface/submission-observation.d.ts +136 -0
  251. package/dist/surface/submission-observation.d.ts.map +1 -0
  252. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  253. package/dist/temporal/gpu.d.ts +13 -1
  254. package/dist/temporal/gpu.d.ts.map +1 -1
  255. package/dist/temporal/index.mjs +5 -5
  256. package/dist/temporal/inspection.d.ts.map +1 -1
  257. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  258. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  259. package/dist/transmission/single-layer-medium.d.ts +89 -0
  260. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  261. package/dist/typed-render-graph-primitives.d.ts +14 -2
  262. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  263. package/dist/volume/component.d.ts +9 -4
  264. package/dist/volume/component.d.ts.map +1 -1
  265. package/dist/volume/extract.d.ts +1 -1
  266. package/dist/volume/extract.d.ts.map +1 -1
  267. package/dist/volume/inspection.d.ts +2 -0
  268. package/dist/volume/inspection.d.ts.map +1 -1
  269. package/dist/volume/passes.d.ts +4 -1
  270. package/dist/volume/passes.d.ts.map +1 -1
  271. package/dist/volume/resources.d.ts.map +1 -1
  272. package/package.json +22 -21
  273. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  274. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  275. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  276. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  277. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  278. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  279. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  280. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  281. package/src/__tests__/factory-contract.integration.test.ts +295 -45
  282. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  283. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
  284. package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
  285. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
  286. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
  287. package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
  288. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +2 -2
  289. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  290. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +6 -5
  291. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +94 -6
  292. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  293. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  294. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  295. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  296. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  297. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  298. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  299. package/src/__tests__/gpu-scene.unit.test.ts +119 -9
  300. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  301. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  302. package/src/__tests__/instances-store.unit.test.ts +97 -305
  303. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  304. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +146 -0
  305. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  306. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  307. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  308. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  309. package/src/__tests__/motion-blur-compute.dawn.test.ts +13 -7
  310. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  311. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  312. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  313. package/src/__tests__/physical-clearcoat.integration.test.ts +58 -0
  314. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  315. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  316. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  317. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  318. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  319. package/src/__tests__/render-error-code-owner.test-d.ts +7 -0
  320. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  321. package/src/__tests__/render-feature-error-reporting.unit.test.ts +21 -1
  322. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  323. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  324. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  325. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  326. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +20 -20
  327. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  328. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  329. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  330. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +41 -7
  331. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  332. package/src/__tests__/renderer-frame-transaction.integration.test.ts +26 -10
  333. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  334. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  335. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  336. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  337. package/src/__tests__/skinned-shadow-caster.test.ts +15 -0
  338. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  339. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  340. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  341. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  342. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  343. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  344. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  345. package/src/__tests__/standard-pipeline.integration.test.ts +182 -1
  346. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  347. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  348. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  349. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  350. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  351. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  352. package/src/__tests__/volume-optics-oracle.unit.test.ts +4 -2
  353. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +30 -20
  354. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  355. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  356. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  357. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +113 -9
  358. package/src/__tests__/volumetric-fog-topology.unit.test.ts +5 -3
  359. package/src/assembly/backend-contract.ts +30 -0
  360. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  361. package/src/assembly/canvas-draw-guard.ts +11 -0
  362. package/src/assembly/cloud-shadow-entry.ts +22 -0
  363. package/src/assembly/dynamic-geometry-host.ts +65 -37
  364. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  365. package/src/assembly/factory.ts +2 -30
  366. package/src/assembly/host-contract.ts +3 -3
  367. package/src/assembly/material/assembly.ts +25 -13
  368. package/src/assembly/material/pipeline-helpers.ts +53 -12
  369. package/src/assembly/material-shader-policy.ts +55 -0
  370. package/src/assembly/material-shader-prewarm.ts +21 -10
  371. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  372. package/src/assembly/recovery/renderer-recover.ts +41 -18
  373. package/src/assembly/render-feature-post-processes.ts +54 -0
  374. package/src/assembly/renderer-facade.ts +4 -1
  375. package/src/assembly/renderer-feature-inspection.ts +8 -2
  376. package/src/assembly/renderer-frame-transaction.ts +23 -0
  377. package/src/assembly/renderer-inspection.ts +28 -0
  378. package/src/assembly/skin-palette-owner.ts +1 -1
  379. package/src/assembly/temporal-post-process-prewarm.ts +3 -3
  380. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  381. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  382. package/src/assembly/webgpu-ready.ts +115 -61
  383. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  384. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  385. package/src/assembly/webgpu-renderer.ts +214 -252
  386. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  387. package/src/barrel-distortion.ts +247 -0
  388. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  389. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  390. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  391. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  392. package/src/cloud/density.ts +600 -0
  393. package/src/cloud/extract.ts +110 -0
  394. package/src/cloud/feature.ts +1810 -0
  395. package/src/cloud/frame-context.ts +42 -0
  396. package/src/cloud/inspection.ts +130 -0
  397. package/src/cloud/optics.ts +285 -0
  398. package/src/cloud/parameters.ts +255 -0
  399. package/src/cloud/resources.ts +122 -0
  400. package/src/cloud/shadow.ts +167 -0
  401. package/src/cloud/temporal.ts +226 -0
  402. package/src/components/barrel-distortion.ts +46 -0
  403. package/src/components/cloud-layer.ts +47 -0
  404. package/src/components/index.ts +2 -0
  405. package/src/components/instances.ts +18 -45
  406. package/src/components/sprite-instances.ts +1 -1
  407. package/src/construct-renderer.ts +1 -1
  408. package/src/dynamic-geometry.ts +12 -1
  409. package/src/errors/cloud.ts +103 -0
  410. package/src/errors/render.ts +44 -7
  411. package/src/examples/single-layer-medium-surface.ts +59 -0
  412. package/src/extract/camera.ts +15 -1
  413. package/src/extract/contracts.ts +5 -0
  414. package/src/features/barrel-distortion.ts +172 -0
  415. package/src/features/error-reporting.ts +26 -6
  416. package/src/features/host.ts +115 -14
  417. package/src/features/plan.ts +8 -0
  418. package/src/features/render-graph-contribution.ts +9 -0
  419. package/src/features/render-graph-raster.ts +45 -3
  420. package/src/features/targets.ts +66 -0
  421. package/src/features/types.ts +47 -0
  422. package/src/fullscreen-post-process-pass.ts +41 -9
  423. package/src/gpu-dirty-ranges.ts +48 -0
  424. package/src/gpu-driven/batch-topology.ts +53 -0
  425. package/src/gpu-driven/material-artifacts.ts +33 -25
  426. package/src/gpu-driven/prepared-draw.ts +12 -4
  427. package/src/gpu-driven/production-raster-material.ts +1894 -0
  428. package/src/gpu-driven/production-raster.ts +825 -784
  429. package/src/gpu-driven/resource-allocation.ts +142 -0
  430. package/src/gpu-driven/shadow-ownership.ts +71 -0
  431. package/src/gpu-driven/view-gpu.ts +164 -10
  432. package/src/gpu-scene.ts +75 -84
  433. package/src/hdrp-buffers.ts +4 -3
  434. package/src/ibl/skylight-bind-group.ts +49 -7
  435. package/src/index.ts +196 -9
  436. package/src/inspection-types.ts +162 -0
  437. package/src/instance-buffer-cache.ts +4 -4
  438. package/src/instances.ts +149 -569
  439. package/src/pbr-pipeline.ts +219 -13
  440. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  441. package/src/pipeline/standard-forward-lane.ts +313 -57
  442. package/src/pipeline/standard-output/graph.ts +8 -2
  443. package/src/pipeline/standard-output/types.ts +12 -1
  444. package/src/pipeline/standard-pipeline.ts +291 -18
  445. package/src/pipeline/standard-post.ts +90 -24
  446. package/src/pipeline-builder.ts +1 -1
  447. package/src/plugin.ts +4 -0
  448. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  449. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  450. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  451. package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
  452. package/src/record/__tests__/temporal-payload.unit.test.ts +1 -1
  453. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  454. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  455. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  456. package/src/record/barrel-distortion-frame.ts +110 -0
  457. package/src/record/dynamic-geometry-consumption.ts +39 -9
  458. package/src/record/fold-buckets.ts +852 -0
  459. package/src/record/fold-instance-buffer.ts +51 -0
  460. package/src/record/frame-lighting.ts +36 -2
  461. package/src/record/frame-snapshot.ts +39 -3
  462. package/src/record/frame.ts +217 -1084
  463. package/src/record/gpu-draw-receipts.ts +38 -0
  464. package/src/record/gpu-timing.ts +48 -5
  465. package/src/record/main-pass-geometry.ts +251 -82
  466. package/src/record/main-pass-material.ts +66 -4
  467. package/src/record/main-pass-sprite-draws.ts +7 -65
  468. package/src/record/main-pass.ts +156 -20
  469. package/src/record/motion-blur-receipt.ts +91 -0
  470. package/src/record/probe-blend-buffer.ts +180 -0
  471. package/src/record/recovery-pipeline.ts +35 -3
  472. package/src/record/render-context.ts +39 -3
  473. package/src/record/shadow-pass.ts +30 -1
  474. package/src/record/skybox-post-pass.ts +28 -4
  475. package/src/record/typed-frame-graph.ts +770 -134
  476. package/src/record/view-ubo.ts +26 -3
  477. package/src/record/volume-params.ts +225 -0
  478. package/src/recovery/render-system-candidate.ts +235 -42
  479. package/src/reflection/record-owner.ts +7 -3
  480. package/src/render-contract.ts +48 -4
  481. package/src/render-graph-primitives.ts +7 -1
  482. package/src/render-pipeline.ts +130 -0
  483. package/src/render-system-extract-tail.ts +57 -61
  484. package/src/render-system-extract.ts +74 -76
  485. package/src/render-system-presentation.ts +56 -0
  486. package/src/render-system-producer-roots.ts +96 -0
  487. package/src/render-system-residency.ts +159 -0
  488. package/src/render-system.ts +347 -363
  489. package/src/scene/probe-blend.ts +96 -4
  490. package/src/scene/render-scene.ts +349 -280
  491. package/src/ssao-buffers.ts +95 -2
  492. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  493. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  494. package/src/surface/dynamic-input.ts +658 -0
  495. package/src/surface/gpu-driven.ts +147 -0
  496. package/src/surface/submission-observation.ts +517 -0
  497. package/src/systems/skin-palette-allocator.ts +4 -1
  498. package/src/temporal/gpu.ts +101 -17
  499. package/src/temporal/inspection.ts +7 -1
  500. package/src/temporal/standard-scene-data.ts +5 -0
  501. package/src/temporal/target-coverage-attachment.ts +4 -0
  502. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  503. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  504. package/src/transmission/single-layer-medium.ts +322 -0
  505. package/src/typed-render-graph-primitives.ts +70 -2
  506. package/src/volume/component.ts +10 -9
  507. package/src/volume/extract.ts +11 -5
  508. package/src/volume/inspection.ts +3 -0
  509. package/src/volume/passes.ts +87 -4
  510. package/src/volume/resources.ts +2 -1
  511. package/dist/chunk-3RTCJTH4.mjs.map +0 -1
  512. package/dist/chunk-NB2S7ZRX.mjs.map +0 -1
  513. package/dist/chunk-NNQL7CB3.mjs +0 -1713
  514. package/dist/chunk-NNQL7CB3.mjs.map +0 -1
  515. package/dist/chunk-OO3DRYK7.mjs.map +0 -1
  516. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
  517. package/dist/chunk-PLAXOOX5.mjs.map +0 -1
  518. package/dist/chunk-VOZWDM3L.mjs.map +0 -1
@@ -4,8 +4,11 @@ import {
4
4
  type GraphResourceResolver,
5
5
  RenderGraphBuilder,
6
6
  RenderGraphError,
7
+ type RenderGraphGenerationAllocationEntry,
8
+ type RenderGraphGenerationAllocationInspection,
7
9
  type RenderGraphPassInstrumentation,
8
10
  type RenderGraphPassInstrumentationScope,
11
+ type RenderGraphResourceAllocationInspection,
9
12
  } from '@forgeax/engine-render-graph';
10
13
  import type {
11
14
  BindGroup,
@@ -23,6 +26,7 @@ import { RhiError } from '@forgeax/engine-rhi';
23
26
  import { err, ok, type Result } from '@forgeax/engine-types';
24
27
  import { executeRendererFrameTransaction } from '../assembly/renderer-frame-transaction';
25
28
  import { standardBloomAdmitted } from '../bloom-admission';
29
+ import { CLOUD_LAYER_FEATURE_IDENTITY } from '../cloud/feature';
26
30
  import { type RenderError, RenderFeatureStageFailedError } from '../errors/render';
27
31
  import { depthOfFieldTopology } from '../features/depth-of-field/depth-of-field-feature';
28
32
  import { resolveDepthOfFieldFrameParams } from '../features/depth-of-field/depth-of-field-params';
@@ -64,11 +68,7 @@ import {
64
68
  } from '../gpu-texture-usage';
65
69
  import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from '../gpu-usage';
66
70
  import { createStandardSurfaceLightingBindGroup } from '../hdrp-buffers';
67
- import type {
68
- MotionBlurExecutionPass,
69
- MotionBlurExecutionReceipt,
70
- RenderFeatureGraphInspection,
71
- } from '../inspection-types';
71
+ import type { RenderFeatureGraphInspection } from '../inspection-types';
72
72
  import type { RenderExtent } from '../pipeline/render-extent';
73
73
  import {
74
74
  type StandardTopologyInputValue,
@@ -99,11 +99,13 @@ import type { RenderTarget } from '../targets/contracts';
99
99
  import type { RenderTargetPhysical } from '../targets/physical';
100
100
  import {
101
101
  abortTemporalGpuSubmit,
102
+ CLOUD_HISTORY_FORMATS,
102
103
  commitTemporalGpuSubmit,
103
104
  getTemporalGpuState,
104
105
  hasPendingTemporalGpuSubmit,
105
106
  retireTemporalGpuState,
106
107
  retireTemporalGpuStateAfterFence,
108
+ stageTemporalGpuSubmit,
107
109
  TEMPORAL_HISTORY_FORMATS,
108
110
  temporalReadIndex,
109
111
  temporalWriteIndex,
@@ -124,6 +126,7 @@ import type { GpuPassTimingPassIdentity } from './gpu-pass-timing/contract.js';
124
126
  import type { GpuPassTimingCapture, GpuPassTimingSession } from './gpu-pass-timing/session.js';
125
127
  import type { GpuTimingCapture } from './gpu-timing';
126
128
  import { encodeMainPass } from './main-pass';
129
+ import { deriveMotionBlurExecutionReceipt } from './motion-blur-receipt';
127
130
  import type {
128
131
  _InternalRenderPipelineContext,
129
132
  PipelineState,
@@ -327,6 +330,125 @@ function importTemporalHistoryTargets(
327
330
  };
328
331
  }
329
332
 
333
+ type CloudHistoryKind = keyof typeof CLOUD_HISTORY_FORMATS;
334
+
335
+ function cloudHistorySurface(
336
+ state: import('../temporal/gpu').TemporalGpuState,
337
+ kind: CloudHistoryKind,
338
+ index: 0 | 1,
339
+ ): { readonly texture: Texture; readonly view: TextureView } {
340
+ if (!state.cloudHistoryEnabled) {
341
+ throw new Error('cloud history target resolved without a cloud-enabled temporal state');
342
+ }
343
+ switch (kind) {
344
+ case 'radiance':
345
+ if (state.cloudRadiance === undefined)
346
+ throw new Error('cloud radiance history is unavailable');
347
+ return state.cloudRadiance[index];
348
+ case 'transmittance':
349
+ if (state.cloudTransmittance === undefined)
350
+ throw new Error('cloud transmittance history is unavailable');
351
+ return state.cloudTransmittance[index];
352
+ case 'depth':
353
+ if (state.cloudDepth === undefined) throw new Error('cloud depth history is unavailable');
354
+ return state.cloudDepth[index];
355
+ }
356
+ }
357
+
358
+ /** Import one renderer-owned cloud history attachment into the typed graph. */
359
+ function importCloudHistoryTarget(
360
+ graph: RenderGraphBuilder<RenderPipelineFrame>,
361
+ label: string,
362
+ kind: CloudHistoryKind,
363
+ role: 'read' | 'write',
364
+ ): import('../render-pipeline').RenderPipelineTarget {
365
+ const format = CLOUD_HISTORY_FORMATS[kind];
366
+ const texture = graph.importTexture(
367
+ label,
368
+ {
369
+ format,
370
+ // Cloud transport/history is intentionally half-resolution. The
371
+ // renderer-owned temporal state allocates the same ceil(width/2) extent
372
+ // and the full-resolution resolve reconstructs scene colour from it.
373
+ size: 'half-surface',
374
+ usage:
375
+ GPU_TEXTURE_USAGE_COPY_SRC |
376
+ GPU_TEXTURE_USAGE_COPY_DST |
377
+ GPU_TEXTURE_USAGE_RENDER_ATTACHMENT |
378
+ GPU_TEXTURE_USAGE_TEXTURE_BINDING,
379
+ domain: 'linear-hdr',
380
+ },
381
+ (frame) => {
382
+ const internal = frame as _InternalRenderPipelineContext;
383
+ const state = getTemporalGpuState(
384
+ internal.frameState,
385
+ internal.runtime.device,
386
+ internal.runtime.deviceScope,
387
+ frame.targetW,
388
+ frame.targetH,
389
+ true,
390
+ );
391
+ const index = role === 'read' ? temporalReadIndex(state) : temporalWriteIndex(state);
392
+ return cloudHistorySurface(state, kind, index).texture;
393
+ },
394
+ );
395
+ if (!texture.ok) throw texture.error;
396
+ const view = graph.importView(
397
+ texture.value,
398
+ { label: `${label}.view`, dimension: '2d' },
399
+ (frame) => {
400
+ const internal = frame as _InternalRenderPipelineContext;
401
+ const state = getTemporalGpuState(
402
+ internal.frameState,
403
+ internal.runtime.device,
404
+ internal.runtime.deviceScope,
405
+ frame.targetW,
406
+ frame.targetH,
407
+ true,
408
+ );
409
+ const index = role === 'read' ? temporalReadIndex(state) : temporalWriteIndex(state);
410
+ return cloudHistorySurface(state, kind, index).view;
411
+ },
412
+ );
413
+ if (!view.ok) throw view.error;
414
+ return { texture: texture.value, view: view.value, format, sampleCount: 1 };
415
+ }
416
+
417
+ function importCloudHistoryTargets(
418
+ graph: RenderGraphBuilder<RenderPipelineFrame>,
419
+ ): NonNullable<
420
+ import('../render-pipeline').RenderPipelineBuildContext<RenderPipelineFrame>['cloudHistory']
421
+ > {
422
+ return {
423
+ currentRadiance: importCloudHistoryTarget(
424
+ graph,
425
+ 'cloud-history-radiance-current',
426
+ 'radiance',
427
+ 'write',
428
+ ),
429
+ previousRadiance: importCloudHistoryTarget(
430
+ graph,
431
+ 'cloud-history-radiance-previous',
432
+ 'radiance',
433
+ 'read',
434
+ ),
435
+ currentTransmittance: importCloudHistoryTarget(
436
+ graph,
437
+ 'cloud-history-transmittance-current',
438
+ 'transmittance',
439
+ 'write',
440
+ ),
441
+ previousTransmittance: importCloudHistoryTarget(
442
+ graph,
443
+ 'cloud-history-transmittance-previous',
444
+ 'transmittance',
445
+ 'read',
446
+ ),
447
+ currentDepth: importCloudHistoryTarget(graph, 'cloud-history-depth-current', 'depth', 'write'),
448
+ previousDepth: importCloudHistoryTarget(graph, 'cloud-history-depth-previous', 'depth', 'read'),
449
+ };
450
+ }
451
+
330
452
  function importSsrHistoryTarget(
331
453
  graph: RenderGraphBuilder<RenderPipelineFrame>,
332
454
  label: string,
@@ -982,6 +1104,7 @@ export interface RenderFeatureGraphRuntimeState {
982
1104
  string,
983
1105
  import('../fullscreen-post-process-pass').PostProcessShaderEntry
984
1106
  >;
1107
+ postProcessIdentities: readonly string[];
985
1108
  }
986
1109
 
987
1110
  /** The exact fields that define one validated feature-plan collection. */
@@ -1042,6 +1165,8 @@ export interface RenderFeatureGraphCandidate {
1042
1165
  string,
1043
1166
  import('../fullscreen-post-process-pass').PostProcessShaderEntry
1044
1167
  >;
1168
+ /** Public Standard post-effect identities; graph-local resources stay internal. */
1169
+ readonly postProcessIdentities?: readonly string[];
1045
1170
  /**
1046
1171
  * Physical transient graphics resources are imported into the compiled
1047
1172
  * graph. Their lease therefore needs a new graph even when the declarative
@@ -1065,6 +1190,216 @@ export interface RenderFeatureGraphCandidate {
1065
1190
 
1066
1191
  const featureGraphStates = new WeakMap<RenderSystemInternals, RenderFeatureGraphRuntimeState>();
1067
1192
  const graphDeviceGenerations = new WeakMap<object, number>();
1193
+ const graphRetirementFailures = new WeakMap<
1194
+ RenderFrameState,
1195
+ Set<CompiledRenderGraph<RenderPipelineFrame>>
1196
+ >();
1197
+
1198
+ type GraphGenerationRole = 'active' | 'candidate' | 'retiring';
1199
+ type GraphGenerationStatus = 'active' | 'retiring' | 'failed' | 'retired';
1200
+
1201
+ interface GraphGenerationAllocationRecord {
1202
+ readonly graph: CompiledRenderGraph<RenderPipelineFrame>;
1203
+ status: GraphGenerationStatus;
1204
+ allocation: RenderGraphResourceAllocationInspection | undefined;
1205
+ }
1206
+
1207
+ /**
1208
+ * One stable allocation owner for a RenderSystem lifetime. Recovery builds a
1209
+ * detached frame state, but that state intentionally shares this owner before
1210
+ * it can publish a candidate into the active RenderSystem. Graph records stay
1211
+ * here across asynchronous retirement so a successful replacement cannot
1212
+ * erase the overlap peak before the next public inspection.
1213
+ */
1214
+ interface GraphGenerationAllocationOwner {
1215
+ readonly records: Map<CompiledRenderGraph<RenderPipelineFrame>, GraphGenerationAllocationRecord>;
1216
+ peakBytes: number;
1217
+ }
1218
+
1219
+ const graphGenerationAllocationOwners = new WeakMap<
1220
+ RenderFrameState,
1221
+ GraphGenerationAllocationOwner
1222
+ >();
1223
+
1224
+ function graphGenerationAllocationOwner(
1225
+ frameState: RenderFrameState,
1226
+ ): GraphGenerationAllocationOwner {
1227
+ const existing = graphGenerationAllocationOwners.get(frameState);
1228
+ if (existing !== undefined) return existing;
1229
+ const created: GraphGenerationAllocationOwner = { records: new Map(), peakBytes: 0 };
1230
+ graphGenerationAllocationOwners.set(frameState, created);
1231
+ return created;
1232
+ }
1233
+
1234
+ function graphRetirementFailureSet(
1235
+ frameState: RenderFrameState,
1236
+ ): Set<CompiledRenderGraph<RenderPipelineFrame>> {
1237
+ const existing = graphRetirementFailures.get(frameState);
1238
+ if (existing !== undefined) return existing;
1239
+ const created = new Set<CompiledRenderGraph<RenderPipelineFrame>>();
1240
+ graphRetirementFailures.set(frameState, created);
1241
+ return created;
1242
+ }
1243
+
1244
+ /** Share the renderer-owned graph ledger with a detached recovery frame state. */
1245
+ export function shareRenderGraphGenerationAllocationOwner(
1246
+ source: RenderFrameState,
1247
+ target: RenderFrameState,
1248
+ ): void {
1249
+ graphGenerationAllocationOwners.set(target, graphGenerationAllocationOwner(source));
1250
+ graphRetirementFailures.set(target, graphRetirementFailureSet(source));
1251
+ }
1252
+
1253
+ function observeGraphGenerationAllocation(frameState: RenderFrameState): void {
1254
+ const owner = graphGenerationAllocationOwner(frameState);
1255
+ let simultaneousBytes = 0;
1256
+ for (const record of owner.records.values()) {
1257
+ if (record.status === 'retired') continue;
1258
+ const allocation = record.graph.inspect().resourceAllocation;
1259
+ record.allocation = allocation;
1260
+ if (allocation !== undefined) {
1261
+ simultaneousBytes += allocation.liveBytes + allocation.pendingRetirementBytes;
1262
+ }
1263
+ }
1264
+ owner.peakBytes = Math.max(owner.peakBytes, simultaneousBytes);
1265
+ }
1266
+
1267
+ function trackGraphGeneration(
1268
+ frameState: RenderFrameState,
1269
+ graph: CompiledRenderGraph<RenderPipelineFrame>,
1270
+ ): void {
1271
+ const owner = graphGenerationAllocationOwner(frameState);
1272
+ const record = owner.records.get(graph);
1273
+ if (record === undefined) {
1274
+ owner.records.set(graph, { graph, status: 'active', allocation: undefined });
1275
+ } else if (record.status === 'retired') {
1276
+ record.status = 'active';
1277
+ }
1278
+ observeGraphGenerationAllocation(frameState);
1279
+ }
1280
+
1281
+ function setGraphGenerationRetiring(
1282
+ frameState: RenderFrameState,
1283
+ graph: CompiledRenderGraph<RenderPipelineFrame>,
1284
+ ): void {
1285
+ const owner = graphGenerationAllocationOwner(frameState);
1286
+ const record = owner.records.get(graph);
1287
+ if (record === undefined) {
1288
+ owner.records.set(graph, { graph, status: 'retiring', allocation: undefined });
1289
+ } else if (record.status !== 'failed') {
1290
+ record.status = 'retiring';
1291
+ }
1292
+ }
1293
+
1294
+ function setGraphGenerationRetirementResult(
1295
+ frameState: RenderFrameState,
1296
+ graph: CompiledRenderGraph<RenderPipelineFrame>,
1297
+ succeeded: boolean,
1298
+ ): void {
1299
+ const owner = graphGenerationAllocationOwner(frameState);
1300
+ const record = owner.records.get(graph);
1301
+ if (record !== undefined) record.status = succeeded ? 'retired' : 'failed';
1302
+ observeGraphGenerationAllocation(frameState);
1303
+ // A successful graph no longer owns any handles. Keep the renderer-level
1304
+ // peak scalar, but release the graph object from the generation map; failed
1305
+ // and pending owners remain retained for recovery inspection.
1306
+ if (succeeded) owner.records.delete(graph);
1307
+ }
1308
+
1309
+ /**
1310
+ * Project the actual RenderSystem graph-generation owners as one detached
1311
+ * allocation receipt. The candidate and fallback graph can overlap before a
1312
+ * submit settles; failed retirement stays visible until the frame owner is
1313
+ * discarded instead of disappearing with the asynchronous Set entry.
1314
+ */
1315
+ export function inspectRenderGraphGenerationAllocation(
1316
+ frameState: RenderFrameState,
1317
+ ): RenderGraphGenerationAllocationInspection {
1318
+ const rolesByGraph = new Map<
1319
+ CompiledRenderGraph<RenderPipelineFrame>,
1320
+ Set<GraphGenerationRole>
1321
+ >();
1322
+ const add = (
1323
+ graph: CompiledRenderGraph<RenderPipelineFrame> | null | undefined,
1324
+ role: GraphGenerationRole,
1325
+ ) => {
1326
+ if (graph === null || graph === undefined) return;
1327
+ const roles = rolesByGraph.get(graph);
1328
+ if (roles === undefined) rolesByGraph.set(graph, new Set([role]));
1329
+ else roles.add(role);
1330
+ };
1331
+ add(frameState.compiledFrameGraph, 'active');
1332
+ add(frameState.volumetricFogCandidateGraph, 'candidate');
1333
+ add(frameState.volumetricFogPreviousGraph, 'retiring');
1334
+ add(frameState.depthOfFieldCandidateGraph, 'candidate');
1335
+ add(frameState.depthOfFieldPreviousGraph, 'retiring');
1336
+ add(frameState.barrelDistortionCandidateGraph, 'candidate');
1337
+ add(frameState.barrelDistortionPreviousGraph, 'retiring');
1338
+ for (const graph of frameState.retiredCompiledFrameGraphs) add(graph, 'retiring');
1339
+ for (const graph of graphRetirementFailureSet(frameState)) add(graph, 'retiring');
1340
+
1341
+ const owner = graphGenerationAllocationOwner(frameState);
1342
+ const entries: RenderGraphGenerationAllocationEntry[] = [];
1343
+ let unavailableGenerationCount = 0;
1344
+ for (const [graph, record] of owner.records) {
1345
+ if (record.status === 'retired') continue;
1346
+ const roles = rolesByGraph.get(graph) ?? new Set<GraphGenerationRole>();
1347
+ if (record.status === 'retiring' || record.status === 'failed') roles.add('retiring');
1348
+ if (roles.size === 0) {
1349
+ // A candidate can be detached from the active state's role fields while
1350
+ // it is still being prepared. Keep its owner record observable until
1351
+ // the publication or cleanup event settles it.
1352
+ roles.add(record.status === 'active' ? 'active' : 'retiring');
1353
+ }
1354
+ // Inspection is a projection of the graph's current ledger. The cached
1355
+ // snapshot is only for event-time peak updates; using it here would make
1356
+ // a successfully fenced graph look live after its retirement started.
1357
+ const allocation = graph.inspect().resourceAllocation;
1358
+ record.allocation = allocation;
1359
+ if (allocation === undefined) {
1360
+ unavailableGenerationCount += 1;
1361
+ continue;
1362
+ }
1363
+ const retirement =
1364
+ record.status === 'failed' ? 'failed' : roles.has('retiring') ? 'pending' : 'active';
1365
+ entries.push({
1366
+ generation: graph.inspect().generation,
1367
+ roles: Object.freeze([...roles]),
1368
+ retirement,
1369
+ allocation,
1370
+ });
1371
+ }
1372
+ entries.sort((left, right) => left.generation - right.generation);
1373
+ const liveBytes = entries.reduce((sum, entry) => sum + entry.allocation.liveBytes, 0);
1374
+ const pendingRetirementBytes = entries.reduce(
1375
+ (sum, entry) => sum + entry.allocation.pendingRetirementBytes,
1376
+ 0,
1377
+ );
1378
+ const peakBytes = Math.max(owner.peakBytes, liveBytes + pendingRetirementBytes);
1379
+ const failedEntries = entries.filter((entry) => entry.retirement === 'failed');
1380
+ return Object.freeze({
1381
+ unit: 'engine-allocation-bytes',
1382
+ physicalResidency: 'unknown',
1383
+ availability:
1384
+ entries.length === 0
1385
+ ? unavailableGenerationCount === 0
1386
+ ? 'complete'
1387
+ : 'unavailable'
1388
+ : unavailableGenerationCount === 0
1389
+ ? 'complete'
1390
+ : 'partial',
1391
+ unavailableGenerationCount,
1392
+ entries: Object.freeze(entries.map((entry) => Object.freeze(entry))),
1393
+ liveBytes,
1394
+ pendingRetirementBytes,
1395
+ peakBytes,
1396
+ failedRetirementCount: failedEntries.length,
1397
+ failedRetirementBytes: failedEntries.reduce(
1398
+ (sum, entry) => sum + entry.allocation.liveBytes + entry.allocation.pendingRetirementBytes,
1399
+ 0,
1400
+ ),
1401
+ });
1402
+ }
1068
1403
 
1069
1404
  interface MutableRenderFeatureGraphInspection {
1070
1405
  planRevisionState: RenderFeaturePlanRevisionState;
@@ -1230,6 +1565,7 @@ export function getRenderFeatureGraphState(
1230
1565
  const created: RenderFeatureGraphRuntimeState = {
1231
1566
  plans: [],
1232
1567
  fullscreenEffects: new Map(),
1568
+ postProcessIdentities: [],
1233
1569
  };
1234
1570
  featureGraphStates.set(internals, created);
1235
1571
  return created;
@@ -1239,6 +1575,7 @@ export function resetRenderFeatureGraphState(internals: RenderSystemInternals):
1239
1575
  const state = getRenderFeatureGraphState(internals);
1240
1576
  state.plans = [];
1241
1577
  state.fullscreenEffects = new Map();
1578
+ state.postProcessIdentities = [];
1242
1579
  }
1243
1580
 
1244
1581
  export function reportRenderFeatureGraphError(
@@ -1357,6 +1694,7 @@ function commitFeatureCandidate(
1357
1694
  const state = getRenderFeatureGraphState(internals);
1358
1695
  state.plans = candidate.plans;
1359
1696
  state.fullscreenEffects = candidate.fullscreenEffects;
1697
+ state.postProcessIdentities = candidate.postProcessIdentities ?? [];
1360
1698
  }
1361
1699
 
1362
1700
  function topologyOf(
@@ -1378,6 +1716,7 @@ function topologyOf(
1378
1716
  standardLighting: StandardTopologyInputValue | undefined,
1379
1717
  ssr: SsrSpatialAdmission | undefined,
1380
1718
  renderExtent: RenderExtent | undefined,
1719
+ surfaceMediumActive: boolean | undefined,
1381
1720
  analyticFog: boolean,
1382
1721
  ): Result<RenderPipelineTopology, RhiError> {
1383
1722
  const cascadeCount = Math.max(1, Math.min(4, Math.round(lights.cascadeCount ?? 1))) as
@@ -1399,10 +1738,9 @@ function topologyOf(
1399
1738
  floatRenderAttachment: internals.device.caps.rgba16floatRenderable,
1400
1739
  });
1401
1740
  if (!surfaceProfile.ok) return surfaceProfile;
1402
- const featurePostEffects = [
1403
- ...(featureGraphCandidate?.fullscreenEffects ??
1404
- getRenderFeatureGraphState(internals).fullscreenEffects),
1405
- ].map(([id]) => id);
1741
+ const featurePostEffects =
1742
+ featureGraphCandidate?.postProcessIdentities ??
1743
+ getRenderFeatureGraphState(internals).postProcessIdentities;
1406
1744
  const featurePostEffectDeclarations = [
1407
1745
  ...(featureGraphCandidate?.fullscreenEffects ??
1408
1746
  getRenderFeatureGraphState(internals).fullscreenEffects),
@@ -1461,6 +1799,7 @@ function topologyOf(
1461
1799
  // positive intensity changes update the recorder UBO in place.
1462
1800
  bloom: standardBloomAdmitted(camera) ? 'on' : 'off',
1463
1801
  bloomIntensity: camera.bloomIntensity,
1802
+ barrelDistortion: !clearOnly && (camera.barrelDistortion?.strength ?? 0) > 0,
1464
1803
  ...(depthOfField === undefined ? {} : { depthOfField }),
1465
1804
  },
1466
1805
  atmosphere: !clearOnly && frameState.environmentFrame?.source.kind === 'atmosphere',
@@ -1514,6 +1853,16 @@ function topologyOf(
1514
1853
  return {
1515
1854
  enabled: true,
1516
1855
  format: volumetricFog.densityAsset.format,
1856
+ additional: (volumetricFog.additional ?? []).flatMap((member) =>
1857
+ member.densityAsset?.shape.viewDimension === '3d'
1858
+ ? [
1859
+ {
1860
+ format: member.densityAsset.format,
1861
+ extent: member.densityAsset.shape.extent,
1862
+ },
1863
+ ]
1864
+ : [],
1865
+ ),
1517
1866
  extent: {
1518
1867
  width: volumetricFog.densityAsset.shape.extent.width,
1519
1868
  height: volumetricFog.densityAsset.shape.extent.height,
@@ -1521,7 +1870,7 @@ function topologyOf(
1521
1870
  },
1522
1871
  froxelExtent: deriveVolumetricFogExtent(
1523
1872
  { width, height },
1524
- volumeProfile.depth,
1873
+ volumeProfile.depth * (1 + (volumetricFog.additional?.length ?? 0)),
1525
1874
  volumeProfile.tileSize,
1526
1875
  ),
1527
1876
  resolvedExtent: deriveVolumetricFogResolvedExtent(
@@ -1562,6 +1911,9 @@ function topologyOf(
1562
1911
  activeCount: 0,
1563
1912
  needsRoughMips: false,
1564
1913
  },
1914
+ ...((surfaceMediumActive ?? gpuDriven?.surfaceSubmission !== undefined)
1915
+ ? { singleLayerMedium: true }
1916
+ : {}),
1565
1917
  });
1566
1918
  }
1567
1919
 
@@ -1586,6 +1938,30 @@ function targetResolver(
1586
1938
  ...(named.resolveTarget === undefined ? {} : { resolveTarget: named.resolveTarget }),
1587
1939
  };
1588
1940
  }
1941
+ if (typeof resource === 'string') {
1942
+ const target = targets.find((candidate) => candidate.name === resource);
1943
+ if (target !== undefined) {
1944
+ return {
1945
+ texture: target.texture,
1946
+ view: target.view,
1947
+ ...(target.resolveTarget === undefined ? {} : { resolveTarget: target.resolveTarget }),
1948
+ };
1949
+ }
1950
+ // Standard lanes expose the depth attachment as a semantic target
1951
+ // without duplicating the public `depth` name on every concrete graph
1952
+ // projection. Keep the string alias stable for feature-authored
1953
+ // fullscreen bindings while preserving the typed target identity.
1954
+ if (resource === 'depth') {
1955
+ const depth = targets.find((candidate) => candidate.kind === 'scene-depth');
1956
+ if (depth !== undefined) {
1957
+ return {
1958
+ texture: depth.texture,
1959
+ view: depth.view,
1960
+ ...(depth.resolveTarget === undefined ? {} : { resolveTarget: depth.resolveTarget }),
1961
+ };
1962
+ }
1963
+ }
1964
+ }
1589
1965
  }
1590
1966
  if (isSceneDataTarget(resource)) {
1591
1967
  const semantic = semanticTargets.find(
@@ -1597,11 +1973,27 @@ function targetResolver(
1597
1973
  : { texture: semantic.texture, view: semantic.view };
1598
1974
  }
1599
1975
  if (!isRenderFeatureTargetHandle(resource)) return undefined;
1976
+ // A typed feature handle is an identity, not a format request. Falling
1977
+ // back to another same-format target can silently bind scene color in
1978
+ // place of a missing cloud history/shadow resource and turn a producer
1979
+ // failure into plausible but incorrect pixels.
1980
+ // Standard lanes expose scene depth as a semantic target without a
1981
+ // duplicated public name, while feature plans use the stable `depth`
1982
+ // handle name. Preserve that one semantic alias here as well as above for
1983
+ // string-authored fullscreen bindings.
1600
1984
  const target = targets.find(
1601
1985
  (candidate) =>
1602
1986
  candidate.kind === resource.kind &&
1603
1987
  candidate.format === resource.format &&
1604
- candidate.sampleCount === resource.sampleCount,
1988
+ candidate.sampleCount === resource.sampleCount &&
1989
+ (resource.name === undefined ||
1990
+ candidate.name === resource.name ||
1991
+ (resource.kind === 'scene-color' &&
1992
+ resource.name === 'color' &&
1993
+ candidate.kind === 'scene-color') ||
1994
+ (resource.kind === 'scene-depth' &&
1995
+ resource.name === 'depth' &&
1996
+ candidate.kind === 'scene-depth')),
1605
1997
  );
1606
1998
  return target === undefined
1607
1999
  ? undefined
@@ -1621,6 +2013,9 @@ function resolveTargetBindings(
1621
2013
  };
1622
2014
  readonly resources: GraphResourceResolver;
1623
2015
  readonly resolveTarget: RenderFeatureGraphTargetResolver;
2016
+ readonly resolveGpuResource?: (
2017
+ name: string,
2018
+ ) => import('../features/prepared-gpu-work').RenderFeatureResolvedGpuBuffer | undefined;
1624
2019
  },
1625
2020
  featurePostProcessEntries?: ReadonlyMap<string, PostProcessShaderEntry>,
1626
2021
  ): RenderFeatureGraphBindingsResolution | undefined {
@@ -1632,6 +2027,136 @@ function resolveTargetBindings(
1632
2027
  const sceneDepth = descriptor?.values.sceneDepth;
1633
2028
  const sceneDepthBinding = descriptor?.values.sceneDepthBinding;
1634
2029
  if (pipeline === undefined) return undefined;
2030
+ if (descriptor?.values.view === true) {
2031
+ const viewBindGroup = buildPerFrameBindGroups(
2032
+ frame.runtime as RenderSystemInternals,
2033
+ frame.frameState,
2034
+ frame.pipelineState,
2035
+ true,
2036
+ frame.bindGroupCounts,
2037
+ {
2038
+ directionalShadow: frame.frameState.currentDirectionalShadowView ?? undefined,
2039
+ spotShadow: frame.frameState.currentSpotShadowView ?? undefined,
2040
+ },
2041
+ true,
2042
+ frame.standardLighting,
2043
+ ).viewBindGroup;
2044
+ return viewBindGroup === null ? undefined : { handle: viewBindGroup, dynamicOffsets: [0, 0] };
2045
+ }
2046
+ // Generic fullscreen producers (for example the CloudLayer scene pass)
2047
+ // share the renderer-owned post-process bind-group contract without opting
2048
+ // into the temporal two-input path below. Their input/depth aliases are
2049
+ // resolved from the active typed graph, so they cannot accidentally sample
2050
+ // a stale surface view or manufacture a second resource owner.
2051
+ if (descriptor?.values.fullscreen === true && !isTemporalFullscreenBinding(descriptor.values)) {
2052
+ const shader = descriptor.values.shader;
2053
+ if (typeof shader !== 'string') return undefined;
2054
+ const entry =
2055
+ featurePostProcessEntries?.get(shader) ?? frame.runtime.lookupPostProcess?.(shader);
2056
+ if (entry === undefined) return undefined;
2057
+ const depthFallback = descriptor.values.depthFallback === true;
2058
+ const depthValue = descriptor.values.depth;
2059
+ const depthTarget =
2060
+ depthFallback || depthValue === undefined
2061
+ ? undefined
2062
+ : input.resolveTarget(depthValue as never);
2063
+ const fullscreen = buildFullscreenPostProcessPass(
2064
+ { device: frame.runtime.device, errorRegistry: frame.runtime.errorRegistry },
2065
+ entry,
2066
+ depthTarget?.resolveTarget !== undefined,
2067
+ );
2068
+ if (fullscreen === null || fullscreen.sampler === null) return undefined;
2069
+ const inputDisabled = descriptor.values.input === false;
2070
+ const inputTarget = inputDisabled
2071
+ ? undefined
2072
+ : input.resolveTarget(descriptor.values.input as never);
2073
+ const inputView =
2074
+ inputTarget === undefined
2075
+ ? { ok: true as const, value: frame.pipelineState.defaultWhiteTextureView }
2076
+ : input.resources.textureView(inputTarget.view);
2077
+ if (!inputView.ok) return undefined;
2078
+ const depthView = depthFallback
2079
+ ? frame.pipelineState.shadowFallbackTextureView
2080
+ : depthTarget === undefined
2081
+ ? undefined
2082
+ : (() => {
2083
+ const depthTexture = input.resources.texture(depthTarget.texture);
2084
+ if (!depthTexture.ok) return undefined;
2085
+ const created = frame.runtime.device.createTextureView(depthTexture.value as Texture, {
2086
+ aspect: 'depth-only',
2087
+ dimension: '2d',
2088
+ });
2089
+ return created.ok ? created.value : undefined;
2090
+ })();
2091
+ if (
2092
+ depthTarget !== undefined &&
2093
+ (depthView === undefined || fullscreen.depthSampler === null)
2094
+ ) {
2095
+ return undefined;
2096
+ }
2097
+ const additionalViews: { readonly binding: number; readonly view: TextureView }[] = [];
2098
+ const additionalNames = descriptor.values.additionalTextures;
2099
+ if (Array.isArray(additionalNames)) {
2100
+ for (const [index, value] of additionalNames.entries()) {
2101
+ if (typeof value !== 'string') return undefined;
2102
+ const target = input.resolveTarget(value);
2103
+ if (target === undefined) return undefined;
2104
+ const view = input.resources.textureView(target.view);
2105
+ if (!view.ok) return undefined;
2106
+ const binding = fullscreen.extraColorBindings[index];
2107
+ if (binding === undefined) return undefined;
2108
+ additionalViews.push({ binding, view: view.value });
2109
+ }
2110
+ }
2111
+ const layout = pipeline.getBindGroupLayout?.(1);
2112
+ if (layout === undefined) return undefined;
2113
+ const paramsBuffer = frame.runtime.getPostProcessParamsBuffer?.(shader);
2114
+ if (entry.params !== undefined && paramsBuffer === undefined) return undefined;
2115
+ if (entry.params !== undefined && paramsBuffer !== undefined) {
2116
+ const data = frame.postProcessParams.get(shader) ?? entry.params.defaultValue;
2117
+ if (data.byteLength !== entry.params.byteSize) return undefined;
2118
+ const written = frame.runtime.device.queue.writeBuffer(paramsBuffer, 0, data);
2119
+ if (!written.ok) return undefined;
2120
+ }
2121
+ const createdBindGroup = createFullscreenBindGroup(
2122
+ frame.runtime.device,
2123
+ layout,
2124
+ inputView.value,
2125
+ fullscreen.sampler,
2126
+ paramsBuffer,
2127
+ depthView ?? null,
2128
+ fullscreen.depthSampler,
2129
+ additionalViews,
2130
+ fullscreen.extraStorageBindings.map((binding, index) => {
2131
+ const names = descriptor.values.storageBuffers;
2132
+ const name = Array.isArray(names) ? names[index] : undefined;
2133
+ if (typeof name !== 'string') throw new Error('fullscreen storage buffer name missing');
2134
+ const buffer = input.resolveGpuResource?.(name);
2135
+ if (buffer === undefined) throw new Error(`fullscreen storage buffer unavailable: ${name}`);
2136
+ return { binding, buffer: buffer.buffer };
2137
+ }),
2138
+ );
2139
+ if (createdBindGroup !== null) {
2140
+ // Cloud history is a producer of the shared temporal ping-pong state even
2141
+ // when TAA is disabled. Stage the write at encode time; execute's finish
2142
+ // guard and submit transaction then give it the same abort/commit/fence
2143
+ // semantics as the TAA resolve.
2144
+ if (shader === 'cloud-layer-resolve') {
2145
+ const state = getTemporalGpuState(
2146
+ frame.frameState,
2147
+ frame.runtime.device,
2148
+ frame.runtime.deviceScope,
2149
+ frame.targetW,
2150
+ frame.targetH,
2151
+ true,
2152
+ );
2153
+ stageTemporalGpuSubmit(state);
2154
+ frame.frameState.temporalGpuState = state;
2155
+ }
2156
+ return createdBindGroup;
2157
+ }
2158
+ return undefined;
2159
+ }
1635
2160
  if (descriptor?.values.fullscreen === true && isTemporalFullscreenBinding(descriptor.values)) {
1636
2161
  const shader = descriptor.values.shader;
1637
2162
  if (typeof shader !== 'string') return undefined;
@@ -1646,11 +2171,9 @@ function resolveTargetBindings(
1646
2171
  );
1647
2172
  if (fullscreen === null || fullscreen.sampler === null) return undefined;
1648
2173
  const inputTarget = input.resolveTarget(descriptor.values.input as never);
1649
- const temporalTarget = input.resolveTarget(descriptor.values.temporal as never);
1650
- if (inputTarget === undefined || temporalTarget === undefined) return undefined;
2174
+ if (inputTarget === undefined) return undefined;
1651
2175
  const inputView = input.resources.textureView(inputTarget.view);
1652
- const temporalView = input.resources.textureView(temporalTarget.view);
1653
- if (!inputView.ok || !temporalView.ok) return undefined;
2176
+ if (!inputView.ok) return undefined;
1654
2177
  const layout = pipeline.getBindGroupLayout?.(1);
1655
2178
  if (layout === undefined) return undefined;
1656
2179
  const depth = input.resolveTarget(descriptor.values.depth as never);
@@ -1669,13 +2192,41 @@ function resolveTargetBindings(
1669
2192
  if (depth !== undefined && (depthView === undefined || fullscreen.depthSampler === null)) {
1670
2193
  return undefined;
1671
2194
  }
2195
+
2196
+ const paramsBuffer = frame.runtime.getPostProcessParamsBuffer?.(shader);
2197
+ if (entry.params !== undefined && paramsBuffer === undefined) return undefined;
2198
+ if (entry.params !== undefined && paramsBuffer !== undefined) {
2199
+ const data = frame.postProcessParams.get(shader) ?? entry.params.defaultValue;
2200
+ if (data.byteLength !== entry.params.byteSize) return undefined;
2201
+ const written = frame.runtime.device.queue.writeBuffer(paramsBuffer, 0, data);
2202
+ if (!written.ok) return undefined;
2203
+ }
2204
+
2205
+ if (!isTemporalFullscreenBinding(descriptor.values)) {
2206
+ return (
2207
+ createFullscreenBindGroup(
2208
+ frame.runtime.device,
2209
+ layout,
2210
+ inputView.value,
2211
+ fullscreen.sampler,
2212
+ paramsBuffer,
2213
+ depthView ?? null,
2214
+ fullscreen.depthSampler,
2215
+ ) ?? undefined
2216
+ );
2217
+ }
2218
+
2219
+ const temporalTarget = input.resolveTarget(descriptor.values.temporal as never);
2220
+ if (temporalTarget === undefined) return undefined;
2221
+ const temporalView = input.resources.textureView(temporalTarget.view);
2222
+ if (!temporalView.ok) return undefined;
1672
2223
  return (
1673
2224
  createFullscreenBindGroup(
1674
2225
  frame.runtime.device,
1675
2226
  layout,
1676
2227
  inputView.value,
1677
2228
  fullscreen.sampler,
1678
- frame.runtime.getPostProcessParamsBuffer?.(shader),
2229
+ paramsBuffer,
1679
2230
  depthView ?? null,
1680
2231
  fullscreen.depthSampler,
1681
2232
  [{ binding: fullscreen.extraColorBindings[0] ?? 5, view: temporalView.value }],
@@ -1753,15 +2304,35 @@ function resolveTargetBindings(
1753
2304
  return created.value;
1754
2305
  }
1755
2306
 
1756
- function retire(
2307
+ export function retire(
1757
2308
  frameState: RenderFrameState,
1758
2309
  graph: CompiledRenderGraph<RenderPipelineFrame>,
1759
2310
  ): void {
2311
+ if (frameState.retiredCompiledFrameGraphs.has(graph)) return;
1760
2312
  frameState.retiredCompiledFrameGraphs.add(graph);
1761
- graph
1762
- .retire()
1763
- .finally(() => frameState.retiredCompiledFrameGraphs.delete(graph))
1764
- .catch(() => undefined);
2313
+ setGraphGenerationRetiring(frameState, graph);
2314
+ const failures = graphRetirementFailureSet(frameState);
2315
+ const retirement = graph.retire();
2316
+ // retire() synchronously moves graph-owned resources to pending before its
2317
+ // queue-fence await. Capture that overlap at the lifecycle event, rather
2318
+ // than waiting for a later inspection call.
2319
+ observeGraphGenerationAllocation(frameState);
2320
+ retirement
2321
+ .then((result) => {
2322
+ if (result.ok) {
2323
+ failures?.delete(graph);
2324
+ setGraphGenerationRetirementResult(frameState, graph, true);
2325
+ } else {
2326
+ failures?.add(graph);
2327
+ setGraphGenerationRetirementResult(frameState, graph, false);
2328
+ }
2329
+ frameState.retiredCompiledFrameGraphs.delete(graph);
2330
+ })
2331
+ .catch(() => {
2332
+ failures?.add(graph);
2333
+ setGraphGenerationRetirementResult(frameState, graph, false);
2334
+ frameState.retiredCompiledFrameGraphs.delete(graph);
2335
+ });
1765
2336
  }
1766
2337
 
1767
2338
  /**
@@ -1838,6 +2409,54 @@ export function settleDepthOfFieldGraphCandidate(
1838
2409
  frameState.depthOfFieldCandidateGraph = null;
1839
2410
  }
1840
2411
 
2412
+ /**
2413
+ * Settle an ordinary graph replacement at the same submit barrier as the
2414
+ * barrel mapping. Resize, camera topology, and post-process enable/disable
2415
+ * all replace the compiled graph; a failed command must restore that graph so
2416
+ * the retained mapping still describes the pixels that are actually shown.
2417
+ */
2418
+ export function settleBarrelDistortionGraphCandidate(
2419
+ frameState: RenderFrameState,
2420
+ submitted: boolean,
2421
+ ): void {
2422
+ const candidate = frameState.barrelDistortionCandidateGraph ?? null;
2423
+ if (candidate === null) return;
2424
+ const previous = frameState.barrelDistortionPreviousGraph ?? null;
2425
+ const sharedWithSpecializedOwner =
2426
+ frameState.depthOfFieldCandidateGraph === candidate ||
2427
+ frameState.volumetricFogCandidateGraph === candidate;
2428
+ if (submitted) {
2429
+ if (!sharedWithSpecializedOwner && previous !== null && previous !== candidate) {
2430
+ retire(frameState, previous);
2431
+ }
2432
+ } else {
2433
+ if (!sharedWithSpecializedOwner) {
2434
+ if (candidate !== previous) retire(frameState, candidate);
2435
+ frameState.compiledFrameGraph = previous;
2436
+ frameState.compiledFrameGraphTopologyKey =
2437
+ frameState.barrelDistortionPreviousGraphKey ?? null;
2438
+ if (frameState.barrelDistortionPreviousPerFrameGraph === undefined) {
2439
+ delete frameState.perFrameGraph;
2440
+ } else {
2441
+ frameState.perFrameGraph = frameState.barrelDistortionPreviousPerFrameGraph;
2442
+ }
2443
+ frameState.graphGeneration = frameState.barrelDistortionPreviousGraphGeneration ?? 0;
2444
+ frameState.compiledFrameGraphGeneration =
2445
+ frameState.barrelDistortionPreviousCompiledGraphGeneration ?? 0;
2446
+ frameState.standardLightingGraphSignature =
2447
+ frameState.barrelDistortionPreviousStandardLightingGraphSignature ?? '';
2448
+ }
2449
+ frameState.barrelDistortionGraphResolution = previous === null ? 'accepted' : 'retained';
2450
+ }
2451
+ frameState.barrelDistortionPreviousGraph = null;
2452
+ frameState.barrelDistortionPreviousGraphKey = null;
2453
+ frameState.barrelDistortionPreviousPerFrameGraph = undefined;
2454
+ frameState.barrelDistortionPreviousGraphGeneration = 0;
2455
+ frameState.barrelDistortionPreviousCompiledGraphGeneration = 0;
2456
+ frameState.barrelDistortionPreviousStandardLightingGraphSignature = '';
2457
+ frameState.barrelDistortionCandidateGraph = null;
2458
+ }
2459
+
1841
2460
  export function ensureCompiledFrameGraph(
1842
2461
  internals: RenderSystemInternals,
1843
2462
  frameState: RenderFrameState,
@@ -1859,8 +2478,11 @@ export function ensureCompiledFrameGraph(
1859
2478
  onRecoveryError?: (error: unknown) => void,
1860
2479
  ssr?: SsrSpatialAdmission,
1861
2480
  renderExtent?: RenderExtent,
2481
+ surfaceMediumActive?: boolean,
1862
2482
  analyticFog = false,
2483
+ cloudShadowResolution?: number,
1863
2484
  ): CompiledRenderGraph<RenderPipelineFrame> | null {
2485
+ frameState.barrelDistortionGraphResolution = 'retained';
1864
2486
  const featureMetrics = mutableFeatureGraphInspection(internals);
1865
2487
  featureMetrics.ensureCalls += 1;
1866
2488
  const requestedStandardLightingSignature = standardLightingTopologySignature(standardLighting);
@@ -1878,10 +2500,16 @@ export function ensureCompiledFrameGraph(
1878
2500
  : { ...camera, depthOfField: frameDepthOfField };
1879
2501
  const lastKnownGood = (): CompiledRenderGraph<RenderPipelineFrame> | null =>
1880
2502
  frameState.compiledFrameGraph !== null &&
2503
+ frameState.compiledFrameGraphTopologyKey !== null &&
1881
2504
  !hasSubmissionSensitiveFeatures(getRenderFeatureGraphState(internals).plans) &&
1882
2505
  frameState.standardLightingGraphSignature === requestedStandardLightingSignature
1883
2506
  ? frameState.compiledFrameGraph
1884
2507
  : null;
2508
+ const retainLastKnownGood = (): CompiledRenderGraph<RenderPipelineFrame> | null => {
2509
+ const graph = lastKnownGood();
2510
+ if (graph !== null) frameState.barrelDistortionGraphResolution = 'retained';
2511
+ return graph;
2512
+ };
1885
2513
  const surfaceWidth = Math.max(1, width);
1886
2514
  const surfaceHeight = Math.max(1, height);
1887
2515
  const featurePlans = featureGraphCandidate?.plans ?? getRenderFeatureGraphState(internals).plans;
@@ -1893,8 +2521,15 @@ export function ensureCompiledFrameGraph(
1893
2521
  if (featureGraphCandidate !== undefined) {
1894
2522
  recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
1895
2523
  }
1896
- return volumeTopologyCandidate ? null : lastKnownGood();
2524
+ return volumeTopologyCandidate ? null : retainLastKnownGood();
1897
2525
  }
2526
+ const cloudHistoryDemand = projectedFeaturePlans.value.some(
2527
+ (execution) =>
2528
+ execution.featureIdentity === CLOUD_LAYER_FEATURE_IDENTITY &&
2529
+ execution.passes.some(
2530
+ (pass) => pass.name === 'cloud-layer-transport' || pass.name === 'cloud-layer-resolve',
2531
+ ),
2532
+ );
1898
2533
  const nextPlanRevision = updateRenderFeaturePlanRevision(
1899
2534
  featureMetrics.planRevisionState,
1900
2535
  featurePlans,
@@ -1925,6 +2560,7 @@ export function ensureCompiledFrameGraph(
1925
2560
  standardLighting,
1926
2561
  ssr,
1927
2562
  renderExtent,
2563
+ surfaceMediumActive,
1928
2564
  analyticFog,
1929
2565
  );
1930
2566
  if (!topologyResult.ok) {
@@ -1934,7 +2570,7 @@ export function ensureCompiledFrameGraph(
1934
2570
  if (featureGraphCandidate !== undefined) {
1935
2571
  recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
1936
2572
  }
1937
- return volumeTopologyCandidate ? null : lastKnownGood();
2573
+ return volumeTopologyCandidate ? null : retainLastKnownGood();
1938
2574
  }
1939
2575
  const requestedPostEffects = topologyResult.value.config?.postEffects ?? [];
1940
2576
  const featurePostProcessEntries =
@@ -1986,6 +2622,7 @@ export function ensureCompiledFrameGraph(
1986
2622
  if (featureGraphCandidate !== undefined) {
1987
2623
  recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
1988
2624
  }
2625
+ frameState.barrelDistortionGraphResolution = 'retained';
1989
2626
  return acceptedLkg;
1990
2627
  }
1991
2628
  const topology = postProcessAdmission.pending
@@ -2022,6 +2659,11 @@ export function ensureCompiledFrameGraph(
2022
2659
  // so include the prepared projection's presence to invalidate a cached
2023
2660
  // CPU-only graph when the GPU owner becomes ready, and vice versa.
2024
2661
  gpuDrivenProjectionActive: gpuDriven !== undefined,
2662
+ // Cloud shadow targets are quality-derived graph resources. Include the
2663
+ // resolved texel grid in the cache key so a quality/resize change cannot
2664
+ // reuse a graph compiled for the previous allocation.
2665
+ cloudShadowResolution: cloudShadowResolution ?? 0,
2666
+ cloudHistory: cloudHistoryDemand,
2025
2667
  // Query reservations are frame-local transport state. The projection is
2026
2668
  // renderer-owned and read dynamically by the compiled pass, so a page
2027
2669
  // rotation must not invalidate the graph or its cached bind groups.
@@ -2036,9 +2678,11 @@ export function ensureCompiledFrameGraph(
2036
2678
  ]),
2037
2679
  });
2038
2680
  if (frameState.compiledFrameGraph !== null && frameState.compiledFrameGraphTopologyKey === key) {
2681
+ frameState.barrelDistortionGraphResolution = 'accepted';
2039
2682
  featureMetrics.reuseHits += 1;
2040
2683
  featureMetrics.lastTopologyKey = key;
2041
2684
  frameState.standardLightingGraphSignature = requestedStandardLightingSignature;
2685
+ frameState.pendingCloudHistoryActive = cloudHistoryDemand;
2042
2686
  if (featureGraphCandidate !== undefined && !postProcessAdmission.pending) {
2043
2687
  commitFeatureCandidate(internals, featureGraphCandidate);
2044
2688
  } else if (featureGraphCandidate !== undefined) {
@@ -2068,9 +2712,11 @@ export function ensureCompiledFrameGraph(
2068
2712
  topology.output?.autoExposure === true ? importAutoExposureGraphResources(builder) : undefined;
2069
2713
  if (autoExposureResources !== undefined && !autoExposureResources.ok) {
2070
2714
  internals.errorRegistry.fire(autoExposureResources.error);
2071
- featureGraphCandidate?.onRejected?.();
2072
2715
  discardPendingStandardOutput();
2073
- return lastKnownGood();
2716
+ if (featureGraphCandidate !== undefined) {
2717
+ recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
2718
+ }
2719
+ return retainLastKnownGood();
2074
2720
  }
2075
2721
  const lutResources =
2076
2722
  topology.output?.colorLut === true
@@ -2114,20 +2760,25 @@ export function ensureCompiledFrameGraph(
2114
2760
  : undefined;
2115
2761
  if (lutResources !== undefined && !lutResources.ok) {
2116
2762
  internals.errorRegistry.fire(lutResources.error);
2117
- featureGraphCandidate?.onRejected?.();
2118
2763
  discardPendingStandardOutput();
2119
- return lastKnownGood();
2764
+ if (featureGraphCandidate !== undefined) {
2765
+ recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
2766
+ }
2767
+ return retainLastKnownGood();
2120
2768
  }
2121
2769
  const taaHistory = camera.antialias === 'taa' ? importTemporalHistoryTargets(builder) : undefined;
2770
+ const cloudHistory = cloudHistoryDemand ? importCloudHistoryTargets(builder) : undefined;
2122
2771
  const targetCoverage =
2123
2772
  camera.dynamicResolution === undefined || topology.extent === undefined
2124
2773
  ? undefined
2125
2774
  : createTargetCoverageAttachment(builder, topology.extent);
2126
2775
  if (targetCoverage !== undefined && !targetCoverage.ok) {
2127
2776
  internals.errorRegistry.fire(targetCoverage.error);
2128
- featureGraphCandidate?.onRejected?.();
2129
2777
  discardPendingStandardOutput();
2130
- return lastKnownGood();
2778
+ if (featureGraphCandidate !== undefined) {
2779
+ recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
2780
+ }
2781
+ return retainLastKnownGood();
2131
2782
  }
2132
2783
  const ssrHistory = ssr?.status === 'admitted' ? importSsrHistoryTargets(builder) : undefined;
2133
2784
  const featureProjection = createRenderFeatureProjectionState();
@@ -2161,8 +2812,10 @@ export function ensureCompiledFrameGraph(
2161
2812
  : {}),
2162
2813
  ...(standardLighting === undefined ? {} : { standardLighting }),
2163
2814
  capabilities: { rgba16floatRenderable: internals.device.caps.rgba16floatRenderable },
2815
+ ...(cloudShadowResolution === undefined ? {} : { cloudShadowResolution }),
2164
2816
  ...(occlusion === undefined ? {} : { occlusion }),
2165
2817
  ...(taaHistory === undefined ? {} : { taaHistory }),
2818
+ ...(cloudHistory === undefined ? {} : { cloudHistory }),
2166
2819
  ...(targetCoverage?.ok === true ? { targetCoverage: targetCoverage.value } : {}),
2167
2820
  ...(ssrHistory === undefined ? {} : { ssrHistory }),
2168
2821
  encodeTransmissionMip: ({ pass, resources, source }) => {
@@ -2178,6 +2831,7 @@ export function ensureCompiledFrameGraph(
2178
2831
  builder,
2179
2832
  target.format,
2180
2833
  target.sampleCount,
2834
+ () => frameState.surfaceSubmissionObservation,
2181
2835
  target.additionalColorFormats,
2182
2836
  );
2183
2837
  },
@@ -2198,23 +2852,42 @@ export function ensureCompiledFrameGraph(
2198
2852
  semanticTargets = [],
2199
2853
  namedTargets = {},
2200
2854
  standardSurfaceAccesses = [],
2855
+ featureSelectionOrPlacement,
2856
+ passNames = [],
2201
2857
  ) => {
2202
- // Forward/deferred scene lanes contribute feature plans before the
2858
+ const selection =
2859
+ featureSelectionOrPlacement !== undefined &&
2860
+ typeof featureSelectionOrPlacement !== 'string'
2861
+ ? featureSelectionOrPlacement
2862
+ : undefined;
2863
+ const placement =
2864
+ typeof featureSelectionOrPlacement === 'string' ? featureSelectionOrPlacement : 'post';
2865
+ const include = selection?.include === undefined ? undefined : new Set(selection.include);
2866
+ const exclude = selection?.exclude === undefined ? undefined : new Set(selection.exclude);
2867
+ // Forward/deferred scene lanes contribute scene plans before the
2203
2868
  // Standard post chain has created its named input/output targets.
2204
2869
  // Fullscreen effects (Motion Blur included) must wait for that later
2205
- // call so their temporal input is resolved after TAA. Other feature
2206
- // plans can still be projected at the forward contribution point.
2207
- const plans =
2870
+ // call so their temporal input is resolved after TAA. The shared
2871
+ // projection state below deduplicates only the execution/pass that
2872
+ // actually reached the graph; an empty early projection therefore
2873
+ // cannot reserve the later fullscreen pass.
2874
+ const plans = (
2208
2875
  Object.keys(namedTargets).length === 0
2209
2876
  ? projectedFeaturePlans.value.filter(
2210
2877
  (execution) => !fullscreenFeatureIds.has(execution.featureIdentity),
2211
2878
  )
2212
- : projectedFeaturePlans.value;
2879
+ : projectedFeaturePlans.value
2880
+ )
2881
+ .filter((execution) => (execution.placement ?? 'post') === placement)
2882
+ .filter((execution) => include === undefined || include.has(execution.featureIdentity))
2883
+ .filter((execution) => exclude === undefined || !exclude.has(execution.featureIdentity));
2213
2884
  if (plans.length === 0) return ok(undefined);
2214
2885
  const resolveTarget = targetResolver(targets, semanticTargets, namedTargets);
2215
2886
  const projected = projectPlanExecutions(builder, plans, {
2216
2887
  state: featureProjection,
2217
2888
  resolveTarget,
2889
+ placement,
2890
+ ...(passNames.length === 0 ? {} : { passNames: new Set(passNames) }),
2218
2891
  standardSurfaceAccesses,
2219
2892
  resolveBindings: (input) => resolveTargetBindings(input, featurePostProcessEntries),
2220
2893
  resolveStandardLighting: (frame, pipeline) => {
@@ -2239,7 +2912,8 @@ export function ensureCompiledFrameGraph(
2239
2912
  },
2240
2913
  reportError: (error) => reportRenderFeatureGraphError(internals, error),
2241
2914
  });
2242
- return projected.ok ? ok(undefined) : projected;
2915
+ if (!projected.ok) return projected;
2916
+ return ok(undefined);
2243
2917
  },
2244
2918
  contributeCubeCaptures: (environmentCube) => {
2245
2919
  const reflectionProbes = addReflectionProbeGraphPasses(
@@ -2254,6 +2928,20 @@ export function ensureCompiledFrameGraph(
2254
2928
  },
2255
2929
  hasFeature: (identity) =>
2256
2930
  projectedFeaturePlans.value.some((execution) => execution.featureIdentity === identity),
2931
+ hasFeatureWork: (identity, placement) =>
2932
+ projectedFeaturePlans.value.some(
2933
+ (execution) =>
2934
+ execution.featureIdentity === identity &&
2935
+ execution.passes.length > 0 &&
2936
+ (placement === undefined || (execution.placement ?? 'post') === placement),
2937
+ ),
2938
+ hasFeatureRasterWork: (identity, placement) =>
2939
+ projectedFeaturePlans.value.some(
2940
+ (execution) =>
2941
+ execution.featureIdentity === identity &&
2942
+ execution.passes.some((pass) => pass.graphics !== undefined) &&
2943
+ (placement === undefined || (execution.placement ?? 'post') === placement),
2944
+ ),
2257
2945
  },
2258
2946
  topology,
2259
2947
  );
@@ -2261,12 +2949,11 @@ export function ensureCompiledFrameGraph(
2261
2949
  featureMetrics.buildFailures += 1;
2262
2950
  onRecoveryError?.(built.error);
2263
2951
  internals.errorRegistry.fire(built.error);
2264
- featureGraphCandidate?.onRejected?.();
2265
2952
  discardPendingStandardOutput();
2266
2953
  if (featureGraphCandidate !== undefined) {
2267
2954
  recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
2268
2955
  }
2269
- return volumeTopologyCandidate ? null : lastKnownGood();
2956
+ return volumeTopologyCandidate ? null : retainLastKnownGood();
2270
2957
  }
2271
2958
  featureMetrics.compileAttempts += 1;
2272
2959
  const compileStarted = typeof performance === 'undefined' ? Date.now() : performance.now();
@@ -2280,13 +2967,16 @@ export function ensureCompiledFrameGraph(
2280
2967
  featureMetrics.compileFailures += 1;
2281
2968
  onRecoveryError?.(compiled.error);
2282
2969
  internals.errorRegistry.fire(compiled.error);
2283
- featureGraphCandidate?.onRejected?.();
2284
2970
  discardPendingStandardOutput();
2285
2971
  if (featureGraphCandidate !== undefined) {
2286
2972
  recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'rejected');
2287
2973
  }
2288
- return lastKnownGood();
2974
+ return retainLastKnownGood();
2289
2975
  }
2976
+ // Register the generation before publishing it. The owner observes the
2977
+ // overlap while the previous graph is still live, even if a queue fence
2978
+ // retires that graph before the next public inspect() call.
2979
+ trackGraphGeneration(frameState, compiled.value);
2290
2980
  const previous = frameState.compiledFrameGraph;
2291
2981
  const depthOfFieldReplacement =
2292
2982
  (frameState.depthOfFieldCandidateGraph === null ||
@@ -2314,8 +3004,22 @@ export function ensureCompiledFrameGraph(
2314
3004
  frameState.depthOfFieldPreviousStandardLightingGraphSignature =
2315
3005
  frameState.standardLightingGraphSignature;
2316
3006
  frameState.depthOfFieldCandidateGraph = compiled.value;
3007
+ } else if (compiled.value !== previous) {
3008
+ // Keep the complete accepted graph projection until queue submission.
3009
+ // This covers barrel enable/disable, output resize, and camera topology
3010
+ // changes without introducing a second resource owner.
3011
+ frameState.barrelDistortionPreviousGraph = previous;
3012
+ frameState.barrelDistortionPreviousGraphKey = frameState.compiledFrameGraphTopologyKey;
3013
+ frameState.barrelDistortionPreviousPerFrameGraph = frameState.perFrameGraph;
3014
+ frameState.barrelDistortionPreviousGraphGeneration = frameState.graphGeneration;
3015
+ frameState.barrelDistortionPreviousCompiledGraphGeneration =
3016
+ frameState.compiledFrameGraphGeneration;
3017
+ frameState.barrelDistortionPreviousStandardLightingGraphSignature =
3018
+ frameState.standardLightingGraphSignature;
3019
+ frameState.barrelDistortionCandidateGraph = compiled.value;
2317
3020
  }
2318
3021
  frameState.compiledFrameGraph = compiled.value;
3022
+ frameState.barrelDistortionGraphResolution = 'accepted';
2319
3023
  frameState.compiledFrameGraphTopologyKey = key;
2320
3024
  frameState.standardLightingGraphSignature = requestedStandardLightingSignature;
2321
3025
  frameState.compiledFrameGraphGeneration += 1;
@@ -2347,99 +3051,11 @@ export function ensureCompiledFrameGraph(
2347
3051
  } else if (featureGraphCandidate !== undefined) {
2348
3052
  recordRenderFeatureCandidateEvent(internals, featureGraphCandidate, 'abandoned');
2349
3053
  }
2350
- if (!volumeTopologyCandidate && !depthOfFieldReplacement && previous !== null) {
2351
- retire(frameState, previous);
2352
- }
3054
+ frameState.pendingCloudHistoryActive = cloudHistoryDemand;
3055
+ // The selected candidate owner retires the previous graph only after submit.
2353
3056
  return compiled.value;
2354
3057
  }
2355
3058
 
2356
- /**
2357
- * Project one accepted Motion Blur compute execution from the same graph and
2358
- * resolved dispatch objects used by the encoder. A plan/capability candidate
2359
- * without both executed passes deliberately returns no receipt.
2360
- */
2361
- function deriveMotionBlurExecutionReceipt(
2362
- internals: RenderSystemInternals,
2363
- frameState: RenderFrameState,
2364
- ): MotionBlurExecutionReceipt | undefined {
2365
- const graph = frameState.compiledFrameGraph;
2366
- const deviceGeneration = internals.deviceScope?.generation;
2367
- if (graph === null || deviceGeneration === undefined) return undefined;
2368
- const info = graph.inspect();
2369
- const graphPasses = new Map(info.passes.map((pass) => [pass.name, pass]));
2370
- const summaryGraphPass = graphPasses.get('motion-blur-tile-summary');
2371
- const reconstructGraphPass = graphPasses.get('motion-blur');
2372
- if (summaryGraphPass?.kind !== 'compute' || reconstructGraphPass?.kind !== 'compute') {
2373
- return undefined;
2374
- }
2375
- const planned = getRenderFeatureGraphState(internals).plans.find(
2376
- (candidate) => candidate.featureIdentity === 'forgeax.motion-blur',
2377
- );
2378
- const execution =
2379
- planned === undefined ? undefined : getRenderFeaturePlanExecutionProjection(planned);
2380
- if (execution === undefined || execution.featureIdentity !== 'forgeax.motion-blur') {
2381
- return undefined;
2382
- }
2383
- const executionPass = (name: 'motion-blur-tile-summary' | 'motion-blur') =>
2384
- execution.passes.find((pass) => pass.name === name);
2385
- const summary = executionPass('motion-blur-tile-summary');
2386
- const reconstruct = executionPass('motion-blur');
2387
- if (
2388
- summary?.gpuCompute === undefined ||
2389
- reconstruct?.gpuCompute === undefined ||
2390
- summary.resolvedGpuCompute === undefined ||
2391
- reconstruct.resolvedGpuCompute === undefined
2392
- ) {
2393
- return undefined;
2394
- }
2395
- const directWorkgroups = (
2396
- pass: NonNullable<typeof summary.resolvedGpuCompute>,
2397
- ): readonly [number, number, number] | undefined => {
2398
- const dispatch = pass.dispatches[0];
2399
- if (dispatch === undefined || !('workgroups' in dispatch)) return undefined;
2400
- return dispatch.workgroups;
2401
- };
2402
- const summaryWorkgroups = directWorkgroups(summary.resolvedGpuCompute);
2403
- const reconstructWorkgroups = directWorkgroups(reconstruct.resolvedGpuCompute);
2404
- if (summaryWorkgroups === undefined || reconstructWorkgroups === undefined) return undefined;
2405
- const outputResource = info.resources.find(
2406
- (resource) =>
2407
- resource.label === 'motion-blurred-color' ||
2408
- reconstructGraphPass.accesses.some(
2409
- (access) => access.resource === resource.label && access.usage === 'storage-write',
2410
- ),
2411
- );
2412
- if (
2413
- outputResource?.descriptor.kind !== 'texture' ||
2414
- outputResource.descriptor.format !== 'rgba16float'
2415
- ) {
2416
- return undefined;
2417
- }
2418
- const summaryReceipt: MotionBlurExecutionPass = {
2419
- name: 'motion-blur-tile-summary',
2420
- kind: 'compute',
2421
- entryPoint: 'tile_summary',
2422
- workgroups: summaryWorkgroups,
2423
- };
2424
- const reconstructReceipt: MotionBlurExecutionPass = {
2425
- name: 'motion-blur',
2426
- kind: 'compute',
2427
- entryPoint: 'reconstruct',
2428
- workgroups: reconstructWorkgroups,
2429
- };
2430
- return Object.freeze({
2431
- frameId: frameState.frameNumber,
2432
- deviceGeneration,
2433
- // RenderGraphBuilder generations are process-global. The renderer's
2434
- // frame-state generation is the identity used by inspection and by the
2435
- // same-frame admission check below, so retain that owner-scoped value.
2436
- graphGeneration: frameState.graphGeneration,
2437
- program: 'motion-blur-compute' as const,
2438
- outputFormat: 'rgba16float' as const,
2439
- passes: [summaryReceipt, reconstructReceipt] as const,
2440
- });
2441
- }
2442
-
2443
3059
  export function executeCompiledFrameGraph(
2444
3060
  internals: RenderSystemInternals,
2445
3061
  frameState: RenderFrameState,
@@ -2462,21 +3078,31 @@ export function executeCompiledFrameGraph(
2462
3078
  const legacySubmitCommit = typeof frameHooks === 'function' ? frameHooks : undefined;
2463
3079
  const resolvedFrameHooks = typeof frameHooks === 'function' ? undefined : frameHooks;
2464
3080
  const graph = frameState.compiledFrameGraph;
3081
+ // Recovery keeps the candidate graph for detached inspection, but clears
3082
+ // its topology key before the replacement canvas context is published. It
3083
+ // is evidence only until the first post-recovery draw compiles a new graph.
3084
+ // Never let that retained graph reach record/execute or queue.submit.
3085
+ const detachedGraph = graph !== null && frameState.compiledFrameGraphTopologyKey === null;
2465
3086
  const rejectTemporalFrame = (): void => {
2466
3087
  if (frameState.temporalFrameInput === undefined) return;
2467
3088
  frameState.temporalFrameTransaction.commit({ accepted: false });
2468
3089
  frameState.temporalFrameInput = undefined;
2469
3090
  };
2470
- if (graph === null) {
3091
+ if (graph === null || detachedGraph) {
2471
3092
  timingCapture?.discard();
2472
3093
  frameState.pendingAutoExposureState = undefined;
2473
3094
  frameState.pendingStandardLutState = undefined;
3095
+ settleBarrelDistortionGraphCandidate(frameState, false);
2474
3096
  settleVolumetricFogGraphCandidate(frameState, false);
2475
3097
  settleDepthOfFieldGraphCandidate(frameState, false);
2476
3098
  rejectTemporalFrame();
2477
3099
  return false;
2478
3100
  }
2479
- const motionBlurExecution = deriveMotionBlurExecutionReceipt(internals, frameState);
3101
+ const motionBlurExecution = deriveMotionBlurExecutionReceipt(
3102
+ internals,
3103
+ frameState,
3104
+ getRenderFeatureGraphState(internals).plans,
3105
+ );
2480
3106
  const legacyTimingInstrumentation =
2481
3107
  timingCapture === undefined ? undefined : createTimingInstrumentation(timingCapture);
2482
3108
  const timingHost = internals as RenderSystemInternals & {
@@ -2539,6 +3165,7 @@ export function executeCompiledFrameGraph(
2539
3165
  );
2540
3166
  settleVolumetricFogGraphCandidate(frameState, false);
2541
3167
  settleDepthOfFieldGraphCandidate(frameState, false);
3168
+ settleBarrelDistortionGraphCandidate(frameState, false);
2542
3169
  return false;
2543
3170
  }
2544
3171
  const capturedGeneration = deviceScope?.generation;
@@ -3042,8 +3669,8 @@ export function executeCompiledFrameGraph(
3042
3669
  internals.errorRegistry.fire(
3043
3670
  new RhiError({
3044
3671
  code: 'webgpu-runtime-error',
3045
- expected: 'TAA resolve stages a history write before submit',
3046
- hint: 'retry the frame after the temporal resolve pass has encoded successfully',
3672
+ expected: 'an admitted temporal producer stages a history write before submit',
3673
+ hint: 'retry the frame after the TAA or CloudLayer history pass has encoded successfully',
3047
3674
  }),
3048
3675
  );
3049
3676
  return { ok: false, stage: 'finish' };
@@ -3197,6 +3824,11 @@ export function executeCompiledFrameGraph(
3197
3824
  }
3198
3825
  }
3199
3826
  const stagedTemporalCommit = frameState.pendingTemporalCommit ?? { kind: 'none' as const };
3827
+ const stagedCloudHistoryActive = frameState.pendingCloudHistoryActive;
3828
+ if (stagedCloudHistoryActive !== undefined) {
3829
+ frameState.cloudHistoryActive = stagedCloudHistoryActive;
3830
+ delete frameState.pendingCloudHistoryActive;
3831
+ }
3200
3832
  if (stagedTemporalCommit.kind !== 'none') {
3201
3833
  frameState.lastSuccessfulTemporalView = stagedTemporalCommit.view;
3202
3834
  }
@@ -3205,7 +3837,7 @@ export function executeCompiledFrameGraph(
3205
3837
  } else if (stagedTemporalCommit.kind === 'off') {
3206
3838
  frameState.successfulTemporalFrameIndex = 0;
3207
3839
  }
3208
- if (stagedTemporalCommit.kind === 'off') {
3840
+ if (stagedTemporalCommit.kind === 'off' && frameState.cloudHistoryActive !== true) {
3209
3841
  internals.clearPostProcessPipelineCache?.('forgeax.taa-resolve');
3210
3842
  // Retire frame-sized resources, not the device-owned prewarmed
3211
3843
  // shader. The next TAA frame must not start an async cold compile.
@@ -3236,6 +3868,7 @@ export function executeCompiledFrameGraph(
3236
3868
  frameState.pendingAtmospherePublish?.();
3237
3869
  frameState.pendingAtmospherePublish = undefined;
3238
3870
  onSubmitted?.(internals.device.queue.onSubmittedWorkDone());
3871
+ settleBarrelDistortionGraphCandidate(frameState, true);
3239
3872
  settleVolumetricFogGraphCandidate(frameState, true);
3240
3873
  settleDepthOfFieldGraphCandidate(frameState, true);
3241
3874
  },
@@ -3271,11 +3904,13 @@ export function executeCompiledFrameGraph(
3271
3904
  }
3272
3905
  frameState.temporalGpuState = undefined;
3273
3906
  }
3907
+ delete frameState.pendingCloudHistoryActive;
3274
3908
  frameState.pendingTemporalCommit = { kind: 'none' };
3275
3909
  if (frameState.environmentGeneration !== undefined) {
3276
3910
  frameState.environmentLifecycle?.discard(frameState.environmentGeneration);
3277
3911
  frameState.environmentGeneration = undefined;
3278
3912
  }
3913
+ settleBarrelDistortionGraphCandidate(frameState, false);
3279
3914
  settleVolumetricFogGraphCandidate(frameState, false);
3280
3915
  settleDepthOfFieldGraphCandidate(frameState, false);
3281
3916
  },
@@ -3283,6 +3918,7 @@ export function executeCompiledFrameGraph(
3283
3918
  // The callbacks above settle the normal transaction path. Keep this guard
3284
3919
  // for synthetic callers and any future early transaction result that does
3285
3920
  // not enter a callback.
3921
+ settleBarrelDistortionGraphCandidate(frameState, transaction.ok);
3286
3922
  settleVolumetricFogGraphCandidate(frameState, transaction.ok);
3287
3923
  settleDepthOfFieldGraphCandidate(frameState, transaction.ok);
3288
3924
  if (!transaction.ok) rejectTemporalFrame();