@photivra/engine 2.20.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 (275) hide show
  1. package/CHANGELOG.md +295 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +7 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +18 -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-lens-correction-opcodes.d.ts +2 -2
  13. package/dist/capture/dng-lens-correction-opcodes.js +2 -2
  14. package/dist/capture/dng-lens-correction-opcodes.js.map +1 -1
  15. package/dist/capture/dng-opcodes.d.ts +96 -0
  16. package/dist/capture/dng-opcodes.js +391 -0
  17. package/dist/capture/dng-opcodes.js.map +1 -0
  18. package/dist/capture/dng-profile-maps.d.ts +99 -0
  19. package/dist/capture/dng-profile-maps.js +381 -0
  20. package/dist/capture/dng-profile-maps.js.map +1 -0
  21. package/dist/capture/dng-stage-image.d.ts +23 -0
  22. package/dist/capture/dng-stage-image.js +65 -0
  23. package/dist/capture/dng-stage-image.js.map +1 -0
  24. package/dist/capture/exposure-build-up-stage.d.ts +16 -0
  25. package/dist/capture/exposure-build-up-stage.js +4 -0
  26. package/dist/capture/exposure-build-up-stage.js.map +1 -0
  27. package/dist/capture/jpeg-xl-tiles.d.ts +22 -0
  28. package/dist/capture/jpeg-xl-tiles.js +151 -0
  29. package/dist/capture/jpeg-xl-tiles.js.map +1 -0
  30. package/dist/capture/lossy-jpeg.d.ts +17 -0
  31. package/dist/capture/lossy-jpeg.js +369 -0
  32. package/dist/capture/lossy-jpeg.js.map +1 -0
  33. package/dist/capture/native-environment-lean.d.ts +2 -1
  34. package/dist/capture/native-environment-lean.js +41 -73
  35. package/dist/capture/native-environment-lean.js.map +1 -1
  36. package/dist/capture/native-environment-tabulated.js +4 -3
  37. package/dist/capture/native-environment-tabulated.js.map +1 -1
  38. package/dist/capture/native-raw-development.d.ts +2 -1
  39. package/dist/capture/native-raw-development.js.map +1 -1
  40. package/dist/capture/native-raw.d.ts +3 -2
  41. package/dist/capture/native-raw.js +5 -4
  42. package/dist/capture/native-raw.js.map +1 -1
  43. package/dist/capture/sensor-raw-producer.d.ts +4 -1
  44. package/dist/capture/sensor-raw-producer.js +12 -3
  45. package/dist/capture/sensor-raw-producer.js.map +1 -1
  46. package/dist/color/neighbor-highlight-reconstruction.d.ts +66 -0
  47. package/dist/color/neighbor-highlight-reconstruction.js +234 -0
  48. package/dist/color/neighbor-highlight-reconstruction.js.map +1 -0
  49. package/dist/color/sensor-color-development.d.ts +5 -0
  50. package/dist/color/sensor-color-development.js +7 -1
  51. package/dist/color/sensor-color-development.js.map +1 -1
  52. package/dist/color/spectral-spikiness.d.ts +11 -0
  53. package/dist/color/spectral-spikiness.js +23 -0
  54. package/dist/color/spectral-spikiness.js.map +1 -0
  55. package/dist/core/execution-error.d.ts +21 -0
  56. package/dist/core/execution-error.js +12 -0
  57. package/dist/core/execution-error.js.map +1 -0
  58. package/dist/core/version.d.ts +1 -1
  59. package/dist/core/version.js +1 -1
  60. package/dist/core/version.js.map +1 -1
  61. package/dist/equipment/generic-lens-catalogue.d.ts +3 -2
  62. package/dist/equipment/generic-lens-catalogue.js +18 -5
  63. package/dist/equipment/generic-lens-catalogue.js.map +1 -1
  64. package/dist/equipment/generic-tier-assets.d.ts +32 -0
  65. package/dist/equipment/generic-tier-assets.js +16 -11
  66. package/dist/equipment/generic-tier-assets.js.map +1 -1
  67. package/dist/equipment/sensor-profile-body.d.ts +78 -0
  68. package/dist/equipment/sensor-profile-body.js +77 -0
  69. package/dist/equipment/sensor-profile-body.js.map +1 -0
  70. package/dist/equipment/sensor-profiles.d.ts +8 -0
  71. package/dist/equipment/sensor-profiles.js +82 -0
  72. package/dist/equipment/sensor-profiles.js.map +1 -0
  73. package/dist/facade/catalogue-lens-signal.d.ts +28 -3
  74. package/dist/facade/catalogue-lens-signal.js +83 -78
  75. package/dist/facade/catalogue-lens-signal.js.map +1 -1
  76. package/dist/facade/chart-radiance-batch.js +2 -1
  77. package/dist/facade/chart-radiance-batch.js.map +1 -1
  78. package/dist/facade/defocus-image-space-provider.d.ts +7 -0
  79. package/dist/facade/defocus-image-space-provider.js +64 -76
  80. package/dist/facade/defocus-image-space-provider.js.map +1 -1
  81. package/dist/facade/diffraction-provider.d.ts +1 -1
  82. package/dist/facade/diffraction-provider.js +4 -4
  83. package/dist/facade/diffraction-provider.js.map +1 -1
  84. package/dist/facade/display-view-gpu-session.js +39 -5
  85. package/dist/facade/display-view-gpu-session.js.map +1 -1
  86. package/dist/facade/display-view-gpu.d.ts +19 -6
  87. package/dist/facade/display-view-gpu.js.map +1 -1
  88. package/dist/facade/display-view-lens-signal.js +25 -23
  89. package/dist/facade/display-view-lens-signal.js.map +1 -1
  90. package/dist/facade/display-view-normal.d.ts +15 -0
  91. package/dist/facade/display-view-normal.js +44 -0
  92. package/dist/facade/display-view-normal.js.map +1 -0
  93. package/dist/facade/display-view-rendering.d.ts +97 -0
  94. package/dist/facade/display-view-rendering.js +598 -0
  95. package/dist/facade/display-view-rendering.js.map +1 -0
  96. package/dist/facade/display-view.d.ts +37 -65
  97. package/dist/facade/display-view.js +152 -499
  98. package/dist/facade/display-view.js.map +1 -1
  99. package/dist/facade/expected-signal-setup.d.ts +3 -1
  100. package/dist/facade/expected-signal-setup.js +3 -3
  101. package/dist/facade/expected-signal-setup.js.map +1 -1
  102. package/dist/facade/expected-signal.d.ts +47 -14
  103. package/dist/facade/expected-signal.js +117 -112
  104. package/dist/facade/expected-signal.js.map +1 -1
  105. package/dist/facade/exposure-build-up.d.ts +70 -0
  106. package/dist/facade/exposure-build-up.js +164 -0
  107. package/dist/facade/exposure-build-up.js.map +1 -0
  108. package/dist/facade/fast-methods.d.ts +1 -1
  109. package/dist/facade/fast-methods.js +46 -9
  110. package/dist/facade/fast-methods.js.map +1 -1
  111. package/dist/facade/generic-lens-capture.d.ts +3 -3
  112. package/dist/facade/generic-lens-capture.js +3 -3
  113. package/dist/facade/generic-lens-capture.js.map +1 -1
  114. package/dist/facade/image-plane-enable.d.ts +7 -0
  115. package/dist/facade/image-plane-enable.js +17 -0
  116. package/dist/facade/image-plane-enable.js.map +1 -0
  117. package/dist/facade/image-plane-lattice-data.d.ts +6 -0
  118. package/dist/facade/image-plane-lattice-data.js +10 -0
  119. package/dist/facade/image-plane-lattice-data.js.map +1 -0
  120. package/dist/facade/image-plane-support.d.ts +33 -0
  121. package/dist/facade/image-plane-support.js +11 -0
  122. package/dist/facade/image-plane-support.js.map +1 -0
  123. package/dist/facade/image-plane.d.ts +36 -0
  124. package/dist/facade/image-plane.js +384 -0
  125. package/dist/facade/image-plane.js.map +1 -0
  126. package/dist/facade/mixed-illumination.d.ts +108 -0
  127. package/dist/facade/mixed-illumination.js +174 -0
  128. package/dist/facade/mixed-illumination.js.map +1 -0
  129. package/dist/facade/on-demand-kits.d.ts +14 -0
  130. package/dist/facade/on-demand-kits.js +36 -0
  131. package/dist/facade/on-demand-kits.js.map +1 -0
  132. package/dist/facade/opt-in-kit.d.ts +9 -0
  133. package/dist/facade/opt-in-kit.js +11 -0
  134. package/dist/facade/opt-in-kit.js.map +1 -0
  135. package/dist/facade/quality-kit.d.ts +21 -0
  136. package/dist/facade/quality-kit.js +23 -0
  137. package/dist/facade/quality-kit.js.map +1 -0
  138. package/dist/facade/quality-paths.d.ts +22 -0
  139. package/dist/facade/quality-paths.js +44 -0
  140. package/dist/facade/quality-paths.js.map +1 -0
  141. package/dist/facade/representative-light-sources.d.ts +72 -0
  142. package/dist/facade/representative-light-sources.js +124 -0
  143. package/dist/facade/representative-light-sources.js.map +1 -0
  144. package/dist/facade/scene-radiance.d.ts +145 -4
  145. package/dist/facade/scene-radiance.js +50 -13
  146. package/dist/facade/scene-radiance.js.map +1 -1
  147. package/dist/facade/simulate-capture.d.ts +68 -20
  148. package/dist/facade/simulate-capture.js +399 -146
  149. package/dist/facade/simulate-capture.js.map +1 -1
  150. package/dist/facade/simulate-display-view.d.ts +10 -12
  151. package/dist/facade/simulate-display-view.js +70 -53
  152. package/dist/facade/simulate-display-view.js.map +1 -1
  153. package/dist/gpu/chart-composite-gpu.d.ts +13 -1
  154. package/dist/gpu/chart-composite-gpu.js +48 -9
  155. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  156. package/dist/gpu/chart-radiance-gpu.js +4 -30
  157. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  158. package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
  159. package/dist/gpu/display-view-composite-material-module.js +39 -13
  160. package/dist/gpu/display-view-composite-material-module.js.map +1 -1
  161. package/dist/gpu/display-view-gpu-renderer.d.ts +43 -0
  162. package/dist/gpu/display-view-gpu-renderer.js +134 -0
  163. package/dist/gpu/display-view-gpu-renderer.js.map +1 -0
  164. package/dist/gpu/display-view-image-material-module.d.ts +87 -0
  165. package/dist/gpu/display-view-image-material-module.js +361 -0
  166. package/dist/gpu/display-view-image-material-module.js.map +1 -0
  167. package/dist/gpu/display-view-image-quality.d.ts +87 -0
  168. package/dist/gpu/display-view-image-quality.js +625 -0
  169. package/dist/gpu/display-view-image-quality.js.map +1 -0
  170. package/dist/gpu/display-view-quality-gpu.d.ts +80 -65
  171. package/dist/gpu/display-view-quality-gpu.js +187 -445
  172. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  173. package/dist/gpu/display-view-speed-gpu.d.ts +67 -19
  174. package/dist/gpu/display-view-speed-gpu.js +181 -105
  175. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  176. package/dist/gpu/image-kernel-accelerator.js +3 -0
  177. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  178. package/dist/gpu/image-kernel-execution.d.ts +2 -13
  179. package/dist/gpu/image-kernel-execution.js +2 -10
  180. package/dist/gpu/image-kernel-execution.js.map +1 -1
  181. package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +8 -0
  182. package/dist/gpu/wgsl-display-view-coupled-stub.js +21 -0
  183. package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -0
  184. package/dist/gpu/wgsl-display-view-coupled.d.ts +88 -0
  185. package/dist/gpu/wgsl-display-view-coupled.js +353 -0
  186. package/dist/gpu/wgsl-display-view-coupled.js.map +1 -0
  187. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +33 -24
  188. package/dist/gpu/wgsl-display-view-quality-kernel.js +166 -146
  189. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  190. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +19 -44
  191. package/dist/gpu/wgsl-display-view-speed-kernel.js +116 -18
  192. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  193. package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
  194. package/dist/gpu/wgsl-exact-sum.js +42 -0
  195. package/dist/gpu/wgsl-exact-sum.js.map +1 -0
  196. package/dist/index.d.ts +22 -6
  197. package/dist/index.js +16 -2
  198. package/dist/index.js.map +1 -1
  199. package/dist/motion/subject-motion.d.ts +66 -0
  200. package/dist/motion/subject-motion.js +52 -0
  201. package/dist/motion/subject-motion.js.map +1 -0
  202. package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
  203. package/dist/optics/generic-lens-lateral-color.js +77 -0
  204. package/dist/optics/generic-lens-lateral-color.js.map +1 -0
  205. package/dist/optics/generic-lens-psf.js +54 -46
  206. package/dist/optics/generic-lens-psf.js.map +1 -1
  207. package/dist/optics/lens-psf-profile.d.ts +7 -0
  208. package/dist/optics/lens-psf-profile.js +2 -1
  209. package/dist/optics/lens-psf-profile.js.map +1 -1
  210. package/dist/output/camera-channel-response.d.ts +50 -0
  211. package/dist/output/camera-channel-response.js +69 -0
  212. package/dist/output/camera-channel-response.js.map +1 -0
  213. package/dist/output/developed-lens-corrections.d.ts +19 -5
  214. package/dist/output/developed-lens-corrections.js +28 -9
  215. package/dist/output/developed-lens-corrections.js.map +1 -1
  216. package/dist/output/generic-lens-corrections.d.ts +41 -5
  217. package/dist/output/generic-lens-corrections.js +52 -8
  218. package/dist/output/generic-lens-corrections.js.map +1 -1
  219. package/dist/output/native-capture-sdr.js +5 -2
  220. package/dist/output/native-capture-sdr.js.map +1 -1
  221. package/dist/output/print-plan.d.ts +41 -5
  222. package/dist/output/print-plan.js +59 -14
  223. package/dist/output/print-plan.js.map +1 -1
  224. package/dist/output/sdr-rendering.d.ts +18 -1
  225. package/dist/output/sdr-rendering.js +39 -2
  226. package/dist/output/sdr-rendering.js.map +1 -1
  227. package/dist/sensor/binomial-sample.d.ts +3 -0
  228. package/dist/sensor/binomial-sample.js +86 -0
  229. package/dist/sensor/binomial-sample.js.map +1 -0
  230. package/dist/sensor/native-sensor-report.d.ts +10 -0
  231. package/dist/sensor/native-sensor-report.js +16 -0
  232. package/dist/sensor/native-sensor-report.js.map +1 -0
  233. package/dist/sensor/sensor-characteristics.d.ts +91 -13
  234. package/dist/sensor/sensor-characteristics.js +30 -15
  235. package/dist/sensor/sensor-characteristics.js.map +1 -1
  236. package/docs/AGENT_GUIDE.md +1 -0
  237. package/docs/API_REFERENCE.md +214 -4
  238. package/docs/QUICK_START.md +162 -8
  239. package/docs/USAGE.md +195 -22
  240. package/docs/api/capture-c2pa-embed.md +177 -0
  241. package/docs/api/capture-dng-import.md +86 -10
  242. package/docs/api/capture-dng-opcodes.md +93 -0
  243. package/docs/api/capture-dng-profile-maps.md +141 -0
  244. package/docs/api/capture-dng-stage-image.md +43 -0
  245. package/docs/api/capture-jpeg-xl-tiles.md +33 -0
  246. package/docs/api/capture-lossy-jpeg.md +41 -0
  247. package/docs/api/capture-native-raw-development.md +2 -1
  248. package/docs/api/core-execution-error.md +43 -0
  249. package/docs/api/core-version.md +1 -1
  250. package/docs/api/equipment-generic-lens-catalogue.md +10 -5
  251. package/docs/api/equipment-sensor-profile-body.md +64 -0
  252. package/docs/api/equipment-sensor-profiles.md +86 -0
  253. package/docs/api/exports.json +460 -5
  254. package/docs/api/facade-display-view-gpu.md +5 -2
  255. package/docs/api/facade-display-view.md +11 -15
  256. package/docs/api/facade-exposure-build-up.md +105 -0
  257. package/docs/api/facade-fast-methods.md +47 -10
  258. package/docs/api/facade-image-plane-enable.md +16 -0
  259. package/docs/api/facade-mixed-illumination.md +67 -0
  260. package/docs/api/facade-quality-paths.md +24 -0
  261. package/docs/api/facade-representative-light-sources.md +131 -0
  262. package/docs/api/facade-scene-radiance.md +54 -3
  263. package/docs/api/facade-simulate-capture.md +48 -11
  264. package/docs/api/facade-simulate-display-view.md +10 -12
  265. package/docs/api/gpu-image-kernel-execution.md +0 -36
  266. package/docs/api/motion-subject-motion.md +23 -0
  267. package/docs/api/optics-generic-lens-lateral-color.md +79 -0
  268. package/docs/api/output-camera-channel-response.md +55 -0
  269. package/docs/api/output-developed-lens-corrections.md +10 -1
  270. package/docs/api/output-generic-lens-corrections.md +40 -4
  271. package/docs/api/output-print-plan.md +35 -1
  272. package/docs/api/output-sdr-rendering.md +6 -1
  273. package/docs/api/sensor-sensor-characteristics.md +54 -2
  274. package/llms.txt +3 -0
  275. package/package.json +6 -5
@@ -1,22 +1,9 @@
1
- /**
2
- * Host side of the display view's Speed path on the GPU (#478): the support check, the float64 tables (site table,
3
- * overlaps over the scale, axis sums over the squared scale, 1/Σw²), banded dispatch of
4
- * `DISPLAY_VIEW_SPEED_KERNEL_WGSL`, and the finishing the CPU reference does after its per-sample loop: the output
5
- * quantization term, the seeded noise draw, the Cholesky factor, the clipped-normal centre, the sRGB encode and the
6
- * rounding, all with the reference's own float64 functions. So the noise draw is bit-identical to the CPU's, and only
7
- * the expected value and the covariance carry the float32 bound.
8
- *
9
- * The float64 reference is `renderDisplayView` (facade/display-view.ts) on the exact-area-mean path without
10
- * diffraction, with the bilinear demosaic and chart materials. Internal: not exported from the package. The
11
- * material lookup per query point is a module the caller supplies (`DisplayViewGpuMaterialModule`, the seam of
12
- * #477); `display-view-chart-material-module.ts` provides one for the eight 2.0 chart presets and
13
- * `display-view-composite-material-module.ts` (#491) one for composite charts, the 2.3 elements and `material-layers` scenes.
14
- */
15
- import { type DisplayViewRenderResult, type DisplayViewSetup } from "../facade/display-view.js";
16
- import type { DisplayViewExpectedSignalInput } from "../facade/expected-signal.js";
1
+ import { type DisplayViewKernelTap, type DisplayViewRenderResult, type DisplayViewSetup } from "../facade/display-view.js";
2
+ import { type DisplayViewExpectedSignalInput } from "../facade/expected-signal.js";
3
+ import { resolvePortableChartScene } from "../facade/chart-scenes.js";
17
4
  import { type DisplayViewGpuConformanceCase, type DisplayViewGpuRenderer, type DisplayViewGpuRendererFactory, type DisplayViewGpuRequest } from "../facade/display-view-gpu.js";
18
5
  import { type ChartCompositeProbeRecord, type ImageKernelExecution, type ImageKernelRelativeErrorBound } from "./image-kernel-execution.js";
19
- import { type WebGpuRuntime } from "./webgpu-runtime.js";
6
+ import { GpuResourceScope, type WebGpuRuntime } from "./webgpu-runtime.js";
20
7
  type PipelineSource = (label: string, code: string) => Promise<GPUComputePipeline>;
21
8
  type YieldControl = ((signal: AbortSignal) => Promise<void>) | undefined;
22
9
  /**
@@ -47,6 +34,18 @@ export interface DisplayViewGpuMaterialModule {
47
34
  buffer: GPUBuffer;
48
35
  data?: undefined;
49
36
  })[];
37
+ /**
38
+ * #581: a module that defines `scene_value` itself (the image-plane module) states the roundings its value carries beyond the
39
+ * table lookup's one, added to the mean's and (doubled) the variance's chains; and it gives the data of the kernel's binding 3 in place of the spectral table.
40
+ */
41
+ readonly extraSiteRoundings?: number;
42
+ readonly siteTable?: Float32Array<ArrayBuffer>;
43
+ /**
44
+ * #581: a module whose data is too large for one binding (a large photo) gives group 1 per band instead of `bindings`: for the
45
+ * native site rows [y0, y1] the band reads, and the most display rows a band may hold at `sitesPerDisplayRow`.
46
+ */
47
+ band?(scope: GpuResourceScope, layout: GPUBindGroupLayout, y0: number, y1: number): GPUBindGroup;
48
+ bandRows?(sitesPerDisplayRow: number): number;
50
49
  }
51
50
  /** What a GPU render of a display view needs: the reference's own setup and expected-signal input, plus the material module. */
52
51
  export interface DisplayViewSpeedGpuInput {
@@ -77,7 +76,7 @@ export interface DisplayViewSpeedGpuResult extends DisplayViewRenderResult {
77
76
  /**
78
77
  * Why the kernel does not cover a request, or null when it does. The kernel is the Speed path: the exact area mean
79
78
  * (views where a footprint holds at most 25 real pixels and at most K², and here at most 6 per axis), no diffraction
80
- * kernels, chart materials, the bilinear demosaic (no AHD candidates or correction), the identity or positive Reinhard
79
+ * kernels, chart materials, the bilinear demosaic (no AHD candidates), the identity or positive Reinhard
81
80
  * tone curve, and at most 16 query offsets.
82
81
  */
83
82
  export declare function displayViewSpeedGpuUnsupportedReason(setup: DisplayViewSetup, signalInput: DisplayViewExpectedSignalInput): string | null;
@@ -101,7 +100,7 @@ export declare function gaussLegendre16(): {
101
100
  /** One axis record of the geometry buffer: first real pixel, count, overlaps over the scale, axis sums over the squared scale. */
102
101
  export declare function axisRecord(geometry: Float32Array, at: number, start: number, end: number, limit: number, margin: number, scale: number): void;
103
102
  /** The a-priori relative bounds of the expected value and the variance (docs/WEBGPU.md, "Display view (Speed)"), doubled. */
104
- export declare function displayViewSpeedGpuRelativeBounds(toneCurve: DisplayViewSetup["toneCurve"]): {
103
+ export declare function displayViewSpeedGpuRelativeBounds(toneCurve: DisplayViewSetup["toneCurve"], extraSiteRoundings?: number): {
105
104
  mean: number;
106
105
  variance: number;
107
106
  };
@@ -119,6 +118,12 @@ export declare function finishDisplayViewPixel(moments: ArrayLike<number>, pixel
119
118
  * absolute variance bound; it changes no computed value.
120
119
  */
121
120
  export declare function displayViewSpeedQuantizationBound(moments: ArrayLike<number>, relative: number, absolute: number, pixels: number, step: number): number;
121
+ /**
122
+ * Renders the display pixels `indices` (ascending, region row-major) with the reference, a run of consecutive pixels along a row
123
+ * as one region (#457: AHD's choice flags stretches near edges): a region render is byte-identical to the same pixels of the whole
124
+ * view, so each pixel equals its one-pixel render, and a run's counts are the sums of its pixels'.
125
+ */
126
+ export declare function recomputeRuns(setup: DisplayViewSetup, signalInput: DisplayViewExpectedSignalInput, indices: readonly number[], abort: AbortController, take: (first: number, run: number, part: DisplayViewRenderResult) => void): Promise<void>;
122
127
  /** Renders one region of a Speed display view on the GPU (see the module comment). */
123
128
  export declare function renderDisplayViewSpeedOnGpu(runtime: WebGpuRuntime, bandBytes: number, input: DisplayViewSpeedGpuInput, abort: AbortController, yieldControl: YieldControl, pipelines: PipelineSource): Promise<DisplayViewSpeedGpuResult>;
124
129
  /**
@@ -130,11 +135,40 @@ export declare function renderDisplayViewSpeedOnGpu(runtime: WebGpuRuntime, band
130
135
  * render (`displayViewSpeedGpuUnsupportedReason`, `createDisplayViewMaterialModules`).
131
136
  */
132
137
  export declare function displayViewSpeedGpuSupports(request: DisplayViewGpuRequest): true | string;
138
+ /** Why the GPU's material lookup, tone curves or query-offset table do not cover a request (shared by the Speed and Quality support checks), or true. */
139
+ export declare function sceneToneAndOffsetsReason(request: DisplayViewGpuRequest, maximumOffsets: number): true | string;
140
+ /**
141
+ * A self-check's verdict: every component of a GPU render inside its a-priori bounds against the reference, every sample within
142
+ * one code, and (#533) the saturated-pixel count the reference's bit for bit; true, or a sentence saying where it failed.
143
+ */
144
+ export declare function conformanceVerdict(reference: DisplayViewRenderResult, gpu: Pick<DisplayViewSpeedGpuResult, "samples" | "expected" | "noiseStd" | "expectedValueBound" | "varianceBound" | "expectedValueAbsoluteBound" | "varianceAbsoluteBound" | "saturatedPixelCount">): true | string;
133
145
  /**
134
146
  * The renderer's run-time self-check (`DisplayViewGpuRenderer.conformanceCases`): a 38 × 26 checkerboard frame of 6 µm sites
135
147
  * shown at 13 × 9 (2.9 real pixels per display pixel) with the 2 × 2 box, light squares at the white clip, the GPU against
136
148
  * `renderDisplayView` within the a-priori bounds, every flagged pixel recomputed.
137
149
  */
150
+ /** The self-checks' common frame: 40 × 28 native sites (a 38 × 26 frame and a one-site margin) shown at 13 × 9, at three spectral nodes. */
151
+ export declare const CONFORMANCE_FRAME: Readonly<{
152
+ nativeWidth: 40;
153
+ nativeHeight: 28;
154
+ margin: 1;
155
+ frameWidth: 38;
156
+ frameHeight: 26;
157
+ nodes: number[];
158
+ }>;
159
+ /** The self-checks' channel responses per node (`scale` 1, or 1/10 for the low-signal correction's few electrons). */
160
+ export declare function conformanceResponses(scale: number): [Float64Array, Float64Array, Float64Array];
161
+ /** The RGGB channel of a native site. */
162
+ export declare const conformanceChannelAt: (x: number, y: number) => number;
163
+ /** The self-checks' checkerboard and its expected-signal scene (neutral materials under a white radiance per node). */
164
+ export declare function conformanceChart(): {
165
+ chart: ReturnType<typeof resolvePortableChartScene>;
166
+ scene: DisplayViewExpectedSignalInput["scene"];
167
+ };
168
+ /** A self-check's setup around its expected-signal input: the common frame and view, the bilinear demosaic, gains, matrix and tone curve, plus `extra`. */
169
+ export declare function conformanceSetup(signalInput: DisplayViewExpectedSignalInput, readout: DisplayViewSetup["readout"][0], seed: number, reachSites: number, signal: AbortSignal | undefined, extra?: Partial<DisplayViewSetup>): DisplayViewSetup;
170
+ /** Runs a self-check's GPU render with an abort controller that follows `signal`, and the device's pipelines under it. */
171
+ export declare function withConformanceAbort<T>(runtime: WebGpuRuntime, signal: AbortSignal | undefined, run: (abort: AbortController, pipelines: PipelineSource) => Promise<T>): Promise<T>;
138
172
  export declare const DISPLAY_VIEW_SPEED_CONFORMANCE_CASE: DisplayViewGpuConformanceCase;
139
173
  /**
140
174
  * The display view's Speed renderer on one runtime (#481's `DisplayViewGpuRenderer`): one pipeline cache per device, the
@@ -155,4 +189,18 @@ export declare const displayViewSpeedGpuRendererFactory: DisplayViewGpuRendererF
155
189
  * registered before. Nothing registers it on import: the integration of #481 decides where the engine registers it.
156
190
  */
157
191
  export declare function registerDisplayViewSpeedGpuRenderer(): DisplayViewGpuRendererFactory | null;
192
+ /** #564: the GPU refusal of a defocus kernel above the direct radius (the CPU convolves it by runs). */
193
+ export declare const LARGE_DEFOCUS_REASON = "a defocus kernel radius above 64 sites (convolved by runs on the CPU, #564) is not on the GPU";
194
+ /** The footprint shape of a view: real pixels per display pixel along each axis, and whether the exact area mean applies. */
195
+ export declare function footprintShape(setup: DisplayViewSetup): {
196
+ sx: number;
197
+ sy: number;
198
+ exact: boolean;
199
+ maximumColumns: number;
200
+ maximumRows: number;
201
+ };
202
+ /** The pixel-aperture samples per axis and anti-aliasing spots of a request. */
203
+ export declare function offsetCount(request: DisplayViewGpuRequest): number;
204
+ /** The bilinear RGGB demosaic kernels per phase (phaseY·2 + phaseX) and channel, as `bilinearPhaseProfiles` gives them. */
205
+ export declare function bilinearKernels(): DisplayViewKernelTap[][][];
158
206
  export {};
@@ -13,7 +13,9 @@
13
13
  * #477); `display-view-chart-material-module.ts` provides one for the eight 2.0 chart presets and
14
14
  * `display-view-composite-material-module.ts` (#491) one for composite charts, the 2.3 elements and `material-layers` scenes.
15
15
  */
16
- import { DISPLAY_VIEW_NOISE_STREAM_ID, axisSums, clipCentre, createDisplayViewExpectedSignal, displayViewDecidedSaturation, overlaps, quantizationVariance, renderDisplayView } from "../facade/display-view.js";
16
+ import { SDR_LUMINANCE_WEIGHTS } from "../output/sdr-rendering.js";
17
+ import { DISPLAY_VIEW_NOISE_STREAM_ID, EXACT_AREA_MEAN_MAX_PIXELS, axisSums, clipCentre, createDisplayViewExpectedSignal, displayViewDecidedSaturation, overlaps, quantizationVariance, renderDisplayView } from "../facade/display-view.js";
18
+ import { displayViewSignalTables } from "../facade/expected-signal.js";
17
19
  import { resolvePortableChartScene } from "../facade/chart-scenes.js";
18
20
  import { setDisplayViewGpuRendererFactory } from "../facade/display-view-gpu.js";
19
21
  import { createDisplayViewMaterialModules } from "./display-view-composite-material-module.js";
@@ -23,8 +25,7 @@ import { createDeterministicUniformStream, deriveNativeSiteStreamSeedUint32, sam
23
25
  import { createHostYieldGate } from "../capture/host-yield-cadence.js";
24
26
  import { ImageKernelExecutionError, WEBGPU_F32_UNIT_ROUNDOFF, webGpuExecution } from "./image-kernel-execution.js";
25
27
  import { BUFFER_USAGE, GpuResourceScope, abortedError, createComputePipeline, linearDispatch, runComputePass, throwIfAborted as checkAbort } from "./webgpu-runtime.js";
26
- import { DISPLAY_VIEW_AUX, DISPLAY_VIEW_FLAG_WORDS, DISPLAY_VIEW_GEOMETRY_STRIDE, DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS, DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_AXIS, DISPLAY_VIEW_OUTPUT_FLOATS, DISPLAY_VIEW_PARAM_WORDS, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U, DISPLAY_VIEW_WORKGROUP_SIZE, assembleDisplayViewSpeedWgsl } from "./wgsl-display-view-speed-kernel.js";
27
- import { footprintShape, bilinearKernels, offsetCount } from "./display-view-quality-gpu.js";
28
+ import { DISPLAY_VIEW_AUX, DISPLAY_VIEW_FLAG_WORDS, DISPLAY_VIEW_SPEED_MAXIMUM_NODES, DISPLAY_VIEW_GEOMETRY_STRIDE, DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS, DISPLAY_VIEW_GPU_MAXIMUM_PIXELS_PER_AXIS, DISPLAY_VIEW_OUTPUT_FLOATS, DISPLAY_VIEW_PARAM_WORDS, DISPLAY_VIEW_REINHARD_MEAN_U, DISPLAY_VIEW_REINHARD_VARIANCE_U, DISPLAY_VIEW_WORKGROUP_SIZE, assembleDisplayViewSpeedWgsl } from "./wgsl-display-view-speed-kernel.js";
28
29
  /** One WGSL rounding of + − × in either direction (W3C WGSL "Floating Point Accuracy"; #471, #487): twice the binary32 unit roundoff. */
29
30
  const E = 2 * WEBGPU_F32_UNIT_ROUNDOFF;
30
31
  /** Higham's γ_k with E in place of u. */
@@ -52,7 +53,7 @@ const BYTES_PER_PIXEL = 4 * DISPLAY_VIEW_OUTPUT_FLOATS + 4 * DISPLAY_VIEW_FLAG_W
52
53
  /**
53
54
  * Why the kernel does not cover a request, or null when it does. The kernel is the Speed path: the exact area mean
54
55
  * (views where a footprint holds at most 25 real pixels and at most K², and here at most 6 per axis), no diffraction
55
- * kernels, chart materials, the bilinear demosaic (no AHD candidates or correction), the identity or positive Reinhard
56
+ * kernels, chart materials, the bilinear demosaic (no AHD candidates), the identity or positive Reinhard
56
57
  * tone curve, and at most 16 query offsets.
57
58
  */
58
59
  export function displayViewSpeedGpuUnsupportedReason(setup, signalInput) {
@@ -60,16 +61,22 @@ export function displayViewSpeedGpuUnsupportedReason(setup, signalInput) {
60
61
  return "a defocus kernel radius above 64 sites (convolved by runs on the CPU, #564) is not on the GPU";
61
62
  if (signalInput.kernels)
62
63
  return "diffraction kernels (the Quality path) are not on the GPU";
63
- if (signalInput.scene.kind !== "materials")
64
+ if (signalInput.scene.kind !== "materials" && !signalInput.scene.signalAt)
64
65
  return "radiance scenes (caller functions) are not on the GPU";
65
66
  if (!signalInput.queryPoint)
66
67
  return "the expected signal has no query points";
67
- if (setup.candidates || setup.ahdCorrection || setup.ahdCollect)
68
- return "AHD candidates and the low-signal correction are not on the GPU";
69
- if (setup.toneCurve !== "identity" && setup.toneCurve !== "positive-reinhard-per-channel")
68
+ if (setup.candidates || setup.ahdChoice)
69
+ return "AHD candidates are not on the GPU";
70
+ if (!GPU_TONE_CURVES.includes(setup.toneCurve))
70
71
  return "unsupported tone curve " + String(setup.toneCurve);
72
+ if (setup.rendering && !setup.coupled)
73
+ return "the coupled rendering steps were not prepared";
74
+ if (setup.rendering?.highlightMode === "reconstruct-neighbors")
75
+ return "highlightMode reconstruct-neighbors (#602; its regions' ratios are found on the host) is not on the GPU in 2.22.0";
71
76
  if (signalInput.offsets.length < 1 || signalInput.offsets.length > DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS)
72
77
  return "the kernel holds 1 to " + DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS + " query offsets";
78
+ if (signalInput.wavelengthsNm.length < 1 || signalInput.wavelengthsNm.length > DISPLAY_VIEW_SPEED_MAXIMUM_NODES)
79
+ return "the kernel integrates 1 to " + DISPLAY_VIEW_SPEED_MAXIMUM_NODES + " spectral nodes per site";
73
80
  if (setup.region.width < 1 || setup.region.height < 1)
74
81
  return "an empty region";
75
82
  const shape = footprintShape(setup);
@@ -85,27 +92,19 @@ export function displayViewSpeedGpuUnsupportedReason(setup, signalInput) {
85
92
  }
86
93
  return null;
87
94
  }
88
- /** The site table: Σ_λ A_c(λ)·L_m(λ) per material and channel in float64 (the reference's `materialSignalDirect`), rounded once to float32. */
89
- function siteTable(signalInput) {
90
- const scene = signalInput.scene, A = signalInput.electronsPerRadiance, nodes = signalInput.wavelengthsNm.length;
91
- const neutral = [0, 1, 2].map((c) => { let s = 0; for (let n = 0; n < nodes; n++)
92
- s += A[c][n] * scene.whiteRadiance[n]; return s; });
93
- const table = new Float32Array(scene.materials.length * 3);
94
- scene.materials.forEach((m, index) => {
95
- for (let c = 0; c < 3; c++) {
96
- let value;
97
- if ("neutral" in m)
98
- value = m.neutral * neutral[c];
99
- else {
100
- value = 0;
101
- for (let n = 0; n < nodes; n++)
102
- value += A[c][n] * m.radiance[n];
103
- }
104
- if (!(Math.abs(value) <= FLOAT32_SAFE_MAGNITUDE))
105
- throw new ImageKernelExecutionError("unsupported-float32-domain", "A material's channel signal exceeds the float32 domain.");
106
- table[index * 3 + c] = value;
107
- }
108
- });
95
+ /**
96
+ * The spectral table (2.21.0: signals are integrated from the material spectrum per site): the three channel responses A_c(λ_n)
97
+ * (3·N floats), then each material's spectral radiance L_m(λ_n) (N floats per material; a neutral one its reflectance times the
98
+ * white radiance, the reference's own float64 spectra), each rounded once to float32. The kernel integrates Σ_n A_c(λ_n)·L_m(λ_n)
99
+ * for the material and channel a site reads, when it reads it; no per-material channel value is formed.
100
+ *
101
+ * Domain: every value is non-negative and the largest possible sum, N·max A·max L, stays below the float32 safe magnitude (NaN fails it).
102
+ */
103
+ function spectralTable(signalInput) {
104
+ const N = signalInput.wavelengthsNm.length, rows = [...signalInput.electronsPerRadiance, ...displayViewSignalTables(signalInput).spectra], largest = [0, 0];
105
+ const table = Float32Array.from(rows.flatMap((row, r) => Array.from(row.subarray(0, N), (v) => { largest[r < 3 ? 0 : 1] = Math.max(largest[r < 3 ? 0 : 1], v >= 0 ? v : Infinity); return v; })));
106
+ if (!(largest[0] * largest[1] * N <= FLOAT32_SAFE_MAGNITUDE))
107
+ throw new ImageKernelExecutionError("unsupported-float32-domain", "A material's channel signal exceeds the float32 domain.");
109
108
  return table;
110
109
  }
111
110
  /**
@@ -173,6 +172,8 @@ export function axisRecord(geometry, at, start, end, limit, margin, scale) {
173
172
  const order = ["bEven", "bOdd", "ppEven", "ppOdd", "qqEven", "qqOdd", "pqEven", "pqOdd", "ww"];
174
173
  order.forEach((key, i) => { geometry[at + 8 + i] = sums[key] / scale2; });
175
174
  }
175
+ /** The kernel's tone code: identity 0, positive Reinhard 1, extended Reinhard (luminance) 2, Naka–Rushton (luminance) 3. */
176
+ const TONE_CODE = { "identity": 0, "positive-reinhard-per-channel": 1, "extended-reinhard-luminance": 2, "naka-rushton-luminance": 3 };
176
177
  /** The uniform parameters and the aux table of a render. */
177
178
  function constants(setup, signalInput) {
178
179
  const params = new ArrayBuffer(4 * DISPLAY_VIEW_PARAM_WORDS), words = new Uint32Array(params), floats = new Float32Array(params);
@@ -183,8 +184,9 @@ function constants(setup, signalInput) {
183
184
  words[6] = setup.nativeWidth;
184
185
  words[7] = setup.nativeHeight;
185
186
  words[8] = setup.region.width;
186
- words[9] = setup.toneCurve === "positive-reinhard-per-channel" ? 1 : 0;
187
+ words[9] = TONE_CODE[setup.toneCurve];
187
188
  words[10] = signalInput.offsets.length;
189
+ words[11] = signalInput.wavelengthsNm.length;
188
190
  for (let c = 0; c < 3; c++) {
189
191
  floats[12 + c] = setup.readout[c].perElectron;
190
192
  floats[16 + c] = setup.readout[c].darkElectrons;
@@ -218,10 +220,10 @@ function constants(setup, signalInput) {
218
220
  return { params, aux };
219
221
  }
220
222
  /** The a-priori relative bounds of the expected value and the variance (docs/WEBGPU.md, "Display view (Speed)"), doubled. */
221
- export function displayViewSpeedGpuRelativeBounds(toneCurve) {
223
+ export function displayViewSpeedGpuRelativeBounds(toneCurve, extraSiteRoundings = 0) {
222
224
  const reinhard = toneCurve === "positive-reinhard-per-channel";
223
- return { mean: MARGIN * (gammaE(MEAN_ROUNDINGS) + CPU_RELATIVE + (reinhard ? DISPLAY_VIEW_REINHARD_MEAN_U * E : 0)),
224
- variance: MARGIN * (gammaE(VARIANCE_ROUNDINGS) + CPU_RELATIVE + (reinhard ? DISPLAY_VIEW_REINHARD_VARIANCE_U * E : 0)) };
225
+ return { mean: MARGIN * (gammaE(MEAN_ROUNDINGS + extraSiteRoundings) + CPU_RELATIVE + (reinhard ? DISPLAY_VIEW_REINHARD_MEAN_U * E : 0)),
226
+ variance: MARGIN * (gammaE(VARIANCE_ROUNDINGS + 2 * extraSiteRoundings) + CPU_RELATIVE + (reinhard ? DISPLAY_VIEW_REINHARD_VARIANCE_U * E : 0)) };
225
227
  }
226
228
  /**
227
229
  * The reference's finishing of one display pixel from its linear-light moments (display-view.ts, after the sample loop):
@@ -263,13 +265,31 @@ export function displayViewSpeedQuantizationBound(moments, relative, absolute, p
263
265
  }
264
266
  return MARGIN * pixels * largest * step * step / 12;
265
267
  }
268
+ /**
269
+ * Renders the display pixels `indices` (ascending, region row-major) with the reference, a run of consecutive pixels along a row
270
+ * as one region (#457: AHD's choice flags stretches near edges): a region render is byte-identical to the same pixels of the whole
271
+ * view, so each pixel equals its one-pixel render, and a run's counts are the sums of its pixels'.
272
+ */
273
+ export async function recomputeRuns(setup, signalInput, indices, abort, take) {
274
+ const region = setup.region;
275
+ let reference;
276
+ for (let r = 0; r < indices.length;) {
277
+ checkAbort(abort.signal);
278
+ const first = indices[r], rx = first % region.width, ry = (first - rx) / region.width;
279
+ let run = 1;
280
+ while (r + run < indices.length && indices[r + run] === first + run && rx + run < region.width)
281
+ run++;
282
+ take(first, run, await renderDisplayView({ ...setup, signal: reference ??= createDisplayViewExpectedSignal(signalInput), region: { x: region.x + rx, y: region.y + ry, width: run, height: 1 } }));
283
+ r += run;
284
+ }
285
+ }
266
286
  /** Renders one region of a Speed display view on the GPU (see the module comment). */
267
287
  export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abort, yieldControl, pipelines) {
268
288
  const { setup, signalInput, material } = input;
269
289
  const reason = displayViewSpeedGpuUnsupportedReason(setup, signalInput);
270
290
  if (reason)
271
291
  throw new ImageKernelExecutionError("unsupported-on-backend", "webgpu-f32 display view (Speed): " + reason + "; use cpu-reference.");
272
- const { region, margin } = setup, shape = footprintShape(setup), table = siteTable(signalInput), { params, aux } = constants(setup, signalInput);
292
+ const { region, margin } = setup, shape = footprintShape(setup), table = material.siteTable ?? spectralTable(signalInput), { params, aux } = constants(setup, signalInput);
273
293
  const stride = DISPLAY_VIEW_GEOMETRY_STRIDE, geometry = new Float32Array((region.width + region.height) * stride);
274
294
  const columnSums = [], rowSums = [];
275
295
  for (let i = 0; i < region.width; i++) {
@@ -281,19 +301,26 @@ export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abo
281
301
  rowSums.push(axisSums((region.y + j) * shape.sy, (region.y + j + 1) * shape.sy, setup.frameHeight, margin));
282
302
  }
283
303
  const area = shape.sx * shape.sy, pixelCount = region.width * region.height;
284
- const rows = Math.max(1, Math.min(region.height, Math.floor(bandBytes / (region.width * BYTES_PER_PIXEL))));
304
+ const rows = Math.max(1, Math.min(region.height, Math.floor(bandBytes / (region.width * BYTES_PER_PIXEL)), material.bandRows?.(shape.sy) ?? Infinity));
285
305
  if (Math.max(rows * region.width * 4 * DISPLAY_VIEW_OUTPUT_FLOATS, geometry.byteLength, table.byteLength) > runtime.maximumStorageBindingBytes)
286
306
  throw new ImageKernelExecutionError("insufficient-limits", "One display-view band exceeds the device's storage binding limit.");
287
- const pipeline = await pipelines("photivra-display-view-speed:" + material.id, assembleDisplayViewSpeedWgsl(material.wgsl));
307
+ // #593, #601: the coupled steps' shader text and constants, loaded on demand for a view that asks for them.
308
+ const coupled = setup.rendering ? await import("./wgsl-display-view-coupled.js") : null;
309
+ const coupledConstants = coupled ? coupled.displayViewCoupledConstants(setup, SDR_LUMINANCE_WEIGHTS, gaussLegendre16()) : new Float32Array(1);
310
+ const pipeline = await pipelines("photivra-display-view-speed:" + material.id + (coupled ? "+coupled" : ""), assembleDisplayViewSpeedWgsl(material.wgsl, coupled?.DISPLAY_VIEW_COUPLED_WGSL));
288
311
  const moments = new Float32Array(pixelCount * DISPLAY_VIEW_OUTPUT_FLOATS), flagged = [], flagWords = new Uint32Array(pixelCount * DISPLAY_VIEW_FLAG_WORDS);
289
312
  const words = new Uint32Array(params);
290
313
  const scope = new GpuResourceScope(runtime);
291
314
  try {
292
- const auxBuffer = scope.upload("photivra-display-view-aux", aux, BUFFER_USAGE.STORAGE);
315
+ // #593, #601: the coupled steps' constants follow the aux table's own entries (one zero word without them).
316
+ const auxWithCoupled = new Float32Array(aux.length + coupledConstants.length);
317
+ auxWithCoupled.set(aux);
318
+ auxWithCoupled.set(coupledConstants, aux.length);
319
+ const auxBuffer = scope.upload("photivra-display-view-aux", auxWithCoupled, BUFFER_USAGE.STORAGE);
293
320
  const geometryBuffer = scope.upload("photivra-display-view-geometry", geometry, BUFFER_USAGE.STORAGE);
294
321
  const tableBuffer = scope.upload("photivra-display-view-table", table, BUFFER_USAGE.STORAGE);
295
- const materialEntries = material.bindings.map((b) => ({ binding: b.binding, resource: { buffer: b.buffer ?? scope.upload("photivra-display-view-material-" + b.binding, b.data, b.kind === "uniform" ? BUFFER_USAGE.UNIFORM : BUFFER_USAGE.STORAGE) } }));
296
- const materialGroup = runtime.device.createBindGroup({ layout: pipeline.getBindGroupLayout(1), entries: materialEntries });
322
+ const materialGroup = material.band ? null : runtime.device.createBindGroup({ layout: pipeline.getBindGroupLayout(1), entries: material.bindings.map((b) => ({ binding: b.binding,
323
+ resource: { buffer: b.buffer ?? scope.upload("photivra-display-view-material-" + b.binding, b.data, b.kind === "uniform" ? BUFFER_USAGE.UNIFORM : BUFFER_USAGE.STORAGE) } })) });
297
324
  const gate = createHostYieldGate();
298
325
  for (let row0 = 0; row0 < region.height; row0 += rows) {
299
326
  checkAbort(abort.signal);
@@ -309,7 +336,8 @@ export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abo
309
336
  { binding: 0, resource: { buffer: uniform } }, { binding: 1, resource: { buffer: auxBuffer } }, { binding: 2, resource: { buffer: geometryBuffer } },
310
337
  { binding: 3, resource: { buffer: tableBuffer } }, { binding: 4, resource: { buffer: out } }, { binding: 5, resource: { buffer: flagBuffer } }
311
338
  ] });
312
- const [data, flagData] = await runComputePass(runtime, band, pipeline, [bindGroup, materialGroup], linearDispatch(count, DISPLAY_VIEW_WORKGROUP_SIZE, runtime.maximumWorkgroupsPerDimension), [{ buffer: out, bytes: count * 4 * DISPLAY_VIEW_OUTPUT_FLOATS }, { buffer: flagBuffer, bytes: count * 4 * DISPLAY_VIEW_FLAG_WORDS }], abort.signal);
339
+ // #581: a module that bands its own data (a large photo) gives group 1 per band, for the native rows the band reads (with margin).
340
+ const [data, flagData] = await runComputePass(runtime, band, pipeline, [bindGroup, materialGroup ?? material.band(band, pipeline.getBindGroupLayout(1), Math.floor((region.y + row0) * shape.sy) + margin - 2, Math.ceil((region.y + row1) * shape.sy) + margin + 2)], linearDispatch(count, DISPLAY_VIEW_WORKGROUP_SIZE, runtime.maximumWorkgroupsPerDimension), [{ buffer: out, bytes: count * 4 * DISPLAY_VIEW_OUTPUT_FLOATS }, { buffer: flagBuffer, bytes: count * 4 * DISPLAY_VIEW_FLAG_WORDS }], abort.signal);
313
341
  moments.set(new Float32Array(data, 0, count * DISPLAY_VIEW_OUTPUT_FLOATS), first * DISPLAY_VIEW_OUTPUT_FLOATS);
314
342
  const bandFlags = new Uint32Array(flagData, 0, count * DISPLAY_VIEW_FLAG_WORDS);
315
343
  flagWords.set(bandFlags, first * DISPLAY_VIEW_FLAG_WORDS);
@@ -341,7 +369,7 @@ export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abo
341
369
  const expected = new Float64Array(pixelCount * 3), noiseStd = new Float64Array(pixelCount * 3), saturated = new Float64Array(pixelCount);
342
370
  const meanBound = new Float32Array(pixelCount), varianceBound = new Float32Array(pixelCount);
343
371
  let largestMean = 0, largestVariance = 0, sampleCount = 0, siteCount = 0;
344
- const counts = new Int32Array(geometry.buffer), relative = displayViewSpeedGpuRelativeBounds(setup.toneCurve);
372
+ const counts = new Int32Array(geometry.buffer), relative = displayViewSpeedGpuRelativeBounds(setup.toneCurve, material.extraSiteRoundings ?? 0);
345
373
  // #533: each display pixel's saturated-pixel share from the kernel's mask (the reference's arithmetic, exact where it decided).
346
374
  const columnOverlaps = Array.from({ length: region.width }, (_, i) => overlaps((region.x + i) * shape.sx, (region.x + i + 1) * shape.sx, setup.frameWidth));
347
375
  for (let ry = 0, i = 0; ry < region.height; ry++) {
@@ -372,33 +400,29 @@ export async function renderDisplayViewSpeedOnGpu(runtime, bandBytes, input, abo
372
400
  for (let i = 0; i < pixelCount; i++)
373
401
  if (isFlagged[i] === 0 && (flagWords[DISPLAY_VIEW_FLAG_WORDS * i] & 2) !== 0)
374
402
  undecided.push(i);
375
- if (input.recomputeFlagged !== false && flagged.length + undecided.length > 0) {
376
- const signal = createDisplayViewExpectedSignal(signalInput);
377
- for (const i of flagged) {
378
- checkAbort(abort.signal);
379
- const rx = i % region.width, ry = (i - rx) / region.width;
380
- const pixel = await renderDisplayView({ ...setup, signal, region: { x: region.x + rx, y: region.y + ry, width: 1, height: 1 } });
381
- samples.set(pixel.samples, i * 3);
382
- expected.set(pixel.expected, i * 3);
383
- noiseStd.set(pixel.noiseStd, i * 3);
384
- saturated[i] = pixel.saturated[0];
385
- meanBound[i] = 0;
386
- varianceBound[i] = 0;
387
- recomputed.push(i);
388
- }
389
- for (const i of undecided) {
390
- checkAbort(abort.signal);
391
- const rx = i % region.width, ry = (i - rx) / region.width;
392
- saturated[i] = (await renderDisplayView({ ...setup, signal, region: { x: region.x + rx, y: region.y + ry, width: 1, height: 1 } })).saturated[0];
393
- highlights.push(i);
394
- }
403
+ if (input.recomputeFlagged !== false) {
404
+ await recomputeRuns(setup, signalInput, flagged, abort, (first, run, part) => {
405
+ samples.set(part.samples, first * 3);
406
+ expected.set(part.expected, first * 3);
407
+ noiseStd.set(part.noiseStd, first * 3);
408
+ saturated.set(part.saturated, first);
409
+ for (let k = 0; k < run; k++) {
410
+ meanBound[first + k] = 0;
411
+ varianceBound[first + k] = 0;
412
+ recomputed.push(first + k);
413
+ }
414
+ });
415
+ await recomputeRuns(setup, signalInput, undecided, abort, (first, run, part) => { for (let k = 0; k < run; k++) {
416
+ saturated[first + k] = part.saturated[k];
417
+ highlights.push(first + k);
418
+ } });
395
419
  }
396
420
  let saturatedPixelCount = 0;
397
421
  for (let i = 0; i < pixelCount; i++)
398
422
  saturatedPixelCount += saturated[i];
399
423
  const expectedValueBound = { kind: "relative-to-reference", relative: relative.mean, absolute: largestMean };
400
424
  const uncertain = flagged.length - recomputed.length + undecided.length - highlights.length;
401
- return { samples, expected, noiseStd, sampleCount, siteCount, detailPixelCount: pixelCount, uniformPixelCount: 0, ahdCorrectedPixelCount: 0, estimation: "exact-area-mean", saturated, saturatedPixelCount,
425
+ return { samples, expected, noiseStd, sampleCount, siteCount, detailPixelCount: pixelCount, uniformPixelCount: 0, estimation: "exact-area-mean", saturated, saturatedPixelCount,
402
426
  execution: webGpuExecution(expectedValueBound, uncertain), expectedValueBound,
403
427
  varianceBound: { kind: "relative-to-reference", relative: relative.variance, absolute: largestVariance },
404
428
  expectedValueAbsoluteBound: meanBound, varianceAbsoluteBound: varianceBound,
@@ -417,12 +441,38 @@ export function displayViewSpeedGpuSupports(request) {
417
441
  return "AHD (the Quality demosaic) is not on the GPU";
418
442
  if (request.diffraction)
419
443
  return "diffraction (the Quality optics) is not on the GPU";
420
- if (request.scene !== "chart" && request.scene !== "material-layers")
444
+ return sceneToneAndOffsetsReason(request, DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS);
445
+ }
446
+ /** Why the GPU's material lookup, tone curves or query-offset table do not cover a request (shared by the Speed and Quality support checks), or true. */
447
+ export function sceneToneAndOffsetsReason(request, maximumOffsets) {
448
+ if (request.scene !== "chart" && request.scene !== "material-layers" && request.scene !== "image-plane")
421
449
  return "the scene is a caller " + request.scene + "; the GPU material lookup covers engine charts and declared material-layers scenes (describe the scene as material-layers)";
422
- if (request.toneCurve !== "identity" && request.toneCurve !== "positive-reinhard-per-channel")
450
+ // #593: the identity, Reinhard and the two luminance tone curves (the luminance curves through the coupled steps).
451
+ if (!GPU_TONE_CURVES.includes(request.toneCurve))
423
452
  return "unsupported tone curve " + String(request.toneCurve);
424
- if (offsetCount(request) > DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS)
425
- return "more than " + DISPLAY_VIEW_GPU_MAXIMUM_OFFSETS + " query offsets per site";
453
+ if (offsetCount(request) > maximumOffsets)
454
+ return "more than " + maximumOffsets + " query offsets per site";
455
+ return true;
456
+ }
457
+ /** The tone curves the GPU kernels render (#593 adds the luminance curves). */
458
+ const GPU_TONE_CURVES = ["identity", "positive-reinhard-per-channel", "extended-reinhard-luminance", "naka-rushton-luminance"];
459
+ /**
460
+ * A self-check's verdict: every component of a GPU render inside its a-priori bounds against the reference, every sample within
461
+ * one code, and (#533) the saturated-pixel count the reference's bit for bit; true, or a sentence saying where it failed.
462
+ */
463
+ export function conformanceVerdict(reference, gpu) {
464
+ for (let i = 0; i < reference.expected.length; i++) {
465
+ const p = Math.floor(i / 3), e0 = reference.expected[i], e1 = gpu.expected[i], v0 = reference.noiseStd[i] * reference.noiseStd[i], v1 = gpu.noiseStd[i] * gpu.noiseStd[i];
466
+ const expectedLimit = gpu.expectedValueBound.relative * Math.abs(e0) + gpu.expectedValueAbsoluteBound[p], varianceLimit = gpu.varianceBound.relative * v0 + gpu.varianceAbsoluteBound[p];
467
+ if (!(Math.abs(e1 - e0) <= expectedLimit))
468
+ return "component " + i + ": expected value " + e1 + " differs from the reference " + e0 + " by more than the a-priori bound " + expectedLimit + ".";
469
+ if (!(Math.abs(v1 - v0) <= varianceLimit))
470
+ return "component " + i + ": variance " + v1 + " differs from the reference " + v0 + " by more than the a-priori bound " + varianceLimit + ".";
471
+ if (Math.abs(gpu.samples[i] - reference.samples[i]) > 1)
472
+ return "component " + i + ": sample " + gpu.samples[i] + " differs from the reference " + reference.samples[i] + " by more than one code.";
473
+ }
474
+ if (gpu.saturatedPixelCount !== reference.saturatedPixelCount)
475
+ return "the saturated-pixel count " + gpu.saturatedPixelCount + " differs from the reference's " + reference.saturatedPixelCount + ".";
426
476
  return true;
427
477
  }
428
478
  /**
@@ -430,48 +480,52 @@ export function displayViewSpeedGpuSupports(request) {
430
480
  * shown at 13 × 9 (2.9 real pixels per display pixel) with the 2 × 2 box, light squares at the white clip, the GPU against
431
481
  * `renderDisplayView` within the a-priori bounds, every flagged pixel recomputed.
432
482
  */
483
+ /** The self-checks' common frame: 40 × 28 native sites (a 38 × 26 frame and a one-site margin) shown at 13 × 9, at three spectral nodes. */
484
+ export const CONFORMANCE_FRAME = /* @__PURE__ */ Object.freeze({ nativeWidth: 40, nativeHeight: 28, margin: 1, frameWidth: 38, frameHeight: 26, nodes: [450, 550, 650] });
485
+ /** The self-checks' channel responses per node (`scale` 1, or 1/10 for the low-signal correction's few electrons). */
486
+ export function conformanceResponses(scale) {
487
+ return [Float64Array.of(9000, 30000, 4000).map((v) => v * scale), Float64Array.of(6000, 42000, 9000).map((v) => v * scale), Float64Array.of(30000, 9000, 3000).map((v) => v * scale)];
488
+ }
489
+ /** The RGGB channel of a native site. */
490
+ export const conformanceChannelAt = (x, y) => ((y & 1) === 0 ? ((x & 1) === 0 ? 0 : 1) : ((x & 1) === 0 ? 1 : 2));
491
+ /** The self-checks' checkerboard and its expected-signal scene (neutral materials under a white radiance per node). */
492
+ export function conformanceChart() {
493
+ const chart = resolvePortableChartScene({ scene: { preset: "checkerboard", squaresAcross: 6 }, aspect: CONFORMANCE_FRAME.frameHeight / CONFORMANCE_FRAME.frameWidth });
494
+ return { chart, scene: { kind: "materials", materialAt: (u, v) => chart.materialAt(u, v), materials: chart.materials.map((m) => ({ neutral: m.kind === "neutral" ? m.reflectance : 0.5 })),
495
+ whiteRadiance: Float64Array.of(0.5, 0.9, 0.7) } };
496
+ }
497
+ /** A self-check's setup around its expected-signal input: the common frame and view, the bilinear demosaic, gains, matrix and tone curve, plus `extra`. */
498
+ export function conformanceSetup(signalInput, readout, seed, reachSites, signal, extra = {}) {
499
+ const { frameWidth, frameHeight, margin, nativeWidth, nativeHeight } = CONFORMANCE_FRAME;
500
+ return { frameWidth, frameHeight, margin, nativeWidth, nativeHeight, viewWidth: 13, viewHeight: 9, region: { x: 0, y: 0, width: 13, height: 9 },
501
+ samplesPerAxis: 16, reachSites, bitDepth: 8, seed, signal: createDisplayViewExpectedSignal(signalInput), readout: [readout, readout, readout], demosaic: bilinearKernels(),
502
+ gains: [1.1, 1, 1.6], clipLevel: 1, matrix: [[3.08, -1.54, -0.54], [-0.92, 1.88, 0.045], [0.053, -0.2, 1.15]], toneCurve: "identity",
503
+ yieldControl: async () => undefined, checkAbort: () => { if (signal?.aborted)
504
+ throw abortedError(); }, ...extra };
505
+ }
506
+ /** Runs a self-check's GPU render with an abort controller that follows `signal`, and the device's pipelines under it. */
507
+ export async function withConformanceAbort(runtime, signal, run) {
508
+ const abort = new AbortController(), onAbort = () => abort.abort();
509
+ signal?.addEventListener("abort", onAbort, { once: true });
510
+ try {
511
+ if (signal?.aborted)
512
+ abort.abort();
513
+ return await run(abort, (label, code) => createComputePipeline(runtime, label, code, abort.signal));
514
+ }
515
+ finally {
516
+ signal?.removeEventListener("abort", onAbort);
517
+ }
518
+ }
433
519
  export const DISPLAY_VIEW_SPEED_CONFORMANCE_CASE = { id: "display-view:speed:checkerboard-13x9", async run(handle, signal) {
434
520
  // The handle is the engine's own runtime (the contract keeps WebGPU types out of the published declarations).
435
- const runtime = handle;
436
- const nativeWidth = 40, nativeHeight = 28, margin = 1, frameWidth = 38, frameHeight = 26, g = 2 / frameWidth, nodes = [450, 550, 650];
437
- const chart = resolvePortableChartScene({ scene: { preset: "checkerboard", squaresAcross: 6 }, aspect: frameHeight / frameWidth });
521
+ const runtime = handle, { nativeWidth, nativeHeight, frameWidth, nodes } = CONFORMANCE_FRAME, g = 2 / frameWidth, { chart, scene } = conformanceChart();
438
522
  const offsets = [[-0.25, -0.25], [0.25, -0.25], [-0.25, 0.25], [0.25, 0.25]].map(([x, y]) => ({ x: x, y: y, weight: 0.25 }));
439
- const signalInput = { nativeWidth, nativeHeight, outputWidth: frameWidth, wavelengthsNm: nodes,
440
- electronsPerRadiance: [Float64Array.of(9000, 30000, 4000), Float64Array.of(6000, 42000, 9000), Float64Array.of(30000, 9000, 3000)], offsets,
441
- scene: { kind: "materials", materialAt: (u, v) => chart.materialAt(u, v), materials: chart.materials.map((m) => ({ neutral: m.kind === "neutral" ? m.reflectance : 0.5 })), whiteRadiance: Float64Array.of(0.5, 0.9, 0.7) },
442
- channelAt: (x, y) => ((y & 1) === 0 ? ((x & 1) === 0 ? 0 : 1) : ((x & 1) === 0 ? 1 : 2)),
443
- queryPoint: (x, y, o, out) => { out[0] = (x + 0.5 + offsets[o].x - nativeWidth / 2) * g; out[1] = -(y + 0.5 + offsets[o].y - nativeHeight / 2) * g; } };
444
- const readout = { perElectron: 1 / 60000, darkElectrons: 0.07, constantVariance: 1.5e-9 };
445
- const setup = { frameWidth, frameHeight, margin, nativeWidth, nativeHeight, viewWidth: 13, viewHeight: 9, region: { x: 0, y: 0, width: 13, height: 9 },
446
- samplesPerAxis: 16, reachSites: 2, bitDepth: 8, seed: 478, signal: createDisplayViewExpectedSignal(signalInput), readout: [readout, readout, readout], demosaic: bilinearKernels(),
447
- gains: [1.1, 1, 1.6], clipLevel: 1, matrix: [[3.08, -1.54, -0.54], [-0.92, 1.88, 0.045], [0.053, -0.2, 1.15]], toneCurve: "identity",
448
- yieldControl: async () => undefined, checkAbort: () => { if (signal?.aborted)
449
- throw abortedError(); } };
523
+ const signalInput = { nativeWidth, nativeHeight, outputWidth: frameWidth, wavelengthsNm: nodes, electronsPerRadiance: conformanceResponses(1), offsets, scene,
524
+ channelAt: conformanceChannelAt, queryPoint: (x, y, o, out) => { out[0] = (x + 0.5 + offsets[o].x - nativeWidth / 2) * g; out[1] = -(y + 0.5 + offsets[o].y - nativeHeight / 2) * g; } };
525
+ const setup = conformanceSetup(signalInput, { perElectron: 1 / 60000, darkElectrons: 0.07, constantVariance: 1.5e-9 }, 478, 2, signal);
450
526
  const reference = await renderDisplayView(setup);
451
- const abort = new AbortController(), onAbort = () => abort.abort();
452
- signal?.addEventListener("abort", onAbort, { once: true });
453
- try {
454
- if (signal?.aborted)
455
- abort.abort();
456
- const gpu = await renderDisplayViewSpeedOnGpu(runtime, 1 << 20, { setup, signalInput, material: chartPresetMaterialModule(chart) }, abort, undefined, (label, code) => createComputePipeline(runtime, label, code, abort.signal));
457
- for (let i = 0; i < reference.expected.length; i++) {
458
- const p = Math.floor(i / 3), e0 = reference.expected[i], e1 = gpu.expected[i], v0 = reference.noiseStd[i] * reference.noiseStd[i], v1 = gpu.noiseStd[i] * gpu.noiseStd[i];
459
- const expectedLimit = gpu.expectedValueBound.relative * Math.abs(e0) + gpu.expectedValueAbsoluteBound[p], varianceLimit = gpu.varianceBound.relative * v0 + gpu.varianceAbsoluteBound[p];
460
- if (!(Math.abs(e1 - e0) <= expectedLimit))
461
- return "component " + i + ": expected value " + e1 + " differs from the reference " + e0 + " by more than the a-priori bound " + expectedLimit + ".";
462
- if (!(Math.abs(v1 - v0) <= varianceLimit))
463
- return "component " + i + ": variance " + v1 + " differs from the reference " + v0 + " by more than the a-priori bound " + varianceLimit + ".";
464
- if (Math.abs(gpu.samples[i] - reference.samples[i]) > 1)
465
- return "component " + i + ": sample " + gpu.samples[i] + " differs from the reference " + reference.samples[i] + " by more than one code.";
466
- }
467
- // #533: the saturated-pixel count is the reference's, bit for bit (the light squares sit at the white clip).
468
- if (gpu.saturatedPixelCount !== reference.saturatedPixelCount)
469
- return "the saturated-pixel count " + gpu.saturatedPixelCount + " differs from the reference's " + reference.saturatedPixelCount + ".";
470
- return true;
471
- }
472
- finally {
473
- signal?.removeEventListener("abort", onAbort);
474
- }
527
+ // #533: the verdict includes the saturated-pixel count (the light squares sit at the white clip).
528
+ return await withConformanceAbort(runtime, signal, async (abort, pipelines) => conformanceVerdict(reference, await renderDisplayViewSpeedOnGpu(runtime, 1 << 20, { setup, signalInput, material: chartPresetMaterialModule(chart) }, abort, undefined, pipelines)));
475
529
  } };
476
530
  /**
477
531
  * The display view's Speed renderer on one runtime (#481's `DisplayViewGpuRenderer`): one pipeline cache per device, the
@@ -510,7 +564,7 @@ export function createDisplayViewSpeedGpuRenderer(runtime, options = {}) {
510
564
  const material = await modules.moduleFor(setup.gpu, abort.signal);
511
565
  const result = await renderDisplayViewSpeedOnGpu(runtime, bandBytes, { setup, signalInput: setup.gpu.signalInput, material }, abort, options.yieldControl, source);
512
566
  return { samples: result.samples, expected: result.expected, noiseStd: result.noiseStd, sampleCount: result.sampleCount, siteCount: result.siteCount, detailPixelCount: result.detailPixelCount,
513
- uniformPixelCount: result.uniformPixelCount, ahdCorrectedPixelCount: result.ahdCorrectedPixelCount, estimation: result.estimation, saturated: result.saturated,
567
+ uniformPixelCount: result.uniformPixelCount, estimation: result.estimation, saturated: result.saturated,
514
568
  saturatedPixelCount: result.saturatedPixelCount, errorBound: result.expectedValueBound,
515
569
  diagnosticUncertaintySampleCount: result.execution.diagnosticUncertaintySampleCount, ...(material.compositeProbe ? { compositeProbe: material.compositeProbe } : {}) };
516
570
  }
@@ -530,4 +584,26 @@ export const displayViewSpeedGpuRendererFactory = (handle) => createDisplayViewS
530
584
  export function registerDisplayViewSpeedGpuRenderer() {
531
585
  return setDisplayViewGpuRendererFactory(displayViewSpeedGpuRendererFactory);
532
586
  }
587
+ // #594: shared with the on-demand Quality host (display-view-quality-gpu.ts), so the Speed path does not import it.
588
+ /** #564: the GPU refusal of a defocus kernel above the direct radius (the CPU convolves it by runs). */
589
+ export const LARGE_DEFOCUS_REASON = "a defocus kernel radius above 64 sites (convolved by runs on the CPU, #564) is not on the GPU";
590
+ /** The footprint shape of a view: real pixels per display pixel along each axis, and whether the exact area mean applies. */
591
+ export function footprintShape(setup) {
592
+ const sx = setup.frameWidth / setup.viewWidth, sy = setup.frameHeight / setup.viewHeight, area = sx * sy;
593
+ const exact = area <= Math.min(EXACT_AREA_MEAN_MAX_PIXELS, setup.samplesPerAxis * setup.samplesPerAxis);
594
+ let maximumColumns = 0, maximumRows = 0;
595
+ for (let i = 0; i < setup.region.width; i++)
596
+ maximumColumns = Math.max(maximumColumns, overlaps((setup.region.x + i) * sx, (setup.region.x + i + 1) * sx, setup.frameWidth).w.length);
597
+ for (let j = 0; j < setup.region.height; j++)
598
+ maximumRows = Math.max(maximumRows, overlaps((setup.region.y + j) * sy, (setup.region.y + j + 1) * sy, setup.frameHeight).w.length);
599
+ return { sx, sy, exact, maximumColumns, maximumRows };
600
+ }
601
+ /** The pixel-aperture samples per axis and anti-aliasing spots of a request. */
602
+ export function offsetCount(request) { return request.boxSamples * request.boxSamples * (request.antiAliasing ? 4 : 1); }
603
+ /** The bilinear RGGB demosaic kernels per phase (phaseY·2 + phaseX) and channel, as `bilinearPhaseProfiles` gives them. */
604
+ export function bilinearKernels() {
605
+ 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]];
606
+ const average = (offsets) => offsets.map(([dx, dy]) => ({ dx: dx, dy: dy, weight: 1 / offsets.length })), self = [{ dx: 0, dy: 0, weight: 1 }];
607
+ return [[self, average(cross), average(diagonal)], [average(horizontal), self, average(vertical)], [average(vertical), self, average(horizontal)], [average(diagonal), average(cross), self]];
608
+ }
533
609
  //# sourceMappingURL=display-view-speed-gpu.js.map