@photivra/engine 2.13.0 → 2.14.1

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 (128) hide show
  1. package/CHANGELOG.md +75 -1
  2. package/CITATION.cff +1 -1
  3. package/NOTICE +5 -4
  4. package/README.md +10 -8
  5. package/SECURITY.md +1 -1
  6. package/THIRD_PARTY.md +16 -10
  7. package/dist/camera/lens-projection.js +2 -1
  8. package/dist/camera/lens-projection.js.map +1 -1
  9. package/dist/capture/dng-import.js +1 -1
  10. package/dist/capture/dng-import.js.map +1 -1
  11. package/dist/capture/host-yield-cadence.js +1 -1
  12. package/dist/capture/host-yield-cadence.js.map +1 -1
  13. package/dist/capture/native-environment-optical-stage.js +1 -1
  14. package/dist/capture/native-environment-optical-stage.js.map +1 -1
  15. package/dist/capture/native-raw.js +1 -1
  16. package/dist/capture/native-raw.js.map +1 -1
  17. package/dist/capture/packed-sensor-raw.js +3 -3
  18. package/dist/capture/packed-sensor-raw.js.map +1 -1
  19. package/dist/capture/sensor-raw-producer.js +1 -1
  20. package/dist/capture/sensor-raw-producer.js.map +1 -1
  21. package/dist/color/capture-color.js +3 -3
  22. package/dist/color/capture-color.js.map +1 -1
  23. package/dist/color/chromatic-adaptation.js +2 -2
  24. package/dist/color/chromatic-adaptation.js.map +1 -1
  25. package/dist/color/color-matching-functions.js +1 -1
  26. package/dist/color/color-matching-functions.js.map +1 -1
  27. package/dist/color/colorimetry-math.js +2 -2
  28. package/dist/color/colorimetry-math.js.map +1 -1
  29. package/dist/color/illuminants.js +3 -3
  30. package/dist/color/illuminants.js.map +1 -1
  31. package/dist/color/rgb-color-spaces.js +1 -1
  32. package/dist/color/rgb-color-spaces.js.map +1 -1
  33. package/dist/color/sensor-color-development.js +1 -1
  34. package/dist/color/sensor-color-development.js.map +1 -1
  35. package/dist/core/record-validation.js +3 -3
  36. package/dist/core/record-validation.js.map +1 -1
  37. package/dist/core/version.d.ts +1 -1
  38. package/dist/core/version.js +1 -1
  39. package/dist/core/version.js.map +1 -1
  40. package/dist/equipment/generic-prime-lenses.js +3 -3
  41. package/dist/equipment/generic-prime-lenses.js.map +1 -1
  42. package/dist/equipment/generic-tier-assets.d.ts +23 -1
  43. package/dist/equipment/generic-tier-assets.js +58 -30
  44. package/dist/equipment/generic-tier-assets.js.map +1 -1
  45. package/dist/equipment/generic-tier-presets.d.ts +6 -3
  46. package/dist/equipment/generic-tier-presets.js +26 -13
  47. package/dist/equipment/generic-tier-presets.js.map +1 -1
  48. package/dist/exposure/auto-iso-slowest-shutter.js +2 -1
  49. package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
  50. package/dist/exposure/flash-ttl.js +1 -1
  51. package/dist/exposure/flash-ttl.js.map +1 -1
  52. package/dist/exposure/flash.js +1 -1
  53. package/dist/exposure/flash.js.map +1 -1
  54. package/dist/facade/chart-material-layers.js +2 -2
  55. package/dist/facade/chart-material-layers.js.map +1 -1
  56. package/dist/facade/chart-radiance-batch.js +1 -1
  57. package/dist/facade/chart-radiance-batch.js.map +1 -1
  58. package/dist/facade/chart-scenes.d.ts +45 -8
  59. package/dist/facade/chart-scenes.js +101 -44
  60. package/dist/facade/chart-scenes.js.map +1 -1
  61. package/dist/facade/color-patches.d.ts +129 -0
  62. package/dist/facade/color-patches.js +209 -0
  63. package/dist/facade/color-patches.js.map +1 -0
  64. package/dist/facade/display-view-gpu.js +1 -1
  65. package/dist/facade/display-view-gpu.js.map +1 -1
  66. package/dist/facade/scene-radiance.js +2 -2
  67. package/dist/facade/scene-radiance.js.map +1 -1
  68. package/dist/facade/simulate-capture.d.ts +47 -3
  69. package/dist/facade/simulate-capture.js +71 -22
  70. package/dist/facade/simulate-capture.js.map +1 -1
  71. package/dist/facade/simulate-display-view.js +2 -2
  72. package/dist/facade/simulate-display-view.js.map +1 -1
  73. package/dist/gpu/chart-composite-gpu.js +2 -2
  74. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  75. package/dist/gpu/display-view-composite-material-module.js +1 -1
  76. package/dist/gpu/display-view-composite-material-module.js.map +1 -1
  77. package/dist/gpu/image-kernel-execution.js +2 -2
  78. package/dist/gpu/image-kernel-execution.js.map +1 -1
  79. package/dist/gpu/temporal-sum-gpu.js +23 -23
  80. package/dist/gpu/webgpu-runtime.js +1 -1
  81. package/dist/gpu/webgpu-runtime.js.map +1 -1
  82. package/dist/gpu/wgsl-chart-composite-kernel.js +318 -318
  83. package/dist/gpu/wgsl-chart-composite-kernel.js.map +1 -1
  84. package/dist/gpu/wgsl-chart-kernel.js +117 -117
  85. package/dist/gpu/wgsl-chart-kernel.js.map +1 -1
  86. package/dist/gpu/wgsl-display-view-quality-kernel.js +505 -505
  87. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  88. package/dist/gpu/wgsl-display-view-speed-kernel.js +248 -248
  89. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  90. package/dist/gpu/wgsl-kernels.js +203 -203
  91. package/dist/gpu/wgsl-kernels.js.map +1 -1
  92. package/dist/gpu/wgsl-sensor-kernel.js +302 -302
  93. package/dist/gpu/wgsl-sensor-kernel.js.map +1 -1
  94. package/dist/index.d.ts +5 -3
  95. package/dist/index.js +4 -2
  96. package/dist/index.js.map +1 -1
  97. package/dist/motion/temporal-exposure-sum.js +1 -1
  98. package/dist/motion/temporal-exposure-sum.js.map +1 -1
  99. package/dist/optics/diffraction-psf-profile.js +4 -4
  100. package/dist/optics/diffraction-psf-profile.js.map +1 -1
  101. package/dist/optics/front-of-lens-filter.js +1 -1
  102. package/dist/optics/front-of-lens-filter.js.map +1 -1
  103. package/dist/optics/irradiance-convolution.js +1 -1
  104. package/dist/optics/irradiance-convolution.js.map +1 -1
  105. package/dist/output/native-capture-sdr.js +1 -1
  106. package/dist/output/native-capture-sdr.js.map +1 -1
  107. package/dist/output/png-encoder.js +2 -2
  108. package/dist/output/png-encoder.js.map +1 -1
  109. package/dist/output/print-contrast-reference.js +2 -2
  110. package/dist/output/print-contrast-reference.js.map +1 -1
  111. package/dist/output/print-jpeg.js +1 -1
  112. package/dist/output/print-jpeg.js.map +1 -1
  113. package/dist/sensor/fixed-pattern-noise.js +1 -1
  114. package/dist/sensor/fixed-pattern-noise.js.map +1 -1
  115. package/dist/sensor/readout-exposure-linkage.js +1 -1
  116. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  117. package/docs/API_REFERENCE.md +29 -1
  118. package/docs/QUICK_START.md +40 -5
  119. package/docs/USAGE.md +35 -1
  120. package/docs/api/core-version.md +1 -1
  121. package/docs/api/equipment-generic-tier-assets.md +41 -1
  122. package/docs/api/equipment-generic-tier-presets.md +11 -3
  123. package/docs/api/exports.json +107 -2
  124. package/docs/api/facade-chart-scenes.md +36 -5
  125. package/docs/api/facade-color-patches.md +150 -0
  126. package/docs/api/facade-simulate-capture.md +52 -2
  127. package/llms.txt +1 -0
  128. package/package.json +2 -2
@@ -64,7 +64,7 @@ export const DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES = 49;
64
64
  export const DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS = 8;
65
65
  const CANDIDATE_PHASE_STRIDE = 4 + DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES * DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS;
66
66
  /** Layout of the aux table (float32 words). */
67
- export const DISPLAY_VIEW_QUALITY_AUX = Object.freeze({
67
+ export const DISPLAY_VIEW_QUALITY_AUX = /* @__PURE__ */ Object.freeze({
68
68
  /** Per phase p, channel c, tap t: dx, dy, weight, 0 (the bilinear kernels, 4 taps; unused taps have weight 0). */
69
69
  bilinear: 0,
70
70
  /** Per phase p, channel c: 1 / Σ_t w_t² of the bilinear kernel (host float64). */
@@ -77,7 +77,7 @@ export const DISPLAY_VIEW_QUALITY_AUX = Object.freeze({
77
77
  size: 240 + 4 * CANDIDATE_PHASE_STRIDE
78
78
  });
79
79
  /** Pixel flag bits of the output flag word. */
80
- export const DISPLAY_VIEW_QUALITY_FLAG = Object.freeze({
80
+ export const DISPLAY_VIEW_QUALITY_FLAG = /* @__PURE__ */ Object.freeze({
81
81
  /** A site the pixel read had an uncertain lattice material (recompute on the host). */
82
82
  materialUncertain: 1,
83
83
  /** A site the pixel read lies outside the band's site window (a host geometry error; recompute). */
@@ -138,20 +138,20 @@ const SITE: u32 = ${DISPLAY_VIEW_QUALITY_SITE_FLOATS}u;
138
138
  const GROUP: u32 = ${DISPLAY_VIEW_QUALITY_GROUP_WORDS}u;
139
139
  const TAP: u32 = ${DISPLAY_VIEW_QUALITY_TAP_WORDS}u;
140
140
  struct Params {
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,
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,
155
155
  };
156
156
  @group(0) @binding(0) var<uniform> params: Params;
157
157
 
@@ -159,161 +159,161 @@ fn k_of(c: u32) -> f32 { if (c == 0u) { return params.k0; } if (c == 1u) { retur
159
159
  fn dark_of(c: u32) -> f32 { if (c == 0u) { return params.dark0; } if (c == 1u) { return params.dark1; } return params.dark2; }
160
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; }
161
161
  fn g_of(c: u32) -> f32 { if (c == 0u) { return params.g0; } if (c == 1u) { return params.g1; } return params.g2; }
162
- // Whether the channel's white-balance gain is the clip level itself (the smallest gain), exactly, in the reference's float64.
162
+
163
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; }
164
164
  fn m_of(r: u32, c: u32) -> f32 {
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
- }
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
+ }
171
171
  }
172
172
  fn channel_of(x: i32, y: i32) -> u32 {
173
- if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }
174
- 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);
175
175
  }
176
- // A value with its absolute error bound (x: value, y: bound). First-order rules; the host doubles the final bound.
176
+
177
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)); }
178
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)); }
179
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)); }
180
- // Times an exact constant (a kernel weight, an integer, a power of two).
180
+
181
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)); }
182
- // A host constant rounded once to float32 (a gain, a matrix entry).
182
+
183
183
  fn b_rounded(k: f32) -> vec2<f32> { return vec2<f32>(k, E * abs(k)); }
184
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)); }
185
- // |sqrt(v + d) - sqrt(v)| <= min(d / (2 sqrt v), sqrt d), plus the 4.5 ULP of the built-in.
185
+
186
186
  fn b_sqrt(a: vec2<f32>) -> vec2<f32> {
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));
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));
190
190
  }
191
- // Standard normal density, as the reference forms it, and Abramowitz & Stegun 26.2.17 as (Φ, 1 − Φ).
191
+
192
192
  fn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }
193
193
  fn pq(z: f32) -> vec2<f32> {
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);
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);
199
199
  }
200
200
  struct Moments { mean: vec2<f32>, variance: vec2<f32>, slope: vec2<f32> }
201
- // Moments of min(Y, top) for Y ~ N(m, v) with their bounds: Var = v·(Φ − zφ + z²Q − δ²), δ = φ − zQ (the Speed forms).
201
+
202
202
  fn upper_clip(m: vec2<f32>, v: vec2<f32>, top: f32) -> Moments {
203
- var out: Moments;
204
- if (!(v.x > 0.0)) {
205
- // 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.
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
- // First-order propagation of the inputs' bounds: with g = min(Z, z), E[g] = −δ and ρ = Var(g) = ratio, ∂E/∂m = Φ, ∂E/∂s = φ,
219
- // ∂Var/∂m = −s·ρ_z, ∂Var/∂s = s·(2ρ − z·ρ_z) with ρ_z = 2Q·(z + δ), ∂Φ/∂m = −φ/s and ∂Φ/∂s = −zφ/s.
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
- // Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1), with their bounds (the
227
- // 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
228
- // 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
+
229
229
  fn unit_clip(m: vec2<f32>, v: vec2<f32>, skew: vec2<f32>) -> Moments {
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
- // |aφa − bφb| ≤ 0.48, so |shift| ≤ 0.08·s·|γ| and a wrong γ moves it by at most 0.16·s.
255
- let d_shift = 0.1 * s.x * min(2.0, skew.y) + 0.1 * abs(gamma) * s.y + E * abs(shift);
256
- // 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,
257
- // ∂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),
258
- // ∂Var/∂s = s(2ρ − aρ_a − bρ_b); the slope's derivatives are (φa − φb)/s and (aφa − bφb)/s.
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
- // 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
264
- // [0, X_cpu] when the shift term is non-negative on both sides (shift > Δshift), which caps the bound by max(Var, X + ΔX).
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;
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;
274
274
  }
275
275
  `;
276
276
  /** The Gauss–Legendre Reinhard moments read the aux table, so they live in the pixel kernel. */
277
277
  const REINHARD_WGSL = /* wgsl */ `
278
- // 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
279
- // 16-point Gauss–Legendre rule in standardized units (the Speed forms), with the bounds of the quadrature (REINHARD_*_U)
280
- // and of its inputs (|h′| ≤ 1, |h″| ≤ 2).
278
+
279
+
280
+
281
281
  fn reinhard_moments(m: vec2<f32>, v: vec2<f32>) -> Moments {
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) {
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) {
302
302
  let z = zc + zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre}u + k];
303
- let x = m.x + s.x * z;
303
+ let x = m.x + s.x * z;
304
304
  let weight = zhalf * aux[${DISPLAY_VIEW_QUALITY_AUX.gaussLegendre + 16}u + k] * phi(z);
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;
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;
317
317
  }
318
318
  `;
319
319
  /**
@@ -328,21 +328,21 @@ export const DISPLAY_VIEW_QUALITY_LATTICE_WGSL = /* wgsl */ `${COMMON_WGSL}
328
328
  @compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
329
329
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
330
330
  let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
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);
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);
346
346
  }
347
347
  `;
348
348
  /**
@@ -351,21 +351,21 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
351
351
  */
352
352
  const SITE_TAIL_WGSL = /* wgsl */ `
353
353
  fn write_site(i: u32, c: u32, e: f32, de: f32, flag: u32) {
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;
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;
369
369
  }
370
370
  `;
371
371
  /**
@@ -392,28 +392,28 @@ export const DISPLAY_VIEW_QUALITY_SITES_WGSL = /* wgsl */ `${COMMON_WGSL}${siteB
392
392
  @compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
393
393
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
394
394
  let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
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);
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);
417
417
  }
418
418
  `;
419
419
  /**
@@ -440,47 +440,47 @@ ${SITE_TAIL_WGSL}
440
440
  @compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
441
441
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
442
442
  let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
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);
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);
484
484
  }
485
485
  `;
486
486
  /** Pass 3: the display pixels (see the module comment). */
@@ -512,288 +512,288 @@ var<private> pixel_flags: u32;
512
512
  ${REINHARD_WGSL}
513
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; }
514
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; }
515
- // 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
+
516
516
  fn site_at(x: i32, y: i32) -> u32 {
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
- // 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
+
525
525
  fn find_state(key: i32) -> u32 {
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
- // The flat-neighbourhood correction at channel means (m0, m1, m2) with its bound: trilinear between the eight grid states
535
- // around (log2(clamp(m, 2^-12, 1)) + 12)·8; a missing state flags the pixel. The bound of each axis's coordinate times the
536
- // 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
+
537
537
  fn flat_correction(m0: vec2<f32>, m1: vec2<f32>, m2: vec2<f32>, corr: ptr<function, array<vec2<f32>, 3>>) {
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
- // log2: 1/ln 2 per relative input error, 2^-21 absolute or 3 ULP (≤ 36E on [2^-12, 1]), the sum and the product.
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
- // The largest state difference along each axis (over the four edges), times that axis's coordinate bound.
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
- }
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
+ }
583
583
  }
584
584
  @compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
585
585
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
586
586
  let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
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
- // B_c of the footprint from the host's axis sums (each over its scale squared): red, green, blue.
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
- // #457: Σ_j a_j·½|h(H) − h(V)| per component, how far the reference's choice can move the value from the candidates' average.
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
- // The overlap weight a_j: two host roundings (the overlaps over the scale) and the product.
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
- // Site variance of each channel through the bilinear kernels, over Σw² (for B_c).
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);
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);
631
631
  let tb = ${DISPLAY_VIEW_QUALITY_AUX.bilinear}u + (phase * 3u + c) * 16u;
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
- }
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
+ }
638
638
  csv[c] = b_mul(variance, b_rounded(aux[${DISPLAY_VIEW_QUALITY_AUX.inverseW2}u + phase * 3u + c]));
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
- // #457: each candidate's rendered mean, for the reference's choice between them.
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
- // Camera-channel mean and covariance of candidate d from its dense weights over the window.
651
- var cm: array<vec2<f32>, 3>;
652
- var C: array<vec2<f32>, 6>;
653
- // Whether every site a channel reads is exactly at white (a zero bound: the reference's site is exactly 1 too); the
654
- // dyadic kernel weights then sum exactly on both sides, so the channel's mean carries no error (the host checks the weights).
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) {
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) {
659
659
  let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;
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; }
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; }
662
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]));
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
- // White balance and the highlight clip at the smallest gain. A channel exactly at white whose gain is the clip level is
673
- // exactly at the clip level on both sides.
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
- // Colour matrix with the full channel covariance, then the rendering of each component.
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
- // κ₃ of the component: Σ_sites (Σ_c M_comp,c·Γ_c·w_c,site)³·κ₃(site), over the candidate's dense weights.
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) {
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) {
704
704
  let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u + 2u + d * 3u;
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; }
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; }
707
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]));
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
- // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance aᵀCa for the residual.
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
- // #457: the reference takes AHD's choice on the noise-free mosaic, one candidate or (a tie) their average, so its value
747
- // lies within half the candidates' difference plus their bounds of the average: the bound carries that, and the move
748
- // accumulates in choice, which flags the pixel below when it exceeds CHOICE_SIGMA of the pixel's noise σ.
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
- // AHD's per-pixel choice: ¼·Var(H − V) at the last candidate's slopes, linear in the sites.
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) {
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) {
761
761
  let eb = cb + 4u + n * ${DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS}u;
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
- // The low-signal correction where the pixel noise exceeds the threshold; a decision within its bound is flagged.
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
- // #457: a pixel whose candidates' choice could move it by more than CHOICE_SIGMA of its own noise σ is recomputed on the host.
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; }
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; }
790
790
  let o = i * ${DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS}u;
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;
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;
797
797
  }
798
798
  `;
799
799
  /** The lattice pass with a material module appended (`fn material_at(p: vec2<f32>) -> u32`, `var<private> material_uncertain: bool`). */