@photivra/engine 2.20.0 → 2.21.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 +178 -1
- package/CITATION.cff +1 -1
- package/README.md +7 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +7 -0
- 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/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/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.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/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 +2 -2
- package/dist/facade/catalogue-lens-signal.js +58 -71
- 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/display-view-gpu-session.js +2 -0
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +6 -1
- 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.js +5 -23
- 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 +1 -1
- 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.js +9 -1
- package/dist/facade/fast-methods.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +2 -2
- package/dist/facade/generic-lens-capture.js +2 -2
- 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 +353 -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/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 +129 -4
- package/dist/facade/scene-radiance.js +48 -13
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +39 -10
- package/dist/facade/simulate-capture.js +252 -92
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +14 -6
- 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-image-material-module.d.ts +86 -0
- package/dist/gpu/display-view-image-material-module.js +339 -0
- package/dist/gpu/display-view-image-material-module.js.map +1 -0
- package/dist/gpu/display-view-image-quality.d.ts +45 -0
- package/dist/gpu/display-view-image-quality.js +380 -0
- package/dist/gpu/display-view-image-quality.js.map +1 -0
- package/dist/gpu/display-view-quality-gpu.d.ts +67 -1
- package/dist/gpu/display-view-quality-gpu.js +136 -205
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +51 -3
- package/dist/gpu/display-view-speed-gpu.js +131 -93
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +29 -15
- package/dist/gpu/wgsl-display-view-quality-kernel.js +58 -36
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +21 -6
- package/dist/gpu/wgsl-display-view-speed-kernel.js +46 -11
- 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 +11 -3
- package/dist/index.js +7 -1
- 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/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/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/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/sensor-characteristics.d.ts +10 -0
- package/dist/sensor/sensor-characteristics.js +20 -9
- package/dist/sensor/sensor-characteristics.js.map +1 -1
- package/docs/AGENT_GUIDE.md +1 -0
- package/docs/API_REFERENCE.md +98 -1
- package/docs/QUICK_START.md +125 -0
- package/docs/USAGE.md +92 -1
- 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 +207 -2
- package/docs/api/facade-exposure-build-up.md +105 -0
- package/docs/api/facade-fast-methods.md +9 -1
- package/docs/api/facade-image-plane-enable.md +16 -0
- package/docs/api/facade-mixed-illumination.md +67 -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 +26 -6
- package/docs/api/motion-subject-motion.md +23 -0
- package/docs/api/optics-generic-lens-lateral-color.md +79 -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/llms.txt +2 -0
- package/package.json +1 -1
|
@@ -25,18 +25,17 @@
|
|
|
25
25
|
* reference, so they are exact. Every stage's wall-clock time is reported (`stageMilliseconds`); on a software adapter it
|
|
26
26
|
* is not GPU performance.
|
|
27
27
|
*/
|
|
28
|
-
import { AHD_KEY_LEVELS, EXACT_AREA_MEAN_MAX_PIXELS, axisSums, computeDisplayViewCorrectionStates,
|
|
28
|
+
import { AHD_KEY_LEVELS, EXACT_AREA_MEAN_MAX_PIXELS, axisSums, computeDisplayViewCorrectionStates, displayViewCorrectionKey, displayViewDecidedSaturation, overlaps, renderDisplayView, ahdCandidateKernels } from "../facade/display-view.js";
|
|
29
29
|
import { displayViewSignalTables } from "../facade/expected-signal.js";
|
|
30
|
-
import { resolvePortableChartScene } from "../facade/chart-scenes.js";
|
|
31
30
|
import { createDisplayViewMaterialModules } from "./display-view-composite-material-module.js";
|
|
32
31
|
import { setDisplayViewGpuRendererFactory } from "../facade/display-view-gpu.js";
|
|
33
32
|
import { chartPresetMaterialModule } from "./display-view-chart-material-module.js";
|
|
34
|
-
import { DISPLAY_VIEW_SPEED_CONFORMANCE_CASE, axisRecord, displayViewSpeedGpuSupports, displayViewSpeedGpuUnsupportedReason, finishDisplayViewPixel, gaussLegendre16, queryGeometry, renderDisplayViewSpeedOnGpu } from "./display-view-speed-gpu.js";
|
|
33
|
+
import { DISPLAY_VIEW_SPEED_CONFORMANCE_CASE, CONFORMANCE_FRAME, recomputeRuns, sceneToneAndOffsetsReason, conformanceChannelAt, conformanceChart, conformanceResponses, conformanceSetup, conformanceVerdict, withConformanceAbort, axisRecord, displayViewSpeedGpuSupports, displayViewSpeedGpuUnsupportedReason, finishDisplayViewPixel, gaussLegendre16, queryGeometry, renderDisplayViewSpeedOnGpu } from "./display-view-speed-gpu.js";
|
|
35
34
|
import { createHostYieldGate } from "../capture/host-yield-cadence.js";
|
|
36
35
|
import { createDefocusDiscSiteKernel } from "../optics/defocus-disc-kernel.js";
|
|
37
36
|
import { ImageKernelExecutionError, WEBGPU_F32_UNIT_ROUNDOFF, webGpuExecution } from "./image-kernel-execution.js";
|
|
38
37
|
import { BUFFER_USAGE, GpuResourceScope, abortedError, createComputePipeline, linearDispatch, runComputePass, throwIfAborted as checkAbort } from "./webgpu-runtime.js";
|
|
39
|
-
import { DISPLAY_VIEW_QUALITY_AUX, DISPLAY_VIEW_QUALITY_BITMAP_WORDS, DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES, DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS, DISPLAY_VIEW_QUALITY_FLAG, DISPLAY_VIEW_QUALITY_FLAG_WORDS, DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE, DISPLAY_VIEW_QUALITY_GROUP_WORDS, DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS, DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS, DISPLAY_VIEW_QUALITY_PARAM_WORDS, DISPLAY_VIEW_QUALITY_PIXELS_WGSL, DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL, DISPLAY_VIEW_QUALITY_SITES_WGSL, DISPLAY_VIEW_QUALITY_SITE_FLOATS, DISPLAY_VIEW_QUALITY_TAP_WORDS, DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE, assembleDisplayViewQualityLatticeWgsl } from "./wgsl-display-view-quality-kernel.js";
|
|
38
|
+
import { DISPLAY_VIEW_QUALITY_AUX, DISPLAY_VIEW_QUALITY_BITMAP_WORDS, DISPLAY_VIEW_QUALITY_CANDIDATE_ENTRIES, DISPLAY_VIEW_QUALITY_CANDIDATE_FLOATS, DISPLAY_VIEW_QUALITY_FLAG, DISPLAY_VIEW_QUALITY_FLAG_WORDS, DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE, DISPLAY_VIEW_QUALITY_GROUP_WORDS, DISPLAY_VIEW_QUALITY_MAXIMUM_NODES, DISPLAY_VIEW_QUALITY_MAXIMUM_PIXELS_PER_AXIS, DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS, DISPLAY_VIEW_QUALITY_PARAM_WORDS, DISPLAY_VIEW_QUALITY_PIXELS_WGSL, DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL, DISPLAY_VIEW_QUALITY_SITES_WGSL, DISPLAY_VIEW_QUALITY_SITE_FLOATS, DISPLAY_VIEW_QUALITY_TAP_WORDS, DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE, assembleDisplayViewQualityLatticeWgsl } from "./wgsl-display-view-quality-kernel.js";
|
|
40
39
|
/** One WGSL rounding of + − × in either direction (W3C WGSL "Floating Point Accuracy"; #471, #487): twice the binary32 unit roundoff. */
|
|
41
40
|
const E = 2 * WEBGPU_F32_UNIT_ROUNDOFF;
|
|
42
41
|
/** Every reported bound is doubled: the kernel's running bounds are first order, and their own float32 arithmetic rounds. */
|
|
@@ -62,7 +61,7 @@ export const DISPLAY_VIEW_QUALITY_MAXIMUM_KERNEL_RADIUS = 96;
|
|
|
62
61
|
* several invocations per site is needed.
|
|
63
62
|
*/
|
|
64
63
|
export const DISPLAY_VIEW_QUALITY_SITE_TAPS_PER_DISPATCH = 2 ** 30;
|
|
65
|
-
/** The smallest positive normal float32:
|
|
64
|
+
/** The smallest positive normal float32: values below it may be flushed to zero by a device (WGSL allows it). */
|
|
66
65
|
const FLOAT32_MINIMUM_NORMAL = 2 ** -126;
|
|
67
66
|
/** Candidate table entries per phase: the (2·reach + 1)² window. */
|
|
68
67
|
const WINDOW_SLOTS = (2 * MAXIMUM_REACH + 1) ** 2;
|
|
@@ -154,7 +153,7 @@ function exactLatticeConstant(c, span) {
|
|
|
154
153
|
export function displayViewQualityGpuUnsupportedReason(setup, signalInput) {
|
|
155
154
|
if (signalInput.largeKernel)
|
|
156
155
|
return LARGE_DEFOCUS_REASON;
|
|
157
|
-
if (signalInput.scene.kind !== "materials")
|
|
156
|
+
if (signalInput.scene.kind !== "materials" && !signalInput.scene.image)
|
|
158
157
|
return "radiance scenes (caller functions) are not on the GPU";
|
|
159
158
|
if (!signalInput.kernels && !signalInput.queryPoint)
|
|
160
159
|
return "the expected signal has no query points";
|
|
@@ -164,6 +163,8 @@ export function displayViewQualityGpuUnsupportedReason(setup, signalInput) {
|
|
|
164
163
|
return "unsupported tone curve " + String(setup.toneCurve);
|
|
165
164
|
if (setup.region.width < 1 || setup.region.height < 1)
|
|
166
165
|
return "an empty region";
|
|
166
|
+
if (signalInput.wavelengthsNm.length < 1 || signalInput.wavelengthsNm.length > DISPLAY_VIEW_QUALITY_MAXIMUM_NODES)
|
|
167
|
+
return "the site passes integrate 1 to " + DISPLAY_VIEW_QUALITY_MAXIMUM_NODES + " spectral nodes per site";
|
|
167
168
|
if (signalInput.kernels && (signalInput.kernels.radius < 0 || signalInput.kernels.radius > DISPLAY_VIEW_QUALITY_MAXIMUM_KERNEL_RADIUS))
|
|
168
169
|
return "a site kernel radius above " + DISPLAY_VIEW_QUALITY_MAXIMUM_KERNEL_RADIUS + " (the diffraction and defocus caps)";
|
|
169
170
|
const shape = footprintShape(setup);
|
|
@@ -209,9 +210,9 @@ export function displayViewQualityGpuUnsupportedReason(setup, signalInput) {
|
|
|
209
210
|
}
|
|
210
211
|
return null;
|
|
211
212
|
}
|
|
212
|
-
function signalGather(signalInput
|
|
213
|
-
const tables = displayViewSignalTables(signalInput),
|
|
214
|
-
const W = signalInput.nativeWidth, H = signalInput.nativeHeight, R = signalInput.kernels ? signalInput.kernels.radius : 0, materials =
|
|
213
|
+
function signalGather(signalInput) {
|
|
214
|
+
const tables = displayViewSignalTables(signalInput), A = signalInput.electronsPerRadiance, N = signalInput.wavelengthsNm.length, spectra = tables.spectra;
|
|
215
|
+
const W = signalInput.nativeWidth, H = signalInput.nativeHeight, R = signalInput.kernels ? signalInput.kernels.radius : 0, materials = spectra.length;
|
|
215
216
|
// Taps in order: per group, with site kernels the dense rectangle row-major (tap (dx, dy) reads lattice offset (minIx − R + dx,
|
|
216
217
|
// minIy − R + dy)), without them one tap per member. Plain loops: a radius-96 kernel has up to 150 544 taps.
|
|
217
218
|
const tapGroup = [], tapDp = [], tapDq = [], tapIndex = [];
|
|
@@ -239,7 +240,6 @@ function signalGather(signalInput, factored) {
|
|
|
239
240
|
groups.push({ dpMin, dqMin, dpSpan: dpMax - dpMin, dqSpan: dqMax - dqMin, cu: 0.5 + group.fx - W / 2, cv: 0.5 + group.fy - H / 2, firstTap: first,
|
|
240
241
|
cw: signalInput.kernels ? group.cw : 0, ch: signalInput.kernels ? group.ch : 0 });
|
|
241
242
|
});
|
|
242
|
-
const direct = (m, c) => { const si = tables.spectralIndex[m]; return si < 0 ? tables.reflectance[m] * tables.neutralDirect[c] : tables.spectralDirect[c][si]; };
|
|
243
243
|
const T = tapGroup.length, taps = new Uint32Array(T * DISPLAY_VIEW_QUALITY_TAP_WORDS), spread = new Float32Array(3 * T);
|
|
244
244
|
for (let t = 0; t < T; t++) {
|
|
245
245
|
const g = tapGroup[t], group = groups[g];
|
|
@@ -247,91 +247,64 @@ function signalGather(signalInput, factored) {
|
|
|
247
247
|
taps[t * DISPLAY_VIEW_QUALITY_TAP_WORDS + 1] = tapDp[t] - group.dpMin;
|
|
248
248
|
taps[t * DISPLAY_VIEW_QUALITY_TAP_WORDS + 2] = tapDq[t] - group.dqMin;
|
|
249
249
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const r = check(tables.reflectance[m]);
|
|
266
|
-
materialRows[2 * m] = 0;
|
|
267
|
-
reflectanceBits[2 * m + 1] = r;
|
|
268
|
-
neutralLow = Math.min(neutralLow, r);
|
|
269
|
-
neutralHigh = Math.max(neutralHigh, r);
|
|
270
|
-
if (r > 0)
|
|
271
|
-
neutralSmallest = Math.min(neutralSmallest, r);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
materialRows[2 * m] = 1 + si;
|
|
275
|
-
reflectanceBits[2 * m + 1] = 1;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
for (let t = 0; t < T; t++) {
|
|
279
|
-
const source = tables.groups[tapGroup[t]], index = tapIndex[t];
|
|
280
|
-
for (let c = 0; c < 3; c++) {
|
|
281
|
-
let low = Infinity, high = -Infinity;
|
|
282
|
-
const white = check(source.neutral[c][index]);
|
|
283
|
-
table[c * T + t] = white;
|
|
284
|
-
if (neutralHigh >= neutralLow) {
|
|
285
|
-
low = neutralLow * white;
|
|
286
|
-
high = neutralHigh * white;
|
|
287
|
-
// A neutral term r·N below the float32 normal range may be flushed: at most min(r_max·N, 2⁻¹²⁶).
|
|
288
|
-
if (neutralSmallest * white < FLOAT32_MINIMUM_NORMAL && white > 0)
|
|
289
|
-
tiny[t] = Math.max(tiny[t], Math.min(neutralHigh * white, FLOAT32_MINIMUM_NORMAL));
|
|
290
|
-
}
|
|
291
|
-
for (let si = 0; si < spectralCount; si++) {
|
|
292
|
-
const value = check(source.spectral[c][si][index]);
|
|
293
|
-
table[((1 + si) * 3 + c) * T + t] = value;
|
|
294
|
-
low = Math.min(low, value);
|
|
295
|
-
high = Math.max(high, value);
|
|
296
|
-
if (value > 0 && value < FLOAT32_MINIMUM_NORMAL)
|
|
297
|
-
tiny[t] = Math.max(tiny[t], value);
|
|
298
|
-
}
|
|
299
|
-
spread[c * T + t] = Math.fround((high - low) * (1 + 4 * E));
|
|
300
|
-
}
|
|
250
|
+
// Every value finite and non-negative, every spectrum value normal (a device may flush the others) and every sum in range.
|
|
251
|
+
const domain = () => { throw new ImageKernelExecutionError("unsupported-float32-domain", "A site's spectral terms are outside the float32 domain."); };
|
|
252
|
+
// The spectra and their envelope per node.
|
|
253
|
+
const base = 3 * T * N, table = new Float32Array(base + materials * N), low = new Float64Array(N).fill(Infinity), high = new Float64Array(N).fill(-Infinity);
|
|
254
|
+
let smallestL = Infinity;
|
|
255
|
+
spectra.forEach((L, m) => {
|
|
256
|
+
for (let n = 0; n < N; n++) {
|
|
257
|
+
const l = L[n];
|
|
258
|
+
if (!(l >= 0 && l <= FLOAT32_SAFE_MAGNITUDE))
|
|
259
|
+
domain();
|
|
260
|
+
table[base + m * N + n] = l;
|
|
261
|
+
low[n] = Math.min(low[n], l);
|
|
262
|
+
high[n] = Math.max(high[n], l);
|
|
263
|
+
if (l > 0)
|
|
264
|
+
smallestL = Math.min(smallestL, l);
|
|
301
265
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
const table = new Float32Array(materials * 3 * T);
|
|
266
|
+
});
|
|
267
|
+
if (smallestL < FLOAT32_MINIMUM_NORMAL)
|
|
268
|
+
domain();
|
|
269
|
+
let largestSum = 0, tinyMass = (T + 2) * N * FLOAT32_MINIMUM_NORMAL;
|
|
308
270
|
for (let t = 0; t < T; t++) {
|
|
309
271
|
const source = tables.groups[tapGroup[t]], index = tapIndex[t];
|
|
272
|
+
let tiny = 0;
|
|
310
273
|
for (let c = 0; c < 3; c++) {
|
|
311
|
-
let
|
|
312
|
-
for (let
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
274
|
+
let range = 0, mass = 0, tinyHere = 0;
|
|
275
|
+
for (let n = 0; n < N; n++) {
|
|
276
|
+
const value = signalInput.kernels ? source.nodeKernels[c][index * N + n] : source.members[index].weight * A[c][n];
|
|
277
|
+
if (!(value >= 0 && value <= FLOAT32_SAFE_MAGNITUDE))
|
|
278
|
+
domain();
|
|
279
|
+
table[(c * T + t) * N + n] = value;
|
|
280
|
+
if (materials > 0) {
|
|
281
|
+
const top = value * high[n];
|
|
282
|
+
range += value * (high[n] - low[n]);
|
|
283
|
+
mass += top;
|
|
284
|
+
// A kernel value or product below the normal range may be flushed: at most the product with the largest spectrum.
|
|
285
|
+
if (value > 0 && (value < FLOAT32_MINIMUM_NORMAL || top < 2 * FLOAT32_MINIMUM_NORMAL))
|
|
286
|
+
tinyHere += top;
|
|
287
|
+
}
|
|
320
288
|
}
|
|
321
|
-
// The spread rounded up (
|
|
322
|
-
spread[c * T + t] = Math.fround(
|
|
289
|
+
// The spread rounded up (the float32 roundings of the operands, the products and the sum).
|
|
290
|
+
spread[c * T + t] = Math.fround(range * (1 + (N + 4) * E));
|
|
291
|
+
largestSum = Math.max(largestSum, mass);
|
|
292
|
+
tiny = Math.max(tiny, tinyHere);
|
|
323
293
|
}
|
|
294
|
+
tinyMass += tiny;
|
|
324
295
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
return { groups, taps, tapCount: T, table, materialRows: null, spread, materials, tinyMass };
|
|
296
|
+
if (!(largestSum * T <= FLOAT32_SAFE_MAGNITUDE))
|
|
297
|
+
domain();
|
|
298
|
+
return { groups, taps, tapCount: T, nodes: N, table, spread, materials, tinyMass };
|
|
329
299
|
}
|
|
330
300
|
/**
|
|
331
301
|
* The relative bound of a site's electrons (`params.site_gamma`): (T + 3)·E for the tap-list sum (#479), and with the blocked sum
|
|
332
|
-
* (#492) (cw_max + ch_max + G + 3)·E
|
|
333
|
-
*
|
|
334
|
-
*
|
|
302
|
+
* (#492) (cw_max + ch_max + G + 3)·E, both unchanged by 2.21.0. Every term C_c(t, n)·L_m(λ_n) is non-negative and carries two host
|
|
303
|
+
* roundings (u = E/2 each: the kernel value and the spectrum) and its product (E); the tap-list pass adds it into its node's
|
|
304
|
+
* partial over the T taps (at most T − 1 inexact additions), the blocked pass into a row, group and total partial per node (at
|
|
305
|
+
* most cw − 1 + ch − 1 + G − 1). The N node partials and the dark electrons (host-rounded, E/2) are then summed in integer
|
|
306
|
+
* arithmetic and rounded once (E, `EXACT_SUM_WGSL`). So a site carries at most (T + 2.5)·E, or (cw + ch + G + 0.5)·E, inside the
|
|
307
|
+
* bounds (docs/WEBGPU.md).
|
|
335
308
|
*/
|
|
336
309
|
function siteSumRelativeBound(gather, blocked) {
|
|
337
310
|
if (!blocked)
|
|
@@ -406,8 +379,7 @@ function resolveUncertainLattice(lattice, groupWords, signalInput) {
|
|
|
406
379
|
if ((raw >>> 31) === 0)
|
|
407
380
|
continue;
|
|
408
381
|
const p = lx0 + (local % lw), q = ly0 + Math.floor(local / lw);
|
|
409
|
-
const
|
|
410
|
-
const m = scene.materialAt(u, v);
|
|
382
|
+
const m = scene.materialAt(latticeCoordinate(p, group.fx, W, g), -latticeCoordinate(q, group.fy, H, g));
|
|
411
383
|
if (!(Number.isInteger(m) && m >= 0 && m < scene.materials.length))
|
|
412
384
|
throw new ImageKernelExecutionError("unsupported-on-backend", "webgpu-f32 display view (Quality): a lattice point has no scene material; use cpu-reference.");
|
|
413
385
|
lattice[first + local] = m;
|
|
@@ -416,6 +388,11 @@ function resolveUncertainLattice(lattice, groupWords, signalInput) {
|
|
|
416
388
|
});
|
|
417
389
|
return resolved;
|
|
418
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* One axis of the reference's lattice point (createDisplayViewExpectedSignal's latticeU / latticeV, written as it writes them):
|
|
393
|
+
* (i + 0.5 − size/2)·g with no shift, (i + 0.5 + f − size/2)·g otherwise; v is its negative.
|
|
394
|
+
*/
|
|
395
|
+
export const latticeCoordinate = (i, f, size, g) => (f === 0 ? i + 0.5 - size / 2 : i + 0.5 + f - size / 2) * g;
|
|
419
396
|
/** The uniform parameters shared by the three passes (per-pass words are set per dispatch). */
|
|
420
397
|
function constants(setup, signalInput, gather, reach, count, blocked) {
|
|
421
398
|
const params = new ArrayBuffer(4 * DISPLAY_VIEW_QUALITY_PARAM_WORDS), words = new Uint32Array(params), ints = new Int32Array(params), floats = new Float32Array(params);
|
|
@@ -458,9 +435,10 @@ function constants(setup, signalInput, gather, reach, count, blocked) {
|
|
|
458
435
|
floats[49] = geometry.scaleV;
|
|
459
436
|
}
|
|
460
437
|
floats[50] = siteSumRelativeBound(gather, blocked);
|
|
461
|
-
//
|
|
462
|
-
|
|
463
|
-
|
|
438
|
+
// 2.21.0: the spectral nodes the site passes integrate.
|
|
439
|
+
words[53] = gather.nodes;
|
|
440
|
+
// What a flushing device can drop (both site passes), rounded up and kept normal so the uniform is not flushed either.
|
|
441
|
+
floats[52] = Math.max(FLOAT32_MINIMUM_NORMAL, Math.fround(gather.tinyMass * (1 + 4 * E)));
|
|
464
442
|
return { params, words, ints, floats };
|
|
465
443
|
}
|
|
466
444
|
/** The reference's correction setup of a display-view setup with the AHD correction. */
|
|
@@ -479,7 +457,7 @@ function quantizationBound(moments, o, meanBound, pixels, step) {
|
|
|
479
457
|
}
|
|
480
458
|
/** Renders one region of a Quality display view on the GPU (see the module comment). */
|
|
481
459
|
export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, abort, yieldControl, pipelines) {
|
|
482
|
-
const { setup, signalInput, material } = input;
|
|
460
|
+
const { setup, signalInput, material, image } = input;
|
|
483
461
|
const reason = displayViewQualityGpuUnsupportedReason(setup, signalInput);
|
|
484
462
|
if (reason)
|
|
485
463
|
throw new ImageKernelExecutionError("unsupported-on-backend", "webgpu-f32 display view (Quality): " + reason + "; use cpu-reference.");
|
|
@@ -487,11 +465,16 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
|
|
|
487
465
|
const timings = { lattice: 0, sites: 0, pixels: 0, states: 0, finishing: 0, recompute: 0, pixelRedispatches: 0, siteDispatches: 0 };
|
|
488
466
|
const { region, margin } = setup, shape = footprintShape(setup), { reach, count } = candidatesOf(setup);
|
|
489
467
|
// #492: the defocus disc's kernels are summed in blocks (their dense rectangles), in focus the tap list runs as before.
|
|
490
|
-
const blocked = input.blockedSum ?? (setup.gpu?.defocusRadius ?? 0) > 0;
|
|
491
|
-
if (blocked && !signalInput.kernels)
|
|
468
|
+
const blocked = !!image || (input.blockedSum ?? (setup.gpu?.defocusRadius ?? 0) > 0);
|
|
469
|
+
if (blocked && !signalInput.kernels && !image)
|
|
492
470
|
throw new ImageKernelExecutionError("unsupported-on-backend", "webgpu-f32 display view (Quality): the blocked site sum needs site kernels; use cpu-reference.");
|
|
493
471
|
const tapBudget = input.maximumSiteTapsPerDispatch ?? DISPLAY_VIEW_QUALITY_SITE_TAPS_PER_DISPATCH;
|
|
494
|
-
const gather = signalGather(signalInput
|
|
472
|
+
const gather = image?.gather ?? signalGather(signalInput), aux = auxTable(setup), { params, words, ints, floats } = constants(setup, signalInput, gather, reach, count, blocked);
|
|
473
|
+
// #581: the print's site bound and its radiance values per lattice point (the site pass's node count in the last word).
|
|
474
|
+
if (image) {
|
|
475
|
+
floats[50] = image.siteGamma;
|
|
476
|
+
words[55] = image.pointFloats;
|
|
477
|
+
}
|
|
495
478
|
const stride = DISPLAY_VIEW_QUALITY_GEOMETRY_STRIDE, geometry = new Float32Array((region.width + region.height) * stride);
|
|
496
479
|
const columnSums = [], rowSums = [];
|
|
497
480
|
for (let i = 0; i < region.width; i++) {
|
|
@@ -512,7 +495,7 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
|
|
|
512
495
|
const { y0, y1 } = siteRows(0, rows), siteH = y1 - y0 + 1;
|
|
513
496
|
let lattice = 0;
|
|
514
497
|
for (const g of gather.groups)
|
|
515
|
-
lattice += (siteW + g.dpSpan) * (siteH + g.dqSpan) * 4;
|
|
498
|
+
lattice += (siteW + g.dpSpan) * (siteH + g.dqSpan) * 4 * (image ? image.pointFloats + 1 : 1);
|
|
516
499
|
return lattice + siteW * siteH * SITE_BYTES + rows * region.width * OUTPUT_BYTES_PER_PIXEL;
|
|
517
500
|
};
|
|
518
501
|
let rows = region.height;
|
|
@@ -521,14 +504,13 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
|
|
|
521
504
|
// The value table (materials × 3 channels × taps) is one binding: with a large defocus kernel and many materials it can pass the
|
|
522
505
|
// device's limit, which is then refused by name (never clipped).
|
|
523
506
|
if (gather.table.byteLength > runtime.maximumStorageBindingBytes)
|
|
524
|
-
throw new ImageKernelExecutionError("insufficient-limits", "The display view's kernel table (" +
|
|
525
|
-
|
|
526
|
-
gather.tapCount + " taps per site, " + gather.table.byteLength + " bytes) exceeds the device's storage binding limit of " + runtime.maximumStorageBindingBytes + " bytes.");
|
|
507
|
+
throw new ImageKernelExecutionError("insufficient-limits", "The display view's kernel table (" + gather.table.byteLength +
|
|
508
|
+
" bytes: 3 channels × " + gather.tapCount + " taps × " + gather.nodes + " spectral nodes, and the material spectra) exceeds the device's storage binding limit of " + runtime.maximumStorageBindingBytes + " bytes.");
|
|
527
509
|
const largestBinding = Math.max(bandBytesOf(rows), geometry.byteLength, gather.table.byteLength, 4 * DISPLAY_VIEW_QUALITY_BITMAP_WORDS);
|
|
528
510
|
if (largestBinding > runtime.maximumStorageBindingBytes)
|
|
529
511
|
throw new ImageKernelExecutionError("insufficient-limits", "One display-view band exceeds the device's storage binding limit.");
|
|
530
|
-
const latticePipeline = await pipelines("photivra-display-view-quality-lattice:" + material.id, assembleDisplayViewQualityLatticeWgsl(material.wgsl));
|
|
531
|
-
const sitesPipeline = blocked ? await pipelines("photivra-display-view-quality-sites-blocked", DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL) :
|
|
512
|
+
const latticePipeline = image ? await pipelines(...image.lattice) : await pipelines("photivra-display-view-quality-lattice:" + material.id, assembleDisplayViewQualityLatticeWgsl(material.wgsl));
|
|
513
|
+
const sitesPipeline = image ? await pipelines(...image.sites) : blocked ? await pipelines("photivra-display-view-quality-sites-blocked", DISPLAY_VIEW_QUALITY_SITES_BLOCKED_WGSL) :
|
|
532
514
|
await pipelines("photivra-display-view-quality-sites", DISPLAY_VIEW_QUALITY_SITES_WGSL);
|
|
533
515
|
const pixelsPipeline = await pipelines("photivra-display-view-quality-pixels", DISPLAY_VIEW_QUALITY_PIXELS_WGSL);
|
|
534
516
|
const moments = new Float32Array(pixelCount * DISPLAY_VIEW_QUALITY_OUTPUT_FLOATS), pixelFlags = new Uint32Array(pixelCount), masks = new Uint32Array(2 * pixelCount);
|
|
@@ -542,11 +524,9 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
|
|
|
542
524
|
const tapsBuffer = blocked ? null : scope.upload("photivra-display-view-taps", gather.taps, BUFFER_USAGE.STORAGE);
|
|
543
525
|
const tableBuffer = scope.upload("photivra-display-view-table", gather.table, BUFFER_USAGE.STORAGE);
|
|
544
526
|
const spreadBuffer = scope.upload("photivra-display-view-spread", gather.spread, BUFFER_USAGE.STORAGE);
|
|
545
|
-
const materialRowsBuffer = gather.materialRows ? scope.upload("photivra-display-view-material-rows", gather.materialRows, BUFFER_USAGE.STORAGE) : null;
|
|
546
527
|
// A module's binding is data uploaded per render or a buffer the module already holds on the device (#491: scene records).
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
const materialGroup = runtime.device.createBindGroup({ layout: latticePipeline.getBindGroupLayout(1), entries: materialEntries });
|
|
528
|
+
const materialGroup = image ? null : runtime.device.createBindGroup({ layout: latticePipeline.getBindGroupLayout(1), entries: material.bindings.map((b) => ({ binding: b.binding, resource: { buffer: b.buffer ??
|
|
529
|
+
scope.upload("photivra-display-view-material-" + b.binding, b.data, b.kind === "uniform" ? BUFFER_USAGE.UNIFORM : BUFFER_USAGE.STORAGE) } })) });
|
|
550
530
|
const gate = createHostYieldGate();
|
|
551
531
|
for (let row0 = 0; row0 < region.height; row0 += rows) {
|
|
552
532
|
checkAbort(abort.signal);
|
|
@@ -576,26 +556,35 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
|
|
|
576
556
|
const band = new GpuResourceScope(runtime);
|
|
577
557
|
try {
|
|
578
558
|
const groupBuffer = band.upload("photivra-display-view-groups", groupWords, BUFFER_USAGE.STORAGE);
|
|
579
|
-
let latticeBuffer = band.buffer("photivra-display-view-lattice", latticeTotal * 4, BUFFER_USAGE.STORAGE | (blocked ? BUFFER_USAGE.COPY_SRC : 0));
|
|
580
559
|
const siteBuffer = band.buffer("photivra-display-view-sites", siteW * siteH * SITE_BYTES, BUFFER_USAGE.STORAGE);
|
|
581
|
-
// Pass 1: lattice materials.
|
|
560
|
+
// Pass 1: lattice materials (#581: an image-plane print's radiance per node, with its flags, and their host resolution).
|
|
582
561
|
let t0 = now();
|
|
583
562
|
words[0] = latticeTotal;
|
|
584
563
|
const latticeParams = band.upload("photivra-display-view-params-lattice", new Uint8Array(params.slice(0)), BUFFER_USAGE.UNIFORM);
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
564
|
+
let latticeBuffer, pointFlags = null;
|
|
565
|
+
if (image) {
|
|
566
|
+
const pass = await image.runLattice(band, latticePipeline, latticeParams, groupBuffer, groupWords, latticeTotal, abort.signal);
|
|
567
|
+
latticeBuffer = pass.values;
|
|
568
|
+
pointFlags = pass.flags;
|
|
569
|
+
latticePointsResolved += pass.resolved;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
latticeBuffer = band.buffer("photivra-display-view-lattice", latticeTotal * 4, BUFFER_USAGE.STORAGE | (blocked ? BUFFER_USAGE.COPY_SRC : 0));
|
|
573
|
+
const latticeGroup = runtime.device.createBindGroup({ layout: latticePipeline.getBindGroupLayout(0), entries: [
|
|
574
|
+
{ binding: 0, resource: { buffer: latticeParams } }, { binding: 1, resource: { buffer: groupBuffer } }, { binding: 2, resource: { buffer: latticeBuffer } }
|
|
575
|
+
] });
|
|
576
|
+
const [latticeData] = await runComputePass(runtime, band, latticePipeline, [latticeGroup, materialGroup], linearDispatch(latticeTotal, DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE, runtime.maximumWorkgroupsPerDimension), blocked ? [{ buffer: latticeBuffer, bytes: latticeTotal * 4 }] : [], abort.signal);
|
|
577
|
+
await queueIdle(runtime, abort.signal);
|
|
578
|
+
if (blocked) {
|
|
579
|
+
// #492: with a wide disc nearly every site reads some uncertain lattice point, and its spread over every material would flag
|
|
580
|
+
// it. The host resolves each uncertain point with the reference's own material lookup at the reference's lattice point
|
|
581
|
+
// (createDisplayViewExpectedSignal's latticeU/latticeV), so the sites read exactly the reference's materials.
|
|
582
|
+
const lattice = new Uint32Array(latticeData, 0, latticeTotal);
|
|
583
|
+
const resolved = resolveUncertainLattice(lattice, groupWords, signalInput);
|
|
584
|
+
if (resolved > 0)
|
|
585
|
+
latticeBuffer = band.upload("photivra-display-view-lattice-resolved", lattice, BUFFER_USAGE.STORAGE);
|
|
586
|
+
latticePointsResolved += resolved;
|
|
587
|
+
}
|
|
599
588
|
}
|
|
600
589
|
timings.lattice += now() - t0;
|
|
601
590
|
// Pass 2: site moments, in chunks of whole workgroups of sites whose taps stay within the budget per dispatch (#492).
|
|
@@ -610,8 +599,7 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
|
|
|
610
599
|
const siteParams = chunk.upload("photivra-display-view-params-sites", new Uint8Array(params.slice(0)), BUFFER_USAGE.UNIFORM);
|
|
611
600
|
const siteGroup = runtime.device.createBindGroup({ layout: sitesPipeline.getBindGroupLayout(0), entries: [
|
|
612
601
|
{ binding: 0, resource: { buffer: siteParams } }, { binding: 1, resource: { buffer: groupBuffer } }, ...(tapsBuffer ? [{ binding: 2, resource: { buffer: tapsBuffer } }] : []),
|
|
613
|
-
{ binding: 3, resource: { buffer: tableBuffer } }, { binding: 4, resource: { buffer: latticeBuffer } }, { binding: 5, resource: { buffer: siteBuffer } }, { binding: 6, resource: { buffer: spreadBuffer } },
|
|
614
|
-
...(materialRowsBuffer ? [{ binding: 7, resource: { buffer: materialRowsBuffer } }] : [])
|
|
602
|
+
{ binding: 3, resource: { buffer: tableBuffer } }, { binding: 4, resource: { buffer: latticeBuffer } }, { binding: 5, resource: { buffer: siteBuffer } }, { binding: 6, resource: { buffer: pointFlags ?? spreadBuffer } },
|
|
615
603
|
] });
|
|
616
604
|
await runComputePass(runtime, chunk, sitesPipeline, siteGroup, linearDispatch(words[0], DISPLAY_VIEW_QUALITY_WORKGROUP_SIZE, runtime.maximumWorkgroupsPerDimension), [], abort.signal);
|
|
617
605
|
await queueIdle(runtime, abort.signal);
|
|
@@ -754,22 +742,11 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
|
|
|
754
742
|
for (let i = 0; i < pixelCount; i++)
|
|
755
743
|
if (isFlagged[i] === 0 && (pixelFlags[i] & DISPLAY_VIEW_QUALITY_FLAG.highlightUndecided) !== 0)
|
|
756
744
|
undecided.push(i);
|
|
757
|
-
if (
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
let run = 1;
|
|
763
|
-
while (r + run < undecided.length && undecided[r + run] === first + run && rx + run < region.width)
|
|
764
|
-
run++;
|
|
765
|
-
const part = await renderDisplayView({ ...setup, signal, region: { x: region.x + rx, y: region.y + ry, width: run, height: 1 } });
|
|
766
|
-
for (let k = 0; k < run; k++) {
|
|
767
|
-
saturated[first + k] = part.saturated[k];
|
|
768
|
-
highlights.push(first + k);
|
|
769
|
-
}
|
|
770
|
-
r += run;
|
|
771
|
-
}
|
|
772
|
-
}
|
|
745
|
+
if (input.recomputeFlagged !== false)
|
|
746
|
+
await recomputeRuns(setup, signalInput, undecided, abort, (first, run, part) => { for (let k = 0; k < run; k++) {
|
|
747
|
+
saturated[first + k] = part.saturated[k];
|
|
748
|
+
highlights.push(first + k);
|
|
749
|
+
} });
|
|
773
750
|
if (flagged.length > 0) {
|
|
774
751
|
if (input.recomputeFlagged === false) {
|
|
775
752
|
// Without recomputation a flagged pixel keeps the kernel's values where they are finite (its bound then does not hold).
|
|
@@ -787,18 +764,9 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
|
|
|
787
764
|
varianceBound[i] = MARGIN * moments[o + 10] + quantizationBound(moments, o, meanBound[i], pixels, step);
|
|
788
765
|
}
|
|
789
766
|
}
|
|
790
|
-
else
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
// edges); a region render is byte-identical to the same pixels of the whole view, so each pixel equals its one-pixel
|
|
794
|
-
// render, and the run's counts are the sums of its pixels' counts.
|
|
795
|
-
for (let r = 0; r < flagged.length;) {
|
|
796
|
-
checkAbort(abort.signal);
|
|
797
|
-
const first = flagged[r], rx = first % region.width, ry = (first - rx) / region.width;
|
|
798
|
-
let run = 1;
|
|
799
|
-
while (r + run < flagged.length && flagged[r + run] === first + run && rx + run < region.width)
|
|
800
|
-
run++;
|
|
801
|
-
const part = await renderDisplayView({ ...setup, signal, region: { x: region.x + rx, y: region.y + ry, width: run, height: 1 } });
|
|
767
|
+
else
|
|
768
|
+
await recomputeRuns(setup, signalInput, flagged, abort, (first, run, part) => {
|
|
769
|
+
// The run's counts are the sums of its pixels' counts.
|
|
802
770
|
samples.set(part.samples, first * 3);
|
|
803
771
|
expected.set(part.expected, first * 3);
|
|
804
772
|
noiseStd.set(part.noiseStd, first * 3);
|
|
@@ -811,9 +779,7 @@ export async function renderDisplayViewQualityOnGpu(runtime, bandBytes, input, a
|
|
|
811
779
|
corrected += part.ahdCorrectedPixelCount;
|
|
812
780
|
sampleCount += part.sampleCount;
|
|
813
781
|
siteCount += part.siteCount;
|
|
814
|
-
|
|
815
|
-
}
|
|
816
|
-
}
|
|
782
|
+
});
|
|
817
783
|
}
|
|
818
784
|
timings.recompute = now() - t0;
|
|
819
785
|
const relative = MARGIN * CPU_RELATIVE;
|
|
@@ -845,8 +811,8 @@ export function qualityMaterialSetup(gpu) {
|
|
|
845
811
|
const offsets = groups.map((group) => ({ x: group.fx, y: group.fy, weight: 1 / groups.length }));
|
|
846
812
|
const queryPoint = (x, y, o, out) => {
|
|
847
813
|
const group = groups[o];
|
|
848
|
-
out[0] = (
|
|
849
|
-
out[1] = -(
|
|
814
|
+
out[0] = latticeCoordinate(x, group.fx, W, g);
|
|
815
|
+
out[1] = -latticeCoordinate(y, group.fy, H, g);
|
|
850
816
|
};
|
|
851
817
|
const { kernels: _kernels, ...rest } = input;
|
|
852
818
|
void _kernels;
|
|
@@ -867,13 +833,7 @@ export function offsetCount(request) { return request.boxSamples * request.boxSa
|
|
|
867
833
|
* table fits the device's storage binding limit, is known only per render.
|
|
868
834
|
*/
|
|
869
835
|
export function displayViewQualityGpuSupports(request) {
|
|
870
|
-
|
|
871
|
-
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)";
|
|
872
|
-
if (request.toneCurve !== "identity" && request.toneCurve !== "positive-reinhard-per-channel")
|
|
873
|
-
return "unsupported tone curve " + String(request.toneCurve);
|
|
874
|
-
if (offsetCount(request) > MAXIMUM_GROUPS)
|
|
875
|
-
return "more than " + MAXIMUM_GROUPS + " query offsets per site";
|
|
876
|
-
return true;
|
|
836
|
+
return sceneToneAndOffsetsReason(request, MAXIMUM_GROUPS);
|
|
877
837
|
}
|
|
878
838
|
/** A normalized, symmetric site kernel of radius R for the self-check (a binned Gaussian of width `sigma` sites). */
|
|
879
839
|
function syntheticKernel(radius, sigma) {
|
|
@@ -902,59 +862,28 @@ function syntheticKernel(radius, sigma) {
|
|
|
902
862
|
* each band's site pass over several dispatches.
|
|
903
863
|
*/
|
|
904
864
|
async function runQualityConformance(handle, signal, defocus) {
|
|
905
|
-
const runtime = handle;
|
|
906
|
-
const nativeWidth = 40, nativeHeight = 28, margin = 1, frameWidth = 38, frameHeight = 26, nodes = [450, 550, 650];
|
|
907
|
-
const chart = resolvePortableChartScene({ scene: { preset: "checkerboard", squaresAcross: 6 }, aspect: frameHeight / frameWidth });
|
|
865
|
+
const runtime = handle, { nativeWidth, nativeHeight, frameWidth, nodes } = CONFORMANCE_FRAME, { chart, scene } = conformanceChart();
|
|
908
866
|
const offsets = [];
|
|
909
867
|
for (const [nx, ny] of [[-0.25, -0.25], [0.25, -0.25], [-0.25, 0.25], [0.25, 0.25]])
|
|
910
868
|
for (const [ax, ay] of [[-0.5, -0.5], [0.5, -0.5], [-0.5, 0.5], [0.5, 0.5]])
|
|
911
869
|
offsets.push({ x: nx - ax, y: ny - ay, weight: 1 / 16 });
|
|
912
870
|
const disc = defocus ? createDefocusDiscSiteKernel(6.3) : null;
|
|
913
871
|
const kernels = disc ? { radius: disc.radius, perNode: nodes.map(() => disc.weights) } : { radius: 1, perNode: [syntheticKernel(1, 0.6), syntheticKernel(1, 0.7), syntheticKernel(1, 0.8)] };
|
|
914
|
-
const signalInput = { nativeWidth, nativeHeight, outputWidth: frameWidth, wavelengthsNm: nodes,
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
channelAt: (x, y) => ((y & 1) === 0 ? ((x & 1) === 0 ? 0 : 1) : ((x & 1) === 0 ? 1 : 2)) };
|
|
919
|
-
const readout = { perElectron: defocus ? 1 / 40000 : 1 / 4000, darkElectrons: 0.07, constantVariance: 2.5e-7 };
|
|
920
|
-
const bilinear = bilinearKernels();
|
|
921
|
-
const setup = { frameWidth, frameHeight, margin, nativeWidth, nativeHeight, viewWidth: 13, viewHeight: 9, region: { x: 0, y: 0, width: 13, height: 9 },
|
|
922
|
-
samplesPerAxis: 16, reachSites: 5 + (disc ? disc.radius : 0), bitDepth: 8, seed: 479, signal: createDisplayViewExpectedSignal(signalInput), readout: [readout, readout, readout], demosaic: bilinear,
|
|
923
|
-
...(defocus ? {} : { candidates: [0, 1, 2, 3].map((p) => ahdCandidateKernels(p % 2, Math.floor(p / 2))),
|
|
924
|
-
ahdCorrection: { labMatrix: [[0.4124, 0.3576, 0.1805], [0.2126, 0.7152, 0.0722], [0.0193, 0.1192, 0.9505]].map((row) => row.map((v, c) => v * [1.1, 1, 1.6][c])), drawsPerState: 64, thresholdVariance: 1e-4 } }),
|
|
925
|
-
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",
|
|
926
|
-
yieldControl: async () => undefined, checkAbort: () => { if (signal?.aborted)
|
|
927
|
-
throw abortedError(); } };
|
|
872
|
+
const signalInput = { nativeWidth, nativeHeight, outputWidth: frameWidth, wavelengthsNm: nodes, electronsPerRadiance: conformanceResponses(defocus ? 1 : 0.1),
|
|
873
|
+
offsets, kernels, scene, channelAt: conformanceChannelAt };
|
|
874
|
+
const setup = conformanceSetup(signalInput, { perElectron: defocus ? 1 / 40000 : 1 / 4000, darkElectrons: 0.07, constantVariance: 2.5e-7 }, 479, 5 + (disc ? disc.radius : 0), signal, defocus ? {} : { candidates: [0, 1, 2, 3].map((p) => ahdCandidateKernels(p % 2, Math.floor(p / 2))),
|
|
875
|
+
ahdCorrection: { labMatrix: [[0.4124, 0.3576, 0.1805], [0.2126, 0.7152, 0.0722], [0.0193, 0.1192, 0.9505]].map((row) => row.map((v, c) => v * [1.1, 1, 1.6][c])), drawsPerState: 64, thresholdVariance: 1e-4 } });
|
|
928
876
|
const reference = await renderDisplayView(setup);
|
|
929
|
-
|
|
930
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
931
|
-
try {
|
|
932
|
-
if (signal?.aborted)
|
|
933
|
-
abort.abort();
|
|
877
|
+
return await withConformanceAbort(runtime, signal, async (abort, pipelines) => {
|
|
934
878
|
const gather = defocus ? { blockedSum: true, maximumSiteTapsPerDispatch: 64 * 4 * (2 * 3 + 2) ** 2 } : {};
|
|
935
|
-
const gpu = await renderDisplayViewQualityOnGpu(runtime, 1 << 20, { setup, signalInput, material: chartPresetMaterialModule(chart), ...gather }, abort, undefined,
|
|
879
|
+
const gpu = await renderDisplayViewQualityOnGpu(runtime, 1 << 20, { setup, signalInput, material: chartPresetMaterialModule(chart), ...gather }, abort, undefined, pipelines);
|
|
936
880
|
if (!defocus && (gpu.ahdCorrectedPixelCount === 0 || reference.ahdCorrectedPixelCount === 0))
|
|
937
881
|
return "the self-check did not exercise the low-signal correction.";
|
|
938
882
|
if (defocus && (!gpu.blockedSum || gpu.stageMilliseconds.siteDispatches < 2))
|
|
939
883
|
return "the self-check did not exercise the blocked site sum over several dispatches.";
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
if (!(Math.abs(e1 - e0) <= expectedLimit))
|
|
944
|
-
return "component " + i + ": expected value " + e1 + " differs from the reference " + e0 + " by more than the a-priori bound " + expectedLimit + ".";
|
|
945
|
-
if (!(Math.abs(v1 - v0) <= varianceLimit))
|
|
946
|
-
return "component " + i + ": variance " + v1 + " differs from the reference " + v0 + " by more than the a-priori bound " + varianceLimit + ".";
|
|
947
|
-
if (Math.abs(gpu.samples[i] - reference.samples[i]) > 1)
|
|
948
|
-
return "component " + i + ": sample " + gpu.samples[i] + " differs from the reference " + reference.samples[i] + " by more than one code.";
|
|
949
|
-
}
|
|
950
|
-
// #533: the saturated-pixel count is the reference's, bit for bit (the light squares sit at the white clip).
|
|
951
|
-
if (gpu.saturatedPixelCount !== reference.saturatedPixelCount)
|
|
952
|
-
return "the saturated-pixel count " + gpu.saturatedPixelCount + " differs from the reference's " + reference.saturatedPixelCount + ".";
|
|
953
|
-
return true;
|
|
954
|
-
}
|
|
955
|
-
finally {
|
|
956
|
-
signal?.removeEventListener("abort", onAbort);
|
|
957
|
-
}
|
|
884
|
+
// #533: the verdict includes the saturated-pixel count (the light squares sit at the white clip).
|
|
885
|
+
return conformanceVerdict(reference, gpu);
|
|
886
|
+
});
|
|
958
887
|
}
|
|
959
888
|
/** #479's self-check (see `runQualityConformance`). */
|
|
960
889
|
export const DISPLAY_VIEW_QUALITY_CONFORMANCE_CASE = { id: "display-view:quality:checkerboard-13x9-diffraction-ahd",
|
|
@@ -1026,14 +955,16 @@ export function createDisplayViewGpuRenderer(runtime, options = {}) {
|
|
|
1026
955
|
result = await renderDisplayViewSpeedOnGpu(runtime, bandBytes, { setup, signalInput, material }, abort, options.yieldControl, source);
|
|
1027
956
|
}
|
|
1028
957
|
else {
|
|
1029
|
-
|
|
958
|
+
// #581: an image-plane print brings its own passes 1 and 2.
|
|
959
|
+
const image = signalInput.scene.image ? await modules.imagePasses(setup.gpu, abort.signal) : undefined;
|
|
960
|
+
material = image ? { id: "image-plane", wgsl: "", bindings: [] } : await qualityMaterialModule(modules, setup.gpu, abort.signal);
|
|
1030
961
|
const key = displayViewCorrectionKey(setup);
|
|
1031
962
|
let states = key === null ? undefined : stores.get(key);
|
|
1032
963
|
if (key !== null && !states) {
|
|
1033
964
|
states = new CorrectionStateStore();
|
|
1034
965
|
stores.set(key, states);
|
|
1035
966
|
}
|
|
1036
|
-
result = await renderDisplayViewQualityOnGpu(runtime, bandBytes, { setup, signalInput, material, ...(states ? { states } : {}) }, abort, options.yieldControl, source);
|
|
967
|
+
result = await renderDisplayViewQualityOnGpu(runtime, bandBytes, { setup, signalInput, material, ...(image ? { image } : {}), ...(states ? { states } : {}) }, abort, options.yieldControl, source);
|
|
1037
968
|
}
|
|
1038
969
|
return { samples: result.samples, expected: result.expected, noiseStd: result.noiseStd, sampleCount: result.sampleCount, siteCount: result.siteCount, detailPixelCount: result.detailPixelCount,
|
|
1039
970
|
uniformPixelCount: result.uniformPixelCount, ahdCorrectedPixelCount: result.ahdCorrectedPixelCount, estimation: result.estimation, saturated: result.saturated,
|