@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.
- package/README.md +447 -34
- package/dist/assembly/analytic-fog-registration.d.ts +3 -0
- package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
- package/dist/assembly/fxaa-registration.d.ts +4 -0
- package/dist/assembly/fxaa-registration.d.ts.map +1 -0
- package/dist/assembly/lod-observation.d.ts +4 -0
- package/dist/assembly/lod-observation.d.ts.map +1 -0
- package/dist/assembly/motion-blur-registration.d.ts +3 -0
- package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
- package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
- package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
- package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
- package/dist/assembly/webgpu-ready.d.ts +3 -2
- package/dist/assembly/webgpu-ready.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/bloom-admission.d.ts +13 -0
- package/dist/bloom-admission.d.ts.map +1 -0
- package/dist/{chunk-FUUO4UND.mjs → chunk-3RTCJTH4.mjs} +12 -9
- package/dist/chunk-3RTCJTH4.mjs.map +1 -0
- package/dist/{chunk-NO4WZCAH.mjs → chunk-LEAXOMYJ.mjs} +3 -3
- package/dist/{chunk-NO4WZCAH.mjs.map → chunk-LEAXOMYJ.mjs.map} +1 -1
- package/dist/{chunk-JRBDO2KB.mjs → chunk-NB2S7ZRX.mjs} +166 -10
- package/dist/chunk-NB2S7ZRX.mjs.map +1 -0
- package/dist/{chunk-7MQ3XBSP.mjs → chunk-NNQL7CB3.mjs} +4 -4
- package/dist/{chunk-7MQ3XBSP.mjs.map → chunk-NNQL7CB3.mjs.map} +1 -1
- package/dist/{chunk-C3DDSXYK.mjs → chunk-OO3DRYK7.mjs} +171 -230
- package/dist/chunk-OO3DRYK7.mjs.map +1 -0
- package/dist/{chunk-S5UE2T5P.mjs → chunk-PLAXOOX5.mjs} +608 -7
- package/dist/chunk-PLAXOOX5.mjs.map +1 -0
- package/dist/{chunk-PSEQ6QPH.mjs → chunk-VOZWDM3L.mjs} +295 -198
- package/dist/chunk-VOZWDM3L.mjs.map +1 -0
- package/dist/{chunk-MDQJPY3T.mjs → chunk-ZDQWGQW7.mjs} +10 -4
- package/dist/chunk-ZDQWGQW7.mjs.map +1 -0
- package/dist/components/atmosphere.d.ts +2 -0
- package/dist/components/atmosphere.d.ts.map +1 -1
- package/dist/components/camera.d.ts +20 -4
- package/dist/components/camera.d.ts.map +1 -1
- package/dist/components/depth-of-field.d.ts +28 -0
- package/dist/components/depth-of-field.d.ts.map +1 -0
- package/dist/components/directional-light.d.ts +2 -1
- package/dist/components/directional-light.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/mesh-filter.d.ts +6 -5
- package/dist/components/mesh-filter.d.ts.map +1 -1
- package/dist/components/motion-blur.d.ts +1 -0
- package/dist/components/motion-blur.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +3746 -837
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/gpu-residency.d.ts +6 -0
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/environment/analytic-fog.d.ts +6 -0
- package/dist/environment/analytic-fog.d.ts.map +1 -0
- package/dist/errors/render.d.ts +6 -2
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/camera.d.ts.map +1 -1
- package/dist/extract/contracts.d.ts +7 -0
- package/dist/extract/contracts.d.ts.map +1 -1
- package/dist/extract/environment.d.ts +2 -0
- package/dist/extract/environment.d.ts.map +1 -1
- package/dist/features/depth-of-field/depth-of-field-assembly.d.ts +19 -0
- package/dist/features/depth-of-field/depth-of-field-assembly.d.ts.map +1 -0
- package/dist/features/depth-of-field/depth-of-field-feature.d.ts +43 -0
- package/dist/features/depth-of-field/depth-of-field-feature.d.ts.map +1 -0
- package/dist/features/depth-of-field/depth-of-field-inspection.d.ts +34 -0
- package/dist/features/depth-of-field/depth-of-field-inspection.d.ts.map +1 -0
- package/dist/features/depth-of-field/depth-of-field-params.d.ts +83 -0
- package/dist/features/depth-of-field/depth-of-field-params.d.ts.map +1 -0
- package/dist/features/error-reporting.d.ts +13 -0
- package/dist/features/error-reporting.d.ts.map +1 -0
- package/dist/features/host.d.ts +5 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
- package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
- package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
- package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
- package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
- package/dist/features/plan.d.ts +7 -4
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/prepared-gpu-work.d.ts +9 -5
- package/dist/features/prepared-gpu-work.d.ts.map +1 -1
- package/dist/features/render-graph-compute.d.ts.map +1 -1
- package/dist/features/render-graph-contribution.d.ts.map +1 -1
- package/dist/features/types.d.ts +19 -1
- package/dist/features/types.d.ts.map +1 -1
- package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
- package/dist/gpu-driven/prepared-draw.d.ts +7 -0
- package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +3 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +9 -0
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/gpu-scene.d.ts +6 -1
- package/dist/gpu-scene.d.ts.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +6 -5
- package/dist/index.mjs.map +1 -1
- package/dist/inspection-types.d.ts +78 -2
- package/dist/inspection-types.d.ts.map +1 -1
- package/dist/instances.d.ts +15 -1
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +6 -6
- package/dist/mesh-material-bindings.d.ts +3 -0
- package/dist/mesh-material-bindings.d.ts.map +1 -1
- package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
- package/dist/pipeline/standard-output/types.d.ts +4 -2
- package/dist/pipeline/standard-output/types.d.ts.map +1 -1
- package/dist/pipeline/standard-post.d.ts +28 -19
- package/dist/pipeline/standard-post.d.ts.map +1 -1
- package/dist/pipeline-spec.d.ts +2 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +24 -1
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame-targets.d.ts.map +1 -1
- package/dist/record/frame.d.ts +5 -2
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/mesh-ssbo.d.ts +6 -5
- package/dist/record/mesh-ssbo.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +12 -28
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/skybox-post-pass.d.ts +5 -3
- package/dist/record/skybox-post-pass.d.ts.map +1 -1
- package/dist/record/typed-frame-graph.d.ts +8 -1
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-contract.d.ts +32 -5
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-graph-primitives.d.ts +2 -0
- package/dist/render-graph-primitives.d.ts.map +1 -1
- package/dist/render-pipeline.d.ts +15 -1
- package/dist/render-pipeline.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +26 -5
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system.d.ts +9 -2
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +2 -0
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/temporal/frame-coordinator.d.ts.map +1 -1
- package/dist/temporal/frame.d.ts +7 -0
- package/dist/temporal/frame.d.ts.map +1 -1
- package/dist/temporal/index.mjs +5 -5
- package/dist/temporal/view.d.ts +6 -1
- package/dist/temporal/view.d.ts.map +1 -1
- package/dist/typed-render-graph-primitives.d.ts +10 -7
- package/dist/typed-render-graph-primitives.d.ts.map +1 -1
- package/dist/volume/component.d.ts +0 -1
- package/dist/volume/component.d.ts.map +1 -1
- package/dist/volume/passes.d.ts.map +1 -1
- package/dist/volume/temporal.d.ts +3 -1
- package/dist/volume/temporal.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
- package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
- package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
- package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
- package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
- package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
- package/src/__tests__/environment-selection.unit.test.ts +12 -0
- package/src/__tests__/factory-contract.integration.test.ts +331 -68
- package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
- package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
- package/src/__tests__/gpu-driven-baseline.characterization.test.ts +12 -0
- package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
- package/src/__tests__/gpu-driven-production.integration.test.ts +27 -1
- package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +1 -1
- package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
- package/src/__tests__/gpu-driven-view-gpu-evidence.ts +11 -0
- package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
- package/src/__tests__/gpu-scene.unit.test.ts +91 -0
- package/src/__tests__/instances-store.unit.test.ts +90 -0
- package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
- package/src/__tests__/motion-blur-compute.dawn.test.ts +583 -0
- package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +49 -0
- package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
- package/src/__tests__/public-surface.test-d.ts +8 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +2 -0
- package/src/__tests__/render-feature-error-reporting.unit.test.ts +24 -0
- package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
- package/src/__tests__/render-scene-temporal-retry.unit.test.ts +31 -0
- package/src/__tests__/renderer-frame-transaction.integration.test.ts +7 -1
- package/src/__tests__/shader-manifest-fixture.ts +4 -4
- package/src/__tests__/skinned-shadow-caster.test.ts +2 -0
- package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
- package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
- package/src/__tests__/standard-pipeline.integration.test.ts +18 -8
- package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
- package/src/__tests__/temporal-view.unit.test.ts +2 -1
- package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
- package/src/__tests__/volume-optics-oracle.unit.test.ts +3 -1
- package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +177 -13
- package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +447 -61
- package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
- package/src/__tests__/volumetric-fog-topology.unit.test.ts +19 -3
- package/src/assembly/analytic-fog-registration.ts +14 -0
- package/src/assembly/fxaa-registration.ts +17 -0
- package/src/assembly/lod-observation.ts +12 -0
- package/src/assembly/motion-blur-registration.ts +17 -0
- package/src/assembly/renderer-facade.ts +2 -0
- package/src/assembly/temporal-post-process-prewarm.ts +40 -0
- package/src/assembly/webgpu-ready-contract.ts +8 -3
- package/src/assembly/webgpu-ready.ts +186 -250
- package/src/assembly/webgpu-renderer.ts +30 -45
- package/src/bloom-admission.ts +21 -0
- package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
- package/src/components/atmosphere.ts +4 -0
- package/src/components/camera.ts +66 -10
- package/src/components/depth-of-field.ts +45 -0
- package/src/components/directional-light.ts +2 -1
- package/src/components/index.ts +1 -0
- package/src/components/mesh-filter.ts +11 -9
- package/src/components/motion-blur.ts +1 -0
- package/src/device/gpu-residency.ts +56 -12
- package/src/environment/analytic-fog.ts +44 -0
- package/src/environment/background.ts +2 -2
- package/src/errors/render.ts +14 -1
- package/src/extract/camera.ts +42 -9
- package/src/extract/contracts.ts +7 -0
- package/src/extract/environment.ts +4 -0
- package/src/features/depth-of-field/__tests__/depth-of-field-params.unit.test.ts +943 -0
- package/src/features/depth-of-field/depth-of-field-assembly.ts +100 -0
- package/src/features/depth-of-field/depth-of-field-feature.ts +295 -0
- package/src/features/depth-of-field/depth-of-field-inspection.ts +237 -0
- package/src/features/depth-of-field/depth-of-field-params.ts +556 -0
- package/src/features/error-reporting.ts +78 -0
- package/src/features/host.ts +26 -13
- package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
- package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
- package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
- package/src/features/motion-blur/motion-blur-params.ts +70 -2
- package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
- package/src/features/plan.ts +25 -8
- package/src/features/prepared-gpu-work.ts +86 -35
- package/src/features/render-graph-compute.ts +13 -5
- package/src/features/render-graph-contribution.ts +2 -0
- package/src/features/types.ts +18 -1
- package/src/gpu-driven/material-artifacts.ts +2 -7
- package/src/gpu-driven/prepared-draw.ts +15 -10
- package/src/gpu-driven/production-raster.ts +83 -7
- package/src/gpu-driven/view-gpu.ts +17 -0
- package/src/gpu-scene.ts +188 -21
- package/src/index.ts +64 -0
- package/src/inspection-types.ts +98 -2
- package/src/instances.ts +93 -0
- package/src/mesh-material-bindings.ts +4 -0
- package/src/pipeline/standard-output/graph.ts +3 -0
- package/src/pipeline/standard-output/types.ts +4 -1
- package/src/pipeline/standard-post.ts +324 -76
- package/src/pipeline-spec.ts +5 -5
- package/src/plugin.ts +2 -0
- package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
- package/src/record/__tests__/temporal-payload.unit.test.ts +2 -1
- package/src/record/frame-snapshot.ts +31 -2
- package/src/record/frame-targets.ts +10 -6
- package/src/record/frame.ts +202 -28
- package/src/record/main-pass-geometry.ts +30 -1
- package/src/record/main-pass-sprite-draws.ts +6 -1
- package/src/record/main-pass.ts +1 -5
- package/src/record/mesh-ssbo.ts +43 -6
- package/src/record/render-context.ts +12 -28
- package/src/record/shadow-pass.ts +6 -1
- package/src/record/skybox-post-pass.ts +122 -196
- package/src/record/typed-frame-graph.ts +259 -15
- package/src/recovery/render-system-candidate.ts +28 -10
- package/src/render-contract.ts +34 -4
- package/src/render-graph-primitives.ts +8 -1
- package/src/render-pipeline.ts +22 -1
- package/src/render-system-extract-tail.ts +103 -13
- package/src/render-system-extract.ts +148 -38
- package/src/render-system.ts +332 -119
- package/src/scene/render-scene.ts +85 -4
- package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
- package/src/temporal/frame-coordinator.ts +8 -1
- package/src/temporal/frame.ts +20 -0
- package/src/temporal/view.ts +13 -2
- package/src/typed-render-graph-primitives.ts +124 -48
- package/src/volume/component.ts +0 -1
- package/src/volume/passes.ts +8 -4
- package/src/volume/resources.ts +1 -1
- package/src/volume/temporal.ts +11 -0
- package/dist/chunk-C3DDSXYK.mjs.map +0 -1
- package/dist/chunk-FUUO4UND.mjs.map +0 -1
- package/dist/chunk-JRBDO2KB.mjs.map +0 -1
- package/dist/chunk-MDQJPY3T.mjs.map +0 -1
- package/dist/chunk-PSEQ6QPH.mjs.map +0 -1
- package/dist/chunk-S5UE2T5P.mjs.map +0 -1
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
import { err, ok, type Result } from '@forgeax/engine-types';
|
|
2
|
+
import {
|
|
3
|
+
type DepthOfFieldData,
|
|
4
|
+
type DepthOfFieldQuality,
|
|
5
|
+
DepthOfFieldQualityValue,
|
|
6
|
+
type DepthOfFieldSide,
|
|
7
|
+
DepthOfFieldSideValue,
|
|
8
|
+
depthOfFieldQualityFromF32,
|
|
9
|
+
depthOfFieldSideFromF32,
|
|
10
|
+
} from '../../components/depth-of-field';
|
|
11
|
+
|
|
12
|
+
export const DEPTH_OF_FIELD_PARAMS_BYTE_SIZE = 64;
|
|
13
|
+
|
|
14
|
+
// Camera component values cross the UBO boundary as f32. Keep bounds in that
|
|
15
|
+
// same representation so the authored decimal boundary 0.7 remains valid
|
|
16
|
+
// after quantization.
|
|
17
|
+
const MIN_F_STOP = Math.fround(0.7);
|
|
18
|
+
const MAX_F_STOP = Math.fround(32);
|
|
19
|
+
const MAX_LINEAR_DEPTH_RAW = Math.fround(1 - 2 ** -24);
|
|
20
|
+
|
|
21
|
+
export interface DepthOfFieldParams {
|
|
22
|
+
readonly focusDistance: number;
|
|
23
|
+
readonly fStop: number;
|
|
24
|
+
readonly sensorHeight: number;
|
|
25
|
+
readonly maxRadiusPixels: number;
|
|
26
|
+
readonly quality: DepthOfFieldQuality;
|
|
27
|
+
readonly blurSide: DepthOfFieldSide;
|
|
28
|
+
readonly focalLength: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_DEPTH_OF_FIELD_PARAMS: DepthOfFieldParams = Object.freeze({
|
|
32
|
+
focusDistance: 8,
|
|
33
|
+
fStop: 2.8,
|
|
34
|
+
sensorHeight: 0.024,
|
|
35
|
+
maxRadiusPixels: 16,
|
|
36
|
+
quality: 'medium',
|
|
37
|
+
blurSide: 'both',
|
|
38
|
+
focalLength: 0,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function quantizeF32(value: number): number {
|
|
42
|
+
return Math.fround(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type DepthOfFieldErrorCode =
|
|
46
|
+
| 'depth-of-field-invalid-params'
|
|
47
|
+
| 'depth-of-field-orthographic-unsupported';
|
|
48
|
+
|
|
49
|
+
export interface DepthOfFieldInvalidParamsDetail {
|
|
50
|
+
readonly field: string;
|
|
51
|
+
readonly value: unknown;
|
|
52
|
+
readonly expected: string;
|
|
53
|
+
readonly bound?: readonly [number, number] | undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface DepthOfFieldOrthographicDetail {
|
|
57
|
+
readonly projection: 'orthographic';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type DepthOfFieldErrorDetail =
|
|
61
|
+
| DepthOfFieldInvalidParamsDetail
|
|
62
|
+
| DepthOfFieldOrthographicDetail;
|
|
63
|
+
|
|
64
|
+
/** Detached validation facts carried from extract to renderer inspection. */
|
|
65
|
+
export interface DepthOfFieldRequestFailure {
|
|
66
|
+
readonly code: DepthOfFieldErrorCode;
|
|
67
|
+
readonly detail: DepthOfFieldErrorDetail;
|
|
68
|
+
readonly expected: string;
|
|
69
|
+
readonly hint: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class DepthOfFieldValidationError extends Error {
|
|
73
|
+
readonly code: DepthOfFieldErrorCode;
|
|
74
|
+
readonly expected: string;
|
|
75
|
+
readonly hint: string;
|
|
76
|
+
readonly detail: DepthOfFieldErrorDetail;
|
|
77
|
+
|
|
78
|
+
constructor(
|
|
79
|
+
code: DepthOfFieldErrorCode,
|
|
80
|
+
detail: DepthOfFieldErrorDetail,
|
|
81
|
+
expected: string,
|
|
82
|
+
hint: string,
|
|
83
|
+
) {
|
|
84
|
+
super(`${code}: ${expected}`);
|
|
85
|
+
this.name = 'DepthOfFieldValidationError';
|
|
86
|
+
this.code = code;
|
|
87
|
+
this.expected = expected;
|
|
88
|
+
this.hint = hint;
|
|
89
|
+
this.detail = detail;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function depthOfFieldRequestFailure(
|
|
94
|
+
error: DepthOfFieldValidationError,
|
|
95
|
+
): DepthOfFieldRequestFailure {
|
|
96
|
+
return Object.freeze({
|
|
97
|
+
code: error.code,
|
|
98
|
+
detail: error.detail,
|
|
99
|
+
expected: error.expected,
|
|
100
|
+
hint: error.hint,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the params used by a frame when authoring validation failed.
|
|
106
|
+
* Invalid/unsupported requests stay visible as errors, but an already
|
|
107
|
+
* accepted DoF graph must keep receiving its last-known-good UBO until the
|
|
108
|
+
* author submits a valid replacement or explicitly removes the component.
|
|
109
|
+
*/
|
|
110
|
+
export function resolveDepthOfFieldFrameParams(
|
|
111
|
+
requested: DepthOfFieldParams | undefined,
|
|
112
|
+
requestFailure: DepthOfFieldRequestFailure | undefined,
|
|
113
|
+
accepted: DepthOfFieldParams | undefined,
|
|
114
|
+
): DepthOfFieldParams | undefined {
|
|
115
|
+
if (requested !== undefined) return requested;
|
|
116
|
+
if (
|
|
117
|
+
requestFailure === undefined ||
|
|
118
|
+
requestFailure.code === 'depth-of-field-orthographic-unsupported'
|
|
119
|
+
) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
return accepted;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface DepthOfFieldCameraInput {
|
|
126
|
+
readonly projection: 'perspective' | 'orthographic';
|
|
127
|
+
readonly fov: number;
|
|
128
|
+
readonly near: number;
|
|
129
|
+
readonly far: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function invalid(
|
|
133
|
+
field: string,
|
|
134
|
+
value: unknown,
|
|
135
|
+
expected: string,
|
|
136
|
+
bound?: readonly [number, number],
|
|
137
|
+
): DepthOfFieldValidationError {
|
|
138
|
+
return new DepthOfFieldValidationError(
|
|
139
|
+
'depth-of-field-invalid-params',
|
|
140
|
+
{ field, value, expected, ...(bound === undefined ? {} : { bound }) },
|
|
141
|
+
expected,
|
|
142
|
+
`set ${field} to ${expected}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function focalLength(sensorHeight: number, fov: number): number {
|
|
147
|
+
return sensorHeight / (2 * Math.tan(fov / 2));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function opticalCocScale(
|
|
151
|
+
params: Pick<DepthOfFieldParams, 'focalLength' | 'sensorHeight' | 'fStop' | 'focusDistance'>,
|
|
152
|
+
): number {
|
|
153
|
+
// This is evaluated on the CPU once per frame. Keep the ratios separate so
|
|
154
|
+
// the final f32 coefficient does not inherit an avoidable focalLength^2
|
|
155
|
+
// overflow.
|
|
156
|
+
return (
|
|
157
|
+
(params.focalLength / params.sensorHeight / (2 * params.fStop)) *
|
|
158
|
+
(params.focalLength / (params.focusDistance - params.focalLength))
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function frameCocCoefficient(
|
|
163
|
+
params: Pick<DepthOfFieldParams, 'focalLength' | 'sensorHeight' | 'fStop' | 'focusDistance'>,
|
|
164
|
+
outputHeight: number,
|
|
165
|
+
): number {
|
|
166
|
+
return quantizeF32(quantizeF32(outputHeight) * opticalCocScale(params));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
interface DepthOfFieldFrameValues {
|
|
170
|
+
readonly focusDistance: number;
|
|
171
|
+
readonly fStop: number;
|
|
172
|
+
readonly sensorHeight: number;
|
|
173
|
+
readonly focalLength: number;
|
|
174
|
+
readonly maxRadiusPixels: number;
|
|
175
|
+
readonly outputHeight: number;
|
|
176
|
+
readonly near: number;
|
|
177
|
+
readonly far: number;
|
|
178
|
+
readonly cocCoefficient: number;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function resolveDepthOfFieldFrameValues(
|
|
182
|
+
params: DepthOfFieldParams,
|
|
183
|
+
input: {
|
|
184
|
+
readonly outputHeight: number;
|
|
185
|
+
readonly near: number;
|
|
186
|
+
readonly far: number;
|
|
187
|
+
readonly useTemporalDepth: boolean;
|
|
188
|
+
},
|
|
189
|
+
): Result<DepthOfFieldFrameValues, DepthOfFieldValidationError> {
|
|
190
|
+
const focusDistance = quantizeF32(params.focusDistance);
|
|
191
|
+
const fStop = quantizeF32(params.fStop);
|
|
192
|
+
const sensorHeight = quantizeF32(params.sensorHeight);
|
|
193
|
+
const focal = quantizeF32(params.focalLength);
|
|
194
|
+
const maxRadiusPixels = quantizeF32(params.maxRadiusPixels);
|
|
195
|
+
const outputHeight = quantizeF32(input.outputHeight);
|
|
196
|
+
const near = quantizeF32(input.near);
|
|
197
|
+
const far = quantizeF32(input.far);
|
|
198
|
+
const finitePositiveFields: readonly [string, number, number][] = [
|
|
199
|
+
['focusDistance', focusDistance, params.focusDistance],
|
|
200
|
+
['sensorHeight', sensorHeight, params.sensorHeight],
|
|
201
|
+
['focalLength', focal, params.focalLength],
|
|
202
|
+
['near', near, input.near],
|
|
203
|
+
['far', far, input.far],
|
|
204
|
+
];
|
|
205
|
+
for (const [field, value, raw] of finitePositiveFields) {
|
|
206
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
207
|
+
return err(invalid(field, raw, 'a finite positive f32 value'));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (!Number.isFinite(fStop) || fStop < MIN_F_STOP || fStop > MAX_F_STOP) {
|
|
211
|
+
return err(
|
|
212
|
+
invalid('fStop', params.fStop, 'a finite f32 value in [0.7, 32]', [MIN_F_STOP, MAX_F_STOP]),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (!Number.isFinite(maxRadiusPixels) || maxRadiusPixels < 0 || maxRadiusPixels > 32) {
|
|
216
|
+
return err(
|
|
217
|
+
invalid('maxRadiusPixels', params.maxRadiusPixels, 'a finite f32 value in [0, 32]', [0, 32]),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (!Number.isFinite(outputHeight) || outputHeight <= 0) {
|
|
221
|
+
return err(invalid('outputHeight', input.outputHeight, 'a finite positive f32 value'));
|
|
222
|
+
}
|
|
223
|
+
if (far <= near) {
|
|
224
|
+
return err(invalid('near/far', [input.near, input.far], 'finite f32 near > 0 and far > near'));
|
|
225
|
+
}
|
|
226
|
+
if (focusDistance <= focal) {
|
|
227
|
+
return err(invalid('focusDistance', params.focusDistance, 'greater than focalLength in f32'));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// linearDepth() uses this ratio and denominator order in both WGSL
|
|
231
|
+
// variants. Reject a ratio that disappears at f32 precision, then check the
|
|
232
|
+
// near, mid and largest-valid raw-depth samples for finite denominators and
|
|
233
|
+
// reconstructed distances. The denominator is strictly positive for valid
|
|
234
|
+
// raw depth, so no world-unit epsilon belongs in the shader.
|
|
235
|
+
const depthRatio = quantizeF32(near / far);
|
|
236
|
+
if (!Number.isFinite(depthRatio) || depthRatio <= 0) {
|
|
237
|
+
return err(invalid('near/far ratio', depthRatio, 'a finite positive f32 value'));
|
|
238
|
+
}
|
|
239
|
+
for (const raw of [quantizeF32(2 ** -149), 0.5, MAX_LINEAR_DEPTH_RAW]) {
|
|
240
|
+
const oneMinusRaw = quantizeF32(1 - raw);
|
|
241
|
+
const weightedRatio = quantizeF32(raw * depthRatio);
|
|
242
|
+
const denominator = quantizeF32(oneMinusRaw + weightedRatio);
|
|
243
|
+
const distance = quantizeF32(near / denominator);
|
|
244
|
+
if (
|
|
245
|
+
!Number.isFinite(oneMinusRaw) ||
|
|
246
|
+
oneMinusRaw <= 0 ||
|
|
247
|
+
!Number.isFinite(weightedRatio) ||
|
|
248
|
+
!Number.isFinite(denominator) ||
|
|
249
|
+
denominator <= 0 ||
|
|
250
|
+
!Number.isFinite(distance) ||
|
|
251
|
+
distance <= 0
|
|
252
|
+
) {
|
|
253
|
+
return err(invalid('linearDepth', distance, 'finite positive f32 reconstruction'));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const cocCoefficient = frameCocCoefficient(
|
|
258
|
+
{
|
|
259
|
+
focusDistance,
|
|
260
|
+
fStop,
|
|
261
|
+
sensorHeight,
|
|
262
|
+
focalLength: focal,
|
|
263
|
+
},
|
|
264
|
+
outputHeight,
|
|
265
|
+
);
|
|
266
|
+
if (!Number.isFinite(cocCoefficient) || cocCoefficient <= 0) {
|
|
267
|
+
return err(
|
|
268
|
+
invalid('cocCoefficient', cocCoefficient, 'a finite positive value representable as f32'),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
// The shader evaluates C * (1 - focus / depth). Check each f32 operation at
|
|
272
|
+
// the projection endpoints before the UBO is written.
|
|
273
|
+
for (const [field, depth] of [
|
|
274
|
+
['near', near],
|
|
275
|
+
['far', far],
|
|
276
|
+
] as const) {
|
|
277
|
+
const focusOverDepth = quantizeF32(focusDistance / depth);
|
|
278
|
+
const depthFactor = quantizeF32(1 - focusOverDepth);
|
|
279
|
+
const radius = quantizeF32(cocCoefficient * depthFactor);
|
|
280
|
+
if (
|
|
281
|
+
!Number.isFinite(focusOverDepth) ||
|
|
282
|
+
!Number.isFinite(depthFactor) ||
|
|
283
|
+
!Number.isFinite(radius)
|
|
284
|
+
) {
|
|
285
|
+
return err(invalid(`coc.${field}`, radius, 'a finite value representable as f32'));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return ok({
|
|
290
|
+
focusDistance,
|
|
291
|
+
fStop,
|
|
292
|
+
sensorHeight,
|
|
293
|
+
focalLength: focal,
|
|
294
|
+
maxRadiusPixels,
|
|
295
|
+
outputHeight,
|
|
296
|
+
near,
|
|
297
|
+
far,
|
|
298
|
+
cocCoefficient,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Validate the values that cross the f32 camera UBO boundary for one frame.
|
|
304
|
+
* Camera and DoF schemas already use f32 columns, but direct callers and
|
|
305
|
+
* synthetic frame tests can still supply wider JS numbers. Quantize first so
|
|
306
|
+
* admission and the WGSL consumer make the same decision.
|
|
307
|
+
*/
|
|
308
|
+
export function validateDepthOfFieldFrameParams(
|
|
309
|
+
params: DepthOfFieldParams,
|
|
310
|
+
input: {
|
|
311
|
+
readonly outputHeight: number;
|
|
312
|
+
readonly near: number;
|
|
313
|
+
readonly far: number;
|
|
314
|
+
readonly useTemporalDepth: boolean;
|
|
315
|
+
},
|
|
316
|
+
): Result<void, DepthOfFieldValidationError> {
|
|
317
|
+
const resolved = resolveDepthOfFieldFrameValues(params, input);
|
|
318
|
+
return resolved.ok ? ok(undefined) : resolved;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Validate and derive one camera DoF declaration at the frame admission seam.
|
|
323
|
+
* The returned focal length is a derived fact; callers never author it.
|
|
324
|
+
*/
|
|
325
|
+
export function validateDepthOfFieldParams(
|
|
326
|
+
input: Partial<DepthOfFieldData> | undefined,
|
|
327
|
+
camera?: DepthOfFieldCameraInput,
|
|
328
|
+
): Result<DepthOfFieldParams, DepthOfFieldValidationError> {
|
|
329
|
+
if (camera?.projection === 'orthographic') {
|
|
330
|
+
return err(
|
|
331
|
+
new DepthOfFieldValidationError(
|
|
332
|
+
'depth-of-field-orthographic-unsupported',
|
|
333
|
+
{ projection: 'orthographic' },
|
|
334
|
+
'DepthOfField requires a perspective Camera',
|
|
335
|
+
'use a perspective Camera or remove the DepthOfField component',
|
|
336
|
+
),
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const value = input ?? {};
|
|
340
|
+
const focusDistance = quantizeF32(
|
|
341
|
+
value.focusDistance ?? DEFAULT_DEPTH_OF_FIELD_PARAMS.focusDistance,
|
|
342
|
+
);
|
|
343
|
+
const fStop = quantizeF32(value.fStop ?? DEFAULT_DEPTH_OF_FIELD_PARAMS.fStop);
|
|
344
|
+
const sensorHeight = quantizeF32(
|
|
345
|
+
value.sensorHeight ?? DEFAULT_DEPTH_OF_FIELD_PARAMS.sensorHeight,
|
|
346
|
+
);
|
|
347
|
+
const maxRadiusPixels = quantizeF32(
|
|
348
|
+
value.maxRadiusPixels ?? DEFAULT_DEPTH_OF_FIELD_PARAMS.maxRadiusPixels,
|
|
349
|
+
);
|
|
350
|
+
const qualityValue = quantizeF32(value.quality ?? DepthOfFieldQualityValue.medium);
|
|
351
|
+
const blurSideValue = quantizeF32(value.blurSide ?? DepthOfFieldSideValue.both);
|
|
352
|
+
const quality = depthOfFieldQualityFromF32(qualityValue);
|
|
353
|
+
const blurSide = depthOfFieldSideFromF32(blurSideValue);
|
|
354
|
+
if (!Number.isFinite(focusDistance) || focusDistance <= 0) {
|
|
355
|
+
return err(
|
|
356
|
+
invalid('focusDistance', value.focusDistance ?? focusDistance, 'a finite positive f32 value'),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (!Number.isFinite(fStop) || fStop < MIN_F_STOP || fStop > MAX_F_STOP) {
|
|
360
|
+
return err(
|
|
361
|
+
invalid('fStop', value.fStop ?? fStop, 'a finite f32 value in [0.7, 32]', [
|
|
362
|
+
MIN_F_STOP,
|
|
363
|
+
MAX_F_STOP,
|
|
364
|
+
]),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
if (!Number.isFinite(sensorHeight) || sensorHeight <= 0) {
|
|
368
|
+
return err(
|
|
369
|
+
invalid('sensorHeight', value.sensorHeight ?? sensorHeight, 'a finite positive f32 value'),
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (!Number.isFinite(maxRadiusPixels) || maxRadiusPixels < 0 || maxRadiusPixels > 32) {
|
|
373
|
+
return err(
|
|
374
|
+
invalid(
|
|
375
|
+
'maxRadiusPixels',
|
|
376
|
+
value.maxRadiusPixels ?? maxRadiusPixels,
|
|
377
|
+
'a finite f32 value in [0, 32]',
|
|
378
|
+
[0, 32],
|
|
379
|
+
),
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (quality === undefined) {
|
|
383
|
+
return err(invalid('quality', qualityValue, 'DepthOfFieldQualityValue.low, medium, or high'));
|
|
384
|
+
}
|
|
385
|
+
if (blurSide === undefined) {
|
|
386
|
+
return err(invalid('blurSide', blurSideValue, 'DepthOfFieldSideValue.both, near, or far'));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let focal = DEFAULT_DEPTH_OF_FIELD_PARAMS.focalLength;
|
|
390
|
+
if (camera !== undefined) {
|
|
391
|
+
const fov = quantizeF32(camera.fov);
|
|
392
|
+
const near = quantizeF32(camera.near);
|
|
393
|
+
const far = quantizeF32(camera.far);
|
|
394
|
+
if (!Number.isFinite(fov) || fov <= 0 || fov >= Math.PI) {
|
|
395
|
+
return err(invalid('fov', camera.fov, 'a finite f32 value in (0, PI)'));
|
|
396
|
+
}
|
|
397
|
+
if (!Number.isFinite(near) || !Number.isFinite(far) || near <= 0 || far <= near) {
|
|
398
|
+
return err(
|
|
399
|
+
invalid('near/far', [camera.near, camera.far], 'finite f32 near > 0 and far > near'),
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
focal = quantizeF32(focalLength(sensorHeight, fov));
|
|
403
|
+
if (!Number.isFinite(focal) || focal <= 0) {
|
|
404
|
+
return err(invalid('focalLength', focal, 'a finite positive f32 value'));
|
|
405
|
+
}
|
|
406
|
+
if (focusDistance < near || focusDistance > far) {
|
|
407
|
+
return err(
|
|
408
|
+
invalid('focusDistance', focusDistance, `a value in [${near}, ${far}]`, [near, far]),
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
if (focusDistance <= focal) {
|
|
412
|
+
return err(
|
|
413
|
+
invalid('focusDistance', focusDistance, `greater than focalLength in f32 (${focal})`),
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return ok(
|
|
418
|
+
Object.freeze({
|
|
419
|
+
focusDistance,
|
|
420
|
+
fStop,
|
|
421
|
+
sensorHeight,
|
|
422
|
+
maxRadiusPixels,
|
|
423
|
+
quality,
|
|
424
|
+
blurSide,
|
|
425
|
+
focalLength: focal,
|
|
426
|
+
}),
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function resolveDepthOfFieldParams(
|
|
431
|
+
input: Partial<DepthOfFieldData> | undefined,
|
|
432
|
+
camera?: DepthOfFieldCameraInput,
|
|
433
|
+
): Result<DepthOfFieldParams | undefined, DepthOfFieldValidationError> {
|
|
434
|
+
if (input === undefined) return ok(undefined);
|
|
435
|
+
return validateDepthOfFieldParams(input, camera);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function depthOfFieldTapCount(quality: DepthOfFieldQuality): 16 | 32 | 64 {
|
|
439
|
+
switch (quality) {
|
|
440
|
+
case 'low':
|
|
441
|
+
return 16;
|
|
442
|
+
case 'medium':
|
|
443
|
+
return 32;
|
|
444
|
+
case 'high':
|
|
445
|
+
return 64;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function depthOfFieldSideCode(side: DepthOfFieldSide): 0 | 1 | 2 {
|
|
450
|
+
switch (side) {
|
|
451
|
+
case 'both':
|
|
452
|
+
return DepthOfFieldSideValue.both;
|
|
453
|
+
case 'near':
|
|
454
|
+
return DepthOfFieldSideValue.near;
|
|
455
|
+
case 'far':
|
|
456
|
+
return DepthOfFieldSideValue.far;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function depthOfFieldQualityCode(quality: DepthOfFieldQuality): 0 | 1 | 2 {
|
|
461
|
+
switch (quality) {
|
|
462
|
+
case 'low':
|
|
463
|
+
return DepthOfFieldQualityValue.low;
|
|
464
|
+
case 'medium':
|
|
465
|
+
return DepthOfFieldQualityValue.medium;
|
|
466
|
+
case 'high':
|
|
467
|
+
return DepthOfFieldQualityValue.high;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** Signed thin-lens circle-of-confusion radius in final output pixels. */
|
|
472
|
+
export function signedDepthOfFieldCoC(
|
|
473
|
+
params: Pick<DepthOfFieldParams, 'focalLength' | 'sensorHeight' | 'fStop' | 'focusDistance'> &
|
|
474
|
+
Partial<Pick<DepthOfFieldParams, 'maxRadiusPixels'>>,
|
|
475
|
+
viewDepth: number,
|
|
476
|
+
outputHeight: number,
|
|
477
|
+
): number {
|
|
478
|
+
if (
|
|
479
|
+
!Number.isFinite(viewDepth) ||
|
|
480
|
+
viewDepth <= 0 ||
|
|
481
|
+
!Number.isFinite(outputHeight) ||
|
|
482
|
+
outputHeight <= 0 ||
|
|
483
|
+
!Number.isFinite(params.focalLength) ||
|
|
484
|
+
params.focalLength <= 0 ||
|
|
485
|
+
!Number.isFinite(params.sensorHeight) ||
|
|
486
|
+
params.sensorHeight <= 0 ||
|
|
487
|
+
!Number.isFinite(params.fStop) ||
|
|
488
|
+
params.fStop <= 0 ||
|
|
489
|
+
!Number.isFinite(params.focusDistance) ||
|
|
490
|
+
params.focusDistance <= params.focalLength
|
|
491
|
+
) {
|
|
492
|
+
return 0;
|
|
493
|
+
}
|
|
494
|
+
const focusDistance = quantizeF32(params.focusDistance);
|
|
495
|
+
const depth = quantizeF32(viewDepth);
|
|
496
|
+
const focalLength = quantizeF32(params.focalLength);
|
|
497
|
+
const sensorHeight = quantizeF32(params.sensorHeight);
|
|
498
|
+
const fStop = quantizeF32(params.fStop);
|
|
499
|
+
const coefficient = frameCocCoefficient(
|
|
500
|
+
{ focusDistance, focalLength, sensorHeight, fStop },
|
|
501
|
+
outputHeight,
|
|
502
|
+
);
|
|
503
|
+
const depthFactor = quantizeF32(1 - quantizeF32(focusDistance / depth));
|
|
504
|
+
const radius = quantizeF32(coefficient * depthFactor);
|
|
505
|
+
const limit = Number.isFinite(params.maxRadiusPixels)
|
|
506
|
+
? quantizeF32(params.maxRadiusPixels as number)
|
|
507
|
+
: Number.POSITIVE_INFINITY;
|
|
508
|
+
return Math.max(-limit, Math.min(limit, Number.isFinite(radius) ? radius : 0));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** Pack the one renderer-owned per-frame DoF UBO (4 x vec4, 64 bytes). */
|
|
512
|
+
export function packDepthOfFieldParams(
|
|
513
|
+
params: DepthOfFieldParams,
|
|
514
|
+
input: {
|
|
515
|
+
readonly outputHeight: number;
|
|
516
|
+
readonly near: number;
|
|
517
|
+
readonly far: number;
|
|
518
|
+
readonly useTemporalDepth: boolean;
|
|
519
|
+
},
|
|
520
|
+
): Uint8Array {
|
|
521
|
+
const frameValues = resolveDepthOfFieldFrameValues(params, input);
|
|
522
|
+
if (!frameValues.ok) throw frameValues.error;
|
|
523
|
+
const {
|
|
524
|
+
focusDistance,
|
|
525
|
+
fStop,
|
|
526
|
+
sensorHeight,
|
|
527
|
+
focalLength: focal,
|
|
528
|
+
outputHeight,
|
|
529
|
+
maxRadiusPixels,
|
|
530
|
+
near,
|
|
531
|
+
far,
|
|
532
|
+
cocCoefficient,
|
|
533
|
+
} = frameValues.value;
|
|
534
|
+
const payload = new Float32Array(DEPTH_OF_FIELD_PARAMS_BYTE_SIZE / 4);
|
|
535
|
+
// row3.x is the frame CoC coefficient; the remaining reserved lanes stay
|
|
536
|
+
// zero so the existing 64-byte binding contract remains unchanged.
|
|
537
|
+
payload.set([
|
|
538
|
+
focusDistance,
|
|
539
|
+
fStop,
|
|
540
|
+
sensorHeight,
|
|
541
|
+
focal,
|
|
542
|
+
outputHeight,
|
|
543
|
+
maxRadiusPixels,
|
|
544
|
+
depthOfFieldSideCode(params.blurSide),
|
|
545
|
+
depthOfFieldQualityCode(params.quality),
|
|
546
|
+
near,
|
|
547
|
+
far,
|
|
548
|
+
input.useTemporalDepth ? 1 : 0,
|
|
549
|
+
0, // row2.w reserved
|
|
550
|
+
cocCoefficient, // row3.x / WGSL reserved.x
|
|
551
|
+
0,
|
|
552
|
+
0,
|
|
553
|
+
0,
|
|
554
|
+
]);
|
|
555
|
+
return new Uint8Array(payload.buffer);
|
|
556
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { RenderError } from '../errors/render';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recoverable feature preparation failures are still useful diagnostics, but
|
|
5
|
+
* they are not new failures on every render tick. Keep the reporting policy
|
|
6
|
+
* independent from the frame orchestration so it can be exercised without a
|
|
7
|
+
* renderer or device.
|
|
8
|
+
*/
|
|
9
|
+
export const PENDING_RENDER_FEATURE_ERROR_REPORT_INTERVAL_FRAMES = 60;
|
|
10
|
+
|
|
11
|
+
export function shouldReportPendingRenderFeatureError(
|
|
12
|
+
lastReportedFrame: Map<string, number>,
|
|
13
|
+
key: string,
|
|
14
|
+
frameNumber: number,
|
|
15
|
+
intervalFrames = PENDING_RENDER_FEATURE_ERROR_REPORT_INTERVAL_FRAMES,
|
|
16
|
+
): boolean {
|
|
17
|
+
const lastReported = lastReportedFrame.get(key);
|
|
18
|
+
if (lastReported !== undefined && frameNumber - lastReported < intervalFrames) return false;
|
|
19
|
+
lastReportedFrame.set(key, frameNumber);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function reportPendingRenderFeatureErrors(
|
|
24
|
+
errors: readonly RenderError[],
|
|
25
|
+
frameNumber: number,
|
|
26
|
+
lastReportedFrame: Map<string, number>,
|
|
27
|
+
errorRegistry: { fire(error: RenderError): void },
|
|
28
|
+
): void {
|
|
29
|
+
for (const error of errors) {
|
|
30
|
+
if (!isPendingRenderFeaturePreparation(error)) {
|
|
31
|
+
errorRegistry.fire(error);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
// A missing prepared resource during its normal asynchronous warm-up is
|
|
35
|
+
// an expected candidate state. Keep that path silent; other next-frame
|
|
36
|
+
// failures still receive first/periodic reporting.
|
|
37
|
+
if (isSilentRenderFeatureWarmup(error)) continue;
|
|
38
|
+
const key = pendingRenderFeatureErrorKey(error);
|
|
39
|
+
if (shouldReportPendingRenderFeatureError(lastReportedFrame, key, frameNumber)) {
|
|
40
|
+
errorRegistry.fire(error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isPendingRenderFeaturePreparation(error: RenderError): boolean {
|
|
46
|
+
if (!('detail' in error)) return false;
|
|
47
|
+
switch (error.code) {
|
|
48
|
+
case 'render-feature-stage-failed':
|
|
49
|
+
case 'render-feature-preparation-failed':
|
|
50
|
+
case 'render-feature-prepared-state-mismatch':
|
|
51
|
+
case 'render-feature-draw-recording-failed':
|
|
52
|
+
return error.detail.recovery === 'next-frame';
|
|
53
|
+
default:
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isSilentRenderFeatureWarmup(error: RenderError): boolean {
|
|
59
|
+
return (
|
|
60
|
+
error.code === 'render-feature-preparation-failed' &&
|
|
61
|
+
error.detail.reason.startsWith('rhi-not-available:')
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function pendingRenderFeatureErrorKey(error: RenderError): string {
|
|
66
|
+
const detail = 'detail' in error ? error.detail : undefined;
|
|
67
|
+
const featureIdentity =
|
|
68
|
+
detail !== undefined &&
|
|
69
|
+
'featureIdentity' in detail &&
|
|
70
|
+
typeof detail.featureIdentity === 'string'
|
|
71
|
+
? detail.featureIdentity
|
|
72
|
+
: '';
|
|
73
|
+
const order =
|
|
74
|
+
detail !== undefined && 'order' in detail && typeof detail.order === 'number'
|
|
75
|
+
? detail.order
|
|
76
|
+
: -1;
|
|
77
|
+
return `${error.code}\0${featureIdentity}\0${order}\0${error.expected}\0${error.hint}`;
|
|
78
|
+
}
|