@photivra/engine 2.19.0 → 2.21.0
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/CHANGELOG.md +289 -1
- package/CITATION.cff +1 -1
- package/NODE_SUPPORT.md +7 -5
- package/README.md +7 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +21 -10
- package/dist/capture/dng-lens-correction-opcodes.d.ts +32 -0
- package/dist/capture/dng-lens-correction-opcodes.js +119 -0
- package/dist/capture/dng-lens-correction-opcodes.js.map +1 -0
- package/dist/capture/exposure-build-up-stage.d.ts +16 -0
- package/dist/capture/exposure-build-up-stage.js +4 -0
- package/dist/capture/exposure-build-up-stage.js.map +1 -0
- package/dist/capture/native-environment-lean.d.ts +2 -1
- package/dist/capture/native-environment-lean.js +42 -78
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-tabulated.js +4 -3
- package/dist/capture/native-environment-tabulated.js.map +1 -1
- package/dist/capture/native-raw-dng.d.ts +6 -0
- package/dist/capture/native-raw-dng.js +16 -2
- package/dist/capture/native-raw-dng.js.map +1 -1
- package/dist/capture/native-raw.d.ts +3 -2
- package/dist/capture/native-raw.js +5 -4
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.d.ts +4 -1
- package/dist/capture/sensor-raw-producer.js +15 -8
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/core/shared-helpers.d.ts +2 -0
- package/dist/core/shared-helpers.js +4 -0
- package/dist/core/shared-helpers.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/generic-lens-catalogue.d.ts +11 -4
- package/dist/equipment/generic-lens-catalogue.js +49 -7
- package/dist/equipment/generic-lens-catalogue.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +32 -0
- package/dist/equipment/generic-tier-assets.js +18 -14
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/generic-tier-presets.js +2 -3
- package/dist/equipment/generic-tier-presets.js.map +1 -1
- package/dist/equipment/sensor-profile-body.d.ts +78 -0
- package/dist/equipment/sensor-profile-body.js +77 -0
- package/dist/equipment/sensor-profile-body.js.map +1 -0
- package/dist/equipment/sensor-profiles.d.ts +8 -0
- package/dist/equipment/sensor-profiles.js +82 -0
- package/dist/equipment/sensor-profiles.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +2 -2
- package/dist/facade/catalogue-lens-signal.js +58 -71
- package/dist/facade/catalogue-lens-signal.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +2 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/defocus-image-space-provider.d.ts +7 -0
- package/dist/facade/defocus-image-space-provider.js +64 -76
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +2 -0
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +13 -5
- package/dist/facade/display-view-gpu.js +4 -4
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.d.ts +1 -1
- package/dist/facade/display-view-lens-signal.js +30 -26
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/display-view.d.ts +3 -1
- package/dist/facade/display-view.js +6 -24
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +3 -1
- package/dist/facade/expected-signal-setup.js +1 -1
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +47 -14
- package/dist/facade/expected-signal.js +117 -112
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/exposure-build-up.d.ts +70 -0
- package/dist/facade/exposure-build-up.js +164 -0
- package/dist/facade/exposure-build-up.js.map +1 -0
- package/dist/facade/fast-methods.d.ts +50 -0
- package/dist/facade/fast-methods.js +58 -0
- package/dist/facade/fast-methods.js.map +1 -0
- package/dist/facade/fast-resolution.d.ts +12 -0
- package/dist/facade/fast-resolution.js +18 -0
- package/dist/facade/fast-resolution.js.map +1 -0
- package/dist/facade/generic-lens-capture.d.ts +5 -4
- package/dist/facade/generic-lens-capture.js +5 -4
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/image-plane-enable.d.ts +7 -0
- package/dist/facade/image-plane-enable.js +17 -0
- package/dist/facade/image-plane-enable.js.map +1 -0
- package/dist/facade/image-plane-lattice-data.d.ts +6 -0
- package/dist/facade/image-plane-lattice-data.js +10 -0
- package/dist/facade/image-plane-lattice-data.js.map +1 -0
- package/dist/facade/image-plane-support.d.ts +33 -0
- package/dist/facade/image-plane-support.js +11 -0
- package/dist/facade/image-plane-support.js.map +1 -0
- package/dist/facade/image-plane.d.ts +36 -0
- package/dist/facade/image-plane.js +353 -0
- package/dist/facade/image-plane.js.map +1 -0
- package/dist/facade/mixed-illumination.d.ts +108 -0
- package/dist/facade/mixed-illumination.js +174 -0
- package/dist/facade/mixed-illumination.js.map +1 -0
- package/dist/facade/representative-light-sources.d.ts +72 -0
- package/dist/facade/representative-light-sources.js +124 -0
- package/dist/facade/representative-light-sources.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +129 -4
- package/dist/facade/scene-radiance.js +48 -13
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +88 -19
- package/dist/facade/simulate-capture.js +374 -145
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +3 -2
- package/dist/facade/simulate-display-view.js +36 -18
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.d.ts +13 -1
- package/dist/gpu/chart-composite-gpu.js +48 -9
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/chart-radiance-gpu.js +4 -30
- package/dist/gpu/chart-radiance-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
- package/dist/gpu/display-view-composite-material-module.js +39 -13
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/display-view-image-material-module.d.ts +86 -0
- package/dist/gpu/display-view-image-material-module.js +339 -0
- package/dist/gpu/display-view-image-material-module.js.map +1 -0
- package/dist/gpu/display-view-image-quality.d.ts +45 -0
- package/dist/gpu/display-view-image-quality.js +380 -0
- package/dist/gpu/display-view-image-quality.js.map +1 -0
- package/dist/gpu/display-view-quality-gpu.d.ts +67 -1
- package/dist/gpu/display-view-quality-gpu.js +136 -205
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +51 -3
- package/dist/gpu/display-view-speed-gpu.js +131 -93
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +29 -15
- package/dist/gpu/wgsl-display-view-quality-kernel.js +58 -36
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +21 -6
- package/dist/gpu/wgsl-display-view-speed-kernel.js +46 -11
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
- package/dist/gpu/wgsl-exact-sum.js +42 -0
- package/dist/gpu/wgsl-exact-sum.js.map +1 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/motion/subject-motion.d.ts +66 -0
- package/dist/motion/subject-motion.js +52 -0
- package/dist/motion/subject-motion.js.map +1 -0
- package/dist/optics/aperture-diameter.d.ts +47 -0
- package/dist/optics/aperture-diameter.js +50 -0
- package/dist/optics/aperture-diameter.js.map +1 -0
- package/dist/optics/detail-limit.d.ts +182 -0
- package/dist/optics/detail-limit.js +336 -0
- package/dist/optics/detail-limit.js.map +1 -0
- package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
- package/dist/optics/generic-lens-lateral-color.js +77 -0
- package/dist/optics/generic-lens-lateral-color.js.map +1 -0
- package/dist/optics/generic-lens-psf.d.ts +18 -2
- package/dist/optics/generic-lens-psf.js +79 -48
- package/dist/optics/generic-lens-psf.js.map +1 -1
- package/dist/optics/lens-psf-profile.d.ts +7 -0
- package/dist/optics/lens-psf-profile.js +2 -1
- package/dist/optics/lens-psf-profile.js.map +1 -1
- package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
- package/dist/optics/scene-to-sensor-irradiance.js +7 -0
- package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
- package/dist/output/crop-to-match-focal-length.d.ts +62 -0
- package/dist/output/crop-to-match-focal-length.js +63 -0
- package/dist/output/crop-to-match-focal-length.js.map +1 -0
- package/dist/output/developed-lens-corrections.d.ts +19 -5
- package/dist/output/developed-lens-corrections.js +30 -11
- package/dist/output/developed-lens-corrections.js.map +1 -1
- package/dist/output/generic-lens-corrections.d.ts +41 -3
- package/dist/output/generic-lens-corrections.js +57 -9
- package/dist/output/generic-lens-corrections.js.map +1 -1
- package/dist/output/lens-corrections.d.ts +12 -2
- package/dist/output/lens-corrections.js +22 -7
- package/dist/output/lens-corrections.js.map +1 -1
- package/dist/output/print-contrast-reference.js +1 -1
- package/dist/output/print-contrast-reference.js.map +1 -1
- package/dist/output/print-plan.d.ts +41 -5
- package/dist/output/print-plan.js +59 -14
- package/dist/output/print-plan.js.map +1 -1
- package/dist/output/print-size-blur-noise.d.ts +110 -0
- package/dist/output/print-size-blur-noise.js +138 -0
- package/dist/output/print-size-blur-noise.js.map +1 -0
- package/dist/sensor/binomial-sample.d.ts +3 -0
- package/dist/sensor/binomial-sample.js +86 -0
- package/dist/sensor/binomial-sample.js.map +1 -0
- package/dist/sensor/eqe-electron-rate.js +1 -7
- package/dist/sensor/eqe-electron-rate.js.map +1 -1
- package/dist/sensor/exposure-window.d.ts +2 -0
- package/dist/sensor/exposure-window.js +2 -2
- package/dist/sensor/exposure-window.js.map +1 -1
- package/dist/sensor/readout-timing.js +2 -23
- package/dist/sensor/readout-timing.js.map +1 -1
- package/dist/sensor/response-application-compatibility.js +1 -6
- package/dist/sensor/response-application-compatibility.js.map +1 -1
- package/dist/sensor/responsivity-photocurrent.js +1 -7
- package/dist/sensor/responsivity-photocurrent.js.map +1 -1
- package/dist/sensor/sensor-characteristics.d.ts +147 -0
- package/dist/sensor/sensor-characteristics.js +126 -0
- package/dist/sensor/sensor-characteristics.js.map +1 -0
- package/dist/sensor/spatial-sample-reduction.js +1 -5
- package/dist/sensor/spatial-sample-reduction.js.map +1 -1
- package/dist/sensor/spectral-quadrature.js +1 -6
- package/dist/sensor/spectral-quadrature.js.map +1 -1
- package/dist/sensor/temporal-photo-signal.d.ts +3 -0
- package/dist/sensor/temporal-photo-signal.js +5 -2
- package/dist/sensor/temporal-photo-signal.js.map +1 -1
- package/dist/stabilization/system.d.ts +9 -0
- package/dist/stabilization/system.js +5 -6
- package/dist/stabilization/system.js.map +1 -1
- package/docs/AGENT_GUIDE.md +5 -0
- package/docs/API_REFERENCE.md +173 -2
- package/docs/QUICK_START.md +213 -6
- package/docs/USAGE.md +157 -3
- package/docs/api/capture-native-raw-dng.md +6 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +17 -7
- package/docs/api/equipment-sensor-profile-body.md +64 -0
- package/docs/api/equipment-sensor-profiles.md +86 -0
- package/docs/api/exports.json +357 -2
- package/docs/api/facade-display-view-gpu.md +5 -2
- package/docs/api/facade-exposure-build-up.md +105 -0
- package/docs/api/facade-fast-methods.md +99 -0
- package/docs/api/facade-image-plane-enable.md +16 -0
- package/docs/api/facade-mixed-illumination.md +67 -0
- package/docs/api/facade-representative-light-sources.md +131 -0
- package/docs/api/facade-scene-radiance.md +54 -3
- package/docs/api/facade-simulate-capture.md +60 -15
- package/docs/api/facade-simulate-display-view.md +3 -2
- package/docs/api/motion-subject-motion.md +23 -0
- package/docs/api/optics-aperture-diameter.md +51 -0
- package/docs/api/optics-detail-limit.md +203 -0
- package/docs/api/optics-generic-lens-lateral-color.md +79 -0
- package/docs/api/output-crop-to-match-focal-length.md +67 -0
- package/docs/api/output-developed-lens-corrections.md +10 -1
- package/docs/api/output-generic-lens-corrections.md +40 -2
- package/docs/api/output-lens-corrections.md +11 -1
- package/docs/api/output-print-plan.md +35 -1
- package/docs/api/output-print-size-blur-noise.md +115 -0
- package/docs/api/sensor-sensor-characteristics.md +105 -0
- package/llms.txt +6 -0
- package/package.json +1 -1
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
* `display-view-composite-material-module.ts` (#491) one for composite charts, the 2.3 elements and `material-layers` scenes.
|
|
14
14
|
*/
|
|
15
15
|
import { type DisplayViewRenderResult, type DisplayViewSetup } from "../facade/display-view.js";
|
|
16
|
-
import type
|
|
16
|
+
import { type DisplayViewExpectedSignalInput } from "../facade/expected-signal.js";
|
|
17
|
+
import { resolvePortableChartScene } from "../facade/chart-scenes.js";
|
|
17
18
|
import { type DisplayViewGpuConformanceCase, type DisplayViewGpuRenderer, type DisplayViewGpuRendererFactory, type DisplayViewGpuRequest } from "../facade/display-view-gpu.js";
|
|
18
19
|
import { type ChartCompositeProbeRecord, type ImageKernelExecution, type ImageKernelRelativeErrorBound } from "./image-kernel-execution.js";
|
|
19
|
-
import { type WebGpuRuntime } from "./webgpu-runtime.js";
|
|
20
|
+
import { GpuResourceScope, type WebGpuRuntime } from "./webgpu-runtime.js";
|
|
20
21
|
type PipelineSource = (label: string, code: string) => Promise<GPUComputePipeline>;
|
|
21
22
|
type YieldControl = ((signal: AbortSignal) => Promise<void>) | undefined;
|
|
22
23
|
/**
|
|
@@ -47,6 +48,18 @@ export interface DisplayViewGpuMaterialModule {
|
|
|
47
48
|
buffer: GPUBuffer;
|
|
48
49
|
data?: undefined;
|
|
49
50
|
})[];
|
|
51
|
+
/**
|
|
52
|
+
* #581: a module that defines `scene_value` itself (the image-plane module) states the roundings its value carries beyond the
|
|
53
|
+
* table lookup's one, added to the mean's and (doubled) the variance's chains; and it gives the data of the kernel's binding 3 in place of the spectral table.
|
|
54
|
+
*/
|
|
55
|
+
readonly extraSiteRoundings?: number;
|
|
56
|
+
readonly siteTable?: Float32Array<ArrayBuffer>;
|
|
57
|
+
/**
|
|
58
|
+
* #581: a module whose data is too large for one binding (a large photo) gives group 1 per band instead of `bindings`: for the
|
|
59
|
+
* native site rows [y0, y1] the band reads, and the most display rows a band may hold at `sitesPerDisplayRow`.
|
|
60
|
+
*/
|
|
61
|
+
band?(scope: GpuResourceScope, layout: GPUBindGroupLayout, y0: number, y1: number): GPUBindGroup;
|
|
62
|
+
bandRows?(sitesPerDisplayRow: number): number;
|
|
50
63
|
}
|
|
51
64
|
/** What a GPU render of a display view needs: the reference's own setup and expected-signal input, plus the material module. */
|
|
52
65
|
export interface DisplayViewSpeedGpuInput {
|
|
@@ -101,7 +114,7 @@ export declare function gaussLegendre16(): {
|
|
|
101
114
|
/** One axis record of the geometry buffer: first real pixel, count, overlaps over the scale, axis sums over the squared scale. */
|
|
102
115
|
export declare function axisRecord(geometry: Float32Array, at: number, start: number, end: number, limit: number, margin: number, scale: number): void;
|
|
103
116
|
/** The a-priori relative bounds of the expected value and the variance (docs/WEBGPU.md, "Display view (Speed)"), doubled. */
|
|
104
|
-
export declare function displayViewSpeedGpuRelativeBounds(toneCurve: DisplayViewSetup["toneCurve"]): {
|
|
117
|
+
export declare function displayViewSpeedGpuRelativeBounds(toneCurve: DisplayViewSetup["toneCurve"], extraSiteRoundings?: number): {
|
|
105
118
|
mean: number;
|
|
106
119
|
variance: number;
|
|
107
120
|
};
|
|
@@ -119,6 +132,12 @@ export declare function finishDisplayViewPixel(moments: ArrayLike<number>, pixel
|
|
|
119
132
|
* absolute variance bound; it changes no computed value.
|
|
120
133
|
*/
|
|
121
134
|
export declare function displayViewSpeedQuantizationBound(moments: ArrayLike<number>, relative: number, absolute: number, pixels: number, step: number): number;
|
|
135
|
+
/**
|
|
136
|
+
* Renders the display pixels `indices` (ascending, region row-major) with the reference, a run of consecutive pixels along a row
|
|
137
|
+
* as one region (#457: AHD's choice flags stretches near edges): a region render is byte-identical to the same pixels of the whole
|
|
138
|
+
* view, so each pixel equals its one-pixel render, and a run's counts are the sums of its pixels'.
|
|
139
|
+
*/
|
|
140
|
+
export declare function recomputeRuns(setup: DisplayViewSetup, signalInput: DisplayViewExpectedSignalInput, indices: readonly number[], abort: AbortController, take: (first: number, run: number, part: DisplayViewRenderResult) => void): Promise<void>;
|
|
122
141
|
/** Renders one region of a Speed display view on the GPU (see the module comment). */
|
|
123
142
|
export declare function renderDisplayViewSpeedOnGpu(runtime: WebGpuRuntime, bandBytes: number, input: DisplayViewSpeedGpuInput, abort: AbortController, yieldControl: YieldControl, pipelines: PipelineSource): Promise<DisplayViewSpeedGpuResult>;
|
|
124
143
|
/**
|
|
@@ -130,11 +149,40 @@ export declare function renderDisplayViewSpeedOnGpu(runtime: WebGpuRuntime, band
|
|
|
130
149
|
* render (`displayViewSpeedGpuUnsupportedReason`, `createDisplayViewMaterialModules`).
|
|
131
150
|
*/
|
|
132
151
|
export declare function displayViewSpeedGpuSupports(request: DisplayViewGpuRequest): true | string;
|
|
152
|
+
/** Why the GPU's material lookup, tone curves or query-offset table do not cover a request (shared by the Speed and Quality support checks), or true. */
|
|
153
|
+
export declare function sceneToneAndOffsetsReason(request: DisplayViewGpuRequest, maximumOffsets: number): true | string;
|
|
154
|
+
/**
|
|
155
|
+
* A self-check's verdict: every component of a GPU render inside its a-priori bounds against the reference, every sample within
|
|
156
|
+
* one code, and (#533) the saturated-pixel count the reference's bit for bit; true, or a sentence saying where it failed.
|
|
157
|
+
*/
|
|
158
|
+
export declare function conformanceVerdict(reference: DisplayViewRenderResult, gpu: Pick<DisplayViewSpeedGpuResult, "samples" | "expected" | "noiseStd" | "expectedValueBound" | "varianceBound" | "expectedValueAbsoluteBound" | "varianceAbsoluteBound" | "saturatedPixelCount">): true | string;
|
|
133
159
|
/**
|
|
134
160
|
* The renderer's run-time self-check (`DisplayViewGpuRenderer.conformanceCases`): a 38 × 26 checkerboard frame of 6 µm sites
|
|
135
161
|
* shown at 13 × 9 (2.9 real pixels per display pixel) with the 2 × 2 box, light squares at the white clip, the GPU against
|
|
136
162
|
* `renderDisplayView` within the a-priori bounds, every flagged pixel recomputed.
|
|
137
163
|
*/
|
|
164
|
+
/** The self-checks' common frame: 40 × 28 native sites (a 38 × 26 frame and a one-site margin) shown at 13 × 9, at three spectral nodes. */
|
|
165
|
+
export declare const CONFORMANCE_FRAME: Readonly<{
|
|
166
|
+
nativeWidth: 40;
|
|
167
|
+
nativeHeight: 28;
|
|
168
|
+
margin: 1;
|
|
169
|
+
frameWidth: 38;
|
|
170
|
+
frameHeight: 26;
|
|
171
|
+
nodes: number[];
|
|
172
|
+
}>;
|
|
173
|
+
/** The self-checks' channel responses per node (`scale` 1, or 1/10 for the low-signal correction's few electrons). */
|
|
174
|
+
export declare function conformanceResponses(scale: number): [Float64Array, Float64Array, Float64Array];
|
|
175
|
+
/** The RGGB channel of a native site. */
|
|
176
|
+
export declare const conformanceChannelAt: (x: number, y: number) => number;
|
|
177
|
+
/** The self-checks' checkerboard and its expected-signal scene (neutral materials under a white radiance per node). */
|
|
178
|
+
export declare function conformanceChart(): {
|
|
179
|
+
chart: ReturnType<typeof resolvePortableChartScene>;
|
|
180
|
+
scene: DisplayViewExpectedSignalInput["scene"];
|
|
181
|
+
};
|
|
182
|
+
/** A self-check's setup around its expected-signal input: the common frame and view, the bilinear demosaic, gains, matrix and tone curve, plus `extra`. */
|
|
183
|
+
export declare function conformanceSetup(signalInput: DisplayViewExpectedSignalInput, readout: DisplayViewSetup["readout"][0], seed: number, reachSites: number, signal: AbortSignal | undefined, extra?: Partial<DisplayViewSetup>): DisplayViewSetup;
|
|
184
|
+
/** Runs a self-check's GPU render with an abort controller that follows `signal`, and the device's pipelines under it. */
|
|
185
|
+
export declare function withConformanceAbort<T>(runtime: WebGpuRuntime, signal: AbortSignal | undefined, run: (abort: AbortController, pipelines: PipelineSource) => Promise<T>): Promise<T>;
|
|
138
186
|
export declare const DISPLAY_VIEW_SPEED_CONFORMANCE_CASE: DisplayViewGpuConformanceCase;
|
|
139
187
|
/**
|
|
140
188
|
* The display view's Speed renderer on one runtime (#481's `DisplayViewGpuRenderer`): one pipeline cache per device, the
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* `display-view-composite-material-module.ts` (#491) one for composite charts, the 2.3 elements and `material-layers` scenes.
|
|
15
15
|
*/
|
|
16
16
|
import { DISPLAY_VIEW_NOISE_STREAM_ID, axisSums, clipCentre, createDisplayViewExpectedSignal, displayViewDecidedSaturation, overlaps, quantizationVariance, renderDisplayView } from "../facade/display-view.js";
|
|
17
|
+
import { displayViewSignalTables } from "../facade/expected-signal.js";
|
|
17
18
|
import { resolvePortableChartScene } from "../facade/chart-scenes.js";
|
|
18
19
|
import { setDisplayViewGpuRendererFactory } from "../facade/display-view-gpu.js";
|
|
19
20
|
import { createDisplayViewMaterialModules } from "./display-view-composite-material-module.js";
|
|
@@ -23,7 +24,7 @@ import { createDeterministicUniformStream, deriveNativeSiteStreamSeedUint32, sam
|
|
|
23
24
|
import { createHostYieldGate } from "../capture/host-yield-cadence.js";
|
|
24
25
|
import { ImageKernelExecutionError, WEBGPU_F32_UNIT_ROUNDOFF, webGpuExecution } from "./image-kernel-execution.js";
|
|
25
26
|
import { BUFFER_USAGE, GpuResourceScope, abortedError, createComputePipeline, linearDispatch, runComputePass, throwIfAborted as checkAbort } from "./webgpu-runtime.js";
|
|
26
|
-
import { DISPLAY_VIEW_AUX, DISPLAY_VIEW_FLAG_WORDS, DISPLAY_VIEW_GEOMETRY_STRIDE, DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS, DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_AXIS, DISPLAY_VIEW_OUTPUT_FLOATS, DISPLAY_VIEW_PARAM_WORDS, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U, DISPLAY_VIEW_WORKGROUP_SIZE, assembleDisplayViewSpeedWgsl } from "./wgsl-display-view-speed-kernel.js";
|
|
27
|
+
import { DISPLAY_VIEW_AUX, DISPLAY_VIEW_FLAG_WORDS, DISPLAY_VIEW_SPEED_MAXIMUM_NODES, DISPLAY_VIEW_GEOMETRY_STRIDE, DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS, DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_AXIS, DISPLAY_VIEW_OUTPUT_FLOATS, DISPLAY_VIEW_PARAM_WORDS, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U, DISPLAY_VIEW_WORKGROUP_SIZE, assembleDisplayViewSpeedWgsl } from "./wgsl-display-view-speed-kernel.js";
|
|
27
28
|
import { footprintShape, bilinearKernels, offsetCount } from "./display-view-quality-gpu.js";
|
|
28
29
|
/** One WGSL rounding of + − × in either direction (W3C WGSL "Floating Point Accuracy"; #471, #487): twice the binary32 unit roundoff. */
|
|
29
30
|
const E = 2 * WEBGPU_F32_UNIT_ROUNDOFF;
|
|
@@ -60,7 +61,7 @@ export function displayViewSpeedGpuUnsupportedReason(setup, signalInput) {
|
|
|
60
61
|
return "a defocus kernel radius above 64 sites (convolved by runs on the CPU, #564) is not on the GPU";
|
|
61
62
|
if (signalInput.kernels)
|
|
62
63
|
return "diffraction kernels (the Quality path) are not on the GPU";
|
|
63
|
-
if (signalInput.scene.kind !== "materials")
|
|
64
|
+
if (signalInput.scene.kind !== "materials" && !signalInput.scene.signalAt)
|
|
64
65
|
return "radiance scenes (caller functions) are not on the GPU";
|
|
65
66
|
if (!signalInput.queryPoint)
|
|
66
67
|
return "the expected signal has no query points";
|
|
@@ -70,6 +71,8 @@ export function displayViewSpeedGpuUnsupportedReason(setup, signalInput) {
|
|
|
70
71
|
return "unsupported tone curve " + String(setup.toneCurve);
|
|
71
72
|
if (signalInput.offsets.length < 1 || signalInput.offsets.length > DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS)
|
|
72
73
|
return "the kernel holds 1 to " + DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS + " query offsets";
|
|
74
|
+
if (signalInput.wavelengthsNm.length < 1 || signalInput.wavelengthsNm.length > DISPLAY_VIEW_SPEED_MAXIMUM_NODES)
|
|
75
|
+
return "the kernel integrates 1 to " + DISPLAY_VIEW_SPEED_MAXIMUM_NODES + " spectral nodes per site";
|
|
73
76
|
if (setup.region.width < 1 || setup.region.height < 1)
|
|
74
77
|
return "an empty region";
|
|
75
78
|
const shape = footprintShape(setup);
|
|
@@ -85,27 +88,19 @@ export function displayViewSpeedGpuUnsupportedReason(setup, signalInput) {
|
|
|
85
88
|
}
|
|
86
89
|
return null;
|
|
87
90
|
}
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
for (let n = 0; n < nodes; n++)
|
|
102
|
-
value += A[c][n] * m.radiance[n];
|
|
103
|
-
}
|
|
104
|
-
if (!(Math.abs(value) <= FLOAT32_SAFE_MAGNITUDE))
|
|
105
|
-
throw new ImageKernelExecutionError("unsupported-float32-domain", "A material's channel signal exceeds the float32 domain.");
|
|
106
|
-
table[index * 3 + c] = value;
|
|
107
|
-
}
|
|
108
|
-
});
|
|
91
|
+
/**
|
|
92
|
+
* The spectral table (2.21.0: signals are integrated from the material spectrum per site): the three channel responses A_c(λ_n)
|
|
93
|
+
* (3·N floats), then each material's spectral radiance L_m(λ_n) (N floats per material; a neutral one its reflectance times the
|
|
94
|
+
* white radiance, the reference's own float64 spectra), each rounded once to float32. The kernel integrates Σ_n A_c(λ_n)·L_m(λ_n)
|
|
95
|
+
* for the material and channel a site reads, when it reads it; no per-material channel value is formed.
|
|
96
|
+
*
|
|
97
|
+
* Domain: every value is non-negative and the largest possible sum, N·max A·max L, stays below the float32 safe magnitude (NaN fails it).
|
|
98
|
+
*/
|
|
99
|
+
function spectralTable(signalInput) {
|
|
100
|
+
const N = signalInput.wavelengthsNm.length, rows = [...signalInput.electronsPerRadiance, ...displayViewSignalTables(signalInput).spectra], largest = [0, 0];
|
|
101
|
+
const table = Float32Array.from(rows.flatMap((row, r) => Array.from(row.subarray(0, N), (v) => { largest[r < 3 ? 0 : 1] = Math.max(largest[r < 3 ? 0 : 1], v >= 0 ? v : Infinity); return v; })));
|
|
102
|
+
if (!(largest[0] * largest[1] * N <= FLOAT32_SAFE_MAGNITUDE))
|
|
103
|
+
throw new ImageKernelExecutionError("unsupported-float32-domain", "A material's channel signal exceeds the float32 domain.");
|
|
109
104
|
return table;
|
|
110
105
|
}
|
|
111
106
|
/**
|
|
@@ -185,6 +180,7 @@ function constants(setup, signalInput) {
|
|
|
185
180
|
words[8] = setup.region.width;
|
|
186
181
|
words[9] = setup.toneCurve === "positive-reinhard-per-channel" ? 1 : 0;
|
|
187
182
|
words[10] = signalInput.offsets.length;
|
|
183
|
+
words[11] = signalInput.wavelengthsNm.length;
|
|
188
184
|
for (let c = 0; c < 3; c++) {
|
|
189
185
|
floats[12 + c] = setup.readout[c].perElectron;
|
|
190
186
|
floats[16 + c] = setup.readout[c].darkElectrons;
|
|
@@ -218,10 +214,10 @@ function constants(setup, signalInput) {
|
|
|
218
214
|
return { params, aux };
|
|
219
215
|
}
|
|
220
216
|
/** The a-priori relative bounds of the expected value and the variance (docs/WEBGPU.md, "Display view (Speed)"), doubled. */
|
|
221
|
-
export function displayViewSpeedGpuRelativeBounds(toneCurve) {
|
|
217
|
+
export function displayViewSpeedGpuRelativeBounds(toneCurve, extraSiteRoundings = 0) {
|
|
222
218
|
const reinhard = toneCurve === "positive-reinhard-per-channel";
|
|
223
|
-
return { mean: MARGIN * (gammaE(MEAN_ROUNDINGS) + CPU_RELATIVE + (reinhard ? DISPLAY_VIEW_REINHARD_MEAN_U * E : 0)),
|
|
224
|
-
variance: MARGIN * (gammaE(VARIANCE_ROUNDINGS) + CPU_RELATIVE + (reinhard ? DISPLAY_VIEW_REINHARD_VARIANCE_U * E : 0)) };
|
|
219
|
+
return { mean: MARGIN * (gammaE(MEAN_ROUNDINGS + extraSiteRoundings) + CPU_RELATIVE + (reinhard ? DISPLAY_VIEW_REINHARD_MEAN_U * E : 0)),
|
|
220
|
+
variance: MARGIN * (gammaE(VARIANCE_ROUNDINGS + 2 * extraSiteRoundings) + CPU_RELATIVE + (reinhard ? DISPLAY_VIEW_REINHARD_VARIANCE_U * E : 0)) };
|
|
225
221
|
}
|
|
226
222
|
/**
|
|
227
223
|
* The reference's finishing of one display pixel from its linear-light moments (display-view.ts, after the sample loop):
|
|
@@ -263,13 +259,31 @@ export function displayViewSpeedQuantizationBound(moments, relative, absolute, p
|
|
|
263
259
|
}
|
|
264
260
|
return MARGIN * pixels * largest * step * step / 12;
|
|
265
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Renders the display pixels `indices` (ascending, region row-major) with the reference, a run of consecutive pixels along a row
|
|
264
|
+
* as one region (#457: AHD's choice flags stretches near edges): a region render is byte-identical to the same pixels of the whole
|
|
265
|
+
* view, so each pixel equals its one-pixel render, and a run's counts are the sums of its pixels'.
|
|
266
|
+
*/
|
|
267
|
+
export async function recomputeRuns(setup, signalInput, indices, abort, take) {
|
|
268
|
+
const region = setup.region;
|
|
269
|
+
let reference;
|
|
270
|
+
for (let r = 0; r < indices.length;) {
|
|
271
|
+
checkAbort(abort.signal);
|
|
272
|
+
const first = indices[r], rx = first % region.width, ry = (first - rx) / region.width;
|
|
273
|
+
let run = 1;
|
|
274
|
+
while (r + run < indices.length && indices[r + run] === first + run && rx + run < region.width)
|
|
275
|
+
run++;
|
|
276
|
+
take(first, run, await renderDisplayView({ ...setup, signal: reference ??= createDisplayViewExpectedSignal(signalInput), region: { x: region.x + rx, y: region.y + ry, width: run, height: 1 } }));
|
|
277
|
+
r += run;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
266
280
|
/** Renders one region of a Speed display view on the GPU (see the module comment). */
|
|
267
281
|
export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abort, yieldControl, pipelines) {
|
|
268
282
|
const { setup, signalInput, material } = input;
|
|
269
283
|
const reason = displayViewSpeedGpuUnsupportedReason(setup, signalInput);
|
|
270
284
|
if (reason)
|
|
271
285
|
throw new ImageKernelExecutionError("unsupported-on-backend", "webgpu-f32 display view (Speed): " + reason + "; use cpu-reference.");
|
|
272
|
-
const { region, margin } = setup, shape = footprintShape(setup), table = siteTable(signalInput), { params, aux } = constants(setup, signalInput);
|
|
286
|
+
const { region, margin } = setup, shape = footprintShape(setup), table = material.siteTable ?? spectralTable(signalInput), { params, aux } = constants(setup, signalInput);
|
|
273
287
|
const stride = DISPLAY_VIEW_GEOMETRY_STRIDE, geometry = new Float32Array((region.width + region.height) * stride);
|
|
274
288
|
const columnSums = [], rowSums = [];
|
|
275
289
|
for (let i = 0; i < region.width; i++) {
|
|
@@ -281,7 +295,7 @@ export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abo
|
|
|
281
295
|
rowSums.push(axisSums((region.y + j) * shape.sy, (region.y + j + 1) * shape.sy, setup.frameHeight, margin));
|
|
282
296
|
}
|
|
283
297
|
const area = shape.sx * shape.sy, pixelCount = region.width * region.height;
|
|
284
|
-
const rows = Math.max(1, Math.min(region.height, Math.floor(bandBytes / (region.width * BYTES_PER_PIXEL))));
|
|
298
|
+
const rows = Math.max(1, Math.min(region.height, Math.floor(bandBytes / (region.width * BYTES_PER_PIXEL)), material.bandRows?.(shape.sy) ?? Infinity));
|
|
285
299
|
if (Math.max(rows * region.width * 4 * DISPLAY_VIEW_OUTPUT_FLOATS, geometry.byteLength, table.byteLength) > runtime.maximumStorageBindingBytes)
|
|
286
300
|
throw new ImageKernelExecutionError("insufficient-limits", "One display-view band exceeds the device's storage binding limit.");
|
|
287
301
|
const pipeline = await pipelines("photivra-display-view-speed:" + material.id, assembleDisplayViewSpeedWgsl(material.wgsl));
|
|
@@ -292,8 +306,8 @@ export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abo
|
|
|
292
306
|
const auxBuffer = scope.upload("photivra-display-view-aux", aux, BUFFER_USAGE.STORAGE);
|
|
293
307
|
const geometryBuffer = scope.upload("photivra-display-view-geometry", geometry, BUFFER_USAGE.STORAGE);
|
|
294
308
|
const tableBuffer = scope.upload("photivra-display-view-table", table, BUFFER_USAGE.STORAGE);
|
|
295
|
-
const
|
|
296
|
-
|
|
309
|
+
const materialGroup = material.band ? null : runtime.device.createBindGroup({ layout: pipeline.getBindGroupLayout(1), entries: material.bindings.map((b) => ({ binding: b.binding,
|
|
310
|
+
resource: { buffer: b.buffer ?? scope.upload("photivra-display-view-material-" + b.binding, b.data, b.kind === "uniform" ? BUFFER_USAGE.UNIFORM : BUFFER_USAGE.STORAGE) } })) });
|
|
297
311
|
const gate = createHostYieldGate();
|
|
298
312
|
for (let row0 = 0; row0 < region.height; row0 += rows) {
|
|
299
313
|
checkAbort(abort.signal);
|
|
@@ -309,7 +323,8 @@ export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abo
|
|
|
309
323
|
{ binding: 0, resource: { buffer: uniform } }, { binding: 1, resource: { buffer: auxBuffer } }, { binding: 2, resource: { buffer: geometryBuffer } },
|
|
310
324
|
{ binding: 3, resource: { buffer: tableBuffer } }, { binding: 4, resource: { buffer: out } }, { binding: 5, resource: { buffer: flagBuffer } }
|
|
311
325
|
] });
|
|
312
|
-
|
|
326
|
+
// #581: a module that bands its own data (a large photo) gives group 1 per band, for the native rows the band reads (with margin).
|
|
327
|
+
const [data, flagData] = await runComputePass(runtime, band, pipeline, [bindGroup, materialGroup ?? material.band(band, pipeline.getBindGroupLayout(1), Math.floor((region.y + row0) * shape.sy) + margin - 2, Math.ceil((region.y + row1) * shape.sy) + margin + 2)], linearDispatch(count, DISPLAY_VIEW_WORKGROUP_SIZE, runtime.maximumWorkgroupsPerDimension), [{ buffer: out, bytes: count * 4 * DISPLAY_VIEW_OUTPUT_FLOATS }, { buffer: flagBuffer, bytes: count * 4 * DISPLAY_VIEW_FLAG_WORDS }], abort.signal);
|
|
313
328
|
moments.set(new Float32Array(data, 0, count * DISPLAY_VIEW_OUTPUT_FLOATS), first * DISPLAY_VIEW_OUTPUT_FLOATS);
|
|
314
329
|
const bandFlags = new Uint32Array(flagData, 0, count * DISPLAY_VIEW_FLAG_WORDS);
|
|
315
330
|
flagWords.set(bandFlags, first * DISPLAY_VIEW_FLAG_WORDS);
|
|
@@ -341,7 +356,7 @@ export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abo
|
|
|
341
356
|
const expected = new Float64Array(pixelCount * 3), noiseStd = new Float64Array(pixelCount * 3), saturated = new Float64Array(pixelCount);
|
|
342
357
|
const meanBound = new Float32Array(pixelCount), varianceBound = new Float32Array(pixelCount);
|
|
343
358
|
let largestMean = 0, largestVariance = 0, sampleCount = 0, siteCount = 0;
|
|
344
|
-
const counts = new Int32Array(geometry.buffer), relative = displayViewSpeedGpuRelativeBounds(setup.toneCurve);
|
|
359
|
+
const counts = new Int32Array(geometry.buffer), relative = displayViewSpeedGpuRelativeBounds(setup.toneCurve, material.extraSiteRoundings ?? 0);
|
|
345
360
|
// #533: each display pixel's saturated-pixel share from the kernel's mask (the reference's arithmetic, exact where it decided).
|
|
346
361
|
const columnOverlaps = Array.from({ length: region.width }, (_, i) => overlaps((region.x + i) * shape.sx, (region.x + i + 1) * shape.sx, setup.frameWidth));
|
|
347
362
|
for (let ry = 0, i = 0; ry < region.height; ry++) {
|
|
@@ -372,26 +387,22 @@ export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abo
|
|
|
372
387
|
for (let i = 0; i < pixelCount; i++)
|
|
373
388
|
if (isFlagged[i] === 0 && (flagWords[DISPLAY_VIEW_FLAG_WORDS * i] & 2) !== 0)
|
|
374
389
|
undecided.push(i);
|
|
375
|
-
if (input.recomputeFlagged !== false
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const rx = i % region.width, ry = (i - rx) / region.width;
|
|
392
|
-
saturated[i] = (await renderDisplayView({ ...setup, signal, region: { x: region.x + rx, y: region.y + ry, width: 1, height: 1 } })).saturated[0];
|
|
393
|
-
highlights.push(i);
|
|
394
|
-
}
|
|
390
|
+
if (input.recomputeFlagged !== false) {
|
|
391
|
+
await recomputeRuns(setup, signalInput, flagged, abort, (first, run, part) => {
|
|
392
|
+
samples.set(part.samples, first * 3);
|
|
393
|
+
expected.set(part.expected, first * 3);
|
|
394
|
+
noiseStd.set(part.noiseStd, first * 3);
|
|
395
|
+
saturated.set(part.saturated, first);
|
|
396
|
+
for (let k = 0; k < run; k++) {
|
|
397
|
+
meanBound[first + k] = 0;
|
|
398
|
+
varianceBound[first + k] = 0;
|
|
399
|
+
recomputed.push(first + k);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
await recomputeRuns(setup, signalInput, undecided, abort, (first, run, part) => { for (let k = 0; k < run; k++) {
|
|
403
|
+
saturated[first + k] = part.saturated[k];
|
|
404
|
+
highlights.push(first + k);
|
|
405
|
+
} });
|
|
395
406
|
}
|
|
396
407
|
let saturatedPixelCount = 0;
|
|
397
408
|
for (let i = 0; i < pixelCount; i++)
|
|
@@ -417,12 +428,35 @@ export function displayViewSpeedGpuSupports(request) {
|
|
|
417
428
|
return "AHD (the Quality demosaic) is not on the GPU";
|
|
418
429
|
if (request.diffraction)
|
|
419
430
|
return "diffraction (the Quality optics) is not on the GPU";
|
|
420
|
-
|
|
431
|
+
return sceneToneAndOffsetsReason(request, DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS);
|
|
432
|
+
}
|
|
433
|
+
/** Why the GPU's material lookup, tone curves or query-offset table do not cover a request (shared by the Speed and Quality support checks), or true. */
|
|
434
|
+
export function sceneToneAndOffsetsReason(request, maximumOffsets) {
|
|
435
|
+
if (request.scene !== "chart" && request.scene !== "material-layers" && request.scene !== "image-plane")
|
|
421
436
|
return "the scene is a caller " + request.scene + "; the GPU material lookup covers engine charts and declared material-layers scenes (describe the scene as material-layers)";
|
|
422
437
|
if (request.toneCurve !== "identity" && request.toneCurve !== "positive-reinhard-per-channel")
|
|
423
438
|
return "unsupported tone curve " + String(request.toneCurve);
|
|
424
|
-
if (offsetCount(request) >
|
|
425
|
-
return "more than " +
|
|
439
|
+
if (offsetCount(request) > maximumOffsets)
|
|
440
|
+
return "more than " + maximumOffsets + " query offsets per site";
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* A self-check's verdict: every component of a GPU render inside its a-priori bounds against the reference, every sample within
|
|
445
|
+
* one code, and (#533) the saturated-pixel count the reference's bit for bit; true, or a sentence saying where it failed.
|
|
446
|
+
*/
|
|
447
|
+
export function conformanceVerdict(reference, gpu) {
|
|
448
|
+
for (let i = 0; i < reference.expected.length; i++) {
|
|
449
|
+
const p = Math.floor(i / 3), e0 = reference.expected[i], e1 = gpu.expected[i], v0 = reference.noiseStd[i] * reference.noiseStd[i], v1 = gpu.noiseStd[i] * gpu.noiseStd[i];
|
|
450
|
+
const expectedLimit = gpu.expectedValueBound.relative * Math.abs(e0) + gpu.expectedValueAbsoluteBound[p], varianceLimit = gpu.varianceBound.relative * v0 + gpu.varianceAbsoluteBound[p];
|
|
451
|
+
if (!(Math.abs(e1 - e0) <= expectedLimit))
|
|
452
|
+
return "component " + i + ": expected value " + e1 + " differs from the reference " + e0 + " by more than the a-priori bound " + expectedLimit + ".";
|
|
453
|
+
if (!(Math.abs(v1 - v0) <= varianceLimit))
|
|
454
|
+
return "component " + i + ": variance " + v1 + " differs from the reference " + v0 + " by more than the a-priori bound " + varianceLimit + ".";
|
|
455
|
+
if (Math.abs(gpu.samples[i] - reference.samples[i]) > 1)
|
|
456
|
+
return "component " + i + ": sample " + gpu.samples[i] + " differs from the reference " + reference.samples[i] + " by more than one code.";
|
|
457
|
+
}
|
|
458
|
+
if (gpu.saturatedPixelCount !== reference.saturatedPixelCount)
|
|
459
|
+
return "the saturated-pixel count " + gpu.saturatedPixelCount + " differs from the reference's " + reference.saturatedPixelCount + ".";
|
|
426
460
|
return true;
|
|
427
461
|
}
|
|
428
462
|
/**
|
|
@@ -430,48 +464,52 @@ export function displayViewSpeedGpuSupports(request) {
|
|
|
430
464
|
* shown at 13 × 9 (2.9 real pixels per display pixel) with the 2 × 2 box, light squares at the white clip, the GPU against
|
|
431
465
|
* `renderDisplayView` within the a-priori bounds, every flagged pixel recomputed.
|
|
432
466
|
*/
|
|
467
|
+
/** The self-checks' common frame: 40 × 28 native sites (a 38 × 26 frame and a one-site margin) shown at 13 × 9, at three spectral nodes. */
|
|
468
|
+
export const CONFORMANCE_FRAME = /* @__PURE__ */ Object.freeze({ nativeWidth: 40, nativeHeight: 28, margin: 1, frameWidth: 38, frameHeight: 26, nodes: [450, 550, 650] });
|
|
469
|
+
/** The self-checks' channel responses per node (`scale` 1, or 1/10 for the low-signal correction's few electrons). */
|
|
470
|
+
export function conformanceResponses(scale) {
|
|
471
|
+
return [Float64Array.of(9000, 30000, 4000).map((v) => v * scale), Float64Array.of(6000, 42000, 9000).map((v) => v * scale), Float64Array.of(30000, 9000, 3000).map((v) => v * scale)];
|
|
472
|
+
}
|
|
473
|
+
/** The RGGB channel of a native site. */
|
|
474
|
+
export const conformanceChannelAt = (x, y) => ((y & 1) === 0 ? ((x & 1) === 0 ? 0 : 1) : ((x & 1) === 0 ? 1 : 2));
|
|
475
|
+
/** The self-checks' checkerboard and its expected-signal scene (neutral materials under a white radiance per node). */
|
|
476
|
+
export function conformanceChart() {
|
|
477
|
+
const chart = resolvePortableChartScene({ scene: { preset: "checkerboard", squaresAcross: 6 }, aspect: CONFORMANCE_FRAME.frameHeight / CONFORMANCE_FRAME.frameWidth });
|
|
478
|
+
return { chart, scene: { kind: "materials", materialAt: (u, v) => chart.materialAt(u, v), materials: chart.materials.map((m) => ({ neutral: m.kind === "neutral" ? m.reflectance : 0.5 })),
|
|
479
|
+
whiteRadiance: Float64Array.of(0.5, 0.9, 0.7) } };
|
|
480
|
+
}
|
|
481
|
+
/** A self-check's setup around its expected-signal input: the common frame and view, the bilinear demosaic, gains, matrix and tone curve, plus `extra`. */
|
|
482
|
+
export function conformanceSetup(signalInput, readout, seed, reachSites, signal, extra = {}) {
|
|
483
|
+
const { frameWidth, frameHeight, margin, nativeWidth, nativeHeight } = CONFORMANCE_FRAME;
|
|
484
|
+
return { frameWidth, frameHeight, margin, nativeWidth, nativeHeight, viewWidth: 13, viewHeight: 9, region: { x: 0, y: 0, width: 13, height: 9 },
|
|
485
|
+
samplesPerAxis: 16, reachSites, bitDepth: 8, seed, signal: createDisplayViewExpectedSignal(signalInput), readout: [readout, readout, readout], demosaic: bilinearKernels(),
|
|
486
|
+
gains: [1.1, 1, 1.6], clipLevel: 1, matrix: [[3.08, -1.54, -0.54], [-0.92, 1.88, 0.045], [0.053, -0.2, 1.15]], toneCurve: "identity",
|
|
487
|
+
yieldControl: async () => undefined, checkAbort: () => { if (signal?.aborted)
|
|
488
|
+
throw abortedError(); }, ...extra };
|
|
489
|
+
}
|
|
490
|
+
/** Runs a self-check's GPU render with an abort controller that follows `signal`, and the device's pipelines under it. */
|
|
491
|
+
export async function withConformanceAbort(runtime, signal, run) {
|
|
492
|
+
const abort = new AbortController(), onAbort = () => abort.abort();
|
|
493
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
494
|
+
try {
|
|
495
|
+
if (signal?.aborted)
|
|
496
|
+
abort.abort();
|
|
497
|
+
return await run(abort, (label, code) => createComputePipeline(runtime, label, code, abort.signal));
|
|
498
|
+
}
|
|
499
|
+
finally {
|
|
500
|
+
signal?.removeEventListener("abort", onAbort);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
433
503
|
export const DISPLAY_VIEW_SPEED_CONFORMANCE_CASE = { id: "display-view:speed:checkerboard-13x9", async run(handle, signal) {
|
|
434
504
|
// The handle is the engine's own runtime (the contract keeps WebGPU types out of the published declarations).
|
|
435
|
-
const runtime = handle;
|
|
436
|
-
const nativeWidth = 40, nativeHeight = 28, margin = 1, frameWidth = 38, frameHeight = 26, g = 2 / frameWidth, nodes = [450, 550, 650];
|
|
437
|
-
const chart = resolvePortableChartScene({ scene: { preset: "checkerboard", squaresAcross: 6 }, aspect: frameHeight / frameWidth });
|
|
505
|
+
const runtime = handle, { nativeWidth, nativeHeight, frameWidth, nodes } = CONFORMANCE_FRAME, g = 2 / frameWidth, { chart, scene } = conformanceChart();
|
|
438
506
|
const offsets = [[-0.25, -0.25], [0.25, -0.25], [-0.25, 0.25], [0.25, 0.25]].map(([x, y]) => ({ x: x, y: y, weight: 0.25 }));
|
|
439
|
-
const signalInput = { nativeWidth, nativeHeight, outputWidth: frameWidth, wavelengthsNm: nodes,
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
channelAt: (x, y) => ((y & 1) === 0 ? ((x & 1) === 0 ? 0 : 1) : ((x & 1) === 0 ? 1 : 2)),
|
|
443
|
-
queryPoint: (x, y, o, out) => { out[0] = (x + 0.5 + offsets[o].x - nativeWidth / 2) * g; out[1] = -(y + 0.5 + offsets[o].y - nativeHeight / 2) * g; } };
|
|
444
|
-
const readout = { perElectron: 1 / 60000, darkElectrons: 0.07, constantVariance: 1.5e-9 };
|
|
445
|
-
const setup = { frameWidth, frameHeight, margin, nativeWidth, nativeHeight, viewWidth: 13, viewHeight: 9, region: { x: 0, y: 0, width: 13, height: 9 },
|
|
446
|
-
samplesPerAxis: 16, reachSites: 2, bitDepth: 8, seed: 478, signal: createDisplayViewExpectedSignal(signalInput), readout: [readout, readout, readout], demosaic: bilinearKernels(),
|
|
447
|
-
gains: [1.1, 1, 1.6], clipLevel: 1, matrix: [[3.08, -1.54, -0.54], [-0.92, 1.88, 0.045], [0.053, -0.2, 1.15]], toneCurve: "identity",
|
|
448
|
-
yieldControl: async () => undefined, checkAbort: () => { if (signal?.aborted)
|
|
449
|
-
throw abortedError(); } };
|
|
507
|
+
const signalInput = { nativeWidth, nativeHeight, outputWidth: frameWidth, wavelengthsNm: nodes, electronsPerRadiance: conformanceResponses(1), offsets, scene,
|
|
508
|
+
channelAt: conformanceChannelAt, queryPoint: (x, y, o, out) => { out[0] = (x + 0.5 + offsets[o].x - nativeWidth / 2) * g; out[1] = -(y + 0.5 + offsets[o].y - nativeHeight / 2) * g; } };
|
|
509
|
+
const setup = conformanceSetup(signalInput, { perElectron: 1 / 60000, darkElectrons: 0.07, constantVariance: 1.5e-9 }, 478, 2, signal);
|
|
450
510
|
const reference = await renderDisplayView(setup);
|
|
451
|
-
|
|
452
|
-
signal
|
|
453
|
-
try {
|
|
454
|
-
if (signal?.aborted)
|
|
455
|
-
abort.abort();
|
|
456
|
-
const gpu = await renderDisplayViewSpeedOnGpu(runtime, 1 << 20, { setup, signalInput, material: chartPresetMaterialModule(chart) }, abort, undefined, (label, code) => createComputePipeline(runtime, label, code, abort.signal));
|
|
457
|
-
for (let i = 0; i < reference.expected.length; i++) {
|
|
458
|
-
const p = Math.floor(i / 3), e0 = reference.expected[i], e1 = gpu.expected[i], v0 = reference.noiseStd[i] * reference.noiseStd[i], v1 = gpu.noiseStd[i] * gpu.noiseStd[i];
|
|
459
|
-
const expectedLimit = gpu.expectedValueBound.relative * Math.abs(e0) + gpu.expectedValueAbsoluteBound[p], varianceLimit = gpu.varianceBound.relative * v0 + gpu.varianceAbsoluteBound[p];
|
|
460
|
-
if (!(Math.abs(e1 - e0) <= expectedLimit))
|
|
461
|
-
return "component " + i + ": expected value " + e1 + " differs from the reference " + e0 + " by more than the a-priori bound " + expectedLimit + ".";
|
|
462
|
-
if (!(Math.abs(v1 - v0) <= varianceLimit))
|
|
463
|
-
return "component " + i + ": variance " + v1 + " differs from the reference " + v0 + " by more than the a-priori bound " + varianceLimit + ".";
|
|
464
|
-
if (Math.abs(gpu.samples[i] - reference.samples[i]) > 1)
|
|
465
|
-
return "component " + i + ": sample " + gpu.samples[i] + " differs from the reference " + reference.samples[i] + " by more than one code.";
|
|
466
|
-
}
|
|
467
|
-
// #533: the saturated-pixel count is the reference's, bit for bit (the light squares sit at the white clip).
|
|
468
|
-
if (gpu.saturatedPixelCount !== reference.saturatedPixelCount)
|
|
469
|
-
return "the saturated-pixel count " + gpu.saturatedPixelCount + " differs from the reference's " + reference.saturatedPixelCount + ".";
|
|
470
|
-
return true;
|
|
471
|
-
}
|
|
472
|
-
finally {
|
|
473
|
-
signal?.removeEventListener("abort", onAbort);
|
|
474
|
-
}
|
|
511
|
+
// #533: the verdict includes the saturated-pixel count (the light squares sit at the white clip).
|
|
512
|
+
return await withConformanceAbort(runtime, signal, async (abort, pipelines) => conformanceVerdict(reference, await renderDisplayViewSpeedOnGpu(runtime, 1 << 20, { setup, signalInput, material: chartPresetMaterialModule(chart) }, abort, undefined, pipelines)));
|
|
475
513
|
} };
|
|
476
514
|
/**
|
|
477
515
|
* The display view's Speed renderer on one runtime (#481's `DisplayViewGpuRenderer`): one pipeline cache per device, the
|