@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
|
@@ -51,9 +51,12 @@
|
|
|
51
51
|
*/
|
|
52
52
|
import { DISPLAY_VIEW_CLIP_MEAN_U, DISPLAY_VIEW_CLIP_VARIANCE_U, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U } from "./wgsl-display-view-speed-kernel.js";
|
|
53
53
|
import { AHD_KEY_FLOOR, AHD_KEY_LEVELS, AHD_KEY_OCTAVES, AHD_KEY_STEPS } from "../facade/display-view.js";
|
|
54
|
+
import { EXACT_SUM_WGSL } from "./wgsl-exact-sum.js";
|
|
54
55
|
export const DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE = 64;
|
|
55
56
|
/** Largest footprint per axis (real pixels), as the Speed kernel. */
|
|
56
57
|
export const DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS = 6;
|
|
58
|
+
/** Most spectral nodes the site passes integrate per site (2.21.0: the per-node partials are private arrays of this length). */
|
|
59
|
+
export const DISPLAY_VIEW_QUALITY_MAXIMUM_NODES = 96;
|
|
57
60
|
/** Uniform parameter words (see `Params`): 56 words, 224 bytes. */
|
|
58
61
|
export const DISPLAY_VIEW_QUALITY_PARAM_WORDS = 56;
|
|
59
62
|
/** Words per lattice-group record: lx0, ly0, lw, lh, lbase, first tap (the blocked site pass, #492), cu (f32), cv (f32). */
|
|
@@ -133,7 +136,7 @@ const literal = (value) => {
|
|
|
133
136
|
return /[.e]/u.test(text) ? text : `${text}.0`;
|
|
134
137
|
};
|
|
135
138
|
/** The parameter struct and the bound helpers shared by the three passes. */
|
|
136
|
-
const
|
|
139
|
+
export const DISPLAY_VIEW_QUALITY_COMMON_WGSL = /* wgsl */ `
|
|
137
140
|
const E: f32 = ${E_LITERAL};
|
|
138
141
|
const SLACK: f32 = 1e-14;
|
|
139
142
|
const CLIP_REACH: f32 = 8.5;
|
|
@@ -162,7 +165,7 @@ g0: f32, g1: f32, g2: f32, scale_u: f32,
|
|
|
162
165
|
m00: f32, m01: f32, m02: f32, m10: f32,
|
|
163
166
|
m11: f32, m12: f32, m20: f32, m21: f32,
|
|
164
167
|
m22: f32, scale_v: f32, site_gamma: f32, site_first: u32,
|
|
165
|
-
site_tiny: f32,
|
|
168
|
+
site_tiny: f32, nodes: u32, pad_g: f32, pad_h: f32,
|
|
166
169
|
};
|
|
167
170
|
@group(0) @binding(0) var<uniform> params: Params;
|
|
168
171
|
|
|
@@ -344,7 +347,7 @@ return out;
|
|
|
344
347
|
* records hold each group's lattice window (lx0, ly0, lw, lh), its first index in the lattice buffer and the point constants
|
|
345
348
|
* c_u = 0.5 + f_x − W/2 and c_v = 0.5 + f_y − H/2 (exact in float32, checked by the host).
|
|
346
349
|
*/
|
|
347
|
-
export const DISPLAY_VIEW_QUALITY_LATTICE_WGSL = /* wgsl */ `${
|
|
350
|
+
export const DISPLAY_VIEW_QUALITY_LATTICE_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}
|
|
348
351
|
@group(0) @binding(1) var<storage, read> groups: array<u32>;
|
|
349
352
|
@group(0) @binding(2) var<storage, read_write> lattice_out: array<u32>;
|
|
350
353
|
@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
|
|
@@ -371,7 +374,7 @@ lattice_out[i] = m | select(0u, 0x80000000u, material_uncertain);
|
|
|
371
374
|
* The end of pass 2, shared by both site passes: from the site's electrons e and their absolute bound de, the readout moments
|
|
372
375
|
* (mean k·e, variance k²e + c₀, third cumulant k³e), the clip at the white level and their bounds, written to site i.
|
|
373
376
|
*/
|
|
374
|
-
const
|
|
377
|
+
export const DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL = /* wgsl */ `
|
|
375
378
|
fn write_site(i: u32, c: u32, e: f32, de: f32, flag: u32) {
|
|
376
379
|
let kc = k_of(c);
|
|
377
380
|
let mean = kc * e;
|
|
@@ -404,16 +407,38 @@ const siteBindingsWgsl = (tapRecords) => /* wgsl */ `
|
|
|
404
407
|
@group(0) @binding(4) var<storage, read> lattice: array<u32>;
|
|
405
408
|
@group(0) @binding(5) var<storage, read_write> sites: array<f32>;
|
|
406
409
|
@group(0) @binding(6) var<storage, read> spread: array<f32>;
|
|
410
|
+
${EXACT_SUM_WGSL}
|
|
411
|
+
alias Nodes = array<f32, ${DISPLAY_VIEW_QUALITY_MAXIMUM_NODES}>;
|
|
412
|
+
fn add_tap(p: ptr<function, Nodes>, c: u32, t: u32, m: u32) {
|
|
413
|
+
let N = params.nodes;
|
|
414
|
+
for (var n = 0u; n < N; n = n + 1u) { (*p)[n] = (*p)[n] + table[(c * params.taps + t) * N + n] * table[(3u * params.taps + m) * N + n]; }
|
|
415
|
+
}
|
|
416
|
+
fn add_nodes(p: ptr<function, Nodes>, q: ptr<function, Nodes>) { for (var n = 0u; n < params.nodes; n = n + 1u) { (*p)[n] = (*p)[n] + (*q)[n]; (*q)[n] = 0.0; } }
|
|
417
|
+
fn node_total(p: ptr<function, Nodes>, dark: f32) -> f32 {
|
|
418
|
+
var anchor = exact_exponent(dark);
|
|
419
|
+
for (var k = 0u; k < params.nodes; k = k + 1u) { if ((*p)[k] > 0.0) { anchor = max(anchor, exact_exponent((*p)[k])); } }
|
|
420
|
+
var sum: ExactSum;
|
|
421
|
+
sum.anchor = anchor;
|
|
422
|
+
for (var k = 0u; k < params.nodes; k = k + 1u) { exact_add(&sum, (*p)[k]); }
|
|
423
|
+
exact_add(&sum, dark);
|
|
424
|
+
return exact_round(&sum);
|
|
425
|
+
}
|
|
407
426
|
`;
|
|
408
427
|
/**
|
|
409
428
|
* Pass 2: the expected electrons, readout moments and white clip of every native site of the band's window, with their bounds.
|
|
410
|
-
*
|
|
411
|
-
*
|
|
429
|
+
* 2.21.0 (signals integrated from the material spectrum per site): the site's electrons are Σ_t Σ_n C_c(t, n)·L_{m_t}(λ_n) +
|
|
430
|
+
* dark_c, from the channel's per-node kernel values and the spectrum of the material each tap reads (`table`: C at
|
|
431
|
+
* (c·T + t)·N + n, the spectra from 3·T·N), accumulated per node over the taps, then the node partials and the dark electrons
|
|
432
|
+
* summed in integer arithmetic and rounded once (EXACT_SUM_WGSL). Every term is non-negative and carries two host roundings (E/2
|
|
433
|
+
* each), its product (E) and at most T − 1 additions, then the final rounding (E), and the dark electrons their host rounding
|
|
434
|
+
* (E/2), so the sum carries at most (T + 2.5)·E, inside (T + 3)·E
|
|
435
|
+
* (`params.site_gamma`),
|
|
436
|
+
* plus `params.site_tiny`, the host's absolute bound on what a device that flushes below the float32 normal range can drop. An uncertain lattice point adds the tap's value spread
|
|
412
437
|
* over the materials (`spread`, host float64) to the site's uncertainty, which enters its bounds, or flags the site when it
|
|
413
438
|
* exceeds UNCERTAINTY_LIMIT of the electrons. One dispatch covers the `params.count` sites from `params.site_first` (#492: the
|
|
414
439
|
* host splits a band's sites over several dispatches so that no dispatch gathers more than its tap budget).
|
|
415
440
|
*/
|
|
416
|
-
export const DISPLAY_VIEW_QUALITY_SITES_WGSL = /* wgsl */ `${
|
|
441
|
+
export const DISPLAY_VIEW_QUALITY_SITES_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}${siteBindingsWgsl(true)}${DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL}
|
|
417
442
|
@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
|
|
418
443
|
fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
|
|
419
444
|
let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
|
|
@@ -424,44 +449,42 @@ let jj = i / params.site_w;
|
|
|
424
449
|
let x = params.site_x0 + i32(ii);
|
|
425
450
|
let y = params.site_y0 + i32(jj);
|
|
426
451
|
let c = channel_of(x, y);
|
|
427
|
-
var sum = 0.0;
|
|
428
452
|
var uncertainty = 0.0;
|
|
429
453
|
let T = params.taps;
|
|
454
|
+
var partial: Nodes;
|
|
430
455
|
for (var t = 0u; t < T; t = t + 1u) {
|
|
431
456
|
let tb = t * TAP;
|
|
432
457
|
let g = taps[tb] * GROUP;
|
|
433
458
|
let index = groups[g + 4u] + (jj + taps[tb + 2u]) * groups[g + 2u] + ii + taps[tb + 1u];
|
|
434
459
|
let raw = lattice[index];
|
|
435
460
|
if ((raw >> 31u) != 0u) { uncertainty = uncertainty + spread[c * T + t]; }
|
|
436
|
-
|
|
461
|
+
add_tap(&partial, c, t, raw & 0x7fffffffu);
|
|
437
462
|
}
|
|
438
|
-
let e =
|
|
463
|
+
let e = node_total(&partial, dark_of(c));
|
|
439
464
|
let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);
|
|
440
|
-
let de = params.site_gamma * e + uncertainty;
|
|
465
|
+
let de = params.site_gamma * e + uncertainty + params.site_tiny;
|
|
441
466
|
write_site(i, c, e, de, flag);
|
|
442
467
|
}
|
|
443
468
|
`;
|
|
444
469
|
/**
|
|
445
|
-
* Pass 2 with the defocus disc (#492): the same site electrons
|
|
470
|
+
* Pass 2 with the defocus disc (#492): the same site electrons, summed in blocks.
|
|
446
471
|
*
|
|
447
|
-
* - **
|
|
448
|
-
*
|
|
449
|
-
* `materials` holds per material its row (0 for neutral, 1 + s for spectral material s) and its reflectance as float32 bits
|
|
450
|
-
* (1 for a spectral material, an exact product). A scene of thousands of neutral materials (#491's composite: 4 143) then
|
|
451
|
-
* needs 3·T values instead of 3·T per material.
|
|
472
|
+
* - **Spectral table.** The pass reads the tap-list pass's table (2.21.0): the channel's per-node kernel values C_c(t, n) and each
|
|
473
|
+
* material's spectrum, so the signal of each tap is integrated from its material's spectrum when the site is read.
|
|
452
474
|
* - **Blocked sum.** Every group's taps form a dense cw × ch rectangle (the combined kernel's window, row-major from the group's
|
|
453
|
-
* first tap, `groups[g + 5]`), so a site sums each row of a group into a row partial, the rows into a group partial
|
|
454
|
-
* groups into the total. No tap records are read: the tap's lattice offset
|
|
475
|
+
* first tap, `groups[g + 5]`), so a site sums each row of a group into a row partial per node, the rows into a group partial
|
|
476
|
+
* and the groups into the total, then the nodes in integer arithmetic. No tap records are read: the tap's lattice offset
|
|
477
|
+
* within its group is its (dx, dy).
|
|
455
478
|
*
|
|
456
|
-
* Every term is non-negative and carries two host roundings (u each, u = E/2: the
|
|
457
|
-
* (E) and at most cw + ch + G additions,
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
479
|
+
* Every term is non-negative and carries two host roundings (u each, u = E/2: the kernel value and the spectrum), one product
|
|
480
|
+
* (E) and at most cw − 1 + ch − 1 + G − 1 inexact additions, then the integer sum of the node partials and the dark electrons,
|
|
481
|
+
* rounded once (E), and the dark electrons' host rounding (E/2), so the electrons carry at most (cw_max + ch_max + G + 0.5)·E, inside (cw_max + ch_max + G + 3)·E (`params.site_gamma`;
|
|
482
|
+
* recursive summation of the up to 4·(2·96 + 2)² = 150 544 taps of a combined radius of 96 would carry (T + 3)·E ≈ 1.8 %
|
|
483
|
+
* instead). The uncertainty, summed the same way, is scaled by 1 + site_gamma, and `params.site_tiny` (host float64, rounded up)
|
|
484
|
+
* bounds what a device that flushes results below the float32 normal range can drop (the tap-list pass adds it too).
|
|
461
485
|
*/
|
|
462
|
-
export const DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL = /* wgsl */ `${
|
|
463
|
-
|
|
464
|
-
${SITE_TAIL_WGSL}
|
|
486
|
+
export const DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}${siteBindingsWgsl(false)}
|
|
487
|
+
${DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL}
|
|
465
488
|
@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
|
|
466
489
|
fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
|
|
467
490
|
let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
|
|
@@ -473,7 +496,9 @@ let x = params.site_x0 + i32(ii);
|
|
|
473
496
|
let y = params.site_y0 + i32(jj);
|
|
474
497
|
let c = channel_of(x, y);
|
|
475
498
|
let T = params.taps;
|
|
476
|
-
var
|
|
499
|
+
var total: Nodes;
|
|
500
|
+
var group_sum: Nodes;
|
|
501
|
+
var row_sum: Nodes;
|
|
477
502
|
var uncertainty = 0.0;
|
|
478
503
|
for (var k = 0u; k < params.groups; k = k + 1u) {
|
|
479
504
|
let g = k * GROUP;
|
|
@@ -482,34 +507,31 @@ let cw = lw - params.site_w + 1u;
|
|
|
482
507
|
let ch = groups[g + 3u] - params.site_h + 1u;
|
|
483
508
|
let lattice0 = groups[g + 4u] + jj * lw + ii;
|
|
484
509
|
let tap0 = groups[g + 5u];
|
|
485
|
-
var group_sum = 0.0;
|
|
486
510
|
var group_uncertainty = 0.0;
|
|
487
511
|
for (var dy = 0u; dy < ch; dy = dy + 1u) {
|
|
488
512
|
let row = lattice0 + dy * lw;
|
|
489
513
|
let row_tap = tap0 + dy * cw;
|
|
490
|
-
var row_sum = 0.0;
|
|
491
514
|
var row_uncertainty = 0.0;
|
|
492
515
|
for (var dx = 0u; dx < cw; dx = dx + 1u) {
|
|
493
516
|
let raw = lattice[row + dx];
|
|
494
517
|
let t = row_tap + dx;
|
|
495
|
-
let m = 2u * (raw & 0x7fffffffu);
|
|
496
518
|
if ((raw >> 31u) != 0u) { row_uncertainty = row_uncertainty + spread[c * T + t]; }
|
|
497
|
-
row_sum
|
|
519
|
+
add_tap(&row_sum, c, t, raw & 0x7fffffffu);
|
|
498
520
|
}
|
|
499
|
-
group_sum
|
|
521
|
+
add_nodes(&group_sum, &row_sum);
|
|
500
522
|
group_uncertainty = group_uncertainty + row_uncertainty;
|
|
501
523
|
}
|
|
502
|
-
|
|
524
|
+
add_nodes(&total, &group_sum);
|
|
503
525
|
uncertainty = uncertainty + group_uncertainty;
|
|
504
526
|
}
|
|
505
|
-
let e =
|
|
527
|
+
let e = node_total(&total, dark_of(c));
|
|
506
528
|
let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);
|
|
507
529
|
let de = params.site_gamma * e + uncertainty * (1.0 + params.site_gamma) + params.site_tiny;
|
|
508
530
|
write_site(i, c, e, de, flag);
|
|
509
531
|
}
|
|
510
532
|
`;
|
|
511
533
|
/** Pass 3: the display pixels (see the module comment). */
|
|
512
|
-
export const DISPLAY_VIEW_QUALITY_PIXELS_WGSL = /* wgsl */ `${
|
|
534
|
+
export const DISPLAY_VIEW_QUALITY_PIXELS_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}
|
|
513
535
|
const KEY_LEVELS: u32 = ${AHD_KEY_LEVELS}u;
|
|
514
536
|
const KEY_STEPS: f32 = ${literal(AHD_KEY_STEPS)};
|
|
515
537
|
const KEY_OCTAVES: f32 = ${literal(AHD_KEY_OCTAVES)};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wgsl-display-view-quality-kernel.js","sourceRoot":"","sources":["../../src/gpu/wgsl-display-view-quality-kernel.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * WGSL kernels of the display view's Quality path on the GPU (#479): the exact area mean with diffraction kernels, the\n * anti-aliasing filter's spots, the AHD candidates and the low-signal correction, in three passes over one band of display\n * rows (docs/WEBGPU.md, \"Display view (Quality) on the GPU\"; the float64 reference is `renderDisplayView` with\n * `createDisplayViewExpectedSignal`, facade/display-view.ts and facade/expected-signal.ts):\n *\n * 1. **Lattice** (`DISPLAY_VIEW_QUALITY_LATTICE_WGSL` plus the material module): one invocation per lattice point of each\n * lattice group (offsets that share a fractional shift, as the reference groups them): the chart material at\n * (p + 0.5 + f_x − W/2)·g, −(q + 0.5 + f_y − H/2)·g (the reference's lattice formula, an exact float32 sum times the\n * scale rounded once), with the module's uncertainty flag in the top bit.\n * 2. **Sites** (`DISPLAY_VIEW_QUALITY_SITES_WGSL`): one invocation per native site of the band's window: the expected\n * electrons Σ_t V_c(m_t, t) over the taps t of every group (the combined kernel values per material and channel, formed on\n * the host in float64 as the reference forms them: Σ_members w·Σ_λ A_c(λ)·K_λ·L_m(λ); without diffraction the offset\n * weights times the material's channel signal), plus the dark electrons; the readout moments (mean k·e, variance k²e + c₀,\n * third cumulant k³e) and the clip at the white level; and each quantity's running absolute error bound. With the defocus\n * disc (#492) the variant `DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL` reads a factored table (one neutral row, one row per\n * spectral material) and sums each group's dense tap rectangle in blocks, so its bound grows with the kernel's width.\n * 3. **Pixels** (`DISPLAY_VIEW_QUALITY_PIXELS_WGSL`): one invocation per display pixel of the band: for every real pixel of\n * the footprint, the demosaic candidates (one bilinear, or AHD's two directional linear candidates, each with its full\n * camera-channel covariance), white balance and the highlight clip, the colour matrix, the tone curve and the clip to\n * [0, 1] with the skewness term, the candidates' average, the Stein covariance with the closed-form B_c, the independent\n * residual, AHD's choice variance ¼·Var(H − V), the flag for AHD's choice on the noise-free mosaic, which the reference takes and\n * the kernel does not (#457), and the low-signal correction interpolated trilinearly between the grid\n * states of a table the host uploaded (the states themselves are computed on the host, #447; the kernel records every\n * grid key it needs in a bitmap and flags a display pixel whose state the table lacks).\n *\n * **Highlights (#533).** The site pass records whether each site is certainly below white, certainly at or above it, or\n * undecided (`clip_state`, from the site's mean and variance and their running bounds: certain only beyond twice the reference's\n * 8.5 standard deviations after moving the mean by twice its bound, where the reference's clip probability is exactly 0 or 1), in\n * bits 1 and 2 of its flag word. The pixel pass marks a real pixel saturated (P = 1) when a site its demosaic reads is certainly\n * clipped, and the display pixel `highlightUndecided` when a real pixel is neither that nor certainly unclipped. Three flag words\n * per display pixel: the flags, then a bit per real pixel j·nx + i of the footprint with P = 1 (36 bits).\n *\n * **Running bounds.** Every quantity after the site table is carried as a pair (value, absolute bound) in float32: the bound\n * of a sum is the sum of the bounds plus one rounding of the result, of a product |a|·Δb + |b|·Δa plus one rounding, of a\n * square root Δv/(2s) plus 4.5 ULP, of a quotient 2.5 ULP plus the propagated terms; the clipped-normal moments add the\n * Speed kernel's clip constants (CLIP_MEAN_U, CLIP_VARIANCE_U per standard deviation or variance) and propagate their inputs\n * with the moments' first-order derivatives at the pixel's own standardized clip levels (∂E/∂m = P(inside), ∂E/∂s = φa − φb,\n * ∂Var/∂m = −s(ρ_a + ρ_b), ∂Var/∂s = s(2ρ − aρ_a − bρ_b) with ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]), and the slope's\n * (φa − φb)/s and (aφa − bφb)/s; without noise a slope is exact unless the mean's interval straddles a clip level); the\n * Reinhard quadrature adds REINHARD_MEAN_U and REINHARD_VARIANCE_U. Mixed-sign sums (the AHD\n * kernels, the colour matrix) are therefore bounded by the magnitudes of their terms, not relative to their result. The\n * pixel's expected value and variance leave the kernel with their absolute bounds; the host doubles them (MARGIN) and adds\n * the footprint sum's relative term. Second-order terms (products of bounds) and the bound arithmetic's own float32\n * roundings are what the doubling covers.\n *\n * Precision assumptions are those of the Speed kernel: the W3C WGSL accuracy table (+ − × one rounding in either direction,\n * E = 2^-23; division 2.5 ULP, sqrt 4.5 ULP, exp (3 + 2|x|) ULP, log2 2^-21 absolute on [0.5, 2] and 3 ULP outside; no\n * trigonometric built-in).\n */\n\nimport { DISPLAY_VIEW_CLIP_MEAN_U, DISPLAY_VIEW_CLIP_VARIANCE_U, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U } from \"./wgsl-display-view-speed-kernel.js\";\nimport { AHD_KEY_FLOOR, AHD_KEY_LEVELS, AHD_KEY_OCTAVES, AHD_KEY_STEPS } from \"../facade/display-view.js\";\n\nexport const DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE = 64;\n/** Largest footprint per axis (real pixels), as the Speed kernel. */\nexport const DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS = 6;\n/** Uniform parameter words (see `Params`): 56 words, 224 bytes. */\nexport const DISPLAY_VIEW_QUALITY_PARAM_WORDS = 56;\n/** Words per lattice-group record: lx0, ly0, lw, lh, lbase, first tap (the blocked site pass, #492), cu (f32), cv (f32). */\nexport const DISPLAY_VIEW_QUALITY_GROUP_WORDS = 8;\n/** Words per tap record: group, dp offset, dq offset, pad. */\nexport const DISPLAY_VIEW_QUALITY_TAP_WORDS = 4;\n/** Floats per native site of the site buffer: mean, variance, third cumulant, their three bounds, flag (u32 bits), electrons. */\nexport const DISPLAY_VIEW_QUALITY_SITE_FLOATS = 8;\n/** Floats per display pixel in the output buffer: m0 m1 m2, v00 v11 v22, c01 c02 c12, mean bound, variance bound (absolute). */\nexport const DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS = 11;\n/** Floats per column or row record of the geometry buffer (the Speed layout). */\nexport const DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE = 20;\n/** Candidate table: entries per phase (the 7 × 7 window of AHD's reach 3) and floats per entry (dx, dy, dh0, dh1, dh2, dv0, dv1, dv2). */\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES = 49;\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS = 8;\nconst CANDIDATE_PHASE_STRIDE = 4 + DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES * DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS;\n/** Layout of the aux table (float32 words). */\nexport const DISPLAY_VIEW_QUALITY_AUX = /* @__PURE__ */ Object.freeze({\n /** Per phase p, channel c, tap t: dx, dy, weight, 0 (the bilinear kernels, 4 taps; unused taps have weight 0). */\n bilinear: 0,\n /** Per phase p, channel c: 1 / Σ_t w_t² of the bilinear kernel (host float64). */\n inverseW2: 192,\n /** 16 Gauss–Legendre nodes then 16 weights on [−1, 1]. */\n gaussLegendre: 208,\n /** Per phase: the entry count (as u32 bits) and 3 pad words, then the entries (dx, dy, dense weights of candidate 0 and 1 per channel). */\n candidates: 240,\n candidatePhaseStride: CANDIDATE_PHASE_STRIDE,\n size: 240 + 4 * CANDIDATE_PHASE_STRIDE\n});\n/** Pixel flag bits of the output flag word. */\nexport const DISPLAY_VIEW_QUALITY_FLAG = /* @__PURE__ */ Object.freeze({\n /** A site the pixel read had an uncertain lattice material (recompute on the host). */\n materialUncertain: 1,\n /** A site the pixel read lies outside the band's site window (a host geometry error; recompute). */\n siteOutsideWindow: 2,\n /** The correction needed a grid state the table does not hold (recompute, or compute the state and dispatch again). */\n stateMissing: 4,\n /** A sample's pixel variance lies within its bound of the correction threshold (recompute). */\n thresholdTie: 8,\n /** Informational: the pixel's expected value includes the low-signal correction. */\n corrected: 16,\n /**\n * #457: the reference takes AHD's choice on the noise-free mosaic (one candidate, or their average on a tie), which the kernel\n * does not compute; it renders the average. The pixel is flagged (recompute) when Σ_j a_j·½|h(H) − h(V)| of some component\n * exceeds `DISPLAY_VIEW_QUALITY_CHOICE_SIGMA` times the pixel's noise standard deviation.\n */\n ahdChoice: 32,\n /** #533: a real pixel's saturation is undecided (the host computes the display pixel's count with the reference; its samples stay). */\n highlightUndecided: 64,\n /** The bits that require the host's recomputation. */\n recompute: 47\n});\n/** #533: flag words per display pixel: the flags, then the 36-bit mask of saturated real pixels. */\nexport const DISPLAY_VIEW_QUALITY_FLAG_WORDS = 3;\n/**\n * #457: the largest move, in units of the display pixel's own noise σ, that AHD's choice between its candidates may make to a pixel\n * the kernel keeps (it renders the candidates' average and its bound carries the move); a larger one is recomputed on the host. A\n * design tolerance, not a measurement: 2^-10 σ is a tenth of the 0.01 σ to which the float32 tests hold the GPU to the reference.\n */\nexport const DISPLAY_VIEW_QUALITY_CHOICE_SIGMA = 2 ** -10;\n/** Words of the grid-key bitmap: one bit per grid key (AHD_KEY_LEVELS³ keys). */\nexport const DISPLAY_VIEW_QUALITY_BITMAP_WORDS = Math.ceil(AHD_KEY_LEVELS ** 3 / 32);\n/**\n * A site whose uncertain lattice points (the material module's flag) could move its electrons by more than this fraction of\n * their value is flagged for host recomputation; a smaller uncertainty enters the site's bound instead (the tap weights times\n * the largest value difference between any two materials at that tap).\n */\nexport const DISPLAY_VIEW_QUALITY_UNCERTAINTY_LIMIT = 2 ** -10;\n/** Bound constant of a clipped-normal slope Φ(z) (in units of E): z's 8E through φ·|z| ≤ 0.24 plus the Abramowitz–Stegun 147E. */\nexport const DISPLAY_VIEW_QUALITY_SLOPE_U = 200;\n/** Relative bound of the Reinhard slope E[h′] (in units of E). */\nconst REINHARD_SLOPE_U = 64;\n/** The `E` of the bounds: one WGSL rounding of + − × in either direction, 2^-23. */\nconst E_LITERAL = \"1.1920928955078125e-7\";\nconst literal = (value: number): string => {\n const text = String(Math.fround(value));\n return /[.e]/u.test(text) ? text : `${text}.0`;\n};\n\n/** The parameter struct and the bound helpers shared by the three passes. */\nconst COMMON_WGSL = /* wgsl */ `\nconst E: f32 = ${E_LITERAL};\nconst SLACK: f32 = 1e-14;\nconst CLIP_REACH: f32 = 8.5;\nconst CLIP_MEAN_U: f32 = ${literal(DISPLAY_VIEW_CLIP_MEAN_U)};\nconst CLIP_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_CLIP_VARIANCE_U)};\nconst CLIP_SLOPE_U: f32 = ${literal(DISPLAY_VIEW_QUALITY_SLOPE_U)};\nconst REINHARD_MEAN_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_MEAN_U)};\nconst REINHARD_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_VARIANCE_U)};\nconst REINHARD_SLOPE_U: f32 = ${literal(REINHARD_SLOPE_U)};\nconst UNCERTAINTY_LIMIT: f32 = ${literal(2 ** -10)};\nconst STRIDE: u32 = ${DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE}u;\nconst SITE: u32 = ${DISPLAY_VIEW_QUALITY_SITE_FLOATS}u;\nconst GROUP: u32 = ${DISPLAY_VIEW_QUALITY_GROUP_WORDS}u;\nconst TAP: u32 = ${DISPLAY_VIEW_QUALITY_TAP_WORDS}u;\nstruct Params {\n count: u32, row0: u32, region_w: u32, region_x: u32,\n region_y: u32, margin: u32, native_w: u32, native_h: u32,\n columns: u32, tone: u32, reach: u32, candidates: u32,\n site_x0: i32, site_y0: i32, site_w: u32, site_h: u32,\n correction: u32, state_count: u32, taps: u32, groups: u32,\n lattice_total: u32, gain_at_clip0: u32, gain_at_clip1: u32, gain_at_clip2: u32,\n k0: f32, k1: f32, k2: f32, reach2: f32,\n dark0: f32, dark1: f32, dark2: f32, clip_level: f32,\n c0_0: f32, c0_1: f32, c0_2: f32, threshold: f32,\n g0: f32, g1: f32, g2: f32, scale_u: f32,\n m00: f32, m01: f32, m02: f32, m10: f32,\n m11: f32, m12: f32, m20: f32, m21: f32,\n m22: f32, scale_v: f32, site_gamma: f32, site_first: u32,\n site_tiny: f32, pad_f: f32, pad_g: f32, pad_h: f32,\n};\n@group(0) @binding(0) var<uniform> params: Params;\n\nfn k_of(c: u32) -> f32 { if (c == 0u) { return params.k0; } if (c == 1u) { return params.k1; } return params.k2; }\nfn dark_of(c: u32) -> f32 { if (c == 0u) { return params.dark0; } if (c == 1u) { return params.dark1; } return params.dark2; }\nfn c0_of(c: u32) -> f32 { if (c == 0u) { return params.c0_0; } if (c == 1u) { return params.c0_1; } return params.c0_2; }\nfn g_of(c: u32) -> f32 { if (c == 0u) { return params.g0; } if (c == 1u) { return params.g1; } return params.g2; }\n// Whether the channel's white-balance gain is the clip level itself (the smallest gain), exactly, in the reference's float64.\nfn gain_at_clip(c: u32) -> bool { if (c == 0u) { return params.gain_at_clip0 == 1u; } if (c == 1u) { return params.gain_at_clip1 == 1u; } return params.gain_at_clip2 == 1u; }\nfn m_of(r: u32, c: u32) -> f32 {\n let i = r * 3u + c;\n switch (i) {\n case 0u: { return params.m00; } case 1u: { return params.m01; } case 2u: { return params.m02; }\n case 3u: { return params.m10; } case 4u: { return params.m11; } case 5u: { return params.m12; }\n case 6u: { return params.m20; } case 7u: { return params.m21; } default: { return params.m22; }\n }\n}\nfn channel_of(x: i32, y: i32) -> u32 {\n if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }\n return select(2u, 1u, (x & 1) == 0);\n}\n// A value with its absolute error bound (x: value, y: bound). First-order rules; the host doubles the final bound.\nfn b_add(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x + b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_sub(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x - b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x * b.x; return vec2<f32>(v, abs(a.x) * b.y + abs(b.x) * a.y + E * abs(v)); }\n// Times an exact constant (a kernel weight, an integer, a power of two).\nfn b_scale(a: vec2<f32>, k: f32) -> vec2<f32> { let v = a.x * k; return vec2<f32>(v, a.y * abs(k) + E * abs(v)); }\n// #533: 0 when a site of mean m (bound dm) and variance v (bound dv) is certainly below white, 1 certainly at or above it, 2\n// undecided (the Speed kernel's rule: twice the reference's 8.5 standard deviations, the mean moved by twice its bound).\nfn clip_state(m: f32, dm: f32, v: f32, dv: f32) -> u32 {\n let gap = 1.0 - m;\n let wide = 4.0 * params.reach2 * (v + 2.0 * dv);\n let below = gap - 2.0 * dm;\n let above = gap + 2.0 * dm;\n if (below > 0.0 && below * below > wide) { return 0u; }\n if (above < 0.0 && above * above > wide) { return 1u; }\n return 2u;\n}\n// A host constant rounded once to float32 (a gain, a matrix entry).\nfn b_rounded(k: f32) -> vec2<f32> { return vec2<f32>(k, E * abs(k)); }\nfn b_div(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x / b.x; return vec2<f32>(v, (a.y + abs(v) * b.y) / abs(b.x) + 2.5 * E * abs(v)); }\n// |sqrt(v + d) - sqrt(v)| <= min(d / (2 sqrt v), sqrt d), plus the 4.5 ULP of the built-in.\nfn b_sqrt(a: vec2<f32>) -> vec2<f32> {\n let s = sqrt(max(a.x, 0.0));\n if (s > 0.0) { return vec2<f32>(s, min(a.y / (2.0 * s), sqrt(a.y)) + 4.5 * E * s); }\n return vec2<f32>(0.0, sqrt(a.y));\n}\n// Standard normal density, as the reference forms it, and Abramowitz & Stegun 26.2.17 as (Φ, 1 − Φ).\nfn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }\nfn pq(z: f32) -> vec2<f32> {\n let x = abs(z);\n let t = 1.0 / (1.0 + 0.2316419 * x);\n let q = phi(x) * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));\n if (z >= 0.0) { return vec2<f32>(1.0 - q, q); }\n return vec2<f32>(q, 1.0 - q);\n}\nstruct Moments { mean: vec2<f32>, variance: vec2<f32>, slope: vec2<f32> }\n// Moments of min(Y, top) for Y ~ N(m, v) with their bounds: Var = v·(Φ − zφ + z²Q − δ²), δ = φ − zQ (the Speed forms).\nfn upper_clip(m: vec2<f32>, v: vec2<f32>, top: f32) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n // No noise on either side (a zero bound): the reference's slope is 1 or 0 by m < top, decided when m's interval lies on one side.\n let decided = v.y == 0.0 && (m.x - m.y >= top || m.x + m.y < top);\n out.mean = vec2<f32>(min(m.x, top), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x < top), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let z = (top - m.x) / s.x;\n if (z > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n if (z < -CLIP_REACH) { out.mean = vec2<f32>(top, 0.0); out.variance = vec2<f32>(0.0, 0.0); out.slope = vec2<f32>(0.0, 0.0); return out; }\n let p = pq(z);\n let d = phi(z);\n let delta = d - z * p.y;\n let ratio = p.x - z * d + z * z * p.y - delta * delta;\n // First-order propagation of the inputs' bounds: with g = min(Z, z), E[g] = −δ and ρ = Var(g) = ratio, ∂E/∂m = Φ, ∂E/∂s = φ,\n // ∂Var/∂m = −s·ρ_z, ∂Var/∂s = s·(2ρ − z·ρ_z) with ρ_z = 2Q·(z + δ), ∂Φ/∂m = −φ/s and ∂Φ/∂s = −zφ/s.\n let rho_z = 2.0 * p.y * (z + delta);\n out.mean = vec2<f32>(m.x - s.x * delta, p.x * m.y + d * s.y + CLIP_MEAN_U * E * s.x);\n out.variance = vec2<f32>(v.x * max(0.0, ratio), s.x * abs(rho_z) * m.y + s.x * abs(2.0 * ratio - z * rho_z) * s.y + CLIP_VARIANCE_U * E * v.x);\n out.slope = vec2<f32>(p.x, min(1.0, (d * m.y + abs(z) * d * s.y) / s.x + CLIP_SLOPE_U * E));\n return out;\n}\n// Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1), with their bounds (the\n// Speed forms: W = m + s·g(Z), E[g] = aΦa + φa − φb + bQb, E[g²] = a²Φa + (Φb − Φa) − (bφb − aφa) + b²Qb, the Edgeworth\n// shift (s·γ/6)·(aφa − bφb) on the mean and −shift·(2·E[Y] + shift) on the variance).\nfn unit_clip(m: vec2<f32>, v: vec2<f32>, skew: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n let decided = v.y == 0.0 && ((m.x - m.y > 0.0 && m.x + m.y < 1.0) || m.x + m.y <= 0.0 || m.x - m.y >= 1.0);\n out.mean = vec2<f32>(clamp(m.x, 0.0, 1.0), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x > 0.0 && m.x < 1.0), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let a = -m.x / s.x;\n let b = (1.0 - m.x) / s.x;\n if (a < -CLIP_REACH && b > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n var pa = 0.0; var da = 0.0;\n if (!(a < -CLIP_REACH)) { pa = pq(a).x; da = phi(a); }\n var pb = 1.0; var qb = 0.0; var db = 0.0;\n if (!(b > CLIP_REACH)) { let p = pq(b); pb = p.x; qb = p.y; db = phi(b); }\n let inside = pb - pa;\n let gamma = clamp(skew.x, -1.0, 1.0);\n let aa = a * da;\n let bb = b * db;\n let eg = a * pa + (da - db) + b * qb;\n let eg2 = a * a * pa + inside - (bb - aa) + b * b * qb;\n let normal_mean = m.x + s.x * eg;\n var mean = normal_mean;\n var shift = 0.0;\n if (gamma != 0.0) { shift = s.x * gamma / 6.0 * (aa - bb); mean = normal_mean + shift; }\n // |aφa − bφb| ≤ 0.48, so |shift| ≤ 0.08·s·|γ| and a wrong γ moves it by at most 0.16·s.\n let d_shift = 0.1 * s.x * min(2.0, skew.y) + 0.1 * abs(gamma) * s.y + E * abs(shift);\n // First-order propagation of the inputs' bounds: with ρ = E[g²] − E[g]², ∂E[g]/∂a = Φa, ∂E[g]/∂b = Qb, ∂E[g²]/∂a = 2aΦa,\n // ∂E[g²]/∂b = 2bQb, so ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]); ∂E/∂m = inside, ∂E/∂s = φa − φb, ∂Var/∂m = −s(ρ_a + ρ_b),\n // ∂Var/∂s = s(2ρ − aρ_a − bρ_b); the slope's derivatives are (φa − φb)/s and (aφa − bφb)/s.\n let rho = eg2 - eg * eg;\n let rho_a = 2.0 * pa * (a - eg);\n let rho_b = 2.0 * qb * (b - eg);\n let d_mean = m.y * inside + abs(da - db) * s.y + 2.0 * CLIP_MEAN_U * E * s.x + d_shift;\n // The normal part X = v·ρ and its bound; the reference's variance is max(0, X − shift·(2·E[Y] + shift)), so it lies in\n // [0, X_cpu] when the shift term is non-negative on both sides (shift > Δshift), which caps the bound by max(Var, X + ΔX).\n let normal_variance = v.x * rho;\n let d_normal = s.x * abs(rho_a + rho_b) * m.y + s.x * abs(2.0 * rho - a * rho_a - b * rho_b) * s.y + 2.0 * CLIP_VARIANCE_U * E * v.x;\n let variance = max(0.0, normal_variance - shift * (2.0 * normal_mean + shift));\n var d_var = d_normal + 2.0 * (d_shift * (abs(normal_mean) + abs(shift)) + abs(shift) * d_mean);\n if (shift > d_shift) { d_var = min(d_var, max(variance, max(0.0, normal_variance) + d_normal)); }\n out.mean = vec2<f32>(mean, d_mean);\n out.variance = vec2<f32>(variance, d_var);\n out.slope = vec2<f32>(inside, min(1.0, (abs(da - db) * m.y + abs(aa - bb) * s.y) / s.x + 2.0 * CLIP_SLOPE_U * E));\n return out;\n}\n`;\n\n/** The Gauss–Legendre Reinhard moments read the aux table, so they live in the pixel kernel. */\nconst REINHARD_WGSL = /* wgsl */ `\n// Positive Reinhard per channel then the clip to [0, 1]: h(x) = x/(1 + x) for x > 0, else 0, by the reference's composite\n// 16-point Gauss–Legendre rule in standardized units (the Speed forms), with the bounds of the quadrature (REINHARD_*_U)\n// and of its inputs (|h′| ≤ 1, |h″| ≤ 2).\nfn reinhard_moments(m: vec2<f32>, v: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n if (m.x > 0.0) {\n let h = m.x / (1.0 + m.x);\n out.mean = vec2<f32>(h, m.y + 4.0 * E * h); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(1.0 / ((1.0 + m.x) * (1.0 + m.x)), min(1.0, 2.0 * m.y + 6.0 * E));\n return out;\n }\n out.mean = vec2<f32>(0.0, m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0);\n return out;\n }\n let s = b_sqrt(v);\n let zlo = max(-m.x / s.x, -CLIP_REACH);\n if (!(CLIP_REACH > zlo)) { out.mean = vec2<f32>(0.0, m.y + s.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0); return out; }\n let hm = select(0.0, m.x / (1.0 + m.x), m.x > 0.0);\n var e1 = 0.0; var e2 = 0.0; var ed = 0.0;\n let zwidth = (CLIP_REACH - zlo) / 4.0;\n for (var p = 0u; p < 4u; p = p + 1u) {\n let zc = zlo + (f32(p) + 0.5) * zwidth;\n let zhalf = zwidth / 2.0;\n for (var k = 0u; k < 16u; k = k + 1u) {\n let z = zc + zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre}u + k];\n let x = m.x + s.x * z;\n let weight = zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre + 16}u + k] * phi(z);\n let h = x / (1.0 + x);\n e1 = e1 + weight * h;\n e2 = e2 + weight * (h - hm) * (h - hm);\n ed = ed + weight / ((1.0 + x) * (1.0 + x));\n }\n }\n let shifted = e1 - hm;\n let variance = max(0.0, e2 - shifted * shifted);\n out.mean = vec2<f32>(e1, m.y + s.y + REINHARD_MEAN_U * E * abs(e1));\n out.variance = vec2<f32>(variance, 2.0 * s.x * m.y + 2.0 * v.y + REINHARD_VARIANCE_U * E * variance);\n out.slope = vec2<f32>(ed, min(1.0, 2.0 * m.y + 2.0 * s.y + REINHARD_SLOPE_U * E));\n return out;\n}\n`;\n\n/**\n * Pass 1: the material of every lattice point of every group (the module's `material_at`, its uncertainty in bit 31; the output\n * is `lattice_out`, since a material module may declare its own `lattice`, as #491's composite module does). The group\n * records hold each group's lattice window (lx0, ly0, lw, lh), its first index in the lattice buffer and the point constants\n * c_u = 0.5 + f_x − W/2 and c_v = 0.5 + f_y − H/2 (exact in float32, checked by the host).\n */\nexport const DISPLAY_VIEW_QUALITY_LATTICE_WGSL = /* wgsl */ `${COMMON_WGSL}\n@group(0) @binding(1) var<storage, read> groups: array<u32>;\n@group(0) @binding(2) var<storage, read_write> lattice_out: array<u32>;\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.lattice_total) { return; }\n var g = 0u;\n for (var k = 1u; k < params.groups; k = k + 1u) { if (i >= groups[k * GROUP + 4u]) { g = k; } }\n let base = g * GROUP;\n let lx0 = bitcast<i32>(groups[base]);\n let ly0 = bitcast<i32>(groups[base + 1u]);\n let lw = groups[base + 2u];\n let local = i - groups[base + 4u];\n let q = ly0 + i32(local / lw);\n let p = lx0 + i32(local % lw);\n let u = (f32(p) + bitcast<f32>(groups[base + 6u])) * params.scale_u;\n let v = (f32(q) + bitcast<f32>(groups[base + 7u])) * params.scale_v;\n material_uncertain = false;\n let m = material_at(vec2<f32>(u, v));\n lattice_out[i] = m | select(0u, 0x80000000u, material_uncertain);\n}\n`;\n\n/**\n * The end of pass 2, shared by both site passes: from the site's electrons e and their absolute bound de, the readout moments\n * (mean k·e, variance k²e + c₀, third cumulant k³e), the clip at the white level and their bounds, written to site i.\n */\nconst SITE_TAIL_WGSL = /* wgsl */ `\nfn write_site(i: u32, c: u32, e: f32, de: f32, flag: u32) {\n let kc = k_of(c);\n let mean = kc * e;\n let variance = kc * kc * e + c0_of(c);\n let third = kc * kc * kc * e;\n var m = vec2<f32>(mean, kc * de + 2.0 * E * mean);\n var v = vec2<f32>(variance, kc * kc * de + 4.0 * E * variance);\n let gap = 1.0 - mean;\n // #533: bits 1 (certainly clipped) and 2 (undecided) of the flag word.\n let state = clip_state(m.x, m.y, v.x, v.y);\n let bits = flag | select(0u, 2u, state == 1u) | select(0u, 4u, state == 2u);\n if (!(gap > 0.0 && gap * gap > params.reach2 * variance)) {\n let clipped = upper_clip(m, v, 1.0);\n m = clipped.mean; v = clipped.variance;\n }\n let o = i * SITE;\n sites[o] = m.x; sites[o + 1u] = v.x; sites[o + 2u] = third;\n sites[o + 3u] = m.y; sites[o + 4u] = v.y; sites[o + 5u] = kc * kc * kc * de + 5.0 * E * third;\n sites[o + 6u] = bitcast<f32>(bits); sites[o + 7u] = e;\n}\n`;\n\n/**\n * The bindings of the site passes (pass 2); the blocked pass reads no tap records, so it declares no binding 2 (an automatic\n * layout leaves out a binding the entry point does not use, and the host binds it only for the tap-list pass).\n */\nconst siteBindingsWgsl = (tapRecords: boolean): string => /* wgsl */ `\n@group(0) @binding(1) var<storage, read> groups: array<u32>;${tapRecords ? `\n@group(0) @binding(2) var<storage, read> taps: array<u32>;` : \"\"}\n@group(0) @binding(3) var<storage, read> table: array<f32>;\n@group(0) @binding(4) var<storage, read> lattice: array<u32>;\n@group(0) @binding(5) var<storage, read_write> sites: array<f32>;\n@group(0) @binding(6) var<storage, read> spread: array<f32>;\n`;\n\n/**\n * Pass 2: the expected electrons, readout moments and white clip of every native site of the band's window, with their bounds.\n * The site's electrons are Σ_t table[(m_t·3 + c)·T + t] + dark_c, every term non-negative and rounded once on the host, so the\n * sum carries (T + 3)·E relative (`params.site_gamma`, the host's value). An uncertain lattice point adds the tap's value spread\n * over the materials (`spread`, host float64) to the site's uncertainty, which enters its bounds, or flags the site when it\n * exceeds UNCERTAINTY_LIMIT of the electrons. One dispatch covers the `params.count` sites from `params.site_first` (#492: the\n * host splits a band's sites over several dispatches so that no dispatch gathers more than its tap budget).\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_WGSL = /* wgsl */ `${COMMON_WGSL}${siteBindingsWgsl(true)}${SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n var sum = 0.0;\n var uncertainty = 0.0;\n let T = params.taps;\n for (var t = 0u; t < T; t = t + 1u) {\n let tb = t * TAP;\n let g = taps[tb] * GROUP;\n let index = groups[g + 4u] + (jj + taps[tb + 2u]) * groups[g + 2u] + ii + taps[tb + 1u];\n let raw = lattice[index];\n if ((raw >> 31u) != 0u) { uncertainty = uncertainty + spread[c * T + t]; }\n sum = sum + table[((raw & 0x7fffffffu) * 3u + c) * T + t];\n }\n let e = sum + dark_of(c);\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/**\n * Pass 2 with the defocus disc (#492): the same site electrons from a factored table, summed in blocks.\n *\n * - **Factored table.** A neutral material's value is its reflectance times the white's combined kernel (the reference forms it\n * so: reflectance[m]·neutral_c(t)), so the table holds one neutral row per channel and one row per spectral material, and\n * `materials` holds per material its row (0 for neutral, 1 + s for spectral material s) and its reflectance as float32 bits\n * (1 for a spectral material, an exact product). A scene of thousands of neutral materials (#491's composite: 4 143) then\n * needs 3·T values instead of 3·T per material.\n * - **Blocked sum.** Every group's taps form a dense cw × ch rectangle (the combined kernel's window, row-major from the group's\n * first tap, `groups[g + 5]`), so a site sums each row of a group into a row partial, the rows into a group partial and the\n * groups into the total. No tap records are read: the tap's lattice offset within its group is its (dx, dy).\n *\n * Every term is non-negative and carries two host roundings (u each, u = E/2: the row value and the reflectance), one product\n * (E) and at most cw + ch + G additions, plus the dark electrons' addition, so the electrons carry (cw_max + ch_max + G + 3)·E\n * relative (`params.site_gamma`; recursive summation of the up to 4·(2·96 + 2)² = 150 544 taps of a combined radius of 96 would\n * carry (T + 3)·E ≈ 1.8 % instead). The uncertainty, summed the same way, is scaled by 1 + site_gamma, and `params.site_tiny`\n * (host float64, rounded up) bounds the terms below the float32 normal range, which a device may flush to zero.\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL = /* wgsl */ `${COMMON_WGSL}${siteBindingsWgsl(false)}\n@group(0) @binding(7) var<storage, read> materials: array<u32>;\n${SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n let T = params.taps;\n var sum = 0.0;\n var uncertainty = 0.0;\n for (var k = 0u; k < params.groups; k = k + 1u) {\n let g = k * GROUP;\n let lw = groups[g + 2u];\n let cw = lw - params.site_w + 1u;\n let ch = groups[g + 3u] - params.site_h + 1u;\n let lattice0 = groups[g + 4u] + jj * lw + ii;\n let tap0 = groups[g + 5u];\n var group_sum = 0.0;\n var group_uncertainty = 0.0;\n for (var dy = 0u; dy < ch; dy = dy + 1u) {\n let row = lattice0 + dy * lw;\n let row_tap = tap0 + dy * cw;\n var row_sum = 0.0;\n var row_uncertainty = 0.0;\n for (var dx = 0u; dx < cw; dx = dx + 1u) {\n let raw = lattice[row + dx];\n let t = row_tap + dx;\n let m = 2u * (raw & 0x7fffffffu);\n if ((raw >> 31u) != 0u) { row_uncertainty = row_uncertainty + spread[c * T + t]; }\n row_sum = row_sum + bitcast<f32>(materials[m + 1u]) * table[(materials[m] * 3u + c) * T + t];\n }\n group_sum = group_sum + row_sum;\n group_uncertainty = group_uncertainty + row_uncertainty;\n }\n sum = sum + group_sum;\n uncertainty = uncertainty + group_uncertainty;\n }\n let e = sum + dark_of(c);\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty * (1.0 + params.site_gamma) + params.site_tiny;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/** Pass 3: the display pixels (see the module comment). */\nexport const DISPLAY_VIEW_QUALITY_PIXELS_WGSL = /* wgsl */ `${COMMON_WGSL}\nconst KEY_LEVELS: u32 = ${AHD_KEY_LEVELS}u;\nconst KEY_STEPS: f32 = ${literal(AHD_KEY_STEPS)};\nconst KEY_OCTAVES: f32 = ${literal(AHD_KEY_OCTAVES)};\nconst KEY_FLOOR: f32 = ${literal(AHD_KEY_FLOOR)};\nconst KEY_TOP: u32 = ${AHD_KEY_OCTAVES * AHD_KEY_STEPS - 1}u;\nconst CAND_BASE: u32 = ${DISPLAY_VIEW_QUALITY_AUX.candidates}u;\nconst CAND_STRIDE: u32 = ${CANDIDATE_PHASE_STRIDE}u;\nconst FLAG_UNCERTAIN: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.materialUncertain}u;\nconst FLAG_OUTSIDE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.siteOutsideWindow}u;\nconst FLAG_STATE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.stateMissing}u;\nconst FLAG_TIE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.thresholdTie}u;\nconst FLAG_CORRECTED: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.corrected}u;\nconst FLAG_CHOICE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.ahdChoice}u;\nconst FLAG_HIGHLIGHT: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.highlightUndecided}u;\nconst CHOICE_SIGMA: f32 = ${literal(DISPLAY_VIEW_QUALITY_CHOICE_SIGMA)};\n@group(0) @binding(1) var<storage, read> aux: array<f32>;\n@group(0) @binding(2) var<storage, read> geometry: array<f32>;\n@group(0) @binding(3) var<storage, read> sites: array<f32>;\n@group(0) @binding(4) var<storage, read> state_keys: array<i32>;\n@group(0) @binding(5) var<storage, read> state_values: array<f32>;\n@group(0) @binding(6) var<storage, read_write> bitmap: array<atomic<u32>>;\n@group(0) @binding(7) var<storage, read_write> out: array<f32>;\n@group(0) @binding(8) var<storage, read_write> flags: array<u32>;\n\nvar<private> pixel_flags: u32;\n${REINHARD_WGSL}\nfn reflect_x(x: i32) -> i32 { if (x < 0) { return -x; } if (x >= i32(params.native_w)) { return 2 * i32(params.native_w) - 2 - x; } return x; }\nfn reflect_y(y: i32) -> i32 { if (y < 0) { return -y; } if (y >= i32(params.native_h)) { return 2 * i32(params.native_h) - 2 - y; } return y; }\n// The site buffer offset of native site (x, y) (reflected at the raster's borders), or a flag when it lies outside the window.\nfn site_at(x: i32, y: i32) -> u32 {\n let rx = reflect_x(x) - params.site_x0;\n let ry = reflect_y(y) - params.site_y0;\n if (rx < 0 || ry < 0 || rx >= i32(params.site_w) || ry >= i32(params.site_h)) { pixel_flags = pixel_flags | FLAG_OUTSIDE; return 0u; }\n let o = (u32(ry) * params.site_w + u32(rx)) * SITE;\n pixel_flags = pixel_flags | (bitcast<u32>(sites[o + 6u]) & 1u);\n return o;\n}\n// #533: the clip state bits (1 certainly clipped, 2 undecided) of the site at offset o.\nfn clip_bits(o: u32) -> u32 { return (bitcast<u32>(sites[o + 6u]) >> 1u) & 3u; }\n// The offset of a grid key in the sorted state table, or state_count when absent.\nfn find_state(key: i32) -> u32 {\n var lo = 0u; var hi = params.state_count;\n while (lo < hi) {\n let mid = (lo + hi) / 2u;\n if (state_keys[mid] < key) { lo = mid + 1u; } else { hi = mid; }\n }\n if (lo < params.state_count && state_keys[lo] == key) { return lo; }\n return params.state_count;\n}\n// The flat-neighbourhood correction at channel means (m0, m1, m2) with its bound: trilinear between the eight grid states\n// around (log2(clamp(m, 2^-12, 1)) + 12)·8; a missing state flags the pixel. The bound of each axis's coordinate times the\n// largest state difference along that axis bounds the interpolation's dependence on the coordinates' error.\nfn flat_correction(m0: vec2<f32>, m1: vec2<f32>, m2: vec2<f32>, corr: ptr<function, array<vec2<f32>, 3>>) {\n var base: array<u32, 3>; var frac: array<f32, 3>; var dg: array<f32, 3>;\n for (var i = 0u; i < 3u; i = i + 1u) {\n var mi = m0; if (i == 1u) { mi = m1; } if (i == 2u) { mi = m2; }\n let clamped = clamp(mi.x, KEY_FLOOR, 1.0);\n let g = (log2(clamped) + KEY_OCTAVES) * KEY_STEPS;\n let b = min(u32(max(floor(g), 0.0)), KEY_TOP);\n base[i] = b; frac[i] = g - f32(b);\n // log2: 1/ln 2 per relative input error, 2^-21 absolute or 3 ULP (≤ 36E on [2^-12, 1]), the sum and the product.\n var slope = 0.0; if (mi.x > KEY_FLOOR && mi.x < 1.0) { slope = 1.4426950408889634 * mi.y / clamped; }\n dg[i] = KEY_STEPS * (slope + 4.76837158203125e-7 + 36.0 * E) + 2.0 * E * (g + 1.0);\n }\n var values: array<f32, 24>;\n var missing = false;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n let i0 = base[0] + (corner & 1u); let i1 = base[1] + ((corner >> 1u) & 1u); let i2 = base[2] + ((corner >> 2u) & 1u);\n let key = i32((i0 * KEY_LEVELS + i1) * KEY_LEVELS + i2);\n var weight = 1.0;\n for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }\n if (weight == 0.0) { for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = 0.0; } continue; }\n atomicOr(&bitmap[u32(key) / 32u], 1u << (u32(key) % 32u));\n let at = find_state(key);\n if (at >= params.state_count) { missing = true; continue; }\n for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = state_values[at * 3u + c]; }\n }\n if (missing) { pixel_flags = pixel_flags | FLAG_STATE; for (var c = 0u; c < 3u; c = c + 1u) { (*corr)[c] = vec2<f32>(0.0, 0.0); } return; }\n for (var c = 0u; c < 3u; c = c + 1u) {\n var sum = 0.0; var mag = 0.0;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n var weight = 1.0;\n for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }\n sum = sum + weight * values[corner * 3u + c];\n mag = mag + abs(weight * values[corner * 3u + c]);\n }\n // The largest state difference along each axis (over the four edges), times that axis's coordinate bound.\n var slope_term = 0.0;\n for (var i = 0u; i < 3u; i = i + 1u) {\n var largest = 0.0;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n if (((corner >> i) & 1u) == 1u) { continue; }\n largest = max(largest, abs(values[(corner | (1u << i)) * 3u + c] - values[corner * 3u + c]));\n }\n slope_term = slope_term + dg[i] * largest;\n }\n (*corr)[c] = vec2<f32>(sum, slope_term + 12.0 * E * mag);\n }\n}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.count) { return; }\n pixel_flags = 0u;\n let rx = i % params.region_w;\n let ry = params.row0 + i / params.region_w;\n let col = rx * STRIDE;\n let row = (params.columns + ry) * STRIDE;\n let col_first = bitcast<i32>(geometry[col]);\n let nx = bitcast<u32>(geometry[col + 1u]);\n let row_first = bitcast<i32>(geometry[row]);\n let ny = bitcast<u32>(geometry[row + 1u]);\n let margin = i32(params.margin);\n let reinhard = params.tone == 1u;\n let count = params.candidates;\n let share = select(1.0, 0.5, count == 2u);\n // B_c of the footprint from the host's axis sums (each over its scale squared): red, green, blue.\n let xb_even = geometry[col + 8u]; let xb_odd = geometry[col + 9u];\n let xpp_even = geometry[col + 10u]; let xpp_odd = geometry[col + 11u];\n let xqq_even = geometry[col + 12u]; let xqq_odd = geometry[col + 13u];\n let xpq_even = geometry[col + 14u]; let xpq_odd = geometry[col + 15u];\n let yb_even = geometry[row + 8u]; let yb_odd = geometry[row + 9u];\n let ypp_even = geometry[row + 10u]; let ypp_odd = geometry[row + 11u];\n let yqq_even = geometry[row + 12u]; let yqq_odd = geometry[row + 13u];\n let ypq_even = geometry[row + 14u]; let ypq_odd = geometry[row + 15u];\n let b0 = xb_even * yb_even;\n let b1 = (xpp_odd * ypp_even + xqq_odd * ypp_even + xpp_odd * yqq_even + 2.0 * xpq_odd * ypp_even + 2.0 * xpp_odd * ypq_even + 2.0 * xpq_odd * ypq_even) +\n (xpp_even * ypp_odd + xqq_even * ypp_odd + xpp_even * yqq_odd + 2.0 * xpq_even * ypp_odd + 2.0 * xpp_even * ypq_odd + 2.0 * xpq_even * ypq_odd);\n let b2 = xb_odd * yb_odd;\n var s = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cov = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var res = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n // #457: Σ_j a_j·½|h(H) − h(V)| per component, how far the reference's choice can move the value from the candidates' average.\n var choice = array<f32, 3>(0.0, 0.0, 0.0);\n // #533: the saturated real pixels (bit j·nx + i) and whether a real pixel is undecided.\n var mask0 = 0u; var mask1 = 0u; var undecided = false;\n for (var j = 0u; j < ny; j = j + 1u) {\n let ay = geometry[row + 2u + j];\n for (var ii = 0u; ii < nx; ii = ii + 1u) {\n // The overlap weight a_j: two host roundings (the overlaps over the scale) and the product.\n let aj = vec2<f32>(geometry[col + 2u + ii] * ay, 3.0 * E * abs(geometry[col + 2u + ii] * ay));\n let x = col_first + i32(ii) + margin;\n let y = row_first + i32(j) + margin;\n let phase = u32(y & 1) * 2u + u32(x & 1);\n // Site variance of each channel through the bilinear kernels, over Σw² (for B_c).\n var csv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n var variance = vec2<f32>(0.0, 0.0);\n let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n let w = aux[tb + 4u * t + 2u];\n if (w == 0.0) { continue; }\n let o = site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u]));\n variance = b_add(variance, b_scale(vec2<f32>(sites[o + 1u], sites[o + 4u]), w * w));\n }\n csv[c] = b_mul(variance, b_rounded(aux[${DISPLAY_VIEW_QUALITY_AUX.inverseW2}u + phase * 3u + c]));\n }\n let cb = CAND_BASE + phase * CAND_STRIDE;\n let entries = bitcast<u32>(aux[cb]);\n // #533: the sites the demosaic reads (the bilinear kernels and every candidate's entries), as the reference's sample does.\n var clip_any = 0u;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n if (aux[tb + 4u * t + 2u] == 0.0) { continue; }\n clip_any = clip_any | clip_bits(site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u])));\n }\n }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n var used = false;\n for (var k = 0u; k < 3u * count; k = k + 1u) { if (aux[eb + 2u + k] != 0.0) { used = true; } }\n if (used) { clip_any = clip_any | clip_bits(site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]))); }\n }\n if ((clip_any & 1u) != 0u) {\n let bit = j * nx + ii;\n if (bit < 32u) { mask0 = mask0 | (1u << bit); } else { mask1 = mask1 | (1u << (bit - 32u)); }\n } else if ((clip_any & 2u) != 0u) { undecided = true; }\n var e_mean = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cm_average = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var pixel_variance = vec2<f32>(0.0, 0.0);\n var bs: array<vec2<f32>, 3>; var hs: array<vec2<f32>, 3>;\n var hv: array<vec2<f32>, 3>;\n // #457: each candidate's rendered mean, for the reference's choice between them.\n var h_cand = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n for (var d = 0u; d < count; d = d + 1u) {\n // Camera-channel mean and covariance of candidate d from its dense weights over the window.\n var cm: array<vec2<f32>, 3>;\n var C: array<vec2<f32>, 6>;\n // Whether every site a channel reads is exactly at white (a zero bound: the reference's site is exactly 1 too); the\n // dyadic kernel weights then sum exactly on both sides, so the channel's mean carries no error (the host checks the weights).\n var exact = array<bool, 3>(true, true, true);\n for (var c = 0u; c < 3u; c = c + 1u) { cm[c] = vec2<f32>(0.0, 0.0); }\n for (var k = 0u; k < 6u; k = k + 1u) { C[k] = vec2<f32>(0.0, 0.0); }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let sm = vec2<f32>(sites[o], sites[o + 3u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let white = sm.y == 0.0 && sm.x == 1.0;\n exact[0] = exact[0] && (white || w0 == 0.0); exact[1] = exact[1] && (white || w1 == 0.0); exact[2] = exact[2] && (white || w2 == 0.0);\n cm[0] = b_add(cm[0], b_scale(sm, w0)); cm[1] = b_add(cm[1], b_scale(sm, w1)); cm[2] = b_add(cm[2], b_scale(sm, w2));\n C[0] = b_add(C[0], b_scale(sv, w0 * w0)); C[1] = b_add(C[1], b_scale(sv, w0 * w1)); C[2] = b_add(C[2], b_scale(sv, w0 * w2));\n C[3] = b_add(C[3], b_scale(sv, w1 * w1)); C[4] = b_add(C[4], b_scale(sv, w1 * w2)); C[5] = b_add(C[5], b_scale(sv, w2 * w2));\n }\n for (var c = 0u; c < 3u; c = c + 1u) { if (exact[c]) { cm[c].y = 0.0; } cm_average[c] = b_add(cm_average[c], b_scale(cm[c], share)); }\n // White balance and the highlight clip at the smallest gain. A channel exactly at white whose gain is the clip level is\n // exactly at the clip level on both sides.\n var bm: array<vec2<f32>, 3>; var bv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let gc = b_rounded(g_of(c));\n var um = b_mul(gc, cm[c]);\n if (exact[c] && cm[c].x == 1.0 && gain_at_clip(c)) { um = vec2<f32>(params.clip_level, 0.0); }\n let uv = b_mul(b_mul(gc, gc), C[select(select(0u, 3u, c == 1u), 5u, c == 2u)]);\n let gap = params.clip_level - um.x;\n if (gap > 0.0 && gap * gap > params.reach2 * uv.x) { bm[c] = um; bv[c] = uv; bs[c] = gc; }\n else { let clipped = upper_clip(um, uv, params.clip_level); bm[c] = clipped.mean; bv[c] = clipped.variance; bs[c] = b_mul(gc, clipped.slope); }\n }\n // Colour matrix with the full channel covariance, then the rendering of each component.\n var hm: array<vec2<f32>, 3>;\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let r0 = b_rounded(m_of(comp, 0u)); let r1 = b_rounded(m_of(comp, 1u)); let r2 = b_rounded(m_of(comp, 2u));\n let mean = b_add(b_add(b_mul(r0, bm[0]), b_mul(r1, bm[1])), b_mul(r2, bm[2]));\n var variance = b_add(b_add(b_mul(b_mul(r0, r0), bv[0]), b_mul(b_mul(r1, r1), bv[1])), b_mul(b_mul(r2, r2), bv[2]));\n let x01 = b_mul(b_mul(b_mul(r0, r1), b_mul(bs[0], bs[1])), C[1]);\n let x02 = b_mul(b_mul(b_mul(r0, r2), b_mul(bs[0], bs[2])), C[2]);\n let x12 = b_mul(b_mul(b_mul(r1, r2), b_mul(bs[1], bs[2])), C[4]);\n variance = b_add(variance, b_scale(b_add(b_add(x01, x02), x12), 2.0));\n var moments: Moments;\n if (!reinhard && mean.x > 0.0 && mean.x < 1.0 && mean.x * mean.x > params.reach2 * variance.x && (1.0 - mean.x) * (1.0 - mean.x) > params.reach2 * variance.x) {\n moments.mean = mean; moments.variance = variance; moments.slope = vec2<f32>(1.0, 0.0);\n } else if (reinhard) {\n moments = reinhard_moments(mean, variance);\n } else {\n // κ₃ of the component: Σ_sites (Σ_c M_comp,c·Γ_c·w_c,site)³·κ₃(site), over the candidate's dense weights.\n var third = vec2<f32>(0.0, 0.0);\n let k0 = b_mul(r0, bs[0]); let k1 = b_mul(r1, bs[1]); let k2 = b_mul(r2, bs[2]);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let coefficient = b_add(b_add(b_scale(k0, w0), b_scale(k1, w1)), b_scale(k2, w2));\n let cube = b_mul(b_mul(coefficient, coefficient), coefficient);\n third = b_add(third, b_mul(cube, vec2<f32>(sites[o + 2u], sites[o + 5u])));\n }\n var skew = vec2<f32>(0.0, 0.0);\n if (variance.x > 0.0) { skew = b_div(third, b_mul(variance, b_sqrt(variance))); }\n moments = unit_clip(mean, variance, skew);\n }\n hm[comp] = moments.mean; hv[comp] = moments.variance; hs[comp] = moments.slope;\n h_cand[d * 3u + comp] = moments.mean;\n e_mean[comp] = b_add(e_mean[comp], b_scale(moments.mean, share));\n pixel_variance = vec2<f32>(max(pixel_variance.x, moments.variance.x), max(pixel_variance.y, moments.variance.y));\n }\n // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance aᵀCa for the residual.\n var an: array<vec2<f32>, 9>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n an[c] = b_mul(b_mul(hs[0], b_rounded(m_of(0u, c))), bs[c]);\n an[3u + c] = b_mul(b_mul(hs[1], b_rounded(m_of(1u, c))), bs[c]);\n an[6u + c] = b_mul(b_mul(hs[2], b_rounded(m_of(2u, c))), bs[c]);\n var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }\n let wgt = b_mul(b_mul(b_scale(aj, share), b_rounded(bc)), csv[c]);\n cov[0] = b_add(cov[0], b_mul(b_mul(wgt, an[c]), an[c])); cov[1] = b_add(cov[1], b_mul(b_mul(wgt, an[c]), an[3u + c])); cov[2] = b_add(cov[2], b_mul(b_mul(wgt, an[c]), an[6u + c]));\n cov[3] = b_add(cov[3], b_mul(b_mul(wgt, an[3u + c]), an[3u + c])); cov[4] = b_add(cov[4], b_mul(b_mul(wgt, an[3u + c]), an[6u + c])); cov[5] = b_add(cov[5], b_mul(b_mul(wgt, an[6u + c]), an[6u + c]));\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n var l = vec2<f32>(0.0, 0.0);\n for (var p = 0u; p < 3u; p = p + 1u) {\n for (var q = 0u; q < 3u; q = q + 1u) {\n var cij = C[0]; if (p + q == 1u) { cij = C[1]; } if ((p == 0u && q == 2u) || (p == 2u && q == 0u)) { cij = C[2]; } if (p == 1u && q == 1u) { cij = C[3]; }\n if ((p == 1u && q == 2u) || (p == 2u && q == 1u)) { cij = C[4]; } if (p == 2u && q == 2u) { cij = C[5]; }\n l = b_add(l, b_mul(b_mul(an[comp * 3u + p], an[comp * 3u + q]), cij));\n }\n }\n let t = vec2<f32>(share * max(0.0, hv[comp].x - l.x), share * (hv[comp].y + l.y));\n res[comp] = b_add(res[comp], b_mul(b_mul(aj, aj), t));\n }\n }\n if (count == 2u) {\n // #457: the reference takes AHD's choice on the noise-free mosaic, one candidate or (a tie) their average, so its value\n // lies within half the candidates' difference plus their bounds of the average: the bound carries that, and the move\n // accumulates in choice, which flags the pixel below when it exceeds CHOICE_SIGMA of the pixel's noise σ.\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let diff = abs(h_cand[comp].x - h_cand[3u + comp].x);\n let both = h_cand[comp].y + h_cand[3u + comp].y;\n choice[comp] = choice[comp] + aj.x * 0.5 * diff;\n e_mean[comp].y = e_mean[comp].y + 0.5 * (diff + both);\n }\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, e_mean[comp])); }\n if (count == 2u) {\n // AHD's per-pixel choice: ¼·Var(H − V) at the last candidate's slopes, linear in the sites.\n var x0 = vec2<f32>(0.0, 0.0); var x1 = vec2<f32>(0.0, 0.0); var x2 = vec2<f32>(0.0, 0.0);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n let o = site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]));\n let d0 = b_scale(bs[0], aux[eb + 2u] - aux[eb + 5u]);\n let d1 = b_scale(bs[1], aux[eb + 3u] - aux[eb + 6u]);\n let d2 = b_scale(bs[2], aux[eb + 4u] - aux[eb + 7u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let a = b_mul(hs[0], b_add(b_add(b_mul(b_rounded(params.m00), d0), b_mul(b_rounded(params.m01), d1)), b_mul(b_rounded(params.m02), d2)));\n let b = b_mul(hs[1], b_add(b_add(b_mul(b_rounded(params.m10), d0), b_mul(b_rounded(params.m11), d1)), b_mul(b_rounded(params.m12), d2)));\n let c = b_mul(hs[2], b_add(b_add(b_mul(b_rounded(params.m20), d0), b_mul(b_rounded(params.m21), d1)), b_mul(b_rounded(params.m22), d2)));\n x0 = b_add(x0, b_mul(b_mul(a, a), sv)); x1 = b_add(x1, b_mul(b_mul(b, b), sv)); x2 = b_add(x2, b_mul(b_mul(c, c), sv));\n }\n let ajaj = b_mul(aj, aj);\n res[0] = b_add(res[0], b_mul(ajaj, b_scale(x0, 0.25))); res[1] = b_add(res[1], b_mul(ajaj, b_scale(x1, 0.25))); res[2] = b_add(res[2], b_mul(ajaj, b_scale(x2, 0.25)));\n }\n if (params.correction == 1u) {\n // The low-signal correction where the pixel noise exceeds the threshold; a decision within its bound is flagged.\n if (abs(pixel_variance.x - params.threshold) <= pixel_variance.y) { pixel_flags = pixel_flags | FLAG_TIE; }\n if (pixel_variance.x > params.threshold) {\n var corr: array<vec2<f32>, 3>;\n flat_correction(cm_average[0], cm_average[1], cm_average[2], &corr);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, corr[comp])); }\n pixel_flags = pixel_flags | FLAG_CORRECTED;\n }\n }\n }\n }\n // #457: a pixel whose candidates' choice could move it by more than CHOICE_SIGMA of its own noise σ is recomputed on the host.\n if (choice[0] > CHOICE_SIGMA * sqrt(max(cov[0].x + res[0].x, 0.0)) || choice[1] > CHOICE_SIGMA * sqrt(max(cov[3].x + res[1].x, 0.0)) ||\n choice[2] > CHOICE_SIGMA * sqrt(max(cov[5].x + res[2].x, 0.0))) { pixel_flags = pixel_flags | FLAG_CHOICE; }\n let o = i * ${DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS}u;\n out[o] = s[0].x; out[o + 1u] = s[1].x; out[o + 2u] = s[2].x;\n out[o + 3u] = cov[0].x + res[0].x; out[o + 4u] = cov[3].x + res[1].x; out[o + 5u] = cov[5].x + res[2].x;\n out[o + 6u] = cov[1].x; out[o + 7u] = cov[2].x; out[o + 8u] = cov[4].x;\n out[o + 9u] = max(s[0].y, max(s[1].y, s[2].y));\n out[o + 10u] = max(max(cov[0].y + res[0].y, cov[3].y + res[1].y), max(cov[5].y + res[2].y, max(cov[1].y, max(cov[2].y, cov[4].y))));\n flags[3u * i] = pixel_flags | select(0u, FLAG_HIGHLIGHT, undecided);\n flags[3u * i + 1u] = mask0;\n flags[3u * i + 2u] = mask1;\n}\n`;\n\n/** The lattice pass with a material module appended (`fn material_at(p: vec2<f32>) -> u32`, `var<private> material_uncertain: bool`). */\nexport function assembleDisplayViewQualityLatticeWgsl(materialModuleWgsl: string): string {\n return DISPLAY_VIEW_QUALITY_LATTICE_WGSL + \"\\n\" + materialModuleWgsl + \"\\n\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"wgsl-display-view-quality-kernel.js","sourceRoot":"","sources":["../../src/gpu/wgsl-display-view-quality-kernel.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * WGSL kernels of the display view's Quality path on the GPU (#479): the exact area mean with diffraction kernels, the\n * anti-aliasing filter's spots, the AHD candidates and the low-signal correction, in three passes over one band of display\n * rows (docs/WEBGPU.md, \"Display view (Quality) on the GPU\"; the float64 reference is `renderDisplayView` with\n * `createDisplayViewExpectedSignal`, facade/display-view.ts and facade/expected-signal.ts):\n *\n * 1. **Lattice** (`DISPLAY_VIEW_QUALITY_LATTICE_WGSL` plus the material module): one invocation per lattice point of each\n * lattice group (offsets that share a fractional shift, as the reference groups them): the chart material at\n * (p + 0.5 + f_x − W/2)·g, −(q + 0.5 + f_y − H/2)·g (the reference's lattice formula, an exact float32 sum times the\n * scale rounded once), with the module's uncertainty flag in the top bit.\n * 2. **Sites** (`DISPLAY_VIEW_QUALITY_SITES_WGSL`): one invocation per native site of the band's window: the expected\n * electrons Σ_t V_c(m_t, t) over the taps t of every group (the combined kernel values per material and channel, formed on\n * the host in float64 as the reference forms them: Σ_members w·Σ_λ A_c(λ)·K_λ·L_m(λ); without diffraction the offset\n * weights times the material's channel signal), plus the dark electrons; the readout moments (mean k·e, variance k²e + c₀,\n * third cumulant k³e) and the clip at the white level; and each quantity's running absolute error bound. With the defocus\n * disc (#492) the variant `DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL` reads a factored table (one neutral row, one row per\n * spectral material) and sums each group's dense tap rectangle in blocks, so its bound grows with the kernel's width.\n * 3. **Pixels** (`DISPLAY_VIEW_QUALITY_PIXELS_WGSL`): one invocation per display pixel of the band: for every real pixel of\n * the footprint, the demosaic candidates (one bilinear, or AHD's two directional linear candidates, each with its full\n * camera-channel covariance), white balance and the highlight clip, the colour matrix, the tone curve and the clip to\n * [0, 1] with the skewness term, the candidates' average, the Stein covariance with the closed-form B_c, the independent\n * residual, AHD's choice variance ¼·Var(H − V), the flag for AHD's choice on the noise-free mosaic, which the reference takes and\n * the kernel does not (#457), and the low-signal correction interpolated trilinearly between the grid\n * states of a table the host uploaded (the states themselves are computed on the host, #447; the kernel records every\n * grid key it needs in a bitmap and flags a display pixel whose state the table lacks).\n *\n * **Highlights (#533).** The site pass records whether each site is certainly below white, certainly at or above it, or\n * undecided (`clip_state`, from the site's mean and variance and their running bounds: certain only beyond twice the reference's\n * 8.5 standard deviations after moving the mean by twice its bound, where the reference's clip probability is exactly 0 or 1), in\n * bits 1 and 2 of its flag word. The pixel pass marks a real pixel saturated (P = 1) when a site its demosaic reads is certainly\n * clipped, and the display pixel `highlightUndecided` when a real pixel is neither that nor certainly unclipped. Three flag words\n * per display pixel: the flags, then a bit per real pixel j·nx + i of the footprint with P = 1 (36 bits).\n *\n * **Running bounds.** Every quantity after the site table is carried as a pair (value, absolute bound) in float32: the bound\n * of a sum is the sum of the bounds plus one rounding of the result, of a product |a|·Δb + |b|·Δa plus one rounding, of a\n * square root Δv/(2s) plus 4.5 ULP, of a quotient 2.5 ULP plus the propagated terms; the clipped-normal moments add the\n * Speed kernel's clip constants (CLIP_MEAN_U, CLIP_VARIANCE_U per standard deviation or variance) and propagate their inputs\n * with the moments' first-order derivatives at the pixel's own standardized clip levels (∂E/∂m = P(inside), ∂E/∂s = φa − φb,\n * ∂Var/∂m = −s(ρ_a + ρ_b), ∂Var/∂s = s(2ρ − aρ_a − bρ_b) with ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]), and the slope's\n * (φa − φb)/s and (aφa − bφb)/s; without noise a slope is exact unless the mean's interval straddles a clip level); the\n * Reinhard quadrature adds REINHARD_MEAN_U and REINHARD_VARIANCE_U. Mixed-sign sums (the AHD\n * kernels, the colour matrix) are therefore bounded by the magnitudes of their terms, not relative to their result. The\n * pixel's expected value and variance leave the kernel with their absolute bounds; the host doubles them (MARGIN) and adds\n * the footprint sum's relative term. Second-order terms (products of bounds) and the bound arithmetic's own float32\n * roundings are what the doubling covers.\n *\n * Precision assumptions are those of the Speed kernel: the W3C WGSL accuracy table (+ − × one rounding in either direction,\n * E = 2^-23; division 2.5 ULP, sqrt 4.5 ULP, exp (3 + 2|x|) ULP, log2 2^-21 absolute on [0.5, 2] and 3 ULP outside; no\n * trigonometric built-in).\n */\n\nimport { DISPLAY_VIEW_CLIP_MEAN_U, DISPLAY_VIEW_CLIP_VARIANCE_U, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U } from \"./wgsl-display-view-speed-kernel.js\";\nimport { AHD_KEY_FLOOR, AHD_KEY_LEVELS, AHD_KEY_OCTAVES, AHD_KEY_STEPS } from \"../facade/display-view.js\";\nimport { EXACT_SUM_WGSL } from \"./wgsl-exact-sum.js\";\n\nexport const DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE = 64;\n/** Largest footprint per axis (real pixels), as the Speed kernel. */\nexport const DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS = 6;\n/** Most spectral nodes the site passes integrate per site (2.21.0: the per-node partials are private arrays of this length). */\nexport const DISPLAY_VIEW_QUALITY_MAXIMUM_NODES = 96;\n/** Uniform parameter words (see `Params`): 56 words, 224 bytes. */\nexport const DISPLAY_VIEW_QUALITY_PARAM_WORDS = 56;\n/** Words per lattice-group record: lx0, ly0, lw, lh, lbase, first tap (the blocked site pass, #492), cu (f32), cv (f32). */\nexport const DISPLAY_VIEW_QUALITY_GROUP_WORDS = 8;\n/** Words per tap record: group, dp offset, dq offset, pad. */\nexport const DISPLAY_VIEW_QUALITY_TAP_WORDS = 4;\n/** Floats per native site of the site buffer: mean, variance, third cumulant, their three bounds, flag (u32 bits), electrons. */\nexport const DISPLAY_VIEW_QUALITY_SITE_FLOATS = 8;\n/** Floats per display pixel in the output buffer: m0 m1 m2, v00 v11 v22, c01 c02 c12, mean bound, variance bound (absolute). */\nexport const DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS = 11;\n/** Floats per column or row record of the geometry buffer (the Speed layout). */\nexport const DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE = 20;\n/** Candidate table: entries per phase (the 7 × 7 window of AHD's reach 3) and floats per entry (dx, dy, dh0, dh1, dh2, dv0, dv1, dv2). */\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES = 49;\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS = 8;\nconst CANDIDATE_PHASE_STRIDE = 4 + DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES * DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS;\n/** Layout of the aux table (float32 words). */\nexport const DISPLAY_VIEW_QUALITY_AUX = /* @__PURE__ */ Object.freeze({\n /** Per phase p, channel c, tap t: dx, dy, weight, 0 (the bilinear kernels, 4 taps; unused taps have weight 0). */\n bilinear: 0,\n /** Per phase p, channel c: 1 / Σ_t w_t² of the bilinear kernel (host float64). */\n inverseW2: 192,\n /** 16 Gauss–Legendre nodes then 16 weights on [−1, 1]. */\n gaussLegendre: 208,\n /** Per phase: the entry count (as u32 bits) and 3 pad words, then the entries (dx, dy, dense weights of candidate 0 and 1 per channel). */\n candidates: 240,\n candidatePhaseStride: CANDIDATE_PHASE_STRIDE,\n size: 240 + 4 * CANDIDATE_PHASE_STRIDE\n});\n/** Pixel flag bits of the output flag word. */\nexport const DISPLAY_VIEW_QUALITY_FLAG = /* @__PURE__ */ Object.freeze({\n /** A site the pixel read had an uncertain lattice material (recompute on the host). */\n materialUncertain: 1,\n /** A site the pixel read lies outside the band's site window (a host geometry error; recompute). */\n siteOutsideWindow: 2,\n /** The correction needed a grid state the table does not hold (recompute, or compute the state and dispatch again). */\n stateMissing: 4,\n /** A sample's pixel variance lies within its bound of the correction threshold (recompute). */\n thresholdTie: 8,\n /** Informational: the pixel's expected value includes the low-signal correction. */\n corrected: 16,\n /**\n * #457: the reference takes AHD's choice on the noise-free mosaic (one candidate, or their average on a tie), which the kernel\n * does not compute; it renders the average. The pixel is flagged (recompute) when Σ_j a_j·½|h(H) − h(V)| of some component\n * exceeds `DISPLAY_VIEW_QUALITY_CHOICE_SIGMA` times the pixel's noise standard deviation.\n */\n ahdChoice: 32,\n /** #533: a real pixel's saturation is undecided (the host computes the display pixel's count with the reference; its samples stay). */\n highlightUndecided: 64,\n /** The bits that require the host's recomputation. */\n recompute: 47\n});\n/** #533: flag words per display pixel: the flags, then the 36-bit mask of saturated real pixels. */\nexport const DISPLAY_VIEW_QUALITY_FLAG_WORDS = 3;\n/**\n * #457: the largest move, in units of the display pixel's own noise σ, that AHD's choice between its candidates may make to a pixel\n * the kernel keeps (it renders the candidates' average and its bound carries the move); a larger one is recomputed on the host. A\n * design tolerance, not a measurement: 2^-10 σ is a tenth of the 0.01 σ to which the float32 tests hold the GPU to the reference.\n */\nexport const DISPLAY_VIEW_QUALITY_CHOICE_SIGMA = 2 ** -10;\n/** Words of the grid-key bitmap: one bit per grid key (AHD_KEY_LEVELS³ keys). */\nexport const DISPLAY_VIEW_QUALITY_BITMAP_WORDS = Math.ceil(AHD_KEY_LEVELS ** 3 / 32);\n/**\n * A site whose uncertain lattice points (the material module's flag) could move its electrons by more than this fraction of\n * their value is flagged for host recomputation; a smaller uncertainty enters the site's bound instead (the tap weights times\n * the largest value difference between any two materials at that tap).\n */\nexport const DISPLAY_VIEW_QUALITY_UNCERTAINTY_LIMIT = 2 ** -10;\n/** Bound constant of a clipped-normal slope Φ(z) (in units of E): z's 8E through φ·|z| ≤ 0.24 plus the Abramowitz–Stegun 147E. */\nexport const DISPLAY_VIEW_QUALITY_SLOPE_U = 200;\n/** Relative bound of the Reinhard slope E[h′] (in units of E). */\nconst REINHARD_SLOPE_U = 64;\n/** The `E` of the bounds: one WGSL rounding of + − × in either direction, 2^-23. */\nconst E_LITERAL = \"1.1920928955078125e-7\";\nconst literal = (value: number): string => {\n const text = String(Math.fround(value));\n return /[.e]/u.test(text) ? text : `${text}.0`;\n};\n\n/** The parameter struct and the bound helpers shared by the three passes. */\nexport const DISPLAY_VIEW_QUALITY_COMMON_WGSL = /* wgsl */ `\nconst E: f32 = ${E_LITERAL};\nconst SLACK: f32 = 1e-14;\nconst CLIP_REACH: f32 = 8.5;\nconst CLIP_MEAN_U: f32 = ${literal(DISPLAY_VIEW_CLIP_MEAN_U)};\nconst CLIP_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_CLIP_VARIANCE_U)};\nconst CLIP_SLOPE_U: f32 = ${literal(DISPLAY_VIEW_QUALITY_SLOPE_U)};\nconst REINHARD_MEAN_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_MEAN_U)};\nconst REINHARD_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_VARIANCE_U)};\nconst REINHARD_SLOPE_U: f32 = ${literal(REINHARD_SLOPE_U)};\nconst UNCERTAINTY_LIMIT: f32 = ${literal(2 ** -10)};\nconst STRIDE: u32 = ${DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE}u;\nconst SITE: u32 = ${DISPLAY_VIEW_QUALITY_SITE_FLOATS}u;\nconst GROUP: u32 = ${DISPLAY_VIEW_QUALITY_GROUP_WORDS}u;\nconst TAP: u32 = ${DISPLAY_VIEW_QUALITY_TAP_WORDS}u;\nstruct Params {\n count: u32, row0: u32, region_w: u32, region_x: u32,\n region_y: u32, margin: u32, native_w: u32, native_h: u32,\n columns: u32, tone: u32, reach: u32, candidates: u32,\n site_x0: i32, site_y0: i32, site_w: u32, site_h: u32,\n correction: u32, state_count: u32, taps: u32, groups: u32,\n lattice_total: u32, gain_at_clip0: u32, gain_at_clip1: u32, gain_at_clip2: u32,\n k0: f32, k1: f32, k2: f32, reach2: f32,\n dark0: f32, dark1: f32, dark2: f32, clip_level: f32,\n c0_0: f32, c0_1: f32, c0_2: f32, threshold: f32,\n g0: f32, g1: f32, g2: f32, scale_u: f32,\n m00: f32, m01: f32, m02: f32, m10: f32,\n m11: f32, m12: f32, m20: f32, m21: f32,\n m22: f32, scale_v: f32, site_gamma: f32, site_first: u32,\n site_tiny: f32, nodes: u32, pad_g: f32, pad_h: f32,\n};\n@group(0) @binding(0) var<uniform> params: Params;\n\nfn k_of(c: u32) -> f32 { if (c == 0u) { return params.k0; } if (c == 1u) { return params.k1; } return params.k2; }\nfn dark_of(c: u32) -> f32 { if (c == 0u) { return params.dark0; } if (c == 1u) { return params.dark1; } return params.dark2; }\nfn c0_of(c: u32) -> f32 { if (c == 0u) { return params.c0_0; } if (c == 1u) { return params.c0_1; } return params.c0_2; }\nfn g_of(c: u32) -> f32 { if (c == 0u) { return params.g0; } if (c == 1u) { return params.g1; } return params.g2; }\n// Whether the channel's white-balance gain is the clip level itself (the smallest gain), exactly, in the reference's float64.\nfn gain_at_clip(c: u32) -> bool { if (c == 0u) { return params.gain_at_clip0 == 1u; } if (c == 1u) { return params.gain_at_clip1 == 1u; } return params.gain_at_clip2 == 1u; }\nfn m_of(r: u32, c: u32) -> f32 {\n let i = r * 3u + c;\n switch (i) {\n case 0u: { return params.m00; } case 1u: { return params.m01; } case 2u: { return params.m02; }\n case 3u: { return params.m10; } case 4u: { return params.m11; } case 5u: { return params.m12; }\n case 6u: { return params.m20; } case 7u: { return params.m21; } default: { return params.m22; }\n }\n}\nfn channel_of(x: i32, y: i32) -> u32 {\n if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }\n return select(2u, 1u, (x & 1) == 0);\n}\n// A value with its absolute error bound (x: value, y: bound). First-order rules; the host doubles the final bound.\nfn b_add(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x + b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_sub(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x - b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x * b.x; return vec2<f32>(v, abs(a.x) * b.y + abs(b.x) * a.y + E * abs(v)); }\n// Times an exact constant (a kernel weight, an integer, a power of two).\nfn b_scale(a: vec2<f32>, k: f32) -> vec2<f32> { let v = a.x * k; return vec2<f32>(v, a.y * abs(k) + E * abs(v)); }\n// #533: 0 when a site of mean m (bound dm) and variance v (bound dv) is certainly below white, 1 certainly at or above it, 2\n// undecided (the Speed kernel's rule: twice the reference's 8.5 standard deviations, the mean moved by twice its bound).\nfn clip_state(m: f32, dm: f32, v: f32, dv: f32) -> u32 {\n let gap = 1.0 - m;\n let wide = 4.0 * params.reach2 * (v + 2.0 * dv);\n let below = gap - 2.0 * dm;\n let above = gap + 2.0 * dm;\n if (below > 0.0 && below * below > wide) { return 0u; }\n if (above < 0.0 && above * above > wide) { return 1u; }\n return 2u;\n}\n// A host constant rounded once to float32 (a gain, a matrix entry).\nfn b_rounded(k: f32) -> vec2<f32> { return vec2<f32>(k, E * abs(k)); }\nfn b_div(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x / b.x; return vec2<f32>(v, (a.y + abs(v) * b.y) / abs(b.x) + 2.5 * E * abs(v)); }\n// |sqrt(v + d) - sqrt(v)| <= min(d / (2 sqrt v), sqrt d), plus the 4.5 ULP of the built-in.\nfn b_sqrt(a: vec2<f32>) -> vec2<f32> {\n let s = sqrt(max(a.x, 0.0));\n if (s > 0.0) { return vec2<f32>(s, min(a.y / (2.0 * s), sqrt(a.y)) + 4.5 * E * s); }\n return vec2<f32>(0.0, sqrt(a.y));\n}\n// Standard normal density, as the reference forms it, and Abramowitz & Stegun 26.2.17 as (Φ, 1 − Φ).\nfn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }\nfn pq(z: f32) -> vec2<f32> {\n let x = abs(z);\n let t = 1.0 / (1.0 + 0.2316419 * x);\n let q = phi(x) * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));\n if (z >= 0.0) { return vec2<f32>(1.0 - q, q); }\n return vec2<f32>(q, 1.0 - q);\n}\nstruct Moments { mean: vec2<f32>, variance: vec2<f32>, slope: vec2<f32> }\n// Moments of min(Y, top) for Y ~ N(m, v) with their bounds: Var = v·(Φ − zφ + z²Q − δ²), δ = φ − zQ (the Speed forms).\nfn upper_clip(m: vec2<f32>, v: vec2<f32>, top: f32) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n // No noise on either side (a zero bound): the reference's slope is 1 or 0 by m < top, decided when m's interval lies on one side.\n let decided = v.y == 0.0 && (m.x - m.y >= top || m.x + m.y < top);\n out.mean = vec2<f32>(min(m.x, top), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x < top), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let z = (top - m.x) / s.x;\n if (z > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n if (z < -CLIP_REACH) { out.mean = vec2<f32>(top, 0.0); out.variance = vec2<f32>(0.0, 0.0); out.slope = vec2<f32>(0.0, 0.0); return out; }\n let p = pq(z);\n let d = phi(z);\n let delta = d - z * p.y;\n let ratio = p.x - z * d + z * z * p.y - delta * delta;\n // First-order propagation of the inputs' bounds: with g = min(Z, z), E[g] = −δ and ρ = Var(g) = ratio, ∂E/∂m = Φ, ∂E/∂s = φ,\n // ∂Var/∂m = −s·ρ_z, ∂Var/∂s = s·(2ρ − z·ρ_z) with ρ_z = 2Q·(z + δ), ∂Φ/∂m = −φ/s and ∂Φ/∂s = −zφ/s.\n let rho_z = 2.0 * p.y * (z + delta);\n out.mean = vec2<f32>(m.x - s.x * delta, p.x * m.y + d * s.y + CLIP_MEAN_U * E * s.x);\n out.variance = vec2<f32>(v.x * max(0.0, ratio), s.x * abs(rho_z) * m.y + s.x * abs(2.0 * ratio - z * rho_z) * s.y + CLIP_VARIANCE_U * E * v.x);\n out.slope = vec2<f32>(p.x, min(1.0, (d * m.y + abs(z) * d * s.y) / s.x + CLIP_SLOPE_U * E));\n return out;\n}\n// Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1), with their bounds (the\n// Speed forms: W = m + s·g(Z), E[g] = aΦa + φa − φb + bQb, E[g²] = a²Φa + (Φb − Φa) − (bφb − aφa) + b²Qb, the Edgeworth\n// shift (s·γ/6)·(aφa − bφb) on the mean and −shift·(2·E[Y] + shift) on the variance).\nfn unit_clip(m: vec2<f32>, v: vec2<f32>, skew: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n let decided = v.y == 0.0 && ((m.x - m.y > 0.0 && m.x + m.y < 1.0) || m.x + m.y <= 0.0 || m.x - m.y >= 1.0);\n out.mean = vec2<f32>(clamp(m.x, 0.0, 1.0), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x > 0.0 && m.x < 1.0), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let a = -m.x / s.x;\n let b = (1.0 - m.x) / s.x;\n if (a < -CLIP_REACH && b > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n var pa = 0.0; var da = 0.0;\n if (!(a < -CLIP_REACH)) { pa = pq(a).x; da = phi(a); }\n var pb = 1.0; var qb = 0.0; var db = 0.0;\n if (!(b > CLIP_REACH)) { let p = pq(b); pb = p.x; qb = p.y; db = phi(b); }\n let inside = pb - pa;\n let gamma = clamp(skew.x, -1.0, 1.0);\n let aa = a * da;\n let bb = b * db;\n let eg = a * pa + (da - db) + b * qb;\n let eg2 = a * a * pa + inside - (bb - aa) + b * b * qb;\n let normal_mean = m.x + s.x * eg;\n var mean = normal_mean;\n var shift = 0.0;\n if (gamma != 0.0) { shift = s.x * gamma / 6.0 * (aa - bb); mean = normal_mean + shift; }\n // |aφa − bφb| ≤ 0.48, so |shift| ≤ 0.08·s·|γ| and a wrong γ moves it by at most 0.16·s.\n let d_shift = 0.1 * s.x * min(2.0, skew.y) + 0.1 * abs(gamma) * s.y + E * abs(shift);\n // First-order propagation of the inputs' bounds: with ρ = E[g²] − E[g]², ∂E[g]/∂a = Φa, ∂E[g]/∂b = Qb, ∂E[g²]/∂a = 2aΦa,\n // ∂E[g²]/∂b = 2bQb, so ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]); ∂E/∂m = inside, ∂E/∂s = φa − φb, ∂Var/∂m = −s(ρ_a + ρ_b),\n // ∂Var/∂s = s(2ρ − aρ_a − bρ_b); the slope's derivatives are (φa − φb)/s and (aφa − bφb)/s.\n let rho = eg2 - eg * eg;\n let rho_a = 2.0 * pa * (a - eg);\n let rho_b = 2.0 * qb * (b - eg);\n let d_mean = m.y * inside + abs(da - db) * s.y + 2.0 * CLIP_MEAN_U * E * s.x + d_shift;\n // The normal part X = v·ρ and its bound; the reference's variance is max(0, X − shift·(2·E[Y] + shift)), so it lies in\n // [0, X_cpu] when the shift term is non-negative on both sides (shift > Δshift), which caps the bound by max(Var, X + ΔX).\n let normal_variance = v.x * rho;\n let d_normal = s.x * abs(rho_a + rho_b) * m.y + s.x * abs(2.0 * rho - a * rho_a - b * rho_b) * s.y + 2.0 * CLIP_VARIANCE_U * E * v.x;\n let variance = max(0.0, normal_variance - shift * (2.0 * normal_mean + shift));\n var d_var = d_normal + 2.0 * (d_shift * (abs(normal_mean) + abs(shift)) + abs(shift) * d_mean);\n if (shift > d_shift) { d_var = min(d_var, max(variance, max(0.0, normal_variance) + d_normal)); }\n out.mean = vec2<f32>(mean, d_mean);\n out.variance = vec2<f32>(variance, d_var);\n out.slope = vec2<f32>(inside, min(1.0, (abs(da - db) * m.y + abs(aa - bb) * s.y) / s.x + 2.0 * CLIP_SLOPE_U * E));\n return out;\n}\n`;\n\n/** The Gauss–Legendre Reinhard moments read the aux table, so they live in the pixel kernel. */\nconst REINHARD_WGSL = /* wgsl */ `\n// Positive Reinhard per channel then the clip to [0, 1]: h(x) = x/(1 + x) for x > 0, else 0, by the reference's composite\n// 16-point Gauss–Legendre rule in standardized units (the Speed forms), with the bounds of the quadrature (REINHARD_*_U)\n// and of its inputs (|h′| ≤ 1, |h″| ≤ 2).\nfn reinhard_moments(m: vec2<f32>, v: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n if (m.x > 0.0) {\n let h = m.x / (1.0 + m.x);\n out.mean = vec2<f32>(h, m.y + 4.0 * E * h); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(1.0 / ((1.0 + m.x) * (1.0 + m.x)), min(1.0, 2.0 * m.y + 6.0 * E));\n return out;\n }\n out.mean = vec2<f32>(0.0, m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0);\n return out;\n }\n let s = b_sqrt(v);\n let zlo = max(-m.x / s.x, -CLIP_REACH);\n if (!(CLIP_REACH > zlo)) { out.mean = vec2<f32>(0.0, m.y + s.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0); return out; }\n let hm = select(0.0, m.x / (1.0 + m.x), m.x > 0.0);\n var e1 = 0.0; var e2 = 0.0; var ed = 0.0;\n let zwidth = (CLIP_REACH - zlo) / 4.0;\n for (var p = 0u; p < 4u; p = p + 1u) {\n let zc = zlo + (f32(p) + 0.5) * zwidth;\n let zhalf = zwidth / 2.0;\n for (var k = 0u; k < 16u; k = k + 1u) {\n let z = zc + zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre}u + k];\n let x = m.x + s.x * z;\n let weight = zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre + 16}u + k] * phi(z);\n let h = x / (1.0 + x);\n e1 = e1 + weight * h;\n e2 = e2 + weight * (h - hm) * (h - hm);\n ed = ed + weight / ((1.0 + x) * (1.0 + x));\n }\n }\n let shifted = e1 - hm;\n let variance = max(0.0, e2 - shifted * shifted);\n out.mean = vec2<f32>(e1, m.y + s.y + REINHARD_MEAN_U * E * abs(e1));\n out.variance = vec2<f32>(variance, 2.0 * s.x * m.y + 2.0 * v.y + REINHARD_VARIANCE_U * E * variance);\n out.slope = vec2<f32>(ed, min(1.0, 2.0 * m.y + 2.0 * s.y + REINHARD_SLOPE_U * E));\n return out;\n}\n`;\n\n/**\n * Pass 1: the material of every lattice point of every group (the module's `material_at`, its uncertainty in bit 31; the output\n * is `lattice_out`, since a material module may declare its own `lattice`, as #491's composite module does). The group\n * records hold each group's lattice window (lx0, ly0, lw, lh), its first index in the lattice buffer and the point constants\n * c_u = 0.5 + f_x − W/2 and c_v = 0.5 + f_y − H/2 (exact in float32, checked by the host).\n */\nexport const DISPLAY_VIEW_QUALITY_LATTICE_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}\n@group(0) @binding(1) var<storage, read> groups: array<u32>;\n@group(0) @binding(2) var<storage, read_write> lattice_out: array<u32>;\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.lattice_total) { return; }\n var g = 0u;\n for (var k = 1u; k < params.groups; k = k + 1u) { if (i >= groups[k * GROUP + 4u]) { g = k; } }\n let base = g * GROUP;\n let lx0 = bitcast<i32>(groups[base]);\n let ly0 = bitcast<i32>(groups[base + 1u]);\n let lw = groups[base + 2u];\n let local = i - groups[base + 4u];\n let q = ly0 + i32(local / lw);\n let p = lx0 + i32(local % lw);\n let u = (f32(p) + bitcast<f32>(groups[base + 6u])) * params.scale_u;\n let v = (f32(q) + bitcast<f32>(groups[base + 7u])) * params.scale_v;\n material_uncertain = false;\n let m = material_at(vec2<f32>(u, v));\n lattice_out[i] = m | select(0u, 0x80000000u, material_uncertain);\n}\n`;\n\n/**\n * The end of pass 2, shared by both site passes: from the site's electrons e and their absolute bound de, the readout moments\n * (mean k·e, variance k²e + c₀, third cumulant k³e), the clip at the white level and their bounds, written to site i.\n */\nexport const DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL = /* wgsl */ `\nfn write_site(i: u32, c: u32, e: f32, de: f32, flag: u32) {\n let kc = k_of(c);\n let mean = kc * e;\n let variance = kc * kc * e + c0_of(c);\n let third = kc * kc * kc * e;\n var m = vec2<f32>(mean, kc * de + 2.0 * E * mean);\n var v = vec2<f32>(variance, kc * kc * de + 4.0 * E * variance);\n let gap = 1.0 - mean;\n // #533: bits 1 (certainly clipped) and 2 (undecided) of the flag word.\n let state = clip_state(m.x, m.y, v.x, v.y);\n let bits = flag | select(0u, 2u, state == 1u) | select(0u, 4u, state == 2u);\n if (!(gap > 0.0 && gap * gap > params.reach2 * variance)) {\n let clipped = upper_clip(m, v, 1.0);\n m = clipped.mean; v = clipped.variance;\n }\n let o = i * SITE;\n sites[o] = m.x; sites[o + 1u] = v.x; sites[o + 2u] = third;\n sites[o + 3u] = m.y; sites[o + 4u] = v.y; sites[o + 5u] = kc * kc * kc * de + 5.0 * E * third;\n sites[o + 6u] = bitcast<f32>(bits); sites[o + 7u] = e;\n}\n`;\n\n/**\n * The bindings of the site passes (pass 2); the blocked pass reads no tap records, so it declares no binding 2 (an automatic\n * layout leaves out a binding the entry point does not use, and the host binds it only for the tap-list pass).\n */\nconst siteBindingsWgsl = (tapRecords: boolean): string => /* wgsl */ `\n@group(0) @binding(1) var<storage, read> groups: array<u32>;${tapRecords ? `\n@group(0) @binding(2) var<storage, read> taps: array<u32>;` : \"\"}\n@group(0) @binding(3) var<storage, read> table: array<f32>;\n@group(0) @binding(4) var<storage, read> lattice: array<u32>;\n@group(0) @binding(5) var<storage, read_write> sites: array<f32>;\n@group(0) @binding(6) var<storage, read> spread: array<f32>;\n${EXACT_SUM_WGSL}\nalias Nodes = array<f32, ${DISPLAY_VIEW_QUALITY_MAXIMUM_NODES}>;\nfn add_tap(p: ptr<function, Nodes>, c: u32, t: u32, m: u32) {\n let N = params.nodes;\n for (var n = 0u; n < N; n = n + 1u) { (*p)[n] = (*p)[n] + table[(c * params.taps + t) * N + n] * table[(3u * params.taps + m) * N + n]; }\n}\nfn add_nodes(p: ptr<function, Nodes>, q: ptr<function, Nodes>) { for (var n = 0u; n < params.nodes; n = n + 1u) { (*p)[n] = (*p)[n] + (*q)[n]; (*q)[n] = 0.0; } }\nfn node_total(p: ptr<function, Nodes>, dark: f32) -> f32 {\n var anchor = exact_exponent(dark);\n for (var k = 0u; k < params.nodes; k = k + 1u) { if ((*p)[k] > 0.0) { anchor = max(anchor, exact_exponent((*p)[k])); } }\n var sum: ExactSum;\n sum.anchor = anchor;\n for (var k = 0u; k < params.nodes; k = k + 1u) { exact_add(&sum, (*p)[k]); }\n exact_add(&sum, dark);\n return exact_round(&sum);\n}\n`;\n\n/**\n * Pass 2: the expected electrons, readout moments and white clip of every native site of the band's window, with their bounds.\n * 2.21.0 (signals integrated from the material spectrum per site): the site's electrons are Σ_t Σ_n C_c(t, n)·L_{m_t}(λ_n) +\n * dark_c, from the channel's per-node kernel values and the spectrum of the material each tap reads (`table`: C at\n * (c·T + t)·N + n, the spectra from 3·T·N), accumulated per node over the taps, then the node partials and the dark electrons\n * summed in integer arithmetic and rounded once (EXACT_SUM_WGSL). Every term is non-negative and carries two host roundings (E/2\n * each), its product (E) and at most T − 1 additions, then the final rounding (E), and the dark electrons their host rounding\n * (E/2), so the sum carries at most (T + 2.5)·E, inside (T + 3)·E\n * (`params.site_gamma`),\n * plus `params.site_tiny`, the host's absolute bound on what a device that flushes below the float32 normal range can drop. An uncertain lattice point adds the tap's value spread\n * over the materials (`spread`, host float64) to the site's uncertainty, which enters its bounds, or flags the site when it\n * exceeds UNCERTAINTY_LIMIT of the electrons. One dispatch covers the `params.count` sites from `params.site_first` (#492: the\n * host splits a band's sites over several dispatches so that no dispatch gathers more than its tap budget).\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}${siteBindingsWgsl(true)}${DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n var uncertainty = 0.0;\n let T = params.taps;\n var partial: Nodes;\n for (var t = 0u; t < T; t = t + 1u) {\n let tb = t * TAP;\n let g = taps[tb] * GROUP;\n let index = groups[g + 4u] + (jj + taps[tb + 2u]) * groups[g + 2u] + ii + taps[tb + 1u];\n let raw = lattice[index];\n if ((raw >> 31u) != 0u) { uncertainty = uncertainty + spread[c * T + t]; }\n add_tap(&partial, c, t, raw & 0x7fffffffu);\n }\n let e = node_total(&partial, dark_of(c));\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty + params.site_tiny;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/**\n * Pass 2 with the defocus disc (#492): the same site electrons, summed in blocks.\n *\n * - **Spectral table.** The pass reads the tap-list pass's table (2.21.0): the channel's per-node kernel values C_c(t, n) and each\n * material's spectrum, so the signal of each tap is integrated from its material's spectrum when the site is read.\n * - **Blocked sum.** Every group's taps form a dense cw × ch rectangle (the combined kernel's window, row-major from the group's\n * first tap, `groups[g + 5]`), so a site sums each row of a group into a row partial per node, the rows into a group partial\n * and the groups into the total, then the nodes in integer arithmetic. No tap records are read: the tap's lattice offset\n * within its group is its (dx, dy).\n *\n * Every term is non-negative and carries two host roundings (u each, u = E/2: the kernel value and the spectrum), one product\n * (E) and at most cw − 1 + ch − 1 + G − 1 inexact additions, then the integer sum of the node partials and the dark electrons,\n * rounded once (E), and the dark electrons' host rounding (E/2), so the electrons carry at most (cw_max + ch_max + G + 0.5)·E, inside (cw_max + ch_max + G + 3)·E (`params.site_gamma`;\n * recursive summation of the up to 4·(2·96 + 2)² = 150 544 taps of a combined radius of 96 would carry (T + 3)·E ≈ 1.8 %\n * instead). The uncertainty, summed the same way, is scaled by 1 + site_gamma, and `params.site_tiny` (host float64, rounded up)\n * bounds what a device that flushes results below the float32 normal range can drop (the tap-list pass adds it too).\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}${siteBindingsWgsl(false)}\n${DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n let T = params.taps;\n var total: Nodes;\n var group_sum: Nodes;\n var row_sum: Nodes;\n var uncertainty = 0.0;\n for (var k = 0u; k < params.groups; k = k + 1u) {\n let g = k * GROUP;\n let lw = groups[g + 2u];\n let cw = lw - params.site_w + 1u;\n let ch = groups[g + 3u] - params.site_h + 1u;\n let lattice0 = groups[g + 4u] + jj * lw + ii;\n let tap0 = groups[g + 5u];\n var group_uncertainty = 0.0;\n for (var dy = 0u; dy < ch; dy = dy + 1u) {\n let row = lattice0 + dy * lw;\n let row_tap = tap0 + dy * cw;\n var row_uncertainty = 0.0;\n for (var dx = 0u; dx < cw; dx = dx + 1u) {\n let raw = lattice[row + dx];\n let t = row_tap + dx;\n if ((raw >> 31u) != 0u) { row_uncertainty = row_uncertainty + spread[c * T + t]; }\n add_tap(&row_sum, c, t, raw & 0x7fffffffu);\n }\n add_nodes(&group_sum, &row_sum);\n group_uncertainty = group_uncertainty + row_uncertainty;\n }\n add_nodes(&total, &group_sum);\n uncertainty = uncertainty + group_uncertainty;\n }\n let e = node_total(&total, dark_of(c));\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty * (1.0 + params.site_gamma) + params.site_tiny;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/** Pass 3: the display pixels (see the module comment). */\nexport const DISPLAY_VIEW_QUALITY_PIXELS_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}\nconst KEY_LEVELS: u32 = ${AHD_KEY_LEVELS}u;\nconst KEY_STEPS: f32 = ${literal(AHD_KEY_STEPS)};\nconst KEY_OCTAVES: f32 = ${literal(AHD_KEY_OCTAVES)};\nconst KEY_FLOOR: f32 = ${literal(AHD_KEY_FLOOR)};\nconst KEY_TOP: u32 = ${AHD_KEY_OCTAVES * AHD_KEY_STEPS - 1}u;\nconst CAND_BASE: u32 = ${DISPLAY_VIEW_QUALITY_AUX.candidates}u;\nconst CAND_STRIDE: u32 = ${CANDIDATE_PHASE_STRIDE}u;\nconst FLAG_UNCERTAIN: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.materialUncertain}u;\nconst FLAG_OUTSIDE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.siteOutsideWindow}u;\nconst FLAG_STATE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.stateMissing}u;\nconst FLAG_TIE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.thresholdTie}u;\nconst FLAG_CORRECTED: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.corrected}u;\nconst FLAG_CHOICE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.ahdChoice}u;\nconst FLAG_HIGHLIGHT: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.highlightUndecided}u;\nconst CHOICE_SIGMA: f32 = ${literal(DISPLAY_VIEW_QUALITY_CHOICE_SIGMA)};\n@group(0) @binding(1) var<storage, read> aux: array<f32>;\n@group(0) @binding(2) var<storage, read> geometry: array<f32>;\n@group(0) @binding(3) var<storage, read> sites: array<f32>;\n@group(0) @binding(4) var<storage, read> state_keys: array<i32>;\n@group(0) @binding(5) var<storage, read> state_values: array<f32>;\n@group(0) @binding(6) var<storage, read_write> bitmap: array<atomic<u32>>;\n@group(0) @binding(7) var<storage, read_write> out: array<f32>;\n@group(0) @binding(8) var<storage, read_write> flags: array<u32>;\n\nvar<private> pixel_flags: u32;\n${REINHARD_WGSL}\nfn reflect_x(x: i32) -> i32 { if (x < 0) { return -x; } if (x >= i32(params.native_w)) { return 2 * i32(params.native_w) - 2 - x; } return x; }\nfn reflect_y(y: i32) -> i32 { if (y < 0) { return -y; } if (y >= i32(params.native_h)) { return 2 * i32(params.native_h) - 2 - y; } return y; }\n// The site buffer offset of native site (x, y) (reflected at the raster's borders), or a flag when it lies outside the window.\nfn site_at(x: i32, y: i32) -> u32 {\n let rx = reflect_x(x) - params.site_x0;\n let ry = reflect_y(y) - params.site_y0;\n if (rx < 0 || ry < 0 || rx >= i32(params.site_w) || ry >= i32(params.site_h)) { pixel_flags = pixel_flags | FLAG_OUTSIDE; return 0u; }\n let o = (u32(ry) * params.site_w + u32(rx)) * SITE;\n pixel_flags = pixel_flags | (bitcast<u32>(sites[o + 6u]) & 1u);\n return o;\n}\n// #533: the clip state bits (1 certainly clipped, 2 undecided) of the site at offset o.\nfn clip_bits(o: u32) -> u32 { return (bitcast<u32>(sites[o + 6u]) >> 1u) & 3u; }\n// The offset of a grid key in the sorted state table, or state_count when absent.\nfn find_state(key: i32) -> u32 {\n var lo = 0u; var hi = params.state_count;\n while (lo < hi) {\n let mid = (lo + hi) / 2u;\n if (state_keys[mid] < key) { lo = mid + 1u; } else { hi = mid; }\n }\n if (lo < params.state_count && state_keys[lo] == key) { return lo; }\n return params.state_count;\n}\n// The flat-neighbourhood correction at channel means (m0, m1, m2) with its bound: trilinear between the eight grid states\n// around (log2(clamp(m, 2^-12, 1)) + 12)·8; a missing state flags the pixel. The bound of each axis's coordinate times the\n// largest state difference along that axis bounds the interpolation's dependence on the coordinates' error.\nfn flat_correction(m0: vec2<f32>, m1: vec2<f32>, m2: vec2<f32>, corr: ptr<function, array<vec2<f32>, 3>>) {\n var base: array<u32, 3>; var frac: array<f32, 3>; var dg: array<f32, 3>;\n for (var i = 0u; i < 3u; i = i + 1u) {\n var mi = m0; if (i == 1u) { mi = m1; } if (i == 2u) { mi = m2; }\n let clamped = clamp(mi.x, KEY_FLOOR, 1.0);\n let g = (log2(clamped) + KEY_OCTAVES) * KEY_STEPS;\n let b = min(u32(max(floor(g), 0.0)), KEY_TOP);\n base[i] = b; frac[i] = g - f32(b);\n // log2: 1/ln 2 per relative input error, 2^-21 absolute or 3 ULP (≤ 36E on [2^-12, 1]), the sum and the product.\n var slope = 0.0; if (mi.x > KEY_FLOOR && mi.x < 1.0) { slope = 1.4426950408889634 * mi.y / clamped; }\n dg[i] = KEY_STEPS * (slope + 4.76837158203125e-7 + 36.0 * E) + 2.0 * E * (g + 1.0);\n }\n var values: array<f32, 24>;\n var missing = false;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n let i0 = base[0] + (corner & 1u); let i1 = base[1] + ((corner >> 1u) & 1u); let i2 = base[2] + ((corner >> 2u) & 1u);\n let key = i32((i0 * KEY_LEVELS + i1) * KEY_LEVELS + i2);\n var weight = 1.0;\n for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }\n if (weight == 0.0) { for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = 0.0; } continue; }\n atomicOr(&bitmap[u32(key) / 32u], 1u << (u32(key) % 32u));\n let at = find_state(key);\n if (at >= params.state_count) { missing = true; continue; }\n for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = state_values[at * 3u + c]; }\n }\n if (missing) { pixel_flags = pixel_flags | FLAG_STATE; for (var c = 0u; c < 3u; c = c + 1u) { (*corr)[c] = vec2<f32>(0.0, 0.0); } return; }\n for (var c = 0u; c < 3u; c = c + 1u) {\n var sum = 0.0; var mag = 0.0;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n var weight = 1.0;\n for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }\n sum = sum + weight * values[corner * 3u + c];\n mag = mag + abs(weight * values[corner * 3u + c]);\n }\n // The largest state difference along each axis (over the four edges), times that axis's coordinate bound.\n var slope_term = 0.0;\n for (var i = 0u; i < 3u; i = i + 1u) {\n var largest = 0.0;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n if (((corner >> i) & 1u) == 1u) { continue; }\n largest = max(largest, abs(values[(corner | (1u << i)) * 3u + c] - values[corner * 3u + c]));\n }\n slope_term = slope_term + dg[i] * largest;\n }\n (*corr)[c] = vec2<f32>(sum, slope_term + 12.0 * E * mag);\n }\n}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.count) { return; }\n pixel_flags = 0u;\n let rx = i % params.region_w;\n let ry = params.row0 + i / params.region_w;\n let col = rx * STRIDE;\n let row = (params.columns + ry) * STRIDE;\n let col_first = bitcast<i32>(geometry[col]);\n let nx = bitcast<u32>(geometry[col + 1u]);\n let row_first = bitcast<i32>(geometry[row]);\n let ny = bitcast<u32>(geometry[row + 1u]);\n let margin = i32(params.margin);\n let reinhard = params.tone == 1u;\n let count = params.candidates;\n let share = select(1.0, 0.5, count == 2u);\n // B_c of the footprint from the host's axis sums (each over its scale squared): red, green, blue.\n let xb_even = geometry[col + 8u]; let xb_odd = geometry[col + 9u];\n let xpp_even = geometry[col + 10u]; let xpp_odd = geometry[col + 11u];\n let xqq_even = geometry[col + 12u]; let xqq_odd = geometry[col + 13u];\n let xpq_even = geometry[col + 14u]; let xpq_odd = geometry[col + 15u];\n let yb_even = geometry[row + 8u]; let yb_odd = geometry[row + 9u];\n let ypp_even = geometry[row + 10u]; let ypp_odd = geometry[row + 11u];\n let yqq_even = geometry[row + 12u]; let yqq_odd = geometry[row + 13u];\n let ypq_even = geometry[row + 14u]; let ypq_odd = geometry[row + 15u];\n let b0 = xb_even * yb_even;\n let b1 = (xpp_odd * ypp_even + xqq_odd * ypp_even + xpp_odd * yqq_even + 2.0 * xpq_odd * ypp_even + 2.0 * xpp_odd * ypq_even + 2.0 * xpq_odd * ypq_even) +\n (xpp_even * ypp_odd + xqq_even * ypp_odd + xpp_even * yqq_odd + 2.0 * xpq_even * ypp_odd + 2.0 * xpp_even * ypq_odd + 2.0 * xpq_even * ypq_odd);\n let b2 = xb_odd * yb_odd;\n var s = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cov = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var res = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n // #457: Σ_j a_j·½|h(H) − h(V)| per component, how far the reference's choice can move the value from the candidates' average.\n var choice = array<f32, 3>(0.0, 0.0, 0.0);\n // #533: the saturated real pixels (bit j·nx + i) and whether a real pixel is undecided.\n var mask0 = 0u; var mask1 = 0u; var undecided = false;\n for (var j = 0u; j < ny; j = j + 1u) {\n let ay = geometry[row + 2u + j];\n for (var ii = 0u; ii < nx; ii = ii + 1u) {\n // The overlap weight a_j: two host roundings (the overlaps over the scale) and the product.\n let aj = vec2<f32>(geometry[col + 2u + ii] * ay, 3.0 * E * abs(geometry[col + 2u + ii] * ay));\n let x = col_first + i32(ii) + margin;\n let y = row_first + i32(j) + margin;\n let phase = u32(y & 1) * 2u + u32(x & 1);\n // Site variance of each channel through the bilinear kernels, over Σw² (for B_c).\n var csv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n var variance = vec2<f32>(0.0, 0.0);\n let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n let w = aux[tb + 4u * t + 2u];\n if (w == 0.0) { continue; }\n let o = site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u]));\n variance = b_add(variance, b_scale(vec2<f32>(sites[o + 1u], sites[o + 4u]), w * w));\n }\n csv[c] = b_mul(variance, b_rounded(aux[${DISPLAY_VIEW_QUALITY_AUX.inverseW2}u + phase * 3u + c]));\n }\n let cb = CAND_BASE + phase * CAND_STRIDE;\n let entries = bitcast<u32>(aux[cb]);\n // #533: the sites the demosaic reads (the bilinear kernels and every candidate's entries), as the reference's sample does.\n var clip_any = 0u;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n if (aux[tb + 4u * t + 2u] == 0.0) { continue; }\n clip_any = clip_any | clip_bits(site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u])));\n }\n }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n var used = false;\n for (var k = 0u; k < 3u * count; k = k + 1u) { if (aux[eb + 2u + k] != 0.0) { used = true; } }\n if (used) { clip_any = clip_any | clip_bits(site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]))); }\n }\n if ((clip_any & 1u) != 0u) {\n let bit = j * nx + ii;\n if (bit < 32u) { mask0 = mask0 | (1u << bit); } else { mask1 = mask1 | (1u << (bit - 32u)); }\n } else if ((clip_any & 2u) != 0u) { undecided = true; }\n var e_mean = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cm_average = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var pixel_variance = vec2<f32>(0.0, 0.0);\n var bs: array<vec2<f32>, 3>; var hs: array<vec2<f32>, 3>;\n var hv: array<vec2<f32>, 3>;\n // #457: each candidate's rendered mean, for the reference's choice between them.\n var h_cand = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n for (var d = 0u; d < count; d = d + 1u) {\n // Camera-channel mean and covariance of candidate d from its dense weights over the window.\n var cm: array<vec2<f32>, 3>;\n var C: array<vec2<f32>, 6>;\n // Whether every site a channel reads is exactly at white (a zero bound: the reference's site is exactly 1 too); the\n // dyadic kernel weights then sum exactly on both sides, so the channel's mean carries no error (the host checks the weights).\n var exact = array<bool, 3>(true, true, true);\n for (var c = 0u; c < 3u; c = c + 1u) { cm[c] = vec2<f32>(0.0, 0.0); }\n for (var k = 0u; k < 6u; k = k + 1u) { C[k] = vec2<f32>(0.0, 0.0); }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let sm = vec2<f32>(sites[o], sites[o + 3u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let white = sm.y == 0.0 && sm.x == 1.0;\n exact[0] = exact[0] && (white || w0 == 0.0); exact[1] = exact[1] && (white || w1 == 0.0); exact[2] = exact[2] && (white || w2 == 0.0);\n cm[0] = b_add(cm[0], b_scale(sm, w0)); cm[1] = b_add(cm[1], b_scale(sm, w1)); cm[2] = b_add(cm[2], b_scale(sm, w2));\n C[0] = b_add(C[0], b_scale(sv, w0 * w0)); C[1] = b_add(C[1], b_scale(sv, w0 * w1)); C[2] = b_add(C[2], b_scale(sv, w0 * w2));\n C[3] = b_add(C[3], b_scale(sv, w1 * w1)); C[4] = b_add(C[4], b_scale(sv, w1 * w2)); C[5] = b_add(C[5], b_scale(sv, w2 * w2));\n }\n for (var c = 0u; c < 3u; c = c + 1u) { if (exact[c]) { cm[c].y = 0.0; } cm_average[c] = b_add(cm_average[c], b_scale(cm[c], share)); }\n // White balance and the highlight clip at the smallest gain. A channel exactly at white whose gain is the clip level is\n // exactly at the clip level on both sides.\n var bm: array<vec2<f32>, 3>; var bv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let gc = b_rounded(g_of(c));\n var um = b_mul(gc, cm[c]);\n if (exact[c] && cm[c].x == 1.0 && gain_at_clip(c)) { um = vec2<f32>(params.clip_level, 0.0); }\n let uv = b_mul(b_mul(gc, gc), C[select(select(0u, 3u, c == 1u), 5u, c == 2u)]);\n let gap = params.clip_level - um.x;\n if (gap > 0.0 && gap * gap > params.reach2 * uv.x) { bm[c] = um; bv[c] = uv; bs[c] = gc; }\n else { let clipped = upper_clip(um, uv, params.clip_level); bm[c] = clipped.mean; bv[c] = clipped.variance; bs[c] = b_mul(gc, clipped.slope); }\n }\n // Colour matrix with the full channel covariance, then the rendering of each component.\n var hm: array<vec2<f32>, 3>;\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let r0 = b_rounded(m_of(comp, 0u)); let r1 = b_rounded(m_of(comp, 1u)); let r2 = b_rounded(m_of(comp, 2u));\n let mean = b_add(b_add(b_mul(r0, bm[0]), b_mul(r1, bm[1])), b_mul(r2, bm[2]));\n var variance = b_add(b_add(b_mul(b_mul(r0, r0), bv[0]), b_mul(b_mul(r1, r1), bv[1])), b_mul(b_mul(r2, r2), bv[2]));\n let x01 = b_mul(b_mul(b_mul(r0, r1), b_mul(bs[0], bs[1])), C[1]);\n let x02 = b_mul(b_mul(b_mul(r0, r2), b_mul(bs[0], bs[2])), C[2]);\n let x12 = b_mul(b_mul(b_mul(r1, r2), b_mul(bs[1], bs[2])), C[4]);\n variance = b_add(variance, b_scale(b_add(b_add(x01, x02), x12), 2.0));\n var moments: Moments;\n if (!reinhard && mean.x > 0.0 && mean.x < 1.0 && mean.x * mean.x > params.reach2 * variance.x && (1.0 - mean.x) * (1.0 - mean.x) > params.reach2 * variance.x) {\n moments.mean = mean; moments.variance = variance; moments.slope = vec2<f32>(1.0, 0.0);\n } else if (reinhard) {\n moments = reinhard_moments(mean, variance);\n } else {\n // κ₃ of the component: Σ_sites (Σ_c M_comp,c·Γ_c·w_c,site)³·κ₃(site), over the candidate's dense weights.\n var third = vec2<f32>(0.0, 0.0);\n let k0 = b_mul(r0, bs[0]); let k1 = b_mul(r1, bs[1]); let k2 = b_mul(r2, bs[2]);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let coefficient = b_add(b_add(b_scale(k0, w0), b_scale(k1, w1)), b_scale(k2, w2));\n let cube = b_mul(b_mul(coefficient, coefficient), coefficient);\n third = b_add(third, b_mul(cube, vec2<f32>(sites[o + 2u], sites[o + 5u])));\n }\n var skew = vec2<f32>(0.0, 0.0);\n if (variance.x > 0.0) { skew = b_div(third, b_mul(variance, b_sqrt(variance))); }\n moments = unit_clip(mean, variance, skew);\n }\n hm[comp] = moments.mean; hv[comp] = moments.variance; hs[comp] = moments.slope;\n h_cand[d * 3u + comp] = moments.mean;\n e_mean[comp] = b_add(e_mean[comp], b_scale(moments.mean, share));\n pixel_variance = vec2<f32>(max(pixel_variance.x, moments.variance.x), max(pixel_variance.y, moments.variance.y));\n }\n // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance aᵀCa for the residual.\n var an: array<vec2<f32>, 9>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n an[c] = b_mul(b_mul(hs[0], b_rounded(m_of(0u, c))), bs[c]);\n an[3u + c] = b_mul(b_mul(hs[1], b_rounded(m_of(1u, c))), bs[c]);\n an[6u + c] = b_mul(b_mul(hs[2], b_rounded(m_of(2u, c))), bs[c]);\n var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }\n let wgt = b_mul(b_mul(b_scale(aj, share), b_rounded(bc)), csv[c]);\n cov[0] = b_add(cov[0], b_mul(b_mul(wgt, an[c]), an[c])); cov[1] = b_add(cov[1], b_mul(b_mul(wgt, an[c]), an[3u + c])); cov[2] = b_add(cov[2], b_mul(b_mul(wgt, an[c]), an[6u + c]));\n cov[3] = b_add(cov[3], b_mul(b_mul(wgt, an[3u + c]), an[3u + c])); cov[4] = b_add(cov[4], b_mul(b_mul(wgt, an[3u + c]), an[6u + c])); cov[5] = b_add(cov[5], b_mul(b_mul(wgt, an[6u + c]), an[6u + c]));\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n var l = vec2<f32>(0.0, 0.0);\n for (var p = 0u; p < 3u; p = p + 1u) {\n for (var q = 0u; q < 3u; q = q + 1u) {\n var cij = C[0]; if (p + q == 1u) { cij = C[1]; } if ((p == 0u && q == 2u) || (p == 2u && q == 0u)) { cij = C[2]; } if (p == 1u && q == 1u) { cij = C[3]; }\n if ((p == 1u && q == 2u) || (p == 2u && q == 1u)) { cij = C[4]; } if (p == 2u && q == 2u) { cij = C[5]; }\n l = b_add(l, b_mul(b_mul(an[comp * 3u + p], an[comp * 3u + q]), cij));\n }\n }\n let t = vec2<f32>(share * max(0.0, hv[comp].x - l.x), share * (hv[comp].y + l.y));\n res[comp] = b_add(res[comp], b_mul(b_mul(aj, aj), t));\n }\n }\n if (count == 2u) {\n // #457: the reference takes AHD's choice on the noise-free mosaic, one candidate or (a tie) their average, so its value\n // lies within half the candidates' difference plus their bounds of the average: the bound carries that, and the move\n // accumulates in choice, which flags the pixel below when it exceeds CHOICE_SIGMA of the pixel's noise σ.\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let diff = abs(h_cand[comp].x - h_cand[3u + comp].x);\n let both = h_cand[comp].y + h_cand[3u + comp].y;\n choice[comp] = choice[comp] + aj.x * 0.5 * diff;\n e_mean[comp].y = e_mean[comp].y + 0.5 * (diff + both);\n }\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, e_mean[comp])); }\n if (count == 2u) {\n // AHD's per-pixel choice: ¼·Var(H − V) at the last candidate's slopes, linear in the sites.\n var x0 = vec2<f32>(0.0, 0.0); var x1 = vec2<f32>(0.0, 0.0); var x2 = vec2<f32>(0.0, 0.0);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n let o = site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]));\n let d0 = b_scale(bs[0], aux[eb + 2u] - aux[eb + 5u]);\n let d1 = b_scale(bs[1], aux[eb + 3u] - aux[eb + 6u]);\n let d2 = b_scale(bs[2], aux[eb + 4u] - aux[eb + 7u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let a = b_mul(hs[0], b_add(b_add(b_mul(b_rounded(params.m00), d0), b_mul(b_rounded(params.m01), d1)), b_mul(b_rounded(params.m02), d2)));\n let b = b_mul(hs[1], b_add(b_add(b_mul(b_rounded(params.m10), d0), b_mul(b_rounded(params.m11), d1)), b_mul(b_rounded(params.m12), d2)));\n let c = b_mul(hs[2], b_add(b_add(b_mul(b_rounded(params.m20), d0), b_mul(b_rounded(params.m21), d1)), b_mul(b_rounded(params.m22), d2)));\n x0 = b_add(x0, b_mul(b_mul(a, a), sv)); x1 = b_add(x1, b_mul(b_mul(b, b), sv)); x2 = b_add(x2, b_mul(b_mul(c, c), sv));\n }\n let ajaj = b_mul(aj, aj);\n res[0] = b_add(res[0], b_mul(ajaj, b_scale(x0, 0.25))); res[1] = b_add(res[1], b_mul(ajaj, b_scale(x1, 0.25))); res[2] = b_add(res[2], b_mul(ajaj, b_scale(x2, 0.25)));\n }\n if (params.correction == 1u) {\n // The low-signal correction where the pixel noise exceeds the threshold; a decision within its bound is flagged.\n if (abs(pixel_variance.x - params.threshold) <= pixel_variance.y) { pixel_flags = pixel_flags | FLAG_TIE; }\n if (pixel_variance.x > params.threshold) {\n var corr: array<vec2<f32>, 3>;\n flat_correction(cm_average[0], cm_average[1], cm_average[2], &corr);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, corr[comp])); }\n pixel_flags = pixel_flags | FLAG_CORRECTED;\n }\n }\n }\n }\n // #457: a pixel whose candidates' choice could move it by more than CHOICE_SIGMA of its own noise σ is recomputed on the host.\n if (choice[0] > CHOICE_SIGMA * sqrt(max(cov[0].x + res[0].x, 0.0)) || choice[1] > CHOICE_SIGMA * sqrt(max(cov[3].x + res[1].x, 0.0)) ||\n choice[2] > CHOICE_SIGMA * sqrt(max(cov[5].x + res[2].x, 0.0))) { pixel_flags = pixel_flags | FLAG_CHOICE; }\n let o = i * ${DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS}u;\n out[o] = s[0].x; out[o + 1u] = s[1].x; out[o + 2u] = s[2].x;\n out[o + 3u] = cov[0].x + res[0].x; out[o + 4u] = cov[3].x + res[1].x; out[o + 5u] = cov[5].x + res[2].x;\n out[o + 6u] = cov[1].x; out[o + 7u] = cov[2].x; out[o + 8u] = cov[4].x;\n out[o + 9u] = max(s[0].y, max(s[1].y, s[2].y));\n out[o + 10u] = max(max(cov[0].y + res[0].y, cov[3].y + res[1].y), max(cov[5].y + res[2].y, max(cov[1].y, max(cov[2].y, cov[4].y))));\n flags[3u * i] = pixel_flags | select(0u, FLAG_HIGHLIGHT, undecided);\n flags[3u * i + 1u] = mask0;\n flags[3u * i + 2u] = mask1;\n}\n`;\n\n/** The lattice pass with a material module appended (`fn material_at(p: vec2<f32>) -> u32`, `var<private> material_uncertain: bool`). */\nexport function assembleDisplayViewQualityLatticeWgsl(materialModuleWgsl: string): string {\n return DISPLAY_VIEW_QUALITY_LATTICE_WGSL + \"\\n\" + materialModuleWgsl + \"\\n\";\n}\n"]}
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* divided by the scale in float64; the kernel multiplies the two axes);
|
|
9
9
|
* 2. for every native site the real pixels' demosaic reads (the footprint plus one site of reach), the expected
|
|
10
10
|
* electrons: the scene material at the pixel aperture's query offsets (the material module, `material_at`), the
|
|
11
|
-
* material's channel signal
|
|
12
|
-
* in the reference's order, plus the dark
|
|
11
|
+
* material's channel signal integrated in the kernel from its spectrum (Σ_λ A_c(λ)·L(λ), `material_signal`), or (#581) the image-plane
|
|
12
|
+
* module's channel signal at the point (`scene_value`), summed over the offsets in the reference's order, plus the dark
|
|
13
|
+
* electrons;
|
|
13
14
|
* 3. the readout moments of each site (mean k·e, variance k²·e + c₀, third cumulant k³·e) and the clip at the white
|
|
14
15
|
* level (the clipped-normal moments, in a cancellation-free standardized form);
|
|
15
16
|
* 4. per real pixel the bilinear kernels, white balance and highlight clip, the colour matrix, the tone curve
|
|
@@ -49,10 +50,16 @@ export declare const DISPLAY_VIEW_OUTPUT_FLOATS = 11;
|
|
|
49
50
|
export declare const DISPLAY_VIEW_FLAG_WORDS = 3;
|
|
50
51
|
/**
|
|
51
52
|
* #533: relative bound of a site's float32 mean and variance against the reference's float64, in units of E: the electrons'
|
|
52
|
-
* sum of at most 16 offsets (each term a host-rounded weight and
|
|
53
|
-
* readout's products and sum carry at most 26 E
|
|
53
|
+
* sum of at most 16 offsets (each term a host-rounded weight and a material signal and one product), the dark electrons, and
|
|
54
|
+
* the readout's products and sum carry at most 26 E with a host-rounded material signal (E/2). 2.21.0: the kernel integrates
|
|
55
|
+
* the material signal Σ_n A_c(λ_n)·L_m(λ_n) itself from the host-rounded response and spectrum: each non-negative product
|
|
56
|
+
* carries E/2 + E/2 for its operands and E for its rounding, and the products are summed in blocks of 16 nodes (at most 15
|
|
57
|
+
* additions within a block and 16 across blocks for N ≤ 256 nodes): 33 E instead of E/2, so a site carries at most 58.5 E,
|
|
58
|
+
* inside the unchanged 64.
|
|
54
59
|
*/
|
|
55
60
|
export declare const DISPLAY_VIEW_SITE_RELATIVE_U = 64;
|
|
61
|
+
/** Most spectral nodes the Speed kernel integrates per site (DISPLAY_VIEW_SITE_RELATIVE_U: 16 blocks of 16). */
|
|
62
|
+
export declare const DISPLAY_VIEW_SPEED_MAXIMUM_NODES = 256;
|
|
56
63
|
/** Floats per column or row record of the geometry buffer. */
|
|
57
64
|
export declare const DISPLAY_VIEW_GEOMETRY_STRIDE = 20;
|
|
58
65
|
/** Layout of the aux table (float32 words). */
|
|
@@ -83,8 +90,16 @@ export declare const DISPLAY_VIEW_REINHARD_VARIANCE_U = 512;
|
|
|
83
90
|
* The kernel's own WGSL, without the material module. `assembleDisplayViewSpeedWgsl` appends a module that defines
|
|
84
91
|
* `fn material_at(p: vec2<f32>) -> u32` and `var<private> material_uncertain: bool` (set to true when the point's
|
|
85
92
|
* classification is within its bound of a material boundary; the kernel then flags the display pixel for host
|
|
86
|
-
* recomputation)
|
|
93
|
+
* recomputation), and the adapter `scene_value(p, c) = material_signal(material_at(p), c)`. #581: a module may instead define
|
|
94
|
+
* `fn scene_value(p: vec2<f32>, c: u32) -> f32` itself (the image-plane module: the print's channel signal at p) and then
|
|
95
|
+
* declares how many roundings it adds (`DisplayViewGpuMaterialModule.extraSiteRoundings`). The module may declare bindings
|
|
96
|
+
* in @group(1) and use the constants E and SLACK declared here.
|
|
87
97
|
*/
|
|
88
98
|
export declare const DISPLAY_VIEW_SPEED_KERNEL_WGSL: string;
|
|
89
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* The channel signal of the material a material module's `material_at` gives (the kernel's `scene_value`), integrated from its
|
|
101
|
+
* spectrum when it changes (2.21.0: signals are integrated from the material spectrum per site).
|
|
102
|
+
*/
|
|
103
|
+
export declare const DISPLAY_VIEW_MATERIAL_SCENE_VALUE_WGSL = "\nvar<private> scene_m: u32 = 0xffffffffu;\nvar<private> scene_c: u32 = 0u;\nvar<private> scene_s: f32 = 0.0;\nfn scene_value(p: vec2<f32>, c: u32) -> f32 {\n let m = material_at(p);\n if (m != scene_m || c != scene_c) { scene_s = material_signal(m, c); scene_m = m; scene_c = c; }\n return scene_s;\n}\n";
|
|
104
|
+
/** The full shader: the kernel plus a material module (see `DISPLAY_VIEW_SPEED_KERNEL_WGSL`), and the table adapter unless the module defines `scene_value`. */
|
|
90
105
|
export declare function assembleDisplayViewSpeedWgsl(materialModuleWgsl: string): string;
|