@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
@@ -42,7 +42,7 @@ export const DISPLAY_VIEW_OUTPUT_FLOATS = 11;
42
42
  /** Floats per column or row record of the geometry buffer. */
43
43
  export const DISPLAY_VIEW_GEOMETRY_STRIDE = 20;
44
44
  /** Layout of the aux table (float32 words). */
45
- export const DISPLAY_VIEW_AUX = Object.freeze({
45
+ export const DISPLAY_VIEW_AUX = /* @__PURE__ */ Object.freeze({
46
46
  /** Per offset o: ou (0.5 + ox − W/2), ov (0.5 + oy − H/2), weight, 0. */
47
47
  offsets: 0,
48
48
  /** Per phase p, channel c, tap t: dx, dy, weight, 0 (4 taps; unused taps have weight 0). */
@@ -86,16 +86,16 @@ const CLIP_VARIANCE_U: f32 = ${literal(DISPLAY_VIEW_CLIP_VARIANCE_U)};
86
86
  const WINDOW: u32 = ${DISPLAY_VIEW_GPU_WINDOW_SIDE}u;
87
87
  const STRIDE: u32 = ${DISPLAY_VIEW_GEOMETRY_STRIDE}u;
88
88
  struct Params {
89
- count: u32, row0: u32, region_w: u32, region_x: u32,
90
- region_y: u32, margin: u32, native_w: u32, native_h: u32,
91
- columns: u32, tone: u32, offsets: u32, pad0: u32,
92
- k0: f32, k1: f32, k2: f32, reach2: f32,
93
- dark0: f32, dark1: f32, dark2: f32, clip_level: f32,
94
- c0_0: f32, c0_1: f32, c0_2: f32, pad1: f32,
95
- g0: f32, g1: f32, g2: f32, pad2: f32,
96
- m00: f32, m01: f32, m02: f32, m10: f32,
97
- m11: f32, m12: f32, m20: f32, m21: f32,
98
- m22: f32, scale_u: f32, scale_v: f32, pad3: f32,
89
+ count: u32, row0: u32, region_w: u32, region_x: u32,
90
+ region_y: u32, margin: u32, native_w: u32, native_h: u32,
91
+ columns: u32, tone: u32, offsets: u32, pad0: u32,
92
+ k0: f32, k1: f32, k2: f32, reach2: f32,
93
+ dark0: f32, dark1: f32, dark2: f32, clip_level: f32,
94
+ c0_0: f32, c0_1: f32, c0_2: f32, pad1: f32,
95
+ g0: f32, g1: f32, g2: f32, pad2: f32,
96
+ m00: f32, m01: f32, m02: f32, m10: f32,
97
+ m11: f32, m12: f32, m20: f32, m21: f32,
98
+ m22: f32, scale_u: f32, scale_v: f32, pad3: f32,
99
99
  };
100
100
  @group(0) @binding(0) var<uniform> params: Params;
101
101
  @group(0) @binding(1) var<storage, read> aux: array<f32>;
@@ -114,269 +114,269 @@ fn dark_of(c: u32) -> f32 { if (c == 0u) { return params.dark0; } if (c == 1u) {
114
114
  fn c0_of(c: u32) -> f32 { if (c == 0u) { return params.c0_0; } if (c == 1u) { return params.c0_1; } return params.c0_2; }
115
115
  fn g_of(c: u32) -> f32 { if (c == 0u) { return params.g0; } if (c == 1u) { return params.g1; } return params.g2; }
116
116
  fn m_of(r: u32, c: u32) -> f32 {
117
- let i = r * 3u + c;
118
- switch (i) {
119
- case 0u: { return params.m00; } case 1u: { return params.m01; } case 2u: { return params.m02; }
120
- case 3u: { return params.m10; } case 4u: { return params.m11; } case 5u: { return params.m12; }
121
- case 6u: { return params.m20; } case 7u: { return params.m21; } default: { return params.m22; }
122
- }
117
+ let i = r * 3u + c;
118
+ switch (i) {
119
+ case 0u: { return params.m00; } case 1u: { return params.m01; } case 2u: { return params.m02; }
120
+ case 3u: { return params.m10; } case 4u: { return params.m11; } case 5u: { return params.m12; }
121
+ case 6u: { return params.m20; } case 7u: { return params.m21; } default: { return params.m22; }
123
122
  }
124
- // Standard normal density, as the reference forms it: 0.3989422804014327·exp(−0.5·z·z).
123
+ }
124
+
125
125
  fn phi(z: f32) -> f32 { return 0.3989422804014327 * exp(-0.5 * z * z); }
126
- // Abramowitz & Stegun 26.2.17: returns (Φ(z), 1 − Φ(z)), each formed from the tail q directly where it is small.
126
+
127
127
  fn pq(z: f32) -> vec2<f32> {
128
- let x = abs(z);
129
- let t = 1.0 / (1.0 + 0.2316419 * x);
130
- let q = phi(x) * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
131
- if (z >= 0.0) { return vec2<f32>(1.0 - q, q); }
132
- return vec2<f32>(q, 1.0 - q);
128
+ let x = abs(z);
129
+ let t = 1.0 / (1.0 + 0.2316419 * x);
130
+ let q = phi(x) * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
131
+ if (z >= 0.0) { return vec2<f32>(1.0 - q, q); }
132
+ return vec2<f32>(q, 1.0 - q);
133
133
  }
134
- // Moments of min(Y, top) for Y ~ N(m, v): (mean, variance, slope P). The variance is v·(P − zφ + z²Q − δ²) with
135
- // δ = φ − zQ: algebraically E[min²] − E[min]² without the m² cancellation.
134
+
135
+
136
136
  fn upper_clip(m: f32, v: f32, top: f32) -> vec3<f32> {
137
- if (!(v > 0.0)) { return vec3<f32>(min(m, top), 0.0, select(0.0, 1.0, m < top)); }
138
- let s = sqrt(v);
139
- let z = (top - m) / s;
140
- if (z > CLIP_REACH) { return vec3<f32>(m, v, 1.0); }
141
- if (z < -CLIP_REACH) { return vec3<f32>(top, 0.0, 0.0); }
142
- let p = pq(z);
143
- let d = phi(z);
144
- let delta = d - z * p.y;
145
- let mean = m - s * delta;
146
- let ratio = p.x - z * d + z * z * p.y - delta * delta;
147
- return vec3<f32>(mean, v * max(0.0, ratio), p.x);
137
+ if (!(v > 0.0)) { return vec3<f32>(min(m, top), 0.0, select(0.0, 1.0, m < top)); }
138
+ let s = sqrt(v);
139
+ let z = (top - m) / s;
140
+ if (z > CLIP_REACH) { return vec3<f32>(m, v, 1.0); }
141
+ if (z < -CLIP_REACH) { return vec3<f32>(top, 0.0, 0.0); }
142
+ let p = pq(z);
143
+ let d = phi(z);
144
+ let delta = d - z * p.y;
145
+ let mean = m - s * delta;
146
+ let ratio = p.x - z * d + z * z * p.y - delta * delta;
147
+ return vec3<f32>(mean, v * max(0.0, ratio), p.x);
148
148
  }
149
- // Moments of clip(Y, 0, 1) for Y with mean m, variance v and standardized skewness γ (|γ| ≤ 1): (mean, variance, inside).
150
- // With Z standard normal, W = m + s·g(Z), g = clip(Z, a, b): E[g] = aΦa + φa − φb + bQb, E[g²] = a²Φa + (Φb − Φa) −
151
- // (bφb − aφa) + b²Qb; the mean adds the reference's Edgeworth term (s·γ/6)·(aφa − bφb).
149
+
150
+
151
+
152
152
  fn unit_clip(m: f32, v: f32, skewness: f32) -> vec3<f32> {
153
- if (!(v > 0.0)) { return vec3<f32>(clamp(m, 0.0, 1.0), 0.0, select(0.0, 1.0, m > 0.0 && m < 1.0)); }
154
- let s = sqrt(v);
155
- let a = -m / s;
156
- let b = (1.0 - m) / s;
157
- if (a < -CLIP_REACH && b > CLIP_REACH) { return vec3<f32>(m, v, 1.0); }
158
- var pa = 0.0; var da = 0.0;
159
- if (!(a < -CLIP_REACH)) { pa = pq(a).x; da = phi(a); }
160
- var pb = 1.0; var qb = 0.0; var db = 0.0;
161
- if (!(b > CLIP_REACH)) { let p = pq(b); pb = p.x; qb = p.y; db = phi(b); }
162
- let inside = pb - pa;
163
- let gamma = clamp(skewness, -1.0, 1.0);
164
- let aa = a * da;
165
- let bb = b * db;
166
- let eg = a * pa + (da - db) + b * qb;
167
- let eg2 = a * a * pa + inside - (bb - aa) + b * b * qb;
168
- let normal_mean = m + s * eg;
169
- var mean = normal_mean;
170
- var skew_shift = 0.0;
171
- if (gamma != 0.0) { skew_shift = s * gamma / 6.0 * (aa - bb); mean = normal_mean + skew_shift; }
172
- // The reference forms E[Y²] − (E[Y] + shift)², so the shift also enters its variance: −shift·(2·E[Y] + shift).
173
- return vec3<f32>(mean, max(0.0, v * (eg2 - eg * eg) - skew_shift * (2.0 * normal_mean + skew_shift)), inside);
153
+ if (!(v > 0.0)) { return vec3<f32>(clamp(m, 0.0, 1.0), 0.0, select(0.0, 1.0, m > 0.0 && m < 1.0)); }
154
+ let s = sqrt(v);
155
+ let a = -m / s;
156
+ let b = (1.0 - m) / s;
157
+ if (a < -CLIP_REACH && b > CLIP_REACH) { return vec3<f32>(m, v, 1.0); }
158
+ var pa = 0.0; var da = 0.0;
159
+ if (!(a < -CLIP_REACH)) { pa = pq(a).x; da = phi(a); }
160
+ var pb = 1.0; var qb = 0.0; var db = 0.0;
161
+ if (!(b > CLIP_REACH)) { let p = pq(b); pb = p.x; qb = p.y; db = phi(b); }
162
+ let inside = pb - pa;
163
+ let gamma = clamp(skewness, -1.0, 1.0);
164
+ let aa = a * da;
165
+ let bb = b * db;
166
+ let eg = a * pa + (da - db) + b * qb;
167
+ let eg2 = a * a * pa + inside - (bb - aa) + b * b * qb;
168
+ let normal_mean = m + s * eg;
169
+ var mean = normal_mean;
170
+ var skew_shift = 0.0;
171
+ if (gamma != 0.0) { skew_shift = s * gamma / 6.0 * (aa - bb); mean = normal_mean + skew_shift; }
172
+
173
+ return vec3<f32>(mean, max(0.0, v * (eg2 - eg * eg) - skew_shift * (2.0 * normal_mean + skew_shift)), inside);
174
174
  }
175
- // Positive Reinhard per channel then the clip to [0, 1]: h(x) = x/(1 + x) for x > 0, else 0. Composite 16-point
176
- // Gauss–Legendre over [max(0, m − 8.5s), m + 8.5s] in 4 panels, as the reference; the variance is formed about h(m).
175
+
176
+
177
177
  fn reinhard_moments(m: f32, v: f32) -> vec3<f32> {
178
- if (!(v > 0.0)) {
179
- if (m > 0.0) { return vec3<f32>(m / (1.0 + m), 0.0, 1.0 / ((1.0 + m) * (1.0 + m))); }
180
- return vec3<f32>(0.0, 0.0, 0.0);
181
- }
182
- let s = sqrt(v);
183
- // The reference's panels [max(0, m − 8.5s), m + 8.5s] in standardized units z = (x − m)/s, so the node's z is formed
184
- // without the cancellation x − m: zlo = max(−m/s, −8.5), weight = zhalf·w_k·φ(z_k) (= half·w_k·φ((x − m)/s)/s), x = m + s·z.
185
- let zlo = max(-m / s, -CLIP_REACH);
186
- if (!(CLIP_REACH > zlo)) { return vec3<f32>(0.0, 0.0, 0.0); }
187
- let hm = select(0.0, m / (1.0 + m), m > 0.0);
188
- var e1 = 0.0; var e2 = 0.0; var ed = 0.0;
189
- let zwidth = (CLIP_REACH - zlo) / 4.0;
190
- for (var p = 0u; p < 4u; p = p + 1u) {
191
- let zc = zlo + (f32(p) + 0.5) * zwidth;
192
- let zhalf = zwidth / 2.0;
193
- for (var k = 0u; k < 16u; k = k + 1u) {
178
+ if (!(v > 0.0)) {
179
+ if (m > 0.0) { return vec3<f32>(m / (1.0 + m), 0.0, 1.0 / ((1.0 + m) * (1.0 + m))); }
180
+ return vec3<f32>(0.0, 0.0, 0.0);
181
+ }
182
+ let s = sqrt(v);
183
+
184
+
185
+ let zlo = max(-m / s, -CLIP_REACH);
186
+ if (!(CLIP_REACH > zlo)) { return vec3<f32>(0.0, 0.0, 0.0); }
187
+ let hm = select(0.0, m / (1.0 + m), m > 0.0);
188
+ var e1 = 0.0; var e2 = 0.0; var ed = 0.0;
189
+ let zwidth = (CLIP_REACH - zlo) / 4.0;
190
+ for (var p = 0u; p < 4u; p = p + 1u) {
191
+ let zc = zlo + (f32(p) + 0.5) * zwidth;
192
+ let zhalf = zwidth / 2.0;
193
+ for (var k = 0u; k < 16u; k = k + 1u) {
194
194
  let z = zc + zhalf * aux[${DISPLAY_VIEW_AUX.gaussLegendre}u + k];
195
- let x = m + s * z;
195
+ let x = m + s * z;
196
196
  let weight = zhalf * aux[${DISPLAY_VIEW_AUX.gaussLegendre + 16}u + k] * phi(z);
197
- let h = x / (1.0 + x);
198
- e1 = e1 + weight * h;
199
- e2 = e2 + weight * (h - hm) * (h - hm);
200
- ed = ed + weight / ((1.0 + x) * (1.0 + x));
201
- }
202
- }
203
- let shifted = e1 - hm;
204
- return vec3<f32>(e1, max(0.0, e2 - shifted * shifted), ed);
197
+ let h = x / (1.0 + x);
198
+ e1 = e1 + weight * h;
199
+ e2 = e2 + weight * (h - hm) * (h - hm);
200
+ ed = ed + weight / ((1.0 + x) * (1.0 + x));
201
+ }
202
+ }
203
+ let shifted = e1 - hm;
204
+ return vec3<f32>(e1, max(0.0, e2 - shifted * shifted), ed);
205
205
  }
206
206
  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; }
207
207
  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; }
208
208
  fn channel_of(x: i32, y: i32) -> u32 {
209
- if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }
210
- return select(2u, 1u, (x & 1) == 0);
209
+ if ((y & 1) == 0) { return select(1u, 0u, (x & 1) == 0); }
210
+ return select(2u, 1u, (x & 1) == 0);
211
211
  }
212
- // The expected photo electrons of native site (x, y): Σ_o w_o·table[m_o, c] in offset order (directSite).
212
+
213
213
  fn site_electrons(x: i32, y: i32, c: u32) -> f32 {
214
- var sum = 0.0;
215
- let fx = f32(x);
216
- let fy = f32(y);
217
- for (var o = 0u; o < params.offsets; o = o + 1u) {
214
+ var sum = 0.0;
215
+ let fx = f32(x);
216
+ let fy = f32(y);
217
+ for (var o = 0u; o < params.offsets; o = o + 1u) {
218
218
  let base = ${DISPLAY_VIEW_AUX.offsets}u + 4u * o;
219
- let u = (fx + aux[base]) * params.scale_u;
220
- let v = (fy + aux[base + 1u]) * params.scale_v;
221
- let m = material_at(vec2<f32>(u, v));
222
- sum = sum + aux[base + 2u] * site_table[m * 3u + c];
223
- }
224
- return sum;
219
+ let u = (fx + aux[base]) * params.scale_u;
220
+ let v = (fy + aux[base + 1u]) * params.scale_v;
221
+ let m = material_at(vec2<f32>(u, v));
222
+ sum = sum + aux[base + 2u] * site_table[m * 3u + c];
223
+ }
224
+ return sum;
225
225
  }
226
226
  @compute @workgroup_size(${DISPLAY_VIEW_WORKGROUP_SIZE})
227
227
  fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) groups: vec3<u32>) {
228
228
  let i = gid.x + gid.y * groups.x * ${DISPLAY_VIEW_WORKGROUP_SIZE}u;
229
- if (i >= params.count) { return; }
230
- material_uncertain = false;
231
- let rx = i % params.region_w;
232
- let ry = params.row0 + i / params.region_w;
233
- let col = rx * STRIDE;
234
- let row = (params.columns + ry) * STRIDE;
235
- let col_first = bitcast<i32>(geometry[col]);
236
- let nx = bitcast<u32>(geometry[col + 1u]);
237
- let row_first = bitcast<i32>(geometry[row]);
238
- let ny = bitcast<u32>(geometry[row + 1u]);
239
- let margin = i32(params.margin);
240
- // 2. and 3.: the sites of the footprint plus one site of reach, once each.
241
- let x0 = col_first + margin - 1;
242
- let y0 = row_first + margin - 1;
243
- let window_w = nx + 2u;
244
- for (var sj = 0u; sj < ny + 2u; sj = sj + 1u) {
245
- let sy = reflect_y(y0 + i32(sj));
246
- for (var si = 0u; si < window_w; si = si + 1u) {
247
- let sx = reflect_x(x0 + i32(si));
248
- let c = channel_of(sx, sy);
249
- let e = site_electrons(sx, sy, c) + dark_of(c);
250
- let kc = k_of(c);
251
- let mean = kc * e;
252
- let variance = kc * kc * e + c0_of(c);
253
- let gap = 1.0 - mean;
254
- let slot = sj * window_w + si;
255
- site_third[slot] = kc * kc * kc * e;
256
- if (gap > 0.0 && gap * gap > params.reach2 * variance) {
257
- site_mean[slot] = mean; site_var[slot] = variance; site_bound[slot] = 0.0;
258
- } else {
259
- let clipped = upper_clip(mean, variance, 1.0);
260
- site_mean[slot] = clipped.x; site_var[slot] = clipped.y; site_bound[slot] = sqrt(variance);
261
- }
262
- }
263
- }
264
- // B_c of the footprint from the host's axis sums (each over its scale squared): red, green, blue.
265
- let xb_even = geometry[col + 8u]; let xb_odd = geometry[col + 9u];
266
- let xpp_even = geometry[col + 10u]; let xpp_odd = geometry[col + 11u];
267
- let xqq_even = geometry[col + 12u]; let xqq_odd = geometry[col + 13u];
268
- let xpq_even = geometry[col + 14u]; let xpq_odd = geometry[col + 15u];
269
- let yb_even = geometry[row + 8u]; let yb_odd = geometry[row + 9u];
270
- let ypp_even = geometry[row + 10u]; let ypp_odd = geometry[row + 11u];
271
- let yqq_even = geometry[row + 12u]; let yqq_odd = geometry[row + 13u];
272
- let ypq_even = geometry[row + 14u]; let ypq_odd = geometry[row + 15u];
273
- let b0 = xb_even * yb_even;
274
- 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) +
275
- (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);
276
- let b2 = xb_odd * yb_odd;
277
- let reinhard = params.tone == 1u;
278
- var s0 = 0.0; var s1 = 0.0; var s2 = 0.0;
279
- var c00 = 0.0; var c01 = 0.0; var c02 = 0.0; var c11 = 0.0; var c12 = 0.0; var c22 = 0.0;
280
- var r0 = 0.0; var r1 = 0.0; var r2 = 0.0;
281
- var bound_mean = 0.0; var bound_var = 0.0;
282
- // 4. and 5.: every real pixel of the footprint with its overlap weight.
283
- for (var j = 0u; j < ny; j = j + 1u) {
284
- let ay = geometry[row + 2u + j];
285
- for (var ii = 0u; ii < nx; ii = ii + 1u) {
286
- let aj = geometry[col + 2u + ii] * ay;
287
- let x = col_first + i32(ii) + margin;
288
- let y = row_first + i32(j) + margin;
289
- let phase = u32(y & 1) * 2u + u32(x & 1);
290
- var cm: array<f32, 3>; var cvv: array<f32, 3>; var cth: array<f32, 3>; var csv: array<f32, 3>; var cb: array<f32, 3>;
291
- for (var c = 0u; c < 3u; c = c + 1u) {
292
- var mean = 0.0; var variance = 0.0; var third = 0.0; var bnd = 0.0;
229
+ if (i >= params.count) { return; }
230
+ material_uncertain = false;
231
+ let rx = i % params.region_w;
232
+ let ry = params.row0 + i / params.region_w;
233
+ let col = rx * STRIDE;
234
+ let row = (params.columns + ry) * STRIDE;
235
+ let col_first = bitcast<i32>(geometry[col]);
236
+ let nx = bitcast<u32>(geometry[col + 1u]);
237
+ let row_first = bitcast<i32>(geometry[row]);
238
+ let ny = bitcast<u32>(geometry[row + 1u]);
239
+ let margin = i32(params.margin);
240
+
241
+ let x0 = col_first + margin - 1;
242
+ let y0 = row_first + margin - 1;
243
+ let window_w = nx + 2u;
244
+ for (var sj = 0u; sj < ny + 2u; sj = sj + 1u) {
245
+ let sy = reflect_y(y0 + i32(sj));
246
+ for (var si = 0u; si < window_w; si = si + 1u) {
247
+ let sx = reflect_x(x0 + i32(si));
248
+ let c = channel_of(sx, sy);
249
+ let e = site_electrons(sx, sy, c) + dark_of(c);
250
+ let kc = k_of(c);
251
+ let mean = kc * e;
252
+ let variance = kc * kc * e + c0_of(c);
253
+ let gap = 1.0 - mean;
254
+ let slot = sj * window_w + si;
255
+ site_third[slot] = kc * kc * kc * e;
256
+ if (gap > 0.0 && gap * gap > params.reach2 * variance) {
257
+ site_mean[slot] = mean; site_var[slot] = variance; site_bound[slot] = 0.0;
258
+ } else {
259
+ let clipped = upper_clip(mean, variance, 1.0);
260
+ site_mean[slot] = clipped.x; site_var[slot] = clipped.y; site_bound[slot] = sqrt(variance);
261
+ }
262
+ }
263
+ }
264
+
265
+ let xb_even = geometry[col + 8u]; let xb_odd = geometry[col + 9u];
266
+ let xpp_even = geometry[col + 10u]; let xpp_odd = geometry[col + 11u];
267
+ let xqq_even = geometry[col + 12u]; let xqq_odd = geometry[col + 13u];
268
+ let xpq_even = geometry[col + 14u]; let xpq_odd = geometry[col + 15u];
269
+ let yb_even = geometry[row + 8u]; let yb_odd = geometry[row + 9u];
270
+ let ypp_even = geometry[row + 10u]; let ypp_odd = geometry[row + 11u];
271
+ let yqq_even = geometry[row + 12u]; let yqq_odd = geometry[row + 13u];
272
+ let ypq_even = geometry[row + 14u]; let ypq_odd = geometry[row + 15u];
273
+ let b0 = xb_even * yb_even;
274
+ 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) +
275
+ (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);
276
+ let b2 = xb_odd * yb_odd;
277
+ let reinhard = params.tone == 1u;
278
+ var s0 = 0.0; var s1 = 0.0; var s2 = 0.0;
279
+ var c00 = 0.0; var c01 = 0.0; var c02 = 0.0; var c11 = 0.0; var c12 = 0.0; var c22 = 0.0;
280
+ var r0 = 0.0; var r1 = 0.0; var r2 = 0.0;
281
+ var bound_mean = 0.0; var bound_var = 0.0;
282
+
283
+ for (var j = 0u; j < ny; j = j + 1u) {
284
+ let ay = geometry[row + 2u + j];
285
+ for (var ii = 0u; ii < nx; ii = ii + 1u) {
286
+ let aj = geometry[col + 2u + ii] * ay;
287
+ let x = col_first + i32(ii) + margin;
288
+ let y = row_first + i32(j) + margin;
289
+ let phase = u32(y & 1) * 2u + u32(x & 1);
290
+ var cm: array<f32, 3>; var cvv: array<f32, 3>; var cth: array<f32, 3>; var csv: array<f32, 3>; var cb: array<f32, 3>;
291
+ for (var c = 0u; c < 3u; c = c + 1u) {
292
+ var mean = 0.0; var variance = 0.0; var third = 0.0; var bnd = 0.0;
293
293
  let taps = ${DISPLAY_VIEW_AUX.taps}u + (phase * 3u + c) * 16u;
294
- for (var t = 0u; t < 4u; t = t + 1u) {
295
- let w = aux[taps + 4u * t + 2u];
296
- let slot = u32((i32(j) + 1 + i32(aux[taps + 4u * t + 1u])) * i32(window_w) + i32(ii) + 1 + i32(aux[taps + 4u * t]));
297
- mean = mean + w * site_mean[slot];
298
- variance = variance + w * w * site_var[slot];
299
- third = third + w * w * w * site_third[slot];
300
- bnd = max(bnd, select(0.0, site_bound[slot], w != 0.0));
301
- }
302
- cm[c] = mean; cvv[c] = variance; cth[c] = third; cb[c] = bnd;
294
+ for (var t = 0u; t < 4u; t = t + 1u) {
295
+ let w = aux[taps + 4u * t + 2u];
296
+ let slot = u32((i32(j) + 1 + i32(aux[taps + 4u * t + 1u])) * i32(window_w) + i32(ii) + 1 + i32(aux[taps + 4u * t]));
297
+ mean = mean + w * site_mean[slot];
298
+ variance = variance + w * w * site_var[slot];
299
+ third = third + w * w * w * site_third[slot];
300
+ bnd = max(bnd, select(0.0, site_bound[slot], w != 0.0));
301
+ }
302
+ cm[c] = mean; cvv[c] = variance; cth[c] = third; cb[c] = bnd;
303
303
  csv[c] = variance * aux[${DISPLAY_VIEW_AUX.inverseW2}u + phase * 3u + c];
304
- }
305
- // White balance and the highlight clip at the smallest gain: bm, bv, bs (slope), with the clip's bound terms.
306
- var bm: array<f32, 3>; var bv: array<f32, 3>; var bs: array<f32, 3>; var bbm: array<f32, 3>; var bbv: array<f32, 3>;
307
- for (var c = 0u; c < 3u; c = c + 1u) {
308
- let gc = g_of(c);
309
- let um = gc * cm[c];
310
- let uv = gc * gc * cvv[c];
311
- let gap = params.clip_level - um;
312
- bbm[c] = gc * CLIP_MEAN_U * cb[c];
313
- bbv[c] = gc * gc * CLIP_VARIANCE_U * cb[c] * cb[c];
314
- if (gap > 0.0 && gap * gap > params.reach2 * uv) { bm[c] = um; bv[c] = uv; bs[c] = gc; }
315
- else {
316
- let clipped = upper_clip(um, uv, params.clip_level);
317
- bm[c] = clipped.x; bv[c] = clipped.y; bs[c] = gc * clipped.z;
318
- bbm[c] = bbm[c] + CLIP_MEAN_U * sqrt(uv);
319
- bbv[c] = bbv[c] + CLIP_VARIANCE_U * uv;
320
- }
321
- }
322
- // Colour matrix, then the rendering of each component (tone curve, clip to [0, 1]).
323
- var hm: array<f32, 3>; var hv: array<f32, 3>; var hs: array<f32, 3>; var hbm: array<f32, 3>; var hbv: array<f32, 3>;
324
- for (var comp = 0u; comp < 3u; comp = comp + 1u) {
325
- let r0m = m_of(comp, 0u); let r1m = m_of(comp, 1u); let r2m = m_of(comp, 2u);
326
- let mean = r0m * bm[0] + r1m * bm[1] + r2m * bm[2];
327
- let variance = r0m * r0m * bv[0] + r1m * r1m * bv[1] + r2m * r2m * bv[2];
328
- var bm_c = abs(r0m) * bbm[0] + abs(r1m) * bbm[1] + abs(r2m) * bbm[2];
329
- var bv_c = r0m * r0m * bbv[0] + r1m * r1m * bbv[1] + r2m * r2m * bbv[2];
330
- if (!reinhard && mean > 0.0 && mean < 1.0 && mean * mean > params.reach2 * variance && (1.0 - mean) * (1.0 - mean) > params.reach2 * variance) {
331
- hm[comp] = mean; hv[comp] = variance; hs[comp] = 1.0;
332
- } else if (reinhard) {
333
- let moments = reinhard_moments(mean, variance);
334
- hm[comp] = moments.x; hv[comp] = moments.y; hs[comp] = moments.z;
335
- bm_c = bm_c + 2.0 * CLIP_MEAN_U * sqrt(variance);
336
- bv_c = bv_c + 2.0 * CLIP_VARIANCE_U * variance;
337
- } else {
338
- // κ₃ of the component: Σ_c (M_comp,c·Γ_c)³·Σ_t w_t³·κ₃(site) (the sites of one channel share the coefficient).
339
- let k0 = r0m * bs[0]; let k1 = r1m * bs[1]; let k2 = r2m * bs[2];
340
- let third = k0 * k0 * k0 * cth[0] + k1 * k1 * k1 * cth[1] + k2 * k2 * k2 * cth[2];
341
- let skew = select(0.0, third / (variance * sqrt(variance)), variance > 0.0);
342
- let moments = unit_clip(mean, variance, skew);
343
- hm[comp] = moments.x; hv[comp] = moments.y; hs[comp] = moments.z;
344
- bm_c = bm_c + 2.0 * CLIP_MEAN_U * sqrt(variance);
345
- bv_c = bv_c + 2.0 * CLIP_VARIANCE_U * variance;
346
- }
347
- hbm[comp] = bm_c; hbv[comp] = bv_c;
348
- }
349
- s0 = s0 + aj * hm[0]; s1 = s1 + aj * hm[1]; s2 = s2 + aj * hm[2];
350
- // Linear (Stein) part with B_c·σ_c², and the pixel's own linear variance for the residual.
351
- var l0 = 0.0; var l1 = 0.0; var l2 = 0.0;
352
- var lb0 = 0.0; var lb1 = 0.0; var lb2 = 0.0;
353
- for (var c = 0u; c < 3u; c = c + 1u) {
354
- let a0 = hs[0] * m_of(0u, c) * bs[c];
355
- let a1 = hs[1] * m_of(1u, c) * bs[c];
356
- let a2 = hs[2] * m_of(2u, c) * bs[c];
357
- var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }
358
- let wgt = aj * bc * csv[c];
359
- c00 = c00 + wgt * a0 * a0; c01 = c01 + wgt * a0 * a1; c02 = c02 + wgt * a0 * a2;
360
- c11 = c11 + wgt * a1 * a1; c12 = c12 + wgt * a1 * a2; c22 = c22 + wgt * a2 * a2;
361
- l0 = l0 + a0 * a0 * cvv[c]; l1 = l1 + a1 * a1 * cvv[c]; l2 = l2 + a2 * a2 * cvv[c];
362
- // The clip bound of each channel's variance enters the covariance through a_j·B_c·(Δσ_c²/Σw²)·a_i·a_j and the
363
- // residual's linear prediction through a²·Δσ_c².
364
- lb0 = lb0 + (a0 * a0) * bbv[c]; lb1 = lb1 + (a1 * a1) * bbv[c]; lb2 = lb2 + (a2 * a2) * bbv[c];
304
+ }
305
+
306
+ var bm: array<f32, 3>; var bv: array<f32, 3>; var bs: array<f32, 3>; var bbm: array<f32, 3>; var bbv: array<f32, 3>;
307
+ for (var c = 0u; c < 3u; c = c + 1u) {
308
+ let gc = g_of(c);
309
+ let um = gc * cm[c];
310
+ let uv = gc * gc * cvv[c];
311
+ let gap = params.clip_level - um;
312
+ bbm[c] = gc * CLIP_MEAN_U * cb[c];
313
+ bbv[c] = gc * gc * CLIP_VARIANCE_U * cb[c] * cb[c];
314
+ if (gap > 0.0 && gap * gap > params.reach2 * uv) { bm[c] = um; bv[c] = uv; bs[c] = gc; }
315
+ else {
316
+ let clipped = upper_clip(um, uv, params.clip_level);
317
+ bm[c] = clipped.x; bv[c] = clipped.y; bs[c] = gc * clipped.z;
318
+ bbm[c] = bbm[c] + CLIP_MEAN_U * sqrt(uv);
319
+ bbv[c] = bbv[c] + CLIP_VARIANCE_U * uv;
320
+ }
321
+ }
322
+
323
+ var hm: array<f32, 3>; var hv: array<f32, 3>; var hs: array<f32, 3>; var hbm: array<f32, 3>; var hbv: array<f32, 3>;
324
+ for (var comp = 0u; comp < 3u; comp = comp + 1u) {
325
+ let r0m = m_of(comp, 0u); let r1m = m_of(comp, 1u); let r2m = m_of(comp, 2u);
326
+ let mean = r0m * bm[0] + r1m * bm[1] + r2m * bm[2];
327
+ let variance = r0m * r0m * bv[0] + r1m * r1m * bv[1] + r2m * r2m * bv[2];
328
+ var bm_c = abs(r0m) * bbm[0] + abs(r1m) * bbm[1] + abs(r2m) * bbm[2];
329
+ var bv_c = r0m * r0m * bbv[0] + r1m * r1m * bbv[1] + r2m * r2m * bbv[2];
330
+ if (!reinhard && mean > 0.0 && mean < 1.0 && mean * mean > params.reach2 * variance && (1.0 - mean) * (1.0 - mean) > params.reach2 * variance) {
331
+ hm[comp] = mean; hv[comp] = variance; hs[comp] = 1.0;
332
+ } else if (reinhard) {
333
+ let moments = reinhard_moments(mean, variance);
334
+ hm[comp] = moments.x; hv[comp] = moments.y; hs[comp] = moments.z;
335
+ bm_c = bm_c + 2.0 * CLIP_MEAN_U * sqrt(variance);
336
+ bv_c = bv_c + 2.0 * CLIP_VARIANCE_U * variance;
337
+ } else {
338
+
339
+ let k0 = r0m * bs[0]; let k1 = r1m * bs[1]; let k2 = r2m * bs[2];
340
+ let third = k0 * k0 * k0 * cth[0] + k1 * k1 * k1 * cth[1] + k2 * k2 * k2 * cth[2];
341
+ let skew = select(0.0, third / (variance * sqrt(variance)), variance > 0.0);
342
+ let moments = unit_clip(mean, variance, skew);
343
+ hm[comp] = moments.x; hv[comp] = moments.y; hs[comp] = moments.z;
344
+ bm_c = bm_c + 2.0 * CLIP_MEAN_U * sqrt(variance);
345
+ bv_c = bv_c + 2.0 * CLIP_VARIANCE_U * variance;
346
+ }
347
+ hbm[comp] = bm_c; hbv[comp] = bv_c;
348
+ }
349
+ s0 = s0 + aj * hm[0]; s1 = s1 + aj * hm[1]; s2 = s2 + aj * hm[2];
350
+
351
+ var l0 = 0.0; var l1 = 0.0; var l2 = 0.0;
352
+ var lb0 = 0.0; var lb1 = 0.0; var lb2 = 0.0;
353
+ for (var c = 0u; c < 3u; c = c + 1u) {
354
+ let a0 = hs[0] * m_of(0u, c) * bs[c];
355
+ let a1 = hs[1] * m_of(1u, c) * bs[c];
356
+ let a2 = hs[2] * m_of(2u, c) * bs[c];
357
+ var bc = b0; if (c == 1u) { bc = b1; } if (c == 2u) { bc = b2; }
358
+ let wgt = aj * bc * csv[c];
359
+ c00 = c00 + wgt * a0 * a0; c01 = c01 + wgt * a0 * a1; c02 = c02 + wgt * a0 * a2;
360
+ c11 = c11 + wgt * a1 * a1; c12 = c12 + wgt * a1 * a2; c22 = c22 + wgt * a2 * a2;
361
+ l0 = l0 + a0 * a0 * cvv[c]; l1 = l1 + a1 * a1 * cvv[c]; l2 = l2 + a2 * a2 * cvv[c];
362
+
363
+
364
+ lb0 = lb0 + (a0 * a0) * bbv[c]; lb1 = lb1 + (a1 * a1) * bbv[c]; lb2 = lb2 + (a2 * a2) * bbv[c];
365
365
  bound_var = bound_var + aj * bc * (bbv[c] * aux[${DISPLAY_VIEW_AUX.inverseW2}u + phase * 3u + c]) * max(a0 * a0, max(a1 * a1, a2 * a2));
366
- }
367
- r0 = r0 + aj * aj * max(0.0, hv[0] - l0);
368
- r1 = r1 + aj * aj * max(0.0, hv[1] - l1);
369
- r2 = r2 + aj * aj * max(0.0, hv[2] - l2);
370
- bound_mean = bound_mean + aj * max(hbm[0], max(hbm[1], hbm[2]));
371
- bound_var = bound_var + aj * aj * (max(hbv[0], max(hbv[1], hbv[2])) + max(lb0, max(lb1, lb2)));
372
- }
373
- }
366
+ }
367
+ r0 = r0 + aj * aj * max(0.0, hv[0] - l0);
368
+ r1 = r1 + aj * aj * max(0.0, hv[1] - l1);
369
+ r2 = r2 + aj * aj * max(0.0, hv[2] - l2);
370
+ bound_mean = bound_mean + aj * max(hbm[0], max(hbm[1], hbm[2]));
371
+ bound_var = bound_var + aj * aj * (max(hbv[0], max(hbv[1], hbv[2])) + max(lb0, max(lb1, lb2)));
372
+ }
373
+ }
374
374
  let o = i * ${DISPLAY_VIEW_OUTPUT_FLOATS}u;
375
- out[o] = s0; out[o + 1u] = s1; out[o + 2u] = s2;
376
- out[o + 3u] = c00 + r0; out[o + 4u] = c11 + r1; out[o + 5u] = c22 + r2;
377
- out[o + 6u] = c01; out[o + 7u] = c02; out[o + 8u] = c12;
378
- out[o + 9u] = bound_mean * E; out[o + 10u] = bound_var * E;
379
- flags[i] = select(0u, 1u, material_uncertain);
375
+ out[o] = s0; out[o + 1u] = s1; out[o + 2u] = s2;
376
+ out[o + 3u] = c00 + r0; out[o + 4u] = c11 + r1; out[o + 5u] = c22 + r2;
377
+ out[o + 6u] = c01; out[o + 7u] = c02; out[o + 8u] = c12;
378
+ out[o + 9u] = bound_mean * E; out[o + 10u] = bound_var * E;
379
+ flags[i] = select(0u, 1u, material_uncertain);
380
380
  }
381
381
  `;
382
382
  /** The full shader: the kernel plus a material module (see `DISPLAY_VIEW_SPEED_KERNEL_WGSL`). */