@forgeax/engine-render 0.1.20 → 0.1.23

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 (645) hide show
  1. package/README.md +365 -29
  2. package/dist/assembly/backend-contract.d.ts +13 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts +18 -0
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  7. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +30 -19
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/host-contract.d.ts +19 -0
  11. package/dist/assembly/host-contract.d.ts.map +1 -1
  12. package/dist/assembly/render-target-host.d.ts +46 -0
  13. package/dist/assembly/render-target-host.d.ts.map +1 -0
  14. package/dist/assets/render-asset-port.d.ts.map +1 -1
  15. package/dist/authoring.mjs +4 -1
  16. package/dist/authoring.mjs.map +1 -1
  17. package/dist/capture/cube-views.d.ts +21 -0
  18. package/dist/capture/cube-views.d.ts.map +1 -0
  19. package/dist/capture/scheduler.d.ts +54 -0
  20. package/dist/capture/scheduler.d.ts.map +1 -0
  21. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  22. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  23. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  24. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  25. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  26. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  27. package/dist/chunk-FBC4R44J.mjs +1003 -0
  28. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  29. package/dist/{chunk-CDTWTHZR.mjs → chunk-FF7K3FSO.mjs} +474 -168
  30. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  31. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  32. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  33. package/dist/{chunk-TBQJOK2L.mjs → chunk-QODA5UKU.mjs} +321 -269
  34. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  35. package/dist/chunk-YALPS44A.mjs +296 -0
  36. package/dist/chunk-YALPS44A.mjs.map +1 -0
  37. package/dist/{chunk-4XINDLAY.mjs → chunk-ZUWKAZ2I.mjs} +1963 -2178
  38. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  39. package/dist/cluster-binner.d.ts +5 -0
  40. package/dist/cluster-binner.d.ts.map +1 -1
  41. package/dist/components/camera.d.ts +1 -0
  42. package/dist/components/camera.d.ts.map +1 -1
  43. package/dist/components/cube-camera.d.ts +20 -0
  44. package/dist/components/cube-camera.d.ts.map +1 -0
  45. package/dist/components/directional-light.d.ts +11 -2
  46. package/dist/components/directional-light.d.ts.map +1 -1
  47. package/dist/components/directional-shadow-filter.d.ts +25 -0
  48. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  49. package/dist/components/index.d.ts +5 -0
  50. package/dist/components/index.d.ts.map +1 -1
  51. package/dist/components/light-helpers.d.ts +4 -0
  52. package/dist/components/light-helpers.d.ts.map +1 -1
  53. package/dist/components/light-probe.d.ts +9 -0
  54. package/dist/components/light-probe.d.ts.map +1 -0
  55. package/dist/components/rect-area-light.d.ts +12 -0
  56. package/dist/components/rect-area-light.d.ts.map +1 -0
  57. package/dist/components/reflection-probe.d.ts +17 -0
  58. package/dist/components/reflection-probe.d.ts.map +1 -0
  59. package/dist/components/scene-instance.d.ts +4 -0
  60. package/dist/components/scene-instance.d.ts.map +1 -1
  61. package/dist/components/spot-light.d.ts +3 -0
  62. package/dist/components/spot-light.d.ts.map +1 -1
  63. package/dist/construct-renderer.mjs +14836 -9620
  64. package/dist/construct-renderer.mjs.map +1 -1
  65. package/dist/device/gpu-residency.d.ts +8 -1
  66. package/dist/device/gpu-residency.d.ts.map +1 -1
  67. package/dist/device/lifecycle-transaction.d.ts +2 -0
  68. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  69. package/dist/environment/frame.d.ts.map +1 -1
  70. package/dist/environment/lifecycle.d.ts +5 -0
  71. package/dist/environment/lifecycle.d.ts.map +1 -1
  72. package/dist/errors/gpu-driven.d.ts +26 -0
  73. package/dist/errors/gpu-driven.d.ts.map +1 -0
  74. package/dist/errors/index.d.ts +1 -0
  75. package/dist/errors/index.d.ts.map +1 -1
  76. package/dist/errors/recover.d.ts +9 -1
  77. package/dist/errors/recover.d.ts.map +1 -1
  78. package/dist/errors/render.d.ts +166 -93
  79. package/dist/errors/render.d.ts.map +1 -1
  80. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  81. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  82. package/dist/extract/environment.d.ts +14 -0
  83. package/dist/extract/environment.d.ts.map +1 -1
  84. package/dist/extract/gpu-driven.d.ts +50 -0
  85. package/dist/extract/gpu-driven.d.ts.map +1 -0
  86. package/dist/extract/visibility.d.ts.map +1 -1
  87. package/dist/features/fullscreen.d.ts.map +1 -1
  88. package/dist/features/targets.d.ts.map +1 -1
  89. package/dist/features/types.d.ts +10 -0
  90. package/dist/features/types.d.ts.map +1 -1
  91. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  92. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  93. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  94. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  95. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  96. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  97. package/dist/gpu-driven/production-raster.d.ts +97 -2
  98. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  99. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  100. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  101. package/dist/gpu-scene-schema.d.ts +14 -0
  102. package/dist/gpu-scene-schema.d.ts.map +1 -1
  103. package/dist/gpu-scene.d.ts +8 -1
  104. package/dist/gpu-scene.d.ts.map +1 -1
  105. package/dist/gpu-usage.d.ts +0 -1
  106. package/dist/gpu-usage.d.ts.map +1 -1
  107. package/dist/hdrp-buffers.d.ts +21 -19
  108. package/dist/hdrp-buffers.d.ts.map +1 -1
  109. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  110. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  111. package/dist/ibl/kernel-cache.d.ts +36 -0
  112. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  113. package/dist/index.d.ts +39 -3
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.mjs +91 -6
  116. package/dist/index.mjs.map +1 -1
  117. package/dist/inspection-types.d.ts +214 -1
  118. package/dist/inspection-types.d.ts.map +1 -1
  119. package/dist/instances-derived-bounds.d.ts +50 -0
  120. package/dist/instances-derived-bounds.d.ts.map +1 -0
  121. package/dist/internal.d.ts +9 -0
  122. package/dist/internal.d.ts.map +1 -1
  123. package/dist/internal.mjs +23 -2
  124. package/dist/internal.mjs.map +1 -1
  125. package/dist/light-buffer-layout.d.ts +58 -140
  126. package/dist/light-buffer-layout.d.ts.map +1 -1
  127. package/dist/materials.d.ts.map +1 -1
  128. package/dist/mesh-material-bindings.d.ts +54 -0
  129. package/dist/mesh-material-bindings.d.ts.map +1 -1
  130. package/dist/pbr-pipeline.d.ts +38 -14
  131. package/dist/pbr-pipeline.d.ts.map +1 -1
  132. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  133. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  134. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  135. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  136. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  137. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  138. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  139. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  140. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  141. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  142. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  143. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  144. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  145. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  146. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  147. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  148. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  149. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  150. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  151. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  152. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  153. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  154. package/dist/pipeline/standard-post.d.ts.map +1 -1
  155. package/dist/pipeline/standard-profile.d.ts +5 -26
  156. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  157. package/dist/pipeline-builder.d.ts +2 -0
  158. package/dist/pipeline-builder.d.ts.map +1 -1
  159. package/dist/pipeline-spec.d.ts +40 -7
  160. package/dist/pipeline-spec.d.ts.map +1 -1
  161. package/dist/plugin.d.ts.map +1 -1
  162. package/dist/point-shadow-inspection.d.ts +21 -0
  163. package/dist/point-shadow-inspection.d.ts.map +1 -0
  164. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  165. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  166. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  167. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  168. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  169. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  170. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  171. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  172. package/dist/record/frame-lighting.d.ts +33 -36
  173. package/dist/record/frame-lighting.d.ts.map +1 -1
  174. package/dist/record/frame-snapshot.d.ts +78 -24
  175. package/dist/record/frame-snapshot.d.ts.map +1 -1
  176. package/dist/record/frame-targets.d.ts +5 -0
  177. package/dist/record/frame-targets.d.ts.map +1 -1
  178. package/dist/record/frame.d.ts +16 -6
  179. package/dist/record/frame.d.ts.map +1 -1
  180. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  181. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  182. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  183. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  184. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  185. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  186. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  187. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  188. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  189. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  190. package/dist/record/gpu-timing.d.ts +20 -3
  191. package/dist/record/gpu-timing.d.ts.map +1 -1
  192. package/dist/record/helpers.d.ts +0 -20
  193. package/dist/record/helpers.d.ts.map +1 -1
  194. package/dist/record/main-pass-geometry.d.ts +6 -1
  195. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  196. package/dist/record/main-pass-material.d.ts +25 -2
  197. package/dist/record/main-pass-material.d.ts.map +1 -1
  198. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  199. package/dist/record/main-pass.d.ts +2 -0
  200. package/dist/record/main-pass.d.ts.map +1 -1
  201. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  202. package/dist/record/render-context.d.ts +98 -6
  203. package/dist/record/render-context.d.ts.map +1 -1
  204. package/dist/record/shadow-pass.d.ts.map +1 -1
  205. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  206. package/dist/record/typed-frame-graph.d.ts +119 -4
  207. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  208. package/dist/record/view-ubo.d.ts +7 -5
  209. package/dist/record/view-ubo.d.ts.map +1 -1
  210. package/dist/reflection/filter.d.ts +22 -0
  211. package/dist/reflection/filter.d.ts.map +1 -0
  212. package/dist/reflection/gpu-table.d.ts +25 -0
  213. package/dist/reflection/gpu-table.d.ts.map +1 -0
  214. package/dist/reflection/inspection.d.ts +10 -0
  215. package/dist/reflection/inspection.d.ts.map +1 -0
  216. package/dist/reflection/projection.d.ts +140 -0
  217. package/dist/reflection/projection.d.ts.map +1 -0
  218. package/dist/reflection/record-owner.d.ts +60 -0
  219. package/dist/reflection/record-owner.d.ts.map +1 -0
  220. package/dist/render-contract.d.ts +62 -14
  221. package/dist/render-contract.d.ts.map +1 -1
  222. package/dist/render-graph-primitives.d.ts +1 -0
  223. package/dist/render-graph-primitives.d.ts.map +1 -1
  224. package/dist/render-pipeline.d.ts +49 -2
  225. package/dist/render-pipeline.d.ts.map +1 -1
  226. package/dist/render-system-extract.d.ts +160 -31
  227. package/dist/render-system-extract.d.ts.map +1 -1
  228. package/dist/render-system.d.ts +28 -2
  229. package/dist/render-system.d.ts.map +1 -1
  230. package/dist/renderer.d.ts +43 -0
  231. package/dist/renderer.d.ts.map +1 -0
  232. package/dist/scene/probe-blend-record.d.ts +22 -0
  233. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  234. package/dist/scene/probe-blend.d.ts +142 -0
  235. package/dist/scene/probe-blend.d.ts.map +1 -0
  236. package/dist/scene/render-scene-types.d.ts +0 -6
  237. package/dist/scene/render-scene-types.d.ts.map +1 -1
  238. package/dist/scene/render-scene.d.ts +117 -9
  239. package/dist/scene/render-scene.d.ts.map +1 -1
  240. package/dist/scene/visibility/bounds.d.ts +20 -0
  241. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  242. package/dist/scene/visibility/budget.d.ts +16 -0
  243. package/dist/scene/visibility/budget.d.ts.map +1 -0
  244. package/dist/scene/visibility/facet.d.ts +73 -0
  245. package/dist/scene/visibility/facet.d.ts.map +1 -0
  246. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  247. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  248. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  249. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  250. package/dist/scene/visibility/inspection.d.ts +132 -0
  251. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  252. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  253. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  254. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  255. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  256. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  257. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  258. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  259. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  260. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  261. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  262. package/dist/scene/visibility/types.d.ts +34 -0
  263. package/dist/scene/visibility/types.d.ts.map +1 -0
  264. package/dist/shadow-atlas.d.ts +3 -3
  265. package/dist/shadow-atlas.d.ts.map +1 -1
  266. package/dist/ssao-buffers.d.ts +3 -0
  267. package/dist/ssao-buffers.d.ts.map +1 -1
  268. package/dist/ssr/admission.d.ts +122 -0
  269. package/dist/ssr/admission.d.ts.map +1 -0
  270. package/dist/ssr/composition.d.ts +32 -0
  271. package/dist/ssr/composition.d.ts.map +1 -0
  272. package/dist/ssr/errors.d.ts +39 -0
  273. package/dist/ssr/errors.d.ts.map +1 -0
  274. package/dist/ssr/identity.d.ts +13 -0
  275. package/dist/ssr/identity.d.ts.map +1 -0
  276. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  277. package/dist/targets/contracts.d.ts +66 -0
  278. package/dist/targets/contracts.d.ts.map +1 -0
  279. package/dist/targets/graph-projection.d.ts +29 -0
  280. package/dist/targets/graph-projection.d.ts.map +1 -0
  281. package/dist/targets/material-source.d.ts +34 -0
  282. package/dist/targets/material-source.d.ts.map +1 -0
  283. package/dist/targets/owner.d.ts +32 -0
  284. package/dist/targets/owner.d.ts.map +1 -0
  285. package/dist/targets/physical.d.ts +18 -0
  286. package/dist/targets/physical.d.ts.map +1 -0
  287. package/dist/targets/readback.d.ts +39 -0
  288. package/dist/targets/readback.d.ts.map +1 -0
  289. package/dist/temporal/frame-coordinator.d.ts +13 -0
  290. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  291. package/dist/temporal/frame.d.ts +2 -0
  292. package/dist/temporal/frame.d.ts.map +1 -1
  293. package/dist/temporal/index.mjs +6 -5
  294. package/dist/temporal/view.d.ts +7 -0
  295. package/dist/temporal/view.d.ts.map +1 -1
  296. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  297. package/dist/typed-render-graph-primitives.d.ts +5 -1
  298. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  299. package/dist/typed-shadow-passes.d.ts +1 -1
  300. package/dist/typed-shadow-passes.d.ts.map +1 -1
  301. package/dist/volume/passes.d.ts +9 -5
  302. package/dist/volume/passes.d.ts.map +1 -1
  303. package/package.json +20 -20
  304. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  305. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  306. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  307. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  308. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +12 -2
  309. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  310. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  311. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  312. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  313. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  314. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  315. package/src/__tests__/device-feature-admission.unit.test.ts +85 -0
  316. package/src/__tests__/direct-light-snapshot.unit.test.ts +62 -6
  317. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  318. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  319. package/src/__tests__/evidence-identity.ts +13 -0
  320. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  321. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  322. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  323. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  324. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  325. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  326. package/src/__tests__/factory-contract.integration.test.ts +288 -29
  327. package/src/__tests__/fog-component.unit.test.ts +67 -0
  328. package/src/__tests__/fog.integration.test.ts +86 -0
  329. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  330. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  331. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  332. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -8
  333. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +18 -1
  334. package/src/__tests__/gpu-driven-production.integration.test.ts +579 -17
  335. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  336. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  337. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +101 -6
  338. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  339. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  340. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  341. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  342. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  343. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  344. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  345. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  346. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  347. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  348. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  349. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  350. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  351. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  352. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  353. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  354. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  355. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  356. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  357. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  358. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  359. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  360. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  361. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  362. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  363. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  364. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  365. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  366. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  367. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  368. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  369. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  370. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  371. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  372. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  373. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  374. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  375. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  376. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  377. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  378. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  379. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  380. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  381. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  382. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  383. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  384. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  385. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  386. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  387. package/src/__tests__/material-observation.integration.test.ts +101 -0
  388. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  389. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  390. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  391. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  392. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  393. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  394. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  395. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  396. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  397. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  398. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  399. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  400. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  401. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  402. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  403. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  404. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  405. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  406. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  407. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  408. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  409. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  410. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  411. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  412. package/src/__tests__/public-surface.test-d.ts +10 -0
  413. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  414. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  415. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  416. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  417. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  418. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  419. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  420. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  421. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  422. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  423. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  424. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  425. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  426. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  427. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  428. package/src/__tests__/render-error-code-owner.test-d.ts +19 -3
  429. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  430. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  431. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  432. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  433. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  434. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  435. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  436. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  437. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  438. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  439. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  440. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  441. package/src/__tests__/render-target-public-schema.json +107 -0
  442. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  443. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  444. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  445. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  446. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +124 -0
  447. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  448. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  449. package/src/__tests__/skin-motion-regression.unit.test.ts +154 -0
  450. package/src/__tests__/skinned-shadow-caster.test.ts +13 -4
  451. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  452. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  453. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  454. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  455. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  456. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  457. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  458. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +299 -0
  459. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +15 -5
  460. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  461. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  462. package/src/__tests__/standard-lighting-write.unit.test.ts +441 -0
  463. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  464. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  465. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  466. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  467. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  468. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  469. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  470. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  471. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  472. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  473. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  474. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  475. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  476. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  477. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  478. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  479. package/src/assembly/backend-contract.ts +13 -0
  480. package/src/assembly/device-feature-admission.ts +80 -0
  481. package/src/assembly/directional-shadow-inspection.ts +122 -0
  482. package/src/assembly/factory.ts +1411 -366
  483. package/src/assembly/host-contract.ts +35 -0
  484. package/src/assembly/render-target-host.ts +847 -0
  485. package/src/assets/asset-decoders.ts +1 -1
  486. package/src/assets/render-asset-port.ts +7 -1
  487. package/src/capture/cube-views.ts +69 -0
  488. package/src/capture/scheduler.ts +265 -0
  489. package/src/cluster-binner.ts +31 -0
  490. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  491. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  492. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  493. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  494. package/src/components/camera.ts +4 -0
  495. package/src/components/cube-camera.ts +52 -0
  496. package/src/components/directional-light.ts +22 -2
  497. package/src/components/directional-shadow-filter.ts +57 -0
  498. package/src/components/index.ts +5 -0
  499. package/src/components/light-helpers.ts +133 -8
  500. package/src/components/light-probe.ts +13 -0
  501. package/src/components/rect-area-light.ts +15 -0
  502. package/src/components/reflection-probe.ts +49 -0
  503. package/src/components/scene-instance.ts +6 -0
  504. package/src/components/spot-light.ts +3 -0
  505. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  506. package/src/device/gpu-residency.ts +140 -9
  507. package/src/device/lifecycle-transaction.ts +12 -0
  508. package/src/environment/frame.ts +6 -24
  509. package/src/environment/lifecycle.ts +20 -1
  510. package/src/errors/gpu-driven.ts +92 -0
  511. package/src/errors/index.ts +1 -0
  512. package/src/errors/recover.ts +32 -0
  513. package/src/errors/render.ts +341 -143
  514. package/src/extract/camera.ts +3 -3
  515. package/src/extract/directional-shadow-projection.ts +61 -0
  516. package/src/extract/environment.ts +57 -0
  517. package/src/extract/gpu-driven.ts +108 -0
  518. package/src/extract/visibility.ts +2 -1
  519. package/src/features/fullscreen.ts +3 -0
  520. package/src/features/targets.ts +4 -1
  521. package/src/features/types.ts +10 -0
  522. package/src/fullscreen-post-process-pass.ts +43 -2
  523. package/src/gpu-driven/batch-topology.ts +207 -28
  524. package/src/gpu-driven/prepared-draw.ts +208 -0
  525. package/src/gpu-driven/production-raster.ts +953 -43
  526. package/src/gpu-driven/view-gpu.ts +411 -29
  527. package/src/gpu-scene-schema.ts +13 -0
  528. package/src/gpu-scene.ts +140 -64
  529. package/src/gpu-usage.ts +0 -1
  530. package/src/hdrp-buffers.ts +169 -63
  531. package/src/ibl/IblPipelineCache.ts +11 -0
  532. package/src/ibl/kernel-cache.ts +88 -0
  533. package/src/index.ts +222 -0
  534. package/src/inspection-types.ts +276 -1
  535. package/src/instances-derived-bounds.ts +171 -0
  536. package/src/internal.ts +38 -0
  537. package/src/light-buffer-layout.ts +155 -310
  538. package/src/materials.ts +16 -2
  539. package/src/mesh-material-bindings.ts +132 -0
  540. package/src/pbr-pipeline.ts +255 -60
  541. package/src/pipeline/standard-forward-lane.ts +93 -16
  542. package/src/pipeline/standard-lighting/errors.ts +6 -0
  543. package/src/pipeline/standard-lighting/graph.ts +158 -0
  544. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  545. package/src/pipeline/standard-lighting/layout.ts +31 -0
  546. package/src/pipeline/standard-lighting/light-frame.ts +35 -0
  547. package/src/pipeline/standard-lighting/membership.ts +43 -0
  548. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  549. package/src/pipeline/standard-lighting/topology.ts +120 -0
  550. package/src/pipeline/standard-lighting/transport.ts +78 -0
  551. package/src/pipeline/standard-pipeline.ts +124 -193
  552. package/src/pipeline/standard-post.ts +26 -7
  553. package/src/pipeline/standard-profile.ts +17 -47
  554. package/src/pipeline-builder.ts +5 -2
  555. package/src/pipeline-spec.ts +95 -21
  556. package/src/plugin.ts +4 -0
  557. package/src/point-shadow-inspection.ts +55 -0
  558. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  559. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  560. package/src/prepare/extended-lighting/resources.ts +389 -0
  561. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  562. package/src/prepare/extended-lighting/state.ts +92 -0
  563. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  564. package/src/record/__tests__/temporal-payload.unit.test.ts +64 -1
  565. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  566. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  567. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  568. package/src/record/frame-lighting.ts +591 -452
  569. package/src/record/frame-snapshot.ts +92 -39
  570. package/src/record/frame-targets.ts +17 -0
  571. package/src/record/frame.ts +662 -158
  572. package/src/record/gpu-pass-timing/contract.ts +180 -0
  573. package/src/record/gpu-pass-timing/errors.ts +34 -0
  574. package/src/record/gpu-pass-timing/index.ts +37 -0
  575. package/src/record/gpu-pass-timing/parser.ts +97 -0
  576. package/src/record/gpu-pass-timing/session.ts +938 -0
  577. package/src/record/gpu-timing.ts +112 -39
  578. package/src/record/helpers.ts +0 -58
  579. package/src/record/main-pass-geometry.ts +324 -119
  580. package/src/record/main-pass-material.ts +103 -10
  581. package/src/record/main-pass-sprite-draws.ts +99 -103
  582. package/src/record/main-pass.ts +71 -15
  583. package/src/record/mesh-ssbo.ts +47 -36
  584. package/src/record/render-context.ts +136 -3
  585. package/src/record/shadow-pass.ts +83 -68
  586. package/src/record/skybox-post-pass.ts +20 -4
  587. package/src/record/typed-frame-graph.ts +1034 -43
  588. package/src/record/view-ubo.ts +72 -47
  589. package/src/reflection/filter.ts +81 -0
  590. package/src/reflection/gpu-table.ts +44 -0
  591. package/src/reflection/inspection.ts +67 -0
  592. package/src/reflection/projection.ts +476 -0
  593. package/src/reflection/record-owner.ts +1283 -0
  594. package/src/render-contract.ts +93 -10
  595. package/src/render-graph-primitives.ts +9 -4
  596. package/src/render-pipeline.ts +122 -2
  597. package/src/render-system-extract.ts +1041 -225
  598. package/src/render-system.ts +1310 -85
  599. package/src/renderer.ts +222 -0
  600. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  601. package/src/scene/probe-blend-record.ts +54 -0
  602. package/src/scene/probe-blend.ts +813 -0
  603. package/src/scene/render-scene-types.ts +0 -7
  604. package/src/scene/render-scene.ts +942 -171
  605. package/src/scene/visibility/bounds.ts +57 -0
  606. package/src/scene/visibility/budget.ts +40 -0
  607. package/src/scene/visibility/facet.ts +400 -0
  608. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  609. package/src/scene/visibility/gpu-lod.ts +119 -0
  610. package/src/scene/visibility/inspection.ts +313 -0
  611. package/src/scene/visibility/lod-selector.ts +84 -0
  612. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  613. package/src/scene/visibility/occlusion-pass.ts +165 -0
  614. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  615. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  616. package/src/scene/visibility/types.ts +50 -0
  617. package/src/shadow-atlas.ts +5 -5
  618. package/src/ssao-buffers.ts +9 -2
  619. package/src/ssr/admission.ts +451 -0
  620. package/src/ssr/composition.ts +73 -0
  621. package/src/ssr/errors.ts +76 -0
  622. package/src/ssr/identity.ts +12 -0
  623. package/src/systems/skin-palette-allocator.ts +1 -1
  624. package/src/targets/contracts.ts +182 -0
  625. package/src/targets/graph-projection.ts +169 -0
  626. package/src/targets/material-source.ts +143 -0
  627. package/src/targets/owner.ts +214 -0
  628. package/src/targets/physical.ts +18 -0
  629. package/src/targets/readback.ts +153 -0
  630. package/src/temporal/frame-coordinator.ts +26 -0
  631. package/src/temporal/frame.ts +14 -1
  632. package/src/temporal/view.ts +11 -0
  633. package/src/tilemap-chunk-extract-system.ts +4 -4
  634. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  635. package/src/typed-render-graph-primitives.ts +53 -3
  636. package/src/typed-shadow-passes.ts +46 -40
  637. package/src/volume/passes.ts +61 -58
  638. package/dist/.tsbuildinfo +0 -1
  639. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  640. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  641. package/dist/chunk-M2MP4ZOU.mjs +0 -73
  642. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  643. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  644. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
  645. package/dist/chunk-VFPLTKCU.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,10 @@ 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 { SsrAdmissionResult } from '../ssr/admission';
66
+ import type { RenderTarget } from '../targets/contracts';
67
+ import type { RenderTargetPhysical } from '../targets/physical';
51
68
  import {
52
69
  abortTemporalGpuSubmit,
53
70
  commitTemporalGpuSubmit,
@@ -64,8 +81,15 @@ import { hasVolumetricFogCapability } from '../volume/capability';
64
81
  import { deriveVolumetricFogExtent, deriveVolumetricFogResolvedExtent } from '../volume/resources';
65
82
  import { buildPerFrameBindGroups } from './frame-lighting';
66
83
  import { getTextureIdentity, type RenderFrameState } from './frame-snapshot';
84
+ import type { GpuPassTimingPassIdentity } from './gpu-pass-timing/contract.js';
85
+ import type { GpuPassTimingCapture, GpuPassTimingSession } from './gpu-pass-timing/session.js';
67
86
  import type { GpuTimingCapture } from './gpu-timing';
68
- import type { PipelineState, RenderSystemInternals } from './render-context';
87
+ import { encodeMainPass } from './main-pass';
88
+ import type {
89
+ _InternalRenderPipelineContext,
90
+ PipelineState,
91
+ RenderSystemInternals,
92
+ } from './render-context';
69
93
  import { resolveSurfaceFormatPair, resolveSurfaceProfile } from './render-context';
70
94
 
71
95
  export interface TemporalGraphCapabilities {
@@ -81,6 +105,97 @@ export interface TemporalGraphRoster {
81
105
  readonly uploads: number;
82
106
  }
83
107
 
108
+ export interface ReflectionFallbackGraphRoster {
109
+ readonly enabled: boolean;
110
+ readonly attachments: readonly string[];
111
+ readonly passes: readonly string[];
112
+ readonly bindings: readonly string[];
113
+ readonly historyCount: 0;
114
+ readonly temporalDemand: 0;
115
+ }
116
+
117
+ export interface ReflectionFallbackGraphCandidate {
118
+ readonly generation: number;
119
+ readonly source: 'probe' | 'skylight' | 'neutral';
120
+ readonly roster: ReflectionFallbackGraphRoster;
121
+ }
122
+
123
+ export interface SsrAdmissionGraphRoster {
124
+ readonly enabled: boolean;
125
+ readonly attachments: readonly string[];
126
+ readonly passes: readonly string[];
127
+ readonly bindings: readonly string[];
128
+ readonly historyCount: 0;
129
+ readonly temporalDemand: 0;
130
+ }
131
+
132
+ /**
133
+ * Admission is the sole switch for parent SSR graph work. A blocked result
134
+ * projects to an empty roster, so missing receipts cannot allocate or bind
135
+ * any SSR resource.
136
+ */
137
+ export function createSsrAdmissionGraphRoster(
138
+ admission: Pick<SsrAdmissionResult, 'status'>,
139
+ ): SsrAdmissionGraphRoster {
140
+ if (admission.status !== 'admitted') {
141
+ return {
142
+ enabled: false,
143
+ attachments: [],
144
+ passes: [],
145
+ bindings: [],
146
+ historyCount: 0,
147
+ temporalDemand: 0,
148
+ };
149
+ }
150
+ return {
151
+ enabled: true,
152
+ attachments: ['ssr-reflection-admission'],
153
+ passes: ['ssr-m0-admission'],
154
+ bindings: ['ssr-reflection-admission'],
155
+ historyCount: 0,
156
+ temporalDemand: 0,
157
+ };
158
+ }
159
+
160
+ export type ReflectionFallbackGraphFailure = 'compile-failed' | 'encode-failed' | 'submit-failed';
161
+
162
+ export function createReflectionFallbackGraphRoster(input: {
163
+ readonly fallbackDemand: boolean;
164
+ }): ReflectionFallbackGraphRoster {
165
+ if (!input.fallbackDemand) {
166
+ return {
167
+ enabled: false,
168
+ attachments: [],
169
+ passes: [],
170
+ bindings: [],
171
+ historyCount: 0,
172
+ temporalDemand: 0,
173
+ };
174
+ }
175
+ return {
176
+ enabled: true,
177
+ attachments: ['reflection-fallback-linear-hdr'],
178
+ passes: ['standard-main'],
179
+ bindings: ['reflection-fallback-output'],
180
+ historyCount: 0,
181
+ temporalDemand: 0,
182
+ };
183
+ }
184
+
185
+ export function commitReflectionFallbackGraph(
186
+ candidate: ReflectionFallbackGraphCandidate,
187
+ result:
188
+ | { readonly ok: true }
189
+ | { readonly ok: false; readonly reason: ReflectionFallbackGraphFailure },
190
+ ): {
191
+ readonly visible: boolean;
192
+ readonly generation: number;
193
+ readonly source?: ReflectionFallbackGraphCandidate['source'];
194
+ } {
195
+ if (!result.ok || !candidate.roster.enabled) return { visible: false, generation: 0 };
196
+ return { visible: true, generation: candidate.generation, source: candidate.source };
197
+ }
198
+
84
199
  function importTemporalHistoryTarget(
85
200
  graph: RenderGraphBuilder<RenderPipelineFrame>,
86
201
  label: string,
@@ -206,6 +321,465 @@ export function createTemporalGraphRoster(input: {
206
321
  };
207
322
  }
208
323
 
324
+ export interface CubeCaptureGraphWork {
325
+ readonly target: RenderTarget;
326
+ readonly faceIndex: number;
327
+ readonly physical: RenderTargetPhysical;
328
+ readonly faceCamera: import('../render-contract').CameraSnapshot;
329
+ readonly viewBindGroupDynamicOffset: number;
330
+ }
331
+
332
+ export interface CubeCaptureGraphState {
333
+ work: readonly CubeCaptureGraphWork[];
334
+ reflectionProbes?: ReflectionProbeGraphState;
335
+ }
336
+
337
+ export interface ReflectionProbeFilterGraphWork {
338
+ readonly probeIndex: number;
339
+ readonly faceIndex: number;
340
+ readonly mipLevel: number;
341
+ }
342
+
343
+ export interface ReflectionProbeFilterGraphState {
344
+ readonly work: readonly ReflectionProbeFilterGraphWork[];
345
+ readonly maxStepsPerFrame: number;
346
+ }
347
+
348
+ export interface ReflectionProbeGraphWork {
349
+ readonly probeIndex: number;
350
+ readonly rawTexture: Texture;
351
+ readonly rawCubeView: TextureView;
352
+ readonly rawFaceViews: readonly TextureView[];
353
+ readonly rawDepthTexture: Texture;
354
+ readonly rawCaptureFace: number | undefined;
355
+ readonly rawDepthView: TextureView;
356
+ readonly rawSize: number;
357
+ readonly faceCamera?: import('../render-contract').CameraSnapshot;
358
+ readonly viewBindGroupDynamicOffset?: number;
359
+ readonly filteredTexture: Texture;
360
+ readonly filteredCubeView: TextureView;
361
+ readonly filteredFaceViewsByMip: readonly (readonly TextureView[])[];
362
+ readonly outputFormat: TextureFormat;
363
+ readonly sampler: Sampler;
364
+ readonly filterPipeline: RenderPipeline;
365
+ readonly filterGroup0: BindGroup | undefined;
366
+ readonly filterGroup1: BindGroup;
367
+ readonly cubeVertexBuffer: Buffer;
368
+ readonly filteredSize: number;
369
+ readonly step: ReflectionProbeFilterGraphWork | undefined;
370
+ }
371
+
372
+ export interface ReflectionProbeGraphState {
373
+ readonly work: readonly ReflectionProbeGraphWork[];
374
+ }
375
+
376
+ export function boundedReflectionProbeFilterWork(
377
+ state: ReflectionProbeFilterGraphState,
378
+ ): readonly ReflectionProbeFilterGraphWork[] {
379
+ return state.work.slice(0, Math.max(0, state.maxStepsPerFrame));
380
+ }
381
+
382
+ function renderTargetMipCount(work: CubeCaptureGraphWork): number {
383
+ const { descriptor } = work.physical;
384
+ return descriptor.mipLevels === 1
385
+ ? 1
386
+ : Math.floor(Math.log2(Math.max(descriptor.width, descriptor.height))) + 1;
387
+ }
388
+
389
+ function addCubeCaptureGraphPasses(
390
+ builder: RenderGraphBuilder<RenderPipelineFrame>,
391
+ state: CubeCaptureGraphState,
392
+ ): Result<void, RenderGraphError> {
393
+ for (let index = 0; index < state.work.length; index += 1) {
394
+ const slot = index;
395
+ const initial = state.work[slot];
396
+ if (initial === undefined) continue;
397
+ const current = (): CubeCaptureGraphWork => state.work[slot] ?? initial;
398
+ const texture = builder.importTexture(
399
+ `cube-capture.${slot}.texture`,
400
+ {
401
+ format: initial.physical.descriptor.format,
402
+ size: {
403
+ width: initial.physical.descriptor.width,
404
+ height: initial.physical.descriptor.height,
405
+ depthOrArrayLayers: initial.physical.descriptor.sampleCount === 4 ? 1 : 6,
406
+ },
407
+ mipLevelCount:
408
+ initial.physical.descriptor.sampleCount === 4 ? 1 : renderTargetMipCount(initial),
409
+ sampleCount: initial.physical.descriptor.sampleCount,
410
+ dimension: '2d',
411
+ usage: 0x10 | 0x04 | 0x01,
412
+ },
413
+ () => current().physical.colorTextures[current().faceIndex] ?? initial.physical.texture,
414
+ );
415
+ if (!texture.ok) return texture;
416
+ const color = builder.importView(
417
+ texture.value,
418
+ {
419
+ label: `cube-capture.${slot}.color`,
420
+ dimension: '2d',
421
+ baseMipLevel: 0,
422
+ mipLevelCount: 1,
423
+ baseArrayLayer: initial.physical.descriptor.sampleCount === 4 ? 0 : initial.faceIndex,
424
+ arrayLayerCount: 1,
425
+ },
426
+ () =>
427
+ current().physical.faceViews[current().faceIndex] ??
428
+ initial.physical.faceViews[initial.faceIndex] ??
429
+ initial.physical.view,
430
+ );
431
+ if (!color.ok) return color;
432
+ const depthTexture = builder.importTexture(
433
+ `cube-capture.${slot}.depth-texture`,
434
+ {
435
+ format: 'depth24plus-stencil8',
436
+ size: {
437
+ width: initial.physical.descriptor.width,
438
+ height: initial.physical.descriptor.height,
439
+ depthOrArrayLayers: initial.physical.descriptor.sampleCount === 4 ? 1 : 6,
440
+ },
441
+ mipLevelCount: 1,
442
+ sampleCount: initial.physical.descriptor.sampleCount,
443
+ dimension: '2d',
444
+ usage: 0x10,
445
+ },
446
+ () => current().physical.depthTextures[current().faceIndex] as Texture,
447
+ );
448
+ if (!depthTexture.ok) return depthTexture;
449
+ const depth = builder.importView(
450
+ depthTexture.value,
451
+ {
452
+ label: `cube-capture.${slot}.depth`,
453
+ dimension: '2d',
454
+ baseArrayLayer: initial.physical.descriptor.sampleCount === 4 ? 0 : initial.faceIndex,
455
+ arrayLayerCount: 1,
456
+ },
457
+ () => current().physical.depthViews[current().faceIndex] as TextureView,
458
+ );
459
+ if (!depth.ok) return depth;
460
+ const resolveTexture =
461
+ initial.physical.descriptor.sampleCount === 4
462
+ ? builder.importTexture(
463
+ `cube-capture.${slot}.resolve-texture`,
464
+ {
465
+ format: initial.physical.descriptor.format,
466
+ size: {
467
+ width: initial.physical.descriptor.width,
468
+ height: initial.physical.descriptor.height,
469
+ depthOrArrayLayers: 6,
470
+ },
471
+ mipLevelCount: renderTargetMipCount(initial),
472
+ sampleCount: 1,
473
+ dimension: '2d',
474
+ usage: 0x10 | 0x04 | 0x01,
475
+ },
476
+ () =>
477
+ current().physical.resolveTexture ??
478
+ initial.physical.resolveTexture ??
479
+ initial.physical.texture,
480
+ )
481
+ : undefined;
482
+ if (resolveTexture !== undefined && !resolveTexture.ok) return resolveTexture;
483
+ const resolve =
484
+ resolveTexture === undefined
485
+ ? undefined
486
+ : builder.importView(
487
+ resolveTexture.value,
488
+ {
489
+ label: `cube-capture.${slot}.resolve`,
490
+ dimension: '2d',
491
+ baseMipLevel: 0,
492
+ mipLevelCount: 1,
493
+ baseArrayLayer: initial.faceIndex,
494
+ arrayLayerCount: 1,
495
+ },
496
+ () =>
497
+ current().physical.resolveFaceViews[current().faceIndex] ??
498
+ initial.physical.resolveFaceViews[initial.faceIndex] ??
499
+ initial.physical.resolveView,
500
+ );
501
+ if (resolve !== undefined && !resolve.ok) return resolve;
502
+ const added = builder.addRasterPass(`cube-capture-face.${slot}`, {
503
+ accesses: [
504
+ { resource: color.value, usage: 'color-attachment' },
505
+ { resource: depth.value, usage: 'depth-stencil-write' },
506
+ ...(resolve === undefined
507
+ ? []
508
+ : [{ resource: resolve.value, usage: 'color-attachment' as const }]),
509
+ ],
510
+ colorAttachments: [
511
+ {
512
+ view: color.value,
513
+ ...(resolve === undefined ? {} : { resolveTarget: resolve.value }),
514
+ loadOp: 'clear',
515
+ storeOp: 'store',
516
+ clearValue: () => {
517
+ return { r: 0, g: 0, b: 0, a: 1 };
518
+ },
519
+ },
520
+ ],
521
+ depthStencilAttachment: {
522
+ view: depth.value,
523
+ depthClearValue: 1,
524
+ depthLoadOp: 'clear',
525
+ depthStoreOp: 'store',
526
+ stencilClearValue: 0,
527
+ stencilLoadOp: 'clear',
528
+ stencilStoreOp: 'store',
529
+ },
530
+ encode: ({ pass, frame, resources }) => {
531
+ const internal = frame as _InternalRenderPipelineContext;
532
+ const captureColor = resources.textureView(color.value);
533
+ if (!captureColor.ok) return;
534
+ const captureDepth = resources.textureView(depth.value);
535
+ if (!captureDepth.ok) return;
536
+ const captureResolve =
537
+ resolve === undefined ? undefined : resources.textureView(resolve.value);
538
+ if (resolve !== undefined && (captureResolve === undefined || !captureResolve.ok)) return;
539
+ const captureResolveView = captureResolve?.ok ? captureResolve.value : null;
540
+ const captureContext = {
541
+ ...internal,
542
+ gpuDrivenEntityKeys: new Set<number>(),
543
+ camera: current().faceCamera,
544
+ // Cube faces are raw scene captures, not display-camera HDR output.
545
+ // Select the PSO against the actual target attachment so an LDR
546
+ // cube target is rendered by a compatible forward pipeline.
547
+ tonemapActive: false,
548
+ skyboxActive: false,
549
+ transparentColorFormat: initial.physical.descriptor.format as GPUTextureFormat,
550
+ msaaActive: initial.physical.descriptor.sampleCount === 4,
551
+ viewBindGroupDynamicOffset: current().viewBindGroupDynamicOffset,
552
+ geometryColorView: captureColor.value,
553
+ geometryDepthView: captureDepth.value,
554
+ geometryColorResolveView: captureResolveView,
555
+ splitLdrSprite: false,
556
+ ldrSpritePassView: null,
557
+ };
558
+ encodeMainPass(captureContext, pass, undefined, {
559
+ colorViews: [captureColor.value],
560
+ colorFormats: [initial.physical.descriptor.format as GPUTextureFormat],
561
+ depthView: captureDepth.value,
562
+ passKind: 'forward',
563
+ clearColor: [0, 0, 0, 1],
564
+ });
565
+ },
566
+ });
567
+ if (!added.ok) return added;
568
+ }
569
+ return ok(undefined);
570
+ }
571
+
572
+ export function addReflectionProbeGraphPasses(
573
+ builder: RenderGraphBuilder<RenderPipelineFrame>,
574
+ state: ReflectionProbeGraphState,
575
+ ): Result<readonly import('@forgeax/engine-render-graph').GraphAccess[], RenderGraphError> {
576
+ const accesses: import('@forgeax/engine-render-graph').GraphAccess[] = [];
577
+ for (const work of state.work) {
578
+ const raw = builder.importTexture(
579
+ `reflection-probe.${work.probeIndex}.raw`,
580
+ {
581
+ format: work.outputFormat,
582
+ size: { width: work.rawSize, height: work.rawSize, depthOrArrayLayers: 6 },
583
+ mipLevelCount: 1,
584
+ sampleCount: 1,
585
+ dimension: '2d',
586
+ usage: 0x10 | 0x04 | 0x01,
587
+ },
588
+ () => work.rawTexture,
589
+ );
590
+ if (!raw.ok) return raw;
591
+ const rawCube = builder.importView(
592
+ raw.value,
593
+ {
594
+ label: `reflection-probe.${work.probeIndex}.raw-cube`,
595
+ dimension: 'cube',
596
+ arrayLayerCount: 6,
597
+ },
598
+ () => work.rawCubeView,
599
+ );
600
+ if (!rawCube.ok) return rawCube;
601
+ const filtered = builder.importTexture(
602
+ `reflection-probe.${work.probeIndex}.filtered`,
603
+ {
604
+ format: work.outputFormat,
605
+ size: { width: work.filteredSize, height: work.filteredSize, depthOrArrayLayers: 6 },
606
+ mipLevelCount: work.filteredFaceViewsByMip.length,
607
+ sampleCount: 1,
608
+ dimension: '2d',
609
+ usage: 0x10 | 0x04 | 0x01,
610
+ },
611
+ () => work.filteredTexture,
612
+ );
613
+ if (!filtered.ok) return filtered;
614
+ const filteredCube = builder.importView(
615
+ filtered.value,
616
+ {
617
+ label: `reflection-probe.${work.probeIndex}.filtered-cube`,
618
+ dimension: 'cube',
619
+ baseMipLevel: 0,
620
+ mipLevelCount: work.filteredFaceViewsByMip.length,
621
+ arrayLayerCount: 6,
622
+ },
623
+ () => work.filteredCubeView,
624
+ );
625
+ if (!filteredCube.ok) return filteredCube;
626
+ if (work.rawCaptureFace !== undefined) {
627
+ const face = work.rawCaptureFace;
628
+ const view = builder.importView(
629
+ raw.value,
630
+ {
631
+ label: `reflection-probe.${work.probeIndex}.capture-face-${face}`,
632
+ dimension: '2d',
633
+ baseArrayLayer: face,
634
+ arrayLayerCount: 1,
635
+ },
636
+ () => work.rawFaceViews[face] as TextureView,
637
+ );
638
+ if (!view.ok) return view;
639
+ const depthTexture = builder.importTexture(
640
+ `reflection-probe.${work.probeIndex}.capture-depth`,
641
+ {
642
+ format: 'depth24plus-stencil8',
643
+ size: { width: work.rawSize, height: work.rawSize, depthOrArrayLayers: 1 },
644
+ mipLevelCount: 1,
645
+ sampleCount: 1,
646
+ dimension: '2d',
647
+ usage: 0x10,
648
+ },
649
+ () => work.rawDepthTexture,
650
+ );
651
+ if (!depthTexture.ok) return depthTexture;
652
+ const depth = builder.importView(
653
+ depthTexture.value,
654
+ { label: `reflection-probe.${work.probeIndex}.capture-depth-view`, dimension: '2d' },
655
+ () => work.rawDepthView,
656
+ );
657
+ if (!depth.ok) return depth;
658
+ const added = builder.addRasterPass(`reflection-probe.${work.probeIndex}.capture.${face}`, {
659
+ accesses: [
660
+ { resource: view.value, usage: 'color-attachment' },
661
+ { resource: depth.value, usage: 'depth-stencil-write' },
662
+ ],
663
+ colorAttachments: [
664
+ {
665
+ view: view.value,
666
+ loadOp: 'clear',
667
+ storeOp: 'store',
668
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
669
+ },
670
+ ],
671
+ depthStencilAttachment: {
672
+ view: depth.value,
673
+ depthClearValue: 1,
674
+ depthLoadOp: 'clear',
675
+ depthStoreOp: 'store',
676
+ stencilClearValue: 0,
677
+ stencilLoadOp: 'clear',
678
+ stencilStoreOp: 'store',
679
+ },
680
+ encode: ({ pass, frame, resources }) => {
681
+ const internal = frame as _InternalRenderPipelineContext;
682
+ const captureColor = resources.textureView(view.value);
683
+ const captureDepth = resources.textureView(depth.value);
684
+ if (!captureColor.ok || !captureDepth.ok) return;
685
+ if (work.faceCamera === undefined || work.viewBindGroupDynamicOffset === undefined)
686
+ return;
687
+ encodeMainPass(
688
+ {
689
+ ...internal,
690
+ camera: work.faceCamera,
691
+ viewBindGroupDynamicOffset: work.viewBindGroupDynamicOffset,
692
+ geometryColorView: captureColor.value,
693
+ geometryDepthView: captureDepth.value,
694
+ geometryColorResolveView: null,
695
+ transparentColorFormat: work.outputFormat as GPUTextureFormat,
696
+ msaaActive: false,
697
+ splitLdrSprite: false,
698
+ ldrSpritePassView: null,
699
+ },
700
+ pass,
701
+ undefined,
702
+ {
703
+ colorViews: [captureColor.value],
704
+ colorFormats: [work.outputFormat as GPUTextureFormat],
705
+ depthView: captureDepth.value,
706
+ passKind: 'forward',
707
+ clearColor: [0, 0, 0, 1],
708
+ },
709
+ );
710
+ },
711
+ });
712
+ if (!added.ok) return added;
713
+ }
714
+ const step = work.step;
715
+ if (step !== undefined) {
716
+ const mipViews = work.filteredFaceViewsByMip[step.mipLevel];
717
+ const outputView = mipViews?.[step.faceIndex];
718
+ if (outputView === undefined) {
719
+ return err(
720
+ new RenderGraphError({
721
+ code: 'resource-resolution-failed',
722
+ expected: 'every scheduled probe mip has a filtered face view',
723
+ hint: 'rebuild the renderer-owned probe output before compiling the Standard graph',
724
+ detail: {
725
+ resourceLabel: `reflection-probe.${work.probeIndex}.filtered`,
726
+ passName: 'pmrem',
727
+ },
728
+ }),
729
+ );
730
+ }
731
+ const output = builder.importView(
732
+ filtered.value,
733
+ {
734
+ label: `reflection-probe.${work.probeIndex}.filtered-mip${step.mipLevel}-face${step.faceIndex}`,
735
+ dimension: '2d',
736
+ baseMipLevel: step.mipLevel,
737
+ mipLevelCount: 1,
738
+ baseArrayLayer: step.faceIndex,
739
+ arrayLayerCount: 1,
740
+ },
741
+ () => outputView,
742
+ );
743
+ if (!output.ok) return output;
744
+ const added = builder.addRasterPass(
745
+ `reflection-probe.${work.probeIndex}.pmrem.${step.mipLevel}.${step.faceIndex}`,
746
+ {
747
+ accesses: [
748
+ { resource: rawCube.value, usage: 'sampled-read' },
749
+ { resource: output.value, usage: 'color-attachment' },
750
+ ],
751
+ colorAttachments: [
752
+ {
753
+ view: output.value,
754
+ loadOp: 'clear',
755
+ storeOp: 'store',
756
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
757
+ },
758
+ ],
759
+ encode: ({ pass }) => {
760
+ if (work.filterGroup0 === undefined) return;
761
+ pass.setPipeline(work.filterPipeline);
762
+ pass.setBindGroup(0, work.filterGroup0);
763
+ pass.setBindGroup(1, work.filterGroup1);
764
+ pass.setVertexBuffer(0, work.cubeVertexBuffer);
765
+ pass.draw(6, 1, step.faceIndex * 6, 0);
766
+ },
767
+ },
768
+ );
769
+ if (!added.ok) return added;
770
+ }
771
+ accesses.push({ resource: filteredCube.value, usage: 'sampled-read' });
772
+ }
773
+ return ok(accesses);
774
+ }
775
+
776
+ export {
777
+ projectRenderTargetGraph,
778
+ type RenderTargetGraphInput,
779
+ type RenderTargetGraphProjection,
780
+ type RenderTargetGraphSubresource,
781
+ } from '../targets/graph-projection';
782
+
209
783
  export interface RenderFeatureGraphRuntimeState {
210
784
  plans: readonly RenderFeaturePlannedFrame[];
211
785
  fullscreenEffects: ReadonlyMap<
@@ -230,10 +804,24 @@ export interface RenderFeatureGraphCandidate {
230
804
  readonly preparedResourceKey?: string;
231
805
  /** Release candidate-owned prepared resources when graph promotion fails. */
232
806
  readonly onRejected?: () => void;
807
+ /** Publish candidate-owned runtime declarations only after graph promotion. */
808
+ readonly onAccepted?: () => void;
809
+ /** Discard candidate-only resources when a base graph is kept as fallback. */
810
+ readonly onAbandoned?: () => void;
233
811
  }
234
812
 
235
813
  const featureGraphStates = new WeakMap<RenderSystemInternals, RenderFeatureGraphRuntimeState>();
236
814
 
815
+ interface TimingInstrumentationState {
816
+ readonly instrumentation: RenderGraphPassInstrumentation<RenderPipelineFrame>;
817
+ setCapture(capture: GpuPassTimingCapture | undefined): void;
818
+ }
819
+
820
+ const timingInstrumentationStates = new WeakMap<
821
+ RenderSystemInternals,
822
+ TimingInstrumentationState
823
+ >();
824
+
237
825
  type CompiledGraphTargetAccess = CompiledRenderGraph<RenderPipelineFrame> & {
238
826
  readonly getColorTargetDescriptor: (
239
827
  name: string,
@@ -284,6 +872,31 @@ export function renderFeatureGraphPlanSignature(
284
872
  );
285
873
  }
286
874
 
875
+ /**
876
+ * Admit fullscreen post-processes as one chain, never as independent passes.
877
+ * A pass whose shader module is still compiling cannot clear a graph target and
878
+ * leave the remainder of the chain sampling an undefined intermediate. The
879
+ * renderer therefore keeps the base output-transform graph until every
880
+ * requested effect is ready, then admits the complete ordered chain together.
881
+ */
882
+ export function resolvePostProcessChainAdmission(
883
+ effects: readonly string[],
884
+ isReady: (identity: string) => boolean,
885
+ ): { readonly admitted: readonly string[]; readonly pending: boolean } {
886
+ // Probe every declaration even after the first miss. A single frame may
887
+ // introduce several effects; evaluating all of them lets the shared cache
888
+ // start every independent compile together instead of serialising warmup on
889
+ // array order.
890
+ let ready = true;
891
+ for (const identity of effects) {
892
+ if (!isReady(identity)) ready = false;
893
+ }
894
+ return {
895
+ admitted: ready ? effects : [],
896
+ pending: !ready,
897
+ };
898
+ }
899
+
287
900
  function validateRenderFeaturePlans(
288
901
  plans: readonly RenderFeaturePlannedFrame[],
289
902
  ): Result<readonly RenderFeaturePlanExecution[], RenderError> {
@@ -314,6 +927,10 @@ function commitFeatureCandidate(
314
927
  internals: RenderSystemInternals,
315
928
  candidate: RenderFeatureGraphCandidate,
316
929
  ): void {
930
+ // Publish renderer-owned declarations only after their device resources have
931
+ // been admitted. If the acceptance hook throws, the previously accepted
932
+ // graph/state pair remains visible to the caller.
933
+ candidate.onAccepted?.();
317
934
  const state = getRenderFeatureGraphState(internals);
318
935
  state.plans = candidate.plans;
319
936
  state.fullscreenEffects = candidate.fullscreenEffects;
@@ -331,8 +948,10 @@ function topologyOf(
331
948
  shadowMapSize: number | undefined,
332
949
  gpuDriven: PreparedGpuDrivenFrame | undefined,
333
950
  featureGraphCandidate: RenderFeatureGraphCandidate | undefined,
951
+ cubeCaptureState: CubeCaptureGraphState | undefined,
334
952
  transmissionDemand: TransmissionDemand | undefined,
335
953
  volumetricFog: ExtractedVolumetricFog | undefined,
954
+ standardLighting: StandardTopologyInputValue | undefined,
336
955
  ): Result<RenderPipelineTopology, RhiError> {
337
956
  const cascadeCount = Math.max(1, Math.min(4, Math.round(lights.cascadeCount ?? 1))) as
338
957
  | 1
@@ -357,6 +976,10 @@ function topologyOf(
357
976
  ...(featureGraphCandidate?.fullscreenEffects ??
358
977
  getRenderFeatureGraphState(internals).fullscreenEffects),
359
978
  ].map(([id]) => id);
979
+ const featurePostEffectDeclarations = [
980
+ ...(featureGraphCandidate?.fullscreenEffects ??
981
+ getRenderFeatureGraphState(internals).fullscreenEffects),
982
+ ].map(([id, entry]) => [id, postProcessShaderEntrySignature(entry)] as const);
360
983
  const config =
361
984
  featurePostEffects.length === 0
362
985
  ? frameState.installedPipelineConfig
@@ -389,6 +1012,7 @@ function topologyOf(
389
1012
  standardProfile: internals.standardProfile,
390
1013
  config,
391
1014
  clearOnly,
1015
+ reflectionFallback: { enabled: frameState.reflectionFallbackDemand === true },
392
1016
  surface: {
393
1017
  width,
394
1018
  height,
@@ -406,10 +1030,13 @@ function topologyOf(
406
1030
  motionBlur: isMotionBlurTemporalDemand(camera.motionBlur),
407
1031
  },
408
1032
  shadow: {
409
- mapSize: shadowMapSize ?? 1024,
410
- cascadeCount,
411
- pointCount: Math.min(4, lights.pointShadow.length),
412
- pointFaceSize: lights.pointShadow[0]?.mapSize ?? 512,
1033
+ directional:
1034
+ shadowMapSize === undefined || lights.cascadeCount === undefined
1035
+ ? 'disabled'
1036
+ : { mapSize: shadowMapSize, cascadeCount },
1037
+ spotMapSize: shadowMapSize ?? 1024,
1038
+ pointCount: Math.min(SHADOW_ATLAS_DEFAULT_LAYERS, lights.pointShadow.length),
1039
+ pointFaceSize: lights.pointShadow[0]?.mapSize ?? SHADOW_ATLAS_DEFAULT_FACE_SIZE,
413
1040
  spotCount: Math.min(
414
1041
  4,
415
1042
  lights.spot.filter(
@@ -468,10 +1095,12 @@ function topologyOf(
468
1095
  plans: renderFeatureGraphPlanSignature(
469
1096
  featureGraphCandidate?.plans ?? getRenderFeatureGraphState(internals).plans,
470
1097
  ),
471
- fullscreenEffects: featurePostEffects,
1098
+ fullscreenEffects: featurePostEffectDeclarations,
472
1099
  preparedResourceKey: featureGraphCandidate?.preparedResourceKey ?? '',
1100
+ cubeCaptureSlots: cubeCaptureState?.work.length ?? 0,
473
1101
  }),
474
1102
  gpuDrivenTopologySignature: gpuDriven?.topologySignature ?? '',
1103
+ standardLightingTopologySignature: standardLightingTopologySignature(standardLighting),
475
1104
  transmissionDemand: transmissionDemand ?? {
476
1105
  activeCount: 0,
477
1106
  needsRoughMips: false,
@@ -527,14 +1156,17 @@ function targetResolver(
527
1156
  };
528
1157
  }
529
1158
 
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 {
1159
+ function resolveTargetBindings(
1160
+ input: {
1161
+ readonly frame: RenderPipelineFrame;
1162
+ readonly binding: import('../prepare/prepared-graphics-resolver').PreparedGraphicsResolvedResource & {
1163
+ readonly kind: 'bindings';
1164
+ };
1165
+ readonly resources: GraphResourceResolver;
1166
+ readonly resolveTarget: RenderFeatureGraphTargetResolver;
1167
+ },
1168
+ featurePostProcessEntries?: ReadonlyMap<string, PostProcessShaderEntry>,
1169
+ ): RenderFeatureGraphBindingsResolution | undefined {
538
1170
  const frame = input.frame as import('./render-context')._InternalRenderPipelineContext;
539
1171
  const descriptor = input.binding.descriptor;
540
1172
  const pipeline = input.binding.pipeline as
@@ -546,7 +1178,8 @@ function resolveTargetBindings(input: {
546
1178
  if (descriptor?.values.fullscreen === true && isTemporalFullscreenBinding(descriptor.values)) {
547
1179
  const shader = descriptor.values.shader;
548
1180
  if (typeof shader !== 'string') return undefined;
549
- const entry = frame.runtime.lookupPostProcess?.(shader);
1181
+ const entry =
1182
+ featurePostProcessEntries?.get(shader) ?? frame.runtime.lookupPostProcess?.(shader);
550
1183
  if (entry === undefined) return undefined;
551
1184
  const fullscreen = buildFullscreenPostProcessPass(
552
1185
  { device: frame.runtime.device, errorRegistry: frame.runtime.errorRegistry },
@@ -636,8 +1269,10 @@ function resolveTargetBindings(input: {
636
1269
  directionalShadow: frame.frameState.currentDirectionalShadowView ?? undefined,
637
1270
  spotShadow: frame.frameState.currentSpotShadowView ?? undefined,
638
1271
  },
1272
+ true,
1273
+ frame.standardLighting,
639
1274
  ).viewBindGroup;
640
- return viewBindGroup === null ? undefined : { handle: viewBindGroup, dynamicOffsets: [0] };
1275
+ return viewBindGroup === null ? undefined : { handle: viewBindGroup, dynamicOffsets: [0, 0] };
641
1276
  }
642
1277
  const target = input.resolveTarget(sceneDepth);
643
1278
  if (target === undefined) return undefined;
@@ -707,11 +1342,20 @@ export function ensureCompiledFrameGraph(
707
1342
  shadowMapSize: number | undefined,
708
1343
  gpuDriven?: PreparedGpuDrivenFrame,
709
1344
  featureGraphCandidate?: RenderFeatureGraphCandidate,
1345
+ cubeCaptureState?: CubeCaptureGraphState,
710
1346
  clearOnly = false,
711
1347
  transmissionDemand?: TransmissionDemand,
1348
+ occlusion?: import('../scene/visibility/occlusion-runtime').OcclusionFrameProjection,
712
1349
  volumetricFog?: ExtractedVolumetricFog,
713
1350
  volumeTopologyCandidate = false,
1351
+ standardLighting?: StandardTopologyInputValue,
714
1352
  ): CompiledRenderGraph<RenderPipelineFrame> | null {
1353
+ const requestedStandardLightingSignature = standardLightingTopologySignature(standardLighting);
1354
+ const lastKnownGood = (): CompiledRenderGraph<RenderPipelineFrame> | null =>
1355
+ frameState.compiledFrameGraph !== null &&
1356
+ frameState.standardLightingGraphSignature === requestedStandardLightingSignature
1357
+ ? frameState.compiledFrameGraph
1358
+ : null;
715
1359
  const surfaceWidth = Math.max(1, width);
716
1360
  const surfaceHeight = Math.max(1, height);
717
1361
  const featurePlans = featureGraphCandidate?.plans ?? getRenderFeatureGraphState(internals).plans;
@@ -719,7 +1363,7 @@ export function ensureCompiledFrameGraph(
719
1363
  if (!projectedFeaturePlans.ok) {
720
1364
  reportRenderFeatureGraphError(internals, projectedFeaturePlans.error);
721
1365
  featureGraphCandidate?.onRejected?.();
722
- return volumeTopologyCandidate ? null : frameState.compiledFrameGraph;
1366
+ return volumeTopologyCandidate ? null : lastKnownGood();
723
1367
  }
724
1368
  const topologyResult = topologyOf(
725
1369
  internals,
@@ -733,19 +1377,92 @@ export function ensureCompiledFrameGraph(
733
1377
  shadowMapSize,
734
1378
  gpuDriven,
735
1379
  featureGraphCandidate,
1380
+ cubeCaptureState,
736
1381
  transmissionDemand,
737
1382
  volumetricFog,
1383
+ standardLighting,
738
1384
  );
739
1385
  if (!topologyResult.ok) {
740
1386
  internals.errorRegistry.fire(topologyResult.error);
741
1387
  featureGraphCandidate?.onRejected?.();
742
- return volumeTopologyCandidate ? null : frameState.compiledFrameGraph;
1388
+ return volumeTopologyCandidate ? null : lastKnownGood();
1389
+ }
1390
+ const requestedPostEffects = topologyResult.value.config?.postEffects ?? [];
1391
+ const featurePostProcessEntries =
1392
+ featureGraphCandidate?.fullscreenEffects ??
1393
+ getRenderFeatureGraphState(internals).fullscreenEffects;
1394
+ const postProcessAdmission = resolvePostProcessChainAdmission(
1395
+ requestedPostEffects,
1396
+ (identity) => {
1397
+ const entry =
1398
+ featurePostProcessEntries.get(identity) ?? internals.lookupPostProcess?.(identity);
1399
+ // Preserve the existing missing-entry failure contract. The graph will
1400
+ // report post-process-not-found during record instead of silently
1401
+ // removing an authored id from the topology.
1402
+ if (entry === undefined) return true;
1403
+ const buildPipeline = internals.buildPostProcessPipeline;
1404
+ const getPipeline = internals.getPostProcessPipeline;
1405
+ if (buildPipeline === undefined || getPipeline === undefined) return true;
1406
+ const built = buildFullscreenPostProcessPass(
1407
+ { device: internals.device, errorRegistry: internals.errorRegistry },
1408
+ entry,
1409
+ camera.antialias === 'msaa' && topologyResult.value.lane.multisample,
1410
+ );
1411
+ if (built === null) return false;
1412
+ return (
1413
+ getPipeline(
1414
+ identity,
1415
+ built.bindGroupLayout,
1416
+ topologyResult.value.surface.storageFormat as GPUTextureFormat,
1417
+ entry,
1418
+ ) !== null
1419
+ );
1420
+ },
1421
+ );
1422
+ const acceptedLkg = lastKnownGood();
1423
+ if (postProcessAdmission.pending && acceptedLkg !== null) {
1424
+ // Do not publish a candidate whose fullscreen chain is only partially
1425
+ // ready. Keep the accepted graph (including its prior post-effect chain)
1426
+ // and retry the same feature candidate on the next frame.
1427
+ featureGraphCandidate?.onRejected?.();
1428
+ return acceptedLkg;
743
1429
  }
744
- const topology = topologyResult.value;
745
- const key = JSON.stringify(topology);
1430
+ const topology = postProcessAdmission.pending
1431
+ ? {
1432
+ ...topologyResult.value,
1433
+ config: {
1434
+ ...(topologyResult.value.config ?? {}),
1435
+ postEffects: [],
1436
+ },
1437
+ }
1438
+ : topologyResult.value;
1439
+ const key = JSON.stringify({
1440
+ topology,
1441
+ // GPU-driven residency can become available after the first record pass
1442
+ // (custom mesh handles are resolved into the per-frame projection). The
1443
+ // topology signature alone is intentionally stable across that lifecycle,
1444
+ // so include the prepared projection's presence to invalidate a cached
1445
+ // CPU-only graph when the GPU owner becomes ready, and vice versa.
1446
+ gpuDrivenProjectionActive: gpuDriven !== undefined,
1447
+ // Query reservations are frame-local transport state. The projection is
1448
+ // renderer-owned and read dynamically by the compiled pass, so a page
1449
+ // rotation must not invalidate the graph or its cached bind groups.
1450
+ occlusionQuery:
1451
+ occlusion === undefined ? undefined : { active: true, sampleCount: occlusion.sampleCount },
1452
+ cubeCaptureSlots: cubeCaptureState?.work.length ?? 0,
1453
+ reflectionProbeSlots: cubeCaptureState?.reflectionProbes?.work.map((work) => [
1454
+ work.probeIndex,
1455
+ work.rawCaptureFace ?? -1,
1456
+ work.step?.faceIndex ?? -1,
1457
+ work.step?.mipLevel ?? -1,
1458
+ ]),
1459
+ });
746
1460
  if (frameState.compiledFrameGraph !== null && frameState.compiledFrameGraphTopologyKey === key) {
747
- if (featureGraphCandidate !== undefined) {
1461
+ frameState.standardLightingGraphSignature = requestedStandardLightingSignature;
1462
+ if (featureGraphCandidate !== undefined && !postProcessAdmission.pending) {
748
1463
  commitFeatureCandidate(internals, featureGraphCandidate);
1464
+ } else if (featureGraphCandidate !== undefined) {
1465
+ featureGraphCandidate.onAbandoned?.();
749
1466
  }
750
1467
  // A candidate flag only describes a possible topology replacement. When
751
1468
  // the derived key is unchanged there is no replacement to stage: reuse the
@@ -761,7 +1478,9 @@ export function ensureCompiledFrameGraph(
761
1478
  const built = frameState.activePipeline.build(
762
1479
  {
763
1480
  graph: builder,
1481
+ ...(standardLighting === undefined ? {} : { standardLighting }),
764
1482
  capabilities: { rgba16floatRenderable: internals.device.caps.rgba16floatRenderable },
1483
+ ...(occlusion === undefined ? {} : { occlusion }),
765
1484
  ...(taaHistory === undefined ? {} : { taaHistory }),
766
1485
  encodeTransmissionMip: ({ pass, resources, source }) => {
767
1486
  const sourceView = resources.textureView(source);
@@ -781,11 +1500,21 @@ export function ensureCompiledFrameGraph(
781
1500
  const resolveTarget = targetResolver(targets, semanticTargets, namedTargets);
782
1501
  const projected = projectPlanExecutions(builder, projectedFeaturePlans.value, {
783
1502
  resolveTarget,
784
- resolveBindings: resolveTargetBindings,
1503
+ resolveBindings: (input) => resolveTargetBindings(input, featurePostProcessEntries),
785
1504
  reportError: (error) => reportRenderFeatureGraphError(internals, error),
786
1505
  });
787
1506
  return projected.ok ? ok(undefined) : projected;
788
1507
  },
1508
+ contributeCubeCaptures: () => {
1509
+ const reflectionProbes = addReflectionProbeGraphPasses(
1510
+ builder,
1511
+ cubeCaptureState?.reflectionProbes ?? { work: [] },
1512
+ );
1513
+ if (!reflectionProbes.ok) return reflectionProbes;
1514
+ return cubeCaptureState === undefined
1515
+ ? ok(undefined)
1516
+ : addCubeCaptureGraphPasses(builder, cubeCaptureState);
1517
+ },
789
1518
  hasFeature: (identity) =>
790
1519
  projectedFeaturePlans.value.some((execution) => execution.featureIdentity === identity),
791
1520
  },
@@ -794,7 +1523,7 @@ export function ensureCompiledFrameGraph(
794
1523
  if (!built.ok) {
795
1524
  internals.errorRegistry.fire(built.error);
796
1525
  featureGraphCandidate?.onRejected?.();
797
- return volumeTopologyCandidate ? null : frameState.compiledFrameGraph;
1526
+ return volumeTopologyCandidate ? null : lastKnownGood();
798
1527
  }
799
1528
  const compiled = builder.compile({
800
1529
  device: internals.device,
@@ -803,7 +1532,7 @@ export function ensureCompiledFrameGraph(
803
1532
  if (!compiled.ok) {
804
1533
  internals.errorRegistry.fire(compiled.error);
805
1534
  featureGraphCandidate?.onRejected?.();
806
- return frameState.compiledFrameGraph;
1535
+ return lastKnownGood();
807
1536
  }
808
1537
  const previous = frameState.compiledFrameGraph;
809
1538
  if (volumeTopologyCandidate) {
@@ -813,6 +1542,8 @@ export function ensureCompiledFrameGraph(
813
1542
  }
814
1543
  frameState.compiledFrameGraph = compiled.value;
815
1544
  frameState.compiledFrameGraphTopologyKey = key;
1545
+ frameState.standardLightingGraphSignature = requestedStandardLightingSignature;
1546
+ frameState.compiledFrameGraphGeneration += 1;
816
1547
  const compiledTargets = compiled.value as CompiledGraphTargetAccess;
817
1548
  frameState.graphGeneration += 1;
818
1549
  frameState.perFrameGraph = {
@@ -822,8 +1553,10 @@ export function ensureCompiledFrameGraph(
822
1553
  graphGeneration: frameState.graphGeneration,
823
1554
  };
824
1555
  gpuDriven?._commitResourceReplacement();
825
- if (featureGraphCandidate !== undefined) {
1556
+ if (featureGraphCandidate !== undefined && !postProcessAdmission.pending) {
826
1557
  commitFeatureCandidate(internals, featureGraphCandidate);
1558
+ } else if (featureGraphCandidate !== undefined) {
1559
+ featureGraphCandidate.onAbandoned?.();
827
1560
  }
828
1561
  if (!volumeTopologyCandidate && previous !== null) retire(frameState, previous);
829
1562
  return compiled.value;
@@ -835,9 +1568,22 @@ export function executeCompiledFrameGraph(
835
1568
  frame: RenderPipelineFrame,
836
1569
  encoder: RhiCommandEncoder,
837
1570
  runPass?: import('@forgeax/engine-render-graph').RenderGraphPassRunner,
1571
+ frameHooks?: {
1572
+ readonly afterGraphExecute?: () => Result<void, RhiError>;
1573
+ readonly onSubmitted?: () => void;
1574
+ readonly onAborted?: () => void;
1575
+ },
1576
+ readbackFaces?: readonly number[],
1577
+ onSubmitted?: (completed: Promise<unknown>) => void,
838
1578
  timingCapture?: GpuTimingCapture,
839
1579
  ): boolean {
840
1580
  const graph = frameState.compiledFrameGraph;
1581
+ const invalidatePostProcessModule = (id: string): void => {
1582
+ const entry = internals.lookupPostProcess?.(id);
1583
+ if (entry !== undefined) {
1584
+ internals.invalidateShaderModule?.(postProcessShaderModuleLabel(id, entry.source));
1585
+ }
1586
+ };
841
1587
  const rejectTemporalFrame = (): void => {
842
1588
  if (frameState.temporalFrameInput === undefined) return;
843
1589
  frameState.temporalFrameTransaction.commit({ accepted: false });
@@ -849,31 +1595,64 @@ export function executeCompiledFrameGraph(
849
1595
  rejectTemporalFrame();
850
1596
  return false;
851
1597
  }
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
- };
1598
+ const legacyTimingInstrumentation =
1599
+ timingCapture === undefined ? undefined : createTimingInstrumentation(timingCapture);
1600
+ const timingHost = internals as RenderSystemInternals & {
1601
+ gpuPassTimingSession?: GpuPassTimingSession | undefined;
1602
+ gpuPassTimingCapture?: GpuPassTimingCapture | undefined;
1603
+ gpuPassTimingSubmittedWork?: Promise<void> | undefined;
1604
+ gpuPassTimingFrameIdentity?:
1605
+ | import('./gpu-pass-timing/session.js').GpuPassTimingFrameIdentity
1606
+ | undefined;
1607
+ gpuPassTimingBeginReason?:
1608
+ | import('./gpu-pass-timing/errors.js').GpuPassTimingReason
1609
+ | undefined;
1610
+ };
1611
+ timingHost.gpuPassTimingCapture = undefined;
1612
+ timingHost.gpuPassTimingSubmittedWork = undefined;
1613
+ timingHost.gpuPassTimingBeginReason = undefined;
1614
+ const timingIdentity = timingHost.gpuPassTimingFrameIdentity;
1615
+ const passTimingResult =
1616
+ timingIdentity === undefined
1617
+ ? undefined
1618
+ : timingHost.gpuPassTimingSession?.beginFrame({
1619
+ ...timingIdentity,
1620
+ graphGeneration: frameState.compiledFrameGraphGeneration,
1621
+ });
1622
+ const capture = passTimingResult?.ok === true ? passTimingResult.value : undefined;
1623
+ if (passTimingResult?.ok === false) timingHost.gpuPassTimingBeginReason = passTimingResult.error;
1624
+ const passTimingCapture = capture;
1625
+ timingHost.gpuPassTimingCapture = passTimingCapture;
1626
+ const timingInstrumentationState =
1627
+ passTimingCapture !== undefined
1628
+ ? getTimingInstrumentationState(internals, passTimingCapture)
1629
+ : timingCapture === undefined
1630
+ ? undefined
1631
+ : undefined;
1632
+ const graphInstrumentation =
1633
+ passTimingCapture !== undefined
1634
+ ? timingInstrumentationState?.instrumentation
1635
+ : legacyTimingInstrumentation;
867
1636
  type PreparedCommand = { readonly command: CommandBuffer };
868
1637
  let preparedCommand: PreparedCommand = { command: undefined as never };
869
1638
  const transaction = executeRendererFrameTransaction<PreparedCommand>({
870
1639
  build: () => ({ ok: true, value: { command: undefined as never } }),
871
1640
  execute: () => {
872
- const executed = graph.execute(frame, timedRunPass);
1641
+ const executed = graph.execute(frame, runPass, graphInstrumentation);
1642
+ timingInstrumentationState?.setCapture(undefined);
873
1643
  if (!executed.ok) {
1644
+ passTimingCapture?.abort({ code: executed.error.code });
874
1645
  internals.errorRegistry.fire(executed.error);
875
1646
  return { ok: false, stage: 'execute' };
876
1647
  }
1648
+ const afterGraphExecute = frameHooks?.afterGraphExecute;
1649
+ if (afterGraphExecute !== undefined) {
1650
+ const recorded = afterGraphExecute();
1651
+ if (!recorded.ok) {
1652
+ internals.errorRegistry.fire(recorded.error);
1653
+ return { ok: false, stage: 'execute' };
1654
+ }
1655
+ }
877
1656
  return { ok: true, value: undefined };
878
1657
  },
879
1658
  finish: () => {
@@ -1000,6 +1779,44 @@ export function executeCompiledFrameGraph(
1000
1779
  );
1001
1780
  }
1002
1781
  }
1782
+ const fallbackReadback = frameState.reflectionFallbackReadback;
1783
+ frameState.reflectionFallbackReadback = undefined;
1784
+ if (fallbackReadback !== undefined) {
1785
+ const graphAccess = frameState.perFrameGraph;
1786
+ const graphTexture = graphAccess?.getColorTargetTexture(fallbackReadback.name);
1787
+ const descriptor = graphAccess?.getColorTargetDescriptor(fallbackReadback.name);
1788
+ const matches =
1789
+ graphAccess !== undefined &&
1790
+ graphAccess !== null &&
1791
+ graphTexture !== undefined &&
1792
+ descriptor !== undefined &&
1793
+ descriptor.format === fallbackReadback.expected.format &&
1794
+ descriptor.size.width === fallbackReadback.expected.width &&
1795
+ descriptor.size.height === fallbackReadback.expected.height &&
1796
+ descriptor.sample === 1 &&
1797
+ (descriptor.usage & fallbackReadback.expected.usage) ===
1798
+ fallbackReadback.expected.usage &&
1799
+ graphAccess.graphGeneration === fallbackReadback.expected.graphGeneration &&
1800
+ frameState.frameNumber === fallbackReadback.expected.frameId &&
1801
+ getTextureIdentity(graphTexture) === fallbackReadback.expected.textureIdentity;
1802
+ if (matches && graphTexture !== undefined) {
1803
+ encoder.copyTextureToBuffer(
1804
+ { texture: graphTexture },
1805
+ {
1806
+ buffer: fallbackReadback.buffer,
1807
+ bytesPerRow: fallbackReadback.bytesPerRow,
1808
+ rowsPerImage: fallbackReadback.height,
1809
+ },
1810
+ {
1811
+ width: fallbackReadback.width,
1812
+ height: fallbackReadback.height,
1813
+ depthOrArrayLayers: 1,
1814
+ },
1815
+ );
1816
+ fallbackReadback.encoded = true;
1817
+ }
1818
+ }
1819
+ internals.encodeRenderTargetReadbacks?.(encoder, readbackFaces);
1003
1820
  const stagedGpuState = frameState.temporalGpuState;
1004
1821
  if (stagedGpuState !== undefined && !hasPendingTemporalGpuSubmit(stagedGpuState)) {
1005
1822
  internals.errorRegistry.fire(
@@ -1011,6 +1828,10 @@ export function executeCompiledFrameGraph(
1011
1828
  );
1012
1829
  return { ok: false, stage: 'finish' };
1013
1830
  }
1831
+ if (passTimingCapture !== undefined) {
1832
+ const tail = passTimingCapture.encodeTail(encoder);
1833
+ if (!tail.ok) passTimingCapture.abort({ code: tail.error.code });
1834
+ }
1014
1835
  const resolved = timingCapture?.resolve(encoder);
1015
1836
  if (resolved !== undefined && !resolved.ok) {
1016
1837
  timingCapture?.discard();
@@ -1018,6 +1839,7 @@ export function executeCompiledFrameGraph(
1018
1839
  }
1019
1840
  const finished = encoder.finish();
1020
1841
  if (!finished.ok) {
1842
+ passTimingCapture?.abort({ code: finished.error.code });
1021
1843
  timingCapture?.discard();
1022
1844
  internals.errorRegistry.fire(finished.error);
1023
1845
  return { ok: false, stage: 'finish' };
@@ -1026,8 +1848,16 @@ export function executeCompiledFrameGraph(
1026
1848
  return { ok: true, value: undefined };
1027
1849
  },
1028
1850
  submit: () => {
1851
+ const injectedFailure = internals.beforeSubmit?.(internals.device);
1852
+ if (injectedFailure !== undefined) {
1853
+ passTimingCapture?.abort({ code: injectedFailure.code });
1854
+ timingCapture?.discard();
1855
+ internals.errorRegistry.fire(injectedFailure);
1856
+ return { ok: false, stage: 'submit' };
1857
+ }
1029
1858
  const submitted = internals.device.queue.submit([preparedCommand.command]);
1030
1859
  if (!submitted.ok) {
1860
+ passTimingCapture?.abort({ code: submitted.error.code });
1031
1861
  timingCapture?.discard();
1032
1862
  internals.errorRegistry.fire(submitted.error);
1033
1863
  return { ok: false, stage: 'submit' };
@@ -1036,6 +1866,28 @@ export function executeCompiledFrameGraph(
1036
1866
  return { ok: true, value: undefined };
1037
1867
  },
1038
1868
  commit: () => {
1869
+ frameHooks?.onSubmitted?.();
1870
+ if (frameState.temporalFrameInput !== undefined) {
1871
+ const temporal = frameState.temporalFrameTransaction.commit({ accepted: true });
1872
+ if (temporal.ok) {
1873
+ frameState.temporalFrame = temporal.value;
1874
+ } else {
1875
+ internals.errorRegistry.fire(
1876
+ new RhiError({
1877
+ code: 'webgpu-runtime-error',
1878
+ expected: 'the staged temporal frame to commit after queue submission',
1879
+ hint: 'discard the temporal candidate and retry the next frame',
1880
+ detail: { error: temporal.error },
1881
+ }),
1882
+ );
1883
+ }
1884
+ frameState.temporalFrameInput = undefined;
1885
+ }
1886
+ if (passTimingCapture !== undefined) {
1887
+ const completion = internals.device.queue.onSubmittedWorkDone();
1888
+ timingHost.gpuPassTimingSubmittedWork = completion;
1889
+ passTimingCapture.markSubmitted(completion);
1890
+ }
1039
1891
  if (frameState.bloomFrameReceipts !== undefined) {
1040
1892
  internals
1041
1893
  .getPipelineState()
@@ -1049,7 +1901,7 @@ export function executeCompiledFrameGraph(
1049
1901
  frameState.activeTemporalGpuState = stagedGpuState;
1050
1902
  if (previousGpuState !== undefined && previousGpuState !== stagedGpuState) {
1051
1903
  internals.clearPostProcessPipelineCache?.('forgeax.taa-resolve');
1052
- internals.invalidateShaderModule?.('post-process-forgeax.taa-resolve-module');
1904
+ invalidatePostProcessModule('forgeax.taa-resolve');
1053
1905
  retireTemporalGpuStateAfterFence(
1054
1906
  previousGpuState,
1055
1907
  internals.device.queue,
@@ -1079,7 +1931,7 @@ export function executeCompiledFrameGraph(
1079
1931
  }
1080
1932
  if (stagedTemporalCommit.kind === 'off') {
1081
1933
  internals.clearPostProcessPipelineCache?.('forgeax.taa-resolve');
1082
- internals.invalidateShaderModule?.('post-process-forgeax.taa-resolve-module');
1934
+ invalidatePostProcessModule('forgeax.taa-resolve');
1083
1935
  const activeGpuState = frameState.activeTemporalGpuState;
1084
1936
  if (activeGpuState !== undefined) {
1085
1937
  retireTemporalGpuStateAfterFence(
@@ -1104,8 +1956,13 @@ export function executeCompiledFrameGraph(
1104
1956
  frameState.environmentLifecycle?.publish(frameState.environmentGeneration);
1105
1957
  frameState.environmentGeneration = undefined;
1106
1958
  }
1959
+ onSubmitted?.(internals.device.queue.onSubmittedWorkDone());
1107
1960
  },
1108
1961
  abort: (failure) => {
1962
+ frameHooks?.onAborted?.();
1963
+ passTimingCapture?.abort({ code: failure.stage });
1964
+ timingHost.gpuPassTimingCapture = undefined;
1965
+ timingHost.gpuPassTimingSubmittedWork = undefined;
1109
1966
  timingCapture?.discard();
1110
1967
  frameState.bloomFrameReceipts = undefined;
1111
1968
  if (frameState.environmentGeneration !== undefined) {
@@ -1133,3 +1990,137 @@ export function executeCompiledFrameGraph(
1133
1990
  if (!transaction.ok) rejectTemporalFrame();
1134
1991
  return transaction.ok;
1135
1992
  }
1993
+
1994
+ function createTimingInstrumentation(
1995
+ capture: GpuTimingCapture,
1996
+ ): RenderGraphPassInstrumentation<RenderPipelineFrame> {
1997
+ return {
1998
+ begin: (pass): RenderGraphPassInstrumentationScope | undefined => {
1999
+ // WebGPU has no portable timestamp boundary for a copy pass. Do not
2000
+ // pretend a command-encoder marker exists; frame timing remains valid
2001
+ // when at least one raster/compute pass is present.
2002
+ if (pass.kind === 'copy') return undefined;
2003
+ const writes = capture.beginPass(pass.name, pass.kind, pass.executionIndex);
2004
+ if (writes === undefined) return undefined;
2005
+ if (pass.kind === 'raster') {
2006
+ return {
2007
+ renderPassDescriptor: (descriptor) => {
2008
+ if (descriptor.timestampWrites !== undefined) {
2009
+ capture.markOwnerConflict(pass.name);
2010
+ return descriptor;
2011
+ }
2012
+ return { ...descriptor, timestampWrites: writes };
2013
+ },
2014
+ };
2015
+ }
2016
+ return {
2017
+ computePassDescriptor: (descriptor) => {
2018
+ if (descriptor.timestampWrites !== undefined) {
2019
+ capture.markOwnerConflict(pass.name);
2020
+ return descriptor;
2021
+ }
2022
+ return { ...descriptor, timestampWrites: writes };
2023
+ },
2024
+ };
2025
+ },
2026
+ };
2027
+ }
2028
+
2029
+ function getTimingInstrumentationState(
2030
+ internals: RenderSystemInternals,
2031
+ capture: GpuPassTimingCapture,
2032
+ ): TimingInstrumentationState {
2033
+ const existing = timingInstrumentationStates.get(internals);
2034
+ if (existing !== undefined) {
2035
+ existing.setCapture(capture);
2036
+ return existing;
2037
+ }
2038
+ type TimestampWrites = NonNullable<ReturnType<GpuPassTimingCapture['timestampWrites']>>;
2039
+ const activeIdentity: {
2040
+ passName: string;
2041
+ passKind: GpuPassTimingPassIdentity['passKind'];
2042
+ executionIndex: number;
2043
+ } = {
2044
+ passName: '',
2045
+ passKind: 'raster',
2046
+ executionIndex: 0,
2047
+ };
2048
+ let activeWrites: TimestampWrites | undefined;
2049
+ let activeCapture: GpuPassTimingCapture | undefined = capture;
2050
+ const rasterScope: RenderGraphPassInstrumentationScope = {
2051
+ renderPassDescriptor: (descriptor) => {
2052
+ const capture = activeCapture;
2053
+ const identity = activeIdentity;
2054
+ const writes = activeWrites;
2055
+ if (capture === undefined || identity === undefined || writes === undefined) {
2056
+ return descriptor;
2057
+ }
2058
+ if (descriptor.timestampWrites !== undefined) {
2059
+ capture.markOwnerConflict(identity);
2060
+ return descriptor;
2061
+ }
2062
+ (descriptor as { timestampWrites?: TimestampWrites }).timestampWrites = writes;
2063
+ return descriptor;
2064
+ },
2065
+ };
2066
+ const computeScope: RenderGraphPassInstrumentationScope = {
2067
+ computePassDescriptor: (descriptor) => {
2068
+ const capture = activeCapture;
2069
+ const identity = activeIdentity;
2070
+ const writes = activeWrites;
2071
+ if (capture === undefined || identity === undefined || writes === undefined) {
2072
+ return descriptor;
2073
+ }
2074
+ if (descriptor.timestampWrites !== undefined) {
2075
+ capture.markOwnerConflict(identity);
2076
+ return descriptor;
2077
+ }
2078
+ (descriptor as { timestampWrites?: TimestampWrites }).timestampWrites = writes;
2079
+ return descriptor;
2080
+ },
2081
+ };
2082
+ const copyScope: RenderGraphPassInstrumentationScope = {
2083
+ beforeCopy: (encoder) => {
2084
+ const capture = activeCapture;
2085
+ const identity = activeIdentity;
2086
+ if (capture === undefined || identity === undefined) return;
2087
+ capture.copyBoundaryBefore(identity, encoder);
2088
+ },
2089
+ afterCopy: (encoder) => {
2090
+ const capture = activeCapture;
2091
+ const identity = activeIdentity;
2092
+ if (capture === undefined || identity === undefined) return;
2093
+ capture.copyBoundaryAfter(identity, encoder);
2094
+ },
2095
+ };
2096
+ const instrumentation: RenderGraphPassInstrumentation<RenderPipelineFrame> = {
2097
+ begin: (pass) => {
2098
+ const capture = activeCapture;
2099
+ if (capture === undefined) return undefined;
2100
+ activeIdentity.passName = pass.name;
2101
+ activeIdentity.passKind = pass.kind;
2102
+ activeIdentity.executionIndex = pass.executionIndex;
2103
+ activeWrites = capture.recordPass(activeIdentity);
2104
+ if (activeWrites === undefined) return undefined;
2105
+ switch (pass.kind) {
2106
+ case 'raster':
2107
+ return rasterScope;
2108
+ case 'compute':
2109
+ return computeScope;
2110
+ case 'copy':
2111
+ return copyScope;
2112
+ }
2113
+ },
2114
+ };
2115
+ const state: TimingInstrumentationState = {
2116
+ instrumentation,
2117
+ setCapture: (capture) => {
2118
+ activeCapture = capture;
2119
+ if (capture === undefined) {
2120
+ activeWrites = undefined;
2121
+ }
2122
+ },
2123
+ };
2124
+ timingInstrumentationStates.set(internals, state);
2125
+ return state;
2126
+ }