@forgeax/engine-render 0.1.32 → 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 (250) hide show
  1. package/README.md +379 -33
  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 +1 -1
  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-DRR7D3KX.mjs → chunk-3RTCJTH4.mjs} +12 -9
  21. package/dist/chunk-3RTCJTH4.mjs.map +1 -0
  22. package/dist/{chunk-S665NJNF.mjs → chunk-LEAXOMYJ.mjs} +3 -3
  23. package/dist/{chunk-S665NJNF.mjs.map → chunk-LEAXOMYJ.mjs.map} +1 -1
  24. package/dist/{chunk-6TFBXLRJ.mjs → chunk-NB2S7ZRX.mjs} +155 -11
  25. package/dist/chunk-NB2S7ZRX.mjs.map +1 -0
  26. package/dist/{chunk-LULCA3OF.mjs → chunk-NNQL7CB3.mjs} +4 -4
  27. package/dist/{chunk-LULCA3OF.mjs.map → chunk-NNQL7CB3.mjs.map} +1 -1
  28. package/dist/{chunk-4NNZQCL3.mjs → chunk-OO3DRYK7.mjs} +66 -200
  29. package/dist/chunk-OO3DRYK7.mjs.map +1 -0
  30. package/dist/{chunk-GWCEMDVR.mjs → chunk-PLAXOOX5.mjs} +232 -7
  31. package/dist/chunk-PLAXOOX5.mjs.map +1 -0
  32. package/dist/{chunk-JRIPCMYC.mjs → chunk-VOZWDM3L.mjs} +283 -193
  33. package/dist/chunk-VOZWDM3L.mjs.map +1 -0
  34. package/dist/{chunk-JCIRYQ4V.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/mesh-filter.d.ts +6 -5
  41. package/dist/components/mesh-filter.d.ts.map +1 -1
  42. package/dist/components/motion-blur.d.ts +1 -0
  43. package/dist/components/motion-blur.d.ts.map +1 -1
  44. package/dist/construct-renderer.mjs +2865 -526
  45. package/dist/construct-renderer.mjs.map +1 -1
  46. package/dist/device/gpu-residency.d.ts +6 -0
  47. package/dist/device/gpu-residency.d.ts.map +1 -1
  48. package/dist/environment/analytic-fog.d.ts +6 -0
  49. package/dist/environment/analytic-fog.d.ts.map +1 -0
  50. package/dist/errors/render.d.ts +6 -2
  51. package/dist/errors/render.d.ts.map +1 -1
  52. package/dist/extract/camera.d.ts.map +1 -1
  53. package/dist/extract/contracts.d.ts +7 -0
  54. package/dist/extract/contracts.d.ts.map +1 -1
  55. package/dist/extract/environment.d.ts +2 -0
  56. package/dist/extract/environment.d.ts.map +1 -1
  57. package/dist/features/error-reporting.d.ts +13 -0
  58. package/dist/features/error-reporting.d.ts.map +1 -0
  59. package/dist/features/host.d.ts +5 -0
  60. package/dist/features/host.d.ts.map +1 -1
  61. package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
  62. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
  63. package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
  64. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
  65. package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
  66. package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
  67. package/dist/features/plan.d.ts +7 -4
  68. package/dist/features/plan.d.ts.map +1 -1
  69. package/dist/features/prepared-gpu-work.d.ts +9 -5
  70. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  71. package/dist/features/render-graph-compute.d.ts.map +1 -1
  72. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  73. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  74. package/dist/gpu-driven/prepared-draw.d.ts +7 -0
  75. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  76. package/dist/gpu-driven/production-raster.d.ts +3 -1
  77. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  78. package/dist/gpu-driven/view-gpu.d.ts +9 -0
  79. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  80. package/dist/gpu-scene.d.ts +6 -1
  81. package/dist/gpu-scene.d.ts.map +1 -1
  82. package/dist/index.d.ts +5 -2
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.mjs +5 -5
  85. package/dist/inspection-types.d.ts +43 -2
  86. package/dist/inspection-types.d.ts.map +1 -1
  87. package/dist/instances.d.ts +8 -1
  88. package/dist/instances.d.ts.map +1 -1
  89. package/dist/internal.mjs +6 -6
  90. package/dist/pipeline/standard-post.d.ts +28 -19
  91. package/dist/pipeline/standard-post.d.ts.map +1 -1
  92. package/dist/pipeline-spec.d.ts +2 -2
  93. package/dist/record/frame-snapshot.d.ts +3 -0
  94. package/dist/record/frame-snapshot.d.ts.map +1 -1
  95. package/dist/record/frame-targets.d.ts.map +1 -1
  96. package/dist/record/frame.d.ts +5 -2
  97. package/dist/record/frame.d.ts.map +1 -1
  98. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  99. package/dist/record/mesh-ssbo.d.ts +6 -5
  100. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  101. package/dist/record/render-context.d.ts +12 -28
  102. package/dist/record/render-context.d.ts.map +1 -1
  103. package/dist/record/shadow-pass.d.ts.map +1 -1
  104. package/dist/record/skybox-post-pass.d.ts +5 -3
  105. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  106. package/dist/record/typed-frame-graph.d.ts +1 -1
  107. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  108. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  109. package/dist/render-contract.d.ts +24 -4
  110. package/dist/render-contract.d.ts.map +1 -1
  111. package/dist/render-pipeline.d.ts +3 -1
  112. package/dist/render-pipeline.d.ts.map +1 -1
  113. package/dist/render-system-extract-tail.d.ts.map +1 -1
  114. package/dist/render-system-extract.d.ts +8 -1
  115. package/dist/render-system-extract.d.ts.map +1 -1
  116. package/dist/render-system.d.ts +7 -1
  117. package/dist/render-system.d.ts.map +1 -1
  118. package/dist/scene/render-scene.d.ts +2 -0
  119. package/dist/scene/render-scene.d.ts.map +1 -1
  120. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  121. package/dist/temporal/frame.d.ts +7 -0
  122. package/dist/temporal/frame.d.ts.map +1 -1
  123. package/dist/temporal/index.mjs +5 -5
  124. package/dist/temporal/view.d.ts +6 -1
  125. package/dist/temporal/view.d.ts.map +1 -1
  126. package/dist/typed-render-graph-primitives.d.ts +10 -7
  127. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  128. package/dist/volume/component.d.ts +0 -1
  129. package/dist/volume/component.d.ts.map +1 -1
  130. package/dist/volume/passes.d.ts.map +1 -1
  131. package/dist/volume/temporal.d.ts +3 -1
  132. package/dist/volume/temporal.d.ts.map +1 -1
  133. package/package.json +21 -21
  134. package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
  135. package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
  136. package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
  137. package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
  138. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
  139. package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
  140. package/src/__tests__/environment-selection.unit.test.ts +12 -0
  141. package/src/__tests__/factory-contract.integration.test.ts +331 -68
  142. package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
  143. package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
  144. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
  145. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +1 -1
  146. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
  147. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +11 -0
  148. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
  149. package/src/__tests__/gpu-scene.unit.test.ts +91 -0
  150. package/src/__tests__/instances-store.unit.test.ts +75 -0
  151. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
  152. package/src/__tests__/motion-blur-compute.dawn.test.ts +583 -0
  153. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +21 -0
  154. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
  155. package/src/__tests__/public-surface.test-d.ts +8 -0
  156. package/src/__tests__/render-error-code-owner.test-d.ts +2 -0
  157. package/src/__tests__/render-feature-error-reporting.unit.test.ts +24 -0
  158. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
  159. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +31 -0
  160. package/src/__tests__/renderer-frame-transaction.integration.test.ts +7 -1
  161. package/src/__tests__/shader-manifest-fixture.ts +4 -4
  162. package/src/__tests__/skinned-shadow-caster.test.ts +2 -0
  163. package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
  164. package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
  165. package/src/__tests__/standard-pipeline.integration.test.ts +18 -8
  166. package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
  167. package/src/__tests__/temporal-view.unit.test.ts +2 -1
  168. package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
  169. package/src/__tests__/volume-optics-oracle.unit.test.ts +3 -1
  170. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +177 -13
  171. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +447 -61
  172. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
  173. package/src/__tests__/volumetric-fog-topology.unit.test.ts +19 -3
  174. package/src/assembly/analytic-fog-registration.ts +14 -0
  175. package/src/assembly/fxaa-registration.ts +17 -0
  176. package/src/assembly/lod-observation.ts +12 -0
  177. package/src/assembly/motion-blur-registration.ts +17 -0
  178. package/src/assembly/renderer-facade.ts +2 -0
  179. package/src/assembly/temporal-post-process-prewarm.ts +40 -0
  180. package/src/assembly/webgpu-ready-contract.ts +8 -3
  181. package/src/assembly/webgpu-ready.ts +181 -245
  182. package/src/assembly/webgpu-renderer.ts +16 -31
  183. package/src/bloom-admission.ts +21 -0
  184. package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
  185. package/src/components/atmosphere.ts +4 -0
  186. package/src/components/camera.ts +66 -10
  187. package/src/components/mesh-filter.ts +11 -9
  188. package/src/components/motion-blur.ts +1 -0
  189. package/src/device/gpu-residency.ts +56 -12
  190. package/src/environment/analytic-fog.ts +44 -0
  191. package/src/environment/background.ts +2 -2
  192. package/src/errors/render.ts +14 -1
  193. package/src/extract/camera.ts +20 -7
  194. package/src/extract/contracts.ts +7 -0
  195. package/src/extract/environment.ts +4 -0
  196. package/src/features/error-reporting.ts +78 -0
  197. package/src/features/host.ts +25 -12
  198. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
  199. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
  200. package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
  201. package/src/features/motion-blur/motion-blur-params.ts +70 -2
  202. package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
  203. package/src/features/plan.ts +25 -8
  204. package/src/features/prepared-gpu-work.ts +86 -35
  205. package/src/features/render-graph-compute.ts +13 -5
  206. package/src/features/render-graph-contribution.ts +2 -0
  207. package/src/gpu-driven/material-artifacts.ts +2 -7
  208. package/src/gpu-driven/prepared-draw.ts +15 -10
  209. package/src/gpu-driven/production-raster.ts +83 -7
  210. package/src/gpu-driven/view-gpu.ts +17 -0
  211. package/src/gpu-scene.ts +188 -21
  212. package/src/index.ts +32 -0
  213. package/src/inspection-types.ts +60 -2
  214. package/src/instances.ts +80 -1
  215. package/src/pipeline/standard-post.ts +307 -78
  216. package/src/pipeline-spec.ts +5 -5
  217. package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
  218. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -1
  219. package/src/record/frame-snapshot.ts +5 -1
  220. package/src/record/frame-targets.ts +10 -6
  221. package/src/record/frame.ts +163 -22
  222. package/src/record/main-pass-geometry.ts +30 -1
  223. package/src/record/main-pass-sprite-draws.ts +6 -1
  224. package/src/record/mesh-ssbo.ts +43 -6
  225. package/src/record/render-context.ts +12 -28
  226. package/src/record/shadow-pass.ts +6 -1
  227. package/src/record/skybox-post-pass.ts +122 -196
  228. package/src/record/typed-frame-graph.ts +153 -9
  229. package/src/recovery/render-system-candidate.ts +22 -10
  230. package/src/render-contract.ts +23 -4
  231. package/src/render-pipeline.ts +6 -1
  232. package/src/render-system-extract-tail.ts +16 -0
  233. package/src/render-system-extract.ts +45 -13
  234. package/src/render-system.ts +255 -120
  235. package/src/scene/render-scene.ts +85 -4
  236. package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
  237. package/src/temporal/frame-coordinator.ts +8 -1
  238. package/src/temporal/frame.ts +20 -0
  239. package/src/temporal/view.ts +13 -2
  240. package/src/typed-render-graph-primitives.ts +121 -48
  241. package/src/volume/component.ts +0 -1
  242. package/src/volume/passes.ts +8 -4
  243. package/src/volume/resources.ts +1 -1
  244. package/src/volume/temporal.ts +11 -0
  245. package/dist/chunk-4NNZQCL3.mjs.map +0 -1
  246. package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
  247. package/dist/chunk-DRR7D3KX.mjs.map +0 -1
  248. package/dist/chunk-GWCEMDVR.mjs.map +0 -1
  249. package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
  250. package/dist/chunk-JRIPCMYC.mjs.map +0 -1
@@ -57,7 +57,7 @@ import {
57
57
  FALLBACK_BYTES_PER_ROW,
58
58
  type SkylightFallback,
59
59
  } from '../ibl/skylight-bind-group';
60
- import type { BloomInspection } from '../inspection-types';
60
+ import { type BloomInspection, emptyBloomInspection } from '../inspection-types';
61
61
  import type { DeviceScope, RhiErrorListenerRegistry } from '../lifecycle';
62
62
  import { assertStorageBufferCap } from '../light-buffer-layout';
63
63
  import {
@@ -119,11 +119,12 @@ import {
119
119
  selectProbePrewarmVariants,
120
120
  selectStandardPbrTransmissionPrewarmVariants,
121
121
  } from './shader-prewarm-policy';
122
+ import { createTemporalShaderPrewarmer } from './temporal-post-process-prewarm';
122
123
  import { buildGpuDrivenPbrReadyLayouts, buildGpuDrivenPbrReadyModules } from './webgpu-pbr-ready';
123
124
  import {
124
- BLUR_PARAMS_BYTES,
125
- BRIGHT_PARAMS_BYTES,
126
- COMPOSITE_PARAMS_BYTES,
125
+ BLOOM_COMPOSITE_PARAMS_BYTES,
126
+ BLOOM_DOWNSAMPLE_PARAMS_BYTES,
127
+ BLOOM_UPSAMPLE_PARAMS_BYTES,
127
128
  HDR_COLOR_ATTACHMENT_FORMAT,
128
129
  VIEW_UBO_BYTES,
129
130
  } from './webgpu-ready-contract';
@@ -219,6 +220,7 @@ export async function buildReadyWebGPU(
219
220
  registerBuiltinTonemap: (source: string) => void,
220
221
  /** Register the special FXAA pass' final-output policy params UBO. */
221
222
  registerBuiltinFxaa: (source: string) => void,
223
+ registerBuiltinAnalyticFog: (source: string) => void,
222
224
  registerBuiltinTemporalPostProcesses: (entries: {
223
225
  readonly motionBlur?: string;
224
226
  readonly taaResolve?: string;
@@ -320,8 +322,8 @@ export async function buildReadyWebGPU(
320
322
  let spriteLitModule: ShaderModule | null = null;
321
323
  let fxaaModule: ShaderModule | null = null;
322
324
  let skyboxModule: ShaderModule | null = null;
323
- let bloomBrightModule: ShaderModule | null = null;
324
- let bloomBlurModule: ShaderModule | null = null;
325
+ let bloomDownsampleModule: ShaderModule | null = null;
326
+ let bloomUpsampleModule: ShaderModule | null = null;
325
327
  let bloomCompositeModule: ShaderModule | null = null;
326
328
  let ssaoModule: ShaderModule | null = null;
327
329
  const findEngineManifestEntry = (identifier: string): ManifestEntry | undefined => {
@@ -385,8 +387,8 @@ export async function buildReadyWebGPU(
385
387
  let probeBackgroundEntry: ManifestEntry | undefined;
386
388
  let fxaaEntry: ManifestEntry | undefined;
387
389
  let skyboxEntry: ManifestEntry | undefined;
388
- let bloomBrightEntry: ManifestEntry | undefined;
389
- let bloomBlurEntry: ManifestEntry | undefined;
390
+ let bloomDownsampleEntry: ManifestEntry | undefined;
391
+ let bloomUpsampleEntry: ManifestEntry | undefined;
390
392
  let bloomCompositeEntry: ManifestEntry | undefined;
391
393
  let ssaoEntry: ManifestEntry | undefined;
392
394
  // feat-20260609 R3-fixup: shadow_caster module pre-bake. T-009 deleted
@@ -462,23 +464,16 @@ export async function buildReadyWebGPU(
462
464
  skyboxEntry ??= entry;
463
465
  continue;
464
466
  }
465
- // feat-20260531-bloom-first-declarative-render-graph-pass / w13:
466
- // bloom marker triage (D-7). Identify the 3 bloom WGSL modules by their
467
- // unique uniform-struct names ('BloomBrightParams' in bloom-bright.wgsl,
468
- // 'BloomBlurParams' in bloom-blur.wgsl shared by H/V pipelines,
469
- // 'BloomCompositeParams' in bloom-composite.wgsl). Struct names are naga
470
- // IR and survive naga_oil composition unchanged -- unlike the original
471
- // `// bloomBrightExtract` content-marker COMMENTS, which naga's WGSL
472
- // writeback drops (comments are not part of the IR), leaving the markers
473
- // absent from the composed `entry.wgsl` so the triage never matched and
474
- // bloom silently never initialised (bug-20260625). Same survives-naga
475
- // rationale as the SSAO `fs_ssao_calc` entry-point marker below.
476
- if (entry.wgsl.includes('BloomBrightParams')) {
477
- bloomBrightEntry ??= entry;
467
+ // Multiscale Bloom marker triage. The composed WGSL retains these
468
+ // uniform-struct names through naga_oil, so shader ownership is resolved
469
+ // from the cooked manifest rather than from a runtime compiler or a
470
+ // second heuristic path.
471
+ if (entry.wgsl.includes('BloomDownsampleParams')) {
472
+ bloomDownsampleEntry ??= entry;
478
473
  continue;
479
474
  }
480
- if (entry.wgsl.includes('BloomBlurParams')) {
481
- bloomBlurEntry ??= entry;
475
+ if (entry.wgsl.includes('BloomUpsampleParams')) {
476
+ bloomUpsampleEntry ??= entry;
482
477
  continue;
483
478
  }
484
479
  if (entry.wgsl.includes('BloomCompositeParams')) {
@@ -1149,26 +1144,14 @@ export async function buildReadyWebGPU(
1149
1144
 
1150
1145
  // Prewarm temporal fullscreen modules before readiness so first-frame
1151
1146
  // recording cannot encounter an asynchronous compile miss.
1152
- const prewarmTemporal = async (entry: ManifestEntry | undefined, id: string) => {
1153
- if (entry === undefined) return;
1154
- const prewarm = await runShimStep(
1155
- () =>
1156
- asyncCreateShaderModule
1157
- ? asyncCreateShaderModule(rhiDevice, {
1158
- code: entry.wgsl,
1159
- label: postProcessShaderModuleLabel(id, entry.wgsl),
1160
- })
1161
- : invokeDeviceCreateShaderModule(rhiDevice, {
1162
- code: entry.wgsl,
1163
- label: postProcessShaderModuleLabel(id, entry.wgsl),
1164
- }),
1165
- 'shader-compile-failed',
1166
- `${id} shader module compiled (unified post-process prewarm)`,
1167
- `inspect manifest ${id} entry composed wgsl; check device.features`,
1168
- );
1169
- if (!prewarm.ok) throw prewarm.error;
1170
- seedShaderModule(postProcessShaderModuleLabel(id, entry.wgsl), prewarm.value);
1171
- };
1147
+ const prewarmTemporal = createTemporalShaderPrewarmer({
1148
+ device: rhiDevice,
1149
+ asyncCreateShaderModule,
1150
+ seedShaderModule,
1151
+ });
1152
+ const analyticFogEntry = findEngineManifestEntry('forgeax::analytic-fog');
1153
+ await prewarmTemporal(analyticFogEntry, 'forgeax.analytic-fog');
1154
+ if (analyticFogEntry !== undefined) registerBuiltinAnalyticFog(analyticFogEntry.wgsl);
1172
1155
  await prewarmTemporal(motionBlurEntry, 'forgeax.motion-blur');
1173
1156
  await prewarmTemporal(taaResolveEntry, 'forgeax.taa-resolve');
1174
1157
  await prewarmDepthOfField(manifestEntries, prewarmTemporal, registerBuiltinDepthOfField);
@@ -1354,40 +1337,43 @@ export async function buildReadyWebGPU(
1354
1337
  // Their construction is still part of renderer readiness because the
1355
1338
  // backend may expose only an asynchronous module factory; the expensive
1356
1339
  // pipeline, binding, and parameter resources stay deferred until admission.
1357
- if (bloomBrightEntry !== undefined) {
1340
+ if (bloomDownsampleEntry !== undefined) {
1358
1341
  const result = await runShimStep(
1359
1342
  () =>
1360
1343
  asyncCreateShaderModule
1361
1344
  ? asyncCreateShaderModule(rhiDevice, {
1362
- code: bloomBrightEntry.wgsl,
1363
- label: 'bloom-bright',
1345
+ code: bloomDownsampleEntry.wgsl,
1346
+ label: 'bloom-downsample',
1364
1347
  })
1365
1348
  : invokeDeviceCreateShaderModule(rhiDevice, {
1366
- code: bloomBrightEntry.wgsl,
1367
- label: 'bloom-bright',
1349
+ code: bloomDownsampleEntry.wgsl,
1350
+ label: 'bloom-downsample',
1368
1351
  }),
1369
1352
  'shader-compile-failed',
1370
- 'bloom-bright shader module compiled',
1371
- 'inspect manifest bloom-bright entry composed wgsl; check device.features',
1353
+ 'bloom-downsample shader module compiled',
1354
+ 'inspect manifest bloom-downsample entry composed wgsl; check device.features',
1372
1355
  );
1373
1356
  if (!result.ok) throw result.error;
1374
- bloomBrightModule = result.value;
1357
+ bloomDownsampleModule = result.value;
1375
1358
  }
1376
- if (bloomBlurEntry !== undefined) {
1359
+ if (bloomUpsampleEntry !== undefined) {
1377
1360
  const result = await runShimStep(
1378
1361
  () =>
1379
1362
  asyncCreateShaderModule
1380
- ? asyncCreateShaderModule(rhiDevice, { code: bloomBlurEntry.wgsl, label: 'bloom-blur' })
1363
+ ? asyncCreateShaderModule(rhiDevice, {
1364
+ code: bloomUpsampleEntry.wgsl,
1365
+ label: 'bloom-upsample',
1366
+ })
1381
1367
  : invokeDeviceCreateShaderModule(rhiDevice, {
1382
- code: bloomBlurEntry.wgsl,
1383
- label: 'bloom-blur',
1368
+ code: bloomUpsampleEntry.wgsl,
1369
+ label: 'bloom-upsample',
1384
1370
  }),
1385
1371
  'shader-compile-failed',
1386
- 'bloom-blur shader module compiled',
1387
- 'inspect manifest bloom-blur entry composed wgsl; check device.features',
1372
+ 'bloom-upsample shader module compiled',
1373
+ 'inspect manifest bloom-upsample entry composed wgsl; check device.features',
1388
1374
  );
1389
1375
  if (!result.ok) throw result.error;
1390
- bloomBlurModule = result.value;
1376
+ bloomUpsampleModule = result.value;
1391
1377
  }
1392
1378
  if (bloomCompositeEntry !== undefined) {
1393
1379
  const result = await runShimStep(
@@ -2514,10 +2500,10 @@ export async function buildReadyWebGPU(
2514
2500
  } else if (fragModule === skyboxModule) {
2515
2501
  const msaa = d.multisample as Record<string, unknown> | undefined;
2516
2502
  label = msaa !== undefined ? 'skybox-pipeline-msaa' : 'skybox-pipeline';
2517
- } else if (fragModule === bloomBrightModule) {
2518
- label = 'bloom-bright-pipeline';
2519
- } else if (fragModule === bloomBlurModule) {
2520
- label = (d.label as string) ?? 'bloom-blur-h-pipeline';
2503
+ } else if (fragModule === bloomDownsampleModule) {
2504
+ label = 'bloom-downsample-pipeline';
2505
+ } else if (fragModule === bloomUpsampleModule) {
2506
+ label = 'bloom-upsample-pipeline';
2521
2507
  } else if (fragModule === bloomCompositeModule) {
2522
2508
  label = 'bloom-composite-pipeline';
2523
2509
  } else if (fragModule === ssaoModule) {
@@ -2745,23 +2731,18 @@ export async function buildReadyWebGPU(
2745
2731
  let skyboxSamplerHandle: Sampler | null = null;
2746
2732
  let skyboxRotationBufferHandle: Buffer | null = null;
2747
2733
  let skyboxPipelineMsaaHandle: RenderPipeline | null = null;
2748
- // feat-20260531-bloom-first-declarative-render-graph-pass / w13:
2749
- // bloom pipeline handles (D-1, D-4, D-6). Bright + 2x blur (H/V per-axis)
2750
- // + 1x composite = 4 pipelines. Blur H/V share the same WGSL module but
2751
- // are separate pipelines with per-axis texelSize baked at creation (D-1).
2752
- // All 4 use rgba16float target format (D-6).
2753
- let bloomBrightPipelineHandle: RenderPipeline | null = null;
2754
- let bloomBlurHPipelineHandle: RenderPipeline | null = null;
2755
- let bloomBlurVPipelineHandle: RenderPipeline | null = null;
2734
+ // Multiscale Bloom pipeline handles. One level-parameterized downsample
2735
+ // pipeline and one tent upsample pipeline serve the whole five-level graph;
2736
+ // the composite is the only other Bloom pipeline. All use rgba16float.
2737
+ let bloomDownsamplePipelineHandle: RenderPipeline | null = null;
2738
+ let bloomUpsamplePipelineHandle: RenderPipeline | null = null;
2756
2739
  let bloomCompositePipelineHandle: RenderPipeline | null = null;
2757
- let bloomBrightBglHandle: BindGroupLayout | null = null;
2758
- let bloomBlurBglHandle: BindGroupLayout | null = null;
2740
+ let bloomDownsampleBglHandle: BindGroupLayout | null = null;
2741
+ let bloomUpsampleBglHandle: BindGroupLayout | null = null;
2759
2742
  let bloomCompositeBglHandle: BindGroupLayout | null = null;
2760
2743
  let bloomSamplerHandle: Sampler | null = null;
2761
- let bloomBrightParamsBufferHandle: Buffer | null = null;
2762
- // bug-20260625: separate H/V blur params UBOs (see PerPassResources comment).
2763
- let bloomBlurHParamsBufferHandle: Buffer | null = null;
2764
- let bloomBlurVParamsBufferHandle: Buffer | null = null;
2744
+ let bloomDownsampleParamsBufferHandle: Buffer | null = null;
2745
+ let bloomUpsampleParamsBufferHandle: Buffer | null = null;
2765
2746
  let bloomCompositeParamsBufferHandle: Buffer | null = null;
2766
2747
  let bloomResourcesReady = false;
2767
2748
  let ensureBloomResources: (() => void) | undefined;
@@ -2799,22 +2780,7 @@ export async function buildReadyWebGPU(
2799
2780
  const inspectBloomResourcesStable = (
2800
2781
  graph?: import('@forgeax/engine-render-graph').CompiledRenderGraphInfo,
2801
2782
  ): BloomInspection => {
2802
- return (
2803
- inspectBloomResources?.(graph) ?? {
2804
- graphStatus: 'empty',
2805
- enabled: false,
2806
- targetCount: 0,
2807
- targetBytes: 0,
2808
- resourceCount: 0,
2809
- passCount: 0,
2810
- encodeCount: 0,
2811
- bindGroupCount: 0,
2812
- uploadCount: 0,
2813
- residentChildBytes: 0,
2814
- generation: 0,
2815
- state: 'off',
2816
- }
2817
- );
2783
+ return inspectBloomResources?.(graph) ?? emptyBloomInspection();
2818
2784
  };
2819
2785
  // feat-20260612-hdrp-ssao M6 / w26 + w43: SSAO pipeline handles (D-A).
2820
2786
  // calc + blur RenderPipeline pair sharing a dedicated 6-entry BGL.
@@ -3108,18 +3074,10 @@ export async function buildReadyWebGPU(
3108
3074
  skyboxRotationBufferHandle = skyboxRotationBufferResult.value;
3109
3075
  }
3110
3076
 
3111
- // feat-20260531-bloom-first-declarative-render-graph-pass / w13:
3112
- // bloom pipeline assembly (D-1, D-4, D-6). Assembled inside the
3113
- // (unlit+pbr+tonemap) gate for rhiDevice access but bloom modules
3114
- // are optional each guard is independent. When bloom modules are
3115
- // absent (legacy manifest), handles stay null and execute closures
3116
- // skip the bloom passes entirely (zero-overhead opt-out).
3117
- //
3118
- // Pipeline roster:
3119
- // bloom-bright : 1-tex + sampler + UBO@2 BGL, rgba16float target
3120
- // bloom-blur-h : same BGL, same module as blur-v, H-axis texelSize
3121
- // bloom-blur-v : same BGL, same module as blur-h, V-axis texelSize
3122
- // bloom-composite: 2-tex + sampler + UBO@3 BGL, rgba16float target
3077
+ // Multiscale Bloom pipeline assembly. The runtime owns exactly one
3078
+ // parameterized downsample PSO, one tent upsample PSO, and one composite
3079
+ // PSO. The graph owns the five downsample and up to four upsample target
3080
+ // instances; no axis/radius compatibility route is retained.
3123
3081
 
3124
3082
  ensureBloomResources = (): void => {
3125
3083
  if (bloomResourcesReady) return;
@@ -3133,11 +3091,11 @@ export async function buildReadyWebGPU(
3133
3091
  return value;
3134
3092
  };
3135
3093
  try {
3136
- // Shared bloom sampler: linear filter + clamp-to-edge (all 4 passes
3094
+ // Shared Bloom sampler: linear filter + clamp-to-edge (all passes
3137
3095
  // sample from textures using fullscreen triangle UVs).
3138
3096
  if (
3139
- bloomBrightModule !== null ||
3140
- bloomBlurModule !== null ||
3097
+ bloomDownsampleModule !== null ||
3098
+ bloomUpsampleModule !== null ||
3141
3099
  bloomCompositeModule !== null
3142
3100
  ) {
3143
3101
  const bloomSamplerResult = runShimSyncStep(
@@ -3158,12 +3116,12 @@ export async function buildReadyWebGPU(
3158
3116
  bloomSamplerHandle = adopt('binding', bloomSamplerResult.value, () => undefined);
3159
3117
  }
3160
3118
 
3161
- // Bloom bright: 1-tex + sampler + UBO@2 BGL (D-4).
3162
- if (bloomBrightModule !== null && bloomSamplerHandle !== null) {
3163
- const brightBglResult = runShimSyncStep(
3119
+ // Bloom downsample: source texture + sampler + one level slice.
3120
+ if (bloomDownsampleModule !== null && bloomSamplerHandle !== null) {
3121
+ const downsampleBglResult = runShimSyncStep(
3164
3122
  () =>
3165
3123
  rhiDevice.createBindGroupLayout({
3166
- label: 'bloom-bright-bgl',
3124
+ label: 'bloom-downsample-bgl',
3167
3125
  entries: [
3168
3126
  {
3169
3127
  binding: 0,
@@ -3183,30 +3141,30 @@ export async function buildReadyWebGPU(
3183
3141
  ],
3184
3142
  }),
3185
3143
  'webgpu-runtime-error',
3186
- 'createBindGroupLayout(bloom-bright) succeeded',
3144
+ 'createBindGroupLayout(bloom-downsample) succeeded',
3187
3145
  'check device.limits.maxBindGroupsPerPipelineLayout',
3188
3146
  );
3189
- if (!brightBglResult.ok) throw brightBglResult.error;
3190
- bloomBrightBglHandle = adopt('binding', brightBglResult.value, () => undefined);
3147
+ if (!downsampleBglResult.ok) throw downsampleBglResult.error;
3148
+ bloomDownsampleBglHandle = adopt('binding', downsampleBglResult.value, () => undefined);
3191
3149
 
3192
- const brightPlResult = runShimSyncStep(
3150
+ const downsamplePlResult = runShimSyncStep(
3193
3151
  () =>
3194
3152
  rhiDevice.createPipelineLayout({
3195
- label: 'bloom-bright-pl',
3196
- bindGroupLayouts: [brightBglResult.value],
3153
+ label: 'bloom-downsample-pl',
3154
+ bindGroupLayouts: [downsampleBglResult.value],
3197
3155
  }),
3198
3156
  'webgpu-runtime-error',
3199
- 'createPipelineLayout(bloom-bright) succeeded',
3200
- 'verify bloom-bright BindGroupLayout matches shader @group(0) bindings',
3157
+ 'createPipelineLayout(bloom-downsample) succeeded',
3158
+ 'verify bloom-downsample BindGroupLayout matches shader @group(0) bindings',
3201
3159
  );
3202
- if (!brightPlResult.ok) throw brightPlResult.error;
3203
- adopt('binding', brightPlResult.value, () => undefined);
3160
+ if (!downsamplePlResult.ok) throw downsamplePlResult.error;
3161
+ adopt('binding', downsamplePlResult.value, () => undefined);
3204
3162
 
3205
- // M2-T4: bloom-bright pipeline via getOrBuildPipeline (lazy-build).
3163
+ // One pipeline serves D0 extraction and D1..D4 fixed reduction.
3206
3164
  {
3207
- const brightSpec: PipelineSpec = {
3165
+ const downsampleSpec: PipelineSpec = {
3208
3166
  shader: {
3209
- id: 'forgeax::post::bloom-bright',
3167
+ id: 'forgeax::post::bloom-downsample',
3210
3168
  passKind: 'post-process',
3211
3169
  variantSet: undefined,
3212
3170
  },
@@ -3223,20 +3181,21 @@ export async function buildReadyWebGPU(
3223
3181
  renderState: { cullMode: 'none' },
3224
3182
  };
3225
3183
  const modules = {
3226
- vertex: bloomBrightModule,
3227
- fragment: bloomBrightModule,
3228
- layout: brightPlResult.value,
3184
+ vertex: bloomDownsampleModule,
3185
+ fragment: bloomDownsampleModule,
3186
+ layout: downsamplePlResult.value,
3187
+ label: 'bloom-downsample-pipeline',
3229
3188
  };
3230
3189
  try {
3231
- bloomBrightPipelineHandle = getOrBuildPipeline(
3232
- brightSpec,
3190
+ bloomDownsamplePipelineHandle = getOrBuildPipeline(
3191
+ downsampleSpec,
3233
3192
  pipelineDeviceProvider,
3234
3193
  pipelineCache,
3235
3194
  modules,
3236
3195
  ) as RenderPipeline;
3237
- bloomBrightPipelineHandle = adopt(
3196
+ bloomDownsamplePipelineHandle = adopt(
3238
3197
  'pipeline',
3239
- bloomBrightPipelineHandle,
3198
+ bloomDownsamplePipelineHandle,
3240
3199
  () => undefined,
3241
3200
  );
3242
3201
  } catch (err) {
@@ -3244,37 +3203,41 @@ export async function buildReadyWebGPU(
3244
3203
  throw new PipelineSpecError({
3245
3204
  code: 'pipeline-build-failed',
3246
3205
  detail: { cause: err },
3247
- hint: 'createRenderPipeline (bloom-bright fullscreen) failed; inspect gpuMessage',
3206
+ hint: 'createRenderPipeline (bloom-downsample fullscreen) failed; inspect gpuMessage',
3248
3207
  });
3249
3208
  }
3250
3209
  }
3251
3210
 
3252
- // Bright params UBO: 16 B std140 (threshold f32 + 12 B pad).
3253
- const brightParamsResult = runShimSyncStep(
3211
+ // Five 32-byte std140 slices: threshold, knee, destination extent,
3212
+ // level and padding. The level is selected by the graph pass.
3213
+ const downsampleParamsResult = runShimSyncStep(
3254
3214
  () =>
3255
3215
  rhiDevice.createBuffer({
3256
- label: 'bloom-bright-params-ubo',
3257
- size: BRIGHT_PARAMS_BYTES,
3216
+ label: 'bloom-downsample-params-ubo',
3217
+ size: BLOOM_DOWNSAMPLE_PARAMS_BYTES,
3258
3218
  usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
3259
3219
  mappedAtCreation: false,
3260
3220
  }),
3261
3221
  'webgpu-runtime-error',
3262
- 'createBuffer (bloom-bright params UBO) succeeded',
3222
+ 'createBuffer (bloom-downsample params UBO) succeeded',
3263
3223
  'check device.limits.maxUniformBufferBindingSize',
3264
3224
  );
3265
- if (!brightParamsResult.ok) throw brightParamsResult.error;
3266
- bloomBrightParamsBufferHandle = adopt('buffer', brightParamsResult.value, (value) => {
3267
- rhiDevice.destroyBuffer(value);
3268
- });
3225
+ if (!downsampleParamsResult.ok) throw downsampleParamsResult.error;
3226
+ bloomDownsampleParamsBufferHandle = adopt(
3227
+ 'buffer',
3228
+ downsampleParamsResult.value,
3229
+ (value) => {
3230
+ rhiDevice.destroyBuffer(value);
3231
+ },
3232
+ );
3269
3233
  }
3270
3234
 
3271
- // Bloom blur H/V: same BGL (1-tex + sampler + UBO@2), same module,
3272
- // two separate pipelines with per-axis texelSize (D-1, D-4).
3273
- if (bloomBlurModule !== null && bloomSamplerHandle !== null) {
3274
- const blurBglResult = runShimSyncStep(
3235
+ // Bloom upsample: current/coarse textures + sampler + scatter slice.
3236
+ if (bloomUpsampleModule !== null && bloomSamplerHandle !== null) {
3237
+ const upsampleBglResult = runShimSyncStep(
3275
3238
  () =>
3276
3239
  rhiDevice.createBindGroupLayout({
3277
- label: 'bloom-blur-bgl',
3240
+ label: 'bloom-upsample-bgl',
3278
3241
  entries: [
3279
3242
  {
3280
3243
  binding: 0,
@@ -3284,43 +3247,45 @@ export async function buildReadyWebGPU(
3284
3247
  {
3285
3248
  binding: 1,
3286
3249
  visibility: GPU_SHADER_STAGE_FRAGMENT,
3287
- sampler: { type: 'filtering' },
3250
+ texture: { sampleType: 'float', viewDimension: '2d' },
3288
3251
  },
3289
3252
  {
3290
3253
  binding: 2,
3291
3254
  visibility: GPU_SHADER_STAGE_FRAGMENT,
3255
+ sampler: { type: 'filtering' },
3256
+ },
3257
+ {
3258
+ binding: 3,
3259
+ visibility: GPU_SHADER_STAGE_FRAGMENT,
3292
3260
  buffer: { type: 'uniform' },
3293
3261
  },
3294
3262
  ],
3295
3263
  }),
3296
3264
  'webgpu-runtime-error',
3297
- 'createBindGroupLayout(bloom-blur) succeeded',
3265
+ 'createBindGroupLayout(bloom-upsample) succeeded',
3298
3266
  'check device.limits.maxBindGroupsPerPipelineLayout',
3299
3267
  );
3300
- if (!blurBglResult.ok) throw blurBglResult.error;
3301
- bloomBlurBglHandle = adopt('binding', blurBglResult.value, () => undefined);
3268
+ if (!upsampleBglResult.ok) throw upsampleBglResult.error;
3269
+ bloomUpsampleBglHandle = adopt('binding', upsampleBglResult.value, () => undefined);
3302
3270
 
3303
- const blurPlResult = runShimSyncStep(
3271
+ const upsamplePlResult = runShimSyncStep(
3304
3272
  () =>
3305
3273
  rhiDevice.createPipelineLayout({
3306
- label: 'bloom-blur-pl',
3307
- bindGroupLayouts: [blurBglResult.value],
3274
+ label: 'bloom-upsample-pl',
3275
+ bindGroupLayouts: [upsampleBglResult.value],
3308
3276
  }),
3309
3277
  'webgpu-runtime-error',
3310
- 'createPipelineLayout(bloom-blur) succeeded',
3311
- 'verify bloom-blur BindGroupLayout matches shader @group(0) bindings',
3278
+ 'createPipelineLayout(bloom-upsample) succeeded',
3279
+ 'verify bloom-upsample BindGroupLayout matches shader @group(0) bindings',
3312
3280
  );
3313
- if (!blurPlResult.ok) throw blurPlResult.error;
3314
- adopt('binding', blurPlResult.value, () => undefined);
3281
+ if (!upsamplePlResult.ok) throw upsamplePlResult.error;
3282
+ adopt('binding', upsamplePlResult.value, () => undefined);
3315
3283
 
3316
- // M2-T4: bloom-blur H/V pipelines via getOrBuildPipeline (lazy-build).
3317
- // H and V share the same PSO descriptor — only per-axis texelSize UBO
3318
- // distinguishes them at record time. getOrBuildPipeline cache-hit on the
3319
- // second call returns the same handle (identical spec, identical PSO).
3284
+ // One pipeline serves every coarse-to-fine edge.
3320
3285
  {
3321
- const blurSpec: PipelineSpec = {
3286
+ const upsampleSpec: PipelineSpec = {
3322
3287
  shader: {
3323
- id: 'forgeax::post::bloom-blur',
3288
+ id: 'forgeax::post::bloom-upsample',
3324
3289
  passKind: 'post-process',
3325
3290
  variantSet: undefined,
3326
3291
  },
@@ -3337,21 +3302,21 @@ export async function buildReadyWebGPU(
3337
3302
  renderState: { cullMode: 'none' },
3338
3303
  };
3339
3304
  const modules = {
3340
- vertex: bloomBlurModule,
3341
- fragment: bloomBlurModule,
3342
- layout: blurPlResult.value,
3343
- label: 'bloom-blur-h-pipeline',
3305
+ vertex: bloomUpsampleModule,
3306
+ fragment: bloomUpsampleModule,
3307
+ layout: upsamplePlResult.value,
3308
+ label: 'bloom-upsample-pipeline',
3344
3309
  };
3345
3310
  try {
3346
- bloomBlurHPipelineHandle = getOrBuildPipeline(
3347
- blurSpec,
3311
+ bloomUpsamplePipelineHandle = getOrBuildPipeline(
3312
+ upsampleSpec,
3348
3313
  pipelineDeviceProvider,
3349
3314
  pipelineCache,
3350
3315
  modules,
3351
3316
  ) as RenderPipeline;
3352
- bloomBlurHPipelineHandle = adopt(
3317
+ bloomUpsamplePipelineHandle = adopt(
3353
3318
  'pipeline',
3354
- bloomBlurHPipelineHandle,
3319
+ bloomUpsamplePipelineHandle,
3355
3320
  () => undefined,
3356
3321
  );
3357
3322
  } catch (err) {
@@ -3359,47 +3324,26 @@ export async function buildReadyWebGPU(
3359
3324
  throw new PipelineSpecError({
3360
3325
  code: 'pipeline-build-failed',
3361
3326
  detail: { cause: err },
3362
- hint: 'createRenderPipeline (bloom-blur-h fullscreen) failed; inspect gpuMessage',
3327
+ hint: 'createRenderPipeline (bloom-upsample fullscreen) failed; inspect gpuMessage',
3363
3328
  });
3364
3329
  }
3365
- // V is cache-hit on the same spec (identical PSO; per-axis UBO differentiates at record time).
3366
- bloomBlurVPipelineHandle = bloomBlurHPipelineHandle;
3367
3330
  }
3368
3331
 
3369
- // Blur params UBOs: 16 B std140 (texelSize.xy + radius + pad) each.
3370
- // bug-20260625: one per axis -- H and V must not share a buffer (the
3371
- // shared-buffer writeBuffer race made both passes blur vertically).
3372
- const blurHParamsResult = runShimSyncStep(
3373
- () =>
3374
- rhiDevice.createBuffer({
3375
- label: 'bloom-blur-h-params-ubo',
3376
- size: BLUR_PARAMS_BYTES,
3377
- usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
3378
- mappedAtCreation: false,
3379
- }),
3380
- 'webgpu-runtime-error',
3381
- 'createBuffer (bloom-blur-h params UBO) succeeded',
3382
- 'check device.limits.maxUniformBufferBindingSize',
3383
- );
3384
- if (!blurHParamsResult.ok) throw blurHParamsResult.error;
3385
- bloomBlurHParamsBufferHandle = adopt('buffer', blurHParamsResult.value, (value) => {
3386
- rhiDevice.destroyBuffer(value);
3387
- });
3388
-
3389
- const blurVParamsResult = runShimSyncStep(
3332
+ // Four 16-byte std140 slices carry scatter for U0..U3.
3333
+ const upsampleParamsResult = runShimSyncStep(
3390
3334
  () =>
3391
3335
  rhiDevice.createBuffer({
3392
- label: 'bloom-blur-v-params-ubo',
3393
- size: BLUR_PARAMS_BYTES,
3336
+ label: 'bloom-upsample-params-ubo',
3337
+ size: BLOOM_UPSAMPLE_PARAMS_BYTES,
3394
3338
  usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
3395
3339
  mappedAtCreation: false,
3396
3340
  }),
3397
3341
  'webgpu-runtime-error',
3398
- 'createBuffer (bloom-blur-v params UBO) succeeded',
3342
+ 'createBuffer (bloom-upsample params UBO) succeeded',
3399
3343
  'check device.limits.maxUniformBufferBindingSize',
3400
3344
  );
3401
- if (!blurVParamsResult.ok) throw blurVParamsResult.error;
3402
- bloomBlurVParamsBufferHandle = adopt('buffer', blurVParamsResult.value, (value) => {
3345
+ if (!upsampleParamsResult.ok) throw upsampleParamsResult.error;
3346
+ bloomUpsampleParamsBufferHandle = adopt('buffer', upsampleParamsResult.value, (value) => {
3403
3347
  rhiDevice.destroyBuffer(value);
3404
3348
  });
3405
3349
  }
@@ -3505,7 +3449,7 @@ export async function buildReadyWebGPU(
3505
3449
  () =>
3506
3450
  rhiDevice.createBuffer({
3507
3451
  label: 'bloom-composite-params-ubo',
3508
- size: COMPOSITE_PARAMS_BYTES,
3452
+ size: BLOOM_COMPOSITE_PARAMS_BYTES,
3509
3453
  usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
3510
3454
  mappedAtCreation: false,
3511
3455
  }),
@@ -3526,17 +3470,15 @@ export async function buildReadyWebGPU(
3526
3470
  bloomCandidateBundle = {
3527
3471
  scope: candidateScope,
3528
3472
  generation: bloomGeneration + 1,
3529
- bloomBrightPipeline: bloomBrightPipelineHandle,
3530
- bloomBlurHPipeline: bloomBlurHPipelineHandle,
3531
- bloomBlurVPipeline: bloomBlurVPipelineHandle,
3473
+ bloomDownsamplePipeline: bloomDownsamplePipelineHandle,
3474
+ bloomUpsamplePipeline: bloomUpsamplePipelineHandle,
3532
3475
  bloomCompositePipeline: bloomCompositePipelineHandle,
3533
- bloomBrightBindGroupLayout: bloomBrightBglHandle,
3534
- bloomBlurBindGroupLayout: bloomBlurBglHandle,
3476
+ bloomDownsampleBindGroupLayout: bloomDownsampleBglHandle,
3477
+ bloomUpsampleBindGroupLayout: bloomUpsampleBglHandle,
3535
3478
  bloomCompositeBindGroupLayout: bloomCompositeBglHandle,
3536
3479
  bloomSampler: bloomSamplerHandle,
3537
- bloomBrightParamsBuffer: bloomBrightParamsBufferHandle,
3538
- bloomBlurHParamsBuffer: bloomBlurHParamsBufferHandle,
3539
- bloomBlurVParamsBuffer: bloomBlurVParamsBufferHandle,
3480
+ bloomDownsampleParamsBuffer: bloomDownsampleParamsBufferHandle,
3481
+ bloomUpsampleParamsBuffer: bloomUpsampleParamsBufferHandle,
3540
3482
  bloomCompositeParamsBuffer: bloomCompositeParamsBufferHandle,
3541
3483
  };
3542
3484
  inspectBloomResources = (graph): BloomInspection => {
@@ -3550,6 +3492,10 @@ export async function buildReadyWebGPU(
3550
3492
  return {
3551
3493
  graphStatus: 'empty',
3552
3494
  enabled: false,
3495
+ levelCount: 0,
3496
+ levelDimensions: [],
3497
+ downsamplePassCount: 0,
3498
+ upsamplePassCount: 0,
3553
3499
  targetCount: 0,
3554
3500
  targetBytes: 0,
3555
3501
  resourceCount: 0,
@@ -3573,6 +3519,10 @@ export async function buildReadyWebGPU(
3573
3519
  return {
3574
3520
  graphStatus: active === undefined ? 'empty' : graphInspection.status,
3575
3521
  enabled: active !== undefined,
3522
+ levelCount: active === undefined ? 0 : graphInspection.levelCount,
3523
+ levelDimensions: active === undefined ? [] : graphInspection.levelDimensions,
3524
+ downsamplePassCount: active === undefined ? 0 : graphInspection.downsamplePassCount,
3525
+ upsamplePassCount: active === undefined ? 0 : graphInspection.upsamplePassCount,
3576
3526
  targetCount: active === undefined ? 0 : graphInspection.targetCount,
3577
3527
  targetBytes: active === undefined ? 0 : graphInspection.targetBytes,
3578
3528
  resourceCount: ownedBundles.reduce(
@@ -3583,7 +3533,14 @@ export async function buildReadyWebGPU(
3583
3533
  encodeCount: active === undefined ? 0 : receipts.encodeCount,
3584
3534
  bindGroupCount: active === undefined ? 0 : receipts.bindGroupCount,
3585
3535
  uploadCount: active === undefined ? 0 : receipts.uploadCount,
3586
- residentChildBytes: 0,
3536
+ // The graph owns the target allocations. Use its validated
3537
+ // descriptor byte sum for the active child; zero is reserved for
3538
+ // an absent/invalid current graph, never a placeholder for a
3539
+ // live Bloom allocation.
3540
+ residentChildBytes:
3541
+ active === undefined || graphInspection.status !== 'valid'
3542
+ ? 0
3543
+ : graphInspection.targetBytes,
3587
3544
  generation: selected?.generation ?? 0,
3588
3545
  state,
3589
3546
  };
@@ -3660,17 +3617,15 @@ export async function buildReadyWebGPU(
3660
3617
  commitBloomResources = undefined;
3661
3618
  discardBloomResources = undefined;
3662
3619
  drainBloomResources = undefined;
3663
- bloomBrightPipelineHandle = null;
3664
- bloomBlurHPipelineHandle = null;
3665
- bloomBlurVPipelineHandle = null;
3620
+ bloomDownsamplePipelineHandle = null;
3621
+ bloomUpsamplePipelineHandle = null;
3666
3622
  bloomCompositePipelineHandle = null;
3667
- bloomBrightBglHandle = null;
3668
- bloomBlurBglHandle = null;
3623
+ bloomDownsampleBglHandle = null;
3624
+ bloomUpsampleBglHandle = null;
3669
3625
  bloomCompositeBglHandle = null;
3670
3626
  bloomSamplerHandle = null;
3671
- bloomBrightParamsBufferHandle = null;
3672
- bloomBlurHParamsBufferHandle = null;
3673
- bloomBlurVParamsBufferHandle = null;
3627
+ bloomDownsampleParamsBufferHandle = null;
3628
+ bloomUpsampleParamsBufferHandle = null;
3674
3629
  bloomCompositeParamsBufferHandle = null;
3675
3630
  throw cause;
3676
3631
  }
@@ -4039,24 +3994,17 @@ export async function buildReadyWebGPU(
4039
3994
  shadowLightSpaceMatrix: null,
4040
3995
  shadowCsmLightViewProj: null,
4041
3996
  shadowCsmSelection: null,
4042
- // feat-20260531-bloom-first-declarative-render-graph-pass / w13 + w16:
4043
- // bloom per-pass resource slots. Pipeline handles assembled during
4044
- // buildReadyWebGPU (marker-triage + compile + createRenderPipeline).
4045
- // Intermediate textures are allocate in the execute closures at 1/2-res
4046
- // (ensureLazyTexture, slot width/height tracking for size-drift rebuild).
4047
- // BindGroup caches survive until the intermediate view is invalidated
4048
- // by a resize (width/height drift forces null).
4049
- bloomBrightPipeline: null,
4050
- bloomBlurHPipeline: null,
4051
- bloomBlurVPipeline: null,
3997
+ // Multiscale Bloom pipeline handles are generation-owned by the ready
3998
+ // builder; graph targets are frame-owned and are not stored here.
3999
+ bloomDownsamplePipeline: null,
4000
+ bloomUpsamplePipeline: null,
4052
4001
  bloomCompositePipeline: null,
4053
- bloomBrightBindGroupLayout: null,
4054
- bloomBlurBindGroupLayout: null,
4002
+ bloomDownsampleBindGroupLayout: null,
4003
+ bloomUpsampleBindGroupLayout: null,
4055
4004
  bloomCompositeBindGroupLayout: null,
4056
4005
  bloomSampler: null,
4057
- bloomBrightParamsBuffer: null,
4058
- bloomBlurHParamsBuffer: null,
4059
- bloomBlurVParamsBuffer: null,
4006
+ bloomDownsampleParamsBuffer: null,
4007
+ bloomUpsampleParamsBuffer: null,
4060
4008
  bloomCompositeParamsBuffer: null,
4061
4009
  ensureBloomResources: ensureBloomResourcesStable,
4062
4010
  getBloomResources: getBloomResourcesStable,
@@ -4068,18 +4016,6 @@ export async function buildReadyWebGPU(
4068
4016
  retireBloomResources: retireBloomResourcesStable,
4069
4017
  drainBloomResources: drainBloomResourcesStable,
4070
4018
  inspectBloomResources: inspectBloomResourcesStable,
4071
- bloomBrightTexture: null,
4072
- bloomBrightView: null,
4073
- bloomBrightWidth: 0,
4074
- bloomBrightHeight: 0,
4075
- bloomBlurHTexture: null,
4076
- bloomBlurHView: null,
4077
- bloomBlurHWidth: 0,
4078
- bloomBlurHHeight: 0,
4079
- bloomBlurVTexture: null,
4080
- bloomBlurVView: null,
4081
- bloomBlurVWidth: 0,
4082
- bloomBlurVHeight: 0,
4083
4019
  // feat-20260612-hdrp-ssao M6 / w26 + M8 / w38: SSAO pipeline slots.
4084
4020
  ssaoCalcPipeline: ssaoCalcPipelineHandle,
4085
4021
  ssaoBlurPipeline: ssaoBlurPipelineHandle,