@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
@@ -5,8 +5,10 @@ import type {
5
5
  } from '@forgeax/engine-render-graph';
6
6
  import { RenderGraphError } from '@forgeax/engine-render-graph';
7
7
  import { err, ok, type Result } from '@forgeax/engine-types';
8
+ import { standardBloomAdmitted } from '../bloom-admission';
8
9
  import { addTypedDebugOverlayPass } from '../debug-draw-glue';
9
10
  import { SceneDataUnavailableError } from '../errors/render';
11
+ import { addDepthOfFieldPasses } from '../features/depth-of-field/depth-of-field-feature';
10
12
  import { addMotionBlurPass } from '../features/motion-blur/motion-blur-feature';
11
13
  import { GPU_TEXTURE_USAGE_COPY_SRC } from '../gpu-texture-usage';
12
14
  import type {
@@ -29,6 +31,7 @@ import {
29
31
  TYPED_BLOOM_PASS_NAMES,
30
32
  type TypedTemporalResolveTargets,
31
33
  } from '../typed-render-graph-primitives';
34
+ import { renderExtentSize } from './render-extent';
32
35
  import {
33
36
  addAutoExposureExposurePass,
34
37
  addAutoExposureGraphPasses,
@@ -41,22 +44,81 @@ interface StandardPostSurface {
41
44
  readonly storage: RenderPipelineTarget;
42
45
  }
43
46
 
47
+ export const STANDARD_BLOOM_MAX_LEVELS = 5;
48
+
44
49
  /** One authored Bloom contract drives declaration and compiled-graph validation. */
45
50
  export const STANDARD_BLOOM_TARGET_SPEC = {
46
- composited: { label: 'bloom-composited', format: 'rgba16float', size: 'surface' },
47
- bright: { label: 'bloom-bright', format: 'rgba16float', size: 'half-surface' },
48
- blurH: { label: 'bloom-blur-h', format: 'rgba16float', size: 'half-surface' },
49
- blurV: { label: 'bloom-blur-v', format: 'rgba16float', size: 'half-surface' },
50
- } as const satisfies Record<
51
- 'composited' | 'bright' | 'blurH' | 'blurV',
52
- Pick<GraphTextureDescriptor, 'format' | 'size'> & { readonly label: string }
53
- >;
51
+ composited: { label: 'bloom-composited', format: 'rgba16float' },
52
+ downsamplePrefix: 'bloom-downsample-',
53
+ upsamplePrefix: 'bloom-upsample-',
54
+ } as const;
55
+
56
+ export interface StandardBloomLevelDimensions {
57
+ readonly width: number;
58
+ readonly height: number;
59
+ }
60
+
61
+ /** Derive the bounded ceil-halving output pyramid, including D0. */
62
+ export function deriveStandardBloomLevels(
63
+ outputWidth: number,
64
+ outputHeight: number,
65
+ ): readonly StandardBloomLevelDimensions[] {
66
+ const levels: StandardBloomLevelDimensions[] = [];
67
+ let width = Math.max(1, Math.ceil(outputWidth / 2));
68
+ let height = Math.max(1, Math.ceil(outputHeight / 2));
69
+ for (let level = 0; level < STANDARD_BLOOM_MAX_LEVELS; level += 1) {
70
+ levels.push({ width, height });
71
+ if (width === 1 && height === 1) break;
72
+ width = Math.max(1, Math.ceil(width / 2));
73
+ height = Math.max(1, Math.ceil(height / 2));
74
+ }
75
+ return levels;
76
+ }
77
+
78
+ export interface StandardBloomTargetSpecs {
79
+ readonly composited: GraphTextureDescriptor & { readonly label: string };
80
+ readonly downsample: readonly (GraphTextureDescriptor & { readonly label: string })[];
81
+ readonly upsample: readonly (GraphTextureDescriptor & { readonly label: string })[];
82
+ readonly levels: readonly StandardBloomLevelDimensions[];
83
+ }
84
+
85
+ function standardBloomTargetSpecs(
86
+ outputWidth: number,
87
+ outputHeight: number,
88
+ ): StandardBloomTargetSpecs {
89
+ const levels = deriveStandardBloomLevels(outputWidth, outputHeight);
90
+ return {
91
+ composited: {
92
+ label: STANDARD_BLOOM_TARGET_SPEC.composited.label,
93
+ format: STANDARD_BLOOM_TARGET_SPEC.composited.format,
94
+ size: { width: outputWidth, height: outputHeight },
95
+ domain: 'linear-hdr',
96
+ },
97
+ downsample: levels.map((size, level) => ({
98
+ label: `${STANDARD_BLOOM_TARGET_SPEC.downsamplePrefix}${level}`,
99
+ format: 'rgba16float' as const,
100
+ size,
101
+ domain: 'linear-hdr' as const,
102
+ })),
103
+ upsample: levels.slice(0, -1).map((size, level) => ({
104
+ label: `${STANDARD_BLOOM_TARGET_SPEC.upsamplePrefix}${level}`,
105
+ format: 'rgba16float' as const,
106
+ size,
107
+ domain: 'linear-hdr' as const,
108
+ })),
109
+ levels,
110
+ };
111
+ }
54
112
 
55
113
  export interface StandardBloomGraphInspection {
56
114
  readonly status: 'empty' | 'valid' | 'invalid';
57
115
  readonly targetCount: number;
58
116
  readonly targetBytes: number;
59
117
  readonly passCount: number;
118
+ readonly levelCount: number;
119
+ readonly levelDimensions: readonly StandardBloomLevelDimensions[];
120
+ readonly downsamplePassCount: number;
121
+ readonly upsamplePassCount: number;
60
122
  }
61
123
 
62
124
  const emptyBloomGraphInspection = (): StandardBloomGraphInspection => ({
@@ -64,6 +126,10 @@ const emptyBloomGraphInspection = (): StandardBloomGraphInspection => ({
64
126
  targetCount: 0,
65
127
  targetBytes: 0,
66
128
  passCount: 0,
129
+ levelCount: 0,
130
+ levelDimensions: [],
131
+ downsamplePassCount: 0,
132
+ upsamplePassCount: 0,
67
133
  });
68
134
 
69
135
  const invalidBloomGraphInspection = (): StandardBloomGraphInspection => ({
@@ -71,21 +137,23 @@ const invalidBloomGraphInspection = (): StandardBloomGraphInspection => ({
71
137
  targetCount: 0,
72
138
  targetBytes: 0,
73
139
  passCount: 0,
140
+ levelCount: 0,
141
+ levelDimensions: [],
142
+ downsamplePassCount: 0,
143
+ upsamplePassCount: 0,
74
144
  });
75
145
 
76
- function textureBytes(
77
- resource: CompiledRenderGraphInfo['resources'][number],
78
- expected: (typeof STANDARD_BLOOM_TARGET_SPEC)[keyof typeof STANDARD_BLOOM_TARGET_SPEC],
79
- ): number {
146
+ type CompiledBloomResource = CompiledRenderGraphInfo['resources'][number];
147
+
148
+ function textureBytes(resource: CompiledBloomResource): number | undefined {
80
149
  const descriptor = resource.descriptor;
81
150
  if (
151
+ resource.kind !== 'texture' ||
82
152
  descriptor === undefined ||
83
153
  descriptor.kind !== 'texture' ||
84
- descriptor.format !== expected.format ||
85
- !('size' in descriptor) ||
86
- descriptor.size !== expected.size
154
+ descriptor.format !== 'rgba16float'
87
155
  )
88
- return 0;
156
+ return undefined;
89
157
  const texelBytes = 8;
90
158
  let bytes = 0;
91
159
  for (let level = 0; level < descriptor.mipLevelCount; level += 1) {
@@ -95,7 +163,56 @@ function textureBytes(
95
163
  descriptor.depthOrArrayLayers *
96
164
  texelBytes;
97
165
  }
98
- return bytes * descriptor.sampleCount;
166
+ const descriptorBytes = bytes * descriptor.sampleCount;
167
+ return resource.byteSize === descriptorBytes && descriptorBytes > 0 ? descriptorBytes : undefined;
168
+ }
169
+
170
+ function resourceSize(
171
+ resource: CompiledBloomResource,
172
+ ): { readonly width: number; readonly height: number } | undefined {
173
+ const descriptor = resource.descriptor;
174
+ if (resource.kind !== 'texture' || descriptor.kind !== 'texture') return undefined;
175
+ if (typeof descriptor.size === 'string') return undefined;
176
+ if (
177
+ resource.extent === undefined ||
178
+ resource.extent.width !== descriptor.width ||
179
+ resource.extent.height !== descriptor.height ||
180
+ resource.extent.depthOrArrayLayers !== descriptor.depthOrArrayLayers
181
+ ) {
182
+ return undefined;
183
+ }
184
+ if (
185
+ descriptor.format !== 'rgba16float' ||
186
+ descriptor.domain !== 'linear-hdr' ||
187
+ descriptor.width <= 0 ||
188
+ descriptor.height <= 0 ||
189
+ descriptor.depthOrArrayLayers !== 1 ||
190
+ descriptor.mipLevelCount !== 1 ||
191
+ descriptor.sampleCount !== 1 ||
192
+ resource.dimension !== '2d' ||
193
+ descriptor.size.width !== descriptor.width ||
194
+ descriptor.size.height !== descriptor.height ||
195
+ textureBytes(resource) === undefined
196
+ ) {
197
+ return undefined;
198
+ }
199
+ return { width: descriptor.width, height: descriptor.height };
200
+ }
201
+
202
+ function hasPassAccesses(
203
+ pass: CompiledRenderGraphInfo['passes'][number] | undefined,
204
+ accesses: readonly { readonly resource: string; readonly usage: string }[],
205
+ ): boolean {
206
+ return (
207
+ pass !== undefined &&
208
+ pass.kind === 'raster' &&
209
+ pass.accesses.length === accesses.length &&
210
+ accesses.every(
211
+ (access, index) =>
212
+ pass.accesses[index]?.resource === access.resource &&
213
+ pass.accesses[index]?.usage === access.usage,
214
+ )
215
+ );
99
216
  }
100
217
 
101
218
  /** Derive Bloom target/pass facts solely from the compiled Standard graph. */
@@ -103,33 +220,125 @@ export function inspectStandardBloomGraph(
103
220
  graph: CompiledRenderGraphInfo | undefined,
104
221
  ): StandardBloomGraphInspection {
105
222
  if (graph === undefined) return emptyBloomGraphInspection();
106
- type BloomTargetSpec =
107
- (typeof STANDARD_BLOOM_TARGET_SPEC)[keyof typeof STANDARD_BLOOM_TARGET_SPEC];
108
- const targetSpec = new Map<string, BloomTargetSpec>(
109
- Object.values(STANDARD_BLOOM_TARGET_SPEC).map((entry) => [entry.label, entry] as const),
223
+ const composited = graph.resources.filter(
224
+ (resource) => resource.label === STANDARD_BLOOM_TARGET_SPEC.composited.label,
110
225
  );
111
- const targets = graph.resources.flatMap((resource) => {
112
- const expected = targetSpec.get(resource.label);
113
- return expected === undefined ? [] : [{ resource, expected }];
114
- });
115
- const passNames = new Set<string>(Object.values(TYPED_BLOOM_PASS_NAMES));
116
- const passes = graph.passes.filter((pass) => passNames.has(pass.name));
226
+ const downsample = graph.resources
227
+ .filter((resource) => resource.label.startsWith(STANDARD_BLOOM_TARGET_SPEC.downsamplePrefix))
228
+ .sort((a, b) => a.label.localeCompare(b.label, undefined, { numeric: true }));
229
+ const upsample = graph.resources
230
+ .filter((resource) => resource.label.startsWith(STANDARD_BLOOM_TARGET_SPEC.upsamplePrefix))
231
+ .sort((a, b) => a.label.localeCompare(b.label, undefined, { numeric: true }));
232
+ const targets = [...composited, ...downsample, ...upsample];
233
+ const passes = graph.passes
234
+ .filter(
235
+ (pass) =>
236
+ pass.name === TYPED_BLOOM_PASS_NAMES.composite ||
237
+ pass.name.startsWith(`${TYPED_BLOOM_PASS_NAMES.downsample}-`) ||
238
+ pass.name.startsWith(`${TYPED_BLOOM_PASS_NAMES.upsample}-`),
239
+ )
240
+ .sort((left, right) => left.executionIndex - right.executionIndex);
117
241
  if (targets.length === 0 && passes.length === 0) return emptyBloomGraphInspection();
242
+ if (composited.length !== 1) return invalidBloomGraphInspection();
243
+ const compositedResource = composited[0];
244
+ if (compositedResource === undefined) return invalidBloomGraphInspection();
245
+ const compositedSize = resourceSize(compositedResource);
246
+ if (compositedSize === undefined) return invalidBloomGraphInspection();
247
+
248
+ const expected = standardBloomTargetSpecs(compositedSize.width, compositedSize.height);
249
+ const expectedDownsampleLabels = expected.downsample.map((entry) => entry.label);
250
+ const expectedUpsampleLabels = expected.upsample.map((entry) => entry.label);
251
+ const orderedDownsample = downsample.sort((left, right) =>
252
+ left.label.localeCompare(right.label, undefined, { numeric: true }),
253
+ );
254
+ const orderedUpsample = upsample.sort((left, right) =>
255
+ left.label.localeCompare(right.label, undefined, { numeric: true }),
256
+ );
118
257
  const completeTargetSet =
119
- targets.length === Object.values(STANDARD_BLOOM_TARGET_SPEC).length &&
120
- targets.every(({ resource, expected }) => textureBytes(resource, expected) > 0);
121
- const completePassSet = passes.length === Object.values(TYPED_BLOOM_PASS_NAMES).length;
122
- if (!completeTargetSet || !completePassSet) {
258
+ targets.length === 1 + expectedDownsampleLabels.length + expectedUpsampleLabels.length &&
259
+ orderedDownsample.length === expectedDownsampleLabels.length &&
260
+ orderedUpsample.length === expectedUpsampleLabels.length &&
261
+ orderedDownsample.every(
262
+ (resource, level) => resource.label === expectedDownsampleLabels[level],
263
+ ) &&
264
+ orderedUpsample.every((resource, level) => resource.label === expectedUpsampleLabels[level]) &&
265
+ orderedDownsample.every(
266
+ (resource, level) =>
267
+ resourceSize(resource)?.width === expected.levels[level]?.width &&
268
+ resourceSize(resource)?.height === expected.levels[level]?.height,
269
+ ) &&
270
+ orderedUpsample.every(
271
+ (resource, level) =>
272
+ resourceSize(resource)?.width === expected.levels[level]?.width &&
273
+ resourceSize(resource)?.height === expected.levels[level]?.height,
274
+ );
275
+ const expectedPassNames = [
276
+ ...expectedDownsampleLabels,
277
+ ...[...expectedUpsampleLabels].reverse(),
278
+ TYPED_BLOOM_PASS_NAMES.composite,
279
+ ];
280
+ const completePassSet =
281
+ passes.length === expected.levels.length + expected.upsample.length + 1 &&
282
+ passes.every((pass, index) => pass.name === expectedPassNames[index]);
283
+ const sceneLabel = passes[0]?.accesses[0]?.resource;
284
+ const passTopology =
285
+ completePassSet &&
286
+ sceneLabel !== undefined &&
287
+ !sceneLabel.startsWith(STANDARD_BLOOM_TARGET_SPEC.downsamplePrefix) &&
288
+ !sceneLabel.startsWith(STANDARD_BLOOM_TARGET_SPEC.upsamplePrefix) &&
289
+ sceneLabel !== STANDARD_BLOOM_TARGET_SPEC.composited.label &&
290
+ graph.resources.some((resource) => resource.label === sceneLabel) &&
291
+ orderedDownsample.every((_, level) =>
292
+ hasPassAccesses(passes[level], [
293
+ {
294
+ resource: level === 0 ? sceneLabel : `bloom-downsample-${level - 1}`,
295
+ usage: 'sampled-read',
296
+ },
297
+ { resource: `bloom-downsample-${level}`, usage: 'color-attachment' },
298
+ ]),
299
+ ) &&
300
+ orderedUpsample.every((_, level) => {
301
+ const passIndex = expected.levels.length + expected.upsample.length - 1 - level;
302
+ return hasPassAccesses(passes[passIndex], [
303
+ { resource: `bloom-downsample-${level}`, usage: 'sampled-read' },
304
+ {
305
+ resource:
306
+ level === expected.levels.length - 2
307
+ ? `bloom-downsample-${level + 1}`
308
+ : `bloom-upsample-${level + 1}`,
309
+ usage: 'sampled-read',
310
+ },
311
+ { resource: `bloom-upsample-${level}`, usage: 'color-attachment' },
312
+ ]);
313
+ }) &&
314
+ hasPassAccesses(passes.at(-1), [
315
+ { resource: sceneLabel, usage: 'sampled-read' },
316
+ {
317
+ resource: expected.levels.length === 1 ? 'bloom-downsample-0' : 'bloom-upsample-0',
318
+ usage: 'sampled-read',
319
+ },
320
+ { resource: STANDARD_BLOOM_TARGET_SPEC.composited.label, usage: 'color-attachment' },
321
+ ]);
322
+ if (!completeTargetSet || !passTopology) {
323
+ return invalidBloomGraphInspection();
324
+ }
325
+ const levelDimensions = orderedDownsample.flatMap((resource) => {
326
+ const size = resourceSize(resource);
327
+ return size === undefined ? [] : [size];
328
+ });
329
+ if (levelDimensions.length !== orderedDownsample.length) {
123
330
  return invalidBloomGraphInspection();
124
331
  }
332
+ const targetBytes = targets.reduce((bytes, resource) => bytes + (textureBytes(resource) ?? 0), 0);
125
333
  return {
126
334
  status: 'valid',
127
335
  targetCount: targets.length,
128
- targetBytes: targets.reduce(
129
- (bytes, target) => bytes + textureBytes(target.resource, target.expected),
130
- 0,
131
- ),
336
+ targetBytes,
132
337
  passCount: passes.length,
338
+ levelCount: orderedDownsample.length,
339
+ levelDimensions,
340
+ downsamplePassCount: orderedDownsample.length,
341
+ upsamplePassCount: orderedUpsample.length,
133
342
  };
134
343
  }
135
344
 
@@ -159,8 +368,8 @@ function missingTemporalContributor(
159
368
  /**
160
369
  * Build the one Standard post chain shared by direct and clustered lighting.
161
370
  * Bloom is admitted before any Bloom target is declared, keeping the disabled
162
- * path exact-zero while preserving the existing half-resolution four-pass
163
- * implementation for the enabled path.
371
+ * path exact-zero while building one bounded five-level HDR
372
+ * downsample/tent-upsample pyramid for the enabled path.
164
373
  */
165
374
  export function addStandardPost(
166
375
  context: RenderPipelineBuildContext<RenderPipelineFrame>,
@@ -178,7 +387,8 @@ export function addStandardPost(
178
387
  lane: topology.standardProfile?.renderPath === 'deferred' ? 'clustered' : 'direct',
179
388
  temporal: !clearOnly && topology.camera.antialias === 'taa',
180
389
  meter: topology.output?.autoExposure === true,
181
- bloom: !clearOnly && topology.camera.bloom === 'on' && topology.camera.tonemap !== 'none',
390
+ bloom: !clearOnly && standardBloomAdmitted(topology.camera),
391
+ dof: !clearOnly && topology.camera.depthOfField !== undefined,
182
392
  exposure: topology.output?.autoExposure === true,
183
393
  whiteBalance: topology.output?.whiteBalance === true,
184
394
  lut: topology.output?.colorLut === true,
@@ -218,6 +428,33 @@ export function addStandardPost(
218
428
  const finalPresent = surface.storage;
219
429
  let postInput = scene;
220
430
  let postTemporal = sceneTemporal;
431
+ if (!clearOnly && topology.analyticFog === true) {
432
+ if (depth.sampleCount !== 1)
433
+ return err(
434
+ new RenderGraphError({
435
+ code: 'resource-resolution-failed',
436
+ expected: 'analytic fog receives single-sample scene depth',
437
+ hint: 'select FXAA, TAA or no AA for analytic fog',
438
+ detail: { resourceLabel: 'scene-depth' },
439
+ }),
440
+ );
441
+ const fogged = target(context, 'analytic-fog-color', {
442
+ format: 'rgba16float',
443
+ size:
444
+ topology.extent === undefined ? 'surface' : renderExtentSize(topology.extent, 'internal'),
445
+ domain: hdr ? 'linear-hdr' : 'linear-ldr',
446
+ });
447
+ if (!fogged.ok) return fogged;
448
+ const fog = addTypedFullscreenPass(context.graph, {
449
+ name: 'analytic-fog',
450
+ shader: 'forgeax.analytic-fog',
451
+ input: postInput,
452
+ depth,
453
+ output: fogged.value,
454
+ });
455
+ if (!fog.ok) return fog;
456
+ postInput = fogged.value;
457
+ }
221
458
 
222
459
  const autoExposure = context.standardOutput?.autoExposure;
223
460
  if (outputPlan.features.exposure && autoExposure === undefined) {
@@ -239,7 +476,7 @@ export function addStandardPost(
239
476
  return err(missingTemporalContributor(topology, 'forgeax::standard::taa-history'));
240
477
  }
241
478
  const resolveTargets: TypedTemporalResolveTargets = {
242
- scene,
479
+ scene: postInput,
243
480
  depth,
244
481
  currentTemporal: sceneTemporal,
245
482
  ...(coverage === undefined ? {} : { coverage }),
@@ -260,6 +497,20 @@ export function addStandardPost(
260
497
  postTemporal = history.currentTemporal;
261
498
  }
262
499
 
500
+ // Meter the temporally resolved color before lens-domain filters. Motion
501
+ // Blur and Bloom must consume a single exposure decision for this frame;
502
+ // running the meter after either filter makes the measured luminance depend
503
+ // on the authored streak radius and changes the exposure feedback loop.
504
+ if (outputPlan.features.exposure && autoExposure !== undefined) {
505
+ const added = addAutoExposureGraphPasses(context, {
506
+ source: postInput,
507
+ resources: autoExposure,
508
+ width: topology.surface.width,
509
+ height: topology.surface.height,
510
+ });
511
+ if (!added.ok) return added;
512
+ }
513
+
263
514
  // Feature projection and the built-in fallback share one target contract.
264
515
  // The projection runs after TAA so Motion Blur consumes the resolved color,
265
516
  // then both paths feed the same linear float target into Bloom/Output.
@@ -313,49 +564,46 @@ export function addStandardPost(
313
564
  }
314
565
  }
315
566
 
316
- // Meter the temporally resolved color before Bloom. This preserves the
317
- // Standard order: temporal -> meter -> bloom -> exposure/WB -> tone -> LUT.
318
- if (outputPlan.features.exposure && autoExposure !== undefined) {
319
- const added = addAutoExposureGraphPasses(context, {
320
- source: postInput,
321
- resources: autoExposure,
322
- width: topology.surface.width,
323
- height: topology.surface.height,
324
- });
325
- if (!added.ok) return added;
567
+ if (outputPlan.features.dof) {
568
+ const dof = addDepthOfFieldPasses(
569
+ {
570
+ graph: context.graph,
571
+ camera: context.camera ?? {},
572
+ temporalTaa: topology.temporal?.taa === true,
573
+ msaaActive: depth.sampleCount === 4,
574
+ ...(topology.extent === undefined ? {} : { extent: topology.extent }),
575
+ },
576
+ postInput,
577
+ depth,
578
+ topology.temporal?.taa === true ? postTemporal : undefined,
579
+ );
580
+ if (!dof.ok) return dof;
581
+ postInput = dof.value;
326
582
  }
327
-
328
583
  if (bloomActive) {
329
- const composited = target(
330
- context,
331
- STANDARD_BLOOM_TARGET_SPEC.composited.label,
332
- STANDARD_BLOOM_TARGET_SPEC.composited,
333
- );
584
+ const outputWidth = topology.extent?.outputWidth ?? topology.surface.width;
585
+ const outputHeight = topology.extent?.outputHeight ?? topology.surface.height;
586
+ const bloomSpecs = standardBloomTargetSpecs(outputWidth, outputHeight);
587
+ const composited = target(context, bloomSpecs.composited.label, bloomSpecs.composited);
334
588
  if (!composited.ok) return composited;
335
- const bright = target(
336
- context,
337
- STANDARD_BLOOM_TARGET_SPEC.bright.label,
338
- STANDARD_BLOOM_TARGET_SPEC.bright,
339
- );
340
- if (!bright.ok) return bright;
341
- const blurH = target(
342
- context,
343
- STANDARD_BLOOM_TARGET_SPEC.blurH.label,
344
- STANDARD_BLOOM_TARGET_SPEC.blurH,
345
- );
346
- if (!blurH.ok) return blurH;
347
- const blurV = target(
348
- context,
349
- STANDARD_BLOOM_TARGET_SPEC.blurV.label,
350
- STANDARD_BLOOM_TARGET_SPEC.blurV,
351
- );
352
- if (!blurV.ok) return blurV;
589
+ const downsample: RenderPipelineTarget[] = [];
590
+ for (const spec of bloomSpecs.downsample) {
591
+ const level = target(context, spec.label, spec);
592
+ if (!level.ok) return level;
593
+ downsample.push(level.value);
594
+ }
595
+ const upsample: RenderPipelineTarget[] = [];
596
+ for (const spec of bloomSpecs.upsample) {
597
+ const level = target(context, spec.label, spec);
598
+ if (!level.ok) return level;
599
+ upsample.push(level.value);
600
+ }
353
601
  const bloom = addTypedBloomPasses(context.graph, {
354
602
  scene: postInput,
355
603
  composited: composited.value,
356
- bright: bright.value,
357
- blurH: blurH.value,
358
- blurV: blurV.value,
604
+ downsample,
605
+ upsample,
606
+ levelDimensions: bloomSpecs.levels,
359
607
  });
360
608
  if (!bloom.ok) return bloom;
361
609
  postInput = composited.value;
@@ -562,8 +562,8 @@ export interface PassKindAttachmentPolicy {
562
562
  * 4. `'point-shadow-caster'` — HDRP point-shadow caster: depth-only
563
563
  * 5. `'skybox'` — fullscreen skybox: color-only clear/store
564
564
  * 6. `'tonemap'` — HDR→LDR tonemap fullscreen: color-only clear/store
565
- * 7. `'bloom-bright'` / `'bloom-blur'` — bloom downsample/blur: color-only
566
- * clear/store
565
+ * 7. `'bloom-downsample'` / `'bloom-upsample'` — multiscale Bloom pyramid:
566
+ * color-only clear/store
567
567
  * 8. `'bloom-composite'` — bloom add-back: color-only load/store (NOT clear)
568
568
  * 9. `'fxaa'` — fullscreen FXAA: color-only clear/store
569
569
  * 10. `'post-process'` — generic fullscreen primitive (SSAO, render-graph
@@ -633,7 +633,7 @@ export const passKindPolicyTable: Readonly<Record<string, PassKindAttachmentPoli
633
633
  },
634
634
  defaultDepthOps: undefined,
635
635
  },
636
- 'bloom-bright': {
636
+ 'bloom-downsample': {
637
637
  shape: 'color-only',
638
638
  defaultColorOps: {
639
639
  loadOp: 'clear',
@@ -642,7 +642,7 @@ export const passKindPolicyTable: Readonly<Record<string, PassKindAttachmentPoli
642
642
  },
643
643
  defaultDepthOps: undefined,
644
644
  },
645
- 'bloom-blur': {
645
+ 'bloom-upsample': {
646
646
  shape: 'color-only',
647
647
  defaultColorOps: {
648
648
  loadOp: 'clear',
@@ -1220,7 +1220,7 @@ export function buildSpecConstTable(
1220
1220
  // has sampleCount=4. Both use empty vertexLayout (fullscreen triangle, no
1221
1221
  // attributes) and cullMode='none' (forward cull for fullscreen-quad passthrough).
1222
1222
  //
1223
- // Shadow-probe / fxaa / bloom 4-stage / SSAO 2-stage are lazy-build — not in
1223
+ // Shadow-probe / fxaa / Bloom 10-pass / SSAO 2-stage are lazy-build — not in
1224
1224
  // SPEC_CONST_TABLE (R-D4 decision: only entries that are always-present on
1225
1225
  // every boot).
1226
1226
 
package/src/plugin.ts CHANGED
@@ -2,6 +2,7 @@ import type { Component, World } from '@forgeax/engine-ecs';
2
2
  import type { Plugin } from '@forgeax/engine-plugin';
3
3
  import { Atmosphere } from './components/atmosphere';
4
4
  import { Camera } from './components/camera';
5
+ import { DepthOfField } from './components/depth-of-field';
5
6
  import { DirectionalLight } from './components/directional-light';
6
7
  import { DynamicResolution } from './components/dynamic-resolution';
7
8
  import { Instances } from './components/instances';
@@ -33,6 +34,7 @@ const RENDER_COMPONENTS: readonly Component[] = [
33
34
  Camera,
34
35
  DirectionalLight,
35
36
  DynamicResolution,
37
+ DepthOfField,
36
38
  Instances,
37
39
  Layer,
38
40
  LightProbe,
@@ -25,7 +25,8 @@ function camera(): CameraSnapshot {
25
25
  bloom: 'off',
26
26
  bloomThreshold: 1,
27
27
  bloomIntensity: 1,
28
- bloomBlurRadius: 4,
28
+ bloomSoftKnee: 0.5,
29
+ bloomScatter: 0.7,
29
30
  clearColor: [0, 0, 0, 1],
30
31
  };
31
32
  }
@@ -70,7 +70,8 @@ function camera(): CameraSnapshot {
70
70
  bloom: 'off',
71
71
  bloomThreshold: 1,
72
72
  bloomIntensity: 1,
73
- bloomBlurRadius: 4,
73
+ bloomSoftKnee: 0.5,
74
+ bloomScatter: 0.7,
74
75
  clearColor: [0, 0, 0, 1],
75
76
  };
76
77
  }
@@ -4,7 +4,10 @@
4
4
  import type { World } from '@forgeax/engine-ecs';
5
5
  import type { RenderReadVersion } from '@forgeax/engine-ecs/projection';
6
6
  import { vec3 } from '@forgeax/engine-math';
7
- import type { ResolvedColorTargetDescriptor } from '@forgeax/engine-render-graph';
7
+ import type {
8
+ CompiledRenderGraphInfo,
9
+ ResolvedColorTargetDescriptor,
10
+ } from '@forgeax/engine-render-graph';
8
11
  import type {
9
12
  BindGroup,
10
13
  BindGroupLayout,
@@ -17,7 +20,9 @@ import type { ClusterBinScratch } from '../cluster-binner';
17
20
  import type { DirectionalShadowQuality } from '../components/directional-shadow-filter';
18
21
  import type { EnvironmentGeneration } from '../environment/generation';
19
22
  import type { EnvironmentLifecycle } from '../environment/lifecycle';
23
+ import type { DepthOfFieldParams } from '../features/depth-of-field/depth-of-field-params';
20
24
  import type { GpuBuffer } from '../gpu-resource';
25
+ import type { MotionBlurExecutionReceipt } from '../inspection-types';
21
26
  import type { InstanceCollectionStore } from '../instances';
22
27
  import type { IblBindingInspection } from '../mesh-material-bindings';
23
28
  import type { StandardLightingInspection } from '../pipeline/standard-lighting/inspection';
@@ -378,7 +383,8 @@ export function makeZeroCameraFallbackSnapshot(): CameraSnapshot {
378
383
  bloom: 'off',
379
384
  bloomThreshold: 1.0,
380
385
  bloomIntensity: 1.0,
381
- bloomBlurRadius: 4.0,
386
+ bloomSoftKnee: 0.5,
387
+ bloomScatter: 0.7,
382
388
  clearColor: ZERO_CAMERA_CLEAR_FALLBACK,
383
389
  };
384
390
  }
@@ -403,6 +409,8 @@ export function makeZeroCameraFallbackSnapshot(): CameraSnapshot {
403
409
  */
404
410
  export interface RenderFrameState {
405
411
  frameNumber: number;
412
+ /** Last successful Motion Blur compute execution; failed submits retain the prior receipt. */
413
+ motionBlurExecution?: MotionBlurExecutionReceipt;
406
414
  /** Final-submit Standard IBL binding-chain receipt for this frame. */
407
415
  iblBindingInspection?: IblBindingInspection;
408
416
  /** Demand for the frame-local Standard fallback MRT. */
@@ -452,6 +460,27 @@ export interface RenderFrameState {
452
460
  compiledFrameGraphTopologyKey: string | null;
453
461
  /** Monotonic generation of successfully promoted compiled graphs. */
454
462
  compiledFrameGraphGeneration: number;
463
+ /**
464
+ * DoF graph candidate staged until the surrounding frame transaction
465
+ * submits. The previous graph, access projection, generation, and lighting
466
+ * signature are restored together when encoding or submission aborts.
467
+ */
468
+ depthOfFieldPreviousGraph: CompiledRenderGraph<RenderPipelineFrame> | null;
469
+ depthOfFieldPreviousGraphKey: string | null;
470
+ depthOfFieldPreviousPerFrameGraph?: RenderFrameState['perFrameGraph'];
471
+ depthOfFieldPreviousGraphGeneration: number;
472
+ depthOfFieldPreviousStandardLightingGraphSignature: string;
473
+ depthOfFieldCandidateGraph: CompiledRenderGraph<RenderPipelineFrame> | null;
474
+ /** DoF graph/params detached at the same successful-submit boundary. */
475
+ depthOfFieldAccepted?:
476
+ | {
477
+ readonly params: DepthOfFieldParams;
478
+ readonly graph: CompiledRenderGraphInfo;
479
+ readonly deviceGeneration: number;
480
+ }
481
+ | undefined;
482
+ /** A DoF candidate was present when the frame failed to submit. */
483
+ depthOfFieldLastSubmitFailed?: boolean | undefined;
455
484
  /**
456
485
  * Last-known-good graph held while a volume topology candidate is tested.
457
486
  * The candidate is promoted only after its command buffer is submitted;