@forgeax/engine-render 0.1.28 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (642) hide show
  1. package/README.md +627 -124
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
  4. package/dist/assembly/dynamic-geometry-host.d.ts +21 -0
  5. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  6. package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
  7. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +3 -1
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/gpu-driven-owner.d.ts +10 -0
  11. package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
  12. package/dist/assembly/host-contract.d.ts +8 -0
  13. package/dist/assembly/host-contract.d.ts.map +1 -1
  14. package/dist/assembly/material/assembly.d.ts +7 -0
  15. package/dist/assembly/material/assembly.d.ts.map +1 -1
  16. package/dist/assembly/material-shader-policy.d.ts +3 -1
  17. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  18. package/dist/assembly/recovery/failure-location.d.ts +12 -0
  19. package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
  20. package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
  21. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  22. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  23. package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
  24. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
  25. package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
  26. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  27. package/dist/assembly/renderer-helpers.d.ts +3 -0
  28. package/dist/assembly/renderer-helpers.d.ts.map +1 -1
  29. package/dist/assembly/renderer-inspection.d.ts +4 -0
  30. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  31. package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
  32. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  33. package/dist/assembly/skin-palette-owner.d.ts +8 -0
  34. package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
  35. package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
  36. package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
  37. package/dist/assembly/webgpu-device-loss.d.ts +15 -0
  38. package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
  39. package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
  40. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
  41. package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
  42. package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
  43. package/dist/assembly/webgpu-ready.d.ts +7 -3
  44. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  45. package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
  46. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
  47. package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
  48. package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
  49. package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
  50. package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
  51. package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
  52. package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
  53. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
  54. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
  55. package/dist/assembly/webgpu-renderer.d.ts +7 -141
  56. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  57. package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
  58. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  59. package/dist/authoring.mjs +2 -2
  60. package/dist/{chunk-JAASCMXA.mjs → chunk-33XZFTQU.mjs} +245 -39
  61. package/dist/chunk-33XZFTQU.mjs.map +1 -0
  62. package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
  63. package/dist/chunk-BLXGONR2.mjs.map +1 -0
  64. package/dist/{chunk-J52NMQKR.mjs → chunk-BQVQOQJH.mjs} +1086 -838
  65. package/dist/chunk-BQVQOQJH.mjs.map +1 -0
  66. package/dist/{chunk-IQMKJLNU.mjs → chunk-BTWH7BP4.mjs} +386 -17
  67. package/dist/chunk-BTWH7BP4.mjs.map +1 -0
  68. package/dist/{chunk-MBU4ZVE7.mjs → chunk-FUODTTIA.mjs} +3921 -671
  69. package/dist/chunk-FUODTTIA.mjs.map +1 -0
  70. package/dist/{chunk-MYJ5RPJO.mjs → chunk-JQTSEI3Z.mjs} +3 -3
  71. package/dist/{chunk-MYJ5RPJO.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
  72. package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
  73. package/dist/chunk-NICXSHAT.mjs.map +1 -0
  74. package/dist/{chunk-J6KACNOH.mjs → chunk-NRNXBLIJ.mjs} +577 -7
  75. package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
  76. package/dist/{chunk-RBDBZ5R7.mjs → chunk-PA5DRJNI.mjs} +2 -2
  77. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  78. package/dist/{chunk-VSL23LZE.mjs → chunk-S4DNKQYQ.mjs} +1117 -445
  79. package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
  80. package/dist/components/camera.d.ts +62 -2
  81. package/dist/components/camera.d.ts.map +1 -1
  82. package/dist/components/index.d.ts +1 -0
  83. package/dist/components/index.d.ts.map +1 -1
  84. package/dist/components/instances.d.ts +6 -20
  85. package/dist/components/instances.d.ts.map +1 -1
  86. package/dist/components/scene-instance.d.ts +2 -1
  87. package/dist/components/scene-instance.d.ts.map +1 -1
  88. package/dist/components/screen-space-reflection.d.ts +14 -0
  89. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  90. package/dist/construct-renderer.mjs +12888 -7025
  91. package/dist/construct-renderer.mjs.map +1 -1
  92. package/dist/device/gpu-residency.d.ts +1 -12
  93. package/dist/device/gpu-residency.d.ts.map +1 -1
  94. package/dist/environment/background.d.ts +6 -0
  95. package/dist/environment/background.d.ts.map +1 -0
  96. package/dist/errors/gpu-driven.d.ts +6 -3
  97. package/dist/errors/gpu-driven.d.ts.map +1 -1
  98. package/dist/errors/render.d.ts +7 -4
  99. package/dist/errors/render.d.ts.map +1 -1
  100. package/dist/extract/camera.d.ts.map +1 -1
  101. package/dist/extract/gpu-driven.d.ts +33 -3
  102. package/dist/extract/gpu-driven.d.ts.map +1 -1
  103. package/dist/features/host.d.ts +23 -1
  104. package/dist/features/host.d.ts.map +1 -1
  105. package/dist/features/noise-texture.d.ts +17 -0
  106. package/dist/features/noise-texture.d.ts.map +1 -0
  107. package/dist/features/plan.d.ts +83 -2
  108. package/dist/features/plan.d.ts.map +1 -1
  109. package/dist/features/prepared-gpu-work.d.ts +37 -4
  110. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  111. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  112. package/dist/features/prepared-graphics.d.ts +5 -0
  113. package/dist/features/prepared-graphics.d.ts.map +1 -1
  114. package/dist/features/render-graph-compute.d.ts +5 -3
  115. package/dist/features/render-graph-compute.d.ts.map +1 -1
  116. package/dist/features/render-graph-contribution.d.ts +11 -3
  117. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  118. package/dist/features/render-graph-raster.d.ts +19 -3
  119. package/dist/features/render-graph-raster.d.ts.map +1 -1
  120. package/dist/features/types.d.ts +11 -0
  121. package/dist/features/types.d.ts.map +1 -1
  122. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  123. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  124. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  125. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  126. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  127. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  128. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  129. package/dist/gpu-driven/production-raster.d.ts +144 -23
  130. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  131. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  132. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  133. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  134. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  135. package/dist/gpu-scene-schema.d.ts +2 -8
  136. package/dist/gpu-scene-schema.d.ts.map +1 -1
  137. package/dist/gpu-scene.d.ts +14 -0
  138. package/dist/gpu-scene.d.ts.map +1 -1
  139. package/dist/gpu-texture-usage.d.ts +1 -0
  140. package/dist/gpu-texture-usage.d.ts.map +1 -1
  141. package/dist/hdrp-buffers.d.ts +6 -0
  142. package/dist/hdrp-buffers.d.ts.map +1 -1
  143. package/dist/ibl/frame-resources.d.ts +8 -0
  144. package/dist/ibl/frame-resources.d.ts.map +1 -0
  145. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  146. package/dist/index.d.ts +18 -8
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.mjs +8 -8
  149. package/dist/inspection-types.d.ts +164 -0
  150. package/dist/inspection-types.d.ts.map +1 -1
  151. package/dist/instances-derived-bounds.d.ts +11 -0
  152. package/dist/instances-derived-bounds.d.ts.map +1 -1
  153. package/dist/instances.d.ts +89 -44
  154. package/dist/instances.d.ts.map +1 -1
  155. package/dist/internal.d.ts +3 -0
  156. package/dist/internal.d.ts.map +1 -1
  157. package/dist/internal.mjs +117 -9
  158. package/dist/internal.mjs.map +1 -1
  159. package/dist/lifecycle.d.ts +2 -1
  160. package/dist/lifecycle.d.ts.map +1 -1
  161. package/dist/material-row.d.ts +5 -0
  162. package/dist/material-row.d.ts.map +1 -0
  163. package/dist/materials.d.ts +15 -0
  164. package/dist/materials.d.ts.map +1 -1
  165. package/dist/pbr-pipeline.d.ts +21 -5
  166. package/dist/pbr-pipeline.d.ts.map +1 -1
  167. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  168. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  169. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  170. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  171. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  172. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  173. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  174. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  175. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  176. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  177. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  178. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  179. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  180. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  181. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  182. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  183. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  184. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  185. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  186. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  187. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  188. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  189. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  190. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  191. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  192. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  193. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  194. package/dist/pipeline/standard-output/types.d.ts +39 -0
  195. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  196. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  197. package/dist/pipeline/standard-post.d.ts +2 -2
  198. package/dist/pipeline/standard-post.d.ts.map +1 -1
  199. package/dist/pipeline-builder.d.ts +2 -1
  200. package/dist/pipeline-builder.d.ts.map +1 -1
  201. package/dist/pipeline-spec-types.d.ts +4 -0
  202. package/dist/pipeline-spec-types.d.ts.map +1 -1
  203. package/dist/pipeline-spec.d.ts +1 -9
  204. package/dist/pipeline-spec.d.ts.map +1 -1
  205. package/dist/points-lines/standard-owner.d.ts +1 -1
  206. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  207. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  208. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  209. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  210. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  211. package/dist/record/frame-lighting.d.ts +3 -3
  212. package/dist/record/frame-lighting.d.ts.map +1 -1
  213. package/dist/record/frame-snapshot.d.ts +106 -5
  214. package/dist/record/frame-snapshot.d.ts.map +1 -1
  215. package/dist/record/frame-targets.d.ts.map +1 -1
  216. package/dist/record/frame.d.ts +39 -4
  217. package/dist/record/frame.d.ts.map +1 -1
  218. package/dist/record/main-pass-geometry.d.ts +7 -8
  219. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  220. package/dist/record/main-pass-material.d.ts +38 -1
  221. package/dist/record/main-pass-material.d.ts.map +1 -1
  222. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  223. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  224. package/dist/record/main-pass.d.ts +12 -2
  225. package/dist/record/main-pass.d.ts.map +1 -1
  226. package/dist/record/mesh-ssbo.d.ts +15 -6
  227. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  228. package/dist/record/prepared-material-bindings.d.ts +19 -0
  229. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  230. package/dist/record/recovery-pipeline.d.ts +2 -9
  231. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  232. package/dist/record/render-context.d.ts +74 -26
  233. package/dist/record/render-context.d.ts.map +1 -1
  234. package/dist/record/shadow-pass.d.ts +62 -7
  235. package/dist/record/shadow-pass.d.ts.map +1 -1
  236. package/dist/record/skybox-post-pass.d.ts +1 -1
  237. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  238. package/dist/record/typed-frame-graph.d.ts +35 -7
  239. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  240. package/dist/record/view-ubo.d.ts +1 -0
  241. package/dist/record/view-ubo.d.ts.map +1 -1
  242. package/dist/recovery/render-system-candidate.d.ts +10 -2
  243. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  244. package/dist/recovery/render-system-roots.d.ts +11 -0
  245. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  246. package/dist/recovery/types.d.ts +76 -0
  247. package/dist/recovery/types.d.ts.map +1 -0
  248. package/dist/reflection/record-owner.d.ts +0 -2
  249. package/dist/reflection/record-owner.d.ts.map +1 -1
  250. package/dist/render-contract.d.ts +173 -30
  251. package/dist/render-contract.d.ts.map +1 -1
  252. package/dist/render-graph-primitives.d.ts +1 -0
  253. package/dist/render-graph-primitives.d.ts.map +1 -1
  254. package/dist/render-pipeline.d.ts +64 -5
  255. package/dist/render-pipeline.d.ts.map +1 -1
  256. package/dist/render-system-extract-tail.d.ts +7 -5
  257. package/dist/render-system-extract-tail.d.ts.map +1 -1
  258. package/dist/render-system-extract.d.ts +101 -41
  259. package/dist/render-system-extract.d.ts.map +1 -1
  260. package/dist/render-system-projections.d.ts +22 -0
  261. package/dist/render-system-projections.d.ts.map +1 -0
  262. package/dist/render-system.d.ts +27 -77
  263. package/dist/render-system.d.ts.map +1 -1
  264. package/dist/scene/render-scene-types.d.ts +14 -7
  265. package/dist/scene/render-scene-types.d.ts.map +1 -1
  266. package/dist/scene/render-scene.d.ts +29 -11
  267. package/dist/scene/render-scene.d.ts.map +1 -1
  268. package/dist/scene/shadow-visibility.d.ts +5 -0
  269. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  270. package/dist/ssr/admission.d.ts +70 -1
  271. package/dist/ssr/admission.d.ts.map +1 -1
  272. package/dist/ssr/compose.d.ts +13 -0
  273. package/dist/ssr/compose.d.ts.map +1 -0
  274. package/dist/ssr/errors.d.ts +35 -0
  275. package/dist/ssr/errors.d.ts.map +1 -1
  276. package/dist/ssr/graph.d.ts +60 -0
  277. package/dist/ssr/graph.d.ts.map +1 -0
  278. package/dist/ssr/history.d.ts +97 -0
  279. package/dist/ssr/history.d.ts.map +1 -0
  280. package/dist/ssr/hiz.d.ts +37 -0
  281. package/dist/ssr/hiz.d.ts.map +1 -0
  282. package/dist/ssr/inspection.d.ts +42 -0
  283. package/dist/ssr/inspection.d.ts.map +1 -0
  284. package/dist/ssr/resources.d.ts +58 -0
  285. package/dist/ssr/resources.d.ts.map +1 -0
  286. package/dist/ssr/temporal.d.ts +38 -0
  287. package/dist/ssr/temporal.d.ts.map +1 -0
  288. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  289. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  290. package/dist/systems/skin-palette-types.d.ts +15 -0
  291. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  292. package/dist/systems/transparent-dispatch.d.ts +9 -0
  293. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  294. package/dist/targets/material-source.d.ts +7 -0
  295. package/dist/targets/material-source.d.ts.map +1 -1
  296. package/dist/temporal/frame-coordinator.d.ts +7 -0
  297. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  298. package/dist/temporal/gpu.d.ts +8 -0
  299. package/dist/temporal/gpu.d.ts.map +1 -1
  300. package/dist/temporal/history.d.ts.map +1 -1
  301. package/dist/temporal/index.mjs +6 -6
  302. package/dist/temporal/inspection.d.ts.map +1 -1
  303. package/dist/temporal/standard-scene-data.d.ts +2 -1
  304. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  305. package/dist/temporal/temporal-view.d.ts +2 -0
  306. package/dist/temporal/temporal-view.d.ts.map +1 -1
  307. package/dist/typed-render-graph-primitives.d.ts +35 -1
  308. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  309. package/dist/typed-shadow-passes.d.ts +3 -1
  310. package/dist/typed-shadow-passes.d.ts.map +1 -1
  311. package/dist/volume/temporal.d.ts +6 -0
  312. package/dist/volume/temporal.d.ts.map +1 -1
  313. package/package.json +21 -20
  314. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  315. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  316. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  317. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  318. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  319. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  320. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  321. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  322. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  323. package/src/__tests__/factory-contract.integration.test.ts +67 -8
  324. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  325. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  326. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  327. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  328. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  329. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  330. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  331. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  332. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  333. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
  334. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  335. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  336. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
  337. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
  338. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
  339. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  340. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  341. package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
  342. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  343. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  344. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  345. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  346. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  347. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  348. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  349. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  350. package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
  351. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
  352. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  353. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  354. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  355. package/src/__tests__/gpu-scene.unit.test.ts +559 -33
  356. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  357. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  358. package/src/__tests__/instances-store.unit.test.ts +242 -39
  359. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  360. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  361. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  362. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  363. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  364. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  365. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  366. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  367. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  368. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  369. package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
  370. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  371. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  372. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  373. package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
  374. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  375. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  376. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  377. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  378. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
  379. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  380. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  381. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  382. package/src/__tests__/public-surface.test-d.ts +3 -0
  383. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  384. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
  385. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
  386. package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
  387. package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
  388. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  389. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  390. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  391. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  392. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  393. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  394. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  395. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  396. package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
  397. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  398. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  399. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  400. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  401. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +60 -0
  402. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  403. package/src/__tests__/shader-manifest-fixture.ts +39 -0
  404. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  405. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  406. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  407. package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
  408. package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
  409. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  410. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  411. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  412. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  413. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
  414. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
  415. package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
  416. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  417. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  418. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  419. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  420. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  421. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  422. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  423. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  424. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  425. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  426. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  427. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  428. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  429. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  430. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  431. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  432. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  433. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  434. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  435. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  436. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  437. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  438. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
  439. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  440. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  441. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  442. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  443. package/src/__tests__/standard-output.browser.test.ts +15 -0
  444. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  445. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
  446. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  447. package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
  448. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  449. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  450. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  451. package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
  452. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  453. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
  454. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  455. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  456. package/src/__tests__/taa-stability-feedback.ts +341 -0
  457. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  458. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  459. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  460. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  461. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  462. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  463. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  464. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  465. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  466. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  467. package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
  468. package/src/assembly/dynamic-geometry-host.ts +48 -0
  469. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  470. package/src/assembly/factory.ts +4 -0
  471. package/src/assembly/gpu-driven-owner.ts +29 -0
  472. package/src/assembly/host-contract.ts +10 -0
  473. package/src/assembly/material/assembly.ts +38 -0
  474. package/src/assembly/material-shader-policy.ts +71 -28
  475. package/src/assembly/recovery/failure-location.ts +62 -0
  476. package/src/assembly/recovery/renderer-recover.ts +19 -17
  477. package/src/assembly/renderer-facade.ts +12 -0
  478. package/src/assembly/renderer-feature-inspection.ts +13 -0
  479. package/src/assembly/renderer-frame-transaction.ts +63 -0
  480. package/src/assembly/renderer-helpers.ts +26 -0
  481. package/src/assembly/renderer-inspection.ts +6 -0
  482. package/src/assembly/shader-prewarm-policy.ts +32 -0
  483. package/src/assembly/skin-palette-owner.ts +21 -0
  484. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  485. package/src/assembly/webgpu-device-loss.ts +75 -0
  486. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  487. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  488. package/src/assembly/webgpu-ready.ts +142 -121
  489. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  490. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  491. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  492. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  493. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  494. package/src/assembly/webgpu-renderer.ts +666 -652
  495. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  496. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  497. package/src/components/camera.ts +287 -7
  498. package/src/components/index.ts +1 -0
  499. package/src/components/instances.ts +45 -18
  500. package/src/components/scene-instance.ts +2 -1
  501. package/src/components/screen-space-reflection.ts +21 -0
  502. package/src/components/sprite-animation.ts +2 -2
  503. package/src/components/sprite-instances.ts +1 -1
  504. package/src/device/gpu-residency.ts +34 -31
  505. package/src/environment/background.ts +263 -0
  506. package/src/errors/gpu-driven.ts +39 -5
  507. package/src/errors/render.ts +27 -3
  508. package/src/extract/camera.ts +26 -1
  509. package/src/extract/gpu-driven.ts +185 -10
  510. package/src/features/host.ts +237 -19
  511. package/src/features/noise-texture.ts +91 -0
  512. package/src/features/plan.ts +553 -24
  513. package/src/features/prepared-gpu-work.ts +631 -49
  514. package/src/features/prepared-graphics-store.ts +31 -0
  515. package/src/features/prepared-graphics.ts +5 -0
  516. package/src/features/render-graph-compute.ts +41 -8
  517. package/src/features/render-graph-contribution.ts +141 -3
  518. package/src/features/render-graph-raster.ts +143 -30
  519. package/src/features/types.ts +11 -0
  520. package/src/fullscreen-post-process-pass.ts +3 -0
  521. package/src/gpu-driven/batch-topology.ts +424 -102
  522. package/src/gpu-driven/pbr-program.ts +31 -0
  523. package/src/gpu-driven/prepared-draw.ts +29 -4
  524. package/src/gpu-driven/production-raster.ts +2267 -694
  525. package/src/gpu-driven/shadow-views.ts +539 -0
  526. package/src/gpu-driven/view-gpu.ts +188 -41
  527. package/src/gpu-scene-schema.ts +52 -11
  528. package/src/gpu-scene.ts +186 -99
  529. package/src/gpu-texture-usage.ts +1 -0
  530. package/src/hdrp-buffers.ts +72 -36
  531. package/src/ibl/frame-resources.ts +38 -0
  532. package/src/ibl/skylight-bind-group.ts +5 -4
  533. package/src/index.ts +109 -1
  534. package/src/inspection-types.ts +191 -0
  535. package/src/instances-derived-bounds.ts +258 -34
  536. package/src/instances.ts +459 -150
  537. package/src/internal.ts +22 -0
  538. package/src/lifecycle.ts +2 -0
  539. package/src/material-row.ts +145 -0
  540. package/src/materials.ts +44 -8
  541. package/src/pbr-pipeline.ts +159 -17
  542. package/src/pipeline/standard-forward-lane.ts +52 -24
  543. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  544. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  545. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  546. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  547. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  548. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  549. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  550. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  551. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  552. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  553. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  554. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  555. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  556. package/src/pipeline/standard-output/color-transform.ts +124 -0
  557. package/src/pipeline/standard-output/graph.ts +139 -0
  558. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  559. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  560. package/src/pipeline/standard-output/lut-state.ts +176 -0
  561. package/src/pipeline/standard-output/resources.ts +121 -0
  562. package/src/pipeline/standard-output/types.ts +58 -0
  563. package/src/pipeline/standard-pipeline.ts +192 -59
  564. package/src/pipeline/standard-post.ts +186 -27
  565. package/src/pipeline-builder.ts +16 -6
  566. package/src/pipeline-spec-types.ts +4 -0
  567. package/src/pipeline-spec.ts +37 -29
  568. package/src/points-lines/standard-owner.ts +6 -14
  569. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  570. package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
  571. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  572. package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
  573. package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
  574. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  575. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  576. package/src/record/dynamic-geometry-consumption.ts +59 -0
  577. package/src/record/frame-lighting.ts +12 -11
  578. package/src/record/frame-snapshot.ts +133 -23
  579. package/src/record/frame-targets.ts +9 -0
  580. package/src/record/frame.ts +725 -133
  581. package/src/record/main-pass-geometry.ts +149 -94
  582. package/src/record/main-pass-material.ts +115 -26
  583. package/src/record/main-pass-sprite-draws.ts +16 -107
  584. package/src/record/main-pass.ts +78 -8
  585. package/src/record/material-uniforms.ts +1 -1
  586. package/src/record/mesh-ssbo.ts +59 -15
  587. package/src/record/prepared-material-bindings.ts +234 -0
  588. package/src/record/recovery-pipeline.ts +59 -136
  589. package/src/record/render-context.ts +90 -22
  590. package/src/record/shadow-pass.ts +1231 -209
  591. package/src/record/skybox-post-pass.ts +14 -3
  592. package/src/record/typed-frame-graph.ts +1004 -44
  593. package/src/record/view-ubo.ts +11 -0
  594. package/src/recovery/render-system-candidate.ts +90 -66
  595. package/src/recovery/render-system-roots.ts +113 -0
  596. package/src/recovery/types.ts +77 -0
  597. package/src/reflection/record-owner.ts +26 -54
  598. package/src/render-contract.ts +195 -25
  599. package/src/render-graph-primitives.ts +6 -2
  600. package/src/render-pipeline.ts +70 -1
  601. package/src/render-system-extract-tail.ts +409 -160
  602. package/src/render-system-extract.ts +376 -102
  603. package/src/render-system-projections.ts +241 -0
  604. package/src/render-system.ts +681 -822
  605. package/src/scene/render-scene-types.ts +13 -4
  606. package/src/scene/render-scene.ts +569 -355
  607. package/src/scene/shadow-visibility.ts +21 -0
  608. package/src/ssr/admission.ts +380 -14
  609. package/src/ssr/compose.ts +144 -0
  610. package/src/ssr/errors.ts +48 -0
  611. package/src/ssr/graph.ts +777 -0
  612. package/src/ssr/history.ts +436 -0
  613. package/src/ssr/hiz.ts +124 -0
  614. package/src/ssr/inspection.ts +124 -0
  615. package/src/ssr/resources.ts +202 -0
  616. package/src/ssr/temporal.ts +164 -0
  617. package/src/systems/skin-palette-allocator.ts +495 -148
  618. package/src/systems/skin-palette-types.ts +17 -0
  619. package/src/systems/transparent-dispatch.ts +90 -0
  620. package/src/targets/material-source.ts +34 -0
  621. package/src/temporal/coverage.ts +1 -1
  622. package/src/temporal/frame-coordinator.ts +23 -2
  623. package/src/temporal/gpu.ts +74 -12
  624. package/src/temporal/history.ts +3 -1
  625. package/src/temporal/inspection.ts +2 -1
  626. package/src/temporal/standard-scene-data.ts +3 -1
  627. package/src/temporal/temporal-view.ts +3 -0
  628. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
  629. package/src/typed-render-graph-primitives.ts +266 -74
  630. package/src/typed-shadow-passes.ts +122 -12
  631. package/src/volume/temporal.ts +10 -0
  632. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  633. package/dist/chunk-GF523LHF.mjs.map +0 -1
  634. package/dist/chunk-IQMKJLNU.mjs.map +0 -1
  635. package/dist/chunk-J52NMQKR.mjs.map +0 -1
  636. package/dist/chunk-J6KACNOH.mjs.map +0 -1
  637. package/dist/chunk-JAASCMXA.mjs.map +0 -1
  638. package/dist/chunk-MBU4ZVE7.mjs.map +0 -1
  639. package/dist/chunk-RBDBZ5R7.mjs.map +0 -1
  640. package/dist/chunk-VSL23LZE.mjs.map +0 -1
  641. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  642. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
@@ -1,3 +1,4 @@
1
+ import type { RenderSceneBounds } from './scene/render-scene-types';
1
2
  // @forgeax/engine-runtime - RenderSystem main entry (D-S2 three-stage Extract ->
2
3
  // Prepare -> Record + 4-tier error fan-out).
3
4
  //
@@ -16,31 +17,17 @@
16
17
  // .invert` (charter proposition 5: no math reinvention; render-system.test.ts
17
18
  // asserts `/@forgeax\/engine-math/` shows up in render-system.ts source).
18
19
 
19
- import {
20
- type MipmapEncoderWork,
21
- numMipLevels,
22
- resolveAssetHandle,
23
- } from '@forgeax/engine-assets-runtime';
24
- import type { World } from '@forgeax/engine-ecs';
20
+ import { numMipLevels, resolveAssetHandle } from '@forgeax/engine-assets-runtime';
21
+ import { Time, type World } from '@forgeax/engine-ecs';
25
22
  import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
26
23
  import type { RecorderSession } from '@forgeax/engine-profiler';
27
24
  import type { CompiledRenderGraphInfo } from '@forgeax/engine-render-graph';
28
- import type {
29
- BindGroupEntry,
30
- BindGroupLayout,
31
- Buffer,
32
- RenderPipeline,
33
- RhiDevice,
34
- } from '@forgeax/engine-rhi';
25
+ import type { BindGroupLayout, Buffer, RenderPipeline, RhiDevice } from '@forgeax/engine-rhi';
35
26
  import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
36
27
  import {
37
- derive,
38
28
  deriveTextureLayout,
39
- type Handle,
40
29
  type MaterialAsset,
41
- type MaterialTextureValue,
42
30
  type RenderPipelineAsset,
43
- RenderQueue,
44
31
  type SamplerAsset,
45
32
  type TextureAsset,
46
33
  toShared,
@@ -50,9 +37,8 @@ import {
50
37
  directionalShadowProfileFromQuality,
51
38
  projectDirectionalShadowInspectionSource,
52
39
  } from './assembly/directional-shadow-inspection';
40
+ import { createGpuDrivenOwner } from './assembly/gpu-driven-owner';
53
41
  import { createClusterBinScratch } from './cluster-binner';
54
- import type { DeviceScope, LifecycleResourceSpec } from './device/device-scope';
55
- import type { EnvironmentGeneration } from './environment/generation';
56
42
  import { EnvironmentLifecycle } from './environment/lifecycle';
57
43
  import {
58
44
  type ObservationUnavailableError,
@@ -60,11 +46,11 @@ import {
60
46
  TransmissionCapabilityMissingError,
61
47
  } from './errors/render';
62
48
  import {
63
- type RenderFeatureHost,
64
49
  type RenderFeaturePreparedGraphicsResolverInput,
65
50
  runRenderFeatureFrame,
66
51
  settlePreparedGraphicsCompletion,
67
52
  } from './features/host';
53
+ import { createFeatureNoiseResolver } from './features/noise-texture';
68
54
  import {
69
55
  createRenderFeatureGpuWorkOwner,
70
56
  type RenderFeatureGpuWorkOwner,
@@ -79,22 +65,45 @@ import {
79
65
  postProcessShaderModuleLabel,
80
66
  postProcessShaderPipelineLabel,
81
67
  } from './fullscreen-post-process-pass';
82
- import { GpuDrivenProduction } from './gpu-driven/production-raster';
83
68
  import type {
84
69
  BloomInspection,
85
70
  DirectionalShadowInspection,
86
71
  GpuDrivenProductionInspection,
87
72
  LodOcclusionInspection,
88
- LodOcclusionInspectionRow,
89
- LodOcclusionWorldInspection,
73
+ MaterialTextureSourceInspection,
90
74
  MotionBlurInspection,
91
75
  ReflectionProbeInspection,
92
76
  RenderSceneInspection,
93
77
  SsrDependenciesInspection,
78
+ SsrSpatialInspection,
94
79
  TemporalTargetInspection,
95
80
  TransmissionInspection,
96
81
  } from './inspection-types';
97
82
  import { buildOcclusionProxyVertices } from './occlusion-proxy';
83
+ import {
84
+ AUTO_EXPOSURE_HISTOGRAM_BYTES,
85
+ retireAutoExposureGpuResources,
86
+ writeAutoExposureParameters,
87
+ } from './pipeline/standard-output/auto-exposure/gpu';
88
+ import {
89
+ type AutoExposureInspection,
90
+ createAutoExposureInspection,
91
+ } from './pipeline/standard-output/auto-exposure/inspection';
92
+ import {
93
+ createAutoExposureState,
94
+ resetAutoExposureState,
95
+ } from './pipeline/standard-output/auto-exposure/state';
96
+ import {
97
+ createStandardLutState,
98
+ inspectStandardLutState,
99
+ prepareStandardLutCandidate,
100
+ resetStandardLutState,
101
+ type StandardLutInspection,
102
+ } from './pipeline/standard-output/lut-state';
103
+ import {
104
+ prepareStandardOutputResources,
105
+ resetStandardOutputForDeviceLoss,
106
+ } from './pipeline/standard-output/resources';
98
107
  import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
99
108
  import { StandardPointsLinesOwner } from './points-lines/standard-owner';
100
109
  import {
@@ -102,6 +111,35 @@ import {
102
111
  materialSamplerHandles,
103
112
  materialTextureHandles,
104
113
  } from './recovery/render-system-candidate';
114
+
115
+ export { materialTextureHandlesForResidency } from './recovery/render-system-candidate';
116
+
117
+ import {
118
+ createDynamicGeometryFrameBindings,
119
+ resetDynamicGeometryTemporalHistory,
120
+ } from './record/dynamic-geometry-consumption';
121
+ import type {
122
+ RecoveryGraphCandidate,
123
+ RecoveryGraphCandidatePreparation,
124
+ RecoveryGraphCandidateRuntime,
125
+ RecoveryGraphSetupSubmission,
126
+ RecoveryPostProcessResources,
127
+ RecoveryRootBundle,
128
+ RecoveryRootRuntime,
129
+ } from './recovery/types';
130
+
131
+ export type {
132
+ RecoveryGraphCandidate,
133
+ RecoveryGraphCandidatePreparation,
134
+ RecoveryGraphCandidateRuntime,
135
+ RecoveryGraphSetupSubmission,
136
+ RecoveryPointsLinesCandidate,
137
+ RecoveryPostProcessResources,
138
+ RecoveryRootBundle,
139
+ RecoveryRootRuntime,
140
+ } from './recovery/types';
141
+
142
+ import { prepareRenderSystemRecoveryRoots } from './recovery/render-system-roots';
105
143
  import { ReflectionProbeRecordOwner } from './reflection/record-owner';
106
144
  import {
107
145
  OcclusionRenderRuntime,
@@ -113,18 +151,18 @@ import { inspectVolumetricFog } from './volume/inspection';
113
151
  export type { RenderSceneInspection } from './inspection-types';
114
152
 
115
153
  import { createCubeCaptureScheduler } from './capture/scheduler';
116
- import type { GpuResidencyCache } from './device/gpu-residency';
117
154
  import type { EnvironmentInspection } from './environment/inspection';
155
+ import { gpuDrivenShadowDrawKey } from './extract/gpu-driven';
118
156
  import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from './gpu-usage';
119
157
  import { resetHdrpBuffers } from './hdrp-buffers';
120
- import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
158
+ import type { SkylightBindGroupResources } from './ibl/skylight-bind-group';
121
159
  import {
122
160
  disposeInstanceBufferChunks,
123
161
  disposeInstanceBuffers,
124
162
  disposeTransientInstanceBuffers,
125
163
  } from './instance-buffer-cache';
126
164
  import type { InstanceCollectionInspection } from './instances';
127
- import { InstanceProjectionStore } from './instances';
165
+ import { InstanceCollectionStore } from './instances';
128
166
  import {
129
167
  type MeshMaterialBindingObservation,
130
168
  type MeshMaterialBindingPreparationFailure,
@@ -163,12 +201,18 @@ import type {
163
201
  GpuPassTimingSession,
164
202
  } from './record/gpu-pass-timing/session';
165
203
  import type { GpuTimingCapture } from './record/gpu-timing';
166
- import { applyParamSnapshotToUbo, residentTextureView } from './record/main-pass-material';
167
- import type { RecoveryPipelineReadiness } from './record/recovery-pipeline';
204
+ import { resolveMaterialSkylight } from './record/main-pass-material';
205
+ import {
206
+ createPreparedResolverCaches,
207
+ type PreparedResolverCaches,
208
+ preparedMaterialBindings,
209
+ } from './record/prepared-material-bindings';
168
210
  import type { PipelineState, RenderSystemInternals } from './record/render-context';
169
211
  import type { CubeCaptureGraphState } from './record/typed-frame-graph';
170
212
  import {
213
+ getRenderFeatureGraphInspection,
171
214
  type RenderFeatureGraphCandidate,
215
+ recordRenderFeatureCandidate,
172
216
  resetRenderFeatureGraphState,
173
217
  settleVolumetricFogGraphCandidate,
174
218
  } from './record/typed-frame-graph';
@@ -182,27 +226,27 @@ import {
182
226
  } from './render-contract';
183
227
  import type {
184
228
  DispatchEntry,
185
- ExtractedFrame,
186
229
  ExtractedLights,
187
230
  MaterialSnapshot,
188
231
  RenderableSnapshot,
232
+ ShadowCasterMembership,
189
233
  } from './render-system-extract';
190
234
  import { extractFrames } from './render-system-extract';
191
235
  import {
192
- type PersistentGpuDrivenCandidate,
193
- type PersistentRenderCandidateRequest,
194
- PersistentRenderScene,
195
- } from './scene/render-scene';
236
+ canonicalizeWorldComposition,
237
+ createLodWorldInspections,
238
+ } from './render-system-projections';
239
+ import { PersistentRenderScene } from './scene/render-scene';
196
240
  import { inspectLodOcclusion } from './scene/visibility/inspection';
197
241
  import { resolveSsaoParameters } from './ssao-config';
198
- import { projectSsrDependencies, resolveSsrAdmissionGeneration } from './ssr/admission';
199
242
  import {
200
- getTransparentSortConfig,
201
- TRANSPARENT_SORT_MODE_DISTANCE,
202
- TRANSPARENT_SORT_MODE_LAYER_Y,
203
- TRANSPARENT_SORT_MODE_LAYER_YZ,
204
- TRANSPARENT_SORT_MODE_LAYER_Z,
205
- } from './systems/transparent-sort-config';
243
+ projectSsrDependencies,
244
+ resolveSsrAdmissionGeneration,
245
+ type SsrSpatialAdmission,
246
+ zeroSsrAdmissionWork,
247
+ } from './ssr/admission';
248
+ import { projectSsrSpatialInspection } from './ssr/inspection';
249
+ import { sortTransparentDispatch } from './systems/transparent-dispatch';
206
250
  import type { RenderTarget } from './targets/contracts';
207
251
  import { createTemporalFrameTransaction, type TemporalFrame } from './temporal/frame';
208
252
  import { retireTemporalGpuState } from './temporal/gpu';
@@ -219,147 +263,71 @@ import {
219
263
  inspectTransmissionFromAdmission,
220
264
  } from './transmission/inspection';
221
265
 
222
- function canonicalizeWorldComposition(
266
+ const WORLD_ENTITY_KEY_STRIDE = 4294967296;
267
+
268
+ /**
269
+ * Extract emits world/entity keys in the transient `worlds[]` index domain,
270
+ * while PersistentRenderScene owns stable renderer-local World keys for the
271
+ * retained GPU scene. Rebase ShadowCaster ownership at this orchestration
272
+ * boundary before both GPU projection and CPU residual recording consume it.
273
+ */
274
+ function rebaseShadowCasterOwnership(
223
275
  worlds: readonly World[],
224
- owners: Pick<DrawOwnerOptions, 'cameraOwner' | 'resourceOwner'>,
225
- leases: readonly RenderReadLease[] | undefined,
276
+ stableWorldKeys: readonly number[],
277
+ entityKeys: ReadonlySet<number>,
278
+ drawKeys: ReadonlySet<string>,
279
+ membership: readonly ShadowCasterMembership[] | undefined,
226
280
  ): {
227
- readonly worlds: readonly World[];
228
- readonly owners: Pick<DrawOwnerOptions, 'cameraOwner' | 'resourceOwner'>;
229
- readonly leases: readonly RenderReadLease[] | undefined;
281
+ readonly entityKeys: ReadonlySet<number>;
282
+ readonly drawKeys: ReadonlySet<string>;
283
+ readonly membership: readonly ShadowCasterMembership[] | undefined;
230
284
  } {
231
- const order = worlds
232
- .map((world, index) => ({ world, index }))
233
- .sort((left, right) => left.world.identity.localeCompare(right.world.identity));
234
- const indexByOriginal = new Map(order.map((entry, index) => [entry.index, index]));
235
- return {
236
- worlds: Object.freeze(order.map((entry) => entry.world)),
237
- owners: {
238
- cameraOwner: indexByOriginal.get(owners.cameraOwner) ?? owners.cameraOwner,
239
- resourceOwner: indexByOriginal.get(owners.resourceOwner) ?? owners.resourceOwner,
240
- },
241
- leases:
242
- leases === undefined
243
- ? undefined
244
- : Object.freeze(
245
- order.map((entry) => leases[entry.index]).filter((lease) => lease !== undefined),
246
- ),
285
+ const rebaseWorldEntity = (composite: number): number => {
286
+ const transientWorldId = Math.floor(composite / WORLD_ENTITY_KEY_STRIDE);
287
+ if (
288
+ !Number.isInteger(transientWorldId) ||
289
+ transientWorldId < 0 ||
290
+ transientWorldId >= worlds.length
291
+ ) {
292
+ return composite;
293
+ }
294
+ const entityKey = composite - transientWorldId * WORLD_ENTITY_KEY_STRIDE;
295
+ return worldEntityKey(stableWorldKeys[transientWorldId] ?? transientWorldId, entityKey);
247
296
  };
248
- }
249
297
 
250
- interface LodInspectionQuerySummary {
251
- readonly used: number;
252
- readonly capacity: number;
253
- }
254
-
255
- function createLodWorldInspections(
256
- worlds: readonly World[],
257
- renderables: readonly RenderableSnapshot[],
258
- submittedRenderables: readonly RenderableSnapshot[],
259
- camera: CameraSnapshot,
260
- frameId: number,
261
- worldKeys: readonly number[],
262
- slots: ReadonlyMap<number, { readonly slot: number; readonly generation: number }>,
263
- query: LodInspectionQuerySummary,
264
- fallback: LodOcclusionInspection['fallback'],
265
- degradation: LodOcclusionInspection['degradation'],
266
- ): readonly LodOcclusionWorldInspection[] {
267
- // The CPU projection knows which renderables belong to each World, but it
268
- // does not receive the GPU selector/query counters split by World from the
269
- // same submit. Keep these rows available for diagnostics while explicitly
270
- // marking their attribution unavailable; producers must not promote them
271
- // to World-reorder evidence by inferring facts from array order.
272
- const attribution = Object.freeze({
273
- status: 'unavailable' as const,
274
- reason: 'projection-only' as const,
275
- });
276
- const submitted = new Set(submittedRenderables);
277
- const sampleLimit = 64;
278
- const accumulators = worlds.map(() => ({
279
- allCount: 0,
280
- allVisible: 0,
281
- allSamples: [] as RenderableSnapshot[],
282
- lodCount: 0,
283
- lodVisible: 0,
284
- lodSamples: [] as RenderableSnapshot[],
285
- }));
286
- // Keep this projection bounded: the old implementation filtered the full
287
- // 100k renderable list twice per World on every frame and retained another
288
- // Set-backed traversal just to derive the six counters below. One pass is
289
- // enough because the inspection only needs counts, the first row identity,
290
- // and at most 64 detached samples.
291
- for (const renderable of renderables) {
292
- const accumulator = accumulators[renderable.worldId];
293
- if (accumulator === undefined) continue;
294
- const visible = submitted.has(renderable);
295
- accumulator.allCount += 1;
296
- if (visible) accumulator.allVisible += 1;
297
- if (accumulator.allSamples.length < sampleLimit) accumulator.allSamples.push(renderable);
298
- if ((renderable.lods?.length ?? 0) > 0) {
299
- accumulator.lodCount += 1;
300
- if (visible) accumulator.lodVisible += 1;
301
- if (accumulator.lodSamples.length < sampleLimit) accumulator.lodSamples.push(renderable);
302
- }
303
- }
304
- const result: LodOcclusionWorldInspection[] = [];
305
- for (let worldId = 0; worldId < worlds.length; worldId += 1) {
306
- const world = worlds[worldId];
307
- if (world === undefined) continue;
308
- const accumulator = accumulators[worldId];
309
- if (accumulator === undefined || accumulator.allCount === 0) {
310
- result.push(
311
- Object.freeze({ attachmentId: world.identity, rows: Object.freeze([]), attribution }),
312
- );
313
- continue;
314
- }
315
- const usesLod = accumulator.lodCount > 0;
316
- const candidateCount = usesLod ? accumulator.lodCount : accumulator.allCount;
317
- const visible = usesLod ? accumulator.lodVisible : accumulator.allVisible;
318
- const samples = usesLod ? accumulator.lodSamples : accumulator.allSamples;
319
- const first = samples[0];
320
- const slot =
321
- first === undefined
322
- ? undefined
323
- : slots.get(worldEntityKey(worldKeys[first.worldId] ?? first.worldId, first.entityKey));
324
- const row: LodOcclusionInspectionRow = {
325
- root: {
326
- guid: first === undefined ? 'none' : `asset-handle:${first.assetHandle}`,
327
- sourceKey: `render-world:${world.identity}`,
328
- },
329
- view: {
330
- attachmentId: world.identity,
331
- cameraEntity: world === worlds[camera.worldId ?? 0] ? (camera.entityKey ?? 0) : 0,
332
- viewRole: 'main',
333
- viewGeneration: camera.historyVersion ?? 0,
334
- },
335
- slot: { primitiveSlot: slot?.slot ?? 0, slotGeneration: slot?.generation ?? 0 },
336
- generation: frameId,
337
- count: { candidates: candidateCount, visible, occluded: candidateCount - visible },
338
- lodHistogram: [{ level: 0, count: candidateCount }],
339
- queryLatencyUs: { median: 0, p95: 0, last: 0 },
340
- pagePressure: query,
341
- fallback,
342
- degradation,
343
- samples: samples.map((candidate, index) => ({
344
- primitiveSlot:
345
- slots.get(
346
- worldEntityKey(worldKeys[candidate.worldId] ?? candidate.worldId, candidate.entityKey),
347
- )?.slot ?? index,
348
- level: 0,
349
- visible: submitted.has(candidate),
350
- })),
351
- };
352
- result.push(
353
- Object.freeze({
354
- attachmentId: world.identity,
355
- rows: Object.freeze([row]),
356
- attribution,
357
- }),
358
- );
359
- }
360
- return Object.freeze(
361
- result.sort((left, right) => left.attachmentId.localeCompare(right.attachmentId)),
362
- );
298
+ const stableMembership =
299
+ membership === undefined
300
+ ? undefined
301
+ : membership.map((entry) => ({
302
+ ...entry,
303
+ worldEntity: rebaseWorldEntity(entry.worldEntity),
304
+ }));
305
+ const stableDrawKeys =
306
+ stableMembership === undefined
307
+ ? new Set(
308
+ [...drawKeys].map((key) => {
309
+ const parts = key.split(':');
310
+ if (parts.length !== 4) return key;
311
+ const transientWorldEntity = Number(parts[0]);
312
+ if (!Number.isFinite(transientWorldEntity)) return key;
313
+ return `${rebaseWorldEntity(transientWorldEntity)}:${parts[1]}:${parts[2]}:${parts[3]}`;
314
+ }),
315
+ )
316
+ : new Set(
317
+ stableMembership.map((entry) =>
318
+ gpuDrivenShadowDrawKey(
319
+ entry.worldEntity,
320
+ entry.materialHandle,
321
+ entry.drawItemIndex,
322
+ entry.passIndex,
323
+ ),
324
+ ),
325
+ );
326
+ return {
327
+ entityKeys: new Set([...entityKeys].map(rebaseWorldEntity)),
328
+ drawKeys: stableDrawKeys,
329
+ membership: stableMembership,
330
+ };
363
331
  }
364
332
 
365
333
  type TimingSessionHost = RenderSystemInternals & {
@@ -418,12 +386,6 @@ export interface RendererProducerRoot {
418
386
  readonly disabledWork: 'zero' | 'lazy';
419
387
  }
420
388
 
421
- /** Device-bound post-process resources prepared off to the side of the active renderer. */
422
- export interface RecoveryPostProcessResources {
423
- readonly device: RhiDevice;
424
- readonly paramsBuffers: Map<string, Buffer>;
425
- }
426
-
427
389
  const PRODUCER_ROOT_MATRIX: readonly RendererProducerRoot[] = Object.freeze([
428
390
  {
429
391
  kind: 'backend-surface',
@@ -520,129 +482,6 @@ export interface RecoveryProductionEvidence {
520
482
  };
521
483
  }
522
484
 
523
- /**
524
- * Unified transparent-queue sub-sort covering all four
525
- * {@link TransparentSortConfig} modes. Reorders the
526
- * `queue === RenderQueue.Transparent` segment of the dispatch list;
527
- * all other queue segments keep their relative order.
528
- *
529
- * | mode | primary key | secondary key | tertiary key |
530
- * |:--:|:--|:--|:--|
531
- * | 0 (LAYER_Z) | `layer` ASC | `posZ` ASC | `materialHandle` ASC |
532
- * | 1 (LAYER_Y) | `layer` ASC | `-(posY - pivotY * sizeY)` ASC | `materialHandle` ASC |
533
- * | 2 (LAYER_YZ) | `layer` ASC | `(posY - pivotY * sizeY) + yzAlpha * posZ` ASC | `materialHandle` ASC |
534
- * | 3 (DISTANCE) | `-(dist² from camera)` ASC (back-to-front, layer ignored) | — |
535
- *
536
- * The `materialHandle` tertiary key for modes 0/1/2 groups same-material
537
- * entries together whenever the primary+secondary sort values are equal
538
- * (e.g. tilemap tiles in the same row/layer share `posY` in LAYER_Y mode).
539
- * Consecutive same-material groups then collapse into fold buckets in the
540
- * record-stage fold operator, significantly reducing draw call count for
541
- * tilemap-heavy scenes.
542
- *
543
- * `posX/Y/Z` = translation column of the entity's world mat4 (indices 12/13/14).
544
- * `pivotY` = `RenderableSnapshot.material.paramSnapshot.pivotAndSize[1]` (default 0.5).
545
- * `sizeY` = length of the Y-axis column of the world mat4 (indices 4/5/6).
546
- */
547
- function sortTransparentDispatch(
548
- dispatch: DispatchEntry[],
549
- world: World,
550
- cameras: readonly CameraSnapshot[],
551
- renderables: readonly RenderableSnapshot[],
552
- ): DispatchEntry[] {
553
- const cfg = getTransparentSortConfig(world);
554
- const mode = cfg.mode;
555
-
556
- // Indices of Transparent-queue entries within the dispatch list.
557
- const transparentSlots: number[] = [];
558
- for (let i = 0; i < dispatch.length; i++) {
559
- if (dispatch[i]?.queue === RenderQueue.Transparent) transparentSlots.push(i);
560
- }
561
- if (transparentSlots.length <= 1) return dispatch;
562
-
563
- let sortedSlotOrder: number[];
564
-
565
- if (mode === TRANSPARENT_SORT_MODE_DISTANCE) {
566
- const camera = cameras[0];
567
- if (camera === undefined) return dispatch;
568
- const camPos = camera.position;
569
-
570
- // Squared camera distance per entry (negated → ascending = back-to-front).
571
- // D-3: world position = translation column of GlobalTransform.world (m[12,13,14]).
572
- const negDistSq = (entry: DispatchEntry): number => {
573
- const tx = renderables[entry.renderableIndex]?.transform;
574
- if (tx === undefined) return 0;
575
- const w = tx.world;
576
- const dx = (w[12] ?? 0) - (camPos[0] ?? 0);
577
- const dy = (w[13] ?? 0) - (camPos[1] ?? 0);
578
- const dz = (w[14] ?? 0) - (camPos[2] ?? 0);
579
- return -(dx * dx + dy * dy + dz * dz);
580
- };
581
-
582
- sortedSlotOrder = transparentSlots.slice().sort((a, b) => {
583
- const da = negDistSq(dispatch[a] as DispatchEntry);
584
- const db = negDistSq(dispatch[b] as DispatchEntry);
585
- if (da < db) return -1;
586
- if (da > db) return 1;
587
- return 0;
588
- });
589
- } else {
590
- // Modes 0/1/2: primary = layer ASC, secondary = mode-formula ASC.
591
- // sizeY = length of the Y-axis column (col1 = indices 4,5,6) of the
592
- // world mat4; rotation-invariant and handles flipV sign correctly.
593
- const sortVal = (entry: DispatchEntry): number => {
594
- const tx = renderables[entry.renderableIndex]?.transform;
595
- const w = tx?.world;
596
- const posY = (w?.[13] ?? 0) as number;
597
- const posZ = (w?.[14] ?? 0) as number;
598
- if (mode === TRANSPARENT_SORT_MODE_LAYER_Z) return posZ;
599
- const mat = renderables[entry.renderableIndex]?.material;
600
- // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w15: post
601
- // SpriteFieldsSnapshot ablation, pivot lives in paramSnapshot
602
- // .pivotAndSize[0..1] (UBO-aligned vec4 slot 2, plan D-6). Non-sprite
603
- // materials carry paramSnapshot=undefined and fall back to 0.5.
604
- const pivotAndSize = mat?.paramSnapshot?.pivotAndSize as readonly number[] | undefined;
605
- const pivotY = (pivotAndSize?.[1] ?? 0.5) as number;
606
- const wy4 = (w?.[4] ?? 0) as number;
607
- const wy5 = (w?.[5] ?? 1) as number;
608
- const wy6 = (w?.[6] ?? 0) as number;
609
- const sizeY = Math.sqrt(wy4 * wy4 + wy5 * wy5 + wy6 * wy6);
610
- const footY = posY - pivotY * sizeY;
611
- if (mode === TRANSPARENT_SORT_MODE_LAYER_Y) return -footY;
612
- if (mode === TRANSPARENT_SORT_MODE_LAYER_YZ) return footY + cfg.yzAlpha * posZ;
613
- // Defensive fallback for an unknown mode that slips past setTransparentSortConfig.
614
- return posZ;
615
- };
616
-
617
- sortedSlotOrder = transparentSlots.slice().sort((a, b) => {
618
- const da = dispatch[a] as DispatchEntry;
619
- const db = dispatch[b] as DispatchEntry;
620
- const la = da.layer;
621
- const lb = db.layer;
622
- if (la !== lb) return la - lb;
623
- const va = sortVal(da);
624
- const vb = sortVal(db);
625
- if (va < vb) return -1;
626
- if (va > vb) return 1;
627
- // Tertiary tiebreaker: group same-materialHandle entries together so
628
- // fold-eligible consecutive runs form. Entries at equal (layer,
629
- // sortVal) — e.g. tilemap tiles in the same row under LAYER_Y — are
630
- // depth-equivalent; reordering them by material does not change the
631
- // visual result but maximises fold-bucket width.
632
- return da.materialHandle - db.materialHandle;
633
- });
634
- }
635
-
636
- // Scatter the reordered Transparent entries back into their original slots.
637
- const result = dispatch.slice();
638
- for (let k = 0; k < transparentSlots.length; k++) {
639
- const targetSlot = transparentSlots[k] as number;
640
- const sourceSlot = sortedSlotOrder[k] as number;
641
- result[targetSlot] = dispatch[sourceSlot] as DispatchEntry;
642
- }
643
- return result;
644
- }
645
-
646
485
  /**
647
486
  * Engine-internal Extract / Prepare / Record driver; constructed by createRenderer.
648
487
  *
@@ -660,6 +499,9 @@ function sortTransparentDispatch(
660
499
  * whose shader identity is `forgeax::default-standard-pbr`).
661
500
  */
662
501
  export interface RenderSystem {
502
+ bounds(world: World, entity: number): RenderSceneBounds | undefined;
503
+ /** Complete explicit SSR capability preparation before drawing a device generation. */
504
+ initializeSsr(device?: RhiDevice): Promise<void>;
663
505
  /** Returns true only when this invocation reached queue submission. */
664
506
  draw(
665
507
  worlds: readonly World[],
@@ -677,7 +519,7 @@ export interface RenderSystem {
677
519
  /** Prepare a detached graph candidate without entering the frame record path. */
678
520
  prepareRecoveryGraphCandidate(
679
521
  runtime: RecoveryGraphCandidateRuntime,
680
- ): Promise<RecoveryGraphCandidatePreparation>;
522
+ ): RecoveryGraphCandidatePreparation;
681
523
  /** Finish and submit detached candidate setup, never a frame receipt. */
682
524
  submitCandidateSetup(
683
525
  candidate: RecoveryGraphCandidate,
@@ -703,6 +545,8 @@ export interface RenderSystem {
703
545
  readonly pipelineDispatchCounts: {
704
546
  readonly unlit: number;
705
547
  };
548
+ /** Renderer-owned source for explicit large instance collections. */
549
+ readonly instances: InstanceCollectionStore;
706
550
  /** Detached residency and upload facts for explicit instance collections. */
707
551
  readonly instanceCollectionsInspection: readonly InstanceCollectionInspection[];
708
552
  /**
@@ -717,11 +561,17 @@ export interface RenderSystem {
717
561
  readonly reflectionProbes: ReflectionProbeInspection;
718
562
  /** Live producer/RHI/temporal receipts projected through SSR admission. */
719
563
  readonly ssrDependencies: SsrDependenciesInspection;
564
+ /** Live SSR spatial admission and bounded graph/history projection. */
565
+ readonly ssr: SsrSpatialInspection;
720
566
  /** Producer completion fence included in the public FrameReceipt. */
721
567
  readonly reflectionFallbackCompletion: Promise<void> | undefined;
722
568
  /** True while a progressive CubeCamera candidate still owns unfinished faces. */
723
569
  isCubeCapturePending(target: RenderTarget): boolean;
724
570
  readonly environment: EnvironmentInspection;
571
+ /** Detached auto-exposure state published after a successful frame submit. */
572
+ readonly autoExposure: AutoExposureInspection | undefined;
573
+ /** Detached Standard LUT state published after a successful frame submit. */
574
+ readonly standardLut: StandardLutInspection;
725
575
  readonly temporal: TemporalInspection;
726
576
  readonly bloom: BloomInspection;
727
577
  readonly motionBlurInspection: MotionBlurInspection | undefined;
@@ -756,6 +606,10 @@ export interface RenderSystem {
756
606
  readonly lastSuccessfulCameraAntialias: CameraSnapshot['antialias'] | undefined;
757
607
  /** Detached compiled-graph facts used by the renderer inspection owner. */
758
608
  readonly perFrameGraphInfo: CompiledRenderGraphInfo | undefined;
609
+ /** Detached feature signature/compile/lifetime counters. */
610
+ readonly featureGraphInspection: import('./inspection-types').RenderFeatureGraphInspection;
611
+ /** Detached material texture-source probe counters from the last extract. */
612
+ readonly materialTextureSources: MaterialTextureSourceInspection;
759
613
  /** Production-owner recovery facts used by Renderer.inspect(). */
760
614
  readonly recoveryEvidence: RecoveryProductionEvidence;
761
615
  /**
@@ -878,13 +732,6 @@ function makePreparedPipelinePendingError(): RhiError {
878
732
  });
879
733
  }
880
734
 
881
- /** The single Points/Lines owner’s staged device-generation handoff. */
882
- export interface RecoveryPointsLinesCandidate {
883
- createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown>;
884
- publish(): void;
885
- release(): void;
886
- }
887
-
888
735
  function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationFailure | undefined {
889
736
  if (typeof error !== 'object' || error === null) return undefined;
890
737
  const value = error as {
@@ -912,58 +759,6 @@ function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationF
912
759
  };
913
760
  }
914
761
 
915
- /** Candidate-only device/runtime inputs used by the recovery prepare seam. */
916
- export interface RecoveryGraphCandidateRuntime {
917
- readonly internals: RenderSystemInternals;
918
- readonly pipelineState: PipelineState;
919
- }
920
-
921
- /** Detached graph state prepared on a replacement device, before publication. */
922
- export interface RecoveryGraphCandidate {
923
- readonly frameState: RenderFrameState;
924
- readonly setupWorks: readonly MipmapEncoderWork[];
925
- readonly device: RhiDevice;
926
- readonly generation: number;
927
- readonly gpuDrivenProduction?: GpuDrivenProduction;
928
- readonly gpuDrivenScene?: PersistentGpuDrivenCandidate;
929
- /** Staged CPU-expanded Points/Lines buffers owned by the same RenderSystem. */
930
- readonly pointsLines?: RecoveryPointsLinesCandidate;
931
- readonly featureHost?: RenderFeatureHost;
932
- readonly featureGpuWork?: RenderFeatureGpuWorkOwner;
933
- readonly featureGraphCandidate?: RenderFeatureGraphCandidate;
934
- readonly postProcessPipelines?: ReadonlyMap<string, RenderPipeline>;
935
- readonly recoveryReadiness: RecoveryPipelineReadiness;
936
- /** Release all candidate roots before publication; safe to call repeatedly. */
937
- readonly release: () => void;
938
- /** Transfer candidate-root ownership to the published RenderSystem. */
939
- readonly markPublished: () => void;
940
- }
941
-
942
- /** Candidate setup completion; completion is bounded by the recovery owner. */
943
- export interface RecoveryGraphSetupSubmission {
944
- readonly completion?: PromiseLike<unknown>;
945
- }
946
-
947
- export type RecoveryGraphCandidatePreparation =
948
- | { readonly kind: 'no-seed' }
949
- | { readonly kind: 'ready'; readonly candidate: RecoveryGraphCandidate }
950
- | { readonly kind: 'failed'; readonly reason: string; readonly cause?: unknown };
951
-
952
- /** Explicit candidate-owned inputs for lifecycle-root preparation. */
953
- export interface RecoveryRootRuntime {
954
- readonly scope: DeviceScope;
955
- readonly device: RhiDevice;
956
- readonly gpuStore: GpuResidencyCache;
957
- readonly graphCandidate?: RecoveryGraphCandidate | undefined;
958
- }
959
-
960
- /** Candidate roots plus the one synchronous owner switch at publication. */
961
- export interface RecoveryRootBundle {
962
- readonly roots: readonly LifecycleResourceSpec<unknown>[];
963
- publish(): void;
964
- discard(): void;
965
- }
966
-
967
762
  /**
968
763
  * Project the producer-owned material and GPU store state into the one
969
764
  * renderer observation surface. This is intentionally recomputed from the
@@ -1088,42 +883,37 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1088
883
  let releaseProfilerCatalog =
1089
884
  phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : undefined;
1090
885
  let preparedWorlds: readonly World[] = [];
1091
- let lastSubmittedDynamicGeometryBindings = new WeakMap<World, ReadonlyMap<number, number>>();
886
+ const resolveFeatureNoise = createFeatureNoiseResolver({
887
+ scope: internals.deviceScope,
888
+ getDevice: () => internals.device,
889
+ onError: (error) => internals.errorRegistry.fire(error),
890
+ });
891
+ let preparedFeatureSkylightResources: SkylightBindGroupResources | undefined;
892
+ const dynamicGeometryFrames = createDynamicGeometryFrameBindings();
1092
893
  let latestCamera: CameraSnapshot | undefined;
1093
894
  const pointsLinesOwner = new StandardPointsLinesOwner(internals);
1094
- const instanceCollections = new InstanceProjectionStore();
895
+ const instanceCollections = new InstanceCollectionStore();
1095
896
  const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
1096
- let ssrFormatReceipt: import('@forgeax/engine-rhi').RhiTextureFormatCapabilityReceipt | undefined;
1097
- let ssrFormatProbeGeneration = -1;
1098
- let ssrFormatProbe: Promise<void> | undefined;
897
+ const ssrFormatReceipts = new WeakMap<
898
+ RhiDevice,
899
+ import('@forgeax/engine-rhi').RhiTextureFormatCapabilityReceipt
900
+ >();
1099
901
  let ssrFallbackGeneration: number | undefined;
1100
- const ensureSsrFormatProbe = (): void => {
1101
- if (internals.ssrIdentity === undefined) return;
1102
- const generation = internals.deviceScope.generation;
902
+ const ensureSsrFormatProbe = async (device = internals.device): Promise<void> => {
1103
903
  if (
1104
- ssrFormatReceipt?.deviceGeneration === generation ||
1105
- ssrFormatProbeGeneration === generation ||
1106
- ssrFormatProbe !== undefined ||
1107
- typeof internals.device.probeTextureFormatCapability !== 'function'
1108
- ) {
904
+ internals.ssrIdentity === undefined ||
905
+ typeof device.probeTextureFormatCapability !== 'function'
906
+ )
1109
907
  return;
908
+ // RHI memoizes the probe per physical device. Retain the detached candidate
909
+ // receipt without publishing it: inspection selects only the active device.
910
+ // A failed/late recovery therefore cannot overwrite the active generation.
911
+ try {
912
+ const result = await device.probeTextureFormatCapability();
913
+ if (result.ok) ssrFormatReceipts.set(device, result.value);
914
+ } catch {
915
+ // Missing capability remains an explicit fallback-only admission.
1110
916
  }
1111
- ssrFormatProbeGeneration = generation;
1112
- const device = internals.device;
1113
- ssrFormatProbe = device
1114
- .probeTextureFormatCapability()
1115
- .then((result) => {
1116
- if (internals.device !== device || internals.deviceScope.generation !== generation) return;
1117
- ssrFormatReceipt = result.ok ? result.value : undefined;
1118
- })
1119
- .catch(() => {
1120
- if (internals.device === device && internals.deviceScope.generation === generation) {
1121
- ssrFormatReceipt = undefined;
1122
- }
1123
- })
1124
- .finally(() => {
1125
- ssrFormatProbe = undefined;
1126
- });
1127
917
  };
1128
918
  const cubeCaptureScheduler = createCubeCaptureScheduler({ maxFacesPerFrame: 1 });
1129
919
  const cubeCaptureState: CubeCaptureGraphState = { work: [] };
@@ -1134,7 +924,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1134
924
  internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
1135
925
  },
1136
926
  instanceCollections,
1137
- getSkinPaletteAllocator: () => internals.getPipelineState()?.skinPaletteAllocator ?? null,
1138
927
  });
1139
928
  let environmentLifecycle = new EnvironmentLifecycle(internals.deviceScope);
1140
929
  const transmissionAdmission = new TransmissionCandidateAdmission();
@@ -1278,7 +1067,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1278
1067
  }),
1279
1068
  ),
1280
1069
  } satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory);
1281
- let gpuDrivenProduction = GpuDrivenProduction.forDevice(internals.device, gpuDrivenShaderFactory);
1070
+ let gpuDrivenRecoveryCount = 0;
1071
+ let gpuDrivenProduction = createGpuDrivenOwner(
1072
+ internals.device,
1073
+ gpuDrivenShaderFactory,
1074
+ gpuDrivenRecoveryCount,
1075
+ );
1282
1076
  let occlusionRuntime: OcclusionRenderRuntime | undefined = new OcclusionRenderRuntime(
1283
1077
  internals.device,
1284
1078
  persistentRenderScene.visibilityFacetStore(),
@@ -1346,6 +1140,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1346
1140
  const frameState: RenderFrameState = {
1347
1141
  frameNumber: 0,
1348
1142
  reflectionFallbackDemand: false,
1143
+ ssrRequested: false,
1144
+ ssrSpatialAdmission: undefined,
1145
+ ssrHistoryOwner: undefined,
1146
+ ssrHistoryCandidate: undefined,
1147
+ ssrTemporalParamsPayload: new Uint8Array(32),
1148
+ ssrLastCameraEntity: undefined,
1149
+ ssrLastHistoryVersion: undefined,
1349
1150
  graphGeneration: 0,
1350
1151
  successfulTemporalFrameIndex: 0,
1351
1152
  lastSuccessfulBloom: 'off',
@@ -1376,6 +1177,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1376
1177
  reflectionFallbackObservationSource: undefined,
1377
1178
  lastSuccessfulCameraAntialias: undefined,
1378
1179
  temporalGpuState: undefined,
1180
+ autoExposureGpuResources: undefined,
1181
+ pendingAutoExposureGpuResources: undefined,
1182
+ autoExposureState: undefined,
1183
+ pendingAutoExposureState: undefined,
1184
+ standardLutGpuResources: undefined,
1185
+ pendingStandardLutGpuResources: undefined,
1186
+ standardLutState: createStandardLutState({ deviceEpoch: internals.deviceScope.generation }),
1187
+ pendingStandardLutState: undefined,
1379
1188
  activeTemporalGpuState: undefined,
1380
1189
  retiringTemporalGpuStates: new Set(),
1381
1190
  lastSuccessfulTemporalView: undefined,
@@ -1385,7 +1194,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1385
1194
  currentDirectionalShadowView: null,
1386
1195
  currentSpotShadowView: null,
1387
1196
  instanceBuffers: new Map(),
1388
- instanceResidency: new Map(),
1197
+ instanceCollections,
1389
1198
  instanceBufferChunks: new Map(),
1390
1199
  probeBlendBuffers: new Map(),
1391
1200
  probeBlendRecordBufferCapacity: 0,
@@ -1468,6 +1277,25 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1468
1277
  // tiles until the first frame with a castShadow spot (AC-03).
1469
1278
  spotShadowSnapshots: [],
1470
1279
  };
1280
+ const resolveSsrDependencies = (requested: boolean): SsrDependenciesInspection => {
1281
+ // Explicit SSR capability preparation belongs to initialization. Inspection
1282
+ // only projects receipts and never starts work or creates image history.
1283
+ const owner = reflectionProbeOwner.inspect();
1284
+ const temporal =
1285
+ frameState.lastSuccessfulTemporalView === undefined
1286
+ ? undefined
1287
+ : ({
1288
+ successfulSubmit: true,
1289
+ generation: frameState.lastSuccessfulTemporalView.input.deviceGeneration,
1290
+ } as const);
1291
+ return projectSsrDependencies({
1292
+ requested,
1293
+ identity: internals.ssrIdentity,
1294
+ reflectionFallback: owner.reflectionFallback,
1295
+ format: ssrFormatReceipts.get(internals.device),
1296
+ temporal,
1297
+ });
1298
+ };
1471
1299
  let directFrameId = 0;
1472
1300
  let submittedFrameCount = 0;
1473
1301
  let lastSubmittedGeneration: number | undefined;
@@ -1475,6 +1303,122 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1475
1303
  let lastDirectionalShadowCandidate: 'accepted' | 'failed' = 'failed';
1476
1304
  let lastDirectionalShadowError: DirectionalShadowInspectionSource['error'];
1477
1305
 
1306
+ /**
1307
+ * Stage the detached auto/LUT facts beside the GPU candidates already owned
1308
+ * by RenderFrameState. The typed graph promotes both records only after its
1309
+ * single finish/submit transaction succeeds; no side registry or compile
1310
+ * callback can publish a value early.
1311
+ */
1312
+ function stageLiveFeatureState(camera: CameraSnapshot | undefined, deltaTime: number): void {
1313
+ frameState.pendingAutoExposureState = undefined;
1314
+ frameState.pendingStandardLutState = undefined;
1315
+ const output = camera?.output;
1316
+ if (output === undefined) return;
1317
+
1318
+ const publicFrameId =
1319
+ (internals as RenderSystemInternals & { readonly observationFrameId?: number })
1320
+ .observationFrameId ?? frameState.frameNumber;
1321
+ const targetGeneration = Math.max(1, camera?.historyVersion ?? 0);
1322
+ const deviceEpoch = internals.deviceScope.generation;
1323
+
1324
+ if (
1325
+ output.exposure.kind === 'auto' &&
1326
+ (frameState.pendingAutoExposureGpuResources !== undefined ||
1327
+ frameState.autoExposureGpuResources !== undefined)
1328
+ ) {
1329
+ let state = frameState.autoExposureState;
1330
+ if (state === undefined || state.fallback !== output.exposure.fallback) {
1331
+ const created = createAutoExposureState({
1332
+ fallback: output.exposure.fallback,
1333
+ targetGeneration,
1334
+ deviceEpoch,
1335
+ frameId: publicFrameId,
1336
+ });
1337
+ if (!created.ok) {
1338
+ internals.errorRegistry.fire(created.error);
1339
+ return;
1340
+ }
1341
+ state = created.value;
1342
+ } else if (state.targetGeneration !== targetGeneration || state.deviceEpoch !== deviceEpoch) {
1343
+ state = resetAutoExposureState(state, 'camera-change', {
1344
+ targetGeneration,
1345
+ deviceEpoch,
1346
+ });
1347
+ }
1348
+ const resources =
1349
+ frameState.pendingAutoExposureGpuResources ?? frameState.autoExposureGpuResources;
1350
+ if (resources !== undefined) {
1351
+ const parameterWrite = writeAutoExposureParameters(resources, {
1352
+ compensationEv: output.exposure.compensationEv,
1353
+ rangeMinEv: output.exposure.rangeEv[0],
1354
+ rangeMaxEv: output.exposure.rangeEv[1],
1355
+ upRate: output.exposure.rates[0],
1356
+ downRate: output.exposure.rates[1],
1357
+ deltaTime,
1358
+ fallback: output.exposure.fallback,
1359
+ generation: state.targetGeneration,
1360
+ });
1361
+ if (!parameterWrite.ok) {
1362
+ internals.errorRegistry.fire(parameterWrite.error as RhiError);
1363
+ return;
1364
+ }
1365
+ }
1366
+ // The GPU adapt pass owns the numeric candidate. CPU staging carries
1367
+ // only the generation/transaction facts needed for submit publication.
1368
+ frameState.pendingAutoExposureState = Object.freeze({
1369
+ state,
1370
+ generation: state.targetGeneration,
1371
+ deviceEpoch: state.deviceEpoch,
1372
+ frameId: publicFrameId,
1373
+ });
1374
+ }
1375
+
1376
+ let lutState = frameState.standardLutState;
1377
+ if (lutState.targetGeneration !== targetGeneration || lutState.deviceEpoch !== deviceEpoch) {
1378
+ lutState = resetStandardLutState(lutState, 'device-recovered', {
1379
+ targetGeneration,
1380
+ deviceEpoch,
1381
+ });
1382
+ frameState.standardLutState = lutState;
1383
+ }
1384
+ const wantsLut = output.colorLutStrength > 0 && output.colorLut > 0;
1385
+ const lutResources =
1386
+ frameState.pendingStandardLutGpuResources ?? frameState.standardLutGpuResources;
1387
+ if (wantsLut && lutResources !== undefined) {
1388
+ const prepared = prepareStandardLutCandidate(lutState, {
1389
+ // sourceKey is the Catalog-owned identity returned by the same
1390
+ // preparation that built the live LUT bind group; numeric handles do
1391
+ // not cross the inspection boundary.
1392
+ resident: lutResources.sourceKey,
1393
+ sourceKey: lutResources.sourceKey,
1394
+ generation: lutState.targetGeneration,
1395
+ deviceEpoch: lutState.deviceEpoch,
1396
+ frameId: publicFrameId,
1397
+ });
1398
+ if (prepared.ok) {
1399
+ frameState.pendingStandardLutState = Object.freeze({
1400
+ state: lutState,
1401
+ candidate: prepared.value,
1402
+ remove: false,
1403
+ targetGeneration,
1404
+ deviceEpoch,
1405
+ });
1406
+ }
1407
+ } else if (!wantsLut && lutState.resident !== null) {
1408
+ frameState.pendingStandardLutState = Object.freeze({
1409
+ state: lutState,
1410
+ remove: true,
1411
+ targetGeneration,
1412
+ deviceEpoch,
1413
+ });
1414
+ }
1415
+ }
1416
+
1417
+ function discardLiveFeatureState(): void {
1418
+ frameState.pendingAutoExposureState = undefined;
1419
+ frameState.pendingStandardLutState = undefined;
1420
+ }
1421
+
1478
1422
  function beginProfilePhase(session: RecorderSession | undefined, phase: RenderPhase): boolean {
1479
1423
  if (session === undefined) return false;
1480
1424
  try {
@@ -1706,22 +1650,21 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1706
1650
  const getPostProcessPipeline = (
1707
1651
  id: string,
1708
1652
  bgl: BindGroupLayout,
1709
- colorFormat: GPUTextureFormat,
1653
+ colorFormats: readonly GPUTextureFormat[],
1710
1654
  entryOverride?: PostProcessShaderEntry,
1711
1655
  ): RenderPipeline | null => {
1712
1656
  const entry = entryOverride ?? lookupPostProcess(id);
1713
1657
  if (entry === undefined) return null;
1714
1658
  const signature = postProcessShaderEntrySignature(entry);
1715
- const key = `${id}|${colorFormat}|${signature}`;
1659
+ const key = `${id}|${colorFormats.join(',')}|${signature}`;
1716
1660
  const cached = postProcessPipelineCache.get(key);
1717
1661
  if (cached !== undefined) return cached;
1718
- internals.recoveryColdWorkGuard?.notePipelineColdWork();
1719
1662
  const factory = internals.buildPostProcessPipeline;
1720
1663
  if (factory === undefined) return null;
1721
1664
  const built = factory(
1722
1665
  entry,
1723
1666
  bgl,
1724
- colorFormat,
1667
+ colorFormats,
1725
1668
  postProcessShaderPipelineLabel(id, entry.source),
1726
1669
  );
1727
1670
  if (built === null) return null;
@@ -1892,193 +1835,18 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1892
1835
  createBindGroup: 0,
1893
1836
  keys: [],
1894
1837
  };
1895
- type PreparedResolverCaches = {
1896
- readonly preparedPipelineIds: WeakMap<object, string>;
1897
- readonly preparedMaterialPipelineShaders: WeakMap<object, string>;
1898
- readonly preparedGroup0Pipelines: WeakSet<object>;
1899
- readonly preparedViewOnlyPipelines: WeakSet<object>;
1900
- readonly preparedRenderMaterialPipelines: WeakSet<object>;
1901
- readonly preparedAssetHandles: WeakMap<World, Map<string, number>>;
1902
- };
1903
- const createPreparedResolverCaches = (): PreparedResolverCaches => ({
1904
- preparedPipelineIds: new WeakMap(),
1905
- preparedMaterialPipelineShaders: new WeakMap(),
1906
- preparedGroup0Pipelines: new WeakSet(),
1907
- preparedViewOnlyPipelines: new WeakSet(),
1908
- preparedRenderMaterialPipelines: new WeakSet(),
1909
- preparedAssetHandles: new WeakMap(),
1910
- });
1911
1838
  const activePreparedResolverCaches = createPreparedResolverCaches();
1912
- const preparedHandle = <Brand extends string>(
1913
- runtime: RenderSystemInternals,
1914
- caches: PreparedResolverCaches,
1915
- world: World,
1916
- guid: string,
1917
- brand: Brand,
1918
- ): Handle<Brand, 'shared'> | undefined => {
1919
- let handles = caches.preparedAssetHandles.get(world);
1920
- if (handles === undefined) {
1921
- handles = new Map();
1922
- caches.preparedAssetHandles.set(world, handles);
1923
- }
1924
- const key = `${brand}:${guid.toLowerCase()}`;
1925
- const cached = handles.get(key);
1926
- if (cached !== undefined) return cached as Handle<Brand, 'shared'>;
1927
- const asset = runtime.assets.lookup(guid);
1928
- if (asset === undefined) return undefined;
1929
- const handle = world.allocSharedRef(brand, asset);
1930
- handles.set(key, handle as number);
1931
- return handle;
1932
- };
1933
- const preparedMaterialBindings = (
1934
- runtime: RenderSystemInternals,
1935
- caches: PreparedResolverCaches,
1936
- worlds: readonly World[],
1937
- materialShaderId: string,
1938
- worldIndex: number,
1939
- materialGuid: string,
1940
- layout: BindGroupLayout,
1941
- ) => {
1942
- const world = worlds[worldIndex];
1943
- const material = runtime.assets.lookup(materialGuid) as MaterialAsset | undefined;
1944
- const pipelineState = runtime.getPipelineState();
1945
- if (world === undefined || material?.kind !== 'material' || pipelineState === null) {
1946
- return err(new Error('prepared material asset is unavailable'));
1947
- }
1948
- const schema = runtime.getParamSchema?.(materialShaderId) ?? [];
1949
- const values = material.values ?? {};
1950
- const derived = derive(schema);
1951
- const fields = [...derived.textureFieldNames];
1952
- let buffer: Buffer | undefined;
1953
- let payloadByteLength = 0;
1954
- if (derived.uboLayout.totalBytes > 0) {
1955
- const payload = new Uint8Array(Math.max(16, derived.uboLayout.totalBytes));
1956
- applyParamSnapshotToUbo(
1957
- payload,
1958
- schema,
1959
- values as Readonly<Record<string, number | readonly number[]>>,
1960
- );
1961
- const created = runtime.device.createBuffer({
1962
- label: `prepared-material:${materialGuid}`,
1963
- size: payload.byteLength,
1964
- usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1965
- });
1966
- if (!created.ok) return created;
1967
- buffer = created.value;
1968
- payloadByteLength = payload.byteLength;
1969
- const written = runtime.device.queue.writeBuffer(buffer, 0, payload);
1970
- if (!written.ok) {
1971
- runtime.device.destroyBuffer(buffer);
1972
- return written;
1973
- }
1974
- }
1975
- const textureResources: Array<{
1976
- sampler: import('@forgeax/engine-rhi').Sampler;
1977
- view: import('@forgeax/engine-rhi').TextureView;
1978
- }> = [];
1979
- for (let index = 0; index < fields.length; index += 1) {
1980
- const field = fields[index];
1981
- const value = field === undefined ? undefined : values[field];
1982
- const textureValue =
1983
- typeof value === 'object' && value !== null && 'texture' in value
1984
- ? (value as MaterialTextureValue)
1985
- : undefined;
1986
- let sampler = pipelineState.defaultSampler;
1987
- if (textureValue?.sampler !== undefined) {
1988
- const handle = preparedHandle(
1989
- runtime,
1990
- caches,
1991
- world,
1992
- String(textureValue.sampler),
1993
- 'SamplerAsset',
1994
- );
1995
- if (handle !== undefined) {
1996
- const asset = resolveAssetHandle<SamplerAsset>(world, handle);
1997
- if (asset.ok) {
1998
- const resident = runtime.gpuStore.ensureSamplerResident(handle, asset.value, world);
1999
- if (resident.ok) sampler = resident.value;
2000
- }
2001
- }
2002
- }
2003
- let view = pipelineState.defaultWhiteTextureView;
2004
- if (textureValue !== undefined) {
2005
- const handle = preparedHandle(
2006
- runtime,
2007
- caches,
2008
- world,
2009
- String(textureValue.texture),
2010
- 'TextureAsset',
2011
- );
2012
- if (handle !== undefined) {
2013
- view = residentTextureView(world, runtime.gpuStore, runtime, handle) ?? view;
2014
- }
2015
- }
2016
- textureResources.push({ sampler, view });
2017
- }
2018
- const entries: BindGroupEntry[] = [];
2019
- let textureIndex = 0;
2020
- for (let index = 0; index < derived.bglEntries.length; index += 1) {
2021
- const expected = derived.bglEntries[index];
2022
- if (expected?.buffer?.type === 'uniform' && buffer !== undefined) {
2023
- entries.push({
2024
- binding: expected.binding,
2025
- resource: { kind: 'buffer', value: { buffer, size: payloadByteLength } },
2026
- });
2027
- continue;
2028
- }
2029
- const textureResource = textureResources[textureIndex];
2030
- if (
2031
- expected?.sampler?.type === 'filtering' &&
2032
- derived.bglEntries[index + 1]?.texture !== undefined &&
2033
- textureResource !== undefined
2034
- ) {
2035
- entries.push({
2036
- binding: expected.binding,
2037
- resource: { kind: 'sampler', value: textureResource.sampler },
2038
- });
2039
- continue;
2040
- }
2041
- if (expected?.texture !== undefined && textureResource !== undefined) {
2042
- entries.push({
2043
- binding: expected.binding,
2044
- resource: { kind: 'textureView', value: textureResource.view },
2045
- });
2046
- textureIndex += 1;
2047
- continue;
2048
- }
2049
- if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
2050
- return err(new Error('prepared material schema contains unsupported binding kinds'));
2051
- }
2052
- const fallback = pipelineState.skylightFallback;
2053
- if (fallback === null) {
2054
- if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
2055
- return err(new Error('prepared material skylight fallback is unavailable'));
2056
- }
2057
- const completeEntries = assembleMaterialWithSkylightEntries(entries, {
2058
- irradianceView: fallback.irradianceView,
2059
- irradianceSampler: fallback.sampler,
2060
- prefilterView: fallback.prefilterView,
2061
- prefilterSampler: fallback.sampler,
2062
- brdfLutView: fallback.brdfLutView,
2063
- brdfLutSampler: fallback.sampler,
2064
- intensityBuffer: fallback.intensityBuffer,
2065
- });
2066
- const group = runtime.device.createBindGroup({ layout, entries: completeEntries });
2067
- if (!group.ok) {
2068
- if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
2069
- return group;
2070
- }
2071
- if (buffer === undefined) return group;
2072
- return ok({
2073
- handle: group.value,
2074
- dynamicOffsets: [0, 0],
2075
- release: () => runtime.device.destroyBuffer(buffer),
2076
- });
2077
- };
2078
1839
  const lastFrustumStats: { culled: number; total: number } = { culled: 0, total: 0 };
2079
1840
  const lastVisibilityStats: { explicitlyHidden: number } = { explicitlyHidden: 0 };
1841
+ let lastMaterialTextureSources: MaterialTextureSourceInspection = {
1842
+ sourceFieldsVisited: 0,
1843
+ numericSharedRefProbes: 0,
1844
+ sourceCacheHits: 0,
1845
+ sourceCacheMisses: 0,
1846
+ producerRoutes: Object.freeze({}),
1847
+ };
2080
1848
  let lastMeshMaterialBindings: readonly MeshMaterialBindingObservation[] = [];
2081
- let lastMeshMaterialBindingFrame: ExtractedFrame | undefined;
1849
+ let lastMeshMaterialBindingRenderables: readonly RenderableSnapshot[] | undefined;
2082
1850
  const createPreparedResolverFactory =
2083
1851
  (
2084
1852
  runtime: RenderSystemInternals,
@@ -2115,7 +1883,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2115
1883
  const pipeline = runtime.getPostProcessPipeline?.(
2116
1884
  descriptor.shader,
2117
1885
  fullscreen.bindGroupLayout,
2118
- descriptor.colorFormats[0] as GPUTextureFormat,
1886
+ descriptor.colorFormats as readonly GPUTextureFormat[],
2119
1887
  postProcessEntry,
2120
1888
  );
2121
1889
  if (pipeline !== null && pipeline !== undefined) {
@@ -2128,14 +1896,26 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2128
1896
  ? err(makePreparedPipelinePendingError())
2129
1897
  : ok(pipeline);
2130
1898
  }
2131
- const preparedPipeline = runtime.getMaterialShaderPipeline?.(
1899
+ const clusteredParticleMesh =
1900
+ runtime.device.caps.storageBuffer &&
1901
+ (descriptor.shader === 'forgeax::vfx-render.particles.mesh' ||
1902
+ descriptor.shader === 'forgeax::vfx-render.particles.mesh-inputs');
1903
+ const requestedVariantSet = clusteredParticleMesh
1904
+ ? 'CLUSTER_FORWARD_AVAILABLE=true'
1905
+ : undefined;
1906
+ // A colorless prepared program is a depth-only pass. Vertex-only
1907
+ // shadow programs must use the depth policy; resolving them as
1908
+ // forward asks Dawn for a non-existent `fs_main` entry point.
1909
+ const preparedPassKind =
1910
+ descriptor.colorFormats.length === 0 ? 'shadow-caster' : 'forward';
1911
+ const preparedPipelineEntry = runtime.getMaterialShaderPipelineEntry?.(
2132
1912
  descriptor.shader,
2133
1913
  descriptor.colorFormats[0] === 'rgba16float',
2134
1914
  descriptor.renderState,
2135
1915
  descriptor.topology,
2136
1916
  descriptor.indexFormat,
2137
- undefined,
2138
- 'forward',
1917
+ requestedVariantSet,
1918
+ preparedPassKind,
2139
1919
  undefined,
2140
1920
  descriptor.sampleCount ?? 1,
2141
1921
  // Prepared graphics own their declared attachment format. Passing
@@ -2150,7 +1930,41 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2150
1930
  descriptor.vertexLayout,
2151
1931
  undefined,
2152
1932
  input.shaderModuleMode,
1933
+ undefined,
1934
+ undefined,
1935
+ undefined,
1936
+ undefined,
1937
+ undefined,
1938
+ descriptor.particleInputLanes,
2153
1939
  );
1940
+ const preparedPipeline =
1941
+ preparedPipelineEntry === undefined
1942
+ ? (runtime.getMaterialShaderPipeline?.(
1943
+ descriptor.shader,
1944
+ descriptor.colorFormats[0] === 'rgba16float',
1945
+ descriptor.renderState,
1946
+ descriptor.topology,
1947
+ descriptor.indexFormat,
1948
+ requestedVariantSet,
1949
+ preparedPassKind,
1950
+ undefined,
1951
+ descriptor.sampleCount ?? 1,
1952
+ descriptor.colorFormats[0] as GPUTextureFormat,
1953
+ undefined,
1954
+ descriptor.depthFormat === undefined
1955
+ ? null
1956
+ : (descriptor.depthFormat as GPUTextureFormat),
1957
+ descriptor.vertexLayout,
1958
+ undefined,
1959
+ input.shaderModuleMode,
1960
+ undefined,
1961
+ undefined,
1962
+ undefined,
1963
+ undefined,
1964
+ undefined,
1965
+ descriptor.particleInputLanes,
1966
+ ) ?? null)
1967
+ : (preparedPipelineEntry?.pipeline ?? null);
2154
1968
  // A requested material shader is an exact pipeline contract. During
2155
1969
  // async shader warmup, substituting the generic unlit pipeline can
2156
1970
  // mismatch vertex layouts and HDR attachment formats, turning a
@@ -2167,7 +1981,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2167
1981
  preparedRenderMaterialPipelines.add(pipeline as object);
2168
1982
  }
2169
1983
  }
2170
- return pipeline === null ? err(makePreparedPipelinePendingError()) : ok(pipeline);
1984
+ return pipeline === null
1985
+ ? err(makePreparedPipelinePendingError())
1986
+ : ok({
1987
+ handle: pipeline,
1988
+ standardLighting: preparedPipelineEntry?.group2Contract === 'cluster',
1989
+ });
2171
1990
  },
2172
1991
  resolveBindings: (descriptor, pipeline) => {
2173
1992
  const materialShaderId = preparedMaterialPipelineShaders.get(pipeline as object);
@@ -2183,6 +2002,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2183
2002
  if (bindingContract === 'view-and-scene-depth') {
2184
2003
  return ok(undefined);
2185
2004
  }
2005
+ if (
2006
+ preparedRenderMaterialPipelines.has(pipeline as object) &&
2007
+ descriptor.values.group === 0 &&
2008
+ descriptor.values.sceneDepth === undefined &&
2009
+ descriptor.values.sceneDepthBinding !== undefined
2010
+ ) {
2011
+ return err(new Error('prepared scene-depth pipeline requires a scene target'));
2012
+ }
2186
2013
  if (preparedGroup0Pipelines.has(pipeline as object)) {
2187
2014
  const layout =
2188
2015
  (materialShaderId === undefined
@@ -2236,6 +2063,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2236
2063
  material.world,
2237
2064
  material.guid,
2238
2065
  layout,
2066
+ preparedFeatureSkylightResources,
2239
2067
  );
2240
2068
  }
2241
2069
  return layout === undefined
@@ -2319,23 +2147,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2319
2147
  createPreparedResolverCaches,
2320
2148
  });
2321
2149
  return {
2150
+ instances: instanceCollections,
2151
+ initializeSsr: ensureSsrFormatProbe,
2322
2152
  get instanceCollectionsInspection(): readonly InstanceCollectionInspection[] {
2323
- return instanceCollections._inspections(
2324
- frameState.instanceResidency,
2325
- frameState.frameNumber - 1,
2326
- );
2153
+ return instanceCollections._inspections();
2327
2154
  },
2328
2155
  releaseProfilerCatalog(): void {
2329
2156
  releaseProfilerCatalog?.();
2330
2157
  releaseProfilerCatalog = undefined;
2331
2158
  },
2332
2159
  invalidateGeometryHistory(): void {
2333
- frameState.temporalFrameTransaction.reset('signature-change');
2334
- frameState.temporalFrame = undefined;
2335
- frameState.temporalFrameInput = undefined;
2336
- frameState.lastSuccessfulTemporalView = undefined;
2337
- frameState.successfulTemporalFrameIndex = 0;
2338
- frameState.pendingTemporalCommit = { kind: 'none' };
2160
+ resetDynamicGeometryTemporalHistory(frameState);
2161
+ },
2162
+ bounds(world, entity) {
2163
+ return persistentRenderScene.bounds(world, entity);
2339
2164
  },
2340
2165
  get renderScene(): RenderSceneInspection {
2341
2166
  return {
@@ -2350,32 +2175,46 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2350
2175
  return reflectionProbeOwner.inspect();
2351
2176
  },
2352
2177
  get ssrDependencies(): SsrDependenciesInspection {
2353
- ensureSsrFormatProbe();
2354
- const owner = reflectionProbeOwner.inspect();
2355
- const nextGeneration = owner.reflectionFallback.projectionGeneration;
2356
- const reset = resolveSsrAdmissionGeneration(ssrFallbackGeneration, nextGeneration);
2357
- if (reset.changed) {
2358
- frameState.temporalFrameTransaction.reset('signature-change');
2359
- frameState.temporalFrame = undefined;
2360
- frameState.temporalFrameInput = undefined;
2361
- frameState.lastSuccessfulTemporalView = undefined;
2362
- frameState.successfulTemporalFrameIndex = 0;
2363
- frameState.pendingTemporalCommit = { kind: 'none' };
2364
- }
2365
- ssrFallbackGeneration = reset.generation;
2366
- const temporal =
2367
- frameState.lastSuccessfulTemporalView === undefined
2368
- ? undefined
2369
- : ({
2370
- successfulSubmit: true,
2371
- generation: frameState.lastSuccessfulTemporalView.input.deviceGeneration,
2372
- } as const);
2373
- return projectSsrDependencies({
2374
- requested: frameState.reflectionFallbackDemand === true,
2375
- identity: internals.ssrIdentity,
2376
- reflectionFallback: owner.reflectionFallback,
2377
- format: ssrFormatReceipt,
2378
- temporal,
2178
+ return resolveSsrDependencies(frameState.ssrRequested === true);
2179
+ },
2180
+ get ssr(): SsrSpatialInspection {
2181
+ const camera = latestCamera;
2182
+ const lane = internals.standardProfile?.renderPath === 'deferred' ? 'deferred' : 'forward';
2183
+ const admission: SsrSpatialAdmission =
2184
+ frameState.ssrSpatialAdmission ??
2185
+ Object.freeze({
2186
+ status: camera?.screenSpaceReflection === undefined ? 'not-requested' : 'requested',
2187
+ lane,
2188
+ config: camera?.screenSpaceReflection,
2189
+ viewRange: camera === undefined ? 0 : camera.far - camera.near,
2190
+ work: zeroSsrAdmissionWork(),
2191
+ });
2192
+ const admitted = admission.status === 'admitted';
2193
+ const status =
2194
+ admitted && internals.ssrShaders === undefined
2195
+ ? ('structural-only' as const)
2196
+ : admission.status;
2197
+ const graph = frameState.compiledFrameGraph?.inspect();
2198
+ const passRoster =
2199
+ admitted && graph !== undefined
2200
+ ? graph.passes.filter((pass) => pass.name.startsWith('ssr-')).map((pass) => pass.name)
2201
+ : [];
2202
+ const history = frameState.ssrHistoryOwner?.inspect();
2203
+ const dependencies = resolveSsrDependencies(frameState.ssrRequested === true);
2204
+ const fallbackSource = dependencies.reflectionFallback?.source;
2205
+ return projectSsrSpatialInspection(admission, {
2206
+ status,
2207
+ ...(history === undefined
2208
+ ? {}
2209
+ : {
2210
+ history: {
2211
+ state: history.state,
2212
+ bytes: history.activeBytes + history.candidateBytes + history.retiringBytes,
2213
+ resetCount: history.resetCount,
2214
+ },
2215
+ }),
2216
+ passRoster,
2217
+ ...(fallbackSource === undefined ? {} : { fallbackSource }),
2379
2218
  });
2380
2219
  },
2381
2220
  get reflectionFallbackCompletion(): Promise<void> | undefined {
@@ -2387,6 +2226,27 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2387
2226
  get environment(): EnvironmentInspection {
2388
2227
  return environmentLifecycle.inspect();
2389
2228
  },
2229
+ get autoExposure(): AutoExposureInspection | undefined {
2230
+ const camera = latestCamera;
2231
+ const state = frameState.autoExposureState;
2232
+ if (camera?.output?.exposure.kind !== 'auto' || state === undefined) return undefined;
2233
+ const actual = state.acceptedEv > 0 ? state.acceptedEv : null;
2234
+ return createAutoExposureInspection({
2235
+ requested: camera.output.exposure,
2236
+ actual,
2237
+ actualState:
2238
+ actual === null ? (state.receipt.committed ? 'gpu-resident' : 'fallback') : 'accepted',
2239
+ fallback: state.fallback,
2240
+ lastKnownGood: state.lastKnownGood,
2241
+ targetGeneration: state.targetGeneration,
2242
+ reset: state.reset,
2243
+ cost: { histogramBytes: AUTO_EXPOSURE_HISTOGRAM_BYTES, passCount: 3, physicalPassCount: 1 },
2244
+ receipt: state.receipt,
2245
+ });
2246
+ },
2247
+ get standardLut(): StandardLutInspection {
2248
+ return inspectStandardLutState(frameState.standardLutState);
2249
+ },
2390
2250
  get temporal(): TemporalInspection {
2391
2251
  return inspectTemporal(frameState.lastSuccessfulTemporalView, undefined, {
2392
2252
  ...(frameState.activeTemporalGpuState === undefined
@@ -2526,6 +2386,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2526
2386
  return persistentRenderScene.pointsLinesSnapshots();
2527
2387
  },
2528
2388
  detachScene(world: World): void {
2389
+ const worldId = preparedWorlds.indexOf(world);
2390
+ const allocator = internals.getPipelineState()?.skinPaletteAllocator;
2391
+ if (allocator !== undefined && allocator !== null) {
2392
+ for (const slot of persistentRenderScene.compositionSlots()) {
2393
+ if (slot.worldId === worldId && slot.snapshot.skin !== undefined)
2394
+ allocator.releasePersistentSlice(slot.snapshot.skin.identity);
2395
+ }
2396
+ }
2529
2397
  persistentRenderScene.detach(world);
2530
2398
  },
2531
2399
  draw(
@@ -2543,8 +2411,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2543
2411
  let submitted = false;
2544
2412
  // A failed draw must never leave a previous frame eligible to publish a
2545
2413
  // newly accepted dynamic-geometry candidate.
2546
- lastSubmittedDynamicGeometryBindings = new WeakMap();
2547
- const frameConsumedDynamicGeometryBindings = new WeakMap<World, Map<number, number>>();
2414
+ dynamicGeometryFrames.begin();
2548
2415
  if (profileSession !== undefined && opts.profileFrame === undefined) {
2549
2416
  try {
2550
2417
  ownsProfileFrame = profileSession.beginFrame(++directFrameId).ok;
@@ -2557,7 +2424,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2557
2424
  // cameraOwner drives the surfaced cameras + frustum
2558
2425
  // cull; resourceOwner drives skylight/skybox/postProcess + per-world
2559
2426
  // record config.
2560
- const { cameraOwner, resourceOwner } = composition.owners;
2427
+ const { cameraOwner, resourceOwner, cameraEntityKey } = composition.owners;
2561
2428
  // Keep the compiled graph as last-known-good until the candidate topology
2562
2429
  // compiles. The failed candidate frame does not execute it; successful
2563
2430
  // replacement retires it atomically in ensureCompiledFrameGraph.
@@ -2571,37 +2438,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2571
2438
  }
2572
2439
  dispatchCounts.unlit = 0;
2573
2440
 
2574
- // feat-20260708-composited-multi-world-rendering M3 / D-2 / m3-i2:
2575
- // extractFrames merges per-world snapshots (renderables + lights from
2576
- // every world, cameras + singleton resources from the owner world),
2577
- // runs read-only extractFrame per world over the already-updated World,
2578
- // resets frame state once, and isolates
2579
- // per-world errors (AC-09).
2580
- // Single-world draw([world], { cameraOwner: 0, resourceOwner: 0 }) is the identity path
2581
- // (worldId=0), byte-for-byte equivalent to the pre-M3 direct
2582
- // extractFrame path (AC-03 regression guarantee).
2583
- //
2584
- // Entry validation (empty worlds / owner out of range) is enforced by
2585
- // the public renderer.draw facade (createRenderer.ts, D-5) before this
2586
- // internal method is reached; the owner world is guaranteed present.
2587
- //
2588
- // feat-20260709-editor-world-partition M1 / w5+w6 (D-3): per-world
2589
- // record configuration (transparent-sort mode, fold buckets, skybox /
2590
- // asset resolution) is read from the resource-owner world — the world
2591
- // that owns skylight / skybox / postProcessParams. w6 sources it from
2592
- // the dedicated resourceOwner index. The variable is named
2593
- // `resourceWorld` so the record-stage reads are self-describing (they
2594
- // are resource-owner reads, not camera reads).
2595
2441
  const resourceWorld = compositionWorlds[resourceOwner] as World;
2596
2442
  const frame = runProfiledRenderPhase(profileSession, 'extract', () => {
2597
2443
  return persistentRenderScene.extractComposition(
2598
2444
  compositionWorlds,
2599
2445
  { cameraOwner, resourceOwner },
2600
2446
  internals.assets.catalogEpoch,
2601
- (request: PersistentRenderCandidateRequest) =>
2447
+ (renderables) =>
2602
2448
  extractFrames(
2603
2449
  compositionWorlds,
2604
- { cameraOwner, resourceOwner },
2450
+ {
2451
+ cameraOwner,
2452
+ resourceOwner,
2453
+ ...(cameraEntityKey === undefined ? {} : { cameraEntityKey }),
2454
+ },
2605
2455
  internals.assets,
2606
2456
  internals.getPipelineState(),
2607
2457
  persistentRenderScene.materialSnapshotCacheStore(),
@@ -2626,13 +2476,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2626
2476
  },
2627
2477
  }),
2628
2478
  cull: 'none',
2629
- renderables: request === 'none' ? 'none' : 'full',
2630
- ...(request === 'none' || request === 'full'
2631
- ? {}
2632
- : { renderableEntitiesByWorld: request.entitiesByWorld }),
2633
2479
  retainHidden: true,
2480
+ renderables,
2634
2481
  instanceCollections,
2635
- ...(internals.getMaterialShaderArtifact === undefined
2482
+ ...(!internals.device.caps.storageBuffer ||
2483
+ internals.getMaterialShaderArtifact === undefined
2636
2484
  ? {}
2637
2485
  : { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
2638
2486
  },
@@ -2641,7 +2489,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2641
2489
  );
2642
2490
  });
2643
2491
  const {
2644
- cameras,
2492
+ cameras: extractedCameras,
2645
2493
  cubeCameras,
2646
2494
  lights,
2647
2495
  environment,
@@ -2649,6 +2497,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2649
2497
  volumetricFog,
2650
2498
  renderables,
2651
2499
  dispatch,
2500
+ shadowCasterEntityKeys: extractedShadowCasterEntityKeys,
2501
+ shadowCasterDrawKeys: extractedShadowCasterDrawKeys,
2502
+ shadowCasterMembership: extractedShadowCasterMembership,
2652
2503
  skylight,
2653
2504
  skylightCount,
2654
2505
  skybox,
@@ -2658,6 +2509,28 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2658
2509
  visibilityStats,
2659
2510
  postProcessParams,
2660
2511
  } = frame;
2512
+ const cameras = extractedCameras;
2513
+ const preparedOutput = prepareStandardOutputResources(
2514
+ frameState,
2515
+ internals,
2516
+ cameras[0],
2517
+ compositionWorlds[cameraOwner] as World,
2518
+ );
2519
+ if (!preparedOutput.ok) {
2520
+ internals.errorRegistry.fire(preparedOutput.error);
2521
+ return false;
2522
+ }
2523
+ const stableShadowOwnership = rebaseShadowCasterOwnership(
2524
+ compositionWorlds,
2525
+ persistentRenderScene.visibilityWorldKeysFor(compositionWorlds),
2526
+ extractedShadowCasterEntityKeys,
2527
+ extractedShadowCasterDrawKeys,
2528
+ extractedShadowCasterMembership,
2529
+ );
2530
+ const shadowCasterEntityKeys = stableShadowOwnership.entityKeys;
2531
+ const shadowCasterDrawKeys = stableShadowOwnership.drawKeys;
2532
+ const shadowCasterMembership = stableShadowOwnership.membership;
2533
+ const shadowCasterProjection = persistentRenderScene.shadowCasterProjection();
2661
2534
  lastDirectionalShadowLights = lights;
2662
2535
  lastDirectionalShadowCandidate = 'failed';
2663
2536
  lastDirectionalShadowError = lights.directionalShadowError;
@@ -2669,6 +2542,26 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2669
2542
  skylight,
2670
2543
  );
2671
2544
  latestCamera = cameras[0];
2545
+ frameState.ssrRequested = latestCamera?.screenSpaceReflection !== undefined;
2546
+ // Resolve before a committed fallback change resets accumulation.
2547
+ // Fresh spatial SSR does not require a previous temporal submission.
2548
+ // Inspection uses the read-only projection above and never resets state.
2549
+ const ssrDependencies = resolveSsrDependencies(frameState.ssrRequested);
2550
+ const reset = resolveSsrAdmissionGeneration(
2551
+ ssrFallbackGeneration,
2552
+ ssrDependencies.reflectionFallback?.projectionGeneration,
2553
+ );
2554
+ if (reset.changed) {
2555
+ frameState.temporalFrameTransaction.reset('signature-change');
2556
+ frameState.temporalFrame = undefined;
2557
+ frameState.temporalFrameInput = undefined;
2558
+ frameState.lastSuccessfulTemporalView = undefined;
2559
+ frameState.successfulTemporalFrameIndex = 0;
2560
+ frameState.pendingTemporalCommit = { kind: 'none' };
2561
+ frameState.ssrHistoryOwner?.reset('reflection-generation');
2562
+ frameState.ssrHistoryCandidate = undefined;
2563
+ }
2564
+ ssrFallbackGeneration = reset.generation;
2672
2565
  persistentRenderScene.updateVisibilityFacet(compositionWorlds, latestCamera, renderables);
2673
2566
  let occlusionProjection:
2674
2567
  | import('./scene/visibility/occlusion-runtime').OcclusionFrameProjection
@@ -2940,6 +2833,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2940
2833
  if (temporalDemanded) {
2941
2834
  persistentRenderScene.prepareTemporalFrame(submissionRenderables);
2942
2835
  }
2836
+ frameState.environmentFrame = environment as
2837
+ | import('./environment/frame').SelectedEnvironmentFrame
2838
+ | undefined;
2943
2839
  environmentLifecycle.bindScope(internals.deviceScope);
2944
2840
  if (fogFailure !== undefined) {
2945
2841
  environmentLifecycle.recordSelectionFailure(fogFailure);
@@ -2970,8 +2866,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2970
2866
  updateTransmissionInspection(transmissionAntialias, false);
2971
2867
  return false;
2972
2868
  }
2973
- if (lastMeshMaterialBindingFrame !== frame) {
2974
- lastMeshMaterialBindingFrame = frame;
2869
+ if (lastMeshMaterialBindingRenderables !== renderables) {
2870
+ lastMeshMaterialBindingRenderables = renderables;
2975
2871
  lastMeshMaterialBindings = renderables.map((renderable) => {
2976
2872
  const materialWorld = preparedWorlds[renderable.worldId];
2977
2873
  return projectMeshMaterialBindingObservation({
@@ -3024,6 +2920,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3024
2920
  lastFrustumStats.culled = frustumStats.culled;
3025
2921
  lastFrustumStats.total = frustumStats.total;
3026
2922
  lastVisibilityStats.explicitlyHidden = visibilityStats.explicitlyHidden;
2923
+ if (frame.materialTextureSources !== undefined) {
2924
+ lastMaterialTextureSources = Object.freeze({
2925
+ sourceFieldsVisited: frame.materialTextureSources.sourceFieldsVisited,
2926
+ numericSharedRefProbes: frame.materialTextureSources.numericSharedRefProbes,
2927
+ sourceCacheHits: frame.materialTextureSources.sourceCacheHits,
2928
+ sourceCacheMisses: frame.materialTextureSources.sourceCacheMisses,
2929
+ producerRoutes: Object.freeze({ ...frame.materialTextureSources.producerRoutes }),
2930
+ });
2931
+ }
3027
2932
  const featureTargets =
3028
2933
  preparedPipelineState === null || cameras[0] === undefined
3029
2934
  ? []
@@ -3034,6 +2939,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3034
2939
  storageBuffer: internals.device.caps.storageBuffer,
3035
2940
  multisample: internals.device.caps.backendKind !== 'wgpu-webgl2',
3036
2941
  });
2942
+ preparedFeatureSkylightResources =
2943
+ preparedPipelineState === null
2944
+ ? undefined
2945
+ : resolveMaterialSkylight(internals, preparedPipelineState, skylight, skylightCount)
2946
+ .skylightResources;
3037
2947
  let featureGraphCandidate: RenderFeatureGraphCandidate | undefined;
3038
2948
  const preparedResourceBatches = runProfiledRenderPhase(profileSession, 'features', () => {
3039
2949
  if (internals.featureHost === undefined) return [];
@@ -3049,6 +2959,28 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3049
2959
  ...(internals.getMaterialShaderBindingContract === undefined
3050
2960
  ? {}
3051
2961
  : { materialShaderBindingContract: internals.getMaterialShaderBindingContract }),
2962
+ ...(preparedPipelineState === null
2963
+ ? {}
2964
+ : {
2965
+ preparedDataInterfaces: {
2966
+ camera: {
2967
+ kind: 'buffer' as const,
2968
+ value: preparedPipelineState.viewUniformBuffer,
2969
+ size: 64,
2970
+ usage: 'uniform' as const,
2971
+ },
2972
+ 'scene-depth': {
2973
+ kind: 'texture-view' as const,
2974
+ value: preparedPipelineState.shadowFallbackTextureView,
2975
+ },
2976
+ get noise() {
2977
+ const value = resolveFeatureNoise(frameState.frameNumber);
2978
+ return value === undefined
2979
+ ? undefined
2980
+ : { kind: 'texture-view' as const, value };
2981
+ },
2982
+ },
2983
+ }),
3052
2984
  createPreparedGraphicsResolver: preparedResolverFactory,
3053
2985
  gpuWork: featureGpuWork,
3054
2986
  });
@@ -3072,10 +3004,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3072
3004
  featureGraphCandidate = {
3073
3005
  plans: featureFrame.plans,
3074
3006
  fullscreenEffects: featureFrame.fullscreenEffects,
3075
- ...(featureFrame.preparedResourceBatches.length === 0
3076
- ? {}
3077
- : { preparedResourceKey: `frame-${frameState.frameNumber}` }),
3007
+ ...(featureFrame.requiresPreparedResourceKey
3008
+ ? { preparedResourceKey: `frame-${frameState.frameNumber}` }
3009
+ : {}),
3078
3010
  onRejected: () => {
3011
+ featureFrame.onAborted();
3079
3012
  for (const batch of featureFrame.preparedResourceBatches) {
3080
3013
  const released = batch.release();
3081
3014
  if (!released.ok) {
@@ -3085,8 +3018,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3085
3018
  discardPostProcessParamsCandidate(postProcessParamsCandidate.value);
3086
3019
  },
3087
3020
  onAbandoned: () => {
3021
+ featureFrame.onAborted();
3088
3022
  discardPostProcessParamsCandidate(postProcessParamsCandidate.value);
3089
3023
  },
3024
+ onSubmitted: () => {
3025
+ featureFrame.onSubmitted();
3026
+ },
3027
+ onAborted: () => {
3028
+ featureFrame.onAborted();
3029
+ },
3090
3030
  onAccepted: () => {
3091
3031
  for (const [id, previous] of activeFeaturePostProcessEntries) {
3092
3032
  const next = featureFrame.fullscreenEffects.get(id);
@@ -3107,6 +3047,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3107
3047
  activeFeaturePostProcessEntries = featureFrame.fullscreenEffects;
3108
3048
  },
3109
3049
  };
3050
+ recordRenderFeatureCandidate(internals, featureGraphCandidate);
3110
3051
  return featureFrame.preparedResourceBatches;
3111
3052
  });
3112
3053
 
@@ -3184,13 +3125,32 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3184
3125
  if (cameras[0]?.bloom === 'on') {
3185
3126
  internals.getPipelineState()?.perPassResources.ensureBloomResources?.();
3186
3127
  }
3128
+ const liveOutput = cameras[0]?.output;
3129
+ const hasLiveFeatureState =
3130
+ liveOutput !== undefined ||
3131
+ frameState.autoExposureGpuResources !== undefined ||
3132
+ frameState.pendingAutoExposureGpuResources !== undefined ||
3133
+ frameState.autoExposureState !== undefined ||
3134
+ frameState.pendingAutoExposureState !== undefined ||
3135
+ frameState.standardLutGpuResources !== undefined ||
3136
+ frameState.pendingStandardLutGpuResources !== undefined ||
3137
+ frameState.standardLutState.resident !== null ||
3138
+ frameState.pendingStandardLutState !== undefined;
3139
+ if (hasLiveFeatureState) {
3140
+ const cameraWorld = compositionWorlds[cameraOwner] as World;
3141
+ const deltaTime = cameraWorld.getResource(Time).delta;
3142
+ stageLiveFeatureState(
3143
+ cameras[0],
3144
+ Number.isFinite(deltaTime) ? Math.max(0, deltaTime) : 0,
3145
+ );
3146
+ }
3187
3147
  submitted = runProfiledRenderPhase(profileSession, 'record', () =>
3188
3148
  recordFrame(
3189
3149
  internals,
3190
3150
  resourceWorld,
3191
3151
  cameras,
3192
3152
  lights,
3193
- [...submissionRenderables],
3153
+ submissionRenderables,
3194
3154
  orderedDispatch,
3195
3155
  frameState,
3196
3156
  dispatchCounts,
@@ -3226,32 +3186,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3226
3186
  environment?.fogSignature ?? '',
3227
3187
  environmentReady && environment !== undefined,
3228
3188
  persistentRenderScene.transmissionTopologyDemand(),
3189
+ shadowCasterEntityKeys,
3190
+ shadowCasterDrawKeys,
3229
3191
  volumetricFog,
3230
3192
  timingCapture,
3231
- (entry) => {
3232
- const renderable = entry.source;
3233
- const world = compositionWorlds[renderable.worldId];
3234
- if (world === undefined) return;
3235
- let bindings = frameConsumedDynamicGeometryBindings.get(world);
3236
- if (bindings === undefined) {
3237
- bindings = new Map();
3238
- frameConsumedDynamicGeometryBindings.set(world, bindings);
3239
- }
3240
- bindings.set(renderable.entityKey, renderable.assetHandle);
3241
- },
3193
+ shadowCasterMembership,
3194
+ shadowCasterProjection,
3195
+ worldKeys,
3196
+ undefined,
3197
+ ssrDependencies,
3198
+ dynamicGeometryFrames.onRenderableDraw(compositionWorlds),
3242
3199
  ),
3243
3200
  );
3244
- if (submitted) {
3245
- lastSubmittedDynamicGeometryBindings = frameConsumedDynamicGeometryBindings;
3246
- }
3247
- if (submitted) {
3248
- try {
3249
- internals.recoveryColdWorkGuard?.finish();
3250
- } catch (cause) {
3251
- submitted = false;
3252
- throw cause;
3253
- }
3254
- }
3201
+ dynamicGeometryFrames.commit(submitted);
3202
+ if (!submitted) discardLiveFeatureState();
3255
3203
  if (submitted && lastLodOcclusionInspection !== undefined) {
3256
3204
  const query = ensureOcclusionRuntime().inspect();
3257
3205
  lastLodOcclusionInspection = {
@@ -3346,6 +3294,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3346
3294
  }
3347
3295
  }
3348
3296
  } catch (err) {
3297
+ discardLiveFeatureState();
3349
3298
  if (!submitted) {
3350
3299
  internals.getPipelineState()?.perPassResources.discardBloomResources?.();
3351
3300
  }
@@ -3359,7 +3308,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3359
3308
  }
3360
3309
  : err instanceof RhiError
3361
3310
  ? err
3362
- : { code: 'unknown' as const, message: String(err), name: (err as Error)?.name };
3311
+ : {
3312
+ code: 'unknown' as const,
3313
+ message: String(err),
3314
+ name: (err as Error)?.name,
3315
+ };
3363
3316
  internals.errorRegistry.fire(
3364
3317
  new RhiError({
3365
3318
  code: 'webgpu-runtime-error',
@@ -3379,14 +3332,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3379
3332
  }
3380
3333
  return submitted;
3381
3334
  },
3382
- isDynamicGeometryConsumed(
3383
- world: World,
3384
- entity: number,
3385
- meshHandle: number | undefined,
3386
- ): boolean {
3387
- if (!Number.isInteger(entity) || meshHandle === undefined) return false;
3388
- return lastSubmittedDynamicGeometryBindings.get(world)?.get(entity) === meshHandle;
3389
- },
3335
+ isDynamicGeometryConsumed: (world, entity, meshHandle) =>
3336
+ dynamicGeometryFrames.isConsumed(world, entity, meshHandle),
3390
3337
  pipelineDispatchCounts: dispatchCounts,
3391
3338
  observeCurrentFrame(options: FrameObservationOptions) {
3392
3339
  const currentFrameId = frameState.frameNumber - 1;
@@ -3457,6 +3404,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3457
3404
  get perFrameGraphInfo(): CompiledRenderGraphInfo | undefined {
3458
3405
  return frameState.compiledFrameGraph?.inspect();
3459
3406
  },
3407
+ get featureGraphInspection(): import('./inspection-types').RenderFeatureGraphInspection {
3408
+ return getRenderFeatureGraphInspection(internals);
3409
+ },
3410
+ get materialTextureSources(): MaterialTextureSourceInspection {
3411
+ return lastMaterialTextureSources;
3412
+ },
3460
3413
  get recoveryEvidence(): RecoveryProductionEvidence {
3461
3414
  const graph = frameState.compiledFrameGraph?.inspect();
3462
3415
  return Object.freeze({
@@ -3583,6 +3536,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3583
3536
  (internals as TimingSessionHost).gpuPassTimingSubmittedWork = undefined;
3584
3537
  (internals as TimingSessionHost).gpuPassTimingFrameIdentity = undefined;
3585
3538
  (internals as TimingSessionHost).gpuPassTimingBeginReason = undefined;
3539
+ internals.getPipelineState()?.skinPaletteAllocator?.dispose();
3586
3540
  pointsLinesOwner.dispose();
3587
3541
  persistentRenderScene.dispose();
3588
3542
  instanceCollections.dispose();
@@ -3590,6 +3544,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3590
3544
  occlusionRuntime?.dispose();
3591
3545
  disposeFeatureGpuWork();
3592
3546
  reflectionProbeOwner.dispose();
3547
+ frameState.ssrHistoryOwner?.dispose();
3548
+ frameState.ssrHistoryOwner = undefined;
3549
+ frameState.ssrHistoryCandidate = undefined;
3550
+ frameState.ssrSpatialAdmission = undefined;
3593
3551
  // Bloom generations are renderer-owned rather than graph-owned. Drain
3594
3552
  // candidate, active, and fence-retiring bundles before the rest of the
3595
3553
  // frame state is torn down; the owner callback is idempotent so the
@@ -3610,6 +3568,19 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3610
3568
  frameState.lastSuccessfulCameraAntialias = undefined;
3611
3569
  frameState.currentDirectionalShadowView = null;
3612
3570
  frameState.currentSpotShadowView = null;
3571
+ if (frameState.pendingAutoExposureGpuResources !== undefined) {
3572
+ retireAutoExposureGpuResources(frameState.pendingAutoExposureGpuResources);
3573
+ frameState.pendingAutoExposureGpuResources = undefined;
3574
+ }
3575
+ if (frameState.autoExposureGpuResources !== undefined) {
3576
+ retireAutoExposureGpuResources(frameState.autoExposureGpuResources);
3577
+ frameState.autoExposureGpuResources = undefined;
3578
+ }
3579
+ frameState.autoExposureState = undefined;
3580
+ frameState.pendingAutoExposureState = undefined;
3581
+ frameState.pendingStandardLutGpuResources = undefined;
3582
+ frameState.standardLutGpuResources = undefined;
3583
+ frameState.pendingStandardLutState = undefined;
3613
3584
  if (frameState.temporalGpuState !== undefined) {
3614
3585
  retireTemporalGpuState(frameState.temporalGpuState);
3615
3586
  frameState.temporalGpuState = undefined;
@@ -3655,7 +3626,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3655
3626
  // so destroy failures fire structured errors (unified per-frame +
3656
3627
  // dispose error strategy).
3657
3628
  disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
3658
- frameState.instanceResidency?.clear();
3659
3629
  if (frameState.instanceBufferChunks !== undefined) {
3660
3630
  disposeInstanceBufferChunks(frameState.instanceBufferChunks, internals.errorRegistry);
3661
3631
  }
@@ -3696,9 +3666,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3696
3666
  // their handles without invoking destroy on a device that can no longer
3697
3667
  // service cleanup; the staged candidate owns the replacement buffers.
3698
3668
  pointsLinesOwner.abandonForDeviceLoss();
3699
- ssrFormatReceipt = undefined;
3700
- ssrFormatProbeGeneration = -1;
3701
- ssrFormatProbe = undefined;
3669
+ resetStandardOutputForDeviceLoss(frameState, internals.deviceScope.generation);
3670
+ ssrFormatReceipts.delete(internals.device);
3702
3671
  ssrFallbackGeneration = undefined;
3703
3672
  transmissionAdmission.markDeviceLost();
3704
3673
  lastTransmissionKey = '';
@@ -3716,13 +3685,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3716
3685
  updateTransmissionInspection(lastTransmissionAntialias, false);
3717
3686
  persistentRenderScene.resetGpuForRecover();
3718
3687
  gpuDrivenProduction.dispose();
3719
- // The active renderer still points at the lost device until the caller
3720
- // completes the synchronous publication boundary. Bind the replacement
3721
- // explicitly so the first published draw never routes through an owner
3722
- // recreated against the old physical device.
3723
- gpuDrivenProduction = GpuDrivenProduction.forDevice(
3688
+ gpuDrivenRecoveryCount += 1;
3689
+ gpuDrivenProduction = createGpuDrivenOwner(
3724
3690
  replacementDevice ?? internals.device,
3725
3691
  gpuDrivenShaderFactory,
3692
+ gpuDrivenRecoveryCount,
3726
3693
  );
3727
3694
  occlusionRuntime?.dispose();
3728
3695
  // The replacement device is installed by the host after this hook. Do
@@ -3732,6 +3699,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3732
3699
  disposeFeatureGpuWork();
3733
3700
  featureGpuWork = createFeatureGpuWorkOwner(internals);
3734
3701
  reflectionProbeOwner.dispose();
3702
+ frameState.ssrHistoryOwner?.dispose();
3703
+ frameState.ssrHistoryOwner = undefined;
3704
+ frameState.ssrHistoryCandidate = undefined;
3705
+ frameState.ssrSpatialAdmission = undefined;
3706
+ frameState.ssrRequested = false;
3707
+ frameState.ssrLastCameraEntity = undefined;
3708
+ frameState.ssrLastHistoryVersion = undefined;
3735
3709
  // A recovered device cannot retain handles from the lost device. The
3736
3710
  // Bloom owner abandons candidates and retires every committed generation
3737
3711
  // before the new PipelineState is rebuilt.
@@ -3801,7 +3775,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3801
3775
  frameState.retiredCompiledFrameGraphs.clear();
3802
3776
  frameState.instanceBuffers.clear();
3803
3777
  frameState.instanceBufferChunks?.clear();
3804
- frameState.instanceResidency?.clear();
3778
+ instanceCollections._resetResidency();
3805
3779
  delete frameState.probeBlendRecordBuffer;
3806
3780
  frameState.probeBlendRecordBufferCapacity = 0;
3807
3781
  frameState.probeBlendBuffers.clear();
@@ -3844,124 +3818,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3844
3818
  publishRecoveryPostProcessResources(prepared.value);
3845
3819
  },
3846
3820
  prepareRecoveryRoots(runtime: RecoveryRootRuntime): RecoveryRootBundle {
3847
- // Snapshot only CPU projections/declarations here. The root create
3848
- // callbacks below must never close over a mutable active owner while
3849
- // LifecycleTransaction is awaiting candidate construction.
3850
- const candidateEnvironmentLifecycle = environmentLifecycle.createRecoveryCandidate(
3851
- runtime.scope,
3852
- );
3853
- const roots: LifecycleResourceSpec<unknown>[] = [];
3854
- let candidateEnvironmentGeneration: EnvironmentGeneration | undefined;
3855
- let environmentRootCleaned = false;
3856
- let published = false;
3857
- if (candidateEnvironmentLifecycle.hasRecoveryFrame()) {
3858
- const environmentRoot = candidateEnvironmentLifecycle.createRecoveryRoot(runtime.scope);
3859
- roots.push({
3860
- ...environmentRoot,
3861
- create: async () => {
3862
- const generation = (await environmentRoot.create()) as EnvironmentGeneration;
3863
- candidateEnvironmentGeneration = generation;
3864
- return generation;
3865
- },
3866
- cleanup: (value) => {
3867
- if (environmentRootCleaned) return;
3868
- environmentRootCleaned = true;
3869
- if (candidateEnvironmentGeneration === value) {
3870
- candidateEnvironmentGeneration = undefined;
3871
- }
3872
- if (published) {
3873
- candidateEnvironmentLifecycle.retirePublishedGeneration(
3874
- value as EnvironmentGeneration,
3875
- );
3876
- } else {
3877
- environmentRoot.cleanup(value);
3878
- }
3879
- },
3880
- });
3881
- }
3882
- const graphCandidate = runtime.graphCandidate;
3883
- if (graphCandidate !== undefined) {
3884
- const releaseGraphCandidate = (): void => graphCandidate.release();
3885
- const compiledGraph = graphCandidate.frameState.compiledFrameGraph;
3886
- if (compiledGraph !== null) {
3887
- roots.push({
3888
- kind: 'pipeline',
3889
- create: () => {
3890
- if (!runtime.scope.isAlive()) {
3891
- throw new Error('Recovery graph candidate scope is not active.');
3892
- }
3893
- if (graphCandidate.frameState.compiledFrameGraph !== compiledGraph) {
3894
- throw new Error('Recovery graph candidate was changed before aggregate assembly.');
3895
- }
3896
- return compiledGraph;
3897
- },
3898
- // All graph/feature/GPU-driven candidate resources share one
3899
- // owner. The root is a real release seam, not a marker; the
3900
- // candidate latch makes every root cleanup call harmless after
3901
- // the first one.
3902
- cleanup: releaseGraphCandidate,
3903
- });
3904
- }
3905
- if (graphCandidate.featureHost !== undefined) {
3906
- const featureRoot = graphCandidate.featureHost.createRecoveryRoot(runtime.scope);
3907
- roots.push({ ...featureRoot, cleanup: releaseGraphCandidate });
3908
- }
3909
- if (graphCandidate.featureGpuWork !== undefined) {
3910
- const featureWorkRoot = graphCandidate.featureGpuWork.createRecoveryRoot(runtime.scope);
3911
- roots.push({ ...featureWorkRoot, cleanup: releaseGraphCandidate });
3912
- }
3913
- if (graphCandidate.gpuDrivenProduction !== undefined) {
3914
- const productionRoot = graphCandidate.gpuDrivenProduction.createRecoveryRoot(
3915
- runtime.scope,
3916
- );
3917
- roots.push({ ...productionRoot, cleanup: releaseGraphCandidate });
3918
- }
3919
- if (graphCandidate.gpuDrivenScene?.state !== undefined) {
3920
- const sceneRoot = graphCandidate.gpuDrivenScene.createRecoveryRoot(runtime.scope);
3921
- roots.push({ ...sceneRoot, cleanup: releaseGraphCandidate });
3922
- }
3923
- if (graphCandidate.pointsLines !== undefined) {
3924
- const pointsLinesRoot = graphCandidate.pointsLines.createRecoveryRoot(runtime.scope);
3925
- roots.push({ ...pointsLinesRoot, cleanup: releaseGraphCandidate });
3926
- }
3927
- if (runtime.gpuStore.recoveryResourceCount() > 0) {
3928
- const gpuStoreRoot = runtime.gpuStore.createRecoveryRoot(runtime.scope);
3929
- roots.push({ ...gpuStoreRoot, cleanup: releaseGraphCandidate });
3930
- }
3931
- }
3932
- let discarded = false;
3933
- return {
3934
- roots: Object.freeze(roots),
3935
- publish: () => {
3936
- if (published || discarded) return;
3937
- if (
3938
- candidateEnvironmentGeneration !== undefined &&
3939
- !candidateEnvironmentGeneration.retired
3940
- ) {
3941
- environmentLifecycle.retireActiveForReplacement();
3942
- candidateEnvironmentLifecycle.publish(candidateEnvironmentGeneration);
3943
- }
3944
- published = true;
3945
- environmentLifecycle = candidateEnvironmentLifecycle;
3946
- Object.assign(frameState, { environmentLifecycle });
3947
- },
3948
- discard: () => {
3949
- if (published || discarded) return;
3950
- discarded = true;
3951
- if (candidateEnvironmentGeneration !== undefined) {
3952
- candidateEnvironmentLifecycle.discard(candidateEnvironmentGeneration);
3953
- candidateEnvironmentGeneration = undefined;
3954
- }
3955
- candidateEnvironmentLifecycle.discardRecoveryCandidates();
3956
- // The transaction can fail before it reaches any graph root (for
3957
- // example while the environment root is created). Keep the graph,
3958
- // feature, GPU-driven, and residency resources on the same real
3959
- // candidate owner in that case; later scope/factory cleanup is a
3960
- // harmless repeat through the candidate latch.
3961
- graphCandidate?.release();
3962
- runtime.scope.abandon();
3963
- },
3964
- };
3821
+ return prepareRenderSystemRecoveryRoots(runtime, environmentLifecycle, frameState, (next) => {
3822
+ environmentLifecycle = next;
3823
+ });
3965
3824
  },
3966
3825
  };
3967
3826
  }