@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.
- package/CHANGELOG.md +295 -1
- package/CITATION.cff +1 -1
- package/README.md +7 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +18 -0
- package/dist/capture/c2pa-embed.d.ts +116 -0
- package/dist/capture/c2pa-embed.js +493 -0
- package/dist/capture/c2pa-embed.js.map +1 -0
- package/dist/capture/dng-import.d.ts +73 -8
- package/dist/capture/dng-import.js +315 -80
- package/dist/capture/dng-import.js.map +1 -1
- package/dist/capture/dng-lens-correction-opcodes.d.ts +2 -2
- package/dist/capture/dng-lens-correction-opcodes.js +2 -2
- package/dist/capture/dng-lens-correction-opcodes.js.map +1 -1
- package/dist/capture/dng-opcodes.d.ts +96 -0
- package/dist/capture/dng-opcodes.js +391 -0
- package/dist/capture/dng-opcodes.js.map +1 -0
- package/dist/capture/dng-profile-maps.d.ts +99 -0
- package/dist/capture/dng-profile-maps.js +381 -0
- package/dist/capture/dng-profile-maps.js.map +1 -0
- package/dist/capture/dng-stage-image.d.ts +23 -0
- package/dist/capture/dng-stage-image.js +65 -0
- package/dist/capture/dng-stage-image.js.map +1 -0
- package/dist/capture/exposure-build-up-stage.d.ts +16 -0
- package/dist/capture/exposure-build-up-stage.js +4 -0
- package/dist/capture/exposure-build-up-stage.js.map +1 -0
- package/dist/capture/jpeg-xl-tiles.d.ts +22 -0
- package/dist/capture/jpeg-xl-tiles.js +151 -0
- package/dist/capture/jpeg-xl-tiles.js.map +1 -0
- package/dist/capture/lossy-jpeg.d.ts +17 -0
- package/dist/capture/lossy-jpeg.js +369 -0
- package/dist/capture/lossy-jpeg.js.map +1 -0
- package/dist/capture/native-environment-lean.d.ts +2 -1
- package/dist/capture/native-environment-lean.js +41 -73
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-tabulated.js +4 -3
- package/dist/capture/native-environment-tabulated.js.map +1 -1
- package/dist/capture/native-raw-development.d.ts +2 -1
- package/dist/capture/native-raw-development.js.map +1 -1
- package/dist/capture/native-raw.d.ts +3 -2
- package/dist/capture/native-raw.js +5 -4
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.d.ts +4 -1
- package/dist/capture/sensor-raw-producer.js +12 -3
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/color/neighbor-highlight-reconstruction.d.ts +66 -0
- package/dist/color/neighbor-highlight-reconstruction.js +234 -0
- package/dist/color/neighbor-highlight-reconstruction.js.map +1 -0
- package/dist/color/sensor-color-development.d.ts +5 -0
- package/dist/color/sensor-color-development.js +7 -1
- package/dist/color/sensor-color-development.js.map +1 -1
- package/dist/color/spectral-spikiness.d.ts +11 -0
- package/dist/color/spectral-spikiness.js +23 -0
- package/dist/color/spectral-spikiness.js.map +1 -0
- package/dist/core/execution-error.d.ts +21 -0
- package/dist/core/execution-error.js +12 -0
- package/dist/core/execution-error.js.map +1 -0
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/generic-lens-catalogue.d.ts +3 -2
- package/dist/equipment/generic-lens-catalogue.js +18 -5
- package/dist/equipment/generic-lens-catalogue.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +32 -0
- package/dist/equipment/generic-tier-assets.js +16 -11
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/sensor-profile-body.d.ts +78 -0
- package/dist/equipment/sensor-profile-body.js +77 -0
- package/dist/equipment/sensor-profile-body.js.map +1 -0
- package/dist/equipment/sensor-profiles.d.ts +8 -0
- package/dist/equipment/sensor-profiles.js +82 -0
- package/dist/equipment/sensor-profiles.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +28 -3
- package/dist/facade/catalogue-lens-signal.js +83 -78
- package/dist/facade/catalogue-lens-signal.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +2 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/defocus-image-space-provider.d.ts +7 -0
- package/dist/facade/defocus-image-space-provider.js +64 -76
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/diffraction-provider.d.ts +1 -1
- package/dist/facade/diffraction-provider.js +4 -4
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +39 -5
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +19 -6
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.js +25 -23
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/display-view-normal.d.ts +15 -0
- package/dist/facade/display-view-normal.js +44 -0
- package/dist/facade/display-view-normal.js.map +1 -0
- package/dist/facade/display-view-rendering.d.ts +97 -0
- package/dist/facade/display-view-rendering.js +598 -0
- package/dist/facade/display-view-rendering.js.map +1 -0
- package/dist/facade/display-view.d.ts +37 -65
- package/dist/facade/display-view.js +152 -499
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +3 -1
- package/dist/facade/expected-signal-setup.js +3 -3
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +47 -14
- package/dist/facade/expected-signal.js +117 -112
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/exposure-build-up.d.ts +70 -0
- package/dist/facade/exposure-build-up.js +164 -0
- package/dist/facade/exposure-build-up.js.map +1 -0
- package/dist/facade/fast-methods.d.ts +1 -1
- package/dist/facade/fast-methods.js +46 -9
- package/dist/facade/fast-methods.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +3 -3
- package/dist/facade/generic-lens-capture.js +3 -3
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/image-plane-enable.d.ts +7 -0
- package/dist/facade/image-plane-enable.js +17 -0
- package/dist/facade/image-plane-enable.js.map +1 -0
- package/dist/facade/image-plane-lattice-data.d.ts +6 -0
- package/dist/facade/image-plane-lattice-data.js +10 -0
- package/dist/facade/image-plane-lattice-data.js.map +1 -0
- package/dist/facade/image-plane-support.d.ts +33 -0
- package/dist/facade/image-plane-support.js +11 -0
- package/dist/facade/image-plane-support.js.map +1 -0
- package/dist/facade/image-plane.d.ts +36 -0
- package/dist/facade/image-plane.js +384 -0
- package/dist/facade/image-plane.js.map +1 -0
- package/dist/facade/mixed-illumination.d.ts +108 -0
- package/dist/facade/mixed-illumination.js +174 -0
- package/dist/facade/mixed-illumination.js.map +1 -0
- package/dist/facade/on-demand-kits.d.ts +14 -0
- package/dist/facade/on-demand-kits.js +36 -0
- package/dist/facade/on-demand-kits.js.map +1 -0
- package/dist/facade/opt-in-kit.d.ts +9 -0
- package/dist/facade/opt-in-kit.js +11 -0
- package/dist/facade/opt-in-kit.js.map +1 -0
- package/dist/facade/quality-kit.d.ts +21 -0
- package/dist/facade/quality-kit.js +23 -0
- package/dist/facade/quality-kit.js.map +1 -0
- package/dist/facade/quality-paths.d.ts +22 -0
- package/dist/facade/quality-paths.js +44 -0
- package/dist/facade/quality-paths.js.map +1 -0
- package/dist/facade/representative-light-sources.d.ts +72 -0
- package/dist/facade/representative-light-sources.js +124 -0
- package/dist/facade/representative-light-sources.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +145 -4
- package/dist/facade/scene-radiance.js +50 -13
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +68 -20
- package/dist/facade/simulate-capture.js +399 -146
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +10 -12
- package/dist/facade/simulate-display-view.js +70 -53
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.d.ts +13 -1
- package/dist/gpu/chart-composite-gpu.js +48 -9
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/chart-radiance-gpu.js +4 -30
- package/dist/gpu/chart-radiance-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
- package/dist/gpu/display-view-composite-material-module.js +39 -13
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/display-view-gpu-renderer.d.ts +43 -0
- package/dist/gpu/display-view-gpu-renderer.js +134 -0
- package/dist/gpu/display-view-gpu-renderer.js.map +1 -0
- package/dist/gpu/display-view-image-material-module.d.ts +87 -0
- package/dist/gpu/display-view-image-material-module.js +361 -0
- package/dist/gpu/display-view-image-material-module.js.map +1 -0
- package/dist/gpu/display-view-image-quality.d.ts +87 -0
- package/dist/gpu/display-view-image-quality.js +625 -0
- package/dist/gpu/display-view-image-quality.js.map +1 -0
- package/dist/gpu/display-view-quality-gpu.d.ts +80 -65
- package/dist/gpu/display-view-quality-gpu.js +187 -445
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +67 -19
- package/dist/gpu/display-view-speed-gpu.js +181 -105
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/image-kernel-accelerator.js +3 -0
- package/dist/gpu/image-kernel-accelerator.js.map +1 -1
- package/dist/gpu/image-kernel-execution.d.ts +2 -13
- package/dist/gpu/image-kernel-execution.js +2 -10
- package/dist/gpu/image-kernel-execution.js.map +1 -1
- package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +8 -0
- package/dist/gpu/wgsl-display-view-coupled-stub.js +21 -0
- package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -0
- package/dist/gpu/wgsl-display-view-coupled.d.ts +88 -0
- package/dist/gpu/wgsl-display-view-coupled.js +353 -0
- package/dist/gpu/wgsl-display-view-coupled.js.map +1 -0
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +33 -24
- package/dist/gpu/wgsl-display-view-quality-kernel.js +166 -146
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +19 -44
- package/dist/gpu/wgsl-display-view-speed-kernel.js +116 -18
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
- package/dist/gpu/wgsl-exact-sum.js +42 -0
- package/dist/gpu/wgsl-exact-sum.js.map +1 -0
- package/dist/index.d.ts +22 -6
- package/dist/index.js +16 -2
- package/dist/index.js.map +1 -1
- package/dist/motion/subject-motion.d.ts +66 -0
- package/dist/motion/subject-motion.js +52 -0
- package/dist/motion/subject-motion.js.map +1 -0
- package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
- package/dist/optics/generic-lens-lateral-color.js +77 -0
- package/dist/optics/generic-lens-lateral-color.js.map +1 -0
- package/dist/optics/generic-lens-psf.js +54 -46
- package/dist/optics/generic-lens-psf.js.map +1 -1
- package/dist/optics/lens-psf-profile.d.ts +7 -0
- package/dist/optics/lens-psf-profile.js +2 -1
- package/dist/optics/lens-psf-profile.js.map +1 -1
- package/dist/output/camera-channel-response.d.ts +50 -0
- package/dist/output/camera-channel-response.js +69 -0
- package/dist/output/camera-channel-response.js.map +1 -0
- package/dist/output/developed-lens-corrections.d.ts +19 -5
- package/dist/output/developed-lens-corrections.js +28 -9
- package/dist/output/developed-lens-corrections.js.map +1 -1
- package/dist/output/generic-lens-corrections.d.ts +41 -5
- package/dist/output/generic-lens-corrections.js +52 -8
- package/dist/output/generic-lens-corrections.js.map +1 -1
- package/dist/output/native-capture-sdr.js +5 -2
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/print-plan.d.ts +41 -5
- package/dist/output/print-plan.js +59 -14
- package/dist/output/print-plan.js.map +1 -1
- package/dist/output/sdr-rendering.d.ts +18 -1
- package/dist/output/sdr-rendering.js +39 -2
- package/dist/output/sdr-rendering.js.map +1 -1
- package/dist/sensor/binomial-sample.d.ts +3 -0
- package/dist/sensor/binomial-sample.js +86 -0
- package/dist/sensor/binomial-sample.js.map +1 -0
- package/dist/sensor/native-sensor-report.d.ts +10 -0
- package/dist/sensor/native-sensor-report.js +16 -0
- package/dist/sensor/native-sensor-report.js.map +1 -0
- package/dist/sensor/sensor-characteristics.d.ts +91 -13
- package/dist/sensor/sensor-characteristics.js +30 -15
- package/dist/sensor/sensor-characteristics.js.map +1 -1
- package/docs/AGENT_GUIDE.md +1 -0
- package/docs/API_REFERENCE.md +214 -4
- package/docs/QUICK_START.md +162 -8
- package/docs/USAGE.md +195 -22
- package/docs/api/capture-c2pa-embed.md +177 -0
- package/docs/api/capture-dng-import.md +86 -10
- package/docs/api/capture-dng-opcodes.md +93 -0
- package/docs/api/capture-dng-profile-maps.md +141 -0
- package/docs/api/capture-dng-stage-image.md +43 -0
- package/docs/api/capture-jpeg-xl-tiles.md +33 -0
- package/docs/api/capture-lossy-jpeg.md +41 -0
- package/docs/api/capture-native-raw-development.md +2 -1
- package/docs/api/core-execution-error.md +43 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +10 -5
- package/docs/api/equipment-sensor-profile-body.md +64 -0
- package/docs/api/equipment-sensor-profiles.md +86 -0
- package/docs/api/exports.json +460 -5
- package/docs/api/facade-display-view-gpu.md +5 -2
- package/docs/api/facade-display-view.md +11 -15
- package/docs/api/facade-exposure-build-up.md +105 -0
- package/docs/api/facade-fast-methods.md +47 -10
- package/docs/api/facade-image-plane-enable.md +16 -0
- package/docs/api/facade-mixed-illumination.md +67 -0
- package/docs/api/facade-quality-paths.md +24 -0
- package/docs/api/facade-representative-light-sources.md +131 -0
- package/docs/api/facade-scene-radiance.md +54 -3
- package/docs/api/facade-simulate-capture.md +48 -11
- package/docs/api/facade-simulate-display-view.md +10 -12
- package/docs/api/gpu-image-kernel-execution.md +0 -36
- package/docs/api/motion-subject-motion.md +23 -0
- package/docs/api/optics-generic-lens-lateral-color.md +79 -0
- package/docs/api/output-camera-channel-response.md +55 -0
- package/docs/api/output-developed-lens-corrections.md +10 -1
- package/docs/api/output-generic-lens-corrections.md +40 -4
- package/docs/api/output-print-plan.md +35 -1
- package/docs/api/output-sdr-rendering.md +6 -1
- package/docs/api/sensor-sensor-characteristics.md +54 -2
- package/llms.txt +3 -0
- package/package.json +6 -5
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* The image-plane print in the GPU display view's Quality passes (#581): diffraction kernels, the defocus disc, the anti-aliasing
|
|
4
|
+
* filter's spots and AHD for a photo, loaded with the image-plane GPU module (only after `prepareImagePlaneScenes()`). Passes 1
|
|
5
|
+
* and 2 are the print's own; pass 3 (the display pixels) and the host finishing are the Quality renderer's, unchanged.
|
|
6
|
+
*
|
|
7
|
+
* **What the reference computes** (expected-signal.ts, the radiance path): with site kernels, per site of channel c,
|
|
8
|
+
* Σ_g Σ_d Σ_n C_n,c(d)·L_n(p_g + d), where L_n(p) is the print's spectral radiance at spectral node n at lattice point p (the
|
|
9
|
+
* provider's `radianceAt` on the lattice formula) and C_n,c(d) the reference's combined kernel per node (`perNode` of its
|
|
10
|
+
* tables: Σ_members w·A_c(λ_n)·K_n(t)); without kernels, per query offset w·Σ_n A_c(λ_n)·L_n(p_o), the same value as the direct
|
|
11
|
+
* path's `signalAt`.
|
|
12
|
+
*
|
|
13
|
+
* **Pass 1 (lattice radiance).** One invocation per lattice point of each group: the exact point (p + c)·g in double-float32
|
|
14
|
+
* (p + c is exact in float32, checked by the Quality host; the scale's hi + lo from float64), the print-local point, the print's
|
|
15
|
+
* edge in the composite kernel's bound arithmetic, the pixel coordinates, the bilinear weights as the Speed module forms them,
|
|
16
|
+
* and for every node n
|
|
17
|
+
*
|
|
18
|
+
* L_n = W_n·Σ_j b_j·(ρ_black + (ρ_white − ρ_black)·Σ_corners w_jk·s_k(λ_n)),
|
|
19
|
+
*
|
|
20
|
+
* the reference's arithmetic regrouped. Each pixel's lattice cell and fractions come from the host (float64, as the reference
|
|
21
|
+
* decodes the pixel), per band for the rows its lattice windows reach: 16 bytes per pixel (the cell, and per channel the smaller
|
|
22
|
+
* of f and 1 − f rounded once, with a bit for which), so every corner weight carries at most 2E relative per channel. All terms
|
|
23
|
+
* are non-negative: L_n carries `IMAGE_PLANE_LATTICE_U`·E relative (s 1, three channel weights 6, the nested trilinear form 6,
|
|
24
|
+
* the paper 3, the bilinear weights 4, their products and sums 4, the moved-weight budget 1, W_n 2: 27, declared 32), or the
|
|
25
|
+
* point is flagged: on the print's edge, near a nearest-pixel edge, or where a bilinear weight moved by the point's error could
|
|
26
|
+
* change the value by more than E of it (the Speed module's rule, per node).
|
|
27
|
+
*
|
|
28
|
+
* **Mixed light (#597).** Under `illumination` W_n is replaced by the irradiance / π at the point, E_n(p) = Σ_i w_i(p)·T_i,n +
|
|
29
|
+
* Σ_j c_j,n·h_j/d_j³ (src/facade/mixed-illumination.ts), from the light table (`imagePlaneLightTable`: the parts and the spectra at
|
|
30
|
+
* the nodes, no channel value). The blend's position t is formed in double-float32 with te, its error beyond its own low part; each
|
|
31
|
+
* weight comes from its own distance to the segment's ends (rounded once: E relative, plus te times the segment's slope); a regions
|
|
32
|
+
* edge, or a hard edge of equal stops, within the point's bound flags it; a choice of the neighbouring segment moves the weights by
|
|
33
|
+
* at most te times the steepest slope; each point source's h/d³ is a bounded value (B arithmetic). The node value's computed bound
|
|
34
|
+
* (the tables' rounding, the weights, the products and the sums) must stay within (IMAGE_PLANE_LIGHT_U − 1)·E of it, else the point
|
|
35
|
+
* is flagged, so a lit point carries (IMAGE_PLANE_LATTICE_U + IMAGE_PLANE_LIGHT_U)·E relative.
|
|
36
|
+
*
|
|
37
|
+
* **Flagged points.** With site kernels the reference evaluates the lattice formula itself, so the host replaces a flagged
|
|
38
|
+
* point's values with the reference's own `radianceAt` there and clears its flag (as #492 resolves uncertain chart materials).
|
|
39
|
+
* Without kernels the reference reads the environment's own query points, so a site that reads a flagged point (with a nonzero
|
|
40
|
+
* weight) is flagged and its display pixels are recomputed on the host.
|
|
41
|
+
*
|
|
42
|
+
* **Pass 2 (sites).** The blocked sum (#492's order) of Σ_n L_n·C_n,c(t) over each group's dense tap rectangle (without kernels the
|
|
43
|
+
* members' span, zero where no member sits): a term carries L's bound, the table's rounding and the product, the node sum's N − 1,
|
|
44
|
+
* and the row, group and total additions, so the electrons carry (IMAGE_PLANE_LATTICE_U + N + cw + ch + G + 4)·E relative.
|
|
45
|
+
* Then the Quality site tail (readout moments, white clip, running bounds), unchanged.
|
|
46
|
+
*/
|
|
47
|
+
import { ImageKernelExecutionError, WEBGPU_F32_UNIT_ROUNDOFF } from "./image-kernel-execution.js";
|
|
48
|
+
import { BUFFER_USAGE, linearDispatch, runComputePass } from "./webgpu-runtime.js";
|
|
49
|
+
import { dwSplit } from "./chart-composite-gpu.js";
|
|
50
|
+
import { displayViewModuleCommonWgsl } from "./display-view-composite-material-module.js";
|
|
51
|
+
import { DISPLAY_VIEW_QUALITY_COMMON_WGSL, DISPLAY_VIEW_QUALITY_GROUP_WORDS, DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL, DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE } from "./wgsl-display-view-quality-kernel.js";
|
|
52
|
+
import { latticeCoordinate } from "./display-view-quality-gpu.js";
|
|
53
|
+
import { queryGeometry } from "./display-view-speed-gpu.js";
|
|
54
|
+
import { displayViewSignalTables } from "../facade/expected-signal.js";
|
|
55
|
+
import { calculateIlluminantIrradianceScale } from "../facade/scene-radiance.js";
|
|
56
|
+
const E = 2 * WEBGPU_F32_UNIT_ROUNDOFF;
|
|
57
|
+
/** Relative roundings of a lattice point's radiance (see the module comment). */
|
|
58
|
+
export const IMAGE_PLANE_LATTICE_U = 32;
|
|
59
|
+
/**
|
|
60
|
+
* #597: relative roundings of the mixed light at a lattice point (a point whose computed bound exceeds IMAGE_PLANE_LIGHT_U − 1 is
|
|
61
|
+
* flagged; see the module comment), added to IMAGE_PLANE_LATTICE_U for a lit print.
|
|
62
|
+
*/
|
|
63
|
+
export const IMAGE_PLANE_LIGHT_U = 40;
|
|
64
|
+
/** #597: layout of the light table (float32 / u32 words; see `imagePlaneLightTable`). */
|
|
65
|
+
export const IMAGE_PLANE_LIGHT = /* @__PURE__ */ Object.freeze({ kind: 0, sources: 1, points: 2, regions: 3, outside: 4, regionsAt: 6, pointsAt: 7, tablesAt: 8, slack: 9,
|
|
66
|
+
surround: 10, maximumInverse: 11, halfHi: 12, halfLo: 13, aU: 16, aV: 18, dU: 20, dV: 22, inverse: 24, stops: 26, regionWords: 9, pointWords: 6, maximumPoints: 8 });
|
|
67
|
+
/** Lattice nodes of the print (5 × 5 × 5). */
|
|
68
|
+
const NODES = 125;
|
|
69
|
+
/** Words per pixel record. */
|
|
70
|
+
const RECORD_WORDS = 4;
|
|
71
|
+
/** Layout of the photo uniform (u32 / float32 words, 96 bytes). */
|
|
72
|
+
export const IMAGE_PLANE_QUALITY_PARAMS = /* @__PURE__ */ Object.freeze({ width: 0, height: 1, nearest: 2, nodes: 3, row0: 4, zero: 5, lit: 6, suHi: 8, suLo: 9, svHi: 10, svLo: 11,
|
|
73
|
+
kHi: 12, kLo: 13, halfWHi: 14, halfWLo: 15, halfHHi: 16, halfHLo: 17, aspectHi: 18, aspectLo: 19, slackX: 20, slackY: 21, black: 22, span: 23, words: 24 });
|
|
74
|
+
const FLOAT32_SAFE_MAGNITUDE = 2 ** 120, FLOAT32_MINIMUM_NORMAL = 2 ** -126;
|
|
75
|
+
const L = IMAGE_PLANE_LIGHT;
|
|
76
|
+
/**
|
|
77
|
+
* #597: the mixed light at a lattice point (see the module comment): `light_prepare` once per point (the blend's source and weights
|
|
78
|
+
* with their error bound, and each point source's h/d³ as a bounded value), then `light_at(n)` per node, flagging the point when the
|
|
79
|
+
* computed bound exceeds (IMAGE_PLANE_LIGHT_U − 1)·E of the value.
|
|
80
|
+
*/
|
|
81
|
+
const LIGHT_WGSL = /* wgsl */ `
|
|
82
|
+
var<private> blend_i: u32;
|
|
83
|
+
var<private> blend_w0: f32;
|
|
84
|
+
var<private> blend_w1: f32;
|
|
85
|
+
var<private> blend_e0: f32;
|
|
86
|
+
var<private> blend_e1: f32;
|
|
87
|
+
var<private> blend_amb: f32;
|
|
88
|
+
var<private> fall_v: array<f32, ${L.maximumPoints}>;
|
|
89
|
+
var<private> fall_e: array<f32, ${L.maximumPoints}>;
|
|
90
|
+
fn lw(i: u32) -> u32 { return bitcast<u32>(light[i]); }
|
|
91
|
+
fn ld(i: u32) -> D { return D(light[i], light[i + 1u]); }
|
|
92
|
+
|
|
93
|
+
fn d_gt(a: D, b: D) -> bool { let d = d_sub(a, b); return d.h > 0.0 || (d.h == 0.0 && d.l > 0.0); }
|
|
94
|
+
|
|
95
|
+
fn bsqrt(a: B) -> B {
|
|
96
|
+
let v = sqrt(max(a.v, 0.0));
|
|
97
|
+
let low = a.v - SAFETY * a.e;
|
|
98
|
+
if (!(low > 0.0)) { material_uncertain = true; return B(v, 3.0e38); }
|
|
99
|
+
return B(v, a.e / (2.0 * sqrt(low)) + 5.0 * E * v);
|
|
100
|
+
}
|
|
101
|
+
fn light_prepare(u: D, v: D) {
|
|
102
|
+
let kind = lw(${L.kind}u);
|
|
103
|
+
let S = lw(${L.sources}u);
|
|
104
|
+
blend_i = 0u; blend_w0 = 1.0; blend_w1 = 0.0; blend_e0 = 0.0; blend_e1 = 0.0; blend_amb = 0.0;
|
|
105
|
+
|
|
106
|
+
let ce = 4.0 * DW * (abs(u.h) + abs(v.h) + 1.0) + light[${L.slack}u];
|
|
107
|
+
if (kind == 3u) {
|
|
108
|
+
var source = lw(${L.outside}u);
|
|
109
|
+
let bu = bd(u);
|
|
110
|
+
let bv = bd(v);
|
|
111
|
+
for (var r = 0u; r < lw(${L.regions}u); r = r + 1u) {
|
|
112
|
+
let b = lw(${L.regionsAt}u) + r * ${L.regionWords}u;
|
|
113
|
+
if (ge(bu, bd(ld(b + 1u))) && le(bu, bd(ld(b + 3u))) && ge(bv, bd(ld(b + 5u))) && le(bv, bd(ld(b + 7u)))) { source = lw(b); break; }
|
|
114
|
+
}
|
|
115
|
+
blend_i = source;
|
|
116
|
+
} else if (kind != 0u) {
|
|
117
|
+
|
|
118
|
+
var t: D;
|
|
119
|
+
var te: f32;
|
|
120
|
+
let a_u = d_sub(u, ld(${L.aU}u));
|
|
121
|
+
let a_v = d_sub(v, ld(${L.aV}u));
|
|
122
|
+
let inv = ld(${L.inverse}u);
|
|
123
|
+
let ce_t = ce + 4.0 * DW * (abs(light[${L.aU}u]) + abs(light[${L.aV}u]));
|
|
124
|
+
if (kind == 1u) {
|
|
125
|
+
let x = d_mul(a_u, ld(${L.dU}u));
|
|
126
|
+
let y = d_mul(a_v, ld(${L.dV}u));
|
|
127
|
+
let scale = (abs(x.h) + abs(y.h)) * abs(inv.h);
|
|
128
|
+
t = d_mul(d_add(x, y), inv);
|
|
129
|
+
te = 8.0 * DW * (scale + 1.0) + ce_t * (abs(light[${L.dU}u]) + abs(light[${L.dV}u])) * abs(inv.h);
|
|
130
|
+
if (t.h < 0.0) { t = D(0.0, 0.0); }
|
|
131
|
+
if (d_gt(t, D(1.0, 0.0))) { t = D(1.0, 0.0); }
|
|
132
|
+
} else {
|
|
133
|
+
|
|
134
|
+
let dd = d_add(d_mul(a_u, a_u), d_mul(a_v, a_v));
|
|
135
|
+
let r0 = sqrt(max(dd.h, 0.0));
|
|
136
|
+
var r = D(r0, 0.0);
|
|
137
|
+
if (r0 > 0.0) { let res = d_sub(dd, d_mul(D(r0, 0.0), D(r0, 0.0))); r = quick_two_sum(r0, ob(ob(res.h + res.l) / ob(2.0 * r0))); }
|
|
138
|
+
t = d_mul(r, inv);
|
|
139
|
+
te = 8.0 * DW * (abs(t.h) + 1.0) + 2.0 * ce_t * abs(inv.h);
|
|
140
|
+
if (d_gt(t, D(1.0, 0.0))) { t = D(1.0, 0.0); }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
var i = 0u;
|
|
144
|
+
loop { if (i + 1u >= S) { break; } if (d_gt(ld(${L.stops}u + (i + 1u) * 4u), t)) { break; } i = i + 1u; }
|
|
145
|
+
for (var k = 0u; k + 1u < S; k = k + 1u) {
|
|
146
|
+
if (light[${L.stops}u + k * 4u + 2u] == 0.0 && abs(d_sub(t, ld(${L.stops}u + k * 4u)).h) <= SAFETY * te + DW * (abs(t.h) + 1.0)) { material_uncertain = true; }
|
|
147
|
+
}
|
|
148
|
+
let si = ld(${L.stops}u + i * 4u);
|
|
149
|
+
|
|
150
|
+
blend_amb = te * light[${L.maximumInverse}u];
|
|
151
|
+
if (i + 1u < S && d_gt(t, si)) {
|
|
152
|
+
|
|
153
|
+
let inv_i = ld(${L.stops}u + i * 4u + 2u);
|
|
154
|
+
let fd = d_mul(d_sub(t, si), inv_i);
|
|
155
|
+
let gd = d_mul(d_sub(ld(${L.stops}u + (i + 1u) * 4u), t), inv_i);
|
|
156
|
+
blend_w1 = clamp(ob(fd.h + fd.l), 0.0, 1.0);
|
|
157
|
+
blend_w0 = clamp(ob(gd.h + gd.l), 0.0, 1.0);
|
|
158
|
+
let shared_e = (te + 4.0 * DW * (abs(t.h) + abs(si.h) + 1.0)) * abs(inv_i.h);
|
|
159
|
+
blend_e1 = E * blend_w1 + shared_e;
|
|
160
|
+
blend_e0 = E * blend_w0 + shared_e;
|
|
161
|
+
}
|
|
162
|
+
blend_i = i;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let half = ld(${L.halfHi}u);
|
|
166
|
+
let x = d_mul(u, half);
|
|
167
|
+
let y = d_mul(v, half);
|
|
168
|
+
let cm = ce * abs(half.h) + 4.0 * DW * (abs(x.h) + abs(y.h));
|
|
169
|
+
for (var j = 0u; j < lw(${L.points}u); j = j + 1u) {
|
|
170
|
+
let b = lw(${L.pointsAt}u) + j * ${L.pointWords}u;
|
|
171
|
+
var dx = bds(d_sub(x, ld(b)), abs(x.h) + abs(light[b]));
|
|
172
|
+
dx.e = dx.e + cm;
|
|
173
|
+
var dy = bds(d_sub(y, ld(b + 2u)), abs(y.h) + abs(light[b + 2u]));
|
|
174
|
+
dy.e = dy.e + cm;
|
|
175
|
+
let h = bd(ld(b + 4u));
|
|
176
|
+
let d2 = badd(badd(bmul(dx, dx), bmul(dy, dy)), bmul(h, h));
|
|
177
|
+
let fall = bdiv(h, bmul(d2, bsqrt(d2)));
|
|
178
|
+
fall_v[j] = fall.v;
|
|
179
|
+
fall_e[j] = fall.e;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fn light_at(n: u32) -> f32 {
|
|
184
|
+
let N = photo.nodes;
|
|
185
|
+
let S = lw(${L.sources}u);
|
|
186
|
+
let P = lw(${L.points}u);
|
|
187
|
+
let tab = lw(${L.tablesAt}u);
|
|
188
|
+
var value = 0.0;
|
|
189
|
+
var err = 0.0;
|
|
190
|
+
if (S > 0u) {
|
|
191
|
+
let ti = light[tab + blend_i * N + n];
|
|
192
|
+
var top = ti;
|
|
193
|
+
for (var s = 0u; s < S; s = s + 1u) { top = max(top, light[tab + s * N + n]); }
|
|
194
|
+
var tj = 0.0;
|
|
195
|
+
if (blend_w1 == 0.0) { value = ti; } else { tj = light[tab + (blend_i + 1u) * N + n]; value = blend_w0 * ti + blend_w1 * tj; }
|
|
196
|
+
err = 3.0 * E * value + blend_e0 * ti + blend_e1 * tj + blend_amb * top;
|
|
197
|
+
}
|
|
198
|
+
for (var j = 0u; j < P; j = j + 1u) {
|
|
199
|
+
let c = light[tab + (S + j) * N + n];
|
|
200
|
+
let term = c * fall_v[j];
|
|
201
|
+
value = value + term;
|
|
202
|
+
err = err + c * fall_e[j] + 2.0 * E * term;
|
|
203
|
+
}
|
|
204
|
+
err = err + f32(P + 1u) * E * value;
|
|
205
|
+
if (!(err <= ${IMAGE_PLANE_LIGHT_U - 1}.0 * E * value)) { material_uncertain = true; }
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
209
|
+
/**
|
|
210
|
+
* #597: the light table of a lit print at the spectral nodes (the layout `IMAGE_PLANE_LIGHT`): the blend (kind, sources, its geometry
|
|
211
|
+
* and stops with each segment's inverse width, 0 at a hard edge), the regions, the point sources (x, y, h in metres), then per node
|
|
212
|
+
* T_i,n = k_i·S_i(λ_n)/π for each blend source and c_j·S_j(λ_n)/π for each point source: spectra only, no channel value. `slack` is the
|
|
213
|
+
* reference's own rounding of a query point in print-local units.
|
|
214
|
+
*/
|
|
215
|
+
export function imagePlaneLightTable(light, wavelengths, surroundReflectance, slack) {
|
|
216
|
+
const resolved = light.layout;
|
|
217
|
+
if (!resolved)
|
|
218
|
+
throw new ImageKernelExecutionError("unsupported-on-backend", "The print's mixed light carries no parts for the GPU; use cpu-reference.");
|
|
219
|
+
// The blend's sources come first, then the point sources (their report carries the position and the intensity, c = I·k(1 lux)).
|
|
220
|
+
const blend = resolved.blend;
|
|
221
|
+
const sources = resolved.sources.filter((s) => s.report.position === undefined), points = resolved.sources.filter((s) => s.report.position !== undefined).map((s) => {
|
|
222
|
+
const position = s.report.position;
|
|
223
|
+
return { ...position, scale: calculateIlluminantIrradianceScale(s.illuminant.shape, 1) * s.report.intensityCandela, shape: s.illuminant.shape };
|
|
224
|
+
});
|
|
225
|
+
const S = sources.length, P = points.length, N = wavelengths.length, regions = blend?.regions ?? [];
|
|
226
|
+
if (P > L.maximumPoints)
|
|
227
|
+
throw new ImageKernelExecutionError("unsupported-on-backend", "More point sources than the GPU pass holds.");
|
|
228
|
+
const regionsAt = L.stops + 4 * S, pointsAt = regionsAt + L.regionWords * regions.length, tablesAt = pointsAt + L.pointWords * P;
|
|
229
|
+
const table = new Float32Array(tablesAt + (S + P) * N), words = new Uint32Array(table.buffer);
|
|
230
|
+
const pair = (at, x) => { [table[at], table[at + 1]] = dwSplit(x); };
|
|
231
|
+
words[L.kind] = blend === null ? 0 : blend.kind === "linear-gradient" ? 1 : blend.kind === "radial" ? 2 : 3;
|
|
232
|
+
words[L.sources] = S;
|
|
233
|
+
words[L.points] = P;
|
|
234
|
+
words[L.regions] = regions.length;
|
|
235
|
+
words[L.outside] = blend?.outsideSource ?? 0;
|
|
236
|
+
words[L.regionsAt] = regionsAt;
|
|
237
|
+
words[L.pointsAt] = pointsAt;
|
|
238
|
+
words[L.tablesAt] = tablesAt;
|
|
239
|
+
table[L.slack] = slack;
|
|
240
|
+
table[L.surround] = surroundReflectance;
|
|
241
|
+
pair(L.halfHi, light.halfWidthM);
|
|
242
|
+
if (blend?.kind === "linear-gradient") {
|
|
243
|
+
const from = blend.from, du = blend.to.u - from.u, dv = blend.to.v - from.v;
|
|
244
|
+
pair(L.aU, from.u);
|
|
245
|
+
pair(L.aV, from.v);
|
|
246
|
+
pair(L.dU, du);
|
|
247
|
+
pair(L.dV, dv);
|
|
248
|
+
pair(L.inverse, 1 / (du * du + dv * dv));
|
|
249
|
+
}
|
|
250
|
+
else if (blend?.kind === "radial") {
|
|
251
|
+
pair(L.aU, blend.center.u);
|
|
252
|
+
pair(L.aV, blend.center.v);
|
|
253
|
+
pair(L.inverse, 1 / blend.radius);
|
|
254
|
+
}
|
|
255
|
+
let maximumInverse = 0;
|
|
256
|
+
const stops = blend?.stops ?? [];
|
|
257
|
+
stops.forEach((stop, i) => {
|
|
258
|
+
const next = stops[i + 1], inverse = next !== undefined && next > stop ? 1 / (next - stop) : 0;
|
|
259
|
+
pair(L.stops + 4 * i, stop);
|
|
260
|
+
pair(L.stops + 4 * i + 2, inverse);
|
|
261
|
+
maximumInverse = Math.max(maximumInverse, inverse);
|
|
262
|
+
});
|
|
263
|
+
table[L.maximumInverse] = maximumInverse;
|
|
264
|
+
regions.forEach((r, i) => {
|
|
265
|
+
const at = regionsAt + L.regionWords * i;
|
|
266
|
+
words[at] = r.source;
|
|
267
|
+
pair(at + 1, r.uMin);
|
|
268
|
+
pair(at + 3, r.uMax);
|
|
269
|
+
pair(at + 5, r.vMin);
|
|
270
|
+
pair(at + 7, r.vMax);
|
|
271
|
+
});
|
|
272
|
+
points.forEach((p, j) => { const at = pointsAt + L.pointWords * j; pair(at, p.xM); pair(at + 2, p.yM); pair(at + 4, p.heightM); });
|
|
273
|
+
// The largest irradiance / π anywhere (the blend's largest source and every point at normal incidence, 1/h²), for the tiny-value bound.
|
|
274
|
+
let largestIrradiance = 0;
|
|
275
|
+
wavelengths.forEach((nm, n) => {
|
|
276
|
+
let top = 0, atNormal = 0;
|
|
277
|
+
sources.forEach((source, i) => { const value = source.irradianceScale * source.illuminant.shape.evaluate(nm) / Math.PI; table[tablesAt + i * N + n] = value; top = Math.max(top, value); });
|
|
278
|
+
points.forEach((p, j) => { const value = p.scale * p.shape.evaluate(nm) / Math.PI; table[tablesAt + (S + j) * N + n] = value; atNormal += value / (p.heightM * p.heightM); });
|
|
279
|
+
largestIrradiance = Math.max(largestIrradiance, top + atNormal);
|
|
280
|
+
});
|
|
281
|
+
for (let i = tablesAt; i < table.length; i++)
|
|
282
|
+
if (!(table[i] >= 0 && table[i] <= FLOAT32_SAFE_MAGNITUDE))
|
|
283
|
+
throw new ImageKernelExecutionError("unsupported-float32-domain", "A light's spectrum is outside the float32 domain.");
|
|
284
|
+
return { table, largestIrradiance };
|
|
285
|
+
}
|
|
286
|
+
/** The display-view helpers of the composite kernel's common part, with the barrier word read from the photo uniform. */
|
|
287
|
+
const photoCommon = (path) => displayViewModuleCommonWgsl(path).replace(/\blattice\.zero\b/gu, "photo.zero");
|
|
288
|
+
/** Pass 1's WGSL (see the module comment). */
|
|
289
|
+
export function imagePlaneQualityLatticeWgsl(path) {
|
|
290
|
+
return `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}
|
|
291
|
+
@group(0) @binding(1) var<storage, read> groups: array<u32>;
|
|
292
|
+
@group(0) @binding(2) var<storage, read_write> lattice_values: array<f32>;
|
|
293
|
+
@group(0) @binding(3) var<storage, read_write> lattice_flags: array<u32>;
|
|
294
|
+
struct PhotoParams {
|
|
295
|
+
width: u32, height: u32, nearest: u32, nodes: u32, row0: u32, zero: u32, lit: u32, pad1: u32,
|
|
296
|
+
su_hi: f32, su_lo: f32, sv_hi: f32, sv_lo: f32, k_hi: f32, k_lo: f32, hw_hi: f32, hw_lo: f32,
|
|
297
|
+
hh_hi: f32, hh_lo: f32, aspect_hi: f32, aspect_lo: f32, slack_x: f32, slack_y: f32, black: f32, span: f32,
|
|
298
|
+
};
|
|
299
|
+
@group(1) @binding(0) var<storage, read> records: array<u32>;
|
|
300
|
+
@group(1) @binding(1) var<uniform> photo: PhotoParams;
|
|
301
|
+
@group(1) @binding(2) var<storage, read> node_table: array<f32>;
|
|
302
|
+
@group(1) @binding(3) var<storage, read> light: array<f32>;
|
|
303
|
+
${photoCommon(path)}
|
|
304
|
+
${LIGHT_WGSL}
|
|
305
|
+
fn pixel_slack(x: D, slack: f32) -> f32 { return 4.0 * (DW * (abs(x.h) + 1.0)) + 4.0 * slack; }
|
|
306
|
+
struct Axis { i0: u32, i1: u32, w0: f32, w1: f32 }
|
|
307
|
+
fn bilinear_axis(s: D, n: u32) -> Axis {
|
|
308
|
+
var f0 = floor(s.h);
|
|
309
|
+
if (f0 == s.h && s.l < 0.0) { f0 = f0 - 1.0; }
|
|
310
|
+
let frac = d_sub(s, D(f0, 0.0));
|
|
311
|
+
let comp = d_sub(D(ob(f0 + 1.0), 0.0), s);
|
|
312
|
+
var w0 = 0.0; var w1 = 0.0;
|
|
313
|
+
if (frac.h <= 0.5) { w1 = ob(frac.h + frac.l); w0 = ob(1.0 - w1); } else { w0 = ob(comp.h + comp.l); w1 = ob(1.0 - w0); }
|
|
314
|
+
let last = i32(n) - 1;
|
|
315
|
+
return Axis(u32(clamp(i32(f0), 0, last)), u32(clamp(i32(f0) + 1, 0, last)), w0, w1);
|
|
316
|
+
}
|
|
317
|
+
// A channel's two corner weights from its record: the stored smaller one and 1 minus it.
|
|
318
|
+
fn channel_weights(word: u32, bit: u32, small: f32) -> vec2<f32> {
|
|
319
|
+
if (((word >> bit) & 1u) == 0u) { return vec2<f32>(ob(1.0 - small), small); }
|
|
320
|
+
return vec2<f32>(small, ob(1.0 - small));
|
|
321
|
+
}
|
|
322
|
+
struct Pixel { base: u32, r: vec2<f32>, g: vec2<f32>, b: vec2<f32> }
|
|
323
|
+
fn pixel_at(i: u32, j: u32) -> Pixel {
|
|
324
|
+
let o = ((j - photo.row0) * photo.width + i) * ${RECORD_WORDS}u;
|
|
325
|
+
let w = records[o];
|
|
326
|
+
return Pixel((w & 3u) * 25u + ((w >> 2u) & 3u) * 5u + ((w >> 4u) & 3u), channel_weights(w, 6u, bitcast<f32>(records[o + 1u])),
|
|
327
|
+
channel_weights(w, 7u, bitcast<f32>(records[o + 2u])), channel_weights(w, 8u, bitcast<f32>(records[o + 3u])));
|
|
328
|
+
}
|
|
329
|
+
fn node(k: u32, n: u32) -> f32 { return node_table[k * photo.nodes + n]; }
|
|
330
|
+
// The white radiance at node n, or (#597) under mixed light the irradiance / π at the point.
|
|
331
|
+
fn white_at(white: u32, n: u32) -> f32 { if (photo.lit != 0u) { return light_at(n); } return node_table[white + n]; }
|
|
332
|
+
// One pixel's reflectance at node n: the reference's nested trilinear form, then the paper.
|
|
333
|
+
fn pixel_value(px: Pixel, n: u32) -> f32 {
|
|
334
|
+
let b = px.base;
|
|
335
|
+
let lat = px.r.x * (px.g.x * (px.b.x * node(b, n) + px.b.y * node(b + 1u, n)) + px.g.y * (px.b.x * node(b + 5u, n) + px.b.y * node(b + 6u, n))) +
|
|
336
|
+
px.r.y * (px.g.x * (px.b.x * node(b + 25u, n) + px.b.y * node(b + 26u, n)) + px.g.y * (px.b.x * node(b + 30u, n) + px.b.y * node(b + 31u, n)));
|
|
337
|
+
return photo.black + photo.span * lat;
|
|
338
|
+
}
|
|
339
|
+
@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
|
|
340
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
|
|
341
|
+
let i = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
|
|
342
|
+
if (i >= params.lattice_total) { return; }
|
|
343
|
+
var g = 0u;
|
|
344
|
+
for (var k = 1u; k < params.groups; k = k + 1u) { if (i >= groups[k * GROUP + 4u]) { g = k; } }
|
|
345
|
+
let base = g * GROUP;
|
|
346
|
+
let lw = groups[base + 2u];
|
|
347
|
+
let local = i - groups[base + 4u];
|
|
348
|
+
let q = bitcast<i32>(groups[base + 1u]) + i32(local / lw);
|
|
349
|
+
let p = bitcast<i32>(groups[base]) + i32(local % lw);
|
|
350
|
+
material_uncertain = false;
|
|
351
|
+
let N = photo.nodes;
|
|
352
|
+
let out = i * N;
|
|
353
|
+
let white = ${NODES}u * N;
|
|
354
|
+
let k = D(photo.k_hi, photo.k_lo);
|
|
355
|
+
let u = d_mul(d_mul(D(f32(p) + bitcast<f32>(groups[base + 6u]), 0.0), D(photo.su_hi, photo.su_lo)), k);
|
|
356
|
+
let v = d_mul(d_mul(D(f32(q) + bitcast<f32>(groups[base + 7u]), 0.0), D(photo.sv_hi, photo.sv_lo)), k);
|
|
357
|
+
let aspect = D(photo.aspect_hi, photo.aspect_lo);
|
|
358
|
+
// #597: under mixed light the irradiance at the point (its blend and point sources) replaces the white radiance per node.
|
|
359
|
+
if (photo.lit != 0u) { light_prepare(u, v); }
|
|
360
|
+
if (!(le(babs(bd(u)), bx(1.0)) && le(babs(bd(v)), bd(aspect)))) {
|
|
361
|
+
for (var n = 0u; n < N; n = n + 1u) {
|
|
362
|
+
if (photo.lit != 0u) { lattice_values[out + n] = light[${L.surround}u] * light_at(n); } else { lattice_values[out + n] = node_table[white + N + n]; }
|
|
363
|
+
}
|
|
364
|
+
lattice_flags[i] = select(0u, 1u, material_uncertain);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
let x = d_mul(d_add(u, D(1.0, 0.0)), D(photo.hw_hi, photo.hw_lo));
|
|
368
|
+
let y = d_mul(d_sub(aspect, v), D(photo.hh_hi, photo.hh_lo));
|
|
369
|
+
if (photo.nearest != 0u) {
|
|
370
|
+
let last_x = f32(photo.width - 1u);
|
|
371
|
+
let last_y = f32(photo.height - 1u);
|
|
372
|
+
let px = pixel_at(u32(clamp(bfloor(bd(x), 1.0, last_x), 0.0, last_x)), u32(clamp(bfloor(bd(y), 1.0, last_y), 0.0, last_y)));
|
|
373
|
+
for (var n = 0u; n < N; n = n + 1u) { lattice_values[out + n] = white_at(white, n) * pixel_value(px, n); }
|
|
374
|
+
} else {
|
|
375
|
+
let ax = bilinear_axis(d_sub(x, D(0.5, 0.0)), photo.width);
|
|
376
|
+
let ay = bilinear_axis(d_sub(y, D(0.5, 0.0)), photo.height);
|
|
377
|
+
let p00 = pixel_at(ax.i0, ay.i0);
|
|
378
|
+
let p10 = pixel_at(ax.i1, ay.i0);
|
|
379
|
+
let p01 = pixel_at(ax.i0, ay.i1);
|
|
380
|
+
let p11 = pixel_at(ax.i1, ay.i1);
|
|
381
|
+
let slack = (pixel_slack(x, photo.slack_x) + pixel_slack(y, photo.slack_y)) * 2.0;
|
|
382
|
+
for (var n = 0u; n < N; n = n + 1u) {
|
|
383
|
+
let s00 = pixel_value(p00, n);
|
|
384
|
+
let s10 = pixel_value(p10, n);
|
|
385
|
+
let s01 = pixel_value(p01, n);
|
|
386
|
+
let s11 = pixel_value(p11, n);
|
|
387
|
+
let value = ay.w0 * (ax.w0 * s00 + ax.w1 * s10) + ay.w1 * (ax.w0 * s01 + ax.w1 * s11);
|
|
388
|
+
if (!(slack * max(max(s00, s10), max(s01, s11)) <= E * value)) { material_uncertain = true; }
|
|
389
|
+
lattice_values[out + n] = white_at(white, n) * value;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
lattice_flags[i] = select(0u, 1u, material_uncertain);
|
|
393
|
+
}
|
|
394
|
+
`;
|
|
395
|
+
}
|
|
396
|
+
/** Pass 2's WGSL (see the module comment): the blocked sum over the nodes, then the Quality site tail. */
|
|
397
|
+
export const IMAGE_PLANE_QUALITY_SITES_WGSL = /* wgsl */ `${DISPLAY_VIEW_QUALITY_COMMON_WGSL}
|
|
398
|
+
@group(0) @binding(1) var<storage, read> groups: array<u32>;
|
|
399
|
+
@group(0) @binding(3) var<storage, read> table: array<f32>;
|
|
400
|
+
@group(0) @binding(4) var<storage, read> lattice: array<f32>;
|
|
401
|
+
@group(0) @binding(5) var<storage, read_write> sites: array<f32>;
|
|
402
|
+
@group(0) @binding(6) var<storage, read> lattice_flags: array<u32>;
|
|
403
|
+
${DISPLAY_VIEW_QUALITY_SITE_TAIL_WGSL}
|
|
404
|
+
@compute @workgroup_size(${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE})
|
|
405
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) wg: vec3<u32>) {
|
|
406
|
+
let local = gid.x + gid.y * wg.x * ${DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE}u;
|
|
407
|
+
if (local >= params.count) { return; }
|
|
408
|
+
let i = params.site_first + local;
|
|
409
|
+
let ii = i % params.site_w;
|
|
410
|
+
let jj = i / params.site_w;
|
|
411
|
+
let c = channel_of(params.site_x0 + i32(ii), params.site_y0 + i32(jj));
|
|
412
|
+
let T = params.taps;
|
|
413
|
+
let N = bitcast<u32>(params.pad_h);
|
|
414
|
+
var sum = 0.0;
|
|
415
|
+
var flagged = false;
|
|
416
|
+
for (var k = 0u; k < params.groups; k = k + 1u) {
|
|
417
|
+
let g = k * GROUP;
|
|
418
|
+
let lw = groups[g + 2u];
|
|
419
|
+
let cw = lw - params.site_w + 1u;
|
|
420
|
+
let ch = groups[g + 3u] - params.site_h + 1u;
|
|
421
|
+
let lattice0 = groups[g + 4u] + jj * lw + ii;
|
|
422
|
+
let tap0 = groups[g + 5u];
|
|
423
|
+
var group_sum = 0.0;
|
|
424
|
+
for (var dy = 0u; dy < ch; dy = dy + 1u) {
|
|
425
|
+
let row = lattice0 + dy * lw;
|
|
426
|
+
let row_tap = tap0 + dy * cw;
|
|
427
|
+
var row_sum = 0.0;
|
|
428
|
+
for (var dx = 0u; dx < cw; dx = dx + 1u) {
|
|
429
|
+
let point = row + dx;
|
|
430
|
+
let t = row_tap + dx;
|
|
431
|
+
var cell = 0.0;
|
|
432
|
+
var weight = 0.0;
|
|
433
|
+
for (var n = 0u; n < N; n = n + 1u) {
|
|
434
|
+
let w = table[(n * 3u + c) * T + t];
|
|
435
|
+
weight = weight + w;
|
|
436
|
+
cell = cell + lattice[point * N + n] * w;
|
|
437
|
+
}
|
|
438
|
+
if (lattice_flags[point] != 0u && weight > 0.0) { flagged = true; }
|
|
439
|
+
row_sum = row_sum + cell;
|
|
440
|
+
}
|
|
441
|
+
group_sum = group_sum + row_sum;
|
|
442
|
+
}
|
|
443
|
+
sum = sum + group_sum;
|
|
444
|
+
}
|
|
445
|
+
let e = sum + dark_of(c);
|
|
446
|
+
write_site(i, c, e, params.site_gamma * e + params.site_tiny, select(0u, 1u, flagged));
|
|
447
|
+
}
|
|
448
|
+
`;
|
|
449
|
+
/**
|
|
450
|
+
* The print's gather: per group its dense tap rectangle (with site kernels the reference's combined window; without them the
|
|
451
|
+
* members' span), the lattice extent beyond the site window and the point constants, and the table C_n,c(t) at (n·3 + c)·T + t,
|
|
452
|
+
* the reference's float64 values rounded once.
|
|
453
|
+
*/
|
|
454
|
+
export function imagePlaneQualityGather(signalInput, largestLitRadiance) {
|
|
455
|
+
const tables = displayViewSignalTables(signalInput), W = signalInput.nativeWidth, H = signalInput.nativeHeight, R = signalInput.kernels ? signalInput.kernels.radius : 0;
|
|
456
|
+
const N = signalInput.wavelengthsNm.length, A = signalInput.electronsPerRadiance, groups = [];
|
|
457
|
+
const rects = tables.groups.map((group) => {
|
|
458
|
+
const cw = signalInput.kernels ? group.cw : group.maxIx - group.minIx + 1, ch = signalInput.kernels ? group.ch : group.maxIy - group.minIy + 1;
|
|
459
|
+
return { group, cw, ch, dpMin: group.minIx - R, dqMin: group.minIy - R };
|
|
460
|
+
});
|
|
461
|
+
let T = 0;
|
|
462
|
+
for (const r of rects) {
|
|
463
|
+
groups.push({ dpMin: r.dpMin, dqMin: r.dqMin, dpSpan: r.cw - 1, dqSpan: r.ch - 1, cu: 0.5 + r.group.fx - W / 2, cv: 0.5 + r.group.fy - H / 2, firstTap: T, cw: r.cw, ch: r.ch });
|
|
464
|
+
T += r.cw * r.ch;
|
|
465
|
+
}
|
|
466
|
+
const table = new Float32Array(N * 3 * T), tiny = new Float64Array(T);
|
|
467
|
+
let largestRadiance = 0;
|
|
468
|
+
rects.forEach((r, g) => {
|
|
469
|
+
const first = groups[g].firstTap;
|
|
470
|
+
for (let n = 0; n < N; n++)
|
|
471
|
+
for (let c = 0; c < 3; c++) {
|
|
472
|
+
const values = new Float64Array(r.cw * r.ch);
|
|
473
|
+
if (signalInput.kernels)
|
|
474
|
+
values.set(r.group.perNode[n][c]);
|
|
475
|
+
else
|
|
476
|
+
for (const member of r.group.members) {
|
|
477
|
+
const at = (member.iy - r.group.minIy) * r.cw + member.ix - r.group.minIx;
|
|
478
|
+
values[at] = values[at] + member.weight * A[c][n];
|
|
479
|
+
}
|
|
480
|
+
for (let t = 0; t < values.length; t++) {
|
|
481
|
+
const value = values[t];
|
|
482
|
+
if (!(value >= 0 && value <= FLOAT32_SAFE_MAGNITUDE))
|
|
483
|
+
throw new ImageKernelExecutionError("unsupported-float32-domain", "A combined kernel value is outside the float32 domain.");
|
|
484
|
+
table[(n * 3 + c) * T + first + t] = value;
|
|
485
|
+
if (value > 0 && value < FLOAT32_MINIMUM_NORMAL)
|
|
486
|
+
tiny[first + t] = Math.max(tiny[first + t], value);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
const image = signalInput.scene.image;
|
|
491
|
+
if (largestLitRadiance !== undefined)
|
|
492
|
+
largestRadiance = largestLitRadiance;
|
|
493
|
+
else
|
|
494
|
+
for (const w of image.whiteRadiance)
|
|
495
|
+
largestRadiance = Math.max(largestRadiance, w);
|
|
496
|
+
let tinyMass = 0;
|
|
497
|
+
for (let t = 0; t < T; t++)
|
|
498
|
+
tinyMass += tiny[t] * largestRadiance * N;
|
|
499
|
+
return { groups, taps: new Uint32Array(0), tapCount: T, nodes: N, table, spread: new Float32Array(3 * Math.max(1, T)), materials: 0, tinyMass };
|
|
500
|
+
}
|
|
501
|
+
/** The print's Quality passes for a setup (see `DisplayViewQualityImagePasses`), with the double-word product path the device's probe chose. */
|
|
502
|
+
export function imagePlaneQualityPasses(runtime, gpu, path) {
|
|
503
|
+
const signalInput = gpu.signalInput, image = signalInput.scene.image;
|
|
504
|
+
if (!image || !gpu.placement)
|
|
505
|
+
throw new ImageKernelExecutionError("unsupported-on-backend", "The setup carries no image-plane print and placement; use cpu-reference.");
|
|
506
|
+
// #597: under mixed light, the light's table (its parts and spectra at the nodes), which replaces the white radiance in pass 1.
|
|
507
|
+
const k0 = gpu.placement.chartUnitsPerFrameUnit, lit = image.light ? imagePlaneLightTable(image.light, signalInput.wavelengthsNm, image.surroundReflectance, 1e-14 * k0) : undefined;
|
|
508
|
+
const scene = image.scene, N = signalInput.wavelengthsNm.length, gather = imagePlaneQualityGather(signalInput, lit?.largestIrradiance);
|
|
509
|
+
let cw = 0, ch = 0;
|
|
510
|
+
for (const g of gather.groups) {
|
|
511
|
+
cw = Math.max(cw, g.cw);
|
|
512
|
+
ch = Math.max(ch, g.ch);
|
|
513
|
+
}
|
|
514
|
+
// The lattice scale: the lattice formula's g with site kernels, else the environment's query lattice (checked within 2⁻⁴⁰ by queryGeometry).
|
|
515
|
+
const g = 2 / signalInput.outputWidth, geometry = signalInput.kernels ? { scaleU: g, scaleV: -g } : queryGeometry(signalInput);
|
|
516
|
+
const halfW = scene.width / 2, halfH = scene.height / (2 * scene.aspect), k = gpu.placement.chartUnitsPerFrameUnit;
|
|
517
|
+
// The node table: s_k(λ_n) per node, then W_n, then the surround's radiance per node.
|
|
518
|
+
const nodeTable = new Float32Array((NODES + 2) * N);
|
|
519
|
+
signalInput.wavelengthsNm.forEach((nm, n) => {
|
|
520
|
+
const reflectances = scene.nodeReflectances(nm);
|
|
521
|
+
for (let node = 0; node < NODES; node++)
|
|
522
|
+
nodeTable[node * N + n] = reflectances[node];
|
|
523
|
+
nodeTable[NODES * N + n] = image.whiteRadiance[n];
|
|
524
|
+
nodeTable[(NODES + 1) * N + n] = image.surroundReflectance * image.whiteRadiance[n];
|
|
525
|
+
});
|
|
526
|
+
const P = IMAGE_PLANE_QUALITY_PARAMS, params = new ArrayBuffer(4 * P.words), words = new Uint32Array(params), floats = new Float32Array(params);
|
|
527
|
+
words[P.width] = scene.width;
|
|
528
|
+
words[P.height] = scene.height;
|
|
529
|
+
words[P.nearest] = scene.interpolation === "nearest" ? 1 : 0;
|
|
530
|
+
words[P.nodes] = N;
|
|
531
|
+
words[P.zero] = 0;
|
|
532
|
+
words[P.lit] = lit ? 1 : 0;
|
|
533
|
+
const lightTable = lit ? lit.table : new Float32Array(1);
|
|
534
|
+
[floats[P.suHi], floats[P.suLo]] = dwSplit(geometry.scaleU);
|
|
535
|
+
[floats[P.svHi], floats[P.svLo]] = dwSplit(geometry.scaleV);
|
|
536
|
+
[floats[P.kHi], floats[P.kLo]] = dwSplit(k);
|
|
537
|
+
[floats[P.halfWHi], floats[P.halfWLo]] = dwSplit(halfW);
|
|
538
|
+
[floats[P.halfHHi], floats[P.halfHLo]] = dwSplit(halfH);
|
|
539
|
+
[floats[P.aspectHi], floats[P.aspectLo]] = dwSplit(scene.aspect);
|
|
540
|
+
floats[P.slackX] = 1e-14 * k * halfW;
|
|
541
|
+
floats[P.slackY] = 1e-14 * k * halfH;
|
|
542
|
+
floats[P.black] = scene.paperBlackReflectance;
|
|
543
|
+
floats[P.span] = scene.paperWhiteReflectance - scene.paperBlackReflectance;
|
|
544
|
+
const label = path === "dekker" ? "image-plane" : "image-plane-fma";
|
|
545
|
+
const fractions = new Float64Array(3);
|
|
546
|
+
const passes = {
|
|
547
|
+
gather, siteGamma: (IMAGE_PLANE_LATTICE_U + (lit ? IMAGE_PLANE_LIGHT_U : 0) + N + cw + ch + gather.groups.length + 4) * E, pointFloats: N,
|
|
548
|
+
lattice: ["photivra-display-view-quality-photo-lattice:" + label, imagePlaneQualityLatticeWgsl(path)],
|
|
549
|
+
sites: ["photivra-display-view-quality-photo-sites", IMAGE_PLANE_QUALITY_SITES_WGSL],
|
|
550
|
+
async runLattice(band, pipeline, latticeParams, groupBuffer, groupWords, latticeTotal, signal) {
|
|
551
|
+
let values = band.buffer("photivra-display-view-photo-lattice", latticeTotal * 4 * N, BUFFER_USAGE.STORAGE | BUFFER_USAGE.COPY_SRC);
|
|
552
|
+
let flags = band.buffer("photivra-display-view-photo-lattice-flags", latticeTotal * 4, BUFFER_USAGE.STORAGE | BUFFER_USAGE.COPY_SRC);
|
|
553
|
+
const group0 = runtime.device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: latticeParams } },
|
|
554
|
+
{ binding: 1, resource: { buffer: groupBuffer } }, { binding: 2, resource: { buffer: values } }, { binding: 3, resource: { buffer: flags } }] });
|
|
555
|
+
const [valueData, flagData] = await runComputePass(runtime, band, pipeline, [group0, bandGroup(band, pipeline.getBindGroupLayout(1), groupWords)], linearDispatch(latticeTotal, DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE, runtime.maximumWorkgroupsPerDimension), [{ buffer: values, bytes: latticeTotal * 4 * N }, { buffer: flags, bytes: latticeTotal * 4 }], signal);
|
|
556
|
+
const valueArray = new Float32Array(valueData, 0, latticeTotal * N), flagArray = new Uint32Array(flagData, 0, latticeTotal), resolved = resolve(valueArray, flagArray, groupWords);
|
|
557
|
+
if (resolved > 0) {
|
|
558
|
+
values = band.upload("photivra-display-view-photo-lattice-resolved", valueArray, BUFFER_USAGE.STORAGE);
|
|
559
|
+
flags = band.upload("photivra-display-view-photo-flags-resolved", flagArray, BUFFER_USAGE.STORAGE);
|
|
560
|
+
}
|
|
561
|
+
return { values, flags, resolved };
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
function bandGroup(scope, layout, groupWords) {
|
|
565
|
+
// The pixel rows the band's lattice windows reach (the print-local v of each window's first and last rows, two rows of margin).
|
|
566
|
+
const ints = new Int32Array(groupWords.buffer, groupWords.byteOffset, groupWords.length), groupFloats = new Float32Array(groupWords.buffer, groupWords.byteOffset, groupWords.length);
|
|
567
|
+
let low = Infinity, high = -Infinity;
|
|
568
|
+
for (let gi = 0; gi < gather.groups.length; gi++) {
|
|
569
|
+
const base = gi * DISPLAY_VIEW_QUALITY_GROUP_WORDS, cv = groupFloats[base + 7];
|
|
570
|
+
for (const q of [ints[base + 1], ints[base + 1] + groupWords[base + 3] - 1]) {
|
|
571
|
+
const Y = (scene.aspect - (q + cv) * geometry.scaleV * k) * halfH;
|
|
572
|
+
low = Math.min(low, Y);
|
|
573
|
+
high = Math.max(high, Y);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const row0 = Math.max(0, Math.min(scene.height - 1, Math.floor(low) - 2)), row1 = Math.max(row0, Math.min(scene.height - 1, Math.floor(high) + 2));
|
|
577
|
+
const records = new Uint32Array((row1 - row0 + 1) * scene.width * RECORD_WORDS), recordFloats = new Float32Array(records.buffer);
|
|
578
|
+
for (let j = row0, o = 0; j <= row1; j++)
|
|
579
|
+
for (let i = 0; i < scene.width; i++, o += RECORD_WORDS) {
|
|
580
|
+
const cell = scene.pixelLattice(j * scene.width + i, fractions);
|
|
581
|
+
let word = Math.floor(cell / 25) | ((Math.floor(cell / 5) % 5) << 2) | ((cell % 5) << 4);
|
|
582
|
+
for (let c = 0; c < 3; c++) {
|
|
583
|
+
const f = fractions[c], high1 = f > 0.5;
|
|
584
|
+
if (high1)
|
|
585
|
+
word |= 1 << (6 + c);
|
|
586
|
+
recordFloats[o + 1 + c] = high1 ? 1 - f : f;
|
|
587
|
+
}
|
|
588
|
+
records[o] = word;
|
|
589
|
+
}
|
|
590
|
+
const bandParams = params.slice(0);
|
|
591
|
+
new Uint32Array(bandParams)[P.row0] = row0;
|
|
592
|
+
if (records.byteLength > runtime.maximumStorageBindingBytes)
|
|
593
|
+
throw new ImageKernelExecutionError("insufficient-limits", "A band's pixel records exceed the device's storage binding limit.");
|
|
594
|
+
return runtime.device.createBindGroup({ layout, entries: [
|
|
595
|
+
{ binding: 0, resource: { buffer: scope.upload("photivra-display-view-photo-records", records, BUFFER_USAGE.STORAGE) } },
|
|
596
|
+
{ binding: 1, resource: { buffer: scope.upload("photivra-display-view-photo-params", new Uint8Array(bandParams), BUFFER_USAGE.UNIFORM) } },
|
|
597
|
+
{ binding: 2, resource: { buffer: scope.upload("photivra-display-view-photo-nodes", nodeTable, BUFFER_USAGE.STORAGE) } },
|
|
598
|
+
{ binding: 3, resource: { buffer: scope.upload("photivra-display-view-photo-light", lightTable, BUFFER_USAGE.STORAGE) } }
|
|
599
|
+
] });
|
|
600
|
+
}
|
|
601
|
+
function resolve(values, flags, groupWords) {
|
|
602
|
+
if (!signalInput.kernels)
|
|
603
|
+
return 0;
|
|
604
|
+
// The reference's lattice point (createDisplayViewExpectedSignal's latticeU / latticeV) and its radiance per node.
|
|
605
|
+
const ints = new Int32Array(groupWords.buffer, groupWords.byteOffset, groupWords.length), groupsOf = displayViewSignalTables(signalInput).groups;
|
|
606
|
+
const W = signalInput.nativeWidth, H = signalInput.nativeHeight, scale = 2 / signalInput.outputWidth;
|
|
607
|
+
let resolved = 0;
|
|
608
|
+
groupsOf.forEach((group, gi) => {
|
|
609
|
+
const base = gi * DISPLAY_VIEW_QUALITY_GROUP_WORDS, lx0 = ints[base], ly0 = ints[base + 1], lw = groupWords[base + 2], lh = groupWords[base + 3], first = groupWords[base + 4];
|
|
610
|
+
for (let local = 0; local < lw * lh; local++) {
|
|
611
|
+
if (flags[first + local] === 0)
|
|
612
|
+
continue;
|
|
613
|
+
const p = lx0 + (local % lw), q = ly0 + Math.floor(local / lw);
|
|
614
|
+
const u = latticeCoordinate(p, group.fx, W, scale), v = -latticeCoordinate(q, group.fy, H, scale);
|
|
615
|
+
for (let n = 0; n < N; n++)
|
|
616
|
+
values[(first + local) * N + n] = signalInput.scene.kind === "radiance" ? signalInput.scene.radianceAt(u, v, signalInput.wavelengthsNm[n]) : 0;
|
|
617
|
+
flags[first + local] = 0;
|
|
618
|
+
resolved++;
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
return resolved;
|
|
622
|
+
}
|
|
623
|
+
return passes;
|
|
624
|
+
}
|
|
625
|
+
//# sourceMappingURL=display-view-image-quality.js.map
|