@forgeax/engine-render 0.1.20 → 0.1.21

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 (372) hide show
  1. package/README.md +215 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/assembly/device-feature-admission.d.ts +18 -0
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  5. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  7. package/dist/assembly/factory.d.ts +21 -18
  8. package/dist/assembly/factory.d.ts.map +1 -1
  9. package/dist/assembly/host-contract.d.ts +9 -0
  10. package/dist/assembly/host-contract.d.ts.map +1 -1
  11. package/dist/assembly/render-target-host.d.ts +46 -0
  12. package/dist/assembly/render-target-host.d.ts.map +1 -0
  13. package/dist/assets/render-asset-port.d.ts.map +1 -1
  14. package/dist/authoring.mjs +3 -0
  15. package/dist/authoring.mjs.map +1 -1
  16. package/dist/capture/cube-views.d.ts +21 -0
  17. package/dist/capture/cube-views.d.ts.map +1 -0
  18. package/dist/capture/scheduler.d.ts +54 -0
  19. package/dist/capture/scheduler.d.ts.map +1 -0
  20. package/dist/{chunk-4XINDLAY.mjs → chunk-5YADUCA7.mjs} +1552 -1361
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-TBQJOK2L.mjs → chunk-6OPXDQ2E.mjs} +364 -51
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-M2MP4ZOU.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/{chunk-CDTWTHZR.mjs → chunk-FS2KNUQH.mjs} +548 -6
  27. package/dist/chunk-FS2KNUQH.mjs.map +1 -0
  28. package/dist/components/camera.d.ts +1 -0
  29. package/dist/components/camera.d.ts.map +1 -1
  30. package/dist/components/cube-camera.d.ts +20 -0
  31. package/dist/components/cube-camera.d.ts.map +1 -0
  32. package/dist/components/directional-light.d.ts +11 -2
  33. package/dist/components/directional-light.d.ts.map +1 -1
  34. package/dist/components/directional-shadow-filter.d.ts +25 -0
  35. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/light-helpers.d.ts.map +1 -1
  39. package/dist/components/reflection-probe.d.ts +17 -0
  40. package/dist/components/reflection-probe.d.ts.map +1 -0
  41. package/dist/construct-renderer.mjs +5826 -1003
  42. package/dist/construct-renderer.mjs.map +1 -1
  43. package/dist/environment/frame.d.ts.map +1 -1
  44. package/dist/environment/lifecycle.d.ts +5 -0
  45. package/dist/environment/lifecycle.d.ts.map +1 -1
  46. package/dist/errors/render.d.ts +141 -93
  47. package/dist/errors/render.d.ts.map +1 -1
  48. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  49. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  50. package/dist/extract/environment.d.ts +14 -0
  51. package/dist/extract/environment.d.ts.map +1 -1
  52. package/dist/extract/visibility.d.ts.map +1 -1
  53. package/dist/features/fullscreen.d.ts.map +1 -1
  54. package/dist/features/targets.d.ts.map +1 -1
  55. package/dist/features/types.d.ts +10 -0
  56. package/dist/features/types.d.ts.map +1 -1
  57. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  58. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  59. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  60. package/dist/gpu-driven/production-raster.d.ts +0 -1
  61. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  62. package/dist/gpu-scene.d.ts.map +1 -1
  63. package/dist/hdrp-buffers.d.ts +19 -17
  64. package/dist/hdrp-buffers.d.ts.map +1 -1
  65. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  66. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  67. package/dist/ibl/kernel-cache.d.ts +36 -0
  68. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  69. package/dist/index.d.ts +28 -3
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.mjs +4 -4
  72. package/dist/index.mjs.map +1 -1
  73. package/dist/inspection-types.d.ts +51 -0
  74. package/dist/inspection-types.d.ts.map +1 -1
  75. package/dist/instances-derived-bounds.d.ts +50 -0
  76. package/dist/instances-derived-bounds.d.ts.map +1 -0
  77. package/dist/internal.mjs +1 -1
  78. package/dist/light-buffer-layout.d.ts +10 -1
  79. package/dist/light-buffer-layout.d.ts.map +1 -1
  80. package/dist/mesh-material-bindings.d.ts +54 -0
  81. package/dist/mesh-material-bindings.d.ts.map +1 -1
  82. package/dist/pbr-pipeline.d.ts +14 -4
  83. package/dist/pbr-pipeline.d.ts.map +1 -1
  84. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  85. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  86. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  87. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  88. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  89. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  90. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  91. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  92. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  93. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  95. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  97. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  99. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  101. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  103. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  104. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  105. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/standard-post.d.ts.map +1 -1
  107. package/dist/pipeline/standard-profile.d.ts +5 -26
  108. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  109. package/dist/pipeline-spec.d.ts +40 -7
  110. package/dist/pipeline-spec.d.ts.map +1 -1
  111. package/dist/point-shadow-inspection.d.ts +21 -0
  112. package/dist/point-shadow-inspection.d.ts.map +1 -0
  113. package/dist/record/frame-lighting.d.ts +27 -32
  114. package/dist/record/frame-lighting.d.ts.map +1 -1
  115. package/dist/record/frame-snapshot.d.ts +28 -14
  116. package/dist/record/frame-snapshot.d.ts.map +1 -1
  117. package/dist/record/frame-targets.d.ts +5 -0
  118. package/dist/record/frame-targets.d.ts.map +1 -1
  119. package/dist/record/frame.d.ts +10 -4
  120. package/dist/record/frame.d.ts.map +1 -1
  121. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  122. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  123. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  124. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  125. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  126. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  127. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  128. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  130. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  131. package/dist/record/gpu-timing.d.ts +8 -3
  132. package/dist/record/gpu-timing.d.ts.map +1 -1
  133. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  134. package/dist/record/main-pass-material.d.ts +22 -1
  135. package/dist/record/main-pass-material.d.ts.map +1 -1
  136. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  137. package/dist/record/main-pass.d.ts +2 -0
  138. package/dist/record/main-pass.d.ts.map +1 -1
  139. package/dist/record/render-context.d.ts +43 -2
  140. package/dist/record/render-context.d.ts.map +1 -1
  141. package/dist/record/shadow-pass.d.ts.map +1 -1
  142. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  143. package/dist/record/typed-frame-graph.d.ts +72 -4
  144. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  145. package/dist/record/view-ubo.d.ts +7 -4
  146. package/dist/record/view-ubo.d.ts.map +1 -1
  147. package/dist/reflection/filter.d.ts +22 -0
  148. package/dist/reflection/filter.d.ts.map +1 -0
  149. package/dist/reflection/gpu-table.d.ts +25 -0
  150. package/dist/reflection/gpu-table.d.ts.map +1 -0
  151. package/dist/reflection/projection.d.ts +91 -0
  152. package/dist/reflection/projection.d.ts.map +1 -0
  153. package/dist/render-contract.d.ts +49 -7
  154. package/dist/render-contract.d.ts.map +1 -1
  155. package/dist/render-graph-primitives.d.ts +1 -0
  156. package/dist/render-graph-primitives.d.ts.map +1 -1
  157. package/dist/render-pipeline.d.ts +42 -2
  158. package/dist/render-pipeline.d.ts.map +1 -1
  159. package/dist/render-system-extract.d.ts +58 -17
  160. package/dist/render-system-extract.d.ts.map +1 -1
  161. package/dist/render-system.d.ts +17 -2
  162. package/dist/render-system.d.ts.map +1 -1
  163. package/dist/renderer.d.ts +43 -0
  164. package/dist/renderer.d.ts.map +1 -0
  165. package/dist/scene/render-scene.d.ts +14 -0
  166. package/dist/scene/render-scene.d.ts.map +1 -1
  167. package/dist/shadow-atlas.d.ts +3 -3
  168. package/dist/shadow-atlas.d.ts.map +1 -1
  169. package/dist/ssao-buffers.d.ts +3 -0
  170. package/dist/ssao-buffers.d.ts.map +1 -1
  171. package/dist/targets/contracts.d.ts +66 -0
  172. package/dist/targets/contracts.d.ts.map +1 -0
  173. package/dist/targets/graph-projection.d.ts +29 -0
  174. package/dist/targets/graph-projection.d.ts.map +1 -0
  175. package/dist/targets/material-source.d.ts +34 -0
  176. package/dist/targets/material-source.d.ts.map +1 -0
  177. package/dist/targets/owner.d.ts +32 -0
  178. package/dist/targets/owner.d.ts.map +1 -0
  179. package/dist/targets/physical.d.ts +18 -0
  180. package/dist/targets/physical.d.ts.map +1 -0
  181. package/dist/targets/readback.d.ts +39 -0
  182. package/dist/targets/readback.d.ts.map +1 -0
  183. package/dist/temporal/index.mjs +3 -3
  184. package/dist/typed-render-graph-primitives.d.ts +1 -1
  185. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  186. package/dist/typed-shadow-passes.d.ts +1 -1
  187. package/dist/typed-shadow-passes.d.ts.map +1 -1
  188. package/dist/volume/passes.d.ts +2 -2
  189. package/dist/volume/passes.d.ts.map +1 -1
  190. package/package.json +20 -20
  191. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  192. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  193. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  194. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  195. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  196. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  197. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  198. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  199. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  200. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  201. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  202. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  203. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  204. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  205. package/src/__tests__/fog-component.unit.test.ts +67 -0
  206. package/src/__tests__/fog.integration.test.ts +86 -0
  207. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  208. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  209. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  210. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  211. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  212. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  213. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  214. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  215. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  216. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  217. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  218. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  219. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  220. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  221. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  222. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  223. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  224. package/src/__tests__/material-observation.integration.test.ts +101 -0
  225. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  226. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  227. package/src/__tests__/public-surface.test-d.ts +10 -0
  228. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  229. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  230. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  231. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  232. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  233. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  234. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  235. package/src/__tests__/render-error-code-owner.test-d.ts +18 -3
  236. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  237. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  238. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  239. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  240. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  241. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  242. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  243. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  244. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  245. package/src/__tests__/render-target-public-schema.json +107 -0
  246. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  247. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  248. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  249. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  250. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +48 -0
  251. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  252. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  253. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  254. package/src/__tests__/skinned-shadow-caster.test.ts +11 -2
  255. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  256. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  257. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  258. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  259. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  260. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  261. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  262. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  263. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  264. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  265. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  266. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  267. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  268. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  269. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  270. package/src/assembly/device-feature-admission.ts +73 -0
  271. package/src/assembly/directional-shadow-inspection.ts +122 -0
  272. package/src/assembly/factory.ts +877 -286
  273. package/src/assembly/host-contract.ts +25 -0
  274. package/src/assembly/render-target-host.ts +847 -0
  275. package/src/assets/asset-decoders.ts +1 -1
  276. package/src/assets/render-asset-port.ts +7 -1
  277. package/src/capture/cube-views.ts +69 -0
  278. package/src/capture/scheduler.ts +265 -0
  279. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  280. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  281. package/src/components/camera.ts +4 -0
  282. package/src/components/cube-camera.ts +52 -0
  283. package/src/components/directional-light.ts +22 -2
  284. package/src/components/directional-shadow-filter.ts +57 -0
  285. package/src/components/index.ts +2 -0
  286. package/src/components/light-helpers.ts +81 -7
  287. package/src/components/reflection-probe.ts +49 -0
  288. package/src/environment/frame.ts +6 -24
  289. package/src/environment/lifecycle.ts +12 -0
  290. package/src/errors/render.ts +286 -142
  291. package/src/extract/directional-shadow-projection.ts +61 -0
  292. package/src/extract/environment.ts +57 -0
  293. package/src/extract/visibility.ts +25 -1
  294. package/src/features/fullscreen.ts +3 -0
  295. package/src/features/targets.ts +4 -1
  296. package/src/features/types.ts +10 -0
  297. package/src/fullscreen-post-process-pass.ts +43 -2
  298. package/src/gpu-driven/batch-topology.ts +1 -0
  299. package/src/gpu-driven/production-raster.ts +1 -3
  300. package/src/gpu-scene.ts +8 -1
  301. package/src/hdrp-buffers.ts +165 -59
  302. package/src/ibl/IblPipelineCache.ts +11 -0
  303. package/src/ibl/kernel-cache.ts +88 -0
  304. package/src/index.ts +148 -0
  305. package/src/inspection-types.ts +56 -0
  306. package/src/instances-derived-bounds.ts +171 -0
  307. package/src/light-buffer-layout.ts +20 -5
  308. package/src/mesh-material-bindings.ts +132 -0
  309. package/src/pbr-pipeline.ts +106 -17
  310. package/src/pipeline/standard-forward-lane.ts +46 -16
  311. package/src/pipeline/standard-lighting/errors.ts +6 -0
  312. package/src/pipeline/standard-lighting/graph.ts +157 -0
  313. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  314. package/src/pipeline/standard-lighting/layout.ts +31 -0
  315. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  316. package/src/pipeline/standard-lighting/membership.ts +43 -0
  317. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  318. package/src/pipeline/standard-lighting/topology.ts +120 -0
  319. package/src/pipeline/standard-lighting/transport.ts +77 -0
  320. package/src/pipeline/standard-pipeline.ts +122 -193
  321. package/src/pipeline/standard-post.ts +26 -7
  322. package/src/pipeline/standard-profile.ts +17 -47
  323. package/src/pipeline-spec.ts +84 -21
  324. package/src/point-shadow-inspection.ts +55 -0
  325. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  326. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  327. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  328. package/src/record/frame-lighting.ts +367 -427
  329. package/src/record/frame-snapshot.ts +35 -27
  330. package/src/record/frame-targets.ts +17 -0
  331. package/src/record/frame.ts +261 -74
  332. package/src/record/gpu-pass-timing/contract.ts +180 -0
  333. package/src/record/gpu-pass-timing/errors.ts +34 -0
  334. package/src/record/gpu-pass-timing/index.ts +37 -0
  335. package/src/record/gpu-pass-timing/parser.ts +97 -0
  336. package/src/record/gpu-pass-timing/session.ts +938 -0
  337. package/src/record/gpu-timing.ts +21 -12
  338. package/src/record/main-pass-geometry.ts +120 -80
  339. package/src/record/main-pass-material.ts +95 -9
  340. package/src/record/main-pass-sprite-draws.ts +54 -25
  341. package/src/record/main-pass.ts +71 -15
  342. package/src/record/render-context.ts +72 -0
  343. package/src/record/shadow-pass.ts +17 -28
  344. package/src/record/skybox-post-pass.ts +20 -4
  345. package/src/record/typed-frame-graph.ts +893 -44
  346. package/src/record/view-ubo.ts +63 -28
  347. package/src/reflection/filter.ts +81 -0
  348. package/src/reflection/gpu-table.ts +44 -0
  349. package/src/reflection/projection.ts +281 -0
  350. package/src/render-contract.ts +76 -5
  351. package/src/render-graph-primitives.ts +9 -4
  352. package/src/render-pipeline.ts +117 -2
  353. package/src/render-system-extract.ts +557 -100
  354. package/src/render-system.ts +1095 -66
  355. package/src/renderer.ts +222 -0
  356. package/src/scene/render-scene.ts +115 -9
  357. package/src/shadow-atlas.ts +5 -5
  358. package/src/ssao-buffers.ts +9 -2
  359. package/src/targets/contracts.ts +182 -0
  360. package/src/targets/graph-projection.ts +169 -0
  361. package/src/targets/material-source.ts +143 -0
  362. package/src/targets/owner.ts +214 -0
  363. package/src/targets/physical.ts +18 -0
  364. package/src/targets/readback.ts +153 -0
  365. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  366. package/src/typed-render-graph-primitives.ts +5 -3
  367. package/src/typed-shadow-passes.ts +46 -40
  368. package/src/volume/passes.ts +15 -9
  369. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  370. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  371. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  372. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
@@ -3,12 +3,18 @@ import {
3
3
  type CompiledRenderGraph,
4
4
  type GraphResourceResolver,
5
5
  RenderGraphBuilder,
6
+ RenderGraphError,
7
+ type RenderGraphPassInstrumentation,
8
+ type RenderGraphPassInstrumentationScope,
6
9
  } from '@forgeax/engine-render-graph';
7
10
  import type {
11
+ BindGroup,
8
12
  BindGroupLayout,
13
+ Buffer,
9
14
  CommandBuffer,
10
15
  RenderPipeline,
11
16
  RhiCommandEncoder,
17
+ Sampler,
12
18
  Texture,
13
19
  TextureFormat,
14
20
  TextureView,
@@ -28,10 +34,13 @@ import type {
28
34
  RenderFeatureGraphTargetResolver,
29
35
  } from '../features/render-graph-raster';
30
36
  import { isRenderFeatureTargetHandle } from '../features/targets';
37
+ import type { PostProcessShaderEntry } from '../fullscreen-post-process-pass';
31
38
  import {
32
39
  buildFullscreenPostProcessPass,
33
40
  createFullscreenBindGroup,
34
41
  isTemporalFullscreenBinding,
42
+ postProcessShaderEntrySignature,
43
+ postProcessShaderModuleLabel,
35
44
  } from '../fullscreen-post-process-pass';
36
45
  import type { PreparedGpuDrivenFrame } from '../gpu-driven/production-raster';
37
46
  import {
@@ -40,6 +49,10 @@ import {
40
49
  GPU_TEXTURE_USAGE_RENDER_ATTACHMENT,
41
50
  GPU_TEXTURE_USAGE_TEXTURE_BINDING,
42
51
  } from '../gpu-texture-usage';
52
+ import {
53
+ type StandardTopologyInputValue,
54
+ standardLightingTopologySignature,
55
+ } from '../pipeline/standard-lighting/topology';
43
56
  import { resolveVolumetricFogProfile, STANDARD_PIPELINE_ID } from '../pipeline/standard-profile';
44
57
  import type { CameraSnapshot } from '../render-contract';
45
58
  import type {
@@ -48,6 +61,9 @@ import type {
48
61
  RenderPipelineTopology,
49
62
  } from '../render-pipeline';
50
63
  import type { ExtractedLights, ExtractedVolumetricFog } from '../render-system-extract';
64
+ import { SHADOW_ATLAS_DEFAULT_FACE_SIZE, SHADOW_ATLAS_DEFAULT_LAYERS } from '../shadow-atlas';
65
+ import type { RenderTarget } from '../targets/contracts';
66
+ import type { RenderTargetPhysical } from '../targets/physical';
51
67
  import {
52
68
  abortTemporalGpuSubmit,
53
69
  commitTemporalGpuSubmit,
@@ -64,8 +80,15 @@ import { hasVolumetricFogCapability } from '../volume/capability';
64
80
  import { deriveVolumetricFogExtent, deriveVolumetricFogResolvedExtent } from '../volume/resources';
65
81
  import { buildPerFrameBindGroups } from './frame-lighting';
66
82
  import { getTextureIdentity, type RenderFrameState } from './frame-snapshot';
67
- import type { GpuTimingCapture } from './gpu-timing';
68
- import type { PipelineState, RenderSystemInternals } from './render-context';
83
+ import type { GpuPassTimingPassIdentity } from './gpu-pass-timing/contract.js';
84
+ import type { GpuPassTimingCapture, GpuPassTimingSession } from './gpu-pass-timing/session.js';
85
+ import type { GpuTimingCapture, VolumeTimingTimestampWrites } from './gpu-timing';
86
+ import { encodeMainPass } from './main-pass';
87
+ import type {
88
+ _InternalRenderPipelineContext,
89
+ PipelineState,
90
+ RenderSystemInternals,
91
+ } from './render-context';
69
92
  import { resolveSurfaceFormatPair, resolveSurfaceProfile } from './render-context';
70
93
 
71
94
  export interface TemporalGraphCapabilities {
@@ -206,6 +229,465 @@ export function createTemporalGraphRoster(input: {
206
229
  };
207
230
  }
208
231
 
232
+ export interface CubeCaptureGraphWork {
233
+ readonly target: RenderTarget;
234
+ readonly faceIndex: number;
235
+ readonly physical: RenderTargetPhysical;
236
+ readonly faceCamera: import('../render-contract').CameraSnapshot;
237
+ readonly viewBindGroupDynamicOffset: number;
238
+ }
239
+
240
+ export interface CubeCaptureGraphState {
241
+ work: readonly CubeCaptureGraphWork[];
242
+ reflectionProbes?: ReflectionProbeGraphState;
243
+ }
244
+
245
+ export interface ReflectionProbeFilterGraphWork {
246
+ readonly probeIndex: number;
247
+ readonly faceIndex: number;
248
+ readonly mipLevel: number;
249
+ }
250
+
251
+ export interface ReflectionProbeFilterGraphState {
252
+ readonly work: readonly ReflectionProbeFilterGraphWork[];
253
+ readonly maxStepsPerFrame: number;
254
+ }
255
+
256
+ export interface ReflectionProbeGraphWork {
257
+ readonly probeIndex: number;
258
+ readonly rawTexture: Texture;
259
+ readonly rawCubeView: TextureView;
260
+ readonly rawFaceViews: readonly TextureView[];
261
+ readonly rawDepthTexture: Texture;
262
+ readonly rawCaptureFace: number | undefined;
263
+ readonly rawDepthView: TextureView;
264
+ readonly rawSize: number;
265
+ readonly faceCamera?: import('../render-contract').CameraSnapshot;
266
+ readonly viewBindGroupDynamicOffset?: number;
267
+ readonly filteredTexture: Texture;
268
+ readonly filteredCubeView: TextureView;
269
+ readonly filteredFaceViewsByMip: readonly (readonly TextureView[])[];
270
+ readonly outputFormat: TextureFormat;
271
+ readonly sampler: Sampler;
272
+ readonly filterPipeline: RenderPipeline;
273
+ readonly filterGroup0: BindGroup | undefined;
274
+ readonly filterGroup1: BindGroup;
275
+ readonly cubeVertexBuffer: Buffer;
276
+ readonly filteredSize: number;
277
+ readonly step: ReflectionProbeFilterGraphWork | undefined;
278
+ }
279
+
280
+ export interface ReflectionProbeGraphState {
281
+ readonly work: readonly ReflectionProbeGraphWork[];
282
+ }
283
+
284
+ export function boundedReflectionProbeFilterWork(
285
+ state: ReflectionProbeFilterGraphState,
286
+ ): readonly ReflectionProbeFilterGraphWork[] {
287
+ return state.work.slice(0, Math.max(0, state.maxStepsPerFrame));
288
+ }
289
+
290
+ function renderTargetMipCount(work: CubeCaptureGraphWork): number {
291
+ const { descriptor } = work.physical;
292
+ return descriptor.mipLevels === 1
293
+ ? 1
294
+ : Math.floor(Math.log2(Math.max(descriptor.width, descriptor.height))) + 1;
295
+ }
296
+
297
+ function addCubeCaptureGraphPasses(
298
+ builder: RenderGraphBuilder<RenderPipelineFrame>,
299
+ state: CubeCaptureGraphState,
300
+ ): Result<void, RenderGraphError> {
301
+ for (let index = 0; index < state.work.length; index += 1) {
302
+ const slot = index;
303
+ const initial = state.work[slot];
304
+ if (initial === undefined) continue;
305
+ const current = (): CubeCaptureGraphWork => state.work[slot] ?? initial;
306
+ const texture = builder.importTexture(
307
+ `cube-capture.${slot}.texture`,
308
+ {
309
+ format: initial.physical.descriptor.format,
310
+ size: {
311
+ width: initial.physical.descriptor.width,
312
+ height: initial.physical.descriptor.height,
313
+ depthOrArrayLayers: initial.physical.descriptor.sampleCount === 4 ? 1 : 6,
314
+ },
315
+ mipLevelCount:
316
+ initial.physical.descriptor.sampleCount === 4 ? 1 : renderTargetMipCount(initial),
317
+ sampleCount: initial.physical.descriptor.sampleCount,
318
+ dimension: '2d',
319
+ usage: 0x10 | 0x04 | 0x01,
320
+ },
321
+ () => current().physical.colorTextures[current().faceIndex] ?? initial.physical.texture,
322
+ );
323
+ if (!texture.ok) return texture;
324
+ const color = builder.importView(
325
+ texture.value,
326
+ {
327
+ label: `cube-capture.${slot}.color`,
328
+ dimension: '2d',
329
+ baseMipLevel: 0,
330
+ mipLevelCount: 1,
331
+ baseArrayLayer: initial.physical.descriptor.sampleCount === 4 ? 0 : initial.faceIndex,
332
+ arrayLayerCount: 1,
333
+ },
334
+ () =>
335
+ current().physical.faceViews[current().faceIndex] ??
336
+ initial.physical.faceViews[initial.faceIndex] ??
337
+ initial.physical.view,
338
+ );
339
+ if (!color.ok) return color;
340
+ const depthTexture = builder.importTexture(
341
+ `cube-capture.${slot}.depth-texture`,
342
+ {
343
+ format: 'depth24plus-stencil8',
344
+ size: {
345
+ width: initial.physical.descriptor.width,
346
+ height: initial.physical.descriptor.height,
347
+ depthOrArrayLayers: initial.physical.descriptor.sampleCount === 4 ? 1 : 6,
348
+ },
349
+ mipLevelCount: 1,
350
+ sampleCount: initial.physical.descriptor.sampleCount,
351
+ dimension: '2d',
352
+ usage: 0x10,
353
+ },
354
+ () => current().physical.depthTextures[current().faceIndex] as Texture,
355
+ );
356
+ if (!depthTexture.ok) return depthTexture;
357
+ const depth = builder.importView(
358
+ depthTexture.value,
359
+ {
360
+ label: `cube-capture.${slot}.depth`,
361
+ dimension: '2d',
362
+ baseArrayLayer: initial.physical.descriptor.sampleCount === 4 ? 0 : initial.faceIndex,
363
+ arrayLayerCount: 1,
364
+ },
365
+ () => current().physical.depthViews[current().faceIndex] as TextureView,
366
+ );
367
+ if (!depth.ok) return depth;
368
+ const resolveTexture =
369
+ initial.physical.descriptor.sampleCount === 4
370
+ ? builder.importTexture(
371
+ `cube-capture.${slot}.resolve-texture`,
372
+ {
373
+ format: initial.physical.descriptor.format,
374
+ size: {
375
+ width: initial.physical.descriptor.width,
376
+ height: initial.physical.descriptor.height,
377
+ depthOrArrayLayers: 6,
378
+ },
379
+ mipLevelCount: renderTargetMipCount(initial),
380
+ sampleCount: 1,
381
+ dimension: '2d',
382
+ usage: 0x10 | 0x04 | 0x01,
383
+ },
384
+ () =>
385
+ current().physical.resolveTexture ??
386
+ initial.physical.resolveTexture ??
387
+ initial.physical.texture,
388
+ )
389
+ : undefined;
390
+ if (resolveTexture !== undefined && !resolveTexture.ok) return resolveTexture;
391
+ const resolve =
392
+ resolveTexture === undefined
393
+ ? undefined
394
+ : builder.importView(
395
+ resolveTexture.value,
396
+ {
397
+ label: `cube-capture.${slot}.resolve`,
398
+ dimension: '2d',
399
+ baseMipLevel: 0,
400
+ mipLevelCount: 1,
401
+ baseArrayLayer: initial.faceIndex,
402
+ arrayLayerCount: 1,
403
+ },
404
+ () =>
405
+ current().physical.resolveFaceViews[current().faceIndex] ??
406
+ initial.physical.resolveFaceViews[initial.faceIndex] ??
407
+ initial.physical.resolveView,
408
+ );
409
+ if (resolve !== undefined && !resolve.ok) return resolve;
410
+ const added = builder.addRasterPass(`cube-capture-face.${slot}`, {
411
+ accesses: [
412
+ { resource: color.value, usage: 'color-attachment' },
413
+ { resource: depth.value, usage: 'depth-stencil-write' },
414
+ ...(resolve === undefined
415
+ ? []
416
+ : [{ resource: resolve.value, usage: 'color-attachment' as const }]),
417
+ ],
418
+ colorAttachments: [
419
+ {
420
+ view: color.value,
421
+ ...(resolve === undefined ? {} : { resolveTarget: resolve.value }),
422
+ loadOp: 'clear',
423
+ storeOp: 'store',
424
+ clearValue: () => {
425
+ return { r: 0, g: 0, b: 0, a: 1 };
426
+ },
427
+ },
428
+ ],
429
+ depthStencilAttachment: {
430
+ view: depth.value,
431
+ depthClearValue: 1,
432
+ depthLoadOp: 'clear',
433
+ depthStoreOp: 'store',
434
+ stencilClearValue: 0,
435
+ stencilLoadOp: 'clear',
436
+ stencilStoreOp: 'store',
437
+ },
438
+ encode: ({ pass, frame, resources }) => {
439
+ const internal = frame as _InternalRenderPipelineContext;
440
+ const captureColor = resources.textureView(color.value);
441
+ if (!captureColor.ok) return;
442
+ const captureDepth = resources.textureView(depth.value);
443
+ if (!captureDepth.ok) return;
444
+ const captureResolve =
445
+ resolve === undefined ? undefined : resources.textureView(resolve.value);
446
+ if (resolve !== undefined && (captureResolve === undefined || !captureResolve.ok)) return;
447
+ const captureResolveView = captureResolve?.ok ? captureResolve.value : null;
448
+ const captureContext = {
449
+ ...internal,
450
+ gpuDrivenEntityKeys: new Set<number>(),
451
+ camera: current().faceCamera,
452
+ // Cube faces are raw scene captures, not display-camera HDR output.
453
+ // Select the PSO against the actual target attachment so an LDR
454
+ // cube target is rendered by a compatible forward pipeline.
455
+ tonemapActive: false,
456
+ skyboxActive: false,
457
+ transparentColorFormat: initial.physical.descriptor.format as GPUTextureFormat,
458
+ msaaActive: initial.physical.descriptor.sampleCount === 4,
459
+ viewBindGroupDynamicOffset: current().viewBindGroupDynamicOffset,
460
+ geometryColorView: captureColor.value,
461
+ geometryDepthView: captureDepth.value,
462
+ geometryColorResolveView: captureResolveView,
463
+ splitLdrSprite: false,
464
+ ldrSpritePassView: null,
465
+ };
466
+ encodeMainPass(captureContext, pass, undefined, {
467
+ colorViews: [captureColor.value],
468
+ colorFormats: [initial.physical.descriptor.format as GPUTextureFormat],
469
+ depthView: captureDepth.value,
470
+ passKind: 'forward',
471
+ clearColor: [0, 0, 0, 1],
472
+ });
473
+ },
474
+ });
475
+ if (!added.ok) return added;
476
+ }
477
+ return ok(undefined);
478
+ }
479
+
480
+ export function addReflectionProbeGraphPasses(
481
+ builder: RenderGraphBuilder<RenderPipelineFrame>,
482
+ state: ReflectionProbeGraphState,
483
+ ): Result<readonly import('@forgeax/engine-render-graph').GraphAccess[], RenderGraphError> {
484
+ const accesses: import('@forgeax/engine-render-graph').GraphAccess[] = [];
485
+ for (const work of state.work) {
486
+ const raw = builder.importTexture(
487
+ `reflection-probe.${work.probeIndex}.raw`,
488
+ {
489
+ format: work.outputFormat,
490
+ size: { width: work.rawSize, height: work.rawSize, depthOrArrayLayers: 6 },
491
+ mipLevelCount: 1,
492
+ sampleCount: 1,
493
+ dimension: '2d',
494
+ usage: 0x10 | 0x04 | 0x01,
495
+ },
496
+ () => work.rawTexture,
497
+ );
498
+ if (!raw.ok) return raw;
499
+ const rawCube = builder.importView(
500
+ raw.value,
501
+ {
502
+ label: `reflection-probe.${work.probeIndex}.raw-cube`,
503
+ dimension: 'cube',
504
+ arrayLayerCount: 6,
505
+ },
506
+ () => work.rawCubeView,
507
+ );
508
+ if (!rawCube.ok) return rawCube;
509
+ const filtered = builder.importTexture(
510
+ `reflection-probe.${work.probeIndex}.filtered`,
511
+ {
512
+ format: work.outputFormat,
513
+ size: { width: work.filteredSize, height: work.filteredSize, depthOrArrayLayers: 6 },
514
+ mipLevelCount: work.filteredFaceViewsByMip.length,
515
+ sampleCount: 1,
516
+ dimension: '2d',
517
+ usage: 0x10 | 0x04 | 0x01,
518
+ },
519
+ () => work.filteredTexture,
520
+ );
521
+ if (!filtered.ok) return filtered;
522
+ const filteredCube = builder.importView(
523
+ filtered.value,
524
+ {
525
+ label: `reflection-probe.${work.probeIndex}.filtered-cube`,
526
+ dimension: 'cube',
527
+ baseMipLevel: 0,
528
+ mipLevelCount: work.filteredFaceViewsByMip.length,
529
+ arrayLayerCount: 6,
530
+ },
531
+ () => work.filteredCubeView,
532
+ );
533
+ if (!filteredCube.ok) return filteredCube;
534
+ if (work.rawCaptureFace !== undefined) {
535
+ const face = work.rawCaptureFace;
536
+ const view = builder.importView(
537
+ raw.value,
538
+ {
539
+ label: `reflection-probe.${work.probeIndex}.capture-face-${face}`,
540
+ dimension: '2d',
541
+ baseArrayLayer: face,
542
+ arrayLayerCount: 1,
543
+ },
544
+ () => work.rawFaceViews[face] as TextureView,
545
+ );
546
+ if (!view.ok) return view;
547
+ const depthTexture = builder.importTexture(
548
+ `reflection-probe.${work.probeIndex}.capture-depth`,
549
+ {
550
+ format: 'depth24plus-stencil8',
551
+ size: { width: work.rawSize, height: work.rawSize, depthOrArrayLayers: 1 },
552
+ mipLevelCount: 1,
553
+ sampleCount: 1,
554
+ dimension: '2d',
555
+ usage: 0x10,
556
+ },
557
+ () => work.rawDepthTexture,
558
+ );
559
+ if (!depthTexture.ok) return depthTexture;
560
+ const depth = builder.importView(
561
+ depthTexture.value,
562
+ { label: `reflection-probe.${work.probeIndex}.capture-depth-view`, dimension: '2d' },
563
+ () => work.rawDepthView,
564
+ );
565
+ if (!depth.ok) return depth;
566
+ const added = builder.addRasterPass(`reflection-probe.${work.probeIndex}.capture.${face}`, {
567
+ accesses: [
568
+ { resource: view.value, usage: 'color-attachment' },
569
+ { resource: depth.value, usage: 'depth-stencil-write' },
570
+ ],
571
+ colorAttachments: [
572
+ {
573
+ view: view.value,
574
+ loadOp: 'clear',
575
+ storeOp: 'store',
576
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
577
+ },
578
+ ],
579
+ depthStencilAttachment: {
580
+ view: depth.value,
581
+ depthClearValue: 1,
582
+ depthLoadOp: 'clear',
583
+ depthStoreOp: 'store',
584
+ stencilClearValue: 0,
585
+ stencilLoadOp: 'clear',
586
+ stencilStoreOp: 'store',
587
+ },
588
+ encode: ({ pass, frame, resources }) => {
589
+ const internal = frame as _InternalRenderPipelineContext;
590
+ const captureColor = resources.textureView(view.value);
591
+ const captureDepth = resources.textureView(depth.value);
592
+ if (!captureColor.ok || !captureDepth.ok) return;
593
+ if (work.faceCamera === undefined || work.viewBindGroupDynamicOffset === undefined)
594
+ return;
595
+ encodeMainPass(
596
+ {
597
+ ...internal,
598
+ camera: work.faceCamera,
599
+ viewBindGroupDynamicOffset: work.viewBindGroupDynamicOffset,
600
+ geometryColorView: captureColor.value,
601
+ geometryDepthView: captureDepth.value,
602
+ geometryColorResolveView: null,
603
+ transparentColorFormat: work.outputFormat as GPUTextureFormat,
604
+ msaaActive: false,
605
+ splitLdrSprite: false,
606
+ ldrSpritePassView: null,
607
+ },
608
+ pass,
609
+ undefined,
610
+ {
611
+ colorViews: [captureColor.value],
612
+ colorFormats: [work.outputFormat as GPUTextureFormat],
613
+ depthView: captureDepth.value,
614
+ passKind: 'forward',
615
+ clearColor: [0, 0, 0, 1],
616
+ },
617
+ );
618
+ },
619
+ });
620
+ if (!added.ok) return added;
621
+ }
622
+ const step = work.step;
623
+ if (step !== undefined) {
624
+ const mipViews = work.filteredFaceViewsByMip[step.mipLevel];
625
+ const outputView = mipViews?.[step.faceIndex];
626
+ if (outputView === undefined) {
627
+ return err(
628
+ new RenderGraphError({
629
+ code: 'resource-resolution-failed',
630
+ expected: 'every scheduled probe mip has a filtered face view',
631
+ hint: 'rebuild the renderer-owned probe output before compiling the Standard graph',
632
+ detail: {
633
+ resourceLabel: `reflection-probe.${work.probeIndex}.filtered`,
634
+ passName: 'pmrem',
635
+ },
636
+ }),
637
+ );
638
+ }
639
+ const output = builder.importView(
640
+ filtered.value,
641
+ {
642
+ label: `reflection-probe.${work.probeIndex}.filtered-mip${step.mipLevel}-face${step.faceIndex}`,
643
+ dimension: '2d',
644
+ baseMipLevel: step.mipLevel,
645
+ mipLevelCount: 1,
646
+ baseArrayLayer: step.faceIndex,
647
+ arrayLayerCount: 1,
648
+ },
649
+ () => outputView,
650
+ );
651
+ if (!output.ok) return output;
652
+ const added = builder.addRasterPass(
653
+ `reflection-probe.${work.probeIndex}.pmrem.${step.mipLevel}.${step.faceIndex}`,
654
+ {
655
+ accesses: [
656
+ { resource: rawCube.value, usage: 'sampled-read' },
657
+ { resource: output.value, usage: 'color-attachment' },
658
+ ],
659
+ colorAttachments: [
660
+ {
661
+ view: output.value,
662
+ loadOp: 'clear',
663
+ storeOp: 'store',
664
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
665
+ },
666
+ ],
667
+ encode: ({ pass }) => {
668
+ if (work.filterGroup0 === undefined) return;
669
+ pass.setPipeline(work.filterPipeline);
670
+ pass.setBindGroup(0, work.filterGroup0);
671
+ pass.setBindGroup(1, work.filterGroup1);
672
+ pass.setVertexBuffer(0, work.cubeVertexBuffer);
673
+ pass.draw(6, 1, step.faceIndex * 6, 0);
674
+ },
675
+ },
676
+ );
677
+ if (!added.ok) return added;
678
+ }
679
+ accesses.push({ resource: filteredCube.value, usage: 'sampled-read' });
680
+ }
681
+ return ok(accesses);
682
+ }
683
+
684
+ export {
685
+ projectRenderTargetGraph,
686
+ type RenderTargetGraphInput,
687
+ type RenderTargetGraphProjection,
688
+ type RenderTargetGraphSubresource,
689
+ } from '../targets/graph-projection';
690
+
209
691
  export interface RenderFeatureGraphRuntimeState {
210
692
  plans: readonly RenderFeaturePlannedFrame[];
211
693
  fullscreenEffects: ReadonlyMap<
@@ -230,10 +712,34 @@ export interface RenderFeatureGraphCandidate {
230
712
  readonly preparedResourceKey?: string;
231
713
  /** Release candidate-owned prepared resources when graph promotion fails. */
232
714
  readonly onRejected?: () => void;
715
+ /** Publish candidate-owned runtime declarations only after graph promotion. */
716
+ readonly onAccepted?: () => void;
717
+ /** Discard candidate-only resources when a base graph is kept as fallback. */
718
+ readonly onAbandoned?: () => void;
233
719
  }
234
720
 
235
721
  const featureGraphStates = new WeakMap<RenderSystemInternals, RenderFeatureGraphRuntimeState>();
236
722
 
723
+ interface TimingInstrumentationState {
724
+ readonly instrumentation: RenderGraphPassInstrumentation<RenderPipelineFrame>;
725
+ setCapture(capture: GpuPassTimingCapture | undefined): void;
726
+ }
727
+
728
+ const timingInstrumentationStates = new WeakMap<
729
+ RenderSystemInternals,
730
+ TimingInstrumentationState
731
+ >();
732
+
733
+ interface VolumeTimingInstrumentationState {
734
+ readonly instrumentation: RenderGraphPassInstrumentation<RenderPipelineFrame>;
735
+ setCapture(capture: GpuTimingCapture | undefined): void;
736
+ }
737
+
738
+ const volumeTimingInstrumentationStates = new WeakMap<
739
+ RenderSystemInternals,
740
+ VolumeTimingInstrumentationState
741
+ >();
742
+
237
743
  type CompiledGraphTargetAccess = CompiledRenderGraph<RenderPipelineFrame> & {
238
744
  readonly getColorTargetDescriptor: (
239
745
  name: string,
@@ -284,6 +790,31 @@ export function renderFeatureGraphPlanSignature(
284
790
  );
285
791
  }
286
792
 
793
+ /**
794
+ * Admit fullscreen post-processes as one chain, never as independent passes.
795
+ * A pass whose shader module is still compiling cannot clear a graph target and
796
+ * leave the remainder of the chain sampling an undefined intermediate. The
797
+ * renderer therefore keeps the base output-transform graph until every
798
+ * requested effect is ready, then admits the complete ordered chain together.
799
+ */
800
+ export function resolvePostProcessChainAdmission(
801
+ effects: readonly string[],
802
+ isReady: (identity: string) => boolean,
803
+ ): { readonly admitted: readonly string[]; readonly pending: boolean } {
804
+ // Probe every declaration even after the first miss. A single frame may
805
+ // introduce several effects; evaluating all of them lets the shared cache
806
+ // start every independent compile together instead of serialising warmup on
807
+ // array order.
808
+ let ready = true;
809
+ for (const identity of effects) {
810
+ if (!isReady(identity)) ready = false;
811
+ }
812
+ return {
813
+ admitted: ready ? effects : [],
814
+ pending: !ready,
815
+ };
816
+ }
817
+
287
818
  function validateRenderFeaturePlans(
288
819
  plans: readonly RenderFeaturePlannedFrame[],
289
820
  ): Result<readonly RenderFeaturePlanExecution[], RenderError> {
@@ -314,6 +845,10 @@ function commitFeatureCandidate(
314
845
  internals: RenderSystemInternals,
315
846
  candidate: RenderFeatureGraphCandidate,
316
847
  ): void {
848
+ // Publish renderer-owned declarations only after their device resources have
849
+ // been admitted. If the acceptance hook throws, the previously accepted
850
+ // graph/state pair remains visible to the caller.
851
+ candidate.onAccepted?.();
317
852
  const state = getRenderFeatureGraphState(internals);
318
853
  state.plans = candidate.plans;
319
854
  state.fullscreenEffects = candidate.fullscreenEffects;
@@ -331,8 +866,10 @@ function topologyOf(
331
866
  shadowMapSize: number | undefined,
332
867
  gpuDriven: PreparedGpuDrivenFrame | undefined,
333
868
  featureGraphCandidate: RenderFeatureGraphCandidate | undefined,
869
+ cubeCaptureState: CubeCaptureGraphState | undefined,
334
870
  transmissionDemand: TransmissionDemand | undefined,
335
871
  volumetricFog: ExtractedVolumetricFog | undefined,
872
+ standardLighting: StandardTopologyInputValue | undefined,
336
873
  ): Result<RenderPipelineTopology, RhiError> {
337
874
  const cascadeCount = Math.max(1, Math.min(4, Math.round(lights.cascadeCount ?? 1))) as
338
875
  | 1
@@ -357,6 +894,10 @@ function topologyOf(
357
894
  ...(featureGraphCandidate?.fullscreenEffects ??
358
895
  getRenderFeatureGraphState(internals).fullscreenEffects),
359
896
  ].map(([id]) => id);
897
+ const featurePostEffectDeclarations = [
898
+ ...(featureGraphCandidate?.fullscreenEffects ??
899
+ getRenderFeatureGraphState(internals).fullscreenEffects),
900
+ ].map(([id, entry]) => [id, postProcessShaderEntrySignature(entry)] as const);
360
901
  const config =
361
902
  featurePostEffects.length === 0
362
903
  ? frameState.installedPipelineConfig
@@ -406,10 +947,13 @@ function topologyOf(
406
947
  motionBlur: isMotionBlurTemporalDemand(camera.motionBlur),
407
948
  },
408
949
  shadow: {
409
- mapSize: shadowMapSize ?? 1024,
410
- cascadeCount,
411
- pointCount: Math.min(4, lights.pointShadow.length),
412
- pointFaceSize: lights.pointShadow[0]?.mapSize ?? 512,
950
+ directional:
951
+ shadowMapSize === undefined || lights.cascadeCount === undefined
952
+ ? 'disabled'
953
+ : { mapSize: shadowMapSize, cascadeCount },
954
+ spotMapSize: shadowMapSize ?? 1024,
955
+ pointCount: Math.min(SHADOW_ATLAS_DEFAULT_LAYERS, lights.pointShadow.length),
956
+ pointFaceSize: lights.pointShadow[0]?.mapSize ?? SHADOW_ATLAS_DEFAULT_FACE_SIZE,
413
957
  spotCount: Math.min(
414
958
  4,
415
959
  lights.spot.filter(
@@ -468,10 +1012,12 @@ function topologyOf(
468
1012
  plans: renderFeatureGraphPlanSignature(
469
1013
  featureGraphCandidate?.plans ?? getRenderFeatureGraphState(internals).plans,
470
1014
  ),
471
- fullscreenEffects: featurePostEffects,
1015
+ fullscreenEffects: featurePostEffectDeclarations,
472
1016
  preparedResourceKey: featureGraphCandidate?.preparedResourceKey ?? '',
1017
+ cubeCaptureSlots: cubeCaptureState?.work.length ?? 0,
473
1018
  }),
474
1019
  gpuDrivenTopologySignature: gpuDriven?.topologySignature ?? '',
1020
+ standardLightingTopologySignature: standardLightingTopologySignature(standardLighting),
475
1021
  transmissionDemand: transmissionDemand ?? {
476
1022
  activeCount: 0,
477
1023
  needsRoughMips: false,
@@ -527,14 +1073,17 @@ function targetResolver(
527
1073
  };
528
1074
  }
529
1075
 
530
- function resolveTargetBindings(input: {
531
- readonly frame: RenderPipelineFrame;
532
- readonly binding: import('../prepare/prepared-graphics-resolver').PreparedGraphicsResolvedResource & {
533
- readonly kind: 'bindings';
534
- };
535
- readonly resources: GraphResourceResolver;
536
- readonly resolveTarget: RenderFeatureGraphTargetResolver;
537
- }): RenderFeatureGraphBindingsResolution | undefined {
1076
+ function resolveTargetBindings(
1077
+ input: {
1078
+ readonly frame: RenderPipelineFrame;
1079
+ readonly binding: import('../prepare/prepared-graphics-resolver').PreparedGraphicsResolvedResource & {
1080
+ readonly kind: 'bindings';
1081
+ };
1082
+ readonly resources: GraphResourceResolver;
1083
+ readonly resolveTarget: RenderFeatureGraphTargetResolver;
1084
+ },
1085
+ featurePostProcessEntries?: ReadonlyMap<string, PostProcessShaderEntry>,
1086
+ ): RenderFeatureGraphBindingsResolution | undefined {
538
1087
  const frame = input.frame as import('./render-context')._InternalRenderPipelineContext;
539
1088
  const descriptor = input.binding.descriptor;
540
1089
  const pipeline = input.binding.pipeline as
@@ -546,7 +1095,8 @@ function resolveTargetBindings(input: {
546
1095
  if (descriptor?.values.fullscreen === true && isTemporalFullscreenBinding(descriptor.values)) {
547
1096
  const shader = descriptor.values.shader;
548
1097
  if (typeof shader !== 'string') return undefined;
549
- const entry = frame.runtime.lookupPostProcess?.(shader);
1098
+ const entry =
1099
+ featurePostProcessEntries?.get(shader) ?? frame.runtime.lookupPostProcess?.(shader);
550
1100
  if (entry === undefined) return undefined;
551
1101
  const fullscreen = buildFullscreenPostProcessPass(
552
1102
  { device: frame.runtime.device, errorRegistry: frame.runtime.errorRegistry },
@@ -636,8 +1186,10 @@ function resolveTargetBindings(input: {
636
1186
  directionalShadow: frame.frameState.currentDirectionalShadowView ?? undefined,
637
1187
  spotShadow: frame.frameState.currentSpotShadowView ?? undefined,
638
1188
  },
1189
+ true,
1190
+ frame.standardLighting,
639
1191
  ).viewBindGroup;
640
- return viewBindGroup === null ? undefined : { handle: viewBindGroup, dynamicOffsets: [0] };
1192
+ return viewBindGroup === null ? undefined : { handle: viewBindGroup, dynamicOffsets: [0, 0] };
641
1193
  }
642
1194
  const target = input.resolveTarget(sceneDepth);
643
1195
  if (target === undefined) return undefined;
@@ -707,11 +1259,19 @@ export function ensureCompiledFrameGraph(
707
1259
  shadowMapSize: number | undefined,
708
1260
  gpuDriven?: PreparedGpuDrivenFrame,
709
1261
  featureGraphCandidate?: RenderFeatureGraphCandidate,
1262
+ cubeCaptureState?: CubeCaptureGraphState,
710
1263
  clearOnly = false,
711
1264
  transmissionDemand?: TransmissionDemand,
712
1265
  volumetricFog?: ExtractedVolumetricFog,
713
1266
  volumeTopologyCandidate = false,
1267
+ standardLighting?: StandardTopologyInputValue,
714
1268
  ): CompiledRenderGraph<RenderPipelineFrame> | null {
1269
+ const requestedStandardLightingSignature = standardLightingTopologySignature(standardLighting);
1270
+ const lastKnownGood = (): CompiledRenderGraph<RenderPipelineFrame> | null =>
1271
+ frameState.compiledFrameGraph !== null &&
1272
+ frameState.standardLightingGraphSignature === requestedStandardLightingSignature
1273
+ ? frameState.compiledFrameGraph
1274
+ : null;
715
1275
  const surfaceWidth = Math.max(1, width);
716
1276
  const surfaceHeight = Math.max(1, height);
717
1277
  const featurePlans = featureGraphCandidate?.plans ?? getRenderFeatureGraphState(internals).plans;
@@ -719,7 +1279,7 @@ export function ensureCompiledFrameGraph(
719
1279
  if (!projectedFeaturePlans.ok) {
720
1280
  reportRenderFeatureGraphError(internals, projectedFeaturePlans.error);
721
1281
  featureGraphCandidate?.onRejected?.();
722
- return volumeTopologyCandidate ? null : frameState.compiledFrameGraph;
1282
+ return volumeTopologyCandidate ? null : lastKnownGood();
723
1283
  }
724
1284
  const topologyResult = topologyOf(
725
1285
  internals,
@@ -733,19 +1293,81 @@ export function ensureCompiledFrameGraph(
733
1293
  shadowMapSize,
734
1294
  gpuDriven,
735
1295
  featureGraphCandidate,
1296
+ cubeCaptureState,
736
1297
  transmissionDemand,
737
1298
  volumetricFog,
1299
+ standardLighting,
738
1300
  );
739
1301
  if (!topologyResult.ok) {
740
1302
  internals.errorRegistry.fire(topologyResult.error);
741
1303
  featureGraphCandidate?.onRejected?.();
742
- return volumeTopologyCandidate ? null : frameState.compiledFrameGraph;
1304
+ return volumeTopologyCandidate ? null : lastKnownGood();
1305
+ }
1306
+ const requestedPostEffects = topologyResult.value.config?.postEffects ?? [];
1307
+ const featurePostProcessEntries =
1308
+ featureGraphCandidate?.fullscreenEffects ??
1309
+ getRenderFeatureGraphState(internals).fullscreenEffects;
1310
+ const postProcessAdmission = resolvePostProcessChainAdmission(
1311
+ requestedPostEffects,
1312
+ (identity) => {
1313
+ const entry =
1314
+ featurePostProcessEntries.get(identity) ?? internals.lookupPostProcess?.(identity);
1315
+ // Preserve the existing missing-entry failure contract. The graph will
1316
+ // report post-process-not-found during record instead of silently
1317
+ // removing an authored id from the topology.
1318
+ if (entry === undefined) return true;
1319
+ const buildPipeline = internals.buildPostProcessPipeline;
1320
+ const getPipeline = internals.getPostProcessPipeline;
1321
+ if (buildPipeline === undefined || getPipeline === undefined) return true;
1322
+ const built = buildFullscreenPostProcessPass(
1323
+ { device: internals.device, errorRegistry: internals.errorRegistry },
1324
+ entry,
1325
+ camera.antialias === 'msaa' && topologyResult.value.lane.multisample,
1326
+ );
1327
+ if (built === null) return false;
1328
+ return (
1329
+ getPipeline(
1330
+ identity,
1331
+ built.bindGroupLayout,
1332
+ topologyResult.value.surface.storageFormat as GPUTextureFormat,
1333
+ entry,
1334
+ ) !== null
1335
+ );
1336
+ },
1337
+ );
1338
+ const acceptedLkg = lastKnownGood();
1339
+ if (postProcessAdmission.pending && acceptedLkg !== null) {
1340
+ // Do not publish a candidate whose fullscreen chain is only partially
1341
+ // ready. Keep the accepted graph (including its prior post-effect chain)
1342
+ // and retry the same feature candidate on the next frame.
1343
+ featureGraphCandidate?.onRejected?.();
1344
+ return acceptedLkg;
743
1345
  }
744
- const topology = topologyResult.value;
745
- const key = JSON.stringify(topology);
1346
+ const topology = postProcessAdmission.pending
1347
+ ? {
1348
+ ...topologyResult.value,
1349
+ config: {
1350
+ ...(topologyResult.value.config ?? {}),
1351
+ postEffects: [],
1352
+ },
1353
+ }
1354
+ : topologyResult.value;
1355
+ const key = JSON.stringify({
1356
+ topology,
1357
+ cubeCaptureSlots: cubeCaptureState?.work.length ?? 0,
1358
+ reflectionProbeSlots: cubeCaptureState?.reflectionProbes?.work.map((work) => [
1359
+ work.probeIndex,
1360
+ work.rawCaptureFace ?? -1,
1361
+ work.step?.faceIndex ?? -1,
1362
+ work.step?.mipLevel ?? -1,
1363
+ ]),
1364
+ });
746
1365
  if (frameState.compiledFrameGraph !== null && frameState.compiledFrameGraphTopologyKey === key) {
747
- if (featureGraphCandidate !== undefined) {
1366
+ frameState.standardLightingGraphSignature = requestedStandardLightingSignature;
1367
+ if (featureGraphCandidate !== undefined && !postProcessAdmission.pending) {
748
1368
  commitFeatureCandidate(internals, featureGraphCandidate);
1369
+ } else if (featureGraphCandidate !== undefined) {
1370
+ featureGraphCandidate.onAbandoned?.();
749
1371
  }
750
1372
  // A candidate flag only describes a possible topology replacement. When
751
1373
  // the derived key is unchanged there is no replacement to stage: reuse the
@@ -761,6 +1383,7 @@ export function ensureCompiledFrameGraph(
761
1383
  const built = frameState.activePipeline.build(
762
1384
  {
763
1385
  graph: builder,
1386
+ ...(standardLighting === undefined ? {} : { standardLighting }),
764
1387
  capabilities: { rgba16floatRenderable: internals.device.caps.rgba16floatRenderable },
765
1388
  ...(taaHistory === undefined ? {} : { taaHistory }),
766
1389
  encodeTransmissionMip: ({ pass, resources, source }) => {
@@ -781,11 +1404,21 @@ export function ensureCompiledFrameGraph(
781
1404
  const resolveTarget = targetResolver(targets, semanticTargets, namedTargets);
782
1405
  const projected = projectPlanExecutions(builder, projectedFeaturePlans.value, {
783
1406
  resolveTarget,
784
- resolveBindings: resolveTargetBindings,
1407
+ resolveBindings: (input) => resolveTargetBindings(input, featurePostProcessEntries),
785
1408
  reportError: (error) => reportRenderFeatureGraphError(internals, error),
786
1409
  });
787
1410
  return projected.ok ? ok(undefined) : projected;
788
1411
  },
1412
+ contributeCubeCaptures: () => {
1413
+ const reflectionProbes = addReflectionProbeGraphPasses(
1414
+ builder,
1415
+ cubeCaptureState?.reflectionProbes ?? { work: [] },
1416
+ );
1417
+ if (!reflectionProbes.ok) return reflectionProbes;
1418
+ return cubeCaptureState === undefined
1419
+ ? ok(undefined)
1420
+ : addCubeCaptureGraphPasses(builder, cubeCaptureState);
1421
+ },
789
1422
  hasFeature: (identity) =>
790
1423
  projectedFeaturePlans.value.some((execution) => execution.featureIdentity === identity),
791
1424
  },
@@ -794,7 +1427,7 @@ export function ensureCompiledFrameGraph(
794
1427
  if (!built.ok) {
795
1428
  internals.errorRegistry.fire(built.error);
796
1429
  featureGraphCandidate?.onRejected?.();
797
- return volumeTopologyCandidate ? null : frameState.compiledFrameGraph;
1430
+ return volumeTopologyCandidate ? null : lastKnownGood();
798
1431
  }
799
1432
  const compiled = builder.compile({
800
1433
  device: internals.device,
@@ -803,7 +1436,7 @@ export function ensureCompiledFrameGraph(
803
1436
  if (!compiled.ok) {
804
1437
  internals.errorRegistry.fire(compiled.error);
805
1438
  featureGraphCandidate?.onRejected?.();
806
- return frameState.compiledFrameGraph;
1439
+ return lastKnownGood();
807
1440
  }
808
1441
  const previous = frameState.compiledFrameGraph;
809
1442
  if (volumeTopologyCandidate) {
@@ -813,6 +1446,8 @@ export function ensureCompiledFrameGraph(
813
1446
  }
814
1447
  frameState.compiledFrameGraph = compiled.value;
815
1448
  frameState.compiledFrameGraphTopologyKey = key;
1449
+ frameState.standardLightingGraphSignature = requestedStandardLightingSignature;
1450
+ frameState.compiledFrameGraphGeneration += 1;
816
1451
  const compiledTargets = compiled.value as CompiledGraphTargetAccess;
817
1452
  frameState.graphGeneration += 1;
818
1453
  frameState.perFrameGraph = {
@@ -822,8 +1457,10 @@ export function ensureCompiledFrameGraph(
822
1457
  graphGeneration: frameState.graphGeneration,
823
1458
  };
824
1459
  gpuDriven?._commitResourceReplacement();
825
- if (featureGraphCandidate !== undefined) {
1460
+ if (featureGraphCandidate !== undefined && !postProcessAdmission.pending) {
826
1461
  commitFeatureCandidate(internals, featureGraphCandidate);
1462
+ } else if (featureGraphCandidate !== undefined) {
1463
+ featureGraphCandidate.onAbandoned?.();
827
1464
  }
828
1465
  if (!volumeTopologyCandidate && previous !== null) retire(frameState, previous);
829
1466
  return compiled.value;
@@ -835,9 +1472,17 @@ export function executeCompiledFrameGraph(
835
1472
  frame: RenderPipelineFrame,
836
1473
  encoder: RhiCommandEncoder,
837
1474
  runPass?: import('@forgeax/engine-render-graph').RenderGraphPassRunner,
1475
+ readbackFaces?: readonly number[],
1476
+ onSubmitted?: (completed: Promise<unknown>) => void,
838
1477
  timingCapture?: GpuTimingCapture,
839
1478
  ): boolean {
840
1479
  const graph = frameState.compiledFrameGraph;
1480
+ const invalidatePostProcessModule = (id: string): void => {
1481
+ const entry = internals.lookupPostProcess?.(id);
1482
+ if (entry !== undefined) {
1483
+ internals.invalidateShaderModule?.(postProcessShaderModuleLabel(id, entry.source));
1484
+ }
1485
+ };
841
1486
  const rejectTemporalFrame = (): void => {
842
1487
  if (frameState.temporalFrameInput === undefined) return;
843
1488
  frameState.temporalFrameTransaction.commit({ accepted: false });
@@ -849,28 +1494,47 @@ export function executeCompiledFrameGraph(
849
1494
  rejectTemporalFrame();
850
1495
  return false;
851
1496
  }
852
- const timedRunPass =
853
- timingCapture === undefined
854
- ? runPass
855
- : (
856
- pass: import('@forgeax/engine-render-graph').RenderGraphPassExecution,
857
- encode: () => void,
858
- ) => {
859
- timingCapture.beforePass(pass.name, encoder);
860
- try {
861
- if (runPass === undefined) encode();
862
- else runPass(pass, encode);
863
- } finally {
864
- timingCapture.afterPass(pass.name, encoder);
865
- }
866
- };
1497
+ const timingHost = internals as RenderSystemInternals & {
1498
+ gpuPassTimingSession?: GpuPassTimingSession | undefined;
1499
+ gpuPassTimingCapture?: GpuPassTimingCapture | undefined;
1500
+ gpuPassTimingSubmittedWork?: Promise<void> | undefined;
1501
+ gpuPassTimingFrameIdentity?:
1502
+ | import('./gpu-pass-timing/session.js').GpuPassTimingFrameIdentity
1503
+ | undefined;
1504
+ gpuPassTimingBeginReason?:
1505
+ | import('./gpu-pass-timing/errors.js').GpuPassTimingReason
1506
+ | undefined;
1507
+ };
1508
+ timingHost.gpuPassTimingCapture = undefined;
1509
+ timingHost.gpuPassTimingSubmittedWork = undefined;
1510
+ timingHost.gpuPassTimingBeginReason = undefined;
1511
+ const timingIdentity = timingHost.gpuPassTimingFrameIdentity;
1512
+ const passTimingResult =
1513
+ timingIdentity === undefined
1514
+ ? undefined
1515
+ : timingHost.gpuPassTimingSession?.beginFrame({
1516
+ ...timingIdentity,
1517
+ graphGeneration: frameState.compiledFrameGraphGeneration,
1518
+ });
1519
+ const capture = passTimingResult?.ok === true ? passTimingResult.value : undefined;
1520
+ if (passTimingResult?.ok === false) timingHost.gpuPassTimingBeginReason = passTimingResult.error;
1521
+ const passTimingCapture = capture;
1522
+ timingHost.gpuPassTimingCapture = passTimingCapture;
1523
+ const timingInstrumentationState =
1524
+ passTimingCapture !== undefined
1525
+ ? getTimingInstrumentationState(internals, passTimingCapture)
1526
+ : timingCapture === undefined
1527
+ ? undefined
1528
+ : getVolumeTimingInstrumentationState(internals, timingCapture);
867
1529
  type PreparedCommand = { readonly command: CommandBuffer };
868
1530
  let preparedCommand: PreparedCommand = { command: undefined as never };
869
1531
  const transaction = executeRendererFrameTransaction<PreparedCommand>({
870
1532
  build: () => ({ ok: true, value: { command: undefined as never } }),
871
1533
  execute: () => {
872
- const executed = graph.execute(frame, timedRunPass);
1534
+ const executed = graph.execute(frame, runPass, timingInstrumentationState?.instrumentation);
1535
+ timingInstrumentationState?.setCapture(undefined);
873
1536
  if (!executed.ok) {
1537
+ passTimingCapture?.abort({ code: executed.error.code });
874
1538
  internals.errorRegistry.fire(executed.error);
875
1539
  return { ok: false, stage: 'execute' };
876
1540
  }
@@ -1000,6 +1664,7 @@ export function executeCompiledFrameGraph(
1000
1664
  );
1001
1665
  }
1002
1666
  }
1667
+ internals.encodeRenderTargetReadbacks?.(encoder, readbackFaces);
1003
1668
  const stagedGpuState = frameState.temporalGpuState;
1004
1669
  if (stagedGpuState !== undefined && !hasPendingTemporalGpuSubmit(stagedGpuState)) {
1005
1670
  internals.errorRegistry.fire(
@@ -1011,6 +1676,10 @@ export function executeCompiledFrameGraph(
1011
1676
  );
1012
1677
  return { ok: false, stage: 'finish' };
1013
1678
  }
1679
+ if (passTimingCapture !== undefined) {
1680
+ const tail = passTimingCapture.encodeTail(encoder);
1681
+ if (!tail.ok) passTimingCapture.abort({ code: tail.error.code });
1682
+ }
1014
1683
  const resolved = timingCapture?.resolve(encoder);
1015
1684
  if (resolved !== undefined && !resolved.ok) {
1016
1685
  timingCapture?.discard();
@@ -1018,6 +1687,7 @@ export function executeCompiledFrameGraph(
1018
1687
  }
1019
1688
  const finished = encoder.finish();
1020
1689
  if (!finished.ok) {
1690
+ passTimingCapture?.abort({ code: finished.error.code });
1021
1691
  timingCapture?.discard();
1022
1692
  internals.errorRegistry.fire(finished.error);
1023
1693
  return { ok: false, stage: 'finish' };
@@ -1028,6 +1698,7 @@ export function executeCompiledFrameGraph(
1028
1698
  submit: () => {
1029
1699
  const submitted = internals.device.queue.submit([preparedCommand.command]);
1030
1700
  if (!submitted.ok) {
1701
+ passTimingCapture?.abort({ code: submitted.error.code });
1031
1702
  timingCapture?.discard();
1032
1703
  internals.errorRegistry.fire(submitted.error);
1033
1704
  return { ok: false, stage: 'submit' };
@@ -1036,6 +1707,11 @@ export function executeCompiledFrameGraph(
1036
1707
  return { ok: true, value: undefined };
1037
1708
  },
1038
1709
  commit: () => {
1710
+ if (passTimingCapture !== undefined) {
1711
+ const completion = internals.device.queue.onSubmittedWorkDone();
1712
+ timingHost.gpuPassTimingSubmittedWork = completion;
1713
+ passTimingCapture.markSubmitted(completion);
1714
+ }
1039
1715
  if (frameState.bloomFrameReceipts !== undefined) {
1040
1716
  internals
1041
1717
  .getPipelineState()
@@ -1049,7 +1725,7 @@ export function executeCompiledFrameGraph(
1049
1725
  frameState.activeTemporalGpuState = stagedGpuState;
1050
1726
  if (previousGpuState !== undefined && previousGpuState !== stagedGpuState) {
1051
1727
  internals.clearPostProcessPipelineCache?.('forgeax.taa-resolve');
1052
- internals.invalidateShaderModule?.('post-process-forgeax.taa-resolve-module');
1728
+ invalidatePostProcessModule('forgeax.taa-resolve');
1053
1729
  retireTemporalGpuStateAfterFence(
1054
1730
  previousGpuState,
1055
1731
  internals.device.queue,
@@ -1079,7 +1755,7 @@ export function executeCompiledFrameGraph(
1079
1755
  }
1080
1756
  if (stagedTemporalCommit.kind === 'off') {
1081
1757
  internals.clearPostProcessPipelineCache?.('forgeax.taa-resolve');
1082
- internals.invalidateShaderModule?.('post-process-forgeax.taa-resolve-module');
1758
+ invalidatePostProcessModule('forgeax.taa-resolve');
1083
1759
  const activeGpuState = frameState.activeTemporalGpuState;
1084
1760
  if (activeGpuState !== undefined) {
1085
1761
  retireTemporalGpuStateAfterFence(
@@ -1104,8 +1780,12 @@ export function executeCompiledFrameGraph(
1104
1780
  frameState.environmentLifecycle?.publish(frameState.environmentGeneration);
1105
1781
  frameState.environmentGeneration = undefined;
1106
1782
  }
1783
+ onSubmitted?.(internals.device.queue.onSubmittedWorkDone());
1107
1784
  },
1108
1785
  abort: (failure) => {
1786
+ passTimingCapture?.abort({ code: failure.stage });
1787
+ timingHost.gpuPassTimingCapture = undefined;
1788
+ timingHost.gpuPassTimingSubmittedWork = undefined;
1109
1789
  timingCapture?.discard();
1110
1790
  frameState.bloomFrameReceipts = undefined;
1111
1791
  if (frameState.environmentGeneration !== undefined) {
@@ -1133,3 +1813,172 @@ export function executeCompiledFrameGraph(
1133
1813
  if (!transaction.ok) rejectTemporalFrame();
1134
1814
  return transaction.ok;
1135
1815
  }
1816
+
1817
+ function getTimingInstrumentationState(
1818
+ internals: RenderSystemInternals,
1819
+ capture: GpuPassTimingCapture,
1820
+ ): TimingInstrumentationState {
1821
+ const existing = timingInstrumentationStates.get(internals);
1822
+ if (existing !== undefined) {
1823
+ existing.setCapture(capture);
1824
+ return existing;
1825
+ }
1826
+ type TimestampWrites = NonNullable<ReturnType<GpuPassTimingCapture['timestampWrites']>>;
1827
+ const activeIdentity: {
1828
+ passName: string;
1829
+ passKind: GpuPassTimingPassIdentity['passKind'];
1830
+ executionIndex: number;
1831
+ } = {
1832
+ passName: '',
1833
+ passKind: 'raster',
1834
+ executionIndex: 0,
1835
+ };
1836
+ let activeWrites: TimestampWrites | undefined;
1837
+ let activeCapture: GpuPassTimingCapture | undefined = capture;
1838
+ const rasterScope: RenderGraphPassInstrumentationScope = {
1839
+ renderPassDescriptor: (descriptor) => {
1840
+ const capture = activeCapture;
1841
+ const identity = activeIdentity;
1842
+ const writes = activeWrites;
1843
+ if (capture === undefined || identity === undefined || writes === undefined) {
1844
+ return descriptor;
1845
+ }
1846
+ if (descriptor.timestampWrites !== undefined) {
1847
+ capture.markOwnerConflict(identity);
1848
+ return descriptor;
1849
+ }
1850
+ (descriptor as { timestampWrites?: TimestampWrites }).timestampWrites = writes;
1851
+ return descriptor;
1852
+ },
1853
+ };
1854
+ const computeScope: RenderGraphPassInstrumentationScope = {
1855
+ computePassDescriptor: (descriptor) => {
1856
+ const capture = activeCapture;
1857
+ const identity = activeIdentity;
1858
+ const writes = activeWrites;
1859
+ if (capture === undefined || identity === undefined || writes === undefined) {
1860
+ return descriptor;
1861
+ }
1862
+ if (descriptor.timestampWrites !== undefined) {
1863
+ capture.markOwnerConflict(identity);
1864
+ return descriptor;
1865
+ }
1866
+ (descriptor as { timestampWrites?: TimestampWrites }).timestampWrites = writes;
1867
+ return descriptor;
1868
+ },
1869
+ };
1870
+ const copyScope: RenderGraphPassInstrumentationScope = {
1871
+ beforeCopy: (encoder) => {
1872
+ const capture = activeCapture;
1873
+ const identity = activeIdentity;
1874
+ if (capture === undefined || identity === undefined) return;
1875
+ capture.copyBoundaryBefore(identity, encoder);
1876
+ },
1877
+ afterCopy: (encoder) => {
1878
+ const capture = activeCapture;
1879
+ const identity = activeIdentity;
1880
+ if (capture === undefined || identity === undefined) return;
1881
+ capture.copyBoundaryAfter(identity, encoder);
1882
+ },
1883
+ };
1884
+ const instrumentation: RenderGraphPassInstrumentation<RenderPipelineFrame> = {
1885
+ begin: (pass) => {
1886
+ const capture = activeCapture;
1887
+ if (capture === undefined) return undefined;
1888
+ activeIdentity.passName = pass.name;
1889
+ activeIdentity.passKind = pass.kind;
1890
+ activeIdentity.executionIndex = pass.executionIndex;
1891
+ activeWrites = capture.recordPass(activeIdentity);
1892
+ if (activeWrites === undefined) return undefined;
1893
+ switch (pass.kind) {
1894
+ case 'raster':
1895
+ return rasterScope;
1896
+ case 'compute':
1897
+ return computeScope;
1898
+ case 'copy':
1899
+ return copyScope;
1900
+ }
1901
+ },
1902
+ };
1903
+ const state: TimingInstrumentationState = {
1904
+ instrumentation,
1905
+ setCapture: (capture) => {
1906
+ activeCapture = capture;
1907
+ if (capture === undefined) {
1908
+ activeWrites = undefined;
1909
+ }
1910
+ },
1911
+ };
1912
+ timingInstrumentationStates.set(internals, state);
1913
+ return state;
1914
+ }
1915
+
1916
+ function getVolumeTimingInstrumentationState(
1917
+ internals: RenderSystemInternals,
1918
+ capture: GpuTimingCapture,
1919
+ ): VolumeTimingInstrumentationState {
1920
+ const existing = volumeTimingInstrumentationStates.get(internals);
1921
+ if (existing !== undefined) {
1922
+ existing.setCapture(capture);
1923
+ return existing;
1924
+ }
1925
+ let activeCapture: GpuTimingCapture | undefined = capture;
1926
+ let activePassName: string | undefined;
1927
+ let activeWrites: VolumeTimingTimestampWrites | undefined;
1928
+ const rasterScope: RenderGraphPassInstrumentationScope = {
1929
+ renderPassDescriptor: (descriptor) => {
1930
+ const currentCapture = activeCapture;
1931
+ const passName = activePassName;
1932
+ const writes = activeWrites;
1933
+ if (currentCapture === undefined || passName === undefined || writes === undefined) {
1934
+ return descriptor;
1935
+ }
1936
+ if (descriptor.timestampWrites !== undefined) {
1937
+ currentCapture.skipPass(passName);
1938
+ activeWrites = undefined;
1939
+ return descriptor;
1940
+ }
1941
+ return { ...descriptor, timestampWrites: writes };
1942
+ },
1943
+ };
1944
+ const computeScope: RenderGraphPassInstrumentationScope = {
1945
+ computePassDescriptor: (descriptor) => {
1946
+ const currentCapture = activeCapture;
1947
+ const passName = activePassName;
1948
+ const writes = activeWrites;
1949
+ if (currentCapture === undefined || passName === undefined || writes === undefined) {
1950
+ return descriptor;
1951
+ }
1952
+ if (descriptor.timestampWrites !== undefined) {
1953
+ currentCapture.skipPass(passName);
1954
+ activeWrites = undefined;
1955
+ return descriptor;
1956
+ }
1957
+ return { ...descriptor, timestampWrites: writes };
1958
+ },
1959
+ };
1960
+ const instrumentation: RenderGraphPassInstrumentation<RenderPipelineFrame> = {
1961
+ begin: (pass) => {
1962
+ const currentCapture = activeCapture;
1963
+ if (currentCapture === undefined) return undefined;
1964
+ activePassName = pass.name;
1965
+ activeWrites = currentCapture.recordPass(pass.name);
1966
+ if (activeWrites === undefined) return undefined;
1967
+ if (pass.kind === 'raster') return rasterScope;
1968
+ if (pass.kind === 'compute') return computeScope;
1969
+ currentCapture.skipPass(pass.name);
1970
+ activeWrites = undefined;
1971
+ return undefined;
1972
+ },
1973
+ };
1974
+ const state: VolumeTimingInstrumentationState = {
1975
+ instrumentation,
1976
+ setCapture: (nextCapture) => {
1977
+ activeCapture = nextCapture;
1978
+ activePassName = undefined;
1979
+ activeWrites = undefined;
1980
+ },
1981
+ };
1982
+ volumeTimingInstrumentationStates.set(internals, state);
1983
+ return state;
1984
+ }