@photivra/engine 2.16.0 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +56 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +24 -0
  6. package/dist/camera/lens-projection.d.ts +5 -0
  7. package/dist/camera/lens-projection.js +10 -0
  8. package/dist/camera/lens-projection.js.map +1 -1
  9. package/dist/core/version.d.ts +1 -1
  10. package/dist/core/version.js +1 -1
  11. package/dist/core/version.js.map +1 -1
  12. package/dist/equipment/generic-lens-catalogue.d.ts +227 -0
  13. package/dist/equipment/generic-lens-catalogue.js +243 -0
  14. package/dist/equipment/generic-lens-catalogue.js.map +1 -0
  15. package/dist/facade/defocus-bokeh-zones.d.ts +33 -0
  16. package/dist/facade/defocus-bokeh-zones.js +53 -0
  17. package/dist/facade/defocus-bokeh-zones.js.map +1 -0
  18. package/dist/facade/defocus-image-space-provider.d.ts +9 -0
  19. package/dist/facade/defocus-image-space-provider.js +96 -7
  20. package/dist/facade/defocus-image-space-provider.js.map +1 -1
  21. package/dist/facade/diffraction-provider.d.ts +29 -0
  22. package/dist/facade/diffraction-provider.js +117 -11
  23. package/dist/facade/diffraction-provider.js.map +1 -1
  24. package/dist/facade/display-view-gpu-session.js +4 -0
  25. package/dist/facade/display-view-gpu-session.js.map +1 -1
  26. package/dist/facade/display-view-gpu.d.ts +10 -0
  27. package/dist/facade/display-view-gpu.js.map +1 -1
  28. package/dist/facade/display-view-lens-signal.d.ts +30 -0
  29. package/dist/facade/display-view-lens-signal.js +427 -0
  30. package/dist/facade/display-view-lens-signal.js.map +1 -0
  31. package/dist/facade/expected-signal-setup.d.ts +7 -0
  32. package/dist/facade/expected-signal-setup.js +12 -2
  33. package/dist/facade/expected-signal-setup.js.map +1 -1
  34. package/dist/facade/expected-signal.d.ts +8 -0
  35. package/dist/facade/expected-signal.js +150 -0
  36. package/dist/facade/expected-signal.js.map +1 -1
  37. package/dist/facade/generic-lens-capture.d.ts +6 -0
  38. package/dist/facade/generic-lens-capture.js +8 -0
  39. package/dist/facade/generic-lens-capture.js.map +1 -0
  40. package/dist/facade/large-defocus-lattice.d.ts +39 -0
  41. package/dist/facade/large-defocus-lattice.js +64 -0
  42. package/dist/facade/large-defocus-lattice.js.map +1 -0
  43. package/dist/facade/scene-radiance.d.ts +5 -0
  44. package/dist/facade/scene-radiance.js +5 -2
  45. package/dist/facade/scene-radiance.js.map +1 -1
  46. package/dist/facade/simulate-capture.d.ts +38 -5
  47. package/dist/facade/simulate-capture.js +306 -49
  48. package/dist/facade/simulate-capture.js.map +1 -1
  49. package/dist/facade/simulate-display-view.js +22 -16
  50. package/dist/facade/simulate-display-view.js.map +1 -1
  51. package/dist/gpu/display-view-quality-gpu.d.ts +0 -9
  52. package/dist/gpu/display-view-quality-gpu.js +6 -0
  53. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  54. package/dist/gpu/display-view-speed-gpu.js +2 -0
  55. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  56. package/dist/index.d.ts +3 -0
  57. package/dist/index.js +3 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/optics/defocus-bokeh-kernel.d.ts +207 -0
  60. package/dist/optics/defocus-bokeh-kernel.js +380 -0
  61. package/dist/optics/defocus-bokeh-kernel.js.map +1 -0
  62. package/dist/optics/defocus-disc-kernel.d.ts +41 -19
  63. package/dist/optics/defocus-disc-kernel.js +44 -23
  64. package/dist/optics/defocus-disc-kernel.js.map +1 -1
  65. package/dist/optics/generic-lens-psf.d.ts +68 -0
  66. package/dist/optics/generic-lens-psf.js +337 -0
  67. package/dist/optics/generic-lens-psf.js.map +1 -0
  68. package/dist/optics/illumination-vignetting.d.ts +6 -0
  69. package/dist/optics/illumination-vignetting.js +24 -7
  70. package/dist/optics/illumination-vignetting.js.map +1 -1
  71. package/dist/optics/sensor-environment-query.d.ts +6 -0
  72. package/dist/optics/sensor-environment-query.js +13 -6
  73. package/dist/optics/sensor-environment-query.js.map +1 -1
  74. package/dist/optics/site-kernel-runs.d.ts +77 -0
  75. package/dist/optics/site-kernel-runs.js +200 -0
  76. package/dist/optics/site-kernel-runs.js.map +1 -0
  77. package/dist/output/generic-lens-corrections.d.ts +24 -0
  78. package/dist/output/generic-lens-corrections.js +51 -0
  79. package/dist/output/generic-lens-corrections.js.map +1 -0
  80. package/docs/AGENT_GUIDE.md +1 -0
  81. package/docs/API_REFERENCE.md +49 -1
  82. package/docs/QUICK_START.md +257 -17
  83. package/docs/USAGE.md +57 -5
  84. package/docs/api/core-version.md +1 -1
  85. package/docs/api/equipment-generic-lens-catalogue.md +281 -0
  86. package/docs/api/exports.json +137 -2
  87. package/docs/api/facade-simulate-capture.md +23 -2
  88. package/docs/api/optics-defocus-bokeh-kernel.md +145 -0
  89. package/docs/api/optics-defocus-disc-kernel.md +22 -15
  90. package/docs/api/optics-sensor-environment-query.md +6 -0
  91. package/docs/api/output-generic-lens-corrections.md +39 -0
  92. package/llms.txt +1 -0
  93. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"display-view-gpu.js","sourceRoot":"","sources":["../../src/facade/display-view-gpu.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #481: how a prepared display view chooses between a WebGPU preview renderer and the CPU reference, and how a render labels\n * which ran. This module holds the option, the label and the renderer contract and imports nothing from the GPU code, so a\n * bundle that never asks for the GPU (`gpu: \"off\"`, the default) does not contain it; the device handling lives in\n * `display-view-gpu-session.ts`, loaded on demand by `prepareDisplayView` when `gpu` is `\"auto\"` or `\"require\"`.\n *\n * The vocabulary is the image-kernel accelerator's (`createImageKernelAccelerator`, docs/WEBGPU.md): `\"auto\"` is\n * `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is\n * `backend: \"cpu-reference\"`. The render's `diagnostics.fast.execution` names the backend that ran, the fallback reason and the\n * bound in the fields of `ImageKernelExecution`, plus the adapter and the run-time conformance check.\n *\n * No GPU display-view renderer exists yet (#478, #479): until one registers itself through {@link setDisplayViewGpuRendererFactory},\n * `\"auto\"` runs the CPU reference labelled `unsupported-on-backend` without touching a device, and `\"require\"` fails with that reason.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport type { ChartCompositeProbeRecord, ImageKernelAdapterDescription, ImageKernelBackend, ImageKernelExecution, ImageKernelFallbackReason } from \"../gpu/image-kernel-execution.js\";\nimport type { DisplayViewRenderResult, DisplayViewSetup } from \"./display-view.js\";\nimport type { SimulateCaptureFastReport } from \"./simulate-capture.js\";\n\n/**\n * 2.11 (#481): whether a prepared display view may render on the GPU. `\"off\"` (the default) is the CPU reference, today's\n * behaviour byte for byte. `\"auto\"` renders on WebGPU when a conforming device is available and the request is supported, and\n * otherwise runs the CPU reference and says why. `\"require\"` fails instead of falling back. In the accelerator's words: `\"auto\"`\n * is `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is `backend: \"cpu-reference\"`.\n */\nexport type DisplayViewGpuMode = \"off\" | \"auto\" | \"require\";\n\n/** 2.11 (#481): the second argument of `prepareDisplayView`. */\nexport interface PrepareDisplayViewOptions {\n /** See {@link DisplayViewGpuMode}. Default `\"off\"`. */\n gpu?: DisplayViewGpuMode;\n /**\n * Where the device comes from, with `gpu: \"auto\"` or `\"require\"`: a WebGPU entry point (a worker's `navigator.gpu`), or an\n * `ImageKernelAccelerator` whose device the view shares (the accelerator keeps ownership; `dispose()` of the prepared view\n * leaves it alone). Without it the engine acquires a device from `globalThis.navigator.gpu` and owns it until `dispose()`.\n * Refused with `gpu: \"off\"`.\n */\n gpuDevice?: unknown;\n}\n\n/** The parsed form of {@link PrepareDisplayViewOptions}. @internal */\nexport interface ParsedPrepareDisplayViewOptions { gpu: DisplayViewGpuMode; gpuDevice: unknown }\n\n/** Internal: validates the second argument of `prepareDisplayView`. */\nexport function parsePrepareDisplayViewOptions(value: unknown): ParsedPrepareDisplayViewOptions {\n if (value === undefined) return { gpu: \"off\", gpuDevice: undefined };\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) throw new InvalidConfigurationError(\"prepareDisplayView options must be an object { gpu, gpuDevice }.\");\n for (const key of Object.keys(value)) if (key !== \"gpu\" && key !== \"gpuDevice\") throw new InvalidConfigurationError(\"Unknown prepareDisplayView options field: \" + key + \".\");\n const { gpu, gpuDevice } = value as Record<string, unknown>;\n const mode: DisplayViewGpuMode = gpu === undefined ? \"off\" : gpu as DisplayViewGpuMode;\n if (mode !== \"off\" && mode !== \"auto\" && mode !== \"require\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpu must be \\\"off\\\", \\\"auto\\\" or \\\"require\\\".\");\n if (gpuDevice !== undefined && (typeof gpuDevice !== \"object\" || gpuDevice === null)) throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice must be a WebGPU entry point (navigator.gpu) or an ImageKernelAccelerator.\");\n if (gpuDevice !== undefined && mode === \"off\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice needs gpu \\\"auto\\\" or \\\"require\\\".\");\n return { gpu: mode, gpuDevice };\n}\n\n/**\n * 2.11 (#481): why a display view that asked for the GPU (`gpu: \"auto\"`) ran the CPU reference. The accelerator's acquisition\n * reasons, plus: `unsupported-on-backend` (no GPU display-view renderer exists in this version, or it does not support the\n * request), `conformance-failed` (the device failed the run-time self-check before the view's first GPU render), `device-lost`\n * (the device was lost; the rest of the view runs on the CPU) and `disposed` (the prepared view was disposed).\n */\nexport type DisplayViewGpuFallbackReason = ImageKernelFallbackReason | \"unsupported-on-backend\" | \"conformance-failed\" | \"device-lost\" | \"disposed\";\n\n/** 2.11 (#481): the run-time conformance self-check of a device, run once before the view's first GPU render. */\nexport interface DisplayViewGpuConformance {\n /** The ids of the cases run, in order (the built-in image-kernel cases, then the renderer's own). */\n cases: readonly string[];\n passed: boolean;\n /** The first case that failed, or null. */\n failedCase: string | null;\n /** What the failed case reported, or null. */\n detail: string | null;\n}\n\n/**\n * 2.11 (#481): where a display view's render ran, in the words of the image-kernel execution record (`ImageKernelExecution`):\n * `backend` names the path that ran (`cpu-reference`, the float64 engine reference, or `webgpu-f32`, a float32 approximation\n * within `errorBound`), `requestedBackend` what `gpu` asked for, and `fallbackReason` why a request for the GPU ran the CPU.\n * `adapter` describes the device's adapter whenever one was involved, also when the view then fell back; `conformance` is\n * the device's self-check once it has run. A CPU render keeps `assurance: \"engine-reference\"` and `fast: false`.\n */\nexport interface DisplayViewExecution extends Omit<ImageKernelExecution, \"fallbackReason\"> {\n /** The `gpu` option the view was prepared with. */\n gpu: DisplayViewGpuMode;\n fallbackReason: DisplayViewGpuFallbackReason | null;\n adapter: ImageKernelAdapterDescription | null;\n conformance: DisplayViewGpuConformance | null;\n /**\n * 2.11: the composite chart kernel's device probe when this render needed it (composite charts, the 2.3 elements and\n * `material-layers` scenes): the device class and the product path the kernel ran with, or, on a refusal (`fallbackReason`\n * `unsupported-on-backend`), why. Null when the render did not run the probe (the 2.0 presets, `gpu: \"off\"`).\n */\n compositeProbe: ChartCompositeProbeRecord | null;\n}\n\n/** 2.11 (#481): `diagnostics.fast` of a display view: the capture's report plus where the render ran. */\nexport interface DisplayViewFastReport extends SimulateCaptureFastReport {\n /** Where this render ran (see {@link DisplayViewExecution}). `assurance` above follows it. */\n execution: DisplayViewExecution;\n}\n\nconst CPU_LIMITATIONS = /* @__PURE__ */ Object.freeze([\"Float64 engine reference execution; results are the reference by definition\"]);\n\n/** Internal: the execution record of a display view rendered on the CPU reference. `requested` names what `gpu` asked for. */\nexport function cpuDisplayViewExecution(gpu: DisplayViewGpuMode, fallbackReason: DisplayViewGpuFallbackReason | null, adapter: ImageKernelAdapterDescription | null,\n conformance: DisplayViewGpuConformance | null, detail?: string, compositeProbe: ChartCompositeProbeRecord | null = null): DisplayViewExecution {\n const requestedBackend: ImageKernelBackend = gpu === \"off\" ? \"cpu-reference\" : \"webgpu-f32\";\n const limitations = detail === undefined ? CPU_LIMITATIONS : Object.freeze([...CPU_LIMITATIONS, \"WebGPU was requested (gpu: \\\"\" + gpu + \"\\\") and the CPU reference ran: \" + detail]);\n return Object.freeze({ gpu, backend: \"cpu-reference\", requestedBackend, fallbackReason, arithmetic: \"float64\", assurance: \"engine-reference\", fast: false,\n bitIdenticalToReference: true, errorBound: null, diagnosticsExact: true, diagnosticUncertaintySampleCount: 0, limitations, adapter, conformance, compositeProbe });\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// The renderer contract (#478 implements it; until then the only implementation is a test fake).\n\n/**\n * Internal (#481): the device a renderer or conformance case runs on. It is the engine's `WebGpuRuntime` object\n * (`src/gpu/webgpu-runtime.ts`: `device`, `guard`, `destroy` and the rest), seen here through its WebGPU-free members only so\n * that no WebGPU type reaches the published declarations (docs/WEBGPU.md); a renderer in the engine casts it to `WebGpuRuntime`.\n */\nexport interface DisplayViewGpuRuntimeHandle {\n readonly adapter: ImageKernelAdapterDescription | null;\n readonly maximumStorageBindingBytes: number;\n readonly maximumWorkgroupsPerDimension: number;\n isLost(): boolean;\n}\n\n/** What a prepared request asks of a GPU renderer (`supports`): the options that shape a render. */\nexport interface DisplayViewGpuRequest {\n ahd: boolean;\n diffraction: boolean;\n antiAliasing: boolean;\n /** Pixel-aperture samples per axis (1: centre sampling). */\n boxSamples: number;\n toneCurve: \"identity\" | \"positive-reinhard-per-channel\";\n /**\n * #492: true when `lens.defocus` needs the defocus disc (out of focus): the expected signal then carries the disc, or each\n * node's diffraction kernel convolved with it, as site kernels (#492 step 2b), which the engine's renderer gathers in its\n * Quality site pass (step 2c). Absent or false otherwise.\n */\n defocus?: boolean;\n /** The scene's form: an engine chart, a declared `material-layers` scene (#491), or a caller function. */\n scene: \"chart\" | \"material-layers\" | \"material-function\" | \"reflectance-function\" | \"radiance-function\";\n}\n/** One render of a region: the CPU renderer's own setup (tables, readout, demosaic, region, callbacks) and the render's signal. */\nexport interface DisplayViewGpuPart { setup: DisplayViewSetup; signal: AbortSignal | undefined }\n/** What a GPU render returns: the CPU renderer's result shape plus the a-priori bound of its values against the reference. */\nexport interface DisplayViewGpuRenderOutput extends DisplayViewRenderResult {\n errorBound: NonNullable<ImageKernelExecution[\"errorBound\"]>;\n /** Samples whose float32 classification fell inside its bound (0 when none). */\n diagnosticUncertaintySampleCount?: number;\n /** The composite chart kernel's device probe record, when the render used the composite material module. */\n compositeProbe?: ChartCompositeProbeRecord;\n}\n/** A conformance case: returns `true` when the device agrees with the reference within the case's bound, or a sentence saying how it did not. */\nexport interface DisplayViewGpuConformanceCase { id: string; run(runtime: DisplayViewGpuRuntimeHandle, signal: AbortSignal | undefined): Promise<true | string> }\n/**\n * Internal (#481): the contract a GPU display-view renderer fulfils (#478). One instance serves one device, created by the\n * registered factory for each prepared view that acquires or shares a device. `supports` says whether the renderer covers a\n * prepared request (`true`) or why not (a sentence; the view then runs the CPU reference labelled `unsupported-on-backend`).\n * `render` renders one region on the device and must throw `ImageKernelExecutionError` with reason `device-lost` or `aborted`\n * for those two events, as the accelerator's operations do; a render that throws `unsupported-on-backend` (a request the\n * renderer finds it does not cover only when rendering) runs the CPU reference labelled with that reason, or fails with it under\n * `\"require\"`. `conformanceCases` are run after the built-in image-kernel cases before the device's first render; a failure keeps\n * the whole view on the CPU. `dispose` releases what the renderer holds on the device (#491: uploaded scene records); the session\n * calls it when the prepared view is disposed.\n */\nexport interface DisplayViewGpuRenderer {\n readonly id: string;\n supports(request: DisplayViewGpuRequest): true | string;\n render(part: DisplayViewGpuPart): Promise<DisplayViewGpuRenderOutput>;\n readonly conformanceCases?: readonly DisplayViewGpuConformanceCase[];\n dispose?(): void;\n}\nexport type DisplayViewGpuRendererFactory = (runtime: DisplayViewGpuRuntimeHandle) => DisplayViewGpuRenderer;\n\nlet rendererFactory: DisplayViewGpuRendererFactory | null = null;\n/**\n * Internal (#481): registers the renderer that `prepareDisplayView` uses for `gpu: \"auto\" | \"require\"`, and returns the one\n * registered before (null: none). #478's renderer registers itself here; tests register a fake. With none registered no device\n * is requested and every GPU request falls back (or fails) with `unsupported-on-backend`.\n */\nexport function setDisplayViewGpuRendererFactory(factory: DisplayViewGpuRendererFactory | null): DisplayViewGpuRendererFactory | null {\n if (factory !== null && typeof factory !== \"function\") throw new InvalidConfigurationError(\"setDisplayViewGpuRendererFactory expects a function or null.\");\n const previous = rendererFactory;\n rendererFactory = factory;\n return previous;\n}\n/** Internal: the registered factory. */\nexport function displayViewGpuRendererFactory(): DisplayViewGpuRendererFactory | null { return rendererFactory; }\n"]}
1
+ {"version":3,"file":"display-view-gpu.js","sourceRoot":"","sources":["../../src/facade/display-view-gpu.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #481: how a prepared display view chooses between a WebGPU preview renderer and the CPU reference, and how a render labels\n * which ran. This module holds the option, the label and the renderer contract and imports nothing from the GPU code, so a\n * bundle that never asks for the GPU (`gpu: \"off\"`, the default) does not contain it; the device handling lives in\n * `display-view-gpu-session.ts`, loaded on demand by `prepareDisplayView` when `gpu` is `\"auto\"` or `\"require\"`.\n *\n * The vocabulary is the image-kernel accelerator's (`createImageKernelAccelerator`, docs/WEBGPU.md): `\"auto\"` is\n * `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is\n * `backend: \"cpu-reference\"`. The render's `diagnostics.fast.execution` names the backend that ran, the fallback reason and the\n * bound in the fields of `ImageKernelExecution`, plus the adapter and the run-time conformance check.\n *\n * No GPU display-view renderer exists yet (#478, #479): until one registers itself through {@link setDisplayViewGpuRendererFactory},\n * `\"auto\"` runs the CPU reference labelled `unsupported-on-backend` without touching a device, and `\"require\"` fails with that reason.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport type { ChartCompositeProbeRecord, ImageKernelAdapterDescription, ImageKernelBackend, ImageKernelExecution, ImageKernelFallbackReason } from \"../gpu/image-kernel-execution.js\";\nimport type { DisplayViewRenderResult, DisplayViewSetup } from \"./display-view.js\";\nimport type { SimulateCaptureFastReport } from \"./simulate-capture.js\";\n\n/**\n * 2.11 (#481): whether a prepared display view may render on the GPU. `\"off\"` (the default) is the CPU reference, today's\n * behaviour byte for byte. `\"auto\"` renders on WebGPU when a conforming device is available and the request is supported, and\n * otherwise runs the CPU reference and says why. `\"require\"` fails instead of falling back. In the accelerator's words: `\"auto\"`\n * is `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is `backend: \"cpu-reference\"`.\n */\nexport type DisplayViewGpuMode = \"off\" | \"auto\" | \"require\";\n\n/** 2.11 (#481): the second argument of `prepareDisplayView`. */\nexport interface PrepareDisplayViewOptions {\n /** See {@link DisplayViewGpuMode}. Default `\"off\"`. */\n gpu?: DisplayViewGpuMode;\n /**\n * Where the device comes from, with `gpu: \"auto\"` or `\"require\"`: a WebGPU entry point (a worker's `navigator.gpu`), or an\n * `ImageKernelAccelerator` whose device the view shares (the accelerator keeps ownership; `dispose()` of the prepared view\n * leaves it alone). Without it the engine acquires a device from `globalThis.navigator.gpu` and owns it until `dispose()`.\n * Refused with `gpu: \"off\"`.\n */\n gpuDevice?: unknown;\n}\n\n/** The parsed form of {@link PrepareDisplayViewOptions}. @internal */\nexport interface ParsedPrepareDisplayViewOptions { gpu: DisplayViewGpuMode; gpuDevice: unknown }\n\n/** Internal: validates the second argument of `prepareDisplayView`. */\nexport function parsePrepareDisplayViewOptions(value: unknown): ParsedPrepareDisplayViewOptions {\n if (value === undefined) return { gpu: \"off\", gpuDevice: undefined };\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) throw new InvalidConfigurationError(\"prepareDisplayView options must be an object { gpu, gpuDevice }.\");\n for (const key of Object.keys(value)) if (key !== \"gpu\" && key !== \"gpuDevice\") throw new InvalidConfigurationError(\"Unknown prepareDisplayView options field: \" + key + \".\");\n const { gpu, gpuDevice } = value as Record<string, unknown>;\n const mode: DisplayViewGpuMode = gpu === undefined ? \"off\" : gpu as DisplayViewGpuMode;\n if (mode !== \"off\" && mode !== \"auto\" && mode !== \"require\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpu must be \\\"off\\\", \\\"auto\\\" or \\\"require\\\".\");\n if (gpuDevice !== undefined && (typeof gpuDevice !== \"object\" || gpuDevice === null)) throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice must be a WebGPU entry point (navigator.gpu) or an ImageKernelAccelerator.\");\n if (gpuDevice !== undefined && mode === \"off\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice needs gpu \\\"auto\\\" or \\\"require\\\".\");\n return { gpu: mode, gpuDevice };\n}\n\n/**\n * 2.11 (#481): why a display view that asked for the GPU (`gpu: \"auto\"`) ran the CPU reference. The accelerator's acquisition\n * reasons, plus: `unsupported-on-backend` (no GPU display-view renderer exists in this version, or it does not support the\n * request), `conformance-failed` (the device failed the run-time self-check before the view's first GPU render), `device-lost`\n * (the device was lost; the rest of the view runs on the CPU) and `disposed` (the prepared view was disposed).\n */\nexport type DisplayViewGpuFallbackReason = ImageKernelFallbackReason | \"unsupported-on-backend\" | \"conformance-failed\" | \"device-lost\" | \"disposed\";\n\n/** 2.11 (#481): the run-time conformance self-check of a device, run once before the view's first GPU render. */\nexport interface DisplayViewGpuConformance {\n /** The ids of the cases run, in order (the built-in image-kernel cases, then the renderer's own). */\n cases: readonly string[];\n passed: boolean;\n /** The first case that failed, or null. */\n failedCase: string | null;\n /** What the failed case reported, or null. */\n detail: string | null;\n}\n\n/**\n * 2.11 (#481): where a display view's render ran, in the words of the image-kernel execution record (`ImageKernelExecution`):\n * `backend` names the path that ran (`cpu-reference`, the float64 engine reference, or `webgpu-f32`, a float32 approximation\n * within `errorBound`), `requestedBackend` what `gpu` asked for, and `fallbackReason` why a request for the GPU ran the CPU.\n * `adapter` describes the device's adapter whenever one was involved, also when the view then fell back; `conformance` is\n * the device's self-check once it has run. A CPU render keeps `assurance: \"engine-reference\"` and `fast: false`.\n */\nexport interface DisplayViewExecution extends Omit<ImageKernelExecution, \"fallbackReason\"> {\n /** The `gpu` option the view was prepared with. */\n gpu: DisplayViewGpuMode;\n fallbackReason: DisplayViewGpuFallbackReason | null;\n adapter: ImageKernelAdapterDescription | null;\n conformance: DisplayViewGpuConformance | null;\n /**\n * 2.11: the composite chart kernel's device probe when this render needed it (composite charts, the 2.3 elements and\n * `material-layers` scenes): the device class and the product path the kernel ran with, or, on a refusal (`fallbackReason`\n * `unsupported-on-backend`), why. Null when the render did not run the probe (the 2.0 presets, `gpu: \"off\"`).\n */\n compositeProbe: ChartCompositeProbeRecord | null;\n}\n\n/** 2.11 (#481): `diagnostics.fast` of a display view: the capture's report plus where the render ran. */\nexport interface DisplayViewFastReport extends SimulateCaptureFastReport {\n /** Where this render ran (see {@link DisplayViewExecution}). `assurance` above follows it. */\n execution: DisplayViewExecution;\n}\n\nconst CPU_LIMITATIONS = /* @__PURE__ */ Object.freeze([\"Float64 engine reference execution; results are the reference by definition\"]);\n\n/** Internal: the execution record of a display view rendered on the CPU reference. `requested` names what `gpu` asked for. */\nexport function cpuDisplayViewExecution(gpu: DisplayViewGpuMode, fallbackReason: DisplayViewGpuFallbackReason | null, adapter: ImageKernelAdapterDescription | null,\n conformance: DisplayViewGpuConformance | null, detail?: string, compositeProbe: ChartCompositeProbeRecord | null = null): DisplayViewExecution {\n const requestedBackend: ImageKernelBackend = gpu === \"off\" ? \"cpu-reference\" : \"webgpu-f32\";\n const limitations = detail === undefined ? CPU_LIMITATIONS : Object.freeze([...CPU_LIMITATIONS, \"WebGPU was requested (gpu: \\\"\" + gpu + \"\\\") and the CPU reference ran: \" + detail]);\n return Object.freeze({ gpu, backend: \"cpu-reference\", requestedBackend, fallbackReason, arithmetic: \"float64\", assurance: \"engine-reference\", fast: false,\n bitIdenticalToReference: true, errorBound: null, diagnosticsExact: true, diagnosticUncertaintySampleCount: 0, limitations, adapter, conformance, compositeProbe });\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// The renderer contract (#478 implements it; until then the only implementation is a test fake).\n\n/**\n * Internal (#481): the device a renderer or conformance case runs on. It is the engine's `WebGpuRuntime` object\n * (`src/gpu/webgpu-runtime.ts`: `device`, `guard`, `destroy` and the rest), seen here through its WebGPU-free members only so\n * that no WebGPU type reaches the published declarations (docs/WEBGPU.md); a renderer in the engine casts it to `WebGpuRuntime`.\n */\nexport interface DisplayViewGpuRuntimeHandle {\n readonly adapter: ImageKernelAdapterDescription | null;\n readonly maximumStorageBindingBytes: number;\n readonly maximumWorkgroupsPerDimension: number;\n isLost(): boolean;\n}\n\n/** What a prepared request asks of a GPU renderer (`supports`): the options that shape a render. */\nexport interface DisplayViewGpuRequest {\n ahd: boolean;\n diffraction: boolean;\n antiAliasing: boolean;\n /** Pixel-aperture samples per axis (1: centre sampling). */\n boxSamples: number;\n toneCurve: \"identity\" | \"positive-reinhard-per-channel\";\n /**\n * #492: true when `lens.defocus` needs the defocus disc (out of focus): the expected signal then carries the disc, or each\n * node's diffraction kernel convolved with it, as site kernels (#492 step 2b), which the engine's renderer gathers in its\n * Quality site pass (step 2c). Absent or false otherwise.\n */\n defocus?: boolean;\n /**\n * #564: true when the defocus kernel's radius is above 64 sites, so the expected signal convolves it by runs on the CPU\n * (`largeKernel`); no GPU renderer takes it. Absent or false otherwise.\n */\n largeDefocus?: boolean;\n /**\n * #563: true when the request has `lens.projection`, `lens.distortion`, `lens.vignetting` or camera rotation: the CPU reference\n * renders it (display-view-lens-signal.ts) and no GPU renderer is asked or device requested (`unsupported-on-backend`).\n */\n lens?: boolean;\n /** The scene's form: an engine chart, a declared `material-layers` scene (#491), or a caller function. */\n scene: \"chart\" | \"material-layers\" | \"material-function\" | \"reflectance-function\" | \"radiance-function\";\n}\n/** One render of a region: the CPU renderer's own setup (tables, readout, demosaic, region, callbacks) and the render's signal. */\nexport interface DisplayViewGpuPart { setup: DisplayViewSetup; signal: AbortSignal | undefined }\n/** What a GPU render returns: the CPU renderer's result shape plus the a-priori bound of its values against the reference. */\nexport interface DisplayViewGpuRenderOutput extends DisplayViewRenderResult {\n errorBound: NonNullable<ImageKernelExecution[\"errorBound\"]>;\n /** Samples whose float32 classification fell inside its bound (0 when none). */\n diagnosticUncertaintySampleCount?: number;\n /** The composite chart kernel's device probe record, when the render used the composite material module. */\n compositeProbe?: ChartCompositeProbeRecord;\n}\n/** A conformance case: returns `true` when the device agrees with the reference within the case's bound, or a sentence saying how it did not. */\nexport interface DisplayViewGpuConformanceCase { id: string; run(runtime: DisplayViewGpuRuntimeHandle, signal: AbortSignal | undefined): Promise<true | string> }\n/**\n * Internal (#481): the contract a GPU display-view renderer fulfils (#478). One instance serves one device, created by the\n * registered factory for each prepared view that acquires or shares a device. `supports` says whether the renderer covers a\n * prepared request (`true`) or why not (a sentence; the view then runs the CPU reference labelled `unsupported-on-backend`).\n * `render` renders one region on the device and must throw `ImageKernelExecutionError` with reason `device-lost` or `aborted`\n * for those two events, as the accelerator's operations do; a render that throws `unsupported-on-backend` (a request the\n * renderer finds it does not cover only when rendering) runs the CPU reference labelled with that reason, or fails with it under\n * `\"require\"`. `conformanceCases` are run after the built-in image-kernel cases before the device's first render; a failure keeps\n * the whole view on the CPU. `dispose` releases what the renderer holds on the device (#491: uploaded scene records); the session\n * calls it when the prepared view is disposed.\n */\nexport interface DisplayViewGpuRenderer {\n readonly id: string;\n supports(request: DisplayViewGpuRequest): true | string;\n render(part: DisplayViewGpuPart): Promise<DisplayViewGpuRenderOutput>;\n readonly conformanceCases?: readonly DisplayViewGpuConformanceCase[];\n dispose?(): void;\n}\nexport type DisplayViewGpuRendererFactory = (runtime: DisplayViewGpuRuntimeHandle) => DisplayViewGpuRenderer;\n\nlet rendererFactory: DisplayViewGpuRendererFactory | null = null;\n/**\n * Internal (#481): registers the renderer that `prepareDisplayView` uses for `gpu: \"auto\" | \"require\"`, and returns the one\n * registered before (null: none). #478's renderer registers itself here; tests register a fake. With none registered no device\n * is requested and every GPU request falls back (or fails) with `unsupported-on-backend`.\n */\nexport function setDisplayViewGpuRendererFactory(factory: DisplayViewGpuRendererFactory | null): DisplayViewGpuRendererFactory | null {\n if (factory !== null && typeof factory !== \"function\") throw new InvalidConfigurationError(\"setDisplayViewGpuRendererFactory expects a function or null.\");\n const previous = rendererFactory;\n rendererFactory = factory;\n return previous;\n}\n/** Internal: the registered factory. */\nexport function displayViewGpuRendererFactory(): DisplayViewGpuRendererFactory | null { return rendererFactory; }\n"]}
@@ -0,0 +1,30 @@
1
+ import type { PreparedDefocusImageSpaceGeometry } from "./defocus-image-space-provider.js";
2
+ import { type DisplayViewExpectedSignal, type DisplayViewExpectedSignalInput } from "./expected-signal.js";
3
+ /** What the lens signal needs from the resolved request. */
4
+ export interface DisplayViewLensSetup {
5
+ geometry: PreparedDefocusImageSpaceGeometry;
6
+ /** Temporal midpoints, seconds from exposure start: (i + 0.5)·shutter/T with camera rotation, [0] without. */
7
+ times: readonly number[];
8
+ /** The planar provider's plane distance and frame-unit scale. */
9
+ distanceM: number;
10
+ metresPerFrameUnit: number;
11
+ pitch: number;
12
+ imagingArea: {
13
+ widthMm: number;
14
+ heightMm: number;
15
+ };
16
+ boxSamples: number;
17
+ /** Anti-aliasing split spots (or one zero spot), in the order of the expected signal's offsets within each aperture node. */
18
+ spots: readonly {
19
+ offsetMicrometers: {
20
+ x: number;
21
+ y: number;
22
+ };
23
+ }[];
24
+ }
25
+ /**
26
+ * Checks that the mapping and the vignetting profile cover the farthest source point the view reads, (W/2 + reachSites, H/2 + reachSites)
27
+ * sites from the axis at each corner (reachSites: the kernel radius plus the largest query offset, less the half site to the outer site
28
+ * centre), and returns the factory of the view's expected signal for this request.
29
+ */
30
+ export declare function prepareDisplayViewLensSignal(lens: DisplayViewLensSetup, nativeWidth: number, nativeHeight: number, reachSites: number, options: string): (input: DisplayViewExpectedSignalInput, spanColumns?: number) => DisplayViewExpectedSignal;
@@ -0,0 +1,427 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * The display view's expected signal with a lens field mapping (`lens.projection`, `lens.distortion`), `lens.vignetting` or
4
+ * camera rotation (#563), in focus and with `lens.defocus` out of focus. Loaded on demand (a dynamic import in `runCapture`),
5
+ * so a display view without these options, and a bundle that never asks for them, does not load it.
6
+ *
7
+ * What it computes. The capture's environment chain forms, for site (i, j) of CFA channel c,
8
+ * μ_c(i, j) = Σ_o w_o · (1/T) Σ_t Σ_λ A_c(λ) Σ_k K_λ(k) · F(s) · L(D_t(s), λ), s = q_o(i, j) − k·p,
9
+ * with q_o the query point of offset o (pixel-aperture node minus anti-aliasing split), K_λ the site kernel (diffraction, the
10
+ * defocus disc or both; a single unit tap without them), D_t the scene ray of sensor point s at temporal midpoint t (inverse
11
+ * distortion, the declared projection, the camera rotation at t), F = T·A its field factor (vignetting throughput × distortion
12
+ * area factor) and A_c(λ) the electrons per unit spectral radiance on the axis (the display view's channel response, ideal
13
+ * optics). In focus with a kernel this is the environment PSF quadrature (the field factor applied once at the source point,
14
+ * before the PSF spreads the light); out of focus it is #548's image-space disc, the same sum in another order
15
+ * (`defocus-image-space-provider.ts`); without a kernel it is the environment's own per-query sum. Only the scene access changes:
16
+ * the readout, the estimators, the demosaic and the development run unchanged on these site values.
17
+ *
18
+ * Scene points. Without a kernel each query point is formed with the environment's own arithmetic (the site centre, aperture node
19
+ * and split offset of `native-environment-lean.ts`), then `lookOf` (the environment's mapping, rotation and field factor,
20
+ * #548) and the planar provider's plane-point arithmetic, so a point exactly on a chart boundary resolves as in the capture.
21
+ * With a kernel the sources are the site lattice of each fractional offset, with #548's lattice formula. Kernels, materials and
22
+ * per-material sums are the display view's own tables (`displayViewSignalTables`), shared with the rectilinear path.
23
+ *
24
+ * Caches (memory only, never values): site values in a ring of native rows across windows, and with a kernel each lattice
25
+ * point's material (or radiance at each node) and field factor per lattice group and temporal midpoint, in a two-dimensional
26
+ * ring tagged with the point, sized from the window and capped at LATTICE_CACHE_LIMIT entries (beyond it, points are
27
+ * evaluated as they are read).
28
+ *
29
+ * Large defocus kernels (#564, `input.largeKernel`, radius above 64 sites). The image-space disc D is the same at every lattice
30
+ * group, spectral node and temporal midpoint, so it commutes with those sums as in the rectilinear signal (`expected-signal.ts`):
31
+ * μ_c(x) = Σ_s D(s)·E_c(x − s), with E_c(x) the sum above through the in-focus kernels (each node's diffraction kernel, or a unit
32
+ * tap) at site position x for channel c, whatever x's own channel. E is computed in fixed 64-site tiles (all three channels, each
33
+ * group's lattice points mapped once per tile and midpoint), and D ⊛ E_c by runs (`site-kernel-runs.ts`) in fixed 128-site tiles,
34
+ * each site taking its own channel: every value depends only on its site, so regions and parts equal the whole view.
35
+ */
36
+ import { InvalidConfigurationError } from "../core/configuration-error.js";
37
+ import { createSiteKernelRunsLattice } from "../optics/site-kernel-runs.js";
38
+ import { displayViewSignalTables } from "./expected-signal.js";
39
+ /** Largest lattice cache, in entries over all lattice groups and temporal midpoints (times the spectral nodes for radiance scenes). */
40
+ const LATTICE_CACHE_LIMIT = 1 << 22;
41
+ const pow2 = (n) => { let p = 1; while (p < n)
42
+ p *= 2; return p; };
43
+ /**
44
+ * Checks that the mapping and the vignetting profile cover the farthest source point the view reads, (W/2 + reachSites, H/2 + reachSites)
45
+ * sites from the axis at each corner (reachSites: the kernel radius plus the largest query offset, less the half site to the outer site
46
+ * centre), and returns the factory of the view's expected signal for this request.
47
+ */
48
+ export function prepareDisplayViewLensSignal(lens, nativeWidth, nativeHeight, reachSites, options) {
49
+ const xMm = (nativeWidth / 2 + reachSites) * lens.pitch / 1000, yMm = (nativeHeight / 2 + reachSites) * lens.pitch / 1000;
50
+ for (const t of lens.times)
51
+ for (const [x, y] of [[-xMm, -yMm], [xMm, -yMm], [-xMm, yMm], [xMm, yMm]]) {
52
+ try {
53
+ if (!(lens.geometry.lookOf(x, y, t).z > 0))
54
+ throw new InvalidConfigurationError("its ray does not point toward the scene plane.");
55
+ }
56
+ catch (error) {
57
+ throw new InvalidConfigurationError("simulateDisplayView with " + options + " reads the scene through the lens up to " + (reachSites + 0.5) + " sites beyond the native " +
58
+ "raster's outer site centres (the kernel radius and the query offsets), and there (" + Math.hypot(x, y).toFixed(3) + " mm from the axis) " + error.message);
59
+ }
60
+ }
61
+ return (input, spanColumns = Infinity) => createLensSignal(lens, input, spanColumns);
62
+ }
63
+ function createLensSignal(lens, input, spanColumns) {
64
+ const W = input.nativeWidth, H = input.nativeHeight, nodes = input.wavelengthsNm.length, T = lens.times.length, times = lens.times;
65
+ const { geometry, distanceM: distance, metresPerFrameUnit: scale, pitch, imagingArea } = lens;
66
+ const mmPerSite = pitch / 1000, scene = input.scene, offsets = input.offsets, A = input.electronsPerRadiance, channelAt = input.channelAt;
67
+ const materials = scene.kind === "materials" ? scene : undefined, radianceAt = scene.kind === "radiance" ? scene.radianceAt : undefined;
68
+ const { reflectance, spectralIndex, neutralDirect, spectralDirect, groups } = displayViewSignalTables(input);
69
+ const kernels = input.kernels, R = kernels ? kernels.radius : 0;
70
+ let lookups = 0;
71
+ // The plane point (frame units) and field factor of native sensor point (x, y) mm at time t: the environment's mapping (lookOf) and
72
+ // the planar provider's own arithmetic for the outgoing direction −look.
73
+ let pu = 0, pv = 0, pf = 0;
74
+ const map = (xMm, yMm, t) => {
75
+ const look = geometry.lookOf(xMm, yMm, t);
76
+ if (!(look.z > 0))
77
+ throw new InvalidConfigurationError("simulateDisplayView: a query ray does not point toward the scene plane.");
78
+ const s = distance / look.z;
79
+ pu = look.x * s / scale;
80
+ pv = look.y * s / scale;
81
+ pf = look.factor;
82
+ };
83
+ const materialOf = (m) => { if (spectralIndex[m] === undefined)
84
+ throw new InvalidConfigurationError("Unknown chart material."); return m; };
85
+ // Without a kernel: the environment's query point of offset o of site (x, y) (native-environment-lean.ts, as buildExpectedSignalInput's queryPoint).
86
+ const box = lens.boxSamples, siteWidthMm = pitch / 1000;
87
+ const phases = offsets.map((_, o) => {
88
+ const node = Math.floor(o / lens.spots.length), spot = lens.spots[o % lens.spots.length].offsetMicrometers;
89
+ return { xPhase: (node % box + 0.5) / box, yPhase: (Math.floor(node / box) + 0.5) / box, aaX: spot.x / 1000, aaY: spot.y / 1000 };
90
+ });
91
+ function directSite(x, y) {
92
+ const c = channelAt(x, y), centerX = -imagingArea.widthMm / 2 + (pitch / 2 + x * pitch + 0) / 1000, centerY = -imagingArea.heightMm / 2 + (pitch / 2 + y * pitch + 0) / 1000;
93
+ let total = 0;
94
+ for (let ti = 0; ti < T; ti++) {
95
+ let sum = 0;
96
+ for (let o = 0; o < offsets.length; o++) {
97
+ const node = phases[o];
98
+ map((centerX - siteWidthMm / 2 + node.xPhase * siteWidthMm) - node.aaX, (centerY - siteWidthMm / 2 + node.yPhase * siteWidthMm) - node.aaY, times[ti]);
99
+ let value;
100
+ if (materials) {
101
+ const m = materialOf(materials.materialAt(pu, pv)), si = spectralIndex[m];
102
+ value = si < 0 ? reflectance[m] * neutralDirect[c] : spectralDirect[c][si];
103
+ lookups++;
104
+ }
105
+ else {
106
+ value = 0;
107
+ for (let n = 0; n < nodes; n++)
108
+ value += A[c][n] * radianceAt(pu, pv, input.wavelengthsNm[n]);
109
+ lookups += nodes;
110
+ }
111
+ sum += offsets[o].weight * (pf * value);
112
+ }
113
+ total += sum;
114
+ }
115
+ return total / T;
116
+ }
117
+ // With a kernel: lattice point (p, q) of group g at time t, #548's lattice formula, cached in a ring tagged with (p, q).
118
+ const G = groups.length, slots = G * T, per = materials ? 1 : nodes;
119
+ let cacheRows = 0, cacheCols = 0, tagQ = new Int32Array(0), tagP = new Int32Array(0), cacheMaterial = new Int32Array(0), cacheFactor = new Float64Array(0), cacheRadiance = new Float64Array(0);
120
+ const radiance = new Float64Array(nodes);
121
+ let lm = 0, lf = 0;
122
+ function lattice(g, ti, p, q) {
123
+ let k = -1;
124
+ if (cacheRows > 0) {
125
+ k = ((g * T + ti) * cacheRows + (q & (cacheRows - 1))) * cacheCols + (p & (cacheCols - 1));
126
+ if (tagQ[k] === q && tagP[k] === p) {
127
+ lf = cacheFactor[k];
128
+ if (materials)
129
+ lm = cacheMaterial[k];
130
+ else
131
+ for (let n = 0; n < nodes; n++)
132
+ radiance[n] = cacheRadiance[k * nodes + n];
133
+ return;
134
+ }
135
+ }
136
+ const group = groups[g];
137
+ map((p + 0.5 + group.fx - W / 2) * mmPerSite, (q + 0.5 + group.fy - H / 2) * mmPerSite, times[ti]);
138
+ lf = pf;
139
+ if (materials) {
140
+ lm = materialOf(materials.materialAt(pu, pv));
141
+ lookups++;
142
+ }
143
+ else {
144
+ for (let n = 0; n < nodes; n++)
145
+ radiance[n] = radianceAt(pu, pv, input.wavelengthsNm[n]);
146
+ lookups += nodes;
147
+ }
148
+ if (k >= 0) {
149
+ tagQ[k] = q;
150
+ tagP[k] = p;
151
+ cacheFactor[k] = lf;
152
+ if (materials)
153
+ cacheMaterial[k] = lm;
154
+ else
155
+ for (let n = 0; n < nodes; n++)
156
+ cacheRadiance[k * nodes + n] = radiance[n];
157
+ }
158
+ }
159
+ function kernelSite(x, y) {
160
+ const c = channelAt(x, y), rowMask = cacheRows - 1, colMask = cacheCols - 1;
161
+ let total = 0;
162
+ for (let ti = 0; ti < T; ti++) {
163
+ let sum = 0;
164
+ for (let g = 0; g < G; g++) {
165
+ const group = groups[g], p0 = x + group.minIx - R, q0 = y + group.minIy - R, cw = group.cw, ch = group.ch;
166
+ if (materials) {
167
+ const nk = group.neutral[c], sk = group.spectral[c];
168
+ for (let dy = 0, t = 0; dy < ch; dy++) {
169
+ // The cache row of lattice row q (read inline: the common case is a hit).
170
+ const q = q0 + dy, row = cacheRows > 0 ? ((g * T + ti) * cacheRows + (q & rowMask)) * cacheCols : -1;
171
+ for (let dx = 0; dx < cw; dx++, t++) {
172
+ const p = p0 + dx, k = row + (p & colMask);
173
+ if (row >= 0 && tagQ[k] === q && tagP[k] === p) {
174
+ lm = cacheMaterial[k];
175
+ lf = cacheFactor[k];
176
+ }
177
+ else
178
+ lattice(g, ti, p, q);
179
+ const si = spectralIndex[lm];
180
+ sum += lf * (si < 0 ? reflectance[lm] * nk[t] : sk[si][t]);
181
+ }
182
+ }
183
+ }
184
+ else
185
+ for (let dy = 0, t = 0; dy < ch; dy++)
186
+ for (let dx = 0; dx < cw; dx++, t++) {
187
+ lattice(g, ti, p0 + dx, q0 + dy);
188
+ let value = 0;
189
+ for (let n = 0; n < nodes; n++)
190
+ value += group.perNode[n][c][t] * radiance[n];
191
+ sum += lf * value;
192
+ }
193
+ }
194
+ total += sum;
195
+ }
196
+ return total / T;
197
+ }
198
+ const pilotNode = input.wavelengthsNm[Math.floor(nodes / 2)];
199
+ if (input.largeKernel)
200
+ return createLargeLensSignal(input, inFocusLensTile, () => lookups, pilot);
201
+ /** E_c (#564) of the native site positions [x0, x0 + T) × [y0, y0 + T) for the three channels: kernelSite's sum for each channel. */
202
+ function inFocusLensTile(x0, y0, TS) {
203
+ const out = [0, 1, 2].map(() => new Float64Array(TS * TS));
204
+ // A fixed tile reaches past the raster: positions more than the disc's radius beyond it feed no site, so they stay 0 and are never
205
+ // mapped (the lens profiles are checked only up to the view's reach).
206
+ const D = input.largeKernel.radius;
207
+ if (x0 + TS <= -D || x0 >= W + D || y0 + TS <= -D || y0 >= H + D)
208
+ return out;
209
+ if (x0 < -D || x0 + TS > W + D || y0 < -D || y0 + TS > H + D) {
210
+ const xa = Math.max(x0, -D), xb = Math.min(x0 + TS, W + D), ya = Math.max(y0, -D), yb = Math.min(y0 + TS, H + D);
211
+ const part = inFocusLensRect(xa, ya, xb - xa, yb - ya);
212
+ for (let c = 0; c < 3; c++)
213
+ for (let y = ya; y < yb; y++)
214
+ out[c].set(part[c].subarray((y - ya) * (xb - xa), (y - ya + 1) * (xb - xa)), (y - y0) * TS + xa - x0);
215
+ return out;
216
+ }
217
+ return inFocusLensRect(x0, y0, TS, TS);
218
+ }
219
+ /** E_c of the native site positions [x0, x0 + w) × [y0, y0 + h), row-major, for the three channels. */
220
+ function inFocusLensRect(x0, y0, TW, TH) {
221
+ const out = [0, 1, 2].map(() => new Float64Array(TW * TH)), sum = [0, 1, 2].map(() => new Float64Array(TW * TH));
222
+ for (let ti = 0; ti < T; ti++) {
223
+ for (const e of sum)
224
+ e.fill(0);
225
+ for (const group of groups) {
226
+ const cw = group.cw, ch = group.ch, gw = TW + cw - 1, gh = TH + ch - 1, p0 = x0 + group.minIx - R, q0 = y0 + group.minIy - R;
227
+ const factor = new Float64Array(gw * gh), material = materials ? new Int32Array(gw * gh) : undefined, values = materials ? undefined : new Float64Array(gw * gh * nodes);
228
+ for (let j = 0; j < gh; j++)
229
+ for (let i = 0; i < gw; i++) {
230
+ const k = j * gw + i;
231
+ map((p0 + i + 0.5 + group.fx - W / 2) * mmPerSite, (q0 + j + 0.5 + group.fy - H / 2) * mmPerSite, times[ti]);
232
+ factor[k] = pf;
233
+ if (material)
234
+ material[k] = materialOf(materials.materialAt(pu, pv));
235
+ else
236
+ for (let n = 0; n < nodes; n++)
237
+ values[k * nodes + n] = radianceAt(pu, pv, input.wavelengthsNm[n]);
238
+ }
239
+ lookups += material ? gw * gh : gw * gh * nodes;
240
+ for (let c = 0; c < 3; c++) {
241
+ const e = sum[c], nk = group.neutral[c], sk = group.spectral[c];
242
+ for (let y = 0; y < TH; y++)
243
+ for (let x = 0; x < TW; x++) {
244
+ let v = 0;
245
+ for (let dy = 0, t = 0; dy < ch; dy++)
246
+ for (let dx = 0; dx < cw; dx++, t++) {
247
+ const k = (y + dy) * gw + x + dx;
248
+ if (material) {
249
+ const m = material[k], si = spectralIndex[m];
250
+ v += factor[k] * (si < 0 ? reflectance[m] * nk[t] : sk[si][t]);
251
+ }
252
+ else {
253
+ let r = 0;
254
+ for (let n = 0; n < nodes; n++)
255
+ r += group.perNode[n][c][t] * values[k * nodes + n];
256
+ v += factor[k] * r;
257
+ }
258
+ }
259
+ e[y * TW + x] = e[y * TW + x] + v;
260
+ }
261
+ }
262
+ }
263
+ for (let c = 0; c < 3; c++) {
264
+ const o = out[c], e = sum[c];
265
+ for (let k = 0; k < o.length; k++)
266
+ o[k] = o[k] + e[k];
267
+ }
268
+ }
269
+ for (const o of out)
270
+ for (let k = 0; k < o.length; k++)
271
+ o[k] = o[k] / T;
272
+ return out;
273
+ }
274
+ // Site values persist across windows in a ring of native rows (a site's value does not depend on the window).
275
+ let ringRows = 0, ring = new Float64Array(0), ringRowId = new Int32Array(0), ringOn = false;
276
+ const compute = kernels ? kernelSite : directSite;
277
+ return {
278
+ get lookups() { return lookups; },
279
+ uniformMaterial: -1,
280
+ tracksMaterial: false,
281
+ get cachesSites() { return ringOn; },
282
+ marksReads: false,
283
+ markReads() { },
284
+ window(x0, y0, x1, y1) {
285
+ const sw = Math.max(0, x1 - x0), sh = Math.max(0, y1 - y0);
286
+ if (sh > 0 && ringRows < 2 * sh) {
287
+ const rows = pow2(2 * sh);
288
+ if (rows * W <= LATTICE_CACHE_LIMIT) {
289
+ ringRows = rows;
290
+ ring = new Float64Array(rows * W).fill(NaN);
291
+ ringRowId = new Int32Array(rows).fill(-1);
292
+ }
293
+ }
294
+ ringOn = sh > 0 && ringRows >= 2 * sh;
295
+ if (!kernels || sw === 0)
296
+ return;
297
+ // The lattice rectangle the window's sites read, per group; the cache keeps two such heights and the columns the render's windows span.
298
+ let cw = 0, ch = 0;
299
+ for (const group of groups) {
300
+ cw = Math.max(cw, group.cw);
301
+ ch = Math.max(ch, group.ch);
302
+ }
303
+ const height = sh + ch - 1, width = sw + cw - 1;
304
+ let rows = pow2(2 * height), cols = pow2(Math.min(W + 2 * (R + cw), Math.max(width, (Number.isFinite(spanColumns) ? spanColumns : W) + width)));
305
+ if (slots * per * rows * cols > LATTICE_CACHE_LIMIT)
306
+ rows = pow2(height);
307
+ if (slots * per * rows * cols > LATTICE_CACHE_LIMIT)
308
+ cols = pow2(width);
309
+ if (slots * per * rows * cols > LATTICE_CACHE_LIMIT) {
310
+ cacheRows = 0;
311
+ return;
312
+ }
313
+ if (rows > cacheRows || cols > cacheCols) {
314
+ rows = Math.max(rows, cacheRows);
315
+ cols = Math.max(cols, cacheCols);
316
+ if (slots * per * rows * cols > LATTICE_CACHE_LIMIT) {
317
+ rows = pow2(height);
318
+ cols = pow2(width);
319
+ }
320
+ cacheRows = rows;
321
+ cacheCols = cols;
322
+ const size = slots * rows * cols;
323
+ tagQ = new Int32Array(size).fill(-2147483648);
324
+ tagP = new Int32Array(size);
325
+ cacheFactor = new Float64Array(size);
326
+ if (materials)
327
+ cacheMaterial = new Int32Array(size);
328
+ else
329
+ cacheRadiance = new Float64Array(size * nodes);
330
+ }
331
+ },
332
+ site(x, y) {
333
+ if (!ringOn)
334
+ return compute(x, y);
335
+ const slot = y & (ringRows - 1), k = slot * W + x;
336
+ if (ringRowId[slot] === y) {
337
+ const v = ring[k];
338
+ if (v === v)
339
+ return v;
340
+ }
341
+ else {
342
+ ringRowId[slot] = y;
343
+ ring.fill(NaN, slot * W, (slot + 1) * W);
344
+ }
345
+ const v = compute(x, y);
346
+ ring[k] = v;
347
+ return v;
348
+ },
349
+ pilot
350
+ };
351
+ /**
352
+ * The renderer's pilot point is a rectilinear plane point of the native raster: back to the sensor point, then through the lens at
353
+ * every temporal midpoint. A point whose scene value differs between the instants (motion) counts as detail (NaN equals nothing).
354
+ */
355
+ function pilot(u, v) {
356
+ const g = 2 / input.outputWidth, xMm = u / g * mmPerSite, yMm = -v / g * mmPerSite;
357
+ let first = NaN;
358
+ try {
359
+ for (let ti = 0; ti < T; ti++) {
360
+ map(xMm, yMm, times[ti]);
361
+ const value = materials ? materials.materialAt(pu, pv) : radianceAt(pu, pv, pilotNode);
362
+ lookups++;
363
+ if (ti === 0)
364
+ first = value;
365
+ else if (value !== first)
366
+ return NaN;
367
+ }
368
+ }
369
+ catch {
370
+ return NaN;
371
+ }
372
+ return first;
373
+ }
374
+ }
375
+ /** #564: tile sides of the large-kernel lens signal (the in-focus E and the convolved sites) and the bytes each tile cache keeps. */
376
+ const LARGE_E_TILE = 64, LARGE_OUTPUT_TILE = 128, LARGE_CACHE_BYTES = 32 * 1048576;
377
+ /** #564: the lens signal with a defocus kernel above 64 sites: D ⊛ E_c by runs over the in-focus tiles (see the module comment). */
378
+ function createLargeLensSignal(input, inFocusTile, lookups, pilot) {
379
+ const runs = input.largeKernel, channelAt = input.channelAt, TE = LARGE_E_TILE;
380
+ // At least the in-focus tiles one output tile's source spans plus one more row of them, so the next output tile of a row reuses them.
381
+ const span = Math.ceil((LARGE_OUTPUT_TILE + 2 * runs.radius) / TE) + 1;
382
+ const inFocus = new Map(), capacity = Math.max(span * (span + 1), Math.floor(LARGE_CACHE_BYTES / (3 * TE * TE * 8)));
383
+ const tileAt = (tx, ty) => {
384
+ const key = (ty + 1048576) * 2097152 + tx + 1048576;
385
+ let tile = inFocus.get(key);
386
+ if (tile) {
387
+ inFocus.delete(key);
388
+ inFocus.set(key, tile);
389
+ }
390
+ else {
391
+ tile = inFocusTile(tx * TE, ty * TE, TE);
392
+ inFocus.set(key, tile);
393
+ if (inFocus.size > capacity)
394
+ inFocus.delete(inFocus.keys().next().value);
395
+ }
396
+ return tile;
397
+ };
398
+ // Which sites of a tile row hold channel c: the CFA's 2 × 2 period (tile origins are even).
399
+ const phase = [0, 1].map((py) => [0, 1].map((px) => channelAt(px, py)));
400
+ const patterns = [0, 1, 2].map((c) => (row) => {
401
+ const p = phase[row & 1], even = p[0] === c, odd = p[1] === c;
402
+ return even && odd ? { start: 0, step: 1 } : even ? { start: 0, step: 2 } : odd ? { start: 1, step: 2 } : null;
403
+ });
404
+ const lattice = createSiteKernelRunsLattice({ runs, tile: LARGE_OUTPUT_TILE, fields: 3, capacityBytes: LARGE_CACHE_BYTES, pattern: (c) => patterns[c],
405
+ source: (x0, y0, width, height) => (c) => {
406
+ const samples = new Float64Array(width * height);
407
+ for (let ty = Math.floor(y0 / TE); ty * TE < y0 + height; ty++)
408
+ for (let tx = Math.floor(x0 / TE); tx * TE < x0 + width; tx++) {
409
+ const tile = tileAt(tx, ty)[c], ax = Math.max(x0, tx * TE), bx = Math.min(x0 + width, (tx + 1) * TE), ay = Math.max(y0, ty * TE), by = Math.min(y0 + height, (ty + 1) * TE);
410
+ for (let y = ay; y < by; y++)
411
+ samples.set(tile.subarray((y - ty * TE) * TE + ax - tx * TE, (y - ty * TE) * TE + bx - tx * TE), (y - y0) * width + ax - x0);
412
+ }
413
+ return samples;
414
+ } });
415
+ return {
416
+ get lookups() { return lookups(); },
417
+ uniformMaterial: -1,
418
+ tracksMaterial: false,
419
+ cachesSites: false,
420
+ marksReads: false,
421
+ markReads() { },
422
+ window() { },
423
+ site(x, y) { return lattice.valueAt(channelAt(x, y), x, y); },
424
+ pilot
425
+ };
426
+ }
427
+ //# sourceMappingURL=display-view-lens-signal.js.map