@photivra/engine 2.13.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 (125) hide show
  1. package/CHANGELOG.md +68 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +7 -7
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +2 -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/host-yield-cadence.js +1 -1
  11. package/dist/capture/host-yield-cadence.js.map +1 -1
  12. package/dist/capture/native-environment-optical-stage.js +1 -1
  13. package/dist/capture/native-environment-optical-stage.js.map +1 -1
  14. package/dist/capture/native-raw.js +1 -1
  15. package/dist/capture/native-raw.js.map +1 -1
  16. package/dist/capture/packed-sensor-raw.js +3 -3
  17. package/dist/capture/packed-sensor-raw.js.map +1 -1
  18. package/dist/capture/sensor-raw-producer.js +1 -1
  19. package/dist/capture/sensor-raw-producer.js.map +1 -1
  20. package/dist/color/capture-color.js +3 -3
  21. package/dist/color/capture-color.js.map +1 -1
  22. package/dist/color/chromatic-adaptation.js +2 -2
  23. package/dist/color/chromatic-adaptation.js.map +1 -1
  24. package/dist/color/color-matching-functions.js +1 -1
  25. package/dist/color/color-matching-functions.js.map +1 -1
  26. package/dist/color/colorimetry-math.js +2 -2
  27. package/dist/color/colorimetry-math.js.map +1 -1
  28. package/dist/color/illuminants.js +3 -3
  29. package/dist/color/illuminants.js.map +1 -1
  30. package/dist/color/rgb-color-spaces.js +1 -1
  31. package/dist/color/rgb-color-spaces.js.map +1 -1
  32. package/dist/color/sensor-color-development.js +1 -1
  33. package/dist/color/sensor-color-development.js.map +1 -1
  34. package/dist/core/record-validation.js +3 -3
  35. package/dist/core/record-validation.js.map +1 -1
  36. package/dist/core/version.d.ts +1 -1
  37. package/dist/core/version.js +1 -1
  38. package/dist/core/version.js.map +1 -1
  39. package/dist/equipment/generic-prime-lenses.js +3 -3
  40. package/dist/equipment/generic-prime-lenses.js.map +1 -1
  41. package/dist/equipment/generic-tier-assets.d.ts +23 -1
  42. package/dist/equipment/generic-tier-assets.js +58 -30
  43. package/dist/equipment/generic-tier-assets.js.map +1 -1
  44. package/dist/equipment/generic-tier-presets.d.ts +6 -3
  45. package/dist/equipment/generic-tier-presets.js +26 -13
  46. package/dist/equipment/generic-tier-presets.js.map +1 -1
  47. package/dist/exposure/auto-iso-slowest-shutter.js +2 -1
  48. package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
  49. package/dist/exposure/flash-ttl.js +1 -1
  50. package/dist/exposure/flash-ttl.js.map +1 -1
  51. package/dist/exposure/flash.js +1 -1
  52. package/dist/exposure/flash.js.map +1 -1
  53. package/dist/facade/chart-material-layers.js +2 -2
  54. package/dist/facade/chart-material-layers.js.map +1 -1
  55. package/dist/facade/chart-radiance-batch.js +1 -1
  56. package/dist/facade/chart-radiance-batch.js.map +1 -1
  57. package/dist/facade/chart-scenes.d.ts +45 -8
  58. package/dist/facade/chart-scenes.js +101 -44
  59. package/dist/facade/chart-scenes.js.map +1 -1
  60. package/dist/facade/color-patches.d.ts +129 -0
  61. package/dist/facade/color-patches.js +209 -0
  62. package/dist/facade/color-patches.js.map +1 -0
  63. package/dist/facade/display-view-gpu.js +1 -1
  64. package/dist/facade/display-view-gpu.js.map +1 -1
  65. package/dist/facade/scene-radiance.js +2 -2
  66. package/dist/facade/scene-radiance.js.map +1 -1
  67. package/dist/facade/simulate-capture.d.ts +47 -3
  68. package/dist/facade/simulate-capture.js +71 -22
  69. package/dist/facade/simulate-capture.js.map +1 -1
  70. package/dist/facade/simulate-display-view.js +2 -2
  71. package/dist/facade/simulate-display-view.js.map +1 -1
  72. package/dist/gpu/chart-composite-gpu.js +2 -2
  73. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  74. package/dist/gpu/display-view-composite-material-module.js +1 -1
  75. package/dist/gpu/display-view-composite-material-module.js.map +1 -1
  76. package/dist/gpu/image-kernel-execution.js +2 -2
  77. package/dist/gpu/image-kernel-execution.js.map +1 -1
  78. package/dist/gpu/temporal-sum-gpu.js +23 -23
  79. package/dist/gpu/webgpu-runtime.js +1 -1
  80. package/dist/gpu/webgpu-runtime.js.map +1 -1
  81. package/dist/gpu/wgsl-chart-composite-kernel.js +318 -318
  82. package/dist/gpu/wgsl-chart-composite-kernel.js.map +1 -1
  83. package/dist/gpu/wgsl-chart-kernel.js +117 -117
  84. package/dist/gpu/wgsl-chart-kernel.js.map +1 -1
  85. package/dist/gpu/wgsl-display-view-quality-kernel.js +505 -505
  86. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  87. package/dist/gpu/wgsl-display-view-speed-kernel.js +248 -248
  88. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  89. package/dist/gpu/wgsl-kernels.js +203 -203
  90. package/dist/gpu/wgsl-kernels.js.map +1 -1
  91. package/dist/gpu/wgsl-sensor-kernel.js +302 -302
  92. package/dist/gpu/wgsl-sensor-kernel.js.map +1 -1
  93. package/dist/index.d.ts +5 -3
  94. package/dist/index.js +4 -2
  95. package/dist/index.js.map +1 -1
  96. package/dist/motion/temporal-exposure-sum.js +1 -1
  97. package/dist/motion/temporal-exposure-sum.js.map +1 -1
  98. package/dist/optics/diffraction-psf-profile.js +4 -4
  99. package/dist/optics/diffraction-psf-profile.js.map +1 -1
  100. package/dist/optics/front-of-lens-filter.js +1 -1
  101. package/dist/optics/front-of-lens-filter.js.map +1 -1
  102. package/dist/optics/irradiance-convolution.js +1 -1
  103. package/dist/optics/irradiance-convolution.js.map +1 -1
  104. package/dist/output/native-capture-sdr.js +1 -1
  105. package/dist/output/native-capture-sdr.js.map +1 -1
  106. package/dist/output/png-encoder.js +2 -2
  107. package/dist/output/png-encoder.js.map +1 -1
  108. package/dist/output/print-jpeg.js +1 -1
  109. package/dist/output/print-jpeg.js.map +1 -1
  110. package/dist/sensor/fixed-pattern-noise.js +1 -1
  111. package/dist/sensor/fixed-pattern-noise.js.map +1 -1
  112. package/dist/sensor/readout-exposure-linkage.js +1 -1
  113. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  114. package/docs/API_REFERENCE.md +29 -1
  115. package/docs/QUICK_START.md +40 -5
  116. package/docs/USAGE.md +35 -1
  117. package/docs/api/core-version.md +1 -1
  118. package/docs/api/equipment-generic-tier-assets.md +41 -1
  119. package/docs/api/equipment-generic-tier-presets.md +11 -3
  120. package/docs/api/exports.json +107 -2
  121. package/docs/api/facade-chart-scenes.md +36 -5
  122. package/docs/api/facade-color-patches.md +150 -0
  123. package/docs/api/facade-simulate-capture.md +52 -2
  124. package/llms.txt +1 -0
  125. package/package.json +2 -2
@@ -42,7 +42,7 @@ export const SENSOR_OUTPUT = Object.freeze({ codeMask: 0xffff, flagShift: 16, un
42
42
  * each: the integer mantissa m (< 2^12), the exponent s and the exact scale 2^s, so that piece = m·2^s. The INL
43
43
  * table holds the knot codes, the knot offsets and the host-computed segment slopes.
44
44
  */
45
- export const SENSOR_AUX = Object.freeze({ stirlingRemainder: 0, sigma: 256, inversePieces: 272, digitalPieces: 284, knots: 296 });
45
+ export const SENSOR_AUX = /* @__PURE__ */ Object.freeze({ stirlingRemainder: 0, sigma: 256, inversePieces: 272, digitalPieces: 284, knots: 296 });
46
46
  export const SENSOR_PIECE_WORDS = 3;
47
47
  export const SENSOR_WORKGROUP_SIZE = 256;
48
48
  /**
@@ -52,11 +52,11 @@ export const SENSOR_WORKGROUP_SIZE = 256;
52
52
  */
53
53
  export const SENSOR_SITE_WORDS = 4;
54
54
  /** Bits of `Params.extras`. */
55
- export const SENSOR_EXTRAS = Object.freeze({ deterministic: 1, fixedPattern: 2 });
55
+ export const SENSOR_EXTRAS = /* @__PURE__ */ Object.freeze({ deterministic: 1, fixedPattern: 2 });
56
56
  /** Partial charge sums saturate here: above every supported capacity (2^24), so no decision changes. */
57
57
  export const SENSOR_REALIZED_CLAMP = 2 ** 30;
58
58
  /** Mean categories in bits 30–31 of a mean word, mirroring `samplePoisson`'s branches. */
59
- export const SENSOR_MEAN_CATEGORY = Object.freeze({ zero: 0, multiplication: 1, transformedRejection: 2, normal: 3 });
59
+ export const SENSOR_MEAN_CATEGORY = /* @__PURE__ */ Object.freeze({ zero: 0, multiplication: 1, transformedRejection: 2, normal: 3 });
60
60
  /** One WGSL rounding (one ULP, either direction) is at most this relative error: 2^-23. */
61
61
  export const SENSOR_ROUNDING = 2 ** -23;
62
62
  /** |z_f32 − z_f64| ≤ SENSOR_NORMAL_ERROR·E·radius for the Box–Muller normal (docs/WEBGPU.md). */
@@ -80,14 +80,14 @@ const CPU_REL: f32 = 7.105427357601002e-15;
80
80
  const LOG_ABS: f32 = 4.76837158203125e-7;
81
81
  const Z_ERR: f32 = ${literal(SENSOR_NORMAL_ERROR)};
82
82
  struct Params {
83
- count: u32, row0: u32, region_w: u32, native_w: u32,
84
- region_x: u32, region_y: u32, seed: u32, components: u32,
85
- knots: u32, digital: u32, saturation: u32, adc_max: u32,
86
- capacity_floor: u32, threshold_floor: u32, black: u32, sat_quantized: i32,
87
- capacity_frac: f32, threshold_frac: f32, inv_gain: f32, inv_residual: f32,
88
- digital_gain: f32, digital_residual: f32, inl_slope: f32, inl_offset: f32,
89
- site_words: u32, poisson_count: u32, extras: u32, pad0: u32,
90
- fpn_e_slack: f32, fpn_c_slack: f32, inl_knot: f32, pad2: f32,
83
+ count: u32, row0: u32, region_w: u32, native_w: u32,
84
+ region_x: u32, region_y: u32, seed: u32, components: u32,
85
+ knots: u32, digital: u32, saturation: u32, adc_max: u32,
86
+ capacity_floor: u32, threshold_floor: u32, black: u32, sat_quantized: i32,
87
+ capacity_frac: f32, threshold_frac: f32, inv_gain: f32, inv_residual: f32,
88
+ digital_gain: f32, digital_residual: f32, inl_slope: f32, inl_offset: f32,
89
+ site_words: u32, poisson_count: u32, extras: u32, pad0: u32,
90
+ fpn_e_slack: f32, fpn_c_slack: f32, inl_knot: f32, pad2: f32,
91
91
  };
92
92
  @group(0) @binding(0) var<uniform> params: Params;
93
93
  @group(0) @binding(1) var<storage, read> means: array<u32>;
@@ -101,352 +101,352 @@ var<private> ip: i32;
101
101
  var<private> fp: f32;
102
102
 
103
103
  fn fmix(value: u32) -> u32 {
104
- var h = value;
105
- h = h ^ (h >> 16u); h = h * 0x85ebca6bu; h = h ^ (h >> 13u); h = h * 0xc2b2ae35u; h = h ^ (h >> 16u);
106
- return h;
104
+ var h = value;
105
+ h = h ^ (h >> 16u); h = h * 0x85ebca6bu; h = h ^ (h >> 13u); h = h * 0xc2b2ae35u; h = h ^ (h >> 16u);
106
+ return h;
107
107
  }
108
108
  fn mix_block(hash: u32, block: u32) -> u32 {
109
- var k = block * 0xcc9e2d51u;
110
- k = (k << 15u) | (k >> 17u);
111
- k = k * 0x1b873593u;
112
- var h = hash ^ k;
113
- h = (h << 13u) | (h >> 19u);
114
- return h * 5u + 0xe6546b64u;
109
+ var k = block * 0xcc9e2d51u;
110
+ k = (k << 15u) | (k >> 17u);
111
+ k = k * 0x1b873593u;
112
+ var h = hash ^ k;
113
+ h = (h << 13u) | (h >> 19u);
114
+ return h * 5u + 0xe6546b64u;
115
115
  }
116
116
  fn site_seed(seed: u32, index: u32, stream: u32) -> u32 {
117
- return fmix(mix_block(mix_block(mix_block(0u, seed), index), stream) ^ 12u);
117
+ return fmix(mix_block(mix_block(mix_block(0u, seed), index), stream) ^ 12u);
118
118
  }
119
119
  fn start(seed: u32) {
120
- state = fmix(seed ^ 0x9e3779b9u);
121
- if (state == 0u) { state = 0x6d2b79f5u; }
120
+ state = fmix(seed ^ 0x9e3779b9u);
121
+ if (state == 0u) { state = 0x6d2b79f5u; }
122
122
  }
123
123
  fn next_bits() -> u32 {
124
- state = state ^ (state << 13u); state = state ^ (state >> 17u); state = state ^ (state << 5u);
125
- return state;
124
+ state = state ^ (state << 13u); state = state ^ (state >> 17u); state = state ^ (state << 5u);
125
+ return state;
126
126
  }
127
- // (bits + 0.5)/2^32 with two roundings (the conversion and the sum; the power-of-two scale is exact): ≤ 2E.
127
+
128
128
  fn unit(bits: u32) -> f32 { return (f32(bits) + 0.5) * INV32; }
129
129
  fn check(distance: f32, margin: f32) { if (abs(distance) <= margin) { uncertain = true; } }
130
130
 
131
- // ln((bits + 0.5)/2^32), relative error ≤ 11E: WGSL log below 1/2 (≤ 6E), else 2·atanh(r) with r = −w/(2 − w)
132
- // on the exact 1 − unit (division 2.5E; ≤ 10.5E in all).
131
+
132
+
133
133
  fn ln_unit(bits: u32) -> f32 {
134
- if (bits < 0x80000000u) { return log(unit(bits)); }
135
- let w = (f32(0xffffffffu - bits) + 0.5) * INV32;
136
- let r = -w / (2.0 - w);
137
- let r2 = r * r;
134
+ if (bits < 0x80000000u) { return log(unit(bits)); }
135
+ let w = (f32(0xffffffffu - bits) + 0.5) * INV32;
136
+ let r = -w / (2.0 - w);
137
+ let r2 = r * r;
138
138
  var p = ${literal(1 / 23)};
139
139
  ${[21, 19, 17, 15, 13, 11, 9, 7, 5, 3].map(n => `p = p * r2 + ${literal(1 / n)};`).join(" ")}
140
- p = p * r2 + 1.0;
141
- return 2.0 * r * p;
140
+ p = p * r2 + 1.0;
141
+ return 2.0 * r * p;
142
142
  }
143
- // cos(2π (bits + 0.5)/2^32): exact octant reduction on the integer, Taylor polynomials on [0, π/4]; absolute error ≤ 8E.
143
+
144
144
  fn cos_unit(bits: u32) -> f32 {
145
- let octant = bits >> 29u;
146
- let low = bits & 0x1fffffffu;
147
- let even = (octant & 1u) == 0u;
148
- var g = (f32(0x1fffffffu - low) + 0.5) * TWO_PI_INV32;
149
- if (even) { g = (f32(low) + 0.5) * TWO_PI_INV32; }
150
- let g2 = g * g;
145
+ let octant = bits >> 29u;
146
+ let low = bits & 0x1fffffffu;
147
+ let even = (octant & 1u) == 0u;
148
+ var g = (f32(0x1fffffffu - low) + 0.5) * TWO_PI_INV32;
149
+ if (even) { g = (f32(low) + 0.5) * TWO_PI_INV32; }
150
+ let g2 = g * g;
151
151
  var c = ${literal(COS_COEFFICIENTS[5])};
152
152
  ${[4, 3, 2, 1, 0].map(j => `c = c * g2 + ${literal(COS_COEFFICIENTS[j])};`).join(" ")}
153
- c = c * g2 + 1.0;
153
+ c = c * g2 + 1.0;
154
154
  var s = ${literal(SIN_COEFFICIENTS[4])};
155
155
  ${[3, 2, 1, 0].map(j => `s = s * g2 + ${literal(SIN_COEFFICIENTS[j])};`).join(" ")}
156
- s = g * (s * g2 + 1.0);
157
- let cb = select(s, c, even);
158
- let sb = select(c, s, even);
159
- let q = octant >> 1u;
160
- if (q == 0u) { return cb; }
161
- if (q == 1u) { return -sb; }
162
- if (q == 2u) { return -cb; }
163
- return sb;
164
- }
165
- // Box–Muller, one value per two uniforms: radius = sqrt(−2 ln u1) (≤ 10E relative), times cos (≤ 8E absolute),
166
- // one product rounding: |z_f32 − z_f64| ≤ Z_ERR·E·radius with Z_ERR = 20 (derivation in docs/WEBGPU.md).
156
+ s = g * (s * g2 + 1.0);
157
+ let cb = select(s, c, even);
158
+ let sb = select(c, s, even);
159
+ let q = octant >> 1u;
160
+ if (q == 0u) { return cb; }
161
+ if (q == 1u) { return -sb; }
162
+ if (q == 2u) { return -cb; }
163
+ return sb;
164
+ }
165
+
166
+
167
167
  fn std_normal() -> f32 {
168
- let b1 = next_bits();
169
- let b2 = next_bits();
170
- radius = sqrt(-2.0 * ln_unit(b1));
171
- return radius * cos_unit(b2);
168
+ let b1 = next_bits();
169
+ let b2 = next_bits();
170
+ radius = sqrt(-2.0 * ln_unit(b1));
171
+ return radius * cos_unit(b2);
172
172
  }
173
- // Multiplication method: product of uniforms (3E per factor) against exp(−mean), whose relative error is
174
- // (3 + 2·mean) ULP plus the float32 mean's absolute error E·(mean + 1): ≤ (4 + 3·mean)·E.
173
+
174
+
175
175
  fn poisson_small(mean: f32) -> u32 {
176
- let threshold = exp(-mean);
177
- var product = 1.0;
178
- var count = 0u;
179
- loop {
180
- count = count + 1u;
181
- product = product * unit(next_bits());
182
- check(product - threshold, MARGIN * (3.0 * f32(count) + 4.0 + 3.0 * mean) * E * max(product, threshold));
183
- if (!(product > threshold)) { break; }
184
- if (count >= 1000u) { uncertain = true; break; }
185
- }
186
- return count - 1u;
187
- }
188
- // g(t) = ln(1 + t) − t for |t| <= 1/2 without cancellation: −t²/(2+t) + 2(s³/3 + s⁵/5 + …), s = t/(2+t).
176
+ let threshold = exp(-mean);
177
+ var product = 1.0;
178
+ var count = 0u;
179
+ loop {
180
+ count = count + 1u;
181
+ product = product * unit(next_bits());
182
+ check(product - threshold, MARGIN * (3.0 * f32(count) + 4.0 + 3.0 * mean) * E * max(product, threshold));
183
+ if (!(product > threshold)) { break; }
184
+ if (count >= 1000u) { uncertain = true; break; }
185
+ }
186
+ return count - 1u;
187
+ }
188
+
189
189
  fn log1p_minus(t: f32) -> f32 {
190
- let d = 2.0 + t;
191
- let s = t / d;
192
- let s2 = s * s;
190
+ let d = 2.0 + t;
191
+ let s = t / d;
192
+ let s2 = s * s;
193
193
  var p = ${literal(1 / 25)};
194
194
  ${[23, 21, 19, 17, 15, 13, 11, 9, 7, 5].map(n => `p = p * s2 + ${literal(1 / n)};`).join(" ")}
195
195
  p = p * s2 + ${literal(1 / 3)};
196
- return -(t * t) / d + 2.0 * s * s2 * p;
196
+ return -(t * t) / d + 2.0 * s * s2 * p;
197
197
  }
198
198
  fn poisson_ptrs(mi: u32, mf: f32) -> u32 {
199
- // mean: ≤ 2.1E relative; sq: ≤ 5.6E; b: ≤ 8.7E; a: ≤ 14E; inv_alpha: ≤ 3.5E; v_r: ≤ 6E absolute (docs/WEBGPU.md).
200
- let mean = f32(mi) + mf;
201
- let sq = sqrt(mean);
202
- let b = 0.931 + 2.53 * sq;
203
- let a = -0.059 + 0.02483 * b;
204
- let inv_alpha = 1.1239 + 1.1328 / (b - 3.4);
205
- let v_r = 0.9277 - 3.6224 / (b - 2.0);
206
- let ln_mean = log(mean);
207
- for (var iteration = 0u; iteration < 64u; iteration = iteration + 1u) {
208
- let su = next_bits();
209
- let sv = next_bits();
210
- var m = su;
211
- var sign = -1.0;
212
- if (su >= 0x80000000u) { m = 0xffffffffu - su; sign = 1.0; }
213
- let us = unit(m);
214
- let u = sign * unit(0x7fffffffu - m);
215
- let v = unit(sv);
216
- // scale ≤ 19.5E, t = scale·u ≤ 22.5E relative; x = (t + mf) + 0.43 in any order adds 2E·(|t| + 1.43) + 2E.
217
- let scale = 2.0 * a / us + b;
218
- let t = scale * u;
219
- let x = (t + mf) + 0.43;
220
- let fl = floor(x);
221
- let ex = MARGIN * (25.0 * E * abs(t) + 4.0 * E + 4.5e-16 * (abs(t) + mean + 1.0));
222
- if (x - fl <= ex || fl + 1.0 - x <= ex) { uncertain = true; }
223
- if (fl <= -1073741824.0 || fl >= 1073741824.0) { continue; }
224
- let k = i32(mi) + i32(fl);
199
+
200
+ let mean = f32(mi) + mf;
201
+ let sq = sqrt(mean);
202
+ let b = 0.931 + 2.53 * sq;
203
+ let a = -0.059 + 0.02483 * b;
204
+ let inv_alpha = 1.1239 + 1.1328 / (b - 3.4);
205
+ let v_r = 0.9277 - 3.6224 / (b - 2.0);
206
+ let ln_mean = log(mean);
207
+ for (var iteration = 0u; iteration < 64u; iteration = iteration + 1u) {
208
+ let su = next_bits();
209
+ let sv = next_bits();
210
+ var m = su;
211
+ var sign = -1.0;
212
+ if (su >= 0x80000000u) { m = 0xffffffffu - su; sign = 1.0; }
213
+ let us = unit(m);
214
+ let u = sign * unit(0x7fffffffu - m);
215
+ let v = unit(sv);
216
+
217
+ let scale = 2.0 * a / us + b;
218
+ let t = scale * u;
219
+ let x = (t + mf) + 0.43;
220
+ let fl = floor(x);
221
+ let ex = MARGIN * (25.0 * E * abs(t) + 4.0 * E + 4.5e-16 * (abs(t) + mean + 1.0));
222
+ if (x - fl <= ex || fl + 1.0 - x <= ex) { uncertain = true; }
223
+ if (fl <= -1073741824.0 || fl >= 1073741824.0) { continue; }
224
+ let k = i32(mi) + i32(fl);
225
225
  if (m >= ${uniformThresholdInteger(0.07)}u) {
226
- if (k >= 0) { check(v - v_r, MARGIN * (7.0 * E + 2.0 * E * v)); }
227
- if (v <= v_r && k >= 0) { return u32(k); }
228
- }
226
+ if (k >= 0) { check(v - v_r, MARGIN * (7.0 * E + 2.0 * E * v)); }
227
+ if (v <= v_r && k >= 0) { return u32(k); }
228
+ }
229
229
  if (k < 0 || (m < ${uniformThresholdInteger(0.013)}u && sv > m)) { continue; }
230
- // lhs: the argument carries ≤ 31.5E (two divisions), the logarithm 3 ULP or 2^-21.
231
- let lhs = log(v * inv_alpha / (a / (us * us) + b));
232
- let dl = 32.0 * E + LOG_ABS + 3.0 * E * abs(lhs);
233
- var rhs: f32;
234
- var dr: f32;
235
- if (k == 0) {
236
- rhs = -mean;
237
- dr = 3.0 * E * mean + CPU_REL * mean;
238
- } else {
239
- let kf = f32(k);
240
- let dd = f32(i32(mi) - k) + mf;
241
- let t2 = dd / kf;
242
- var kg: f32;
243
- var dk: f32;
244
- if (abs(t2) <= 0.5) {
245
- kg = kf * log1p_minus(t2);
246
- dk = 25.0 * E * abs(kg) + E;
247
- } else {
248
- let lr = log(mean / kf);
249
- kg = kf * lr - dd;
250
- dk = kf * (6.0 * E + LOG_ABS + 5.0 * E * abs(lr)) + 2.0 * E * abs(dd) + E + E * abs(kg);
251
- }
252
- let lnk = log(kf);
253
- var corr: f32;
254
- if (k < 256) { corr = aux[u32(k)]; } else { corr = 1.0 / (12.0 * kf) - 1.0 / (360.0 * kf * kf * kf); }
255
- rhs = ((kg - 0.5 * lnk) - 0.91893853320467274) - corr;
256
- dr = dk + 0.5 * LOG_ABS + 1.5 * E * lnk + 2.0 * E + 3.0 * E * (abs(kg) + 0.5 * lnk + 1.0) +
257
- CPU_REL * (mean + kf * (abs(ln_mean) + lnk + 1.0));
258
- }
259
- check(lhs - rhs, MARGIN * (dl + dr));
260
- if (lhs <= rhs) { return u32(k); }
261
- }
262
- uncertain = true;
263
- return mi;
230
+
231
+ let lhs = log(v * inv_alpha / (a / (us * us) + b));
232
+ let dl = 32.0 * E + LOG_ABS + 3.0 * E * abs(lhs);
233
+ var rhs: f32;
234
+ var dr: f32;
235
+ if (k == 0) {
236
+ rhs = -mean;
237
+ dr = 3.0 * E * mean + CPU_REL * mean;
238
+ } else {
239
+ let kf = f32(k);
240
+ let dd = f32(i32(mi) - k) + mf;
241
+ let t2 = dd / kf;
242
+ var kg: f32;
243
+ var dk: f32;
244
+ if (abs(t2) <= 0.5) {
245
+ kg = kf * log1p_minus(t2);
246
+ dk = 25.0 * E * abs(kg) + E;
247
+ } else {
248
+ let lr = log(mean / kf);
249
+ kg = kf * lr - dd;
250
+ dk = kf * (6.0 * E + LOG_ABS + 5.0 * E * abs(lr)) + 2.0 * E * abs(dd) + E + E * abs(kg);
251
+ }
252
+ let lnk = log(kf);
253
+ var corr: f32;
254
+ if (k < 256) { corr = aux[u32(k)]; } else { corr = 1.0 / (12.0 * kf) - 1.0 / (360.0 * kf * kf * kf); }
255
+ rhs = ((kg - 0.5 * lnk) - 0.91893853320467274) - corr;
256
+ dr = dk + 0.5 * LOG_ABS + 1.5 * E * lnk + 2.0 * E + 3.0 * E * (abs(kg) + 0.5 * lnk + 1.0) +
257
+ CPU_REL * (mean + kf * (abs(ln_mean) + lnk + 1.0));
258
+ }
259
+ check(lhs - rhs, MARGIN * (dl + dr));
260
+ if (lhs <= rhs) { return u32(k); }
261
+ }
262
+ uncertain = true;
263
+ return mi;
264
264
  }
265
265
  fn poisson(word: u32, frac: f32) -> u32 {
266
- let category = word >> 30u;
267
- let mi = word & 0x3fffffffu;
268
- if (category == 0u) { return 0u; }
269
- if (category == 1u) { return poisson_small(f32(mi) + frac); }
270
- if (category == 2u) { return poisson_ptrs(mi, frac); }
271
- // Means of 1e9 and above: the draw always exceeds the capacity (<= 2^24), so the code does not depend on it.
272
- let z = std_normal();
273
- let y = sqrt(f32(mi) + frac) * z + frac + 0.5;
274
- return u32(max(0, i32(mi) + i32(floor(y))));
275
- }
276
- // Adds product·2^s for an exact integer product < 2^24: the integer part by an integer shift, the fraction as
277
- // an exact float32 below 1 (the masked low bits times the exact power of two). No float split depends on order.
266
+ let category = word >> 30u;
267
+ let mi = word & 0x3fffffffu;
268
+ if (category == 0u) { return 0u; }
269
+ if (category == 1u) { return poisson_small(f32(mi) + frac); }
270
+ if (category == 2u) { return poisson_ptrs(mi, frac); }
271
+
272
+ let z = std_normal();
273
+ let y = sqrt(f32(mi) + frac) * z + frac + 0.5;
274
+ return u32(max(0, i32(mi) + i32(floor(y))));
275
+ }
276
+
277
+
278
278
  fn add_scaled(product: u32, s: i32, scale: f32) {
279
- if (product == 0u) { return; }
280
- // product·2^s is below 2^30 by the host's domain check, so the shift count is below 31.
281
- if (s >= 0) { ip = ip + i32(product << u32(s)); return; }
282
- if (s <= -24) { fp = fp + f32(product) * scale; return; }
283
- let shift = u32(-s);
284
- ip = ip + i32(product >> shift);
285
- fp = fp + f32(product & ((1u << shift) - 1u)) * scale;
286
- }
287
- // n·(p0 + p1 + p2 + p3) for an integer 0 <= n < 2^24 and pieces p_j = m_j·2^(s_j) with m_j < 2^12: the
288
- // products hi·m_j and lo·m_j are exact 32-bit integers.
279
+ if (product == 0u) { return; }
280
+
281
+ if (s >= 0) { ip = ip + i32(product << u32(s)); return; }
282
+ if (s <= -24) { fp = fp + f32(product) * scale; return; }
283
+ let shift = u32(-s);
284
+ ip = ip + i32(product >> shift);
285
+ fp = fp + f32(product & ((1u << shift) - 1u)) * scale;
286
+ }
287
+
288
+
289
289
  fn scaled(n: u32, base: u32) {
290
- let hi = n >> 12u;
291
- let lo = n & 0xfffu;
292
- for (var j = 0u; j < 4u; j = j + 1u) {
290
+ let hi = n >> 12u;
291
+ let lo = n & 0xfffu;
292
+ for (var j = 0u; j < 4u; j = j + 1u) {
293
293
  let m = u32(aux[base + ${SENSOR_PIECE_WORDS}u * j]);
294
294
  let s = i32(aux[base + ${SENSOR_PIECE_WORDS}u * j + 1u]);
295
295
  let scale = aux[base + ${SENSOR_PIECE_WORDS}u * j + 2u];
296
- add_scaled(hi * m, s + 12, scale * 4096.0);
297
- add_scaled(lo * m, s, scale);
298
- }
296
+ add_scaled(hi * m, s + 12, scale * 4096.0);
297
+ add_scaled(lo * m, s, scale);
298
+ }
299
299
  }
300
300
  fn normalize() {
301
- let f = floor(fp);
302
- ip = ip + i32(f);
303
- fp = fp - f;
301
+ let f = floor(fp);
302
+ ip = ip + i32(f);
303
+ fp = fp - f;
304
304
  }
305
- // Piecewise-linear INL offset with host-computed segment slopes (no division): ao + (code − ac)·slope.
305
+
306
306
  fn inl_offset(code: f32) -> f32 {
307
- let n = params.knots;
307
+ let n = params.knots;
308
308
  let base = ${SENSOR_AUX.knots}u;
309
- if (code <= aux[base]) { return aux[base + n]; }
310
- if (code >= aux[base + n - 1u]) { return aux[base + 2u * n - 1u]; }
311
- var low = 0u;
312
- var high = n - 1u;
313
- loop {
314
- if (high - low <= 1u) { break; }
315
- let middle = (low + high) >> 1u;
316
- if (aux[base + middle] <= code) { low = middle; } else { high = middle; }
317
- }
318
- return aux[base + n + low] + (code - aux[base + low]) * aux[base + 2u * n + low];
309
+ if (code <= aux[base]) { return aux[base + n]; }
310
+ if (code >= aux[base + n - 1u]) { return aux[base + 2u * n - 1u]; }
311
+ var low = 0u;
312
+ var high = n - 1u;
313
+ loop {
314
+ if (high - low <= 1u) { break; }
315
+ let middle = (low + high) >> 1u;
316
+ if (aux[base + middle] <= code) { low = middle; } else { high = middle; }
317
+ }
318
+ return aux[base + n + low] + (code - aux[base + low]) * aux[base + 2u * n + low];
319
319
  }
320
320
  @compute @workgroup_size(${SENSOR_WORKGROUP_SIZE})
321
321
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) groups: vec3<u32>) {
322
322
  let i = gid.x + gid.y * groups.x * ${SENSOR_WORKGROUP_SIZE}u;
323
- if (i >= params.count) { return; }
324
- let row = params.row0 + i / params.region_w;
325
- let index = (params.region_y + row) * params.native_w + params.region_x + i % params.region_w;
326
- uncertain = false;
327
- let base = i * params.site_words;
328
- start(site_seed(params.seed, index, 0u));
329
- let photo = poisson(means[base], bitcast<f32>(means[base + 1u]));
330
- let dark = poisson(means[base + 2u], bitcast<f32>(means[base + 3u]));
331
- // Saturating sums: any total above the clamp is above every supported capacity, as in the reference.
323
+ if (i >= params.count) { return; }
324
+ let row = params.row0 + i / params.region_w;
325
+ let index = (params.region_y + row) * params.native_w + params.region_x + i % params.region_w;
326
+ uncertain = false;
327
+ let base = i * params.site_words;
328
+ start(site_seed(params.seed, index, 0u));
329
+ let photo = poisson(means[base], bitcast<f32>(means[base + 1u]));
330
+ let dark = poisson(means[base + 2u], bitcast<f32>(means[base + 3u]));
331
+
332
332
  var realized = min(photo + dark, ${SENSOR_REALIZED_CLAMP}u);
333
- for (var k = 0u; k < params.poisson_count; k = k + 1u) {
333
+ for (var k = 0u; k < params.poisson_count; k = k + 1u) {
334
334
  realized = min(realized + poisson(means[base + 4u + 2u * k], bitcast<f32>(means[base + 5u + 2u * k])), ${SENSOR_REALIZED_CLAMP}u);
335
- }
336
- var cursor = base + 4u + 2u * params.poisson_count;
335
+ }
336
+ var cursor = base + 4u + 2u * params.poisson_count;
337
337
  let deterministic = (params.extras & ${SENSOR_EXTRAS.deterministic}u) != 0u;
338
338
  let pattern = (params.extras & ${SENSOR_EXTRAS.fixedPattern}u) != 0u;
339
- var r_frac = 0.0;
340
- var det_slack = 0.0;
341
- if (deterministic) {
339
+ var r_frac = 0.0;
340
+ var det_slack = 0.0;
341
+ if (deterministic) {
342
342
  realized = min(realized + means[cursor], ${SENSOR_REALIZED_CLAMP}u);
343
- r_frac = bitcast<f32>(means[cursor + 1u]);
344
- det_slack = E;
345
- cursor = cursor + 2u;
346
- }
347
- var t_floor = i32(params.threshold_floor);
348
- var t_frac = params.threshold_frac;
349
- var fpn_e_slack = 0.0;
350
- if (pattern) {
351
- t_floor = bitcast<i32>(means[cursor]);
352
- t_frac = bitcast<f32>(means[cursor + 1u]);
353
- fpn_e_slack = params.fpn_e_slack;
354
- }
355
- start(site_seed(params.seed, index, 1u));
356
- var noise = 0.0;
357
- var noise_bound = 0.0;
358
- for (var c = 0u; c < params.components; c = c + 1u) {
359
- let sigma = aux[256u + c];
360
- if (sigma != 0.0) {
361
- let z = std_normal();
362
- noise = noise + z * sigma;
363
- // z's error, sigma's rounding, the product, and the order-independent sum over the component count.
364
- noise_bound = noise_bound + sigma * radius * (Z_ERR + 2.0 + f32(params.components)) * E;
365
- }
366
- }
367
- var flags = 0u;
368
- var a_int = realized;
369
- var a_frac = r_frac;
370
- if (!deterministic) {
371
- if (realized > params.capacity_floor) { flags = 1u; a_int = params.capacity_floor; a_frac = params.capacity_frac; }
372
- } else {
373
- // realized > capacity with a non-integer total: a three-term float32 sum (any order) with its bound.
374
- let w = f32(i32(realized) - i32(params.capacity_floor));
375
- let dcap = w + (r_frac - params.capacity_frac);
376
- check(dcap, MARGIN * (3.0 * E * abs(w) + 6.0 * E + CPU_REL * (f32(realized) + 1.0)));
377
- if (dcap > 0.0) { flags = 1u; a_int = params.capacity_floor; a_frac = params.capacity_frac; }
378
- }
379
- // Pre-ADC limit: afterPhysical + noise + offset > threshold, as afterPhysical − (threshold − offset) + noise > 0,
380
- // a four-term float32 sum (any order).
381
- let whole = f32(i32(a_int) - t_floor);
382
- let diff = (whole + (a_frac - t_frac)) + noise;
383
- check(diff, MARGIN * (noise_bound + 4.0 * E * abs(whole) + 3.0 * E * abs(noise) + 8.0 * E + CPU_REL * (f32(a_int) + abs(noise)) +
384
- fpn_e_slack + det_slack));
385
- var q: i32;
386
- if (diff > 0.0) {
387
- flags = flags | 2u;
388
- q = params.sat_quantized;
389
- } else {
390
- // ideal = (afterPhysical + noise + offset)/gain + black, as an exact integer part plus a float32 fraction.
391
- ip = i32(params.black);
392
- fp = 0.0;
393
- var fpn_c_slack = 0.0;
394
- if (pattern) {
395
- ip = ip + bitcast<i32>(means[cursor + 2u]);
396
- fp = bitcast<f32>(means[cursor + 3u]);
397
- fpn_c_slack = params.fpn_c_slack;
398
- }
343
+ r_frac = bitcast<f32>(means[cursor + 1u]);
344
+ det_slack = E;
345
+ cursor = cursor + 2u;
346
+ }
347
+ var t_floor = i32(params.threshold_floor);
348
+ var t_frac = params.threshold_frac;
349
+ var fpn_e_slack = 0.0;
350
+ if (pattern) {
351
+ t_floor = bitcast<i32>(means[cursor]);
352
+ t_frac = bitcast<f32>(means[cursor + 1u]);
353
+ fpn_e_slack = params.fpn_e_slack;
354
+ }
355
+ start(site_seed(params.seed, index, 1u));
356
+ var noise = 0.0;
357
+ var noise_bound = 0.0;
358
+ for (var c = 0u; c < params.components; c = c + 1u) {
359
+ let sigma = aux[256u + c];
360
+ if (sigma != 0.0) {
361
+ let z = std_normal();
362
+ noise = noise + z * sigma;
363
+
364
+ noise_bound = noise_bound + sigma * radius * (Z_ERR + 2.0 + f32(params.components)) * E;
365
+ }
366
+ }
367
+ var flags = 0u;
368
+ var a_int = realized;
369
+ var a_frac = r_frac;
370
+ if (!deterministic) {
371
+ if (realized > params.capacity_floor) { flags = 1u; a_int = params.capacity_floor; a_frac = params.capacity_frac; }
372
+ } else {
373
+
374
+ let w = f32(i32(realized) - i32(params.capacity_floor));
375
+ let dcap = w + (r_frac - params.capacity_frac);
376
+ check(dcap, MARGIN * (3.0 * E * abs(w) + 6.0 * E + CPU_REL * (f32(realized) + 1.0)));
377
+ if (dcap > 0.0) { flags = 1u; a_int = params.capacity_floor; a_frac = params.capacity_frac; }
378
+ }
379
+
380
+
381
+ let whole = f32(i32(a_int) - t_floor);
382
+ let diff = (whole + (a_frac - t_frac)) + noise;
383
+ check(diff, MARGIN * (noise_bound + 4.0 * E * abs(whole) + 3.0 * E * abs(noise) + 8.0 * E + CPU_REL * (f32(a_int) + abs(noise)) +
384
+ fpn_e_slack + det_slack));
385
+ var q: i32;
386
+ if (diff > 0.0) {
387
+ flags = flags | 2u;
388
+ q = params.sat_quantized;
389
+ } else {
390
+
391
+ ip = i32(params.black);
392
+ fp = 0.0;
393
+ var fpn_c_slack = 0.0;
394
+ if (pattern) {
395
+ ip = ip + bitcast<i32>(means[cursor + 2u]);
396
+ fp = bitcast<f32>(means[cursor + 3u]);
397
+ fpn_c_slack = params.fpn_c_slack;
398
+ }
399
399
  scaled(a_int, ${SENSOR_AUX.inversePieces}u);
400
- let noise_codes = noise * params.inv_gain;
401
- let frac_codes = a_frac * params.inv_gain;
402
- fp = fp + frac_codes + noise_codes;
403
- let a_codes = f32(a_int) * params.inv_gain;
404
- // The fraction sum (eleven terms below 1, frac_codes and noise_codes), the two normalizations and the
405
- // half-code comparison in any association: 13E·Σ|terms|; then the term errors, the pieces' residual,
406
- // the reference's float64 rounding and the packed fractions' rounding.
407
- var margin = MARGIN * (13.0 * E * (13.0 + frac_codes + abs(noise_codes)) + 3.0 * E * frac_codes + 2.0 * E * abs(noise_codes) +
408
- noise_bound * params.inv_gain + f32(a_int) * params.inv_residual +
409
- CPU_REL * (a_codes + abs(noise_codes) + f32(params.black)) + fpn_c_slack + det_slack * params.inv_gain);
410
- normalize();
411
- if (params.knots > 0u) {
412
- let ideal = f32(ip) + fp;
413
- let offset = inl_offset(ideal);
414
- fp = fp + offset;
415
- margin = margin * (1.0 + params.inl_slope) + MARGIN * (params.inl_slope * 2.0 * E * (abs(ideal) + 1.0 + params.inl_knot) +
416
- 11.0 * E * (params.inl_offset + 1.0) + CPU_REL * (abs(ideal) + params.inl_offset) * (1.0 + params.inl_slope));
417
- normalize();
418
- }
419
- if ((params.digital & 4u) == 0u) { check(fp - 0.5, margin); }
420
- q = ip + select(0, 1, fp >= 0.5);
421
- }
422
- if ((params.digital & 1u) != 0u) {
423
- let adc = clamp(q, 0, i32(params.adc_max));
424
- let delta = adc - i32(params.black);
425
- let estimate = f32(delta) * params.digital_gain;
426
- if (estimate >= 16777216.0) {
427
- q = 16777216;
428
- } else if (estimate <= -16777216.0) {
429
- q = -16777216;
430
- } else {
431
- ip = 0;
432
- fp = 0.0;
400
+ let noise_codes = noise * params.inv_gain;
401
+ let frac_codes = a_frac * params.inv_gain;
402
+ fp = fp + frac_codes + noise_codes;
403
+ let a_codes = f32(a_int) * params.inv_gain;
404
+
405
+
406
+
407
+ var margin = MARGIN * (13.0 * E * (13.0 + frac_codes + abs(noise_codes)) + 3.0 * E * frac_codes + 2.0 * E * abs(noise_codes) +
408
+ noise_bound * params.inv_gain + f32(a_int) * params.inv_residual +
409
+ CPU_REL * (a_codes + abs(noise_codes) + f32(params.black)) + fpn_c_slack + det_slack * params.inv_gain);
410
+ normalize();
411
+ if (params.knots > 0u) {
412
+ let ideal = f32(ip) + fp;
413
+ let offset = inl_offset(ideal);
414
+ fp = fp + offset;
415
+ margin = margin * (1.0 + params.inl_slope) + MARGIN * (params.inl_slope * 2.0 * E * (abs(ideal) + 1.0 + params.inl_knot) +
416
+ 11.0 * E * (params.inl_offset + 1.0) + CPU_REL * (abs(ideal) + params.inl_offset) * (1.0 + params.inl_slope));
417
+ normalize();
418
+ }
419
+ if ((params.digital & 4u) == 0u) { check(fp - 0.5, margin); }
420
+ q = ip + select(0, 1, fp >= 0.5);
421
+ }
422
+ if ((params.digital & 1u) != 0u) {
423
+ let adc = clamp(q, 0, i32(params.adc_max));
424
+ let delta = adc - i32(params.black);
425
+ let estimate = f32(delta) * params.digital_gain;
426
+ if (estimate >= 16777216.0) {
427
+ q = 16777216;
428
+ } else if (estimate <= -16777216.0) {
429
+ q = -16777216;
430
+ } else {
431
+ ip = 0;
432
+ fp = 0.0;
433
433
  scaled(u32(abs(delta)), ${SENSOR_AUX.digitalPieces}u);
434
- // Eight exact fractions summed in any order, the floor split and the half comparison: ≤ 10 roundings on
435
- // values below 9; the pieces' residual; the reference's float64 product.
436
- if ((params.digital & 2u) == 0u) { check(fp - floor(fp) - 0.5, MARGIN * (90.0 * E + f32(abs(delta)) * params.digital_residual + CPU_REL * (abs(estimate) + f32(params.black)))); }
437
- normalize();
438
- var whole_part = ip;
439
- var fraction = fp;
440
- if (delta < 0) {
441
- whole_part = -ip;
442
- if (fp > 0.0) { whole_part = -ip - 1; fraction = 1.0 - fp; }
443
- }
444
- q = whole_part + i32(params.black) + select(0, 1, fraction >= 0.5);
445
- }
446
- }
447
- if (q >= i32(params.saturation)) { flags = flags | 4u; }
448
- if (q < 0) { flags = flags | 8u; }
449
- let code = u32(clamp(q, 0, i32(params.saturation)));
434
+
435
+
436
+ if ((params.digital & 2u) == 0u) { check(fp - floor(fp) - 0.5, MARGIN * (90.0 * E + f32(abs(delta)) * params.digital_residual + CPU_REL * (abs(estimate) + f32(params.black)))); }
437
+ normalize();
438
+ var whole_part = ip;
439
+ var fraction = fp;
440
+ if (delta < 0) {
441
+ whole_part = -ip;
442
+ if (fp > 0.0) { whole_part = -ip - 1; fraction = 1.0 - fp; }
443
+ }
444
+ q = whole_part + i32(params.black) + select(0, 1, fraction >= 0.5);
445
+ }
446
+ }
447
+ if (q >= i32(params.saturation)) { flags = flags | 4u; }
448
+ if (q < 0) { flags = flags | 8u; }
449
+ let code = u32(clamp(q, 0, i32(params.saturation)));
450
450
  out[i] = code | (flags << 16u) | select(0u, ${1 << 20}u, uncertain);
451
451
  }
452
452
  `;