@photivra/engine 2.12.0 → 2.14.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 (148) hide show
  1. package/CHANGELOG.md +144 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +7 -7
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +3 -0
  6. package/dist/camera/lens-projection.js +2 -1
  7. package/dist/camera/lens-projection.js.map +1 -1
  8. package/dist/capture/dng-import.js +1 -1
  9. package/dist/capture/dng-import.js.map +1 -1
  10. package/dist/capture/export-jpeg.d.ts +1 -1
  11. package/dist/capture/export-jpeg.js +6 -4
  12. package/dist/capture/export-jpeg.js.map +1 -1
  13. package/dist/capture/host-yield-cadence.js +1 -1
  14. package/dist/capture/host-yield-cadence.js.map +1 -1
  15. package/dist/capture/native-environment-optical-stage.js +1 -1
  16. package/dist/capture/native-environment-optical-stage.js.map +1 -1
  17. package/dist/capture/native-raw.js +1 -1
  18. package/dist/capture/native-raw.js.map +1 -1
  19. package/dist/capture/packed-sensor-raw.js +3 -3
  20. package/dist/capture/packed-sensor-raw.js.map +1 -1
  21. package/dist/capture/sensor-raw-producer.js +1 -1
  22. package/dist/capture/sensor-raw-producer.js.map +1 -1
  23. package/dist/color/capture-color.js +3 -3
  24. package/dist/color/capture-color.js.map +1 -1
  25. package/dist/color/chromatic-adaptation.js +2 -2
  26. package/dist/color/chromatic-adaptation.js.map +1 -1
  27. package/dist/color/color-matching-functions.js +1 -1
  28. package/dist/color/color-matching-functions.js.map +1 -1
  29. package/dist/color/colorimetry-math.js +2 -2
  30. package/dist/color/colorimetry-math.js.map +1 -1
  31. package/dist/color/illuminants.js +3 -3
  32. package/dist/color/illuminants.js.map +1 -1
  33. package/dist/color/rgb-color-spaces.js +1 -1
  34. package/dist/color/rgb-color-spaces.js.map +1 -1
  35. package/dist/color/sensor-color-development.js +1 -1
  36. package/dist/color/sensor-color-development.js.map +1 -1
  37. package/dist/core/record-validation.js +3 -3
  38. package/dist/core/record-validation.js.map +1 -1
  39. package/dist/core/version.d.ts +1 -1
  40. package/dist/core/version.js +1 -1
  41. package/dist/core/version.js.map +1 -1
  42. package/dist/equipment/generic-prime-lenses.js +3 -3
  43. package/dist/equipment/generic-prime-lenses.js.map +1 -1
  44. package/dist/equipment/generic-tier-assets.d.ts +23 -1
  45. package/dist/equipment/generic-tier-assets.js +58 -30
  46. package/dist/equipment/generic-tier-assets.js.map +1 -1
  47. package/dist/equipment/generic-tier-presets.d.ts +6 -3
  48. package/dist/equipment/generic-tier-presets.js +26 -13
  49. package/dist/equipment/generic-tier-presets.js.map +1 -1
  50. package/dist/exposure/auto-iso-slowest-shutter.d.ts +120 -0
  51. package/dist/exposure/auto-iso-slowest-shutter.js +217 -0
  52. package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -0
  53. package/dist/exposure/exposure-mode-resolver.d.ts +57 -1
  54. package/dist/exposure/exposure-mode-resolver.js +238 -33
  55. package/dist/exposure/exposure-mode-resolver.js.map +1 -1
  56. package/dist/exposure/flash-ttl.js +1 -1
  57. package/dist/exposure/flash-ttl.js.map +1 -1
  58. package/dist/exposure/flash.js +1 -1
  59. package/dist/exposure/flash.js.map +1 -1
  60. package/dist/facade/chart-material-layers.js +2 -2
  61. package/dist/facade/chart-material-layers.js.map +1 -1
  62. package/dist/facade/chart-radiance-batch.js +1 -1
  63. package/dist/facade/chart-radiance-batch.js.map +1 -1
  64. package/dist/facade/chart-scenes.d.ts +45 -8
  65. package/dist/facade/chart-scenes.js +101 -44
  66. package/dist/facade/chart-scenes.js.map +1 -1
  67. package/dist/facade/color-patches.d.ts +129 -0
  68. package/dist/facade/color-patches.js +209 -0
  69. package/dist/facade/color-patches.js.map +1 -0
  70. package/dist/facade/diffraction-provider.js +12 -13
  71. package/dist/facade/diffraction-provider.js.map +1 -1
  72. package/dist/facade/display-view-gpu.js +1 -1
  73. package/dist/facade/display-view-gpu.js.map +1 -1
  74. package/dist/facade/display-view.d.ts +27 -0
  75. package/dist/facade/display-view.js +234 -65
  76. package/dist/facade/display-view.js.map +1 -1
  77. package/dist/facade/scene-radiance.js +2 -2
  78. package/dist/facade/scene-radiance.js.map +1 -1
  79. package/dist/facade/simulate-capture.d.ts +139 -12
  80. package/dist/facade/simulate-capture.js +209 -36
  81. package/dist/facade/simulate-capture.js.map +1 -1
  82. package/dist/facade/simulate-display-view.js +2 -2
  83. package/dist/facade/simulate-display-view.js.map +1 -1
  84. package/dist/gpu/chart-composite-gpu.js +2 -2
  85. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  86. package/dist/gpu/display-view-composite-material-module.js +1 -1
  87. package/dist/gpu/display-view-composite-material-module.js.map +1 -1
  88. package/dist/gpu/display-view-quality-gpu.d.ts +2 -1
  89. package/dist/gpu/display-view-quality-gpu.js +24 -14
  90. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  91. package/dist/gpu/image-kernel-execution.js +2 -2
  92. package/dist/gpu/image-kernel-execution.js.map +1 -1
  93. package/dist/gpu/temporal-sum-gpu.js +23 -23
  94. package/dist/gpu/webgpu-runtime.js +1 -1
  95. package/dist/gpu/webgpu-runtime.js.map +1 -1
  96. package/dist/gpu/wgsl-chart-composite-kernel.js +318 -318
  97. package/dist/gpu/wgsl-chart-composite-kernel.js.map +1 -1
  98. package/dist/gpu/wgsl-chart-kernel.js +117 -117
  99. package/dist/gpu/wgsl-chart-kernel.js.map +1 -1
  100. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +13 -1
  101. package/dist/gpu/wgsl-display-view-quality-kernel.js +522 -488
  102. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  103. package/dist/gpu/wgsl-display-view-speed-kernel.js +248 -248
  104. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  105. package/dist/gpu/wgsl-kernels.js +203 -203
  106. package/dist/gpu/wgsl-kernels.js.map +1 -1
  107. package/dist/gpu/wgsl-sensor-kernel.js +302 -302
  108. package/dist/gpu/wgsl-sensor-kernel.js.map +1 -1
  109. package/dist/index.d.ts +8 -5
  110. package/dist/index.js +6 -3
  111. package/dist/index.js.map +1 -1
  112. package/dist/motion/temporal-exposure-sum.js +1 -1
  113. package/dist/motion/temporal-exposure-sum.js.map +1 -1
  114. package/dist/optics/diffraction-psf-profile.js +4 -4
  115. package/dist/optics/diffraction-psf-profile.js.map +1 -1
  116. package/dist/optics/front-of-lens-filter.js +1 -1
  117. package/dist/optics/front-of-lens-filter.js.map +1 -1
  118. package/dist/optics/irradiance-convolution.d.ts +10 -0
  119. package/dist/optics/irradiance-convolution.js +49 -14
  120. package/dist/optics/irradiance-convolution.js.map +1 -1
  121. package/dist/output/native-capture-sdr.js +1 -1
  122. package/dist/output/native-capture-sdr.js.map +1 -1
  123. package/dist/output/png-encoder.js +2 -2
  124. package/dist/output/png-encoder.js.map +1 -1
  125. package/dist/output/print-jpeg.js +13 -10
  126. package/dist/output/print-jpeg.js.map +1 -1
  127. package/dist/output/text-metadata.d.ts +22 -2
  128. package/dist/output/text-metadata.js +58 -9
  129. package/dist/output/text-metadata.js.map +1 -1
  130. package/dist/sensor/fixed-pattern-noise.js +1 -1
  131. package/dist/sensor/fixed-pattern-noise.js.map +1 -1
  132. package/dist/sensor/readout-exposure-linkage.js +1 -1
  133. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  134. package/docs/API_REFERENCE.md +49 -1
  135. package/docs/QUICK_START.md +104 -9
  136. package/docs/USAGE.md +241 -2
  137. package/docs/api/core-version.md +1 -1
  138. package/docs/api/equipment-generic-tier-assets.md +41 -1
  139. package/docs/api/equipment-generic-tier-presets.md +11 -3
  140. package/docs/api/exports.json +172 -2
  141. package/docs/api/exposure-auto-iso-slowest-shutter.md +139 -0
  142. package/docs/api/exposure-exposure-mode-resolver.md +68 -0
  143. package/docs/api/facade-chart-scenes.md +36 -5
  144. package/docs/api/facade-color-patches.md +150 -0
  145. package/docs/api/facade-simulate-capture.md +140 -9
  146. package/docs/api/output-text-metadata.md +34 -0
  147. package/llms.txt +1 -0
  148. package/package.json +2 -2
@@ -20,7 +20,8 @@
20
20
  * the footprint, the demosaic candidates (one bilinear, or AHD's two directional linear candidates, each with its full
21
21
  * camera-channel covariance), white balance and the highlight clip, the colour matrix, the tone curve and the clip to
22
22
  * [0, 1] with the skewness term, the candidates' average, the Stein covariance with the closed-form B_c, the independent
23
- * residual, AHD's choice variance ¼·Var(H − V), and the low-signal correction interpolated trilinearly between the grid
23
+ * residual, AHD's choice variance ¼·Var(H − V), the flag for AHD's choice on the noise-free mosaic, which the reference takes and
24
+ * the kernel does not (#457), and the low-signal correction interpolated trilinearly between the grid
24
25
  * states of a table the host uploaded (the states themselves are computed on the host, #447; the kernel records every
25
26
  * grid key it needs in a bitmap and flags a display pixel whose state the table lacks).
26
27
  *
@@ -63,7 +64,7 @@ export const DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES = 49;
63
64
  export const DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS = 8;
64
65
  const CANDIDATE_PHASE_STRIDE = 4 + DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES * DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS;
65
66
  /** Layout of the aux table (float32 words). */
66
- export const DISPLAY_VIEW_QUALITY_AUX = Object.freeze({
67
+ export const DISPLAY_VIEW_QUALITY_AUX = /* @__PURE__ */ Object.freeze({
67
68
  /** Per phase p, channel c, tap t: dx, dy, weight, 0 (the bilinear kernels, 4 taps; unused taps have weight 0). */
68
69
  bilinear: 0,
69
70
  /** Per phase p, channel c: 1 / Σ_t w_t² of the bilinear kernel (host float64). */
@@ -76,7 +77,7 @@ export const DISPLAY_VIEW_QUALITY_AUX = Object.freeze({
76
77
  size: 240 + 4 * CANDIDATE_PHASE_STRIDE
77
78
  });
78
79
  /** Pixel flag bits of the output flag word. */
79
- export const DISPLAY_VIEW_QUALITY_FLAG = Object.freeze({
80
+ export const DISPLAY_VIEW_QUALITY_FLAG = /* @__PURE__ */ Object.freeze({
80
81
  /** A site the pixel read had an uncertain lattice material (recompute on the host). */
81
82
  materialUncertain: 1,
82
83
  /** A site the pixel read lies outside the band's site window (a host geometry error; recompute). */
@@ -87,9 +88,21 @@ export const DISPLAY_VIEW_QUALITY_FLAG = Object.freeze({
87
88
  thresholdTie: 8,
88
89
  /** Informational: the pixel's expected value includes the low-signal correction. */
89
90
  corrected: 16,
91
+ /**
92
+ * #457: the reference takes AHD's choice on the noise-free mosaic (one candidate, or their average on a tie), which the kernel
93
+ * does not compute; it renders the average. The pixel is flagged (recompute) when Σ_j a_j·½|h(H) − h(V)| of some component
94
+ * exceeds `DISPLAY_VIEW_QUALITY_CHOICE_SIGMA` times the pixel's noise standard deviation.
95
+ */
96
+ ahdChoice: 32,
90
97
  /** The bits that require the host's recomputation. */
91
- recompute: 15
98
+ recompute: 47
92
99
  });
100
+ /**
101
+ * #457: the largest move, in units of the display pixel's own noise σ, that AHD's choice between its candidates may make to a pixel
102
+ * the kernel keeps (it renders the candidates' average and its bound carries the move); a larger one is recomputed on the host. A
103
+ * 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.
104
+ */
105
+ export const DISPLAY_VIEW_QUALITY_CHOICE_SIGMA = 2 ** -10;
93
106
  /** Words of the grid-key bitmap: one bit per grid key (AHD_KEY_LEVELS³ keys). */
94
107
  export const DISPLAY_VIEW_QUALITY_BITMAP_WORDS = Math.ceil(AHD_KEY_LEVELS ** 3 / 32);
95
108
  /**
@@ -125,20 +138,20 @@ const SITE: u32 = ${DISPLAY_VIEW_QUALITY_SITE_FLOATS}u;
125
138
  const GROUP: u32 = ${DISPLAY_VIEW_QUALITY_GROUP_WORDS}u;
126
139
  const TAP: u32 = ${DISPLAY_VIEW_QUALITY_TAP_WORDS}u;
127
140
  struct Params {
128
- count: u32, row0: u32, region_w: u32, region_x: u32,
129
- region_y: u32, margin: u32, native_w: u32, native_h: u32,
130
- columns: u32, tone: u32, reach: u32, candidates: u32,
131
- site_x0: i32, site_y0: i32, site_w: u32, site_h: u32,
132
- correction: u32, state_count: u32, taps: u32, groups: u32,
133
- lattice_total: u32, gain_at_clip0: u32, gain_at_clip1: u32, gain_at_clip2: u32,
134
- k0: f32, k1: f32, k2: f32, reach2: f32,
135
- dark0: f32, dark1: f32, dark2: f32, clip_level: f32,
136
- c0_0: f32, c0_1: f32, c0_2: f32, threshold: f32,
137
- g0: f32, g1: f32, g2: f32, scale_u: f32,
138
- m00: f32, m01: f32, m02: f32, m10: f32,
139
- m11: f32, m12: f32, m20: f32, m21: f32,
140
- m22: f32, scale_v: f32, site_gamma: f32, site_first: u32,
141
- site_tiny: f32, pad_f: f32, pad_g: f32, pad_h: f32,
141
+ count: u32, row0: u32, region_w: u32, region_x: u32,
142
+ region_y: u32, margin: u32, native_w: u32, native_h: u32,
143
+ columns: u32, tone: u32, reach: u32, candidates: u32,
144
+ site_x0: i32, site_y0: i32, site_w: u32, site_h: u32,
145
+ correction: u32, state_count: u32, taps: u32, groups: u32,
146
+ lattice_total: u32, gain_at_clip0: u32, gain_at_clip1: u32, gain_at_clip2: u32,
147
+ k0: f32, k1: f32, k2: f32, reach2: f32,
148
+ dark0: f32, dark1: f32, dark2: f32, clip_level: f32,
149
+ c0_0: f32, c0_1: f32, c0_2: f32, threshold: f32,
150
+ g0: f32, g1: f32, g2: f32, scale_u: f32,
151
+ m00: f32, m01: f32, m02: f32, m10: f32,
152
+ m11: f32, m12: f32, m20: f32, m21: f32,
153
+ m22: f32, scale_v: f32, site_gamma: f32, site_first: u32,
154
+ site_tiny: f32, pad_f: f32, pad_g: f32, pad_h: f32,
142
155
  };
143
156
  @group(0) @binding(0) var<uniform> params: Params;
144
157
 
@@ -146,161 +159,161 @@ fn k_of(c: u32) -> f32 { if (c == 0u) { return params.k0; } if (c == 1u) { retur
146
159
  fn dark_of(c: u32) -> f32 { if (c == 0u) { return params.dark0; } if (c == 1u) { return params.dark1; } return params.dark2; }
147
160
  fn c0_of(c: u32) -> f32 { if (c == 0u) { return params.c0_0; } if (c == 1u) { return params.c0_1; } return params.c0_2; }
148
161
  fn g_of(c: u32) -> f32 { if (c == 0u) { return params.g0; } if (c == 1u) { return params.g1; } return params.g2; }
149
- // Whether the channel's white-balance gain is the clip level itself (the smallest gain), exactly, in the reference's float64.
162
+
150
163
  fn 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; }
151
164
  fn m_of(r: u32, c: u32) -> f32 {
152
- let i = r * 3u + c;
153
- switch (i) {
154
- case 0u: { return params.m00; } case 1u: { return params.m01; } case 2u: { return params.m02; }
155
- case 3u: { return params.m10; } case 4u: { return params.m11; } case 5u: { return params.m12; }
156
- case 6u: { return params.m20; } case 7u: { return params.m21; } default: { return params.m22; }
157
- }
165
+ let i = r * 3u + c;
166
+ switch (i) {
167
+ case 0u: { return params.m00; } case 1u: { return params.m01; } case 2u: { return params.m02; }
168
+ case 3u: { return params.m10; } case 4u: { return params.m11; } case 5u: { return params.m12; }
169
+ case 6u: { return params.m20; } case 7u: { return params.m21; } default: { return params.m22; }
170
+ }
158
171
  }
159
172
  fn channel_of(x: i32, y: i32) -> u32 {
160
- if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }
161
- return select(2u, 1u, (x & 1) == 0);
173
+ if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }
174
+ return select(2u, 1u, (x & 1) == 0);
162
175
  }
163
- // A value with its absolute error bound (x: value, y: bound). First-order rules; the host doubles the final bound.
176
+
164
177
  fn 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)); }
165
178
  fn 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)); }
166
179
  fn 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)); }
167
- // Times an exact constant (a kernel weight, an integer, a power of two).
180
+
168
181
  fn b_scale(a: vec2<f32>, k: f32) -> vec2<f32> { let v = a.x * k; return vec2<f32>(v, a.y * abs(k) + E * abs(v)); }
169
- // A host constant rounded once to float32 (a gain, a matrix entry).
182
+
170
183
  fn b_rounded(k: f32) -> vec2<f32> { return vec2<f32>(k, E * abs(k)); }
171
184
  fn b_div(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> { let v = a.x / b.x; return vec2<f32>(v, (a.y + abs(v) * b.y) / abs(b.x) + 2.5 * E * abs(v)); }
172
- // |sqrt(v + d) - sqrt(v)| <= min(d / (2 sqrt v), sqrt d), plus the 4.5 ULP of the built-in.
185
+
173
186
  fn b_sqrt(a: vec2<f32>) -> vec2<f32> {
174
- let s = sqrt(max(a.x, 0.0));
175
- if (s > 0.0) { return vec2<f32>(s, min(a.y / (2.0 * s), sqrt(a.y)) + 4.5 * E * s); }
176
- return vec2<f32>(0.0, sqrt(a.y));
187
+ let s = sqrt(max(a.x, 0.0));
188
+ if (s > 0.0) { return vec2<f32>(s, min(a.y / (2.0 * s), sqrt(a.y)) + 4.5 * E * s); }
189
+ return vec2<f32>(0.0, sqrt(a.y));
177
190
  }
178
- // Standard normal density, as the reference forms it, and Abramowitz & Stegun 26.2.17 as (Φ, 1 − Φ).
191
+
179
192
  fn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }
180
193
  fn pq(z: f32) -> vec2<f32> {
181
- let x = abs(z);
182
- let t = 1.0 / (1.0 + 0.2316419 * x);
183
- let q = phi(x) * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
184
- if (z >= 0.0) { return vec2<f32>(1.0 - q, q); }
185
- return vec2<f32>(q, 1.0 - q);
194
+ let x = abs(z);
195
+ let t = 1.0 / (1.0 + 0.2316419 * x);
196
+ let q = phi(x) * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
197
+ if (z >= 0.0) { return vec2<f32>(1.0 - q, q); }
198
+ return vec2<f32>(q, 1.0 - q);
186
199
  }
187
200
  struct Moments { mean: vec2<f32>, variance: vec2<f32>, slope: vec2<f32> }
188
- // Moments of min(Y, top) for Y ~ N(m, v) with their bounds: Var = v·(Φ − zφ + z²Q − δ²), δ = φ − zQ (the Speed forms).
201
+
189
202
  fn upper_clip(m: vec2<f32>, v: vec2<f32>, top: f32) -> Moments {
190
- var out: Moments;
191
- if (!(v.x > 0.0)) {
192
- // 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.
193
- let decided = v.y == 0.0 && (m.x - m.y >= top || m.x + m.y < top);
194
- 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));
195
- return out;
196
- }
197
- let s = b_sqrt(v);
198
- let z = (top - m.x) / s.x;
199
- if (z > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }
200
- 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; }
201
- let p = pq(z);
202
- let d = phi(z);
203
- let delta = d - z * p.y;
204
- let ratio = p.x - z * d + z * z * p.y - delta * delta;
205
- // First-order propagation of the inputs' bounds: with g = min(Z, z), E[g] = −δ and ρ = Var(g) = ratio, ∂E/∂m = Φ, ∂E/∂s = φ,
206
- // ∂Var/∂m = −s·ρ_z, ∂Var/∂s = s·(2ρ − z·ρ_z) with ρ_z = 2Q·(z + δ), ∂Φ/∂m = −φ/s and ∂Φ/∂s = −zφ/s.
207
- let rho_z = 2.0 * p.y * (z + delta);
208
- out.mean = vec2<f32>(m.x - s.x * delta, p.x * m.y + d * s.y + CLIP_MEAN_U * E * s.x);
209
- 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);
210
- out.slope = vec2<f32>(p.x, min(1.0, (d * m.y + abs(z) * d * s.y) / s.x + CLIP_SLOPE_U * E));
211
- return out;
212
- }
213
- // Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1), with their bounds (the
214
- // 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
215
- // shift (s·γ/6)·(aφa − bφb) on the mean and −shift·(2·E[Y] + shift) on the variance).
203
+ var out: Moments;
204
+ if (!(v.x > 0.0)) {
205
+
206
+ let decided = v.y == 0.0 && (m.x - m.y >= top || m.x + m.y < top);
207
+ 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));
208
+ return out;
209
+ }
210
+ let s = b_sqrt(v);
211
+ let z = (top - m.x) / s.x;
212
+ if (z > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }
213
+ 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; }
214
+ let p = pq(z);
215
+ let d = phi(z);
216
+ let delta = d - z * p.y;
217
+ let ratio = p.x - z * d + z * z * p.y - delta * delta;
218
+
219
+
220
+ let rho_z = 2.0 * p.y * (z + delta);
221
+ out.mean = vec2<f32>(m.x - s.x * delta, p.x * m.y + d * s.y + CLIP_MEAN_U * E * s.x);
222
+ 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);
223
+ out.slope = vec2<f32>(p.x, min(1.0, (d * m.y + abs(z) * d * s.y) / s.x + CLIP_SLOPE_U * E));
224
+ return out;
225
+ }
226
+
227
+
228
+
216
229
  fn unit_clip(m: vec2<f32>, v: vec2<f32>, skew: vec2<f32>) -> Moments {
217
- var out: Moments;
218
- if (!(v.x > 0.0)) {
219
- 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);
220
- 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));
221
- return out;
222
- }
223
- let s = b_sqrt(v);
224
- let a = -m.x / s.x;
225
- let b = (1.0 - m.x) / s.x;
226
- if (a < -CLIP_REACH && b > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }
227
- var pa = 0.0; var da = 0.0;
228
- if (!(a < -CLIP_REACH)) { pa = pq(a).x; da = phi(a); }
229
- var pb = 1.0; var qb = 0.0; var db = 0.0;
230
- if (!(b > CLIP_REACH)) { let p = pq(b); pb = p.x; qb = p.y; db = phi(b); }
231
- let inside = pb - pa;
232
- let gamma = clamp(skew.x, -1.0, 1.0);
233
- let aa = a * da;
234
- let bb = b * db;
235
- let eg = a * pa + (da - db) + b * qb;
236
- let eg2 = a * a * pa + inside - (bb - aa) + b * b * qb;
237
- let normal_mean = m.x + s.x * eg;
238
- var mean = normal_mean;
239
- var shift = 0.0;
240
- if (gamma != 0.0) { shift = s.x * gamma / 6.0 * (aa - bb); mean = normal_mean + shift; }
241
- // |aφa − bφb| ≤ 0.48, so |shift| ≤ 0.08·s·|γ| and a wrong γ moves it by at most 0.16·s.
242
- let d_shift = 0.1 * s.x * min(2.0, skew.y) + 0.1 * abs(gamma) * s.y + E * abs(shift);
243
- // 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,
244
- // ∂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),
245
- // ∂Var/∂s = s(2ρ − aρ_a − bρ_b); the slope's derivatives are (φa − φb)/s and (aφa − bφb)/s.
246
- let rho = eg2 - eg * eg;
247
- let rho_a = 2.0 * pa * (a - eg);
248
- let rho_b = 2.0 * qb * (b - eg);
249
- let d_mean = m.y * inside + abs(da - db) * s.y + 2.0 * CLIP_MEAN_U * E * s.x + d_shift;
250
- // 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
251
- // [0, X_cpu] when the shift term is non-negative on both sides (shift > Δshift), which caps the bound by max(Var, X + ΔX).
252
- let normal_variance = v.x * rho;
253
- 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;
254
- let variance = max(0.0, normal_variance - shift * (2.0 * normal_mean + shift));
255
- var d_var = d_normal + 2.0 * (d_shift * (abs(normal_mean) + abs(shift)) + abs(shift) * d_mean);
256
- if (shift > d_shift) { d_var = min(d_var, max(variance, max(0.0, normal_variance) + d_normal)); }
257
- out.mean = vec2<f32>(mean, d_mean);
258
- out.variance = vec2<f32>(variance, d_var);
259
- 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));
260
- return out;
230
+ var out: Moments;
231
+ if (!(v.x > 0.0)) {
232
+ 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);
233
+ 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));
234
+ return out;
235
+ }
236
+ let s = b_sqrt(v);
237
+ let a = -m.x / s.x;
238
+ let b = (1.0 - m.x) / s.x;
239
+ if (a < -CLIP_REACH && b > CLIP_REACH) { out.mean = m; out.variance = v; out.slope = vec2<f32>(1.0, 0.0); return out; }
240
+ var pa = 0.0; var da = 0.0;
241
+ if (!(a < -CLIP_REACH)) { pa = pq(a).x; da = phi(a); }
242
+ var pb = 1.0; var qb = 0.0; var db = 0.0;
243
+ if (!(b > CLIP_REACH)) { let p = pq(b); pb = p.x; qb = p.y; db = phi(b); }
244
+ let inside = pb - pa;
245
+ let gamma = clamp(skew.x, -1.0, 1.0);
246
+ let aa = a * da;
247
+ let bb = b * db;
248
+ let eg = a * pa + (da - db) + b * qb;
249
+ let eg2 = a * a * pa + inside - (bb - aa) + b * b * qb;
250
+ let normal_mean = m.x + s.x * eg;
251
+ var mean = normal_mean;
252
+ var shift = 0.0;
253
+ if (gamma != 0.0) { shift = s.x * gamma / 6.0 * (aa - bb); mean = normal_mean + shift; }
254
+
255
+ let d_shift = 0.1 * s.x * min(2.0, skew.y) + 0.1 * abs(gamma) * s.y + E * abs(shift);
256
+
257
+
258
+
259
+ let rho = eg2 - eg * eg;
260
+ let rho_a = 2.0 * pa * (a - eg);
261
+ let rho_b = 2.0 * qb * (b - eg);
262
+ let d_mean = m.y * inside + abs(da - db) * s.y + 2.0 * CLIP_MEAN_U * E * s.x + d_shift;
263
+
264
+
265
+ let normal_variance = v.x * rho;
266
+ 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;
267
+ let variance = max(0.0, normal_variance - shift * (2.0 * normal_mean + shift));
268
+ var d_var = d_normal + 2.0 * (d_shift * (abs(normal_mean) + abs(shift)) + abs(shift) * d_mean);
269
+ if (shift > d_shift) { d_var = min(d_var, max(variance, max(0.0, normal_variance) + d_normal)); }
270
+ out.mean = vec2<f32>(mean, d_mean);
271
+ out.variance = vec2<f32>(variance, d_var);
272
+ 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));
273
+ return out;
261
274
  }
262
275
  `;
263
276
  /** The Gauss–Legendre Reinhard moments read the aux table, so they live in the pixel kernel. */
264
277
  const REINHARD_WGSL = /* wgsl */ `
265
- // 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
266
- // 16-point Gauss–Legendre rule in standardized units (the Speed forms), with the bounds of the quadrature (REINHARD_*_U)
267
- // and of its inputs (|h′| ≤ 1, |h″| ≤ 2).
278
+
279
+
280
+
268
281
  fn reinhard_moments(m: vec2<f32>, v: vec2<f32>) -> Moments {
269
- var out: Moments;
270
- if (!(v.x > 0.0)) {
271
- if (m.x > 0.0) {
272
- let h = m.x / (1.0 + m.x);
273
- 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));
274
- return out;
275
- }
276
- out.mean = vec2<f32>(0.0, m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0);
277
- return out;
278
- }
279
- let s = b_sqrt(v);
280
- let zlo = max(-m.x / s.x, -CLIP_REACH);
281
- 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; }
282
- let hm = select(0.0, m.x / (1.0 + m.x), m.x > 0.0);
283
- var e1 = 0.0; var e2 = 0.0; var ed = 0.0;
284
- let zwidth = (CLIP_REACH - zlo) / 4.0;
285
- for (var p = 0u; p < 4u; p = p + 1u) {
286
- let zc = zlo + (f32(p) + 0.5) * zwidth;
287
- let zhalf = zwidth / 2.0;
288
- for (var k = 0u; k < 16u; k = k + 1u) {
282
+ var out: Moments;
283
+ if (!(v.x > 0.0)) {
284
+ if (m.x > 0.0) {
285
+ let h = m.x / (1.0 + m.x);
286
+ 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));
287
+ return out;
288
+ }
289
+ out.mean = vec2<f32>(0.0, m.y); out.variance = vec2<f32>(0.0, v.y); out.slope = vec2<f32>(0.0, 1.0);
290
+ return out;
291
+ }
292
+ let s = b_sqrt(v);
293
+ let zlo = max(-m.x / s.x, -CLIP_REACH);
294
+ 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; }
295
+ let hm = select(0.0, m.x / (1.0 + m.x), m.x > 0.0);
296
+ var e1 = 0.0; var e2 = 0.0; var ed = 0.0;
297
+ let zwidth = (CLIP_REACH - zlo) / 4.0;
298
+ for (var p = 0u; p < 4u; p = p + 1u) {
299
+ let zc = zlo + (f32(p) + 0.5) * zwidth;
300
+ let zhalf = zwidth / 2.0;
301
+ for (var k = 0u; k < 16u; k = k + 1u) {
289
302
  let z = zc + zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre}u + k];
290
- let x = m.x + s.x * z;
303
+ let x = m.x + s.x * z;
291
304
  let weight = zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre + 16}u + k] * phi(z);
292
- let h = x / (1.0 + x);
293
- e1 = e1 + weight * h;
294
- e2 = e2 + weight * (h - hm) * (h - hm);
295
- ed = ed + weight / ((1.0 + x) * (1.0 + x));
296
- }
297
- }
298
- let shifted = e1 - hm;
299
- let variance = max(0.0, e2 - shifted * shifted);
300
- out.mean = vec2<f32>(e1, m.y + s.y + REINHARD_MEAN_U * E * abs(e1));
301
- out.variance = vec2<f32>(variance, 2.0 * s.x * m.y + 2.0 * v.y + REINHARD_VARIANCE_U * E * variance);
302
- out.slope = vec2<f32>(ed, min(1.0, 2.0 * m.y + 2.0 * s.y + REINHARD_SLOPE_U * E));
303
- return out;
305
+ let h = x / (1.0 + x);
306
+ e1 = e1 + weight * h;
307
+ e2 = e2 + weight * (h - hm) * (h - hm);
308
+ ed = ed + weight / ((1.0 + x) * (1.0 + x));
309
+ }
310
+ }
311
+ let shifted = e1 - hm;
312
+ let variance = max(0.0, e2 - shifted * shifted);
313
+ out.mean = vec2<f32>(e1, m.y + s.y + REINHARD_MEAN_U * E * abs(e1));
314
+ out.variance = vec2<f32>(variance, 2.0 * s.x * m.y + 2.0 * v.y + REINHARD_VARIANCE_U * E * variance);
315
+ out.slope = vec2<f32>(ed, min(1.0, 2.0 * m.y + 2.0 * s.y + REINHARD_SLOPE_U * E));
316
+ return out;
304
317
  }
305
318
  `;
306
319
  /**
@@ -315,21 +328,21 @@ export const DISPLAY_VIEW_QUALITY_LATTICE_WGSL = /* wgsl */ `${COMMON_WGSL}
315
328
  @compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
316
329
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
317
330
  let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
318
- if (i >= params.lattice_total) { return; }
319
- var g = 0u;
320
- for (var k = 1u; k < params.groups; k = k + 1u) { if (i >= groups[k * GROUP + 4u]) { g = k; } }
321
- let base = g * GROUP;
322
- let lx0 = bitcast<i32>(groups[base]);
323
- let ly0 = bitcast<i32>(groups[base + 1u]);
324
- let lw = groups[base + 2u];
325
- let local = i - groups[base + 4u];
326
- let q = ly0 + i32(local / lw);
327
- let p = lx0 + i32(local % lw);
328
- let u = (f32(p) + bitcast<f32>(groups[base + 6u])) * params.scale_u;
329
- let v = (f32(q) + bitcast<f32>(groups[base + 7u])) * params.scale_v;
330
- material_uncertain = false;
331
- let m = material_at(vec2<f32>(u, v));
332
- lattice_out[i] = m | select(0u, 0x80000000u, material_uncertain);
331
+ if (i >= params.lattice_total) { return; }
332
+ var g = 0u;
333
+ for (var k = 1u; k < params.groups; k = k + 1u) { if (i >= groups[k * GROUP + 4u]) { g = k; } }
334
+ let base = g * GROUP;
335
+ let lx0 = bitcast<i32>(groups[base]);
336
+ let ly0 = bitcast<i32>(groups[base + 1u]);
337
+ let lw = groups[base + 2u];
338
+ let local = i - groups[base + 4u];
339
+ let q = ly0 + i32(local / lw);
340
+ let p = lx0 + i32(local % lw);
341
+ let u = (f32(p) + bitcast<f32>(groups[base + 6u])) * params.scale_u;
342
+ let v = (f32(q) + bitcast<f32>(groups[base + 7u])) * params.scale_v;
343
+ material_uncertain = false;
344
+ let m = material_at(vec2<f32>(u, v));
345
+ lattice_out[i] = m | select(0u, 0x80000000u, material_uncertain);
333
346
  }
334
347
  `;
335
348
  /**
@@ -338,21 +351,21 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
338
351
  */
339
352
  const SITE_TAIL_WGSL = /* wgsl */ `
340
353
  fn write_site(i: u32, c: u32, e: f32, de: f32, flag: u32) {
341
- let kc = k_of(c);
342
- let mean = kc * e;
343
- let variance = kc * kc * e + c0_of(c);
344
- let third = kc * kc * kc * e;
345
- var m = vec2<f32>(mean, kc * de + 2.0 * E * mean);
346
- var v = vec2<f32>(variance, kc * kc * de + 4.0 * E * variance);
347
- let gap = 1.0 - mean;
348
- if (!(gap > 0.0 && gap * gap > params.reach2 * variance)) {
349
- let clipped = upper_clip(m, v, 1.0);
350
- m = clipped.mean; v = clipped.variance;
351
- }
352
- let o = i * SITE;
353
- sites[o] = m.x; sites[o + 1u] = v.x; sites[o + 2u] = third;
354
- sites[o + 3u] = m.y; sites[o + 4u] = v.y; sites[o + 5u] = kc * kc * kc * de + 5.0 * E * third;
355
- sites[o + 6u] = bitcast<f32>(flag); sites[o + 7u] = e;
354
+ let kc = k_of(c);
355
+ let mean = kc * e;
356
+ let variance = kc * kc * e + c0_of(c);
357
+ let third = kc * kc * kc * e;
358
+ var m = vec2<f32>(mean, kc * de + 2.0 * E * mean);
359
+ var v = vec2<f32>(variance, kc * kc * de + 4.0 * E * variance);
360
+ let gap = 1.0 - mean;
361
+ if (!(gap > 0.0 && gap * gap > params.reach2 * variance)) {
362
+ let clipped = upper_clip(m, v, 1.0);
363
+ m = clipped.mean; v = clipped.variance;
364
+ }
365
+ let o = i * SITE;
366
+ sites[o] = m.x; sites[o + 1u] = v.x; sites[o + 2u] = third;
367
+ sites[o + 3u] = m.y; sites[o + 4u] = v.y; sites[o + 5u] = kc * kc * kc * de + 5.0 * E * third;
368
+ sites[o + 6u] = bitcast<f32>(flag); sites[o + 7u] = e;
356
369
  }
357
370
  `;
358
371
  /**
@@ -379,28 +392,28 @@ export const DISPLAY_VIEW_QUALITY_SITES_WGSL = /* wgsl */ `${COMMON_WGSL}${siteB
379
392
  @compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
380
393
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
381
394
  let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
382
- if (local >= params.count) { return; }
383
- let i = params.site_first + local;
384
- let ii = i % params.site_w;
385
- let jj = i / params.site_w;
386
- let x = params.site_x0 + i32(ii);
387
- let y = params.site_y0 + i32(jj);
388
- let c = channel_of(x, y);
389
- var sum = 0.0;
390
- var uncertainty = 0.0;
391
- let T = params.taps;
392
- for (var t = 0u; t < T; t = t + 1u) {
393
- let tb = t * TAP;
394
- let g = taps[tb] * GROUP;
395
- let index = groups[g + 4u] + (jj + taps[tb + 2u]) * groups[g + 2u] + ii + taps[tb + 1u];
396
- let raw = lattice[index];
397
- if ((raw >> 31u) != 0u) { uncertainty = uncertainty + spread[c * T + t]; }
398
- sum = sum + table[((raw & 0x7fffffffu) * 3u + c) * T + t];
399
- }
400
- let e = sum + dark_of(c);
401
- let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);
402
- let de = params.site_gamma * e + uncertainty;
403
- write_site(i, c, e, de, flag);
395
+ if (local >= params.count) { return; }
396
+ let i = params.site_first + local;
397
+ let ii = i % params.site_w;
398
+ let jj = i / params.site_w;
399
+ let x = params.site_x0 + i32(ii);
400
+ let y = params.site_y0 + i32(jj);
401
+ let c = channel_of(x, y);
402
+ var sum = 0.0;
403
+ var uncertainty = 0.0;
404
+ let T = params.taps;
405
+ for (var t = 0u; t < T; t = t + 1u) {
406
+ let tb = t * TAP;
407
+ let g = taps[tb] * GROUP;
408
+ let index = groups[g + 4u] + (jj + taps[tb + 2u]) * groups[g + 2u] + ii + taps[tb + 1u];
409
+ let raw = lattice[index];
410
+ if ((raw >> 31u) != 0u) { uncertainty = uncertainty + spread[c * T + t]; }
411
+ sum = sum + table[((raw & 0x7fffffffu) * 3u + c) * T + t];
412
+ }
413
+ let e = sum + dark_of(c);
414
+ let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);
415
+ let de = params.site_gamma * e + uncertainty;
416
+ write_site(i, c, e, de, flag);
404
417
  }
405
418
  `;
406
419
  /**
@@ -427,47 +440,47 @@ ${SITE_TAIL_WGSL}
427
440
  @compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
428
441
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
429
442
  let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
430
- if (local >= params.count) { return; }
431
- let i = params.site_first + local;
432
- let ii = i % params.site_w;
433
- let jj = i / params.site_w;
434
- let x = params.site_x0 + i32(ii);
435
- let y = params.site_y0 + i32(jj);
436
- let c = channel_of(x, y);
437
- let T = params.taps;
438
- var sum = 0.0;
439
- var uncertainty = 0.0;
440
- for (var k = 0u; k < params.groups; k = k + 1u) {
441
- let g = k * GROUP;
442
- let lw = groups[g + 2u];
443
- let cw = lw - params.site_w + 1u;
444
- let ch = groups[g + 3u] - params.site_h + 1u;
445
- let lattice0 = groups[g + 4u] + jj * lw + ii;
446
- let tap0 = groups[g + 5u];
447
- var group_sum = 0.0;
448
- var group_uncertainty = 0.0;
449
- for (var dy = 0u; dy < ch; dy = dy + 1u) {
450
- let row = lattice0 + dy * lw;
451
- let row_tap = tap0 + dy * cw;
452
- var row_sum = 0.0;
453
- var row_uncertainty = 0.0;
454
- for (var dx = 0u; dx < cw; dx = dx + 1u) {
455
- let raw = lattice[row + dx];
456
- let t = row_tap + dx;
457
- let m = 2u * (raw & 0x7fffffffu);
458
- if ((raw >> 31u) != 0u) { row_uncertainty = row_uncertainty + spread[c * T + t]; }
459
- row_sum = row_sum + bitcast<f32>(materials[m + 1u]) * table[(materials[m] * 3u + c) * T + t];
460
- }
461
- group_sum = group_sum + row_sum;
462
- group_uncertainty = group_uncertainty + row_uncertainty;
463
- }
464
- sum = sum + group_sum;
465
- uncertainty = uncertainty + group_uncertainty;
466
- }
467
- let e = sum + dark_of(c);
468
- let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);
469
- let de = params.site_gamma * e + uncertainty * (1.0 + params.site_gamma) + params.site_tiny;
470
- write_site(i, c, e, de, flag);
443
+ if (local >= params.count) { return; }
444
+ let i = params.site_first + local;
445
+ let ii = i % params.site_w;
446
+ let jj = i / params.site_w;
447
+ let x = params.site_x0 + i32(ii);
448
+ let y = params.site_y0 + i32(jj);
449
+ let c = channel_of(x, y);
450
+ let T = params.taps;
451
+ var sum = 0.0;
452
+ var uncertainty = 0.0;
453
+ for (var k = 0u; k < params.groups; k = k + 1u) {
454
+ let g = k * GROUP;
455
+ let lw = groups[g + 2u];
456
+ let cw = lw - params.site_w + 1u;
457
+ let ch = groups[g + 3u] - params.site_h + 1u;
458
+ let lattice0 = groups[g + 4u] + jj * lw + ii;
459
+ let tap0 = groups[g + 5u];
460
+ var group_sum = 0.0;
461
+ var group_uncertainty = 0.0;
462
+ for (var dy = 0u; dy < ch; dy = dy + 1u) {
463
+ let row = lattice0 + dy * lw;
464
+ let row_tap = tap0 + dy * cw;
465
+ var row_sum = 0.0;
466
+ var row_uncertainty = 0.0;
467
+ for (var dx = 0u; dx < cw; dx = dx + 1u) {
468
+ let raw = lattice[row + dx];
469
+ let t = row_tap + dx;
470
+ let m = 2u * (raw & 0x7fffffffu);
471
+ if ((raw >> 31u) != 0u) { row_uncertainty = row_uncertainty + spread[c * T + t]; }
472
+ row_sum = row_sum + bitcast<f32>(materials[m + 1u]) * table[(materials[m] * 3u + c) * T + t];
473
+ }
474
+ group_sum = group_sum + row_sum;
475
+ group_uncertainty = group_uncertainty + row_uncertainty;
476
+ }
477
+ sum = sum + group_sum;
478
+ uncertainty = uncertainty + group_uncertainty;
479
+ }
480
+ let e = sum + dark_of(c);
481
+ let flag = select(0u, 1u, uncertainty > UNCERTAINTY_LIMIT * e);
482
+ let de = params.site_gamma * e + uncertainty * (1.0 + params.site_gamma) + params.site_tiny;
483
+ write_site(i, c, e, de, flag);
471
484
  }
472
485
  `;
473
486
  /** Pass 3: the display pixels (see the module comment). */
@@ -484,6 +497,8 @@ const FLAG_OUTSIDE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.siteOutsideWindow}u;
484
497
  const FLAG_STATE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.stateMissing}u;
485
498
  const FLAG_TIE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.thresholdTie}u;
486
499
  const FLAG_CORRECTED: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.corrected}u;
500
+ const FLAG_CHOICE: u32 = ${DISPLAY_VIEW_QUALITY_FLAG.ahdChoice}u;
501
+ const CHOICE_SIGMA: f32 = ${literal(DISPLAY_VIEW_QUALITY_CHOICE_SIGMA)};
487
502
  @group(0) @binding(1) var<storage, read> aux: array<f32>;
488
503
  @group(0) @binding(2) var<storage, read> geometry: array<f32>;
489
504
  @group(0) @binding(3) var<storage, read> sites: array<f32>;
@@ -497,269 +512,288 @@ var<private> pixel_flags: u32;
497
512
  ${REINHARD_WGSL}
498
513
  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; }
499
514
  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; }
500
- // The site buffer offset of native site (x, y) (reflected at the raster's borders), or a flag when it lies outside the window.
515
+
501
516
  fn site_at(x: i32, y: i32) -> u32 {
502
- let rx = reflect_x(x) - params.site_x0;
503
- let ry = reflect_y(y) - params.site_y0;
504
- if (rx < 0 || ry < 0 || rx >= i32(params.site_w) || ry >= i32(params.site_h)) { pixel_flags = pixel_flags | FLAG_OUTSIDE; return 0u; }
505
- let o = (u32(ry) * params.site_w + u32(rx)) * SITE;
506
- pixel_flags = pixel_flags | (bitcast<u32>(sites[o + 6u]) & 1u);
507
- return o;
508
- }
509
- // The offset of a grid key in the sorted state table, or state_count when absent.
517
+ let rx = reflect_x(x) - params.site_x0;
518
+ let ry = reflect_y(y) - params.site_y0;
519
+ if (rx < 0 || ry < 0 || rx >= i32(params.site_w) || ry >= i32(params.site_h)) { pixel_flags = pixel_flags | FLAG_OUTSIDE; return 0u; }
520
+ let o = (u32(ry) * params.site_w + u32(rx)) * SITE;
521
+ pixel_flags = pixel_flags | (bitcast<u32>(sites[o + 6u]) & 1u);
522
+ return o;
523
+ }
524
+
510
525
  fn find_state(key: i32) -> u32 {
511
- var lo = 0u; var hi = params.state_count;
512
- while (lo < hi) {
513
- let mid = (lo + hi) / 2u;
514
- if (state_keys[mid] < key) { lo = mid + 1u; } else { hi = mid; }
515
- }
516
- if (lo < params.state_count && state_keys[lo] == key) { return lo; }
517
- return params.state_count;
518
- }
519
- // The flat-neighbourhood correction at channel means (m0, m1, m2) with its bound: trilinear between the eight grid states
520
- // around (log2(clamp(m, 2^-12, 1)) + 12)·8; a missing state flags the pixel. The bound of each axis's coordinate times the
521
- // largest state difference along that axis bounds the interpolation's dependence on the coordinates' error.
526
+ var lo = 0u; var hi = params.state_count;
527
+ while (lo < hi) {
528
+ let mid = (lo + hi) / 2u;
529
+ if (state_keys[mid] < key) { lo = mid + 1u; } else { hi = mid; }
530
+ }
531
+ if (lo < params.state_count && state_keys[lo] == key) { return lo; }
532
+ return params.state_count;
533
+ }
534
+
535
+
536
+
522
537
  fn flat_correction(m0: vec2<f32>, m1: vec2<f32>, m2: vec2<f32>, corr: ptr<function, array<vec2<f32>, 3>>) {
523
- var base: array<u32, 3>; var frac: array<f32, 3>; var dg: array<f32, 3>;
524
- for (var i = 0u; i < 3u; i = i + 1u) {
525
- var mi = m0; if (i == 1u) { mi = m1; } if (i == 2u) { mi = m2; }
526
- let clamped = clamp(mi.x, KEY_FLOOR, 1.0);
527
- let g = (log2(clamped) + KEY_OCTAVES) * KEY_STEPS;
528
- let b = min(u32(max(floor(g), 0.0)), KEY_TOP);
529
- base[i] = b; frac[i] = g - f32(b);
530
- // log2: 1/ln 2 per relative input error, 2^-21 absolute or 3 ULP (≤ 36E on [2^-12, 1]), the sum and the product.
531
- var slope = 0.0; if (mi.x > KEY_FLOOR && mi.x < 1.0) { slope = 1.4426950408889634 * mi.y / clamped; }
532
- dg[i] = KEY_STEPS * (slope + 4.76837158203125e-7 + 36.0 * E) + 2.0 * E * (g + 1.0);
533
- }
534
- var values: array<f32, 24>;
535
- var missing = false;
536
- for (var corner = 0u; corner < 8u; corner = corner + 1u) {
537
- let i0 = base[0] + (corner & 1u); let i1 = base[1] + ((corner >> 1u) & 1u); let i2 = base[2] + ((corner >> 2u) & 1u);
538
- let key = i32((i0 * KEY_LEVELS + i1) * KEY_LEVELS + i2);
539
- var weight = 1.0;
540
- for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }
541
- if (weight == 0.0) { for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = 0.0; } continue; }
542
- atomicOr(&bitmap[u32(key) / 32u], 1u << (u32(key) % 32u));
543
- let at = find_state(key);
544
- if (at >= params.state_count) { missing = true; continue; }
545
- for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = state_values[at * 3u + c]; }
546
- }
547
- 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; }
548
- for (var c = 0u; c < 3u; c = c + 1u) {
549
- var sum = 0.0; var mag = 0.0;
550
- for (var corner = 0u; corner < 8u; corner = corner + 1u) {
551
- var weight = 1.0;
552
- for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }
553
- sum = sum + weight * values[corner * 3u + c];
554
- mag = mag + abs(weight * values[corner * 3u + c]);
555
- }
556
- // The largest state difference along each axis (over the four edges), times that axis's coordinate bound.
557
- var slope_term = 0.0;
558
- for (var i = 0u; i < 3u; i = i + 1u) {
559
- var largest = 0.0;
560
- for (var corner = 0u; corner < 8u; corner = corner + 1u) {
561
- if (((corner >> i) & 1u) == 1u) { continue; }
562
- largest = max(largest, abs(values[(corner | (1u << i)) * 3u + c] - values[corner * 3u + c]));
563
- }
564
- slope_term = slope_term + dg[i] * largest;
565
- }
566
- (*corr)[c] = vec2<f32>(sum, slope_term + 12.0 * E * mag);
567
- }
538
+ var base: array<u32, 3>; var frac: array<f32, 3>; var dg: array<f32, 3>;
539
+ for (var i = 0u; i < 3u; i = i + 1u) {
540
+ var mi = m0; if (i == 1u) { mi = m1; } if (i == 2u) { mi = m2; }
541
+ let clamped = clamp(mi.x, KEY_FLOOR, 1.0);
542
+ let g = (log2(clamped) + KEY_OCTAVES) * KEY_STEPS;
543
+ let b = min(u32(max(floor(g), 0.0)), KEY_TOP);
544
+ base[i] = b; frac[i] = g - f32(b);
545
+
546
+ var slope = 0.0; if (mi.x > KEY_FLOOR && mi.x < 1.0) { slope = 1.4426950408889634 * mi.y / clamped; }
547
+ dg[i] = KEY_STEPS * (slope + 4.76837158203125e-7 + 36.0 * E) + 2.0 * E * (g + 1.0);
548
+ }
549
+ var values: array<f32, 24>;
550
+ var missing = false;
551
+ for (var corner = 0u; corner < 8u; corner = corner + 1u) {
552
+ let i0 = base[0] + (corner & 1u); let i1 = base[1] + ((corner >> 1u) & 1u); let i2 = base[2] + ((corner >> 2u) & 1u);
553
+ let key = i32((i0 * KEY_LEVELS + i1) * KEY_LEVELS + i2);
554
+ var weight = 1.0;
555
+ for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }
556
+ if (weight == 0.0) { for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = 0.0; } continue; }
557
+ atomicOr(&bitmap[u32(key) / 32u], 1u << (u32(key) % 32u));
558
+ let at = find_state(key);
559
+ if (at >= params.state_count) { missing = true; continue; }
560
+ for (var c = 0u; c < 3u; c = c + 1u) { values[corner * 3u + c] = state_values[at * 3u + c]; }
561
+ }
562
+ 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; }
563
+ for (var c = 0u; c < 3u; c = c + 1u) {
564
+ var sum = 0.0; var mag = 0.0;
565
+ for (var corner = 0u; corner < 8u; corner = corner + 1u) {
566
+ var weight = 1.0;
567
+ for (var i = 0u; i < 3u; i = i + 1u) { weight = weight * select(1.0 - frac[i], frac[i], ((corner >> i) & 1u) == 1u); }
568
+ sum = sum + weight * values[corner * 3u + c];
569
+ mag = mag + abs(weight * values[corner * 3u + c]);
570
+ }
571
+
572
+ var slope_term = 0.0;
573
+ for (var i = 0u; i < 3u; i = i + 1u) {
574
+ var largest = 0.0;
575
+ for (var corner = 0u; corner < 8u; corner = corner + 1u) {
576
+ if (((corner >> i) & 1u) == 1u) { continue; }
577
+ largest = max(largest, abs(values[(corner | (1u << i)) * 3u + c] - values[corner * 3u + c]));
578
+ }
579
+ slope_term = slope_term + dg[i] * largest;
580
+ }
581
+ (*corr)[c] = vec2<f32>(sum, slope_term + 12.0 * E * mag);
582
+ }
568
583
  }
569
584
  @compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
570
585
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
571
586
  let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
572
- if (i >= params.count) { return; }
573
- pixel_flags = 0u;
574
- let rx = i % params.region_w;
575
- let ry = params.row0 + i / params.region_w;
576
- let col = rx * STRIDE;
577
- let row = (params.columns + ry) * STRIDE;
578
- let col_first = bitcast<i32>(geometry[col]);
579
- let nx = bitcast<u32>(geometry[col + 1u]);
580
- let row_first = bitcast<i32>(geometry[row]);
581
- let ny = bitcast<u32>(geometry[row + 1u]);
582
- let margin = i32(params.margin);
583
- let reinhard = params.tone == 1u;
584
- let count = params.candidates;
585
- let share = select(1.0, 0.5, count == 2u);
586
- // B_c of the footprint from the host's axis sums (each over its scale squared): red, green, blue.
587
- let xb_even = geometry[col + 8u]; let xb_odd = geometry[col + 9u];
588
- let xpp_even = geometry[col + 10u]; let xpp_odd = geometry[col + 11u];
589
- let xqq_even = geometry[col + 12u]; let xqq_odd = geometry[col + 13u];
590
- let xpq_even = geometry[col + 14u]; let xpq_odd = geometry[col + 15u];
591
- let yb_even = geometry[row + 8u]; let yb_odd = geometry[row + 9u];
592
- let ypp_even = geometry[row + 10u]; let ypp_odd = geometry[row + 11u];
593
- let yqq_even = geometry[row + 12u]; let yqq_odd = geometry[row + 13u];
594
- let ypq_even = geometry[row + 14u]; let ypq_odd = geometry[row + 15u];
595
- let b0 = xb_even * yb_even;
596
- 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) +
597
- (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);
598
- let b2 = xb_odd * yb_odd;
599
- var s = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
600
- 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));
601
- var res = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
602
- for (var j = 0u; j < ny; j = j + 1u) {
603
- let ay = geometry[row + 2u + j];
604
- for (var ii = 0u; ii < nx; ii = ii + 1u) {
605
- // The overlap weight a_j: two host roundings (the overlaps over the scale) and the product.
606
- let aj = vec2<f32>(geometry[col + 2u + ii] * ay, 3.0 * E * abs(geometry[col + 2u + ii] * ay));
607
- let x = col_first + i32(ii) + margin;
608
- let y = row_first + i32(j) + margin;
609
- let phase = u32(y & 1) * 2u + u32(x & 1);
610
- // Site variance of each channel through the bilinear kernels, over Σw² (for B_c).
611
- var csv: array<vec2<f32>, 3>;
612
- for (var c = 0u; c < 3u; c = c + 1u) {
613
- var variance = vec2<f32>(0.0, 0.0);
587
+ if (i >= params.count) { return; }
588
+ pixel_flags = 0u;
589
+ let rx = i % params.region_w;
590
+ let ry = params.row0 + i / params.region_w;
591
+ let col = rx * STRIDE;
592
+ let row = (params.columns + ry) * STRIDE;
593
+ let col_first = bitcast<i32>(geometry[col]);
594
+ let nx = bitcast<u32>(geometry[col + 1u]);
595
+ let row_first = bitcast<i32>(geometry[row]);
596
+ let ny = bitcast<u32>(geometry[row + 1u]);
597
+ let margin = i32(params.margin);
598
+ let reinhard = params.tone == 1u;
599
+ let count = params.candidates;
600
+ let share = select(1.0, 0.5, count == 2u);
601
+
602
+ let xb_even = geometry[col + 8u]; let xb_odd = geometry[col + 9u];
603
+ let xpp_even = geometry[col + 10u]; let xpp_odd = geometry[col + 11u];
604
+ let xqq_even = geometry[col + 12u]; let xqq_odd = geometry[col + 13u];
605
+ let xpq_even = geometry[col + 14u]; let xpq_odd = geometry[col + 15u];
606
+ let yb_even = geometry[row + 8u]; let yb_odd = geometry[row + 9u];
607
+ let ypp_even = geometry[row + 10u]; let ypp_odd = geometry[row + 11u];
608
+ let yqq_even = geometry[row + 12u]; let yqq_odd = geometry[row + 13u];
609
+ let ypq_even = geometry[row + 14u]; let ypq_odd = geometry[row + 15u];
610
+ let b0 = xb_even * yb_even;
611
+ 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) +
612
+ (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);
613
+ let b2 = xb_odd * yb_odd;
614
+ var s = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
615
+ 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));
616
+ var res = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
617
+
618
+ var choice = array<f32, 3>(0.0, 0.0, 0.0);
619
+ for (var j = 0u; j < ny; j = j + 1u) {
620
+ let ay = geometry[row + 2u + j];
621
+ for (var ii = 0u; ii < nx; ii = ii + 1u) {
622
+
623
+ let aj = vec2<f32>(geometry[col + 2u + ii] * ay, 3.0 * E * abs(geometry[col + 2u + ii] * ay));
624
+ let x = col_first + i32(ii) + margin;
625
+ let y = row_first + i32(j) + margin;
626
+ let phase = u32(y & 1) * 2u + u32(x & 1);
627
+
628
+ var csv: array<vec2<f32>, 3>;
629
+ for (var c = 0u; c < 3u; c = c + 1u) {
630
+ var variance = vec2<f32>(0.0, 0.0);
614
631
  let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;
615
- for (var t = 0u; t < 4u; t = t + 1u) {
616
- let w = aux[tb + 4u * t + 2u];
617
- if (w == 0.0) { continue; }
618
- let o = site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u]));
619
- variance = b_add(variance, b_scale(vec2<f32>(sites[o + 1u], sites[o + 4u]), w * w));
620
- }
632
+ for (var t = 0u; t < 4u; t = t + 1u) {
633
+ let w = aux[tb + 4u * t + 2u];
634
+ if (w == 0.0) { continue; }
635
+ let o = site_at(x + i32(aux[tb + 4u * t]), y + i32(aux[tb + 4u * t + 1u]));
636
+ variance = b_add(variance, b_scale(vec2<f32>(sites[o + 1u], sites[o + 4u]), w * w));
637
+ }
621
638
  csv[c] = b_mul(variance, b_rounded(aux[${DISPLAY_VIEW_QUALITY_AUX.inverseW2}u + phase * 3u + c]));
622
- }
623
- let cb = CAND_BASE + phase * CAND_STRIDE;
624
- let entries = bitcast<u32>(aux[cb]);
625
- var e_mean = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
626
- var cm_average = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
627
- var pixel_variance = vec2<f32>(0.0, 0.0);
628
- var bs: array<vec2<f32>, 3>; var hs: array<vec2<f32>, 3>;
629
- var hv: array<vec2<f32>, 3>;
630
- for (var d = 0u; d < count; d = d + 1u) {
631
- // Camera-channel mean and covariance of candidate d from its dense weights over the window.
632
- var cm: array<vec2<f32>, 3>;
633
- var C: array<vec2<f32>, 6>;
634
- // Whether every site a channel reads is exactly at white (a zero bound: the reference's site is exactly 1 too); the
635
- // dyadic kernel weights then sum exactly on both sides, so the channel's mean carries no error (the host checks the weights).
636
- var exact = array<bool, 3>(true, true, true);
637
- for (var c = 0u; c < 3u; c = c + 1u) { cm[c] = vec2<f32>(0.0, 0.0); }
638
- for (var k = 0u; k < 6u; k = k + 1u) { C[k] = vec2<f32>(0.0, 0.0); }
639
- for (var n = 0u; n < entries; n = n + 1u) {
639
+ }
640
+ let cb = CAND_BASE + phase * CAND_STRIDE;
641
+ let entries = bitcast<u32>(aux[cb]);
642
+ var e_mean = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
643
+ var cm_average = array<vec2<f32>, 3>(vec2<f32>(0.0), vec2<f32>(0.0), vec2<f32>(0.0));
644
+ var pixel_variance = vec2<f32>(0.0, 0.0);
645
+ var bs: array<vec2<f32>, 3>; var hs: array<vec2<f32>, 3>;
646
+ var hv: array<vec2<f32>, 3>;
647
+
648
+ 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));
649
+ for (var d = 0u; d < count; d = d + 1u) {
650
+
651
+ var cm: array<vec2<f32>, 3>;
652
+ var C: array<vec2<f32>, 6>;
653
+
654
+
655
+ var exact = array<bool, 3>(true, true, true);
656
+ for (var c = 0u; c < 3u; c = c + 1u) { cm[c] = vec2<f32>(0.0, 0.0); }
657
+ for (var k = 0u; k < 6u; k = k + 1u) { C[k] = vec2<f32>(0.0, 0.0); }
658
+ for (var n = 0u; n < entries; n = n + 1u) {
640
659
  let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;
641
- let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];
642
- if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }
660
+ let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];
661
+ if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }
643
662
  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]));
644
- let sm = vec2<f32>(sites[o], sites[o + 3u]);
645
- let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);
646
- let white = sm.y == 0.0 && sm.x == 1.0;
647
- exact[0] = exact[0] && (white || w0 == 0.0); exact[1] = exact[1] && (white || w1 == 0.0); exact[2] = exact[2] && (white || w2 == 0.0);
648
- 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));
649
- 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));
650
- 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));
651
- }
652
- 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)); }
653
- // White balance and the highlight clip at the smallest gain. A channel exactly at white whose gain is the clip level is
654
- // exactly at the clip level on both sides.
655
- var bm: array<vec2<f32>, 3>; var bv: array<vec2<f32>, 3>;
656
- for (var c = 0u; c < 3u; c = c + 1u) {
657
- let gc = b_rounded(g_of(c));
658
- var um = b_mul(gc, cm[c]);
659
- if (exact[c] && cm[c].x == 1.0 && gain_at_clip(c)) { um = vec2<f32>(params.clip_level, 0.0); }
660
- let uv = b_mul(b_mul(gc, gc), C[select(select(0u, 3u, c == 1u), 5u, c == 2u)]);
661
- let gap = params.clip_level - um.x;
662
- if (gap > 0.0 && gap * gap > params.reach2 * uv.x) { bm[c] = um; bv[c] = uv; bs[c] = gc; }
663
- 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); }
664
- }
665
- // Colour matrix with the full channel covariance, then the rendering of each component.
666
- var hm: array<vec2<f32>, 3>;
667
- for (var comp = 0u; comp < 3u; comp = comp + 1u) {
668
- let r0 = b_rounded(m_of(comp, 0u)); let r1 = b_rounded(m_of(comp, 1u)); let r2 = b_rounded(m_of(comp, 2u));
669
- let mean = b_add(b_add(b_mul(r0, bm[0]), b_mul(r1, bm[1])), b_mul(r2, bm[2]));
670
- 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]));
671
- let x01 = b_mul(b_mul(b_mul(r0, r1), b_mul(bs[0], bs[1])), C[1]);
672
- let x02 = b_mul(b_mul(b_mul(r0, r2), b_mul(bs[0], bs[2])), C[2]);
673
- let x12 = b_mul(b_mul(b_mul(r1, r2), b_mul(bs[1], bs[2])), C[4]);
674
- variance = b_add(variance, b_scale(b_add(b_add(x01, x02), x12), 2.0));
675
- var moments: Moments;
676
- 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) {
677
- moments.mean = mean; moments.variance = variance; moments.slope = vec2<f32>(1.0, 0.0);
678
- } else if (reinhard) {
679
- moments = reinhard_moments(mean, variance);
680
- } else {
681
- // κ₃ of the component: Σ_sites (Σ_c M_comp,c·Γ_c·w_c,site)³·κ₃(site), over the candidate's dense weights.
682
- var third = vec2<f32>(0.0, 0.0);
683
- let k0 = b_mul(r0, bs[0]); let k1 = b_mul(r1, bs[1]); let k2 = b_mul(r2, bs[2]);
684
- for (var n = 0u; n < entries; n = n + 1u) {
663
+ let sm = vec2<f32>(sites[o], sites[o + 3u]);
664
+ let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);
665
+ let white = sm.y == 0.0 && sm.x == 1.0;
666
+ exact[0] = exact[0] && (white || w0 == 0.0); exact[1] = exact[1] && (white || w1 == 0.0); exact[2] = exact[2] && (white || w2 == 0.0);
667
+ 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));
668
+ 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));
669
+ 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));
670
+ }
671
+ 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)); }
672
+
673
+
674
+ var bm: array<vec2<f32>, 3>; var bv: array<vec2<f32>, 3>;
675
+ for (var c = 0u; c < 3u; c = c + 1u) {
676
+ let gc = b_rounded(g_of(c));
677
+ var um = b_mul(gc, cm[c]);
678
+ if (exact[c] && cm[c].x == 1.0 && gain_at_clip(c)) { um = vec2<f32>(params.clip_level, 0.0); }
679
+ let uv = b_mul(b_mul(gc, gc), C[select(select(0u, 3u, c == 1u), 5u, c == 2u)]);
680
+ let gap = params.clip_level - um.x;
681
+ if (gap > 0.0 && gap * gap > params.reach2 * uv.x) { bm[c] = um; bv[c] = uv; bs[c] = gc; }
682
+ 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); }
683
+ }
684
+
685
+ var hm: array<vec2<f32>, 3>;
686
+ for (var comp = 0u; comp < 3u; comp = comp + 1u) {
687
+ let r0 = b_rounded(m_of(comp, 0u)); let r1 = b_rounded(m_of(comp, 1u)); let r2 = b_rounded(m_of(comp, 2u));
688
+ let mean = b_add(b_add(b_mul(r0, bm[0]), b_mul(r1, bm[1])), b_mul(r2, bm[2]));
689
+ 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]));
690
+ let x01 = b_mul(b_mul(b_mul(r0, r1), b_mul(bs[0], bs[1])), C[1]);
691
+ let x02 = b_mul(b_mul(b_mul(r0, r2), b_mul(bs[0], bs[2])), C[2]);
692
+ let x12 = b_mul(b_mul(b_mul(r1, r2), b_mul(bs[1], bs[2])), C[4]);
693
+ variance = b_add(variance, b_scale(b_add(b_add(x01, x02), x12), 2.0));
694
+ var moments: Moments;
695
+ 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) {
696
+ moments.mean = mean; moments.variance = variance; moments.slope = vec2<f32>(1.0, 0.0);
697
+ } else if (reinhard) {
698
+ moments = reinhard_moments(mean, variance);
699
+ } else {
700
+
701
+ var third = vec2<f32>(0.0, 0.0);
702
+ let k0 = b_mul(r0, bs[0]); let k1 = b_mul(r1, bs[1]); let k2 = b_mul(r2, bs[2]);
703
+ for (var n = 0u; n < entries; n = n + 1u) {
685
704
  let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;
686
- let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];
687
- if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }
705
+ let w0 = aux[eb]; let w1 = aux[eb + 1u]; let w2 = aux[eb + 2u];
706
+ if (w0 == 0.0 && w1 == 0.0 && w2 == 0.0) { continue; }
688
707
  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]));
689
- let coefficient = b_add(b_add(b_scale(k0, w0), b_scale(k1, w1)), b_scale(k2, w2));
690
- let cube = b_mul(b_mul(coefficient, coefficient), coefficient);
691
- third = b_add(third, b_mul(cube, vec2<f32>(sites[o + 2u], sites[o + 5u])));
692
- }
693
- var skew = vec2<f32>(0.0, 0.0);
694
- if (variance.x > 0.0) { skew = b_div(third, b_mul(variance, b_sqrt(variance))); }
695
- moments = unit_clip(mean, variance, skew);
696
- }
697
- hm[comp] = moments.mean; hv[comp] = moments.variance; hs[comp] = moments.slope;
698
- e_mean[comp] = b_add(e_mean[comp], b_scale(moments.mean, share));
699
- pixel_variance = vec2<f32>(max(pixel_variance.x, moments.variance.x), max(pixel_variance.y, moments.variance.y));
700
- }
701
- // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance aᵀCa for the residual.
702
- var an: array<vec2<f32>, 9>;
703
- for (var c = 0u; c < 3u; c = c + 1u) {
704
- an[c] = b_mul(b_mul(hs[0], b_rounded(m_of(0u, c))), bs[c]);
705
- an[3u + c] = b_mul(b_mul(hs[1], b_rounded(m_of(1u, c))), bs[c]);
706
- an[6u + c] = b_mul(b_mul(hs[2], b_rounded(m_of(2u, c))), bs[c]);
707
- var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }
708
- let wgt = b_mul(b_mul(b_scale(aj, share), b_rounded(bc)), csv[c]);
709
- 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]));
710
- 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]));
711
- }
712
- for (var comp = 0u; comp < 3u; comp = comp + 1u) {
713
- var l = vec2<f32>(0.0, 0.0);
714
- for (var p = 0u; p < 3u; p = p + 1u) {
715
- for (var q = 0u; q < 3u; q = q + 1u) {
716
- 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]; }
717
- if ((p == 1u && q == 2u) || (p == 2u && q == 1u)) { cij = C[4]; } if (p == 2u && q == 2u) { cij = C[5]; }
718
- l = b_add(l, b_mul(b_mul(an[comp * 3u + p], an[comp * 3u + q]), cij));
719
- }
720
- }
721
- let t = vec2<f32>(share * max(0.0, hv[comp].x - l.x), share * (hv[comp].y + l.y));
722
- res[comp] = b_add(res[comp], b_mul(b_mul(aj, aj), t));
723
- }
724
- }
725
- for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, e_mean[comp])); }
726
- if (count == 2u) {
727
- // AHD's per-pixel choice: ¼·Var(H − V) at the last candidate's slopes, linear in the sites.
728
- var x0 = vec2<f32>(0.0, 0.0); var x1 = vec2<f32>(0.0, 0.0); var x2 = vec2<f32>(0.0, 0.0);
729
- for (var n = 0u; n < entries; n = n + 1u) {
708
+ let coefficient = b_add(b_add(b_scale(k0, w0), b_scale(k1, w1)), b_scale(k2, w2));
709
+ let cube = b_mul(b_mul(coefficient, coefficient), coefficient);
710
+ third = b_add(third, b_mul(cube, vec2<f32>(sites[o + 2u], sites[o + 5u])));
711
+ }
712
+ var skew = vec2<f32>(0.0, 0.0);
713
+ if (variance.x > 0.0) { skew = b_div(third, b_mul(variance, b_sqrt(variance))); }
714
+ moments = unit_clip(mean, variance, skew);
715
+ }
716
+ hm[comp] = moments.mean; hv[comp] = moments.variance; hs[comp] = moments.slope;
717
+ h_cand[d * 3u + comp] = moments.mean;
718
+ e_mean[comp] = b_add(e_mean[comp], b_scale(moments.mean, share));
719
+ pixel_variance = vec2<f32>(max(pixel_variance.x, moments.variance.x), max(pixel_variance.y, moments.variance.y));
720
+ }
721
+
722
+ var an: array<vec2<f32>, 9>;
723
+ for (var c = 0u; c < 3u; c = c + 1u) {
724
+ an[c] = b_mul(b_mul(hs[0], b_rounded(m_of(0u, c))), bs[c]);
725
+ an[3u + c] = b_mul(b_mul(hs[1], b_rounded(m_of(1u, c))), bs[c]);
726
+ an[6u + c] = b_mul(b_mul(hs[2], b_rounded(m_of(2u, c))), bs[c]);
727
+ var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }
728
+ let wgt = b_mul(b_mul(b_scale(aj, share), b_rounded(bc)), csv[c]);
729
+ 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]));
730
+ 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]));
731
+ }
732
+ for (var comp = 0u; comp < 3u; comp = comp + 1u) {
733
+ var l = vec2<f32>(0.0, 0.0);
734
+ for (var p = 0u; p < 3u; p = p + 1u) {
735
+ for (var q = 0u; q < 3u; q = q + 1u) {
736
+ 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]; }
737
+ if ((p == 1u && q == 2u) || (p == 2u && q == 1u)) { cij = C[4]; } if (p == 2u && q == 2u) { cij = C[5]; }
738
+ l = b_add(l, b_mul(b_mul(an[comp * 3u + p], an[comp * 3u + q]), cij));
739
+ }
740
+ }
741
+ let t = vec2<f32>(share * max(0.0, hv[comp].x - l.x), share * (hv[comp].y + l.y));
742
+ res[comp] = b_add(res[comp], b_mul(b_mul(aj, aj), t));
743
+ }
744
+ }
745
+ if (count == 2u) {
746
+
747
+
748
+
749
+ for (var comp = 0u; comp < 3u; comp = comp + 1u) {
750
+ let diff = abs(h_cand[comp].x - h_cand[3u + comp].x);
751
+ let both = h_cand[comp].y + h_cand[3u + comp].y;
752
+ choice[comp] = choice[comp] + aj.x * 0.5 * diff;
753
+ e_mean[comp].y = e_mean[comp].y + 0.5 * (diff + both);
754
+ }
755
+ }
756
+ for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, e_mean[comp])); }
757
+ if (count == 2u) {
758
+
759
+ var x0 = vec2<f32>(0.0, 0.0); var x1 = vec2<f32>(0.0, 0.0); var x2 = vec2<f32>(0.0, 0.0);
760
+ for (var n = 0u; n < entries; n = n + 1u) {
730
761
  let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;
731
- let o = site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]));
732
- let d0 = b_scale(bs[0], aux[eb + 2u] - aux[eb + 5u]);
733
- let d1 = b_scale(bs[1], aux[eb + 3u] - aux[eb + 6u]);
734
- let d2 = b_scale(bs[2], aux[eb + 4u] - aux[eb + 7u]);
735
- let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);
736
- 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)));
737
- 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)));
738
- 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)));
739
- 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));
740
- }
741
- let ajaj = b_mul(aj, aj);
742
- 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)));
743
- }
744
- if (params.correction == 1u) {
745
- // The low-signal correction where the pixel noise exceeds the threshold; a decision within its bound is flagged.
746
- if (abs(pixel_variance.x - params.threshold) <= pixel_variance.y) { pixel_flags = pixel_flags | FLAG_TIE; }
747
- if (pixel_variance.x > params.threshold) {
748
- var corr: array<vec2<f32>, 3>;
749
- flat_correction(cm_average[0], cm_average[1], cm_average[2], &corr);
750
- for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, corr[comp])); }
751
- pixel_flags = pixel_flags | FLAG_CORRECTED;
752
- }
753
- }
754
- }
755
- }
762
+ let o = site_at(x + i32(aux[eb]), y + i32(aux[eb + 1u]));
763
+ let d0 = b_scale(bs[0], aux[eb + 2u] - aux[eb + 5u]);
764
+ let d1 = b_scale(bs[1], aux[eb + 3u] - aux[eb + 6u]);
765
+ let d2 = b_scale(bs[2], aux[eb + 4u] - aux[eb + 7u]);
766
+ let sv = vec2<f32>(sites[o + 1u], sites[o + 4u]);
767
+ 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)));
768
+ 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)));
769
+ 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)));
770
+ 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));
771
+ }
772
+ let ajaj = b_mul(aj, aj);
773
+ 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)));
774
+ }
775
+ if (params.correction == 1u) {
776
+
777
+ if (abs(pixel_variance.x - params.threshold) <= pixel_variance.y) { pixel_flags = pixel_flags | FLAG_TIE; }
778
+ if (pixel_variance.x > params.threshold) {
779
+ var corr: array<vec2<f32>, 3>;
780
+ flat_correction(cm_average[0], cm_average[1], cm_average[2], &corr);
781
+ for (var comp = 0u; comp < 3u; comp = comp + 1u) { s[comp] = b_add(s[comp], b_mul(aj, corr[comp])); }
782
+ pixel_flags = pixel_flags | FLAG_CORRECTED;
783
+ }
784
+ }
785
+ }
786
+ }
787
+
788
+ 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)) ||
789
+ choice[2] > CHOICE_SIGMA * sqrt(max(cov[5].x + res[2].x, 0.0))) { pixel_flags = pixel_flags | FLAG_CHOICE; }
756
790
  let o = i * ${DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS}u;
757
- out[o] = s[0].x; out[o + 1u] = s[1].x; out[o + 2u] = s[2].x;
758
- 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;
759
- out[o + 6u] = cov[1].x; out[o + 7u] = cov[2].x; out[o + 8u] = cov[4].x;
760
- out[o + 9u] = max(s[0].y, max(s[1].y, s[2].y));
761
- 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))));
762
- flags[i] = pixel_flags;
791
+ out[o] = s[0].x; out[o + 1u] = s[1].x; out[o + 2u] = s[2].x;
792
+ 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;
793
+ out[o + 6u] = cov[1].x; out[o + 7u] = cov[2].x; out[o + 8u] = cov[4].x;
794
+ out[o + 9u] = max(s[0].y, max(s[1].y, s[2].y));
795
+ out[o + 10u] = max(max(cov[0].y + res[0].y, cov[3].y + res[1].y), max(cov[5].y + res[2].y, max(cov[1].y, max(cov[2].y, cov[4].y))));
796
+ flags[i] = pixel_flags;
763
797
  }
764
798
  `;
765
799
  /** The lattice pass with a material module appended (`fn material_at(p: vec2<f32>) -> u32`, `var<private> material_uncertain: bool`). */