@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
@@ -2,6 +2,7 @@ import type { MaterialShaderArtifact } from '@forgeax/engine-shader';
2
2
  import { gpuDrivenMaterialArtifactKey } from '../extract/gpu-driven';
3
3
  import type { RenderSystemInternals } from '../record/render-context';
4
4
  import type { DispatchEntry, RenderableSnapshot } from '../render-system-extract';
5
+ import { hasRequiredVertexInputs } from './prepared-draw';
5
6
 
6
7
  type ArtifactRow = { readonly source: RenderableSnapshot; readonly renderableIndex: number };
7
8
 
@@ -136,13 +137,7 @@ export function collectGpuDrivenMaterialArtifacts({
136
137
  const shadowVertexInputsMatch =
137
138
  pass !== 'shadow' ||
138
139
  (receipt !== undefined &&
139
- receipt.vertexInputs.length === prepared.vertexInputs.length &&
140
- receipt.vertexInputs.every(
141
- (input, index) =>
142
- input.semantic === prepared.vertexInputs[index]?.semantic &&
143
- input.location === prepared.vertexInputs[index]?.location &&
144
- input.format === prepared.vertexInputs[index]?.format,
145
- ));
140
+ hasRequiredVertexInputs(receipt.vertexInputs, prepared.vertexInputs));
146
141
  if (
147
142
  selectedIdentityArtifact === undefined ||
148
143
  receipt === undefined ||
@@ -75,18 +75,23 @@ function failure(
75
75
  return { ok: false, error: new GpuDrivenPreparationError(code, detail) };
76
76
  }
77
77
 
78
- function sameVertexInputs(
78
+ /**
79
+ * A material receipt declares the inputs its shader consumes. Geometry may
80
+ * carry additional authored attributes (for example an unused TEXCOORD_1),
81
+ * so admission checks required locations and formats without treating those
82
+ * producer-owned extras as an ABI mismatch.
83
+ */
84
+ export function hasRequiredVertexInputs(
79
85
  expected: readonly MaterialShaderVertexInput[],
80
86
  actual: readonly MaterialShaderVertexInput[],
81
87
  ): boolean {
82
- return (
83
- expected.length === actual.length &&
84
- expected.every(
85
- (input, index) =>
86
- input.semantic === actual[index]?.semantic &&
87
- input.location === actual[index]?.location &&
88
- input.format === actual[index]?.format,
89
- )
88
+ return expected.every((input) =>
89
+ actual.some(
90
+ (candidate) =>
91
+ candidate.semantic === input.semantic &&
92
+ candidate.location === input.location &&
93
+ candidate.format === input.format,
94
+ ),
90
95
  );
91
96
  }
92
97
 
@@ -167,7 +172,7 @@ export function prepareGpuDrivenDraw(
167
172
  actual: `${input.geometry.vertexInputs.filter((vertex) => vertex.semantic.startsWith('uv')).length} geometry UV sets`,
168
173
  });
169
174
  }
170
- if (!sameVertexInputs(receipt.vertexInputs, input.geometry.vertexInputs)) {
175
+ if (!hasRequiredVertexInputs(receipt.vertexInputs, input.geometry.vertexInputs)) {
171
176
  return failure('vertex-input-mismatch', {
172
177
  reason: 'vertex-semantic-mismatch',
173
178
  owner: 'geometry',
@@ -84,7 +84,12 @@ import {
84
84
  type ShadowViewUpdateInput,
85
85
  shadowViewIdentityKey,
86
86
  } from './shadow-views';
87
- import { type GpuDrivenLodSelectionInspection, GpuDrivenView, selectGpuLodLane } from './view-gpu';
87
+ import {
88
+ type GpuDrivenLodSelectionInspection,
89
+ type GpuDrivenLodSubmitIdentity,
90
+ GpuDrivenView,
91
+ selectGpuLodLane,
92
+ } from './view-gpu';
88
93
 
89
94
  export interface StandardPbrSceneCandidate {
90
95
  readonly primitiveIndex: number;
@@ -2246,6 +2251,8 @@ export class GpuDrivenProduction {
2246
2251
  readonly activeEntityRevision?: number;
2247
2252
  /** Total extracted candidates, including facet-suppressed rows. */
2248
2253
  readonly telemetryCandidateCount?: number;
2254
+ /** Public receipt identity for the GPU telemetry copy encoded this frame. */
2255
+ readonly telemetrySubmit?: GpuDrivenLodSubmitIdentity;
2249
2256
  readonly occlusion?: OcclusionFrameProjection;
2250
2257
  /** Per-frame deltas from the existing RenderScene/GPU Scene/palette owners. */
2251
2258
  readonly structureMetrics?: GpuDrivenStructureMetrics;
@@ -2560,6 +2567,7 @@ export class GpuDrivenProduction {
2560
2567
  }
2561
2568
  const updated = this.view.update(filtered.plan, scene.scene, activeFrustum(input.camera));
2562
2569
  if (!updated.ok) return updated;
2570
+ this.view.setTelemetrySubmit(input.telemetrySubmit);
2563
2571
  this.telemetryPrepared = true;
2564
2572
  const view = this.view;
2565
2573
  const visibleBuffer = view.visibleBuffer;
@@ -3638,11 +3646,49 @@ export class GpuDrivenProduction {
3638
3646
  this.laneReason = 'overflow';
3639
3647
  }
3640
3648
  }
3641
- if (selection === undefined || telemetryCandidateCount <= 0) return selection;
3649
+ if (selection === undefined) return selection;
3642
3650
  if (filtered === undefined) return selection;
3643
- const preparedByBatchId = new Map(
3644
- filtered.batches.map((prepared) => [prepared.batch.batchId, prepared] as const),
3651
+ type LodBatchAttribution = {
3652
+ readonly batch: GpuDrivenBatch;
3653
+ readonly worldKey: number;
3654
+ readonly primitiveSlot: number;
3655
+ readonly slotGeneration: number;
3656
+ };
3657
+ const preparedByBatchId = new Map<number, LodBatchAttribution>(
3658
+ filtered.batches.map((prepared) => [
3659
+ prepared.batch.batchId,
3660
+ {
3661
+ batch: prepared.batch,
3662
+ worldKey: prepared.worldKey,
3663
+ primitiveSlot: prepared.primitiveSlot,
3664
+ slotGeneration: prepared.slotGeneration,
3665
+ },
3666
+ ]),
3645
3667
  );
3668
+ // Selector-only batches remain in filtered.plan so their GPU counters
3669
+ // can describe candidates suppressed from the raster admission lane.
3670
+ // They have no PreparedBatch entry because no draw is encoded, but they
3671
+ // still need the same World join for receipt attribution. Recover that
3672
+ // metadata from the stable primitive slot instead of dropping the whole
3673
+ // readback when the first such batch is encountered.
3674
+ const filteredSlots = this.filteredCache?.slots;
3675
+ const filteredWorldKeys = this.filteredCache?.worldKeys;
3676
+ if (filteredSlots !== undefined) {
3677
+ const slotByPrimitive = new Map(filteredSlots.map((slot) => [slot.slot, slot] as const));
3678
+ for (const batch of filtered.plan.batches) {
3679
+ if (preparedByBatchId.has(batch.batchId)) continue;
3680
+ const candidate = batch.candidates[0];
3681
+ const slot =
3682
+ candidate === undefined ? undefined : slotByPrimitive.get(candidate.primitiveIndex);
3683
+ if (slot === undefined) continue;
3684
+ preparedByBatchId.set(batch.batchId, {
3685
+ batch,
3686
+ worldKey: filteredWorldKeys?.[slot.snapshot.worldId] ?? slot.snapshot.worldId,
3687
+ primitiveSlot: slot.slot,
3688
+ slotGeneration: slot.generation,
3689
+ });
3690
+ }
3691
+ }
3646
3692
  const worldSelections = new Map<
3647
3693
  number,
3648
3694
  {
@@ -3655,9 +3701,33 @@ export class GpuDrivenProduction {
3655
3701
  readonly lodHistogram: Map<number, number>;
3656
3702
  }
3657
3703
  >();
3704
+ const lodHistogram = new Map<number, number>();
3705
+ // The selector visits the full captured plan, including candidates that
3706
+ // the CPU visibility projection suppressed. Use that same plan for the
3707
+ // LOD denominator so World attribution cannot fail merely because the
3708
+ // two lanes have different frustum scopes.
3709
+ let lodCandidateCount = 0;
3710
+ let lodVisible = 0;
3658
3711
  for (const batchSelection of selection.batches) {
3659
3712
  const prepared = preparedByBatchId.get(batchSelection.batchId);
3660
- if (prepared === undefined) return selection;
3713
+ if (prepared === undefined) return undefined;
3714
+ // The GPU selector also visits ordinary GPU-driven meshes so one
3715
+ // compact compute path can populate their indirect args. Those rows
3716
+ // are level-0 by construction, but they are not LOD candidates and
3717
+ // must not enter receipt-bound LOD attribution. Batch topology keeps
3718
+ // LOD coverage/ranges in the grouping key, so a batch is expected to
3719
+ // be homogeneous; fail closed if a producer violates that invariant
3720
+ // instead of guessing a split for aggregate counters.
3721
+ const lodCandidateRows = prepared.batch.candidates.filter(
3722
+ (candidate) => (candidate.lodCoverages?.length ?? 0) > 1,
3723
+ );
3724
+ if (lodCandidateRows.length === 0) continue;
3725
+ if (lodCandidateRows.length !== batchSelection.candidateCount) return undefined;
3726
+ lodCandidateCount += batchSelection.candidateCount;
3727
+ lodVisible += batchSelection.visible;
3728
+ for (const row of batchSelection.lodHistogram) {
3729
+ lodHistogram.set(row.level, (lodHistogram.get(row.level) ?? 0) + row.count);
3730
+ }
3661
3731
  let aggregate = worldSelections.get(prepared.worldKey);
3662
3732
  if (aggregate === undefined) {
3663
3733
  aggregate = {
@@ -3683,8 +3753,14 @@ export class GpuDrivenProduction {
3683
3753
  }
3684
3754
  return {
3685
3755
  ...selection,
3686
- candidateCount: telemetryCandidateCount,
3687
- occluded: Math.max(0, telemetryCandidateCount - selection.visible),
3756
+ candidateCount: lodCandidateCount,
3757
+ visible: lodVisible,
3758
+ occluded: Math.max(0, lodCandidateCount - lodVisible),
3759
+ lodHistogram: Object.freeze(
3760
+ [...lodHistogram.entries()]
3761
+ .sort(([left], [right]) => left - right)
3762
+ .map(([level, count]) => Object.freeze({ level, count })),
3763
+ ),
3688
3764
  worldSelections: Object.freeze(
3689
3765
  [...worldSelections.values()].map((aggregate) =>
3690
3766
  Object.freeze({
@@ -302,8 +302,14 @@ type LodTelemetryCopy = {
302
302
  readonly buffers: ViewBuffers;
303
303
  readonly plan: SubmissionPlan;
304
304
  readonly resourceGeneration: number;
305
+ readonly submit?: GpuDrivenLodSubmitIdentity;
305
306
  };
306
307
 
308
+ export interface GpuDrivenLodSubmitIdentity {
309
+ readonly frameId: number;
310
+ readonly deviceGeneration: number;
311
+ }
312
+
307
313
  export interface GpuDrivenViewGraphResources {
308
314
  readonly primitive: GraphBuffer;
309
315
  readonly instance: GraphBuffer;
@@ -369,6 +375,8 @@ export interface GpuDrivenLodSelectionInspection {
369
375
  }[];
370
376
  readonly geometryWork: number;
371
377
  readonly rootGeometryWork: number;
378
+ /** Public receipt identity captured when the telemetry copy was encoded. */
379
+ readonly submit?: GpuDrivenLodSubmitIdentity;
372
380
  }
373
381
 
374
382
  function nextCapacity(required: number): number {
@@ -431,6 +439,8 @@ export class GpuDrivenView {
431
439
  private resourceGeneration = 0;
432
440
  /** The last readback copy that was actually encoded and is not consumed. */
433
441
  private telemetryPending: LodTelemetryCopy | undefined;
442
+ /** Receipt identity for the copy pass encoded by the current frame. */
443
+ private telemetrySubmit: GpuDrivenLodSubmitIdentity | undefined;
434
444
  /** Coalesce observers while the readback buffer is map-pending. */
435
445
  private lodSelectionReadback:
436
446
  | {
@@ -449,6 +459,10 @@ export class GpuDrivenView {
449
459
  private readonly labelPrefix: string,
450
460
  ) {}
451
461
 
462
+ setTelemetrySubmit(identity: GpuDrivenLodSubmitIdentity | undefined): void {
463
+ this.telemetrySubmit = identity;
464
+ }
465
+
452
466
  static create(input: {
453
467
  readonly device: RhiDevice;
454
468
  readonly shaderModuleFactory: PipelineBuilderShaderModuleFactory;
@@ -848,6 +862,7 @@ export class GpuDrivenView {
848
862
  ),
849
863
  geometryWork,
850
864
  rootGeometryWork,
865
+ ...(copy.submit === undefined ? {} : { submit: copy.submit }),
851
866
  });
852
867
  this.telemetryPending = undefined;
853
868
  return this.lodSelection;
@@ -1048,6 +1063,7 @@ export class GpuDrivenView {
1048
1063
  buffers,
1049
1064
  plan: submittedPlan,
1050
1065
  resourceGeneration: graphResourceGeneration,
1066
+ ...(this.telemetrySubmit === undefined ? {} : { submit: this.telemetrySubmit }),
1051
1067
  };
1052
1068
  }
1053
1069
  },
@@ -1080,6 +1096,7 @@ export class GpuDrivenView {
1080
1096
  this.sceneMaterial = undefined;
1081
1097
  this.sceneCapacity = 0;
1082
1098
  this.telemetryPending = undefined;
1099
+ this.telemetrySubmit = undefined;
1083
1100
  this.lodSelectionReadback = undefined;
1084
1101
  this.lodSelection = undefined;
1085
1102
  this.destroyAfterSubmittedWork(buffers);
package/src/gpu-scene.ts CHANGED
@@ -17,6 +17,9 @@ import type { RenderSceneApplyResult, RenderSceneSlot } from './scene/render-sce
17
17
 
18
18
  export type { GpuSceneInspection } from './inspection-types';
19
19
 
20
+ type GpuSceneTemporalSnapshot = NonNullable<RenderSceneSlot['snapshot']['temporal']>;
21
+ type GpuSceneTemporalResolver = (slot: RenderSceneSlot) => GpuSceneTemporalSnapshot | undefined;
22
+
20
23
  const PRIMITIVE = GPU_SCENE_LAYOUTS.primitive;
21
24
  const INSTANCE = GPU_SCENE_LAYOUTS.instance;
22
25
  const TRANSFORM = GPU_SCENE_LAYOUTS.transform;
@@ -219,6 +222,56 @@ function coalesceSlots(
219
222
  return ranges;
220
223
  }
221
224
 
225
+ /**
226
+ * Build a lookup from the current instance identity to its submitted ordinal.
227
+ * The GPU table is physically compacted by ordinal, so this small CPU lookup
228
+ * is the seam that keeps a stable logical instance from inheriting another
229
+ * row's previous transform after reorder. A missing or duplicate generation
230
+ * deliberately returns no proof and makes the caller seed the current pose.
231
+ */
232
+ function previousInstanceOrdinals(
233
+ current: NonNullable<RenderSceneSlot['snapshot']['instances']> | undefined,
234
+ temporal: GpuSceneTemporalSnapshot | undefined,
235
+ ): Map<number, number> | undefined {
236
+ const previous = temporal?.previousInstances;
237
+ const currentGenerations = current?.generations;
238
+ const previousGenerations = previous?.generations;
239
+ if (
240
+ temporal?.motionValid !== true ||
241
+ current === undefined ||
242
+ previous === undefined ||
243
+ currentGenerations === undefined ||
244
+ previousGenerations === undefined ||
245
+ currentGenerations.length !== current.instanceCount ||
246
+ previousGenerations.length !== previous.instanceCount ||
247
+ current.transforms.length < current.instanceCount * 16 ||
248
+ previous.transforms.length < previous.instanceCount * 16
249
+ ) {
250
+ return undefined;
251
+ }
252
+ const currentSet = new Set<number>();
253
+ for (const generation of currentGenerations) {
254
+ if (generation === 0 || currentSet.has(generation)) return undefined;
255
+ currentSet.add(generation);
256
+ }
257
+ const ordinals = new Map<number, number>();
258
+ for (let ordinal = 0; ordinal < previousGenerations.length; ordinal += 1) {
259
+ const generation = previousGenerations[ordinal] ?? 0;
260
+ if (generation === 0 || ordinals.has(generation)) return undefined;
261
+ ordinals.set(generation, ordinal);
262
+ }
263
+ return ordinals;
264
+ }
265
+
266
+ function instanceIdentityChanged(
267
+ resident: Uint32Array | undefined,
268
+ current: Uint32Array | undefined,
269
+ ordinal: number,
270
+ ): boolean {
271
+ if (resident === undefined || current === undefined) return resident !== current;
272
+ return resident[ordinal] !== current[ordinal];
273
+ }
274
+
222
275
  /** Persistent GPU tables derived only from renderer projection slots. */
223
276
  export class GpuScene {
224
277
  private disposed = false;
@@ -238,6 +291,8 @@ export class GpuScene {
238
291
  private sceneContentRevision = 0;
239
292
  private identityUploaded = false;
240
293
  private readonly allocations = new Map<number, PrimitiveAllocation>();
294
+ /** Current stable instance identities retained beside compact GPU rows. */
295
+ private readonly instanceGenerations = new Map<number, Uint32Array>();
241
296
  private readonly instances = new StableRangeAllocator();
242
297
  private readonly transforms = new StableRangeAllocator();
243
298
  private readonly draws = new StableRangeAllocator();
@@ -322,7 +377,10 @@ export class GpuScene {
322
377
  return allocation.materialStart + materialSlot;
323
378
  }
324
379
 
325
- sync(delta: RenderSceneApplyResult): Result<GpuSceneSyncResult, RhiError> {
380
+ sync(
381
+ delta: RenderSceneApplyResult,
382
+ temporalResolver?: GpuSceneTemporalResolver,
383
+ ): Result<GpuSceneSyncResult, RhiError> {
326
384
  for (const transformIndex of this.pendingTemporalUploads) {
327
385
  this.writesByTable.transform.push(transformIndex);
328
386
  }
@@ -393,11 +451,11 @@ export class GpuScene {
393
451
  for (const record of publications) {
394
452
  const reset = newSlots.has(record.slot) || allocationResets.has(record.slot);
395
453
  if (reset || contentUpdatedSlotIds.has(record.slot)) {
396
- this.writeSlot(record, reset, writesByTable);
454
+ this.writeSlot(record, reset, writesByTable, temporalResolver?.(record));
397
455
  continue;
398
456
  }
399
457
  if (instanceUpdatedSlotIds.has(record.slot))
400
- this.writeInstanceTransforms(record, writesByTable);
458
+ this.writeInstanceTransforms(record, writesByTable, temporalResolver?.(record));
401
459
  this.writeRootTransform(record, false, writesByTable);
402
460
  }
403
461
  const uploaded = this.uploadRows(writesByTable);
@@ -459,6 +517,7 @@ export class GpuScene {
459
517
 
460
518
  rebuild(slots: readonly RenderSceneSlot[]): Result<GpuSceneSyncResult, RhiError> {
461
519
  this.allocations.clear();
520
+ this.instanceGenerations.clear();
462
521
  this.instances.reset();
463
522
  this.transforms.reset(1);
464
523
  this.draws.reset();
@@ -551,6 +610,7 @@ export class GpuScene {
551
610
  record: RenderSceneSlot,
552
611
  resetPrevious: boolean,
553
612
  writes: Record<GpuSceneTableName, number[]>,
613
+ temporal?: GpuSceneTemporalSnapshot,
554
614
  ): void {
555
615
  const allocation = this.allocations.get(record.slot);
556
616
  if (allocation === undefined) throw new RangeError('GPU Scene allocation unavailable');
@@ -610,6 +670,11 @@ export class GpuScene {
610
670
 
611
671
  const instance = new DataView(this.tableBytes.instance);
612
672
  const transform = this.transformView;
673
+ const instances = record.snapshot.instances;
674
+ const previousInstances = temporal?.previousInstances;
675
+ const previousOrdinals = previousInstanceOrdinals(instances, temporal);
676
+ const currentGenerations = instances?.generations;
677
+ const residentGenerations = this.instanceGenerations.get(record.slot);
613
678
  for (let ordinal = 0; ordinal < allocation.instanceCount; ordinal += 1) {
614
679
  const instanceIndex = allocation.instanceStart + ordinal;
615
680
  const instanceOffset = instanceIndex * INSTANCE.stride;
@@ -636,7 +701,7 @@ export class GpuScene {
636
701
  writeChangedU32(instance, instanceOffset + offset(INSTANCE, 'flags'), PRIMITIVE_ACTIVE) ||
637
702
  metadataChanged;
638
703
  if (allocation.instanceTransformCount > 0) {
639
- const transforms = record.snapshot.instances?.transforms;
704
+ const transforms = instances?.transforms;
640
705
  const hasLocal = transforms !== undefined && transforms.length >= (ordinal + 1) * 16;
641
706
  const local = hasLocal ? transforms : IDENTITY_MATRIX;
642
707
  const sourceOffset = hasLocal ? ordinal * 16 : 0;
@@ -647,19 +712,66 @@ export class GpuScene {
647
712
  local,
648
713
  sourceOffset,
649
714
  );
650
- if (resetPrevious)
651
- writeChangedMat4(
652
- transform,
653
- localOffset + offset(TRANSFORM, 'previousWorld'),
654
- local,
655
- sourceOffset,
656
- );
657
- else if (changed) this.pendingTemporalTransforms.add(transformIndex);
658
- if (changed || resetPrevious) writes.transform.push(transformIndex);
715
+ const identityChanged = instanceIdentityChanged(
716
+ residentGenerations,
717
+ currentGenerations,
718
+ ordinal,
719
+ );
720
+ if (resetPrevious) {
721
+ const previousOrdinal = previousOrdinals?.get(currentGenerations?.[ordinal] ?? 0);
722
+ const previousSource =
723
+ previousOrdinal === undefined || previousInstances === undefined
724
+ ? undefined
725
+ : previousInstances.transforms;
726
+ if (previousSource === undefined || previousOrdinal === undefined) {
727
+ writeChangedMat4(
728
+ transform,
729
+ localOffset + offset(TRANSFORM, 'previousWorld'),
730
+ local,
731
+ sourceOffset,
732
+ );
733
+ } else {
734
+ writeChangedMat4(
735
+ transform,
736
+ localOffset + offset(TRANSFORM, 'previousWorld'),
737
+ previousSource,
738
+ previousOrdinal * 16,
739
+ );
740
+ this.pendingTemporalTransforms.add(transformIndex);
741
+ }
742
+ } else if (changed || identityChanged) {
743
+ const previousOrdinal = previousOrdinals?.get(currentGenerations?.[ordinal] ?? 0);
744
+ const previousSource =
745
+ previousOrdinal === undefined || previousInstances === undefined
746
+ ? undefined
747
+ : previousInstances.transforms;
748
+ if (previousSource === undefined || previousOrdinal === undefined) {
749
+ // A changed row without an identity proof must not borrow the
750
+ // compacted row's old velocity. Seed only this row and keep other
751
+ // stable instances eligible for motion.
752
+ writeChangedMat4(
753
+ transform,
754
+ localOffset + offset(TRANSFORM, 'previousWorld'),
755
+ local,
756
+ sourceOffset,
757
+ );
758
+ } else {
759
+ writeChangedMat4(
760
+ transform,
761
+ localOffset + offset(TRANSFORM, 'previousWorld'),
762
+ previousSource,
763
+ previousOrdinal * 16,
764
+ );
765
+ }
766
+ this.pendingTemporalTransforms.add(transformIndex);
767
+ }
768
+ if (changed || identityChanged || resetPrevious) writes.transform.push(transformIndex);
659
769
  }
660
770
  if (metadataChanged || resetPrevious) writes.instance.push(instanceIndex);
661
771
  }
662
772
 
773
+ this.rememberInstanceGenerations(record.slot, currentGenerations, allocation.instanceCount);
774
+
663
775
  this.writeRootTransform(record, resetPrevious, writes);
664
776
  writes.primitive.push(record.slot);
665
777
 
@@ -740,6 +852,7 @@ export class GpuScene {
740
852
  private writeInstanceTransforms(
741
853
  record: RenderSceneSlot,
742
854
  writes: Record<GpuSceneTableName, number[]>,
855
+ temporal?: GpuSceneTemporalSnapshot,
743
856
  ): void {
744
857
  const allocation = this.allocations.get(record.slot);
745
858
  const instances = record.snapshot.instances;
@@ -749,21 +862,74 @@ export class GpuScene {
749
862
  if (allocation.instanceTransformCount !== instances.instanceCount) {
750
863
  throw new RangeError('GPU Scene instance update changed instance count');
751
864
  }
865
+ const previousOrdinals = previousInstanceOrdinals(instances, temporal);
866
+ const previousInstances = temporal?.previousInstances;
867
+ const currentGenerations = instances.generations;
868
+ const residentGenerations = this.instanceGenerations.get(record.slot);
752
869
  for (let ordinal = 0; ordinal < allocation.instanceTransformCount; ordinal += 1) {
753
870
  const transformIndex = allocation.instanceTransformStart + ordinal;
754
871
  const transformOffset = transformIndex * TRANSFORM.stride;
755
- if (
756
- writeChangedMat4(
757
- this.transformView,
758
- transformOffset + offset(TRANSFORM, 'currentWorld'),
759
- instances.transforms,
760
- ordinal * 16,
761
- )
762
- ) {
872
+ const changed = writeChangedMat4(
873
+ this.transformView,
874
+ transformOffset + offset(TRANSFORM, 'currentWorld'),
875
+ instances.transforms,
876
+ ordinal * 16,
877
+ );
878
+ const identityChanged = instanceIdentityChanged(
879
+ residentGenerations,
880
+ currentGenerations,
881
+ ordinal,
882
+ );
883
+ if (changed || identityChanged) {
884
+ const previousOrdinal = previousOrdinals?.get(currentGenerations?.[ordinal] ?? 0);
885
+ const previousSource =
886
+ previousOrdinal === undefined || previousInstances === undefined
887
+ ? undefined
888
+ : previousInstances.transforms;
889
+ if (previousSource === undefined || previousOrdinal === undefined) {
890
+ writeChangedMat4(
891
+ this.transformView,
892
+ transformOffset + offset(TRANSFORM, 'previousWorld'),
893
+ instances.transforms,
894
+ ordinal * 16,
895
+ );
896
+ } else {
897
+ writeChangedMat4(
898
+ this.transformView,
899
+ transformOffset + offset(TRANSFORM, 'previousWorld'),
900
+ previousSource,
901
+ previousOrdinal * 16,
902
+ );
903
+ }
763
904
  this.pendingTemporalTransforms.add(transformIndex);
764
905
  writes.transform.push(transformIndex);
765
906
  }
766
907
  }
908
+ this.rememberInstanceGenerations(
909
+ record.slot,
910
+ currentGenerations,
911
+ allocation.instanceTransformCount,
912
+ );
913
+ }
914
+
915
+ private rememberInstanceGenerations(
916
+ slot: number,
917
+ generations: Uint32Array | undefined,
918
+ instanceCount: number,
919
+ ): void {
920
+ if (generations === undefined || generations.length !== instanceCount) {
921
+ this.instanceGenerations.delete(slot);
922
+ return;
923
+ }
924
+ const resident = this.instanceGenerations.get(slot);
925
+ if (
926
+ resident !== undefined &&
927
+ resident.length === generations.length &&
928
+ resident.every((generation, ordinal) => generation === generations[ordinal])
929
+ ) {
930
+ return;
931
+ }
932
+ this.instanceGenerations.set(slot, generations.slice());
767
933
  }
768
934
 
769
935
  private writeRootTransform(
@@ -795,6 +961,7 @@ export class GpuScene {
795
961
  }
796
962
  const allocation = this.allocations.get(slot);
797
963
  if (allocation === undefined) return;
964
+ this.instanceGenerations.delete(slot);
798
965
  this.clearAllocation(allocation, writes);
799
966
  this.allocations.delete(slot);
800
967
  }
package/src/index.ts CHANGED
@@ -37,6 +37,14 @@ export {
37
37
  ANTIALIAS_TAA,
38
38
  BLOOM_DISABLED,
39
39
  BLOOM_ENABLED,
40
+ CAMERA_BLOOM_INTENSITY_MAX,
41
+ CAMERA_BLOOM_INTENSITY_MIN,
42
+ CAMERA_BLOOM_SCATTER_MAX,
43
+ CAMERA_BLOOM_SCATTER_MIN,
44
+ CAMERA_BLOOM_SOFT_KNEE_MAX,
45
+ CAMERA_BLOOM_SOFT_KNEE_MIN,
46
+ CAMERA_BLOOM_THRESHOLD_MAX,
47
+ CAMERA_BLOOM_THRESHOLD_MIN,
40
48
  CAMERA_EXPOSURE_MODE_AUTO,
41
49
  CAMERA_EXPOSURE_MODE_MANUAL,
42
50
  CAMERA_PROJECTION_ORTHOGRAPHIC,
@@ -59,6 +67,7 @@ export {
59
67
  TONEMAP_NONE,
60
68
  TONEMAP_REINHARD,
61
69
  TONEMAP_REINHARD_EXTENDED,
70
+ validateCameraBloom,
62
71
  validateCameraColorGrading,
63
72
  validateCameraExposure,
64
73
  } from './components/camera.js';
@@ -155,6 +164,10 @@ export {
155
164
  createDynamicGeometryLifecycle,
156
165
  DynamicGeometryError,
157
166
  } from './dynamic-geometry.js';
167
+ // The environment selector owns this structured recovery error. Keep the
168
+ // class on the public environment path so consumers can branch by `code` and
169
+ // inspect its typed `detail` without importing an owner-private module.
170
+ export { SunCardinalityError } from './environment/frame.js';
158
171
  export type {
159
172
  EnvironmentInspection,
160
173
  EnvironmentInspectionFailure,
@@ -225,6 +238,22 @@ export {
225
238
  validateDepthOfFieldFrameParams,
226
239
  validateDepthOfFieldParams,
227
240
  } from './features/depth-of-field/depth-of-field-params.js';
241
+ export type {
242
+ MotionBlurInvalidParamsDetail,
243
+ MotionBlurParams,
244
+ MotionBlurSampleTier,
245
+ } from './features/motion-blur/motion-blur-params.js';
246
+ export {
247
+ DEFAULT_MOTION_BLUR_PARAMS,
248
+ effectiveMotionBlurSampleCount,
249
+ isMotionBlurIntervalValid,
250
+ MotionBlurValidationError,
251
+ motionBlurExposureScale,
252
+ motionBlurSampleDelta,
253
+ motionBlurTemporalDemand,
254
+ resolveMotionBlurParams,
255
+ validateMotionBlurParams,
256
+ } from './features/motion-blur/motion-blur-params.js';
228
257
  export type {
229
258
  RenderFeatureLogicalTarget,
230
259
  RenderFeatureMaterialShaderBindingContract,
@@ -273,7 +302,10 @@ export type {
273
302
  LightInspection,
274
303
  LodOcclusionInspection,
275
304
  MaterialTextureSourceInspection,
305
+ MotionBlurExecutionPass,
306
+ MotionBlurExecutionReceipt,
276
307
  MotionBlurInspection,
308
+ MotionBlurInspectionLane,
277
309
  MotionBlurInspectionStatus,
278
310
  ReflectionFallbackFailureStage,
279
311
  ReflectionFallbackInspection,