@photivra/engine 2.21.0 → 2.22.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 (169) hide show
  1. package/CHANGELOG.md +118 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +11 -0
  6. package/dist/capture/c2pa-embed.d.ts +116 -0
  7. package/dist/capture/c2pa-embed.js +493 -0
  8. package/dist/capture/c2pa-embed.js.map +1 -0
  9. package/dist/capture/dng-import.d.ts +73 -8
  10. package/dist/capture/dng-import.js +315 -80
  11. package/dist/capture/dng-import.js.map +1 -1
  12. package/dist/capture/dng-opcodes.d.ts +96 -0
  13. package/dist/capture/dng-opcodes.js +391 -0
  14. package/dist/capture/dng-opcodes.js.map +1 -0
  15. package/dist/capture/dng-profile-maps.d.ts +99 -0
  16. package/dist/capture/dng-profile-maps.js +381 -0
  17. package/dist/capture/dng-profile-maps.js.map +1 -0
  18. package/dist/capture/dng-stage-image.d.ts +23 -0
  19. package/dist/capture/dng-stage-image.js +65 -0
  20. package/dist/capture/dng-stage-image.js.map +1 -0
  21. package/dist/capture/jpeg-xl-tiles.d.ts +22 -0
  22. package/dist/capture/jpeg-xl-tiles.js +151 -0
  23. package/dist/capture/jpeg-xl-tiles.js.map +1 -0
  24. package/dist/capture/lossy-jpeg.d.ts +17 -0
  25. package/dist/capture/lossy-jpeg.js +369 -0
  26. package/dist/capture/lossy-jpeg.js.map +1 -0
  27. package/dist/capture/native-raw-development.d.ts +2 -1
  28. package/dist/capture/native-raw-development.js.map +1 -1
  29. package/dist/color/neighbor-highlight-reconstruction.d.ts +66 -0
  30. package/dist/color/neighbor-highlight-reconstruction.js +234 -0
  31. package/dist/color/neighbor-highlight-reconstruction.js.map +1 -0
  32. package/dist/color/sensor-color-development.d.ts +5 -0
  33. package/dist/color/sensor-color-development.js +7 -1
  34. package/dist/color/sensor-color-development.js.map +1 -1
  35. package/dist/color/spectral-spikiness.d.ts +11 -0
  36. package/dist/color/spectral-spikiness.js +23 -0
  37. package/dist/color/spectral-spikiness.js.map +1 -0
  38. package/dist/core/execution-error.d.ts +21 -0
  39. package/dist/core/execution-error.js +12 -0
  40. package/dist/core/execution-error.js.map +1 -0
  41. package/dist/core/version.d.ts +1 -1
  42. package/dist/core/version.js +1 -1
  43. package/dist/core/version.js.map +1 -1
  44. package/dist/facade/catalogue-lens-signal.d.ts +26 -1
  45. package/dist/facade/catalogue-lens-signal.js +25 -7
  46. package/dist/facade/catalogue-lens-signal.js.map +1 -1
  47. package/dist/facade/diffraction-provider.d.ts +1 -1
  48. package/dist/facade/diffraction-provider.js +4 -4
  49. package/dist/facade/diffraction-provider.js.map +1 -1
  50. package/dist/facade/display-view-gpu-session.js +38 -6
  51. package/dist/facade/display-view-gpu-session.js.map +1 -1
  52. package/dist/facade/display-view-gpu.d.ts +13 -5
  53. package/dist/facade/display-view-gpu.js.map +1 -1
  54. package/dist/facade/display-view-normal.d.ts +15 -0
  55. package/dist/facade/display-view-normal.js +44 -0
  56. package/dist/facade/display-view-normal.js.map +1 -0
  57. package/dist/facade/display-view-rendering.d.ts +97 -0
  58. package/dist/facade/display-view-rendering.js +598 -0
  59. package/dist/facade/display-view-rendering.js.map +1 -0
  60. package/dist/facade/display-view.d.ts +37 -65
  61. package/dist/facade/display-view.js +147 -476
  62. package/dist/facade/display-view.js.map +1 -1
  63. package/dist/facade/expected-signal-setup.js +2 -2
  64. package/dist/facade/expected-signal-setup.js.map +1 -1
  65. package/dist/facade/fast-methods.d.ts +1 -1
  66. package/dist/facade/fast-methods.js +37 -8
  67. package/dist/facade/fast-methods.js.map +1 -1
  68. package/dist/facade/generic-lens-capture.d.ts +1 -1
  69. package/dist/facade/generic-lens-capture.js +1 -1
  70. package/dist/facade/generic-lens-capture.js.map +1 -1
  71. package/dist/facade/image-plane.js +41 -10
  72. package/dist/facade/image-plane.js.map +1 -1
  73. package/dist/facade/on-demand-kits.d.ts +14 -0
  74. package/dist/facade/on-demand-kits.js +36 -0
  75. package/dist/facade/on-demand-kits.js.map +1 -0
  76. package/dist/facade/opt-in-kit.d.ts +9 -0
  77. package/dist/facade/opt-in-kit.js +11 -0
  78. package/dist/facade/opt-in-kit.js.map +1 -0
  79. package/dist/facade/quality-kit.d.ts +21 -0
  80. package/dist/facade/quality-kit.js +23 -0
  81. package/dist/facade/quality-kit.js.map +1 -0
  82. package/dist/facade/quality-paths.d.ts +22 -0
  83. package/dist/facade/quality-paths.js +44 -0
  84. package/dist/facade/quality-paths.js.map +1 -0
  85. package/dist/facade/scene-radiance.d.ts +18 -2
  86. package/dist/facade/scene-radiance.js +6 -4
  87. package/dist/facade/scene-radiance.js.map +1 -1
  88. package/dist/facade/simulate-capture.d.ts +32 -13
  89. package/dist/facade/simulate-capture.js +162 -69
  90. package/dist/facade/simulate-capture.js.map +1 -1
  91. package/dist/facade/simulate-display-view.d.ts +10 -12
  92. package/dist/facade/simulate-display-view.js +57 -48
  93. package/dist/facade/simulate-display-view.js.map +1 -1
  94. package/dist/gpu/display-view-gpu-renderer.d.ts +43 -0
  95. package/dist/gpu/display-view-gpu-renderer.js +134 -0
  96. package/dist/gpu/display-view-gpu-renderer.js.map +1 -0
  97. package/dist/gpu/display-view-image-material-module.d.ts +3 -2
  98. package/dist/gpu/display-view-image-material-module.js +25 -3
  99. package/dist/gpu/display-view-image-material-module.js.map +1 -1
  100. package/dist/gpu/display-view-image-quality.d.ts +43 -1
  101. package/dist/gpu/display-view-image-quality.js +256 -11
  102. package/dist/gpu/display-view-image-quality.js.map +1 -1
  103. package/dist/gpu/display-view-quality-gpu.d.ts +13 -64
  104. package/dist/gpu/display-view-quality-gpu.js +60 -249
  105. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  106. package/dist/gpu/display-view-speed-gpu.d.ts +16 -16
  107. package/dist/gpu/display-view-speed-gpu.js +50 -12
  108. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  109. package/dist/gpu/image-kernel-accelerator.js +3 -0
  110. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  111. package/dist/gpu/image-kernel-execution.d.ts +2 -13
  112. package/dist/gpu/image-kernel-execution.js +2 -10
  113. package/dist/gpu/image-kernel-execution.js.map +1 -1
  114. package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +8 -0
  115. package/dist/gpu/wgsl-display-view-coupled-stub.js +21 -0
  116. package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -0
  117. package/dist/gpu/wgsl-display-view-coupled.d.ts +88 -0
  118. package/dist/gpu/wgsl-display-view-coupled.js +353 -0
  119. package/dist/gpu/wgsl-display-view-coupled.js.map +1 -0
  120. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -9
  121. package/dist/gpu/wgsl-display-view-quality-kernel.js +109 -111
  122. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  123. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +1 -41
  124. package/dist/gpu/wgsl-display-view-speed-kernel.js +71 -8
  125. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  126. package/dist/index.d.ts +11 -3
  127. package/dist/index.js +9 -1
  128. package/dist/index.js.map +1 -1
  129. package/dist/output/camera-channel-response.d.ts +50 -0
  130. package/dist/output/camera-channel-response.js +69 -0
  131. package/dist/output/camera-channel-response.js.map +1 -0
  132. package/dist/output/native-capture-sdr.js +5 -2
  133. package/dist/output/native-capture-sdr.js.map +1 -1
  134. package/dist/output/sdr-rendering.d.ts +18 -1
  135. package/dist/output/sdr-rendering.js +39 -2
  136. package/dist/output/sdr-rendering.js.map +1 -1
  137. package/dist/sensor/native-sensor-report.d.ts +10 -0
  138. package/dist/sensor/native-sensor-report.js +16 -0
  139. package/dist/sensor/native-sensor-report.js.map +1 -0
  140. package/dist/sensor/sensor-characteristics.d.ts +81 -13
  141. package/dist/sensor/sensor-characteristics.js +10 -6
  142. package/dist/sensor/sensor-characteristics.js.map +1 -1
  143. package/docs/API_REFERENCE.md +117 -4
  144. package/docs/QUICK_START.md +37 -8
  145. package/docs/USAGE.md +104 -22
  146. package/docs/api/capture-c2pa-embed.md +177 -0
  147. package/docs/api/capture-dng-import.md +86 -10
  148. package/docs/api/capture-dng-opcodes.md +93 -0
  149. package/docs/api/capture-dng-profile-maps.md +141 -0
  150. package/docs/api/capture-dng-stage-image.md +43 -0
  151. package/docs/api/capture-jpeg-xl-tiles.md +33 -0
  152. package/docs/api/capture-lossy-jpeg.md +41 -0
  153. package/docs/api/capture-native-raw-development.md +2 -1
  154. package/docs/api/core-execution-error.md +43 -0
  155. package/docs/api/core-version.md +1 -1
  156. package/docs/api/exports.json +255 -5
  157. package/docs/api/facade-display-view-gpu.md +5 -2
  158. package/docs/api/facade-display-view.md +11 -15
  159. package/docs/api/facade-fast-methods.md +38 -9
  160. package/docs/api/facade-quality-paths.md +24 -0
  161. package/docs/api/facade-scene-radiance.md +1 -1
  162. package/docs/api/facade-simulate-capture.md +24 -7
  163. package/docs/api/facade-simulate-display-view.md +10 -12
  164. package/docs/api/gpu-image-kernel-execution.md +0 -36
  165. package/docs/api/output-camera-channel-response.md +55 -0
  166. package/docs/api/output-sdr-rendering.md +6 -1
  167. package/docs/api/sensor-sensor-characteristics.md +54 -2
  168. package/llms.txt +1 -0
  169. package/package.json +6 -5
@@ -1 +1 @@
1
- {"version":3,"file":"wgsl-display-view-quality-kernel.js","sourceRoot":"","sources":["../../src/gpu/wgsl-display-view-quality-kernel.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * WGSL kernels of the display view's Quality path on the GPU (#479): the exact area mean with diffraction kernels, the\n * anti-aliasing filter's spots, the AHD candidates and the low-signal correction, in three passes over one band of display\n * rows (docs/WEBGPU.md, \"Display view (Quality) on the GPU\"; the float64 reference is `renderDisplayView` with\n * `createDisplayViewExpectedSignal`, facade/display-view.ts and facade/expected-signal.ts):\n *\n * 1. **Lattice** (`DISPLAY_VIEW_QUALITY_LATTICE_WGSL` plus the material module): one invocation per lattice point of each\n * lattice group (offsets that share a fractional shift, as the reference groups them): the chart material at\n * (p + 0.5 + f_x − W/2)·g, −(q + 0.5 + f_y − H/2)·g (the reference's lattice formula, an exact float32 sum times the\n * scale rounded once), with the module's uncertainty flag in the top bit.\n * 2. **Sites** (`DISPLAY_VIEW_QUALITY_SITES_WGSL`): one invocation per native site of the band's window: the expected\n * electrons Σ_t V_c(m_t, t) over the taps t of every group (the combined kernel values per material and channel, formed on\n * the host in float64 as the reference forms them: Σ_members w·Σ_λ A_c(λ)·K_λ·L_m(λ); without diffraction the offset\n * weights times the material's channel signal), plus the dark electrons; the readout moments (mean k·e, variance k²e + c₀,\n * third cumulant k³e) and the clip at the white level; and each quantity's running absolute error bound. With the defocus\n * disc (#492) the variant `DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL` reads a factored table (one neutral row, one row per\n * spectral material) and sums each group's dense tap rectangle in blocks, so its bound grows with the kernel's width.\n * 3. **Pixels** (`DISPLAY_VIEW_QUALITY_PIXELS_WGSL`): one invocation per display pixel of the band: for every real pixel of\n * the footprint, the demosaic candidates (one bilinear, or AHD's two directional linear candidates, each with its full\n * camera-channel covariance), white balance and the highlight clip, the colour matrix, the tone curve and the clip to\n * [0, 1] with the skewness term, the candidates' average, the Stein covariance with the closed-form B_c, the independent\n * residual, AHD's choice variance ¼·Var(H − V), the flag for AHD's choice on the noise-free mosaic, which the reference takes and\n * the kernel does not (#457), and the low-signal correction interpolated trilinearly between the grid\n * states of a table the host uploaded (the states themselves are computed on the host, #447; the kernel records every\n * grid key it needs in a bitmap and flags a display pixel whose state the table lacks).\n *\n * **Highlights (#533).** The site pass records whether each site is certainly below white, certainly at or above it, or\n * undecided (`clip_state`, from the site's mean and variance and their running bounds: certain only beyond twice the reference's\n * 8.5 standard deviations after moving the mean by twice its bound, where the reference's clip probability is exactly 0 or 1), in\n * bits 1 and 2 of its flag word. The pixel pass marks a real pixel saturated (P = 1) when a site its demosaic reads is certainly\n * clipped, and the display pixel `highlightUndecided` when a real pixel is neither that nor certainly unclipped. Three flag words\n * per display pixel: the flags, then a bit per real pixel j·nx + i of the footprint with P = 1 (36 bits).\n *\n * **Running bounds.** Every quantity after the site table is carried as a pair (value, absolute bound) in float32: the bound\n * of a sum is the sum of the bounds plus one rounding of the result, of a product |a|·Δb + |b|·Δa plus one rounding, of a\n * square root Δv/(2s) plus 4.5 ULP, of a quotient 2.5 ULP plus the propagated terms; the clipped-normal moments add the\n * Speed kernel's clip constants (CLIP_MEAN_U, CLIP_VARIANCE_U per standard deviation or variance) and propagate their inputs\n * with the moments' first-order derivatives at the pixel's own standardized clip levels (∂E/∂m = P(inside), ∂E/∂s = φa − φb,\n * ∂Var/∂m = −s(ρ_a + ρ_b), ∂Var/∂s = s(2ρ − aρ_a − bρ_b) with ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]), and the slope's\n * (φa − φb)/s and (aφa − bφb)/s; without noise a slope is exact unless the mean's interval straddles a clip level); the\n * Reinhard quadrature adds REINHARD_MEAN_U and REINHARD_VARIANCE_U. Mixed-sign sums (the AHD\n * kernels, the colour matrix) are therefore bounded by the magnitudes of their terms, not relative to their result. The\n * pixel's expected value and variance leave the kernel with their absolute bounds; the host doubles them (MARGIN) and adds\n * the footprint sum's relative term. Second-order terms (products of bounds) and the bound arithmetic's own float32\n * roundings are what the doubling covers.\n *\n * Precision assumptions are those of the Speed kernel: the W3C WGSL accuracy table (+ − × one rounding in either direction,\n * E = 2^-23; division 2.5 ULP, sqrt 4.5 ULP, exp (3 + 2|x|) ULP, log2 2^-21 absolute on [0.5, 2] and 3 ULP outside; no\n * trigonometric built-in).\n */\n\nimport { DISPLAY_VIEW_CLIP_MEAN_U, DISPLAY_VIEW_CLIP_VARIANCE_U, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U } from \"./wgsl-display-view-speed-kernel.js\";\nimport { AHD_KEY_FLOOR, AHD_KEY_LEVELS, AHD_KEY_OCTAVES, AHD_KEY_STEPS } from \"../facade/display-view.js\";\nimport { EXACT_SUM_WGSL } from \"./wgsl-exact-sum.js\";\n\nexport const DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE = 64;\n/** Largest footprint per axis (real pixels), as the Speed kernel. */\nexport const DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS = 6;\n/** Most spectral nodes the site passes integrate per site (2.21.0: the per-node partials are private arrays of this length). */\nexport const DISPLAY_VIEW_QUALITY_MAXIMUM_NODES = 96;\n/** Uniform parameter words (see `Params`): 56 words, 224 bytes. */\nexport const DISPLAY_VIEW_QUALITY_PARAM_WORDS = 56;\n/** Words per lattice-group record: lx0, ly0, lw, lh, lbase, first tap (the blocked site pass, #492), cu (f32), cv (f32). */\nexport const DISPLAY_VIEW_QUALITY_GROUP_WORDS = 8;\n/** Words per tap record: group, dp offset, dq offset, pad. */\nexport const DISPLAY_VIEW_QUALITY_TAP_WORDS = 4;\n/** Floats per native site of the site buffer: mean, variance, third cumulant, their three bounds, flag (u32 bits), electrons. */\nexport const DISPLAY_VIEW_QUALITY_SITE_FLOATS = 8;\n/** Floats per display pixel in the output buffer: m0 m1 m2, v00 v11 v22, c01 c02 c12, mean bound, variance bound (absolute). */\nexport const DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS = 11;\n/** Floats per column or row record of the geometry buffer (the Speed layout). */\nexport const DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE = 20;\n/** Candidate table: entries per phase (the 7 × 7 window of AHD's reach 3) and floats per entry (dx, dy, dh0, dh1, dh2, dv0, dv1, dv2). */\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES = 49;\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS = 8;\nconst CANDIDATE_PHASE_STRIDE = 4 + DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES * DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS;\n/** Layout of the aux table (float32 words). */\nexport const DISPLAY_VIEW_QUALITY_AUX = /* @__PURE__ */ Object.freeze({\n /** Per phase p, channel c, tap t: dx, dy, weight, 0 (the bilinear kernels, 4 taps; unused taps have weight 0). */\n bilinear: 0,\n /** Per phase p, channel c: 1 / Σ_t w_t² of the bilinear kernel (host float64). */\n inverseW2: 192,\n /** 16 Gauss–Legendre nodes then 16 weights on [−1, 1]. */\n gaussLegendre: 208,\n /** Per phase: the entry count (as u32 bits) and 3 pad words, then the entries (dx, dy, dense weights of candidate 0 and 1 per channel). */\n candidates: 240,\n candidatePhaseStride: CANDIDATE_PHASE_STRIDE,\n size: 240 + 4 * CANDIDATE_PHASE_STRIDE\n});\n/** Pixel flag bits of the output flag word. */\nexport const DISPLAY_VIEW_QUALITY_FLAG = /* @__PURE__ */ Object.freeze({\n /** A site the pixel read had an uncertain lattice material (recompute on the host). */\n materialUncertain: 1,\n /** A site the pixel read lies outside the band's site window (a host geometry error; recompute). */\n siteOutsideWindow: 2,\n /** The correction needed a grid state the table does not hold (recompute, or compute the state and dispatch again). */\n stateMissing: 4,\n /** A sample's pixel variance lies within its bound of the correction threshold (recompute). */\n thresholdTie: 8,\n /** Informational: the pixel's expected value includes the low-signal correction. */\n corrected: 16,\n /**\n * #457: the reference takes AHD's choice on the noise-free mosaic (one candidate, or their average on a tie), which the kernel\n * does not compute; it renders the average. The pixel is flagged (recompute) when Σ_j a_j·½|h(H) − h(V)| of some component\n * exceeds `DISPLAY_VIEW_QUALITY_CHOICE_SIGMA` times the pixel's noise standard deviation.\n */\n ahdChoice: 32,\n /** #533: a real pixel's saturation is undecided (the host computes the display pixel's count with the reference; its samples stay). */\n highlightUndecided: 64,\n /** The bits that require the host's recomputation. */\n recompute: 47\n});\n/** #533: flag words per display pixel: the flags, then the 36-bit mask of saturated real pixels. */\nexport const DISPLAY_VIEW_QUALITY_FLAG_WORDS = 3;\n/**\n * #457: the largest move, in units of the display pixel's own noise σ, that AHD's choice between its candidates may make to a pixel\n * the kernel keeps (it renders the candidates' average and its bound carries the move); a larger one is recomputed on the host. A\n * design tolerance, not a measurement: 2^-10 σ is a tenth of the 0.01 σ to which the float32 tests hold the GPU to the reference.\n */\nexport const DISPLAY_VIEW_QUALITY_CHOICE_SIGMA = 2 ** -10;\n/** Words of the grid-key bitmap: one bit per grid key (AHD_KEY_LEVELS³ keys). */\nexport const DISPLAY_VIEW_QUALITY_BITMAP_WORDS = Math.ceil(AHD_KEY_LEVELS ** 3 / 32);\n/**\n * A site whose uncertain lattice points (the material module's flag) could move its electrons by more than this fraction of\n * their value is flagged for host recomputation; a smaller uncertainty enters the site's bound instead (the tap weights times\n * the largest value difference between any two materials at that tap).\n */\nexport const DISPLAY_VIEW_QUALITY_UNCERTAINTY_LIMIT = 2 ** -10;\n/** Bound constant of a clipped-normal slope Φ(z) (in units of E): z's 8E through φ·|z| ≤ 0.24 plus the Abramowitz–Stegun 147E. */\nexport const DISPLAY_VIEW_QUALITY_SLOPE_U = 200;\n/** Relative bound of the Reinhard slope E[h′] (in units of E). */\nconst REINHARD_SLOPE_U = 64;\n/** The `E` of the bounds: one WGSL rounding of + − × in either direction, 2^-23. */\nconst E_LITERAL = \"1.1920928955078125e-7\";\nconst literal = (value: number): string => {\n const text = String(Math.fround(value));\n return /[.e]/u.test(text) ? text : `${text}.0`;\n};\n\n/** The parameter struct and the bound helpers shared by the three passes. */\nexport const DISPLAY_VIEW_QUALITY_COMMON_WGSL = /* wgsl */ `\nconst E: f32 = ${E_LITERAL};\nconst SLACK: f32 = 1e-14;\nconst CLIP_REACH: f32 = 8.5;\nconst CLIP_MEAN_U: f32 = ${literal(DISPLAY_VIEW_CLIP_MEAN_U)};\nconst CLIP_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_CLIP_VARIANCE_U)};\nconst CLIP_SLOPE_U: f32 = ${literal(DISPLAY_VIEW_QUALITY_SLOPE_U)};\nconst REINHARD_MEAN_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_MEAN_U)};\nconst REINHARD_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_VARIANCE_U)};\nconst REINHARD_SLOPE_U: f32 = ${literal(REINHARD_SLOPE_U)};\nconst UNCERTAINTY_LIMIT: f32 = ${literal(2 ** -10)};\nconst STRIDE: u32 = ${DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE}u;\nconst SITE: u32 = ${DISPLAY_VIEW_QUALITY_SITE_FLOATS}u;\nconst GROUP: u32 = ${DISPLAY_VIEW_QUALITY_GROUP_WORDS}u;\nconst TAP: u32 = ${DISPLAY_VIEW_QUALITY_TAP_WORDS}u;\nstruct Params {\n count: u32, row0: u32, region_w: u32, region_x: u32,\n region_y: u32, margin: u32, native_w: u32, native_h: u32,\n columns: u32, tone: u32, reach: u32, candidates: u32,\n site_x0: i32, site_y0: i32, site_w: u32, site_h: u32,\n correction: u32, state_count: u32, taps: u32, groups: u32,\n lattice_total: u32, gain_at_clip0: u32, gain_at_clip1: u32, gain_at_clip2: u32,\n k0: f32, k1: f32, k2: f32, reach2: f32,\n dark0: f32, dark1: f32, dark2: f32, clip_level: f32,\n c0_0: f32, c0_1: f32, c0_2: f32, threshold: f32,\n g0: f32, g1: f32, g2: f32, scale_u: f32,\n m00: f32, m01: f32, m02: f32, m10: f32,\n m11: f32, m12: f32, m20: f32, m21: f32,\n m22: f32, scale_v: f32, site_gamma: f32, site_first: u32,\n site_tiny: f32, nodes: u32, pad_g: f32, pad_h: f32,\n};\n@group(0) @binding(0) var<uniform> params: Params;\n\nfn k_of(c: u32) -> f32 { if (c == 0u) { return params.k0; } if (c == 1u) { return params.k1; } return params.k2; }\nfn dark_of(c: u32) -> f32 { if (c == 0u) { return params.dark0; } if (c == 1u) { return params.dark1; } return params.dark2; }\nfn c0_of(c: u32) -> f32 { if (c == 0u) { return params.c0_0; } if (c == 1u) { return params.c0_1; } return params.c0_2; }\nfn g_of(c: u32) -> f32 { if (c == 0u) { return params.g0; } if (c == 1u) { return params.g1; } return params.g2; }\n// Whether the channel's white-balance gain is the clip level itself (the smallest gain), exactly, in the reference's float64.\nfn gain_at_clip(c: u32) -> bool { if (c == 0u) { return params.gain_at_clip0 == 1u; } if (c == 1u) { return params.gain_at_clip1 == 1u; } return params.gain_at_clip2 == 1u; }\nfn m_of(r: u32, c: u32) -> f32 {\n let i = r * 3u + c;\n switch (i) {\n case 0u: { return params.m00; } case 1u: { return params.m01; } case 2u: { return params.m02; }\n case 3u: { return params.m10; } case 4u: { return params.m11; } case 5u: { return params.m12; }\n case 6u: { return params.m20; } case 7u: { return params.m21; } default: { return params.m22; }\n }\n}\nfn channel_of(x: i32, y: i32) -> u32 {\n if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }\n return select(2u, 1u, (x & 1) == 0);\n}\n// A value with its absolute error bound (x: value, y: bound). First-order rules; the host doubles the final bound.\nfn b_add(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x + b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_sub(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x - b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x * b.x; return vec2<f32>(v, abs(a.x) * b.y + abs(b.x) * a.y + E * abs(v)); }\n// Times an exact constant (a kernel weight, an integer, a power of two).\nfn b_scale(a: vec2<f32>, k: f32) -> vec2<f32> { let v = a.x * k; return vec2<f32>(v, a.y * abs(k) + E * abs(v)); }\n// #533: 0 when a site of mean m (bound dm) and variance v (bound dv) is certainly below white, 1 certainly at or above it, 2\n// undecided (the Speed kernel's rule: twice the reference's 8.5 standard deviations, the mean moved by twice its bound).\nfn clip_state(m: f32, dm: f32, v: f32, dv: f32) -> u32 {\n let gap = 1.0 - m;\n let wide = 4.0 * params.reach2 * (v + 2.0 * dv);\n let below = gap - 2.0 * dm;\n let above = gap + 2.0 * dm;\n if (below > 0.0 && below * below > wide) { return 0u; }\n if (above < 0.0 && above * above > wide) { return 1u; }\n return 2u;\n}\n// A host constant rounded once to float32 (a gain, a matrix entry).\nfn b_rounded(k: f32) -> vec2<f32> { return vec2<f32>(k, E * abs(k)); }\nfn b_div(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x / b.x; return vec2<f32>(v, (a.y + abs(v) * b.y) / abs(b.x) + 2.5 * E * abs(v)); }\n// |sqrt(v + d) - sqrt(v)| <= min(d / (2 sqrt v), sqrt d), plus the 4.5 ULP of the built-in.\nfn b_sqrt(a: vec2<f32>) -> vec2<f32> {\n let s = sqrt(max(a.x, 0.0));\n if (s > 0.0) { return vec2<f32>(s, min(a.y / (2.0 * s), sqrt(a.y)) + 4.5 * E * s); }\n return vec2<f32>(0.0, sqrt(a.y));\n}\n// Standard normal density, as the reference forms it, and Abramowitz & Stegun 26.2.17 as (Φ, 1 − Φ).\nfn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }\nfn pq(z: f32) -> vec2<f32> {\n let x = abs(z);\n let t = 1.0 / (1.0 + 0.2316419 * x);\n let q = phi(x) * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));\n if (z >= 0.0) { return vec2<f32>(1.0 - q, q); }\n return vec2<f32>(q, 1.0 - q);\n}\nstruct Moments { mean: vec2<f32>, variance: vec2<f32>, slope: vec2<f32> }\n// Moments of min(Y, top) for Y ~ N(m, v) with their bounds: Var = v·(Φ − zφ + z²Q − δ²), δ = φ − zQ (the Speed forms).\nfn upper_clip(m: vec2<f32>, v: vec2<f32>, top: f32) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n // No noise on either side (a zero bound): the reference's slope is 1 or 0 by m < top, decided when m's interval lies on one side.\n let decided = v.y == 0.0 && (m.x - m.y >= top || m.x + m.y < top);\n out.mean = vec2<f32>(min(m.x, top), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x < top), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let z = (top - m.x) / s.x;\n if (z > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n if (z < -CLIP_REACH) { out.mean = vec2<f32>(top, 0.0); out.variance = vec2<f32>(0.0, 0.0); out.slope = vec2<f32>(0.0, 0.0); return out; }\n let p = pq(z);\n let d = phi(z);\n let delta = d - z * p.y;\n let ratio = p.x - z * d + z * z * p.y - delta * delta;\n // First-order propagation of the inputs' bounds: with g = min(Z, z), E[g] = −δ and ρ = Var(g) = ratio, ∂E/∂m = Φ, ∂E/∂s = φ,\n // ∂Var/∂m = −s·ρ_z, ∂Var/∂s = s·(2ρ − z·ρ_z) with ρ_z = 2Q·(z + δ), ∂Φ/∂m = −φ/s and ∂Φ/∂s = −zφ/s.\n let rho_z = 2.0 * p.y * (z + delta);\n out.mean = vec2<f32>(m.x - s.x * delta, p.x * m.y + d * s.y + CLIP_MEAN_U * E * s.x);\n out.variance = vec2<f32>(v.x * max(0.0, ratio), s.x * abs(rho_z) * m.y + s.x * abs(2.0 * ratio - z * rho_z) * s.y + CLIP_VARIANCE_U * E * v.x);\n out.slope = vec2<f32>(p.x, min(1.0, (d * m.y + abs(z) * d * s.y) / s.x + CLIP_SLOPE_U * E));\n return out;\n}\n// Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1), with their bounds (the\n// Speed forms: W = m + s·g(Z), E[g] = aΦa + φa − φb + bQb, E[g²] = a²Φa + (Φb − Φa) − (bφb − aφa) + b²Qb, the Edgeworth\n// shift (s·γ/6)·(aφa − bφb) on the mean and −shift·(2·E[Y] + shift) on the variance).\nfn unit_clip(m: vec2<f32>, v: vec2<f32>, skew: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n let decided = v.y == 0.0 && ((m.x - m.y > 0.0 && m.x + m.y < 1.0) || m.x + m.y <= 0.0 || m.x - m.y >= 1.0);\n out.mean = vec2<f32>(clamp(m.x, 0.0, 1.0), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x > 0.0 && m.x < 1.0), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let a = -m.x / s.x;\n let b = (1.0 - m.x) / s.x;\n if (a < -CLIP_REACH && b > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n var pa = 0.0; var da = 0.0;\n if (!(a < -CLIP_REACH)) { pa = pq(a).x; da = phi(a); }\n var pb = 1.0; var qb = 0.0; var db = 0.0;\n if (!(b > CLIP_REACH)) { let p = pq(b); pb = p.x; qb = p.y; db = phi(b); }\n let inside = pb - pa;\n let gamma = clamp(skew.x, -1.0, 1.0);\n let aa = a * da;\n let bb = b * db;\n let eg = a * pa + (da - db) + b * qb;\n let eg2 = a * a * pa + inside - (bb - aa) + b * b * qb;\n let normal_mean = m.x + s.x * eg;\n var mean = normal_mean;\n var shift = 0.0;\n if (gamma != 0.0) { shift = s.x * gamma / 6.0 * (aa - bb); mean = normal_mean + shift; }\n // |aφa − bφb| ≤ 0.48, so |shift| ≤ 0.08·s·|γ| and a wrong γ moves it by at most 0.16·s.\n let d_shift = 0.1 * s.x * min(2.0, skew.y) + 0.1 * abs(gamma) * s.y + E * abs(shift);\n // First-order propagation of the inputs' bounds: with ρ = E[g²] − E[g]², ∂E[g]/∂a = Φa, ∂E[g]/∂b = Qb, ∂E[g²]/∂a = 2aΦa,\n // ∂E[g²]/∂b = 2bQb, so ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]); ∂E/∂m = inside, ∂E/∂s = φa − φb, ∂Var/∂m = −s(ρ_a + ρ_b),\n // ∂Var/∂s = s(2ρ − aρ_a − bρ_b); the slope's derivatives are (φa − φb)/s and (aφa − bφb)/s.\n let rho = eg2 - eg * eg;\n let rho_a = 2.0 * pa * (a - eg);\n let rho_b = 2.0 * qb * (b - eg);\n let d_mean = m.y * inside + abs(da - db) * s.y + 2.0 * CLIP_MEAN_U * E * s.x + d_shift;\n // The normal part X = v·ρ and its bound; the reference's variance is max(0, X − shift·(2·E[Y] + shift)), so it lies in\n // [0, X_cpu] when the shift term is non-negative on both sides (shift > Δshift), which caps the bound by max(Var, X + ΔX).\n let normal_variance = v.x * rho;\n let d_normal = s.x * abs(rho_a + rho_b) * m.y + s.x * abs(2.0 * rho - a * rho_a - b * rho_b) * s.y + 2.0 * CLIP_VARIANCE_U * E * v.x;\n let variance = max(0.0, normal_variance - shift * (2.0 * normal_mean + shift));\n var d_var = d_normal + 2.0 * (d_shift * (abs(normal_mean) + abs(shift)) + abs(shift) * d_mean);\n if (shift > d_shift) { d_var = min(d_var, max(variance, max(0.0, normal_variance) + d_normal)); }\n out.mean = vec2<f32>(mean, d_mean);\n out.variance = vec2<f32>(variance, d_var);\n out.slope = vec2<f32>(inside, min(1.0, (abs(da - db) * m.y + abs(aa - bb) * s.y) / s.x + 2.0 * CLIP_SLOPE_U * E));\n return out;\n}\n`;\n\n/** The Gauss–Legendre Reinhard moments read the aux table, so they live in the pixel kernel. */\nconst REINHARD_WGSL = /* wgsl */ `\n// Positive Reinhard per channel then the clip to [0, 1]: h(x) = x/(1 + x) for x > 0, else 0, by the reference's composite\n// 16-point Gauss–Legendre rule in standardized units (the Speed forms), with the bounds of the quadrature (REINHARD_*_U)\n// and of its inputs (|h′| ≤ 1, |h″| ≤ 2).\nfn reinhard_moments(m: vec2<f32>, v: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n if (m.x > 0.0) {\n let h = m.x / (1.0 + m.x);\n out.mean = vec2<f32>(h, m.y + 4.0 * E * h); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(1.0 / ((1.0 + m.x) * (1.0 + m.x)), min(1.0, 2.0 * m.y + 6.0 * E));\n return out;\n }\n out.mean = vec2<f32>(0.0, m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0);\n return out;\n }\n let s = b_sqrt(v);\n let zlo = max(-m.x / s.x, -CLIP_REACH);\n if (!(CLIP_REACH > zlo)) { out.mean = vec2<f32>(0.0, m.y + s.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0); return out; }\n let hm = select(0.0, m.x / (1.0 + m.x), m.x > 0.0);\n var e1 = 0.0; var e2 = 0.0; var ed = 0.0;\n let zwidth = (CLIP_REACH - zlo) / 4.0;\n for (var p = 0u; p < 4u; p = p + 1u) {\n let zc = zlo + (f32(p) + 0.5) * zwidth;\n let zhalf = zwidth / 2.0;\n for (var k = 0u; k < 16u; k = k + 1u) {\n let z = zc + zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre}u + k];\n let x = m.x + s.x * z;\n let weight = zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre + 16}u + k] * phi(z);\n let h = x / (1.0 + x);\n e1 = e1 + weight * h;\n e2 = e2 + weight * (h - hm) * (h - hm);\n ed = ed + weight / ((1.0 + x) * (1.0 + x));\n }\n }\n let shifted = e1 - hm;\n let variance = max(0.0, e2 - shifted * shifted);\n out.mean = vec2<f32>(e1, m.y + s.y + REINHARD_MEAN_U * E * abs(e1));\n out.variance = vec2<f32>(variance, 2.0 * s.x * m.y + 2.0 * v.y + REINHARD_VARIANCE_U * E * variance);\n out.slope = vec2<f32>(ed, min(1.0, 2.0 * m.y + 2.0 * s.y + REINHARD_SLOPE_U * E));\n return out;\n}\n`;\n\n/**\n * Pass 1: the material of every lattice point of every group (the module's `material_at`, its uncertainty in bit 31; the output\n * is `lattice_out`, since a material module may declare its own `lattice`, as #491's composite module does). The group\n * records hold each group's lattice window (lx0, ly0, lw, lh), its first index in the lattice buffer and the point constants\n * c_u = 0.5 + f_x − W/2 and c_v = 0.5 + f_y − H/2 (exact in float32, checked by the host).\n */\nexport const DISPLAY_VIEW_QUALITY_LATTICE_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}\n@group(0) @binding(1) var<storage, read> groups: array<u32>;\n@group(0) @binding(2) var<storage, read_write> lattice_out: array<u32>;\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.lattice_total) { return; }\n var g = 0u;\n for (var k = 1u; k < params.groups; k = k + 1u) { if (i >= groups[k * GROUP + 4u]) { g = k; } }\n let base = g * GROUP;\n let lx0 = bitcast<i32>(groups[base]);\n let ly0 = bitcast<i32>(groups[base + 1u]);\n let lw = groups[base + 2u];\n let local = i - groups[base + 4u];\n let q = ly0 + i32(local / lw);\n let p = lx0 + i32(local % lw);\n let u = (f32(p) + bitcast<f32>(groups[base + 6u])) * params.scale_u;\n let v = (f32(q) + bitcast<f32>(groups[base + 7u])) * params.scale_v;\n material_uncertain = false;\n let m = material_at(vec2<f32>(u, v));\n lattice_out[i] = m | select(0u, 0x80000000u, material_uncertain);\n}\n`;\n\n/**\n * The end of pass 2, shared by both site passes: from the site's electrons e and their absolute bound de, the readout moments\n * (mean k·e, variance k²e + c₀, third cumulant k³e), the clip at the white level and their bounds, written to site i.\n */\nexport const DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL = /* wgsl */ `\nfn write_site(i: u32, c: u32, e: f32, de: f32, flag: u32) {\n let kc = k_of(c);\n let mean = kc * e;\n let variance = kc * kc * e + c0_of(c);\n let third = kc * kc * kc * e;\n var m = vec2<f32>(mean, kc * de + 2.0 * E * mean);\n var v = vec2<f32>(variance, kc * kc * de + 4.0 * E * variance);\n let gap = 1.0 - mean;\n // #533: bits 1 (certainly clipped) and 2 (undecided) of the flag word.\n let state = clip_state(m.x, m.y, v.x, v.y);\n let bits = flag | select(0u, 2u, state == 1u) | select(0u, 4u, state == 2u);\n if (!(gap > 0.0 && gap * gap > params.reach2 * variance)) {\n let clipped = upper_clip(m, v, 1.0);\n m = clipped.mean; v = clipped.variance;\n }\n let o = i * SITE;\n sites[o] = m.x; sites[o + 1u] = v.x; sites[o + 2u] = third;\n sites[o + 3u] = m.y; sites[o + 4u] = v.y; sites[o + 5u] = kc * kc * kc * de + 5.0 * E * third;\n sites[o + 6u] = bitcast<f32>(bits); sites[o + 7u] = e;\n}\n`;\n\n/**\n * The bindings of the site passes (pass 2); the blocked pass reads no tap records, so it declares no binding 2 (an automatic\n * layout leaves out a binding the entry point does not use, and the host binds it only for the tap-list pass).\n */\nconst siteBindingsWgsl = (tapRecords: boolean): string => /* wgsl */ `\n@group(0) @binding(1) var<storage, read> groups: array<u32>;${tapRecords ? `\n@group(0) @binding(2) var<storage, read> taps: array<u32>;` : \"\"}\n@group(0) @binding(3) var<storage, read> table: array<f32>;\n@group(0) @binding(4) var<storage, read> lattice: array<u32>;\n@group(0) @binding(5) var<storage, read_write> sites: array<f32>;\n@group(0) @binding(6) var<storage, read> spread: array<f32>;\n${EXACT_SUM_WGSL}\nalias Nodes = array<f32, ${DISPLAY_VIEW_QUALITY_MAXIMUM_NODES}>;\nfn add_tap(p: ptr<function, Nodes>, c: u32, t: u32, m: u32) {\n let N = params.nodes;\n for (var n = 0u; n < N; n = n + 1u) { (*p)[n] = (*p)[n] + table[(c * params.taps + t) * N + n] * table[(3u * params.taps + m) * N + n]; }\n}\nfn add_nodes(p: ptr<function, Nodes>, q: ptr<function, Nodes>) { for (var n = 0u; n < params.nodes; n = n + 1u) { (*p)[n] = (*p)[n] + (*q)[n]; (*q)[n] = 0.0; } }\nfn node_total(p: ptr<function, Nodes>, dark: f32) -> f32 {\n var anchor = exact_exponent(dark);\n for (var k = 0u; k < params.nodes; k = k + 1u) { if ((*p)[k] > 0.0) { anchor = max(anchor, exact_exponent((*p)[k])); } }\n var sum: ExactSum;\n sum.anchor = anchor;\n for (var k = 0u; k < params.nodes; k = k + 1u) { exact_add(&sum, (*p)[k]); }\n exact_add(&sum, dark);\n return exact_round(&sum);\n}\n`;\n\n/**\n * Pass 2: the expected electrons, readout moments and white clip of every native site of the band's window, with their bounds.\n * 2.21.0 (signals integrated from the material spectrum per site): the site's electrons are Σ_t Σ_n C_c(t, n)·L_{m_t}(λ_n) +\n * dark_c, from the channel's per-node kernel values and the spectrum of the material each tap reads (`table`: C at\n * (c·T + t)·N + n, the spectra from 3·T·N), accumulated per node over the taps, then the node partials and the dark electrons\n * summed in integer arithmetic and rounded once (EXACT_SUM_WGSL). Every term is non-negative and carries two host roundings (E/2\n * each), its product (E) and at most T − 1 additions, then the final rounding (E), and the dark electrons their host rounding\n * (E/2), so the sum carries at most (T + 2.5)·E, inside (T + 3)·E\n * (`params.site_gamma`),\n * plus `params.site_tiny`, the host's absolute bound on what a device that flushes below the float32 normal range can drop. An uncertain lattice point adds the tap's value spread\n * over the materials (`spread`, host float64) to the site's uncertainty, which enters its bounds, or flags the site when it\n * exceeds UNCERTAINTY_LIMIT of the electrons. One dispatch covers the `params.count` sites from `params.site_first` (#492: the\n * host splits a band's sites over several dispatches so that no dispatch gathers more than its tap budget).\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}${siteBindingsWgsl(true)}${DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n var uncertainty = 0.0;\n let T = params.taps;\n var partial: Nodes;\n for (var t = 0u; t < T; t = t + 1u) {\n let tb = t * TAP;\n let g = taps[tb] * GROUP;\n let index = groups[g + 4u] + (jj + taps[tb + 2u]) * groups[g + 2u] + ii + taps[tb + 1u];\n let raw = lattice[index];\n if ((raw >> 31u) != 0u) { uncertainty = uncertainty + spread[c * T + t]; }\n add_tap(&partial, c, t, raw & 0x7fffffffu);\n }\n let e = node_total(&partial, dark_of(c));\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty + params.site_tiny;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/**\n * Pass 2 with the defocus disc (#492): the same site electrons, summed in blocks.\n *\n * - **Spectral table.** The pass reads the tap-list pass's table (2.21.0): the channel's per-node kernel values C_c(t, n) and each\n * material's spectrum, so the signal of each tap is integrated from its material's spectrum when the site is read.\n * - **Blocked sum.** Every group's taps form a dense cw × ch rectangle (the combined kernel's window, row-major from the group's\n * first tap, `groups[g + 5]`), so a site sums each row of a group into a row partial per node, the rows into a group partial\n * and the groups into the total, then the nodes in integer arithmetic. No tap records are read: the tap's lattice offset\n * within its group is its (dx, dy).\n *\n * Every term is non-negative and carries two host roundings (u each, u = E/2: the kernel value and the spectrum), one product\n * (E) and at most cw − 1 + ch − 1 + G − 1 inexact additions, then the integer sum of the node partials and the dark electrons,\n * rounded once (E), and the dark electrons' host rounding (E/2), so the electrons carry at most (cw_max + ch_max + G + 0.5)·E, inside (cw_max + ch_max + G + 3)·E (`params.site_gamma`;\n * recursive summation of the up to 4·(2·96 + 2)² = 150 544 taps of a combined radius of 96 would carry (T + 3)·E ≈ 1.8 %\n * instead). The uncertainty, summed the same way, is scaled by 1 + site_gamma, and `params.site_tiny` (host float64, rounded up)\n * bounds what a device that flushes results below the float32 normal range can drop (the tap-list pass adds it too).\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}${siteBindingsWgsl(false)}\n${DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n let T = params.taps;\n var total: Nodes;\n var group_sum: Nodes;\n var row_sum: Nodes;\n var uncertainty = 0.0;\n for (var k = 0u; k < params.groups; k = k + 1u) {\n let g = k * GROUP;\n let lw = groups[g + 2u];\n let cw = lw - params.site_w + 1u;\n let ch = groups[g + 3u] - params.site_h + 1u;\n let lattice0 = groups[g + 4u] + jj * lw + ii;\n let tap0 = groups[g + 5u];\n var group_uncertainty = 0.0;\n for (var dy = 0u; dy < ch; dy = dy + 1u) {\n let row = lattice0 + dy * lw;\n let row_tap = tap0 + dy * cw;\n var row_uncertainty = 0.0;\n for (var dx = 0u; dx < cw; dx = dx + 1u) {\n let raw = lattice[row + dx];\n let t = row_tap + dx;\n if ((raw >> 31u) != 0u) { row_uncertainty = row_uncertainty + spread[c * T + t]; }\n add_tap(&row_sum, c, t, raw & 0x7fffffffu);\n }\n add_nodes(&group_sum, &row_sum);\n group_uncertainty = group_uncertainty + row_uncertainty;\n }\n add_nodes(&total, &group_sum);\n uncertainty = uncertainty + group_uncertainty;\n }\n let e = node_total(&total, dark_of(c));\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty * (1.0 + params.site_gamma) + params.site_tiny;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/** Pass 3: the display pixels (see the module comment). */\nexport const DISPLAY_VIEW_QUALITY_PIXELS_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}\nconst KEY_LEVELS: u32 = ${AHD_KEY_LEVELS}u;\nconst KEY_STEPS: f32 = ${literal(AHD_KEY_STEPS)};\nconst KEY_OCTAVES: f32 = ${literal(AHD_KEY_OCTAVES)};\nconst KEY_FLOOR: f32 = ${literal(AHD_KEY_FLOOR)};\nconst KEY_TOP: u32 = ${AHD_KEY_OCTAVES * AHD_KEY_STEPS - 1}u;\nconst CAND_BASE: u32 = ${DISPLAY_VIEW_QUALITY_AUX.candidates}u;\nconst CAND_STRIDE: u32 = ${CANDIDATE_PHASE_STRIDE}u;\nconst FLAG_UNCERTAIN: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.materialUncertain}u;\nconst FLAG_OUTSIDE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.siteOutsideWindow}u;\nconst FLAG_STATE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.stateMissing}u;\nconst FLAG_TIE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.thresholdTie}u;\nconst FLAG_CORRECTED: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.corrected}u;\nconst FLAG_CHOICE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.ahdChoice}u;\nconst FLAG_HIGHLIGHT: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.highlightUndecided}u;\nconst CHOICE_SIGMA: f32 = ${literal(DISPLAY_VIEW_QUALITY_CHOICE_SIGMA)};\n@group(0) @binding(1) var<storage, read> aux: array<f32>;\n@group(0) @binding(2) var<storage, read> geometry: array<f32>;\n@group(0) @binding(3) var<storage, read> sites: array<f32>;\n@group(0) @binding(4) var<storage, read> state_keys: array<i32>;\n@group(0) @binding(5) var<storage, read> state_values: array<f32>;\n@group(0) @binding(6) var<storage, read_write> bitmap: array<atomic<u32>>;\n@group(0) @binding(7) var<storage, read_write> out: array<f32>;\n@group(0) @binding(8) var<storage, read_write> flags: array<u32>;\n\nvar<private> pixel_flags: u32;\n${REINHARD_WGSL}\nfn reflect_x(x: i32) -> i32 { if (x < 0) { return -x; } if (x >= i32(params.native_w)) { return 2 * i32(params.native_w) - 2 - x; } return x; }\nfn reflect_y(y: i32) -> i32 { if (y < 0) { return -y; } if (y >= i32(params.native_h)) { return 2 * i32(params.native_h) - 2 - y; } return y; }\n// The site buffer offset of native site (x, y) (reflected at the raster's borders), or a flag when it lies outside the window.\nfn site_at(x: i32, y: i32) -> u32 {\n let rx = reflect_x(x) - params.site_x0;\n let ry = reflect_y(y) - params.site_y0;\n if (rx < 0 || ry < 0 || rx >= i32(params.site_w) || ry >= i32(params.site_h)) { pixel_flags = pixel_flags | FLAG_OUTSIDE; return 0u; }\n let o = (u32(ry) * params.site_w + u32(rx)) * SITE;\n pixel_flags = pixel_flags | (bitcast<u32>(sites[o + 6u]) & 1u);\n return o;\n}\n// #533: the clip state bits (1 certainly clipped, 2 undecided) of the site at offset o.\nfn clip_bits(o: u32) -> u32 { return (bitcast<u32>(sites[o + 6u]) >> 1u) & 3u; }\n// The offset of a grid key in the sorted state table, or state_count when absent.\nfn find_state(key: i32) -> u32 {\n var lo = 0u; var hi = params.state_count;\n while (lo < hi) {\n let mid = (lo + hi) / 2u;\n if (state_keys[mid] < key) { lo = mid + 1u; } else { hi = mid; }\n }\n if (lo < params.state_count && state_keys[lo] == key) { return lo; }\n return params.state_count;\n}\n// The flat-neighbourhood correction at channel means (m0, m1, m2) with its bound: trilinear between the eight grid states\n// around (log2(clamp(m, 2^-12, 1)) + 12)·8; a missing state flags the pixel. The bound of each axis's coordinate times the\n// largest state difference along that axis bounds the interpolation's dependence on the coordinates' error.\nfn flat_correction(m0: vec2<f32>, m1: vec2<f32>, m2: vec2<f32>, corr: ptr<function, array<vec2<f32>, 3>>) {\n var base: array<u32, 3>; var frac: array<f32, 3>; var dg: array<f32, 3>;\n for (var i = 0u; i < 3u; i = i + 1u) {\n var mi = m0; if (i == 1u) { mi = m1; } if (i == 2u) { mi = m2; }\n let clamped = clamp(mi.x, KEY_FLOOR, 1.0);\n let g = (log2(clamped) + KEY_OCTAVES) * KEY_STEPS;\n let b = min(u32(max(floor(g), 0.0)), KEY_TOP);\n base[i] = b; frac[i] = g - f32(b);\n // log2: 1/ln 2 per relative input error, 2^-21 absolute or 3 ULP (≤ 36E on [2^-12, 1]), the sum and the product.\n var slope = 0.0; if (mi.x > KEY_FLOOR && mi.x < 1.0) { slope = 1.4426950408889634 * mi.y / clamped; }\n dg[i] = KEY_STEPS * (slope + 4.76837158203125e-7 + 36.0 * E) + 2.0 * E * (g + 1.0);\n }\n var values: array<f32, 24>;\n var missing = false;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n let i0 = base[0] + (corner & 1u); let i1 = base[1] + ((corner >> 1u) & 1u); let i2 = base[2] + ((corner >> 2u) & 1u);\n let key = i32((i0 * KEY_LEVELS + i1) * KEY_LEVELS + i2);\n var weight = 1.0;\n for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }\n if (weight == 0.0) { for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = 0.0; } continue; }\n atomicOr(&bitmap[u32(key) / 32u], 1u << (u32(key) % 32u));\n let at = find_state(key);\n if (at >= params.state_count) { missing = true; continue; }\n for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = state_values[at * 3u + c]; }\n }\n if (missing) { pixel_flags = pixel_flags | FLAG_STATE; for (var c = 0u; c < 3u; c = c + 1u) { (*corr)[c] = vec2<f32>(0.0, 0.0); } return; }\n for (var c = 0u; c < 3u; c = c + 1u) {\n var sum = 0.0; var mag = 0.0;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n var weight = 1.0;\n for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }\n sum = sum + weight * values[corner * 3u + c];\n mag = mag + abs(weight * values[corner * 3u + c]);\n }\n // The largest state difference along each axis (over the four edges), times that axis's coordinate bound.\n var slope_term = 0.0;\n for (var i = 0u; i < 3u; i = i + 1u) {\n var largest = 0.0;\n for (var corner = 0u; corner < 8u; corner = corner + 1u) {\n if (((corner >> i) & 1u) == 1u) { continue; }\n largest = max(largest, abs(values[(corner | (1u << i)) * 3u + c] - values[corner * 3u + c]));\n }\n slope_term = slope_term + dg[i] * largest;\n }\n (*corr)[c] = vec2<f32>(sum, slope_term + 12.0 * E * mag);\n }\n}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.count) { return; }\n pixel_flags = 0u;\n let rx = i % params.region_w;\n let ry = params.row0 + i / params.region_w;\n let col = rx * STRIDE;\n let row = (params.columns + ry) * STRIDE;\n let col_first = bitcast<i32>(geometry[col]);\n let nx = bitcast<u32>(geometry[col + 1u]);\n let row_first = bitcast<i32>(geometry[row]);\n let ny = bitcast<u32>(geometry[row + 1u]);\n let margin = i32(params.margin);\n let reinhard = params.tone == 1u;\n let count = params.candidates;\n let share = select(1.0, 0.5, count == 2u);\n // B_c of the footprint from the host's axis sums (each over its scale squared): red, green, blue.\n let xb_even = geometry[col + 8u]; let xb_odd = geometry[col + 9u];\n let xpp_even = geometry[col + 10u]; let xpp_odd = geometry[col + 11u];\n let xqq_even = geometry[col + 12u]; let xqq_odd = geometry[col + 13u];\n let xpq_even = geometry[col + 14u]; let xpq_odd = geometry[col + 15u];\n let yb_even = geometry[row + 8u]; let yb_odd = geometry[row + 9u];\n let ypp_even = geometry[row + 10u]; let ypp_odd = geometry[row + 11u];\n let yqq_even = geometry[row + 12u]; let yqq_odd = geometry[row + 13u];\n let ypq_even = geometry[row + 14u]; let ypq_odd = geometry[row + 15u];\n let b0 = xb_even * yb_even;\n let b1 = (xpp_odd * ypp_even + xqq_odd * ypp_even + xpp_odd * yqq_even + 2.0 * xpq_odd * ypp_even + 2.0 * xpp_odd * ypq_even + 2.0 * xpq_odd * ypq_even) +\n (xpp_even * ypp_odd + xqq_even * ypp_odd + xpp_even * yqq_odd + 2.0 * xpq_even * ypp_odd + 2.0 * xpp_even * ypq_odd + 2.0 * xpq_even * ypq_odd);\n let b2 = xb_odd * yb_odd;\n var s = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cov = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var res = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n // #457: Σ_j a_j·½|h(H) − h(V)| per component, how far the reference's choice can move the value from the candidates' average.\n var choice = array<f32, 3>(0.0, 0.0, 0.0);\n // #533: the saturated real pixels (bit j·nx + i) and whether a real pixel is undecided.\n var mask0 = 0u; var mask1 = 0u; var undecided = false;\n for (var j = 0u; j < ny; j = j + 1u) {\n let ay = geometry[row + 2u + j];\n for (var ii = 0u; ii < nx; ii = ii + 1u) {\n // The overlap weight a_j: two host roundings (the overlaps over the scale) and the product.\n let aj = vec2<f32>(geometry[col + 2u + ii] * ay, 3.0 * E * abs(geometry[col + 2u + ii] * ay));\n let x = col_first + i32(ii) + margin;\n let y = row_first + i32(j) + margin;\n let phase = u32(y & 1) * 2u + u32(x & 1);\n // Site variance of each channel through the bilinear kernels, over Σw² (for B_c).\n var csv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n var variance = vec2<f32>(0.0, 0.0);\n let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n let w = aux[tb + 4u * t + 2u];\n if (w == 0.0) { continue; }\n let o = site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u]));\n variance = b_add(variance, b_scale(vec2<f32>(sites[o + 1u], sites[o + 4u]), w * w));\n }\n csv[c] = b_mul(variance, b_rounded(aux[${DISPLAY_VIEW_QUALITY_AUX.inverseW2}u + phase * 3u + c]));\n }\n let cb = CAND_BASE + phase * CAND_STRIDE;\n let entries = bitcast<u32>(aux[cb]);\n // #533: the sites the demosaic reads (the bilinear kernels and every candidate's entries), as the reference's sample does.\n var clip_any = 0u;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n if (aux[tb + 4u * t + 2u] == 0.0) { continue; }\n clip_any = clip_any | clip_bits(site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u])));\n }\n }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n var used = false;\n for (var k = 0u; k < 3u * count; k = k + 1u) { if (aux[eb + 2u + k] != 0.0) { used = true; } }\n if (used) { clip_any = clip_any | clip_bits(site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]))); }\n }\n if ((clip_any & 1u) != 0u) {\n let bit = j * nx + ii;\n if (bit < 32u) { mask0 = mask0 | (1u << bit); } else { mask1 = mask1 | (1u << (bit - 32u)); }\n } else if ((clip_any & 2u) != 0u) { undecided = true; }\n var e_mean = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cm_average = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var pixel_variance = vec2<f32>(0.0, 0.0);\n var bs: array<vec2<f32>, 3>; var hs: array<vec2<f32>, 3>;\n var hv: array<vec2<f32>, 3>;\n // #457: each candidate's rendered mean, for the reference's choice between them.\n var h_cand = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n for (var d = 0u; d < count; d = d + 1u) {\n // Camera-channel mean and covariance of candidate d from its dense weights over the window.\n var cm: array<vec2<f32>, 3>;\n var C: array<vec2<f32>, 6>;\n // Whether every site a channel reads is exactly at white (a zero bound: the reference's site is exactly 1 too); the\n // dyadic kernel weights then sum exactly on both sides, so the channel's mean carries no error (the host checks the weights).\n var exact = array<bool, 3>(true, true, true);\n for (var c = 0u; c < 3u; c = c + 1u) { cm[c] = vec2<f32>(0.0, 0.0); }\n for (var k = 0u; k < 6u; k = k + 1u) { C[k] = vec2<f32>(0.0, 0.0); }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let sm = vec2<f32>(sites[o], sites[o + 3u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let white = sm.y == 0.0 && sm.x == 1.0;\n exact[0] = exact[0] && (white || w0 == 0.0); exact[1] = exact[1] && (white || w1 == 0.0); exact[2] = exact[2] && (white || w2 == 0.0);\n cm[0] = b_add(cm[0], b_scale(sm, w0)); cm[1] = b_add(cm[1], b_scale(sm, w1)); cm[2] = b_add(cm[2], b_scale(sm, w2));\n C[0] = b_add(C[0], b_scale(sv, w0 * w0)); C[1] = b_add(C[1], b_scale(sv, w0 * w1)); C[2] = b_add(C[2], b_scale(sv, w0 * w2));\n C[3] = b_add(C[3], b_scale(sv, w1 * w1)); C[4] = b_add(C[4], b_scale(sv, w1 * w2)); C[5] = b_add(C[5], b_scale(sv, w2 * w2));\n }\n for (var c = 0u; c < 3u; c = c + 1u) { if (exact[c]) { cm[c].y = 0.0; } cm_average[c] = b_add(cm_average[c], b_scale(cm[c], share)); }\n // White balance and the highlight clip at the smallest gain. A channel exactly at white whose gain is the clip level is\n // exactly at the clip level on both sides.\n var bm: array<vec2<f32>, 3>; var bv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let gc = b_rounded(g_of(c));\n var um = b_mul(gc, cm[c]);\n if (exact[c] && cm[c].x == 1.0 && gain_at_clip(c)) { um = vec2<f32>(params.clip_level, 0.0); }\n let uv = b_mul(b_mul(gc, gc), C[select(select(0u, 3u, c == 1u), 5u, c == 2u)]);\n let gap = params.clip_level - um.x;\n if (gap > 0.0 && gap * gap > params.reach2 * uv.x) { bm[c] = um; bv[c] = uv; bs[c] = gc; }\n else { let clipped = upper_clip(um, uv, params.clip_level); bm[c] = clipped.mean; bv[c] = clipped.variance; bs[c] = b_mul(gc, clipped.slope); }\n }\n // Colour matrix with the full channel covariance, then the rendering of each component.\n var hm: array<vec2<f32>, 3>;\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let r0 = b_rounded(m_of(comp, 0u)); let r1 = b_rounded(m_of(comp, 1u)); let r2 = b_rounded(m_of(comp, 2u));\n let mean = b_add(b_add(b_mul(r0, bm[0]), b_mul(r1, bm[1])), b_mul(r2, bm[2]));\n var variance = b_add(b_add(b_mul(b_mul(r0, r0), bv[0]), b_mul(b_mul(r1, r1), bv[1])), b_mul(b_mul(r2, r2), bv[2]));\n let x01 = b_mul(b_mul(b_mul(r0, r1), b_mul(bs[0], bs[1])), C[1]);\n let x02 = b_mul(b_mul(b_mul(r0, r2), b_mul(bs[0], bs[2])), C[2]);\n let x12 = b_mul(b_mul(b_mul(r1, r2), b_mul(bs[1], bs[2])), C[4]);\n variance = b_add(variance, b_scale(b_add(b_add(x01, x02), x12), 2.0));\n var moments: Moments;\n if (!reinhard && mean.x > 0.0 && mean.x < 1.0 && mean.x * mean.x > params.reach2 * variance.x && (1.0 - mean.x) * (1.0 - mean.x) > params.reach2 * variance.x) {\n moments.mean = mean; moments.variance = variance; moments.slope = vec2<f32>(1.0, 0.0);\n } else if (reinhard) {\n moments = reinhard_moments(mean, variance);\n } else {\n // κ₃ of the component: Σ_sites (Σ_c M_comp,c·Γ_c·w_c,site)³·κ₃(site), over the candidate's dense weights.\n var third = vec2<f32>(0.0, 0.0);\n let k0 = b_mul(r0, bs[0]); let k1 = b_mul(r1, bs[1]); let k2 = b_mul(r2, bs[2]);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let coefficient = b_add(b_add(b_scale(k0, w0), b_scale(k1, w1)), b_scale(k2, w2));\n let cube = b_mul(b_mul(coefficient, coefficient), coefficient);\n third = b_add(third, b_mul(cube, vec2<f32>(sites[o + 2u], sites[o + 5u])));\n }\n var skew = vec2<f32>(0.0, 0.0);\n if (variance.x > 0.0) { skew = b_div(third, b_mul(variance, b_sqrt(variance))); }\n moments = unit_clip(mean, variance, skew);\n }\n hm[comp] = moments.mean; hv[comp] = moments.variance; hs[comp] = moments.slope;\n h_cand[d * 3u + comp] = moments.mean;\n e_mean[comp] = b_add(e_mean[comp], b_scale(moments.mean, share));\n pixel_variance = vec2<f32>(max(pixel_variance.x, moments.variance.x), max(pixel_variance.y, moments.variance.y));\n }\n // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance aᵀCa for the residual.\n var an: array<vec2<f32>, 9>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n an[c] = b_mul(b_mul(hs[0], b_rounded(m_of(0u, c))), bs[c]);\n an[3u + c] = b_mul(b_mul(hs[1], b_rounded(m_of(1u, c))), bs[c]);\n an[6u + c] = b_mul(b_mul(hs[2], b_rounded(m_of(2u, c))), bs[c]);\n var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }\n let wgt = b_mul(b_mul(b_scale(aj, share), b_rounded(bc)), csv[c]);\n cov[0] = b_add(cov[0], b_mul(b_mul(wgt, an[c]), an[c])); cov[1] = b_add(cov[1], b_mul(b_mul(wgt, an[c]), an[3u + c])); cov[2] = b_add(cov[2], b_mul(b_mul(wgt, an[c]), an[6u + c]));\n cov[3] = b_add(cov[3], b_mul(b_mul(wgt, an[3u + c]), an[3u + c])); cov[4] = b_add(cov[4], b_mul(b_mul(wgt, an[3u + c]), an[6u + c])); cov[5] = b_add(cov[5], b_mul(b_mul(wgt, an[6u + c]), an[6u + c]));\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n var l = vec2<f32>(0.0, 0.0);\n for (var p = 0u; p < 3u; p = p + 1u) {\n for (var q = 0u; q < 3u; q = q + 1u) {\n var cij = C[0]; if (p + q == 1u) { cij = C[1]; } if ((p == 0u && q == 2u) || (p == 2u && q == 0u)) { cij = C[2]; } if (p == 1u && q == 1u) { cij = C[3]; }\n if ((p == 1u && q == 2u) || (p == 2u && q == 1u)) { cij = C[4]; } if (p == 2u && q == 2u) { cij = C[5]; }\n l = b_add(l, b_mul(b_mul(an[comp * 3u + p], an[comp * 3u + q]), cij));\n }\n }\n let t = vec2<f32>(share * max(0.0, hv[comp].x - l.x), share * (hv[comp].y + l.y));\n res[comp] = b_add(res[comp], b_mul(b_mul(aj, aj), t));\n }\n }\n if (count == 2u) {\n // #457: the reference takes AHD's choice on the noise-free mosaic, one candidate or (a tie) their average, so its value\n // lies within half the candidates' difference plus their bounds of the average: the bound carries that, and the move\n // accumulates in choice, which flags the pixel below when it exceeds CHOICE_SIGMA of the pixel's noise σ.\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let diff = abs(h_cand[comp].x - h_cand[3u + comp].x);\n let both = h_cand[comp].y + h_cand[3u + comp].y;\n choice[comp] = choice[comp] + aj.x * 0.5 * diff;\n e_mean[comp].y = e_mean[comp].y + 0.5 * (diff + both);\n }\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, e_mean[comp])); }\n if (count == 2u) {\n // AHD's per-pixel choice: ¼·Var(H − V) at the last candidate's slopes, linear in the sites.\n var x0 = vec2<f32>(0.0, 0.0); var x1 = vec2<f32>(0.0, 0.0); var x2 = vec2<f32>(0.0, 0.0);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n let o = site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]));\n let d0 = b_scale(bs[0], aux[eb + 2u] - aux[eb + 5u]);\n let d1 = b_scale(bs[1], aux[eb + 3u] - aux[eb + 6u]);\n let d2 = b_scale(bs[2], aux[eb + 4u] - aux[eb + 7u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let a = b_mul(hs[0], b_add(b_add(b_mul(b_rounded(params.m00), d0), b_mul(b_rounded(params.m01), d1)), b_mul(b_rounded(params.m02), d2)));\n let b = b_mul(hs[1], b_add(b_add(b_mul(b_rounded(params.m10), d0), b_mul(b_rounded(params.m11), d1)), b_mul(b_rounded(params.m12), d2)));\n let c = b_mul(hs[2], b_add(b_add(b_mul(b_rounded(params.m20), d0), b_mul(b_rounded(params.m21), d1)), b_mul(b_rounded(params.m22), d2)));\n x0 = b_add(x0, b_mul(b_mul(a, a), sv)); x1 = b_add(x1, b_mul(b_mul(b, b), sv)); x2 = b_add(x2, b_mul(b_mul(c, c), sv));\n }\n let ajaj = b_mul(aj, aj);\n res[0] = b_add(res[0], b_mul(ajaj, b_scale(x0, 0.25))); res[1] = b_add(res[1], b_mul(ajaj, b_scale(x1, 0.25))); res[2] = b_add(res[2], b_mul(ajaj, b_scale(x2, 0.25)));\n }\n if (params.correction == 1u) {\n // The low-signal correction where the pixel noise exceeds the threshold; a decision within its bound is flagged.\n if (abs(pixel_variance.x - params.threshold) <= pixel_variance.y) { pixel_flags = pixel_flags | FLAG_TIE; }\n if (pixel_variance.x > params.threshold) {\n var corr: array<vec2<f32>, 3>;\n flat_correction(cm_average[0], cm_average[1], cm_average[2], &corr);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, corr[comp])); }\n pixel_flags = pixel_flags | FLAG_CORRECTED;\n }\n }\n }\n }\n // #457: a pixel whose candidates' choice could move it by more than CHOICE_SIGMA of its own noise σ is recomputed on the host.\n if (choice[0] > CHOICE_SIGMA * sqrt(max(cov[0].x + res[0].x, 0.0)) || choice[1] > CHOICE_SIGMA * sqrt(max(cov[3].x + res[1].x, 0.0)) ||\n choice[2] > CHOICE_SIGMA * sqrt(max(cov[5].x + res[2].x, 0.0))) { pixel_flags = pixel_flags | FLAG_CHOICE; }\n let o = i * ${DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS}u;\n out[o] = s[0].x; out[o + 1u] = s[1].x; out[o + 2u] = s[2].x;\n out[o + 3u] = cov[0].x + res[0].x; out[o + 4u] = cov[3].x + res[1].x; out[o + 5u] = cov[5].x + res[2].x;\n out[o + 6u] = cov[1].x; out[o + 7u] = cov[2].x; out[o + 8u] = cov[4].x;\n out[o + 9u] = max(s[0].y, max(s[1].y, s[2].y));\n out[o + 10u] = max(max(cov[0].y + res[0].y, cov[3].y + res[1].y), max(cov[5].y + res[2].y, max(cov[1].y, max(cov[2].y, cov[4].y))));\n flags[3u * i] = pixel_flags | select(0u, FLAG_HIGHLIGHT, undecided);\n flags[3u * i + 1u] = mask0;\n flags[3u * i + 2u] = mask1;\n}\n`;\n\n/** The lattice pass with a material module appended (`fn material_at(p: vec2<f32>) -> u32`, `var<private> material_uncertain: bool`). */\nexport function assembleDisplayViewQualityLatticeWgsl(materialModuleWgsl: string): string {\n return DISPLAY_VIEW_QUALITY_LATTICE_WGSL + \"\\n\" + materialModuleWgsl + \"\\n\";\n}\n"]}
1
+ {"version":3,"file":"wgsl-display-view-quality-kernel.js","sourceRoot":"","sources":["../../src/gpu/wgsl-display-view-quality-kernel.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * WGSL kernels of the display view's Quality path on the GPU (#479): the exact area mean with diffraction kernels, the\n * anti-aliasing filter's spots and the AHD candidates, in three passes over one band of display\n * rows (docs/WEBGPU.md, \"Display view (Quality) on the GPU\"; the float64 reference is `renderDisplayView` with\n * `createDisplayViewExpectedSignal`, facade/display-view.ts and facade/expected-signal.ts):\n *\n * 1. **Lattice** (`DISPLAY_VIEW_QUALITY_LATTICE_WGSL` plus the material module): one invocation per lattice point of each\n * lattice group (offsets that share a fractional shift, as the reference groups them): the chart material at\n * (p + 0.5 + f_x − W/2)·g, −(q + 0.5 + f_y − H/2)·g (the reference's lattice formula, an exact float32 sum times the\n * scale rounded once), with the module's uncertainty flag in the top bit.\n * 2. **Sites** (`DISPLAY_VIEW_QUALITY_SITES_WGSL`): one invocation per native site of the band's window: the expected\n * electrons Σ_t V_c(m_t, t) over the taps t of every group (the combined kernel values per material and channel, formed on\n * the host in float64 as the reference forms them: Σ_members w·Σ_λ A_c(λ)·K_λ·L_m(λ); without diffraction the offset\n * weights times the material's channel signal), plus the dark electrons; the readout moments (mean k·e, variance k²e + c₀,\n * third cumulant k³e) and the clip at the white level; and each quantity's running absolute error bound. With the defocus\n * disc (#492) the variant `DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL` reads a factored table (one neutral row, one row per\n * spectral material) and sums each group's dense tap rectangle in blocks, so its bound grows with the kernel's width.\n * 3. **Pixels** (`DISPLAY_VIEW_QUALITY_PIXELS_WGSL`): one invocation per display pixel of the band: for every real pixel of\n * the footprint, the demosaic candidates (one bilinear, or AHD's two directional linear candidates, each with its full\n * camera-channel covariance), white balance and the highlight clip, the colour matrix, the tone curve and the clip to\n * [0, 1] with the skewness term, the candidates' average, the Stein covariance with the closed-form B_c, the independent\n * residual, AHD's choice variance ¼·Var(H − V), the flag for AHD's choice on the noise-free mosaic, which the reference takes and\n * the kernel does not (#457). 2.22.0: there is no low-signal correction (docs/DISPLAY_VIEW.md, \"AHD at low signal\").\n *\n * **Highlights (#533).** The site pass records whether each site is certainly below white, certainly at or above it, or\n * undecided (`clip_state`, from the site's mean and variance and their running bounds: certain only beyond twice the reference's\n * 8.5 standard deviations after moving the mean by twice its bound, where the reference's clip probability is exactly 0 or 1), in\n * bits 1 and 2 of its flag word. The pixel pass marks a real pixel saturated (P = 1) when a site its demosaic reads is certainly\n * clipped, and the display pixel `highlightUndecided` when a real pixel is neither that nor certainly unclipped. Three flag words\n * per display pixel: the flags, then a bit per real pixel j·nx + i of the footprint with P = 1 (36 bits).\n *\n * **Running bounds.** Every quantity after the site table is carried as a pair (value, absolute bound) in float32: the bound\n * of a sum is the sum of the bounds plus one rounding of the result, of a product |a|·Δb + |b|·Δa plus one rounding, of a\n * square root Δv/(2s) plus 4.5 ULP, of a quotient 2.5 ULP plus the propagated terms; the clipped-normal moments add the\n * Speed kernel's clip constants (CLIP_MEAN_U, CLIP_VARIANCE_U per standard deviation or variance) and propagate their inputs\n * with the moments' first-order derivatives at the pixel's own standardized clip levels (∂E/∂m = P(inside), ∂E/∂s = φa − φb,\n * ∂Var/∂m = −s(ρ_a + ρ_b), ∂Var/∂s = s(2ρ − aρ_a − bρ_b) with ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]), and the slope's\n * (φa − φb)/s and (aφa − bφb)/s; without noise a slope is exact unless the mean's interval straddles a clip level); the\n * Reinhard quadrature adds REINHARD_MEAN_U and REINHARD_VARIANCE_U. Mixed-sign sums (the AHD\n * kernels, the colour matrix) are therefore bounded by the magnitudes of their terms, not relative to their result. The\n * pixel's expected value and variance leave the kernel with their absolute bounds; the host doubles them (MARGIN) and adds\n * the footprint sum's relative term. Second-order terms (products of bounds) and the bound arithmetic's own float32\n * roundings are what the doubling covers.\n *\n * Precision assumptions are those of the Speed kernel: the W3C WGSL accuracy table (+ − × one rounding in either direction,\n * E = 2^-23; division 2.5 ULP, sqrt 4.5 ULP, exp (3 + 2|x|) ULP, log2 2^-21 absolute on [0.5, 2] and 3 ULP outside; no\n * trigonometric built-in).\n */\n\nimport { DISPLAY_VIEW_CLIP_MEAN_U, DISPLAY_VIEW_CLIP_VARIANCE_U, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U } from \"./wgsl-display-view-speed-kernel.js\";\nimport { EXACT_SUM_WGSL } from \"./wgsl-exact-sum.js\";\nimport { DISPLAY_VIEW_COUPLED_STUB_WGSL } from \"./wgsl-display-view-coupled-stub.js\";\n\nexport const DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE = 64;\n/** Largest footprint per axis (real pixels), as the Speed kernel. */\nexport const DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS = 6;\n/** Most spectral nodes the site passes integrate per site (2.21.0: the per-node partials are private arrays of this length). */\nexport const DISPLAY_VIEW_QUALITY_MAXIMUM_NODES = 96;\n/** Uniform parameter words (see `Params`): 56 words, 224 bytes. */\nexport const DISPLAY_VIEW_QUALITY_PARAM_WORDS = 56;\n/** Words per lattice-group record: lx0, ly0, lw, lh, lbase, first tap (the blocked site pass, #492), cu (f32), cv (f32). */\nexport const DISPLAY_VIEW_QUALITY_GROUP_WORDS = 8;\n/** Words per tap record: group, dp offset, dq offset, pad. */\nexport const DISPLAY_VIEW_QUALITY_TAP_WORDS = 4;\n/** Floats per native site of the site buffer: mean, variance, third cumulant, their three bounds, flag (u32 bits), electrons. */\nexport const DISPLAY_VIEW_QUALITY_SITE_FLOATS = 8;\n/** Floats per display pixel in the output buffer: m0 m1 m2, v00 v11 v22, c01 c02 c12, mean bound, variance bound (absolute). */\nexport const DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS = 11;\n/** Floats per column or row record of the geometry buffer (the Speed layout). */\nexport const DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE = 20;\n/** Candidate table: entries per phase (the 7 × 7 window of AHD's reach 3) and floats per entry (dx, dy, dh0, dh1, dh2, dv0, dv1, dv2). */\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES = 49;\nexport const DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS = 8;\nconst CANDIDATE_PHASE_STRIDE = 4 + DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES * DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS;\n/** Layout of the aux table (float32 words). */\nexport const DISPLAY_VIEW_QUALITY_AUX = /* @__PURE__ */ Object.freeze({\n /** Per phase p, channel c, tap t: dx, dy, weight, 0 (the bilinear kernels, 4 taps; unused taps have weight 0). */\n bilinear: 0,\n /** Per phase p, channel c: 1 / Σ_t w_t² of the bilinear kernel (host float64). */\n inverseW2: 192,\n /** 16 Gauss–Legendre nodes then 16 weights on [−1, 1]. */\n gaussLegendre: 208,\n /** Per phase: the entry count (as u32 bits) and 3 pad words, then the entries (dx, dy, dense weights of candidate 0 and 1 per channel). */\n candidates: 240,\n candidatePhaseStride: CANDIDATE_PHASE_STRIDE,\n size: 240 + 4 * CANDIDATE_PHASE_STRIDE\n});\n/** Pixel flag bits of the output flag word. */\nexport const DISPLAY_VIEW_QUALITY_FLAG = /* @__PURE__ */ Object.freeze({\n /** A site the pixel read had an uncertain lattice material (recompute on the host). */\n materialUncertain: 1,\n /** A site the pixel read lies outside the band's site window (a host geometry error; recompute). */\n siteOutsideWindow: 2,\n // Bits 4, 8 and 16 (the low-signal correction's state, threshold and applied flags) are unused since 2.22.0.\n /**\n * #457: the reference takes AHD's choice on the noise-free mosaic (one candidate, or their average on a tie), which the kernel\n * does not compute; it renders the average. The pixel is flagged (recompute) when Σ_j a_j·½|h(H) − h(V)| of some component\n * exceeds `DISPLAY_VIEW_QUALITY_CHOICE_SIGMA` times the pixel's noise standard deviation.\n */\n ahdChoice: 32,\n /** #533: a real pixel's saturation is undecided (the host computes the display pixel's count with the reference; its samples stay). */\n highlightUndecided: 64,\n /** The bits that require the host's recomputation. */\n recompute: 35\n});\n/** #533: flag words per display pixel: the flags, then the 36-bit mask of saturated real pixels. */\nexport const DISPLAY_VIEW_QUALITY_FLAG_WORDS = 3;\n/**\n * #457: the largest move, in units of the display pixel's own noise σ, that AHD's choice between its candidates may make to a pixel\n * the kernel keeps (it renders the candidates' average and its bound carries the move); a larger one is recomputed on the host. A\n * design tolerance, not a measurement: 2^-10 σ is a tenth of the 0.01 σ to which the float32 tests hold the GPU to the reference.\n */\nexport const DISPLAY_VIEW_QUALITY_CHOICE_SIGMA = 2 ** -10;\n/**\n * A site whose uncertain lattice points (the material module's flag) could move its electrons by more than this fraction of\n * their value is flagged for host recomputation; a smaller uncertainty enters the site's bound instead (the tap weights times\n * the largest value difference between any two materials at that tap).\n */\nexport const DISPLAY_VIEW_QUALITY_UNCERTAINTY_LIMIT = 2 ** -10;\n/** Bound constant of a clipped-normal slope Φ(z) (in units of E): z's 8E through φ·|z| ≤ 0.24 plus the Abramowitz–Stegun 147E. */\nexport const DISPLAY_VIEW_QUALITY_SLOPE_U = 200;\n/** Relative bound of the Reinhard slope E[h′] (in units of E). */\nconst REINHARD_SLOPE_U = 64;\n/** The `E` of the bounds: one WGSL rounding of + − × in either direction, 2^-23. */\nconst E_LITERAL = \"1.1920928955078125e-7\";\nconst literal = (value: number): string => {\n const text = String(Math.fround(value));\n return /[.e]/u.test(text) ? text : `${text}.0`;\n};\n\n/** The parameter struct and the bound helpers shared by the three passes. */\nexport const DISPLAY_VIEW_QUALITY_COMMON_WGSL = /* wgsl */ `\nconst E: f32 = ${E_LITERAL};\nconst SLACK: f32 = 1e-14;\nconst CLIP_REACH: f32 = 8.5;\nconst CLIP_MEAN_U: f32 = ${literal(DISPLAY_VIEW_CLIP_MEAN_U)};\nconst CLIP_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_CLIP_VARIANCE_U)};\nconst CLIP_SLOPE_U: f32 = ${literal(DISPLAY_VIEW_QUALITY_SLOPE_U)};\nconst REINHARD_MEAN_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_MEAN_U)};\nconst REINHARD_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_REINHARD_VARIANCE_U)};\nconst REINHARD_SLOPE_U: f32 = ${literal(REINHARD_SLOPE_U)};\nconst UNCERTAINTY_LIMIT: f32 = ${literal(2 ** -10)};\nconst STRIDE: u32 = ${DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE}u;\nconst SITE: u32 = ${DISPLAY_VIEW_QUALITY_SITE_FLOATS}u;\nconst GROUP: u32 = ${DISPLAY_VIEW_QUALITY_GROUP_WORDS}u;\nconst TAP: u32 = ${DISPLAY_VIEW_QUALITY_TAP_WORDS}u;\nstruct Params {\n count: u32, row0: u32, region_w: u32, region_x: u32,\n region_y: u32, margin: u32, native_w: u32, native_h: u32,\n columns: u32, tone: u32, reach: u32, candidates: u32,\n site_x0: i32, site_y0: i32, site_w: u32, site_h: u32,\n unused_16: u32, unused_17: u32, taps: u32, groups: u32,\n lattice_total: u32, gain_at_clip0: u32, gain_at_clip1: u32, gain_at_clip2: u32,\n k0: f32, k1: f32, k2: f32, reach2: f32,\n dark0: f32, dark1: f32, dark2: f32, clip_level: f32,\n c0_0: f32, c0_1: f32, c0_2: f32, unused_35: f32,\n g0: f32, g1: f32, g2: f32, scale_u: f32,\n m00: f32, m01: f32, m02: f32, m10: f32,\n m11: f32, m12: f32, m20: f32, m21: f32,\n m22: f32, scale_v: f32, site_gamma: f32, site_first: u32,\n site_tiny: f32, nodes: u32, pad_g: f32, pad_h: f32,\n};\n@group(0) @binding(0) var<uniform> params: Params;\n\nfn k_of(c: u32) -> f32 { if (c == 0u) { return params.k0; } if (c == 1u) { return params.k1; } return params.k2; }\nfn dark_of(c: u32) -> f32 { if (c == 0u) { return params.dark0; } if (c == 1u) { return params.dark1; } return params.dark2; }\nfn c0_of(c: u32) -> f32 { if (c == 0u) { return params.c0_0; } if (c == 1u) { return params.c0_1; } return params.c0_2; }\nfn g_of(c: u32) -> f32 { if (c == 0u) { return params.g0; } if (c == 1u) { return params.g1; } return params.g2; }\n// Whether the channel's white-balance gain is the clip level itself (the smallest gain), exactly, in the reference's float64.\nfn gain_at_clip(c: u32) -> bool { if (c == 0u) { return params.gain_at_clip0 == 1u; } if (c == 1u) { return params.gain_at_clip1 == 1u; } return params.gain_at_clip2 == 1u; }\nfn m_of(r: u32, c: u32) -> f32 {\n let i = r * 3u + c;\n switch (i) {\n case 0u: { return params.m00; } case 1u: { return params.m01; } case 2u: { return params.m02; }\n case 3u: { return params.m10; } case 4u: { return params.m11; } case 5u: { return params.m12; }\n case 6u: { return params.m20; } case 7u: { return params.m21; } default: { return params.m22; }\n }\n}\nfn channel_of(x: i32, y: i32) -> u32 {\n if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }\n return select(2u, 1u, (x & 1) == 0);\n}\n// A value with its absolute error bound (x: value, y: bound). First-order rules; the host doubles the final bound.\nfn b_add(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x + b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_sub(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x - b.x; return vec2<f32>(v, a.y + b.y + E * abs(v)); }\nfn b_mul(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x * b.x; return vec2<f32>(v, abs(a.x) * b.y + abs(b.x) * a.y + E * abs(v)); }\n// Times an exact constant (a kernel weight, an integer, a power of two).\nfn b_scale(a: vec2<f32>, k: f32) -> vec2<f32> { let v = a.x * k; return vec2<f32>(v, a.y * abs(k) + E * abs(v)); }\n// #533: 0 when a site of mean m (bound dm) and variance v (bound dv) is certainly below white, 1 certainly at or above it, 2\n// undecided (the Speed kernel's rule: twice the reference's 8.5 standard deviations, the mean moved by twice its bound).\nfn clip_state(m: f32, dm: f32, v: f32, dv: f32) -> u32 {\n let gap = 1.0 - m;\n let wide = 4.0 * params.reach2 * (v + 2.0 * dv);\n let below = gap - 2.0 * dm;\n let above = gap + 2.0 * dm;\n if (below > 0.0 && below * below > wide) { return 0u; }\n if (above < 0.0 && above * above > wide) { return 1u; }\n return 2u;\n}\n// A host constant rounded once to float32 (a gain, a matrix entry).\nfn b_rounded(k: f32) -> vec2<f32> { return vec2<f32>(k, E * abs(k)); }\nfn b_div(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x / b.x; return vec2<f32>(v, (a.y + abs(v) * b.y) / abs(b.x) + 2.5 * E * abs(v)); }\n// |sqrt(v + d) - sqrt(v)| <= min(d / (2 sqrt v), sqrt d), plus the 4.5 ULP of the built-in.\nfn b_sqrt(a: vec2<f32>) -> vec2<f32> {\n let s = sqrt(max(a.x, 0.0));\n if (s > 0.0) { return vec2<f32>(s, min(a.y / (2.0 * s), sqrt(a.y)) + 4.5 * E * s); }\n return vec2<f32>(0.0, sqrt(a.y));\n}\n// Standard normal density, as the reference forms it, and Abramowitz & Stegun 26.2.17 as (Φ, 1 − Φ).\nfn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }\nfn pq(z: f32) -> vec2<f32> {\n let x = abs(z);\n let t = 1.0 / (1.0 + 0.2316419 * x);\n let q = phi(x) * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));\n if (z >= 0.0) { return vec2<f32>(1.0 - q, q); }\n return vec2<f32>(q, 1.0 - q);\n}\nstruct Moments { mean: vec2<f32>, variance: vec2<f32>, slope: vec2<f32> }\n// Moments of min(Y, top) for Y ~ N(m, v) with their bounds: Var = v·(Φ − zφ + z²Q − δ²), δ = φ − zQ (the Speed forms).\nfn upper_clip(m: vec2<f32>, v: vec2<f32>, top: f32) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n // No noise on either side (a zero bound): the reference's slope is 1 or 0 by m < top, decided when m's interval lies on one side.\n let decided = v.y == 0.0 && (m.x - m.y >= top || m.x + m.y < top);\n out.mean = vec2<f32>(min(m.x, top), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x < top), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let z = (top - m.x) / s.x;\n if (z > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n if (z < -CLIP_REACH) { out.mean = vec2<f32>(top, 0.0); out.variance = vec2<f32>(0.0, 0.0); out.slope = vec2<f32>(0.0, 0.0); return out; }\n let p = pq(z);\n let d = phi(z);\n let delta = d - z * p.y;\n let ratio = p.x - z * d + z * z * p.y - delta * delta;\n // First-order propagation of the inputs' bounds: with g = min(Z, z), E[g] = −δ and ρ = Var(g) = ratio, ∂E/∂m = Φ, ∂E/∂s = φ,\n // ∂Var/∂m = −s·ρ_z, ∂Var/∂s = s·(2ρ − z·ρ_z) with ρ_z = 2Q·(z + δ), ∂Φ/∂m = −φ/s and ∂Φ/∂s = −zφ/s.\n let rho_z = 2.0 * p.y * (z + delta);\n out.mean = vec2<f32>(m.x - s.x * delta, p.x * m.y + d * s.y + CLIP_MEAN_U * E * s.x);\n out.variance = vec2<f32>(v.x * max(0.0, ratio), s.x * abs(rho_z) * m.y + s.x * abs(2.0 * ratio - z * rho_z) * s.y + CLIP_VARIANCE_U * E * v.x);\n out.slope = vec2<f32>(p.x, min(1.0, (d * m.y + abs(z) * d * s.y) / s.x + CLIP_SLOPE_U * E));\n return out;\n}\n// Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1), with their bounds (the\n// Speed forms: W = m + s·g(Z), E[g] = aΦa + φa − φb + bQb, E[g²] = a²Φa + (Φb − Φa) − (bφb − aφa) + b²Qb, the Edgeworth\n// shift (s·γ/6)·(aφa − bφb) on the mean and −shift·(2·E[Y] + shift) on the variance).\nfn unit_clip(m: vec2<f32>, v: vec2<f32>, skew: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n let decided = v.y == 0.0 && ((m.x - m.y > 0.0 && m.x + m.y < 1.0) || m.x + m.y <= 0.0 || m.x - m.y >= 1.0);\n out.mean = vec2<f32>(clamp(m.x, 0.0, 1.0), m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(select(0.0, 1.0, m.x > 0.0 && m.x < 1.0), select(1.0, 0.0, decided));\n return out;\n }\n let s = b_sqrt(v);\n let a = -m.x / s.x;\n let b = (1.0 - m.x) / s.x;\n if (a < -CLIP_REACH && b > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }\n var pa = 0.0; var da = 0.0;\n if (!(a < -CLIP_REACH)) { pa = pq(a).x; da = phi(a); }\n var pb = 1.0; var qb = 0.0; var db = 0.0;\n if (!(b > CLIP_REACH)) { let p = pq(b); pb = p.x; qb = p.y; db = phi(b); }\n let inside = pb - pa;\n let gamma = clamp(skew.x, -1.0, 1.0);\n let aa = a * da;\n let bb = b * db;\n let eg = a * pa + (da - db) + b * qb;\n let eg2 = a * a * pa + inside - (bb - aa) + b * b * qb;\n let normal_mean = m.x + s.x * eg;\n var mean = normal_mean;\n var shift = 0.0;\n if (gamma != 0.0) { shift = s.x * gamma / 6.0 * (aa - bb); mean = normal_mean + shift; }\n // |aφa − bφb| ≤ 0.48, so |shift| ≤ 0.08·s·|γ| and a wrong γ moves it by at most 0.16·s.\n let d_shift = 0.1 * s.x * min(2.0, skew.y) + 0.1 * abs(gamma) * s.y + E * abs(shift);\n // First-order propagation of the inputs' bounds: with ρ = E[g²] − E[g]², ∂E[g]/∂a = Φa, ∂E[g]/∂b = Qb, ∂E[g²]/∂a = 2aΦa,\n // ∂E[g²]/∂b = 2bQb, so ρ_a = 2Φa(a − E[g]), ρ_b = 2Qb(b − E[g]); ∂E/∂m = inside, ∂E/∂s = φa − φb, ∂Var/∂m = −s(ρ_a + ρ_b),\n // ∂Var/∂s = s(2ρ − aρ_a − bρ_b); the slope's derivatives are (φa − φb)/s and (aφa − bφb)/s.\n let rho = eg2 - eg * eg;\n let rho_a = 2.0 * pa * (a - eg);\n let rho_b = 2.0 * qb * (b - eg);\n let d_mean = m.y * inside + abs(da - db) * s.y + 2.0 * CLIP_MEAN_U * E * s.x + d_shift;\n // The normal part X = v·ρ and its bound; the reference's variance is max(0, X − shift·(2·E[Y] + shift)), so it lies in\n // [0, X_cpu] when the shift term is non-negative on both sides (shift > Δshift), which caps the bound by max(Var, X + ΔX).\n let normal_variance = v.x * rho;\n let d_normal = s.x * abs(rho_a + rho_b) * m.y + s.x * abs(2.0 * rho - a * rho_a - b * rho_b) * s.y + 2.0 * CLIP_VARIANCE_U * E * v.x;\n let variance = max(0.0, normal_variance - shift * (2.0 * normal_mean + shift));\n var d_var = d_normal + 2.0 * (d_shift * (abs(normal_mean) + abs(shift)) + abs(shift) * d_mean);\n if (shift > d_shift) { d_var = min(d_var, max(variance, max(0.0, normal_variance) + d_normal)); }\n out.mean = vec2<f32>(mean, d_mean);\n out.variance = vec2<f32>(variance, d_var);\n out.slope = vec2<f32>(inside, min(1.0, (abs(da - db) * m.y + abs(aa - bb) * s.y) / s.x + 2.0 * CLIP_SLOPE_U * E));\n return out;\n}\n`;\n\n/** The Gauss–Legendre Reinhard moments read the aux table, so they live in the pixel kernel. */\nconst REINHARD_WGSL = /* wgsl */ `\n// Positive Reinhard per channel then the clip to [0, 1]: h(x) = x/(1 + x) for x > 0, else 0, by the reference's composite\n// 16-point Gauss–Legendre rule in standardized units (the Speed forms), with the bounds of the quadrature (REINHARD_*_U)\n// and of its inputs (|h′| ≤ 1, |h″| ≤ 2).\nfn reinhard_moments(m: vec2<f32>, v: vec2<f32>) -> Moments {\n var out: Moments;\n if (!(v.x > 0.0)) {\n if (m.x > 0.0) {\n let h = m.x / (1.0 + m.x);\n out.mean = vec2<f32>(h, m.y + 4.0 * E * h); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(1.0 / ((1.0 + m.x) * (1.0 + m.x)), min(1.0, 2.0 * m.y + 6.0 * E));\n return out;\n }\n out.mean = vec2<f32>(0.0, m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0);\n return out;\n }\n let s = b_sqrt(v);\n let zlo = max(-m.x / s.x, -CLIP_REACH);\n if (!(CLIP_REACH > zlo)) { out.mean = vec2<f32>(0.0, m.y + s.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0); return out; }\n let hm = select(0.0, m.x / (1.0 + m.x), m.x > 0.0);\n var e1 = 0.0; var e2 = 0.0; var ed = 0.0;\n let zwidth = (CLIP_REACH - zlo) / 4.0;\n for (var p = 0u; p < 4u; p = p + 1u) {\n let zc = zlo + (f32(p) + 0.5) * zwidth;\n let zhalf = zwidth / 2.0;\n for (var k = 0u; k < 16u; k = k + 1u) {\n let z = zc + zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre}u + k];\n let x = m.x + s.x * z;\n let weight = zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre + 16}u + k] * phi(z);\n let h = x / (1.0 + x);\n e1 = e1 + weight * h;\n e2 = e2 + weight * (h - hm) * (h - hm);\n ed = ed + weight / ((1.0 + x) * (1.0 + x));\n }\n }\n let shifted = e1 - hm;\n let variance = max(0.0, e2 - shifted * shifted);\n out.mean = vec2<f32>(e1, m.y + s.y + REINHARD_MEAN_U * E * abs(e1));\n out.variance = vec2<f32>(variance, 2.0 * s.x * m.y + 2.0 * v.y + REINHARD_VARIANCE_U * E * variance);\n out.slope = vec2<f32>(ed, min(1.0, 2.0 * m.y + 2.0 * s.y + REINHARD_SLOPE_U * E));\n return out;\n}\n`;\n\n/**\n * Pass 1: the material of every lattice point of every group (the module's `material_at`, its uncertainty in bit 31; the output\n * is `lattice_out`, since a material module may declare its own `lattice`, as #491's composite module does). The group\n * records hold each group's lattice window (lx0, ly0, lw, lh), its first index in the lattice buffer and the point constants\n * c_u = 0.5 + f_x − W/2 and c_v = 0.5 + f_y − H/2 (exact in float32, checked by the host).\n */\nexport const DISPLAY_VIEW_QUALITY_LATTICE_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}\n@group(0) @binding(1) var<storage, read> groups: array<u32>;\n@group(0) @binding(2) var<storage, read_write> lattice_out: array<u32>;\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.lattice_total) { return; }\n var g = 0u;\n for (var k = 1u; k < params.groups; k = k + 1u) { if (i >= groups[k * GROUP + 4u]) { g = k; } }\n let base = g * GROUP;\n let lx0 = bitcast<i32>(groups[base]);\n let ly0 = bitcast<i32>(groups[base + 1u]);\n let lw = groups[base + 2u];\n let local = i - groups[base + 4u];\n let q = ly0 + i32(local / lw);\n let p = lx0 + i32(local % lw);\n let u = (f32(p) + bitcast<f32>(groups[base + 6u])) * params.scale_u;\n let v = (f32(q) + bitcast<f32>(groups[base + 7u])) * params.scale_v;\n material_uncertain = false;\n let m = material_at(vec2<f32>(u, v));\n lattice_out[i] = m | select(0u, 0x80000000u, material_uncertain);\n}\n`;\n\n/**\n * The end of pass 2, shared by both site passes: from the site's electrons e and their absolute bound de, the readout moments\n * (mean k·e, variance k²e + c₀, third cumulant k³e), the clip at the white level and their bounds, written to site i.\n */\nexport const DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL = /* wgsl */ `\nfn write_site(i: u32, c: u32, e: f32, de: f32, flag: u32) {\n let kc = k_of(c);\n let mean = kc * e;\n let variance = kc * kc * e + c0_of(c);\n let third = kc * kc * kc * e;\n var m = vec2<f32>(mean, kc * de + 2.0 * E * mean);\n var v = vec2<f32>(variance, kc * kc * de + 4.0 * E * variance);\n let gap = 1.0 - mean;\n // #533: bits 1 (certainly clipped) and 2 (undecided) of the flag word.\n let state = clip_state(m.x, m.y, v.x, v.y);\n let bits = flag | select(0u, 2u, state == 1u) | select(0u, 4u, state == 2u);\n if (!(gap > 0.0 && gap * gap > params.reach2 * variance)) {\n let clipped = upper_clip(m, v, 1.0);\n m = clipped.mean; v = clipped.variance;\n }\n let o = i * SITE;\n sites[o] = m.x; sites[o + 1u] = v.x; sites[o + 2u] = third;\n sites[o + 3u] = m.y; sites[o + 4u] = v.y; sites[o + 5u] = kc * kc * kc * de + 5.0 * E * third;\n sites[o + 6u] = bitcast<f32>(bits); sites[o + 7u] = e;\n}\n`;\n\n/**\n * The bindings of the site passes (pass 2); the blocked pass reads no tap records, so it declares no binding 2 (an automatic\n * layout leaves out a binding the entry point does not use, and the host binds it only for the tap-list pass).\n */\nconst siteBindingsWgsl = (tapRecords: boolean): string => /* wgsl */ `\n@group(0) @binding(1) var<storage, read> groups: array<u32>;${tapRecords ? `\n@group(0) @binding(2) var<storage, read> taps: array<u32>;` : \"\"}\n@group(0) @binding(3) var<storage, read> table: array<f32>;\n@group(0) @binding(4) var<storage, read> lattice: array<u32>;\n@group(0) @binding(5) var<storage, read_write> sites: array<f32>;\n@group(0) @binding(6) var<storage, read> spread: array<f32>;\n${EXACT_SUM_WGSL}\nalias Nodes = array<f32, ${DISPLAY_VIEW_QUALITY_MAXIMUM_NODES}>;\nfn add_tap(p: ptr<function, Nodes>, c: u32, t: u32, m: u32) {\n let N = params.nodes;\n for (var n = 0u; n < N; n = n + 1u) { (*p)[n] = (*p)[n] + table[(c * params.taps + t) * N + n] * table[(3u * params.taps + m) * N + n]; }\n}\nfn add_nodes(p: ptr<function, Nodes>, q: ptr<function, Nodes>) { for (var n = 0u; n < params.nodes; n = n + 1u) { (*p)[n] = (*p)[n] + (*q)[n]; (*q)[n] = 0.0; } }\nfn node_total(p: ptr<function, Nodes>, dark: f32) -> f32 {\n var anchor = exact_exponent(dark);\n for (var k = 0u; k < params.nodes; k = k + 1u) { if ((*p)[k] > 0.0) { anchor = max(anchor, exact_exponent((*p)[k])); } }\n var sum: ExactSum;\n sum.anchor = anchor;\n for (var k = 0u; k < params.nodes; k = k + 1u) { exact_add(&sum, (*p)[k]); }\n exact_add(&sum, dark);\n return exact_round(&sum);\n}\n`;\n\n/**\n * Pass 2: the expected electrons, readout moments and white clip of every native site of the band's window, with their bounds.\n * 2.21.0 (signals integrated from the material spectrum per site): the site's electrons are Σ_t Σ_n C_c(t, n)·L_{m_t}(λ_n) +\n * dark_c, from the channel's per-node kernel values and the spectrum of the material each tap reads (`table`: C at\n * (c·T + t)·N + n, the spectra from 3·T·N), accumulated per node over the taps, then the node partials and the dark electrons\n * summed in integer arithmetic and rounded once (EXACT_SUM_WGSL). Every term is non-negative and carries two host roundings (E/2\n * each), its product (E) and at most T − 1 additions, then the final rounding (E), and the dark electrons their host rounding\n * (E/2), so the sum carries at most (T + 2.5)·E, inside (T + 3)·E\n * (`params.site_gamma`),\n * plus `params.site_tiny`, the host's absolute bound on what a device that flushes below the float32 normal range can drop. An uncertain lattice point adds the tap's value spread\n * over the materials (`spread`, host float64) to the site's uncertainty, which enters its bounds, or flags the site when it\n * exceeds UNCERTAINTY_LIMIT of the electrons. One dispatch covers the `params.count` sites from `params.site_first` (#492: the\n * host splits a band's sites over several dispatches so that no dispatch gathers more than its tap budget).\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}${siteBindingsWgsl(true)}${DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n var uncertainty = 0.0;\n let T = params.taps;\n var partial: Nodes;\n for (var t = 0u; t < T; t = t + 1u) {\n let tb = t * TAP;\n let g = taps[tb] * GROUP;\n let index = groups[g + 4u] + (jj + taps[tb + 2u]) * groups[g + 2u] + ii + taps[tb + 1u];\n let raw = lattice[index];\n if ((raw >> 31u) != 0u) { uncertainty = uncertainty + spread[c * T + t]; }\n add_tap(&partial, c, t, raw & 0x7fffffffu);\n }\n let e = node_total(&partial, dark_of(c));\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty + params.site_tiny;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/**\n * Pass 2 with the defocus disc (#492): the same site electrons, summed in blocks.\n *\n * - **Spectral table.** The pass reads the tap-list pass's table (2.21.0): the channel's per-node kernel values C_c(t, n) and each\n * material's spectrum, so the signal of each tap is integrated from its material's spectrum when the site is read.\n * - **Blocked sum.** Every group's taps form a dense cw × ch rectangle (the combined kernel's window, row-major from the group's\n * first tap, `groups[g + 5]`), so a site sums each row of a group into a row partial per node, the rows into a group partial\n * and the groups into the total, then the nodes in integer arithmetic. No tap records are read: the tap's lattice offset\n * within its group is its (dx, dy).\n *\n * Every term is non-negative and carries two host roundings (u each, u = E/2: the kernel value and the spectrum), one product\n * (E) and at most cw − 1 + ch − 1 + G − 1 inexact additions, then the integer sum of the node partials and the dark electrons,\n * rounded once (E), and the dark electrons' host rounding (E/2), so the electrons carry at most (cw_max + ch_max + G + 0.5)·E, inside (cw_max + ch_max + G + 3)·E (`params.site_gamma`;\n * recursive summation of the up to 4·(2·96 + 2)² = 150 544 taps of a combined radius of 96 would carry (T + 3)·E ≈ 1.8 %\n * instead). The uncertainty, summed the same way, is scaled by 1 + site_gamma, and `params.site_tiny` (host float64, rounded up)\n * bounds what a device that flushes results below the float32 normal range can drop (the tap-list pass adds it too).\n */\nexport const DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}${siteBindingsWgsl(false)}\n${DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL}\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (local >= params.count) { return; }\n let i = params.site_first + local;\n let ii = i % params.site_w;\n let jj = i / params.site_w;\n let x = params.site_x0 + i32(ii);\n let y = params.site_y0 + i32(jj);\n let c = channel_of(x, y);\n let T = params.taps;\n var total: Nodes;\n var group_sum: Nodes;\n var row_sum: Nodes;\n var uncertainty = 0.0;\n for (var k = 0u; k < params.groups; k = k + 1u) {\n let g = k * GROUP;\n let lw = groups[g + 2u];\n let cw = lw - params.site_w + 1u;\n let ch = groups[g + 3u] - params.site_h + 1u;\n let lattice0 = groups[g + 4u] + jj * lw + ii;\n let tap0 = groups[g + 5u];\n var group_uncertainty = 0.0;\n for (var dy = 0u; dy < ch; dy = dy + 1u) {\n let row = lattice0 + dy * lw;\n let row_tap = tap0 + dy * cw;\n var row_uncertainty = 0.0;\n for (var dx = 0u; dx < cw; dx = dx + 1u) {\n let raw = lattice[row + dx];\n let t = row_tap + dx;\n if ((raw >> 31u) != 0u) { row_uncertainty = row_uncertainty + spread[c * T + t]; }\n add_tap(&row_sum, c, t, raw & 0x7fffffffu);\n }\n add_nodes(&group_sum, &row_sum);\n group_uncertainty = group_uncertainty + row_uncertainty;\n }\n add_nodes(&total, &group_sum);\n uncertainty = uncertainty + group_uncertainty;\n }\n let e = node_total(&total, dark_of(c));\n let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);\n let de = params.site_gamma * e + uncertainty * (1.0 + params.site_gamma) + params.site_tiny;\n write_site(i, c, e, de, flag);\n}\n`;\n\n/** Pass 3: the display pixels (see the module comment). */\n/*\n * 2.22.0: in the luminance branch the nested loops read the colour matrix rows from one local array (`mm`) instead of calling\n * `m_of` (a `switch`): SwiftShader's compiler ended the GPU process on the `switch` inside those loops (after about 44 s). The\n * values are the same (docs/WEBGPU.md, \"Coupled rendering steps\").\n */\nexport const DISPLAY_VIEW_QUALITY_PIXELS_BODY_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}\nconst CAND_BASE: u32 = ${DISPLAY_VIEW_QUALITY_AUX.candidates}u;\nconst CAND_STRIDE: u32 = ${CANDIDATE_PHASE_STRIDE}u;\nconst FLAG_UNCERTAIN: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.materialUncertain}u;\nconst FLAG_OUTSIDE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.siteOutsideWindow}u;\nconst FLAG_CHOICE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.ahdChoice}u;\nconst FLAG_HIGHLIGHT: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.highlightUndecided}u;\nconst CHOICE_SIGMA: f32 = ${literal(DISPLAY_VIEW_QUALITY_CHOICE_SIGMA)};\n@group(0) @binding(1) var<storage, read> aux: array<f32>;\n@group(0) @binding(2) var<storage, read> geometry: array<f32>;\n@group(0) @binding(3) var<storage, read> sites: array<f32>;\n@group(0) @binding(4) var<storage, read_write> out: array<f32>;\n@group(0) @binding(5) var<storage, read_write> flags: array<u32>;\n// #593, #601: the coupled rendering steps' constants, after the aux table's own entries (one zero word without them).\nfn cp_k(i: u32) -> f32 { return aux[${DISPLAY_VIEW_QUALITY_AUX.size}u + i]; }\n\nvar<private> pixel_flags: u32;\n${REINHARD_WGSL}\nfn reflect_x(x: i32) -> i32 { if (x < 0) { return -x; } if (x >= i32(params.native_w)) { return 2 * i32(params.native_w) - 2 - x; } return x; }\nfn reflect_y(y: i32) -> i32 { if (y < 0) { return -y; } if (y >= i32(params.native_h)) { return 2 * i32(params.native_h) - 2 - y; } return y; }\n// The site buffer offset of native site (x, y) (reflected at the raster's borders), or a flag when it lies outside the window.\nfn site_at(x: i32, y: i32) -> u32 {\n let rx = reflect_x(x) - params.site_x0;\n let ry = reflect_y(y) - params.site_y0;\n if (rx < 0 || ry < 0 || rx >= i32(params.site_w) || ry >= i32(params.site_h)) { pixel_flags = pixel_flags | FLAG_OUTSIDE; return 0u; }\n let o = (u32(ry) * params.site_w + u32(rx)) * SITE;\n pixel_flags = pixel_flags | (bitcast<u32>(sites[o + 6u]) & 1u);\n return o;\n}\n// #533: the clip state bits (1 certainly clipped, 2 undecided) of the site at offset o.\nfn clip_bits(o: u32) -> u32 { return (bitcast<u32>(sites[o + 6u]) >> 1u) & 3u; }\n@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})\nfn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {\n let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;\n if (i >= params.count) { return; }\n pixel_flags = 0u;\n let rx = i % params.region_w;\n let ry = params.row0 + i / params.region_w;\n let col = rx * STRIDE;\n let row = (params.columns + ry) * STRIDE;\n let col_first = bitcast<i32>(geometry[col]);\n let nx = bitcast<u32>(geometry[col + 1u]);\n let row_first = bitcast<i32>(geometry[row]);\n let ny = bitcast<u32>(geometry[row + 1u]);\n let margin = i32(params.margin);\n let reinhard = params.tone == 1u;\n let count = params.candidates;\n // #593: whether the last candidate took the coupled slopes, and those slopes (for AHD's choice variance).\n var cp_used = false;\n var cp_a: array<vec2<f32>, 9>;\n let share = select(1.0, 0.5, count == 2u);\n // B_c of the footprint from the host's axis sums (each over its scale squared): red, green, blue.\n let xb_even = geometry[col + 8u]; let xb_odd = geometry[col + 9u];\n let xpp_even = geometry[col + 10u]; let xpp_odd = geometry[col + 11u];\n let xqq_even = geometry[col + 12u]; let xqq_odd = geometry[col + 13u];\n let xpq_even = geometry[col + 14u]; let xpq_odd = geometry[col + 15u];\n let yb_even = geometry[row + 8u]; let yb_odd = geometry[row + 9u];\n let ypp_even = geometry[row + 10u]; let ypp_odd = geometry[row + 11u];\n let yqq_even = geometry[row + 12u]; let yqq_odd = geometry[row + 13u];\n let ypq_even = geometry[row + 14u]; let ypq_odd = geometry[row + 15u];\n let b0 = xb_even * yb_even;\n let b1 = (xpp_odd * ypp_even + xqq_odd * ypp_even + xpp_odd * yqq_even + 2.0 * xpq_odd * ypp_even + 2.0 * xpp_odd * ypq_even + 2.0 * xpq_odd * ypq_even) +\n (xpp_even * ypp_odd + xqq_even * ypp_odd + xpp_even * yqq_odd + 2.0 * xpq_even * ypp_odd + 2.0 * xpp_even * ypq_odd + 2.0 * xpq_even * ypq_odd);\n let b2 = xb_odd * yb_odd;\n var s = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var cov = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var res = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n // #457: Σ_j a_j·½|h(H) − h(V)| per component, how far the reference's choice can move the value from the candidates' average.\n var choice = array<f32, 3>(0.0, 0.0, 0.0);\n // #533: the saturated real pixels (bit j·nx + i) and whether a real pixel is undecided.\n var mask0 = 0u; var mask1 = 0u; var undecided = false;\n for (var j = 0u; j < ny; j = j + 1u) {\n let ay = geometry[row + 2u + j];\n for (var ii = 0u; ii < nx; ii = ii + 1u) {\n // The overlap weight a_j: two host roundings (the overlaps over the scale) and the product.\n let aj = vec2<f32>(geometry[col + 2u + ii] * ay, 3.0 * E * abs(geometry[col + 2u + ii] * ay));\n let x = col_first + i32(ii) + margin;\n let y = row_first + i32(j) + margin;\n let phase = u32(y & 1) * 2u + u32(x & 1);\n // Site variance of each channel through the bilinear kernels, over Σw² (for B_c).\n var csv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n var variance = vec2<f32>(0.0, 0.0);\n let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n let w = aux[tb + 4u * t + 2u];\n if (w == 0.0) { continue; }\n let o = site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u]));\n variance = b_add(variance, b_scale(vec2<f32>(sites[o + 1u], sites[o + 4u]), w * w));\n }\n csv[c] = b_mul(variance, b_rounded(aux[${DISPLAY_VIEW_QUALITY_AUX.inverseW2}u + phase * 3u + c]));\n }\n let cb = CAND_BASE + phase * CAND_STRIDE;\n let entries = bitcast<u32>(aux[cb]);\n // #533: the sites the demosaic reads (the bilinear kernels and every candidate's entries), as the reference's sample does.\n var clip_any = 0u;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;\n for (var t = 0u; t < 4u; t = t + 1u) {\n if (aux[tb + 4u * t + 2u] == 0.0) { continue; }\n clip_any = clip_any | clip_bits(site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u])));\n }\n }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n var used = false;\n for (var k = 0u; k < 3u * count; k = k + 1u) { if (aux[eb + 2u + k] != 0.0) { used = true; } }\n if (used) { clip_any = clip_any | clip_bits(site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]))); }\n }\n if ((clip_any & 1u) != 0u) {\n let bit = j * nx + ii;\n if (bit < 32u) { mask0 = mask0 | (1u << bit); } else { mask1 = mask1 | (1u << (bit - 32u)); }\n } else if ((clip_any & 2u) != 0u) { undecided = true; }\n var e_mean = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n var bs: array<vec2<f32>, 3>; var hs: array<vec2<f32>, 3>;\n var hv: array<vec2<f32>, 3>;\n // #457: each candidate's rendered mean, for the reference's choice between them.\n var h_cand = array<vec2<f32>, 6>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));\n for (var d = 0u; d < count; d = d + 1u) {\n // Camera-channel mean and covariance of candidate d from its dense weights over the window.\n var cm: array<vec2<f32>, 3>;\n var C: array<vec2<f32>, 6>;\n // Whether every site a channel reads is exactly at white (a zero bound: the reference's site is exactly 1 too); the\n // dyadic kernel weights then sum exactly on both sides, so the channel's mean carries no error (the host checks the weights).\n var exact = array<bool, 3>(true, true, true);\n for (var c = 0u; c < 3u; c = c + 1u) { cm[c] = vec2<f32>(0.0, 0.0); }\n for (var k = 0u; k < 6u; k = k + 1u) { C[k] = vec2<f32>(0.0, 0.0); }\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let sm = vec2<f32>(sites[o], sites[o + 3u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n let white = sm.y == 0.0 && sm.x == 1.0;\n exact[0] = exact[0] && (white || w0 == 0.0); exact[1] = exact[1] && (white || w1 == 0.0); exact[2] = exact[2] && (white || w2 == 0.0);\n cm[0] = b_add(cm[0], b_scale(sm, w0)); cm[1] = b_add(cm[1], b_scale(sm, w1)); cm[2] = b_add(cm[2], b_scale(sm, w2));\n C[0] = b_add(C[0], b_scale(sv, w0 * w0)); C[1] = b_add(C[1], b_scale(sv, w0 * w1)); C[2] = b_add(C[2], b_scale(sv, w0 * w2));\n C[3] = b_add(C[3], b_scale(sv, w1 * w1)); C[4] = b_add(C[4], b_scale(sv, w1 * w2)); C[5] = b_add(C[5], b_scale(sv, w2 * w2));\n }\n for (var c = 0u; c < 3u; c = c + 1u) { if (exact[c]) { cm[c].y = 0.0; } }\n // #593: reconstruct within the blend's reach integrates the whole chain below; out of its reach it is the unclipped white\n // balance. An undecided reach is recomputed on the host.\n var cp_chain_run = false;\n if (cp_on() && cp_reconstruct()) {\n let reach = cp_reconstruct_reach(vec3<f32>(cm[0].x, cm[1].x, cm[2].x), vec3<f32>(C[0].x, C[3].x, C[5].x));\n if (reach == 2u) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n cp_chain_run = reach >= 1u;\n }\n let cp_unclipped = cp_on() && cp_reconstruct();\n // White balance and the highlight clip at the smallest gain. A channel exactly at white whose gain is the clip level is\n // exactly at the clip level on both sides.\n var bm: array<vec2<f32>, 3>; var bv: array<vec2<f32>, 3>;\n for (var c = 0u; c < 3u; c = c + 1u) {\n let gc = b_rounded(g_of(c));\n var um = b_mul(gc, cm[c]);\n if (exact[c] && cm[c].x == 1.0 && gain_at_clip(c) && !cp_unclipped) { um = vec2<f32>(params.clip_level, 0.0); }\n let uv = b_mul(b_mul(gc, gc), C[select(select(0u, 3u, c == 1u), 5u, c == 2u)]);\n let gap = params.clip_level - um.x;\n if (cp_unclipped || (gap > 0.0 && gap * gap > params.reach2 * uv.x)) { bm[c] = um; bv[c] = uv; bs[c] = gc; }\n else { let clipped = upper_clip(um, uv, params.clip_level); bm[c] = clipped.mean; bv[c] = clipped.variance; bs[c] = b_mul(gc, clipped.slope); }\n }\n // Colour matrix with the full channel covariance, then the rendering of each component.\n var hm: array<vec2<f32>, 3>;\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let r0 = b_rounded(m_of(comp, 0u)); let r1 = b_rounded(m_of(comp, 1u)); let r2 = b_rounded(m_of(comp, 2u));\n let mean = b_add(b_add(b_mul(r0, bm[0]), b_mul(r1, bm[1])), b_mul(r2, bm[2]));\n var variance = b_add(b_add(b_mul(b_mul(r0, r0), bv[0]), b_mul(b_mul(r1, r1), bv[1])), b_mul(b_mul(r2, r2), bv[2]));\n let x01 = b_mul(b_mul(b_mul(r0, r1), b_mul(bs[0], bs[1])), C[1]);\n let x02 = b_mul(b_mul(b_mul(r0, r2), b_mul(bs[0], bs[2])), C[2]);\n let x12 = b_mul(b_mul(b_mul(r1, r2), b_mul(bs[1], bs[2])), C[4]);\n variance = b_add(variance, b_scale(b_add(b_add(x01, x02), x12), 2.0));\n var moments: Moments;\n if (!reinhard && mean.x > 0.0 && mean.x < 1.0 && mean.x * mean.x > params.reach2 * variance.x && (1.0 - mean.x) * (1.0 - mean.x) > params.reach2 * variance.x) {\n moments.mean = mean; moments.variance = variance; moments.slope = vec2<f32>(1.0, 0.0);\n } else if (reinhard) {\n moments = reinhard_moments(mean, variance);\n } else {\n // κ₃ of the component: Σ_sites (Σ_c M_comp,c·Γ_c·w_c,site)³·κ₃(site), over the candidate's dense weights.\n var third = vec2<f32>(0.0, 0.0);\n let k0 = b_mul(r0, bs[0]); let k1 = b_mul(r1, bs[1]); let k2 = b_mul(r2, bs[2]);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;\n let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];\n if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }\n let o = site_at(x + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u]), y + i32(aux[cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 1u]));\n let coefficient = b_add(b_add(b_scale(k0, w0), b_scale(k1, w1)), b_scale(k2, w2));\n let cube = b_mul(b_mul(coefficient, coefficient), coefficient);\n third = b_add(third, b_mul(cube, vec2<f32>(sites[o + 2u], sites[o + 5u])));\n }\n var skew = vec2<f32>(0.0, 0.0);\n if (variance.x > 0.0) { skew = b_div(third, b_mul(variance, b_sqrt(variance))); }\n moments = unit_clip(mean, variance, skew);\n }\n hm[comp] = moments.mean; hv[comp] = moments.variance; hs[comp] = moments.slope;\n }\n // #593, #601: the coupled steps (wgsl-display-view-coupled.ts) replace the moments and give the slopes ∂out/∂c.\n var an: array<vec2<f32>, 9>;\n cp_used = false;\n if (cp_on()) {\n if (!cp_chain_run && cp_luminance()) {\n var mu = vec3<f32>(0.0); var dmu = vec3<f32>(0.0); var dsv = vec3<f32>(0.0);\n var srow = array<vec3<f32>, 3>(vec3<f32>(0.0), vec3<f32>(0.0), vec3<f32>(0.0));\n var cc = array<f32, 9>(C[0].x, C[1].x, C[2].x, C[1].x, C[3].x, C[4].x, C[2].x, C[4].x, C[5].x);\n var mm = array<vec3<f32>, 3>(vec3<f32>(params.m00, params.m01, params.m02), vec3<f32>(params.m10, params.m11, params.m12), vec3<f32>(params.m20, params.m21, params.m22));\n for (var r = 0u; r < 3u; r = r + 1u) {\n mu[r] = mm[r][0] * bm[0].x + mm[r][1] * bm[1].x + mm[r][2] * bm[2].x;\n dmu[r] = abs(mm[r][0]) * bm[0].y + abs(mm[r][1]) * bm[1].y + abs(mm[r][2]) * bm[2].y;\n dsv[r] = mm[r][0] * mm[r][0] * bv[0].y + mm[r][1] * mm[r][1] * bv[1].y + mm[r][2] * mm[r][2] * bv[2].y;\n for (var q = 0u; q < 3u; q = q + 1u) {\n var v = 0.0;\n for (var a = 0u; a < 3u; a = a + 1u) { for (var b = 0u; b < 3u; b = b + 1u) {\n var bab = bv[a].x; if (a != b) { bab = bs[a].x * bs[b].x * cc[a * 3u + b]; }\n v = v + mm[r][a] * mm[q][b] * bab;\n } }\n srow[r][q] = v;\n }\n }\n let t = cp_tone(mu, srow[0], srow[1], srow[2], dmu, dsv);\n if (t.flag) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n if (cp_desaturate() && t.top) { cp_chain_run = true; }\n else {\n let rows = array<vec3<f32>, 3>(t.a0, t.a1, t.a2);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n hm[comp] = vec2<f32>(t.hm[comp], t.bm[comp]); hv[comp] = vec2<f32>(t.hv[comp], t.bv[comp]);\n for (var c = 0u; c < 3u; c = c + 1u) {\n let jmc = rows[comp].x * m_of(0u, c) + rows[comp].y * m_of(1u, c) + rows[comp].z * m_of(2u, c);\n let ajm = abs(rows[comp].x * m_of(0u, c)) + abs(rows[comp].y * m_of(1u, c)) + abs(rows[comp].z * m_of(2u, c));\n let value = jmc * bs[c].x;\n an[comp * 3u + c] = vec2<f32>(value, ajm * bs[c].y + abs(value) * (t.ju + 4.0 * E));\n }\n }\n cp_used = true;\n }\n } else if (!cp_chain_run && cp_desaturate() && !reinhard) {\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let top = hm[comp].x + CLIP_REACH * sqrt(hv[comp].x);\n if (hm[comp].x > 0.0 && top >= 0.999) { cp_chain_run = true; }\n if (hm[comp].x > 0.0 && hm[comp].x - 2.0 * hm[comp].y < 0.999 && abs(top - 0.999) <= 2.0 * (hm[comp].y + CLIP_REACH * min(sqrt(hv[comp].y), hv[comp].y / (2.0 * sqrt(hv[comp].x)))) + 64.0 * E) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n }\n }\n if (cp_chain_run) {\n let t = cp_chain(vec3<f32>(cm[0].x, cm[1].x, cm[2].x), C[0].x, C[1].x, C[2].x, C[3].x, C[4].x, C[5].x, vec3<f32>(cm[0].y, cm[1].y, cm[2].y), vec3<f32>(C[0].y, C[3].y, C[5].y));\n if (t.flag) { pixel_flags = pixel_flags | FLAG_UNCERTAIN; }\n let rows = array<vec3<f32>, 3>(t.a0, t.a1, t.a2);\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n hm[comp] = vec2<f32>(t.hm[comp], t.bm[comp]); hv[comp] = vec2<f32>(t.hv[comp], t.bv[comp]);\n for (var c = 0u; c < 3u; c = c + 1u) { an[comp * 3u + c] = vec2<f32>(rows[comp][c], abs(rows[comp][c]) * t.ju); }\n }\n cp_used = true;\n }\n if (cp_used) { for (var k = 0u; k < 9u; k = k + 1u) { cp_a[k] = an[k]; } }\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n h_cand[d * 3u + comp] = hm[comp];\n e_mean[comp] = b_add(e_mean[comp], b_scale(hm[comp], share));\n }\n // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance aᵀCa for the residual.\n for (var c = 0u; c < 3u; c = c + 1u) {\n if (!cp_used) {\n an[c] = b_mul(b_mul(hs[0], b_rounded(m_of(0u, c))), bs[c]);\n an[3u + c] = b_mul(b_mul(hs[1], b_rounded(m_of(1u, c))), bs[c]);\n an[6u + c] = b_mul(b_mul(hs[2], b_rounded(m_of(2u, c))), bs[c]);\n }\n var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }\n let wgt = b_mul(b_mul(b_scale(aj, share), b_rounded(bc)), csv[c]);\n cov[0] = b_add(cov[0], b_mul(b_mul(wgt, an[c]), an[c])); cov[1] = b_add(cov[1], b_mul(b_mul(wgt, an[c]), an[3u + c])); cov[2] = b_add(cov[2], b_mul(b_mul(wgt, an[c]), an[6u + c]));\n cov[3] = b_add(cov[3], b_mul(b_mul(wgt, an[3u + c]), an[3u + c])); cov[4] = b_add(cov[4], b_mul(b_mul(wgt, an[3u + c]), an[6u + c])); cov[5] = b_add(cov[5], b_mul(b_mul(wgt, an[6u + c]), an[6u + c]));\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n var l = vec2<f32>(0.0, 0.0);\n for (var p = 0u; p < 3u; p = p + 1u) {\n for (var q = 0u; q < 3u; q = q + 1u) {\n var cij = C[0]; if (p + q == 1u) { cij = C[1]; } if ((p == 0u && q == 2u) || (p == 2u && q == 0u)) { cij = C[2]; } if (p == 1u && q == 1u) { cij = C[3]; }\n if ((p == 1u && q == 2u) || (p == 2u && q == 1u)) { cij = C[4]; } if (p == 2u && q == 2u) { cij = C[5]; }\n l = b_add(l, b_mul(b_mul(an[comp * 3u + p], an[comp * 3u + q]), cij));\n }\n }\n let t = vec2<f32>(share * max(0.0, hv[comp].x - l.x), share * (hv[comp].y + l.y));\n res[comp] = b_add(res[comp], b_mul(b_mul(aj, aj), t));\n }\n }\n if (count == 2u) {\n // #457: the reference takes AHD's choice on the noise-free mosaic, one candidate or (a tie) their average, so its value\n // lies within half the candidates' difference plus their bounds of the average: the bound carries that, and the move\n // accumulates in choice, which flags the pixel below when it exceeds CHOICE_SIGMA of the pixel's noise σ.\n for (var comp = 0u; comp < 3u; comp = comp + 1u) {\n let diff = abs(h_cand[comp].x - h_cand[3u + comp].x);\n let both = h_cand[comp].y + h_cand[3u + comp].y;\n choice[comp] = choice[comp] + aj.x * 0.5 * diff;\n e_mean[comp].y = e_mean[comp].y + 0.5 * (diff + both);\n }\n }\n for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, e_mean[comp])); }\n if (count == 2u) {\n // AHD's per-pixel choice: ¼·Var(H − V) at the last candidate's slopes, linear in the sites.\n var x0 = vec2<f32>(0.0, 0.0); var x1 = vec2<f32>(0.0, 0.0); var x2 = vec2<f32>(0.0, 0.0);\n for (var n = 0u; n < entries; n = n + 1u) {\n let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;\n let o = site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]));\n let d0 = b_scale(bs[0], aux[eb + 2u] - aux[eb + 5u]);\n let d1 = b_scale(bs[1], aux[eb + 3u] - aux[eb + 6u]);\n let d2 = b_scale(bs[2], aux[eb + 4u] - aux[eb + 7u]);\n let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);\n var a = b_mul(hs[0], b_add(b_add(b_mul(b_rounded(params.m00), d0), b_mul(b_rounded(params.m01), d1)), b_mul(b_rounded(params.m02), d2)));\n var b = b_mul(hs[1], b_add(b_add(b_mul(b_rounded(params.m10), d0), b_mul(b_rounded(params.m11), d1)), b_mul(b_rounded(params.m12), d2)));\n var c = b_mul(hs[2], b_add(b_add(b_mul(b_rounded(params.m20), d0), b_mul(b_rounded(params.m21), d1)), b_mul(b_rounded(params.m22), d2)));\n if (cp_used) {\n // #593: the coupled slopes ∂out/∂c of the last candidate.\n let e0 = aux[eb + 2u] - aux[eb + 5u]; let e1 = aux[eb + 3u] - aux[eb + 6u]; let e2 = aux[eb + 4u] - aux[eb + 7u];\n a = b_add(b_add(b_scale(cp_a[0], e0), b_scale(cp_a[1], e1)), b_scale(cp_a[2], e2));\n b = b_add(b_add(b_scale(cp_a[3], e0), b_scale(cp_a[4], e1)), b_scale(cp_a[5], e2));\n c = b_add(b_add(b_scale(cp_a[6], e0), b_scale(cp_a[7], e1)), b_scale(cp_a[8], e2));\n }\n x0 = b_add(x0, b_mul(b_mul(a, a), sv)); x1 = b_add(x1, b_mul(b_mul(b, b), sv)); x2 = b_add(x2, b_mul(b_mul(c, c), sv));\n }\n let ajaj = b_mul(aj, aj);\n res[0] = b_add(res[0], b_mul(ajaj, b_scale(x0, 0.25))); res[1] = b_add(res[1], b_mul(ajaj, b_scale(x1, 0.25))); res[2] = b_add(res[2], b_mul(ajaj, b_scale(x2, 0.25)));\n }\n }\n }\n // #457: a pixel whose candidates' choice could move it by more than CHOICE_SIGMA of its own noise σ is recomputed on the host.\n if (choice[0] > CHOICE_SIGMA * sqrt(max(cov[0].x + res[0].x, 0.0)) || choice[1] > CHOICE_SIGMA * sqrt(max(cov[3].x + res[1].x, 0.0)) ||\n choice[2] > CHOICE_SIGMA * sqrt(max(cov[5].x + res[2].x, 0.0))) { pixel_flags = pixel_flags | FLAG_CHOICE; }\n let o = i * ${DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS}u;\n out[o] = s[0].x; out[o + 1u] = s[1].x; out[o + 2u] = s[2].x;\n out[o + 3u] = cov[0].x + res[0].x; out[o + 4u] = cov[3].x + res[1].x; out[o + 5u] = cov[5].x + res[2].x;\n out[o + 6u] = cov[1].x; out[o + 7u] = cov[2].x; out[o + 8u] = cov[4].x;\n out[o + 9u] = max(s[0].y, max(s[1].y, s[2].y));\n out[o + 10u] = max(max(cov[0].y + res[0].y, cov[3].y + res[1].y), max(cov[5].y + res[2].y, max(cov[1].y, max(cov[2].y, cov[4].y))));\n flags[3u * i] = pixel_flags | select(0u, FLAG_HIGHLIGHT, undecided);\n flags[3u * i + 1u] = mask0;\n flags[3u * i + 2u] = mask1;\n}\n`;\n\n/** The pixel pass (`DISPLAY_VIEW_QUALITY_PIXELS_BODY_WGSL`) with the coupled steps' text: the stub by default, the coupled steps on demand (#593). */\nexport function assembleDisplayViewQualityPixelsWgsl(coupledWgsl: string = DISPLAY_VIEW_COUPLED_STUB_WGSL): string {\n return DISPLAY_VIEW_QUALITY_PIXELS_BODY_WGSL + \"\\n\" + coupledWgsl;\n}\nexport const DISPLAY_VIEW_QUALITY_PIXELS_WGSL = /* @__PURE__ */ assembleDisplayViewQualityPixelsWgsl();\n\n/** The lattice pass with a material module appended (`fn material_at(p: vec2<f32>) -> u32`, `var<private> material_uncertain: bool`). */\nexport function assembleDisplayViewQualityLatticeWgsl(materialModuleWgsl: string): string {\n return DISPLAY_VIEW_QUALITY_LATTICE_WGSL + \"\\n\" + materialModuleWgsl + \"\\n\";\n}\n"]}
@@ -1,43 +1,3 @@
1
- /**
2
- * WGSL kernel of the display view's Speed path on the GPU (#478): one invocation per display pixel of the exact
3
- * area mean (docs/DISPLAY_VIEW.md, "Exact area mean") without diffraction, with the bilinear demosaic and chart
4
- * materials. It evaluates `renderDisplayView`'s per-sample loop (facade/display-view.ts) for every real pixel of the
5
- * display pixel's footprint, in the reference's operation order where the order matters, in float32:
6
- *
7
- * 1. the real pixels the footprint covers and their overlap weights a_j = w_j / A (the host computes the 1-D overlaps
8
- * divided by the scale in float64; the kernel multiplies the two axes);
9
- * 2. for every native site the real pixels' demosaic reads (the footprint plus one site of reach), the expected
10
- * electrons: the scene material at the pixel aperture's query offsets (the material module, `material_at`), the
11
- * material's channel signal integrated in the kernel from its spectrum (Σ_λ A_c(λ)·L(λ), `material_signal`), or (#581) the image-plane
12
- * module's channel signal at the point (`scene_value`), summed over the offsets in the reference's order, plus the dark
13
- * electrons;
14
- * 3. the readout moments of each site (mean k·e, variance k²·e + c₀, third cumulant k³·e) and the clip at the white
15
- * level (the clipped-normal moments, in a cancellation-free standardized form);
16
- * 4. per real pixel the bilinear kernels, white balance and highlight clip, the colour matrix, the tone curve
17
- * (identity, or positive Reinhard by the reference's 4 × 16-point Gauss–Legendre rule) and the clip to [0, 1] with
18
- * the first-order skewness term;
19
- * 5. the display pixel's expected value Σ_j a_j·E[L_j], the linear (Stein) covariance Σ_c B_c·σ_c²·a_c·a_cᵀ with the
20
- * closed-form B_c of the footprint, and the independent residual Σ_j a_j²·r_j.
21
- *
22
- * It outputs, per display pixel, the three means, the three variances (linear plus residual), the three covariances
23
- * and two running error terms: the absolute error bound of the mean and of the variance that the clip steps
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.
32
- * The host adds the output quantization term, draws the noise, encodes and rounds (`display-view-speed-gpu.ts`), so
33
- * the noise draw is the CPU's bit for bit.
34
- *
35
- * Precision: float32 with an a-priori bound per step from the WGSL accuracy table (+ − × within one float32 rounding in
36
- * either direction, E = 2^-23; division 2.5 ULP, sqrt 4.5 ULP, exp 3 + 2|x| ULP; no trigonometric built-in is used). Every quotient that is a constant of
37
- * the request (the overlaps over the scale, the axis sums over the squared scale, 1/Σw² of the demosaic kernels) is
38
- * computed on the host in float64 and uploaded. Data-dependent quotients (z = (t − m)/s, the Abramowitz–Stegun t,
39
- * the skewness) carry the 2.5 ULP bound.
40
- */
41
1
  export declare const DISPLAY_VIEW_WORKGROUP_SIZE = 64;
42
2
  /** Largest footprint per axis (real pixels): the site window is (n + 2)² ≤ 64 entries of private storage. */
43
3
  export declare const DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_AXIS = 6;
@@ -102,4 +62,4 @@ export declare const DISPLAY_VIEW_SPEED_KERNEL_WGSL: string;
102
62
  */
103
63
  export declare const DISPLAY_VIEW_MATERIAL_SCENE_VALUE_WGSL = "\nvar<private> scene_m: u32 = 0xffffffffu;\nvar<private> scene_c: u32 = 0u;\nvar<private> scene_s: f32 = 0.0;\nfn scene_value(p: vec2<f32>, c: u32) -> f32 {\n let m = material_at(p);\n if (m != scene_m || c != scene_c) { scene_s = material_signal(m, c); scene_m = m; scene_c = c; }\n return scene_s;\n}\n";
104
64
  /** The full shader: the kernel plus a material module (see `DISPLAY_VIEW_SPEED_KERNEL_WGSL`), and the table adapter unless the module defines `scene_value`. */
105
- export declare function assembleDisplayViewSpeedWgsl(materialModuleWgsl: string): string;
65
+ export declare function assembleDisplayViewSpeedWgsl(materialModuleWgsl: string, coupledWgsl?: string): string;
@@ -39,6 +39,7 @@
39
39
  * computed on the host in float64 and uploaded. Data-dependent quotients (z = (t − m)/s, the Abramowitz–Stegun t,
40
40
  * the skewness) carry the 2.5 ULP bound.
41
41
  */
42
+ import { DISPLAY_VIEW_COUPLED_STUB_WGSL } from "./wgsl-display-view-coupled-stub.js";
42
43
  export const DISPLAY_VIEW_WORKGROUP_SIZE = 64;
43
44
  /** Largest footprint per axis (real pixels): the site window is (n + 2)² ≤ 64 entries of private storage. */
44
45
  export const DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_AXIS = 6;
@@ -102,6 +103,11 @@ const literal = (value) => {
102
103
  * declares how many roundings it adds (`DisplayViewGpuMaterialModule.extraSiteRoundings`). The module may declare bindings
103
104
  * in @group(1) and use the constants E and SLACK declared here.
104
105
  */
106
+ /*
107
+ * 2.22.0: in the luminance branch the nested loops read the colour matrix rows from one local array (`mm`) instead of calling
108
+ * `m_of` (a `switch`): SwiftShader's compiler ended the GPU process on the `switch` inside those loops (after about 44 s). The
109
+ * values are the same (docs/WEBGPU.md, "Coupled rendering steps").
110
+ */
105
111
  export const DISPLAY_VIEW_SPEED_KERNEL_WGSL = /* wgsl */ `
106
112
  const E: f32 = ${E_LITERAL};
107
113
  const SLACK: f32 = 1e-14;
@@ -130,6 +136,8 @@ m22: f32, scale_u: f32, scale_v: f32, pad3: f32,
130
136
  @group(0) @binding(4) var<storage, read_write> out: array<f32>;
131
137
  @group(0) @binding(5) var<storage, read_write> flags: array<u32>;
132
138
 
139
+ fn cp_k(i: u32) -> f32 { return aux[${DISPLAY_VIEW_AUX.size}u + i]; }
140
+
133
141
  var<private> site_mean: array<f32, 64>;
134
142
  var<private> site_var: array<f32, 64>;
135
143
  var<private> site_third: array<f32, 64>;
@@ -243,6 +251,7 @@ ed = ed + weight / ((1.0 + x) * (1.0 + x));
243
251
  let shifted = e1 - hm;
244
252
  return vec3<f32>(e1, max(0.0, e2 - shifted * shifted), ed);
245
253
  }
254
+
246
255
  fn 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; }
247
256
  fn 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; }
248
257
  fn channel_of(x: i32, y: i32) -> u32 {
@@ -362,6 +371,14 @@ let bit = j * nx + ii;
362
371
  if (bit < 32u) { mask0 = mask0 | (1u << bit); } else { mask1 = mask1 | (1u << (bit - 32u)); }
363
372
  } else if (near_white) { undecided = true; }
364
373
 
374
+ var cp_chain_run = false;
375
+ if (cp_on() && cp_reconstruct()) {
376
+ let reach = cp_reconstruct_reach(vec3<f32>(cm[0], cm[1], cm[2]), vec3<f32>(cvv[0], cvv[1], cvv[2]));
377
+ if (reach == 2u) { material_uncertain = true; }
378
+ cp_chain_run = reach >= 1u;
379
+ }
380
+ let cp_unclipped = cp_on() && cp_reconstruct();
381
+
365
382
  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>;
366
383
  for (var c = 0u; c < 3u; c = c + 1u) {
367
384
  let gc = g_of(c);
@@ -370,7 +387,7 @@ let uv = gc * gc * cvv[c];
370
387
  let gap = params.clip_level - um;
371
388
  bbm[c] = gc * CLIP_MEAN_U * cb[c];
372
389
  bbv[c] = gc * gc * CLIP_VARIANCE_U * cb[c] * cb[c];
373
- if (gap > 0.0 && gap * gap > params.reach2 * uv) { bm[c] = um; bv[c] = uv; bs[c] = gc; }
390
+ if (cp_unclipped || (gap > 0.0 && gap * gap > params.reach2 * uv)) { bm[c] = um; bv[c] = uv; bs[c] = gc; }
374
391
  else {
375
392
  let clipped = upper_clip(um, uv, params.clip_level);
376
393
  bm[c] = clipped.x; bv[c] = clipped.y; bs[c] = gc * clipped.z;
@@ -405,14 +422,60 @@ bv_c = bv_c + 2.0 * CLIP_VARIANCE_U * variance;
405
422
  }
406
423
  hbm[comp] = bm_c; hbv[comp] = bv_c;
407
424
  }
425
+
426
+
427
+ var jm = array<vec3<f32>, 3>(vec3<f32>(0.0), vec3<f32>(0.0), vec3<f32>(0.0));
428
+ var ju = 0.0;
429
+ var cp_used = false;
430
+ if (cp_on()) {
431
+ if (!cp_chain_run && cp_luminance()) {
432
+ var mu = vec3<f32>(0.0); var dmu = vec3<f32>(0.0); var dsv = vec3<f32>(0.0);
433
+ var srow = array<vec3<f32>, 3>(vec3<f32>(0.0), vec3<f32>(0.0), vec3<f32>(0.0));
434
+ var mm = array<vec3<f32>, 3>(vec3<f32>(params.m00, params.m01, params.m02), vec3<f32>(params.m10, params.m11, params.m12), vec3<f32>(params.m20, params.m21, params.m22));
435
+ for (var r = 0u; r < 3u; r = r + 1u) {
436
+ mu[r] = mm[r][0] * bm[0] + mm[r][1] * bm[1] + mm[r][2] * bm[2];
437
+ dmu[r] = (abs(mm[r][0]) * bbm[0] + abs(mm[r][1]) * bbm[1] + abs(mm[r][2]) * bbm[2]) * E;
438
+ dsv[r] = (mm[r][0] * mm[r][0] * bbv[0] + mm[r][1] * mm[r][1] * bbv[1] + mm[r][2] * mm[r][2] * bbv[2]) * E;
439
+ for (var q = 0u; q < 3u; q = q + 1u) { srow[r][q] = mm[r][0] * mm[q][0] * bv[0] + mm[r][1] * mm[q][1] * bv[1] + mm[r][2] * mm[q][2] * bv[2]; }
440
+ }
441
+ let t = cp_tone(mu, srow[0], srow[1], srow[2], dmu, dsv);
442
+ if (t.flag) { material_uncertain = true; }
443
+ if (cp_desaturate() && t.top) { cp_chain_run = true; }
444
+ else {
445
+ let rows = array<vec3<f32>, 3>(t.a0, t.a1, t.a2);
446
+ for (var comp = 0u; comp < 3u; comp = comp + 1u) {
447
+ hm[comp] = t.hm[comp]; hv[comp] = t.hv[comp]; hbm[comp] = t.bm[comp] / E; hbv[comp] = t.bv[comp] / E;
448
+ for (var c = 0u; c < 3u; c = c + 1u) { jm[comp][c] = (rows[comp].x * m_of(0u, c) + rows[comp].y * m_of(1u, c) + rows[comp].z * m_of(2u, c)) * bs[c]; }
449
+ }
450
+ ju = t.ju / E; cp_used = true;
451
+ }
452
+ } else if (!cp_chain_run && cp_desaturate() && params.tone != 1u) {
453
+
454
+ for (var comp = 0u; comp < 3u; comp = comp + 1u) {
455
+ let top = hm[comp] + CLIP_REACH * sqrt(hv[comp]);
456
+ if (hm[comp] > 0.0 && top >= 0.999) { cp_chain_run = true; }
457
+ if (hm[comp] > 0.0 && hm[comp] - 2.0 * hbm[comp] * E < 0.999 && abs(top - 0.999) <= 2.0 * (hbm[comp] * E + CLIP_REACH * min(sqrt(hbv[comp] * E), hbv[comp] * E / (2.0 * sqrt(hv[comp])))) + 64.0 * E) { material_uncertain = true; }
458
+ }
459
+ }
460
+ if (cp_chain_run) {
461
+ let dcm = vec3<f32>(CLIP_MEAN_U * cb[0], CLIP_MEAN_U * cb[1], CLIP_MEAN_U * cb[2]) * E;
462
+ let dcv = vec3<f32>(CLIP_VARIANCE_U * cb[0] * cb[0], CLIP_VARIANCE_U * cb[1] * cb[1], CLIP_VARIANCE_U * cb[2] * cb[2]) * E;
463
+ let t = cp_chain(vec3<f32>(cm[0], cm[1], cm[2]), cvv[0], 0.0, 0.0, cvv[1], 0.0, cvv[2], dcm, dcv);
464
+ if (t.flag) { material_uncertain = true; }
465
+ jm[0] = t.a0; jm[1] = t.a1; jm[2] = t.a2;
466
+ for (var comp = 0u; comp < 3u; comp = comp + 1u) { hm[comp] = t.hm[comp]; hv[comp] = t.hv[comp]; hbm[comp] = t.bm[comp] / E; hbv[comp] = t.bv[comp] / E; }
467
+ ju = t.ju / E; cp_used = true;
468
+ }
469
+ }
408
470
  s0 = s0 + aj * hm[0]; s1 = s1 + aj * hm[1]; s2 = s2 + aj * hm[2];
409
471
 
410
472
  var l0 = 0.0; var l1 = 0.0; var l2 = 0.0;
411
473
  var lb0 = 0.0; var lb1 = 0.0; var lb2 = 0.0;
412
474
  for (var c = 0u; c < 3u; c = c + 1u) {
413
- let a0 = hs[0] * m_of(0u, c) * bs[c];
414
- let a1 = hs[1] * m_of(1u, c) * bs[c];
415
- let a2 = hs[2] * m_of(2u, c) * bs[c];
475
+ var a0 = hs[0] * m_of(0u, c) * bs[c];
476
+ var a1 = hs[1] * m_of(1u, c) * bs[c];
477
+ var a2 = hs[2] * m_of(2u, c) * bs[c];
478
+ if (cp_used) { a0 = jm[0][c]; a1 = jm[1][c]; a2 = jm[2][c]; }
416
479
  var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }
417
480
  let wgt = aj * bc * csv[c];
418
481
  c00 = c00 + wgt * a0 * a0; c01 = c01 + wgt * a0 * a1; c02 = c02 + wgt * a0 * a2;
@@ -420,8 +483,8 @@ c11 = c11 + wgt * a1 * a1; c12 = c12 + wgt * a1 * a2; c22 = c22 + wgt * a2 * a2;
420
483
  l0 = l0 + a0 * a0 * cvv[c]; l1 = l1 + a1 * a1 * cvv[c]; l2 = l2 + a2 * a2 * cvv[c];
421
484
 
422
485
 
423
- lb0 = lb0 + (a0 * a0) * bbv[c]; lb1 = lb1 + (a1 * a1) * bbv[c]; lb2 = lb2 + (a2 * a2) * bbv[c];
424
- bound_var = bound_var + aj * bc * (bbv[c] * aux[${DISPLAY_VIEW_AUX.inverseW2}u + phase * 3u + c]) * max(a0 * a0, max(a1 * a1, a2 * a2));
486
+ lb0 = lb0 + (a0 * a0) * (bbv[c] + 2.0 * ju * cvv[c]); lb1 = lb1 + (a1 * a1) * (bbv[c] + 2.0 * ju * cvv[c]); lb2 = lb2 + (a2 * a2) * (bbv[c] + 2.0 * ju * cvv[c]);
487
+ bound_var = bound_var + aj * bc * ((bbv[c] + 2.0 * ju * cvv[c]) * aux[${DISPLAY_VIEW_AUX.inverseW2}u + phase * 3u + c]) * max(a0 * a0, max(a1 * a1, a2 * a2));
425
488
  }
426
489
  r0 = r0 + aj * aj * max(0.0, hv[0] - l0);
427
490
  r1 = r1 + aj * aj * max(0.0, hv[1] - l1);
@@ -455,8 +518,8 @@ return scene_s;
455
518
  }
456
519
  `;
457
520
  /** The full shader: the kernel plus a material module (see `DISPLAY_VIEW_SPEED_KERNEL_WGSL`), and the table adapter unless the module defines `scene_value`. */
458
- export function assembleDisplayViewSpeedWgsl(materialModuleWgsl) {
521
+ export function assembleDisplayViewSpeedWgsl(materialModuleWgsl, coupledWgsl = DISPLAY_VIEW_COUPLED_STUB_WGSL) {
459
522
  const adapter = /\bfn scene_value\(/u.test(materialModuleWgsl) ? "" : DISPLAY_VIEW_MATERIAL_SCENE_VALUE_WGSL;
460
- return DISPLAY_VIEW_SPEED_KERNEL_WGSL + "\n" + materialModuleWgsl + "\n" + adapter;
523
+ return DISPLAY_VIEW_SPEED_KERNEL_WGSL + "\n" + materialModuleWgsl + "\n" + adapter + "\n" + coupledWgsl;
461
524
  }
462
525
  //# sourceMappingURL=wgsl-display-view-speed-kernel.js.map