@photivra/engine 2.14.1 → 2.16.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 (80) hide show
  1. package/CHANGELOG.md +69 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +14 -0
  6. package/dist/capture/native-environment-lean.js +1 -1
  7. package/dist/capture/native-environment-lean.js.map +1 -1
  8. package/dist/capture/native-raw-development.d.ts +15 -0
  9. package/dist/capture/native-raw-development.js +12 -6
  10. package/dist/capture/native-raw-development.js.map +1 -1
  11. package/dist/capture/raw-development-kernel.d.ts +10 -3
  12. package/dist/capture/raw-development-kernel.js +25 -6
  13. package/dist/capture/raw-development-kernel.js.map +1 -1
  14. package/dist/core/internal-radiance-evaluator.d.ts +5 -2
  15. package/dist/core/internal-radiance-evaluator.js.map +1 -1
  16. package/dist/core/version.d.ts +1 -1
  17. package/dist/core/version.js +1 -1
  18. package/dist/core/version.js.map +1 -1
  19. package/dist/facade/defocus-image-space-provider.d.ts +109 -0
  20. package/dist/facade/defocus-image-space-provider.js +359 -0
  21. package/dist/facade/defocus-image-space-provider.js.map +1 -0
  22. package/dist/facade/diffraction-provider.d.ts +16 -0
  23. package/dist/facade/diffraction-provider.js +3 -3
  24. package/dist/facade/diffraction-provider.js.map +1 -1
  25. package/dist/facade/display-view.d.ts +21 -0
  26. package/dist/facade/display-view.js +66 -8
  27. package/dist/facade/display-view.js.map +1 -1
  28. package/dist/facade/simulate-capture.d.ts +112 -3
  29. package/dist/facade/simulate-capture.js +235 -48
  30. package/dist/facade/simulate-capture.js.map +1 -1
  31. package/dist/facade/simulate-display-view.js +7 -3
  32. package/dist/facade/simulate-display-view.js.map +1 -1
  33. package/dist/gpu/display-view-quality-gpu.d.ts +2 -0
  34. package/dist/gpu/display-view-quality-gpu.js +57 -13
  35. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  36. package/dist/gpu/display-view-speed-gpu.d.ts +2 -0
  37. package/dist/gpu/display-view-speed-gpu.js +45 -16
  38. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  39. package/dist/gpu/image-kernel-accelerator.js +3 -0
  40. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  41. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -0
  42. package/dist/gpu/wgsl-display-view-quality-kernel.js +53 -2
  43. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  44. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +15 -0
  45. package/dist/gpu/wgsl-display-view-speed-kernel.js +42 -1
  46. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  47. package/dist/index.d.ts +3 -2
  48. package/dist/index.js +2 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/optics/defocus-disc-kernel.d.ts +91 -1
  51. package/dist/optics/defocus-disc-kernel.js +59 -5
  52. package/dist/optics/defocus-disc-kernel.js.map +1 -1
  53. package/dist/optics/diffraction-psf-profile.d.ts +9 -4
  54. package/dist/optics/diffraction-psf-profile.js +14 -6
  55. package/dist/optics/diffraction-psf-profile.js.map +1 -1
  56. package/dist/optics/lens-psf-kernels.d.ts +13 -2
  57. package/dist/optics/lens-psf-kernels.js +23 -6
  58. package/dist/optics/lens-psf-kernels.js.map +1 -1
  59. package/dist/optics/sensor-environment-query.d.ts +31 -0
  60. package/dist/optics/sensor-environment-query.js +45 -13
  61. package/dist/optics/sensor-environment-query.js.map +1 -1
  62. package/dist/optics/site-diffraction-kernel.d.ts +1 -1
  63. package/dist/optics/site-diffraction-kernel.js +37 -17
  64. package/dist/optics/site-diffraction-kernel.js.map +1 -1
  65. package/dist/processing/development-detail.d.ts +132 -0
  66. package/dist/processing/development-detail.js +325 -0
  67. package/dist/processing/development-detail.js.map +1 -0
  68. package/docs/AGENT_GUIDE.md +1 -0
  69. package/docs/API_REFERENCE.md +17 -1
  70. package/docs/QUICK_START.md +210 -16
  71. package/docs/USAGE.md +31 -3
  72. package/docs/api/capture-native-raw-development.md +16 -0
  73. package/docs/api/core-version.md +1 -1
  74. package/docs/api/exports.json +47 -2
  75. package/docs/api/facade-simulate-capture.md +129 -4
  76. package/docs/api/optics-defocus-disc-kernel.md +87 -0
  77. package/docs/api/optics-diffraction-psf-profile.md +6 -3
  78. package/docs/api/optics-lens-psf-kernels.md +3 -2
  79. package/llms.txt +1 -0
  80. package/package.json +1 -1
@@ -25,6 +25,13 @@
25
25
  * states of a table the host uploaded (the states themselves are computed on the host, #447; the kernel records every
26
26
  * grid key it needs in a bitmap and flags a display pixel whose state the table lacks).
27
27
  *
28
+ * **Highlights (#533).** The site pass records whether each site is certainly below white, certainly at or above it, or
29
+ * undecided (`clip_state`, from the site's mean and variance and their running bounds: certain only beyond twice the reference's
30
+ * 8.5 standard deviations after moving the mean by twice its bound, where the reference's clip probability is exactly 0 or 1), in
31
+ * 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
32
+ * clipped, and the display pixel `highlightUndecided` when a real pixel is neither that nor certainly unclipped. Three flag words
33
+ * per display pixel: the flags, then a bit per real pixel j·nx + i of the footprint with P = 1 (36 bits).
34
+ *
28
35
  * **Running bounds.** Every quantity after the site table is carried as a pair (value, absolute bound) in float32: the bound
29
36
  * 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
30
37
  * square root Δv/(2s) plus 4.5 ULP, of a quotient 2.5 ULP plus the propagated terms; the clipped-normal moments add the
@@ -94,9 +101,13 @@ export const DISPLAY_VIEW_QUALITY_FLAG = /* @__PURE__ */ Object.freeze({
94
101
  * exceeds `DISPLAY_VIEW_QUALITY_CHOICE_SIGMA` times the pixel's noise standard deviation.
95
102
  */
96
103
  ahdChoice: 32,
104
+ /** #533: a real pixel's saturation is undecided (the host computes the display pixel's count with the reference; its samples stay). */
105
+ highlightUndecided: 64,
97
106
  /** The bits that require the host's recomputation. */
98
107
  recompute: 47
99
108
  });
109
+ /** #533: flag words per display pixel: the flags, then the 36-bit mask of saturated real pixels. */
110
+ export const DISPLAY_VIEW_QUALITY_FLAG_WORDS = 3;
100
111
  /**
101
112
  * #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
102
113
  * the kernel keeps (it renders the candidates' average and its bound carries the move); a larger one is recomputed on the host. A
@@ -180,6 +191,17 @@ fn b_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x * b.x; return ve
180
191
 
181
192
  fn 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)); }
182
193
 
194
+
195
+ fn clip_state(m: f32, dm: f32, v: f32, dv: f32) -> u32 {
196
+ let gap = 1.0 - m;
197
+ let wide = 4.0 * params.reach2 * (v + 2.0 * dv);
198
+ let below = gap - 2.0 * dm;
199
+ let above = gap + 2.0 * dm;
200
+ if (below > 0.0 && below * below > wide) { return 0u; }
201
+ if (above < 0.0 && above * above > wide) { return 1u; }
202
+ return 2u;
203
+ }
204
+
183
205
  fn b_rounded(k: f32) -> vec2<f32> { return vec2<f32>(k, E * abs(k)); }
184
206
  fn 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)); }
185
207
 
@@ -358,6 +380,9 @@ let third = kc * kc * kc * e;
358
380
  var m = vec2<f32>(mean, kc * de + 2.0 * E * mean);
359
381
  var v = vec2<f32>(variance, kc * kc * de + 4.0 * E * variance);
360
382
  let gap = 1.0 - mean;
383
+
384
+ let state = clip_state(m.x, m.y, v.x, v.y);
385
+ let bits = flag | select(0u, 2u, state == 1u) | select(0u, 4u, state == 2u);
361
386
  if (!(gap > 0.0 && gap * gap > params.reach2 * variance)) {
362
387
  let clipped = upper_clip(m, v, 1.0);
363
388
  m = clipped.mean; v = clipped.variance;
@@ -365,7 +390,7 @@ m = clipped.mean; v = clipped.variance;
365
390
  let o = i * SITE;
366
391
  sites[o] = m.x; sites[o + 1u] = v.x; sites[o + 2u] = third;
367
392
  sites[o + 3u] = m.y; sites[o + 4u] = v.y; sites[o + 5u] = kc * kc * kc * de + 5.0 * E * third;
368
- sites[o + 6u] = bitcast<f32>(flag); sites[o + 7u] = e;
393
+ sites[o + 6u] = bitcast<f32>(bits); sites[o + 7u] = e;
369
394
  }
370
395
  `;
371
396
  /**
@@ -498,6 +523,7 @@ const FLAG_STATE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.stateMissing}u;
498
523
  const FLAG_TIE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.thresholdTie}u;
499
524
  const FLAG_CORRECTED: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.corrected}u;
500
525
  const FLAG_CHOICE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.ahdChoice}u;
526
+ const FLAG_HIGHLIGHT: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.highlightUndecided}u;
501
527
  const CHOICE_SIGMA: f32 = ${literal(DISPLAY_VIEW_QUALITY_CHOICE_SIGMA)};
502
528
  @group(0) @binding(1) var<storage, read> aux: array<f32>;
503
529
  @group(0) @binding(2) var<storage, read> geometry: array<f32>;
@@ -522,6 +548,8 @@ pixel_flags = pixel_flags | (bitcast<u32>(sites[o + 6u]) & 1u);
522
548
  return o;
523
549
  }
524
550
 
551
+ fn clip_bits(o: u32) -> u32 { return (bitcast<u32>(sites[o + 6u]) >> 1u) & 3u; }
552
+
525
553
  fn find_state(key: i32) -> u32 {
526
554
  var lo = 0u; var hi = params.state_count;
527
555
  while (lo < hi) {
@@ -616,6 +644,8 @@ var cov = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), ve
616
644
  var res = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
617
645
 
618
646
  var choice = array<f32, 3>(0.0, 0.0, 0.0);
647
+
648
+ var mask0 = 0u; var mask1 = 0u; var undecided = false;
619
649
  for (var j = 0u; j < ny; j = j + 1u) {
620
650
  let ay = geometry[row + 2u + j];
621
651
  for (var ii = 0u; ii < nx; ii = ii + 1u) {
@@ -639,6 +669,25 @@ variance = b_add(variance, b_scale(vec2<f32>(sites[o + 1u], sites[o + 4u]), w *
639
669
  }
640
670
  let cb = CAND_BASE + phase * CAND_STRIDE;
641
671
  let entries = bitcast<u32>(aux[cb]);
672
+
673
+ var clip_any = 0u;
674
+ for (var c = 0u; c < 3u; c = c + 1u) {
675
+ let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;
676
+ for (var t = 0u; t < 4u; t = t + 1u) {
677
+ if (aux[tb + 4u * t + 2u] == 0.0) { continue; }
678
+ clip_any = clip_any | clip_bits(site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u])));
679
+ }
680
+ }
681
+ for (var n = 0u; n < entries; n = n + 1u) {
682
+ let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;
683
+ var used = false;
684
+ for (var k = 0u; k < 3u * count; k = k + 1u) { if (aux[eb + 2u + k] != 0.0) { used = true; } }
685
+ if (used) { clip_any = clip_any | clip_bits(site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]))); }
686
+ }
687
+ if ((clip_any & 1u) != 0u) {
688
+ let bit = j * nx + ii;
689
+ if (bit < 32u) { mask0 = mask0 | (1u << bit); } else { mask1 = mask1 | (1u << (bit - 32u)); }
690
+ } else if ((clip_any & 2u) != 0u) { undecided = true; }
642
691
  var e_mean = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
643
692
  var cm_average = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
644
693
  var pixel_variance = vec2<f32>(0.0, 0.0);
@@ -793,7 +842,9 @@ out[o + 3u] = cov[0].x + res[0].x; out[o + 4u] = cov[3].x + res[1].x; out[o + 5u
793
842
  out[o + 6u] = cov[1].x; out[o + 7u] = cov[2].x; out[o + 8u] = cov[4].x;
794
843
  out[o + 9u] = max(s[0].y, max(s[1].y, s[2].y));
795
844
  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))));
796
- flags[i] = pixel_flags;
845
+ flags[3u * i] = pixel_flags | select(0u, FLAG_HIGHLIGHT, undecided);
846
+ flags[3u * i + 1u] = mask0;
847
+ flags[3u * i + 2u] = mask1;
797
848
  }
798
849
  `;
799
850
  /** The lattice pass with a material module appended (`fn material_at(p: vec2<f32>) -> u32`, `var<private> material_uncertain: bool`). */
@@ -1 +1 @@
1
- {"version":3,"file":"wgsl-display-view-quality-kernel.js","sourceRoot":"","sources":["../../src/gpu/wgsl-display-view-quality-kernel.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * WGSL kernels of the display view's Quality path on the GPU (#479): the exact area mean with diffraction kernels, the\n * anti-aliasing filter's spots, the AHD candidates and the low-signal correction, in three passes over one band of display\n * rows (docs/WEBGPU.md, \"Display view (Quality) on the GPU\"; the float64 reference is `renderDisplayView` with\n * `createDisplayViewExpectedSignal`, facade/display-view.ts and facade/expected-signal.ts):\n *\n * 1. **Lattice** (`DISPLAY_VIEW_QUALITY_LATTICE_WGSL` plus the material module): one invocation per lattice point of each\n * lattice group (offsets that share a fractional shift, as the reference groups them): the chart material at\n * (p + 0.5 + f_x − W/2)·g, −(q + 0.5 + f_y − H/2)·g (the reference's lattice formula, an exact float32 sum times the\n * scale rounded once), with the module's uncertainty flag in the top bit.\n * 2. **Sites** (`DISPLAY_VIEW_QUALITY_SITES_WGSL`): one invocation per native site of the band's window: the expected\n * electrons Σ_t V_c(m_t, t) over the taps t of every group (the combined kernel values per material and channel, formed on\n * the host in float64 as the reference forms them: Σ_members w·Σ_λ A_c(λ)·K_λ·L_m(λ); without diffraction the offset\n * weights times the material's channel signal), plus the dark electrons; the readout moments (mean k·e, variance k²e + c₀,\n * third cumulant k³e) and the clip at the white level; and each quantity's running absolute error bound. With the defocus\n * disc (#492) the variant `DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL` reads a factored table (one neutral row, one row per\n * spectral material) and sums each group's dense tap rectangle in blocks, so its bound grows with the kernel's width.\n * 3. **Pixels** (`DISPLAY_VIEW_QUALITY_PIXELS_WGSL`): one invocation per display pixel of the band: for every real pixel of\n * the footprint, the demosaic candidates (one bilinear, or AHD's two directional linear candidates, each with its full\n * camera-channel covariance), white balance and the highlight clip, the colour matrix, the tone curve and the clip to\n * [0, 1] with the skewness term, the candidates' average, the Stein covariance with the closed-form B_c, the independent\n * residual, AHD's choice variance ¼·Var(H − V), the flag for AHD's choice on the noise-free mosaic, which the reference takes and\n * the kernel does not (#457), and the low-signal correction interpolated trilinearly between the grid\n * states of a table the host uploaded (the states themselves are computed on the host, #447; the kernel records every\n * grid key it needs in a bitmap and flags a display pixel whose state the table lacks).\n *\n * **Running bounds.** Every quantity after the site table is carried as a pair (value, absolute bound) in float32: the bound\n * of a sum is the sum of the bounds plus one rounding of the result, of a product |a|·Δb + |b|·Δa plus one rounding, of a\n * square root Δv/(2s) plus 4.5 ULP, of a quotient 2.5 ULP plus the propagated terms; the clipped-normal moments add the\n * Speed kernel's clip constants (CLIP_MEAN_U, CLIP_VARIANCE_U per standard deviation or variance) and propagate their inputs\n * with the moments' first-order derivatives at the pixel's own standardized clip levels (∂E/∂m = P(inside), ∂E/∂s = φa − φb,\n * ∂Var/∂m = −s(ρ_a + ρ_b), ∂Var/∂s = s(2ρ − aρ_a − bρ_b) with ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]), and the slope's\n * (φa − φb)/s and (aφa − bφb)/s; without noise a slope is exact unless the mean's interval straddles a clip level); the\n * Reinhard quadrature adds REINHARD_MEAN_U and REINHARD_VARIANCE_U. Mixed-sign sums (the AHD\n * kernels, the colour matrix) are therefore bounded by the magnitudes of their terms, not relative to their result. The\n * pixel's expected value and variance leave the kernel with their absolute bounds; the host doubles them (MARGIN) and adds\n * the footprint sum's relative term. Second-order terms (products of bounds) and the bound arithmetic's own float32\n * roundings are what the doubling covers.\n *\n * Precision assumptions are those of the Speed kernel: the W3C WGSL accuracy table (+ − × one rounding in either direction,\n * E = 2^-23; division 2.5 ULP, sqrt 4.5 ULP, exp (3 + 2|x|) ULP, log2 2^-21 absolute on [0.5, 2] and 3 ULP outside; no\n * trigonometric built-in).\n */\n\nimport { DISPLAY_VIEW_CLIP_MEAN_U, DISPLAY_VIEW_CLIP_VARIANCE_U, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U } from \"./wgsl-display-view-speed-kernel.js\";\nimport { AHD_KEY_FLOOR, AHD_KEY_LEVELS, AHD_KEY_OCTAVES, AHD_KEY_STEPS } from \"../facade/display-view.js\";\n\nexport const DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE = 64;\n/** Largest footprint per axis (real pixels), as the Speed kernel. */\nexport const DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS = 6;\n/** Uniform parameter words (see `Params`): 56 words, 224 bytes. */\nexport const DISPLAY_VIEW_QUALITY_PARAM_WORDS = 56;\n/** Words per lattice-group record: lx0, ly0, lw, lh, lbase, first tap (the blocked site pass, #492), cu (f32), cv (f32). */\nexport const DISPLAY_VIEW_QUALITY_GROUP_WORDS = 8;\n/** Words per tap record: group, dp offset, dq offset, pad. */\nexport const DISPLAY_VIEW_QUALITY_TAP_WORDS = 4;\n/** Floats per native site of the site buffer: mean, variance, third cumulant, their three bounds, flag (u32 bits), electrons. */\nexport const DISPLAY_VIEW_QUALITY_SITE_FLOATS = 8;\n/** Floats per display pixel in the output buffer: m0 m1 m2, v00 v11 v22, c01 c02 c12, mean bound, variance bound (absolute). */\nexport const DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS = 11;\n/** Floats per column or row record of the geometry buffer (the Speed layout). */\nexport const DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE = 20;\n/** Candidate table: entries per phase (the 7 × 7 window of AHD's reach 3) and floats per entry (dx, dy, dh0, dh1, dh2, dv0, dv1, dv2). */\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES = 49;\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS = 8;\nconst CANDIDATE_PHASE_STRIDE = 4 + DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES * DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS;\n/** Layout of the aux table (float32 words). */\nexport const DISPLAY_VIEW_QUALITY_AUX = /* @__PURE__ */ Object.freeze({\n /** Per phase p, channel c, tap t: dx, dy, weight, 0 (the bilinear kernels, 4 taps; unused taps have weight 0). */\n bilinear: 0,\n /** Per phase p, channel c: 1 / Σ_t w_t² of the bilinear kernel (host float64). */\n inverseW2: 192,\n /** 16 Gauss–Legendre nodes then 16 weights on [−1, 1]. */\n gaussLegendre: 208,\n /** Per phase: the entry count (as u32 bits) and 3 pad words, then the entries (dx, dy, dense weights of candidate 0 and 1 per channel). */\n candidates: 240,\n candidatePhaseStride: CANDIDATE_PHASE_STRIDE,\n size: 240 + 4 * CANDIDATE_PHASE_STRIDE\n});\n/** Pixel flag bits of the output flag word. */\nexport const DISPLAY_VIEW_QUALITY_FLAG = /* @__PURE__ */ Object.freeze({\n /** A site the pixel read had an uncertain lattice material (recompute on the host). */\n materialUncertain: 1,\n /** A site the pixel read lies outside the band's site window (a host geometry error; recompute). */\n siteOutsideWindow: 2,\n /** The correction needed a grid state the table does not hold (recompute, or compute the state and dispatch again). */\n stateMissing: 4,\n /** A sample's pixel variance lies within its bound of the correction threshold (recompute). */\n thresholdTie: 8,\n /** Informational: the pixel's expected value includes the low-signal correction. */\n corrected: 16,\n /**\n * #457: the reference takes AHD's choice on the noise-free mosaic (one candidate, or their average on a tie), which the kernel\n * does not compute; it renders the average. The pixel is flagged (recompute) when Σ_j a_j·½|h(H) − h(V)| of some component\n * exceeds `DISPLAY_VIEW_QUALITY_CHOICE_SIGMA` times the pixel's noise standard deviation.\n */\n ahdChoice: 32,\n /** The bits that require the host's recomputation. */\n recompute: 47\n});\n/**\n * #457: the largest move, in units of the display pixel's own noise σ, that AHD's choice between its candidates may make to a pixel\n * the kernel keeps (it renders the candidates' average and its bound carries the move); a larger one is recomputed on the host. A\n * design tolerance, not a measurement: 2^-10 σ is a tenth of the 0.01 σ to which the float32 tests hold the GPU to the reference.\n */\nexport const DISPLAY_VIEW_QUALITY_CHOICE_SIGMA = 2 ** -10;\n/** Words of the grid-key bitmap: one bit per grid key (AHD_KEY_LEVELS³ keys). */\nexport const DISPLAY_VIEW_QUALITY_BITMAP_WORDS = Math.ceil(AHD_KEY_LEVELS ** 3 / 32);\n/**\n * A site whose uncertain lattice points (the material module's flag) could move its electrons by more than this fraction of\n * their value is flagged for host recomputation; a smaller uncertainty enters the site's bound instead (the tap weights times\n * the largest value difference between any two materials at that tap).\n */\nexport const DISPLAY_VIEW_QUALITY_UNCERTAINTY_LIMIT = 2 ** -10;\n/** Bound constant of a clipped-normal slope Φ(z) (in units of E): z's 8E through φ·|z| ≤ 0.24 plus the Abramowitz–Stegun 147E. */\nexport const DISPLAY_VIEW_QUALITY_SLOPE_U = 200;\n/** Relative bound of the Reinhard slope E[h′] (in units of E). */\nconst REINHARD_SLOPE_U = 64;\n/** The `E` of the bounds: one WGSL rounding of + − × in either direction, 2^-23. */\nconst E_LITERAL = \"1.1920928955078125e-7\";\nconst literal = (value: number): string => {\n const text = String(Math.fround(value));\n return /[.e]/u.test(text) ? text : `${text}.0`;\n};\n\n/** The parameter struct and the bound helpers shared by the three passes. */\nconst COMMON_WGSL = /* wgsl */ `\nconst E: f32 = ${E_LITERAL};\nconst SLACK: f32 = 1e-14;\nconst CLIP_REACH: f32 = 8.5;\nconst CLIP_MEAN_U: f32 = ${literal(DISPLAY_VIEW_CLIP_MEAN_U)};\nconst CLIP_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_CLIP_VARIANCE_U)};\nconst CLIP_SLOPE_U: f32 = ${literal(DISPLAY_VIEW_QUALITY_SLOPE_U)};\nconst REINHARD_MEAN_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_MEAN_U)};\nconst REINHARD_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_VARIANCE_U)};\nconst REINHARD_SLOPE_U: f32 = ${literal(REINHARD_SLOPE_U)};\nconst UNCERTAINTY_LIMIT: f32 = ${literal(2 ** -10)};\nconst STRIDE: u32 = ${DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE}u;\nconst SITE: u32 = ${DISPLAY_VIEW_QUALITY_SITE_FLOATS}u;\nconst GROUP: u32 = ${DISPLAY_VIEW_QUALITY_GROUP_WORDS}u;\nconst TAP: u32 = ${DISPLAY_VIEW_QUALITY_TAP_WORDS}u;\nstruct Params {\n count: u32, row0: u32, region_w: u32, region_x: u32,\n region_y: u32, margin: u32, native_w: u32, native_h: u32,\n columns: u32, tone: u32, reach: u32, candidates: u32,\n site_x0: i32, site_y0: i32, site_w: u32, site_h: u32,\n correction: u32, state_count: u32, taps: u32, groups: u32,\n lattice_total: u32, gain_at_clip0: u32, gain_at_clip1: u32, gain_at_clip2: u32,\n k0: f32, k1: f32, k2: f32, reach2: f32,\n dark0: f32, dark1: f32, dark2: f32, clip_level: f32,\n c0_0: f32, c0_1: f32, c0_2: f32, threshold: f32,\n g0: f32, g1: f32, g2: f32, scale_u: f32,\n m00: f32, m01: f32, m02: f32, m10: f32,\n m11: f32, m12: f32, m20: f32, m21: f32,\n m22: f32, scale_v: f32, site_gamma: f32, site_first: u32,\n site_tiny: f32, pad_f: f32, pad_g: f32, pad_h: f32,\n};\n@group(0) @binding(0) var<uniform> params: Params;\n\nfn k_of(c: u32) -> f32 { if (c == 0u) { return params.k0; } if (c == 1u) { return params.k1; } return params.k2; }\nfn dark_of(c: u32) -> f32 { if (c == 0u) { return params.dark0; } if (c == 1u) { return params.dark1; } return params.dark2; }\nfn c0_of(c: u32) -> f32 { if (c == 0u) { return params.c0_0; } if (c == 1u) { return params.c0_1; } return params.c0_2; }\nfn g_of(c: u32) -> f32 { if (c == 0u) { return params.g0; } if (c == 1u) { return params.g1; } return params.g2; }\n// Whether the channel's white-balance gain is the clip level itself (the smallest gain), exactly, in the reference's float64.\nfn gain_at_clip(c: u32) -> bool { if (c == 0u) { return params.gain_at_clip0 == 1u; } if (c == 1u) { return params.gain_at_clip1 == 1u; } return params.gain_at_clip2 == 1u; }\nfn m_of(r: u32, c: u32) -> f32 {\n let i = r * 3u + c;\n switch (i) {\n case 0u: { return params.m00; } case 1u: { return params.m01; } case 2u: { return params.m02; }\n case 3u: { return params.m10; } case 4u: { return params.m11; } case 5u: { return params.m12; }\n case 6u: { return params.m20; } case 7u: { return params.m21; } default: { return params.m22; }\n }\n}\nfn channel_of(x: i32, y: i32) -> u32 {\n if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }\n return select(2u, 1u, (x & 1) == 0);\n}\n// A value with its absolute error bound (x: value, y: bound). First-order rules; the host doubles the final bound.\nfn b_add(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x + b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_sub(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x - b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x * b.x; return vec2<f32>(v, abs(a.x) * b.y + abs(b.x) * a.y + E * abs(v)); }\n// Times an exact constant (a kernel weight, an integer, a power of two).\nfn b_scale(a: vec2<f32>, k: f32) -> vec2<f32> { let v = a.x * k; return vec2<f32>(v, a.y * abs(k) + E * abs(v)); }\n// A host constant rounded once to float32 (a gain, a matrix entry).\nfn b_rounded(k: f32) -> vec2<f32> { return vec2<f32>(k, E * abs(k)); }\nfn b_div(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x / b.x; return vec2<f32>(v, (a.y + abs(v) * b.y) / abs(b.x) + 2.5 * E * abs(v)); }\n// |sqrt(v + d) - sqrt(v)| <= min(d / (2 sqrt v), sqrt d), plus the 4.5 ULP of the built-in.\nfn b_sqrt(a: vec2<f32>) -> vec2<f32> {\n let s = sqrt(max(a.x, 0.0));\n if (s > 0.0) { return vec2<f32>(s, min(a.y / (2.0 * s), sqrt(a.y)) + 4.5 * E * s); }\n return vec2<f32>(0.0, sqrt(a.y));\n}\n// Standard normal density, as the reference forms it, and Abramowitz & Stegun 26.2.17 as (Φ, 1 − Φ).\nfn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }\nfn pq(z: f32) -> vec2<f32> {\n let x = abs(z);\n let t = 1.0 / (1.0 + 0.2316419 * x);\n let q = phi(x) * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));\n if (z >= 0.0) { return vec2<f32>(1.0 - q, q); }\n return vec2<f32>(q, 1.0 - q);\n}\nstruct Moments { mean: vec2<f32>, variance: vec2<f32>, slope: vec2<f32> }\n// Moments of min(Y, top) for Y ~ N(m, v) with their bounds: Var = v·(Φ − zφ + z²Q − δ²), δ = φ − zQ (the Speed forms).\nfn upper_clip(m: vec2<f32>, v: vec2<f32>, top: f32) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n // No noise on either side (a zero bound): the reference's slope is 1 or 0 by m < top, decided when m's interval lies on one side.\n let decided = v.y == 0.0 && (m.x - m.y >= top || m.x + m.y < top);\n out.mean = vec2<f32>(min(m.x, top), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x < top), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let z = (top - m.x) / s.x;\n if (z > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n if (z < -CLIP_REACH) { out.mean = vec2<f32>(top, 0.0); out.variance = vec2<f32>(0.0, 0.0); out.slope = vec2<f32>(0.0, 0.0); return out; }\n let p = pq(z);\n let d = phi(z);\n let delta = d - z * p.y;\n let ratio = p.x - z * d + z * z * p.y - delta * delta;\n // First-order propagation of the inputs' bounds: with g = min(Z, z), E[g] = −δ and ρ = Var(g) = ratio, ∂E/∂m = Φ, ∂E/∂s = φ,\n // ∂Var/∂m = −s·ρ_z, ∂Var/∂s = s·(2ρ − z·ρ_z) with ρ_z = 2Q·(z + δ), ∂Φ/∂m = −φ/s and ∂Φ/∂s = −zφ/s.\n let rho_z = 2.0 * p.y * (z + delta);\n out.mean = vec2<f32>(m.x - s.x * delta, p.x * m.y + d * s.y + CLIP_MEAN_U * E * s.x);\n out.variance = vec2<f32>(v.x * max(0.0, ratio), s.x * abs(rho_z) * m.y + s.x * abs(2.0 * ratio - z * rho_z) * s.y + CLIP_VARIANCE_U * E * v.x);\n out.slope = vec2<f32>(p.x, min(1.0, (d * m.y + abs(z) * d * s.y) / s.x + CLIP_SLOPE_U * E));\n return out;\n}\n// Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1), with their bounds (the\n// Speed forms: W = m + s·g(Z), E[g] = aΦa + φa − φb + bQb, E[g²] = a²Φa + (Φb − Φa) − (bφb − aφa) + b²Qb, the Edgeworth\n// shift (s·γ/6)·(aφa − bφb) on the mean and −shift·(2·E[Y] + shift) on the variance).\nfn unit_clip(m: vec2<f32>, v: vec2<f32>, skew: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n let decided = v.y == 0.0 && ((m.x - m.y > 0.0 && m.x + m.y < 1.0) || m.x + m.y <= 0.0 || m.x - m.y >= 1.0);\n out.mean = vec2<f32>(clamp(m.x, 0.0, 1.0), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x > 0.0 && m.x < 1.0), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let a = -m.x / s.x;\n let b = (1.0 - m.x) / s.x;\n if (a < -CLIP_REACH && b > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n var pa = 0.0; var da = 0.0;\n if (!(a < -CLIP_REACH)) { pa = pq(a).x; da = phi(a); }\n var pb = 1.0; var qb = 0.0; var db = 0.0;\n if (!(b > CLIP_REACH)) { let p = pq(b); pb = p.x; qb = p.y; db = phi(b); }\n let inside = pb - pa;\n let gamma = clamp(skew.x, -1.0, 1.0);\n let aa = a * da;\n let bb = b * db;\n let eg = a * pa + (da - db) + b * qb;\n let eg2 = a * a * pa + inside - (bb - aa) + b * b * qb;\n let normal_mean = m.x + s.x * eg;\n var mean = normal_mean;\n var shift = 0.0;\n if (gamma != 0.0) { shift = s.x * gamma / 6.0 * (aa - bb); mean = normal_mean + shift; }\n // |aφa − bφb| ≤ 0.48, so |shift| ≤ 0.08·s·|γ| and a wrong γ moves it by at most 0.16·s.\n let d_shift = 0.1 * s.x * min(2.0, skew.y) + 0.1 * abs(gamma) * s.y + E * abs(shift);\n // First-order propagation of the inputs' bounds: with ρ = E[g²] − E[g]², ∂E[g]/∂a = Φa, ∂E[g]/∂b = Qb, ∂E[g²]/∂a = 2aΦa,\n // ∂E[g²]/∂b = 2bQb, so ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]); ∂E/∂m = inside, ∂E/∂s = φa − φb, ∂Var/∂m = −s(ρ_a + ρ_b),\n // ∂Var/∂s = s(2ρ − aρ_a − bρ_b); the slope's derivatives are (φa − φb)/s and (aφa − bφb)/s.\n let rho = eg2 - eg * eg;\n let rho_a = 2.0 * pa * (a - eg);\n let rho_b = 2.0 * qb * (b - eg);\n let d_mean = m.y * inside + abs(da - db) * s.y + 2.0 * CLIP_MEAN_U * E * s.x + d_shift;\n // The normal part X = v·ρ and its bound; the reference's variance is max(0, X − shift·(2·E[Y] + shift)), so it lies in\n // [0, X_cpu] when the shift term is non-negative on both sides (shift > Δshift), which caps the bound by max(Var, X + ΔX).\n let normal_variance = v.x * rho;\n let d_normal = s.x * abs(rho_a + rho_b) * m.y + s.x * abs(2.0 * rho - a * rho_a - b * rho_b) * s.y + 2.0 * CLIP_VARIANCE_U * E * v.x;\n let variance = max(0.0, normal_variance - shift * (2.0 * normal_mean + shift));\n var d_var = d_normal + 2.0 * (d_shift * (abs(normal_mean) + abs(shift)) + abs(shift) * d_mean);\n if (shift > d_shift) { d_var = min(d_var, max(variance, max(0.0, normal_variance) + d_normal)); }\n out.mean = vec2<f32>(mean, d_mean);\n out.variance = vec2<f32>(variance, d_var);\n out.slope = vec2<f32>(inside, min(1.0, (abs(da - db) * m.y + abs(aa - bb) * s.y) / s.x + 2.0 * CLIP_SLOPE_U * E));\n return out;\n}\n`;\n\n/** The Gauss–Legendre Reinhard moments read the aux table, so they live in the pixel kernel. */\nconst REINHARD_WGSL = /* wgsl */ `\n// Positive Reinhard per channel then the clip to [0, 1]: h(x) = x/(1 + x) for x > 0, else 0, by the reference's composite\n// 16-point Gauss–Legendre rule in standardized units (the Speed forms), with the bounds of the quadrature (REINHARD_*_U)\n// and of its inputs (|h′| ≤ 1, |h″| ≤ 2).\nfn reinhard_moments(m: vec2<f32>, v: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n if (m.x > 0.0) {\n let h = m.x / (1.0 + m.x);\n out.mean = vec2<f32>(h, m.y + 4.0 * E * h); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(1.0 / ((1.0 + m.x) * (1.0 + m.x)), min(1.0, 2.0 * m.y + 6.0 * E));\n return out;\n }\n out.mean = vec2<f32>(0.0, m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0);\n return out;\n }\n let s = b_sqrt(v);\n let zlo = max(-m.x / s.x, -CLIP_REACH);\n if (!(CLIP_REACH > zlo)) { out.mean = vec2<f32>(0.0, m.y + s.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0); return out; }\n let hm = select(0.0, m.x / (1.0 + m.x), m.x > 0.0);\n var e1 = 0.0; var e2 = 0.0; var ed = 0.0;\n let zwidth = (CLIP_REACH - zlo) / 4.0;\n for (var p = 0u; p < 4u; p = p + 1u) {\n let zc = zlo + (f32(p) + 0.5) * zwidth;\n let zhalf = zwidth / 2.0;\n for (var k = 0u; k < 16u; k = k + 1u) {\n let z = zc + zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre}u + k];\n let x = m.x + s.x * z;\n let weight = zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre + 16}u + k] * phi(z);\n let h = x / (1.0 + x);\n e1 = e1 + weight * h;\n e2 = e2 + weight * (h - hm) * (h - hm);\n ed = ed + weight / ((1.0 + x) * (1.0 + x));\n }\n }\n let shifted = e1 - hm;\n let variance = max(0.0, e2 - shifted * shifted);\n out.mean = vec2<f32>(e1, m.y + s.y + REINHARD_MEAN_U * E * abs(e1));\n out.variance = vec2<f32>(variance, 2.0 * s.x * m.y + 2.0 * v.y + REINHARD_VARIANCE_U * E * variance);\n out.slope = vec2<f32>(ed, min(1.0, 2.0 * m.y + 2.0 * s.y + REINHARD_SLOPE_U * E));\n return out;\n}\n`;\n\n/**\n * Pass 1: the material of every lattice point of every group (the module's `material_at`, its uncertainty in bit 31; the output\n * is `lattice_out`, since a material module may declare its own `lattice`, as #491's composite module does). The group\n * records hold each group's lattice window (lx0, ly0, lw, lh), its first index in the lattice buffer and the point constants\n * c_u = 0.5 + f_x − W/2 and c_v = 0.5 + f_y − H/2 (exact in float32, checked by the host).\n */\nexport const DISPLAY_VIEW_QUALITY_LATTICE_WGSL = /* wgsl */ `${COMMON_WGSL}\n@group(0) @binding(1) var<storage, read> groups: array<u32>;\n@group(0) @binding(2) var<storage, read_write> lattice_out: array<u32>;\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.lattice_total) { return; }\n var g = 0u;\n for (var k = 1u; k < params.groups; k = k + 1u) { if (i >= groups[k * GROUP + 4u]) { g = k; } }\n let base = g * GROUP;\n let lx0 = bitcast<i32>(groups[base]);\n let ly0 = bitcast<i32>(groups[base + 1u]);\n let lw = groups[base + 2u];\n let local = i - groups[base + 4u];\n let q = ly0 + i32(local / lw);\n let p = lx0 + i32(local % lw);\n let u = (f32(p) + bitcast<f32>(groups[base + 6u])) * params.scale_u;\n let v = (f32(q) + bitcast<f32>(groups[base + 7u])) * params.scale_v;\n material_uncertain = false;\n let m = material_at(vec2<f32>(u, v));\n lattice_out[i] = m | select(0u, 0x80000000u, material_uncertain);\n}\n`;\n\n/**\n * The end of pass 2, shared by both site passes: from the site's electrons e and their absolute bound de, the readout moments\n * (mean k·e, variance k²e + c₀, third cumulant k³e), the clip at the white level and their bounds, written to site i.\n */\nconst SITE_TAIL_WGSL = /* wgsl */ `\nfn write_site(i: u32, c: u32, e: f32, de: f32, flag: u32) {\n let kc = k_of(c);\n let mean = kc * e;\n let variance = kc * kc * e + c0_of(c);\n let third = kc * kc * kc * e;\n var m = vec2<f32>(mean, kc * de + 2.0 * E * mean);\n var v = vec2<f32>(variance, kc * kc * de + 4.0 * E * variance);\n let gap = 1.0 - mean;\n 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>(flag); sites[o + 7u] = e;\n}\n`;\n\n/**\n * The bindings of the site passes (pass 2); the blocked pass reads no tap records, so it declares no binding 2 (an automatic\n * layout leaves out a binding the entry point does not use, and the host binds it only for the tap-list pass).\n */\nconst siteBindingsWgsl = (tapRecords: boolean): string => /* wgsl */ `\n@group(0) @binding(1) var<storage, read> groups: array<u32>;${tapRecords ? `\n@group(0) @binding(2) var<storage, read> taps: array<u32>;` : \"\"}\n@group(0) @binding(3) var<storage, read> table: array<f32>;\n@group(0) @binding(4) var<storage, read> lattice: array<u32>;\n@group(0) @binding(5) var<storage, read_write> sites: array<f32>;\n@group(0) @binding(6) var<storage, read> spread: array<f32>;\n`;\n\n/**\n * Pass 2: the expected electrons, readout moments and white clip of every native site of the band's window, with their bounds.\n * The site's electrons are Σ_t table[(m_t·3 + c)·T + t] + dark_c, every term non-negative and rounded once on the host, so the\n * sum carries (T + 3)·E relative (`params.site_gamma`, the host's value). An uncertain lattice point adds the tap's value spread\n * over the materials (`spread`, host float64) to the site's uncertainty, which enters its bounds, or flags the site when it\n * exceeds UNCERTAINTY_LIMIT of the electrons. One dispatch covers the `params.count` sites from `params.site_first` (#492: the\n * host splits a band's sites over several dispatches so that no dispatch gathers more than its tap budget).\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_WGSL = /* wgsl */ `${COMMON_WGSL}${siteBindingsWgsl(true)}${SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n var sum = 0.0;\n var uncertainty = 0.0;\n let T = params.taps;\n for (var t = 0u; t < T; t = t + 1u) {\n let tb = t * TAP;\n let g = taps[tb] * GROUP;\n let index = groups[g + 4u] + (jj + taps[tb + 2u]) * groups[g + 2u] + ii + taps[tb + 1u];\n let raw = lattice[index];\n if ((raw >> 31u) != 0u) { uncertainty = uncertainty + spread[c * T + t]; }\n sum = sum + table[((raw & 0x7fffffffu) * 3u + c) * T + t];\n }\n let e = sum + dark_of(c);\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/**\n * Pass 2 with the defocus disc (#492): the same site electrons from a factored table, summed in blocks.\n *\n * - **Factored table.** A neutral material's value is its reflectance times the white's combined kernel (the reference forms it\n * so: reflectance[m]·neutral_c(t)), so the table holds one neutral row per channel and one row per spectral material, and\n * `materials` holds per material its row (0 for neutral, 1 + s for spectral material s) and its reflectance as float32 bits\n * (1 for a spectral material, an exact product). A scene of thousands of neutral materials (#491's composite: 4 143) then\n * needs 3·T values instead of 3·T per material.\n * - **Blocked sum.** Every group's taps form a dense cw × ch rectangle (the combined kernel's window, row-major from the group's\n * first tap, `groups[g + 5]`), so a site sums each row of a group into a row partial, the rows into a group partial and the\n * groups into the total. No tap records are read: the tap's lattice offset within its group is its (dx, dy).\n *\n * Every term is non-negative and carries two host roundings (u each, u = E/2: the row value and the reflectance), one product\n * (E) and at most cw + ch + G additions, plus the dark electrons' addition, so the electrons carry (cw_max + ch_max + G + 3)·E\n * relative (`params.site_gamma`; recursive summation of the up to 4·(2·96 + 2)² = 150 544 taps of a combined radius of 96 would\n * carry (T + 3)·E ≈ 1.8 % instead). The uncertainty, summed the same way, is scaled by 1 + site_gamma, and `params.site_tiny`\n * (host float64, rounded up) bounds the terms below the float32 normal range, which a device may flush to zero.\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL = /* wgsl */ `${COMMON_WGSL}${siteBindingsWgsl(false)}\n@group(0) @binding(7) var<storage, read> materials: array<u32>;\n${SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n let T = params.taps;\n var sum = 0.0;\n var uncertainty = 0.0;\n for (var k = 0u; k < params.groups; k = k + 1u) {\n let g = k * GROUP;\n let lw = groups[g + 2u];\n let cw = lw - params.site_w + 1u;\n let ch = groups[g + 3u] - params.site_h + 1u;\n let lattice0 = groups[g + 4u] + jj * lw + ii;\n let tap0 = groups[g + 5u];\n var group_sum = 0.0;\n var group_uncertainty = 0.0;\n for (var dy = 0u; dy < ch; dy = dy + 1u) {\n let row = lattice0 + dy * lw;\n let row_tap = tap0 + dy * cw;\n var row_sum = 0.0;\n var row_uncertainty = 0.0;\n for (var dx = 0u; dx < cw; dx = dx + 1u) {\n let raw = lattice[row + dx];\n let t = row_tap + dx;\n let m = 2u * (raw & 0x7fffffffu);\n if ((raw >> 31u) != 0u) { row_uncertainty = row_uncertainty + spread[c * T + t]; }\n row_sum = row_sum + bitcast<f32>(materials[m + 1u]) * table[(materials[m] * 3u + c) * T + t];\n }\n group_sum = group_sum + row_sum;\n group_uncertainty = group_uncertainty + row_uncertainty;\n }\n sum = sum + group_sum;\n uncertainty = uncertainty + group_uncertainty;\n }\n let e = sum + dark_of(c);\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty * (1.0 + params.site_gamma) + params.site_tiny;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/** Pass 3: the display pixels (see the module comment). */\nexport const DISPLAY_VIEW_QUALITY_PIXELS_WGSL = /* wgsl */ `${COMMON_WGSL}\nconst KEY_LEVELS: u32 = ${AHD_KEY_LEVELS}u;\nconst KEY_STEPS: f32 = ${literal(AHD_KEY_STEPS)};\nconst KEY_OCTAVES: f32 = ${literal(AHD_KEY_OCTAVES)};\nconst KEY_FLOOR: f32 = ${literal(AHD_KEY_FLOOR)};\nconst KEY_TOP: u32 = ${AHD_KEY_OCTAVES * AHD_KEY_STEPS - 1}u;\nconst CAND_BASE: u32 = ${DISPLAY_VIEW_QUALITY_AUX.candidates}u;\nconst CAND_STRIDE: u32 = ${CANDIDATE_PHASE_STRIDE}u;\nconst FLAG_UNCERTAIN: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.materialUncertain}u;\nconst FLAG_OUTSIDE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.siteOutsideWindow}u;\nconst FLAG_STATE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.stateMissing}u;\nconst FLAG_TIE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.thresholdTie}u;\nconst FLAG_CORRECTED: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.corrected}u;\nconst FLAG_CHOICE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.ahdChoice}u;\nconst CHOICE_SIGMA: f32 = ${literal(DISPLAY_VIEW_QUALITY_CHOICE_SIGMA)};\n@group(0) @binding(1) var<storage, read> aux: array<f32>;\n@group(0) @binding(2) var<storage, read> geometry: array<f32>;\n@group(0) @binding(3) var<storage, read> sites: array<f32>;\n@group(0) @binding(4) var<storage, read> state_keys: array<i32>;\n@group(0) @binding(5) var<storage, read> state_values: array<f32>;\n@group(0) @binding(6) var<storage, read_write> bitmap: array<atomic<u32>>;\n@group(0) @binding(7) var<storage, read_write> out: array<f32>;\n@group(0) @binding(8) var<storage, read_write> flags: array<u32>;\n\nvar<private> pixel_flags: u32;\n${REINHARD_WGSL}\nfn reflect_x(x: i32) -> i32 { if (x < 0) { return -x; } if (x >= i32(params.native_w)) { return 2 * i32(params.native_w) - 2 - x; } return x; }\nfn reflect_y(y: i32) -> i32 { if (y < 0) { return -y; } if (y >= i32(params.native_h)) { return 2 * i32(params.native_h) - 2 - y; } return y; }\n// The site buffer offset of native site (x, y) (reflected at the raster's borders), or a flag when it lies outside the window.\nfn site_at(x: i32, y: i32) -> u32 {\n let rx = reflect_x(x) - params.site_x0;\n let ry = reflect_y(y) - params.site_y0;\n if (rx < 0 || ry < 0 || rx >= i32(params.site_w) || ry >= i32(params.site_h)) { pixel_flags = pixel_flags | FLAG_OUTSIDE; return 0u; }\n let o = (u32(ry) * params.site_w + u32(rx)) * SITE;\n pixel_flags = pixel_flags | (bitcast<u32>(sites[o + 6u]) & 1u);\n return o;\n}\n// The offset of a grid key in the sorted state table, or state_count when absent.\nfn find_state(key: i32) -> u32 {\n var lo = 0u; var hi = params.state_count;\n while (lo < hi) {\n let mid = (lo + hi) / 2u;\n if (state_keys[mid] < key) { lo = mid + 1u; } else { hi = mid; }\n }\n if (lo < params.state_count && state_keys[lo] == key) { return lo; }\n return params.state_count;\n}\n// The flat-neighbourhood correction at channel means (m0, m1, m2) with its bound: trilinear between the eight grid states\n// around (log2(clamp(m, 2^-12, 1)) + 12)·8; a missing state flags the pixel. The bound of each axis's coordinate times the\n// largest state difference along that axis bounds the interpolation's dependence on the coordinates' error.\nfn flat_correction(m0: vec2<f32>, m1: vec2<f32>, m2: vec2<f32>, corr: ptr<function, array<vec2<f32>, 3>>) {\n var base: array<u32, 3>; var frac: array<f32, 3>; var dg: array<f32, 3>;\n for (var i = 0u; i < 3u; i = i + 1u) {\n var mi = m0; if (i == 1u) { mi = m1; } if (i == 2u) { mi = m2; }\n let clamped = clamp(mi.x, KEY_FLOOR, 1.0);\n let g = (log2(clamped) + KEY_OCTAVES) * KEY_STEPS;\n let b = min(u32(max(floor(g), 0.0)), KEY_TOP);\n base[i] = b; frac[i] = g - f32(b);\n // log2: 1/ln 2 per relative input error, 2^-21 absolute or 3 ULP (≤ 36E on [2^-12, 1]), the sum and the product.\n var slope = 0.0; if (mi.x > KEY_FLOOR && mi.x < 1.0) { slope = 1.4426950408889634 * mi.y / clamped; }\n dg[i] = KEY_STEPS * (slope + 4.76837158203125e-7 + 36.0 * E) + 2.0 * E * (g + 1.0);\n }\n var values: array<f32, 24>;\n var missing = false;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n let i0 = base[0] + (corner & 1u); let i1 = base[1] + ((corner >> 1u) & 1u); let i2 = base[2] + ((corner >> 2u) & 1u);\n let key = i32((i0 * KEY_LEVELS + i1) * KEY_LEVELS + i2);\n var weight = 1.0;\n for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }\n if (weight == 0.0) { for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = 0.0; } continue; }\n atomicOr(&bitmap[u32(key) / 32u], 1u << (u32(key) % 32u));\n let at = find_state(key);\n if (at >= params.state_count) { missing = true; continue; }\n for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = state_values[at * 3u + c]; }\n }\n if (missing) { pixel_flags = pixel_flags | FLAG_STATE; for (var c = 0u; c < 3u; c = c + 1u) { (*corr)[c] = vec2<f32>(0.0, 0.0); } return; }\n for (var c = 0u; c < 3u; c = c + 1u) {\n var sum = 0.0; var mag = 0.0;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n var weight = 1.0;\n for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }\n sum = sum + weight * values[corner * 3u + c];\n mag = mag + abs(weight * values[corner * 3u + c]);\n }\n // The largest state difference along each axis (over the four edges), times that axis's coordinate bound.\n var slope_term = 0.0;\n for (var i = 0u; i < 3u; i = i + 1u) {\n var largest = 0.0;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n if (((corner >> i) & 1u) == 1u) { continue; }\n largest = max(largest, abs(values[(corner | (1u << i)) * 3u + c] - values[corner * 3u + c]));\n }\n slope_term = slope_term + dg[i] * largest;\n }\n (*corr)[c] = vec2<f32>(sum, slope_term + 12.0 * E * mag);\n }\n}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.count) { return; }\n pixel_flags = 0u;\n let rx = i % params.region_w;\n let ry = params.row0 + i / params.region_w;\n let col = rx * STRIDE;\n let row = (params.columns + ry) * STRIDE;\n let col_first = bitcast<i32>(geometry[col]);\n let nx = bitcast<u32>(geometry[col + 1u]);\n let row_first = bitcast<i32>(geometry[row]);\n let ny = bitcast<u32>(geometry[row + 1u]);\n let margin = i32(params.margin);\n let reinhard = params.tone == 1u;\n let count = params.candidates;\n let share = select(1.0, 0.5, count == 2u);\n // B_c of the footprint from the host's axis sums (each over its scale squared): red, green, blue.\n let xb_even = geometry[col + 8u]; let xb_odd = geometry[col + 9u];\n let xpp_even = geometry[col + 10u]; let xpp_odd = geometry[col + 11u];\n let xqq_even = geometry[col + 12u]; let xqq_odd = geometry[col + 13u];\n let xpq_even = geometry[col + 14u]; let xpq_odd = geometry[col + 15u];\n let yb_even = geometry[row + 8u]; let yb_odd = geometry[row + 9u];\n let ypp_even = geometry[row + 10u]; let ypp_odd = geometry[row + 11u];\n let yqq_even = geometry[row + 12u]; let yqq_odd = geometry[row + 13u];\n let ypq_even = geometry[row + 14u]; let ypq_odd = geometry[row + 15u];\n let b0 = xb_even * yb_even;\n let b1 = (xpp_odd * ypp_even + xqq_odd * ypp_even + xpp_odd * yqq_even + 2.0 * xpq_odd * ypp_even + 2.0 * xpp_odd * ypq_even + 2.0 * xpq_odd * ypq_even) +\n (xpp_even * ypp_odd + xqq_even * ypp_odd + xpp_even * yqq_odd + 2.0 * xpq_even * ypp_odd + 2.0 * xpp_even * ypq_odd + 2.0 * xpq_even * ypq_odd);\n let b2 = xb_odd * yb_odd;\n var s = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cov = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var res = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n // #457: Σ_j a_j·½|h(H) − h(V)| per component, how far the reference's choice can move the value from the candidates' average.\n var choice = array<f32, 3>(0.0, 0.0, 0.0);\n 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 var e_mean = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cm_average = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var pixel_variance = vec2<f32>(0.0, 0.0);\n var bs: array<vec2<f32>, 3>; var hs: array<vec2<f32>, 3>;\n var hv: array<vec2<f32>, 3>;\n // #457: each candidate's rendered mean, for the reference's choice between them.\n var h_cand = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n for (var d = 0u; d < count; d = d + 1u) {\n // Camera-channel mean and covariance of candidate d from its dense weights over the window.\n var cm: array<vec2<f32>, 3>;\n var C: array<vec2<f32>, 6>;\n // Whether every site a channel reads is exactly at white (a zero bound: the reference's site is exactly 1 too); the\n // dyadic kernel weights then sum exactly on both sides, so the channel's mean carries no error (the host checks the weights).\n var exact = array<bool, 3>(true, true, true);\n for (var c = 0u; c < 3u; c = c + 1u) { cm[c] = vec2<f32>(0.0, 0.0); }\n for (var k = 0u; k < 6u; k = k + 1u) { C[k] = vec2<f32>(0.0, 0.0); }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let sm = vec2<f32>(sites[o], sites[o + 3u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let white = sm.y == 0.0 && sm.x == 1.0;\n exact[0] = exact[0] && (white || w0 == 0.0); exact[1] = exact[1] && (white || w1 == 0.0); exact[2] = exact[2] && (white || w2 == 0.0);\n cm[0] = b_add(cm[0], b_scale(sm, w0)); cm[1] = b_add(cm[1], b_scale(sm, w1)); cm[2] = b_add(cm[2], b_scale(sm, w2));\n C[0] = b_add(C[0], b_scale(sv, w0 * w0)); C[1] = b_add(C[1], b_scale(sv, w0 * w1)); C[2] = b_add(C[2], b_scale(sv, w0 * w2));\n C[3] = b_add(C[3], b_scale(sv, w1 * w1)); C[4] = b_add(C[4], b_scale(sv, w1 * w2)); C[5] = b_add(C[5], b_scale(sv, w2 * w2));\n }\n for (var c = 0u; c < 3u; c = c + 1u) { if (exact[c]) { cm[c].y = 0.0; } cm_average[c] = b_add(cm_average[c], b_scale(cm[c], share)); }\n // White balance and the highlight clip at the smallest gain. A channel exactly at white whose gain is the clip level is\n // exactly at the clip level on both sides.\n var bm: array<vec2<f32>, 3>; var bv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let gc = b_rounded(g_of(c));\n var um = b_mul(gc, cm[c]);\n if (exact[c] && cm[c].x == 1.0 && gain_at_clip(c)) { um = vec2<f32>(params.clip_level, 0.0); }\n let uv = b_mul(b_mul(gc, gc), C[select(select(0u, 3u, c == 1u), 5u, c == 2u)]);\n let gap = params.clip_level - um.x;\n if (gap > 0.0 && gap * gap > params.reach2 * uv.x) { bm[c] = um; bv[c] = uv; bs[c] = gc; }\n else { let clipped = upper_clip(um, uv, params.clip_level); bm[c] = clipped.mean; bv[c] = clipped.variance; bs[c] = b_mul(gc, clipped.slope); }\n }\n // Colour matrix with the full channel covariance, then the rendering of each component.\n var hm: array<vec2<f32>, 3>;\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let r0 = b_rounded(m_of(comp, 0u)); let r1 = b_rounded(m_of(comp, 1u)); let r2 = b_rounded(m_of(comp, 2u));\n let mean = b_add(b_add(b_mul(r0, bm[0]), b_mul(r1, bm[1])), b_mul(r2, bm[2]));\n var variance = b_add(b_add(b_mul(b_mul(r0, r0), bv[0]), b_mul(b_mul(r1, r1), bv[1])), b_mul(b_mul(r2, r2), bv[2]));\n let x01 = b_mul(b_mul(b_mul(r0, r1), b_mul(bs[0], bs[1])), C[1]);\n let x02 = b_mul(b_mul(b_mul(r0, r2), b_mul(bs[0], bs[2])), C[2]);\n let x12 = b_mul(b_mul(b_mul(r1, r2), b_mul(bs[1], bs[2])), C[4]);\n variance = b_add(variance, b_scale(b_add(b_add(x01, x02), x12), 2.0));\n var moments: Moments;\n if (!reinhard && mean.x > 0.0 && mean.x < 1.0 && mean.x * mean.x > params.reach2 * variance.x && (1.0 - mean.x) * (1.0 - mean.x) > params.reach2 * variance.x) {\n moments.mean = mean; moments.variance = variance; moments.slope = vec2<f32>(1.0, 0.0);\n } else if (reinhard) {\n moments = reinhard_moments(mean, variance);\n } else {\n // κ₃ of the component: Σ_sites (Σ_c M_comp,c·Γ_c·w_c,site)³·κ₃(site), over the candidate's dense weights.\n var third = vec2<f32>(0.0, 0.0);\n let k0 = b_mul(r0, bs[0]); let k1 = b_mul(r1, bs[1]); let k2 = b_mul(r2, bs[2]);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let coefficient = b_add(b_add(b_scale(k0, w0), b_scale(k1, w1)), b_scale(k2, w2));\n let cube = b_mul(b_mul(coefficient, coefficient), coefficient);\n third = b_add(third, b_mul(cube, vec2<f32>(sites[o + 2u], sites[o + 5u])));\n }\n var skew = vec2<f32>(0.0, 0.0);\n if (variance.x > 0.0) { skew = b_div(third, b_mul(variance, b_sqrt(variance))); }\n moments = unit_clip(mean, variance, skew);\n }\n hm[comp] = moments.mean; hv[comp] = moments.variance; hs[comp] = moments.slope;\n h_cand[d * 3u + comp] = moments.mean;\n e_mean[comp] = b_add(e_mean[comp], b_scale(moments.mean, share));\n pixel_variance = vec2<f32>(max(pixel_variance.x, moments.variance.x), max(pixel_variance.y, moments.variance.y));\n }\n // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance aᵀCa for the residual.\n var an: array<vec2<f32>, 9>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n an[c] = b_mul(b_mul(hs[0], b_rounded(m_of(0u, c))), bs[c]);\n an[3u + c] = b_mul(b_mul(hs[1], b_rounded(m_of(1u, c))), bs[c]);\n an[6u + c] = b_mul(b_mul(hs[2], b_rounded(m_of(2u, c))), bs[c]);\n var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }\n let wgt = b_mul(b_mul(b_scale(aj, share), b_rounded(bc)), csv[c]);\n cov[0] = b_add(cov[0], b_mul(b_mul(wgt, an[c]), an[c])); cov[1] = b_add(cov[1], b_mul(b_mul(wgt, an[c]), an[3u + c])); cov[2] = b_add(cov[2], b_mul(b_mul(wgt, an[c]), an[6u + c]));\n cov[3] = b_add(cov[3], b_mul(b_mul(wgt, an[3u + c]), an[3u + c])); cov[4] = b_add(cov[4], b_mul(b_mul(wgt, an[3u + c]), an[6u + c])); cov[5] = b_add(cov[5], b_mul(b_mul(wgt, an[6u + c]), an[6u + c]));\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n var l = vec2<f32>(0.0, 0.0);\n for (var p = 0u; p < 3u; p = p + 1u) {\n for (var q = 0u; q < 3u; q = q + 1u) {\n var cij = C[0]; if (p + q == 1u) { cij = C[1]; } if ((p == 0u && q == 2u) || (p == 2u && q == 0u)) { cij = C[2]; } if (p == 1u && q == 1u) { cij = C[3]; }\n if ((p == 1u && q == 2u) || (p == 2u && q == 1u)) { cij = C[4]; } if (p == 2u && q == 2u) { cij = C[5]; }\n l = b_add(l, b_mul(b_mul(an[comp * 3u + p], an[comp * 3u + q]), cij));\n }\n }\n let t = vec2<f32>(share * max(0.0, hv[comp].x - l.x), share * (hv[comp].y + l.y));\n res[comp] = b_add(res[comp], b_mul(b_mul(aj, aj), t));\n }\n }\n if (count == 2u) {\n // #457: the reference takes AHD's choice on the noise-free mosaic, one candidate or (a tie) their average, so its value\n // lies within half the candidates' difference plus their bounds of the average: the bound carries that, and the move\n // accumulates in choice, which flags the pixel below when it exceeds CHOICE_SIGMA of the pixel's noise σ.\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let diff = abs(h_cand[comp].x - h_cand[3u + comp].x);\n let both = h_cand[comp].y + h_cand[3u + comp].y;\n choice[comp] = choice[comp] + aj.x * 0.5 * diff;\n e_mean[comp].y = e_mean[comp].y + 0.5 * (diff + both);\n }\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, e_mean[comp])); }\n if (count == 2u) {\n // AHD's per-pixel choice: ¼·Var(H − V) at the last candidate's slopes, linear in the sites.\n var x0 = vec2<f32>(0.0, 0.0); var x1 = vec2<f32>(0.0, 0.0); var x2 = vec2<f32>(0.0, 0.0);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n let o = site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]));\n let d0 = b_scale(bs[0], aux[eb + 2u] - aux[eb + 5u]);\n let d1 = b_scale(bs[1], aux[eb + 3u] - aux[eb + 6u]);\n let d2 = b_scale(bs[2], aux[eb + 4u] - aux[eb + 7u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let a = b_mul(hs[0], b_add(b_add(b_mul(b_rounded(params.m00), d0), b_mul(b_rounded(params.m01), d1)), b_mul(b_rounded(params.m02), d2)));\n let b = b_mul(hs[1], b_add(b_add(b_mul(b_rounded(params.m10), d0), b_mul(b_rounded(params.m11), d1)), b_mul(b_rounded(params.m12), d2)));\n let c = b_mul(hs[2], b_add(b_add(b_mul(b_rounded(params.m20), d0), b_mul(b_rounded(params.m21), d1)), b_mul(b_rounded(params.m22), d2)));\n x0 = b_add(x0, b_mul(b_mul(a, a), sv)); x1 = b_add(x1, b_mul(b_mul(b, b), sv)); x2 = b_add(x2, b_mul(b_mul(c, c), sv));\n }\n let ajaj = b_mul(aj, aj);\n res[0] = b_add(res[0], b_mul(ajaj, b_scale(x0, 0.25))); res[1] = b_add(res[1], b_mul(ajaj, b_scale(x1, 0.25))); res[2] = b_add(res[2], b_mul(ajaj, b_scale(x2, 0.25)));\n }\n if (params.correction == 1u) {\n // The low-signal correction where the pixel noise exceeds the threshold; a decision within its bound is flagged.\n if (abs(pixel_variance.x - params.threshold) <= pixel_variance.y) { pixel_flags = pixel_flags | FLAG_TIE; }\n if (pixel_variance.x > params.threshold) {\n var corr: array<vec2<f32>, 3>;\n flat_correction(cm_average[0], cm_average[1], cm_average[2], &corr);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, corr[comp])); }\n pixel_flags = pixel_flags | FLAG_CORRECTED;\n }\n }\n }\n }\n // #457: a pixel whose candidates' choice could move it by more than CHOICE_SIGMA of its own noise σ is recomputed on the host.\n if (choice[0] > CHOICE_SIGMA * sqrt(max(cov[0].x + res[0].x, 0.0)) || choice[1] > CHOICE_SIGMA * sqrt(max(cov[3].x + res[1].x, 0.0)) ||\n choice[2] > CHOICE_SIGMA * sqrt(max(cov[5].x + res[2].x, 0.0))) { pixel_flags = pixel_flags | FLAG_CHOICE; }\n let o = i * ${DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS}u;\n out[o] = s[0].x; out[o + 1u] = s[1].x; out[o + 2u] = s[2].x;\n out[o + 3u] = cov[0].x + res[0].x; out[o + 4u] = cov[3].x + res[1].x; out[o + 5u] = cov[5].x + res[2].x;\n out[o + 6u] = cov[1].x; out[o + 7u] = cov[2].x; out[o + 8u] = cov[4].x;\n out[o + 9u] = max(s[0].y, max(s[1].y, s[2].y));\n out[o + 10u] = max(max(cov[0].y + res[0].y, cov[3].y + res[1].y), max(cov[5].y + res[2].y, max(cov[1].y, max(cov[2].y, cov[4].y))));\n flags[i] = pixel_flags;\n}\n`;\n\n/** The lattice pass with a material module appended (`fn material_at(p: vec2<f32>) -> u32`, `var<private> material_uncertain: bool`). */\nexport function assembleDisplayViewQualityLatticeWgsl(materialModuleWgsl: string): string {\n return DISPLAY_VIEW_QUALITY_LATTICE_WGSL + \"\\n\" + materialModuleWgsl + \"\\n\";\n}\n"]}
1
+ {"version":3,"file":"wgsl-display-view-quality-kernel.js","sourceRoot":"","sources":["../../src/gpu/wgsl-display-view-quality-kernel.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * WGSL kernels of the display view's Quality path on the GPU (#479): the exact area mean with diffraction kernels, the\n * anti-aliasing filter's spots, the AHD candidates and the low-signal correction, in three passes over one band of display\n * rows (docs/WEBGPU.md, \"Display view (Quality) on the GPU\"; the float64 reference is `renderDisplayView` with\n * `createDisplayViewExpectedSignal`, facade/display-view.ts and facade/expected-signal.ts):\n *\n * 1. **Lattice** (`DISPLAY_VIEW_QUALITY_LATTICE_WGSL` plus the material module): one invocation per lattice point of each\n * lattice group (offsets that share a fractional shift, as the reference groups them): the chart material at\n * (p + 0.5 + f_x − W/2)·g, −(q + 0.5 + f_y − H/2)·g (the reference's lattice formula, an exact float32 sum times the\n * scale rounded once), with the module's uncertainty flag in the top bit.\n * 2. **Sites** (`DISPLAY_VIEW_QUALITY_SITES_WGSL`): one invocation per native site of the band's window: the expected\n * electrons Σ_t V_c(m_t, t) over the taps t of every group (the combined kernel values per material and channel, formed on\n * the host in float64 as the reference forms them: Σ_members w·Σ_λ A_c(λ)·K_λ·L_m(λ); without diffraction the offset\n * weights times the material's channel signal), plus the dark electrons; the readout moments (mean k·e, variance k²e + c₀,\n * third cumulant k³e) and the clip at the white level; and each quantity's running absolute error bound. With the defocus\n * disc (#492) the variant `DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL` reads a factored table (one neutral row, one row per\n * spectral material) and sums each group's dense tap rectangle in blocks, so its bound grows with the kernel's width.\n * 3. **Pixels** (`DISPLAY_VIEW_QUALITY_PIXELS_WGSL`): one invocation per display pixel of the band: for every real pixel of\n * the footprint, the demosaic candidates (one bilinear, or AHD's two directional linear candidates, each with its full\n * camera-channel covariance), white balance and the highlight clip, the colour matrix, the tone curve and the clip to\n * [0, 1] with the skewness term, the candidates' average, the Stein covariance with the closed-form B_c, the independent\n * residual, AHD's choice variance ¼·Var(H − V), the flag for AHD's choice on the noise-free mosaic, which the reference takes and\n * the kernel does not (#457), and the low-signal correction interpolated trilinearly between the grid\n * states of a table the host uploaded (the states themselves are computed on the host, #447; the kernel records every\n * grid key it needs in a bitmap and flags a display pixel whose state the table lacks).\n *\n * **Highlights (#533).** The site pass records whether each site is certainly below white, certainly at or above it, or\n * undecided (`clip_state`, from the site's mean and variance and their running bounds: certain only beyond twice the reference's\n * 8.5 standard deviations after moving the mean by twice its bound, where the reference's clip probability is exactly 0 or 1), in\n * bits 1 and 2 of its flag word. The pixel pass marks a real pixel saturated (P = 1) when a site its demosaic reads is certainly\n * clipped, and the display pixel `highlightUndecided` when a real pixel is neither that nor certainly unclipped. Three flag words\n * per display pixel: the flags, then a bit per real pixel j·nx + i of the footprint with P = 1 (36 bits).\n *\n * **Running bounds.** Every quantity after the site table is carried as a pair (value, absolute bound) in float32: the bound\n * of a sum is the sum of the bounds plus one rounding of the result, of a product |a|·Δb + |b|·Δa plus one rounding, of a\n * square root Δv/(2s) plus 4.5 ULP, of a quotient 2.5 ULP plus the propagated terms; the clipped-normal moments add the\n * Speed kernel's clip constants (CLIP_MEAN_U, CLIP_VARIANCE_U per standard deviation or variance) and propagate their inputs\n * with the moments' first-order derivatives at the pixel's own standardized clip levels (∂E/∂m = P(inside), ∂E/∂s = φa − φb,\n * ∂Var/∂m = −s(ρ_a + ρ_b), ∂Var/∂s = s(2ρ − aρ_a − bρ_b) with ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]), and the slope's\n * (φa − φb)/s and (aφa − bφb)/s; without noise a slope is exact unless the mean's interval straddles a clip level); the\n * Reinhard quadrature adds REINHARD_MEAN_U and REINHARD_VARIANCE_U. Mixed-sign sums (the AHD\n * kernels, the colour matrix) are therefore bounded by the magnitudes of their terms, not relative to their result. The\n * pixel's expected value and variance leave the kernel with their absolute bounds; the host doubles them (MARGIN) and adds\n * the footprint sum's relative term. Second-order terms (products of bounds) and the bound arithmetic's own float32\n * roundings are what the doubling covers.\n *\n * Precision assumptions are those of the Speed kernel: the W3C WGSL accuracy table (+ − × one rounding in either direction,\n * E = 2^-23; division 2.5 ULP, sqrt 4.5 ULP, exp (3 + 2|x|) ULP, log2 2^-21 absolute on [0.5, 2] and 3 ULP outside; no\n * trigonometric built-in).\n */\n\nimport { DISPLAY_VIEW_CLIP_MEAN_U, DISPLAY_VIEW_CLIP_VARIANCE_U, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U } from \"./wgsl-display-view-speed-kernel.js\";\nimport { AHD_KEY_FLOOR, AHD_KEY_LEVELS, AHD_KEY_OCTAVES, AHD_KEY_STEPS } from \"../facade/display-view.js\";\n\nexport const DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE = 64;\n/** Largest footprint per axis (real pixels), as the Speed kernel. */\nexport const DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS = 6;\n/** Uniform parameter words (see `Params`): 56 words, 224 bytes. */\nexport const DISPLAY_VIEW_QUALITY_PARAM_WORDS = 56;\n/** Words per lattice-group record: lx0, ly0, lw, lh, lbase, first tap (the blocked site pass, #492), cu (f32), cv (f32). */\nexport const DISPLAY_VIEW_QUALITY_GROUP_WORDS = 8;\n/** Words per tap record: group, dp offset, dq offset, pad. */\nexport const DISPLAY_VIEW_QUALITY_TAP_WORDS = 4;\n/** Floats per native site of the site buffer: mean, variance, third cumulant, their three bounds, flag (u32 bits), electrons. */\nexport const DISPLAY_VIEW_QUALITY_SITE_FLOATS = 8;\n/** Floats per display pixel in the output buffer: m0 m1 m2, v00 v11 v22, c01 c02 c12, mean bound, variance bound (absolute). */\nexport const DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS = 11;\n/** Floats per column or row record of the geometry buffer (the Speed layout). */\nexport const DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE = 20;\n/** Candidate table: entries per phase (the 7 × 7 window of AHD's reach 3) and floats per entry (dx, dy, dh0, dh1, dh2, dv0, dv1, dv2). */\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES = 49;\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS = 8;\nconst CANDIDATE_PHASE_STRIDE = 4 + DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES * DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS;\n/** Layout of the aux table (float32 words). */\nexport const DISPLAY_VIEW_QUALITY_AUX = /* @__PURE__ */ Object.freeze({\n /** Per phase p, channel c, tap t: dx, dy, weight, 0 (the bilinear kernels, 4 taps; unused taps have weight 0). */\n bilinear: 0,\n /** Per phase p, channel c: 1 / Σ_t w_t² of the bilinear kernel (host float64). */\n inverseW2: 192,\n /** 16 Gauss–Legendre nodes then 16 weights on [−1, 1]. */\n gaussLegendre: 208,\n /** Per phase: the entry count (as u32 bits) and 3 pad words, then the entries (dx, dy, dense weights of candidate 0 and 1 per channel). */\n candidates: 240,\n candidatePhaseStride: CANDIDATE_PHASE_STRIDE,\n size: 240 + 4 * CANDIDATE_PHASE_STRIDE\n});\n/** Pixel flag bits of the output flag word. */\nexport const DISPLAY_VIEW_QUALITY_FLAG = /* @__PURE__ */ Object.freeze({\n /** A site the pixel read had an uncertain lattice material (recompute on the host). */\n materialUncertain: 1,\n /** A site the pixel read lies outside the band's site window (a host geometry error; recompute). */\n siteOutsideWindow: 2,\n /** The correction needed a grid state the table does not hold (recompute, or compute the state and dispatch again). */\n stateMissing: 4,\n /** A sample's pixel variance lies within its bound of the correction threshold (recompute). */\n thresholdTie: 8,\n /** Informational: the pixel's expected value includes the low-signal correction. */\n corrected: 16,\n /**\n * #457: the reference takes AHD's choice on the noise-free mosaic (one candidate, or their average on a tie), which the kernel\n * does not compute; it renders the average. The pixel is flagged (recompute) when Σ_j a_j·½|h(H) − h(V)| of some component\n * exceeds `DISPLAY_VIEW_QUALITY_CHOICE_SIGMA` times the pixel's noise standard deviation.\n */\n ahdChoice: 32,\n /** #533: a real pixel's saturation is undecided (the host computes the display pixel's count with the reference; its samples stay). */\n highlightUndecided: 64,\n /** The bits that require the host's recomputation. */\n recompute: 47\n});\n/** #533: flag words per display pixel: the flags, then the 36-bit mask of saturated real pixels. */\nexport const DISPLAY_VIEW_QUALITY_FLAG_WORDS = 3;\n/**\n * #457: the largest move, in units of the display pixel's own noise σ, that AHD's choice between its candidates may make to a pixel\n * the kernel keeps (it renders the candidates' average and its bound carries the move); a larger one is recomputed on the host. A\n * design tolerance, not a measurement: 2^-10 σ is a tenth of the 0.01 σ to which the float32 tests hold the GPU to the reference.\n */\nexport const DISPLAY_VIEW_QUALITY_CHOICE_SIGMA = 2 ** -10;\n/** Words of the grid-key bitmap: one bit per grid key (AHD_KEY_LEVELS³ keys). */\nexport const DISPLAY_VIEW_QUALITY_BITMAP_WORDS = Math.ceil(AHD_KEY_LEVELS ** 3 / 32);\n/**\n * A site whose uncertain lattice points (the material module's flag) could move its electrons by more than this fraction of\n * their value is flagged for host recomputation; a smaller uncertainty enters the site's bound instead (the tap weights times\n * the largest value difference between any two materials at that tap).\n */\nexport const DISPLAY_VIEW_QUALITY_UNCERTAINTY_LIMIT = 2 ** -10;\n/** Bound constant of a clipped-normal slope Φ(z) (in units of E): z's 8E through φ·|z| ≤ 0.24 plus the Abramowitz–Stegun 147E. */\nexport const DISPLAY_VIEW_QUALITY_SLOPE_U = 200;\n/** Relative bound of the Reinhard slope E[h′] (in units of E). */\nconst REINHARD_SLOPE_U = 64;\n/** The `E` of the bounds: one WGSL rounding of + − × in either direction, 2^-23. */\nconst E_LITERAL = \"1.1920928955078125e-7\";\nconst literal = (value: number): string => {\n const text = String(Math.fround(value));\n return /[.e]/u.test(text) ? text : `${text}.0`;\n};\n\n/** The parameter struct and the bound helpers shared by the three passes. */\nconst COMMON_WGSL = /* wgsl */ `\nconst E: f32 = ${E_LITERAL};\nconst SLACK: f32 = 1e-14;\nconst CLIP_REACH: f32 = 8.5;\nconst CLIP_MEAN_U: f32 = ${literal(DISPLAY_VIEW_CLIP_MEAN_U)};\nconst CLIP_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_CLIP_VARIANCE_U)};\nconst CLIP_SLOPE_U: f32 = ${literal(DISPLAY_VIEW_QUALITY_SLOPE_U)};\nconst REINHARD_MEAN_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_MEAN_U)};\nconst REINHARD_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_VARIANCE_U)};\nconst REINHARD_SLOPE_U: f32 = ${literal(REINHARD_SLOPE_U)};\nconst UNCERTAINTY_LIMIT: f32 = ${literal(2 ** -10)};\nconst STRIDE: u32 = ${DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE}u;\nconst SITE: u32 = ${DISPLAY_VIEW_QUALITY_SITE_FLOATS}u;\nconst GROUP: u32 = ${DISPLAY_VIEW_QUALITY_GROUP_WORDS}u;\nconst TAP: u32 = ${DISPLAY_VIEW_QUALITY_TAP_WORDS}u;\nstruct Params {\n count: u32, row0: u32, region_w: u32, region_x: u32,\n region_y: u32, margin: u32, native_w: u32, native_h: u32,\n columns: u32, tone: u32, reach: u32, candidates: u32,\n site_x0: i32, site_y0: i32, site_w: u32, site_h: u32,\n correction: u32, state_count: u32, taps: u32, groups: u32,\n lattice_total: u32, gain_at_clip0: u32, gain_at_clip1: u32, gain_at_clip2: u32,\n k0: f32, k1: f32, k2: f32, reach2: f32,\n dark0: f32, dark1: f32, dark2: f32, clip_level: f32,\n c0_0: f32, c0_1: f32, c0_2: f32, threshold: f32,\n g0: f32, g1: f32, g2: f32, scale_u: f32,\n m00: f32, m01: f32, m02: f32, m10: f32,\n m11: f32, m12: f32, m20: f32, m21: f32,\n m22: f32, scale_v: f32, site_gamma: f32, site_first: u32,\n site_tiny: f32, pad_f: f32, pad_g: f32, pad_h: f32,\n};\n@group(0) @binding(0) var<uniform> params: Params;\n\nfn k_of(c: u32) -> f32 { if (c == 0u) { return params.k0; } if (c == 1u) { return params.k1; } return params.k2; }\nfn dark_of(c: u32) -> f32 { if (c == 0u) { return params.dark0; } if (c == 1u) { return params.dark1; } return params.dark2; }\nfn c0_of(c: u32) -> f32 { if (c == 0u) { return params.c0_0; } if (c == 1u) { return params.c0_1; } return params.c0_2; }\nfn g_of(c: u32) -> f32 { if (c == 0u) { return params.g0; } if (c == 1u) { return params.g1; } return params.g2; }\n// Whether the channel's white-balance gain is the clip level itself (the smallest gain), exactly, in the reference's float64.\nfn gain_at_clip(c: u32) -> bool { if (c == 0u) { return params.gain_at_clip0 == 1u; } if (c == 1u) { return params.gain_at_clip1 == 1u; } return params.gain_at_clip2 == 1u; }\nfn m_of(r: u32, c: u32) -> f32 {\n let i = r * 3u + c;\n switch (i) {\n case 0u: { return params.m00; } case 1u: { return params.m01; } case 2u: { return params.m02; }\n case 3u: { return params.m10; } case 4u: { return params.m11; } case 5u: { return params.m12; }\n case 6u: { return params.m20; } case 7u: { return params.m21; } default: { return params.m22; }\n }\n}\nfn channel_of(x: i32, y: i32) -> u32 {\n if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }\n return select(2u, 1u, (x & 1) == 0);\n}\n// A value with its absolute error bound (x: value, y: bound). First-order rules; the host doubles the final bound.\nfn b_add(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x + b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_sub(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x - b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x * b.x; return vec2<f32>(v, abs(a.x) * b.y + abs(b.x) * a.y + E * abs(v)); }\n// Times an exact constant (a kernel weight, an integer, a power of two).\nfn b_scale(a: vec2<f32>, k: f32) -> vec2<f32> { let v = a.x * k; return vec2<f32>(v, a.y * abs(k) + E * abs(v)); }\n// #533: 0 when a site of mean m (bound dm) and variance v (bound dv) is certainly below white, 1 certainly at or above it, 2\n// undecided (the Speed kernel's rule: twice the reference's 8.5 standard deviations, the mean moved by twice its bound).\nfn clip_state(m: f32, dm: f32, v: f32, dv: f32) -> u32 {\n let gap = 1.0 - m;\n let wide = 4.0 * params.reach2 * (v + 2.0 * dv);\n let below = gap - 2.0 * dm;\n let above = gap + 2.0 * dm;\n if (below > 0.0 && below * below > wide) { return 0u; }\n if (above < 0.0 && above * above > wide) { return 1u; }\n return 2u;\n}\n// A host constant rounded once to float32 (a gain, a matrix entry).\nfn b_rounded(k: f32) -> vec2<f32> { return vec2<f32>(k, E * abs(k)); }\nfn b_div(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x / b.x; return vec2<f32>(v, (a.y + abs(v) * b.y) / abs(b.x) + 2.5 * E * abs(v)); }\n// |sqrt(v + d) - sqrt(v)| <= min(d / (2 sqrt v), sqrt d), plus the 4.5 ULP of the built-in.\nfn b_sqrt(a: vec2<f32>) -> vec2<f32> {\n let s = sqrt(max(a.x, 0.0));\n if (s > 0.0) { return vec2<f32>(s, min(a.y / (2.0 * s), sqrt(a.y)) + 4.5 * E * s); }\n return vec2<f32>(0.0, sqrt(a.y));\n}\n// Standard normal density, as the reference forms it, and Abramowitz & Stegun 26.2.17 as (Φ, 1 − Φ).\nfn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }\nfn pq(z: f32) -> vec2<f32> {\n let x = abs(z);\n let t = 1.0 / (1.0 + 0.2316419 * x);\n let q = phi(x) * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));\n if (z >= 0.0) { return vec2<f32>(1.0 - q, q); }\n return vec2<f32>(q, 1.0 - q);\n}\nstruct Moments { mean: vec2<f32>, variance: vec2<f32>, slope: vec2<f32> }\n// Moments of min(Y, top) for Y ~ N(m, v) with their bounds: Var = v·(Φ − zφ + z²Q − δ²), δ = φ − zQ (the Speed forms).\nfn upper_clip(m: vec2<f32>, v: vec2<f32>, top: f32) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n // No noise on either side (a zero bound): the reference's slope is 1 or 0 by m < top, decided when m's interval lies on one side.\n let decided = v.y == 0.0 && (m.x - m.y >= top || m.x + m.y < top);\n out.mean = vec2<f32>(min(m.x, top), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x < top), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let z = (top - m.x) / s.x;\n if (z > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n if (z < -CLIP_REACH) { out.mean = vec2<f32>(top, 0.0); out.variance = vec2<f32>(0.0, 0.0); out.slope = vec2<f32>(0.0, 0.0); return out; }\n let p = pq(z);\n let d = phi(z);\n let delta = d - z * p.y;\n let ratio = p.x - z * d + z * z * p.y - delta * delta;\n // First-order propagation of the inputs' bounds: with g = min(Z, z), E[g] = −δ and ρ = Var(g) = ratio, ∂E/∂m = Φ, ∂E/∂s = φ,\n // ∂Var/∂m = −s·ρ_z, ∂Var/∂s = s·(2ρ − z·ρ_z) with ρ_z = 2Q·(z + δ), ∂Φ/∂m = −φ/s and ∂Φ/∂s = −zφ/s.\n let rho_z = 2.0 * p.y * (z + delta);\n out.mean = vec2<f32>(m.x - s.x * delta, p.x * m.y + d * s.y + CLIP_MEAN_U * E * s.x);\n out.variance = vec2<f32>(v.x * max(0.0, ratio), s.x * abs(rho_z) * m.y + s.x * abs(2.0 * ratio - z * rho_z) * s.y + CLIP_VARIANCE_U * E * v.x);\n out.slope = vec2<f32>(p.x, min(1.0, (d * m.y + abs(z) * d * s.y) / s.x + CLIP_SLOPE_U * E));\n return out;\n}\n// Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1), with their bounds (the\n// Speed forms: W = m + s·g(Z), E[g] = aΦa + φa − φb + bQb, E[g²] = a²Φa + (Φb − Φa) − (bφb − aφa) + b²Qb, the Edgeworth\n// shift (s·γ/6)·(aφa − bφb) on the mean and −shift·(2·E[Y] + shift) on the variance).\nfn unit_clip(m: vec2<f32>, v: vec2<f32>, skew: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n let decided = v.y == 0.0 && ((m.x - m.y > 0.0 && m.x + m.y < 1.0) || m.x + m.y <= 0.0 || m.x - m.y >= 1.0);\n out.mean = vec2<f32>(clamp(m.x, 0.0, 1.0), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x > 0.0 && m.x < 1.0), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let a = -m.x / s.x;\n let b = (1.0 - m.x) / s.x;\n if (a < -CLIP_REACH && b > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n var pa = 0.0; var da = 0.0;\n if (!(a < -CLIP_REACH)) { pa = pq(a).x; da = phi(a); }\n var pb = 1.0; var qb = 0.0; var db = 0.0;\n if (!(b > CLIP_REACH)) { let p = pq(b); pb = p.x; qb = p.y; db = phi(b); }\n let inside = pb - pa;\n let gamma = clamp(skew.x, -1.0, 1.0);\n let aa = a * da;\n let bb = b * db;\n let eg = a * pa + (da - db) + b * qb;\n let eg2 = a * a * pa + inside - (bb - aa) + b * b * qb;\n let normal_mean = m.x + s.x * eg;\n var mean = normal_mean;\n var shift = 0.0;\n if (gamma != 0.0) { shift = s.x * gamma / 6.0 * (aa - bb); mean = normal_mean + shift; }\n // |aφa − bφb| ≤ 0.48, so |shift| ≤ 0.08·s·|γ| and a wrong γ moves it by at most 0.16·s.\n let d_shift = 0.1 * s.x * min(2.0, skew.y) + 0.1 * abs(gamma) * s.y + E * abs(shift);\n // First-order propagation of the inputs' bounds: with ρ = E[g²] − E[g]², ∂E[g]/∂a = Φa, ∂E[g]/∂b = Qb, ∂E[g²]/∂a = 2aΦa,\n // ∂E[g²]/∂b = 2bQb, so ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]); ∂E/∂m = inside, ∂E/∂s = φa − φb, ∂Var/∂m = −s(ρ_a + ρ_b),\n // ∂Var/∂s = s(2ρ − aρ_a − bρ_b); the slope's derivatives are (φa − φb)/s and (aφa − bφb)/s.\n let rho = eg2 - eg * eg;\n let rho_a = 2.0 * pa * (a - eg);\n let rho_b = 2.0 * qb * (b - eg);\n let d_mean = m.y * inside + abs(da - db) * s.y + 2.0 * CLIP_MEAN_U * E * s.x + d_shift;\n // The normal part X = v·ρ and its bound; the reference's variance is max(0, X − shift·(2·E[Y] + shift)), so it lies in\n // [0, X_cpu] when the shift term is non-negative on both sides (shift > Δshift), which caps the bound by max(Var, X + ΔX).\n let normal_variance = v.x * rho;\n let d_normal = s.x * abs(rho_a + rho_b) * m.y + s.x * abs(2.0 * rho - a * rho_a - b * rho_b) * s.y + 2.0 * CLIP_VARIANCE_U * E * v.x;\n let variance = max(0.0, normal_variance - shift * (2.0 * normal_mean + shift));\n var d_var = d_normal + 2.0 * (d_shift * (abs(normal_mean) + abs(shift)) + abs(shift) * d_mean);\n if (shift > d_shift) { d_var = min(d_var, max(variance, max(0.0, normal_variance) + d_normal)); }\n out.mean = vec2<f32>(mean, d_mean);\n out.variance = vec2<f32>(variance, d_var);\n out.slope = vec2<f32>(inside, min(1.0, (abs(da - db) * m.y + abs(aa - bb) * s.y) / s.x + 2.0 * CLIP_SLOPE_U * E));\n return out;\n}\n`;\n\n/** The Gauss–Legendre Reinhard moments read the aux table, so they live in the pixel kernel. */\nconst REINHARD_WGSL = /* wgsl */ `\n// Positive Reinhard per channel then the clip to [0, 1]: h(x) = x/(1 + x) for x > 0, else 0, by the reference's composite\n// 16-point Gauss–Legendre rule in standardized units (the Speed forms), with the bounds of the quadrature (REINHARD_*_U)\n// and of its inputs (|h′| ≤ 1, |h″| ≤ 2).\nfn reinhard_moments(m: vec2<f32>, v: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n if (m.x > 0.0) {\n let h = m.x / (1.0 + m.x);\n out.mean = vec2<f32>(h, m.y + 4.0 * E * h); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(1.0 / ((1.0 + m.x) * (1.0 + m.x)), min(1.0, 2.0 * m.y + 6.0 * E));\n return out;\n }\n out.mean = vec2<f32>(0.0, m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0);\n return out;\n }\n let s = b_sqrt(v);\n let zlo = max(-m.x / s.x, -CLIP_REACH);\n if (!(CLIP_REACH > zlo)) { out.mean = vec2<f32>(0.0, m.y + s.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0); return out; }\n let hm = select(0.0, m.x / (1.0 + m.x), m.x > 0.0);\n var e1 = 0.0; var e2 = 0.0; var ed = 0.0;\n let zwidth = (CLIP_REACH - zlo) / 4.0;\n for (var p = 0u; p < 4u; p = p + 1u) {\n let zc = zlo + (f32(p) + 0.5) * zwidth;\n let zhalf = zwidth / 2.0;\n for (var k = 0u; k < 16u; k = k + 1u) {\n let z = zc + zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre}u + k];\n let x = m.x + s.x * z;\n let weight = zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre + 16}u + k] * phi(z);\n let h = x / (1.0 + x);\n e1 = e1 + weight * h;\n e2 = e2 + weight * (h - hm) * (h - hm);\n ed = ed + weight / ((1.0 + x) * (1.0 + x));\n }\n }\n let shifted = e1 - hm;\n let variance = max(0.0, e2 - shifted * shifted);\n out.mean = vec2<f32>(e1, m.y + s.y + REINHARD_MEAN_U * E * abs(e1));\n out.variance = vec2<f32>(variance, 2.0 * s.x * m.y + 2.0 * v.y + REINHARD_VARIANCE_U * E * variance);\n out.slope = vec2<f32>(ed, min(1.0, 2.0 * m.y + 2.0 * s.y + REINHARD_SLOPE_U * E));\n return out;\n}\n`;\n\n/**\n * Pass 1: the material of every lattice point of every group (the module's `material_at`, its uncertainty in bit 31; the output\n * is `lattice_out`, since a material module may declare its own `lattice`, as #491's composite module does). The group\n * records hold each group's lattice window (lx0, ly0, lw, lh), its first index in the lattice buffer and the point constants\n * c_u = 0.5 + f_x − W/2 and c_v = 0.5 + f_y − H/2 (exact in float32, checked by the host).\n */\nexport const DISPLAY_VIEW_QUALITY_LATTICE_WGSL = /* wgsl */ `${COMMON_WGSL}\n@group(0) @binding(1) var<storage, read> groups: array<u32>;\n@group(0) @binding(2) var<storage, read_write> lattice_out: array<u32>;\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.lattice_total) { return; }\n var g = 0u;\n for (var k = 1u; k < params.groups; k = k + 1u) { if (i >= groups[k * GROUP + 4u]) { g = k; } }\n let base = g * GROUP;\n let lx0 = bitcast<i32>(groups[base]);\n let ly0 = bitcast<i32>(groups[base + 1u]);\n let lw = groups[base + 2u];\n let local = i - groups[base + 4u];\n let q = ly0 + i32(local / lw);\n let p = lx0 + i32(local % lw);\n let u = (f32(p) + bitcast<f32>(groups[base + 6u])) * params.scale_u;\n let v = (f32(q) + bitcast<f32>(groups[base + 7u])) * params.scale_v;\n material_uncertain = false;\n let m = material_at(vec2<f32>(u, v));\n lattice_out[i] = m | select(0u, 0x80000000u, material_uncertain);\n}\n`;\n\n/**\n * The end of pass 2, shared by both site passes: from the site's electrons e and their absolute bound de, the readout moments\n * (mean k·e, variance k²e + c₀, third cumulant k³e), the clip at the white level and their bounds, written to site i.\n */\nconst SITE_TAIL_WGSL = /* wgsl */ `\nfn write_site(i: u32, c: u32, e: f32, de: f32, flag: u32) {\n let kc = k_of(c);\n let mean = kc * e;\n let variance = kc * kc * e + c0_of(c);\n let third = kc * kc * kc * e;\n var m = vec2<f32>(mean, kc * de + 2.0 * E * mean);\n var v = vec2<f32>(variance, kc * kc * de + 4.0 * E * variance);\n let gap = 1.0 - mean;\n // #533: bits 1 (certainly clipped) and 2 (undecided) of the flag word.\n let state = clip_state(m.x, m.y, v.x, v.y);\n let bits = flag | select(0u, 2u, state == 1u) | select(0u, 4u, state == 2u);\n if (!(gap > 0.0 && gap * gap > params.reach2 * variance)) {\n let clipped = upper_clip(m, v, 1.0);\n m = clipped.mean; v = clipped.variance;\n }\n let o = i * SITE;\n sites[o] = m.x; sites[o + 1u] = v.x; sites[o + 2u] = third;\n sites[o + 3u] = m.y; sites[o + 4u] = v.y; sites[o + 5u] = kc * kc * kc * de + 5.0 * E * third;\n sites[o + 6u] = bitcast<f32>(bits); sites[o + 7u] = e;\n}\n`;\n\n/**\n * The bindings of the site passes (pass 2); the blocked pass reads no tap records, so it declares no binding 2 (an automatic\n * layout leaves out a binding the entry point does not use, and the host binds it only for the tap-list pass).\n */\nconst siteBindingsWgsl = (tapRecords: boolean): string => /* wgsl */ `\n@group(0) @binding(1) var<storage, read> groups: array<u32>;${tapRecords ? `\n@group(0) @binding(2) var<storage, read> taps: array<u32>;` : \"\"}\n@group(0) @binding(3) var<storage, read> table: array<f32>;\n@group(0) @binding(4) var<storage, read> lattice: array<u32>;\n@group(0) @binding(5) var<storage, read_write> sites: array<f32>;\n@group(0) @binding(6) var<storage, read> spread: array<f32>;\n`;\n\n/**\n * Pass 2: the expected electrons, readout moments and white clip of every native site of the band's window, with their bounds.\n * The site's electrons are Σ_t table[(m_t·3 + c)·T + t] + dark_c, every term non-negative and rounded once on the host, so the\n * sum carries (T + 3)·E relative (`params.site_gamma`, the host's value). An uncertain lattice point adds the tap's value spread\n * over the materials (`spread`, host float64) to the site's uncertainty, which enters its bounds, or flags the site when it\n * exceeds UNCERTAINTY_LIMIT of the electrons. One dispatch covers the `params.count` sites from `params.site_first` (#492: the\n * host splits a band's sites over several dispatches so that no dispatch gathers more than its tap budget).\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_WGSL = /* wgsl */ `${COMMON_WGSL}${siteBindingsWgsl(true)}${SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n var sum = 0.0;\n var uncertainty = 0.0;\n let T = params.taps;\n for (var t = 0u; t < T; t = t + 1u) {\n let tb = t * TAP;\n let g = taps[tb] * GROUP;\n let index = groups[g + 4u] + (jj + taps[tb + 2u]) * groups[g + 2u] + ii + taps[tb + 1u];\n let raw = lattice[index];\n if ((raw >> 31u) != 0u) { uncertainty = uncertainty + spread[c * T + t]; }\n sum = sum + table[((raw & 0x7fffffffu) * 3u + c) * T + t];\n }\n let e = sum + dark_of(c);\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/**\n * Pass 2 with the defocus disc (#492): the same site electrons from a factored table, summed in blocks.\n *\n * - **Factored table.** A neutral material's value is its reflectance times the white's combined kernel (the reference forms it\n * so: reflectance[m]·neutral_c(t)), so the table holds one neutral row per channel and one row per spectral material, and\n * `materials` holds per material its row (0 for neutral, 1 + s for spectral material s) and its reflectance as float32 bits\n * (1 for a spectral material, an exact product). A scene of thousands of neutral materials (#491's composite: 4 143) then\n * needs 3·T values instead of 3·T per material.\n * - **Blocked sum.** Every group's taps form a dense cw × ch rectangle (the combined kernel's window, row-major from the group's\n * first tap, `groups[g + 5]`), so a site sums each row of a group into a row partial, the rows into a group partial and the\n * groups into the total. No tap records are read: the tap's lattice offset within its group is its (dx, dy).\n *\n * Every term is non-negative and carries two host roundings (u each, u = E/2: the row value and the reflectance), one product\n * (E) and at most cw + ch + G additions, plus the dark electrons' addition, so the electrons carry (cw_max + ch_max + G + 3)·E\n * relative (`params.site_gamma`; recursive summation of the up to 4·(2·96 + 2)² = 150 544 taps of a combined radius of 96 would\n * carry (T + 3)·E ≈ 1.8 % instead). The uncertainty, summed the same way, is scaled by 1 + site_gamma, and `params.site_tiny`\n * (host float64, rounded up) bounds the terms below the float32 normal range, which a device may flush to zero.\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL = /* wgsl */ `${COMMON_WGSL}${siteBindingsWgsl(false)}\n@group(0) @binding(7) var<storage, read> materials: array<u32>;\n${SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n let T = params.taps;\n var sum = 0.0;\n var uncertainty = 0.0;\n for (var k = 0u; k < params.groups; k = k + 1u) {\n let g = k * GROUP;\n let lw = groups[g + 2u];\n let cw = lw - params.site_w + 1u;\n let ch = groups[g + 3u] - params.site_h + 1u;\n let lattice0 = groups[g + 4u] + jj * lw + ii;\n let tap0 = groups[g + 5u];\n var group_sum = 0.0;\n var group_uncertainty = 0.0;\n for (var dy = 0u; dy < ch; dy = dy + 1u) {\n let row = lattice0 + dy * lw;\n let row_tap = tap0 + dy * cw;\n var row_sum = 0.0;\n var row_uncertainty = 0.0;\n for (var dx = 0u; dx < cw; dx = dx + 1u) {\n let raw = lattice[row + dx];\n let t = row_tap + dx;\n let m = 2u * (raw & 0x7fffffffu);\n if ((raw >> 31u) != 0u) { row_uncertainty = row_uncertainty + spread[c * T + t]; }\n row_sum = row_sum + bitcast<f32>(materials[m + 1u]) * table[(materials[m] * 3u + c) * T + t];\n }\n group_sum = group_sum + row_sum;\n group_uncertainty = group_uncertainty + row_uncertainty;\n }\n sum = sum + group_sum;\n uncertainty = uncertainty + group_uncertainty;\n }\n let e = sum + dark_of(c);\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty * (1.0 + params.site_gamma) + params.site_tiny;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/** Pass 3: the display pixels (see the module comment). */\nexport const DISPLAY_VIEW_QUALITY_PIXELS_WGSL = /* wgsl */ `${COMMON_WGSL}\nconst KEY_LEVELS: u32 = ${AHD_KEY_LEVELS}u;\nconst KEY_STEPS: f32 = ${literal(AHD_KEY_STEPS)};\nconst KEY_OCTAVES: f32 = ${literal(AHD_KEY_OCTAVES)};\nconst KEY_FLOOR: f32 = ${literal(AHD_KEY_FLOOR)};\nconst KEY_TOP: u32 = ${AHD_KEY_OCTAVES * AHD_KEY_STEPS - 1}u;\nconst CAND_BASE: u32 = ${DISPLAY_VIEW_QUALITY_AUX.candidates}u;\nconst CAND_STRIDE: u32 = ${CANDIDATE_PHASE_STRIDE}u;\nconst FLAG_UNCERTAIN: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.materialUncertain}u;\nconst FLAG_OUTSIDE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.siteOutsideWindow}u;\nconst FLAG_STATE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.stateMissing}u;\nconst FLAG_TIE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.thresholdTie}u;\nconst FLAG_CORRECTED: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.corrected}u;\nconst FLAG_CHOICE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.ahdChoice}u;\nconst FLAG_HIGHLIGHT: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.highlightUndecided}u;\nconst CHOICE_SIGMA: f32 = ${literal(DISPLAY_VIEW_QUALITY_CHOICE_SIGMA)};\n@group(0) @binding(1) var<storage, read> aux: array<f32>;\n@group(0) @binding(2) var<storage, read> geometry: array<f32>;\n@group(0) @binding(3) var<storage, read> sites: array<f32>;\n@group(0) @binding(4) var<storage, read> state_keys: array<i32>;\n@group(0) @binding(5) var<storage, read> state_values: array<f32>;\n@group(0) @binding(6) var<storage, read_write> bitmap: array<atomic<u32>>;\n@group(0) @binding(7) var<storage, read_write> out: array<f32>;\n@group(0) @binding(8) var<storage, read_write> flags: array<u32>;\n\nvar<private> pixel_flags: u32;\n${REINHARD_WGSL}\nfn reflect_x(x: i32) -> i32 { if (x < 0) { return -x; } if (x >= i32(params.native_w)) { return 2 * i32(params.native_w) - 2 - x; } return x; }\nfn reflect_y(y: i32) -> i32 { if (y < 0) { return -y; } if (y >= i32(params.native_h)) { return 2 * i32(params.native_h) - 2 - y; } return y; }\n// The site buffer offset of native site (x, y) (reflected at the raster's borders), or a flag when it lies outside the window.\nfn site_at(x: i32, y: i32) -> u32 {\n let rx = reflect_x(x) - params.site_x0;\n let ry = reflect_y(y) - params.site_y0;\n if (rx < 0 || ry < 0 || rx >= i32(params.site_w) || ry >= i32(params.site_h)) { pixel_flags = pixel_flags | FLAG_OUTSIDE; return 0u; }\n let o = (u32(ry) * params.site_w + u32(rx)) * SITE;\n pixel_flags = pixel_flags | (bitcast<u32>(sites[o + 6u]) & 1u);\n return o;\n}\n// #533: the clip state bits (1 certainly clipped, 2 undecided) of the site at offset o.\nfn clip_bits(o: u32) -> u32 { return (bitcast<u32>(sites[o + 6u]) >> 1u) & 3u; }\n// The offset of a grid key in the sorted state table, or state_count when absent.\nfn find_state(key: i32) -> u32 {\n var lo = 0u; var hi = params.state_count;\n while (lo < hi) {\n let mid = (lo + hi) / 2u;\n if (state_keys[mid] < key) { lo = mid + 1u; } else { hi = mid; }\n }\n if (lo < params.state_count && state_keys[lo] == key) { return lo; }\n return params.state_count;\n}\n// The flat-neighbourhood correction at channel means (m0, m1, m2) with its bound: trilinear between the eight grid states\n// around (log2(clamp(m, 2^-12, 1)) + 12)·8; a missing state flags the pixel. The bound of each axis's coordinate times the\n// largest state difference along that axis bounds the interpolation's dependence on the coordinates' error.\nfn flat_correction(m0: vec2<f32>, m1: vec2<f32>, m2: vec2<f32>, corr: ptr<function, array<vec2<f32>, 3>>) {\n var base: array<u32, 3>; var frac: array<f32, 3>; var dg: array<f32, 3>;\n for (var i = 0u; i < 3u; i = i + 1u) {\n var mi = m0; if (i == 1u) { mi = m1; } if (i == 2u) { mi = m2; }\n let clamped = clamp(mi.x, KEY_FLOOR, 1.0);\n let g = (log2(clamped) + KEY_OCTAVES) * KEY_STEPS;\n let b = min(u32(max(floor(g), 0.0)), KEY_TOP);\n base[i] = b; frac[i] = g - f32(b);\n // log2: 1/ln 2 per relative input error, 2^-21 absolute or 3 ULP (≤ 36E on [2^-12, 1]), the sum and the product.\n var slope = 0.0; if (mi.x > KEY_FLOOR && mi.x < 1.0) { slope = 1.4426950408889634 * mi.y / clamped; }\n dg[i] = KEY_STEPS * (slope + 4.76837158203125e-7 + 36.0 * E) + 2.0 * E * (g + 1.0);\n }\n var values: array<f32, 24>;\n var missing = false;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n let i0 = base[0] + (corner & 1u); let i1 = base[1] + ((corner >> 1u) & 1u); let i2 = base[2] + ((corner >> 2u) & 1u);\n let key = i32((i0 * KEY_LEVELS + i1) * KEY_LEVELS + i2);\n var weight = 1.0;\n for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }\n if (weight == 0.0) { for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = 0.0; } continue; }\n atomicOr(&bitmap[u32(key) / 32u], 1u << (u32(key) % 32u));\n let at = find_state(key);\n if (at >= params.state_count) { missing = true; continue; }\n for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = state_values[at * 3u + c]; }\n }\n if (missing) { pixel_flags = pixel_flags | FLAG_STATE; for (var c = 0u; c < 3u; c = c + 1u) { (*corr)[c] = vec2<f32>(0.0, 0.0); } return; }\n for (var c = 0u; c < 3u; c = c + 1u) {\n var sum = 0.0; var mag = 0.0;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n var weight = 1.0;\n for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }\n sum = sum + weight * values[corner * 3u + c];\n mag = mag + abs(weight * values[corner * 3u + c]);\n }\n // The largest state difference along each axis (over the four edges), times that axis's coordinate bound.\n var slope_term = 0.0;\n for (var i = 0u; i < 3u; i = i + 1u) {\n var largest = 0.0;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n if (((corner >> i) & 1u) == 1u) { continue; }\n largest = max(largest, abs(values[(corner | (1u << i)) * 3u + c] - values[corner * 3u + c]));\n }\n slope_term = slope_term + dg[i] * largest;\n }\n (*corr)[c] = vec2<f32>(sum, slope_term + 12.0 * E * mag);\n }\n}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.count) { return; }\n pixel_flags = 0u;\n let rx = i % params.region_w;\n let ry = params.row0 + i / params.region_w;\n let col = rx * STRIDE;\n let row = (params.columns + ry) * STRIDE;\n let col_first = bitcast<i32>(geometry[col]);\n let nx = bitcast<u32>(geometry[col + 1u]);\n let row_first = bitcast<i32>(geometry[row]);\n let ny = bitcast<u32>(geometry[row + 1u]);\n let margin = i32(params.margin);\n let reinhard = params.tone == 1u;\n let count = params.candidates;\n let share = select(1.0, 0.5, count == 2u);\n // B_c of the footprint from the host's axis sums (each over its scale squared): red, green, blue.\n let xb_even = geometry[col + 8u]; let xb_odd = geometry[col + 9u];\n let xpp_even = geometry[col + 10u]; let xpp_odd = geometry[col + 11u];\n let xqq_even = geometry[col + 12u]; let xqq_odd = geometry[col + 13u];\n let xpq_even = geometry[col + 14u]; let xpq_odd = geometry[col + 15u];\n let yb_even = geometry[row + 8u]; let yb_odd = geometry[row + 9u];\n let ypp_even = geometry[row + 10u]; let ypp_odd = geometry[row + 11u];\n let yqq_even = geometry[row + 12u]; let yqq_odd = geometry[row + 13u];\n let ypq_even = geometry[row + 14u]; let ypq_odd = geometry[row + 15u];\n let b0 = xb_even * yb_even;\n let b1 = (xpp_odd * ypp_even + xqq_odd * ypp_even + xpp_odd * yqq_even + 2.0 * xpq_odd * ypp_even + 2.0 * xpp_odd * ypq_even + 2.0 * xpq_odd * ypq_even) +\n (xpp_even * ypp_odd + xqq_even * ypp_odd + xpp_even * yqq_odd + 2.0 * xpq_even * ypp_odd + 2.0 * xpp_even * ypq_odd + 2.0 * xpq_even * ypq_odd);\n let b2 = xb_odd * yb_odd;\n var s = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cov = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var res = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n // #457: Σ_j a_j·½|h(H) − h(V)| per component, how far the reference's choice can move the value from the candidates' average.\n var choice = array<f32, 3>(0.0, 0.0, 0.0);\n // #533: the saturated real pixels (bit j·nx + i) and whether a real pixel is undecided.\n var mask0 = 0u; var mask1 = 0u; var undecided = false;\n for (var j = 0u; j < ny; j = j + 1u) {\n let ay = geometry[row + 2u + j];\n for (var ii = 0u; ii < nx; ii = ii + 1u) {\n // The overlap weight a_j: two host roundings (the overlaps over the scale) and the product.\n let aj = vec2<f32>(geometry[col + 2u + ii] * ay, 3.0 * E * abs(geometry[col + 2u + ii] * ay));\n let x = col_first + i32(ii) + margin;\n let y = row_first + i32(j) + margin;\n let phase = u32(y & 1) * 2u + u32(x & 1);\n // Site variance of each channel through the bilinear kernels, over Σw² (for B_c).\n var csv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n var variance = vec2<f32>(0.0, 0.0);\n let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n let w = aux[tb + 4u * t + 2u];\n if (w == 0.0) { continue; }\n let o = site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u]));\n variance = b_add(variance, b_scale(vec2<f32>(sites[o + 1u], sites[o + 4u]), w * w));\n }\n csv[c] = b_mul(variance, b_rounded(aux[${DISPLAY_VIEW_QUALITY_AUX.inverseW2}u + phase * 3u + c]));\n }\n let cb = CAND_BASE + phase * CAND_STRIDE;\n let entries = bitcast<u32>(aux[cb]);\n // #533: the sites the demosaic reads (the bilinear kernels and every candidate's entries), as the reference's sample does.\n var clip_any = 0u;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n if (aux[tb + 4u * t + 2u] == 0.0) { continue; }\n clip_any = clip_any | clip_bits(site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u])));\n }\n }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n var used = false;\n for (var k = 0u; k < 3u * count; k = k + 1u) { if (aux[eb + 2u + k] != 0.0) { used = true; } }\n if (used) { clip_any = clip_any | clip_bits(site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]))); }\n }\n if ((clip_any & 1u) != 0u) {\n let bit = j * nx + ii;\n if (bit < 32u) { mask0 = mask0 | (1u << bit); } else { mask1 = mask1 | (1u << (bit - 32u)); }\n } else if ((clip_any & 2u) != 0u) { undecided = true; }\n var e_mean = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cm_average = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var pixel_variance = vec2<f32>(0.0, 0.0);\n var bs: array<vec2<f32>, 3>; var hs: array<vec2<f32>, 3>;\n var hv: array<vec2<f32>, 3>;\n // #457: each candidate's rendered mean, for the reference's choice between them.\n var h_cand = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n for (var d = 0u; d < count; d = d + 1u) {\n // Camera-channel mean and covariance of candidate d from its dense weights over the window.\n var cm: array<vec2<f32>, 3>;\n var C: array<vec2<f32>, 6>;\n // Whether every site a channel reads is exactly at white (a zero bound: the reference's site is exactly 1 too); the\n // dyadic kernel weights then sum exactly on both sides, so the channel's mean carries no error (the host checks the weights).\n var exact = array<bool, 3>(true, true, true);\n for (var c = 0u; c < 3u; c = c + 1u) { cm[c] = vec2<f32>(0.0, 0.0); }\n for (var k = 0u; k < 6u; k = k + 1u) { C[k] = vec2<f32>(0.0, 0.0); }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let sm = vec2<f32>(sites[o], sites[o + 3u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let white = sm.y == 0.0 && sm.x == 1.0;\n exact[0] = exact[0] && (white || w0 == 0.0); exact[1] = exact[1] && (white || w1 == 0.0); exact[2] = exact[2] && (white || w2 == 0.0);\n cm[0] = b_add(cm[0], b_scale(sm, w0)); cm[1] = b_add(cm[1], b_scale(sm, w1)); cm[2] = b_add(cm[2], b_scale(sm, w2));\n C[0] = b_add(C[0], b_scale(sv, w0 * w0)); C[1] = b_add(C[1], b_scale(sv, w0 * w1)); C[2] = b_add(C[2], b_scale(sv, w0 * w2));\n C[3] = b_add(C[3], b_scale(sv, w1 * w1)); C[4] = b_add(C[4], b_scale(sv, w1 * w2)); C[5] = b_add(C[5], b_scale(sv, w2 * w2));\n }\n for (var c = 0u; c < 3u; c = c + 1u) { if (exact[c]) { cm[c].y = 0.0; } cm_average[c] = b_add(cm_average[c], b_scale(cm[c], share)); }\n // White balance and the highlight clip at the smallest gain. A channel exactly at white whose gain is the clip level is\n // exactly at the clip level on both sides.\n var bm: array<vec2<f32>, 3>; var bv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let gc = b_rounded(g_of(c));\n var um = b_mul(gc, cm[c]);\n if (exact[c] && cm[c].x == 1.0 && gain_at_clip(c)) { um = vec2<f32>(params.clip_level, 0.0); }\n let uv = b_mul(b_mul(gc, gc), C[select(select(0u, 3u, c == 1u), 5u, c == 2u)]);\n let gap = params.clip_level - um.x;\n if (gap > 0.0 && gap * gap > params.reach2 * uv.x) { bm[c] = um; bv[c] = uv; bs[c] = gc; }\n else { let clipped = upper_clip(um, uv, params.clip_level); bm[c] = clipped.mean; bv[c] = clipped.variance; bs[c] = b_mul(gc, clipped.slope); }\n }\n // Colour matrix with the full channel covariance, then the rendering of each component.\n var hm: array<vec2<f32>, 3>;\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let r0 = b_rounded(m_of(comp, 0u)); let r1 = b_rounded(m_of(comp, 1u)); let r2 = b_rounded(m_of(comp, 2u));\n let mean = b_add(b_add(b_mul(r0, bm[0]), b_mul(r1, bm[1])), b_mul(r2, bm[2]));\n var variance = b_add(b_add(b_mul(b_mul(r0, r0), bv[0]), b_mul(b_mul(r1, r1), bv[1])), b_mul(b_mul(r2, r2), bv[2]));\n let x01 = b_mul(b_mul(b_mul(r0, r1), b_mul(bs[0], bs[1])), C[1]);\n let x02 = b_mul(b_mul(b_mul(r0, r2), b_mul(bs[0], bs[2])), C[2]);\n let x12 = b_mul(b_mul(b_mul(r1, r2), b_mul(bs[1], bs[2])), C[4]);\n variance = b_add(variance, b_scale(b_add(b_add(x01, x02), x12), 2.0));\n var moments: Moments;\n if (!reinhard && mean.x > 0.0 && mean.x < 1.0 && mean.x * mean.x > params.reach2 * variance.x && (1.0 - mean.x) * (1.0 - mean.x) > params.reach2 * variance.x) {\n moments.mean = mean; moments.variance = variance; moments.slope = vec2<f32>(1.0, 0.0);\n } else if (reinhard) {\n moments = reinhard_moments(mean, variance);\n } else {\n // κ₃ of the component: Σ_sites (Σ_c M_comp,c·Γ_c·w_c,site)³·κ₃(site), over the candidate's dense weights.\n var third = vec2<f32>(0.0, 0.0);\n let k0 = b_mul(r0, bs[0]); let k1 = b_mul(r1, bs[1]); let k2 = b_mul(r2, bs[2]);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let coefficient = b_add(b_add(b_scale(k0, w0), b_scale(k1, w1)), b_scale(k2, w2));\n let cube = b_mul(b_mul(coefficient, coefficient), coefficient);\n third = b_add(third, b_mul(cube, vec2<f32>(sites[o + 2u], sites[o + 5u])));\n }\n var skew = vec2<f32>(0.0, 0.0);\n if (variance.x > 0.0) { skew = b_div(third, b_mul(variance, b_sqrt(variance))); }\n moments = unit_clip(mean, variance, skew);\n }\n hm[comp] = moments.mean; hv[comp] = moments.variance; hs[comp] = moments.slope;\n h_cand[d * 3u + comp] = moments.mean;\n e_mean[comp] = b_add(e_mean[comp], b_scale(moments.mean, share));\n pixel_variance = vec2<f32>(max(pixel_variance.x, moments.variance.x), max(pixel_variance.y, moments.variance.y));\n }\n // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance aᵀCa for the residual.\n var an: array<vec2<f32>, 9>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n an[c] = b_mul(b_mul(hs[0], b_rounded(m_of(0u, c))), bs[c]);\n an[3u + c] = b_mul(b_mul(hs[1], b_rounded(m_of(1u, c))), bs[c]);\n an[6u + c] = b_mul(b_mul(hs[2], b_rounded(m_of(2u, c))), bs[c]);\n var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }\n let wgt = b_mul(b_mul(b_scale(aj, share), b_rounded(bc)), csv[c]);\n cov[0] = b_add(cov[0], b_mul(b_mul(wgt, an[c]), an[c])); cov[1] = b_add(cov[1], b_mul(b_mul(wgt, an[c]), an[3u + c])); cov[2] = b_add(cov[2], b_mul(b_mul(wgt, an[c]), an[6u + c]));\n cov[3] = b_add(cov[3], b_mul(b_mul(wgt, an[3u + c]), an[3u + c])); cov[4] = b_add(cov[4], b_mul(b_mul(wgt, an[3u + c]), an[6u + c])); cov[5] = b_add(cov[5], b_mul(b_mul(wgt, an[6u + c]), an[6u + c]));\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n var l = vec2<f32>(0.0, 0.0);\n for (var p = 0u; p < 3u; p = p + 1u) {\n for (var q = 0u; q < 3u; q = q + 1u) {\n var cij = C[0]; if (p + q == 1u) { cij = C[1]; } if ((p == 0u && q == 2u) || (p == 2u && q == 0u)) { cij = C[2]; } if (p == 1u && q == 1u) { cij = C[3]; }\n if ((p == 1u && q == 2u) || (p == 2u && q == 1u)) { cij = C[4]; } if (p == 2u && q == 2u) { cij = C[5]; }\n l = b_add(l, b_mul(b_mul(an[comp * 3u + p], an[comp * 3u + q]), cij));\n }\n }\n let t = vec2<f32>(share * max(0.0, hv[comp].x - l.x), share * (hv[comp].y + l.y));\n res[comp] = b_add(res[comp], b_mul(b_mul(aj, aj), t));\n }\n }\n if (count == 2u) {\n // #457: the reference takes AHD's choice on the noise-free mosaic, one candidate or (a tie) their average, so its value\n // lies within half the candidates' difference plus their bounds of the average: the bound carries that, and the move\n // accumulates in choice, which flags the pixel below when it exceeds CHOICE_SIGMA of the pixel's noise σ.\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let diff = abs(h_cand[comp].x - h_cand[3u + comp].x);\n let both = h_cand[comp].y + h_cand[3u + comp].y;\n choice[comp] = choice[comp] + aj.x * 0.5 * diff;\n e_mean[comp].y = e_mean[comp].y + 0.5 * (diff + both);\n }\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, e_mean[comp])); }\n if (count == 2u) {\n // AHD's per-pixel choice: ¼·Var(H − V) at the last candidate's slopes, linear in the sites.\n var x0 = vec2<f32>(0.0, 0.0); var x1 = vec2<f32>(0.0, 0.0); var x2 = vec2<f32>(0.0, 0.0);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n let o = site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]));\n let d0 = b_scale(bs[0], aux[eb + 2u] - aux[eb + 5u]);\n let d1 = b_scale(bs[1], aux[eb + 3u] - aux[eb + 6u]);\n let d2 = b_scale(bs[2], aux[eb + 4u] - aux[eb + 7u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let a = b_mul(hs[0], b_add(b_add(b_mul(b_rounded(params.m00), d0), b_mul(b_rounded(params.m01), d1)), b_mul(b_rounded(params.m02), d2)));\n let b = b_mul(hs[1], b_add(b_add(b_mul(b_rounded(params.m10), d0), b_mul(b_rounded(params.m11), d1)), b_mul(b_rounded(params.m12), d2)));\n let c = b_mul(hs[2], b_add(b_add(b_mul(b_rounded(params.m20), d0), b_mul(b_rounded(params.m21), d1)), b_mul(b_rounded(params.m22), d2)));\n x0 = b_add(x0, b_mul(b_mul(a, a), sv)); x1 = b_add(x1, b_mul(b_mul(b, b), sv)); x2 = b_add(x2, b_mul(b_mul(c, c), sv));\n }\n let ajaj = b_mul(aj, aj);\n res[0] = b_add(res[0], b_mul(ajaj, b_scale(x0, 0.25))); res[1] = b_add(res[1], b_mul(ajaj, b_scale(x1, 0.25))); res[2] = b_add(res[2], b_mul(ajaj, b_scale(x2, 0.25)));\n }\n if (params.correction == 1u) {\n // The low-signal correction where the pixel noise exceeds the threshold; a decision within its bound is flagged.\n if (abs(pixel_variance.x - params.threshold) <= pixel_variance.y) { pixel_flags = pixel_flags | FLAG_TIE; }\n if (pixel_variance.x > params.threshold) {\n var corr: array<vec2<f32>, 3>;\n flat_correction(cm_average[0], cm_average[1], cm_average[2], &corr);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, corr[comp])); }\n pixel_flags = pixel_flags | FLAG_CORRECTED;\n }\n }\n }\n }\n // #457: a pixel whose candidates' choice could move it by more than CHOICE_SIGMA of its own noise σ is recomputed on the host.\n if (choice[0] > CHOICE_SIGMA * sqrt(max(cov[0].x + res[0].x, 0.0)) || choice[1] > CHOICE_SIGMA * sqrt(max(cov[3].x + res[1].x, 0.0)) ||\n choice[2] > CHOICE_SIGMA * sqrt(max(cov[5].x + res[2].x, 0.0))) { pixel_flags = pixel_flags | FLAG_CHOICE; }\n let o = i * ${DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS}u;\n out[o] = s[0].x; out[o + 1u] = s[1].x; out[o + 2u] = s[2].x;\n out[o + 3u] = cov[0].x + res[0].x; out[o + 4u] = cov[3].x + res[1].x; out[o + 5u] = cov[5].x + res[2].x;\n out[o + 6u] = cov[1].x; out[o + 7u] = cov[2].x; out[o + 8u] = cov[4].x;\n out[o + 9u] = max(s[0].y, max(s[1].y, s[2].y));\n out[o + 10u] = max(max(cov[0].y + res[0].y, cov[3].y + res[1].y), max(cov[5].y + res[2].y, max(cov[1].y, max(cov[2].y, cov[4].y))));\n flags[3u * i] = pixel_flags | select(0u, FLAG_HIGHLIGHT, undecided);\n flags[3u * i + 1u] = mask0;\n flags[3u * i + 2u] = mask1;\n}\n`;\n\n/** The lattice pass with a material module appended (`fn material_at(p: vec2<f32>) -> u32`, `var<private> material_uncertain: bool`). */\nexport function assembleDisplayViewQualityLatticeWgsl(materialModuleWgsl: string): string {\n return DISPLAY_VIEW_QUALITY_LATTICE_WGSL + \"\\n\" + materialModuleWgsl + \"\\n\";\n}\n"]}
@@ -21,6 +21,13 @@
21
21
  * It outputs, per display pixel, the three means, the three variances (linear plus residual), the three covariances
22
22
  * and two running error terms: the absolute error bound of the mean and of the variance that the clip steps
23
23
  * contribute (a fixed formula evaluated from the pixel's own float32 values; docs/WEBGPU.md, "Display view (Speed)").
24
+ * #533: it also decides each real pixel's saturation probability where the float32 values leave no doubt: a site is
25
+ * certainly unclipped or certainly clipped when its standardized distance from white, with the site's mean moved by twice
26
+ * `DISPLAY_VIEW_SITE_RELATIVE_U`·E of itself, still exceeds twice the reference's 8.5 (where the reference's probability is
27
+ * exactly 0 or 1), and a real pixel is certainly saturated (P = 1) when a site its demosaic reads is certainly clipped and
28
+ * certainly not (P = 0) when all are certainly unclipped. Three flag words per display pixel: bit 0 of the first the
29
+ * material flag, bit 1 a real pixel left undecided (the host computes the pixel's count with the reference); the other two
30
+ * a bit per real pixel j·nx + i of the footprint with P = 1.
24
31
  * The host adds the output quantization term, draws the noise, encodes and rounds (`display-view-speed-gpu.ts`), so
25
32
  * the noise draw is the CPU's bit for bit.
26
33
  *
@@ -38,6 +45,14 @@ export declare const DISPLAY_VIEW_GPU_WINDOW_SIDE: number;
38
45
  export declare const DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS = 16;
39
46
  /** Floats per display pixel in the output buffer: m0 m1 m2, v00 v11 v22 (linear + residual), c01 c02 c12, mean bound, variance bound. */
40
47
  export declare const DISPLAY_VIEW_OUTPUT_FLOATS = 11;
48
+ /** #533: flag words per display pixel: the flags (bit 0 material uncertain, bit 1 a highlight undecided), then the 36-bit saturated mask. */
49
+ export declare const DISPLAY_VIEW_FLAG_WORDS = 3;
50
+ /**
51
+ * #533: relative bound of a site's float32 mean and variance against the reference's float64, in units of E: the electrons'
52
+ * sum of at most 16 offsets (each term a host-rounded weight and table value and one product), the dark electrons, and the
53
+ * readout's products and sum carry at most 26 E; 64 leaves the rest as spare.
54
+ */
55
+ export declare const DISPLAY_VIEW_SITE_RELATIVE_U = 64;
41
56
  /** Floats per column or row record of the geometry buffer. */
42
57
  export declare const DISPLAY_VIEW_GEOMETRY_STRIDE = 20;
43
58
  /** Layout of the aux table (float32 words). */
@@ -22,6 +22,13 @@
22
22
  * It outputs, per display pixel, the three means, the three variances (linear plus residual), the three covariances
23
23
  * and two running error terms: the absolute error bound of the mean and of the variance that the clip steps
24
24
  * contribute (a fixed formula evaluated from the pixel's own float32 values; docs/WEBGPU.md, "Display view (Speed)").
25
+ * #533: it also decides each real pixel's saturation probability where the float32 values leave no doubt: a site is
26
+ * certainly unclipped or certainly clipped when its standardized distance from white, with the site's mean moved by twice
27
+ * `DISPLAY_VIEW_SITE_RELATIVE_U`·E of itself, still exceeds twice the reference's 8.5 (where the reference's probability is
28
+ * exactly 0 or 1), and a real pixel is certainly saturated (P = 1) when a site its demosaic reads is certainly clipped and
29
+ * certainly not (P = 0) when all are certainly unclipped. Three flag words per display pixel: bit 0 of the first the
30
+ * material flag, bit 1 a real pixel left undecided (the host computes the pixel's count with the reference); the other two
31
+ * a bit per real pixel j·nx + i of the footprint with P = 1.
25
32
  * The host adds the output quantization term, draws the noise, encodes and rounds (`display-view-speed-gpu.ts`), so
26
33
  * the noise draw is the CPU's bit for bit.
27
34
  *
@@ -39,6 +46,14 @@ export const DISPLAY_VIEW_GPU_WINDOW_SIDE = DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_
39
46
  export const DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS = 16;
40
47
  /** Floats per display pixel in the output buffer: m0 m1 m2, v00 v11 v22 (linear + residual), c01 c02 c12, mean bound, variance bound. */
41
48
  export const DISPLAY_VIEW_OUTPUT_FLOATS = 11;
49
+ /** #533: flag words per display pixel: the flags (bit 0 material uncertain, bit 1 a highlight undecided), then the 36-bit saturated mask. */
50
+ export const DISPLAY_VIEW_FLAG_WORDS = 3;
51
+ /**
52
+ * #533: relative bound of a site's float32 mean and variance against the reference's float64, in units of E: the electrons'
53
+ * sum of at most 16 offsets (each term a host-rounded weight and table value and one product), the dark electrons, and the
54
+ * readout's products and sum carry at most 26 E; 64 leaves the rest as spare.
55
+ */
56
+ export const DISPLAY_VIEW_SITE_RELATIVE_U = 64;
42
57
  /** Floats per column or row record of the geometry buffer. */
43
58
  export const DISPLAY_VIEW_GEOMETRY_STRIDE = 20;
44
59
  /** Layout of the aux table (float32 words). */
@@ -83,6 +98,7 @@ const SLACK: f32 = 1e-14;
83
98
  const CLIP_REACH: f32 = 8.5;
84
99
  const CLIP_MEAN_U: f32 = ${literal(DISPLAY_VIEW_CLIP_MEAN_U)};
85
100
  const CLIP_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_CLIP_VARIANCE_U)};
101
+ const SITE_RELATIVE_U: f32 = ${literal(DISPLAY_VIEW_SITE_RELATIVE_U)};
86
102
  const WINDOW: u32 = ${DISPLAY_VIEW_GPU_WINDOW_SIDE}u;
87
103
  const STRIDE: u32 = ${DISPLAY_VIEW_GEOMETRY_STRIDE}u;
88
104
  struct Params {
@@ -109,6 +125,8 @@ var<private> site_var: array<f32, 64>;
109
125
  var<private> site_third: array<f32, 64>;
110
126
  var<private> site_bound: array<f32, 64>;
111
127
 
128
+ var<private> site_clip: array<u32, 64>;
129
+
112
130
  fn k_of(c: u32) -> f32 { if (c == 0u) { return params.k0; } if (c == 1u) { return params.k1; } return params.k2; }
113
131
  fn dark_of(c: u32) -> f32 { if (c == 0u) { return params.dark0; } if (c == 1u) { return params.dark1; } return params.dark2; }
114
132
  fn c0_of(c: u32) -> f32 { if (c == 0u) { return params.c0_0; } if (c == 1u) { return params.c0_1; } return params.c0_2; }
@@ -133,6 +151,18 @@ return vec2<f32>(q, 1.0 - q);
133
151
  }
134
152
 
135
153
 
154
+
155
+ fn clip_state(m: f32, dm: f32, v: f32, dv: f32) -> u32 {
156
+ let gap = 1.0 - m;
157
+ let wide = 4.0 * params.reach2 * (v + 2.0 * dv);
158
+ let below = gap - 2.0 * dm;
159
+ let above = gap + 2.0 * dm;
160
+ if (below > 0.0 && below * below > wide) { return 0u; }
161
+ if (above < 0.0 && above * above > wide) { return 1u; }
162
+ return 2u;
163
+ }
164
+
165
+
136
166
  fn upper_clip(m: f32, v: f32, top: f32) -> vec3<f32> {
137
167
  if (!(v > 0.0)) { return vec3<f32>(min(m, top), 0.0, select(0.0, 1.0, m < top)); }
138
168
  let s = sqrt(v);
@@ -253,6 +283,7 @@ let variance = kc * kc * e + c0_of(c);
253
283
  let gap = 1.0 - mean;
254
284
  let slot = sj * window_w + si;
255
285
  site_third[slot] = kc * kc * kc * e;
286
+ site_clip[slot] = clip_state(mean, SITE_RELATIVE_U * E * mean, variance, SITE_RELATIVE_U * E * variance);
256
287
  if (gap > 0.0 && gap * gap > params.reach2 * variance) {
257
288
  site_mean[slot] = mean; site_var[slot] = variance; site_bound[slot] = 0.0;
258
289
  } else {
@@ -280,6 +311,8 @@ var c00 = 0.0; var c01 = 0.0; var c02 = 0.0; var c11 = 0.0; var c12 = 0.0; var c
280
311
  var r0 = 0.0; var r1 = 0.0; var r2 = 0.0;
281
312
  var bound_mean = 0.0; var bound_var = 0.0;
282
313
 
314
+ var mask0 = 0u; var mask1 = 0u; var undecided = false;
315
+
283
316
  for (var j = 0u; j < ny; j = j + 1u) {
284
317
  let ay = geometry[row + 2u + j];
285
318
  for (var ii = 0u; ii < nx; ii = ii + 1u) {
@@ -288,6 +321,7 @@ let x = col_first + i32(ii) + margin;
288
321
  let y = row_first + i32(j) + margin;
289
322
  let phase = u32(y & 1) * 2u + u32(x & 1);
290
323
  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>;
324
+ var clipped = false; var near_white = false;
291
325
  for (var c = 0u; c < 3u; c = c + 1u) {
292
326
  var mean = 0.0; var variance = 0.0; var third = 0.0; var bnd = 0.0;
293
327
  let taps = ${DISPLAY_VIEW_AUX.taps}u + (phase * 3u + c) * 16u;
@@ -298,10 +332,15 @@ mean = mean + w * site_mean[slot];
298
332
  variance = variance + w * w * site_var[slot];
299
333
  third = third + w * w * w * site_third[slot];
300
334
  bnd = max(bnd, select(0.0, site_bound[slot], w != 0.0));
335
+ if (w != 0.0) { if (site_clip[slot] == 1u) { clipped = true; } else if (site_clip[slot] == 2u) { near_white = true; } }
301
336
  }
302
337
  cm[c] = mean; cvv[c] = variance; cth[c] = third; cb[c] = bnd;
303
338
  csv[c] = variance * aux[${DISPLAY_VIEW_AUX.inverseW2}u + phase * 3u + c];
304
339
  }
340
+ if (clipped) {
341
+ let bit = j * nx + ii;
342
+ if (bit < 32u) { mask0 = mask0 | (1u << bit); } else { mask1 = mask1 | (1u << (bit - 32u)); }
343
+ } else if (near_white) { undecided = true; }
305
344
 
306
345
  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>;
307
346
  for (var c = 0u; c < 3u; c = c + 1u) {
@@ -376,7 +415,9 @@ out[o] = s0; out[o + 1u] = s1; out[o + 2u] = s2;
376
415
  out[o + 3u] = c00 + r0; out[o + 4u] = c11 + r1; out[o + 5u] = c22 + r2;
377
416
  out[o + 6u] = c01; out[o + 7u] = c02; out[o + 8u] = c12;
378
417
  out[o + 9u] = bound_mean * E; out[o + 10u] = bound_var * E;
379
- flags[i] = select(0u, 1u, material_uncertain);
418
+ flags[3u * i] = select(0u, 1u, material_uncertain) | select(0u, 2u, undecided);
419
+ flags[3u * i + 1u] = mask0;
420
+ flags[3u * i + 2u] = mask1;
380
421
  }
381
422
  `;
382
423
  /** The full shader: the kernel plus a material module (see `DISPLAY_VIEW_SPEED_KERNEL_WGSL`). */