@forgeax/engine-render 0.1.27 → 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 (655) hide show
  1. package/README.md +842 -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 +52 -0
  5. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
  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 +19 -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 +3 -3
  60. package/dist/{chunk-ADHHVYLW.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-KM2NOX2I.mjs → chunk-BQVQOQJH.mjs} +958 -254
  65. package/dist/chunk-BQVQOQJH.mjs.map +1 -0
  66. package/dist/{chunk-HKXTW355.mjs → chunk-BTWH7BP4.mjs} +386 -17
  67. package/dist/chunk-BTWH7BP4.mjs.map +1 -0
  68. package/dist/{chunk-TMIASV2N.mjs → chunk-FUODTTIA.mjs} +3934 -676
  69. package/dist/chunk-FUODTTIA.mjs.map +1 -0
  70. package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
  71. package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
  72. package/dist/{chunk-E34VL5VI.mjs → chunk-JQTSEI3Z.mjs} +3 -3
  73. package/dist/{chunk-E34VL5VI.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
  74. package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
  75. package/dist/chunk-NICXSHAT.mjs.map +1 -0
  76. package/dist/{chunk-ZZ4YQ474.mjs → chunk-NRNXBLIJ.mjs} +577 -7
  77. package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
  78. package/dist/{chunk-JDWARUOI.mjs → chunk-PA5DRJNI.mjs} +17 -3
  79. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  80. package/dist/{chunk-XDP6XE5V.mjs → chunk-S4DNKQYQ.mjs} +1118 -446
  81. package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
  82. package/dist/components/camera.d.ts +62 -2
  83. package/dist/components/camera.d.ts.map +1 -1
  84. package/dist/components/index.d.ts +1 -0
  85. package/dist/components/index.d.ts.map +1 -1
  86. package/dist/components/instances.d.ts +6 -20
  87. package/dist/components/instances.d.ts.map +1 -1
  88. package/dist/components/scene-instance.d.ts +2 -1
  89. package/dist/components/scene-instance.d.ts.map +1 -1
  90. package/dist/components/screen-space-reflection.d.ts +14 -0
  91. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  92. package/dist/construct-renderer.mjs +13805 -7186
  93. package/dist/construct-renderer.mjs.map +1 -1
  94. package/dist/device/gpu-residency.d.ts +11 -17
  95. package/dist/device/gpu-residency.d.ts.map +1 -1
  96. package/dist/device/mesh-residency-lifetime.d.ts +21 -0
  97. package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
  98. package/dist/dynamic-geometry.d.ts +125 -0
  99. package/dist/dynamic-geometry.d.ts.map +1 -0
  100. package/dist/environment/background.d.ts +6 -0
  101. package/dist/environment/background.d.ts.map +1 -0
  102. package/dist/errors/gpu-driven.d.ts +6 -3
  103. package/dist/errors/gpu-driven.d.ts.map +1 -1
  104. package/dist/errors/render.d.ts +7 -4
  105. package/dist/errors/render.d.ts.map +1 -1
  106. package/dist/extract/camera.d.ts.map +1 -1
  107. package/dist/extract/gpu-driven.d.ts +33 -3
  108. package/dist/extract/gpu-driven.d.ts.map +1 -1
  109. package/dist/features/host.d.ts +23 -1
  110. package/dist/features/host.d.ts.map +1 -1
  111. package/dist/features/noise-texture.d.ts +17 -0
  112. package/dist/features/noise-texture.d.ts.map +1 -0
  113. package/dist/features/plan.d.ts +83 -2
  114. package/dist/features/plan.d.ts.map +1 -1
  115. package/dist/features/prepared-gpu-work.d.ts +37 -4
  116. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  117. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  118. package/dist/features/prepared-graphics.d.ts +5 -0
  119. package/dist/features/prepared-graphics.d.ts.map +1 -1
  120. package/dist/features/render-graph-compute.d.ts +5 -3
  121. package/dist/features/render-graph-compute.d.ts.map +1 -1
  122. package/dist/features/render-graph-contribution.d.ts +11 -3
  123. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  124. package/dist/features/render-graph-raster.d.ts +19 -3
  125. package/dist/features/render-graph-raster.d.ts.map +1 -1
  126. package/dist/features/types.d.ts +11 -0
  127. package/dist/features/types.d.ts.map +1 -1
  128. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  129. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  130. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  131. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  132. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  133. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  134. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  135. package/dist/gpu-driven/production-raster.d.ts +144 -23
  136. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  137. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  138. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  139. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  140. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  141. package/dist/gpu-scene-schema.d.ts +2 -8
  142. package/dist/gpu-scene-schema.d.ts.map +1 -1
  143. package/dist/gpu-scene.d.ts +14 -0
  144. package/dist/gpu-scene.d.ts.map +1 -1
  145. package/dist/gpu-texture-usage.d.ts +1 -0
  146. package/dist/gpu-texture-usage.d.ts.map +1 -1
  147. package/dist/hdrp-buffers.d.ts +6 -0
  148. package/dist/hdrp-buffers.d.ts.map +1 -1
  149. package/dist/ibl/frame-resources.d.ts +8 -0
  150. package/dist/ibl/frame-resources.d.ts.map +1 -0
  151. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  152. package/dist/index.d.ts +21 -8
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.mjs +8 -7
  155. package/dist/index.mjs.map +1 -1
  156. package/dist/inspection-types.d.ts +164 -0
  157. package/dist/inspection-types.d.ts.map +1 -1
  158. package/dist/instances-derived-bounds.d.ts +11 -0
  159. package/dist/instances-derived-bounds.d.ts.map +1 -1
  160. package/dist/instances.d.ts +89 -44
  161. package/dist/instances.d.ts.map +1 -1
  162. package/dist/internal.d.ts +3 -0
  163. package/dist/internal.d.ts.map +1 -1
  164. package/dist/internal.mjs +118 -10
  165. package/dist/internal.mjs.map +1 -1
  166. package/dist/lifecycle.d.ts +2 -1
  167. package/dist/lifecycle.d.ts.map +1 -1
  168. package/dist/material-row.d.ts +5 -0
  169. package/dist/material-row.d.ts.map +1 -0
  170. package/dist/materials.d.ts +15 -0
  171. package/dist/materials.d.ts.map +1 -1
  172. package/dist/pbr-pipeline.d.ts +21 -5
  173. package/dist/pbr-pipeline.d.ts.map +1 -1
  174. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  175. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  176. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  177. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  178. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  179. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  180. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  181. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  182. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  183. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  184. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  185. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  186. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  187. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  188. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  189. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  190. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  191. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  192. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  193. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  194. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  195. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  196. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  197. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  198. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  199. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  200. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  201. package/dist/pipeline/standard-output/types.d.ts +39 -0
  202. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  203. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  204. package/dist/pipeline/standard-post.d.ts +2 -2
  205. package/dist/pipeline/standard-post.d.ts.map +1 -1
  206. package/dist/pipeline-builder.d.ts +2 -1
  207. package/dist/pipeline-builder.d.ts.map +1 -1
  208. package/dist/pipeline-spec-types.d.ts +4 -0
  209. package/dist/pipeline-spec-types.d.ts.map +1 -1
  210. package/dist/pipeline-spec.d.ts +1 -9
  211. package/dist/pipeline-spec.d.ts.map +1 -1
  212. package/dist/points-lines/standard-owner.d.ts +1 -1
  213. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  214. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  215. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  216. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  217. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  218. package/dist/record/frame-lighting.d.ts +3 -3
  219. package/dist/record/frame-lighting.d.ts.map +1 -1
  220. package/dist/record/frame-snapshot.d.ts +106 -5
  221. package/dist/record/frame-snapshot.d.ts.map +1 -1
  222. package/dist/record/frame-targets.d.ts.map +1 -1
  223. package/dist/record/frame.d.ts +39 -4
  224. package/dist/record/frame.d.ts.map +1 -1
  225. package/dist/record/main-pass-geometry.d.ts +7 -8
  226. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  227. package/dist/record/main-pass-material.d.ts +38 -1
  228. package/dist/record/main-pass-material.d.ts.map +1 -1
  229. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  230. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  231. package/dist/record/main-pass.d.ts +12 -2
  232. package/dist/record/main-pass.d.ts.map +1 -1
  233. package/dist/record/mesh-ssbo.d.ts +15 -6
  234. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  235. package/dist/record/prepared-material-bindings.d.ts +19 -0
  236. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  237. package/dist/record/recovery-pipeline.d.ts +2 -9
  238. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  239. package/dist/record/render-context.d.ts +76 -26
  240. package/dist/record/render-context.d.ts.map +1 -1
  241. package/dist/record/shadow-pass.d.ts +62 -7
  242. package/dist/record/shadow-pass.d.ts.map +1 -1
  243. package/dist/record/skybox-post-pass.d.ts +1 -1
  244. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  245. package/dist/record/typed-frame-graph.d.ts +35 -7
  246. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  247. package/dist/record/view-ubo.d.ts +1 -0
  248. package/dist/record/view-ubo.d.ts.map +1 -1
  249. package/dist/recovery/render-system-candidate.d.ts +10 -2
  250. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  251. package/dist/recovery/render-system-roots.d.ts +11 -0
  252. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  253. package/dist/recovery/types.d.ts +76 -0
  254. package/dist/recovery/types.d.ts.map +1 -0
  255. package/dist/reflection/record-owner.d.ts +0 -2
  256. package/dist/reflection/record-owner.d.ts.map +1 -1
  257. package/dist/render-contract.d.ts +185 -30
  258. package/dist/render-contract.d.ts.map +1 -1
  259. package/dist/render-graph-primitives.d.ts +1 -0
  260. package/dist/render-graph-primitives.d.ts.map +1 -1
  261. package/dist/render-pipeline.d.ts +64 -5
  262. package/dist/render-pipeline.d.ts.map +1 -1
  263. package/dist/render-system-extract-tail.d.ts +7 -5
  264. package/dist/render-system-extract-tail.d.ts.map +1 -1
  265. package/dist/render-system-extract.d.ts +101 -41
  266. package/dist/render-system-extract.d.ts.map +1 -1
  267. package/dist/render-system-projections.d.ts +22 -0
  268. package/dist/render-system-projections.d.ts.map +1 -0
  269. package/dist/render-system.d.ts +34 -77
  270. package/dist/render-system.d.ts.map +1 -1
  271. package/dist/scene/render-scene-types.d.ts +14 -7
  272. package/dist/scene/render-scene-types.d.ts.map +1 -1
  273. package/dist/scene/render-scene.d.ts +29 -11
  274. package/dist/scene/render-scene.d.ts.map +1 -1
  275. package/dist/scene/shadow-visibility.d.ts +5 -0
  276. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  277. package/dist/ssr/admission.d.ts +70 -1
  278. package/dist/ssr/admission.d.ts.map +1 -1
  279. package/dist/ssr/compose.d.ts +13 -0
  280. package/dist/ssr/compose.d.ts.map +1 -0
  281. package/dist/ssr/errors.d.ts +35 -0
  282. package/dist/ssr/errors.d.ts.map +1 -1
  283. package/dist/ssr/graph.d.ts +60 -0
  284. package/dist/ssr/graph.d.ts.map +1 -0
  285. package/dist/ssr/history.d.ts +97 -0
  286. package/dist/ssr/history.d.ts.map +1 -0
  287. package/dist/ssr/hiz.d.ts +37 -0
  288. package/dist/ssr/hiz.d.ts.map +1 -0
  289. package/dist/ssr/inspection.d.ts +42 -0
  290. package/dist/ssr/inspection.d.ts.map +1 -0
  291. package/dist/ssr/resources.d.ts +58 -0
  292. package/dist/ssr/resources.d.ts.map +1 -0
  293. package/dist/ssr/temporal.d.ts +38 -0
  294. package/dist/ssr/temporal.d.ts.map +1 -0
  295. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  296. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  297. package/dist/systems/skin-palette-types.d.ts +15 -0
  298. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  299. package/dist/systems/transparent-dispatch.d.ts +9 -0
  300. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  301. package/dist/targets/material-source.d.ts +7 -0
  302. package/dist/targets/material-source.d.ts.map +1 -1
  303. package/dist/temporal/frame-coordinator.d.ts +7 -0
  304. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  305. package/dist/temporal/gpu.d.ts +8 -0
  306. package/dist/temporal/gpu.d.ts.map +1 -1
  307. package/dist/temporal/history.d.ts.map +1 -1
  308. package/dist/temporal/index.mjs +6 -6
  309. package/dist/temporal/inspection.d.ts.map +1 -1
  310. package/dist/temporal/standard-scene-data.d.ts +2 -1
  311. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  312. package/dist/temporal/temporal-view.d.ts +2 -0
  313. package/dist/temporal/temporal-view.d.ts.map +1 -1
  314. package/dist/typed-render-graph-primitives.d.ts +35 -1
  315. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  316. package/dist/typed-shadow-passes.d.ts +3 -1
  317. package/dist/typed-shadow-passes.d.ts.map +1 -1
  318. package/dist/volume/temporal.d.ts +6 -0
  319. package/dist/volume/temporal.d.ts.map +1 -1
  320. package/package.json +21 -20
  321. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  322. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  323. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  324. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  325. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  326. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  327. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  328. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  329. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  330. package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
  331. package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
  332. package/src/__tests__/factory-contract.integration.test.ts +73 -22
  333. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  334. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  335. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  336. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  337. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  338. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  339. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  340. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  341. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  342. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
  343. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  344. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  345. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
  346. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
  347. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
  348. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  349. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  350. package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
  351. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  352. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  353. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  354. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  355. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  356. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  357. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  358. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  359. package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
  360. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
  361. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  362. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  363. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  364. package/src/__tests__/gpu-scene.unit.test.ts +559 -33
  365. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  366. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  367. package/src/__tests__/instances-store.unit.test.ts +242 -39
  368. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  369. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  370. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  371. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  372. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  373. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  374. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  375. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  376. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  377. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  378. package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
  379. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  380. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
  381. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  382. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  383. package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
  384. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  385. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  386. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  387. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  388. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
  389. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  390. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  391. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  392. package/src/__tests__/public-surface.test-d.ts +3 -0
  393. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  394. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
  395. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
  396. package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
  397. package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
  398. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  399. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  400. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  401. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  402. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  403. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  404. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  405. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  406. package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
  407. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  408. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  409. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  410. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  411. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +61 -1
  412. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  413. package/src/__tests__/shader-manifest-fixture.ts +39 -0
  414. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  415. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  416. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  417. package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
  418. package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
  419. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  420. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  421. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  422. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  423. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
  424. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
  425. package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
  426. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  427. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  428. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  429. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  430. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  431. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  432. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  433. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  434. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  435. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  436. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  437. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  438. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  439. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  440. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  441. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  442. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  443. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  444. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  445. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  446. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  447. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  448. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
  449. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  450. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  451. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  452. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  453. package/src/__tests__/standard-output.browser.test.ts +15 -0
  454. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  455. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
  456. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  457. package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
  458. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  459. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  460. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  461. package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
  462. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  463. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
  464. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  465. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  466. package/src/__tests__/taa-stability-feedback.ts +341 -0
  467. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  468. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  469. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  470. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  471. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  472. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  473. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  474. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  475. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  476. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  477. package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
  478. package/src/assembly/dynamic-geometry-host.ts +887 -0
  479. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  480. package/src/assembly/factory.ts +4 -0
  481. package/src/assembly/gpu-driven-owner.ts +29 -0
  482. package/src/assembly/host-contract.ts +36 -0
  483. package/src/assembly/material/assembly.ts +38 -0
  484. package/src/assembly/material-shader-policy.ts +71 -28
  485. package/src/assembly/recovery/failure-location.ts +62 -0
  486. package/src/assembly/recovery/renderer-recover.ts +19 -17
  487. package/src/assembly/renderer-facade.ts +29 -0
  488. package/src/assembly/renderer-feature-inspection.ts +13 -0
  489. package/src/assembly/renderer-frame-transaction.ts +63 -0
  490. package/src/assembly/renderer-helpers.ts +26 -0
  491. package/src/assembly/renderer-inspection.ts +6 -0
  492. package/src/assembly/shader-prewarm-policy.ts +32 -0
  493. package/src/assembly/skin-palette-owner.ts +21 -0
  494. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  495. package/src/assembly/webgpu-device-loss.ts +75 -0
  496. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  497. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  498. package/src/assembly/webgpu-ready.ts +142 -121
  499. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  500. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  501. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  502. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  503. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  504. package/src/assembly/webgpu-renderer.ts +675 -614
  505. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  506. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  507. package/src/components/camera.ts +287 -7
  508. package/src/components/index.ts +1 -0
  509. package/src/components/instances.ts +45 -18
  510. package/src/components/scene-instance.ts +2 -1
  511. package/src/components/screen-space-reflection.ts +21 -0
  512. package/src/components/sprite-animation.ts +3 -3
  513. package/src/components/sprite-instances.ts +1 -1
  514. package/src/device/gpu-residency.ts +96 -93
  515. package/src/device/mesh-residency-lifetime.ts +73 -0
  516. package/src/dynamic-geometry.ts +736 -0
  517. package/src/environment/background.ts +263 -0
  518. package/src/errors/gpu-driven.ts +39 -5
  519. package/src/errors/render.ts +27 -3
  520. package/src/extract/camera.ts +26 -1
  521. package/src/extract/gpu-driven.ts +185 -10
  522. package/src/features/host.ts +237 -19
  523. package/src/features/noise-texture.ts +91 -0
  524. package/src/features/plan.ts +553 -24
  525. package/src/features/prepared-gpu-work.ts +631 -49
  526. package/src/features/prepared-graphics-store.ts +31 -0
  527. package/src/features/prepared-graphics.ts +5 -0
  528. package/src/features/render-graph-compute.ts +41 -8
  529. package/src/features/render-graph-contribution.ts +141 -3
  530. package/src/features/render-graph-raster.ts +143 -30
  531. package/src/features/types.ts +11 -0
  532. package/src/fullscreen-post-process-pass.ts +3 -0
  533. package/src/gpu-driven/batch-topology.ts +424 -102
  534. package/src/gpu-driven/pbr-program.ts +31 -0
  535. package/src/gpu-driven/prepared-draw.ts +29 -4
  536. package/src/gpu-driven/production-raster.ts +2267 -694
  537. package/src/gpu-driven/shadow-views.ts +539 -0
  538. package/src/gpu-driven/view-gpu.ts +188 -41
  539. package/src/gpu-scene-schema.ts +52 -11
  540. package/src/gpu-scene.ts +186 -99
  541. package/src/gpu-texture-usage.ts +1 -0
  542. package/src/hdrp-buffers.ts +72 -36
  543. package/src/ibl/frame-resources.ts +38 -0
  544. package/src/ibl/skylight-bind-group.ts +5 -4
  545. package/src/index.ts +125 -1
  546. package/src/inspection-types.ts +191 -0
  547. package/src/instances-derived-bounds.ts +258 -34
  548. package/src/instances.ts +459 -150
  549. package/src/internal.ts +22 -0
  550. package/src/lifecycle.ts +2 -0
  551. package/src/material-row.ts +145 -0
  552. package/src/materials.ts +44 -8
  553. package/src/pbr-pipeline.ts +159 -17
  554. package/src/pipeline/standard-forward-lane.ts +52 -24
  555. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  556. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  557. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  558. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  559. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  560. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  561. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  562. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  563. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  564. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  565. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  566. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  567. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  568. package/src/pipeline/standard-output/color-transform.ts +124 -0
  569. package/src/pipeline/standard-output/graph.ts +139 -0
  570. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  571. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  572. package/src/pipeline/standard-output/lut-state.ts +176 -0
  573. package/src/pipeline/standard-output/resources.ts +121 -0
  574. package/src/pipeline/standard-output/types.ts +58 -0
  575. package/src/pipeline/standard-pipeline.ts +192 -59
  576. package/src/pipeline/standard-post.ts +186 -27
  577. package/src/pipeline-builder.ts +16 -6
  578. package/src/pipeline-spec-types.ts +4 -0
  579. package/src/pipeline-spec.ts +37 -29
  580. package/src/points-lines/standard-owner.ts +6 -14
  581. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  582. package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
  583. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  584. package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
  585. package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
  586. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  587. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  588. package/src/record/dynamic-geometry-consumption.ts +59 -0
  589. package/src/record/frame-lighting.ts +12 -11
  590. package/src/record/frame-snapshot.ts +133 -23
  591. package/src/record/frame-targets.ts +9 -0
  592. package/src/record/frame.ts +727 -133
  593. package/src/record/main-pass-geometry.ts +162 -98
  594. package/src/record/main-pass-material.ts +115 -26
  595. package/src/record/main-pass-sprite-draws.ts +18 -107
  596. package/src/record/main-pass.ts +78 -8
  597. package/src/record/material-uniforms.ts +1 -1
  598. package/src/record/mesh-ssbo.ts +59 -15
  599. package/src/record/prepared-material-bindings.ts +234 -0
  600. package/src/record/recovery-pipeline.ts +59 -136
  601. package/src/record/render-context.ts +92 -22
  602. package/src/record/shadow-pass.ts +1231 -209
  603. package/src/record/skybox-post-pass.ts +14 -3
  604. package/src/record/typed-frame-graph.ts +1006 -53
  605. package/src/record/view-ubo.ts +11 -0
  606. package/src/recovery/render-system-candidate.ts +90 -66
  607. package/src/recovery/render-system-roots.ts +113 -0
  608. package/src/recovery/types.ts +77 -0
  609. package/src/reflection/record-owner.ts +26 -54
  610. package/src/render-contract.ts +219 -25
  611. package/src/render-graph-primitives.ts +6 -2
  612. package/src/render-pipeline.ts +70 -1
  613. package/src/render-system-extract-tail.ts +409 -160
  614. package/src/render-system-extract.ts +376 -102
  615. package/src/render-system-projections.ts +241 -0
  616. package/src/render-system.ts +692 -791
  617. package/src/scene/render-scene-types.ts +13 -4
  618. package/src/scene/render-scene.ts +569 -355
  619. package/src/scene/shadow-visibility.ts +21 -0
  620. package/src/ssr/admission.ts +380 -14
  621. package/src/ssr/compose.ts +144 -0
  622. package/src/ssr/errors.ts +48 -0
  623. package/src/ssr/graph.ts +777 -0
  624. package/src/ssr/history.ts +436 -0
  625. package/src/ssr/hiz.ts +124 -0
  626. package/src/ssr/inspection.ts +124 -0
  627. package/src/ssr/resources.ts +202 -0
  628. package/src/ssr/temporal.ts +164 -0
  629. package/src/systems/skin-palette-allocator.ts +495 -148
  630. package/src/systems/skin-palette-types.ts +17 -0
  631. package/src/systems/transparent-dispatch.ts +90 -0
  632. package/src/targets/material-source.ts +34 -0
  633. package/src/temporal/coverage.ts +1 -1
  634. package/src/temporal/frame-coordinator.ts +23 -2
  635. package/src/temporal/gpu.ts +74 -12
  636. package/src/temporal/history.ts +3 -1
  637. package/src/temporal/inspection.ts +2 -1
  638. package/src/temporal/standard-scene-data.ts +3 -1
  639. package/src/temporal/temporal-view.ts +3 -0
  640. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
  641. package/src/typed-render-graph-primitives.ts +266 -74
  642. package/src/typed-shadow-passes.ts +122 -12
  643. package/src/volume/temporal.ts +10 -0
  644. package/dist/chunk-ADHHVYLW.mjs.map +0 -1
  645. package/dist/chunk-FJ6P52EE.mjs.map +0 -1
  646. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  647. package/dist/chunk-GF523LHF.mjs.map +0 -1
  648. package/dist/chunk-HKXTW355.mjs.map +0 -1
  649. package/dist/chunk-JDWARUOI.mjs.map +0 -1
  650. package/dist/chunk-KM2NOX2I.mjs.map +0 -1
  651. package/dist/chunk-TMIASV2N.mjs.map +0 -1
  652. package/dist/chunk-XDP6XE5V.mjs.map +0 -1
  653. package/dist/chunk-ZZ4YQ474.mjs.map +0 -1
  654. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  655. 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
-
250
- interface LodInspectionQuerySummary {
251
- readonly used: number;
252
- readonly capacity: number;
253
- }
254
297
 
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[],
@@ -667,10 +509,17 @@ export interface RenderSystem {
667
509
  renderReadLeases?: readonly RenderReadLease[],
668
510
  timingCapture?: GpuTimingCapture,
669
511
  ): boolean;
512
+ /**
513
+ * Return the asset binding consumed by the last successful record pass.
514
+ * Dynamic geometry uses this renderer-owned frame fact as its publication
515
+ * barrier; ECS component presence alone is not a draw receipt.
516
+ */
517
+ isDynamicGeometryConsumed(world: World, entity: number, meshHandle: number | undefined): boolean;
518
+ invalidateGeometryHistory(): void;
670
519
  /** Prepare a detached graph candidate without entering the frame record path. */
671
520
  prepareRecoveryGraphCandidate(
672
521
  runtime: RecoveryGraphCandidateRuntime,
673
- ): Promise<RecoveryGraphCandidatePreparation>;
522
+ ): RecoveryGraphCandidatePreparation;
674
523
  /** Finish and submit detached candidate setup, never a frame receipt. */
675
524
  submitCandidateSetup(
676
525
  candidate: RecoveryGraphCandidate,
@@ -696,6 +545,8 @@ export interface RenderSystem {
696
545
  readonly pipelineDispatchCounts: {
697
546
  readonly unlit: number;
698
547
  };
548
+ /** Renderer-owned source for explicit large instance collections. */
549
+ readonly instances: InstanceCollectionStore;
699
550
  /** Detached residency and upload facts for explicit instance collections. */
700
551
  readonly instanceCollectionsInspection: readonly InstanceCollectionInspection[];
701
552
  /**
@@ -710,11 +561,17 @@ export interface RenderSystem {
710
561
  readonly reflectionProbes: ReflectionProbeInspection;
711
562
  /** Live producer/RHI/temporal receipts projected through SSR admission. */
712
563
  readonly ssrDependencies: SsrDependenciesInspection;
564
+ /** Live SSR spatial admission and bounded graph/history projection. */
565
+ readonly ssr: SsrSpatialInspection;
713
566
  /** Producer completion fence included in the public FrameReceipt. */
714
567
  readonly reflectionFallbackCompletion: Promise<void> | undefined;
715
568
  /** True while a progressive CubeCamera candidate still owns unfinished faces. */
716
569
  isCubeCapturePending(target: RenderTarget): boolean;
717
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;
718
575
  readonly temporal: TemporalInspection;
719
576
  readonly bloom: BloomInspection;
720
577
  readonly motionBlurInspection: MotionBlurInspection | undefined;
@@ -749,6 +606,10 @@ export interface RenderSystem {
749
606
  readonly lastSuccessfulCameraAntialias: CameraSnapshot['antialias'] | undefined;
750
607
  /** Detached compiled-graph facts used by the renderer inspection owner. */
751
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;
752
613
  /** Production-owner recovery facts used by Renderer.inspect(). */
753
614
  readonly recoveryEvidence: RecoveryProductionEvidence;
754
615
  /**
@@ -871,13 +732,6 @@ function makePreparedPipelinePendingError(): RhiError {
871
732
  });
872
733
  }
873
734
 
874
- /** The single Points/Lines owner’s staged device-generation handoff. */
875
- export interface RecoveryPointsLinesCandidate {
876
- createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown>;
877
- publish(): void;
878
- release(): void;
879
- }
880
-
881
735
  function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationFailure | undefined {
882
736
  if (typeof error !== 'object' || error === null) return undefined;
883
737
  const value = error as {
@@ -905,58 +759,6 @@ function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationF
905
759
  };
906
760
  }
907
761
 
908
- /** Candidate-only device/runtime inputs used by the recovery prepare seam. */
909
- export interface RecoveryGraphCandidateRuntime {
910
- readonly internals: RenderSystemInternals;
911
- readonly pipelineState: PipelineState;
912
- }
913
-
914
- /** Detached graph state prepared on a replacement device, before publication. */
915
- export interface RecoveryGraphCandidate {
916
- readonly frameState: RenderFrameState;
917
- readonly setupWorks: readonly MipmapEncoderWork[];
918
- readonly device: RhiDevice;
919
- readonly generation: number;
920
- readonly gpuDrivenProduction?: GpuDrivenProduction;
921
- readonly gpuDrivenScene?: PersistentGpuDrivenCandidate;
922
- /** Staged CPU-expanded Points/Lines buffers owned by the same RenderSystem. */
923
- readonly pointsLines?: RecoveryPointsLinesCandidate;
924
- readonly featureHost?: RenderFeatureHost;
925
- readonly featureGpuWork?: RenderFeatureGpuWorkOwner;
926
- readonly featureGraphCandidate?: RenderFeatureGraphCandidate;
927
- readonly postProcessPipelines?: ReadonlyMap<string, RenderPipeline>;
928
- readonly recoveryReadiness: RecoveryPipelineReadiness;
929
- /** Release all candidate roots before publication; safe to call repeatedly. */
930
- readonly release: () => void;
931
- /** Transfer candidate-root ownership to the published RenderSystem. */
932
- readonly markPublished: () => void;
933
- }
934
-
935
- /** Candidate setup completion; completion is bounded by the recovery owner. */
936
- export interface RecoveryGraphSetupSubmission {
937
- readonly completion?: PromiseLike<unknown>;
938
- }
939
-
940
- export type RecoveryGraphCandidatePreparation =
941
- | { readonly kind: 'no-seed' }
942
- | { readonly kind: 'ready'; readonly candidate: RecoveryGraphCandidate }
943
- | { readonly kind: 'failed'; readonly reason: string; readonly cause?: unknown };
944
-
945
- /** Explicit candidate-owned inputs for lifecycle-root preparation. */
946
- export interface RecoveryRootRuntime {
947
- readonly scope: DeviceScope;
948
- readonly device: RhiDevice;
949
- readonly gpuStore: GpuResidencyCache;
950
- readonly graphCandidate?: RecoveryGraphCandidate | undefined;
951
- }
952
-
953
- /** Candidate roots plus the one synchronous owner switch at publication. */
954
- export interface RecoveryRootBundle {
955
- readonly roots: readonly LifecycleResourceSpec<unknown>[];
956
- publish(): void;
957
- discard(): void;
958
- }
959
-
960
762
  /**
961
763
  * Project the producer-owned material and GPU store state into the one
962
764
  * renderer observation surface. This is intentionally recomputed from the
@@ -1081,41 +883,37 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1081
883
  let releaseProfilerCatalog =
1082
884
  phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : undefined;
1083
885
  let preparedWorlds: readonly World[] = [];
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();
1084
893
  let latestCamera: CameraSnapshot | undefined;
1085
894
  const pointsLinesOwner = new StandardPointsLinesOwner(internals);
1086
- const instanceCollections = new InstanceProjectionStore();
895
+ const instanceCollections = new InstanceCollectionStore();
1087
896
  const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
1088
- let ssrFormatReceipt: import('@forgeax/engine-rhi').RhiTextureFormatCapabilityReceipt | undefined;
1089
- let ssrFormatProbeGeneration = -1;
1090
- let ssrFormatProbe: Promise<void> | undefined;
897
+ const ssrFormatReceipts = new WeakMap<
898
+ RhiDevice,
899
+ import('@forgeax/engine-rhi').RhiTextureFormatCapabilityReceipt
900
+ >();
1091
901
  let ssrFallbackGeneration: number | undefined;
1092
- const ensureSsrFormatProbe = (): void => {
1093
- if (internals.ssrIdentity === undefined) return;
1094
- const generation = internals.deviceScope.generation;
902
+ const ensureSsrFormatProbe = async (device = internals.device): Promise<void> => {
1095
903
  if (
1096
- ssrFormatReceipt?.deviceGeneration === generation ||
1097
- ssrFormatProbeGeneration === generation ||
1098
- ssrFormatProbe !== undefined ||
1099
- typeof internals.device.probeTextureFormatCapability !== 'function'
1100
- ) {
904
+ internals.ssrIdentity === undefined ||
905
+ typeof device.probeTextureFormatCapability !== 'function'
906
+ )
1101
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.
1102
916
  }
1103
- ssrFormatProbeGeneration = generation;
1104
- const device = internals.device;
1105
- ssrFormatProbe = device
1106
- .probeTextureFormatCapability()
1107
- .then((result) => {
1108
- if (internals.device !== device || internals.deviceScope.generation !== generation) return;
1109
- ssrFormatReceipt = result.ok ? result.value : undefined;
1110
- })
1111
- .catch(() => {
1112
- if (internals.device === device && internals.deviceScope.generation === generation) {
1113
- ssrFormatReceipt = undefined;
1114
- }
1115
- })
1116
- .finally(() => {
1117
- ssrFormatProbe = undefined;
1118
- });
1119
917
  };
1120
918
  const cubeCaptureScheduler = createCubeCaptureScheduler({ maxFacesPerFrame: 1 });
1121
919
  const cubeCaptureState: CubeCaptureGraphState = { work: [] };
@@ -1126,7 +924,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1126
924
  internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
1127
925
  },
1128
926
  instanceCollections,
1129
- getSkinPaletteAllocator: () => internals.getPipelineState()?.skinPaletteAllocator ?? null,
1130
927
  });
1131
928
  let environmentLifecycle = new EnvironmentLifecycle(internals.deviceScope);
1132
929
  const transmissionAdmission = new TransmissionCandidateAdmission();
@@ -1270,7 +1067,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1270
1067
  }),
1271
1068
  ),
1272
1069
  } satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory);
1273
- let gpuDrivenProduction = GpuDrivenProduction.forDevice(internals.device, gpuDrivenShaderFactory);
1070
+ let gpuDrivenRecoveryCount = 0;
1071
+ let gpuDrivenProduction = createGpuDrivenOwner(
1072
+ internals.device,
1073
+ gpuDrivenShaderFactory,
1074
+ gpuDrivenRecoveryCount,
1075
+ );
1274
1076
  let occlusionRuntime: OcclusionRenderRuntime | undefined = new OcclusionRenderRuntime(
1275
1077
  internals.device,
1276
1078
  persistentRenderScene.visibilityFacetStore(),
@@ -1338,6 +1140,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1338
1140
  const frameState: RenderFrameState = {
1339
1141
  frameNumber: 0,
1340
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,
1341
1150
  graphGeneration: 0,
1342
1151
  successfulTemporalFrameIndex: 0,
1343
1152
  lastSuccessfulBloom: 'off',
@@ -1368,6 +1177,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1368
1177
  reflectionFallbackObservationSource: undefined,
1369
1178
  lastSuccessfulCameraAntialias: undefined,
1370
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,
1371
1188
  activeTemporalGpuState: undefined,
1372
1189
  retiringTemporalGpuStates: new Set(),
1373
1190
  lastSuccessfulTemporalView: undefined,
@@ -1377,7 +1194,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1377
1194
  currentDirectionalShadowView: null,
1378
1195
  currentSpotShadowView: null,
1379
1196
  instanceBuffers: new Map(),
1380
- instanceResidency: new Map(),
1197
+ instanceCollections,
1381
1198
  instanceBufferChunks: new Map(),
1382
1199
  probeBlendBuffers: new Map(),
1383
1200
  probeBlendRecordBufferCapacity: 0,
@@ -1460,6 +1277,25 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1460
1277
  // tiles until the first frame with a castShadow spot (AC-03).
1461
1278
  spotShadowSnapshots: [],
1462
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
+ };
1463
1299
  let directFrameId = 0;
1464
1300
  let submittedFrameCount = 0;
1465
1301
  let lastSubmittedGeneration: number | undefined;
@@ -1467,6 +1303,122 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1467
1303
  let lastDirectionalShadowCandidate: 'accepted' | 'failed' = 'failed';
1468
1304
  let lastDirectionalShadowError: DirectionalShadowInspectionSource['error'];
1469
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
+
1470
1422
  function beginProfilePhase(session: RecorderSession | undefined, phase: RenderPhase): boolean {
1471
1423
  if (session === undefined) return false;
1472
1424
  try {
@@ -1698,22 +1650,21 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1698
1650
  const getPostProcessPipeline = (
1699
1651
  id: string,
1700
1652
  bgl: BindGroupLayout,
1701
- colorFormat: GPUTextureFormat,
1653
+ colorFormats: readonly GPUTextureFormat[],
1702
1654
  entryOverride?: PostProcessShaderEntry,
1703
1655
  ): RenderPipeline | null => {
1704
1656
  const entry = entryOverride ?? lookupPostProcess(id);
1705
1657
  if (entry === undefined) return null;
1706
1658
  const signature = postProcessShaderEntrySignature(entry);
1707
- const key = `${id}|${colorFormat}|${signature}`;
1659
+ const key = `${id}|${colorFormats.join(',')}|${signature}`;
1708
1660
  const cached = postProcessPipelineCache.get(key);
1709
1661
  if (cached !== undefined) return cached;
1710
- internals.recoveryColdWorkGuard?.notePipelineColdWork();
1711
1662
  const factory = internals.buildPostProcessPipeline;
1712
1663
  if (factory === undefined) return null;
1713
1664
  const built = factory(
1714
1665
  entry,
1715
1666
  bgl,
1716
- colorFormat,
1667
+ colorFormats,
1717
1668
  postProcessShaderPipelineLabel(id, entry.source),
1718
1669
  );
1719
1670
  if (built === null) return null;
@@ -1884,193 +1835,18 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1884
1835
  createBindGroup: 0,
1885
1836
  keys: [],
1886
1837
  };
1887
- type PreparedResolverCaches = {
1888
- readonly preparedPipelineIds: WeakMap<object, string>;
1889
- readonly preparedMaterialPipelineShaders: WeakMap<object, string>;
1890
- readonly preparedGroup0Pipelines: WeakSet<object>;
1891
- readonly preparedViewOnlyPipelines: WeakSet<object>;
1892
- readonly preparedRenderMaterialPipelines: WeakSet<object>;
1893
- readonly preparedAssetHandles: WeakMap<World, Map<string, number>>;
1894
- };
1895
- const createPreparedResolverCaches = (): PreparedResolverCaches => ({
1896
- preparedPipelineIds: new WeakMap(),
1897
- preparedMaterialPipelineShaders: new WeakMap(),
1898
- preparedGroup0Pipelines: new WeakSet(),
1899
- preparedViewOnlyPipelines: new WeakSet(),
1900
- preparedRenderMaterialPipelines: new WeakSet(),
1901
- preparedAssetHandles: new WeakMap(),
1902
- });
1903
1838
  const activePreparedResolverCaches = createPreparedResolverCaches();
1904
- const preparedHandle = <Brand extends string>(
1905
- runtime: RenderSystemInternals,
1906
- caches: PreparedResolverCaches,
1907
- world: World,
1908
- guid: string,
1909
- brand: Brand,
1910
- ): Handle<Brand, 'shared'> | undefined => {
1911
- let handles = caches.preparedAssetHandles.get(world);
1912
- if (handles === undefined) {
1913
- handles = new Map();
1914
- caches.preparedAssetHandles.set(world, handles);
1915
- }
1916
- const key = `${brand}:${guid.toLowerCase()}`;
1917
- const cached = handles.get(key);
1918
- if (cached !== undefined) return cached as Handle<Brand, 'shared'>;
1919
- const asset = runtime.assets.lookup(guid);
1920
- if (asset === undefined) return undefined;
1921
- const handle = world.allocSharedRef(brand, asset);
1922
- handles.set(key, handle as number);
1923
- return handle;
1924
- };
1925
- const preparedMaterialBindings = (
1926
- runtime: RenderSystemInternals,
1927
- caches: PreparedResolverCaches,
1928
- worlds: readonly World[],
1929
- materialShaderId: string,
1930
- worldIndex: number,
1931
- materialGuid: string,
1932
- layout: BindGroupLayout,
1933
- ) => {
1934
- const world = worlds[worldIndex];
1935
- const material = runtime.assets.lookup(materialGuid) as MaterialAsset | undefined;
1936
- const pipelineState = runtime.getPipelineState();
1937
- if (world === undefined || material?.kind !== 'material' || pipelineState === null) {
1938
- return err(new Error('prepared material asset is unavailable'));
1939
- }
1940
- const schema = runtime.getParamSchema?.(materialShaderId) ?? [];
1941
- const values = material.values ?? {};
1942
- const derived = derive(schema);
1943
- const fields = [...derived.textureFieldNames];
1944
- let buffer: Buffer | undefined;
1945
- let payloadByteLength = 0;
1946
- if (derived.uboLayout.totalBytes > 0) {
1947
- const payload = new Uint8Array(Math.max(16, derived.uboLayout.totalBytes));
1948
- applyParamSnapshotToUbo(
1949
- payload,
1950
- schema,
1951
- values as Readonly<Record<string, number | readonly number[]>>,
1952
- );
1953
- const created = runtime.device.createBuffer({
1954
- label: `prepared-material:${materialGuid}`,
1955
- size: payload.byteLength,
1956
- usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1957
- });
1958
- if (!created.ok) return created;
1959
- buffer = created.value;
1960
- payloadByteLength = payload.byteLength;
1961
- const written = runtime.device.queue.writeBuffer(buffer, 0, payload);
1962
- if (!written.ok) {
1963
- runtime.device.destroyBuffer(buffer);
1964
- return written;
1965
- }
1966
- }
1967
- const textureResources: Array<{
1968
- sampler: import('@forgeax/engine-rhi').Sampler;
1969
- view: import('@forgeax/engine-rhi').TextureView;
1970
- }> = [];
1971
- for (let index = 0; index < fields.length; index += 1) {
1972
- const field = fields[index];
1973
- const value = field === undefined ? undefined : values[field];
1974
- const textureValue =
1975
- typeof value === 'object' && value !== null && 'texture' in value
1976
- ? (value as MaterialTextureValue)
1977
- : undefined;
1978
- let sampler = pipelineState.defaultSampler;
1979
- if (textureValue?.sampler !== undefined) {
1980
- const handle = preparedHandle(
1981
- runtime,
1982
- caches,
1983
- world,
1984
- String(textureValue.sampler),
1985
- 'SamplerAsset',
1986
- );
1987
- if (handle !== undefined) {
1988
- const asset = resolveAssetHandle<SamplerAsset>(world, handle);
1989
- if (asset.ok) {
1990
- const resident = runtime.gpuStore.ensureSamplerResident(handle, asset.value, world);
1991
- if (resident.ok) sampler = resident.value;
1992
- }
1993
- }
1994
- }
1995
- let view = pipelineState.defaultWhiteTextureView;
1996
- if (textureValue !== undefined) {
1997
- const handle = preparedHandle(
1998
- runtime,
1999
- caches,
2000
- world,
2001
- String(textureValue.texture),
2002
- 'TextureAsset',
2003
- );
2004
- if (handle !== undefined) {
2005
- view = residentTextureView(world, runtime.gpuStore, runtime, handle) ?? view;
2006
- }
2007
- }
2008
- textureResources.push({ sampler, view });
2009
- }
2010
- const entries: BindGroupEntry[] = [];
2011
- let textureIndex = 0;
2012
- for (let index = 0; index < derived.bglEntries.length; index += 1) {
2013
- const expected = derived.bglEntries[index];
2014
- if (expected?.buffer?.type === 'uniform' && buffer !== undefined) {
2015
- entries.push({
2016
- binding: expected.binding,
2017
- resource: { kind: 'buffer', value: { buffer, size: payloadByteLength } },
2018
- });
2019
- continue;
2020
- }
2021
- const textureResource = textureResources[textureIndex];
2022
- if (
2023
- expected?.sampler?.type === 'filtering' &&
2024
- derived.bglEntries[index + 1]?.texture !== undefined &&
2025
- textureResource !== undefined
2026
- ) {
2027
- entries.push({
2028
- binding: expected.binding,
2029
- resource: { kind: 'sampler', value: textureResource.sampler },
2030
- });
2031
- continue;
2032
- }
2033
- if (expected?.texture !== undefined && textureResource !== undefined) {
2034
- entries.push({
2035
- binding: expected.binding,
2036
- resource: { kind: 'textureView', value: textureResource.view },
2037
- });
2038
- textureIndex += 1;
2039
- continue;
2040
- }
2041
- if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
2042
- return err(new Error('prepared material schema contains unsupported binding kinds'));
2043
- }
2044
- const fallback = pipelineState.skylightFallback;
2045
- if (fallback === null) {
2046
- if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
2047
- return err(new Error('prepared material skylight fallback is unavailable'));
2048
- }
2049
- const completeEntries = assembleMaterialWithSkylightEntries(entries, {
2050
- irradianceView: fallback.irradianceView,
2051
- irradianceSampler: fallback.sampler,
2052
- prefilterView: fallback.prefilterView,
2053
- prefilterSampler: fallback.sampler,
2054
- brdfLutView: fallback.brdfLutView,
2055
- brdfLutSampler: fallback.sampler,
2056
- intensityBuffer: fallback.intensityBuffer,
2057
- });
2058
- const group = runtime.device.createBindGroup({ layout, entries: completeEntries });
2059
- if (!group.ok) {
2060
- if (buffer !== undefined) runtime.device.destroyBuffer(buffer);
2061
- return group;
2062
- }
2063
- if (buffer === undefined) return group;
2064
- return ok({
2065
- handle: group.value,
2066
- dynamicOffsets: [0, 0],
2067
- release: () => runtime.device.destroyBuffer(buffer),
2068
- });
2069
- };
2070
1839
  const lastFrustumStats: { culled: number; total: number } = { culled: 0, total: 0 };
2071
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
+ };
2072
1848
  let lastMeshMaterialBindings: readonly MeshMaterialBindingObservation[] = [];
2073
- let lastMeshMaterialBindingFrame: ExtractedFrame | undefined;
1849
+ let lastMeshMaterialBindingRenderables: readonly RenderableSnapshot[] | undefined;
2074
1850
  const createPreparedResolverFactory =
2075
1851
  (
2076
1852
  runtime: RenderSystemInternals,
@@ -2107,7 +1883,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2107
1883
  const pipeline = runtime.getPostProcessPipeline?.(
2108
1884
  descriptor.shader,
2109
1885
  fullscreen.bindGroupLayout,
2110
- descriptor.colorFormats[0] as GPUTextureFormat,
1886
+ descriptor.colorFormats as readonly GPUTextureFormat[],
2111
1887
  postProcessEntry,
2112
1888
  );
2113
1889
  if (pipeline !== null && pipeline !== undefined) {
@@ -2120,14 +1896,26 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2120
1896
  ? err(makePreparedPipelinePendingError())
2121
1897
  : ok(pipeline);
2122
1898
  }
2123
- 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?.(
2124
1912
  descriptor.shader,
2125
1913
  descriptor.colorFormats[0] === 'rgba16float',
2126
1914
  descriptor.renderState,
2127
1915
  descriptor.topology,
2128
1916
  descriptor.indexFormat,
2129
- undefined,
2130
- 'forward',
1917
+ requestedVariantSet,
1918
+ preparedPassKind,
2131
1919
  undefined,
2132
1920
  descriptor.sampleCount ?? 1,
2133
1921
  // Prepared graphics own their declared attachment format. Passing
@@ -2142,7 +1930,41 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2142
1930
  descriptor.vertexLayout,
2143
1931
  undefined,
2144
1932
  input.shaderModuleMode,
1933
+ undefined,
1934
+ undefined,
1935
+ undefined,
1936
+ undefined,
1937
+ undefined,
1938
+ descriptor.particleInputLanes,
2145
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);
2146
1968
  // A requested material shader is an exact pipeline contract. During
2147
1969
  // async shader warmup, substituting the generic unlit pipeline can
2148
1970
  // mismatch vertex layouts and HDR attachment formats, turning a
@@ -2159,7 +1981,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2159
1981
  preparedRenderMaterialPipelines.add(pipeline as object);
2160
1982
  }
2161
1983
  }
2162
- 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
+ });
2163
1990
  },
2164
1991
  resolveBindings: (descriptor, pipeline) => {
2165
1992
  const materialShaderId = preparedMaterialPipelineShaders.get(pipeline as object);
@@ -2175,6 +2002,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2175
2002
  if (bindingContract === 'view-and-scene-depth') {
2176
2003
  return ok(undefined);
2177
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
+ }
2178
2013
  if (preparedGroup0Pipelines.has(pipeline as object)) {
2179
2014
  const layout =
2180
2015
  (materialShaderId === undefined
@@ -2228,6 +2063,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2228
2063
  material.world,
2229
2064
  material.guid,
2230
2065
  layout,
2066
+ preparedFeatureSkylightResources,
2231
2067
  );
2232
2068
  }
2233
2069
  return layout === undefined
@@ -2311,16 +2147,21 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2311
2147
  createPreparedResolverCaches,
2312
2148
  });
2313
2149
  return {
2150
+ instances: instanceCollections,
2151
+ initializeSsr: ensureSsrFormatProbe,
2314
2152
  get instanceCollectionsInspection(): readonly InstanceCollectionInspection[] {
2315
- return instanceCollections._inspections(
2316
- frameState.instanceResidency,
2317
- frameState.frameNumber - 1,
2318
- );
2153
+ return instanceCollections._inspections();
2319
2154
  },
2320
2155
  releaseProfilerCatalog(): void {
2321
2156
  releaseProfilerCatalog?.();
2322
2157
  releaseProfilerCatalog = undefined;
2323
2158
  },
2159
+ invalidateGeometryHistory(): void {
2160
+ resetDynamicGeometryTemporalHistory(frameState);
2161
+ },
2162
+ bounds(world, entity) {
2163
+ return persistentRenderScene.bounds(world, entity);
2164
+ },
2324
2165
  get renderScene(): RenderSceneInspection {
2325
2166
  return {
2326
2167
  ...persistentRenderScene.inspect(),
@@ -2334,32 +2175,46 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2334
2175
  return reflectionProbeOwner.inspect();
2335
2176
  },
2336
2177
  get ssrDependencies(): SsrDependenciesInspection {
2337
- ensureSsrFormatProbe();
2338
- const owner = reflectionProbeOwner.inspect();
2339
- const nextGeneration = owner.reflectionFallback.projectionGeneration;
2340
- const reset = resolveSsrAdmissionGeneration(ssrFallbackGeneration, nextGeneration);
2341
- if (reset.changed) {
2342
- frameState.temporalFrameTransaction.reset('signature-change');
2343
- frameState.temporalFrame = undefined;
2344
- frameState.temporalFrameInput = undefined;
2345
- frameState.lastSuccessfulTemporalView = undefined;
2346
- frameState.successfulTemporalFrameIndex = 0;
2347
- frameState.pendingTemporalCommit = { kind: 'none' };
2348
- }
2349
- ssrFallbackGeneration = reset.generation;
2350
- const temporal =
2351
- frameState.lastSuccessfulTemporalView === undefined
2352
- ? undefined
2353
- : ({
2354
- successfulSubmit: true,
2355
- generation: frameState.lastSuccessfulTemporalView.input.deviceGeneration,
2356
- } as const);
2357
- return projectSsrDependencies({
2358
- requested: frameState.reflectionFallbackDemand === true,
2359
- identity: internals.ssrIdentity,
2360
- reflectionFallback: owner.reflectionFallback,
2361
- format: ssrFormatReceipt,
2362
- 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 }),
2363
2218
  });
2364
2219
  },
2365
2220
  get reflectionFallbackCompletion(): Promise<void> | undefined {
@@ -2371,6 +2226,27 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2371
2226
  get environment(): EnvironmentInspection {
2372
2227
  return environmentLifecycle.inspect();
2373
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
+ },
2374
2250
  get temporal(): TemporalInspection {
2375
2251
  return inspectTemporal(frameState.lastSuccessfulTemporalView, undefined, {
2376
2252
  ...(frameState.activeTemporalGpuState === undefined
@@ -2510,6 +2386,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2510
2386
  return persistentRenderScene.pointsLinesSnapshots();
2511
2387
  },
2512
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
+ }
2513
2397
  persistentRenderScene.detach(world);
2514
2398
  },
2515
2399
  draw(
@@ -2525,6 +2409,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2525
2409
  const profileSession = internals.profiler?.activeSession();
2526
2410
  let ownsProfileFrame = false;
2527
2411
  let submitted = false;
2412
+ // A failed draw must never leave a previous frame eligible to publish a
2413
+ // newly accepted dynamic-geometry candidate.
2414
+ dynamicGeometryFrames.begin();
2528
2415
  if (profileSession !== undefined && opts.profileFrame === undefined) {
2529
2416
  try {
2530
2417
  ownsProfileFrame = profileSession.beginFrame(++directFrameId).ok;
@@ -2537,7 +2424,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2537
2424
  // cameraOwner drives the surfaced cameras + frustum
2538
2425
  // cull; resourceOwner drives skylight/skybox/postProcess + per-world
2539
2426
  // record config.
2540
- const { cameraOwner, resourceOwner } = composition.owners;
2427
+ const { cameraOwner, resourceOwner, cameraEntityKey } = composition.owners;
2541
2428
  // Keep the compiled graph as last-known-good until the candidate topology
2542
2429
  // compiles. The failed candidate frame does not execute it; successful
2543
2430
  // replacement retires it atomically in ensureCompiledFrameGraph.
@@ -2551,37 +2438,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2551
2438
  }
2552
2439
  dispatchCounts.unlit = 0;
2553
2440
 
2554
- // feat-20260708-composited-multi-world-rendering M3 / D-2 / m3-i2:
2555
- // extractFrames merges per-world snapshots (renderables + lights from
2556
- // every world, cameras + singleton resources from the owner world),
2557
- // runs read-only extractFrame per world over the already-updated World,
2558
- // resets frame state once, and isolates
2559
- // per-world errors (AC-09).
2560
- // Single-world draw([world], { cameraOwner: 0, resourceOwner: 0 }) is the identity path
2561
- // (worldId=0), byte-for-byte equivalent to the pre-M3 direct
2562
- // extractFrame path (AC-03 regression guarantee).
2563
- //
2564
- // Entry validation (empty worlds / owner out of range) is enforced by
2565
- // the public renderer.draw facade (createRenderer.ts, D-5) before this
2566
- // internal method is reached; the owner world is guaranteed present.
2567
- //
2568
- // feat-20260709-editor-world-partition M1 / w5+w6 (D-3): per-world
2569
- // record configuration (transparent-sort mode, fold buckets, skybox /
2570
- // asset resolution) is read from the resource-owner world — the world
2571
- // that owns skylight / skybox / postProcessParams. w6 sources it from
2572
- // the dedicated resourceOwner index. The variable is named
2573
- // `resourceWorld` so the record-stage reads are self-describing (they
2574
- // are resource-owner reads, not camera reads).
2575
2441
  const resourceWorld = compositionWorlds[resourceOwner] as World;
2576
2442
  const frame = runProfiledRenderPhase(profileSession, 'extract', () => {
2577
2443
  return persistentRenderScene.extractComposition(
2578
2444
  compositionWorlds,
2579
2445
  { cameraOwner, resourceOwner },
2580
2446
  internals.assets.catalogEpoch,
2581
- (request: PersistentRenderCandidateRequest) =>
2447
+ (renderables) =>
2582
2448
  extractFrames(
2583
2449
  compositionWorlds,
2584
- { cameraOwner, resourceOwner },
2450
+ {
2451
+ cameraOwner,
2452
+ resourceOwner,
2453
+ ...(cameraEntityKey === undefined ? {} : { cameraEntityKey }),
2454
+ },
2585
2455
  internals.assets,
2586
2456
  internals.getPipelineState(),
2587
2457
  persistentRenderScene.materialSnapshotCacheStore(),
@@ -2606,13 +2476,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2606
2476
  },
2607
2477
  }),
2608
2478
  cull: 'none',
2609
- renderables: request === 'none' ? 'none' : 'full',
2610
- ...(request === 'none' || request === 'full'
2611
- ? {}
2612
- : { renderableEntitiesByWorld: request.entitiesByWorld }),
2613
2479
  retainHidden: true,
2480
+ renderables,
2614
2481
  instanceCollections,
2615
- ...(internals.getMaterialShaderArtifact === undefined
2482
+ ...(!internals.device.caps.storageBuffer ||
2483
+ internals.getMaterialShaderArtifact === undefined
2616
2484
  ? {}
2617
2485
  : { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
2618
2486
  },
@@ -2621,7 +2489,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2621
2489
  );
2622
2490
  });
2623
2491
  const {
2624
- cameras,
2492
+ cameras: extractedCameras,
2625
2493
  cubeCameras,
2626
2494
  lights,
2627
2495
  environment,
@@ -2629,6 +2497,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2629
2497
  volumetricFog,
2630
2498
  renderables,
2631
2499
  dispatch,
2500
+ shadowCasterEntityKeys: extractedShadowCasterEntityKeys,
2501
+ shadowCasterDrawKeys: extractedShadowCasterDrawKeys,
2502
+ shadowCasterMembership: extractedShadowCasterMembership,
2632
2503
  skylight,
2633
2504
  skylightCount,
2634
2505
  skybox,
@@ -2638,6 +2509,28 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2638
2509
  visibilityStats,
2639
2510
  postProcessParams,
2640
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();
2641
2534
  lastDirectionalShadowLights = lights;
2642
2535
  lastDirectionalShadowCandidate = 'failed';
2643
2536
  lastDirectionalShadowError = lights.directionalShadowError;
@@ -2649,6 +2542,26 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2649
2542
  skylight,
2650
2543
  );
2651
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;
2652
2565
  persistentRenderScene.updateVisibilityFacet(compositionWorlds, latestCamera, renderables);
2653
2566
  let occlusionProjection:
2654
2567
  | import('./scene/visibility/occlusion-runtime').OcclusionFrameProjection
@@ -2920,6 +2833,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2920
2833
  if (temporalDemanded) {
2921
2834
  persistentRenderScene.prepareTemporalFrame(submissionRenderables);
2922
2835
  }
2836
+ frameState.environmentFrame = environment as
2837
+ | import('./environment/frame').SelectedEnvironmentFrame
2838
+ | undefined;
2923
2839
  environmentLifecycle.bindScope(internals.deviceScope);
2924
2840
  if (fogFailure !== undefined) {
2925
2841
  environmentLifecycle.recordSelectionFailure(fogFailure);
@@ -2950,8 +2866,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2950
2866
  updateTransmissionInspection(transmissionAntialias, false);
2951
2867
  return false;
2952
2868
  }
2953
- if (lastMeshMaterialBindingFrame !== frame) {
2954
- lastMeshMaterialBindingFrame = frame;
2869
+ if (lastMeshMaterialBindingRenderables !== renderables) {
2870
+ lastMeshMaterialBindingRenderables = renderables;
2955
2871
  lastMeshMaterialBindings = renderables.map((renderable) => {
2956
2872
  const materialWorld = preparedWorlds[renderable.worldId];
2957
2873
  return projectMeshMaterialBindingObservation({
@@ -3004,6 +2920,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3004
2920
  lastFrustumStats.culled = frustumStats.culled;
3005
2921
  lastFrustumStats.total = frustumStats.total;
3006
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
+ }
3007
2932
  const featureTargets =
3008
2933
  preparedPipelineState === null || cameras[0] === undefined
3009
2934
  ? []
@@ -3014,6 +2939,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3014
2939
  storageBuffer: internals.device.caps.storageBuffer,
3015
2940
  multisample: internals.device.caps.backendKind !== 'wgpu-webgl2',
3016
2941
  });
2942
+ preparedFeatureSkylightResources =
2943
+ preparedPipelineState === null
2944
+ ? undefined
2945
+ : resolveMaterialSkylight(internals, preparedPipelineState, skylight, skylightCount)
2946
+ .skylightResources;
3017
2947
  let featureGraphCandidate: RenderFeatureGraphCandidate | undefined;
3018
2948
  const preparedResourceBatches = runProfiledRenderPhase(profileSession, 'features', () => {
3019
2949
  if (internals.featureHost === undefined) return [];
@@ -3029,6 +2959,28 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3029
2959
  ...(internals.getMaterialShaderBindingContract === undefined
3030
2960
  ? {}
3031
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
+ }),
3032
2984
  createPreparedGraphicsResolver: preparedResolverFactory,
3033
2985
  gpuWork: featureGpuWork,
3034
2986
  });
@@ -3052,10 +3004,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3052
3004
  featureGraphCandidate = {
3053
3005
  plans: featureFrame.plans,
3054
3006
  fullscreenEffects: featureFrame.fullscreenEffects,
3055
- ...(featureFrame.preparedResourceBatches.length === 0
3056
- ? {}
3057
- : { preparedResourceKey: `frame-${frameState.frameNumber}` }),
3007
+ ...(featureFrame.requiresPreparedResourceKey
3008
+ ? { preparedResourceKey: `frame-${frameState.frameNumber}` }
3009
+ : {}),
3058
3010
  onRejected: () => {
3011
+ featureFrame.onAborted();
3059
3012
  for (const batch of featureFrame.preparedResourceBatches) {
3060
3013
  const released = batch.release();
3061
3014
  if (!released.ok) {
@@ -3065,8 +3018,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3065
3018
  discardPostProcessParamsCandidate(postProcessParamsCandidate.value);
3066
3019
  },
3067
3020
  onAbandoned: () => {
3021
+ featureFrame.onAborted();
3068
3022
  discardPostProcessParamsCandidate(postProcessParamsCandidate.value);
3069
3023
  },
3024
+ onSubmitted: () => {
3025
+ featureFrame.onSubmitted();
3026
+ },
3027
+ onAborted: () => {
3028
+ featureFrame.onAborted();
3029
+ },
3070
3030
  onAccepted: () => {
3071
3031
  for (const [id, previous] of activeFeaturePostProcessEntries) {
3072
3032
  const next = featureFrame.fullscreenEffects.get(id);
@@ -3087,6 +3047,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3087
3047
  activeFeaturePostProcessEntries = featureFrame.fullscreenEffects;
3088
3048
  },
3089
3049
  };
3050
+ recordRenderFeatureCandidate(internals, featureGraphCandidate);
3090
3051
  return featureFrame.preparedResourceBatches;
3091
3052
  });
3092
3053
 
@@ -3164,13 +3125,32 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3164
3125
  if (cameras[0]?.bloom === 'on') {
3165
3126
  internals.getPipelineState()?.perPassResources.ensureBloomResources?.();
3166
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
+ }
3167
3147
  submitted = runProfiledRenderPhase(profileSession, 'record', () =>
3168
3148
  recordFrame(
3169
3149
  internals,
3170
3150
  resourceWorld,
3171
3151
  cameras,
3172
3152
  lights,
3173
- [...submissionRenderables],
3153
+ submissionRenderables,
3174
3154
  orderedDispatch,
3175
3155
  frameState,
3176
3156
  dispatchCounts,
@@ -3206,18 +3186,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3206
3186
  environment?.fogSignature ?? '',
3207
3187
  environmentReady && environment !== undefined,
3208
3188
  persistentRenderScene.transmissionTopologyDemand(),
3189
+ shadowCasterEntityKeys,
3190
+ shadowCasterDrawKeys,
3209
3191
  volumetricFog,
3210
3192
  timingCapture,
3193
+ shadowCasterMembership,
3194
+ shadowCasterProjection,
3195
+ worldKeys,
3196
+ undefined,
3197
+ ssrDependencies,
3198
+ dynamicGeometryFrames.onRenderableDraw(compositionWorlds),
3211
3199
  ),
3212
3200
  );
3213
- if (submitted) {
3214
- try {
3215
- internals.recoveryColdWorkGuard?.finish();
3216
- } catch (cause) {
3217
- submitted = false;
3218
- throw cause;
3219
- }
3220
- }
3201
+ dynamicGeometryFrames.commit(submitted);
3202
+ if (!submitted) discardLiveFeatureState();
3221
3203
  if (submitted && lastLodOcclusionInspection !== undefined) {
3222
3204
  const query = ensureOcclusionRuntime().inspect();
3223
3205
  lastLodOcclusionInspection = {
@@ -3312,6 +3294,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3312
3294
  }
3313
3295
  }
3314
3296
  } catch (err) {
3297
+ discardLiveFeatureState();
3315
3298
  if (!submitted) {
3316
3299
  internals.getPipelineState()?.perPassResources.discardBloomResources?.();
3317
3300
  }
@@ -3325,7 +3308,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3325
3308
  }
3326
3309
  : err instanceof RhiError
3327
3310
  ? err
3328
- : { 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
+ };
3329
3316
  internals.errorRegistry.fire(
3330
3317
  new RhiError({
3331
3318
  code: 'webgpu-runtime-error',
@@ -3345,6 +3332,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3345
3332
  }
3346
3333
  return submitted;
3347
3334
  },
3335
+ isDynamicGeometryConsumed: (world, entity, meshHandle) =>
3336
+ dynamicGeometryFrames.isConsumed(world, entity, meshHandle),
3348
3337
  pipelineDispatchCounts: dispatchCounts,
3349
3338
  observeCurrentFrame(options: FrameObservationOptions) {
3350
3339
  const currentFrameId = frameState.frameNumber - 1;
@@ -3415,6 +3404,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3415
3404
  get perFrameGraphInfo(): CompiledRenderGraphInfo | undefined {
3416
3405
  return frameState.compiledFrameGraph?.inspect();
3417
3406
  },
3407
+ get featureGraphInspection(): import('./inspection-types').RenderFeatureGraphInspection {
3408
+ return getRenderFeatureGraphInspection(internals);
3409
+ },
3410
+ get materialTextureSources(): MaterialTextureSourceInspection {
3411
+ return lastMaterialTextureSources;
3412
+ },
3418
3413
  get recoveryEvidence(): RecoveryProductionEvidence {
3419
3414
  const graph = frameState.compiledFrameGraph?.inspect();
3420
3415
  return Object.freeze({
@@ -3541,6 +3536,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3541
3536
  (internals as TimingSessionHost).gpuPassTimingSubmittedWork = undefined;
3542
3537
  (internals as TimingSessionHost).gpuPassTimingFrameIdentity = undefined;
3543
3538
  (internals as TimingSessionHost).gpuPassTimingBeginReason = undefined;
3539
+ internals.getPipelineState()?.skinPaletteAllocator?.dispose();
3544
3540
  pointsLinesOwner.dispose();
3545
3541
  persistentRenderScene.dispose();
3546
3542
  instanceCollections.dispose();
@@ -3548,6 +3544,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3548
3544
  occlusionRuntime?.dispose();
3549
3545
  disposeFeatureGpuWork();
3550
3546
  reflectionProbeOwner.dispose();
3547
+ frameState.ssrHistoryOwner?.dispose();
3548
+ frameState.ssrHistoryOwner = undefined;
3549
+ frameState.ssrHistoryCandidate = undefined;
3550
+ frameState.ssrSpatialAdmission = undefined;
3551
3551
  // Bloom generations are renderer-owned rather than graph-owned. Drain
3552
3552
  // candidate, active, and fence-retiring bundles before the rest of the
3553
3553
  // frame state is torn down; the owner callback is idempotent so the
@@ -3568,6 +3568,19 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3568
3568
  frameState.lastSuccessfulCameraAntialias = undefined;
3569
3569
  frameState.currentDirectionalShadowView = null;
3570
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;
3571
3584
  if (frameState.temporalGpuState !== undefined) {
3572
3585
  retireTemporalGpuState(frameState.temporalGpuState);
3573
3586
  frameState.temporalGpuState = undefined;
@@ -3613,7 +3626,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3613
3626
  // so destroy failures fire structured errors (unified per-frame +
3614
3627
  // dispose error strategy).
3615
3628
  disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
3616
- frameState.instanceResidency?.clear();
3617
3629
  if (frameState.instanceBufferChunks !== undefined) {
3618
3630
  disposeInstanceBufferChunks(frameState.instanceBufferChunks, internals.errorRegistry);
3619
3631
  }
@@ -3654,9 +3666,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3654
3666
  // their handles without invoking destroy on a device that can no longer
3655
3667
  // service cleanup; the staged candidate owns the replacement buffers.
3656
3668
  pointsLinesOwner.abandonForDeviceLoss();
3657
- ssrFormatReceipt = undefined;
3658
- ssrFormatProbeGeneration = -1;
3659
- ssrFormatProbe = undefined;
3669
+ resetStandardOutputForDeviceLoss(frameState, internals.deviceScope.generation);
3670
+ ssrFormatReceipts.delete(internals.device);
3660
3671
  ssrFallbackGeneration = undefined;
3661
3672
  transmissionAdmission.markDeviceLost();
3662
3673
  lastTransmissionKey = '';
@@ -3674,13 +3685,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3674
3685
  updateTransmissionInspection(lastTransmissionAntialias, false);
3675
3686
  persistentRenderScene.resetGpuForRecover();
3676
3687
  gpuDrivenProduction.dispose();
3677
- // The active renderer still points at the lost device until the caller
3678
- // completes the synchronous publication boundary. Bind the replacement
3679
- // explicitly so the first published draw never routes through an owner
3680
- // recreated against the old physical device.
3681
- gpuDrivenProduction = GpuDrivenProduction.forDevice(
3688
+ gpuDrivenRecoveryCount += 1;
3689
+ gpuDrivenProduction = createGpuDrivenOwner(
3682
3690
  replacementDevice ?? internals.device,
3683
3691
  gpuDrivenShaderFactory,
3692
+ gpuDrivenRecoveryCount,
3684
3693
  );
3685
3694
  occlusionRuntime?.dispose();
3686
3695
  // The replacement device is installed by the host after this hook. Do
@@ -3690,6 +3699,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3690
3699
  disposeFeatureGpuWork();
3691
3700
  featureGpuWork = createFeatureGpuWorkOwner(internals);
3692
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;
3693
3709
  // A recovered device cannot retain handles from the lost device. The
3694
3710
  // Bloom owner abandons candidates and retires every committed generation
3695
3711
  // before the new PipelineState is rebuilt.
@@ -3759,7 +3775,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3759
3775
  frameState.retiredCompiledFrameGraphs.clear();
3760
3776
  frameState.instanceBuffers.clear();
3761
3777
  frameState.instanceBufferChunks?.clear();
3762
- frameState.instanceResidency?.clear();
3778
+ instanceCollections._resetResidency();
3763
3779
  delete frameState.probeBlendRecordBuffer;
3764
3780
  frameState.probeBlendRecordBufferCapacity = 0;
3765
3781
  frameState.probeBlendBuffers.clear();
@@ -3802,124 +3818,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3802
3818
  publishRecoveryPostProcessResources(prepared.value);
3803
3819
  },
3804
3820
  prepareRecoveryRoots(runtime: RecoveryRootRuntime): RecoveryRootBundle {
3805
- // Snapshot only CPU projections/declarations here. The root create
3806
- // callbacks below must never close over a mutable active owner while
3807
- // LifecycleTransaction is awaiting candidate construction.
3808
- const candidateEnvironmentLifecycle = environmentLifecycle.createRecoveryCandidate(
3809
- runtime.scope,
3810
- );
3811
- const roots: LifecycleResourceSpec<unknown>[] = [];
3812
- let candidateEnvironmentGeneration: EnvironmentGeneration | undefined;
3813
- let environmentRootCleaned = false;
3814
- let published = false;
3815
- if (candidateEnvironmentLifecycle.hasRecoveryFrame()) {
3816
- const environmentRoot = candidateEnvironmentLifecycle.createRecoveryRoot(runtime.scope);
3817
- roots.push({
3818
- ...environmentRoot,
3819
- create: async () => {
3820
- const generation = (await environmentRoot.create()) as EnvironmentGeneration;
3821
- candidateEnvironmentGeneration = generation;
3822
- return generation;
3823
- },
3824
- cleanup: (value) => {
3825
- if (environmentRootCleaned) return;
3826
- environmentRootCleaned = true;
3827
- if (candidateEnvironmentGeneration === value) {
3828
- candidateEnvironmentGeneration = undefined;
3829
- }
3830
- if (published) {
3831
- candidateEnvironmentLifecycle.retirePublishedGeneration(
3832
- value as EnvironmentGeneration,
3833
- );
3834
- } else {
3835
- environmentRoot.cleanup(value);
3836
- }
3837
- },
3838
- });
3839
- }
3840
- const graphCandidate = runtime.graphCandidate;
3841
- if (graphCandidate !== undefined) {
3842
- const releaseGraphCandidate = (): void => graphCandidate.release();
3843
- const compiledGraph = graphCandidate.frameState.compiledFrameGraph;
3844
- if (compiledGraph !== null) {
3845
- roots.push({
3846
- kind: 'pipeline',
3847
- create: () => {
3848
- if (!runtime.scope.isAlive()) {
3849
- throw new Error('Recovery graph candidate scope is not active.');
3850
- }
3851
- if (graphCandidate.frameState.compiledFrameGraph !== compiledGraph) {
3852
- throw new Error('Recovery graph candidate was changed before aggregate assembly.');
3853
- }
3854
- return compiledGraph;
3855
- },
3856
- // All graph/feature/GPU-driven candidate resources share one
3857
- // owner. The root is a real release seam, not a marker; the
3858
- // candidate latch makes every root cleanup call harmless after
3859
- // the first one.
3860
- cleanup: releaseGraphCandidate,
3861
- });
3862
- }
3863
- if (graphCandidate.featureHost !== undefined) {
3864
- const featureRoot = graphCandidate.featureHost.createRecoveryRoot(runtime.scope);
3865
- roots.push({ ...featureRoot, cleanup: releaseGraphCandidate });
3866
- }
3867
- if (graphCandidate.featureGpuWork !== undefined) {
3868
- const featureWorkRoot = graphCandidate.featureGpuWork.createRecoveryRoot(runtime.scope);
3869
- roots.push({ ...featureWorkRoot, cleanup: releaseGraphCandidate });
3870
- }
3871
- if (graphCandidate.gpuDrivenProduction !== undefined) {
3872
- const productionRoot = graphCandidate.gpuDrivenProduction.createRecoveryRoot(
3873
- runtime.scope,
3874
- );
3875
- roots.push({ ...productionRoot, cleanup: releaseGraphCandidate });
3876
- }
3877
- if (graphCandidate.gpuDrivenScene?.state !== undefined) {
3878
- const sceneRoot = graphCandidate.gpuDrivenScene.createRecoveryRoot(runtime.scope);
3879
- roots.push({ ...sceneRoot, cleanup: releaseGraphCandidate });
3880
- }
3881
- if (graphCandidate.pointsLines !== undefined) {
3882
- const pointsLinesRoot = graphCandidate.pointsLines.createRecoveryRoot(runtime.scope);
3883
- roots.push({ ...pointsLinesRoot, cleanup: releaseGraphCandidate });
3884
- }
3885
- if (runtime.gpuStore.recoveryResourceCount() > 0) {
3886
- const gpuStoreRoot = runtime.gpuStore.createRecoveryRoot(runtime.scope);
3887
- roots.push({ ...gpuStoreRoot, cleanup: releaseGraphCandidate });
3888
- }
3889
- }
3890
- let discarded = false;
3891
- return {
3892
- roots: Object.freeze(roots),
3893
- publish: () => {
3894
- if (published || discarded) return;
3895
- if (
3896
- candidateEnvironmentGeneration !== undefined &&
3897
- !candidateEnvironmentGeneration.retired
3898
- ) {
3899
- environmentLifecycle.retireActiveForReplacement();
3900
- candidateEnvironmentLifecycle.publish(candidateEnvironmentGeneration);
3901
- }
3902
- published = true;
3903
- environmentLifecycle = candidateEnvironmentLifecycle;
3904
- Object.assign(frameState, { environmentLifecycle });
3905
- },
3906
- discard: () => {
3907
- if (published || discarded) return;
3908
- discarded = true;
3909
- if (candidateEnvironmentGeneration !== undefined) {
3910
- candidateEnvironmentLifecycle.discard(candidateEnvironmentGeneration);
3911
- candidateEnvironmentGeneration = undefined;
3912
- }
3913
- candidateEnvironmentLifecycle.discardRecoveryCandidates();
3914
- // The transaction can fail before it reaches any graph root (for
3915
- // example while the environment root is created). Keep the graph,
3916
- // feature, GPU-driven, and residency resources on the same real
3917
- // candidate owner in that case; later scope/factory cleanup is a
3918
- // harmless repeat through the candidate latch.
3919
- graphCandidate?.release();
3920
- runtime.scope.abandon();
3921
- },
3922
- };
3821
+ return prepareRenderSystemRecoveryRoots(runtime, environmentLifecycle, frameState, (next) => {
3822
+ environmentLifecycle = next;
3823
+ });
3923
3824
  },
3924
3825
  };
3925
3826
  }