@photivra/engine 2.22.0 → 2.23.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/dist/color/neighbor-highlight-reconstruction.d.ts +12 -2
  6. package/dist/color/neighbor-highlight-reconstruction.js +26 -3
  7. package/dist/color/neighbor-highlight-reconstruction.js.map +1 -1
  8. package/dist/core/version.d.ts +1 -1
  9. package/dist/core/version.js +1 -1
  10. package/dist/core/version.js.map +1 -1
  11. package/dist/facade/display-view-rendering.d.ts +5 -0
  12. package/dist/facade/display-view-rendering.js +57 -22
  13. package/dist/facade/display-view-rendering.js.map +1 -1
  14. package/dist/facade/fast-methods.js +2 -2
  15. package/dist/facade/fast-methods.js.map +1 -1
  16. package/dist/gpu/display-view-quality-gpu.js +22 -18
  17. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  18. package/dist/gpu/display-view-speed-gpu.js +15 -10
  19. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  20. package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +6 -1
  21. package/dist/gpu/wgsl-display-view-coupled-stub.js +9 -1
  22. package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -1
  23. package/dist/gpu/wgsl-display-view-coupled.d.ts +3 -0
  24. package/dist/gpu/wgsl-display-view-coupled.js +16 -4
  25. package/dist/gpu/wgsl-display-view-coupled.js.map +1 -1
  26. package/dist/gpu/wgsl-display-view-neighbors.d.ts +46 -0
  27. package/dist/gpu/wgsl-display-view-neighbors.js +114 -0
  28. package/dist/gpu/wgsl-display-view-neighbors.js.map +1 -0
  29. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +2 -0
  30. package/dist/gpu/wgsl-display-view-quality-kernel.js +5 -3
  31. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  32. package/dist/gpu/wgsl-display-view-speed-kernel.js +3 -3
  33. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  34. package/docs/API_REFERENCE.md +1 -1
  35. package/docs/USAGE.md +2 -1
  36. package/docs/api/core-version.md +1 -1
  37. package/docs/api/exports.json +2 -2
  38. package/docs/api/facade-fast-methods.md +2 -2
  39. package/package.json +1 -1
@@ -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 and the AHD candidates, 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). 2.22.0: there is no low-signal correction (docs/DISPLAY_VIEW.md, \"AHD at low signal\").\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 { EXACT_SUM_WGSL } from \"./wgsl-exact-sum.js\";\nimport { DISPLAY_VIEW_COUPLED_STUB_WGSL } from \"./wgsl-display-view-coupled-stub.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 // Bits 4, 8 and 16 (the low-signal correction's state, threshold and applied flags) are unused since 2.22.0.\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: 35\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/**\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 unused_16: u32, unused_17: 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, unused_35: 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). */\n/*\n * 2.22.0: in the luminance branch the nested loops read the colour matrix rows from one local array (`mm`) instead of calling\n * `m_of` (a `switch`): SwiftShader's compiler ended the GPU process on the `switch` inside those loops (after about 44 s). The\n * values are the same (docs/WEBGPU.md, \"Coupled rendering steps\").\n */\nexport const DISPLAY_VIEW_QUALITY_PIXELS_BODY_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}\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_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_write> out: array<f32>;\n@group(0) @binding(5) var<storage, read_write> flags: array<u32>;\n// #593, #601: the coupled rendering steps' constants, after the aux table's own entries (one zero word without them).\nfn cp_k(i: u32) -> f32 { return aux[${DISPLAY_VIEW_QUALITY_AUX.size}u + i]; }\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@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 // #593: whether the last candidate took the coupled slopes, and those slopes (for AHD's choice variance).\n var cp_used = false;\n var cp_a: array<vec2<f32>, 9>;\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 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; } }\n // #593: reconstruct within the blend's reach integrates the whole chain below; out of its reach it is the unclipped white\n // balance. An undecided reach is recomputed on the host.\n var cp_chain_run = false;\n if (cp_on() && cp_reconstruct()) {\n let reach = cp_reconstruct_reach(vec3<f32>(cm[0].x, cm[1].x, cm[2].x), vec3<f32>(C[0].x, C[3].x, C[5].x));\n if (reach == 2u) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n cp_chain_run = reach >= 1u;\n }\n let cp_unclipped = cp_on() && cp_reconstruct();\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) && !cp_unclipped) { 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 (cp_unclipped || (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 }\n // #593, #601: the coupled steps (wgsl-display-view-coupled.ts) replace the moments and give the slopes ∂out/∂c.\n var an: array<vec2<f32>, 9>;\n cp_used = false;\n if (cp_on()) {\n if (!cp_chain_run && cp_luminance()) {\n var mu = vec3<f32>(0.0); var dmu = vec3<f32>(0.0); var dsv = vec3<f32>(0.0);\n var srow = array<vec3<f32>, 3>(vec3<f32>(0.0), vec3<f32>(0.0), vec3<f32>(0.0));\n var cc = array<f32, 9>(C[0].x, C[1].x, C[2].x, C[1].x, C[3].x, C[4].x, C[2].x, C[4].x, C[5].x);\n var mm = array<vec3<f32>, 3>(vec3<f32>(params.m00, params.m01, params.m02), vec3<f32>(params.m10, params.m11, params.m12), vec3<f32>(params.m20, params.m21, params.m22));\n for (var r = 0u; r < 3u; r = r + 1u) {\n mu[r] = mm[r][0] * bm[0].x + mm[r][1] * bm[1].x + mm[r][2] * bm[2].x;\n dmu[r] = abs(mm[r][0]) * bm[0].y + abs(mm[r][1]) * bm[1].y + abs(mm[r][2]) * bm[2].y;\n dsv[r] = mm[r][0] * mm[r][0] * bv[0].y + mm[r][1] * mm[r][1] * bv[1].y + mm[r][2] * mm[r][2] * bv[2].y;\n for (var q = 0u; q < 3u; q = q + 1u) {\n var v = 0.0;\n for (var a = 0u; a < 3u; a = a + 1u) { for (var b = 0u; b < 3u; b = b + 1u) {\n var bab = bv[a].x; if (a != b) { bab = bs[a].x * bs[b].x * cc[a * 3u + b]; }\n v = v + mm[r][a] * mm[q][b] * bab;\n } }\n srow[r][q] = v;\n }\n }\n let t = cp_tone(mu, srow[0], srow[1], srow[2], dmu, dsv);\n if (t.flag) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n if (cp_desaturate() && t.top) { cp_chain_run = true; }\n else {\n let rows = array<vec3<f32>, 3>(t.a0, t.a1, t.a2);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n hm[comp] = vec2<f32>(t.hm[comp], t.bm[comp]); hv[comp] = vec2<f32>(t.hv[comp], t.bv[comp]);\n for (var c = 0u; c < 3u; c = c + 1u) {\n let jmc = rows[comp].x * m_of(0u, c) + rows[comp].y * m_of(1u, c) + rows[comp].z * m_of(2u, c);\n let ajm = abs(rows[comp].x * m_of(0u, c)) + abs(rows[comp].y * m_of(1u, c)) + abs(rows[comp].z * m_of(2u, c));\n let value = jmc * bs[c].x;\n an[comp * 3u + c] = vec2<f32>(value, ajm * bs[c].y + abs(value) * (t.ju + 4.0 * E));\n }\n }\n cp_used = true;\n }\n } else if (!cp_chain_run && cp_desaturate() && !reinhard) {\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let top = hm[comp].x + CLIP_REACH * sqrt(hv[comp].x);\n if (hm[comp].x > 0.0 && top >= 0.999) { cp_chain_run = true; }\n if (hm[comp].x > 0.0 && hm[comp].x - 2.0 * hm[comp].y < 0.999 && abs(top - 0.999) <= 2.0 * (hm[comp].y + CLIP_REACH * min(sqrt(hv[comp].y), hv[comp].y / (2.0 * sqrt(hv[comp].x)))) + 64.0 * E) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n }\n }\n if (cp_chain_run) {\n let t = cp_chain(vec3<f32>(cm[0].x, cm[1].x, cm[2].x), C[0].x, C[1].x, C[2].x, C[3].x, C[4].x, C[5].x, vec3<f32>(cm[0].y, cm[1].y, cm[2].y), vec3<f32>(C[0].y, C[3].y, C[5].y));\n if (t.flag) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n let rows = array<vec3<f32>, 3>(t.a0, t.a1, t.a2);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n hm[comp] = vec2<f32>(t.hm[comp], t.bm[comp]); hv[comp] = vec2<f32>(t.hv[comp], t.bv[comp]);\n for (var c = 0u; c < 3u; c = c + 1u) { an[comp * 3u + c] = vec2<f32>(rows[comp][c], abs(rows[comp][c]) * t.ju); }\n }\n cp_used = true;\n }\n if (cp_used) { for (var k = 0u; k < 9u; k = k + 1u) { cp_a[k] = an[k]; } }\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n h_cand[d * 3u + comp] = hm[comp];\n e_mean[comp] = b_add(e_mean[comp], b_scale(hm[comp], share));\n }\n // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance aᵀCa for the residual.\n for (var c = 0u; c < 3u; c = c + 1u) {\n if (!cp_used) {\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 }\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 var 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 var 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 var 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 if (cp_used) {\n // #593: the coupled slopes ∂out/∂c of the last candidate.\n let e0 = aux[eb + 2u] - aux[eb + 5u]; let e1 = aux[eb + 3u] - aux[eb + 6u]; let e2 = aux[eb + 4u] - aux[eb + 7u];\n a = b_add(b_add(b_scale(cp_a[0], e0), b_scale(cp_a[1], e1)), b_scale(cp_a[2], e2));\n b = b_add(b_add(b_scale(cp_a[3], e0), b_scale(cp_a[4], e1)), b_scale(cp_a[5], e2));\n c = b_add(b_add(b_scale(cp_a[6], e0), b_scale(cp_a[7], e1)), b_scale(cp_a[8], e2));\n }\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 }\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 pixel pass (`DISPLAY_VIEW_QUALITY_PIXELS_BODY_WGSL`) with the coupled steps' text: the stub by default, the coupled steps on demand (#593). */\nexport function assembleDisplayViewQualityPixelsWgsl(coupledWgsl: string = DISPLAY_VIEW_COUPLED_STUB_WGSL): string {\n return DISPLAY_VIEW_QUALITY_PIXELS_BODY_WGSL + \"\\n\" + coupledWgsl;\n}\nexport const DISPLAY_VIEW_QUALITY_PIXELS_WGSL = /* @__PURE__ */ assembleDisplayViewQualityPixelsWgsl();\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":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,4BAA4B,EAAE,gCAAgC,EAAE,MAAM,qCAAqC,CAAC;AAC7K,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,8BAA8B,EAAE,MAAM,qCAAqC,CAAC;AAErF,MAAM,CAAC,MAAM,mCAAmC,GAAG,EAAE,CAAC;AACtD,qEAAqE;AACrE,MAAM,CAAC,MAAM,4CAA4C,GAAG,CAAC,CAAC;AAC9D,gIAAgI;AAChI,MAAM,CAAC,MAAM,kCAAkC,GAAG,EAAE,CAAC;AACrD,mEAAmE;AACnE,MAAM,CAAC,MAAM,gCAAgC,GAAG,EAAE,CAAC;AACnD,4HAA4H;AAC5H,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,CAAC;AAClD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC;AAChD,iIAAiI;AACjI,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,CAAC;AAClD,gIAAgI;AAChI,MAAM,CAAC,MAAM,kCAAkC,GAAG,EAAE,CAAC;AACrD,iFAAiF;AACjF,MAAM,CAAC,MAAM,oCAAoC,GAAG,EAAE,CAAC;AACvD,0IAA0I;AAC1I,MAAM,CAAC,MAAM,sCAAsC,GAAG,EAAE,CAAC;AACzD,MAAM,CAAC,MAAM,qCAAqC,GAAG,CAAC,CAAC;AACvD,MAAM,sBAAsB,GAAG,CAAC,GAAG,sCAAsC,GAAG,qCAAqC,CAAC;AAClH,+CAA+C;AAC/C,MAAM,CAAC,MAAM,wBAAwB,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;IACpE,kHAAkH;IAClH,QAAQ,EAAE,CAAC;IACX,kFAAkF;IAClF,SAAS,EAAE,GAAG;IACd,0DAA0D;IAC1D,aAAa,EAAE,GAAG;IAClB,2IAA2I;IAC3I,UAAU,EAAE,GAAG;IACf,oBAAoB,EAAE,sBAAsB;IAC5C,IAAI,EAAE,GAAG,GAAG,CAAC,GAAG,sBAAsB;CACvC,CAAC,CAAC;AACH,+CAA+C;AAC/C,MAAM,CAAC,MAAM,yBAAyB,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;IACrE,uFAAuF;IACvF,iBAAiB,EAAE,CAAC;IACpB,oGAAoG;IACpG,iBAAiB,EAAE,CAAC;IACpB,6GAA6G;IAC7G;;;;OAIG;IACH,SAAS,EAAE,EAAE;IACb,uIAAuI;IACvI,kBAAkB,EAAE,EAAE;IACtB,0HAA0H;IAC1H,cAAc,EAAE,GAAG;IACnB,sDAAsD;IACtD,SAAS,EAAE,EAAE;CACd,CAAC,CAAC;AACH,oGAAoG;AACpG,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,CAAC;AACjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;AAC1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,sCAAsC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;AAC/D,kIAAkI;AAClI,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAChD,kEAAkE;AAClE,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAC5B,oFAAoF;AACpF,MAAM,SAAS,GAAG,uBAAuB,CAAC;AAC1C,MAAM,OAAO,GAAG,CAAC,KAAa,EAAU,EAAE;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACxC,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC;AACjD,CAAC,CAAC;AAEF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,gCAAgC,GAAG,UAAU,CAAC;iBAC1C,SAAS;;;2BAGC,OAAO,CAAC,wBAAwB,CAAC;+BAC7B,OAAO,CAAC,4BAA4B,CAAC;4BACxC,OAAO,CAAC,4BAA4B,CAAC;+BAClC,OAAO,CAAC,4BAA4B,CAAC;mCACjC,OAAO,CAAC,gCAAgC,CAAC;gCAC5C,OAAO,CAAC,gBAAgB,CAAC;iCACxB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;sBAC5B,oCAAoC;oBACtC,gCAAgC;qBAC/B,gCAAgC;mBAClC,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmJhD,CAAC;AAEF,gGAAgG;AAChG,MAAM,aAAa,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;iCAyBA,wBAAwB,CAAC,aAAa;;iCAEtC,wBAAwB,CAAC,aAAa,GAAG,EAAE;;;;;;;;;;;;;;CAc3E,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAAG,UAAU,CAAC,GAAG,gCAAgC;;;2BAGpE,mCAAmC;;mCAE3B,mCAAmC;;;;;;;;;;;;;;;;;CAiBrE,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;CAqB7D,CAAC;AAEF;;;GAGG;AACH,MAAM,gBAAgB,GAAG,CAAC,UAAmB,EAAU,EAAE,CAAC,UAAU,CAAC;8DACP,UAAU,CAAC,CAAC,CAAC;2DAChB,CAAC,CAAC,CAAC,EAAE;;;;;EAK9D,cAAc;2BACW,kCAAkC;;;;;;;;;;;;;;;CAe5D,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,UAAU,CAAC,GAAG,gCAAgC,GAAG,gBAAgB,CAAC,IAAI,CAAC,GAAG,mCAAmC;2BACjI,mCAAmC;;uCAEvB,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;CAwBzE,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,uCAAuC,GAAG,UAAU,CAAC,GAAG,gCAAgC,GAAG,gBAAgB,CAAC,KAAK,CAAC;EAC7H,mCAAmC;2BACV,mCAAmC;;uCAEvB,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CzE,CAAC;AAEF,2DAA2D;AAC3D;;;;GAIG;AACH,MAAM,CAAC,MAAM,qCAAqC,GAAG,UAAU,CAAC,GAAG,gCAAgC;yBAC1E,wBAAwB,CAAC,UAAU;2BACjC,sBAAsB;8BACnB,yBAAyB,CAAC,iBAAiB;4BAC7C,yBAAyB,CAAC,iBAAiB;2BAC5C,yBAAyB,CAAC,SAAS;8BAChC,yBAAyB,CAAC,kBAAkB;4BAC9C,OAAO,CAAC,iCAAiC,CAAC;;;;;;;sCAOhC,wBAAwB,CAAC,IAAI;;;EAGjE,aAAa;;;;;;;;;;;;;;2BAcY,mCAAmC;;mCAE3B,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAkDnD,wBAAwB,CAAC,QAAQ;;;;;;;iDAOH,wBAAwB,CAAC,SAAS;;;;;;;mBAOhE,wBAAwB,CAAC,QAAQ;;;;;;;iCAOnB,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;mCAwBnC,qCAAqC;;;sDAGlB,qCAAqC,kCAAkC,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCAmD3H,qCAAqC;;;0DAGlB,qCAAqC,kCAAkC,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCAiHnI,qCAAqC;;;;;;;;;;;;;;;;;;;;;;;;;;gBA0BxD,kCAAkC;;;;;;;;;;CAUjD,CAAC;AAEF,sJAAsJ;AACtJ,MAAM,UAAU,oCAAoC,CAAC,cAAsB,8BAA8B;IACvG,OAAO,qCAAqC,GAAG,IAAI,GAAG,WAAW,CAAC;AACpE,CAAC;AACD,MAAM,CAAC,MAAM,gCAAgC,GAAG,eAAe,CAAC,oCAAoC,EAAE,CAAC;AAEvG,yIAAyI;AACzI,MAAM,UAAU,qCAAqC,CAAC,kBAA0B;IAC9E,OAAO,iCAAiC,GAAG,IAAI,GAAG,kBAAkB,GAAG,IAAI,CAAC;AAC9E,CAAC","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 and the AHD candidates, 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). 2.22.0: there is no low-signal correction (docs/DISPLAY_VIEW.md, \"AHD at low signal\").\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 { EXACT_SUM_WGSL } from \"./wgsl-exact-sum.js\";\nimport { DISPLAY_VIEW_COUPLED_STUB_WGSL } from \"./wgsl-display-view-coupled-stub.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 // Bits 4, 8 and 16 (the low-signal correction's state, threshold and applied flags) are unused since 2.22.0.\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 /** #605: a chain needed neighbor ratios the host had not given (redone with them; the host sets bit 0 if any is left). */\n neighborRatios: 128,\n /** The bits that require the host's recomputation. */\n recompute: 35\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/**\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 unused_16: u32, unused_17: 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, unused_35: 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). */\n/*\n * 2.22.0: in the luminance branch the nested loops read the colour matrix rows from one local array (`mm`) instead of calling\n * `m_of` (a `switch`): SwiftShader's compiler ended the GPU process on the `switch` inside those loops (after about 44 s). The\n * values are the same (docs/WEBGPU.md, \"Coupled rendering steps\").\n */\nexport const DISPLAY_VIEW_QUALITY_PIXELS_BODY_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}\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_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_write> out: array<f32>;\n@group(0) @binding(5) var<storage, read_write> flags: array<u32>;\n// #593, #601: the coupled rendering steps' constants, after the aux table's own entries (one zero word without them).\nfn cp_k(i: u32) -> f32 { return aux[${DISPLAY_VIEW_QUALITY_AUX.size}u + i]; }\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@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 || nb_skip(i)) { 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 // #593: whether the last candidate took the coupled slopes, and those slopes (for AHD's choice variance).\n var cp_used = false;\n var cp_a: array<vec2<f32>, 9>;\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 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; } }\n // #593: reconstruct within the blend's reach integrates the whole chain below; out of its reach it is the unclipped white\n // balance. An undecided reach is recomputed on the host.\n var cp_chain_run = false;\n if (cp_on() && cp_reconstruct()) {\n let reach = cp_reconstruct_reach(vec3<f32>(cm[0].x, cm[1].x, cm[2].x), vec3<f32>(C[0].x, C[3].x, C[5].x));\n if (reach == 2u) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n cp_chain_run = reach >= 1u;\n }\n let cp_unclipped = cp_on() && cp_reconstruct();\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) && !cp_unclipped) { 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 (cp_unclipped || (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 }\n // #593, #601: the coupled steps (wgsl-display-view-coupled.ts) replace the moments and give the slopes ∂out/∂c.\n var an: array<vec2<f32>, 9>;\n cp_used = false;\n if (cp_on()) {\n if (!cp_chain_run && cp_luminance()) {\n var mu = vec3<f32>(0.0); var dmu = vec3<f32>(0.0); var dsv = vec3<f32>(0.0);\n var srow = array<vec3<f32>, 3>(vec3<f32>(0.0), vec3<f32>(0.0), vec3<f32>(0.0));\n var cc = array<f32, 9>(C[0].x, C[1].x, C[2].x, C[1].x, C[3].x, C[4].x, C[2].x, C[4].x, C[5].x);\n var mm = array<vec3<f32>, 3>(vec3<f32>(params.m00, params.m01, params.m02), vec3<f32>(params.m10, params.m11, params.m12), vec3<f32>(params.m20, params.m21, params.m22));\n for (var r = 0u; r < 3u; r = r + 1u) {\n mu[r] = mm[r][0] * bm[0].x + mm[r][1] * bm[1].x + mm[r][2] * bm[2].x;\n dmu[r] = abs(mm[r][0]) * bm[0].y + abs(mm[r][1]) * bm[1].y + abs(mm[r][2]) * bm[2].y;\n dsv[r] = mm[r][0] * mm[r][0] * bv[0].y + mm[r][1] * mm[r][1] * bv[1].y + mm[r][2] * mm[r][2] * bv[2].y;\n for (var q = 0u; q < 3u; q = q + 1u) {\n var v = 0.0;\n for (var a = 0u; a < 3u; a = a + 1u) { for (var b = 0u; b < 3u; b = b + 1u) {\n var bab = bv[a].x; if (a != b) { bab = bs[a].x * bs[b].x * cc[a * 3u + b]; }\n v = v + mm[r][a] * mm[q][b] * bab;\n } }\n srow[r][q] = v;\n }\n }\n let t = cp_tone(mu, srow[0], srow[1], srow[2], dmu, dsv);\n if (t.flag) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n if (cp_desaturate() && t.top) { cp_chain_run = true; }\n else {\n let rows = array<vec3<f32>, 3>(t.a0, t.a1, t.a2);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n hm[comp] = vec2<f32>(t.hm[comp], t.bm[comp]); hv[comp] = vec2<f32>(t.hv[comp], t.bv[comp]);\n for (var c = 0u; c < 3u; c = c + 1u) {\n let jmc = rows[comp].x * m_of(0u, c) + rows[comp].y * m_of(1u, c) + rows[comp].z * m_of(2u, c);\n let ajm = abs(rows[comp].x * m_of(0u, c)) + abs(rows[comp].y * m_of(1u, c)) + abs(rows[comp].z * m_of(2u, c));\n let value = jmc * bs[c].x;\n an[comp * 3u + c] = vec2<f32>(value, ajm * bs[c].y + abs(value) * (t.ju + 4.0 * E));\n }\n }\n cp_used = true;\n }\n } else if (!cp_chain_run && cp_desaturate() && !reinhard) {\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let top = hm[comp].x + CLIP_REACH * sqrt(hv[comp].x);\n if (hm[comp].x > 0.0 && top >= 0.999) { cp_chain_run = true; }\n if (hm[comp].x > 0.0 && hm[comp].x - 2.0 * hm[comp].y < 0.999 && abs(top - 0.999) <= 2.0 * (hm[comp].y + CLIP_REACH * min(sqrt(hv[comp].y), hv[comp].y / (2.0 * sqrt(hv[comp].x)))) + 64.0 * E) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n }\n }\n if (cp_chain_run && nb_ratio(i, j * nx + ii)) {\n let t = cp_chain(vec3<f32>(cm[0].x, cm[1].x, cm[2].x), C[0].x, C[1].x, C[2].x, C[3].x, C[4].x, C[5].x, vec3<f32>(cm[0].y, cm[1].y, cm[2].y), vec3<f32>(C[0].y, C[3].y, C[5].y));\n if (t.flag) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n let rows = array<vec3<f32>, 3>(t.a0, t.a1, t.a2);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n hm[comp] = vec2<f32>(t.hm[comp], t.bm[comp]); hv[comp] = vec2<f32>(t.hv[comp], t.bv[comp]);\n for (var c = 0u; c < 3u; c = c + 1u) { an[comp * 3u + c] = vec2<f32>(rows[comp][c], abs(rows[comp][c]) * t.ju); }\n }\n cp_used = true;\n }\n if (cp_used) { for (var k = 0u; k < 9u; k = k + 1u) { cp_a[k] = an[k]; } }\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n h_cand[d * 3u + comp] = hm[comp];\n e_mean[comp] = b_add(e_mean[comp], b_scale(hm[comp], share));\n }\n // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance aᵀCa for the residual.\n for (var c = 0u; c < 3u; c = c + 1u) {\n if (!cp_used) {\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 }\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 var 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 var 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 var 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 if (cp_used) {\n // #593: the coupled slopes ∂out/∂c of the last candidate.\n let e0 = aux[eb + 2u] - aux[eb + 5u]; let e1 = aux[eb + 3u] - aux[eb + 6u]; let e2 = aux[eb + 4u] - aux[eb + 7u];\n a = b_add(b_add(b_scale(cp_a[0], e0), b_scale(cp_a[1], e1)), b_scale(cp_a[2], e2));\n b = b_add(b_add(b_scale(cp_a[3], e0), b_scale(cp_a[4], e1)), b_scale(cp_a[5], e2));\n c = b_add(b_add(b_scale(cp_a[6], e0), b_scale(cp_a[7], e1)), b_scale(cp_a[8], e2));\n }\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 }\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) | nb_flag();\n flags[3u * i + 1u] = mask0;\n flags[3u * i + 2u] = mask1;\n}\n`;\n\n/** The pixel pass (`DISPLAY_VIEW_QUALITY_PIXELS_BODY_WGSL`) with the coupled steps' text: the stub by default, the coupled steps on demand (#593). */\nexport function assembleDisplayViewQualityPixelsWgsl(coupledWgsl: string = DISPLAY_VIEW_COUPLED_STUB_WGSL): string {\n return DISPLAY_VIEW_QUALITY_PIXELS_BODY_WGSL + \"\\n\" + coupledWgsl;\n}\nexport const DISPLAY_VIEW_QUALITY_PIXELS_WGSL = /* @__PURE__ */ assembleDisplayViewQualityPixelsWgsl();\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"]}
@@ -285,7 +285,7 @@ return sum;
285
285
  @compute @workgroup_size(${DISPLAY_VIEW_WORKGROUP_SIZE})
286
286
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) groups: vec3<u32>) {
287
287
  let i = gid.x + gid.y * groups.x * ${DISPLAY_VIEW_WORKGROUP_SIZE}u;
288
- if (i >= params.count) { return; }
288
+ if (i >= params.count || nb_skip(i)) { return; }
289
289
  material_uncertain = false;
290
290
  let rx = i % params.region_w;
291
291
  let ry = params.row0 + i / params.region_w;
@@ -457,7 +457,7 @@ if (hm[comp] > 0.0 && top >= 0.999) { cp_chain_run = true; }
457
457
  if (hm[comp] > 0.0 && hm[comp] - 2.0 * hbm[comp] * E < 0.999 && abs(top - 0.999) <= 2.0 * (hbm[comp] * E + CLIP_REACH * min(sqrt(hbv[comp] * E), hbv[comp] * E / (2.0 * sqrt(hv[comp])))) + 64.0 * E) { material_uncertain = true; }
458
458
  }
459
459
  }
460
- if (cp_chain_run) {
460
+ if (cp_chain_run && nb_ratio(i, j * nx + ii)) {
461
461
  let dcm = vec3<f32>(CLIP_MEAN_U * cb[0], CLIP_MEAN_U * cb[1], CLIP_MEAN_U * cb[2]) * E;
462
462
  let dcv = vec3<f32>(CLIP_VARIANCE_U * cb[0] * cb[0], CLIP_VARIANCE_U * cb[1] * cb[1], CLIP_VARIANCE_U * cb[2] * cb[2]) * E;
463
463
  let t = cp_chain(vec3<f32>(cm[0], cm[1], cm[2]), cvv[0], 0.0, 0.0, cvv[1], 0.0, cvv[2], dcm, dcv);
@@ -498,7 +498,7 @@ out[o] = s0; out[o + 1u] = s1; out[o + 2u] = s2;
498
498
  out[o + 3u] = c00 + r0; out[o + 4u] = c11 + r1; out[o + 5u] = c22 + r2;
499
499
  out[o + 6u] = c01; out[o + 7u] = c02; out[o + 8u] = c12;
500
500
  out[o + 9u] = bound_mean * E; out[o + 10u] = bound_var * E;
501
- flags[3u * i] = select(0u, 1u, material_uncertain) | select(0u, 2u, undecided);
501
+ flags[3u * i] = select(0u, 1u, material_uncertain) | select(0u, 2u, undecided) | nb_flag();
502
502
  flags[3u * i + 1u] = mask0;
503
503
  flags[3u * i + 2u] = mask1;
504
504
  }
@@ -1 +1 @@
1
- {"version":3,"file":"wgsl-display-view-speed-kernel.js","sourceRoot":"","sources":["../../src/gpu/wgsl-display-view-speed-kernel.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * WGSL kernel of the display view's Speed path on the GPU (#478): one invocation per display pixel of the exact\n * area mean (docs/DISPLAY_VIEW.md, \"Exact area mean\") without diffraction, with the bilinear demosaic and chart\n * materials. It evaluates `renderDisplayView`'s per-sample loop (facade/display-view.ts) for every real pixel of the\n * display pixel's footprint, in the reference's operation order where the order matters, in float32:\n *\n * 1. the real pixels the footprint covers and their overlap weights a_j = w_j / A (the host computes the 1-D overlaps\n * divided by the scale in float64; the kernel multiplies the two axes);\n * 2. for every native site the real pixels' demosaic reads (the footprint plus one site of reach), the expected\n * electrons: the scene material at the pixel aperture's query offsets (the material module, `material_at`), the\n * material's channel signal integrated in the kernel from its spectrum (Σ_λ A_c(λ)·L(λ), `material_signal`), or (#581) the image-plane\n * module's channel signal at the point (`scene_value`), summed over the offsets in the reference's order, plus the dark\n * electrons;\n * 3. the readout moments of each site (mean k·e, variance k²·e + c₀, third cumulant k³·e) and the clip at the white\n * level (the clipped-normal moments, in a cancellation-free standardized form);\n * 4. per real pixel the bilinear kernels, white balance and highlight clip, the colour matrix, the tone curve\n * (identity, or positive Reinhard by the reference's 4 × 16-point Gauss–Legendre rule) and the clip to [0, 1] with\n * the first-order skewness term;\n * 5. the display pixel's expected value Σ_j a_j·E[L_j], the linear (Stein) covariance Σ_c B_c·σ_c²·a_c·a_cᵀ with the\n * closed-form B_c of the footprint, and the independent residual Σ_j a_j²·r_j.\n *\n * It outputs, per display pixel, the three means, the three variances (linear plus residual), the three covariances\n * and two running error terms: the absolute error bound of the mean and of the variance that the clip steps\n * contribute (a fixed formula evaluated from the pixel's own float32 values; docs/WEBGPU.md, \"Display view (Speed)\").\n * #533: it also decides each real pixel's saturation probability where the float32 values leave no doubt: a site is\n * certainly unclipped or certainly clipped when its standardized distance from white, with the site's mean moved by twice\n * `DISPLAY_VIEW_SITE_RELATIVE_U`·E of itself, still exceeds twice the reference's 8.5 (where the reference's probability is\n * exactly 0 or 1), and a real pixel is certainly saturated (P = 1) when a site its demosaic reads is certainly clipped and\n * certainly not (P = 0) when all are certainly unclipped. Three flag words per display pixel: bit 0 of the first the\n * material flag, bit 1 a real pixel left undecided (the host computes the pixel's count with the reference); the other two\n * a bit per real pixel j·nx + i of the footprint with P = 1.\n * The host adds the output quantization term, draws the noise, encodes and rounds (`display-view-speed-gpu.ts`), so\n * the noise draw is the CPU's bit for bit.\n *\n * Precision: float32 with an a-priori bound per step from the WGSL accuracy table (+ − × within one float32 rounding in\n * either direction, E = 2^-23; division 2.5 ULP, sqrt 4.5 ULP, exp 3 + 2|x| ULP; no trigonometric built-in is used). Every quotient that is a constant of\n * the request (the overlaps over the scale, the axis sums over the squared scale, 1/Σw² of the demosaic kernels) is\n * computed on the host in float64 and uploaded. Data-dependent quotients (z = (t − m)/s, the Abramowitz–Stegun t,\n * the skewness) carry the 2.5 ULP bound.\n */\n\nimport { DISPLAY_VIEW_COUPLED_STUB_WGSL } from \"./wgsl-display-view-coupled-stub.js\";\n\nexport const DISPLAY_VIEW_WORKGROUP_SIZE = 64;\n/** Largest footprint per axis (real pixels): the site window is (n + 2)² ≤ 64 entries of private storage. */\nexport const DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_AXIS = 6;\nexport const DISPLAY_VIEW_GPU_WINDOW_SIDE = DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_AXIS + 2;\n/** Largest number of query offsets (pixel-aperture nodes × filter spots) the aux table holds. */\nexport const DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS = 16;\n/** Floats per display pixel in the output buffer: m0 m1 m2, v00 v11 v22 (linear + residual), c01 c02 c12, mean bound, variance bound. */\nexport const DISPLAY_VIEW_OUTPUT_FLOATS = 11;\n/** #533: flag words per display pixel: the flags (bit 0 material uncertain, bit 1 a highlight undecided), then the 36-bit saturated mask. */\nexport const DISPLAY_VIEW_FLAG_WORDS = 3;\n/**\n * #533: relative bound of a site's float32 mean and variance against the reference's float64, in units of E: the electrons'\n * sum of at most 16 offsets (each term a host-rounded weight and a material signal and one product), the dark electrons, and\n * 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\n * the material signal Σ_n A_c(λ_n)·L_m(λ_n) itself from the host-rounded response and spectrum: each non-negative product\n * 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\n * 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,\n * inside the unchanged 64.\n */\nexport const DISPLAY_VIEW_SITE_RELATIVE_U = 64;\n/** Most spectral nodes the Speed kernel integrates per site (DISPLAY_VIEW_SITE_RELATIVE_U: 16 blocks of 16). */\nexport const DISPLAY_VIEW_SPEED_MAXIMUM_NODES = 256;\n/** Floats per column or row record of the geometry buffer. */\nexport const DISPLAY_VIEW_GEOMETRY_STRIDE = 20;\n/** Layout of the aux table (float32 words). */\nexport const DISPLAY_VIEW_AUX = /* @__PURE__ */ Object.freeze({\n /** Per offset o: ou (0.5 + ox − W/2), ov (0.5 + oy − H/2), weight, 0. */\n offsets: 0,\n /** Per phase p, channel c, tap t: dx, dy, weight, 0 (4 taps; unused taps have weight 0). */\n taps: 64,\n /** Per phase p, channel c: 1 / Σ_t w_t² (host float64). */\n inverseW2: 256,\n /** 16 Gauss–Legendre nodes then 16 weights on [−1, 1]. */\n gaussLegendre: 272,\n size: 304\n});\n\n/** Uniform parameter words (see `Params` below): 40 words, 160 bytes. */\nexport const DISPLAY_VIEW_PARAM_WORDS = 40;\n/** The `E` of the bounds: one WGSL rounding of + − × in either direction, 2^-23 (W3C WGSL \"Floating Point Accuracy\"; #471, #487). */\nconst E_LITERAL = \"1.1920928955078125e-7\";\n/**\n * Clip-step constants of the running bound, in units of E (docs/WEBGPU.md, \"Display view (Speed)\", derivation): the\n * absolute error of the clipped-normal mean in units of the standard deviation (`CLIP_MEAN_U`) and of its variance in\n * units of the variance (`CLIP_VARIANCE_U`), for one clip side. The host doubles every bound (MARGIN).\n */\nexport const DISPLAY_VIEW_CLIP_MEAN_U = 1400;\nexport const DISPLAY_VIEW_CLIP_VARIANCE_U = 36000;\n/** The additional relative error of the Reinhard quadrature, in units of E, for the mean and the variance. */\nexport const DISPLAY_VIEW_REINHARD_MEAN_U = 256;\nexport const DISPLAY_VIEW_REINHARD_VARIANCE_U = 512;\n\nconst literal = (value: number): string => {\n const text = String(Math.fround(value));\n return /[.e]/u.test(text) ? text : `${text}.0`;\n};\n\n/**\n * The kernel's own WGSL, without the material module. `assembleDisplayViewSpeedWgsl` appends a module that defines\n * `fn material_at(p: vec2<f32>) -> u32` and `var<private> material_uncertain: bool` (set to true when the point's\n * classification is within its bound of a material boundary; the kernel then flags the display pixel for host\n * recomputation), and the adapter `scene_value(p, c) = material_signal(material_at(p), c)`. #581: a module may instead define\n * `fn scene_value(p: vec2<f32>, c: u32) -> f32` itself (the image-plane module: the print's channel signal at p) and then\n * declares how many roundings it adds (`DisplayViewGpuMaterialModule.extraSiteRoundings`). The module may declare bindings\n * in @group(1) and use the constants E and SLACK declared here.\n */\n/*\n * 2.22.0: in the luminance branch the nested loops read the colour matrix rows from one local array (`mm`) instead of calling\n * `m_of` (a `switch`): SwiftShader's compiler ended the GPU process on the `switch` inside those loops (after about 44 s). The\n * values are the same (docs/WEBGPU.md, \"Coupled rendering steps\").\n */\nexport const DISPLAY_VIEW_SPEED_KERNEL_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 SITE_RELATIVE_U: f32 = ${literal(DISPLAY_VIEW_SITE_RELATIVE_U)};\nconst WINDOW: u32 = ${DISPLAY_VIEW_GPU_WINDOW_SIDE}u;\nconst STRIDE: u32 = ${DISPLAY_VIEW_GEOMETRY_STRIDE}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, offsets: u32, nodes: 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, pad1: f32,\n g0: f32, g1: f32, g2: f32, pad2: f32,\n m00: f32, m01: f32, m02: f32, m10: f32,\n m11: f32, m12: f32, m20: f32, m21: f32,\n m22: f32, scale_u: f32, scale_v: f32, pad3: f32,\n};\n@group(0) @binding(0) var<uniform> params: Params;\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> spectral_table: array<f32>;\n@group(0) @binding(4) var<storage, read_write> out: array<f32>;\n@group(0) @binding(5) var<storage, read_write> flags: array<u32>;\n// #593, #601: the coupled rendering steps' constants, after the aux table's own entries (one zero word without them).\nfn cp_k(i: u32) -> f32 { return aux[${DISPLAY_VIEW_AUX.size}u + i]; }\n\nvar<private> site_mean: array<f32, 64>;\nvar<private> site_var: array<f32, 64>;\nvar<private> site_third: array<f32, 64>;\nvar<private> site_bound: array<f32, 64>;\n// #533: 0 certainly below white, 1 certainly at or above it, 2 undecided.\nvar<private> site_clip: array<u32, 64>;\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; }\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}\n// Standard normal density, as the reference forms it: 0.3989422804014327·exp(−0.5·z·z).\nfn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }\n// Abramowitz & Stegun 26.2.17: returns (Φ(z), 1 − Φ(z)), each formed from the tail q directly where it is small.\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}\n// #533: whether a site with mean m (bound dm) and variance v (bound dv) is certainly below white (0), certainly at or above it\n// (1) or undecided (2): the reference's clip probability is exactly 0 or 1 beyond 8.5 standard deviations, and the decision here\n// asks for twice that distance (4·reach²) after moving the mean by twice its bound and the variance up 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// Moments of min(Y, top) for Y ~ N(m, v): (mean, variance, slope P). The variance is v·(P − zφ + z²Q − δ²) with\n// δ = φ − zQ: algebraically E[min²] − E[min]² without the m² cancellation.\nfn upper_clip(m: f32, v: f32, top: f32) -> vec3<f32> {\n if (!(v > 0.0)) { return vec3<f32>(min(m, top), 0.0, select(0.0, 1.0, m < top)); }\n let s = sqrt(v);\n let z = (top - m) / s;\n if (z > CLIP_REACH) { return vec3<f32>(m, v, 1.0); }\n if (z < -CLIP_REACH) { return vec3<f32>(top, 0.0, 0.0); }\n let p = pq(z);\n let d = phi(z);\n let delta = d - z * p.y;\n let mean = m - s * delta;\n let ratio = p.x - z * d + z * z * p.y - delta * delta;\n return vec3<f32>(mean, v * max(0.0, ratio), p.x);\n}\n// Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1): (mean, variance, inside).\n// With Z standard normal, W = m + s·g(Z), g = clip(Z, a, b): E[g] = aΦa + φa − φb + bQb, E[g²] = a²Φa + (Φb − Φa) −\n// (bφb − aφa) + b²Qb; the mean adds the reference's Edgeworth term (s·γ/6)·(aφa − bφb).\nfn unit_clip(m: f32, v: f32, skewness: f32) -> vec3<f32> {\n if (!(v > 0.0)) { return vec3<f32>(clamp(m, 0.0, 1.0), 0.0, select(0.0, 1.0, m > 0.0 && m < 1.0)); }\n let s = sqrt(v);\n let a = -m / s;\n let b = (1.0 - m) / s;\n if (a < -CLIP_REACH && b > CLIP_REACH) { return vec3<f32>(m, v, 1.0); }\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(skewness, -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 + s * eg;\n var mean = normal_mean;\n var skew_shift = 0.0;\n if (gamma != 0.0) { skew_shift = s * gamma / 6.0 * (aa - bb); mean = normal_mean + skew_shift; }\n // The reference forms E[Y²] − (E[Y] + shift)², so the shift also enters its variance: −shift·(2·E[Y] + shift).\n return vec3<f32>(mean, max(0.0, v * (eg2 - eg * eg) - skew_shift * (2.0 * normal_mean + skew_shift)), inside);\n}\n// Positive Reinhard per channel then the clip to [0, 1]: h(x) = x/(1 + x) for x > 0, else 0. Composite 16-point\n// Gauss–Legendre over [max(0, m − 8.5s), m + 8.5s] in 4 panels, as the reference; the variance is formed about h(m).\nfn reinhard_moments(m: f32, v: f32) -> vec3<f32> {\n if (!(v > 0.0)) {\n if (m > 0.0) { return vec3<f32>(m / (1.0 + m), 0.0, 1.0 / ((1.0 + m) * (1.0 + m))); }\n return vec3<f32>(0.0, 0.0, 0.0);\n }\n let s = sqrt(v);\n // The reference's panels [max(0, m − 8.5s), m + 8.5s] in standardized units z = (x − m)/s, so the node's z is formed\n // without the cancellation x − m: zlo = max(−m/s, −8.5), weight = zhalf·w_k·φ(z_k) (= half·w_k·φ((x − m)/s)/s), x = m + s·z.\n let zlo = max(-m / s, -CLIP_REACH);\n if (!(CLIP_REACH > zlo)) { return vec3<f32>(0.0, 0.0, 0.0); }\n let hm = select(0.0, m / (1.0 + m), m > 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_AUX.gaussLegendre}u + k];\n let x = m + s * z;\n let weight = zhalf * aux[${DISPLAY_VIEW_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 return vec3<f32>(e1, max(0.0, e2 - shifted * shifted), ed);\n}\n\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; }\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}\nfn material_signal(m: u32, c: u32) -> f32 {\n let n = params.nodes;\n var s = 0.0;\n var b = 0.0;\n for (var k = 0u; k < n; k = k + 1u) {\n b = b + spectral_table[c * n + k] * spectral_table[(3u + m) * n + k];\n if ((k & 15u) == 15u) { s = s + b; b = 0.0; }\n }\n return s + b;\n}\n// The expected photo electrons of native site (x, y): Σ_o w_o·scene_value(p_o, c) in offset order (directSite); with a material\n// module scene_value is S_c(m_o), integrated from the material's spectrum (material_signal).\nfn site_electrons(x: i32, y: i32, c: u32) -> f32 {\n var sum = 0.0;\n let fx = f32(x);\n let fy = f32(y);\n for (var o = 0u; o < params.offsets; o = o + 1u) {\n let base = ${DISPLAY_VIEW_AUX.offsets}u + 4u * o;\n let u = (fx + aux[base]) * params.scale_u;\n let v = (fy + aux[base + 1u]) * params.scale_v;\n sum = sum + aux[base + 2u] * scene_value(vec2<f32>(u, v), c);\n }\n return sum;\n}\n@compute @workgroup_size(${DISPLAY_VIEW_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) groups: vec3<u32>) {\n let i = gid.x + gid.y * groups.x * ${DISPLAY_VIEW_WORKGROUP_SIZE}u;\n if (i >= params.count) { return; }\n material_uncertain = false;\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 // 2. and 3.: the sites of the footprint plus one site of reach, once each.\n let x0 = col_first + margin - 1;\n let y0 = row_first + margin - 1;\n let window_w = nx + 2u;\n for (var sj = 0u; sj < ny + 2u; sj = sj + 1u) {\n let sy = reflect_y(y0 + i32(sj));\n for (var si = 0u; si < window_w; si = si + 1u) {\n let sx = reflect_x(x0 + i32(si));\n let c = channel_of(sx, sy);\n let e = site_electrons(sx, sy, c) + dark_of(c);\n let kc = k_of(c);\n let mean = kc * e;\n let variance = kc * kc * e + c0_of(c);\n let gap = 1.0 - mean;\n let slot = sj * window_w + si;\n site_third[slot] = kc * kc * kc * e;\n site_clip[slot] = clip_state(mean, SITE_RELATIVE_U * E * mean, variance, SITE_RELATIVE_U * E * variance);\n if (gap > 0.0 && gap * gap > params.reach2 * variance) {\n site_mean[slot] = mean; site_var[slot] = variance; site_bound[slot] = 0.0;\n } else {\n let clipped = upper_clip(mean, variance, 1.0);\n site_mean[slot] = clipped.x; site_var[slot] = clipped.y; site_bound[slot] = sqrt(variance);\n }\n }\n }\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 let reinhard = params.tone == 1u;\n var s0 = 0.0; var s1 = 0.0; var s2 = 0.0;\n var c00 = 0.0; var c01 = 0.0; var c02 = 0.0; var c11 = 0.0; var c12 = 0.0; var c22 = 0.0;\n var r0 = 0.0; var r1 = 0.0; var r2 = 0.0;\n var bound_mean = 0.0; var bound_var = 0.0;\n // #533: the saturated real pixels (bit j·nx + i) and whether any real pixel is undecided.\n var mask0 = 0u; var mask1 = 0u; var undecided = false;\n // 4. and 5.: every real pixel of the footprint with its overlap weight.\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 let aj = 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 var cm: array<f32, 3>; var cvv: array<f32, 3>; var cth: array<f32, 3>; var csv: array<f32, 3>; var cb: array<f32, 3>;\n var clipped = false; var near_white = false;\n for (var c = 0u; c < 3u; c = c + 1u) {\n var mean = 0.0; var variance = 0.0; var third = 0.0; var bnd = 0.0;\n let taps = ${DISPLAY_VIEW_AUX.taps}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n let w = aux[taps + 4u * t + 2u];\n let slot = u32((i32(j) + 1 + i32(aux[taps + 4u * t + 1u])) * i32(window_w) + i32(ii) + 1 + i32(aux[taps + 4u * t]));\n mean = mean + w * site_mean[slot];\n variance = variance + w * w * site_var[slot];\n third = third + w * w * w * site_third[slot];\n bnd = max(bnd, select(0.0, site_bound[slot], w != 0.0));\n if (w != 0.0) { if (site_clip[slot] == 1u) { clipped = true; } else if (site_clip[slot] == 2u) { near_white = true; } }\n }\n cm[c] = mean; cvv[c] = variance; cth[c] = third; cb[c] = bnd;\n csv[c] = variance * aux[${DISPLAY_VIEW_AUX.inverseW2}u + phase * 3u + c];\n }\n if (clipped) {\n let bit = j * nx + ii;\n if (bit < 32u) { mask0 = mask0 | (1u << bit); } else { mask1 = mask1 | (1u << (bit - 32u)); }\n } else if (near_white) { undecided = true; }\n // #593: reconstruct within the blend's reach integrates the whole chain below; out of its reach it is the unclipped white balance.\n var cp_chain_run = false;\n if (cp_on() && cp_reconstruct()) {\n let reach = cp_reconstruct_reach(vec3<f32>(cm[0], cm[1], cm[2]), vec3<f32>(cvv[0], cvv[1], cvv[2]));\n if (reach == 2u) { material_uncertain = true; }\n cp_chain_run = reach >= 1u;\n }\n let cp_unclipped = cp_on() && cp_reconstruct();\n // White balance and the highlight clip at the smallest gain: bm, bv, bs (slope), with the clip's bound terms.\n var bm: array<f32, 3>; var bv: array<f32, 3>; var bs: array<f32, 3>; var bbm: array<f32, 3>; var bbv: array<f32, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let gc = g_of(c);\n let um = gc * cm[c];\n let uv = gc * gc * cvv[c];\n let gap = params.clip_level - um;\n bbm[c] = gc * CLIP_MEAN_U * cb[c];\n bbv[c] = gc * gc * CLIP_VARIANCE_U * cb[c] * cb[c];\n if (cp_unclipped || (gap > 0.0 && gap * gap > params.reach2 * uv)) { bm[c] = um; bv[c] = uv; bs[c] = gc; }\n else {\n let clipped = upper_clip(um, uv, params.clip_level);\n bm[c] = clipped.x; bv[c] = clipped.y; bs[c] = gc * clipped.z;\n bbm[c] = bbm[c] + CLIP_MEAN_U * sqrt(uv);\n bbv[c] = bbv[c] + CLIP_VARIANCE_U * uv;\n }\n }\n // Colour matrix, then the rendering of each component (tone curve, clip to [0, 1]).\n var hm: array<f32, 3>; var hv: array<f32, 3>; var hs: array<f32, 3>; var hbm: array<f32, 3>; var hbv: array<f32, 3>;\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let r0m = m_of(comp, 0u); let r1m = m_of(comp, 1u); let r2m = m_of(comp, 2u);\n let mean = r0m * bm[0] + r1m * bm[1] + r2m * bm[2];\n let variance = r0m * r0m * bv[0] + r1m * r1m * bv[1] + r2m * r2m * bv[2];\n var bm_c = abs(r0m) * bbm[0] + abs(r1m) * bbm[1] + abs(r2m) * bbm[2];\n var bv_c = r0m * r0m * bbv[0] + r1m * r1m * bbv[1] + r2m * r2m * bbv[2];\n if (!reinhard && mean > 0.0 && mean < 1.0 && mean * mean > params.reach2 * variance && (1.0 - mean) * (1.0 - mean) > params.reach2 * variance) {\n hm[comp] = mean; hv[comp] = variance; hs[comp] = 1.0;\n } else if (reinhard) {\n let moments = reinhard_moments(mean, variance);\n hm[comp] = moments.x; hv[comp] = moments.y; hs[comp] = moments.z;\n bm_c = bm_c + 2.0 * CLIP_MEAN_U * sqrt(variance);\n bv_c = bv_c + 2.0 * CLIP_VARIANCE_U * variance;\n } else {\n // κ₃ of the component: Σ_c (M_comp,c·Γ_c)³·Σ_t w_t³·κ₃(site) (the sites of one channel share the coefficient).\n let k0 = r0m * bs[0]; let k1 = r1m * bs[1]; let k2 = r2m * bs[2];\n let third = k0 * k0 * k0 * cth[0] + k1 * k1 * k1 * cth[1] + k2 * k2 * k2 * cth[2];\n let skew = select(0.0, third / (variance * sqrt(variance)), variance > 0.0);\n let moments = unit_clip(mean, variance, skew);\n hm[comp] = moments.x; hv[comp] = moments.y; hs[comp] = moments.z;\n bm_c = bm_c + 2.0 * CLIP_MEAN_U * sqrt(variance);\n bv_c = bv_c + 2.0 * CLIP_VARIANCE_U * variance;\n }\n hbm[comp] = bm_c; hbv[comp] = bv_c;\n }\n // #593, #601: the coupled steps (wgsl-display-view-coupled.ts): a luminance tone curve by conditioning on luminance, and the\n // chain's 3-D rule for reconstruct and the desaturation; their slopes ∂out/∂c (camera channels) replace the per-component ones.\n var jm = array<vec3<f32>, 3>(vec3<f32>(0.0), vec3<f32>(0.0), vec3<f32>(0.0));\n var ju = 0.0;\n var cp_used = false;\n if (cp_on()) {\n if (!cp_chain_run && cp_luminance()) {\n var mu = vec3<f32>(0.0); var dmu = vec3<f32>(0.0); var dsv = vec3<f32>(0.0);\n var srow = array<vec3<f32>, 3>(vec3<f32>(0.0), vec3<f32>(0.0), vec3<f32>(0.0));\n var mm = array<vec3<f32>, 3>(vec3<f32>(params.m00, params.m01, params.m02), vec3<f32>(params.m10, params.m11, params.m12), vec3<f32>(params.m20, params.m21, params.m22));\n for (var r = 0u; r < 3u; r = r + 1u) {\n mu[r] = mm[r][0] * bm[0] + mm[r][1] * bm[1] + mm[r][2] * bm[2];\n dmu[r] = (abs(mm[r][0]) * bbm[0] + abs(mm[r][1]) * bbm[1] + abs(mm[r][2]) * bbm[2]) * E;\n dsv[r] = (mm[r][0] * mm[r][0] * bbv[0] + mm[r][1] * mm[r][1] * bbv[1] + mm[r][2] * mm[r][2] * bbv[2]) * E;\n for (var q = 0u; q < 3u; q = q + 1u) { srow[r][q] = mm[r][0] * mm[q][0] * bv[0] + mm[r][1] * mm[q][1] * bv[1] + mm[r][2] * mm[q][2] * bv[2]; }\n }\n let t = cp_tone(mu, srow[0], srow[1], srow[2], dmu, dsv);\n if (t.flag) { material_uncertain = true; }\n if (cp_desaturate() && t.top) { cp_chain_run = true; }\n else {\n let rows = array<vec3<f32>, 3>(t.a0, t.a1, t.a2);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n hm[comp] = t.hm[comp]; hv[comp] = t.hv[comp]; hbm[comp] = t.bm[comp] / E; hbv[comp] = t.bv[comp] / E;\n for (var c = 0u; c < 3u; c = c + 1u) { jm[comp][c] = (rows[comp].x * m_of(0u, c) + rows[comp].y * m_of(1u, c) + rows[comp].z * m_of(2u, c)) * bs[c]; }\n }\n ju = t.ju / E; cp_used = true;\n }\n } else if (!cp_chain_run && cp_desaturate() && params.tone != 1u) {\n // A component reaching 1 within reach is desaturated with the others (the reference's test; undecided: recompute).\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let top = hm[comp] + CLIP_REACH * sqrt(hv[comp]);\n if (hm[comp] > 0.0 && top >= 0.999) { cp_chain_run = true; }\n if (hm[comp] > 0.0 && hm[comp] - 2.0 * hbm[comp] * E < 0.999 && abs(top - 0.999) <= 2.0 * (hbm[comp] * E + CLIP_REACH * min(sqrt(hbv[comp] * E), hbv[comp] * E / (2.0 * sqrt(hv[comp])))) + 64.0 * E) { material_uncertain = true; }\n }\n }\n if (cp_chain_run) {\n let dcm = vec3<f32>(CLIP_MEAN_U * cb[0], CLIP_MEAN_U * cb[1], CLIP_MEAN_U * cb[2]) * E;\n let dcv = vec3<f32>(CLIP_VARIANCE_U * cb[0] * cb[0], CLIP_VARIANCE_U * cb[1] * cb[1], CLIP_VARIANCE_U * cb[2] * cb[2]) * E;\n let t = cp_chain(vec3<f32>(cm[0], cm[1], cm[2]), cvv[0], 0.0, 0.0, cvv[1], 0.0, cvv[2], dcm, dcv);\n if (t.flag) { material_uncertain = true; }\n jm[0] = t.a0; jm[1] = t.a1; jm[2] = t.a2;\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { hm[comp] = t.hm[comp]; hv[comp] = t.hv[comp]; hbm[comp] = t.bm[comp] / E; hbv[comp] = t.bv[comp] / E; }\n ju = t.ju / E; cp_used = true;\n }\n }\n s0 = s0 + aj * hm[0]; s1 = s1 + aj * hm[1]; s2 = s2 + aj * hm[2];\n // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance for the residual.\n var l0 = 0.0; var l1 = 0.0; var l2 = 0.0;\n var lb0 = 0.0; var lb1 = 0.0; var lb2 = 0.0;\n for (var c = 0u; c < 3u; c = c + 1u) {\n var a0 = hs[0] * m_of(0u, c) * bs[c];\n var a1 = hs[1] * m_of(1u, c) * bs[c];\n var a2 = hs[2] * m_of(2u, c) * bs[c];\n if (cp_used) { a0 = jm[0][c]; a1 = jm[1][c]; a2 = jm[2][c]; }\n var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }\n let wgt = aj * bc * csv[c];\n c00 = c00 + wgt * a0 * a0; c01 = c01 + wgt * a0 * a1; c02 = c02 + wgt * a0 * a2;\n c11 = c11 + wgt * a1 * a1; c12 = c12 + wgt * a1 * a2; c22 = c22 + wgt * a2 * a2;\n l0 = l0 + a0 * a0 * cvv[c]; l1 = l1 + a1 * a1 * cvv[c]; l2 = l2 + a2 * a2 * cvv[c];\n // The clip bound of each channel's variance enters the covariance through a_j·B_c·(Δσ_c²/Σw²)·a_i·a_j and the\n // residual's linear prediction through a²·Δσ_c².\n lb0 = lb0 + (a0 * a0) * (bbv[c] + 2.0 * ju * cvv[c]); lb1 = lb1 + (a1 * a1) * (bbv[c] + 2.0 * ju * cvv[c]); lb2 = lb2 + (a2 * a2) * (bbv[c] + 2.0 * ju * cvv[c]);\n bound_var = bound_var + aj * bc * ((bbv[c] + 2.0 * ju * cvv[c]) * aux[${DISPLAY_VIEW_AUX.inverseW2}u + phase * 3u + c]) * max(a0 * a0, max(a1 * a1, a2 * a2));\n }\n r0 = r0 + aj * aj * max(0.0, hv[0] - l0);\n r1 = r1 + aj * aj * max(0.0, hv[1] - l1);\n r2 = r2 + aj * aj * max(0.0, hv[2] - l2);\n bound_mean = bound_mean + aj * max(hbm[0], max(hbm[1], hbm[2]));\n bound_var = bound_var + aj * aj * (max(hbv[0], max(hbv[1], hbv[2])) + max(lb0, max(lb1, lb2)));\n }\n }\n let o = i * ${DISPLAY_VIEW_OUTPUT_FLOATS}u;\n out[o] = s0; out[o + 1u] = s1; out[o + 2u] = s2;\n out[o + 3u] = c00 + r0; out[o + 4u] = c11 + r1; out[o + 5u] = c22 + r2;\n out[o + 6u] = c01; out[o + 7u] = c02; out[o + 8u] = c12;\n out[o + 9u] = bound_mean * E; out[o + 10u] = bound_var * E;\n flags[3u * i] = select(0u, 1u, material_uncertain) | select(0u, 2u, undecided);\n flags[3u * i + 1u] = mask0;\n flags[3u * i + 2u] = mask1;\n}\n`;\n\n/**\n * The channel signal of the material a material module's `material_at` gives (the kernel's `scene_value`), integrated from its\n * spectrum when it changes (2.21.0: signals are integrated from the material spectrum per site).\n */\nexport const DISPLAY_VIEW_MATERIAL_SCENE_VALUE_WGSL = /* 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`;\n/** 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`. */\nexport function assembleDisplayViewSpeedWgsl(materialModuleWgsl: string, coupledWgsl: string = DISPLAY_VIEW_COUPLED_STUB_WGSL): string {\n const adapter = /\\bfn scene_value\\(/u.test(materialModuleWgsl) ? \"\" : DISPLAY_VIEW_MATERIAL_SCENE_VALUE_WGSL;\n return DISPLAY_VIEW_SPEED_KERNEL_WGSL + \"\\n\" + materialModuleWgsl + \"\\n\" + adapter + \"\\n\" + coupledWgsl;\n}\n"]}
1
+ {"version":3,"file":"wgsl-display-view-speed-kernel.js","sourceRoot":"","sources":["../../src/gpu/wgsl-display-view-speed-kernel.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * WGSL kernel of the display view's Speed path on the GPU (#478): one invocation per display pixel of the exact\n * area mean (docs/DISPLAY_VIEW.md, \"Exact area mean\") without diffraction, with the bilinear demosaic and chart\n * materials. It evaluates `renderDisplayView`'s per-sample loop (facade/display-view.ts) for every real pixel of the\n * display pixel's footprint, in the reference's operation order where the order matters, in float32:\n *\n * 1. the real pixels the footprint covers and their overlap weights a_j = w_j / A (the host computes the 1-D overlaps\n * divided by the scale in float64; the kernel multiplies the two axes);\n * 2. for every native site the real pixels' demosaic reads (the footprint plus one site of reach), the expected\n * electrons: the scene material at the pixel aperture's query offsets (the material module, `material_at`), the\n * material's channel signal integrated in the kernel from its spectrum (Σ_λ A_c(λ)·L(λ), `material_signal`), or (#581) the image-plane\n * module's channel signal at the point (`scene_value`), summed over the offsets in the reference's order, plus the dark\n * electrons;\n * 3. the readout moments of each site (mean k·e, variance k²·e + c₀, third cumulant k³·e) and the clip at the white\n * level (the clipped-normal moments, in a cancellation-free standardized form);\n * 4. per real pixel the bilinear kernels, white balance and highlight clip, the colour matrix, the tone curve\n * (identity, or positive Reinhard by the reference's 4 × 16-point Gauss–Legendre rule) and the clip to [0, 1] with\n * the first-order skewness term;\n * 5. the display pixel's expected value Σ_j a_j·E[L_j], the linear (Stein) covariance Σ_c B_c·σ_c²·a_c·a_cᵀ with the\n * closed-form B_c of the footprint, and the independent residual Σ_j a_j²·r_j.\n *\n * It outputs, per display pixel, the three means, the three variances (linear plus residual), the three covariances\n * and two running error terms: the absolute error bound of the mean and of the variance that the clip steps\n * contribute (a fixed formula evaluated from the pixel's own float32 values; docs/WEBGPU.md, \"Display view (Speed)\").\n * #533: it also decides each real pixel's saturation probability where the float32 values leave no doubt: a site is\n * certainly unclipped or certainly clipped when its standardized distance from white, with the site's mean moved by twice\n * `DISPLAY_VIEW_SITE_RELATIVE_U`·E of itself, still exceeds twice the reference's 8.5 (where the reference's probability is\n * exactly 0 or 1), and a real pixel is certainly saturated (P = 1) when a site its demosaic reads is certainly clipped and\n * certainly not (P = 0) when all are certainly unclipped. Three flag words per display pixel: bit 0 of the first the\n * material flag, bit 1 a real pixel left undecided (the host computes the pixel's count with the reference); the other two\n * a bit per real pixel j·nx + i of the footprint with P = 1.\n * The host adds the output quantization term, draws the noise, encodes and rounds (`display-view-speed-gpu.ts`), so\n * the noise draw is the CPU's bit for bit.\n *\n * Precision: float32 with an a-priori bound per step from the WGSL accuracy table (+ − × within one float32 rounding in\n * either direction, E = 2^-23; division 2.5 ULP, sqrt 4.5 ULP, exp 3 + 2|x| ULP; no trigonometric built-in is used). Every quotient that is a constant of\n * the request (the overlaps over the scale, the axis sums over the squared scale, 1/Σw² of the demosaic kernels) is\n * computed on the host in float64 and uploaded. Data-dependent quotients (z = (t − m)/s, the Abramowitz–Stegun t,\n * the skewness) carry the 2.5 ULP bound.\n */\n\nimport { DISPLAY_VIEW_COUPLED_STUB_WGSL } from \"./wgsl-display-view-coupled-stub.js\";\n\nexport const DISPLAY_VIEW_WORKGROUP_SIZE = 64;\n/** Largest footprint per axis (real pixels): the site window is (n + 2)² ≤ 64 entries of private storage. */\nexport const DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_AXIS = 6;\nexport const DISPLAY_VIEW_GPU_WINDOW_SIDE = DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_AXIS + 2;\n/** Largest number of query offsets (pixel-aperture nodes × filter spots) the aux table holds. */\nexport const DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS = 16;\n/** Floats per display pixel in the output buffer: m0 m1 m2, v00 v11 v22 (linear + residual), c01 c02 c12, mean bound, variance bound. */\nexport const DISPLAY_VIEW_OUTPUT_FLOATS = 11;\n/** #533: flag words per display pixel: the flags (bit 0 material uncertain, bit 1 a highlight undecided), then the 36-bit saturated mask. */\nexport const DISPLAY_VIEW_FLAG_WORDS = 3;\n/**\n * #533: relative bound of a site's float32 mean and variance against the reference's float64, in units of E: the electrons'\n * sum of at most 16 offsets (each term a host-rounded weight and a material signal and one product), the dark electrons, and\n * 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\n * the material signal Σ_n A_c(λ_n)·L_m(λ_n) itself from the host-rounded response and spectrum: each non-negative product\n * 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\n * 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,\n * inside the unchanged 64.\n */\nexport const DISPLAY_VIEW_SITE_RELATIVE_U = 64;\n/** Most spectral nodes the Speed kernel integrates per site (DISPLAY_VIEW_SITE_RELATIVE_U: 16 blocks of 16). */\nexport const DISPLAY_VIEW_SPEED_MAXIMUM_NODES = 256;\n/** Floats per column or row record of the geometry buffer. */\nexport const DISPLAY_VIEW_GEOMETRY_STRIDE = 20;\n/** Layout of the aux table (float32 words). */\nexport const DISPLAY_VIEW_AUX = /* @__PURE__ */ Object.freeze({\n /** Per offset o: ou (0.5 + ox − W/2), ov (0.5 + oy − H/2), weight, 0. */\n offsets: 0,\n /** Per phase p, channel c, tap t: dx, dy, weight, 0 (4 taps; unused taps have weight 0). */\n taps: 64,\n /** Per phase p, channel c: 1 / Σ_t w_t² (host float64). */\n inverseW2: 256,\n /** 16 Gauss–Legendre nodes then 16 weights on [−1, 1]. */\n gaussLegendre: 272,\n size: 304\n});\n\n/** Uniform parameter words (see `Params` below): 40 words, 160 bytes. */\nexport const DISPLAY_VIEW_PARAM_WORDS = 40;\n/** The `E` of the bounds: one WGSL rounding of + − × in either direction, 2^-23 (W3C WGSL \"Floating Point Accuracy\"; #471, #487). */\nconst E_LITERAL = \"1.1920928955078125e-7\";\n/**\n * Clip-step constants of the running bound, in units of E (docs/WEBGPU.md, \"Display view (Speed)\", derivation): the\n * absolute error of the clipped-normal mean in units of the standard deviation (`CLIP_MEAN_U`) and of its variance in\n * units of the variance (`CLIP_VARIANCE_U`), for one clip side. The host doubles every bound (MARGIN).\n */\nexport const DISPLAY_VIEW_CLIP_MEAN_U = 1400;\nexport const DISPLAY_VIEW_CLIP_VARIANCE_U = 36000;\n/** The additional relative error of the Reinhard quadrature, in units of E, for the mean and the variance. */\nexport const DISPLAY_VIEW_REINHARD_MEAN_U = 256;\nexport const DISPLAY_VIEW_REINHARD_VARIANCE_U = 512;\n\nconst literal = (value: number): string => {\n const text = String(Math.fround(value));\n return /[.e]/u.test(text) ? text : `${text}.0`;\n};\n\n/**\n * The kernel's own WGSL, without the material module. `assembleDisplayViewSpeedWgsl` appends a module that defines\n * `fn material_at(p: vec2<f32>) -> u32` and `var<private> material_uncertain: bool` (set to true when the point's\n * classification is within its bound of a material boundary; the kernel then flags the display pixel for host\n * recomputation), and the adapter `scene_value(p, c) = material_signal(material_at(p), c)`. #581: a module may instead define\n * `fn scene_value(p: vec2<f32>, c: u32) -> f32` itself (the image-plane module: the print's channel signal at p) and then\n * declares how many roundings it adds (`DisplayViewGpuMaterialModule.extraSiteRoundings`). The module may declare bindings\n * in @group(1) and use the constants E and SLACK declared here.\n */\n/*\n * 2.22.0: in the luminance branch the nested loops read the colour matrix rows from one local array (`mm`) instead of calling\n * `m_of` (a `switch`): SwiftShader's compiler ended the GPU process on the `switch` inside those loops (after about 44 s). The\n * values are the same (docs/WEBGPU.md, \"Coupled rendering steps\").\n */\nexport const DISPLAY_VIEW_SPEED_KERNEL_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 SITE_RELATIVE_U: f32 = ${literal(DISPLAY_VIEW_SITE_RELATIVE_U)};\nconst WINDOW: u32 = ${DISPLAY_VIEW_GPU_WINDOW_SIDE}u;\nconst STRIDE: u32 = ${DISPLAY_VIEW_GEOMETRY_STRIDE}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, offsets: u32, nodes: 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, pad1: f32,\n g0: f32, g1: f32, g2: f32, pad2: f32,\n m00: f32, m01: f32, m02: f32, m10: f32,\n m11: f32, m12: f32, m20: f32, m21: f32,\n m22: f32, scale_u: f32, scale_v: f32, pad3: f32,\n};\n@group(0) @binding(0) var<uniform> params: Params;\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> spectral_table: array<f32>;\n@group(0) @binding(4) var<storage, read_write> out: array<f32>;\n@group(0) @binding(5) var<storage, read_write> flags: array<u32>;\n// #593, #601: the coupled rendering steps' constants, after the aux table's own entries (one zero word without them).\nfn cp_k(i: u32) -> f32 { return aux[${DISPLAY_VIEW_AUX.size}u + i]; }\n\nvar<private> site_mean: array<f32, 64>;\nvar<private> site_var: array<f32, 64>;\nvar<private> site_third: array<f32, 64>;\nvar<private> site_bound: array<f32, 64>;\n// #533: 0 certainly below white, 1 certainly at or above it, 2 undecided.\nvar<private> site_clip: array<u32, 64>;\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; }\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}\n// Standard normal density, as the reference forms it: 0.3989422804014327·exp(−0.5·z·z).\nfn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }\n// Abramowitz & Stegun 26.2.17: returns (Φ(z), 1 − Φ(z)), each formed from the tail q directly where it is small.\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}\n// #533: whether a site with mean m (bound dm) and variance v (bound dv) is certainly below white (0), certainly at or above it\n// (1) or undecided (2): the reference's clip probability is exactly 0 or 1 beyond 8.5 standard deviations, and the decision here\n// asks for twice that distance (4·reach²) after moving the mean by twice its bound and the variance up 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// Moments of min(Y, top) for Y ~ N(m, v): (mean, variance, slope P). The variance is v·(P − zφ + z²Q − δ²) with\n// δ = φ − zQ: algebraically E[min²] − E[min]² without the m² cancellation.\nfn upper_clip(m: f32, v: f32, top: f32) -> vec3<f32> {\n if (!(v > 0.0)) { return vec3<f32>(min(m, top), 0.0, select(0.0, 1.0, m < top)); }\n let s = sqrt(v);\n let z = (top - m) / s;\n if (z > CLIP_REACH) { return vec3<f32>(m, v, 1.0); }\n if (z < -CLIP_REACH) { return vec3<f32>(top, 0.0, 0.0); }\n let p = pq(z);\n let d = phi(z);\n let delta = d - z * p.y;\n let mean = m - s * delta;\n let ratio = p.x - z * d + z * z * p.y - delta * delta;\n return vec3<f32>(mean, v * max(0.0, ratio), p.x);\n}\n// Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1): (mean, variance, inside).\n// With Z standard normal, W = m + s·g(Z), g = clip(Z, a, b): E[g] = aΦa + φa − φb + bQb, E[g²] = a²Φa + (Φb − Φa) −\n// (bφb − aφa) + b²Qb; the mean adds the reference's Edgeworth term (s·γ/6)·(aφa − bφb).\nfn unit_clip(m: f32, v: f32, skewness: f32) -> vec3<f32> {\n if (!(v > 0.0)) { return vec3<f32>(clamp(m, 0.0, 1.0), 0.0, select(0.0, 1.0, m > 0.0 && m < 1.0)); }\n let s = sqrt(v);\n let a = -m / s;\n let b = (1.0 - m) / s;\n if (a < -CLIP_REACH && b > CLIP_REACH) { return vec3<f32>(m, v, 1.0); }\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(skewness, -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 + s * eg;\n var mean = normal_mean;\n var skew_shift = 0.0;\n if (gamma != 0.0) { skew_shift = s * gamma / 6.0 * (aa - bb); mean = normal_mean + skew_shift; }\n // The reference forms E[Y²] − (E[Y] + shift)², so the shift also enters its variance: −shift·(2·E[Y] + shift).\n return vec3<f32>(mean, max(0.0, v * (eg2 - eg * eg) - skew_shift * (2.0 * normal_mean + skew_shift)), inside);\n}\n// Positive Reinhard per channel then the clip to [0, 1]: h(x) = x/(1 + x) for x > 0, else 0. Composite 16-point\n// Gauss–Legendre over [max(0, m − 8.5s), m + 8.5s] in 4 panels, as the reference; the variance is formed about h(m).\nfn reinhard_moments(m: f32, v: f32) -> vec3<f32> {\n if (!(v > 0.0)) {\n if (m > 0.0) { return vec3<f32>(m / (1.0 + m), 0.0, 1.0 / ((1.0 + m) * (1.0 + m))); }\n return vec3<f32>(0.0, 0.0, 0.0);\n }\n let s = sqrt(v);\n // The reference's panels [max(0, m − 8.5s), m + 8.5s] in standardized units z = (x − m)/s, so the node's z is formed\n // without the cancellation x − m: zlo = max(−m/s, −8.5), weight = zhalf·w_k·φ(z_k) (= half·w_k·φ((x − m)/s)/s), x = m + s·z.\n let zlo = max(-m / s, -CLIP_REACH);\n if (!(CLIP_REACH > zlo)) { return vec3<f32>(0.0, 0.0, 0.0); }\n let hm = select(0.0, m / (1.0 + m), m > 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_AUX.gaussLegendre}u + k];\n let x = m + s * z;\n let weight = zhalf * aux[${DISPLAY_VIEW_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 return vec3<f32>(e1, max(0.0, e2 - shifted * shifted), ed);\n}\n\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; }\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}\nfn material_signal(m: u32, c: u32) -> f32 {\n let n = params.nodes;\n var s = 0.0;\n var b = 0.0;\n for (var k = 0u; k < n; k = k + 1u) {\n b = b + spectral_table[c * n + k] * spectral_table[(3u + m) * n + k];\n if ((k & 15u) == 15u) { s = s + b; b = 0.0; }\n }\n return s + b;\n}\n// The expected photo electrons of native site (x, y): Σ_o w_o·scene_value(p_o, c) in offset order (directSite); with a material\n// module scene_value is S_c(m_o), integrated from the material's spectrum (material_signal).\nfn site_electrons(x: i32, y: i32, c: u32) -> f32 {\n var sum = 0.0;\n let fx = f32(x);\n let fy = f32(y);\n for (var o = 0u; o < params.offsets; o = o + 1u) {\n let base = ${DISPLAY_VIEW_AUX.offsets}u + 4u * o;\n let u = (fx + aux[base]) * params.scale_u;\n let v = (fy + aux[base + 1u]) * params.scale_v;\n sum = sum + aux[base + 2u] * scene_value(vec2<f32>(u, v), c);\n }\n return sum;\n}\n@compute @workgroup_size(${DISPLAY_VIEW_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) groups: vec3<u32>) {\n let i = gid.x + gid.y * groups.x * ${DISPLAY_VIEW_WORKGROUP_SIZE}u;\n if (i >= params.count || nb_skip(i)) { return; }\n material_uncertain = false;\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 // 2. and 3.: the sites of the footprint plus one site of reach, once each.\n let x0 = col_first + margin - 1;\n let y0 = row_first + margin - 1;\n let window_w = nx + 2u;\n for (var sj = 0u; sj < ny + 2u; sj = sj + 1u) {\n let sy = reflect_y(y0 + i32(sj));\n for (var si = 0u; si < window_w; si = si + 1u) {\n let sx = reflect_x(x0 + i32(si));\n let c = channel_of(sx, sy);\n let e = site_electrons(sx, sy, c) + dark_of(c);\n let kc = k_of(c);\n let mean = kc * e;\n let variance = kc * kc * e + c0_of(c);\n let gap = 1.0 - mean;\n let slot = sj * window_w + si;\n site_third[slot] = kc * kc * kc * e;\n site_clip[slot] = clip_state(mean, SITE_RELATIVE_U * E * mean, variance, SITE_RELATIVE_U * E * variance);\n if (gap > 0.0 && gap * gap > params.reach2 * variance) {\n site_mean[slot] = mean; site_var[slot] = variance; site_bound[slot] = 0.0;\n } else {\n let clipped = upper_clip(mean, variance, 1.0);\n site_mean[slot] = clipped.x; site_var[slot] = clipped.y; site_bound[slot] = sqrt(variance);\n }\n }\n }\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 let reinhard = params.tone == 1u;\n var s0 = 0.0; var s1 = 0.0; var s2 = 0.0;\n var c00 = 0.0; var c01 = 0.0; var c02 = 0.0; var c11 = 0.0; var c12 = 0.0; var c22 = 0.0;\n var r0 = 0.0; var r1 = 0.0; var r2 = 0.0;\n var bound_mean = 0.0; var bound_var = 0.0;\n // #533: the saturated real pixels (bit j·nx + i) and whether any real pixel is undecided.\n var mask0 = 0u; var mask1 = 0u; var undecided = false;\n // 4. and 5.: every real pixel of the footprint with its overlap weight.\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 let aj = 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 var cm: array<f32, 3>; var cvv: array<f32, 3>; var cth: array<f32, 3>; var csv: array<f32, 3>; var cb: array<f32, 3>;\n var clipped = false; var near_white = false;\n for (var c = 0u; c < 3u; c = c + 1u) {\n var mean = 0.0; var variance = 0.0; var third = 0.0; var bnd = 0.0;\n let taps = ${DISPLAY_VIEW_AUX.taps}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n let w = aux[taps + 4u * t + 2u];\n let slot = u32((i32(j) + 1 + i32(aux[taps + 4u * t + 1u])) * i32(window_w) + i32(ii) + 1 + i32(aux[taps + 4u * t]));\n mean = mean + w * site_mean[slot];\n variance = variance + w * w * site_var[slot];\n third = third + w * w * w * site_third[slot];\n bnd = max(bnd, select(0.0, site_bound[slot], w != 0.0));\n if (w != 0.0) { if (site_clip[slot] == 1u) { clipped = true; } else if (site_clip[slot] == 2u) { near_white = true; } }\n }\n cm[c] = mean; cvv[c] = variance; cth[c] = third; cb[c] = bnd;\n csv[c] = variance * aux[${DISPLAY_VIEW_AUX.inverseW2}u + phase * 3u + c];\n }\n if (clipped) {\n let bit = j * nx + ii;\n if (bit < 32u) { mask0 = mask0 | (1u << bit); } else { mask1 = mask1 | (1u << (bit - 32u)); }\n } else if (near_white) { undecided = true; }\n // #593: reconstruct within the blend's reach integrates the whole chain below; out of its reach it is the unclipped white balance.\n var cp_chain_run = false;\n if (cp_on() && cp_reconstruct()) {\n let reach = cp_reconstruct_reach(vec3<f32>(cm[0], cm[1], cm[2]), vec3<f32>(cvv[0], cvv[1], cvv[2]));\n if (reach == 2u) { material_uncertain = true; }\n cp_chain_run = reach >= 1u;\n }\n let cp_unclipped = cp_on() && cp_reconstruct();\n // White balance and the highlight clip at the smallest gain: bm, bv, bs (slope), with the clip's bound terms.\n var bm: array<f32, 3>; var bv: array<f32, 3>; var bs: array<f32, 3>; var bbm: array<f32, 3>; var bbv: array<f32, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let gc = g_of(c);\n let um = gc * cm[c];\n let uv = gc * gc * cvv[c];\n let gap = params.clip_level - um;\n bbm[c] = gc * CLIP_MEAN_U * cb[c];\n bbv[c] = gc * gc * CLIP_VARIANCE_U * cb[c] * cb[c];\n if (cp_unclipped || (gap > 0.0 && gap * gap > params.reach2 * uv)) { bm[c] = um; bv[c] = uv; bs[c] = gc; }\n else {\n let clipped = upper_clip(um, uv, params.clip_level);\n bm[c] = clipped.x; bv[c] = clipped.y; bs[c] = gc * clipped.z;\n bbm[c] = bbm[c] + CLIP_MEAN_U * sqrt(uv);\n bbv[c] = bbv[c] + CLIP_VARIANCE_U * uv;\n }\n }\n // Colour matrix, then the rendering of each component (tone curve, clip to [0, 1]).\n var hm: array<f32, 3>; var hv: array<f32, 3>; var hs: array<f32, 3>; var hbm: array<f32, 3>; var hbv: array<f32, 3>;\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let r0m = m_of(comp, 0u); let r1m = m_of(comp, 1u); let r2m = m_of(comp, 2u);\n let mean = r0m * bm[0] + r1m * bm[1] + r2m * bm[2];\n let variance = r0m * r0m * bv[0] + r1m * r1m * bv[1] + r2m * r2m * bv[2];\n var bm_c = abs(r0m) * bbm[0] + abs(r1m) * bbm[1] + abs(r2m) * bbm[2];\n var bv_c = r0m * r0m * bbv[0] + r1m * r1m * bbv[1] + r2m * r2m * bbv[2];\n if (!reinhard && mean > 0.0 && mean < 1.0 && mean * mean > params.reach2 * variance && (1.0 - mean) * (1.0 - mean) > params.reach2 * variance) {\n hm[comp] = mean; hv[comp] = variance; hs[comp] = 1.0;\n } else if (reinhard) {\n let moments = reinhard_moments(mean, variance);\n hm[comp] = moments.x; hv[comp] = moments.y; hs[comp] = moments.z;\n bm_c = bm_c + 2.0 * CLIP_MEAN_U * sqrt(variance);\n bv_c = bv_c + 2.0 * CLIP_VARIANCE_U * variance;\n } else {\n // κ₃ of the component: Σ_c (M_comp,c·Γ_c)³·Σ_t w_t³·κ₃(site) (the sites of one channel share the coefficient).\n let k0 = r0m * bs[0]; let k1 = r1m * bs[1]; let k2 = r2m * bs[2];\n let third = k0 * k0 * k0 * cth[0] + k1 * k1 * k1 * cth[1] + k2 * k2 * k2 * cth[2];\n let skew = select(0.0, third / (variance * sqrt(variance)), variance > 0.0);\n let moments = unit_clip(mean, variance, skew);\n hm[comp] = moments.x; hv[comp] = moments.y; hs[comp] = moments.z;\n bm_c = bm_c + 2.0 * CLIP_MEAN_U * sqrt(variance);\n bv_c = bv_c + 2.0 * CLIP_VARIANCE_U * variance;\n }\n hbm[comp] = bm_c; hbv[comp] = bv_c;\n }\n // #593, #601: the coupled steps (wgsl-display-view-coupled.ts): a luminance tone curve by conditioning on luminance, and the\n // chain's 3-D rule for reconstruct and the desaturation; their slopes ∂out/∂c (camera channels) replace the per-component ones.\n var jm = array<vec3<f32>, 3>(vec3<f32>(0.0), vec3<f32>(0.0), vec3<f32>(0.0));\n var ju = 0.0;\n var cp_used = false;\n if (cp_on()) {\n if (!cp_chain_run && cp_luminance()) {\n var mu = vec3<f32>(0.0); var dmu = vec3<f32>(0.0); var dsv = vec3<f32>(0.0);\n var srow = array<vec3<f32>, 3>(vec3<f32>(0.0), vec3<f32>(0.0), vec3<f32>(0.0));\n var mm = array<vec3<f32>, 3>(vec3<f32>(params.m00, params.m01, params.m02), vec3<f32>(params.m10, params.m11, params.m12), vec3<f32>(params.m20, params.m21, params.m22));\n for (var r = 0u; r < 3u; r = r + 1u) {\n mu[r] = mm[r][0] * bm[0] + mm[r][1] * bm[1] + mm[r][2] * bm[2];\n dmu[r] = (abs(mm[r][0]) * bbm[0] + abs(mm[r][1]) * bbm[1] + abs(mm[r][2]) * bbm[2]) * E;\n dsv[r] = (mm[r][0] * mm[r][0] * bbv[0] + mm[r][1] * mm[r][1] * bbv[1] + mm[r][2] * mm[r][2] * bbv[2]) * E;\n for (var q = 0u; q < 3u; q = q + 1u) { srow[r][q] = mm[r][0] * mm[q][0] * bv[0] + mm[r][1] * mm[q][1] * bv[1] + mm[r][2] * mm[q][2] * bv[2]; }\n }\n let t = cp_tone(mu, srow[0], srow[1], srow[2], dmu, dsv);\n if (t.flag) { material_uncertain = true; }\n if (cp_desaturate() && t.top) { cp_chain_run = true; }\n else {\n let rows = array<vec3<f32>, 3>(t.a0, t.a1, t.a2);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n hm[comp] = t.hm[comp]; hv[comp] = t.hv[comp]; hbm[comp] = t.bm[comp] / E; hbv[comp] = t.bv[comp] / E;\n for (var c = 0u; c < 3u; c = c + 1u) { jm[comp][c] = (rows[comp].x * m_of(0u, c) + rows[comp].y * m_of(1u, c) + rows[comp].z * m_of(2u, c)) * bs[c]; }\n }\n ju = t.ju / E; cp_used = true;\n }\n } else if (!cp_chain_run && cp_desaturate() && params.tone != 1u) {\n // A component reaching 1 within reach is desaturated with the others (the reference's test; undecided: recompute).\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let top = hm[comp] + CLIP_REACH * sqrt(hv[comp]);\n if (hm[comp] > 0.0 && top >= 0.999) { cp_chain_run = true; }\n if (hm[comp] > 0.0 && hm[comp] - 2.0 * hbm[comp] * E < 0.999 && abs(top - 0.999) <= 2.0 * (hbm[comp] * E + CLIP_REACH * min(sqrt(hbv[comp] * E), hbv[comp] * E / (2.0 * sqrt(hv[comp])))) + 64.0 * E) { material_uncertain = true; }\n }\n }\n if (cp_chain_run && nb_ratio(i, j * nx + ii)) {\n let dcm = vec3<f32>(CLIP_MEAN_U * cb[0], CLIP_MEAN_U * cb[1], CLIP_MEAN_U * cb[2]) * E;\n let dcv = vec3<f32>(CLIP_VARIANCE_U * cb[0] * cb[0], CLIP_VARIANCE_U * cb[1] * cb[1], CLIP_VARIANCE_U * cb[2] * cb[2]) * E;\n let t = cp_chain(vec3<f32>(cm[0], cm[1], cm[2]), cvv[0], 0.0, 0.0, cvv[1], 0.0, cvv[2], dcm, dcv);\n if (t.flag) { material_uncertain = true; }\n jm[0] = t.a0; jm[1] = t.a1; jm[2] = t.a2;\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { hm[comp] = t.hm[comp]; hv[comp] = t.hv[comp]; hbm[comp] = t.bm[comp] / E; hbv[comp] = t.bv[comp] / E; }\n ju = t.ju / E; cp_used = true;\n }\n }\n s0 = s0 + aj * hm[0]; s1 = s1 + aj * hm[1]; s2 = s2 + aj * hm[2];\n // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance for the residual.\n var l0 = 0.0; var l1 = 0.0; var l2 = 0.0;\n var lb0 = 0.0; var lb1 = 0.0; var lb2 = 0.0;\n for (var c = 0u; c < 3u; c = c + 1u) {\n var a0 = hs[0] * m_of(0u, c) * bs[c];\n var a1 = hs[1] * m_of(1u, c) * bs[c];\n var a2 = hs[2] * m_of(2u, c) * bs[c];\n if (cp_used) { a0 = jm[0][c]; a1 = jm[1][c]; a2 = jm[2][c]; }\n var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }\n let wgt = aj * bc * csv[c];\n c00 = c00 + wgt * a0 * a0; c01 = c01 + wgt * a0 * a1; c02 = c02 + wgt * a0 * a2;\n c11 = c11 + wgt * a1 * a1; c12 = c12 + wgt * a1 * a2; c22 = c22 + wgt * a2 * a2;\n l0 = l0 + a0 * a0 * cvv[c]; l1 = l1 + a1 * a1 * cvv[c]; l2 = l2 + a2 * a2 * cvv[c];\n // The clip bound of each channel's variance enters the covariance through a_j·B_c·(Δσ_c²/Σw²)·a_i·a_j and the\n // residual's linear prediction through a²·Δσ_c².\n lb0 = lb0 + (a0 * a0) * (bbv[c] + 2.0 * ju * cvv[c]); lb1 = lb1 + (a1 * a1) * (bbv[c] + 2.0 * ju * cvv[c]); lb2 = lb2 + (a2 * a2) * (bbv[c] + 2.0 * ju * cvv[c]);\n bound_var = bound_var + aj * bc * ((bbv[c] + 2.0 * ju * cvv[c]) * aux[${DISPLAY_VIEW_AUX.inverseW2}u + phase * 3u + c]) * max(a0 * a0, max(a1 * a1, a2 * a2));\n }\n r0 = r0 + aj * aj * max(0.0, hv[0] - l0);\n r1 = r1 + aj * aj * max(0.0, hv[1] - l1);\n r2 = r2 + aj * aj * max(0.0, hv[2] - l2);\n bound_mean = bound_mean + aj * max(hbm[0], max(hbm[1], hbm[2]));\n bound_var = bound_var + aj * aj * (max(hbv[0], max(hbv[1], hbv[2])) + max(lb0, max(lb1, lb2)));\n }\n }\n let o = i * ${DISPLAY_VIEW_OUTPUT_FLOATS}u;\n out[o] = s0; out[o + 1u] = s1; out[o + 2u] = s2;\n out[o + 3u] = c00 + r0; out[o + 4u] = c11 + r1; out[o + 5u] = c22 + r2;\n out[o + 6u] = c01; out[o + 7u] = c02; out[o + 8u] = c12;\n out[o + 9u] = bound_mean * E; out[o + 10u] = bound_var * E;\n flags[3u * i] = select(0u, 1u, material_uncertain) | select(0u, 2u, undecided) | nb_flag();\n flags[3u * i + 1u] = mask0;\n flags[3u * i + 2u] = mask1;\n}\n`;\n\n/**\n * The channel signal of the material a material module's `material_at` gives (the kernel's `scene_value`), integrated from its\n * spectrum when it changes (2.21.0: signals are integrated from the material spectrum per site).\n */\nexport const DISPLAY_VIEW_MATERIAL_SCENE_VALUE_WGSL = /* 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`;\n/** 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`. */\nexport function assembleDisplayViewSpeedWgsl(materialModuleWgsl: string, coupledWgsl: string = DISPLAY_VIEW_COUPLED_STUB_WGSL): string {\n const adapter = /\\bfn scene_value\\(/u.test(materialModuleWgsl) ? \"\" : DISPLAY_VIEW_MATERIAL_SCENE_VALUE_WGSL;\n return DISPLAY_VIEW_SPEED_KERNEL_WGSL + \"\\n\" + materialModuleWgsl + \"\\n\" + adapter + \"\\n\" + coupledWgsl;\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  # Public API reference
2
2
 
3
- Generated from the exact root exports for **@photivra/engine 2.22.0 / root contract 2.22.0** by `node scripts/generate-api-reference.mjs`. No module deep import is supported.
3
+ Generated from the exact root exports for **@photivra/engine 2.23.0 / root contract 2.23.0** by `node scripts/generate-api-reference.mjs`. No module deep import is supported.
4
4
 
5
5
  Use [the developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md) for executable paths and scientific boundaries. Types are contracts, not runtime validation: parse untrusted data, preserve units/reference frames, read assessment blockers, and retain evidence/limitations. Optional properties do not imply a universal default. Model/schema IDs remain independent of the distribution.
6
6
 
package/docs/USAGE.md CHANGED
@@ -3334,7 +3334,8 @@ void develop;
3334
3334
  for `naka-rushton-luminance` (18 % gray stays at 0.18; the engine's own looks, not a camera maker's).
3335
3335
  - `simulateCapture`'s `output.highlightMode: "reconstruct-neighbors"` (#602, 2.22.0; opt-in, an estimate) rebuilds clipped
3336
3336
  channels from the color ratios around each clipped region: the ring's mean ratios with `fast` (the sRGB default), their harmonic
3337
- interpolation inward for exports and `fast: false`. Whole-image, so not with `output.region`; the display view renders it on the CPU.
3337
+ interpolation inward for exports and `fast: false`. Whole-image, so not with `output.region`; the display view renders it on the CPU and (since 2.23.0, #605) in its
3338
+ WebGPU Speed and Quality kernels, fast and full.
3338
3339
  - `createPhotographicExportPair(input, { signal })` accepts an optional `AbortSignal`.
3339
3340
 
3340
3341
  ## Capture-mode profiles
@@ -11,5 +11,5 @@ version they were made with. Proof-of-concept (POC), schema and model contracts
11
11
  own versions. Release checks confirm it matches package.json and the packed package.
12
12
 
13
13
  ```ts
14
- ENGINE_API_VERSION = "2.22.0" as const
14
+ ENGINE_API_VERSION = "2.23.0" as const
15
15
  ```
@@ -1,6 +1,6 @@
1
1
  {
2
- "packageVersion": "2.22.0",
3
- "engineApiVersion": "2.22.0",
2
+ "packageVersion": "2.23.0",
3
+ "engineApiVersion": "2.23.0",
4
4
  "exports": [
5
5
  {
6
6
  "name": "AbsoluteUncertaintyEstimate",
@@ -74,8 +74,8 @@ SIMULATE_CAPTURE_FAST_METHODS: readonly SimulateCaptureFastMethod[] = Object.fre
74
74
  exact: "the same kernels with the full rules (4 × 16 Gauss–Legendre; 16 points per axis), fast: false with an explicit gpu", followsFormatDefault: true,
75
75
  resolver: { file: "src/gpu/wgsl-display-view-coupled.ts", call: "out[C.fast] = r.fast ? 1 : 0" }, report: { assumption: "view.rendering" } },
76
76
  { id: "output.highlightMode.reconstruct-neighbors/region-ratios", override: "fast", methods: "fast-and-exact", runs: ["simulateCapture", "simulateDisplayView", "prepareDisplayView"],
77
- fast: "#602: each clipped region takes the mean color ratios of the unclipped pixels within 2 pixels of it",
78
- exact: "the ratios' harmonic (Laplace) interpolation inward from the region's unclipped neighbors (successive over-relaxation)", followsFormatDefault: true,
77
+ fast: "#602: each clipped region takes the mean color ratios of the unclipped pixels within 2 pixels of it; #605: on the WebGPU display view too (the host's ratios, the rebuild in float32 within the a-priori bound)",
78
+ exact: "the ratios' harmonic (Laplace) interpolation inward from the region's unclipped neighbors (successive over-relaxation), on the CPU and the WebGPU display view alike", followsFormatDefault: true,
79
79
  resolver: { file: CAPTURE, call: "const neighborFast = fastTop" }, report: { assumption: "output.highlightMode.reconstructNeighbors" } },
80
80
  { id: "lens.diffraction", override: "lens.diffraction", methods: "exact-only", runs: ["simulateCapture", "simulateDisplayView", "prepareDisplayView"],
81
81
  fast: "", exact: "the diffraction kernels (a fast method only under rolling-shutter rotation, above)", followsFormatDefault: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@photivra/engine",
3
- "version": "2.22.0",
3
+ "version": "2.23.0",
4
4
  "private": false,
5
5
  "description": "Scientific TypeScript engine for camera optics, exposure, focus, motion, sensor sampling, diffraction, and digital imaging calculations.",
6
6
  "type": "module",