@photivra/engine 2.15.0 → 2.17.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 (119) hide show
  1. package/CHANGELOG.md +102 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +38 -0
  6. package/dist/camera/lens-projection.d.ts +5 -0
  7. package/dist/camera/lens-projection.js +10 -0
  8. package/dist/camera/lens-projection.js.map +1 -1
  9. package/dist/capture/native-environment-lean.js +1 -1
  10. package/dist/capture/native-environment-lean.js.map +1 -1
  11. package/dist/capture/native-raw-development.d.ts +15 -0
  12. package/dist/capture/native-raw-development.js +12 -6
  13. package/dist/capture/native-raw-development.js.map +1 -1
  14. package/dist/capture/raw-development-kernel.d.ts +10 -3
  15. package/dist/capture/raw-development-kernel.js +25 -6
  16. package/dist/capture/raw-development-kernel.js.map +1 -1
  17. package/dist/core/internal-radiance-evaluator.d.ts +5 -2
  18. package/dist/core/internal-radiance-evaluator.js.map +1 -1
  19. package/dist/core/version.d.ts +1 -1
  20. package/dist/core/version.js +1 -1
  21. package/dist/core/version.js.map +1 -1
  22. package/dist/equipment/generic-lens-catalogue.d.ts +227 -0
  23. package/dist/equipment/generic-lens-catalogue.js +243 -0
  24. package/dist/equipment/generic-lens-catalogue.js.map +1 -0
  25. package/dist/facade/defocus-bokeh-zones.d.ts +33 -0
  26. package/dist/facade/defocus-bokeh-zones.js +53 -0
  27. package/dist/facade/defocus-bokeh-zones.js.map +1 -0
  28. package/dist/facade/defocus-image-space-provider.d.ts +118 -0
  29. package/dist/facade/defocus-image-space-provider.js +448 -0
  30. package/dist/facade/defocus-image-space-provider.js.map +1 -0
  31. package/dist/facade/diffraction-provider.d.ts +45 -0
  32. package/dist/facade/diffraction-provider.js +120 -14
  33. package/dist/facade/diffraction-provider.js.map +1 -1
  34. package/dist/facade/display-view-gpu-session.js +4 -0
  35. package/dist/facade/display-view-gpu-session.js.map +1 -1
  36. package/dist/facade/display-view-gpu.d.ts +10 -0
  37. package/dist/facade/display-view-gpu.js.map +1 -1
  38. package/dist/facade/display-view-lens-signal.d.ts +30 -0
  39. package/dist/facade/display-view-lens-signal.js +427 -0
  40. package/dist/facade/display-view-lens-signal.js.map +1 -0
  41. package/dist/facade/display-view.d.ts +21 -0
  42. package/dist/facade/display-view.js +66 -8
  43. package/dist/facade/display-view.js.map +1 -1
  44. package/dist/facade/expected-signal-setup.d.ts +7 -0
  45. package/dist/facade/expected-signal-setup.js +12 -2
  46. package/dist/facade/expected-signal-setup.js.map +1 -1
  47. package/dist/facade/expected-signal.d.ts +8 -0
  48. package/dist/facade/expected-signal.js +150 -0
  49. package/dist/facade/expected-signal.js.map +1 -1
  50. package/dist/facade/generic-lens-capture.d.ts +6 -0
  51. package/dist/facade/generic-lens-capture.js +8 -0
  52. package/dist/facade/generic-lens-capture.js.map +1 -0
  53. package/dist/facade/large-defocus-lattice.d.ts +39 -0
  54. package/dist/facade/large-defocus-lattice.js +64 -0
  55. package/dist/facade/large-defocus-lattice.js.map +1 -0
  56. package/dist/facade/scene-radiance.d.ts +5 -0
  57. package/dist/facade/scene-radiance.js +5 -2
  58. package/dist/facade/scene-radiance.js.map +1 -1
  59. package/dist/facade/simulate-capture.d.ts +148 -6
  60. package/dist/facade/simulate-capture.js +521 -88
  61. package/dist/facade/simulate-capture.js.map +1 -1
  62. package/dist/facade/simulate-display-view.js +29 -19
  63. package/dist/facade/simulate-display-view.js.map +1 -1
  64. package/dist/gpu/display-view-quality-gpu.d.ts +2 -9
  65. package/dist/gpu/display-view-quality-gpu.js +63 -13
  66. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  67. package/dist/gpu/display-view-speed-gpu.d.ts +2 -0
  68. package/dist/gpu/display-view-speed-gpu.js +47 -16
  69. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  70. package/dist/gpu/image-kernel-accelerator.js +3 -0
  71. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  72. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -0
  73. package/dist/gpu/wgsl-display-view-quality-kernel.js +53 -2
  74. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  75. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +15 -0
  76. package/dist/gpu/wgsl-display-view-speed-kernel.js +42 -1
  77. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  78. package/dist/index.d.ts +5 -2
  79. package/dist/index.js +4 -1
  80. package/dist/index.js.map +1 -1
  81. package/dist/optics/defocus-bokeh-kernel.d.ts +207 -0
  82. package/dist/optics/defocus-bokeh-kernel.js +380 -0
  83. package/dist/optics/defocus-bokeh-kernel.js.map +1 -0
  84. package/dist/optics/defocus-disc-kernel.d.ts +44 -21
  85. package/dist/optics/defocus-disc-kernel.js +47 -25
  86. package/dist/optics/defocus-disc-kernel.js.map +1 -1
  87. package/dist/optics/generic-lens-psf.d.ts +68 -0
  88. package/dist/optics/generic-lens-psf.js +337 -0
  89. package/dist/optics/generic-lens-psf.js.map +1 -0
  90. package/dist/optics/illumination-vignetting.d.ts +6 -0
  91. package/dist/optics/illumination-vignetting.js +24 -7
  92. package/dist/optics/illumination-vignetting.js.map +1 -1
  93. package/dist/optics/sensor-environment-query.d.ts +37 -0
  94. package/dist/optics/sensor-environment-query.js +58 -19
  95. package/dist/optics/sensor-environment-query.js.map +1 -1
  96. package/dist/optics/site-kernel-runs.d.ts +77 -0
  97. package/dist/optics/site-kernel-runs.js +200 -0
  98. package/dist/optics/site-kernel-runs.js.map +1 -0
  99. package/dist/output/generic-lens-corrections.d.ts +24 -0
  100. package/dist/output/generic-lens-corrections.js +51 -0
  101. package/dist/output/generic-lens-corrections.js.map +1 -0
  102. package/dist/processing/development-detail.d.ts +132 -0
  103. package/dist/processing/development-detail.js +325 -0
  104. package/dist/processing/development-detail.js.map +1 -0
  105. package/docs/AGENT_GUIDE.md +1 -0
  106. package/docs/API_REFERENCE.md +54 -1
  107. package/docs/QUICK_START.md +444 -26
  108. package/docs/USAGE.md +71 -8
  109. package/docs/api/capture-native-raw-development.md +16 -0
  110. package/docs/api/core-version.md +1 -1
  111. package/docs/api/equipment-generic-lens-catalogue.md +281 -0
  112. package/docs/api/exports.json +162 -2
  113. package/docs/api/facade-simulate-capture.md +151 -5
  114. package/docs/api/optics-defocus-bokeh-kernel.md +145 -0
  115. package/docs/api/optics-defocus-disc-kernel.md +25 -17
  116. package/docs/api/optics-sensor-environment-query.md +6 -0
  117. package/docs/api/output-generic-lens-corrections.md +39 -0
  118. package/llms.txt +1 -0
  119. package/package.json +1 -1
@@ -25,7 +25,7 @@
25
25
  * reference, so they are exact. Every stage's wall-clock time is reported (`stageMilliseconds`); on a software adapter it
26
26
  * is not GPU performance.
27
27
  */
28
- import { AHD_KEY_LEVELS, EXACT_AREA_MEAN_MAX_PIXELS, axisSums, computeDisplayViewCorrectionStates, createDisplayViewExpectedSignal, displayViewCorrectionKey, overlaps, renderDisplayView, ahdCandidateKernels } from "../facade/display-view.js";
28
+ import { AHD_KEY_LEVELS, EXACT_AREA_MEAN_MAX_PIXELS, axisSums, computeDisplayViewCorrectionStates, createDisplayViewExpectedSignal, displayViewCorrectionKey, displayViewDecidedSaturation, overlaps, renderDisplayView, ahdCandidateKernels } from "../facade/display-view.js";
29
29
  import { displayViewSignalTables } from "../facade/expected-signal.js";
30
30
  import { resolvePortableChartScene } from "../facade/chart-scenes.js";
31
31
  import { createDisplayViewMaterialModules } from "./display-view-composite-material-module.js";
@@ -36,7 +36,7 @@ import { createHostYieldGate } from "../capture/host-yield-cadence.js";
36
36
  import { createDefocusDiscSiteKernel } from "../optics/defocus-disc-kernel.js";
37
37
  import { ImageKernelExecutionError, WEBGPU_F32_UNIT_ROUNDOFF, webGpuExecution } from "./image-kernel-execution.js";
38
38
  import { BUFFER_USAGE, GpuResourceScope, abortedError, createComputePipeline, linearDispatch, runComputePass } from "./webgpu-runtime.js";
39
- import { DISPLAY_VIEW_QUALITY_AUX, DISPLAY_VIEW_QUALITY_BITMAP_WORDS, DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES, DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS, DISPLAY_VIEW_QUALITY_FLAG, DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE, DISPLAY_VIEW_QUALITY_GROUP_WORDS, DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS, DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS, DISPLAY_VIEW_QUALITY_PARAM_WORDS, DISPLAY_VIEW_QUALITY_PIXELS_WGSL, DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL, DISPLAY_VIEW_QUALITY_SITES_WGSL, DISPLAY_VIEW_QUALITY_SITE_FLOATS, DISPLAY_VIEW_QUALITY_TAP_WORDS, DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE, assembleDisplayViewQualityLatticeWgsl } from "./wgsl-display-view-quality-kernel.js";
39
+ import { DISPLAY_VIEW_QUALITY_AUX, DISPLAY_VIEW_QUALITY_BITMAP_WORDS, DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES, DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS, DISPLAY_VIEW_QUALITY_FLAG, DISPLAY_VIEW_QUALITY_FLAG_WORDS, DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE, DISPLAY_VIEW_QUALITY_GROUP_WORDS, DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS, DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS, DISPLAY_VIEW_QUALITY_PARAM_WORDS, DISPLAY_VIEW_QUALITY_PIXELS_WGSL, DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL, DISPLAY_VIEW_QUALITY_SITES_WGSL, DISPLAY_VIEW_QUALITY_SITE_FLOATS, DISPLAY_VIEW_QUALITY_TAP_WORDS, DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE, assembleDisplayViewQualityLatticeWgsl } from "./wgsl-display-view-quality-kernel.js";
40
40
  /** One WGSL rounding of + − × in either direction (W3C WGSL "Floating Point Accuracy"; #471, #487): twice the binary32 unit roundoff. */
41
41
  const E = 2 * WEBGPU_F32_UNIT_ROUNDOFF;
42
42
  /** Every reported bound is doubled: the kernel's running bounds are first order, and their own float32 arithmetic rounds. */
@@ -74,7 +74,7 @@ const WINDOW_SLOTS = (2 * MAXIMUM_REACH + 1) ** 2;
74
74
  */
75
75
  const QUANTIZATION_SLOPE = 6.04, QUANTIZATION_JOIN_JUMP = 2.1e-4, QUANTIZATION_JOIN = 0.0031308;
76
76
  /** Bytes per display pixel in the output buffers (moments plus the flag word). */
77
- const OUTPUT_BYTES_PER_PIXEL = 4 * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS + 4;
77
+ const OUTPUT_BYTES_PER_PIXEL = 4 * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS + 4 * DISPLAY_VIEW_QUALITY_FLAG_WORDS;
78
78
  const SITE_BYTES = 4 * DISPLAY_VIEW_QUALITY_SITE_FLOATS;
79
79
  /**
80
80
  * The low-signal correction states a renderer holds for one correction key, as the GPU table reads them: grid keys ascending
@@ -156,6 +156,8 @@ function exactLatticeConstant(c, span) {
156
156
  * 2·reach + 2 sites on each axis (so a reflected border site lies in the band's window).
157
157
  */
158
158
  export function displayViewQualityGpuUnsupportedReason(setup, signalInput) {
159
+ if (signalInput.largeKernel)
160
+ return LARGE_DEFOCUS_REASON;
159
161
  if (signalInput.scene.kind !== "materials")
160
162
  return "radiance scenes (caller functions) are not on the GPU";
161
163
  if (!signalInput.kernels && !signalInput.queryPoint)
@@ -533,7 +535,7 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
533
535
  const sitesPipeline = blocked ? await pipelines("photivra-display-view-quality-sites-blocked", DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL) :
534
536
  await pipelines("photivra-display-view-quality-sites", DISPLAY_VIEW_QUALITY_SITES_WGSL);
535
537
  const pixelsPipeline = await pipelines("photivra-display-view-quality-pixels", DISPLAY_VIEW_QUALITY_PIXELS_WGSL);
536
- const moments = new Float32Array(pixelCount * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS), pixelFlags = new Uint32Array(pixelCount);
538
+ const moments = new Float32Array(pixelCount * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS), pixelFlags = new Uint32Array(pixelCount), masks = new Uint32Array(2 * pixelCount);
537
539
  const states = input.states ?? new CorrectionStateStore(), correction = setup.ahdCorrection ? correctionSetup(setup) : null;
538
540
  let statesComputed = 0, latticePointsResolved = 0;
539
541
  const scope = new GpuResourceScope(runtime);
@@ -628,7 +630,7 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
628
630
  // Pass 3: display pixels, repeated once when a correction state was missing and has been computed.
629
631
  words[0] = count;
630
632
  const out = band.buffer("photivra-display-view-moments", count * 4 * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS, BUFFER_USAGE.STORAGE | BUFFER_USAGE.COPY_SRC);
631
- const flagBuffer = band.buffer("photivra-display-view-flags", count * 4, BUFFER_USAGE.STORAGE | BUFFER_USAGE.COPY_SRC);
633
+ const flagBuffer = band.buffer("photivra-display-view-flags", count * 4 * DISPLAY_VIEW_QUALITY_FLAG_WORDS, BUFFER_USAGE.STORAGE | BUFFER_USAGE.COPY_SRC);
632
634
  const bitmapBuffer = band.buffer("photivra-display-view-bitmap", 4 * DISPLAY_VIEW_QUALITY_BITMAP_WORDS, BUFFER_USAGE.STORAGE | BUFFER_USAGE.COPY_SRC);
633
635
  for (let attempt = 0;; attempt++) {
634
636
  checkAbort(abort.signal);
@@ -646,13 +648,15 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
646
648
  { binding: 3, resource: { buffer: siteBuffer } }, { binding: 4, resource: { buffer: keysBuffer } }, { binding: 5, resource: { buffer: valuesBuffer } },
647
649
  { binding: 6, resource: { buffer: bitmapBuffer } }, { binding: 7, resource: { buffer: out } }, { binding: 8, resource: { buffer: flagBuffer } }
648
650
  ] });
649
- [data, flagData, bitmapData] = await runComputePass(runtime, pass, pixelsPipeline, pixelGroup, linearDispatch(count, DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE, runtime.maximumWorkgroupsPerDimension), [{ buffer: out, bytes: count * 4 * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS }, { buffer: flagBuffer, bytes: count * 4 }, { buffer: bitmapBuffer, bytes: 4 * DISPLAY_VIEW_QUALITY_BITMAP_WORDS }], abort.signal);
651
+ [data, flagData, bitmapData] = await runComputePass(runtime, pass, pixelsPipeline, pixelGroup, linearDispatch(count, DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE, runtime.maximumWorkgroupsPerDimension), [{ buffer: out, bytes: count * 4 * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS }, { buffer: flagBuffer, bytes: count * 4 * DISPLAY_VIEW_QUALITY_FLAG_WORDS }, { buffer: bitmapBuffer, bytes: 4 * DISPLAY_VIEW_QUALITY_BITMAP_WORDS }], abort.signal);
650
652
  }
651
653
  finally {
652
654
  pass.release();
653
655
  }
654
656
  timings.pixels += now() - t0;
655
- const bandFlags = new Uint32Array(flagData, 0, count);
657
+ const flagWords = new Uint32Array(flagData, 0, count * DISPLAY_VIEW_QUALITY_FLAG_WORDS), bandFlags = new Uint32Array(count);
658
+ for (let i = 0; i < count; i++)
659
+ bandFlags[i] = flagWords[DISPLAY_VIEW_QUALITY_FLAG_WORDS * i];
656
660
  let missing = false;
657
661
  for (let i = 0; i < count && !missing; i++)
658
662
  if ((bandFlags[i] & DISPLAY_VIEW_QUALITY_FLAG.stateMissing) !== 0)
@@ -683,6 +687,10 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
683
687
  }
684
688
  moments.set(new Float32Array(data, 0, count * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS), first * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS);
685
689
  pixelFlags.set(bandFlags, first);
690
+ for (let i = 0; i < count; i++) {
691
+ masks[2 * (first + i)] = flagWords[DISPLAY_VIEW_QUALITY_FLAG_WORDS * i + 1];
692
+ masks[2 * (first + i) + 1] = flagWords[DISPLAY_VIEW_QUALITY_FLAG_WORDS * i + 2];
693
+ }
686
694
  break;
687
695
  }
688
696
  }
@@ -703,11 +711,18 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
703
711
  let t0 = now();
704
712
  const maximum = setup.bitDepth === 8 ? 255 : 65535, step = 1 / maximum;
705
713
  const samples = setup.bitDepth === 8 ? new Uint8Array(pixelCount * 3) : new Uint16Array(pixelCount * 3);
706
- const expected = new Float64Array(pixelCount * 3), noiseStd = new Float64Array(pixelCount * 3);
714
+ const expected = new Float64Array(pixelCount * 3), noiseStd = new Float64Array(pixelCount * 3), saturated = new Float64Array(pixelCount);
707
715
  const meanBound = new Float32Array(pixelCount), varianceBound = new Float32Array(pixelCount);
708
716
  const flagged = [];
709
717
  let largestMean = 0, largestVariance = 0, sampleCount = 0, siteCount = 0, corrected = 0;
710
718
  const counts = new Int32Array(geometry.buffer);
719
+ // #533: each display pixel's saturated-pixel share from the kernel's mask (the reference's arithmetic, exact where it decided).
720
+ const columnOverlaps = Array.from({ length: region.width }, (_, i) => overlaps((region.x + i) * shape.sx, (region.x + i + 1) * shape.sx, setup.frameWidth));
721
+ for (let ry = 0, i = 0; ry < region.height; ry++) {
722
+ const rowOverlaps = overlaps((region.y + ry) * shape.sy, (region.y + ry + 1) * shape.sy, setup.frameHeight);
723
+ for (let rx = 0; rx < region.width; rx++, i++)
724
+ saturated[i] = displayViewDecidedSaturation(columnOverlaps[rx], rowOverlaps, area, masks[2 * i], masks[2 * i + 1]);
725
+ }
711
726
  for (let ry = 0, i = 0; ry < region.height; ry++) {
712
727
  const rowWw = rowSums[ry].ww, ny = counts[(region.width + ry) * stride + 1];
713
728
  for (let rx = 0; rx < region.width; rx++, i++) {
@@ -735,7 +750,30 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
735
750
  timings.finishing = now() - t0;
736
751
  // Flagged display pixels: the reference's own pixels (a region render is byte-identical to the whole view's pixels).
737
752
  t0 = now();
738
- const recomputed = [];
753
+ const recomputed = [], highlights = [];
754
+ // #533: unflagged display pixels with an undecided real pixel take the reference's count only (their samples stay the kernel's).
755
+ const undecided = [], isFlagged = new Uint8Array(pixelCount);
756
+ for (const i of flagged)
757
+ isFlagged[i] = 1;
758
+ for (let i = 0; i < pixelCount; i++)
759
+ if (isFlagged[i] === 0 && (pixelFlags[i] & DISPLAY_VIEW_QUALITY_FLAG.highlightUndecided) !== 0)
760
+ undecided.push(i);
761
+ if (undecided.length > 0 && input.recomputeFlagged !== false) {
762
+ const signal = createDisplayViewExpectedSignal(signalInput);
763
+ for (let r = 0; r < undecided.length;) {
764
+ checkAbort(abort.signal);
765
+ const first = undecided[r], rx = first % region.width, ry = (first - rx) / region.width;
766
+ let run = 1;
767
+ while (r + run < undecided.length && undecided[r + run] === first + run && rx + run < region.width)
768
+ run++;
769
+ const part = await renderDisplayView({ ...setup, signal, region: { x: region.x + rx, y: region.y + ry, width: run, height: 1 } });
770
+ for (let k = 0; k < run; k++) {
771
+ saturated[first + k] = part.saturated[k];
772
+ highlights.push(first + k);
773
+ }
774
+ r += run;
775
+ }
776
+ }
739
777
  if (flagged.length > 0) {
740
778
  if (input.recomputeFlagged === false) {
741
779
  // Without recomputation a flagged pixel keeps the kernel's values where they are finite (its bound then does not hold).
@@ -768,6 +806,7 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
768
806
  samples.set(part.samples, first * 3);
769
807
  expected.set(part.expected, first * 3);
770
808
  noiseStd.set(part.noiseStd, first * 3);
809
+ saturated.set(part.saturated, first);
771
810
  for (let k = 0; k < run; k++) {
772
811
  meanBound[first + k] = 0;
773
812
  varianceBound[first + k] = 0;
@@ -783,11 +822,14 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
783
822
  timings.recompute = now() - t0;
784
823
  const relative = MARGIN * CPU_RELATIVE;
785
824
  const expectedValueBound = { kind: "relative-to-reference", relative, absolute: largestMean };
786
- const uncertain = flagged.length - recomputed.length;
787
- return { samples, expected, noiseStd, sampleCount, siteCount, detailPixelCount: pixelCount, uniformPixelCount: 0, ahdCorrectedPixelCount: corrected, estimation: "exact-area-mean",
825
+ const uncertain = flagged.length - recomputed.length + undecided.length - highlights.length;
826
+ let saturatedPixelCount = 0;
827
+ for (let i = 0; i < pixelCount; i++)
828
+ saturatedPixelCount += saturated[i];
829
+ return { samples, expected, noiseStd, sampleCount, siteCount, detailPixelCount: pixelCount, uniformPixelCount: 0, ahdCorrectedPixelCount: corrected, estimation: "exact-area-mean", saturated, saturatedPixelCount,
788
830
  execution: webGpuExecution(expectedValueBound, uncertain), expectedValueBound, varianceBound: { kind: "relative-to-reference", relative, absolute: largestVariance },
789
831
  expectedValueAbsoluteBound: meanBound, varianceAbsoluteBound: varianceBound, flaggedPixelIndices: Uint32Array.from(flagged), hostRecomputedPixelIndices: Uint32Array.from(recomputed),
790
- pixelFlags, statesComputed, statesHeld: states.size, tapsPerSite: gather.tapCount, blockedSum: blocked, siteSumRelativeBound: floats[50], latticePointsResolved, stageMilliseconds: timings };
832
+ pixelFlags, highlightRecomputedPixelIndices: Uint32Array.from(highlights), statesComputed, statesHeld: states.size, tapsPerSite: gather.tapCount, blockedSum: blocked, siteSumRelativeBound: floats[50], latticePointsResolved, stageMilliseconds: timings };
791
833
  }
792
834
  /**
793
835
  * The GPU setup the material modules read for a Quality render (#491's `createDisplayViewMaterialModules`). The composite module
@@ -909,6 +951,9 @@ async function runQualityConformance(handle, signal, defocus) {
909
951
  if (Math.abs(gpu.samples[i] - reference.samples[i]) > 1)
910
952
  return "component " + i + ": sample " + gpu.samples[i] + " differs from the reference " + reference.samples[i] + " by more than one code.";
911
953
  }
954
+ // #533: the saturated-pixel count is the reference's, bit for bit (the light squares sit at the white clip).
955
+ if (gpu.saturatedPixelCount !== reference.saturatedPixelCount)
956
+ return "the saturated-pixel count " + gpu.saturatedPixelCount + " differs from the reference's " + reference.saturatedPixelCount + ".";
912
957
  return true;
913
958
  }
914
959
  finally {
@@ -936,6 +981,8 @@ function bilinearKernels() {
936
981
  * carries the expected signal's input and the chart; a setup without it, a coarse view or a frame the kernels do not cover
937
982
  * throws `unsupported-on-backend`), and throws `aborted` and `device-lost` as the accelerator's operations do.
938
983
  */
984
+ /** #564: the GPU refusal of a defocus kernel above the direct radius (the CPU convolves it by runs). */
985
+ const LARGE_DEFOCUS_REASON = "a defocus kernel radius above 64 sites (convolved by runs on the CPU, #564) is not on the GPU";
939
986
  export function createDisplayViewGpuRenderer(runtime, options = {}) {
940
987
  const pipelines = new Map(), stores = new Map();
941
988
  const bandBytes = options.maximumBandBytes ?? Math.min(64 * 1024 * 1024, runtime.maximumStorageBindingBytes);
@@ -954,6 +1001,8 @@ export function createDisplayViewGpuRenderer(runtime, options = {}) {
954
1001
  return {
955
1002
  id: "photivra-display-view",
956
1003
  supports(request) {
1004
+ if (request.largeDefocus)
1005
+ return LARGE_DEFOCUS_REASON;
957
1006
  // #492: out of focus the expected signal carries the defocus disc as site kernels, which only the Quality site pass gathers;
958
1007
  // a Speed request then runs the Quality passes with its own bilinear demosaic (one candidate) and finishing.
959
1008
  if (request.defocus)
@@ -991,7 +1040,8 @@ export function createDisplayViewGpuRenderer(runtime, options = {}) {
991
1040
  result = await renderDisplayViewQualityOnGpu(runtime, bandBytes, { setup, signalInput, material, ...(states ? { states } : {}) }, abort, options.yieldControl, source);
992
1041
  }
993
1042
  return { samples: result.samples, expected: result.expected, noiseStd: result.noiseStd, sampleCount: result.sampleCount, siteCount: result.siteCount, detailPixelCount: result.detailPixelCount,
994
- uniformPixelCount: result.uniformPixelCount, ahdCorrectedPixelCount: result.ahdCorrectedPixelCount, estimation: result.estimation, errorBound: result.expectedValueBound,
1043
+ uniformPixelCount: result.uniformPixelCount, ahdCorrectedPixelCount: result.ahdCorrectedPixelCount, estimation: result.estimation, saturated: result.saturated,
1044
+ saturatedPixelCount: result.saturatedPixelCount, errorBound: result.expectedValueBound,
995
1045
  diagnosticUncertaintySampleCount: result.execution.diagnosticUncertaintySampleCount, ...(material.compositeProbe ? { compositeProbe: material.compositeProbe } : {}) };
996
1046
  }
997
1047
  finally {