@forgeax/engine-render 0.1.19 → 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 (452) 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 +34 -20
  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-RJRUS55L.mjs → chunk-5YADUCA7.mjs} +1896 -1624
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-LOSO4CTV.mjs → chunk-6OPXDQ2E.mjs} +423 -46
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-IPXCABEP.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/chunk-FS2KNUQH.mjs +1294 -0
  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/components/spot-light.d.ts +12 -0
  42. package/dist/components/spot-light.d.ts.map +1 -1
  43. package/dist/construct-renderer.mjs +8552 -1612
  44. package/dist/construct-renderer.mjs.map +1 -1
  45. package/dist/device/gpu-residency.d.ts +19 -2
  46. package/dist/device/gpu-residency.d.ts.map +1 -1
  47. package/dist/environment/frame.d.ts.map +1 -1
  48. package/dist/environment/lifecycle.d.ts +5 -0
  49. package/dist/environment/lifecycle.d.ts.map +1 -1
  50. package/dist/errors/render.d.ts +209 -93
  51. package/dist/errors/render.d.ts.map +1 -1
  52. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  53. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  54. package/dist/extract/environment.d.ts +14 -0
  55. package/dist/extract/environment.d.ts.map +1 -1
  56. package/dist/extract/visibility.d.ts.map +1 -1
  57. package/dist/features/fullscreen.d.ts.map +1 -1
  58. package/dist/features/targets.d.ts.map +1 -1
  59. package/dist/features/types.d.ts +10 -0
  60. package/dist/features/types.d.ts.map +1 -1
  61. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  62. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  63. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  64. package/dist/gpu-driven/production-raster.d.ts +0 -1
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-scene.d.ts.map +1 -1
  67. package/dist/gpu-usage.d.ts +2 -0
  68. package/dist/gpu-usage.d.ts.map +1 -1
  69. package/dist/hdrp-buffers.d.ts +19 -17
  70. package/dist/hdrp-buffers.d.ts.map +1 -1
  71. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  72. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  73. package/dist/ibl/kernel-cache.d.ts +36 -0
  74. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  75. package/dist/index.d.ts +35 -4
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.mjs +4 -4
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/inspection-types.d.ts +51 -0
  80. package/dist/inspection-types.d.ts.map +1 -1
  81. package/dist/instances-derived-bounds.d.ts +50 -0
  82. package/dist/instances-derived-bounds.d.ts.map +1 -0
  83. package/dist/internal.mjs +1 -1
  84. package/dist/light-buffer-layout.d.ts +30 -13
  85. package/dist/light-buffer-layout.d.ts.map +1 -1
  86. package/dist/mesh-material-bindings.d.ts +54 -0
  87. package/dist/mesh-material-bindings.d.ts.map +1 -1
  88. package/dist/pbr-pipeline.d.ts +20 -7
  89. package/dist/pbr-pipeline.d.ts.map +1 -1
  90. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  93. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  95. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  97. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  99. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  101. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  103. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  104. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  105. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  106. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  107. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  108. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  109. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  110. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  111. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  112. package/dist/pipeline/standard-post.d.ts.map +1 -1
  113. package/dist/pipeline/standard-profile.d.ts +12 -20
  114. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  115. package/dist/pipeline-spec.d.ts +40 -7
  116. package/dist/pipeline-spec.d.ts.map +1 -1
  117. package/dist/point-shadow-inspection.d.ts +21 -0
  118. package/dist/point-shadow-inspection.d.ts.map +1 -0
  119. package/dist/record/frame-lighting.d.ts +29 -32
  120. package/dist/record/frame-lighting.d.ts.map +1 -1
  121. package/dist/record/frame-snapshot.d.ts +65 -15
  122. package/dist/record/frame-snapshot.d.ts.map +1 -1
  123. package/dist/record/frame-targets.d.ts +5 -0
  124. package/dist/record/frame-targets.d.ts.map +1 -1
  125. package/dist/record/frame.d.ts +12 -5
  126. package/dist/record/frame.d.ts.map +1 -1
  127. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  128. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  130. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  131. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  132. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  133. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  134. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  135. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  136. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  137. package/dist/record/gpu-timing.d.ts +37 -0
  138. package/dist/record/gpu-timing.d.ts.map +1 -0
  139. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  140. package/dist/record/main-pass-material.d.ts +23 -2
  141. package/dist/record/main-pass-material.d.ts.map +1 -1
  142. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  143. package/dist/record/main-pass.d.ts +2 -0
  144. package/dist/record/main-pass.d.ts.map +1 -1
  145. package/dist/record/render-context.d.ts +48 -3
  146. package/dist/record/render-context.d.ts.map +1 -1
  147. package/dist/record/shadow-pass.d.ts.map +1 -1
  148. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  149. package/dist/record/typed-frame-graph.d.ts +81 -5
  150. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  151. package/dist/record/view-ubo.d.ts +7 -4
  152. package/dist/record/view-ubo.d.ts.map +1 -1
  153. package/dist/reflection/filter.d.ts +22 -0
  154. package/dist/reflection/filter.d.ts.map +1 -0
  155. package/dist/reflection/gpu-table.d.ts +25 -0
  156. package/dist/reflection/gpu-table.d.ts.map +1 -0
  157. package/dist/reflection/projection.d.ts +91 -0
  158. package/dist/reflection/projection.d.ts.map +1 -0
  159. package/dist/render-contract.d.ts +114 -7
  160. package/dist/render-contract.d.ts.map +1 -1
  161. package/dist/render-data.d.ts +10 -6
  162. package/dist/render-data.d.ts.map +1 -1
  163. package/dist/render-graph-primitives.d.ts +1 -0
  164. package/dist/render-graph-primitives.d.ts.map +1 -1
  165. package/dist/render-pipeline.d.ts +78 -2
  166. package/dist/render-pipeline.d.ts.map +1 -1
  167. package/dist/render-system-extract.d.ts +113 -20
  168. package/dist/render-system-extract.d.ts.map +1 -1
  169. package/dist/render-system.d.ts +24 -3
  170. package/dist/render-system.d.ts.map +1 -1
  171. package/dist/renderer-inspect.d.ts +29 -0
  172. package/dist/renderer-inspect.d.ts.map +1 -0
  173. package/dist/renderer.d.ts +43 -0
  174. package/dist/renderer.d.ts.map +1 -0
  175. package/dist/scene/render-scene.d.ts +14 -0
  176. package/dist/scene/render-scene.d.ts.map +1 -1
  177. package/dist/shadow-atlas.d.ts +3 -3
  178. package/dist/shadow-atlas.d.ts.map +1 -1
  179. package/dist/ssao-buffers.d.ts +3 -0
  180. package/dist/ssao-buffers.d.ts.map +1 -1
  181. package/dist/targets/contracts.d.ts +66 -0
  182. package/dist/targets/contracts.d.ts.map +1 -0
  183. package/dist/targets/graph-projection.d.ts +29 -0
  184. package/dist/targets/graph-projection.d.ts.map +1 -0
  185. package/dist/targets/material-source.d.ts +34 -0
  186. package/dist/targets/material-source.d.ts.map +1 -0
  187. package/dist/targets/owner.d.ts +32 -0
  188. package/dist/targets/owner.d.ts.map +1 -0
  189. package/dist/targets/physical.d.ts +18 -0
  190. package/dist/targets/physical.d.ts.map +1 -0
  191. package/dist/targets/readback.d.ts +39 -0
  192. package/dist/targets/readback.d.ts.map +1 -0
  193. package/dist/temporal/frame.d.ts +44 -0
  194. package/dist/temporal/frame.d.ts.map +1 -0
  195. package/dist/temporal/index.mjs +3 -3
  196. package/dist/typed-render-graph-primitives.d.ts +6 -1
  197. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  198. package/dist/typed-shadow-atlas.d.ts +10 -0
  199. package/dist/typed-shadow-atlas.d.ts.map +1 -0
  200. package/dist/typed-shadow-passes.d.ts +1 -1
  201. package/dist/typed-shadow-passes.d.ts.map +1 -1
  202. package/dist/volume/capability.d.ts +57 -0
  203. package/dist/volume/capability.d.ts.map +1 -0
  204. package/dist/volume/component.d.ts +61 -0
  205. package/dist/volume/component.d.ts.map +1 -0
  206. package/dist/volume/extract.d.ts +11 -0
  207. package/dist/volume/extract.d.ts.map +1 -0
  208. package/dist/volume/inspection.d.ts +38 -0
  209. package/dist/volume/inspection.d.ts.map +1 -0
  210. package/dist/volume/passes.d.ts +38 -0
  211. package/dist/volume/passes.d.ts.map +1 -0
  212. package/dist/volume/recovery.d.ts +38 -0
  213. package/dist/volume/recovery.d.ts.map +1 -0
  214. package/dist/volume/resources.d.ts +57 -0
  215. package/dist/volume/resources.d.ts.map +1 -0
  216. package/dist/volume/temporal.d.ts +35 -0
  217. package/dist/volume/temporal.d.ts.map +1 -0
  218. package/package.json +20 -20
  219. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  220. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  221. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  222. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  223. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  224. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  225. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  226. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  227. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  228. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  229. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  230. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  231. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  232. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  233. package/src/__tests__/fog-component.unit.test.ts +67 -0
  234. package/src/__tests__/fog.integration.test.ts +86 -0
  235. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  236. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  237. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  238. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  239. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  240. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  241. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  242. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  243. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  244. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  245. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  246. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  247. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  248. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  249. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  250. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  251. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  252. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
  253. package/src/__tests__/material-observation.integration.test.ts +101 -0
  254. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  255. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  256. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  257. package/src/__tests__/public-surface.test-d.ts +10 -0
  258. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  259. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  260. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  261. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  262. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  263. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  264. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  265. package/src/__tests__/render-error-code-owner.test-d.ts +23 -3
  266. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  267. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  268. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  269. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  270. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  271. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  272. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  273. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  274. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  275. package/src/__tests__/render-target-public-schema.json +107 -0
  276. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  277. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  278. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  279. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  280. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  281. package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
  282. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  283. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  284. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  285. package/src/__tests__/skinned-shadow-caster.test.ts +32 -2
  286. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  287. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  288. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  289. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  290. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  291. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  292. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  293. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  294. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  295. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  296. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  297. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -36
  298. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  299. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  300. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  301. package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
  302. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
  303. package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
  304. package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
  305. package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
  306. package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
  307. package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
  308. package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
  309. package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
  310. package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
  311. package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
  312. package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
  313. package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
  315. package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
  316. package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
  317. package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
  318. package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
  319. package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
  320. package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
  321. package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
  322. package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
  323. package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
  324. package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
  325. package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
  326. package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
  327. package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
  328. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
  329. package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
  330. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
  331. package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
  332. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
  333. package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
  334. package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
  335. package/src/assembly/device-feature-admission.ts +73 -0
  336. package/src/assembly/directional-shadow-inspection.ts +122 -0
  337. package/src/assembly/factory.ts +1093 -246
  338. package/src/assembly/host-contract.ts +25 -0
  339. package/src/assembly/render-target-host.ts +847 -0
  340. package/src/assets/asset-decoders.ts +1 -1
  341. package/src/assets/render-asset-port.ts +7 -1
  342. package/src/capture/cube-views.ts +69 -0
  343. package/src/capture/scheduler.ts +265 -0
  344. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  345. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  346. package/src/components/camera.ts +4 -0
  347. package/src/components/cube-camera.ts +52 -0
  348. package/src/components/directional-light.ts +22 -2
  349. package/src/components/directional-shadow-filter.ts +57 -0
  350. package/src/components/index.ts +2 -0
  351. package/src/components/light-helpers.ts +85 -7
  352. package/src/components/reflection-probe.ts +49 -0
  353. package/src/components/spot-light.ts +22 -3
  354. package/src/device/gpu-residency.ts +168 -58
  355. package/src/environment/frame.ts +6 -24
  356. package/src/environment/lifecycle.ts +12 -0
  357. package/src/errors/render.ts +405 -143
  358. package/src/extract/directional-shadow-projection.ts +61 -0
  359. package/src/extract/environment.ts +57 -0
  360. package/src/extract/visibility.ts +25 -1
  361. package/src/features/fullscreen.ts +3 -0
  362. package/src/features/targets.ts +4 -1
  363. package/src/features/types.ts +10 -0
  364. package/src/fullscreen-post-process-pass.ts +43 -2
  365. package/src/gpu-driven/batch-topology.ts +1 -0
  366. package/src/gpu-driven/production-raster.ts +1 -3
  367. package/src/gpu-scene.ts +8 -1
  368. package/src/gpu-usage.ts +2 -0
  369. package/src/hdrp-buffers.ts +165 -59
  370. package/src/ibl/IblPipelineCache.ts +11 -0
  371. package/src/ibl/kernel-cache.ts +88 -0
  372. package/src/index.ts +186 -1
  373. package/src/inspection-types.ts +56 -0
  374. package/src/instances-derived-bounds.ts +171 -0
  375. package/src/light-buffer-layout.ts +74 -26
  376. package/src/mesh-material-bindings.ts +132 -0
  377. package/src/pbr-pipeline.ts +139 -27
  378. package/src/pipeline/standard-forward-lane.ts +65 -15
  379. package/src/pipeline/standard-lighting/errors.ts +6 -0
  380. package/src/pipeline/standard-lighting/graph.ts +157 -0
  381. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  382. package/src/pipeline/standard-lighting/layout.ts +31 -0
  383. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  384. package/src/pipeline/standard-lighting/membership.ts +43 -0
  385. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  386. package/src/pipeline/standard-lighting/topology.ts +120 -0
  387. package/src/pipeline/standard-lighting/transport.ts +77 -0
  388. package/src/pipeline/standard-pipeline.ts +141 -192
  389. package/src/pipeline/standard-post.ts +26 -7
  390. package/src/pipeline/standard-profile.ts +23 -30
  391. package/src/pipeline-spec.ts +84 -21
  392. package/src/point-shadow-inspection.ts +55 -0
  393. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  394. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  395. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  396. package/src/record/frame-lighting.ts +470 -516
  397. package/src/record/frame-snapshot.ts +76 -28
  398. package/src/record/frame-targets.ts +23 -2
  399. package/src/record/frame.ts +1169 -79
  400. package/src/record/gpu-pass-timing/contract.ts +180 -0
  401. package/src/record/gpu-pass-timing/errors.ts +34 -0
  402. package/src/record/gpu-pass-timing/index.ts +37 -0
  403. package/src/record/gpu-pass-timing/parser.ts +97 -0
  404. package/src/record/gpu-pass-timing/session.ts +938 -0
  405. package/src/record/gpu-timing.ts +233 -0
  406. package/src/record/main-pass-geometry.ts +120 -80
  407. package/src/record/main-pass-material.ts +101 -11
  408. package/src/record/main-pass-sprite-draws.ts +54 -25
  409. package/src/record/main-pass.ts +71 -15
  410. package/src/record/render-context.ts +77 -0
  411. package/src/record/shadow-pass.ts +104 -66
  412. package/src/record/skybox-post-pass.ts +20 -4
  413. package/src/record/typed-frame-graph.ts +1016 -32
  414. package/src/record/view-ubo.ts +63 -28
  415. package/src/reflection/filter.ts +81 -0
  416. package/src/reflection/gpu-table.ts +44 -0
  417. package/src/reflection/projection.ts +281 -0
  418. package/src/render-contract.ts +147 -4
  419. package/src/render-data.ts +50 -22
  420. package/src/render-graph-primitives.ts +9 -4
  421. package/src/render-pipeline.ts +161 -2
  422. package/src/render-system-extract.ts +886 -103
  423. package/src/render-system.ts +1174 -66
  424. package/src/renderer-inspect.ts +66 -0
  425. package/src/renderer.ts +222 -0
  426. package/src/scene/render-scene.ts +117 -9
  427. package/src/shadow-atlas.ts +5 -5
  428. package/src/ssao-buffers.ts +9 -2
  429. package/src/targets/contracts.ts +182 -0
  430. package/src/targets/graph-projection.ts +169 -0
  431. package/src/targets/material-source.ts +143 -0
  432. package/src/targets/owner.ts +214 -0
  433. package/src/targets/physical.ts +18 -0
  434. package/src/targets/readback.ts +153 -0
  435. package/src/temporal/frame.ts +128 -0
  436. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +61 -23
  437. package/src/typed-render-graph-primitives.ts +34 -2
  438. package/src/typed-shadow-atlas.ts +17 -0
  439. package/src/typed-shadow-passes.ts +47 -40
  440. package/src/volume/capability.ts +173 -0
  441. package/src/volume/component.ts +110 -0
  442. package/src/volume/extract.ts +21 -0
  443. package/src/volume/inspection.ts +78 -0
  444. package/src/volume/passes.ts +686 -0
  445. package/src/volume/recovery.ts +86 -0
  446. package/src/volume/resources.ts +236 -0
  447. package/src/volume/temporal.ts +89 -0
  448. package/dist/chunk-IPXCABEP.mjs.map +0 -1
  449. package/dist/chunk-LOSO4CTV.mjs.map +0 -1
  450. package/dist/chunk-RJRUS55L.mjs.map +0 -1
  451. package/dist/chunk-YTQ5C7YQ.mjs +0 -584
  452. package/dist/chunk-YTQ5C7YQ.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,14 +49,21 @@ import {
40
49
  GPU_TEXTURE_USAGE_RENDER_ATTACHMENT,
41
50
  GPU_TEXTURE_USAGE_TEXTURE_BINDING,
42
51
  } from '../gpu-texture-usage';
43
- import { STANDARD_PIPELINE_ID } from '../pipeline/standard-profile';
52
+ import {
53
+ type StandardTopologyInputValue,
54
+ standardLightingTopologySignature,
55
+ } from '../pipeline/standard-lighting/topology';
56
+ import { resolveVolumetricFogProfile, STANDARD_PIPELINE_ID } from '../pipeline/standard-profile';
44
57
  import type { CameraSnapshot } from '../render-contract';
45
58
  import type {
46
59
  RenderPipelineFeatureTarget,
47
60
  RenderPipelineFrame,
48
61
  RenderPipelineTopology,
49
62
  } from '../render-pipeline';
50
- import type { ExtractedLights } from '../render-system-extract';
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,
@@ -60,9 +76,19 @@ import {
60
76
  } from '../temporal/gpu';
61
77
  import { isSceneDataTarget } from '../temporal/scene-data';
62
78
  import type { TransmissionDemand } from '../transmission/projection';
79
+ import { hasVolumetricFogCapability } from '../volume/capability';
80
+ import { deriveVolumetricFogExtent, deriveVolumetricFogResolvedExtent } from '../volume/resources';
63
81
  import { buildPerFrameBindGroups } from './frame-lighting';
64
82
  import { getTextureIdentity, type RenderFrameState } from './frame-snapshot';
65
- 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';
66
92
  import { resolveSurfaceFormatPair, resolveSurfaceProfile } from './render-context';
67
93
 
68
94
  export interface TemporalGraphCapabilities {
@@ -203,6 +229,465 @@ export function createTemporalGraphRoster(input: {
203
229
  };
204
230
  }
205
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
+
206
691
  export interface RenderFeatureGraphRuntimeState {
207
692
  plans: readonly RenderFeaturePlannedFrame[];
208
693
  fullscreenEffects: ReadonlyMap<
@@ -227,10 +712,34 @@ export interface RenderFeatureGraphCandidate {
227
712
  readonly preparedResourceKey?: string;
228
713
  /** Release candidate-owned prepared resources when graph promotion fails. */
229
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;
230
719
  }
231
720
 
232
721
  const featureGraphStates = new WeakMap<RenderSystemInternals, RenderFeatureGraphRuntimeState>();
233
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
+
234
743
  type CompiledGraphTargetAccess = CompiledRenderGraph<RenderPipelineFrame> & {
235
744
  readonly getColorTargetDescriptor: (
236
745
  name: string,
@@ -281,6 +790,31 @@ export function renderFeatureGraphPlanSignature(
281
790
  );
282
791
  }
283
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
+
284
818
  function validateRenderFeaturePlans(
285
819
  plans: readonly RenderFeaturePlannedFrame[],
286
820
  ): Result<readonly RenderFeaturePlanExecution[], RenderError> {
@@ -311,6 +845,10 @@ function commitFeatureCandidate(
311
845
  internals: RenderSystemInternals,
312
846
  candidate: RenderFeatureGraphCandidate,
313
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?.();
314
852
  const state = getRenderFeatureGraphState(internals);
315
853
  state.plans = candidate.plans;
316
854
  state.fullscreenEffects = candidate.fullscreenEffects;
@@ -328,7 +866,10 @@ function topologyOf(
328
866
  shadowMapSize: number | undefined,
329
867
  gpuDriven: PreparedGpuDrivenFrame | undefined,
330
868
  featureGraphCandidate: RenderFeatureGraphCandidate | undefined,
869
+ cubeCaptureState: CubeCaptureGraphState | undefined,
331
870
  transmissionDemand: TransmissionDemand | undefined,
871
+ volumetricFog: ExtractedVolumetricFog | undefined,
872
+ standardLighting: StandardTopologyInputValue | undefined,
332
873
  ): Result<RenderPipelineTopology, RhiError> {
333
874
  const cascadeCount = Math.max(1, Math.min(4, Math.round(lights.cascadeCount ?? 1))) as
334
875
  | 1
@@ -353,6 +894,10 @@ function topologyOf(
353
894
  ...(featureGraphCandidate?.fullscreenEffects ??
354
895
  getRenderFeatureGraphState(internals).fullscreenEffects),
355
896
  ].map(([id]) => id);
897
+ const featurePostEffectDeclarations = [
898
+ ...(featureGraphCandidate?.fullscreenEffects ??
899
+ getRenderFeatureGraphState(internals).fullscreenEffects),
900
+ ].map(([id, entry]) => [id, postProcessShaderEntrySignature(entry)] as const);
356
901
  const config =
357
902
  featurePostEffects.length === 0
358
903
  ? frameState.installedPipelineConfig
@@ -365,6 +910,21 @@ function topologyOf(
365
910
  ]),
366
911
  ],
367
912
  };
913
+ const volumeCapability = hasVolumetricFogCapability(
914
+ internals.device.caps,
915
+ internals.volumetricFogShaders,
916
+ );
917
+ const selectedVolumeLight =
918
+ volumetricFog?.lightKind === 'spot'
919
+ ? lights.spot.find((light) => light.entity === volumetricFog.lightEntity)
920
+ : volumetricFog?.lightKind === 'point'
921
+ ? lights.point.find((light) => light.entity === volumetricFog.lightEntity)
922
+ : volumetricFog?.lightKind === 'directional' &&
923
+ lights.directional?.entity === volumetricFog.lightEntity
924
+ ? lights.directional
925
+ : volumetricFog?.lightKind === undefined
926
+ ? lights.directional
927
+ : undefined;
368
928
  return ok({
369
929
  pipelineId: STANDARD_PIPELINE_ID,
370
930
  standardProfile: internals.standardProfile,
@@ -387,10 +947,13 @@ function topologyOf(
387
947
  motionBlur: isMotionBlurTemporalDemand(camera.motionBlur),
388
948
  },
389
949
  shadow: {
390
- mapSize: shadowMapSize ?? 1024,
391
- cascadeCount,
392
- pointCount: Math.min(4, lights.pointShadow.length),
393
- 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,
394
957
  spotCount: Math.min(
395
958
  4,
396
959
  lights.spot.filter(
@@ -398,6 +961,47 @@ function topologyOf(
398
961
  ).length,
399
962
  ),
400
963
  },
964
+ volumetricFog:
965
+ volumetricFog?.status === 'available' &&
966
+ volumetricFog.densityAsset?.shape.viewDimension === '3d' &&
967
+ camera.tonemap !== 'none' &&
968
+ selectedVolumeLight !== undefined &&
969
+ volumeCapability
970
+ ? (() => {
971
+ const volumeProfile = resolveVolumetricFogProfile(internals.standardProfile ?? {});
972
+ return {
973
+ enabled: true,
974
+ format: volumetricFog.densityAsset.format,
975
+ extent: {
976
+ width: volumetricFog.densityAsset.shape.extent.width,
977
+ height: volumetricFog.densityAsset.shape.extent.height,
978
+ depth: volumetricFog.densityAsset.shape.extent.depth,
979
+ },
980
+ froxelExtent: deriveVolumetricFogExtent(
981
+ { width, height },
982
+ volumeProfile.depth,
983
+ volumeProfile.tileSize,
984
+ ),
985
+ resolvedExtent: deriveVolumetricFogResolvedExtent(
986
+ { width, height },
987
+ volumeProfile.tileSize,
988
+ ),
989
+ lightKind: volumetricFog.lightKind,
990
+ lightEntity: volumetricFog.lightEntity,
991
+ pointLightEntity: volumetricFog.pointLightEntity,
992
+ spotLightEntity: volumetricFog.spotLightEntity,
993
+ ...(volumetricFog.projector === undefined
994
+ ? {}
995
+ : {
996
+ projector: {
997
+ guid: volumetricFog.projector.guid,
998
+ generation: volumetricFog.projector.generation,
999
+ revision: volumetricFog.projector.revision,
1000
+ },
1001
+ }),
1002
+ };
1003
+ })()
1004
+ : { enabled: false },
401
1005
  lane: {
402
1006
  compute: internals.device.caps.compute,
403
1007
  storageBuffer: internals.device.caps.storageBuffer,
@@ -408,10 +1012,12 @@ function topologyOf(
408
1012
  plans: renderFeatureGraphPlanSignature(
409
1013
  featureGraphCandidate?.plans ?? getRenderFeatureGraphState(internals).plans,
410
1014
  ),
411
- fullscreenEffects: featurePostEffects,
1015
+ fullscreenEffects: featurePostEffectDeclarations,
412
1016
  preparedResourceKey: featureGraphCandidate?.preparedResourceKey ?? '',
1017
+ cubeCaptureSlots: cubeCaptureState?.work.length ?? 0,
413
1018
  }),
414
1019
  gpuDrivenTopologySignature: gpuDriven?.topologySignature ?? '',
1020
+ standardLightingTopologySignature: standardLightingTopologySignature(standardLighting),
415
1021
  transmissionDemand: transmissionDemand ?? {
416
1022
  activeCount: 0,
417
1023
  needsRoughMips: false,
@@ -467,14 +1073,17 @@ function targetResolver(
467
1073
  };
468
1074
  }
469
1075
 
470
- function resolveTargetBindings(input: {
471
- readonly frame: RenderPipelineFrame;
472
- readonly binding: import('../prepare/prepared-graphics-resolver').PreparedGraphicsResolvedResource & {
473
- readonly kind: 'bindings';
474
- };
475
- readonly resources: GraphResourceResolver;
476
- readonly resolveTarget: RenderFeatureGraphTargetResolver;
477
- }): 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 {
478
1087
  const frame = input.frame as import('./render-context')._InternalRenderPipelineContext;
479
1088
  const descriptor = input.binding.descriptor;
480
1089
  const pipeline = input.binding.pipeline as
@@ -486,7 +1095,8 @@ function resolveTargetBindings(input: {
486
1095
  if (descriptor?.values.fullscreen === true && isTemporalFullscreenBinding(descriptor.values)) {
487
1096
  const shader = descriptor.values.shader;
488
1097
  if (typeof shader !== 'string') return undefined;
489
- const entry = frame.runtime.lookupPostProcess?.(shader);
1098
+ const entry =
1099
+ featurePostProcessEntries?.get(shader) ?? frame.runtime.lookupPostProcess?.(shader);
490
1100
  if (entry === undefined) return undefined;
491
1101
  const fullscreen = buildFullscreenPostProcessPass(
492
1102
  { device: frame.runtime.device, errorRegistry: frame.runtime.errorRegistry },
@@ -576,8 +1186,10 @@ function resolveTargetBindings(input: {
576
1186
  directionalShadow: frame.frameState.currentDirectionalShadowView ?? undefined,
577
1187
  spotShadow: frame.frameState.currentSpotShadowView ?? undefined,
578
1188
  },
1189
+ true,
1190
+ frame.standardLighting,
579
1191
  ).viewBindGroup;
580
- return viewBindGroup === null ? undefined : { handle: viewBindGroup, dynamicOffsets: [0] };
1192
+ return viewBindGroup === null ? undefined : { handle: viewBindGroup, dynamicOffsets: [0, 0] };
581
1193
  }
582
1194
  const target = input.resolveTarget(sceneDepth);
583
1195
  if (target === undefined) return undefined;
@@ -610,6 +1222,32 @@ function retire(
610
1222
  .catch(() => undefined);
611
1223
  }
612
1224
 
1225
+ /**
1226
+ * Settle a volume topology candidate after the command buffer outcome is
1227
+ * known. A graph that failed to encode, finish, or submit must never become
1228
+ * the next frame's accepted graph: retire it and restore the prior graph (or
1229
+ * leave the renderer without a graph when this was the first attempt).
1230
+ */
1231
+ export function settleVolumetricFogGraphCandidate(
1232
+ frameState: RenderFrameState,
1233
+ submitted: boolean,
1234
+ ): void {
1235
+ const candidate = frameState.volumetricFogCandidateGraph ?? null;
1236
+ if (candidate === null) return;
1237
+ const previous = frameState.volumetricFogPreviousGraph ?? null;
1238
+ const previousKey = frameState.volumetricFogPreviousGraphKey ?? null;
1239
+ if (submitted) {
1240
+ if (previous !== null && previous !== candidate) retire(frameState, previous);
1241
+ } else {
1242
+ if (candidate !== previous) retire(frameState, candidate);
1243
+ frameState.compiledFrameGraph = previous;
1244
+ frameState.compiledFrameGraphTopologyKey = previousKey;
1245
+ }
1246
+ frameState.volumetricFogPreviousGraph = null;
1247
+ frameState.volumetricFogPreviousGraphKey = null;
1248
+ frameState.volumetricFogCandidateGraph = null;
1249
+ }
1250
+
613
1251
  export function ensureCompiledFrameGraph(
614
1252
  internals: RenderSystemInternals,
615
1253
  frameState: RenderFrameState,
@@ -621,9 +1259,19 @@ export function ensureCompiledFrameGraph(
621
1259
  shadowMapSize: number | undefined,
622
1260
  gpuDriven?: PreparedGpuDrivenFrame,
623
1261
  featureGraphCandidate?: RenderFeatureGraphCandidate,
1262
+ cubeCaptureState?: CubeCaptureGraphState,
624
1263
  clearOnly = false,
625
1264
  transmissionDemand?: TransmissionDemand,
1265
+ volumetricFog?: ExtractedVolumetricFog,
1266
+ volumeTopologyCandidate = false,
1267
+ standardLighting?: StandardTopologyInputValue,
626
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;
627
1275
  const surfaceWidth = Math.max(1, width);
628
1276
  const surfaceHeight = Math.max(1, height);
629
1277
  const featurePlans = featureGraphCandidate?.plans ?? getRenderFeatureGraphState(internals).plans;
@@ -631,7 +1279,7 @@ export function ensureCompiledFrameGraph(
631
1279
  if (!projectedFeaturePlans.ok) {
632
1280
  reportRenderFeatureGraphError(internals, projectedFeaturePlans.error);
633
1281
  featureGraphCandidate?.onRejected?.();
634
- return frameState.compiledFrameGraph;
1282
+ return volumeTopologyCandidate ? null : lastKnownGood();
635
1283
  }
636
1284
  const topologyResult = topologyOf(
637
1285
  internals,
@@ -645,19 +1293,86 @@ export function ensureCompiledFrameGraph(
645
1293
  shadowMapSize,
646
1294
  gpuDriven,
647
1295
  featureGraphCandidate,
1296
+ cubeCaptureState,
648
1297
  transmissionDemand,
1298
+ volumetricFog,
1299
+ standardLighting,
649
1300
  );
650
1301
  if (!topologyResult.ok) {
651
1302
  internals.errorRegistry.fire(topologyResult.error);
652
1303
  featureGraphCandidate?.onRejected?.();
653
- return 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;
654
1345
  }
655
- const topology = topologyResult.value;
656
- 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
+ });
657
1365
  if (frameState.compiledFrameGraph !== null && frameState.compiledFrameGraphTopologyKey === key) {
658
- if (featureGraphCandidate !== undefined) {
1366
+ frameState.standardLightingGraphSignature = requestedStandardLightingSignature;
1367
+ if (featureGraphCandidate !== undefined && !postProcessAdmission.pending) {
659
1368
  commitFeatureCandidate(internals, featureGraphCandidate);
1369
+ } else if (featureGraphCandidate !== undefined) {
1370
+ featureGraphCandidate.onAbandoned?.();
660
1371
  }
1372
+ // A candidate flag only describes a possible topology replacement. When
1373
+ // the derived key is unchanged there is no replacement to stage: reuse the
1374
+ // accepted graph so dynamic volume history/imported views can advance and
1375
+ // a queue-submit failure can still publish the LKG diagnostic.
661
1376
  return frameState.compiledFrameGraph;
662
1377
  }
663
1378
 
@@ -668,6 +1383,7 @@ export function ensureCompiledFrameGraph(
668
1383
  const built = frameState.activePipeline.build(
669
1384
  {
670
1385
  graph: builder,
1386
+ ...(standardLighting === undefined ? {} : { standardLighting }),
671
1387
  capabilities: { rgba16floatRenderable: internals.device.caps.rgba16floatRenderable },
672
1388
  ...(taaHistory === undefined ? {} : { taaHistory }),
673
1389
  encodeTransmissionMip: ({ pass, resources, source }) => {
@@ -688,11 +1404,21 @@ export function ensureCompiledFrameGraph(
688
1404
  const resolveTarget = targetResolver(targets, semanticTargets, namedTargets);
689
1405
  const projected = projectPlanExecutions(builder, projectedFeaturePlans.value, {
690
1406
  resolveTarget,
691
- resolveBindings: resolveTargetBindings,
1407
+ resolveBindings: (input) => resolveTargetBindings(input, featurePostProcessEntries),
692
1408
  reportError: (error) => reportRenderFeatureGraphError(internals, error),
693
1409
  });
694
1410
  return projected.ok ? ok(undefined) : projected;
695
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
+ },
696
1422
  hasFeature: (identity) =>
697
1423
  projectedFeaturePlans.value.some((execution) => execution.featureIdentity === identity),
698
1424
  },
@@ -701,7 +1427,7 @@ export function ensureCompiledFrameGraph(
701
1427
  if (!built.ok) {
702
1428
  internals.errorRegistry.fire(built.error);
703
1429
  featureGraphCandidate?.onRejected?.();
704
- return frameState.compiledFrameGraph;
1430
+ return volumeTopologyCandidate ? null : lastKnownGood();
705
1431
  }
706
1432
  const compiled = builder.compile({
707
1433
  device: internals.device,
@@ -710,11 +1436,18 @@ export function ensureCompiledFrameGraph(
710
1436
  if (!compiled.ok) {
711
1437
  internals.errorRegistry.fire(compiled.error);
712
1438
  featureGraphCandidate?.onRejected?.();
713
- return frameState.compiledFrameGraph;
1439
+ return lastKnownGood();
714
1440
  }
715
1441
  const previous = frameState.compiledFrameGraph;
1442
+ if (volumeTopologyCandidate) {
1443
+ frameState.volumetricFogPreviousGraph = previous;
1444
+ frameState.volumetricFogPreviousGraphKey = frameState.compiledFrameGraphTopologyKey;
1445
+ frameState.volumetricFogCandidateGraph = compiled.value;
1446
+ }
716
1447
  frameState.compiledFrameGraph = compiled.value;
717
1448
  frameState.compiledFrameGraphTopologyKey = key;
1449
+ frameState.standardLightingGraphSignature = requestedStandardLightingSignature;
1450
+ frameState.compiledFrameGraphGeneration += 1;
718
1451
  const compiledTargets = compiled.value as CompiledGraphTargetAccess;
719
1452
  frameState.graphGeneration += 1;
720
1453
  frameState.perFrameGraph = {
@@ -724,10 +1457,12 @@ export function ensureCompiledFrameGraph(
724
1457
  graphGeneration: frameState.graphGeneration,
725
1458
  };
726
1459
  gpuDriven?._commitResourceReplacement();
727
- if (featureGraphCandidate !== undefined) {
1460
+ if (featureGraphCandidate !== undefined && !postProcessAdmission.pending) {
728
1461
  commitFeatureCandidate(internals, featureGraphCandidate);
1462
+ } else if (featureGraphCandidate !== undefined) {
1463
+ featureGraphCandidate.onAbandoned?.();
729
1464
  }
730
- if (previous !== null) retire(frameState, previous);
1465
+ if (!volumeTopologyCandidate && previous !== null) retire(frameState, previous);
731
1466
  return compiled.value;
732
1467
  }
733
1468
 
@@ -737,16 +1472,69 @@ export function executeCompiledFrameGraph(
737
1472
  frame: RenderPipelineFrame,
738
1473
  encoder: RhiCommandEncoder,
739
1474
  runPass?: import('@forgeax/engine-render-graph').RenderGraphPassRunner,
1475
+ readbackFaces?: readonly number[],
1476
+ onSubmitted?: (completed: Promise<unknown>) => void,
1477
+ timingCapture?: GpuTimingCapture,
740
1478
  ): boolean {
741
1479
  const graph = frameState.compiledFrameGraph;
742
- if (graph === null) return false;
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
+ };
1486
+ const rejectTemporalFrame = (): void => {
1487
+ if (frameState.temporalFrameInput === undefined) return;
1488
+ frameState.temporalFrameTransaction.commit({ accepted: false });
1489
+ frameState.temporalFrameInput = undefined;
1490
+ };
1491
+ if (graph === null) {
1492
+ timingCapture?.discard();
1493
+ settleVolumetricFogGraphCandidate(frameState, false);
1494
+ rejectTemporalFrame();
1495
+ return false;
1496
+ }
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);
743
1529
  type PreparedCommand = { readonly command: CommandBuffer };
744
1530
  let preparedCommand: PreparedCommand = { command: undefined as never };
745
1531
  const transaction = executeRendererFrameTransaction<PreparedCommand>({
746
1532
  build: () => ({ ok: true, value: { command: undefined as never } }),
747
1533
  execute: () => {
748
- const executed = graph.execute(frame, runPass);
1534
+ const executed = graph.execute(frame, runPass, timingInstrumentationState?.instrumentation);
1535
+ timingInstrumentationState?.setCapture(undefined);
749
1536
  if (!executed.ok) {
1537
+ passTimingCapture?.abort({ code: executed.error.code });
750
1538
  internals.errorRegistry.fire(executed.error);
751
1539
  return { ok: false, stage: 'execute' };
752
1540
  }
@@ -876,6 +1664,7 @@ export function executeCompiledFrameGraph(
876
1664
  );
877
1665
  }
878
1666
  }
1667
+ internals.encodeRenderTargetReadbacks?.(encoder, readbackFaces);
879
1668
  const stagedGpuState = frameState.temporalGpuState;
880
1669
  if (stagedGpuState !== undefined && !hasPendingTemporalGpuSubmit(stagedGpuState)) {
881
1670
  internals.errorRegistry.fire(
@@ -887,8 +1676,19 @@ export function executeCompiledFrameGraph(
887
1676
  );
888
1677
  return { ok: false, stage: 'finish' };
889
1678
  }
1679
+ if (passTimingCapture !== undefined) {
1680
+ const tail = passTimingCapture.encodeTail(encoder);
1681
+ if (!tail.ok) passTimingCapture.abort({ code: tail.error.code });
1682
+ }
1683
+ const resolved = timingCapture?.resolve(encoder);
1684
+ if (resolved !== undefined && !resolved.ok) {
1685
+ timingCapture?.discard();
1686
+ return { ok: false, stage: 'finish' };
1687
+ }
890
1688
  const finished = encoder.finish();
891
1689
  if (!finished.ok) {
1690
+ passTimingCapture?.abort({ code: finished.error.code });
1691
+ timingCapture?.discard();
892
1692
  internals.errorRegistry.fire(finished.error);
893
1693
  return { ok: false, stage: 'finish' };
894
1694
  }
@@ -898,12 +1698,20 @@ export function executeCompiledFrameGraph(
898
1698
  submit: () => {
899
1699
  const submitted = internals.device.queue.submit([preparedCommand.command]);
900
1700
  if (!submitted.ok) {
1701
+ passTimingCapture?.abort({ code: submitted.error.code });
1702
+ timingCapture?.discard();
901
1703
  internals.errorRegistry.fire(submitted.error);
902
1704
  return { ok: false, stage: 'submit' };
903
1705
  }
1706
+ timingCapture?.markSubmitted();
904
1707
  return { ok: true, value: undefined };
905
1708
  },
906
1709
  commit: () => {
1710
+ if (passTimingCapture !== undefined) {
1711
+ const completion = internals.device.queue.onSubmittedWorkDone();
1712
+ timingHost.gpuPassTimingSubmittedWork = completion;
1713
+ passTimingCapture.markSubmitted(completion);
1714
+ }
907
1715
  if (frameState.bloomFrameReceipts !== undefined) {
908
1716
  internals
909
1717
  .getPipelineState()
@@ -917,7 +1725,7 @@ export function executeCompiledFrameGraph(
917
1725
  frameState.activeTemporalGpuState = stagedGpuState;
918
1726
  if (previousGpuState !== undefined && previousGpuState !== stagedGpuState) {
919
1727
  internals.clearPostProcessPipelineCache?.('forgeax.taa-resolve');
920
- internals.invalidateShaderModule?.('post-process-forgeax.taa-resolve-module');
1728
+ invalidatePostProcessModule('forgeax.taa-resolve');
921
1729
  retireTemporalGpuStateAfterFence(
922
1730
  previousGpuState,
923
1731
  internals.device.queue,
@@ -947,7 +1755,7 @@ export function executeCompiledFrameGraph(
947
1755
  }
948
1756
  if (stagedTemporalCommit.kind === 'off') {
949
1757
  internals.clearPostProcessPipelineCache?.('forgeax.taa-resolve');
950
- internals.invalidateShaderModule?.('post-process-forgeax.taa-resolve-module');
1758
+ invalidatePostProcessModule('forgeax.taa-resolve');
951
1759
  const activeGpuState = frameState.activeTemporalGpuState;
952
1760
  if (activeGpuState !== undefined) {
953
1761
  retireTemporalGpuStateAfterFence(
@@ -972,8 +1780,13 @@ export function executeCompiledFrameGraph(
972
1780
  frameState.environmentLifecycle?.publish(frameState.environmentGeneration);
973
1781
  frameState.environmentGeneration = undefined;
974
1782
  }
1783
+ onSubmitted?.(internals.device.queue.onSubmittedWorkDone());
975
1784
  },
976
1785
  abort: (failure) => {
1786
+ passTimingCapture?.abort({ code: failure.stage });
1787
+ timingHost.gpuPassTimingCapture = undefined;
1788
+ timingHost.gpuPassTimingSubmittedWork = undefined;
1789
+ timingCapture?.discard();
977
1790
  frameState.bloomFrameReceipts = undefined;
978
1791
  if (frameState.environmentGeneration !== undefined) {
979
1792
  frameState.environmentLifecycle?.recordStageFailure(
@@ -996,5 +1809,176 @@ export function executeCompiledFrameGraph(
996
1809
  }
997
1810
  },
998
1811
  });
1812
+ settleVolumetricFogGraphCandidate(frameState, transaction.ok);
1813
+ if (!transaction.ok) rejectTemporalFrame();
999
1814
  return transaction.ok;
1000
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
+ }