@forgeax/engine-render 0.1.32 → 0.1.34

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 (624) hide show
  1. package/README.md +1374 -142
  2. package/dist/assembly/analytic-fog-registration.d.ts +3 -0
  3. package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
  4. package/dist/assembly/backend-contract.d.ts +2 -0
  5. package/dist/assembly/backend-contract.d.ts.map +1 -1
  6. package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
  7. package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
  8. package/dist/assembly/canvas-draw-guard.d.ts +3 -0
  9. package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
  10. package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
  11. package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
  12. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  13. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  14. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  15. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  16. package/dist/assembly/factory.d.ts +1 -1
  17. package/dist/assembly/factory.d.ts.map +1 -1
  18. package/dist/assembly/fxaa-registration.d.ts +4 -0
  19. package/dist/assembly/fxaa-registration.d.ts.map +1 -0
  20. package/dist/assembly/host-contract.d.ts +3 -3
  21. package/dist/assembly/host-contract.d.ts.map +1 -1
  22. package/dist/assembly/lod-observation.d.ts +4 -0
  23. package/dist/assembly/lod-observation.d.ts.map +1 -0
  24. package/dist/assembly/material/assembly.d.ts.map +1 -1
  25. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  26. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  27. package/dist/assembly/material-shader-policy.d.ts +16 -2
  28. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  29. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  30. package/dist/assembly/motion-blur-registration.d.ts +3 -0
  31. package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
  32. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  33. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  34. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  35. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  36. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  37. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  38. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  39. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  40. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  41. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  42. package/dist/assembly/renderer-inspection.d.ts +11 -0
  43. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  44. package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
  45. package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  47. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  48. package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
  49. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
  50. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  51. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  52. package/dist/assembly/webgpu-ready.d.ts +2 -2
  53. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  54. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  55. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  56. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  57. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  58. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  59. package/dist/authoring.mjs +1 -1
  60. package/dist/barrel-distortion.d.ts +47 -0
  61. package/dist/barrel-distortion.d.ts.map +1 -0
  62. package/dist/bloom-admission.d.ts +13 -0
  63. package/dist/bloom-admission.d.ts.map +1 -0
  64. package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
  65. package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
  66. package/dist/{chunk-JCIRYQ4V.mjs → chunk-HEB564WF.mjs} +10 -4
  67. package/dist/chunk-HEB564WF.mjs.map +1 -0
  68. package/dist/{chunk-JRIPCMYC.mjs → chunk-MKDIT2EM.mjs} +1584 -375
  69. package/dist/chunk-MKDIT2EM.mjs.map +1 -0
  70. package/dist/{chunk-S665NJNF.mjs → chunk-OCDLEI6I.mjs} +3 -3
  71. package/dist/{chunk-S665NJNF.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
  72. package/dist/chunk-ODZWR6JQ.mjs +4927 -0
  73. package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
  74. package/dist/{chunk-DRR7D3KX.mjs → chunk-SHFUFJ5N.mjs} +192 -18
  75. package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
  76. package/dist/{chunk-4NNZQCL3.mjs → chunk-UI5SFODX.mjs} +223 -277
  77. package/dist/chunk-UI5SFODX.mjs.map +1 -0
  78. package/dist/{chunk-GWCEMDVR.mjs → chunk-UINHLCZA.mjs} +787 -80
  79. package/dist/chunk-UINHLCZA.mjs.map +1 -0
  80. package/dist/{chunk-6TFBXLRJ.mjs → chunk-WEX45HKH.mjs} +222 -337
  81. package/dist/chunk-WEX45HKH.mjs.map +1 -0
  82. package/dist/cloud/density.d.ts +55 -0
  83. package/dist/cloud/density.d.ts.map +1 -0
  84. package/dist/cloud/extract.d.ts +25 -0
  85. package/dist/cloud/extract.d.ts.map +1 -0
  86. package/dist/cloud/feature.d.ts +57 -0
  87. package/dist/cloud/feature.d.ts.map +1 -0
  88. package/dist/cloud/frame-context.d.ts +8 -0
  89. package/dist/cloud/frame-context.d.ts.map +1 -0
  90. package/dist/cloud/inspection.d.ts +59 -0
  91. package/dist/cloud/inspection.d.ts.map +1 -0
  92. package/dist/cloud/optics.d.ts +56 -0
  93. package/dist/cloud/optics.d.ts.map +1 -0
  94. package/dist/cloud/parameters.d.ts +73 -0
  95. package/dist/cloud/parameters.d.ts.map +1 -0
  96. package/dist/cloud/resources.d.ts +60 -0
  97. package/dist/cloud/resources.d.ts.map +1 -0
  98. package/dist/cloud/shadow.d.ts +42 -0
  99. package/dist/cloud/shadow.d.ts.map +1 -0
  100. package/dist/cloud/temporal.d.ts +74 -0
  101. package/dist/cloud/temporal.d.ts.map +1 -0
  102. package/dist/components/atmosphere.d.ts +2 -0
  103. package/dist/components/atmosphere.d.ts.map +1 -1
  104. package/dist/components/barrel-distortion.d.ts +13 -0
  105. package/dist/components/barrel-distortion.d.ts.map +1 -0
  106. package/dist/components/camera.d.ts +20 -4
  107. package/dist/components/camera.d.ts.map +1 -1
  108. package/dist/components/cloud-layer.d.ts +29 -0
  109. package/dist/components/cloud-layer.d.ts.map +1 -0
  110. package/dist/components/index.d.ts +2 -0
  111. package/dist/components/index.d.ts.map +1 -1
  112. package/dist/components/instances.d.ts +20 -6
  113. package/dist/components/instances.d.ts.map +1 -1
  114. package/dist/components/mesh-filter.d.ts +6 -5
  115. package/dist/components/mesh-filter.d.ts.map +1 -1
  116. package/dist/components/motion-blur.d.ts +1 -0
  117. package/dist/components/motion-blur.d.ts.map +1 -1
  118. package/dist/construct-renderer.d.ts +1 -1
  119. package/dist/construct-renderer.d.ts.map +1 -1
  120. package/dist/construct-renderer.mjs +9751 -3528
  121. package/dist/construct-renderer.mjs.map +1 -1
  122. package/dist/device/gpu-residency.d.ts +6 -0
  123. package/dist/device/gpu-residency.d.ts.map +1 -1
  124. package/dist/dynamic-geometry.d.ts +5 -1
  125. package/dist/dynamic-geometry.d.ts.map +1 -1
  126. package/dist/environment/analytic-fog.d.ts +6 -0
  127. package/dist/environment/analytic-fog.d.ts.map +1 -0
  128. package/dist/errors/cloud.d.ts +59 -0
  129. package/dist/errors/cloud.d.ts.map +1 -0
  130. package/dist/errors/render.d.ts +23 -5
  131. package/dist/errors/render.d.ts.map +1 -1
  132. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  133. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  134. package/dist/extract/camera.d.ts.map +1 -1
  135. package/dist/extract/contracts.d.ts +12 -1
  136. package/dist/extract/contracts.d.ts.map +1 -1
  137. package/dist/extract/environment.d.ts +2 -0
  138. package/dist/extract/environment.d.ts.map +1 -1
  139. package/dist/features/barrel-distortion.d.ts +29 -0
  140. package/dist/features/barrel-distortion.d.ts.map +1 -0
  141. package/dist/features/error-reporting.d.ts +17 -0
  142. package/dist/features/error-reporting.d.ts.map +1 -0
  143. package/dist/features/host.d.ts +15 -0
  144. package/dist/features/host.d.ts.map +1 -1
  145. package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
  146. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
  147. package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
  148. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
  149. package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
  150. package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
  151. package/dist/features/plan.d.ts +15 -4
  152. package/dist/features/plan.d.ts.map +1 -1
  153. package/dist/features/prepared-gpu-work.d.ts +9 -5
  154. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  155. package/dist/features/render-graph-compute.d.ts.map +1 -1
  156. package/dist/features/render-graph-contribution.d.ts +5 -0
  157. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  158. package/dist/features/render-graph-raster.d.ts +1 -0
  159. package/dist/features/render-graph-raster.d.ts.map +1 -1
  160. package/dist/features/targets.d.ts +2 -0
  161. package/dist/features/targets.d.ts.map +1 -1
  162. package/dist/features/types.d.ts +44 -0
  163. package/dist/features/types.d.ts.map +1 -1
  164. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  165. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  166. package/dist/gpu-dirty-ranges.d.ts +11 -0
  167. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  168. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  169. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  170. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  171. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  172. package/dist/gpu-driven/prepared-draw.d.ts +7 -0
  173. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  174. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  175. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  176. package/dist/gpu-driven/production-raster.d.ts +50 -4
  177. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  178. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  179. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  180. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  181. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  182. package/dist/gpu-driven/view-gpu.d.ts +35 -2
  183. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  184. package/dist/gpu-scene.d.ts +11 -1
  185. package/dist/gpu-scene.d.ts.map +1 -1
  186. package/dist/hdrp-buffers.d.ts +2 -2
  187. package/dist/hdrp-buffers.d.ts.map +1 -1
  188. package/dist/ibl/skylight-bind-group.d.ts +10 -1
  189. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  190. package/dist/index.d.ts +31 -8
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.mjs +258 -8
  193. package/dist/index.mjs.map +1 -1
  194. package/dist/inspection-types.d.ts +178 -3
  195. package/dist/inspection-types.d.ts.map +1 -1
  196. package/dist/instance-buffer-cache.d.ts +4 -4
  197. package/dist/instance-buffer-cache.d.ts.map +1 -1
  198. package/dist/instances.d.ts +49 -94
  199. package/dist/instances.d.ts.map +1 -1
  200. package/dist/internal.mjs +7 -7
  201. package/dist/pbr-pipeline.d.ts +19 -1
  202. package/dist/pbr-pipeline.d.ts.map +1 -1
  203. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  204. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  205. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  206. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  207. package/dist/pipeline/standard-output/types.d.ts +4 -2
  208. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  209. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  210. package/dist/pipeline/standard-post.d.ts +28 -19
  211. package/dist/pipeline/standard-post.d.ts.map +1 -1
  212. package/dist/pipeline-builder.d.ts +1 -1
  213. package/dist/pipeline-builder.d.ts.map +1 -1
  214. package/dist/pipeline-spec.d.ts +2 -2
  215. package/dist/plugin.d.ts.map +1 -1
  216. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  217. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  218. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  219. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  220. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  221. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  222. package/dist/record/fold-buckets.d.ts +95 -0
  223. package/dist/record/fold-buckets.d.ts.map +1 -0
  224. package/dist/record/fold-instance-buffer.d.ts +6 -0
  225. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  226. package/dist/record/frame-lighting.d.ts +1 -0
  227. package/dist/record/frame-lighting.d.ts.map +1 -1
  228. package/dist/record/frame-snapshot.d.ts +40 -3
  229. package/dist/record/frame-snapshot.d.ts.map +1 -1
  230. package/dist/record/frame-targets.d.ts.map +1 -1
  231. package/dist/record/frame.d.ts +14 -62
  232. package/dist/record/frame.d.ts.map +1 -1
  233. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  234. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  235. package/dist/record/gpu-timing.d.ts +8 -0
  236. package/dist/record/gpu-timing.d.ts.map +1 -1
  237. package/dist/record/main-pass-geometry.d.ts +21 -5
  238. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  239. package/dist/record/main-pass-material.d.ts +7 -1
  240. package/dist/record/main-pass-material.d.ts.map +1 -1
  241. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  242. package/dist/record/main-pass.d.ts +13 -2
  243. package/dist/record/main-pass.d.ts.map +1 -1
  244. package/dist/record/mesh-ssbo.d.ts +6 -5
  245. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  246. package/dist/record/motion-blur-receipt.d.ts +11 -0
  247. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  248. package/dist/record/probe-blend-buffer.d.ts +16 -0
  249. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  250. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  251. package/dist/record/render-context.d.ts +34 -31
  252. package/dist/record/render-context.d.ts.map +1 -1
  253. package/dist/record/shadow-pass.d.ts +2 -0
  254. package/dist/record/shadow-pass.d.ts.map +1 -1
  255. package/dist/record/skybox-post-pass.d.ts +6 -4
  256. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  257. package/dist/record/typed-frame-graph.d.ts +22 -2
  258. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  259. package/dist/record/view-ubo.d.ts +11 -3
  260. package/dist/record/view-ubo.d.ts.map +1 -1
  261. package/dist/record/volume-params.d.ts +9 -0
  262. package/dist/record/volume-params.d.ts.map +1 -0
  263. package/dist/recovery/render-system-candidate.d.ts +10 -0
  264. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  265. package/dist/reflection/record-owner.d.ts.map +1 -1
  266. package/dist/render-contract.d.ts +66 -8
  267. package/dist/render-contract.d.ts.map +1 -1
  268. package/dist/render-graph-primitives.d.ts.map +1 -1
  269. package/dist/render-pipeline.d.ts +106 -3
  270. package/dist/render-pipeline.d.ts.map +1 -1
  271. package/dist/render-system-extract-tail.d.ts +3 -2
  272. package/dist/render-system-extract-tail.d.ts.map +1 -1
  273. package/dist/render-system-extract.d.ts +24 -10
  274. package/dist/render-system-extract.d.ts.map +1 -1
  275. package/dist/render-system-presentation.d.ts +23 -0
  276. package/dist/render-system-presentation.d.ts.map +1 -0
  277. package/dist/render-system-producer-roots.d.ts +10 -0
  278. package/dist/render-system-producer-roots.d.ts.map +1 -0
  279. package/dist/render-system-residency.d.ts +12 -0
  280. package/dist/render-system-residency.d.ts.map +1 -0
  281. package/dist/render-system.d.ts +26 -15
  282. package/dist/render-system.d.ts.map +1 -1
  283. package/dist/scene/probe-blend.d.ts +27 -0
  284. package/dist/scene/probe-blend.d.ts.map +1 -1
  285. package/dist/scene/render-scene.d.ts +14 -5
  286. package/dist/scene/render-scene.d.ts.map +1 -1
  287. package/dist/ssao-buffers.d.ts +9 -0
  288. package/dist/ssao-buffers.d.ts.map +1 -1
  289. package/dist/surface/dynamic-input.d.ts +133 -0
  290. package/dist/surface/dynamic-input.d.ts.map +1 -0
  291. package/dist/surface/gpu-driven.d.ts +41 -0
  292. package/dist/surface/gpu-driven.d.ts.map +1 -0
  293. package/dist/surface/submission-observation.d.ts +136 -0
  294. package/dist/surface/submission-observation.d.ts.map +1 -0
  295. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  296. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  297. package/dist/temporal/frame.d.ts +7 -0
  298. package/dist/temporal/frame.d.ts.map +1 -1
  299. package/dist/temporal/gpu.d.ts +13 -1
  300. package/dist/temporal/gpu.d.ts.map +1 -1
  301. package/dist/temporal/index.mjs +5 -5
  302. package/dist/temporal/inspection.d.ts.map +1 -1
  303. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  304. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  305. package/dist/temporal/view.d.ts +6 -1
  306. package/dist/temporal/view.d.ts.map +1 -1
  307. package/dist/transmission/single-layer-medium.d.ts +89 -0
  308. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  309. package/dist/typed-render-graph-primitives.d.ts +24 -9
  310. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  311. package/dist/volume/component.d.ts +9 -5
  312. package/dist/volume/component.d.ts.map +1 -1
  313. package/dist/volume/extract.d.ts +1 -1
  314. package/dist/volume/extract.d.ts.map +1 -1
  315. package/dist/volume/inspection.d.ts +2 -0
  316. package/dist/volume/inspection.d.ts.map +1 -1
  317. package/dist/volume/passes.d.ts +4 -1
  318. package/dist/volume/passes.d.ts.map +1 -1
  319. package/dist/volume/resources.d.ts.map +1 -1
  320. package/dist/volume/temporal.d.ts +3 -1
  321. package/dist/volume/temporal.d.ts.map +1 -1
  322. package/package.json +22 -21
  323. package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
  324. package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
  325. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  326. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  327. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  328. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  329. package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
  330. package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
  331. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  332. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  333. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  334. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  335. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
  336. package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
  337. package/src/__tests__/environment-selection.unit.test.ts +12 -0
  338. package/src/__tests__/factory-contract.integration.test.ts +626 -113
  339. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  340. package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
  341. package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
  342. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
  343. package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
  344. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
  345. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
  346. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
  347. package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
  348. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +2 -2
  349. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  350. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +7 -6
  351. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
  352. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +103 -4
  353. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  354. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  355. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  356. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  357. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  358. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  359. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  360. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
  361. package/src/__tests__/gpu-scene.unit.test.ts +210 -9
  362. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  363. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  364. package/src/__tests__/instances-store.unit.test.ts +105 -238
  365. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  366. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +146 -0
  367. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  368. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  369. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
  370. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  371. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  372. package/src/__tests__/motion-blur-compute.dawn.test.ts +589 -0
  373. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  374. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  375. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  376. package/src/__tests__/physical-clearcoat.integration.test.ts +58 -0
  377. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +21 -0
  378. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  379. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
  380. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  381. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  382. package/src/__tests__/public-surface.test-d.ts +8 -0
  383. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  384. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  385. package/src/__tests__/render-error-code-owner.test-d.ts +9 -0
  386. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  387. package/src/__tests__/render-feature-error-reporting.unit.test.ts +44 -0
  388. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  389. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  390. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
  391. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  392. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  393. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +37 -6
  394. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  395. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  396. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  397. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +41 -7
  398. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  399. package/src/__tests__/renderer-frame-transaction.integration.test.ts +33 -11
  400. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  401. package/src/__tests__/shader-manifest-fixture.ts +4 -4
  402. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  403. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  404. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  405. package/src/__tests__/skinned-shadow-caster.test.ts +17 -0
  406. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  407. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  408. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  409. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  410. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  411. package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
  412. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  413. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  414. package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
  415. package/src/__tests__/standard-pipeline.integration.test.ts +200 -9
  416. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  417. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  418. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  419. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  420. package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
  421. package/src/__tests__/temporal-view.unit.test.ts +2 -1
  422. package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
  423. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  424. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  425. package/src/__tests__/volume-optics-oracle.unit.test.ts +7 -3
  426. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +189 -15
  427. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  428. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  429. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  430. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +558 -68
  431. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
  432. package/src/__tests__/volumetric-fog-topology.unit.test.ts +23 -5
  433. package/src/assembly/analytic-fog-registration.ts +14 -0
  434. package/src/assembly/backend-contract.ts +30 -0
  435. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  436. package/src/assembly/canvas-draw-guard.ts +11 -0
  437. package/src/assembly/cloud-shadow-entry.ts +22 -0
  438. package/src/assembly/dynamic-geometry-host.ts +65 -37
  439. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  440. package/src/assembly/factory.ts +2 -30
  441. package/src/assembly/fxaa-registration.ts +17 -0
  442. package/src/assembly/host-contract.ts +3 -3
  443. package/src/assembly/lod-observation.ts +12 -0
  444. package/src/assembly/material/assembly.ts +25 -13
  445. package/src/assembly/material/pipeline-helpers.ts +53 -12
  446. package/src/assembly/material-shader-policy.ts +55 -0
  447. package/src/assembly/material-shader-prewarm.ts +21 -10
  448. package/src/assembly/motion-blur-registration.ts +17 -0
  449. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  450. package/src/assembly/recovery/renderer-recover.ts +41 -18
  451. package/src/assembly/render-feature-post-processes.ts +54 -0
  452. package/src/assembly/renderer-facade.ts +6 -1
  453. package/src/assembly/renderer-feature-inspection.ts +8 -2
  454. package/src/assembly/renderer-frame-transaction.ts +23 -0
  455. package/src/assembly/renderer-inspection.ts +28 -0
  456. package/src/assembly/skin-palette-owner.ts +1 -1
  457. package/src/assembly/temporal-post-process-prewarm.ts +40 -0
  458. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  459. package/src/assembly/webgpu-ready-contract.ts +8 -3
  460. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  461. package/src/assembly/webgpu-ready.ts +296 -306
  462. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  463. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  464. package/src/assembly/webgpu-renderer.ts +228 -281
  465. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  466. package/src/barrel-distortion.ts +247 -0
  467. package/src/bloom-admission.ts +21 -0
  468. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  469. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  470. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  471. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  472. package/src/cloud/density.ts +600 -0
  473. package/src/cloud/extract.ts +110 -0
  474. package/src/cloud/feature.ts +1810 -0
  475. package/src/cloud/frame-context.ts +42 -0
  476. package/src/cloud/inspection.ts +130 -0
  477. package/src/cloud/optics.ts +285 -0
  478. package/src/cloud/parameters.ts +255 -0
  479. package/src/cloud/resources.ts +122 -0
  480. package/src/cloud/shadow.ts +167 -0
  481. package/src/cloud/temporal.ts +226 -0
  482. package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
  483. package/src/components/atmosphere.ts +4 -0
  484. package/src/components/barrel-distortion.ts +46 -0
  485. package/src/components/camera.ts +66 -10
  486. package/src/components/cloud-layer.ts +47 -0
  487. package/src/components/index.ts +2 -0
  488. package/src/components/instances.ts +18 -45
  489. package/src/components/mesh-filter.ts +11 -9
  490. package/src/components/motion-blur.ts +1 -0
  491. package/src/components/sprite-instances.ts +1 -1
  492. package/src/construct-renderer.ts +1 -1
  493. package/src/device/gpu-residency.ts +56 -12
  494. package/src/dynamic-geometry.ts +12 -1
  495. package/src/environment/analytic-fog.ts +44 -0
  496. package/src/environment/background.ts +2 -2
  497. package/src/errors/cloud.ts +103 -0
  498. package/src/errors/render.ts +58 -8
  499. package/src/examples/single-layer-medium-surface.ts +59 -0
  500. package/src/extract/camera.ts +35 -8
  501. package/src/extract/contracts.ts +12 -0
  502. package/src/extract/environment.ts +4 -0
  503. package/src/features/barrel-distortion.ts +172 -0
  504. package/src/features/error-reporting.ts +98 -0
  505. package/src/features/host.ts +138 -24
  506. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
  507. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
  508. package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
  509. package/src/features/motion-blur/motion-blur-params.ts +70 -2
  510. package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
  511. package/src/features/plan.ts +33 -8
  512. package/src/features/prepared-gpu-work.ts +86 -35
  513. package/src/features/render-graph-compute.ts +13 -5
  514. package/src/features/render-graph-contribution.ts +11 -0
  515. package/src/features/render-graph-raster.ts +45 -3
  516. package/src/features/targets.ts +66 -0
  517. package/src/features/types.ts +47 -0
  518. package/src/fullscreen-post-process-pass.ts +41 -9
  519. package/src/gpu-dirty-ranges.ts +48 -0
  520. package/src/gpu-driven/batch-topology.ts +53 -0
  521. package/src/gpu-driven/material-artifacts.ts +35 -32
  522. package/src/gpu-driven/prepared-draw.ts +27 -14
  523. package/src/gpu-driven/production-raster-material.ts +1894 -0
  524. package/src/gpu-driven/production-raster.ts +904 -787
  525. package/src/gpu-driven/resource-allocation.ts +142 -0
  526. package/src/gpu-driven/shadow-ownership.ts +71 -0
  527. package/src/gpu-driven/view-gpu.ts +181 -10
  528. package/src/gpu-scene.ts +259 -101
  529. package/src/hdrp-buffers.ts +4 -3
  530. package/src/ibl/skylight-bind-group.ts +49 -7
  531. package/src/index.ts +228 -9
  532. package/src/inspection-types.ts +222 -2
  533. package/src/instance-buffer-cache.ts +4 -4
  534. package/src/instances.ts +151 -492
  535. package/src/pbr-pipeline.ts +219 -13
  536. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  537. package/src/pipeline/standard-forward-lane.ts +313 -57
  538. package/src/pipeline/standard-output/graph.ts +8 -2
  539. package/src/pipeline/standard-output/types.ts +12 -1
  540. package/src/pipeline/standard-pipeline.ts +291 -18
  541. package/src/pipeline/standard-post.ts +397 -102
  542. package/src/pipeline-builder.ts +1 -1
  543. package/src/pipeline-spec.ts +5 -5
  544. package/src/plugin.ts +4 -0
  545. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  546. package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
  547. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  548. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  549. package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
  550. package/src/record/__tests__/temporal-payload.unit.test.ts +3 -2
  551. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  552. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  553. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  554. package/src/record/barrel-distortion-frame.ts +110 -0
  555. package/src/record/dynamic-geometry-consumption.ts +39 -9
  556. package/src/record/fold-buckets.ts +852 -0
  557. package/src/record/fold-instance-buffer.ts +51 -0
  558. package/src/record/frame-lighting.ts +36 -2
  559. package/src/record/frame-snapshot.ts +44 -4
  560. package/src/record/frame-targets.ts +10 -6
  561. package/src/record/frame.ts +373 -1099
  562. package/src/record/gpu-draw-receipts.ts +38 -0
  563. package/src/record/gpu-timing.ts +48 -5
  564. package/src/record/main-pass-geometry.ts +281 -83
  565. package/src/record/main-pass-material.ts +66 -4
  566. package/src/record/main-pass-sprite-draws.ts +13 -66
  567. package/src/record/main-pass.ts +156 -20
  568. package/src/record/mesh-ssbo.ts +43 -6
  569. package/src/record/motion-blur-receipt.ts +91 -0
  570. package/src/record/probe-blend-buffer.ts +180 -0
  571. package/src/record/recovery-pipeline.ts +35 -3
  572. package/src/record/render-context.ts +51 -31
  573. package/src/record/shadow-pass.ts +36 -2
  574. package/src/record/skybox-post-pass.ts +150 -200
  575. package/src/record/typed-frame-graph.ts +824 -44
  576. package/src/record/view-ubo.ts +26 -3
  577. package/src/record/volume-params.ts +225 -0
  578. package/src/recovery/render-system-candidate.ts +257 -52
  579. package/src/reflection/record-owner.ts +7 -3
  580. package/src/render-contract.ts +71 -8
  581. package/src/render-graph-primitives.ts +7 -1
  582. package/src/render-pipeline.ts +136 -1
  583. package/src/render-system-extract-tail.ts +72 -60
  584. package/src/render-system-extract.ts +117 -87
  585. package/src/render-system-presentation.ts +56 -0
  586. package/src/render-system-producer-roots.ts +96 -0
  587. package/src/render-system-residency.ts +159 -0
  588. package/src/render-system.ts +601 -482
  589. package/src/scene/probe-blend.ts +96 -4
  590. package/src/scene/render-scene.ts +431 -281
  591. package/src/ssao-buffers.ts +95 -2
  592. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  593. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  594. package/src/surface/dynamic-input.ts +658 -0
  595. package/src/surface/gpu-driven.ts +147 -0
  596. package/src/surface/submission-observation.ts +517 -0
  597. package/src/systems/skin-palette-allocator.ts +4 -1
  598. package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
  599. package/src/temporal/frame-coordinator.ts +8 -1
  600. package/src/temporal/frame.ts +20 -0
  601. package/src/temporal/gpu.ts +101 -17
  602. package/src/temporal/inspection.ts +7 -1
  603. package/src/temporal/standard-scene-data.ts +5 -0
  604. package/src/temporal/target-coverage-attachment.ts +4 -0
  605. package/src/temporal/view.ts +13 -2
  606. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  607. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  608. package/src/transmission/single-layer-medium.ts +322 -0
  609. package/src/typed-render-graph-primitives.ts +191 -50
  610. package/src/volume/component.ts +10 -10
  611. package/src/volume/extract.ts +11 -5
  612. package/src/volume/inspection.ts +3 -0
  613. package/src/volume/passes.ts +95 -8
  614. package/src/volume/resources.ts +2 -1
  615. package/src/volume/temporal.ts +11 -0
  616. package/dist/chunk-4NNZQCL3.mjs.map +0 -1
  617. package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
  618. package/dist/chunk-DRR7D3KX.mjs.map +0 -1
  619. package/dist/chunk-GWCEMDVR.mjs.map +0 -1
  620. package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
  621. package/dist/chunk-JRIPCMYC.mjs.map +0 -1
  622. package/dist/chunk-LULCA3OF.mjs +0 -1713
  623. package/dist/chunk-LULCA3OF.mjs.map +0 -1
  624. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
@@ -1,45 +1,28 @@
1
1
  import { projectCaptureScene } from './capture/scene-projection';
2
2
  import type { RenderSceneBounds } from './scene/render-scene-types';
3
- // @forgeax/engine-runtime - RenderSystem main entry (D-S2 three-stage Extract ->
4
- // Prepare -> Record + 4-tier error fan-out).
5
- //
6
- // Engine-internal phase: NOT registered to World schedule (AC-09);
7
- // `Renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 })` invokes once per frame. See `Renderer` JSDoc in
8
- // `./render-contract.ts` for the public error/frame contract and AGENTS.md
9
- // "ECS render bridge" section for the AI-user-facing contract.
10
- //
11
- // Stage carve-out (review round 1 finding #3 - 505 line cap fallback):
12
- // - render-system.ts (this file) types + orchestration
13
- // - render-system-extract.ts Extract ECS query phase + snapshot helpers
14
- // - render-system-record.ts Prepare + Record GPU phase + matrix helpers
15
- //
16
3
  // @forgeax/engine-math is referenced through render-system-record.ts which builds
17
4
  // view/proj matrices and worldFromLocal via `mat4.compose / .multiply /
18
5
  // .invert` (charter proposition 5: no math reinvention; render-system.test.ts
19
6
  // asserts `/@forgeax\/engine-math/` shows up in render-system.ts source).
20
7
 
21
- import { numMipLevels, resolveAssetHandle } from '@forgeax/engine-assets-runtime';
8
+ import { numMipLevels } from '@forgeax/engine-assets-runtime';
22
9
  import { Time, type World } from '@forgeax/engine-ecs';
23
10
  import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
24
11
  import type { RecorderSession } from '@forgeax/engine-profiler';
25
12
  import type { CompiledRenderGraphInfo } from '@forgeax/engine-render-graph';
26
13
  import type { BindGroupLayout, Buffer, RenderPipeline, RhiDevice } from '@forgeax/engine-rhi';
27
14
  import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
28
- import {
29
- deriveTextureLayout,
30
- type MaterialAsset,
31
- type RenderPipelineAsset,
32
- type SamplerAsset,
33
- type TextureAsset,
34
- toShared,
35
- } from '@forgeax/engine-types';
15
+ import { type RenderPipelineAsset, toShared } from '@forgeax/engine-types';
36
16
  import {
37
17
  type DirectionalShadowInspectionSource,
38
18
  directionalShadowProfileFromQuality,
39
19
  projectDirectionalShadowInspectionSource,
40
20
  } from './assembly/directional-shadow-inspection';
41
21
  import { createGpuDrivenOwner } from './assembly/gpu-driven-owner';
22
+ import { standardBloomAdmitted } from './bloom-admission';
23
+ import { createCloudFeatureFrameContext, createCloudFeatureView } from './cloud/frame-context';
42
24
  import { createClusterBinScratch } from './cluster-binner';
25
+ import type { DynamicGeometryRecordStageLane } from './dynamic-geometry';
43
26
  import { EnvironmentLifecycle } from './environment/lifecycle';
44
27
  import {
45
28
  type ObservationUnavailableError,
@@ -48,11 +31,25 @@ import {
48
31
  } from './errors/render';
49
32
  import { projectDepthOfFieldInspection } from './features/depth-of-field/depth-of-field-inspection';
50
33
  import { resolveDepthOfFieldFrameParams } from './features/depth-of-field/depth-of-field-params';
34
+ import type { PendingRenderFeatureErrorReport } from './features/error-reporting';
35
+ import { reportPendingRenderFeatureErrors } from './features/error-reporting';
51
36
  import {
52
37
  type RenderFeaturePreparedGraphicsResolverInput,
53
38
  runRenderFeatureFrame,
54
39
  settlePreparedGraphicsCompletion,
55
40
  } from './features/host';
41
+ import {
42
+ createMotionBlurFeature,
43
+ type MotionBlurFeatureInput,
44
+ } from './features/motion-blur/motion-blur-feature';
45
+ import {
46
+ MotionBlurValidationError,
47
+ motionBlurTemporalDemand,
48
+ } from './features/motion-blur/motion-blur-params';
49
+ import {
50
+ planMotionBlurFrame,
51
+ projectMotionBlurInspection,
52
+ } from './features/motion-blur/motion-blur-runtime';
56
53
  import { createFeatureNoiseResolver } from './features/noise-texture';
57
54
  import {
58
55
  createRenderFeatureGpuWorkOwner,
@@ -69,6 +66,7 @@ import {
69
66
  postProcessShaderPipelineLabel,
70
67
  } from './fullscreen-post-process-pass';
71
68
  import type {
69
+ BarrelDistortionInspection,
72
70
  BloomInspection,
73
71
  DepthOfFieldInspection,
74
72
  DirectionalShadowInspection,
@@ -83,6 +81,7 @@ import type {
83
81
  TemporalTargetInspection,
84
82
  TransmissionInspection,
85
83
  } from './inspection-types';
84
+ import { emptyBloomInspection } from './inspection-types';
86
85
  import { buildOcclusionProxyVertices } from './occlusion-proxy';
87
86
  import {
88
87
  AUTO_EXPOSURE_HISTOGRAM_BYTES,
@@ -110,11 +109,7 @@ import {
110
109
  } from './pipeline/standard-output/resources';
111
110
  import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
112
111
  import { StandardPointsLinesOwner } from './points-lines/standard-owner';
113
- import {
114
- createRenderSystemRecovery,
115
- materialSamplerHandles,
116
- materialTextureHandles,
117
- } from './recovery/render-system-candidate';
112
+ import { createRenderSystemRecovery } from './recovery/render-system-candidate';
118
113
 
119
114
  export { materialTextureHandlesForResidency } from './recovery/render-system-candidate';
120
115
 
@@ -154,9 +149,10 @@ import { inspectVolumetricFog } from './volume/inspection';
154
149
 
155
150
  export type { RenderSceneInspection } from './inspection-types';
156
151
 
152
+ import type { BarrelDistortionMapping } from './barrel-distortion';
157
153
  import { createCubeCaptureScheduler } from './capture/scheduler';
158
154
  import type { EnvironmentInspection } from './environment/inspection';
159
- import { gpuDrivenShadowDrawKey } from './extract/gpu-driven';
155
+ import { rebaseShadowCasterOwnership } from './gpu-driven/shadow-ownership';
160
156
  import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from './gpu-usage';
161
157
  import { resetHdrpBuffers } from './hdrp-buffers';
162
158
  import type { SkylightBindGroupResources } from './ibl/skylight-bind-group';
@@ -166,11 +162,9 @@ import {
166
162
  disposeTransientInstanceBuffers,
167
163
  } from './instance-buffer-cache';
168
164
  import type { InstanceCollectionInspection } from './instances';
169
- import { InstanceCollectionStore } from './instances';
165
+ import { InstanceProjectionStore } from './instances';
170
166
  import {
171
167
  type MeshMaterialBindingObservation,
172
- type MeshMaterialBindingPreparationFailure,
173
- type MeshMaterialBindingResidency,
174
168
  projectMeshMaterialBindingObservation,
175
169
  } from './mesh-material-bindings';
176
170
  import type { StandardLightingInspection } from './pipeline/standard-lighting/inspection';
@@ -183,6 +177,10 @@ import {
183
177
  createPreparedGraphicsResolver,
184
178
  type PreparedGraphicsResolver,
185
179
  } from './prepare/prepared-graphics-resolver';
180
+ import {
181
+ inspectBarrelDistortionState,
182
+ resetBarrelDistortionGraphState,
183
+ } from './record/barrel-distortion-frame';
186
184
  import {
187
185
  type CubeCaptureFrameInput,
188
186
  type FrameObservation,
@@ -198,6 +196,7 @@ import type {
198
196
  RenderFrameState,
199
197
  } from './record/frame-snapshot';
200
198
  import { getTextureIdentity, worldEntityKey } from './record/frame-snapshot';
199
+ import type { RenderableDrawReceipt } from './record/gpu-draw-receipts';
201
200
  import type { GpuPassTimingReason } from './record/gpu-pass-timing/errors';
202
201
  import type {
203
202
  GpuPassTimingCapture,
@@ -215,34 +214,39 @@ import type { PipelineState, RenderSystemInternals } from './record/render-conte
215
214
  import type { CubeCaptureGraphState } from './record/typed-frame-graph';
216
215
  import {
217
216
  getRenderFeatureGraphInspection,
217
+ inspectRenderGraphGenerationAllocation,
218
218
  type RenderFeatureGraphCandidate,
219
219
  recordRenderFeatureCandidate,
220
220
  resetRenderFeatureGraphState,
221
+ retire as retireCompiledGraph,
222
+ settleBarrelDistortionGraphCandidate,
221
223
  settleDepthOfFieldGraphCandidate,
222
224
  settleVolumetricFogGraphCandidate,
223
225
  } from './record/typed-frame-graph';
224
226
  import {
225
227
  type CameraSnapshot,
226
228
  type DrawOwnerOptions,
229
+ type FramePresentation,
227
230
  RENDER_PHASE_CATALOG,
228
231
  type RenderPhase,
229
232
  type RenderPhaseSkipReason,
230
233
  type RenderRecordPhase,
231
234
  } from './render-contract';
232
- import type {
233
- DispatchEntry,
234
- ExtractedLights,
235
- MaterialSnapshot,
236
- RenderableSnapshot,
237
- ShadowCasterMembership,
238
- } from './render-system-extract';
235
+ import type { DispatchEntry, ExtractedLights, RenderableSnapshot } from './render-system-extract';
239
236
  import { extractFrames } from './render-system-extract';
237
+ import { projectFramePresentation } from './render-system-presentation';
238
+ import {
239
+ createRendererProducerRootMatrix,
240
+ type RendererProducerRoot,
241
+ } from './render-system-producer-roots';
240
242
  import {
241
243
  canonicalizeWorldComposition,
242
244
  createLodWorldInspections,
243
245
  } from './render-system-projections';
246
+ import { observeMaterialResidency } from './render-system-residency';
244
247
  import { PersistentRenderScene } from './scene/render-scene';
245
248
  import { inspectLodOcclusion } from './scene/visibility/inspection';
249
+ import { resetSsaoResources } from './ssao-buffers';
246
250
  import { resolveSsaoParameters } from './ssao-config';
247
251
  import {
248
252
  projectSsrDependencies,
@@ -251,6 +255,8 @@ import {
251
255
  zeroSsrAdmissionWork,
252
256
  } from './ssr/admission';
253
257
  import { projectSsrSpatialInspection } from './ssr/inspection';
258
+ import type { SurfaceDynamicInputFrame } from './surface/dynamic-input';
259
+ import { SurfaceSubmissionObservationOwner } from './surface/submission-observation';
254
260
  import { sortTransparentDispatch } from './systems/transparent-dispatch';
255
261
  import type { RenderTarget } from './targets/contracts';
256
262
  import { createTemporalFrameTransaction, type TemporalFrame } from './temporal/frame';
@@ -268,73 +274,6 @@ import {
268
274
  inspectTransmissionFromAdmission,
269
275
  } from './transmission/inspection';
270
276
 
271
- const WORLD_ENTITY_KEY_STRIDE = 4294967296;
272
-
273
- /**
274
- * Extract emits world/entity keys in the transient `worlds[]` index domain,
275
- * while PersistentRenderScene owns stable renderer-local World keys for the
276
- * retained GPU scene. Rebase ShadowCaster ownership at this orchestration
277
- * boundary before both GPU projection and CPU residual recording consume it.
278
- */
279
- function rebaseShadowCasterOwnership(
280
- worlds: readonly World[],
281
- stableWorldKeys: readonly number[],
282
- entityKeys: ReadonlySet<number>,
283
- drawKeys: ReadonlySet<string>,
284
- membership: readonly ShadowCasterMembership[] | undefined,
285
- ): {
286
- readonly entityKeys: ReadonlySet<number>;
287
- readonly drawKeys: ReadonlySet<string>;
288
- readonly membership: readonly ShadowCasterMembership[] | undefined;
289
- } {
290
- const rebaseWorldEntity = (composite: number): number => {
291
- const transientWorldId = Math.floor(composite / WORLD_ENTITY_KEY_STRIDE);
292
- if (
293
- !Number.isInteger(transientWorldId) ||
294
- transientWorldId < 0 ||
295
- transientWorldId >= worlds.length
296
- ) {
297
- return composite;
298
- }
299
- const entityKey = composite - transientWorldId * WORLD_ENTITY_KEY_STRIDE;
300
- return worldEntityKey(stableWorldKeys[transientWorldId] ?? transientWorldId, entityKey);
301
- };
302
-
303
- const stableMembership =
304
- membership === undefined
305
- ? undefined
306
- : membership.map((entry) => ({
307
- ...entry,
308
- worldEntity: rebaseWorldEntity(entry.worldEntity),
309
- }));
310
- const stableDrawKeys =
311
- stableMembership === undefined
312
- ? new Set(
313
- [...drawKeys].map((key) => {
314
- const parts = key.split(':');
315
- if (parts.length !== 4) return key;
316
- const transientWorldEntity = Number(parts[0]);
317
- if (!Number.isFinite(transientWorldEntity)) return key;
318
- return `${rebaseWorldEntity(transientWorldEntity)}:${parts[1]}:${parts[2]}:${parts[3]}`;
319
- }),
320
- )
321
- : new Set(
322
- stableMembership.map((entry) =>
323
- gpuDrivenShadowDrawKey(
324
- entry.worldEntity,
325
- entry.materialHandle,
326
- entry.drawItemIndex,
327
- entry.passIndex,
328
- ),
329
- ),
330
- );
331
- return {
332
- entityKeys: new Set([...entityKeys].map(rebaseWorldEntity)),
333
- drawKeys: stableDrawKeys,
334
- membership: stableMembership,
335
- };
336
- }
337
-
338
277
  type TimingSessionHost = RenderSystemInternals & {
339
278
  gpuPassTimingSession?: GpuPassTimingSession | undefined;
340
279
  gpuPassTimingCapture?: GpuPassTimingCapture | undefined;
@@ -371,102 +310,11 @@ export {
371
310
  selectSwapChainFormat,
372
311
  } from './record/render-context';
373
312
 
374
- export type RendererProducerRootKind =
375
- | 'backend-surface'
376
- | 'shader-material-pipeline'
377
- | 'mesh-texture-sampler'
378
- | 'render-scene'
379
- | 'gpu-driven'
380
- | 'feature'
381
- | 'external-source'
382
- | 'render-graph'
383
- | 'target-history'
384
- | 'observation-lease';
385
-
386
- export interface RendererProducerRoot {
387
- readonly kind: RendererProducerRootKind;
388
- readonly owner: string;
389
- readonly candidateScope: 'device-scope';
390
- readonly visibility: 'visible-workset' | 'non-visible-lazy';
391
- readonly disabledWork: 'zero' | 'lazy';
392
- }
393
-
394
- const PRODUCER_ROOT_MATRIX: readonly RendererProducerRoot[] = Object.freeze([
395
- {
396
- kind: 'backend-surface',
397
- owner: 'backend-surface-owner',
398
- candidateScope: 'device-scope',
399
- visibility: 'visible-workset',
400
- disabledWork: 'zero',
401
- },
402
- {
403
- kind: 'shader-material-pipeline',
404
- owner: 'shader-material-pipeline-owner',
405
- candidateScope: 'device-scope',
406
- visibility: 'visible-workset',
407
- disabledWork: 'zero',
408
- },
409
- {
410
- kind: 'mesh-texture-sampler',
411
- owner: 'gpu-residency',
412
- candidateScope: 'device-scope',
413
- visibility: 'visible-workset',
414
- disabledWork: 'lazy',
415
- },
416
- {
417
- kind: 'render-scene',
418
- owner: 'persistent-render-scene',
419
- candidateScope: 'device-scope',
420
- visibility: 'visible-workset',
421
- disabledWork: 'zero',
422
- },
423
- {
424
- kind: 'gpu-driven',
425
- owner: 'gpu-driven-production',
426
- candidateScope: 'device-scope',
427
- visibility: 'visible-workset',
428
- disabledWork: 'zero',
429
- },
430
- {
431
- kind: 'feature',
432
- owner: 'render-feature-host',
433
- candidateScope: 'device-scope',
434
- visibility: 'visible-workset',
435
- disabledWork: 'lazy',
436
- },
437
- {
438
- kind: 'external-source',
439
- owner: 'external-source-owner',
440
- candidateScope: 'device-scope',
441
- visibility: 'non-visible-lazy',
442
- disabledWork: 'zero',
443
- },
444
- {
445
- kind: 'render-graph',
446
- owner: 'typed-frame-graph',
447
- candidateScope: 'device-scope',
448
- visibility: 'visible-workset',
449
- disabledWork: 'zero',
450
- },
451
- {
452
- kind: 'target-history',
453
- owner: 'temporal-target-owner',
454
- candidateScope: 'device-scope',
455
- visibility: 'visible-workset',
456
- disabledWork: 'zero',
457
- },
458
- {
459
- kind: 'observation-lease',
460
- owner: 'render-observation-owner',
461
- candidateScope: 'device-scope',
462
- visibility: 'non-visible-lazy',
463
- disabledWork: 'zero',
464
- },
465
- ]);
466
-
467
- export function createRendererProducerRootMatrix(): readonly RendererProducerRoot[] {
468
- return PRODUCER_ROOT_MATRIX;
469
- }
313
+ export type {
314
+ RendererProducerRoot,
315
+ RendererProducerRootKind,
316
+ } from './render-system-producer-roots';
317
+ export { createRendererProducerRootMatrix } from './render-system-producer-roots';
470
318
 
471
319
  /**
472
320
  * Production-owned recovery facts. These values are projections of the live
@@ -505,6 +353,8 @@ export interface RecoveryProductionEvidence {
505
353
  */
506
354
  export interface RenderSystem {
507
355
  bounds(world: World, entity: number): RenderSceneBounds | undefined;
356
+ /** Publish one renderer-owned read-only Surface dynamic page for the next frame. */
357
+ setSurfaceDynamicInput(frame: SurfaceDynamicInputFrame | undefined): void;
508
358
  /** Complete explicit SSR capability preparation before drawing a device generation. */
509
359
  initializeSsr(device?: RhiDevice): Promise<void>;
510
360
  /** Returns true only when this invocation reached queue submission. */
@@ -520,6 +370,11 @@ export interface RenderSystem {
520
370
  * barrier; ECS component presence alone is not a draw receipt.
521
371
  */
522
372
  isDynamicGeometryConsumed(world: World, entity: number, meshHandle: number | undefined): boolean;
373
+ dynamicGeometryRecordStageLane(
374
+ world: World,
375
+ entity: number,
376
+ meshHandle: number | undefined,
377
+ ): DynamicGeometryRecordStageLane | undefined;
523
378
  invalidateGeometryHistory(): void;
524
379
  /** Prepare a detached graph candidate without entering the frame record path. */
525
380
  prepareRecoveryGraphCandidate(
@@ -542,7 +397,10 @@ export interface RenderSystem {
542
397
  options: FrameObservationOptions,
543
398
  ): Promise<Result<FrameObservation, ObservationUnavailableError>>;
544
399
  /** Resolve the renderer-owned GPU LOD counters for the last accepted submit. */
545
- observeLodOcclusion(): Promise<void>;
400
+ observeLodOcclusion(receipt?: {
401
+ readonly frameId: number;
402
+ readonly deviceGeneration: number;
403
+ }): Promise<void>;
546
404
  /** Internal same-frame graph target access for backend diagnostics. */
547
405
  getCurrentGraphTarget(name: string): CurrentGraphTarget | undefined;
548
406
  /** Internal test capture copied into the draw's command submission. */
@@ -550,8 +408,6 @@ export interface RenderSystem {
550
408
  readonly pipelineDispatchCounts: {
551
409
  readonly unlit: number;
552
410
  };
553
- /** Renderer-owned source for explicit large instance collections. */
554
- readonly instances: InstanceCollectionStore;
555
411
  /** Detached residency and upload facts for explicit instance collections. */
556
412
  readonly instanceCollectionsInspection: readonly InstanceCollectionInspection[];
557
413
  /**
@@ -570,6 +426,8 @@ export interface RenderSystem {
570
426
  readonly ssr: SsrSpatialInspection;
571
427
  /** Producer completion fence included in the public FrameReceipt. */
572
428
  readonly reflectionFallbackCompletion: Promise<void> | undefined;
429
+ /** Presentation readiness derived from the producer facts used by draw(). */
430
+ readonly presentation: FramePresentation;
573
431
  /** True while a progressive CubeCamera candidate still owns unfinished faces. */
574
432
  isCubeCapturePending(target: RenderTarget): boolean;
575
433
  readonly environment: EnvironmentInspection;
@@ -580,6 +438,8 @@ export interface RenderSystem {
580
438
  readonly temporal: TemporalInspection;
581
439
  readonly bloom: BloomInspection;
582
440
  readonly motionBlurInspection: MotionBlurInspection | undefined;
441
+ /** The last extraction validation failure, preserved for public draw(). */
442
+ readonly motionBlurInvalidParams: MotionBlurValidationError | undefined;
583
443
  readonly depthOfFieldInspection: DepthOfFieldInspection | undefined;
584
444
  readonly temporalTargetInspection: TemporalTargetInspection | undefined;
585
445
  readonly lodOcclusionInspection: LodOcclusionInspection | undefined;
@@ -610,8 +470,14 @@ export interface RenderSystem {
610
470
  readonly perFramePassNames: readonly string[];
611
471
  /** Camera antialias mode for the last successfully submitted frame. */
612
472
  readonly lastSuccessfulCameraAntialias: CameraSnapshot['antialias'] | undefined;
473
+ /** Effective output mapping for the last successfully submitted camera. */
474
+ readonly lastSuccessfulBarrelDistortion: BarrelDistortionMapping | undefined;
475
+ /** Detached accepted/LKG projection for Renderer.inspect(). */
476
+ readonly barrelDistortionInspection: BarrelDistortionInspection;
613
477
  /** Detached compiled-graph facts used by the renderer inspection owner. */
614
478
  readonly perFrameGraphInfo: CompiledRenderGraphInfo | undefined;
479
+ /** Detached allocation facts across active, candidate, and retiring graphs. */
480
+ readonly renderGraphGenerationAllocation: import('@forgeax/engine-render-graph').RenderGraphGenerationAllocationInspection;
615
481
  /** Detached feature signature/compile/lifetime counters. */
616
482
  readonly featureGraphInspection: import('./inspection-types').RenderFeatureGraphInspection;
617
483
  /** Detached material texture-source probe counters from the last extract. */
@@ -723,13 +589,6 @@ function reportPreparedGraphicsCompletionError(
723
589
  );
724
590
  }
725
591
 
726
- function isPendingRenderFeaturePreparation(error: RenderError): boolean {
727
- return (
728
- error.code === 'render-feature-preparation-failed' &&
729
- error.detail.reason.startsWith('rhi-not-available:')
730
- );
731
- }
732
-
733
592
  function makePreparedPipelinePendingError(): RhiError {
734
593
  return new RhiError({
735
594
  code: 'rhi-not-available',
@@ -738,149 +597,6 @@ function makePreparedPipelinePendingError(): RhiError {
738
597
  });
739
598
  }
740
599
 
741
- function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationFailure | undefined {
742
- if (typeof error !== 'object' || error === null) return undefined;
743
- const value = error as {
744
- readonly code?: unknown;
745
- readonly expected?: unknown;
746
- readonly hint?: unknown;
747
- readonly detail?: unknown;
748
- };
749
- if (
750
- typeof value.code !== 'string' ||
751
- typeof value.expected !== 'string' ||
752
- typeof value.hint !== 'string'
753
- ) {
754
- return undefined;
755
- }
756
- const detail =
757
- typeof value.detail === 'object' && value.detail !== null
758
- ? { ...(value.detail as Record<string, unknown>) }
759
- : undefined;
760
- return {
761
- code: value.code,
762
- expected: value.expected,
763
- hint: value.hint,
764
- ...(detail === undefined ? {} : { detail }),
765
- };
766
- }
767
-
768
- /**
769
- * Project the producer-owned material and GPU store state into the one
770
- * renderer observation surface. This is intentionally recomputed from the
771
- * current frame's detached material snapshots; no readiness map is retained
772
- * by RenderSystem. A producer failure with an already resident resource is
773
- * reported as last-known-good, preserving recovery context without copying
774
- * the producer's lifecycle state.
775
- */
776
- function observeMaterialResidency(
777
- world: World | undefined,
778
- material: MaterialSnapshot,
779
- internals: RenderSystemInternals,
780
- ): MeshMaterialBindingResidency {
781
- const textures: Array<{ readonly handle: number; readonly mipLevelCount: number }> = [];
782
- const samplers: Array<{ readonly handle: number; readonly resident: boolean }> = [];
783
- let pending = false;
784
- let resident = false;
785
- let failure: MeshMaterialBindingPreparationFailure | undefined;
786
- const keepFailure = (error: unknown): void => {
787
- if (failure === undefined) failure = preparationFailureFrom(error);
788
- };
789
-
790
- if (world === undefined) {
791
- pending = true;
792
- } else {
793
- const materialHandle = material.materialHandle;
794
- if (materialHandle !== undefined && materialHandle !== 0) {
795
- const materialResult = resolveAssetHandle<MaterialAsset>(
796
- world,
797
- toShared<'MaterialAsset'>(materialHandle),
798
- );
799
- if (!materialResult.ok) {
800
- keepFailure(materialResult.error);
801
- } else {
802
- const guid = internals.assets.guidOf(materialResult.value);
803
- if (guid !== undefined) {
804
- const readiness = internals.assets.getMaterialReadiness(guid);
805
- if (readiness?.status === 'Error') keepFailure(readiness.error);
806
- const load = internals.assets.loadState.get(guid);
807
- if (load?.status === 'provisional') pending = true;
808
- }
809
- }
810
- }
811
-
812
- for (const handle of materialTextureHandles(material)) {
813
- const existing = internals.gpuStore.getTextureGpuView(handle, world);
814
- const pod = resolveAssetHandle<TextureAsset>(world, handle);
815
- if (!pod.ok) {
816
- keepFailure(pod.error);
817
- continue;
818
- }
819
- let view = existing;
820
- if (view === undefined) {
821
- const prepared = internals.gpuStore.ensureResident(handle, pod.value, world);
822
- if (!prepared.ok) keepFailure(prepared.error);
823
- view = internals.gpuStore.getTextureGpuView(handle, world);
824
- }
825
- if (view === undefined) {
826
- pending = failure === undefined;
827
- continue;
828
- }
829
- const textureLayout = deriveTextureLayout({
830
- shape: pod.value.shape,
831
- format: pod.value.format,
832
- mips: pod.value.mips,
833
- });
834
- if (!textureLayout.ok) {
835
- keepFailure(textureLayout.error);
836
- continue;
837
- }
838
- resident = true;
839
- textures.push({
840
- handle: Number(handle),
841
- mipLevelCount: Math.max(1, textureLayout.value.levels.length),
842
- });
843
- }
844
-
845
- for (const handle of materialSamplerHandles(material)) {
846
- const pod = resolveAssetHandle<SamplerAsset>(world, handle);
847
- if (!pod.ok) {
848
- keepFailure(pod.error);
849
- samplers.push({ handle: Number(handle), resident: false });
850
- continue;
851
- }
852
- const prepared = internals.gpuStore.ensureSamplerResident(handle, pod.value, world);
853
- if (!prepared.ok) {
854
- keepFailure(prepared.error);
855
- samplers.push({ handle: Number(handle), resident: false });
856
- } else {
857
- resident = true;
858
- samplers.push({ handle: Number(handle), resident: true });
859
- }
860
- }
861
- // Video views are supplied by the dynamic host store at record time. They
862
- // are not static TextureAsset residents, so this bounded projection stays
863
- // pending until a dynamic observation contract exists rather than guessing
864
- // from the VideoAsset GUID.
865
- if ((material.videoTextureFields?.size ?? 0) > 0) pending = true;
866
- }
867
-
868
- const readiness =
869
- failure === undefined
870
- ? pending
871
- ? 'pending'
872
- : 'ready'
873
- : resident
874
- ? 'last-known-good'
875
- : 'failed';
876
- return {
877
- readiness,
878
- samplers,
879
- textures,
880
- ...(failure === undefined ? {} : { preparationFailure: failure }),
881
- };
882
- }
883
-
884
600
  export function createRenderSystem(internals: RenderSystemInternals): RenderSystem {
885
601
  const phaseCatalogRegistration = internals.profiler?.registerPhaseCatalog(
886
602
  'render',
@@ -896,9 +612,28 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
896
612
  });
897
613
  let preparedFeatureSkylightResources: SkylightBindGroupResources | undefined;
898
614
  const dynamicGeometryFrames = createDynamicGeometryFrameBindings();
615
+ let surfaceDynamicInputFrame: SurfaceDynamicInputFrame | undefined;
899
616
  let latestCamera: CameraSnapshot | undefined;
617
+ // Last public parameter failure; cleared only after a valid extraction.
618
+ let lastMotionBlurInvalidParams: MotionBlurValidationError | undefined;
619
+ // Lazily install the compute lane when a capable frame demands it.
620
+ let motionBlurFeatureInput: MotionBlurFeatureInput | undefined;
621
+ const motionBlurFeature = createMotionBlurFeature(() => motionBlurFeatureInput);
622
+ const ensureMotionBlurFeature = (): void => {
623
+ const host = internals.featureHost;
624
+ if (
625
+ host === undefined ||
626
+ host.features.some((feature) => feature.identity === 'forgeax.motion-blur')
627
+ ) {
628
+ return;
629
+ }
630
+ const installed = host.install(
631
+ motionBlurFeature as import('./features/types').RenderFeature<unknown>,
632
+ );
633
+ if (!installed.ok) internals.errorRegistry.fire(installed.error);
634
+ };
900
635
  const pointsLinesOwner = new StandardPointsLinesOwner(internals);
901
- const instanceCollections = new InstanceCollectionStore();
636
+ const instanceCollections = new InstanceProjectionStore();
902
637
  const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
903
638
  const ssrFormatReceipts = new WeakMap<
904
639
  RhiDevice,
@@ -926,7 +661,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
926
661
  const persistentRenderScene = new PersistentRenderScene({
927
662
  getDevice: () => internals.device,
928
663
  onGpuError: (error) => internals.errorRegistry.fire(error),
929
- onSharedRefMutation: (worldId, handle) => {
664
+ onRuntimeAssetChange: (worldId, handle) => {
930
665
  internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
931
666
  },
932
667
  instanceCollections,
@@ -1074,6 +809,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1074
809
  ),
1075
810
  } satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory);
1076
811
  let gpuDrivenRecoveryCount = 0;
812
+ const surfaceSubmissionObservation = new SurfaceSubmissionObservationOwner(
813
+ () => internals.deviceScope.generation,
814
+ );
1077
815
  let gpuDrivenProduction = createGpuDrivenOwner(
1078
816
  internals.device,
1079
817
  gpuDrivenShaderFactory,
@@ -1145,6 +883,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1145
883
  // raw (material-handle-based, cross-world collision semantically correct).
1146
884
  const frameState: RenderFrameState = {
1147
885
  frameNumber: 0,
886
+ surfaceSubmissionObservation: undefined,
1148
887
  reflectionFallbackDemand: false,
1149
888
  ssrRequested: false,
1150
889
  ssrSpatialAdmission: undefined,
@@ -1172,6 +911,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1172
911
  depthOfFieldCandidateGraph: null,
1173
912
  depthOfFieldAccepted: undefined,
1174
913
  depthOfFieldLastSubmitFailed: false,
914
+ barrelDistortionPreviousGraph: null,
915
+ barrelDistortionPreviousGraphKey: null,
916
+ barrelDistortionPreviousPerFrameGraph: undefined,
917
+ barrelDistortionPreviousGraphGeneration: 0,
918
+ barrelDistortionPreviousCompiledGraphGeneration: 0,
919
+ barrelDistortionPreviousStandardLightingGraphSignature: '',
920
+ barrelDistortionCandidateGraph: null,
1175
921
  volumetricFogPreviousGraph: null,
1176
922
  volumetricFogPreviousGraphKey: null,
1177
923
  volumetricFogCandidateGraph: null,
@@ -1190,6 +936,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1190
936
  currentFrameObservationSource: undefined,
1191
937
  reflectionFallbackObservationSource: undefined,
1192
938
  lastSuccessfulCameraAntialias: undefined,
939
+ lastSuccessfulBarrelDistortion: undefined,
940
+ barrelDistortionGraphResolution: 'accepted',
1193
941
  temporalGpuState: undefined,
1194
942
  autoExposureGpuResources: undefined,
1195
943
  pendingAutoExposureGpuResources: undefined,
@@ -1291,6 +1039,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1291
1039
  // tiles until the first frame with a castShadow spot (AC-03).
1292
1040
  spotShadowSnapshots: [],
1293
1041
  };
1042
+ // A feature that is explicitly recoverable on the next frame must not
1043
+ // publish the same transient failure on every render tick. Keep one
1044
+ // renderer-owned cooldown per structured feature failure while retaining
1045
+ // the original error for the first and periodic reports.
1046
+ const pendingFeatureErrorLastReportedFrame = new Map<string, PendingRenderFeatureErrorReport>();
1294
1047
  const resolveSsrDependencies = (requested: boolean): SsrDependenciesInspection => {
1295
1048
  // Explicit SSR capability preparation belongs to initialization. Inspection
1296
1049
  // only projects receipts and never starts work or creates image history.
@@ -1496,7 +1249,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1496
1249
  const invalidatePostProcessModule = (id: string): void => {
1497
1250
  const entry = lookupPostProcess(id);
1498
1251
  if (entry !== undefined) {
1499
- internals.invalidateShaderModule?.(postProcessShaderModuleLabel(id, entry.source));
1252
+ internals.invalidateShaderModule?.(postProcessShaderModuleLabel(entry.source));
1500
1253
  }
1501
1254
  };
1502
1255
  // feat-20260621 M-A2 / w8: expose the eager-created per-id params UBO through
@@ -1586,6 +1339,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1586
1339
  if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
1587
1340
  }
1588
1341
  };
1342
+ const disposeActivePostProcessResources = (): void => {
1343
+ const resources = { device: postProcessParamsDevice, paramsBuffers: postProcessParamsBuffers };
1344
+ postProcessParamsBuffers = new Map();
1345
+ disposePostProcessResources(resources);
1346
+ };
1589
1347
  const preparePostProcessParamsResources = (
1590
1348
  featureEntries: ReadonlyMap<string, PostProcessShaderEntry>,
1591
1349
  ): Result<PostProcessParamsCandidate, RhiError> => {
@@ -1752,6 +1510,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1752
1510
  readonly visible: number;
1753
1511
  readonly occluded: number;
1754
1512
  readonly lodHistogram: readonly { readonly level: number; readonly count: number }[];
1513
+ readonly submit?: { readonly frameId: number; readonly deviceGeneration: number };
1755
1514
  readonly worldSelections?: readonly {
1756
1515
  readonly worldKey: number;
1757
1516
  readonly primitiveSlot: number;
@@ -1763,9 +1522,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1763
1522
  }[];
1764
1523
  }
1765
1524
  | undefined,
1525
+ targetInspection: LodOcclusionInspection | undefined = lastLodOcclusionInspection,
1526
+ targetWorldAttachments: ReadonlyMap<number, string> = lastLodWorldAttachments,
1766
1527
  ): void => {
1767
- if (selection === undefined || lastLodOcclusionInspection === undefined) return;
1768
- const inspection = lastLodOcclusionInspection;
1528
+ if (selection === undefined || targetInspection === undefined) return;
1529
+ const inspection = targetInspection;
1769
1530
  // The GPU readback histogram is the selector fact, while final submitted
1770
1531
  // counts are owned by the persistent visibility projection. Keeping the
1771
1532
  // projection count here excludes ordinary non-LOD geometry (for example a
@@ -1777,7 +1538,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1777
1538
  const selectionsByAttachment = new Map<string, (typeof worldSelections)[number][]>();
1778
1539
  let attributionValid = true;
1779
1540
  for (const selected of worldSelections) {
1780
- const attachmentId = lastLodWorldAttachments.get(selected.worldKey);
1541
+ const attachmentId = targetWorldAttachments.get(selected.worldKey);
1781
1542
  if (attachmentId === undefined) {
1782
1543
  attributionValid = false;
1783
1544
  break;
@@ -1790,15 +1551,16 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1790
1551
  const nextWorlds = worlds.map((world) => {
1791
1552
  const selected = selectionsByAttachment.get(world.attachmentId);
1792
1553
  const baseRow = world.rows[0];
1793
- if (selected === undefined || baseRow === undefined) {
1794
- if (world.rows.length > 0) attributionValid = false;
1795
- return world;
1796
- }
1797
- const candidateCount = selected.reduce((sum, row) => sum + row.candidateCount, 0);
1798
- if (candidateCount !== baseRow.count.candidates) {
1799
- attributionValid = false;
1800
- return world;
1801
- }
1554
+ // A World with only plain meshes has no LOD selection row. Keep its
1555
+ // projection-only receipt and allow Worlds with valid LOD rows to
1556
+ // retain same-submit attribution. Unknown selected world keys still
1557
+ // fail closed above.
1558
+ if (selected === undefined || baseRow === undefined) return world;
1559
+ // The projection row is intentionally CPU-scoped and may omit
1560
+ // selector-only candidates that remain in the full GPU plan. The
1561
+ // same-submit GPU row owns the denominator; comparing it to the
1562
+ // projection count would reject a valid receipt (for example 11 GPU
1563
+ // candidates versus 10 CPU-admitted renderables).
1802
1564
  return Object.freeze({
1803
1565
  ...world,
1804
1566
  attribution: Object.freeze({
@@ -1828,7 +1590,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1828
1590
  lastLodOcclusionInspection = inspectLodOcclusion({
1829
1591
  ...inspection,
1830
1592
  count: {
1831
- ...lastLodOcclusionInspection.count,
1593
+ ...inspection.count,
1832
1594
  candidates: selection.candidateCount,
1833
1595
  visible: selection.visible,
1834
1596
  occluded: selection.occluded,
@@ -1861,6 +1623,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1861
1623
  };
1862
1624
  let lastMeshMaterialBindings: readonly MeshMaterialBindingObservation[] = [];
1863
1625
  let lastMeshMaterialBindingRenderables: readonly RenderableSnapshot[] | undefined;
1626
+ let lastPresentation: FramePresentation = 'pending';
1864
1627
  const createPreparedResolverFactory =
1865
1628
  (
1866
1629
  runtime: RenderSystemInternals,
@@ -2024,6 +1787,17 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2024
1787
  ) {
2025
1788
  return err(new Error('prepared scene-depth pipeline requires a scene target'));
2026
1789
  }
1790
+ // The typed graph composes fullscreen feature bindings from its
1791
+ // current graph views and post-process parameter snapshot at encode
1792
+ // time. Do not manufacture a fallback bind group here: during
1793
+ // registration/recovery its params buffer can still be absent, and
1794
+ // a two-entry fallback cannot satisfy a params-aware layout.
1795
+ if (
1796
+ preparedPipelineIds.has(pipeline as object) &&
1797
+ descriptor.values.fullscreen === true
1798
+ ) {
1799
+ return ok(undefined);
1800
+ }
2027
1801
  if (preparedGroup0Pipelines.has(pipeline as object)) {
2028
1802
  const layout =
2029
1803
  (materialShaderId === undefined
@@ -2161,10 +1935,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2161
1935
  createPreparedResolverCaches,
2162
1936
  });
2163
1937
  return {
2164
- instances: instanceCollections,
1938
+ setSurfaceDynamicInput(frame: SurfaceDynamicInputFrame | undefined): void {
1939
+ surfaceDynamicInputFrame = frame;
1940
+ },
2165
1941
  initializeSsr: ensureSsrFormatProbe,
2166
1942
  get instanceCollectionsInspection(): readonly InstanceCollectionInspection[] {
2167
- return instanceCollections._inspections();
1943
+ return instanceCollections._inspections(frameState.frameNumber - 1);
2168
1944
  },
2169
1945
  releaseProfilerCatalog(): void {
2170
1946
  releaseProfilerCatalog?.();
@@ -2177,9 +1953,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2177
1953
  return persistentRenderScene.bounds(world, entity);
2178
1954
  },
2179
1955
  get renderScene(): RenderSceneInspection {
1956
+ const submission = surfaceSubmissionObservation.inspect();
2180
1957
  return {
2181
1958
  ...persistentRenderScene.inspect(),
2182
1959
  gpuDriven: gpuDrivenProduction.inspect(),
1960
+ ...(submission === undefined ? {} : { submission }),
2183
1961
  };
2184
1962
  },
2185
1963
  get gpuDrivenInspection(): GpuDrivenProductionInspection {
@@ -2234,6 +2012,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2234
2012
  get reflectionFallbackCompletion(): Promise<void> | undefined {
2235
2013
  return frameState.reflectionFallbackCompletion;
2236
2014
  },
2015
+ get presentation(): FramePresentation {
2016
+ return lastPresentation;
2017
+ },
2237
2018
  isCubeCapturePending(target: RenderTarget): boolean {
2238
2019
  return cubeCaptureScheduler.inspect(target).candidateGeneration !== undefined;
2239
2020
  },
@@ -2274,24 +2055,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2274
2055
  });
2275
2056
  },
2276
2057
  get motionBlurInspection(): MotionBlurInspection | undefined {
2277
- const camera = latestCamera;
2278
- if (camera?.motionBlur === undefined) return undefined;
2279
- const demanded = camera.motionBlur.shutterAngle > 0;
2280
- const unavailable = demanded && !internals.device.caps.rgba16floatRenderable;
2281
- const admitted =
2282
- frameState.activeTemporalGpuState !== undefined ||
2283
- frameState.temporalGpuState !== undefined;
2284
- return {
2285
- enabled: demanded,
2286
- status: !demanded ? 'off' : unavailable || !admitted ? 'reset' : 'active',
2287
- shutterAngle: camera.motionBlur.shutterAngle,
2288
- maxRadiusPixels: camera.motionBlur.maxRadiusPixels,
2289
- sampleCount: camera.motionBlur.sampleCount,
2290
- temporalDemand: demanded ? 'scene-data-temporal-v1' : 'none',
2291
- passName: 'motion-blur',
2292
- historyWrites: 0,
2293
- ...(unavailable ? { lastFailure: 'scene-data-unavailable' as const } : {}),
2294
- };
2058
+ return projectMotionBlurInspection({
2059
+ camera: latestCamera,
2060
+ invalidParams: lastMotionBlurInvalidParams,
2061
+ capabilities: internals.device.caps,
2062
+ acceptedTemporal: frameState.lastSuccessfulTemporalView,
2063
+ transaction: frameState.temporalFrameTransaction.inspect(),
2064
+ execution: frameState.motionBlurExecution,
2065
+ temporalFrameId: frameState.temporalFrame?.frameId,
2066
+ deviceGeneration: internals.deviceScope.generation,
2067
+ graphGeneration: frameState.graphGeneration,
2068
+ });
2069
+ },
2070
+ get motionBlurInvalidParams(): MotionBlurValidationError | undefined {
2071
+ return lastMotionBlurInvalidParams;
2295
2072
  },
2296
2073
  get depthOfFieldInspection(): DepthOfFieldInspection | undefined {
2297
2074
  const camera = latestCamera;
@@ -2354,22 +2131,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2354
2131
  const lifecycle = resources?.inspectBloomResources?.(
2355
2132
  frameState.compiledFrameGraph?.inspect(),
2356
2133
  );
2357
- return Object.freeze(
2358
- lifecycle ?? {
2359
- graphStatus: 'empty',
2360
- enabled: false,
2361
- targetCount: 0,
2362
- targetBytes: 0,
2363
- resourceCount: 0,
2364
- passCount: 0,
2365
- encodeCount: 0,
2366
- bindGroupCount: 0,
2367
- uploadCount: 0,
2368
- residentChildBytes: 0,
2369
- generation: 0,
2370
- state: 'off',
2371
- },
2372
- );
2134
+ return Object.freeze(lifecycle ?? emptyBloomInspection());
2373
2135
  },
2374
2136
  get transmission(): TransmissionInspection | undefined {
2375
2137
  return lastTransmissionInspection;
@@ -2444,6 +2206,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2444
2206
  const profileSession = internals.profiler?.activeSession();
2445
2207
  let ownsProfileFrame = false;
2446
2208
  let submitted = false;
2209
+ // A failed draw cannot publish a startup completion fact. Reset the
2210
+ // projection before extraction so a later receipt never reuses an old
2211
+ // scene's readiness.
2212
+ lastPresentation = 'pending';
2213
+ // A prior failed record/submit may have staged a temporal capture. A
2214
+ // capture belongs only to the draw call that created it; discard it
2215
+ // before this call can prepare or commit a new candidate.
2216
+ persistentRenderScene.discardTemporalFrame();
2447
2217
  // A failed draw must never leave a previous frame eligible to publish a
2448
2218
  // newly accepted dynamic-geometry candidate.
2449
2219
  dynamicGeometryFrames.begin();
@@ -2475,55 +2245,67 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2475
2245
  dispatchCounts.unlit = 0;
2476
2246
 
2477
2247
  const resourceWorld = compositionWorlds[resourceOwner] as World;
2478
- const frame = runProfiledRenderPhase(profileSession, 'extract', () => {
2479
- return persistentRenderScene.extractComposition(
2480
- compositionWorlds,
2481
- { cameraOwner, resourceOwner },
2482
- internals.assets.catalogEpoch,
2483
- (renderables) =>
2484
- extractFrames(
2485
- compositionWorlds,
2486
- {
2487
- cameraOwner,
2488
- resourceOwner,
2489
- ...(cameraEntityKey === undefined ? {} : { cameraEntityKey }),
2490
- },
2491
- internals.assets,
2492
- internals.getPipelineState(),
2493
- persistentRenderScene.materialSnapshotCacheStore(),
2494
- {
2495
- ...(internals.device.caps.backendKind === 'null'
2496
- ? {}
2497
- : {
2498
- materialContext: {
2499
- backend:
2500
- internals.device.caps.backendKind === 'wgpu-webgl2'
2501
- ? ('webgl2' as const)
2502
- : internals.device.caps.backendKind,
2503
- capability: internals.device.caps.storageBuffer
2504
- ? ('storage-buffer' as const)
2505
- : ('uniform-fallback' as const),
2506
- pipeline: 'forward' as const,
2507
- geometry: 'mesh' as const,
2508
- pass: 'forward' as const,
2509
- profile: 'forgeax-material-wgsl-v1' as const,
2510
- toolchain: 'naga-oil' as const,
2511
- instrumentation: 'none' as const,
2512
- },
2513
- }),
2514
- cull: 'none',
2515
- retainHidden: true,
2516
- renderables,
2517
- instanceCollections,
2518
- ...(!internals.device.caps.storageBuffer ||
2519
- internals.getMaterialShaderArtifact === undefined
2520
- ? {}
2521
- : { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
2522
- },
2523
- ),
2524
- compositionLeases,
2525
- );
2526
- });
2248
+ let frame: ReturnType<PersistentRenderScene['extractComposition']>;
2249
+ try {
2250
+ frame = runProfiledRenderPhase(profileSession, 'extract', () => {
2251
+ return persistentRenderScene.extractComposition(
2252
+ compositionWorlds,
2253
+ { cameraOwner, resourceOwner },
2254
+ internals.assets.catalogEpoch,
2255
+ (renderables) =>
2256
+ extractFrames(
2257
+ compositionWorlds,
2258
+ {
2259
+ cameraOwner,
2260
+ resourceOwner,
2261
+ ...(cameraEntityKey === undefined ? {} : { cameraEntityKey }),
2262
+ },
2263
+ internals.assets,
2264
+ internals.getPipelineState(),
2265
+ persistentRenderScene.materialSnapshotCacheStore(),
2266
+ {
2267
+ ...(internals.device.caps.backendKind === 'null'
2268
+ ? {}
2269
+ : {
2270
+ materialContext: {
2271
+ backend:
2272
+ internals.device.caps.backendKind === 'wgpu-webgl2'
2273
+ ? ('webgl2' as const)
2274
+ : internals.device.caps.backendKind,
2275
+ capability: internals.device.caps.storageBuffer
2276
+ ? ('storage-buffer' as const)
2277
+ : ('uniform-fallback' as const),
2278
+ pipeline: 'forward' as const,
2279
+ geometry: 'mesh' as const,
2280
+ pass: 'forward' as const,
2281
+ profile: 'forgeax-material-wgsl-v1' as const,
2282
+ toolchain: 'naga-oil' as const,
2283
+ instrumentation: 'none' as const,
2284
+ },
2285
+ }),
2286
+ cull: 'none',
2287
+ retainHidden: true,
2288
+ renderables,
2289
+ instanceCollections,
2290
+ ...(!internals.device.caps.storageBuffer ||
2291
+ internals.getMaterialShaderArtifact === undefined
2292
+ ? {}
2293
+ : { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
2294
+ },
2295
+ ),
2296
+ compositionLeases,
2297
+ );
2298
+ });
2299
+ } catch (cause) {
2300
+ if (cause instanceof MotionBlurValidationError) {
2301
+ lastMotionBlurInvalidParams = cause;
2302
+ latestCamera = undefined;
2303
+ internals.errorRegistry.fire(cause);
2304
+ return false;
2305
+ }
2306
+ throw cause;
2307
+ }
2308
+ lastMotionBlurInvalidParams = undefined;
2527
2309
  const {
2528
2310
  cameras: extractedCameras,
2529
2311
  cubeCameras,
@@ -2531,6 +2313,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2531
2313
  environment,
2532
2314
  environmentReady,
2533
2315
  volumetricFog,
2316
+ cloudLayer,
2534
2317
  renderables,
2535
2318
  dispatch,
2536
2319
  shadowCasterEntityKeys: extractedShadowCasterEntityKeys,
@@ -2759,7 +2542,27 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2759
2542
  );
2760
2543
  const submissionRenderables = visibilityProjection.renderables;
2761
2544
  const submissionDispatch = visibilityProjection.dispatch;
2545
+ const presentationProjection = projectFramePresentation({
2546
+ hasCamera: cameras.length > 0,
2547
+ environmentReady,
2548
+ hasEnvironment: environment !== undefined,
2549
+ submissionRenderables,
2550
+ submissionDispatch,
2551
+ preparedWorlds,
2552
+ internals,
2553
+ });
2554
+ const presentationRenderables = presentationProjection.renderables;
2555
+ let framePresentation = presentationProjection.presentation;
2762
2556
  const inspectionCamera = latestCamera;
2557
+ // The public frame receipt is allocated by WebGPURenderer immediately
2558
+ // before this draw (`observationFrameId`). RenderSystem's private
2559
+ // frameNumber is an internal generation and also advances on failed
2560
+ // record attempts, so it cannot be used as receipt identity. Keep the
2561
+ // internal generation for recovery/visibility state, but bind the LOD
2562
+ // inspection to the frame id that the caller will receive.
2563
+ const lodSubmitFrameId =
2564
+ (internals as RenderSystemInternals & { readonly observationFrameId?: number })
2565
+ .observationFrameId ?? frameState.frameNumber;
2763
2566
  let lodCandidateCountForFrame = 0;
2764
2567
  if (inspectionCamera !== undefined) {
2765
2568
  const firstRenderable = renderables[0];
@@ -2802,7 +2605,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2802
2605
  renderables,
2803
2606
  submissionRenderables,
2804
2607
  inspectionCamera,
2805
- frameState.frameNumber,
2608
+ lodSubmitFrameId,
2806
2609
  worldKeys,
2807
2610
  slotByEntity,
2808
2611
  {
@@ -2834,7 +2637,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2834
2637
  firstRenderable === undefined
2835
2638
  ? 'none'
2836
2639
  : `asset-handle:${firstRenderable.assetHandle}`,
2837
- sourceKey: `render-frame:${frameState.frameNumber}`,
2640
+ sourceKey: `render-frame:${lodSubmitFrameId}`,
2838
2641
  },
2839
2642
  view: {
2840
2643
  attachmentId:
@@ -2847,7 +2650,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2847
2650
  primitiveSlot: 0,
2848
2651
  slotGeneration: persistentRenderScene.inspect().topology.revision,
2849
2652
  },
2850
- generation: frameState.frameNumber,
2653
+ generation: lodSubmitFrameId,
2851
2654
  count: {
2852
2655
  candidates: candidateCount,
2853
2656
  visible: inspectionRenderables.length,
@@ -2866,7 +2669,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2866
2669
  visible: true,
2867
2670
  })),
2868
2671
  submit: {
2869
- frameId: frameState.frameNumber,
2672
+ frameId: lodSubmitFrameId,
2870
2673
  build: resolveInspectionBuild(internals),
2871
2674
  deviceGeneration: internals.deviceScope.generation,
2872
2675
  },
@@ -2878,7 +2681,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2878
2681
  lastLodWorldAttachments = new Map();
2879
2682
  }
2880
2683
  const temporalDemanded =
2881
- cameras[0]?.antialias === 'taa' || (cameras[0]?.motionBlur?.shutterAngle ?? 0) > 0;
2684
+ cameras[0]?.antialias === 'taa' ||
2685
+ (cameras[0]?.motionBlur !== undefined &&
2686
+ motionBlurTemporalDemand({
2687
+ shutterAngle: cameras[0].motionBlur.shutterAngle,
2688
+ maxRadiusPixels: cameras[0].motionBlur.maxRadiusPixels,
2689
+ sampleCount: cameras[0].motionBlur.sampleCount,
2690
+ targetFps: cameras[0].motionBlur.targetFps ?? 60,
2691
+ }));
2882
2692
  if (temporalDemanded) {
2883
2693
  persistentRenderScene.prepareTemporalFrame(submissionRenderables);
2884
2694
  }
@@ -2938,6 +2748,17 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2938
2748
  bindGroupCounts.createBindGroup = 0;
2939
2749
  bindGroupCounts.keys = [];
2940
2750
  const preparedPipelineState = internals.getPipelineState();
2751
+ const featureView = createCloudFeatureView(
2752
+ cameras[0],
2753
+ compositionWorlds,
2754
+ cameraOwner,
2755
+ internals.canvas.width,
2756
+ internals.canvas.height,
2757
+ internals.deviceScope.generation,
2758
+ frameState.lastSuccessfulTemporalView === undefined,
2759
+ lastSubmittedGeneration !== undefined &&
2760
+ lastSubmittedGeneration !== internals.deviceScope.generation,
2761
+ );
2941
2762
  const bindGroupSkipReason: RenderPhaseSkipReason | undefined =
2942
2763
  internals.featureHost === undefined
2943
2764
  ? 'feature-host-unavailable'
@@ -2988,7 +2809,46 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2988
2809
  colorAttachmentFormat: preparedPipelineState.colorAttachmentFormat,
2989
2810
  storageBuffer: internals.device.caps.storageBuffer,
2990
2811
  multisample: internals.device.caps.backendKind !== 'wgpu-webgl2',
2812
+ cloudHistory: cloudLayer !== undefined,
2991
2813
  });
2814
+ const motionBlurParams = cameras[0]?.motionBlur;
2815
+ let motionBlurDelta = 0;
2816
+ try {
2817
+ const delta = (compositionWorlds[cameraOwner] as World).getResource(Time).delta;
2818
+ motionBlurDelta = Number.isFinite(delta) ? Math.max(0, delta) : 0;
2819
+ } catch {
2820
+ motionBlurDelta = 0;
2821
+ }
2822
+ const acceptedSampleTime = frameState.lastSuccessfulTemporalView?.sampleTimeSeconds;
2823
+ const cameraViewIdentity = `camera:${cameras[0]?.entityKey ?? 0}`;
2824
+ const canvasWidth = Math.max(1, internals.canvas.width);
2825
+ const canvasHeight = Math.max(1, internals.canvas.height);
2826
+ const motionBlurPlan = planMotionBlurFrame({
2827
+ params: motionBlurParams,
2828
+ sampleTimeSeconds: opts.sampleTimeSeconds,
2829
+ acceptedSampleTimeSeconds: acceptedSampleTime,
2830
+ fallbackDeltaSeconds: motionBlurDelta,
2831
+ acceptedTemporal: frameState.lastSuccessfulTemporalView,
2832
+ temporalReset: opts.temporalReset === true,
2833
+ width: canvasWidth,
2834
+ height: canvasHeight,
2835
+ cameraViewIdentity,
2836
+ historyVersion: cameras[0]?.historyVersion ?? 0,
2837
+ environmentSignature: environment?.environmentSignature ?? '',
2838
+ fogSignature: environment?.fogSignature ?? '',
2839
+ deviceGeneration: internals.deviceScope?.generation ?? 0,
2840
+ });
2841
+ const demandedMotionBlur = motionBlurPlan.demanded;
2842
+ motionBlurFeatureInput = motionBlurPlan.featureInput;
2843
+ if (
2844
+ demandedMotionBlur &&
2845
+ internals.device.caps.compute === true &&
2846
+ internals.device.caps.storageBuffer === true &&
2847
+ internals.device.caps.storageTexture === true &&
2848
+ internals.device.caps.rgba16floatRenderable === true
2849
+ ) {
2850
+ ensureMotionBlurFeature();
2851
+ }
2992
2852
  preparedFeatureSkylightResources =
2993
2853
  preparedPipelineState === null
2994
2854
  ? undefined
@@ -3001,11 +2861,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3001
2861
  worlds: compositionWorlds,
3002
2862
  owner: resourceOwner,
3003
2863
  frameNumber: frameState.frameNumber,
2864
+ ...(cameras[0] === undefined ? {} : { selectedCamera: cameras[0] }),
2865
+ frameSize: { width: internals.canvas.width, height: internals.canvas.height },
3004
2866
  visibilitySnapshots: frame.featureVisibilitySnapshots,
3005
2867
  hiddenEntityReports: frame.hiddenEntityReports,
3006
2868
  targets: featureTargets,
3007
2869
  generation: internals.featureHost?.preparedGeneration ?? 0,
3008
2870
  caps: internals.device.caps,
2871
+ frame: createCloudFeatureFrameContext(cloudLayer, featureView),
3009
2872
  ...(internals.getMaterialShaderBindingContract === undefined
3010
2873
  ? {}
3011
2874
  : { materialShaderBindingContract: internals.getMaterialShaderBindingContract }),
@@ -3035,11 +2898,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3035
2898
  gpuWork: featureGpuWork,
3036
2899
  });
3037
2900
  lastVisibilityStats.explicitlyHidden = featureFrame.hiddenEntityReports.length;
3038
- for (const featureError of featureFrame.errors) {
3039
- if (!isPendingRenderFeaturePreparation(featureError)) {
3040
- internals.errorRegistry.fire(featureError);
3041
- }
3042
- }
2901
+ reportPendingRenderFeatureErrors(
2902
+ featureFrame.errors,
2903
+ frameState.frameNumber,
2904
+ pendingFeatureErrorLastReportedFrame,
2905
+ internals.errorRegistry,
2906
+ );
3043
2907
  const postProcessParamsCandidate = preparePostProcessParamsResources(
3044
2908
  featureFrame.fullscreenEffects,
3045
2909
  );
@@ -3054,6 +2918,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3054
2918
  featureGraphCandidate = {
3055
2919
  plans: featureFrame.plans,
3056
2920
  fullscreenEffects: featureFrame.fullscreenEffects,
2921
+ postProcessIdentities: featureFrame.postProcessIdentities,
3057
2922
  ...(featureFrame.requiresPreparedResourceKey
3058
2923
  ? { preparedResourceKey: `frame-${frameState.frameNumber}` }
3059
2924
  : {}),
@@ -3172,7 +3037,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3172
3037
  }
3173
3038
  return runProfiledRenderPhase(profileSession, phase, action);
3174
3039
  };
3175
- if (cameras[0]?.bloom === 'on') {
3040
+ const bloomAdmitted = standardBloomAdmitted(cameras[0]);
3041
+ if (bloomAdmitted) {
3176
3042
  internals.getPipelineState()?.perPassResources.ensureBloomResources?.();
3177
3043
  }
3178
3044
  const liveOutput = cameras[0]?.output;
@@ -3194,11 +3060,44 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3194
3060
  Number.isFinite(deltaTime) ? Math.max(0, deltaTime) : 0,
3195
3061
  );
3196
3062
  }
3063
+ const surfaceSubmissionCandidate = surfaceSubmissionObservation.begin({
3064
+ frameId:
3065
+ (internals as RenderSystemInternals & { readonly observationFrameId?: number })
3066
+ .observationFrameId ?? frameState.frameNumber,
3067
+ requestedLane: opts.geometryLane === 'direct' ? 'direct' : 'gpu-driven',
3068
+ deviceGeneration: internals.deviceScope.generation,
3069
+ resourceGeneration: gpuDrivenProduction.inspect().resourceGeneration,
3070
+ });
3071
+ frameState.surfaceSubmissionObservation = surfaceSubmissionCandidate;
3197
3072
  const probeCaptureScene =
3198
3073
  shadowCasterProjection !== undefined &&
3199
3074
  reflectionProbeRecord.graph.work.some((work) => work.rawCaptureFace !== undefined)
3200
3075
  ? projectCaptureScene(shadowCasterProjection, submissionRenderables, orderedDispatch)
3201
3076
  : undefined;
3077
+ // This is a transient projection of the record owner's actual visible
3078
+ // draws. It lets the presentation fact distinguish a clear/submit
3079
+ // from a frame where geometry or its pipeline was skipped, without
3080
+ // retaining a second readiness registry or per-asset counter.
3081
+ const presentedRenderableKeys = new Set<number>();
3082
+ const presentedSubmeshKeys = new Set<string>();
3083
+ const consumeDynamicGeometry = dynamicGeometryFrames.onRenderableDraw(compositionWorlds);
3084
+ const markPresentedRenderable = (
3085
+ entry: Parameters<typeof consumeDynamicGeometry>[0],
3086
+ submeshIndex?: number,
3087
+ receipt?: RenderableDrawReceipt,
3088
+ ): void => {
3089
+ const entityKey = worldEntityKey(
3090
+ worldKeys[entry.source.worldId] ?? entry.source.worldId,
3091
+ entry.source.entityKey,
3092
+ );
3093
+ if (receipt?.ready !== false) {
3094
+ presentedRenderableKeys.add(entityKey);
3095
+ }
3096
+ if (submeshIndex !== undefined && receipt?.ready !== false) {
3097
+ presentedSubmeshKeys.add(`${entityKey}:${submeshIndex}`);
3098
+ }
3099
+ consumeDynamicGeometry(entry, receipt?.lane ?? 'cpu');
3100
+ };
3202
3101
  submitted = runProfiledRenderPhase(profileSession, 'record', () =>
3203
3102
  recordFrame(
3204
3103
  internals,
@@ -3226,7 +3125,20 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3226
3125
  activeEntityKeys: visibilityProjection.activeEntityKeys,
3227
3126
  activeEntityRevision: visibilityProjection.activeEntityRevision,
3228
3127
  telemetryCandidateCount: lodCandidateCountForFrame,
3128
+ telemetrySubmit: {
3129
+ frameId: lodSubmitFrameId,
3130
+ deviceGeneration: internals.deviceScope.generation,
3131
+ },
3229
3132
  ...(occlusionProjection === undefined ? {} : { occlusion: occlusionProjection }),
3133
+ frameTime: (() => {
3134
+ const elapsed = (compositionWorlds[cameraOwner] as World).getResource(Time).elapsed;
3135
+ return Number.isFinite(elapsed) ? elapsed : 0;
3136
+ })(),
3137
+ deviceGeneration: internals.deviceScope.generation,
3138
+ ...(surfaceDynamicInputFrame === undefined
3139
+ ? {}
3140
+ : { surfaceDynamicInput: surfaceDynamicInputFrame }),
3141
+ ...(opts.geometryLane === undefined ? {} : { geometryLane: opts.geometryLane }),
3230
3142
  },
3231
3143
  compositionLeases,
3232
3144
  featureGraphCandidate,
@@ -3253,10 +3165,16 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3253
3165
  worldKeys,
3254
3166
  undefined,
3255
3167
  ssrDependencies,
3256
- dynamicGeometryFrames.onRenderableDraw(compositionWorlds),
3168
+ markPresentedRenderable,
3169
+ environment?.fog,
3170
+ opts.sampleTimeSeconds,
3171
+ opts.temporalReset === true,
3172
+ frame.cloudLayer,
3257
3173
  ),
3258
3174
  );
3259
3175
  dynamicGeometryFrames.commit(submitted);
3176
+ if (!submitted) surfaceSubmissionCandidate.abort();
3177
+ frameState.surfaceSubmissionObservation = undefined;
3260
3178
  if (!submitted) discardLiveFeatureState();
3261
3179
  if (submitted && lastLodOcclusionInspection !== undefined) {
3262
3180
  const query = ensureOcclusionRuntime().inspect();
@@ -3280,6 +3198,111 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3280
3198
  (frameState.currentDirectionalShadowView !== null &&
3281
3199
  (lights.lightViewProj?.length ?? 0) > 0 &&
3282
3200
  (internals.getPipelineState()?.perPassResources.shadowMapSize ?? 0) > 0);
3201
+
3202
+ // A successful queue submit is necessary but not sufficient for the
3203
+ // startup presentation fact. Reuse the same visible snapshots and
3204
+ // producer-owned GPU stores consumed by record so missing geometry,
3205
+ // material bindings, dynamic video frames, or requested shadow work
3206
+ // cannot dismiss the loading screen behind a clear/fallback pass.
3207
+ if (submitted && framePresentation === 'ready' && cameras.length > 0) {
3208
+ const pipelineState = internals.getPipelineState();
3209
+ const visibleRenderableKeys = new Set(
3210
+ presentationRenderables.map((renderable) =>
3211
+ worldEntityKey(
3212
+ worldKeys[renderable.worldId] ?? renderable.worldId,
3213
+ renderable.entityKey,
3214
+ ),
3215
+ ),
3216
+ );
3217
+ if (
3218
+ pipelineState === null ||
3219
+ frameState.compiledFrameGraph === null ||
3220
+ [...visibleRenderableKeys].some((key) => !presentedRenderableKeys.has(key))
3221
+ ) {
3222
+ framePresentation = 'pending';
3223
+ }
3224
+ for (const renderable of presentationRenderables) {
3225
+ if (framePresentation === 'pending') break;
3226
+ const renderableWorld = preparedWorlds[renderable.worldId];
3227
+ const meshHandles =
3228
+ renderableWorld === undefined
3229
+ ? undefined
3230
+ : (internals.gpuStore.getMeshGpuHandles(
3231
+ toShared<'MeshAsset'>(renderable.assetHandle),
3232
+ renderableWorld,
3233
+ ) ?? pipelineState?.meshes.get(renderable.assetHandle));
3234
+ if (meshHandles === undefined) {
3235
+ framePresentation = 'pending';
3236
+ break;
3237
+ }
3238
+ const entityKey = worldEntityKey(
3239
+ worldKeys[renderable.worldId] ?? renderable.worldId,
3240
+ renderable.entityKey,
3241
+ );
3242
+ const gpuClaimedSubmeshes = new Set<number>();
3243
+ for (const [compactIndex, draw] of (renderable.gpuDrivenDraws ?? []).entries()) {
3244
+ const submeshIndex = draw.drawItemIndex ?? compactIndex;
3245
+ if (presentedSubmeshKeys.has(`${entityKey}:${submeshIndex}`)) {
3246
+ gpuClaimedSubmeshes.add(submeshIndex);
3247
+ }
3248
+ }
3249
+ if (renderable.pointsLines === undefined) {
3250
+ for (const [submeshIndex] of meshHandles.submeshes.entries()) {
3251
+ if (gpuClaimedSubmeshes.has(submeshIndex)) continue;
3252
+ if (!presentedSubmeshKeys.has(`${entityKey}:${submeshIndex}`)) {
3253
+ framePresentation = 'pending';
3254
+ break;
3255
+ }
3256
+ }
3257
+ }
3258
+ if (framePresentation === 'pending') break;
3259
+ for (const material of renderable.materials) {
3260
+ const residency = observeMaterialResidency(renderableWorld, material, internals);
3261
+ if (residency.readiness === 'pending' || residency.readiness === 'failed') {
3262
+ framePresentation = 'pending';
3263
+ break;
3264
+ }
3265
+ for (const clip of material.videoTextureFields?.values() ?? []) {
3266
+ if (internals.dynamicTextureStore?.getView(clip) === undefined) {
3267
+ framePresentation = 'pending';
3268
+ break;
3269
+ }
3270
+ }
3271
+ if (framePresentation === 'pending') break;
3272
+ }
3273
+ if (framePresentation === 'pending') break;
3274
+ }
3275
+ if (
3276
+ framePresentation === 'ready' &&
3277
+ directionalQuality !== undefined &&
3278
+ (!directionalShadowReady ||
3279
+ (shadowCasterDrawKeys.size > 0 &&
3280
+ !frameState.directionalShadowCacheRecorded &&
3281
+ frameState.directionalShadowCache === null))
3282
+ ) {
3283
+ framePresentation = 'pending';
3284
+ }
3285
+ if (
3286
+ framePresentation === 'ready' &&
3287
+ lights.pointShadow.some(
3288
+ (shadow) =>
3289
+ shadow.shadowAtlasLayer >= 0 &&
3290
+ (shadow.shadowMatrices.length < 96 ||
3291
+ [...shadow.shadowMatrices].some((value) => !Number.isFinite(value))),
3292
+ )
3293
+ ) {
3294
+ framePresentation = 'pending';
3295
+ }
3296
+ if (
3297
+ framePresentation === 'ready' &&
3298
+ lights.spot.some(
3299
+ (spot) =>
3300
+ spot.castShadow && (spot.lightViewProj === undefined || spot.shadowAtlasTile < 0),
3301
+ )
3302
+ ) {
3303
+ framePresentation = 'pending';
3304
+ }
3305
+ }
3283
3306
  if (submitted && directionalShadowReady) {
3284
3307
  lastDirectionalShadowCandidate = 'accepted';
3285
3308
  } else if (directionalQuality !== undefined) {
@@ -3297,6 +3320,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3297
3320
  };
3298
3321
  }
3299
3322
  if (submitted) {
3323
+ lastPresentation = framePresentation;
3300
3324
  recoveryOwner.setLastSuccessfulFrameSeed({
3301
3325
  frame,
3302
3326
  worlds: compositionWorlds,
@@ -3304,6 +3328,19 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3304
3328
  resourceOwner: composition.owners.resourceOwner,
3305
3329
  width: Math.max(1, internals.canvas.width),
3306
3330
  height: Math.max(1, internals.canvas.height),
3331
+ frameTime: (() => {
3332
+ const elapsed = (compositionWorlds[cameraOwner] as World).getResource(Time).elapsed;
3333
+ return Number.isFinite(elapsed) ? elapsed : 0;
3334
+ })(),
3335
+ ...(surfaceDynamicInputFrame === undefined
3336
+ ? {}
3337
+ : { surfaceDynamicInput: surfaceDynamicInputFrame }),
3338
+ ...(frameState.recoveryMaterialArtifacts === undefined
3339
+ ? {}
3340
+ : { materialArtifacts: frameState.recoveryMaterialArtifacts }),
3341
+ ...(frameState.recoveryShadowMaterialArtifacts === undefined
3342
+ ? {}
3343
+ : { shadowMaterialArtifacts: frameState.recoveryShadowMaterialArtifacts }),
3307
3344
  });
3308
3345
  submittedFrameCount += 1;
3309
3346
  lastSubmittedGeneration = internals.deviceScope?.generation;
@@ -3317,13 +3354,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3317
3354
  internals.errorRegistry.fire(temporalCommit.error);
3318
3355
  }
3319
3356
  frameState.lastSuccessfulCameraAntialias = cameras[0]?.antialias;
3320
- const submittedCamera = cameras[0];
3321
3357
  const submittedDepthOfField =
3322
- submittedCamera === undefined
3358
+ cameras[0] === undefined
3323
3359
  ? undefined
3324
3360
  : resolveDepthOfFieldFrameParams(
3325
- submittedCamera.depthOfField,
3326
- submittedCamera.depthOfFieldError,
3361
+ cameras[0].depthOfField,
3362
+ cameras[0].depthOfFieldError,
3327
3363
  frameState.depthOfFieldAccepted?.params,
3328
3364
  );
3329
3365
  const submittedDepthOfFieldGraph = frameState.compiledFrameGraph?.inspect();
@@ -3341,7 +3377,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3341
3377
  frameState.depthOfFieldLastSubmitFailed = true;
3342
3378
  }
3343
3379
  internals.getPipelineState()?.perPassResources.commitBloomResources?.();
3344
- const nextBloom = cameras[0]?.bloom ?? 'off';
3380
+ const nextBloom = bloomAdmitted ? 'on' : 'off';
3345
3381
  if (frameState.lastSuccessfulBloom === 'on' && nextBloom !== 'on') {
3346
3382
  internals
3347
3383
  .getPipelineState()
@@ -3406,6 +3442,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3406
3442
  }),
3407
3443
  );
3408
3444
  } finally {
3445
+ if (!submitted) persistentRenderScene.discardTemporalFrame();
3409
3446
  internals.recoveryColdWorkGuard?.endFrame();
3410
3447
  if (ownsProfileFrame) {
3411
3448
  try {
@@ -3419,18 +3456,67 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3419
3456
  },
3420
3457
  isDynamicGeometryConsumed: (world, entity, meshHandle) =>
3421
3458
  dynamicGeometryFrames.isConsumed(world, entity, meshHandle),
3459
+ dynamicGeometryRecordStageLane: (world, entity, meshHandle) =>
3460
+ dynamicGeometryFrames.recordStageLane(world, entity, meshHandle),
3422
3461
  pipelineDispatchCounts: dispatchCounts,
3423
3462
  observeCurrentFrame(options: FrameObservationOptions) {
3424
3463
  const currentFrameId = frameState.frameNumber - 1;
3425
3464
  return observeCurrentFrame(options, frameState.currentFrameObservationSource, currentFrameId);
3426
3465
  },
3427
- async observeLodOcclusion(): Promise<void> {
3466
+ async observeLodOcclusion(receipt?: {
3467
+ readonly frameId: number;
3468
+ readonly deviceGeneration: number;
3469
+ }): Promise<void> {
3470
+ const capturedInspection = lastLodOcclusionInspection;
3471
+ const capturedWorldAttachments = lastLodWorldAttachments;
3472
+ if (capturedInspection === undefined) return;
3473
+ if (
3474
+ receipt !== undefined &&
3475
+ (capturedInspection.submit.frameId !== receipt.frameId ||
3476
+ capturedInspection.submit.deviceGeneration !== receipt.deviceGeneration)
3477
+ )
3478
+ return;
3428
3479
  await ensureOcclusionRuntime().waitForCompletions();
3429
- applyLodSelectionTelemetry(await gpuDrivenProduction.readLodSelection());
3430
- if (lastLodOcclusionInspection !== undefined) {
3480
+ if (lastLodOcclusionInspection !== capturedInspection) return;
3481
+ const selection = await gpuDrivenProduction.readLodSelection();
3482
+ if (lastLodOcclusionInspection !== capturedInspection || selection === undefined) return;
3483
+ if (
3484
+ selection.submit !== undefined &&
3485
+ (selection.submit.frameId !== capturedInspection.submit.frameId ||
3486
+ selection.submit.deviceGeneration !== capturedInspection.submit.deviceGeneration)
3487
+ )
3488
+ return;
3489
+ if (
3490
+ receipt !== undefined &&
3491
+ (selection.submit === undefined ||
3492
+ selection.submit.frameId !== receipt.frameId ||
3493
+ selection.submit.deviceGeneration !== receipt.deviceGeneration)
3494
+ )
3495
+ return;
3496
+ applyLodSelectionTelemetry(selection, capturedInspection, capturedWorldAttachments);
3497
+ if (
3498
+ selection.surfaceReadback !== undefined &&
3499
+ selection.surfaceActualMemberIds !== undefined &&
3500
+ selection.surfaceIndirectParameters !== undefined
3501
+ ) {
3502
+ surfaceSubmissionObservation.publishGpuMembers(
3503
+ {
3504
+ recording: selection.surfaceReadback,
3505
+ memberIds: selection.surfaceActualMemberIds,
3506
+ indirectParameters: selection.surfaceIndirectParameters,
3507
+ },
3508
+ receipt ?? capturedInspection.submit,
3509
+ );
3510
+ }
3511
+ const published = lastLodOcclusionInspection;
3512
+ if (
3513
+ published !== undefined &&
3514
+ published.submit.frameId === capturedInspection.submit.frameId &&
3515
+ published.submit.deviceGeneration === capturedInspection.submit.deviceGeneration
3516
+ ) {
3431
3517
  const queryLatency = ensureOcclusionRuntime().inspectQueryLatency();
3432
3518
  lastLodOcclusionInspection = inspectLodOcclusion({
3433
- ...lastLodOcclusionInspection,
3519
+ ...published,
3434
3520
  queryLatencyUs: {
3435
3521
  median: queryLatency.median,
3436
3522
  p95: queryLatency.p95,
@@ -3486,9 +3572,18 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3486
3572
  get lastSuccessfulCameraAntialias(): CameraSnapshot['antialias'] | undefined {
3487
3573
  return frameState.lastSuccessfulCameraAntialias;
3488
3574
  },
3575
+ get lastSuccessfulBarrelDistortion(): BarrelDistortionMapping | undefined {
3576
+ return frameState.lastSuccessfulBarrelDistortion;
3577
+ },
3578
+ get barrelDistortionInspection(): BarrelDistortionInspection {
3579
+ return inspectBarrelDistortionState(frameState, internals.deviceScope?.generation ?? 0);
3580
+ },
3489
3581
  get perFrameGraphInfo(): CompiledRenderGraphInfo | undefined {
3490
3582
  return frameState.compiledFrameGraph?.inspect();
3491
3583
  },
3584
+ get renderGraphGenerationAllocation() {
3585
+ return inspectRenderGraphGenerationAllocation(frameState);
3586
+ },
3492
3587
  get featureGraphInspection(): import('./inspection-types').RenderFeatureGraphInspection {
3493
3588
  return getRenderFeatureGraphInspection(internals);
3494
3589
  },
@@ -3515,7 +3610,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3515
3610
  });
3516
3611
  },
3517
3612
  get volumetricFog(): import('./volume/inspection').VolumetricFogInspection {
3518
- return frameState.volumetricFogInspection;
3613
+ const accepted = frameState.volumetricFogAccepted;
3614
+ return {
3615
+ ...frameState.volumetricFogInspection,
3616
+ ownerCount: accepted === undefined ? 0 : 1 + (accepted.additional?.length ?? 0),
3617
+ };
3519
3618
  },
3520
3619
  configureStandard(config: RenderPipelineAsset['config']): void {
3521
3620
  const profileConfig = internals.standardProfile;
@@ -3594,7 +3693,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3594
3693
  }
3595
3694
  builtinPostProcessEntries.set(id, entry);
3596
3695
  if (paramsBuffer !== undefined) postProcessParamsBuffers.set(id, paramsBuffer);
3597
- const moduleLabel = postProcessShaderModuleLabel(id, entry.source);
3696
+ const moduleLabel = postProcessShaderModuleLabel(entry.source);
3598
3697
  return () => {
3599
3698
  if (builtinPostProcessEntries.get(id) === entry) {
3600
3699
  builtinPostProcessEntries.delete(id);
@@ -3643,6 +3742,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3643
3742
  internals.getPipelineState()?.perPassResources.drainBloomResources?.();
3644
3743
  internals.clearPostProcessPipelineCache?.('forgeax.taa-resolve');
3645
3744
  invalidatePostProcessModule('forgeax.taa-resolve');
3745
+ // Post-process parameter UBOs are renderer-owned persistent resources,
3746
+ // rather than graph-owned transient state. Clear the active table before
3747
+ // the feature host is retired so the renderer remains the sole owner.
3748
+ disposeActivePostProcessResources();
3749
+ resetSsaoResources(internals);
3646
3750
  resetHdrpBuffers(internals);
3647
3751
  // Retire graph-owned resources and dispose instance-buffer caches.
3648
3752
  // Both calls are idempotent + tolerate per-handle errors silently;
@@ -3654,6 +3758,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3654
3758
  frameState.directionalShadowCacheRecorded = false;
3655
3759
  frameState.currentFrameObservationSource = undefined;
3656
3760
  frameState.lastSuccessfulCameraAntialias = undefined;
3761
+ frameState.lastSuccessfulBarrelDistortion = undefined;
3762
+ frameState.cloudHistoryActive = false;
3763
+ delete frameState.pendingCloudHistoryActive;
3657
3764
  frameState.currentDirectionalShadowView = null;
3658
3765
  frameState.currentSpotShadowView = null;
3659
3766
  if (frameState.pendingAutoExposureGpuResources !== undefined) {
@@ -3681,6 +3788,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3681
3788
  retireTemporalGpuState(retiring);
3682
3789
  }
3683
3790
  frameState.retiringTemporalGpuStates.clear();
3791
+ settleBarrelDistortionGraphCandidate(frameState, false);
3684
3792
  settleVolumetricFogGraphCandidate(frameState, false);
3685
3793
  for (const buffer of frameState.volumetricFogParamsBuffers) {
3686
3794
  if (buffer === null) continue;
@@ -3714,9 +3822,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3714
3822
  frameState.depthOfFieldCandidateGraph = null;
3715
3823
  frameState.depthOfFieldAccepted = undefined;
3716
3824
  frameState.depthOfFieldLastSubmitFailed = false;
3717
- if (compiled !== null) compiled.retire().catch(() => undefined);
3825
+ resetBarrelDistortionGraphState(frameState);
3826
+ if (compiled !== null) retireCompiledGraph(frameState, compiled);
3718
3827
  for (const retired of frameState.retiredCompiledFrameGraphs) {
3719
- retired.retire().catch(() => undefined);
3828
+ retireCompiledGraph(frameState, retired);
3720
3829
  }
3721
3830
  frameState.retiredCompiledFrameGraphs.clear();
3722
3831
  // feat-20260619 M4 (D-6): pass errorRegistry to disposeInstanceBuffers
@@ -3736,6 +3845,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3736
3845
  }
3737
3846
  frameState.probeBlendRecordBufferCapacity = 0;
3738
3847
  frameState.probeBlendBuffers.clear();
3848
+ delete frameState.probeBlendRecordProjection;
3739
3849
  if (frameState.morphBuffers !== undefined) {
3740
3850
  for (const entry of frameState.morphBuffers.values()) {
3741
3851
  if (!entry.buffer.isDestroyed) {
@@ -3774,6 +3884,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3774
3884
  // device. Drop the shared Standard Cluster bundle before the next
3775
3885
  // frame asks for a new generation; resetHdrpBuffers fences the bundle
3776
3886
  // using its own device/queue instead of the replacement runtime device.
3887
+ resetSsaoResources(internals);
3777
3888
  resetHdrpBuffers(internals);
3778
3889
  // Keep the persistent material demand observable across device loss.
3779
3890
  // The physical resource has already been fenced by markDeviceLost(); the
@@ -3814,9 +3925,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3814
3925
  // must be discarded, not merely marked for destruction: their opaque
3815
3926
  // handles cannot be used on the fresh device and the next draw must
3816
3927
  // lazily build a new graph from the preserved ECS / asset POD caches.
3928
+ settleBarrelDistortionGraphCandidate(frameState, false);
3817
3929
  settleVolumetricFogGraphCandidate(frameState, false);
3818
3930
  settleDepthOfFieldGraphCandidate(frameState, false);
3819
- frameState.compiledFrameGraph?.retire().catch(() => undefined);
3931
+ if (frameState.compiledFrameGraph !== null) {
3932
+ retireCompiledGraph(frameState, frameState.compiledFrameGraph);
3933
+ }
3820
3934
  frameState.compiledFrameGraph = null;
3821
3935
  frameState.compiledFrameGraphTopologyKey = null;
3822
3936
  frameState.volumetricFogPreviousGraph = null;
@@ -3828,6 +3942,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3828
3942
  frameState.depthOfFieldPreviousGraphGeneration = 0;
3829
3943
  frameState.depthOfFieldPreviousStandardLightingGraphSignature = '';
3830
3944
  frameState.depthOfFieldCandidateGraph = null;
3945
+ resetBarrelDistortionGraphState(frameState);
3831
3946
  frameState.volumetricFogAccepted = undefined;
3832
3947
  frameState.volumetricFogAcceptedContext = undefined;
3833
3948
  frameState.volumetricFogHistoryGraph = null;
@@ -3854,6 +3969,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3854
3969
  frameState.directionalShadowCacheRecorded = false;
3855
3970
  frameState.currentFrameObservationSource = undefined;
3856
3971
  frameState.lastSuccessfulCameraAntialias = undefined;
3972
+ frameState.lastSuccessfulBarrelDistortion = undefined;
3973
+ frameState.cloudHistoryActive = false;
3974
+ delete frameState.pendingCloudHistoryActive;
3857
3975
  frameState.temporalFrameTransaction.reset('device-recovery');
3858
3976
  frameState.temporalFrame = undefined;
3859
3977
  frameState.temporalFrameInput = undefined;
@@ -3876,7 +3994,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3876
3994
  frameState.retiringTemporalGpuStates.clear();
3877
3995
  frameState.hdrpClusterMembership = null;
3878
3996
  for (const retired of frameState.retiredCompiledFrameGraphs) {
3879
- retired.retire().catch(() => undefined);
3997
+ retireCompiledGraph(frameState, retired);
3880
3998
  }
3881
3999
  frameState.retiredCompiledFrameGraphs.clear();
3882
4000
  frameState.instanceBuffers.clear();
@@ -3885,6 +4003,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3885
4003
  delete frameState.probeBlendRecordBuffer;
3886
4004
  frameState.probeBlendRecordBufferCapacity = 0;
3887
4005
  frameState.probeBlendBuffers.clear();
4006
+ delete frameState.probeBlendRecordProjection;
3888
4007
  frameState.morphBuffers?.clear();
3889
4008
  frameState.transientInstanceBuffers = [];
3890
4009
  frameState.pointShadowAtlas = null;