@forgeax/engine-render 0.1.31 → 0.1.33

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 (293) hide show
  1. package/README.md +447 -34
  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/fxaa-registration.d.ts +4 -0
  5. package/dist/assembly/fxaa-registration.d.ts.map +1 -0
  6. package/dist/assembly/lod-observation.d.ts +4 -0
  7. package/dist/assembly/lod-observation.d.ts.map +1 -0
  8. package/dist/assembly/motion-blur-registration.d.ts +3 -0
  9. package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
  10. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  11. package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
  12. package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
  13. package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
  14. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
  15. package/dist/assembly/webgpu-ready.d.ts +3 -2
  16. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  17. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  18. package/dist/bloom-admission.d.ts +13 -0
  19. package/dist/bloom-admission.d.ts.map +1 -0
  20. package/dist/{chunk-FUUO4UND.mjs → chunk-3RTCJTH4.mjs} +12 -9
  21. package/dist/chunk-3RTCJTH4.mjs.map +1 -0
  22. package/dist/{chunk-NO4WZCAH.mjs → chunk-LEAXOMYJ.mjs} +3 -3
  23. package/dist/{chunk-NO4WZCAH.mjs.map → chunk-LEAXOMYJ.mjs.map} +1 -1
  24. package/dist/{chunk-JRBDO2KB.mjs → chunk-NB2S7ZRX.mjs} +166 -10
  25. package/dist/chunk-NB2S7ZRX.mjs.map +1 -0
  26. package/dist/{chunk-7MQ3XBSP.mjs → chunk-NNQL7CB3.mjs} +4 -4
  27. package/dist/{chunk-7MQ3XBSP.mjs.map → chunk-NNQL7CB3.mjs.map} +1 -1
  28. package/dist/{chunk-C3DDSXYK.mjs → chunk-OO3DRYK7.mjs} +171 -230
  29. package/dist/chunk-OO3DRYK7.mjs.map +1 -0
  30. package/dist/{chunk-S5UE2T5P.mjs → chunk-PLAXOOX5.mjs} +608 -7
  31. package/dist/chunk-PLAXOOX5.mjs.map +1 -0
  32. package/dist/{chunk-PSEQ6QPH.mjs → chunk-VOZWDM3L.mjs} +295 -198
  33. package/dist/chunk-VOZWDM3L.mjs.map +1 -0
  34. package/dist/{chunk-MDQJPY3T.mjs → chunk-ZDQWGQW7.mjs} +10 -4
  35. package/dist/chunk-ZDQWGQW7.mjs.map +1 -0
  36. package/dist/components/atmosphere.d.ts +2 -0
  37. package/dist/components/atmosphere.d.ts.map +1 -1
  38. package/dist/components/camera.d.ts +20 -4
  39. package/dist/components/camera.d.ts.map +1 -1
  40. package/dist/components/depth-of-field.d.ts +28 -0
  41. package/dist/components/depth-of-field.d.ts.map +1 -0
  42. package/dist/components/directional-light.d.ts +2 -1
  43. package/dist/components/directional-light.d.ts.map +1 -1
  44. package/dist/components/index.d.ts +1 -0
  45. package/dist/components/index.d.ts.map +1 -1
  46. package/dist/components/mesh-filter.d.ts +6 -5
  47. package/dist/components/mesh-filter.d.ts.map +1 -1
  48. package/dist/components/motion-blur.d.ts +1 -0
  49. package/dist/components/motion-blur.d.ts.map +1 -1
  50. package/dist/construct-renderer.mjs +3746 -837
  51. package/dist/construct-renderer.mjs.map +1 -1
  52. package/dist/device/gpu-residency.d.ts +6 -0
  53. package/dist/device/gpu-residency.d.ts.map +1 -1
  54. package/dist/environment/analytic-fog.d.ts +6 -0
  55. package/dist/environment/analytic-fog.d.ts.map +1 -0
  56. package/dist/errors/render.d.ts +6 -2
  57. package/dist/errors/render.d.ts.map +1 -1
  58. package/dist/extract/camera.d.ts.map +1 -1
  59. package/dist/extract/contracts.d.ts +7 -0
  60. package/dist/extract/contracts.d.ts.map +1 -1
  61. package/dist/extract/environment.d.ts +2 -0
  62. package/dist/extract/environment.d.ts.map +1 -1
  63. package/dist/features/depth-of-field/depth-of-field-assembly.d.ts +19 -0
  64. package/dist/features/depth-of-field/depth-of-field-assembly.d.ts.map +1 -0
  65. package/dist/features/depth-of-field/depth-of-field-feature.d.ts +43 -0
  66. package/dist/features/depth-of-field/depth-of-field-feature.d.ts.map +1 -0
  67. package/dist/features/depth-of-field/depth-of-field-inspection.d.ts +34 -0
  68. package/dist/features/depth-of-field/depth-of-field-inspection.d.ts.map +1 -0
  69. package/dist/features/depth-of-field/depth-of-field-params.d.ts +83 -0
  70. package/dist/features/depth-of-field/depth-of-field-params.d.ts.map +1 -0
  71. package/dist/features/error-reporting.d.ts +13 -0
  72. package/dist/features/error-reporting.d.ts.map +1 -0
  73. package/dist/features/host.d.ts +5 -0
  74. package/dist/features/host.d.ts.map +1 -1
  75. package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
  76. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
  77. package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
  78. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
  79. package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
  80. package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
  81. package/dist/features/plan.d.ts +7 -4
  82. package/dist/features/plan.d.ts.map +1 -1
  83. package/dist/features/prepared-gpu-work.d.ts +9 -5
  84. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  85. package/dist/features/render-graph-compute.d.ts.map +1 -1
  86. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  87. package/dist/features/types.d.ts +19 -1
  88. package/dist/features/types.d.ts.map +1 -1
  89. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  90. package/dist/gpu-driven/prepared-draw.d.ts +7 -0
  91. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  92. package/dist/gpu-driven/production-raster.d.ts +3 -1
  93. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  94. package/dist/gpu-driven/view-gpu.d.ts +9 -0
  95. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  96. package/dist/gpu-scene.d.ts +6 -1
  97. package/dist/gpu-scene.d.ts.map +1 -1
  98. package/dist/index.d.ts +8 -3
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.mjs +6 -5
  101. package/dist/index.mjs.map +1 -1
  102. package/dist/inspection-types.d.ts +78 -2
  103. package/dist/inspection-types.d.ts.map +1 -1
  104. package/dist/instances.d.ts +15 -1
  105. package/dist/instances.d.ts.map +1 -1
  106. package/dist/internal.mjs +6 -6
  107. package/dist/mesh-material-bindings.d.ts +3 -0
  108. package/dist/mesh-material-bindings.d.ts.map +1 -1
  109. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  110. package/dist/pipeline/standard-output/types.d.ts +4 -2
  111. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  112. package/dist/pipeline/standard-post.d.ts +28 -19
  113. package/dist/pipeline/standard-post.d.ts.map +1 -1
  114. package/dist/pipeline-spec.d.ts +2 -2
  115. package/dist/plugin.d.ts.map +1 -1
  116. package/dist/record/frame-snapshot.d.ts +24 -1
  117. package/dist/record/frame-snapshot.d.ts.map +1 -1
  118. package/dist/record/frame-targets.d.ts.map +1 -1
  119. package/dist/record/frame.d.ts +5 -2
  120. package/dist/record/frame.d.ts.map +1 -1
  121. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  122. package/dist/record/main-pass.d.ts.map +1 -1
  123. package/dist/record/mesh-ssbo.d.ts +6 -5
  124. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  125. package/dist/record/render-context.d.ts +12 -28
  126. package/dist/record/render-context.d.ts.map +1 -1
  127. package/dist/record/shadow-pass.d.ts.map +1 -1
  128. package/dist/record/skybox-post-pass.d.ts +5 -3
  129. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  130. package/dist/record/typed-frame-graph.d.ts +8 -1
  131. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  132. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  133. package/dist/render-contract.d.ts +32 -5
  134. package/dist/render-contract.d.ts.map +1 -1
  135. package/dist/render-graph-primitives.d.ts +2 -0
  136. package/dist/render-graph-primitives.d.ts.map +1 -1
  137. package/dist/render-pipeline.d.ts +15 -1
  138. package/dist/render-pipeline.d.ts.map +1 -1
  139. package/dist/render-system-extract-tail.d.ts.map +1 -1
  140. package/dist/render-system-extract.d.ts +26 -5
  141. package/dist/render-system-extract.d.ts.map +1 -1
  142. package/dist/render-system.d.ts +9 -2
  143. package/dist/render-system.d.ts.map +1 -1
  144. package/dist/scene/render-scene.d.ts +2 -0
  145. package/dist/scene/render-scene.d.ts.map +1 -1
  146. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  147. package/dist/temporal/frame.d.ts +7 -0
  148. package/dist/temporal/frame.d.ts.map +1 -1
  149. package/dist/temporal/index.mjs +5 -5
  150. package/dist/temporal/view.d.ts +6 -1
  151. package/dist/temporal/view.d.ts.map +1 -1
  152. package/dist/typed-render-graph-primitives.d.ts +10 -7
  153. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  154. package/dist/volume/component.d.ts +0 -1
  155. package/dist/volume/component.d.ts.map +1 -1
  156. package/dist/volume/passes.d.ts.map +1 -1
  157. package/dist/volume/temporal.d.ts +3 -1
  158. package/dist/volume/temporal.d.ts.map +1 -1
  159. package/package.json +21 -21
  160. package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
  161. package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
  162. package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
  163. package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
  164. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
  165. package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
  166. package/src/__tests__/environment-selection.unit.test.ts +12 -0
  167. package/src/__tests__/factory-contract.integration.test.ts +331 -68
  168. package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
  169. package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
  170. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +12 -0
  171. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
  172. package/src/__tests__/gpu-driven-production.integration.test.ts +27 -1
  173. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +1 -1
  174. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
  175. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +11 -0
  176. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
  177. package/src/__tests__/gpu-scene.unit.test.ts +91 -0
  178. package/src/__tests__/instances-store.unit.test.ts +90 -0
  179. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
  180. package/src/__tests__/motion-blur-compute.dawn.test.ts +583 -0
  181. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +49 -0
  182. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
  183. package/src/__tests__/public-surface.test-d.ts +8 -0
  184. package/src/__tests__/render-error-code-owner.test-d.ts +2 -0
  185. package/src/__tests__/render-feature-error-reporting.unit.test.ts +24 -0
  186. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
  187. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +31 -0
  188. package/src/__tests__/renderer-frame-transaction.integration.test.ts +7 -1
  189. package/src/__tests__/shader-manifest-fixture.ts +4 -4
  190. package/src/__tests__/skinned-shadow-caster.test.ts +2 -0
  191. package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
  192. package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
  193. package/src/__tests__/standard-pipeline.integration.test.ts +18 -8
  194. package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
  195. package/src/__tests__/temporal-view.unit.test.ts +2 -1
  196. package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
  197. package/src/__tests__/volume-optics-oracle.unit.test.ts +3 -1
  198. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +177 -13
  199. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +447 -61
  200. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
  201. package/src/__tests__/volumetric-fog-topology.unit.test.ts +19 -3
  202. package/src/assembly/analytic-fog-registration.ts +14 -0
  203. package/src/assembly/fxaa-registration.ts +17 -0
  204. package/src/assembly/lod-observation.ts +12 -0
  205. package/src/assembly/motion-blur-registration.ts +17 -0
  206. package/src/assembly/renderer-facade.ts +2 -0
  207. package/src/assembly/temporal-post-process-prewarm.ts +40 -0
  208. package/src/assembly/webgpu-ready-contract.ts +8 -3
  209. package/src/assembly/webgpu-ready.ts +186 -250
  210. package/src/assembly/webgpu-renderer.ts +30 -45
  211. package/src/bloom-admission.ts +21 -0
  212. package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
  213. package/src/components/atmosphere.ts +4 -0
  214. package/src/components/camera.ts +66 -10
  215. package/src/components/depth-of-field.ts +45 -0
  216. package/src/components/directional-light.ts +2 -1
  217. package/src/components/index.ts +1 -0
  218. package/src/components/mesh-filter.ts +11 -9
  219. package/src/components/motion-blur.ts +1 -0
  220. package/src/device/gpu-residency.ts +56 -12
  221. package/src/environment/analytic-fog.ts +44 -0
  222. package/src/environment/background.ts +2 -2
  223. package/src/errors/render.ts +14 -1
  224. package/src/extract/camera.ts +42 -9
  225. package/src/extract/contracts.ts +7 -0
  226. package/src/extract/environment.ts +4 -0
  227. package/src/features/depth-of-field/__tests__/depth-of-field-params.unit.test.ts +943 -0
  228. package/src/features/depth-of-field/depth-of-field-assembly.ts +100 -0
  229. package/src/features/depth-of-field/depth-of-field-feature.ts +295 -0
  230. package/src/features/depth-of-field/depth-of-field-inspection.ts +237 -0
  231. package/src/features/depth-of-field/depth-of-field-params.ts +556 -0
  232. package/src/features/error-reporting.ts +78 -0
  233. package/src/features/host.ts +26 -13
  234. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
  235. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
  236. package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
  237. package/src/features/motion-blur/motion-blur-params.ts +70 -2
  238. package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
  239. package/src/features/plan.ts +25 -8
  240. package/src/features/prepared-gpu-work.ts +86 -35
  241. package/src/features/render-graph-compute.ts +13 -5
  242. package/src/features/render-graph-contribution.ts +2 -0
  243. package/src/features/types.ts +18 -1
  244. package/src/gpu-driven/material-artifacts.ts +2 -7
  245. package/src/gpu-driven/prepared-draw.ts +15 -10
  246. package/src/gpu-driven/production-raster.ts +83 -7
  247. package/src/gpu-driven/view-gpu.ts +17 -0
  248. package/src/gpu-scene.ts +188 -21
  249. package/src/index.ts +64 -0
  250. package/src/inspection-types.ts +98 -2
  251. package/src/instances.ts +93 -0
  252. package/src/mesh-material-bindings.ts +4 -0
  253. package/src/pipeline/standard-output/graph.ts +3 -0
  254. package/src/pipeline/standard-output/types.ts +4 -1
  255. package/src/pipeline/standard-post.ts +324 -76
  256. package/src/pipeline-spec.ts +5 -5
  257. package/src/plugin.ts +2 -0
  258. package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
  259. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -1
  260. package/src/record/frame-snapshot.ts +31 -2
  261. package/src/record/frame-targets.ts +10 -6
  262. package/src/record/frame.ts +202 -28
  263. package/src/record/main-pass-geometry.ts +30 -1
  264. package/src/record/main-pass-sprite-draws.ts +6 -1
  265. package/src/record/main-pass.ts +1 -5
  266. package/src/record/mesh-ssbo.ts +43 -6
  267. package/src/record/render-context.ts +12 -28
  268. package/src/record/shadow-pass.ts +6 -1
  269. package/src/record/skybox-post-pass.ts +122 -196
  270. package/src/record/typed-frame-graph.ts +259 -15
  271. package/src/recovery/render-system-candidate.ts +28 -10
  272. package/src/render-contract.ts +34 -4
  273. package/src/render-graph-primitives.ts +8 -1
  274. package/src/render-pipeline.ts +22 -1
  275. package/src/render-system-extract-tail.ts +103 -13
  276. package/src/render-system-extract.ts +148 -38
  277. package/src/render-system.ts +332 -119
  278. package/src/scene/render-scene.ts +85 -4
  279. package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
  280. package/src/temporal/frame-coordinator.ts +8 -1
  281. package/src/temporal/frame.ts +20 -0
  282. package/src/temporal/view.ts +13 -2
  283. package/src/typed-render-graph-primitives.ts +124 -48
  284. package/src/volume/component.ts +0 -1
  285. package/src/volume/passes.ts +8 -4
  286. package/src/volume/resources.ts +1 -1
  287. package/src/volume/temporal.ts +11 -0
  288. package/dist/chunk-C3DDSXYK.mjs.map +0 -1
  289. package/dist/chunk-FUUO4UND.mjs.map +0 -1
  290. package/dist/chunk-JRBDO2KB.mjs.map +0 -1
  291. package/dist/chunk-MDQJPY3T.mjs.map +0 -1
  292. package/dist/chunk-PSEQ6QPH.mjs.map +0 -1
  293. package/dist/chunk-S5UE2T5P.mjs.map +0 -1
@@ -39,6 +39,7 @@ import {
39
39
  projectDirectionalShadowInspectionSource,
40
40
  } from './assembly/directional-shadow-inspection';
41
41
  import { createGpuDrivenOwner } from './assembly/gpu-driven-owner';
42
+ import { standardBloomAdmitted } from './bloom-admission';
42
43
  import { createClusterBinScratch } from './cluster-binner';
43
44
  import { EnvironmentLifecycle } from './environment/lifecycle';
44
45
  import {
@@ -46,11 +47,26 @@ import {
46
47
  type RenderError,
47
48
  TransmissionCapabilityMissingError,
48
49
  } from './errors/render';
50
+ import { projectDepthOfFieldInspection } from './features/depth-of-field/depth-of-field-inspection';
51
+ import { resolveDepthOfFieldFrameParams } from './features/depth-of-field/depth-of-field-params';
52
+ import { reportPendingRenderFeatureErrors } from './features/error-reporting';
49
53
  import {
50
54
  type RenderFeaturePreparedGraphicsResolverInput,
51
55
  runRenderFeatureFrame,
52
56
  settlePreparedGraphicsCompletion,
53
57
  } from './features/host';
58
+ import {
59
+ createMotionBlurFeature,
60
+ type MotionBlurFeatureInput,
61
+ } from './features/motion-blur/motion-blur-feature';
62
+ import {
63
+ MotionBlurValidationError,
64
+ motionBlurTemporalDemand,
65
+ } from './features/motion-blur/motion-blur-params';
66
+ import {
67
+ planMotionBlurFrame,
68
+ projectMotionBlurInspection,
69
+ } from './features/motion-blur/motion-blur-runtime';
54
70
  import { createFeatureNoiseResolver } from './features/noise-texture';
55
71
  import {
56
72
  createRenderFeatureGpuWorkOwner,
@@ -68,6 +84,7 @@ import {
68
84
  } from './fullscreen-post-process-pass';
69
85
  import type {
70
86
  BloomInspection,
87
+ DepthOfFieldInspection,
71
88
  DirectionalShadowInspection,
72
89
  GpuDrivenProductionInspection,
73
90
  LodOcclusionInspection,
@@ -80,6 +97,7 @@ import type {
80
97
  TemporalTargetInspection,
81
98
  TransmissionInspection,
82
99
  } from './inspection-types';
100
+ import { emptyBloomInspection } from './inspection-types';
83
101
  import { buildOcclusionProxyVertices } from './occlusion-proxy';
84
102
  import {
85
103
  AUTO_EXPOSURE_HISTOGRAM_BYTES,
@@ -215,6 +233,7 @@ import {
215
233
  type RenderFeatureGraphCandidate,
216
234
  recordRenderFeatureCandidate,
217
235
  resetRenderFeatureGraphState,
236
+ settleDepthOfFieldGraphCandidate,
218
237
  settleVolumetricFogGraphCandidate,
219
238
  } from './record/typed-frame-graph';
220
239
  import {
@@ -538,7 +557,10 @@ export interface RenderSystem {
538
557
  options: FrameObservationOptions,
539
558
  ): Promise<Result<FrameObservation, ObservationUnavailableError>>;
540
559
  /** Resolve the renderer-owned GPU LOD counters for the last accepted submit. */
541
- observeLodOcclusion(): Promise<void>;
560
+ observeLodOcclusion(receipt?: {
561
+ readonly frameId: number;
562
+ readonly deviceGeneration: number;
563
+ }): Promise<void>;
542
564
  /** Internal same-frame graph target access for backend diagnostics. */
543
565
  getCurrentGraphTarget(name: string): CurrentGraphTarget | undefined;
544
566
  /** Internal test capture copied into the draw's command submission. */
@@ -576,6 +598,9 @@ export interface RenderSystem {
576
598
  readonly temporal: TemporalInspection;
577
599
  readonly bloom: BloomInspection;
578
600
  readonly motionBlurInspection: MotionBlurInspection | undefined;
601
+ /** The last extraction validation failure, preserved for public draw(). */
602
+ readonly motionBlurInvalidParams: MotionBlurValidationError | undefined;
603
+ readonly depthOfFieldInspection: DepthOfFieldInspection | undefined;
579
604
  readonly temporalTargetInspection: TemporalTargetInspection | undefined;
580
605
  readonly lodOcclusionInspection: LodOcclusionInspection | undefined;
581
606
  /** Bounded GPU-driven counters without materializing the persistent scene table. */
@@ -718,13 +743,6 @@ function reportPreparedGraphicsCompletionError(
718
743
  );
719
744
  }
720
745
 
721
- function isPendingRenderFeaturePreparation(error: RenderError): boolean {
722
- return (
723
- error.code === 'render-feature-preparation-failed' &&
724
- error.detail.reason.startsWith('rhi-not-available:')
725
- );
726
- }
727
-
728
746
  function makePreparedPipelinePendingError(): RhiError {
729
747
  return new RhiError({
730
748
  code: 'rhi-not-available',
@@ -892,6 +910,24 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
892
910
  let preparedFeatureSkylightResources: SkylightBindGroupResources | undefined;
893
911
  const dynamicGeometryFrames = createDynamicGeometryFrameBindings();
894
912
  let latestCamera: CameraSnapshot | undefined;
913
+ // Last public parameter failure; cleared only after a valid extraction.
914
+ let lastMotionBlurInvalidParams: MotionBlurValidationError | undefined;
915
+ // Lazily install the compute lane when a capable frame demands it.
916
+ let motionBlurFeatureInput: MotionBlurFeatureInput | undefined;
917
+ const motionBlurFeature = createMotionBlurFeature(() => motionBlurFeatureInput);
918
+ const ensureMotionBlurFeature = (): void => {
919
+ const host = internals.featureHost;
920
+ if (
921
+ host === undefined ||
922
+ host.features.some((feature) => feature.identity === 'forgeax.motion-blur')
923
+ ) {
924
+ return;
925
+ }
926
+ const installed = host.install(
927
+ motionBlurFeature as import('./features/types').RenderFeature<unknown>,
928
+ );
929
+ if (!installed.ok) internals.errorRegistry.fire(installed.error);
930
+ };
895
931
  const pointsLinesOwner = new StandardPointsLinesOwner(internals);
896
932
  const instanceCollections = new InstanceCollectionStore();
897
933
  const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
@@ -1159,6 +1195,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1159
1195
  compiledFrameGraph: null,
1160
1196
  compiledFrameGraphTopologyKey: null,
1161
1197
  compiledFrameGraphGeneration: 0,
1198
+ depthOfFieldPreviousGraph: null,
1199
+ depthOfFieldPreviousGraphKey: null,
1200
+ depthOfFieldPreviousPerFrameGraph: undefined,
1201
+ depthOfFieldPreviousGraphGeneration: 0,
1202
+ depthOfFieldPreviousStandardLightingGraphSignature: '',
1203
+ depthOfFieldCandidateGraph: null,
1204
+ depthOfFieldAccepted: undefined,
1205
+ depthOfFieldLastSubmitFailed: false,
1162
1206
  volumetricFogPreviousGraph: null,
1163
1207
  volumetricFogPreviousGraphKey: null,
1164
1208
  volumetricFogCandidateGraph: null,
@@ -1278,6 +1322,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1278
1322
  // tiles until the first frame with a castShadow spot (AC-03).
1279
1323
  spotShadowSnapshots: [],
1280
1324
  };
1325
+ // A feature that is explicitly recoverable on the next frame must not
1326
+ // publish the same transient failure on every render tick. Keep one
1327
+ // renderer-owned cooldown per structured feature failure while retaining
1328
+ // the original error for the first and periodic reports.
1329
+ const pendingFeatureErrorLastReportedFrame = new Map<string, number>();
1281
1330
  const resolveSsrDependencies = (requested: boolean): SsrDependenciesInspection => {
1282
1331
  // Explicit SSR capability preparation belongs to initialization. Inspection
1283
1332
  // only projects receipts and never starts work or creates image history.
@@ -1739,6 +1788,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1739
1788
  readonly visible: number;
1740
1789
  readonly occluded: number;
1741
1790
  readonly lodHistogram: readonly { readonly level: number; readonly count: number }[];
1791
+ readonly submit?: { readonly frameId: number; readonly deviceGeneration: number };
1742
1792
  readonly worldSelections?: readonly {
1743
1793
  readonly worldKey: number;
1744
1794
  readonly primitiveSlot: number;
@@ -1750,9 +1800,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1750
1800
  }[];
1751
1801
  }
1752
1802
  | undefined,
1803
+ targetInspection: LodOcclusionInspection | undefined = lastLodOcclusionInspection,
1804
+ targetWorldAttachments: ReadonlyMap<number, string> = lastLodWorldAttachments,
1753
1805
  ): void => {
1754
- if (selection === undefined || lastLodOcclusionInspection === undefined) return;
1755
- const inspection = lastLodOcclusionInspection;
1806
+ if (selection === undefined || targetInspection === undefined) return;
1807
+ const inspection = targetInspection;
1756
1808
  // The GPU readback histogram is the selector fact, while final submitted
1757
1809
  // counts are owned by the persistent visibility projection. Keeping the
1758
1810
  // projection count here excludes ordinary non-LOD geometry (for example a
@@ -1764,7 +1816,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1764
1816
  const selectionsByAttachment = new Map<string, (typeof worldSelections)[number][]>();
1765
1817
  let attributionValid = true;
1766
1818
  for (const selected of worldSelections) {
1767
- const attachmentId = lastLodWorldAttachments.get(selected.worldKey);
1819
+ const attachmentId = targetWorldAttachments.get(selected.worldKey);
1768
1820
  if (attachmentId === undefined) {
1769
1821
  attributionValid = false;
1770
1822
  break;
@@ -1777,15 +1829,16 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1777
1829
  const nextWorlds = worlds.map((world) => {
1778
1830
  const selected = selectionsByAttachment.get(world.attachmentId);
1779
1831
  const baseRow = world.rows[0];
1780
- if (selected === undefined || baseRow === undefined) {
1781
- if (world.rows.length > 0) attributionValid = false;
1782
- return world;
1783
- }
1784
- const candidateCount = selected.reduce((sum, row) => sum + row.candidateCount, 0);
1785
- if (candidateCount !== baseRow.count.candidates) {
1786
- attributionValid = false;
1787
- return world;
1788
- }
1832
+ // A World with only plain meshes has no LOD selection row. Keep its
1833
+ // projection-only receipt and allow Worlds with valid LOD rows to
1834
+ // retain same-submit attribution. Unknown selected world keys still
1835
+ // fail closed above.
1836
+ if (selected === undefined || baseRow === undefined) return world;
1837
+ // The projection row is intentionally CPU-scoped and may omit
1838
+ // selector-only candidates that remain in the full GPU plan. The
1839
+ // same-submit GPU row owns the denominator; comparing it to the
1840
+ // projection count would reject a valid receipt (for example 11 GPU
1841
+ // candidates versus 10 CPU-admitted renderables).
1789
1842
  return Object.freeze({
1790
1843
  ...world,
1791
1844
  attribution: Object.freeze({
@@ -1815,7 +1868,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1815
1868
  lastLodOcclusionInspection = inspectLodOcclusion({
1816
1869
  ...inspection,
1817
1870
  count: {
1818
- ...lastLodOcclusionInspection.count,
1871
+ ...inspection.count,
1819
1872
  candidates: selection.candidateCount,
1820
1873
  visible: selection.visible,
1821
1874
  occluded: selection.occluded,
@@ -2261,24 +2314,40 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2261
2314
  });
2262
2315
  },
2263
2316
  get motionBlurInspection(): MotionBlurInspection | undefined {
2317
+ return projectMotionBlurInspection({
2318
+ camera: latestCamera,
2319
+ invalidParams: lastMotionBlurInvalidParams,
2320
+ capabilities: internals.device.caps,
2321
+ acceptedTemporal: frameState.lastSuccessfulTemporalView,
2322
+ transaction: frameState.temporalFrameTransaction.inspect(),
2323
+ execution: frameState.motionBlurExecution,
2324
+ temporalFrameId: frameState.temporalFrame?.frameId,
2325
+ deviceGeneration: internals.deviceScope.generation,
2326
+ graphGeneration: frameState.graphGeneration,
2327
+ });
2328
+ },
2329
+ get motionBlurInvalidParams(): MotionBlurValidationError | undefined {
2330
+ return lastMotionBlurInvalidParams;
2331
+ },
2332
+ get depthOfFieldInspection(): DepthOfFieldInspection | undefined {
2264
2333
  const camera = latestCamera;
2265
- if (camera?.motionBlur === undefined) return undefined;
2266
- const demanded = camera.motionBlur.shutterAngle > 0;
2267
- const unavailable = demanded && !internals.device.caps.rgba16floatRenderable;
2268
- const admitted =
2269
- frameState.activeTemporalGpuState !== undefined ||
2270
- frameState.temporalGpuState !== undefined;
2271
- return {
2272
- enabled: demanded,
2273
- status: !demanded ? 'off' : unavailable || !admitted ? 'reset' : 'active',
2274
- shutterAngle: camera.motionBlur.shutterAngle,
2275
- maxRadiusPixels: camera.motionBlur.maxRadiusPixels,
2276
- sampleCount: camera.motionBlur.sampleCount,
2277
- temporalDemand: demanded ? 'scene-data-temporal-v1' : 'none',
2278
- passName: 'motion-blur',
2279
- historyWrites: 0,
2280
- ...(unavailable ? { lastFailure: 'scene-data-unavailable' as const } : {}),
2281
- };
2334
+ if (camera === undefined) return undefined;
2335
+ const graph = frameState.compiledFrameGraph?.inspect();
2336
+ return projectDepthOfFieldInspection({
2337
+ ...(camera.depthOfField === undefined ? {} : { params: camera.depthOfField }),
2338
+ ...(camera.depthOfFieldError === undefined ? {} : { error: camera.depthOfFieldError }),
2339
+ ...(graph === undefined ? {} : { graph }),
2340
+ // The explicit property keeps the inspector on the submit-bound
2341
+ // transaction path even before the first accepted frame. A missing
2342
+ // property remains supported for direct structural callers/tests.
2343
+ accepted: frameState.depthOfFieldAccepted,
2344
+ ...(frameState.depthOfFieldLastSubmitFailed === true ? { lastSubmitFailed: true } : {}),
2345
+ // Inspection can be queried while the renderer is being assembled,
2346
+ // before a device scope has been published. Keep that observation
2347
+ // bounded instead of turning a not-yet-ready renderer into a getter
2348
+ // exception.
2349
+ deviceGeneration: internals.deviceScope?.generation ?? 0,
2350
+ });
2282
2351
  },
2283
2352
  get temporalTargetInspection(): TemporalTargetInspection | undefined {
2284
2353
  const graph = frameState.compiledFrameGraph?.inspect();
@@ -2321,22 +2390,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2321
2390
  const lifecycle = resources?.inspectBloomResources?.(
2322
2391
  frameState.compiledFrameGraph?.inspect(),
2323
2392
  );
2324
- return Object.freeze(
2325
- lifecycle ?? {
2326
- graphStatus: 'empty',
2327
- enabled: false,
2328
- targetCount: 0,
2329
- targetBytes: 0,
2330
- resourceCount: 0,
2331
- passCount: 0,
2332
- encodeCount: 0,
2333
- bindGroupCount: 0,
2334
- uploadCount: 0,
2335
- residentChildBytes: 0,
2336
- generation: 0,
2337
- state: 'off',
2338
- },
2339
- );
2393
+ return Object.freeze(lifecycle ?? emptyBloomInspection());
2340
2394
  },
2341
2395
  get transmission(): TransmissionInspection | undefined {
2342
2396
  return lastTransmissionInspection;
@@ -2411,6 +2465,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2411
2465
  const profileSession = internals.profiler?.activeSession();
2412
2466
  let ownsProfileFrame = false;
2413
2467
  let submitted = false;
2468
+ // A prior failed record/submit may have staged a temporal capture. A
2469
+ // capture belongs only to the draw call that created it; discard it
2470
+ // before this call can prepare or commit a new candidate.
2471
+ persistentRenderScene.discardTemporalFrame();
2414
2472
  // A failed draw must never leave a previous frame eligible to publish a
2415
2473
  // newly accepted dynamic-geometry candidate.
2416
2474
  dynamicGeometryFrames.begin();
@@ -2442,55 +2500,67 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2442
2500
  dispatchCounts.unlit = 0;
2443
2501
 
2444
2502
  const resourceWorld = compositionWorlds[resourceOwner] as World;
2445
- const frame = runProfiledRenderPhase(profileSession, 'extract', () => {
2446
- return persistentRenderScene.extractComposition(
2447
- compositionWorlds,
2448
- { cameraOwner, resourceOwner },
2449
- internals.assets.catalogEpoch,
2450
- (renderables) =>
2451
- extractFrames(
2452
- compositionWorlds,
2453
- {
2454
- cameraOwner,
2455
- resourceOwner,
2456
- ...(cameraEntityKey === undefined ? {} : { cameraEntityKey }),
2457
- },
2458
- internals.assets,
2459
- internals.getPipelineState(),
2460
- persistentRenderScene.materialSnapshotCacheStore(),
2461
- {
2462
- ...(internals.device.caps.backendKind === 'null'
2463
- ? {}
2464
- : {
2465
- materialContext: {
2466
- backend:
2467
- internals.device.caps.backendKind === 'wgpu-webgl2'
2468
- ? ('webgl2' as const)
2469
- : internals.device.caps.backendKind,
2470
- capability: internals.device.caps.storageBuffer
2471
- ? ('storage-buffer' as const)
2472
- : ('uniform-fallback' as const),
2473
- pipeline: 'forward' as const,
2474
- geometry: 'mesh' as const,
2475
- pass: 'forward' as const,
2476
- profile: 'forgeax-material-wgsl-v1' as const,
2477
- toolchain: 'naga-oil' as const,
2478
- instrumentation: 'none' as const,
2479
- },
2480
- }),
2481
- cull: 'none',
2482
- retainHidden: true,
2483
- renderables,
2484
- instanceCollections,
2485
- ...(!internals.device.caps.storageBuffer ||
2486
- internals.getMaterialShaderArtifact === undefined
2487
- ? {}
2488
- : { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
2489
- },
2490
- ),
2491
- compositionLeases,
2492
- );
2493
- });
2503
+ let frame: ReturnType<PersistentRenderScene['extractComposition']>;
2504
+ try {
2505
+ frame = runProfiledRenderPhase(profileSession, 'extract', () => {
2506
+ return persistentRenderScene.extractComposition(
2507
+ compositionWorlds,
2508
+ { cameraOwner, resourceOwner },
2509
+ internals.assets.catalogEpoch,
2510
+ (renderables) =>
2511
+ extractFrames(
2512
+ compositionWorlds,
2513
+ {
2514
+ cameraOwner,
2515
+ resourceOwner,
2516
+ ...(cameraEntityKey === undefined ? {} : { cameraEntityKey }),
2517
+ },
2518
+ internals.assets,
2519
+ internals.getPipelineState(),
2520
+ persistentRenderScene.materialSnapshotCacheStore(),
2521
+ {
2522
+ ...(internals.device.caps.backendKind === 'null'
2523
+ ? {}
2524
+ : {
2525
+ materialContext: {
2526
+ backend:
2527
+ internals.device.caps.backendKind === 'wgpu-webgl2'
2528
+ ? ('webgl2' as const)
2529
+ : internals.device.caps.backendKind,
2530
+ capability: internals.device.caps.storageBuffer
2531
+ ? ('storage-buffer' as const)
2532
+ : ('uniform-fallback' as const),
2533
+ pipeline: 'forward' as const,
2534
+ geometry: 'mesh' as const,
2535
+ pass: 'forward' as const,
2536
+ profile: 'forgeax-material-wgsl-v1' as const,
2537
+ toolchain: 'naga-oil' as const,
2538
+ instrumentation: 'none' as const,
2539
+ },
2540
+ }),
2541
+ cull: 'none',
2542
+ retainHidden: true,
2543
+ renderables,
2544
+ instanceCollections,
2545
+ ...(!internals.device.caps.storageBuffer ||
2546
+ internals.getMaterialShaderArtifact === undefined
2547
+ ? {}
2548
+ : { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
2549
+ },
2550
+ ),
2551
+ compositionLeases,
2552
+ );
2553
+ });
2554
+ } catch (cause) {
2555
+ if (cause instanceof MotionBlurValidationError) {
2556
+ lastMotionBlurInvalidParams = cause;
2557
+ latestCamera = undefined;
2558
+ internals.errorRegistry.fire(cause);
2559
+ return false;
2560
+ }
2561
+ throw cause;
2562
+ }
2563
+ lastMotionBlurInvalidParams = undefined;
2494
2564
  const {
2495
2565
  cameras: extractedCameras,
2496
2566
  cubeCameras,
@@ -2727,6 +2797,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2727
2797
  const submissionRenderables = visibilityProjection.renderables;
2728
2798
  const submissionDispatch = visibilityProjection.dispatch;
2729
2799
  const inspectionCamera = latestCamera;
2800
+ // The public frame receipt is allocated by WebGPURenderer immediately
2801
+ // before this draw (`observationFrameId`). RenderSystem's private
2802
+ // frameNumber is an internal generation and also advances on failed
2803
+ // record attempts, so it cannot be used as receipt identity. Keep the
2804
+ // internal generation for recovery/visibility state, but bind the LOD
2805
+ // inspection to the frame id that the caller will receive.
2806
+ const lodSubmitFrameId =
2807
+ (internals as RenderSystemInternals & { readonly observationFrameId?: number })
2808
+ .observationFrameId ?? frameState.frameNumber;
2730
2809
  let lodCandidateCountForFrame = 0;
2731
2810
  if (inspectionCamera !== undefined) {
2732
2811
  const firstRenderable = renderables[0];
@@ -2769,7 +2848,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2769
2848
  renderables,
2770
2849
  submissionRenderables,
2771
2850
  inspectionCamera,
2772
- frameState.frameNumber,
2851
+ lodSubmitFrameId,
2773
2852
  worldKeys,
2774
2853
  slotByEntity,
2775
2854
  {
@@ -2801,7 +2880,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2801
2880
  firstRenderable === undefined
2802
2881
  ? 'none'
2803
2882
  : `asset-handle:${firstRenderable.assetHandle}`,
2804
- sourceKey: `render-frame:${frameState.frameNumber}`,
2883
+ sourceKey: `render-frame:${lodSubmitFrameId}`,
2805
2884
  },
2806
2885
  view: {
2807
2886
  attachmentId:
@@ -2814,7 +2893,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2814
2893
  primitiveSlot: 0,
2815
2894
  slotGeneration: persistentRenderScene.inspect().topology.revision,
2816
2895
  },
2817
- generation: frameState.frameNumber,
2896
+ generation: lodSubmitFrameId,
2818
2897
  count: {
2819
2898
  candidates: candidateCount,
2820
2899
  visible: inspectionRenderables.length,
@@ -2833,7 +2912,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2833
2912
  visible: true,
2834
2913
  })),
2835
2914
  submit: {
2836
- frameId: frameState.frameNumber,
2915
+ frameId: lodSubmitFrameId,
2837
2916
  build: resolveInspectionBuild(internals),
2838
2917
  deviceGeneration: internals.deviceScope.generation,
2839
2918
  },
@@ -2845,7 +2924,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2845
2924
  lastLodWorldAttachments = new Map();
2846
2925
  }
2847
2926
  const temporalDemanded =
2848
- cameras[0]?.antialias === 'taa' || (cameras[0]?.motionBlur?.shutterAngle ?? 0) > 0;
2927
+ cameras[0]?.antialias === 'taa' ||
2928
+ (cameras[0]?.motionBlur !== undefined &&
2929
+ motionBlurTemporalDemand({
2930
+ shutterAngle: cameras[0].motionBlur.shutterAngle,
2931
+ maxRadiusPixels: cameras[0].motionBlur.maxRadiusPixels,
2932
+ sampleCount: cameras[0].motionBlur.sampleCount,
2933
+ targetFps: cameras[0].motionBlur.targetFps ?? 60,
2934
+ }));
2849
2935
  if (temporalDemanded) {
2850
2936
  persistentRenderScene.prepareTemporalFrame(submissionRenderables);
2851
2937
  }
@@ -2888,6 +2974,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2888
2974
  const materialWorld = preparedWorlds[renderable.worldId];
2889
2975
  return projectMeshMaterialBindingObservation({
2890
2976
  worldId: renderable.worldId,
2977
+ ...(materialWorld === undefined ? {} : { worldIdentity: materialWorld.identity }),
2891
2978
  entityKey: renderable.entityKey,
2892
2979
  bindings: renderable.materials.map((material, slotIndex) => ({
2893
2980
  handle: material.materialHandle ?? 0,
@@ -2955,6 +3042,44 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2955
3042
  storageBuffer: internals.device.caps.storageBuffer,
2956
3043
  multisample: internals.device.caps.backendKind !== 'wgpu-webgl2',
2957
3044
  });
3045
+ const motionBlurParams = cameras[0]?.motionBlur;
3046
+ let motionBlurDelta = 0;
3047
+ try {
3048
+ const delta = (compositionWorlds[cameraOwner] as World).getResource(Time).delta;
3049
+ motionBlurDelta = Number.isFinite(delta) ? Math.max(0, delta) : 0;
3050
+ } catch {
3051
+ motionBlurDelta = 0;
3052
+ }
3053
+ const acceptedSampleTime = frameState.lastSuccessfulTemporalView?.sampleTimeSeconds;
3054
+ const cameraViewIdentity = `camera:${cameras[0]?.entityKey ?? 0}`;
3055
+ const canvasWidth = Math.max(1, internals.canvas.width);
3056
+ const canvasHeight = Math.max(1, internals.canvas.height);
3057
+ const motionBlurPlan = planMotionBlurFrame({
3058
+ params: motionBlurParams,
3059
+ sampleTimeSeconds: opts.sampleTimeSeconds,
3060
+ acceptedSampleTimeSeconds: acceptedSampleTime,
3061
+ fallbackDeltaSeconds: motionBlurDelta,
3062
+ acceptedTemporal: frameState.lastSuccessfulTemporalView,
3063
+ temporalReset: opts.temporalReset === true,
3064
+ width: canvasWidth,
3065
+ height: canvasHeight,
3066
+ cameraViewIdentity,
3067
+ historyVersion: cameras[0]?.historyVersion ?? 0,
3068
+ environmentSignature: environment?.environmentSignature ?? '',
3069
+ fogSignature: environment?.fogSignature ?? '',
3070
+ deviceGeneration: internals.deviceScope?.generation ?? 0,
3071
+ });
3072
+ const demandedMotionBlur = motionBlurPlan.demanded;
3073
+ motionBlurFeatureInput = motionBlurPlan.featureInput;
3074
+ if (
3075
+ demandedMotionBlur &&
3076
+ internals.device.caps.compute === true &&
3077
+ internals.device.caps.storageBuffer === true &&
3078
+ internals.device.caps.storageTexture === true &&
3079
+ internals.device.caps.rgba16floatRenderable === true
3080
+ ) {
3081
+ ensureMotionBlurFeature();
3082
+ }
2958
3083
  preparedFeatureSkylightResources =
2959
3084
  preparedPipelineState === null
2960
3085
  ? undefined
@@ -2967,6 +3092,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2967
3092
  worlds: compositionWorlds,
2968
3093
  owner: resourceOwner,
2969
3094
  frameNumber: frameState.frameNumber,
3095
+ frameSize: { width: internals.canvas.width, height: internals.canvas.height },
2970
3096
  visibilitySnapshots: frame.featureVisibilitySnapshots,
2971
3097
  hiddenEntityReports: frame.hiddenEntityReports,
2972
3098
  targets: featureTargets,
@@ -3001,11 +3127,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3001
3127
  gpuWork: featureGpuWork,
3002
3128
  });
3003
3129
  lastVisibilityStats.explicitlyHidden = featureFrame.hiddenEntityReports.length;
3004
- for (const featureError of featureFrame.errors) {
3005
- if (!isPendingRenderFeaturePreparation(featureError)) {
3006
- internals.errorRegistry.fire(featureError);
3007
- }
3008
- }
3130
+ reportPendingRenderFeatureErrors(
3131
+ featureFrame.errors,
3132
+ frameState.frameNumber,
3133
+ pendingFeatureErrorLastReportedFrame,
3134
+ internals.errorRegistry,
3135
+ );
3009
3136
  const postProcessParamsCandidate = preparePostProcessParamsResources(
3010
3137
  featureFrame.fullscreenEffects,
3011
3138
  );
@@ -3138,7 +3265,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3138
3265
  }
3139
3266
  return runProfiledRenderPhase(profileSession, phase, action);
3140
3267
  };
3141
- if (cameras[0]?.bloom === 'on') {
3268
+ const bloomAdmitted = standardBloomAdmitted(cameras[0]);
3269
+ if (bloomAdmitted) {
3142
3270
  internals.getPipelineState()?.perPassResources.ensureBloomResources?.();
3143
3271
  }
3144
3272
  const liveOutput = cameras[0]?.output;
@@ -3192,6 +3320,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3192
3320
  activeEntityKeys: visibilityProjection.activeEntityKeys,
3193
3321
  activeEntityRevision: visibilityProjection.activeEntityRevision,
3194
3322
  telemetryCandidateCount: lodCandidateCountForFrame,
3323
+ telemetrySubmit: {
3324
+ frameId: lodSubmitFrameId,
3325
+ deviceGeneration: internals.deviceScope.generation,
3326
+ },
3195
3327
  ...(occlusionProjection === undefined ? {} : { occlusion: occlusionProjection }),
3196
3328
  },
3197
3329
  compositionLeases,
@@ -3220,6 +3352,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3220
3352
  undefined,
3221
3353
  ssrDependencies,
3222
3354
  dynamicGeometryFrames.onRenderableDraw(compositionWorlds),
3355
+ environment?.fog,
3356
+ opts.sampleTimeSeconds,
3357
+ opts.temporalReset === true,
3223
3358
  ),
3224
3359
  );
3225
3360
  dynamicGeometryFrames.commit(submitted);
@@ -3283,8 +3418,31 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3283
3418
  internals.errorRegistry.fire(temporalCommit.error);
3284
3419
  }
3285
3420
  frameState.lastSuccessfulCameraAntialias = cameras[0]?.antialias;
3421
+ const submittedCamera = cameras[0];
3422
+ const submittedDepthOfField =
3423
+ submittedCamera === undefined
3424
+ ? undefined
3425
+ : resolveDepthOfFieldFrameParams(
3426
+ submittedCamera.depthOfField,
3427
+ submittedCamera.depthOfFieldError,
3428
+ frameState.depthOfFieldAccepted?.params,
3429
+ );
3430
+ const submittedDepthOfFieldGraph = frameState.compiledFrameGraph?.inspect();
3431
+ if (submittedDepthOfField !== undefined && submittedDepthOfFieldGraph !== undefined) {
3432
+ frameState.depthOfFieldAccepted = {
3433
+ params: submittedDepthOfField,
3434
+ graph: submittedDepthOfFieldGraph,
3435
+ deviceGeneration: internals.deviceScope.generation,
3436
+ };
3437
+ frameState.depthOfFieldLastSubmitFailed = false;
3438
+ } else if (submittedDepthOfField === undefined) {
3439
+ frameState.depthOfFieldAccepted = undefined;
3440
+ frameState.depthOfFieldLastSubmitFailed = false;
3441
+ } else {
3442
+ frameState.depthOfFieldLastSubmitFailed = true;
3443
+ }
3286
3444
  internals.getPipelineState()?.perPassResources.commitBloomResources?.();
3287
- const nextBloom = cameras[0]?.bloom ?? 'off';
3445
+ const nextBloom = bloomAdmitted ? 'on' : 'off';
3288
3446
  if (frameState.lastSuccessfulBloom === 'on' && nextBloom !== 'on') {
3289
3447
  internals
3290
3448
  .getPipelineState()
@@ -3294,6 +3452,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3294
3452
  }
3295
3453
  frameState.lastSuccessfulBloom = nextBloom;
3296
3454
  } else {
3455
+ if (cameras[0]?.depthOfField !== undefined) {
3456
+ frameState.depthOfFieldLastSubmitFailed = true;
3457
+ }
3297
3458
  internals.getPipelineState()?.perPassResources.discardBloomResources?.();
3298
3459
  }
3299
3460
  if (!submitted && frameState.environmentGeneration !== undefined) {
@@ -3346,6 +3507,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3346
3507
  }),
3347
3508
  );
3348
3509
  } finally {
3510
+ if (!submitted) persistentRenderScene.discardTemporalFrame();
3349
3511
  internals.recoveryColdWorkGuard?.endFrame();
3350
3512
  if (ownsProfileFrame) {
3351
3513
  try {
@@ -3364,13 +3526,46 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3364
3526
  const currentFrameId = frameState.frameNumber - 1;
3365
3527
  return observeCurrentFrame(options, frameState.currentFrameObservationSource, currentFrameId);
3366
3528
  },
3367
- async observeLodOcclusion(): Promise<void> {
3529
+ async observeLodOcclusion(receipt?: {
3530
+ readonly frameId: number;
3531
+ readonly deviceGeneration: number;
3532
+ }): Promise<void> {
3533
+ const capturedInspection = lastLodOcclusionInspection;
3534
+ const capturedWorldAttachments = lastLodWorldAttachments;
3535
+ if (capturedInspection === undefined) return;
3536
+ if (
3537
+ receipt !== undefined &&
3538
+ (capturedInspection.submit.frameId !== receipt.frameId ||
3539
+ capturedInspection.submit.deviceGeneration !== receipt.deviceGeneration)
3540
+ )
3541
+ return;
3368
3542
  await ensureOcclusionRuntime().waitForCompletions();
3369
- applyLodSelectionTelemetry(await gpuDrivenProduction.readLodSelection());
3370
- if (lastLodOcclusionInspection !== undefined) {
3543
+ if (lastLodOcclusionInspection !== capturedInspection) return;
3544
+ const selection = await gpuDrivenProduction.readLodSelection();
3545
+ if (lastLodOcclusionInspection !== capturedInspection || selection === undefined) return;
3546
+ if (
3547
+ selection.submit !== undefined &&
3548
+ (selection.submit.frameId !== capturedInspection.submit.frameId ||
3549
+ selection.submit.deviceGeneration !== capturedInspection.submit.deviceGeneration)
3550
+ )
3551
+ return;
3552
+ if (
3553
+ receipt !== undefined &&
3554
+ (selection.submit === undefined ||
3555
+ selection.submit.frameId !== receipt.frameId ||
3556
+ selection.submit.deviceGeneration !== receipt.deviceGeneration)
3557
+ )
3558
+ return;
3559
+ applyLodSelectionTelemetry(selection, capturedInspection, capturedWorldAttachments);
3560
+ const published = lastLodOcclusionInspection;
3561
+ if (
3562
+ published !== undefined &&
3563
+ published.submit.frameId === capturedInspection.submit.frameId &&
3564
+ published.submit.deviceGeneration === capturedInspection.submit.deviceGeneration
3565
+ ) {
3371
3566
  const queryLatency = ensureOcclusionRuntime().inspectQueryLatency();
3372
3567
  lastLodOcclusionInspection = inspectLodOcclusion({
3373
- ...lastLodOcclusionInspection,
3568
+ ...published,
3374
3569
  queryLatencyUs: {
3375
3570
  median: queryLatency.median,
3376
3571
  p95: queryLatency.p95,
@@ -3638,6 +3833,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3638
3833
  frameState.volumetricFogHistorySlot = null;
3639
3834
  frameState.volumetricFogHistorySignature = null;
3640
3835
  frameState.hdrpClusterMembership = null;
3836
+ settleDepthOfFieldGraphCandidate(frameState, false);
3641
3837
  const compiled = frameState.compiledFrameGraph;
3642
3838
  frameState.compiledFrameGraph = null;
3643
3839
  frameState.compiledFrameGraphTopologyKey = null;
@@ -3645,6 +3841,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3645
3841
  frameState.standardLightingGraphSignature = '';
3646
3842
  frameState.standardLightingInspection = undefined;
3647
3843
  frameState.pointShadowInspection = undefined;
3844
+ frameState.depthOfFieldPreviousGraph = null;
3845
+ frameState.depthOfFieldPreviousGraphKey = null;
3846
+ frameState.depthOfFieldPreviousPerFrameGraph = undefined;
3847
+ frameState.depthOfFieldPreviousGraphGeneration = 0;
3848
+ frameState.depthOfFieldPreviousStandardLightingGraphSignature = '';
3849
+ frameState.depthOfFieldCandidateGraph = null;
3850
+ frameState.depthOfFieldAccepted = undefined;
3851
+ frameState.depthOfFieldLastSubmitFailed = false;
3648
3852
  if (compiled !== null) compiled.retire().catch(() => undefined);
3649
3853
  for (const retired of frameState.retiredCompiledFrameGraphs) {
3650
3854
  retired.retire().catch(() => undefined);
@@ -3746,12 +3950,19 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3746
3950
  // handles cannot be used on the fresh device and the next draw must
3747
3951
  // lazily build a new graph from the preserved ECS / asset POD caches.
3748
3952
  settleVolumetricFogGraphCandidate(frameState, false);
3953
+ settleDepthOfFieldGraphCandidate(frameState, false);
3749
3954
  frameState.compiledFrameGraph?.retire().catch(() => undefined);
3750
3955
  frameState.compiledFrameGraph = null;
3751
3956
  frameState.compiledFrameGraphTopologyKey = null;
3752
3957
  frameState.volumetricFogPreviousGraph = null;
3753
3958
  frameState.volumetricFogPreviousGraphKey = null;
3754
3959
  frameState.volumetricFogCandidateGraph = null;
3960
+ frameState.depthOfFieldPreviousGraph = null;
3961
+ frameState.depthOfFieldPreviousGraphKey = null;
3962
+ frameState.depthOfFieldPreviousPerFrameGraph = undefined;
3963
+ frameState.depthOfFieldPreviousGraphGeneration = 0;
3964
+ frameState.depthOfFieldPreviousStandardLightingGraphSignature = '';
3965
+ frameState.depthOfFieldCandidateGraph = null;
3755
3966
  frameState.volumetricFogAccepted = undefined;
3756
3967
  frameState.volumetricFogAcceptedContext = undefined;
3757
3968
  frameState.volumetricFogHistoryGraph = null;
@@ -3772,6 +3983,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3772
3983
  frameState.standardLightingGraphSignature = '';
3773
3984
  frameState.standardLightingInspection = undefined;
3774
3985
  frameState.pointShadowInspection = undefined;
3986
+ frameState.depthOfFieldAccepted = undefined;
3987
+ frameState.depthOfFieldLastSubmitFailed = false;
3775
3988
  frameState.directionalShadowCache = null;
3776
3989
  frameState.directionalShadowCacheRecorded = false;
3777
3990
  frameState.currentFrameObservationSource = undefined;