@photivra/engine 2.21.0 → 2.22.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 (169) hide show
  1. package/CHANGELOG.md +118 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +11 -0
  6. package/dist/capture/c2pa-embed.d.ts +116 -0
  7. package/dist/capture/c2pa-embed.js +493 -0
  8. package/dist/capture/c2pa-embed.js.map +1 -0
  9. package/dist/capture/dng-import.d.ts +73 -8
  10. package/dist/capture/dng-import.js +315 -80
  11. package/dist/capture/dng-import.js.map +1 -1
  12. package/dist/capture/dng-opcodes.d.ts +96 -0
  13. package/dist/capture/dng-opcodes.js +391 -0
  14. package/dist/capture/dng-opcodes.js.map +1 -0
  15. package/dist/capture/dng-profile-maps.d.ts +99 -0
  16. package/dist/capture/dng-profile-maps.js +381 -0
  17. package/dist/capture/dng-profile-maps.js.map +1 -0
  18. package/dist/capture/dng-stage-image.d.ts +23 -0
  19. package/dist/capture/dng-stage-image.js +65 -0
  20. package/dist/capture/dng-stage-image.js.map +1 -0
  21. package/dist/capture/jpeg-xl-tiles.d.ts +22 -0
  22. package/dist/capture/jpeg-xl-tiles.js +151 -0
  23. package/dist/capture/jpeg-xl-tiles.js.map +1 -0
  24. package/dist/capture/lossy-jpeg.d.ts +17 -0
  25. package/dist/capture/lossy-jpeg.js +369 -0
  26. package/dist/capture/lossy-jpeg.js.map +1 -0
  27. package/dist/capture/native-raw-development.d.ts +2 -1
  28. package/dist/capture/native-raw-development.js.map +1 -1
  29. package/dist/color/neighbor-highlight-reconstruction.d.ts +66 -0
  30. package/dist/color/neighbor-highlight-reconstruction.js +234 -0
  31. package/dist/color/neighbor-highlight-reconstruction.js.map +1 -0
  32. package/dist/color/sensor-color-development.d.ts +5 -0
  33. package/dist/color/sensor-color-development.js +7 -1
  34. package/dist/color/sensor-color-development.js.map +1 -1
  35. package/dist/color/spectral-spikiness.d.ts +11 -0
  36. package/dist/color/spectral-spikiness.js +23 -0
  37. package/dist/color/spectral-spikiness.js.map +1 -0
  38. package/dist/core/execution-error.d.ts +21 -0
  39. package/dist/core/execution-error.js +12 -0
  40. package/dist/core/execution-error.js.map +1 -0
  41. package/dist/core/version.d.ts +1 -1
  42. package/dist/core/version.js +1 -1
  43. package/dist/core/version.js.map +1 -1
  44. package/dist/facade/catalogue-lens-signal.d.ts +26 -1
  45. package/dist/facade/catalogue-lens-signal.js +25 -7
  46. package/dist/facade/catalogue-lens-signal.js.map +1 -1
  47. package/dist/facade/diffraction-provider.d.ts +1 -1
  48. package/dist/facade/diffraction-provider.js +4 -4
  49. package/dist/facade/diffraction-provider.js.map +1 -1
  50. package/dist/facade/display-view-gpu-session.js +38 -6
  51. package/dist/facade/display-view-gpu-session.js.map +1 -1
  52. package/dist/facade/display-view-gpu.d.ts +13 -5
  53. package/dist/facade/display-view-gpu.js.map +1 -1
  54. package/dist/facade/display-view-normal.d.ts +15 -0
  55. package/dist/facade/display-view-normal.js +44 -0
  56. package/dist/facade/display-view-normal.js.map +1 -0
  57. package/dist/facade/display-view-rendering.d.ts +97 -0
  58. package/dist/facade/display-view-rendering.js +598 -0
  59. package/dist/facade/display-view-rendering.js.map +1 -0
  60. package/dist/facade/display-view.d.ts +37 -65
  61. package/dist/facade/display-view.js +147 -476
  62. package/dist/facade/display-view.js.map +1 -1
  63. package/dist/facade/expected-signal-setup.js +2 -2
  64. package/dist/facade/expected-signal-setup.js.map +1 -1
  65. package/dist/facade/fast-methods.d.ts +1 -1
  66. package/dist/facade/fast-methods.js +37 -8
  67. package/dist/facade/fast-methods.js.map +1 -1
  68. package/dist/facade/generic-lens-capture.d.ts +1 -1
  69. package/dist/facade/generic-lens-capture.js +1 -1
  70. package/dist/facade/generic-lens-capture.js.map +1 -1
  71. package/dist/facade/image-plane.js +41 -10
  72. package/dist/facade/image-plane.js.map +1 -1
  73. package/dist/facade/on-demand-kits.d.ts +14 -0
  74. package/dist/facade/on-demand-kits.js +36 -0
  75. package/dist/facade/on-demand-kits.js.map +1 -0
  76. package/dist/facade/opt-in-kit.d.ts +9 -0
  77. package/dist/facade/opt-in-kit.js +11 -0
  78. package/dist/facade/opt-in-kit.js.map +1 -0
  79. package/dist/facade/quality-kit.d.ts +21 -0
  80. package/dist/facade/quality-kit.js +23 -0
  81. package/dist/facade/quality-kit.js.map +1 -0
  82. package/dist/facade/quality-paths.d.ts +22 -0
  83. package/dist/facade/quality-paths.js +44 -0
  84. package/dist/facade/quality-paths.js.map +1 -0
  85. package/dist/facade/scene-radiance.d.ts +18 -2
  86. package/dist/facade/scene-radiance.js +6 -4
  87. package/dist/facade/scene-radiance.js.map +1 -1
  88. package/dist/facade/simulate-capture.d.ts +32 -13
  89. package/dist/facade/simulate-capture.js +162 -69
  90. package/dist/facade/simulate-capture.js.map +1 -1
  91. package/dist/facade/simulate-display-view.d.ts +10 -12
  92. package/dist/facade/simulate-display-view.js +57 -48
  93. package/dist/facade/simulate-display-view.js.map +1 -1
  94. package/dist/gpu/display-view-gpu-renderer.d.ts +43 -0
  95. package/dist/gpu/display-view-gpu-renderer.js +134 -0
  96. package/dist/gpu/display-view-gpu-renderer.js.map +1 -0
  97. package/dist/gpu/display-view-image-material-module.d.ts +3 -2
  98. package/dist/gpu/display-view-image-material-module.js +25 -3
  99. package/dist/gpu/display-view-image-material-module.js.map +1 -1
  100. package/dist/gpu/display-view-image-quality.d.ts +43 -1
  101. package/dist/gpu/display-view-image-quality.js +256 -11
  102. package/dist/gpu/display-view-image-quality.js.map +1 -1
  103. package/dist/gpu/display-view-quality-gpu.d.ts +13 -64
  104. package/dist/gpu/display-view-quality-gpu.js +60 -249
  105. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  106. package/dist/gpu/display-view-speed-gpu.d.ts +16 -16
  107. package/dist/gpu/display-view-speed-gpu.js +50 -12
  108. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  109. package/dist/gpu/image-kernel-accelerator.js +3 -0
  110. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  111. package/dist/gpu/image-kernel-execution.d.ts +2 -13
  112. package/dist/gpu/image-kernel-execution.js +2 -10
  113. package/dist/gpu/image-kernel-execution.js.map +1 -1
  114. package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +8 -0
  115. package/dist/gpu/wgsl-display-view-coupled-stub.js +21 -0
  116. package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -0
  117. package/dist/gpu/wgsl-display-view-coupled.d.ts +88 -0
  118. package/dist/gpu/wgsl-display-view-coupled.js +353 -0
  119. package/dist/gpu/wgsl-display-view-coupled.js.map +1 -0
  120. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -9
  121. package/dist/gpu/wgsl-display-view-quality-kernel.js +109 -111
  122. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  123. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +1 -41
  124. package/dist/gpu/wgsl-display-view-speed-kernel.js +71 -8
  125. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  126. package/dist/index.d.ts +11 -3
  127. package/dist/index.js +9 -1
  128. package/dist/index.js.map +1 -1
  129. package/dist/output/camera-channel-response.d.ts +50 -0
  130. package/dist/output/camera-channel-response.js +69 -0
  131. package/dist/output/camera-channel-response.js.map +1 -0
  132. package/dist/output/native-capture-sdr.js +5 -2
  133. package/dist/output/native-capture-sdr.js.map +1 -1
  134. package/dist/output/sdr-rendering.d.ts +18 -1
  135. package/dist/output/sdr-rendering.js +39 -2
  136. package/dist/output/sdr-rendering.js.map +1 -1
  137. package/dist/sensor/native-sensor-report.d.ts +10 -0
  138. package/dist/sensor/native-sensor-report.js +16 -0
  139. package/dist/sensor/native-sensor-report.js.map +1 -0
  140. package/dist/sensor/sensor-characteristics.d.ts +81 -13
  141. package/dist/sensor/sensor-characteristics.js +10 -6
  142. package/dist/sensor/sensor-characteristics.js.map +1 -1
  143. package/docs/API_REFERENCE.md +117 -4
  144. package/docs/QUICK_START.md +37 -8
  145. package/docs/USAGE.md +104 -22
  146. package/docs/api/capture-c2pa-embed.md +177 -0
  147. package/docs/api/capture-dng-import.md +86 -10
  148. package/docs/api/capture-dng-opcodes.md +93 -0
  149. package/docs/api/capture-dng-profile-maps.md +141 -0
  150. package/docs/api/capture-dng-stage-image.md +43 -0
  151. package/docs/api/capture-jpeg-xl-tiles.md +33 -0
  152. package/docs/api/capture-lossy-jpeg.md +41 -0
  153. package/docs/api/capture-native-raw-development.md +2 -1
  154. package/docs/api/core-execution-error.md +43 -0
  155. package/docs/api/core-version.md +1 -1
  156. package/docs/api/exports.json +255 -5
  157. package/docs/api/facade-display-view-gpu.md +5 -2
  158. package/docs/api/facade-display-view.md +11 -15
  159. package/docs/api/facade-fast-methods.md +38 -9
  160. package/docs/api/facade-quality-paths.md +24 -0
  161. package/docs/api/facade-scene-radiance.md +1 -1
  162. package/docs/api/facade-simulate-capture.md +24 -7
  163. package/docs/api/facade-simulate-display-view.md +10 -12
  164. package/docs/api/gpu-image-kernel-execution.md +0 -36
  165. package/docs/api/output-camera-channel-response.md +55 -0
  166. package/docs/api/output-sdr-rendering.md +6 -1
  167. package/docs/api/sensor-sensor-characteristics.md +54 -2
  168. package/llms.txt +1 -0
  169. package/package.json +6 -5
@@ -2,9 +2,7 @@
2
2
  * Host side of the display view's Quality path on the GPU (#479): the support check, the float64 tables (the lattice groups
3
3
  * and their combined diffraction kernels per material and channel, as the reference forms them; the bilinear and AHD
4
4
  * candidate kernels; the footprint geometry), the three passes per band of display rows (lattice materials, site moments,
5
- * display pixels; `wgsl-display-view-quality-kernel.ts`), the low-signal correction states (computed on the host with the
6
- * reference's own `computeDisplayViewCorrectionStates`, #447, uploaded as a sorted table and grown from the grid keys the
7
- * kernel records), and the reference's own float64 finishing per display pixel (`finishDisplayViewPixel` of the Speed host:
5
+ * display pixels; `wgsl-display-view-quality-kernel.ts`), and the reference's own float64 finishing per display pixel (`finishDisplayViewPixel` of the Speed host:
8
6
  * the quantization term, the seeded noise draw, the Cholesky factor, the clipped centre, the sRGB encode and the rounding),
9
7
  * so the noise draw is bit-identical to the CPU's.
10
8
  *
@@ -16,19 +14,21 @@
16
14
  *
17
15
  * The float64 reference is `renderDisplayView` (facade/display-view.ts) on the exact-area-mean path with
18
16
  * `createDisplayViewExpectedSignal` (facade/expected-signal.ts), chart materials, with or without diffraction kernels,
19
- * the bilinear or the AHD candidates and the low-signal correction. Internal: not exported from the package.
17
+ * the bilinear or the AHD candidates. Internal: not exported from the package.
20
18
  *
21
- * Flagged display pixels (an uncertain lattice material among the sites read, a missing correction state after the
22
- * states were computed, a correction-threshold decision within its bound, AHD's choice on the noise-free mosaic able to move
19
+ * #594: this module and its WGSL load on demand (`prepareDisplayViewQualityGpu` in `display-view-gpu-renderer.ts`, a dynamic
20
+ * import), at the first render that takes the Quality passes; the renderer and the Speed path never import it statically.
21
+ *
22
+ * Flagged display pixels (an uncertain lattice material among the sites read, AHD's choice on the noise-free mosaic able to move
23
23
  * the pixel by more than DISPLAY_VIEW_QUALITY_CHOICE_SIGMA of its noise (#457), a non-finite output) are recomputed with the
24
24
  * reference, so they are exact. Every stage's wall-clock time is reported (`stageMilliseconds`); on a software adapter it
25
25
  * is not GPU performance.
26
26
  */
27
- import { type DisplayViewKernelTap, type DisplayViewRenderResult, type DisplayViewSetup } from "../facade/display-view.js";
27
+ import { type DisplayViewRenderResult, type DisplayViewSetup } from "../facade/display-view.js";
28
28
  import { type DisplayViewExpectedSignalInput } from "../facade/expected-signal.js";
29
29
  import type { DisplayViewGpuSetup } from "../facade/display-view.js";
30
- import { type DisplayViewMaterialModules } from "./display-view-composite-material-module.js";
31
- import { type DisplayViewGpuConformanceCase, type DisplayViewGpuRenderer, type DisplayViewGpuRendererFactory, type DisplayViewGpuRequest } from "../facade/display-view-gpu.js";
30
+ import type { DisplayViewMaterialModules } from "./display-view-composite-material-module.js";
31
+ import type { DisplayViewGpuConformanceCase } from "../facade/display-view-gpu.js";
32
32
  import { type DisplayViewGpuMaterialModule } from "./display-view-speed-gpu.js";
33
33
  import { type ImageKernelExecution, type ImageKernelRelativeErrorBound } from "./image-kernel-execution.js";
34
34
  import { GpuResourceScope, type WebGpuRuntime } from "./webgpu-runtime.js";
@@ -56,8 +56,6 @@ export interface DisplayViewQualityGpuInput {
56
56
  material: DisplayViewGpuMaterialModule;
57
57
  /** Recompute flagged display pixels with the reference. Default true. */
58
58
  recomputeFlagged?: boolean;
59
- /** The states this renderer holds for the setup's correction key (grown by the render); a fresh store when absent. */
60
- states?: CorrectionStateStore;
61
59
  /**
62
60
  * #492: sum each site's taps in blocks (rows, groups; `DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL`), whose bound grows with the
63
61
  * kernel's width rather than its tap count. Default: when the setup's expected signal carries the defocus disc
@@ -92,16 +90,12 @@ export interface DisplayViewQualityStageTimings {
92
90
  lattice: number;
93
91
  /** Pass 2: the site electrons through the combined kernels, the readout moments and the white clip. */
94
92
  sites: number;
95
- /** Pass 3: the display pixels (demosaic candidates, development, covariance, correction), every dispatch of it. */
93
+ /** Pass 3: the display pixels (demosaic candidates, development, covariance), every dispatch of it. */
96
94
  pixels: number;
97
- /** The low-signal correction states computed on the host (the reference's own draws) and the table uploads. */
98
- states: number;
99
95
  /** The host finishing (quantization term, noise draw, Cholesky, encode) of every display pixel. */
100
96
  finishing: number;
101
97
  /** The reference's recomputation of flagged display pixels. */
102
98
  recompute: number;
103
- /** Pass 3 dispatches that were repeated because a correction state was missing. */
104
- pixelRedispatches: number;
105
99
  /** Pass 2 dispatches (one per band, or more where a band's sites × taps exceed the tap budget per dispatch, #492). */
106
100
  siteDispatches: number;
107
101
  }
@@ -122,9 +116,6 @@ export interface DisplayViewQualityGpuResult extends DisplayViewRenderResult {
122
116
  pixelFlags: Uint32Array;
123
117
  /** #533: display pixels (not flagged) with a real pixel whose saturation the kernel left undecided; their count is the host reference's. */
124
118
  highlightRecomputedPixelIndices: Uint32Array;
125
- /** Correction states the render computed on the host (new to the store), and the states the table held at the end. */
126
- statesComputed: number;
127
- statesHeld: number;
128
119
  /** Taps per native site of the expected-signal gather (over every lattice group). */
129
120
  tapsPerSite: number;
130
121
  /** #492: whether the site pass summed in blocks (the defocus disc), and the relative bound of a site's electrons it used. */
@@ -137,33 +128,10 @@ export interface DisplayViewQualityGpuResult extends DisplayViewRenderResult {
137
128
  latticePointsResolved: number;
138
129
  stageMilliseconds: DisplayViewQualityStageTimings;
139
130
  }
140
- /**
141
- * The low-signal correction states a renderer holds for one correction key, as the GPU table reads them: grid keys ascending
142
- * with three float32 values each (the host keeps the float64 values; the table is their single rounding).
143
- */
144
- export declare class CorrectionStateStore {
145
- #private;
146
- get size(): number;
147
- has(key: number): boolean;
148
- add(gridKeys: Int32Array, values: Float64Array): void;
149
- /** The sorted table (rebuilt when states were added since the last call). */
150
- table(): {
151
- keys: Int32Array<ArrayBuffer>;
152
- values: Float32Array<ArrayBuffer>;
153
- };
154
- }
155
- /** The footprint shape of a view: real pixels per display pixel along each axis, and whether the exact area mean applies. */
156
- export declare function footprintShape(setup: DisplayViewSetup): {
157
- sx: number;
158
- sy: number;
159
- exact: boolean;
160
- maximumColumns: number;
161
- maximumRows: number;
162
- };
163
131
  /**
164
132
  * Why the Quality kernels do not cover a request, or null when they do: chart materials, the exact area mean (footprints of
165
133
  * at most 25 real pixels and 6 per axis), the identity or positive Reinhard tone curve, demosaic candidates within 3 sites
166
- * (1 or 2 per phase), at most 64 lattice groups, lattice constants exact in float32, no collect pass, and a raster at least
134
+ * (1 or 2 per phase), at most 64 lattice groups, lattice constants exact in float32, and a raster at least
167
135
  * 2·reach + 2 sites on each axis (so a reflected border site lies in the band's window).
168
136
  */
169
137
  export declare function displayViewQualityGpuUnsupportedReason(setup: DisplayViewSetup, signalInput: DisplayViewExpectedSignalInput): string | null;
@@ -229,29 +197,10 @@ export declare function renderDisplayViewQualityOnGpu(runtime: WebGpuRuntime, ba
229
197
  export declare function qualityMaterialSetup(gpu: DisplayViewGpuSetup): DisplayViewGpuSetup;
230
198
  /** The material module of a Quality render's scene (see `qualityMaterialSetup`). */
231
199
  export declare function qualityMaterialModule(modules: DisplayViewMaterialModules, gpu: DisplayViewGpuSetup, signal: AbortSignal): Promise<DisplayViewGpuMaterialModule>;
232
- /** The pixel-aperture samples per axis and anti-aliasing spots of a request. */
233
- export declare function offsetCount(request: DisplayViewGpuRequest): number;
234
- /**
235
- * Why the Quality renderer does not support a prepared request (`DisplayViewGpuRenderer.supports`), or true: engine charts and
236
- * declared `material-layers` scenes (the material modules of `createDisplayViewMaterialModules`, #491), the identity or Reinhard
237
- * tone curve and at most 64 query offsets (lattice groups). `lens.defocus` out of focus is supported (#492 step 2c: the site pass
238
- * gathers the disc, or each node's diffraction kernel convolved with it, up to the reference's own caps of radius 64 and 96);
239
- * whether a view's footprints take the exact area mean, whether the device passes the composite probe, and whether the kernel
240
- * table fits the device's storage binding limit, is known only per render.
241
- */
242
- export declare function displayViewQualityGpuSupports(request: DisplayViewGpuRequest): true | string;
243
200
  /** #479's self-check (see `runQualityConformance`). */
244
201
  export declare const DISPLAY_VIEW_QUALITY_CONFORMANCE_CASE: DisplayViewGpuConformanceCase;
245
202
  /** #492's self-check of the defocus disc's blocked site sum (see `runQualityConformance`). */
246
203
  export declare const DISPLAY_VIEW_QUALITY_DEFOCUS_CONFORMANCE_CASE: DisplayViewGpuConformanceCase;
247
- /** The bilinear RGGB demosaic kernels per phase (phaseY·2 + phaseX) and channel, as `bilinearPhaseProfiles` gives them. */
248
- export declare function bilinearKernels(): DisplayViewKernelTap[][][];
249
- export declare function createDisplayViewGpuRenderer(runtime: WebGpuRuntime, options?: {
250
- maximumBandBytes?: number;
251
- yieldControl?: YieldControl;
252
- }): DisplayViewGpuRenderer;
253
- /** The factory `prepareDisplayView` creates the renderer with (one per device): the Speed and Quality kernels behind one renderer. */
254
- export declare const displayViewGpuRendererFactory: DisplayViewGpuRendererFactory;
255
- /** Registers the combined renderer as the display view's GPU renderer and returns the factory registered before. */
256
- export declare function registerDisplayViewGpuRenderer(): DisplayViewGpuRendererFactory | null;
204
+ /** #594: the Quality kernels' self-check cases, run before the first render that takes the Quality passes (`onDemandConformanceCases`). */
205
+ export declare const DISPLAY_VIEW_QUALITY_CONFORMANCE_CASES: readonly DisplayViewGpuConformanceCase[];
257
206
  export {};
@@ -3,9 +3,7 @@
3
3
  * Host side of the display view's Quality path on the GPU (#479): the support check, the float64 tables (the lattice groups
4
4
  * and their combined diffraction kernels per material and channel, as the reference forms them; the bilinear and AHD
5
5
  * candidate kernels; the footprint geometry), the three passes per band of display rows (lattice materials, site moments,
6
- * display pixels; `wgsl-display-view-quality-kernel.ts`), the low-signal correction states (computed on the host with the
7
- * reference's own `computeDisplayViewCorrectionStates`, #447, uploaded as a sorted table and grown from the grid keys the
8
- * kernel records), and the reference's own float64 finishing per display pixel (`finishDisplayViewPixel` of the Speed host:
6
+ * display pixels; `wgsl-display-view-quality-kernel.ts`), and the reference's own float64 finishing per display pixel (`finishDisplayViewPixel` of the Speed host:
9
7
  * the quantization term, the seeded noise draw, the Cholesky factor, the clipped centre, the sRGB encode and the rounding),
10
8
  * so the noise draw is bit-identical to the CPU's.
11
9
  *
@@ -17,25 +15,26 @@
17
15
  *
18
16
  * The float64 reference is `renderDisplayView` (facade/display-view.ts) on the exact-area-mean path with
19
17
  * `createDisplayViewExpectedSignal` (facade/expected-signal.ts), chart materials, with or without diffraction kernels,
20
- * the bilinear or the AHD candidates and the low-signal correction. Internal: not exported from the package.
18
+ * the bilinear or the AHD candidates. Internal: not exported from the package.
21
19
  *
22
- * Flagged display pixels (an uncertain lattice material among the sites read, a missing correction state after the
23
- * states were computed, a correction-threshold decision within its bound, AHD's choice on the noise-free mosaic able to move
20
+ * #594: this module and its WGSL load on demand (`prepareDisplayViewQualityGpu` in `display-view-gpu-renderer.ts`, a dynamic
21
+ * import), at the first render that takes the Quality passes; the renderer and the Speed path never import it statically.
22
+ *
23
+ * Flagged display pixels (an uncertain lattice material among the sites read, AHD's choice on the noise-free mosaic able to move
24
24
  * the pixel by more than DISPLAY_VIEW_QUALITY_CHOICE_SIGMA of its noise (#457), a non-finite output) are recomputed with the
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, displayViewCorrectionKey, displayViewDecidedSaturation, overlaps, renderDisplayView, ahdCandidateKernels } from "../facade/display-view.js";
28
+ import { axisSums, displayViewDecidedSaturation, overlaps, renderDisplayView, ahdCandidateKernels } from "../facade/display-view.js";
29
29
  import { displayViewSignalTables } from "../facade/expected-signal.js";
30
- import { createDisplayViewMaterialModules } from "./display-view-composite-material-module.js";
31
- import { setDisplayViewGpuRendererFactory } from "../facade/display-view-gpu.js";
32
30
  import { chartPresetMaterialModule } from "./display-view-chart-material-module.js";
33
- import { DISPLAY_VIEW_SPEED_CONFORMANCE_CASE, CONFORMANCE_FRAME, recomputeRuns, sceneToneAndOffsetsReason, conformanceChannelAt, conformanceChart, conformanceResponses, conformanceSetup, conformanceVerdict, withConformanceAbort, axisRecord, displayViewSpeedGpuSupports, displayViewSpeedGpuUnsupportedReason, finishDisplayViewPixel, gaussLegendre16, queryGeometry, renderDisplayViewSpeedOnGpu } from "./display-view-speed-gpu.js";
31
+ import { CONFORMANCE_FRAME, LARGE_DEFOCUS_REASON, recomputeRuns, footprintShape, conformanceChannelAt, conformanceChart, conformanceResponses, conformanceSetup, conformanceVerdict, withConformanceAbort, axisRecord, finishDisplayViewPixel, gaussLegendre16, queryGeometry } from "./display-view-speed-gpu.js";
34
32
  import { createHostYieldGate } from "../capture/host-yield-cadence.js";
35
33
  import { createDefocusDiscSiteKernel } from "../optics/defocus-disc-kernel.js";
36
34
  import { ImageKernelExecutionError, WEBGPU_F32_UNIT_ROUNDOFF, webGpuExecution } from "./image-kernel-execution.js";
37
- import { BUFFER_USAGE, GpuResourceScope, abortedError, createComputePipeline, linearDispatch, runComputePass, throwIfAborted as checkAbort } from "./webgpu-runtime.js";
38
- 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_NODES, 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";
35
+ import { BUFFER_USAGE, GpuResourceScope, linearDispatch, runComputePass, throwIfAborted as checkAbort } from "./webgpu-runtime.js";
36
+ import { DISPLAY_VIEW_QUALITY_AUX, 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_NODES, 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, assembleDisplayViewQualityPixelsWgsl } from "./wgsl-display-view-quality-kernel.js";
37
+ import { SDR_LUMINANCE_WEIGHTS } from "../output/sdr-rendering.js";
39
38
  /** One WGSL rounding of + − × in either direction (W3C WGSL "Floating Point Accuracy"; #471, #487): twice the binary32 unit roundoff. */
40
39
  const E = 2 * WEBGPU_F32_UNIT_ROUNDOFF;
41
40
  /** Every reported bound is doubled: the kernel's running bounds are first order, and their own float32 arithmetic rounds. */
@@ -75,37 +74,6 @@ const QUANTIZATION_SLOPE = 6.04, QUANTIZATION_JOIN_JUMP = 2.1e-4, QUANTIZATION_J
75
74
  /** Bytes per display pixel in the output buffers (moments plus the flag word). */
76
75
  const OUTPUT_BYTES_PER_PIXEL = 4 * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS + 4 * DISPLAY_VIEW_QUALITY_FLAG_WORDS;
77
76
  const SITE_BYTES = 4 * DISPLAY_VIEW_QUALITY_SITE_FLOATS;
78
- /**
79
- * The low-signal correction states a renderer holds for one correction key, as the GPU table reads them: grid keys ascending
80
- * with three float32 values each (the host keeps the float64 values; the table is their single rounding).
81
- */
82
- export class CorrectionStateStore {
83
- #known = new Map();
84
- #keys = new Int32Array(0);
85
- #values = new Float32Array(0);
86
- #dirty = false;
87
- get size() { return this.#known.size; }
88
- has(key) { return this.#known.has(key); }
89
- add(gridKeys, values) {
90
- for (let i = 0; i < gridKeys.length; i++) {
91
- if (this.#known.has(gridKeys[i]))
92
- continue;
93
- this.#known.set(gridKeys[i], [values[3 * i], values[3 * i + 1], values[3 * i + 2]]);
94
- this.#dirty = true;
95
- }
96
- }
97
- /** The sorted table (rebuilt when states were added since the last call). */
98
- table() {
99
- if (this.#dirty) {
100
- const keys = Int32Array.from(this.#known.keys()).sort(), values = new Float32Array(3 * keys.length);
101
- keys.forEach((key, i) => { const v = this.#known.get(key); values[3 * i] = v[0]; values[3 * i + 1] = v[1]; values[3 * i + 2] = v[2]; });
102
- this.#keys = keys;
103
- this.#values = values;
104
- this.#dirty = false;
105
- }
106
- return { keys: this.#keys, values: this.#values };
107
- }
108
- }
109
77
  /**
110
78
  * Waits until the queue has finished the work submitted so far (`GPUQueue.onSubmittedWorkDone`), so a pass that reads nothing
111
79
  * back is timed with its own execution, not the next pass's wait. The passes depend on each other in order anyway.
@@ -115,17 +83,6 @@ async function queueIdle(runtime, signal) {
115
83
  if (typeof queue.onSubmittedWorkDone === "function")
116
84
  await runtime.guard(queue.onSubmittedWorkDone(), signal);
117
85
  }
118
- /** The footprint shape of a view: real pixels per display pixel along each axis, and whether the exact area mean applies. */
119
- export function footprintShape(setup) {
120
- const sx = setup.frameWidth / setup.viewWidth, sy = setup.frameHeight / setup.viewHeight, area = sx * sy;
121
- const exact = area <= Math.min(EXACT_AREA_MEAN_MAX_PIXELS, setup.samplesPerAxis * setup.samplesPerAxis);
122
- let maximumColumns = 0, maximumRows = 0;
123
- for (let i = 0; i < setup.region.width; i++)
124
- maximumColumns = Math.max(maximumColumns, overlaps((setup.region.x + i) * sx, (setup.region.x + i + 1) * sx, setup.frameWidth).w.length);
125
- for (let j = 0; j < setup.region.height; j++)
126
- maximumRows = Math.max(maximumRows, overlaps((setup.region.y + j) * sy, (setup.region.y + j + 1) * sy, setup.frameHeight).w.length);
127
- return { sx, sy, exact, maximumColumns, maximumRows };
128
- }
129
86
  /** The demosaic candidates of a setup (the bilinear kernels when it has none) and their reach in sites. */
130
87
  function candidatesOf(setup) {
131
88
  const candidates = setup.candidates ?? setup.demosaic.map((phase) => [phase]), count = candidates[0]?.length ?? 0;
@@ -147,7 +104,7 @@ function exactLatticeConstant(c, span) {
147
104
  /**
148
105
  * Why the Quality kernels do not cover a request, or null when they do: chart materials, the exact area mean (footprints of
149
106
  * at most 25 real pixels and 6 per axis), the identity or positive Reinhard tone curve, demosaic candidates within 3 sites
150
- * (1 or 2 per phase), at most 64 lattice groups, lattice constants exact in float32, no collect pass, and a raster at least
107
+ * (1 or 2 per phase), at most 64 lattice groups, lattice constants exact in float32, and a raster at least
151
108
  * 2·reach + 2 sites on each axis (so a reflected border site lies in the band's window).
152
109
  */
153
110
  export function displayViewQualityGpuUnsupportedReason(setup, signalInput) {
@@ -157,10 +114,12 @@ export function displayViewQualityGpuUnsupportedReason(setup, signalInput) {
157
114
  return "radiance scenes (caller functions) are not on the GPU";
158
115
  if (!signalInput.kernels && !signalInput.queryPoint)
159
116
  return "the expected signal has no query points";
160
- if (setup.ahdCollect)
161
- return "a collect pass of the correction states runs on the CPU";
162
- if (setup.toneCurve !== "identity" && setup.toneCurve !== "positive-reinhard-per-channel")
117
+ if (!["identity", "positive-reinhard-per-channel", "extended-reinhard-luminance", "naka-rushton-luminance"].includes(setup.toneCurve))
163
118
  return "unsupported tone curve " + String(setup.toneCurve);
119
+ if (setup.rendering && !setup.coupled)
120
+ return "the coupled rendering steps were not prepared";
121
+ if (setup.rendering?.highlightMode === "reconstruct-neighbors")
122
+ return "highlightMode reconstruct-neighbors (#602; its regions' ratios are found on the host) is not on the GPU in 2.22.0";
164
123
  if (setup.region.width < 1 || setup.region.height < 1)
165
124
  return "an empty region";
166
125
  if (signalInput.wavelengthsNm.length < 1 || signalInput.wavelengthsNm.length > DISPLAY_VIEW_QUALITY_MAXIMUM_NODES)
@@ -406,7 +365,6 @@ function constants(setup, signalInput, gather, reach, count, blocked) {
406
365
  words[9] = setup.toneCurve === "positive-reinhard-per-channel" ? 1 : 0;
407
366
  words[10] = reach;
408
367
  words[11] = count;
409
- words[16] = setup.ahdCorrection ? 1 : 0;
410
368
  words[18] = gather.tapCount;
411
369
  words[19] = gather.groups.length;
412
370
  for (let c = 0; c < 3; c++)
@@ -419,7 +377,6 @@ function constants(setup, signalInput, gather, reach, count, blocked) {
419
377
  }
420
378
  floats[27] = 8.5 * 8.5;
421
379
  floats[31] = setup.clipLevel;
422
- floats[35] = setup.ahdCorrection ? setup.ahdCorrection.thresholdVariance : 0;
423
380
  for (let r = 0; r < 3; r++)
424
381
  for (let c = 0; c < 3; c++)
425
382
  floats[40 + r * 3 + c] = setup.matrix[r][c];
@@ -441,10 +398,6 @@ function constants(setup, signalInput, gather, reach, count, blocked) {
441
398
  floats[52] = Math.max(FLOAT32_MINIMUM_NORMAL, Math.fround(gather.tinyMass * (1 + 4 * E)));
442
399
  return { params, words, ints, floats };
443
400
  }
444
- /** The reference's correction setup of a display-view setup with the AHD correction. */
445
- function correctionSetup(setup) {
446
- return { seed: setup.seed, readout: setup.readout, gains: setup.gains, clipLevel: setup.clipLevel, matrix: setup.matrix, toneCurve: setup.toneCurve, ahdCorrection: setup.ahdCorrection };
447
- }
448
401
  /** How far the host's quantization term Σ(w/A)²·q(m) can move with a mean bound Δm (see QUANTIZATION_SLOPE), doubled. */
449
402
  function quantizationBound(moments, o, meanBound, pixels, step) {
450
403
  let join = 0;
@@ -462,7 +415,7 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
462
415
  if (reason)
463
416
  throw new ImageKernelExecutionError("unsupported-on-backend", "webgpu-f32 display view (Quality): " + reason + "; use cpu-reference.");
464
417
  const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
465
- const timings = { lattice: 0, sites: 0, pixels: 0, states: 0, finishing: 0, recompute: 0, pixelRedispatches: 0, siteDispatches: 0 };
418
+ const timings = { lattice: 0, sites: 0, pixels: 0, finishing: 0, recompute: 0, siteDispatches: 0 };
466
419
  const { region, margin } = setup, shape = footprintShape(setup), { reach, count } = candidatesOf(setup);
467
420
  // #492: the defocus disc's kernels are summed in blocks (their dense rectangles), in focus the tap list runs as before.
468
421
  const blocked = !!image || (input.blockedSum ?? (setup.gpu?.defocusRadius ?? 0) > 0);
@@ -506,19 +459,26 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
506
459
  if (gather.table.byteLength > runtime.maximumStorageBindingBytes)
507
460
  throw new ImageKernelExecutionError("insufficient-limits", "The display view's kernel table (" + gather.table.byteLength +
508
461
  " bytes: 3 channels × " + gather.tapCount + " taps × " + gather.nodes + " spectral nodes, and the material spectra) exceeds the device's storage binding limit of " + runtime.maximumStorageBindingBytes + " bytes.");
509
- const largestBinding = Math.max(bandBytesOf(rows), geometry.byteLength, gather.table.byteLength, 4 * DISPLAY_VIEW_QUALITY_BITMAP_WORDS);
462
+ const largestBinding = Math.max(bandBytesOf(rows), geometry.byteLength, gather.table.byteLength);
510
463
  if (largestBinding > runtime.maximumStorageBindingBytes)
511
464
  throw new ImageKernelExecutionError("insufficient-limits", "One display-view band exceeds the device's storage binding limit.");
512
465
  const latticePipeline = image ? await pipelines(...image.lattice) : await pipelines("photivra-display-view-quality-lattice:" + material.id, assembleDisplayViewQualityLatticeWgsl(material.wgsl));
513
466
  const sitesPipeline = image ? await pipelines(...image.sites) : blocked ? await pipelines("photivra-display-view-quality-sites-blocked", DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL) :
514
467
  await pipelines("photivra-display-view-quality-sites", DISPLAY_VIEW_QUALITY_SITES_WGSL);
515
- const pixelsPipeline = await pipelines("photivra-display-view-quality-pixels", DISPLAY_VIEW_QUALITY_PIXELS_WGSL);
468
+ // #593, #601: the coupled steps' text and constants, loaded on demand for a view that asks for them.
469
+ const coupled = setup.rendering ? await import("./wgsl-display-view-coupled.js") : null;
470
+ const coupledConstants = coupled ? coupled.displayViewCoupledConstants(setup, SDR_LUMINANCE_WEIGHTS, gaussLegendre16()) : new Float32Array(1);
471
+ const pixelsPipeline = coupled ? await pipelines("photivra-display-view-quality-pixels+coupled", assembleDisplayViewQualityPixelsWgsl(coupled.DISPLAY_VIEW_COUPLED_WGSL))
472
+ : await pipelines("photivra-display-view-quality-pixels", DISPLAY_VIEW_QUALITY_PIXELS_WGSL);
516
473
  const moments = new Float32Array(pixelCount * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS), pixelFlags = new Uint32Array(pixelCount), masks = new Uint32Array(2 * pixelCount);
517
- const states = input.states ?? new CorrectionStateStore(), correction = setup.ahdCorrection ? correctionSetup(setup) : null;
518
- let statesComputed = 0, latticePointsResolved = 0;
474
+ let latticePointsResolved = 0;
519
475
  const scope = new GpuResourceScope(runtime);
520
476
  try {
521
- const auxBuffer = scope.upload("photivra-display-view-aux", aux, BUFFER_USAGE.STORAGE);
477
+ // #593, #601: the coupled steps' constants follow the aux table's own entries (one zero word without them).
478
+ const auxWithCoupled = new Float32Array(aux.length + coupledConstants.length);
479
+ auxWithCoupled.set(aux);
480
+ auxWithCoupled.set(coupledConstants, aux.length);
481
+ const auxBuffer = scope.upload("photivra-display-view-aux", auxWithCoupled, BUFFER_USAGE.STORAGE);
522
482
  const geometryBuffer = scope.upload("photivra-display-view-geometry", geometry, BUFFER_USAGE.STORAGE);
523
483
  // The blocked pass reads no tap records (its layout has no binding 2).
524
484
  const tapsBuffer = blocked ? null : scope.upload("photivra-display-view-taps", gather.taps, BUFFER_USAGE.STORAGE);
@@ -611,71 +571,32 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
611
571
  }
612
572
  words[51] = 0;
613
573
  timings.sites += now() - t0;
614
- // Pass 3: display pixels, repeated once when a correction state was missing and has been computed.
574
+ // Pass 3: display pixels.
615
575
  words[0] = count;
616
576
  const out = band.buffer("photivra-display-view-moments", count * 4 * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS, BUFFER_USAGE.STORAGE | BUFFER_USAGE.COPY_SRC);
617
577
  const flagBuffer = band.buffer("photivra-display-view-flags", count * 4 * DISPLAY_VIEW_QUALITY_FLAG_WORDS, BUFFER_USAGE.STORAGE | BUFFER_USAGE.COPY_SRC);
618
- const bitmapBuffer = band.buffer("photivra-display-view-bitmap", 4 * DISPLAY_VIEW_QUALITY_BITMAP_WORDS, BUFFER_USAGE.STORAGE | BUFFER_USAGE.COPY_SRC);
619
- for (let attempt = 0;; attempt++) {
620
- checkAbort(abort.signal);
621
- t0 = now();
622
- const table = states.table(), stateKeys = table.keys.length > 0 ? table.keys : new Int32Array(1), stateValues = table.values.length > 0 ? table.values : new Float32Array(3);
623
- words[17] = table.keys.length;
624
- const pass = new GpuResourceScope(runtime);
625
- let data, flagData, bitmapData;
626
- try {
627
- const pixelParams = pass.upload("photivra-display-view-params-pixels", new Uint8Array(params.slice(0)), BUFFER_USAGE.UNIFORM);
628
- const keysBuffer = pass.upload("photivra-display-view-state-keys", stateKeys, BUFFER_USAGE.STORAGE);
629
- const valuesBuffer = pass.upload("photivra-display-view-state-values", stateValues, BUFFER_USAGE.STORAGE);
630
- const pixelGroup = runtime.device.createBindGroup({ layout: pixelsPipeline.getBindGroupLayout(0), entries: [
631
- { binding: 0, resource: { buffer: pixelParams } }, { binding: 1, resource: { buffer: auxBuffer } }, { binding: 2, resource: { buffer: geometryBuffer } },
632
- { binding: 3, resource: { buffer: siteBuffer } }, { binding: 4, resource: { buffer: keysBuffer } }, { binding: 5, resource: { buffer: valuesBuffer } },
633
- { binding: 6, resource: { buffer: bitmapBuffer } }, { binding: 7, resource: { buffer: out } }, { binding: 8, resource: { buffer: flagBuffer } }
634
- ] });
635
- [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);
636
- }
637
- finally {
638
- pass.release();
639
- }
640
- timings.pixels += now() - t0;
641
- const flagWords = new Uint32Array(flagData, 0, count * DISPLAY_VIEW_QUALITY_FLAG_WORDS), bandFlags = new Uint32Array(count);
642
- for (let i = 0; i < count; i++)
643
- bandFlags[i] = flagWords[DISPLAY_VIEW_QUALITY_FLAG_WORDS * i];
644
- let missing = false;
645
- for (let i = 0; i < count && !missing; i++)
646
- if ((bandFlags[i] & DISPLAY_VIEW_QUALITY_FLAG.stateMissing) !== 0)
647
- missing = true;
648
- if (missing && correction && attempt === 0) {
649
- // The grid keys the band needs that the store lacks: compute them with the reference (the realm's cache serves known ones), then dispatch again.
650
- t0 = now();
651
- const bitmap = new Uint32Array(bitmapData, 0, DISPLAY_VIEW_QUALITY_BITMAP_WORDS), needed = [];
652
- for (let w = 0; w < DISPLAY_VIEW_QUALITY_BITMAP_WORDS; w++) {
653
- let bitsWord = bitmap[w];
654
- while (bitsWord !== 0) {
655
- const bit = 31 - Math.clz32(bitsWord);
656
- bitsWord &= ~(1 << bit);
657
- const key = w * 32 + bit;
658
- if (key < AHD_KEY_LEVELS ** 3 && !states.has(key))
659
- needed.push(key);
660
- }
661
- }
662
- if (needed.length > 0) {
663
- const computed = await computeDisplayViewCorrectionStates({ ...correction, yieldControl: async () => { if (yieldControl)
664
- await yieldControl(abort.signal); }, checkAbort: () => checkAbort(abort.signal) }, Int32Array.from(needed));
665
- states.add(computed.gridKeys, computed.values);
666
- statesComputed += needed.length;
667
- }
668
- timings.states += now() - t0;
669
- timings.pixelRedispatches++;
670
- continue;
671
- }
672
- moments.set(new Float32Array(data, 0, count * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS), first * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS);
673
- pixelFlags.set(bandFlags, first);
674
- for (let i = 0; i < count; i++) {
675
- masks[2 * (first + i)] = flagWords[DISPLAY_VIEW_QUALITY_FLAG_WORDS * i + 1];
676
- masks[2 * (first + i) + 1] = flagWords[DISPLAY_VIEW_QUALITY_FLAG_WORDS * i + 2];
677
- }
678
- break;
578
+ checkAbort(abort.signal);
579
+ t0 = now();
580
+ const pass = new GpuResourceScope(runtime);
581
+ let data, flagData;
582
+ try {
583
+ const pixelParams = pass.upload("photivra-display-view-params-pixels", new Uint8Array(params.slice(0)), BUFFER_USAGE.UNIFORM);
584
+ const pixelGroup = runtime.device.createBindGroup({ layout: pixelsPipeline.getBindGroupLayout(0), entries: [
585
+ { binding: 0, resource: { buffer: pixelParams } }, { binding: 1, resource: { buffer: auxBuffer } }, { binding: 2, resource: { buffer: geometryBuffer } },
586
+ { binding: 3, resource: { buffer: siteBuffer } }, { binding: 4, resource: { buffer: out } }, { binding: 5, resource: { buffer: flagBuffer } }
587
+ ] });
588
+ [data, flagData] = 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 }], abort.signal);
589
+ }
590
+ finally {
591
+ pass.release();
592
+ }
593
+ timings.pixels += now() - t0;
594
+ const flagWords = new Uint32Array(flagData, 0, count * DISPLAY_VIEW_QUALITY_FLAG_WORDS);
595
+ moments.set(new Float32Array(data, 0, count * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS), first * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS);
596
+ for (let i = 0; i < count; i++) {
597
+ pixelFlags[first + i] = flagWords[DISPLAY_VIEW_QUALITY_FLAG_WORDS * i];
598
+ masks[2 * (first + i)] = flagWords[DISPLAY_VIEW_QUALITY_FLAG_WORDS * i + 1];
599
+ masks[2 * (first + i) + 1] = flagWords[DISPLAY_VIEW_QUALITY_FLAG_WORDS * i + 2];
679
600
  }
680
601
  }
681
602
  finally {
@@ -698,7 +619,7 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
698
619
  const expected = new Float64Array(pixelCount * 3), noiseStd = new Float64Array(pixelCount * 3), saturated = new Float64Array(pixelCount);
699
620
  const meanBound = new Float32Array(pixelCount), varianceBound = new Float32Array(pixelCount);
700
621
  const flagged = [];
701
- let largestMean = 0, largestVariance = 0, sampleCount = 0, siteCount = 0, corrected = 0;
622
+ let largestMean = 0, largestVariance = 0, sampleCount = 0, siteCount = 0;
702
623
  const counts = new Int32Array(geometry.buffer);
703
624
  // #533: each display pixel's saturated-pixel share from the kernel's mask (the reference's arithmetic, exact where it decided).
704
625
  const columnOverlaps = Array.from({ length: region.width }, (_, i) => overlaps((region.x + i) * shape.sx, (region.x + i + 1) * shape.sx, setup.frameWidth));
@@ -724,8 +645,6 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
724
645
  varianceBound[i] = MARGIN * moments[o + 10] + quantizationBound(moments, o, meanBound[i], pixels, step);
725
646
  largestMean = Math.max(largestMean, meanBound[i]);
726
647
  largestVariance = Math.max(largestVariance, varianceBound[i]);
727
- if ((pixelFlags[i] & DISPLAY_VIEW_QUALITY_FLAG.corrected) !== 0)
728
- corrected++;
729
648
  const nx = counts[rx * stride + 1];
730
649
  sampleCount += nx * ny;
731
650
  siteCount += (nx + 2 * reach) * (ny + 2 * reach);
@@ -776,7 +695,6 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
776
695
  varianceBound[first + k] = 0;
777
696
  recomputed.push(first + k);
778
697
  }
779
- corrected += part.ahdCorrectedPixelCount;
780
698
  sampleCount += part.sampleCount;
781
699
  siteCount += part.siteCount;
782
700
  });
@@ -788,10 +706,10 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
788
706
  let saturatedPixelCount = 0;
789
707
  for (let i = 0; i < pixelCount; i++)
790
708
  saturatedPixelCount += saturated[i];
791
- return { samples, expected, noiseStd, sampleCount, siteCount, detailPixelCount: pixelCount, uniformPixelCount: 0, ahdCorrectedPixelCount: corrected, estimation: "exact-area-mean", saturated, saturatedPixelCount,
709
+ return { samples, expected, noiseStd, sampleCount, siteCount, detailPixelCount: pixelCount, uniformPixelCount: 0, estimation: "exact-area-mean", saturated, saturatedPixelCount,
792
710
  execution: webGpuExecution(expectedValueBound, uncertain), expectedValueBound, varianceBound: { kind: "relative-to-reference", relative, absolute: largestVariance },
793
711
  expectedValueAbsoluteBound: meanBound, varianceAbsoluteBound: varianceBound, flaggedPixelIndices: Uint32Array.from(flagged), hostRecomputedPixelIndices: Uint32Array.from(recomputed),
794
- pixelFlags, highlightRecomputedPixelIndices: Uint32Array.from(highlights), statesComputed, statesHeld: states.size, tapsPerSite: gather.tapCount, blockedSum: blocked, siteSumRelativeBound: floats[50], latticePointsResolved, stageMilliseconds: timings };
712
+ pixelFlags, highlightRecomputedPixelIndices: Uint32Array.from(highlights), tapsPerSite: gather.tapCount, blockedSum: blocked, siteSumRelativeBound: floats[50], latticePointsResolved, stageMilliseconds: timings };
795
713
  }
796
714
  /**
797
715
  * The GPU setup the material modules read for a Quality render (#491's `createDisplayViewMaterialModules`). The composite module
@@ -822,19 +740,6 @@ export function qualityMaterialSetup(gpu) {
822
740
  export function qualityMaterialModule(modules, gpu, signal) {
823
741
  return modules.moduleFor(qualityMaterialSetup(gpu), signal);
824
742
  }
825
- /** The pixel-aperture samples per axis and anti-aliasing spots of a request. */
826
- export function offsetCount(request) { return request.boxSamples * request.boxSamples * (request.antiAliasing ? 4 : 1); }
827
- /**
828
- * Why the Quality renderer does not support a prepared request (`DisplayViewGpuRenderer.supports`), or true: engine charts and
829
- * declared `material-layers` scenes (the material modules of `createDisplayViewMaterialModules`, #491), the identity or Reinhard
830
- * tone curve and at most 64 query offsets (lattice groups). `lens.defocus` out of focus is supported (#492 step 2c: the site pass
831
- * gathers the disc, or each node's diffraction kernel convolved with it, up to the reference's own caps of radius 64 and 96);
832
- * whether a view's footprints take the exact area mean, whether the device passes the composite probe, and whether the kernel
833
- * table fits the device's storage binding limit, is known only per render.
834
- */
835
- export function displayViewQualityGpuSupports(request) {
836
- return sceneToneAndOffsetsReason(request, MAXIMUM_GROUPS);
837
- }
838
743
  /** A normalized, symmetric site kernel of radius R for the self-check (a binned Gaussian of width `sigma` sites). */
839
744
  function syntheticKernel(radius, sigma) {
840
745
  const size = 2 * radius + 1, kernel = new Float64Array(size * size);
@@ -855,8 +760,7 @@ function syntheticKernel(radius, sigma) {
855
760
  * the GPU against `renderDisplayView` within the a-priori bounds, every flagged pixel recomputed:
856
761
  *
857
762
  * - `display-view:quality:checkerboard-13x9-diffraction-ahd` (#479): synthetic radius-1 diffraction kernels at three spectral
858
- * nodes, the AHD candidates and the low-signal correction (few electrons per site, so the correction applies; 64 draws per
859
- * state keep the check short), the tap-list site pass;
763
+ * nodes, the AHD candidates at few electrons per site, the tap-list site pass;
860
764
  * - `display-view:quality:checkerboard-13x9-defocus-blocked` (#492): the engine's own defocus disc of 6.3 sites (radius 3) at
861
765
  * every node, the bilinear demosaic (a defocused Speed request's route), the blocked site sum, and a tap budget that splits
862
766
  * each band's site pass over several dispatches.
@@ -872,13 +776,11 @@ async function runQualityConformance(handle, signal, defocus) {
872
776
  const signalInput = { nativeWidth, nativeHeight, outputWidth: frameWidth, wavelengthsNm: nodes, electronsPerRadiance: conformanceResponses(defocus ? 1 : 0.1),
873
777
  offsets, kernels, scene, channelAt: conformanceChannelAt };
874
778
  const setup = conformanceSetup(signalInput, { perElectron: defocus ? 1 / 40000 : 1 / 4000, darkElectrons: 0.07, constantVariance: 2.5e-7 }, 479, 5 + (disc ? disc.radius : 0), signal, defocus ? {} : { candidates: [0, 1, 2, 3].map((p) => ahdCandidateKernels(p % 2, Math.floor(p / 2))),
875
- ahdCorrection: { labMatrix: [[0.4124, 0.3576, 0.1805], [0.2126, 0.7152, 0.0722], [0.0193, 0.1192, 0.9505]].map((row) => row.map((v, c) => v * [1.1, 1, 1.6][c])), drawsPerState: 64, thresholdVariance: 1e-4 } });
779
+ ahdChoice: { labMatrix: [[0.4124, 0.3576, 0.1805], [0.2126, 0.7152, 0.0722], [0.0193, 0.1192, 0.9505]].map((row) => row.map((v, c) => v * [1.1, 1, 1.6][c])) } });
876
780
  const reference = await renderDisplayView(setup);
877
781
  return await withConformanceAbort(runtime, signal, async (abort, pipelines) => {
878
782
  const gather = defocus ? { blockedSum: true, maximumSiteTapsPerDispatch: 64 * 4 * (2 * 3 + 2) ** 2 } : {};
879
783
  const gpu = await renderDisplayViewQualityOnGpu(runtime, 1 << 20, { setup, signalInput, material: chartPresetMaterialModule(chart), ...gather }, abort, undefined, pipelines);
880
- if (!defocus && (gpu.ahdCorrectedPixelCount === 0 || reference.ahdCorrectedPixelCount === 0))
881
- return "the self-check did not exercise the low-signal correction.";
882
784
  if (defocus && (!gpu.blockedSum || gpu.stageMilliseconds.siteDispatches < 2))
883
785
  return "the self-check did not exercise the blocked site sum over several dispatches.";
884
786
  // #533: the verdict includes the saturated-pixel count (the light squares sit at the white clip).
@@ -891,97 +793,6 @@ export const DISPLAY_VIEW_QUALITY_CONFORMANCE_CASE = { id: "display-view:quality
891
793
  /** #492's self-check of the defocus disc's blocked site sum (see `runQualityConformance`). */
892
794
  export const DISPLAY_VIEW_QUALITY_DEFOCUS_CONFORMANCE_CASE = { id: "display-view:quality:checkerboard-13x9-defocus-blocked",
893
795
  run: (handle, signal) => runQualityConformance(handle, signal, true) };
894
- /** The bilinear RGGB demosaic kernels per phase (phaseY·2 + phaseX) and channel, as `bilinearPhaseProfiles` gives them. */
895
- export function bilinearKernels() {
896
- const cross = [[1, 0], [-1, 0], [0, 1], [0, -1]], diagonal = [[1, 1], [1, -1], [-1, 1], [-1, -1]], horizontal = [[1, 0], [-1, 0]], vertical = [[0, 1], [0, -1]];
897
- const average = (offsets) => offsets.map(([dx, dy]) => ({ dx: dx, dy: dy, weight: 1 / offsets.length })), self = [{ dx: 0, dy: 0, weight: 1 }];
898
- return [[self, average(cross), average(diagonal)], [average(horizontal), self, average(vertical)], [average(vertical), self, average(horizontal)], [average(diagonal), average(cross), self]];
899
- }
900
- /**
901
- * The display view's GPU renderer on one runtime (#481's `DisplayViewGpuRenderer`): the Speed kernel for requests it covers
902
- * (no diffraction, no AHD, no defocus disc, at most 16 offsets) and the Quality kernels for the rest (#492: a defocused Speed
903
- * request too, with its bilinear demosaic). One pipeline cache per device, one set of
904
- * material modules (#491: chart presets, composite charts, the 2.3 elements and material-layers scenes; `dispose` releases their
905
- * uploaded scene records), one correction-state store per correction key. `render` renders the part's region (`setup.gpu`
906
- * carries the expected signal's input and the chart; a setup without it, a coarse view or a frame the kernels do not cover
907
- * throws `unsupported-on-backend`), and throws `aborted` and `device-lost` as the accelerator's operations do.
908
- */
909
- /** #564: the GPU refusal of a defocus kernel above the direct radius (the CPU convolves it by runs). */
910
- const LARGE_DEFOCUS_REASON = "a defocus kernel radius above 64 sites (convolved by runs on the CPU, #564) is not on the GPU";
911
- export function createDisplayViewGpuRenderer(runtime, options = {}) {
912
- const pipelines = new Map(), stores = new Map();
913
- const bandBytes = options.maximumBandBytes ?? Math.min(64 * 1024 * 1024, runtime.maximumStorageBindingBytes);
914
- const source = (label, code) => {
915
- let pending = pipelines.get(label);
916
- if (!pending) {
917
- pending = createComputePipeline(runtime, label, code, undefined);
918
- pipelines.set(label, pending);
919
- pending.catch(() => pipelines.delete(label));
920
- }
921
- return pending;
922
- };
923
- // #491: one set of material modules per device for both kernels (the preset module per chart; the composite module, its probe
924
- // and its uploaded scene records), released by `dispose`.
925
- const modules = createDisplayViewMaterialModules(runtime, source);
926
- return {
927
- id: "photivra-display-view",
928
- supports(request) {
929
- if (request.largeDefocus)
930
- return LARGE_DEFOCUS_REASON;
931
- // #492: out of focus the expected signal carries the defocus disc as site kernels, which only the Quality site pass gathers;
932
- // a Speed request then runs the Quality passes with its own bilinear demosaic (one candidate) and finishing.
933
- if (request.defocus)
934
- return displayViewQualityGpuSupports(request);
935
- const speed = displayViewSpeedGpuSupports(request);
936
- if (speed === true)
937
- return true;
938
- return displayViewQualityGpuSupports(request);
939
- },
940
- conformanceCases: [DISPLAY_VIEW_SPEED_CONFORMANCE_CASE, DISPLAY_VIEW_QUALITY_CONFORMANCE_CASE, DISPLAY_VIEW_QUALITY_DEFOCUS_CONFORMANCE_CASE],
941
- async render(part) {
942
- const { setup, signal } = part;
943
- if (!setup.gpu)
944
- throw new ImageKernelExecutionError("unsupported-on-backend", "The display-view setup carries no GPU inputs (setup.gpu); use cpu-reference.");
945
- const abort = new AbortController(), onAbort = () => abort.abort();
946
- if (signal?.aborted)
947
- throw abortedError();
948
- signal?.addEventListener("abort", onAbort, { once: true });
949
- try {
950
- const signalInput = setup.gpu.signalInput;
951
- let result;
952
- let material;
953
- if (displayViewSpeedGpuUnsupportedReason(setup, signalInput) === null) {
954
- material = await modules.moduleFor(setup.gpu, abort.signal);
955
- result = await renderDisplayViewSpeedOnGpu(runtime, bandBytes, { setup, signalInput, material }, abort, options.yieldControl, source);
956
- }
957
- else {
958
- // #581: an image-plane print brings its own passes 1 and 2.
959
- const image = signalInput.scene.image ? await modules.imagePasses(setup.gpu, abort.signal) : undefined;
960
- material = image ? { id: "image-plane", wgsl: "", bindings: [] } : await qualityMaterialModule(modules, setup.gpu, abort.signal);
961
- const key = displayViewCorrectionKey(setup);
962
- let states = key === null ? undefined : stores.get(key);
963
- if (key !== null && !states) {
964
- states = new CorrectionStateStore();
965
- stores.set(key, states);
966
- }
967
- result = await renderDisplayViewQualityOnGpu(runtime, bandBytes, { setup, signalInput, material, ...(image ? { image } : {}), ...(states ? { states } : {}) }, abort, options.yieldControl, source);
968
- }
969
- return { samples: result.samples, expected: result.expected, noiseStd: result.noiseStd, sampleCount: result.sampleCount, siteCount: result.siteCount, detailPixelCount: result.detailPixelCount,
970
- uniformPixelCount: result.uniformPixelCount, ahdCorrectedPixelCount: result.ahdCorrectedPixelCount, estimation: result.estimation, saturated: result.saturated,
971
- saturatedPixelCount: result.saturatedPixelCount, errorBound: result.expectedValueBound,
972
- diagnosticUncertaintySampleCount: result.execution.diagnosticUncertaintySampleCount, ...(material.compositeProbe ? { compositeProbe: material.compositeProbe } : {}) };
973
- }
974
- finally {
975
- signal?.removeEventListener("abort", onAbort);
976
- }
977
- },
978
- dispose() { modules.release(); stores.clear(); }
979
- };
980
- }
981
- /** The factory `prepareDisplayView` creates the renderer with (one per device): the Speed and Quality kernels behind one renderer. */
982
- export const displayViewGpuRendererFactory = (handle) => createDisplayViewGpuRenderer(handle);
983
- /** Registers the combined renderer as the display view's GPU renderer and returns the factory registered before. */
984
- export function registerDisplayViewGpuRenderer() {
985
- return setDisplayViewGpuRendererFactory(displayViewGpuRendererFactory);
986
- }
796
+ /** #594: the Quality kernels' self-check cases, run before the first render that takes the Quality passes (`onDemandConformanceCases`). */
797
+ export const DISPLAY_VIEW_QUALITY_CONFORMANCE_CASES = Object.freeze([DISPLAY_VIEW_QUALITY_CONFORMANCE_CASE, DISPLAY_VIEW_QUALITY_DEFOCUS_CONFORMANCE_CASE]);
987
798
  //# sourceMappingURL=display-view-quality-gpu.js.map