@forgeax/engine-render 0.1.34 → 0.1.36
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/README.md +69 -3
- package/dist/assembly/device-feature-admission.d.ts +2 -2
- package/dist/assembly/host-contract.d.ts +3 -2
- package/dist/assembly/host-contract.d.ts.map +1 -1
- package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
- package/dist/assembly/render-target-host.d.ts +5 -0
- package/dist/assembly/render-target-host.d.ts.map +1 -1
- package/dist/assembly/renderer-facade.d.ts.map +1 -1
- package/dist/assembly/webgpu-renderer.d.ts +1 -0
- package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
- package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
- package/dist/authoring.mjs +2 -4
- package/dist/authoring.mjs.map +1 -1
- package/dist/chunk-2OZAS3WU.mjs +3489 -0
- package/dist/chunk-2OZAS3WU.mjs.map +1 -0
- package/dist/{chunk-UI5SFODX.mjs → chunk-BZQ4CDRF.mjs} +2338 -1991
- package/dist/chunk-BZQ4CDRF.mjs.map +1 -0
- package/dist/{chunk-MKDIT2EM.mjs → chunk-CJOV2XLO.mjs} +216 -261
- package/dist/chunk-CJOV2XLO.mjs.map +1 -0
- package/dist/chunk-D6WSBXJ2.mjs +1842 -0
- package/dist/chunk-D6WSBXJ2.mjs.map +1 -0
- package/dist/{chunk-HEB564WF.mjs → chunk-DRF6OULX.mjs} +4 -4
- package/dist/{chunk-HEB564WF.mjs.map → chunk-DRF6OULX.mjs.map} +1 -1
- package/dist/{chunk-OCDLEI6I.mjs → chunk-OB5WAFYH.mjs} +117 -5
- package/dist/chunk-OB5WAFYH.mjs.map +1 -0
- package/dist/{chunk-ODZWR6JQ.mjs → chunk-OEYKKMXG.mjs} +2042 -27
- package/dist/chunk-OEYKKMXG.mjs.map +1 -0
- package/dist/chunk-RIPAEGI6.mjs +48 -0
- package/dist/chunk-RIPAEGI6.mjs.map +1 -0
- package/dist/{chunk-ELJFJ2BU.mjs → chunk-X2KA6WHM.mjs} +29 -4
- package/dist/chunk-X2KA6WHM.mjs.map +1 -0
- package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
- package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
- package/dist/cloud/frame-context.d.ts +2 -2
- package/dist/cloud/frame-context.d.ts.map +1 -1
- package/dist/construct-renderer.mjs +936 -2140
- package/dist/construct-renderer.mjs.map +1 -1
- package/dist/device/device-scope.d.ts.map +1 -1
- package/dist/device/gpu-residency.d.ts +19 -19
- package/dist/device/gpu-residency.d.ts.map +1 -1
- package/dist/errors/render.d.ts +3 -2
- package/dist/errors/render.d.ts.map +1 -1
- package/dist/extract/material-context.d.ts +7 -0
- package/dist/extract/material-context.d.ts.map +1 -0
- package/dist/features/host.d.ts +5 -0
- package/dist/features/host.d.ts.map +1 -1
- package/dist/features/particle-mesh-layout.d.ts +19 -0
- package/dist/features/particle-mesh-layout.d.ts.map +1 -0
- package/dist/features/plan.d.ts +2 -0
- package/dist/features/plan.d.ts.map +1 -1
- package/dist/features/types.d.ts +8 -0
- package/dist/features/types.d.ts.map +1 -1
- package/dist/glyph-text-layout-system.d.ts +1 -1
- package/dist/glyph-text-layout-system.d.ts.map +1 -1
- package/dist/gpu-driven/production-raster.d.ts +1 -1
- package/dist/gpu-driven/production-raster.d.ts.map +1 -1
- package/dist/gpu-driven/shadow-ownership.d.ts +3 -3
- package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -1
- package/dist/gpu-driven/shadow-views.d.ts +1 -1
- package/dist/gpu-driven/shadow-views.d.ts.map +1 -1
- package/dist/gpu-driven/view-gpu.d.ts +1 -1
- package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
- package/dist/ibl/frame-resources.d.ts.map +1 -1
- package/dist/ibl/skylight-bind-group.d.ts +4 -4
- package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +542 -9
- package/dist/index.mjs.map +1 -1
- package/dist/instances.d.ts +3 -4
- package/dist/instances.d.ts.map +1 -1
- package/dist/internal.mjs +7 -8
- package/dist/internal.mjs.map +1 -1
- package/dist/pbr-pipeline.d.ts +3 -3
- package/dist/pbr-pipeline.d.ts.map +1 -1
- package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
- package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
- package/dist/pipeline/standard-output/resources.d.ts +2 -2
- package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
- package/dist/publication/contract.d.ts +77 -0
- package/dist/publication/contract.d.ts.map +1 -0
- package/dist/publication/dependencies.d.ts +7 -0
- package/dist/publication/dependencies.d.ts.map +1 -0
- package/dist/publication/prepare-geometry.d.ts +9 -0
- package/dist/publication/prepare-geometry.d.ts.map +1 -0
- package/dist/publication/programs.d.ts +5 -0
- package/dist/publication/programs.d.ts.map +1 -0
- package/dist/publication/publisher.d.ts +34 -0
- package/dist/publication/publisher.d.ts.map +1 -0
- package/dist/publication/receiver.d.ts +29 -0
- package/dist/publication/receiver.d.ts.map +1 -0
- package/dist/publication/resource-scope.d.ts +18 -0
- package/dist/publication/resource-scope.d.ts.map +1 -0
- package/dist/publication/targets.d.ts +33 -0
- package/dist/publication/targets.d.ts.map +1 -0
- package/dist/publication/video.d.ts +5 -0
- package/dist/publication/video.d.ts.map +1 -0
- package/dist/record/fold-buckets.d.ts +3 -3
- package/dist/record/fold-buckets.d.ts.map +1 -1
- package/dist/record/frame-snapshot.d.ts +6 -6
- package/dist/record/frame-snapshot.d.ts.map +1 -1
- package/dist/record/frame.d.ts +5 -4
- package/dist/record/frame.d.ts.map +1 -1
- package/dist/record/helpers.d.ts +2 -2
- package/dist/record/helpers.d.ts.map +1 -1
- package/dist/record/main-pass-geometry.d.ts +2 -2
- package/dist/record/main-pass-geometry.d.ts.map +1 -1
- package/dist/record/main-pass-material.d.ts +7 -9
- package/dist/record/main-pass-material.d.ts.map +1 -1
- package/dist/record/main-pass-sprite-draws.d.ts +3 -16
- package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
- package/dist/record/main-pass.d.ts.map +1 -1
- package/dist/record/prepared-material-bindings.d.ts +2 -3
- package/dist/record/prepared-material-bindings.d.ts.map +1 -1
- package/dist/record/render-context.d.ts +4 -2
- package/dist/record/render-context.d.ts.map +1 -1
- package/dist/record/shadow-pass.d.ts +2 -2
- package/dist/record/shadow-pass.d.ts.map +1 -1
- package/dist/record/sprite-instance-buffer.d.ts +31 -0
- package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
- package/dist/record/target-capture-graph.d.ts +19 -0
- package/dist/record/target-capture-graph.d.ts.map +1 -0
- package/dist/record/typed-frame-graph.d.ts +2 -13
- package/dist/record/typed-frame-graph.d.ts.map +1 -1
- package/dist/record/view-ubo.d.ts.map +1 -1
- package/dist/record/volume-params.d.ts +2 -2
- package/dist/record/volume-params.d.ts.map +1 -1
- package/dist/recovery/render-system-candidate.d.ts +5 -12
- package/dist/recovery/render-system-candidate.d.ts.map +1 -1
- package/dist/render-contract.d.ts +4 -1
- package/dist/render-contract.d.ts.map +1 -1
- package/dist/render-system-extract-tail.d.ts.map +1 -1
- package/dist/render-system-extract.d.ts +2 -0
- package/dist/render-system-extract.d.ts.map +1 -1
- package/dist/render-system-presentation.d.ts +2 -2
- package/dist/render-system-presentation.d.ts.map +1 -1
- package/dist/render-system-projections.d.ts +3 -2
- package/dist/render-system-projections.d.ts.map +1 -1
- package/dist/render-system-residency.d.ts +2 -2
- package/dist/render-system-residency.d.ts.map +1 -1
- package/dist/render-system.d.ts +3 -7
- package/dist/render-system.d.ts.map +1 -1
- package/dist/scene/render-scene.d.ts +8 -4
- package/dist/scene/render-scene.d.ts.map +1 -1
- package/dist/scene/render-source.d.ts +42 -0
- package/dist/scene/render-source.d.ts.map +1 -0
- package/dist/scene/source-systems.d.ts +6 -0
- package/dist/scene/source-systems.d.ts.map +1 -0
- package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
- package/dist/systems/skin-palette-types.d.ts +10 -0
- package/dist/systems/skin-palette-types.d.ts.map +1 -1
- package/dist/systems/transparent-dispatch.d.ts +2 -2
- package/dist/systems/transparent-dispatch.d.ts.map +1 -1
- package/dist/systems/transparent-sort-config.d.ts +2 -1
- package/dist/systems/transparent-sort-config.d.ts.map +1 -1
- package/dist/temporal/index.mjs +6 -7
- package/dist/typed-shadow-passes.d.ts.map +1 -1
- package/package.json +21 -21
- package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
- package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
- package/src/__tests__/device-scope-lifecycle.unit.test.ts +26 -0
- package/src/__tests__/gpu-driven-pbr.dawn.test.ts +3 -4
- package/src/__tests__/gpu-driven-production.integration.test.ts +18 -1
- package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +23 -0
- package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
- package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +0 -1
- package/src/__tests__/material-vertex-texture.browser.test.ts +7 -0
- package/src/__tests__/material-vertex-texture.dawn.test.ts +7 -0
- package/src/__tests__/material-vertex-texture.fixture.ts +31 -0
- package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
- package/src/__tests__/physical-clearcoat.integration.test.ts +23 -23
- package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
- package/src/__tests__/recovery-candidate-prepare.contract.test.ts +5 -0
- package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
- package/src/__tests__/renderer-factory-material-contract.unit.test.ts +107 -5
- package/src/__tests__/runtime-material-program.integration.test.ts +79 -0
- package/src/__tests__/vertex-layouts-ssot.unit.test.ts +23 -3
- package/src/assembly/device-feature-admission.ts +2 -2
- package/src/assembly/host-contract.ts +5 -2
- package/src/assembly/recovery/renderer-recover.ts +9 -0
- package/src/assembly/render-target-host.ts +57 -47
- package/src/assembly/renderer-facade.ts +3 -1
- package/src/assembly/webgpu-ready.ts +1 -1
- package/src/assembly/webgpu-renderer.ts +101 -70
- package/src/assembly/webgpu-vertex-layouts.ts +2 -9
- package/src/cloud/frame-context.ts +2 -2
- package/src/device/device-scope.ts +3 -0
- package/src/device/gpu-residency.ts +26 -25
- package/src/errors/render.ts +3 -1
- package/src/extract/material-context.ts +21 -0
- package/src/features/host.ts +13 -5
- package/src/features/particle-mesh-layout.ts +25 -0
- package/src/features/plan.ts +2 -0
- package/src/features/types.ts +8 -0
- package/src/glyph-text-layout-system.ts +2 -2
- package/src/gpu-driven/production-raster.ts +9 -3
- package/src/gpu-driven/shadow-ownership.ts +3 -3
- package/src/gpu-driven/shadow-views.ts +27 -2
- package/src/gpu-driven/view-gpu.ts +5 -0
- package/src/ibl/frame-resources.ts +0 -1
- package/src/ibl/skylight-bind-group.ts +13 -25
- package/src/index.ts +9 -0
- package/src/instances.ts +9 -6
- package/src/pbr-pipeline.ts +15 -18
- package/src/pipeline/standard-output/lut-gpu.ts +3 -3
- package/src/pipeline/standard-output/resources.ts +2 -2
- package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
- package/src/publication/__tests__/features.integration.test.ts +173 -0
- package/src/publication/__tests__/programs.unit.test.ts +25 -0
- package/src/publication/__tests__/publication.integration.test.ts +415 -0
- package/src/publication/__tests__/targets.integration.test.ts +120 -0
- package/src/publication/__tests__/video.browser.test.ts +50 -0
- package/src/publication/contract.ts +104 -0
- package/src/publication/dependencies.ts +54 -0
- package/src/publication/prepare-geometry.ts +94 -0
- package/src/publication/programs.ts +45 -0
- package/src/publication/publisher.ts +645 -0
- package/src/publication/receiver.ts +297 -0
- package/src/publication/resource-scope.ts +31 -0
- package/src/publication/targets.ts +182 -0
- package/src/publication/video.ts +33 -0
- package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
- package/src/record/__tests__/prepared-runtime-material.integration.test.ts +134 -0
- package/src/record/__tests__/sprite-material-abi.unit.test.ts +8 -10
- package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
- package/src/record/fold-buckets.ts +8 -5
- package/src/record/frame-snapshot.ts +6 -6
- package/src/record/frame.ts +87 -28
- package/src/record/helpers.ts +2 -2
- package/src/record/main-pass-geometry.ts +21 -2
- package/src/record/main-pass-material.ts +37 -33
- package/src/record/main-pass-sprite-draws.ts +6 -152
- package/src/record/main-pass.ts +8 -7
- package/src/record/prepared-material-bindings.ts +16 -37
- package/src/record/render-context.ts +4 -2
- package/src/record/shadow-pass.ts +65 -7
- package/src/record/sprite-instance-buffer.ts +155 -0
- package/src/record/target-capture-graph.ts +258 -0
- package/src/record/typed-frame-graph.ts +11 -207
- package/src/record/view-ubo.ts +1 -1
- package/src/record/volume-params.ts +6 -6
- package/src/recovery/render-system-candidate.ts +10 -18
- package/src/render-contract.ts +6 -1
- package/src/render-system-extract-tail.ts +48 -23
- package/src/render-system-extract.ts +11 -2
- package/src/render-system-presentation.ts +2 -2
- package/src/render-system-projections.ts +3 -2
- package/src/render-system-residency.ts +2 -2
- package/src/render-system.ts +82 -54
- package/src/scene/render-scene.ts +110 -100
- package/src/scene/render-source.ts +90 -0
- package/src/scene/source-systems.ts +33 -0
- package/src/systems/skin-palette-allocator.ts +1 -0
- package/src/systems/skin-palette-types.ts +11 -0
- package/src/systems/transparent-dispatch.ts +2 -2
- package/src/systems/transparent-sort-config.ts +3 -1
- package/src/typed-shadow-passes.ts +36 -5
- package/dist/chunk-BLXGONR2.mjs.map +0 -1
- package/dist/chunk-ELJFJ2BU.mjs.map +0 -1
- package/dist/chunk-ILQ5MEZF.mjs +0 -32
- package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
- package/dist/chunk-MKDIT2EM.mjs.map +0 -1
- package/dist/chunk-OCDLEI6I.mjs.map +0 -1
- package/dist/chunk-ODZWR6JQ.mjs.map +0 -1
- package/dist/chunk-RXZ3RGOU.mjs +0 -53
- package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
- package/dist/chunk-SHFUFJ5N.mjs +0 -1781
- package/dist/chunk-SHFUFJ5N.mjs.map +0 -1
- package/dist/chunk-UI5SFODX.mjs.map +0 -1
- package/dist/chunk-UINHLCZA.mjs +0 -2389
- package/dist/chunk-UINHLCZA.mjs.map +0 -1
- package/dist/chunk-WEX45HKH.mjs +0 -1524
- package/dist/chunk-WEX45HKH.mjs.map +0 -1
|
@@ -0,0 +1,1842 @@
|
|
|
1
|
+
import { freezeBarrelDistortionMapping } from './chunk-BZQ4CDRF.mjs';
|
|
2
|
+
import { PROBE_BLEND_RECORD_CAPACITY, PROBE_BLEND_RECORD_BYTE_SIZE } from './chunk-RIPAEGI6.mjs';
|
|
3
|
+
import { GPU_BUFFER_USAGE_MAP_READ, GPU_BUFFER_USAGE_VERTEX, GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM, GPU_BUFFER_USAGE_QUERY_RESOLVE, GPU_BUFFER_USAGE_COPY_SRC, GPU_TEXTURE_USAGE_COPY_SRC, GPU_TEXTURE_USAGE_COPY_DST, GPU_TEXTURE_USAGE_TEXTURE_BINDING, GPU_TEXTURE_USAGE_STORAGE_BINDING, GPU_TEXTURE_USAGE_RENDER_ATTACHMENT } from './chunk-2OZAS3WU.mjs';
|
|
4
|
+
import { R_MIN, ok, err } from '@forgeax/engine-types';
|
|
5
|
+
import { err as err$1, ok as ok$1, RhiError } from '@forgeax/engine-rhi';
|
|
6
|
+
|
|
7
|
+
// src/ssr/hiz.ts
|
|
8
|
+
var SSR_HIZ_FORMAT = "r32float";
|
|
9
|
+
function validExtent(extent) {
|
|
10
|
+
return Number.isInteger(extent.width) && Number.isInteger(extent.height) && extent.width > 0 && extent.height > 0;
|
|
11
|
+
}
|
|
12
|
+
function requireExtent(extent) {
|
|
13
|
+
if (!validExtent(extent)) {
|
|
14
|
+
throw new RangeError("SSR Hi-Z extent must contain positive integer width and height");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function nextSsrHiZMipExtent(extent) {
|
|
18
|
+
requireExtent(extent);
|
|
19
|
+
return {
|
|
20
|
+
width: Math.max(1, Math.floor(extent.width / 2)),
|
|
21
|
+
height: Math.max(1, Math.floor(extent.height / 2))
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function buildSsrHiZPlan(extent) {
|
|
25
|
+
requireExtent(extent);
|
|
26
|
+
const levels = [];
|
|
27
|
+
let current = { width: extent.width, height: extent.height };
|
|
28
|
+
let level = 0;
|
|
29
|
+
while (true) {
|
|
30
|
+
levels.push({ level, ...current });
|
|
31
|
+
if (current.width === 1 && current.height === 1) break;
|
|
32
|
+
current = nextSsrHiZMipExtent(current);
|
|
33
|
+
level += 1;
|
|
34
|
+
}
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
extent: Object.freeze({ width: extent.width, height: extent.height }),
|
|
37
|
+
format: SSR_HIZ_FORMAT,
|
|
38
|
+
mipLevelCount: levels.length,
|
|
39
|
+
levels: Object.freeze(levels.map((entry) => Object.freeze(entry))),
|
|
40
|
+
graphSize: Object.freeze({ width: extent.width, height: extent.height })
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/ssr/resources.ts
|
|
45
|
+
var SSR_TRACE_FORMAT = "rgba16float";
|
|
46
|
+
var SSR_R32FLOAT_BYTES_PER_PIXEL = 4;
|
|
47
|
+
var SSR_RGBA16FLOAT_BYTES_PER_PIXEL = 8;
|
|
48
|
+
var SSR_TEMPORAL_PARAMS_BYTES = 32;
|
|
49
|
+
var SSR_SPATIAL_MEMORY_BUDGET_BYTES = 45088768;
|
|
50
|
+
function halfExtent(extent) {
|
|
51
|
+
return {
|
|
52
|
+
width: Math.max(1, Math.floor(extent.width / 2)),
|
|
53
|
+
height: Math.max(1, Math.floor(extent.height / 2))
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function byteCount(width, height, bytesPerPixel) {
|
|
57
|
+
const bytes = width * height * bytesPerPixel;
|
|
58
|
+
if (!Number.isSafeInteger(bytes)) {
|
|
59
|
+
throw new RangeError("SSR spatial descriptor byte count exceeds safe integer range");
|
|
60
|
+
}
|
|
61
|
+
return bytes;
|
|
62
|
+
}
|
|
63
|
+
function estimateSsrSpatialMemory(extent, options = {}) {
|
|
64
|
+
const half = halfExtent(extent);
|
|
65
|
+
const plan = buildSsrHiZPlan(half);
|
|
66
|
+
const temporal = options.temporal !== false;
|
|
67
|
+
const hizBytes = plan.levels.reduce(
|
|
68
|
+
(sum, level) => sum + byteCount(level.width, level.height, SSR_R32FLOAT_BYTES_PER_PIXEL),
|
|
69
|
+
0
|
|
70
|
+
);
|
|
71
|
+
const traceBytes = byteCount(half.width, half.height, SSR_RGBA16FLOAT_BYTES_PER_PIXEL);
|
|
72
|
+
const hitReactivityBytes = byteCount(half.width, half.height, SSR_R32FLOAT_BYTES_PER_PIXEL);
|
|
73
|
+
const resolvedBytes = temporal ? plan.levels.reduce(
|
|
74
|
+
(sum, level) => sum + byteCount(level.width, level.height, SSR_RGBA16FLOAT_BYTES_PER_PIXEL),
|
|
75
|
+
0
|
|
76
|
+
) : 0;
|
|
77
|
+
const historyBytes = temporal ? half.width * half.height * 12 * 2 : 0;
|
|
78
|
+
const temporalParamsBytes = temporal ? SSR_TEMPORAL_PARAMS_BYTES : 0;
|
|
79
|
+
const fallbackInputBytes = byteCount(
|
|
80
|
+
extent.width,
|
|
81
|
+
extent.height,
|
|
82
|
+
SSR_RGBA16FLOAT_BYTES_PER_PIXEL
|
|
83
|
+
);
|
|
84
|
+
const ssrOwnedBytes = hizBytes + traceBytes + hitReactivityBytes + resolvedBytes + historyBytes + temporalParamsBytes + fallbackInputBytes;
|
|
85
|
+
return Object.freeze({
|
|
86
|
+
width: extent.width,
|
|
87
|
+
height: extent.height,
|
|
88
|
+
halfWidth: half.width,
|
|
89
|
+
halfHeight: half.height,
|
|
90
|
+
hizBytes,
|
|
91
|
+
traceBytes,
|
|
92
|
+
hitReactivityBytes,
|
|
93
|
+
resolvedBytes,
|
|
94
|
+
historyBytes,
|
|
95
|
+
temporalParamsBytes,
|
|
96
|
+
fallbackInputBytes,
|
|
97
|
+
ssrOwnedBytes,
|
|
98
|
+
budgetBytes: SSR_SPATIAL_MEMORY_BUDGET_BYTES,
|
|
99
|
+
withinBudget: ssrOwnedBytes <= SSR_SPATIAL_MEMORY_BUDGET_BYTES
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function texture(graph, label, format, extent) {
|
|
103
|
+
const created = graph.createTexture(label, {
|
|
104
|
+
format,
|
|
105
|
+
size: { width: extent.width, height: extent.height },
|
|
106
|
+
domain: "linear-hdr"
|
|
107
|
+
});
|
|
108
|
+
if (!created.ok) return created;
|
|
109
|
+
const view = graph.view(created.value, { label: `${label}.view`, dimension: "2d" });
|
|
110
|
+
if (!view.ok) return view;
|
|
111
|
+
return ok({
|
|
112
|
+
texture: created.value,
|
|
113
|
+
view: view.value,
|
|
114
|
+
width: extent.width,
|
|
115
|
+
height: extent.height,
|
|
116
|
+
format
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function createSsrHiZResources(graph, extent) {
|
|
120
|
+
const plan = buildSsrHiZPlan(halfExtent(extent));
|
|
121
|
+
const created = graph.createTexture("ssr-hiz", {
|
|
122
|
+
format: SSR_HIZ_FORMAT,
|
|
123
|
+
size: plan.graphSize,
|
|
124
|
+
mipLevelCount: plan.mipLevelCount
|
|
125
|
+
});
|
|
126
|
+
if (!created.ok) return created;
|
|
127
|
+
const levels = [];
|
|
128
|
+
for (const entry of plan.levels) {
|
|
129
|
+
const view = graph.view(created.value, {
|
|
130
|
+
label: `ssr-hiz.mip-${entry.level}.view`,
|
|
131
|
+
dimension: "2d",
|
|
132
|
+
baseMipLevel: entry.level,
|
|
133
|
+
mipLevelCount: 1
|
|
134
|
+
});
|
|
135
|
+
if (!view.ok) return view;
|
|
136
|
+
levels.push(view.value);
|
|
137
|
+
}
|
|
138
|
+
return ok({ plan, texture: created.value, levels: Object.freeze(levels) });
|
|
139
|
+
}
|
|
140
|
+
function createSsrSpatialResources(graph, extent) {
|
|
141
|
+
const hiz = createSsrHiZResources(graph, extent);
|
|
142
|
+
if (!hiz.ok) return hiz;
|
|
143
|
+
const traceExtent = halfExtent(extent);
|
|
144
|
+
const trace = texture(graph, "ssr-trace", SSR_TRACE_FORMAT, traceExtent);
|
|
145
|
+
if (!trace.ok) return trace;
|
|
146
|
+
const hitTexture = graph.createTexture("ssr-hit-reactivity", {
|
|
147
|
+
format: "r32float",
|
|
148
|
+
size: traceExtent
|
|
149
|
+
});
|
|
150
|
+
if (!hitTexture.ok) return hitTexture;
|
|
151
|
+
const hitReactivity = graph.view(hitTexture.value, {
|
|
152
|
+
label: "ssr-hit-reactivity.view",
|
|
153
|
+
dimension: "2d"
|
|
154
|
+
});
|
|
155
|
+
if (!hitReactivity.ok) return hitReactivity;
|
|
156
|
+
return ok({ hiz: hiz.value, trace: trace.value, hitReactivity: hitReactivity.value });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/inspection-types.ts
|
|
160
|
+
function projectLightInspection(input) {
|
|
161
|
+
return Object.freeze({
|
|
162
|
+
generation: input.generation,
|
|
163
|
+
candidate: input.candidate,
|
|
164
|
+
accepted: input.accepted,
|
|
165
|
+
lastKnownGood: input.lastKnownGood,
|
|
166
|
+
failure: input.failure,
|
|
167
|
+
failureKeys: [...input.failureKeys],
|
|
168
|
+
resourceCount: input.resourceCount,
|
|
169
|
+
uploadBytes: input.uploadBytes
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function lightInspectionIdentity(topology, generation) {
|
|
173
|
+
return `${topology}:generation-${generation}`;
|
|
174
|
+
}
|
|
175
|
+
var emptyBloomInspection = () => ({
|
|
176
|
+
graphStatus: "empty",
|
|
177
|
+
enabled: false,
|
|
178
|
+
levelCount: 0,
|
|
179
|
+
levelDimensions: [],
|
|
180
|
+
downsamplePassCount: 0,
|
|
181
|
+
upsamplePassCount: 0,
|
|
182
|
+
targetCount: 0,
|
|
183
|
+
targetBytes: 0,
|
|
184
|
+
resourceCount: 0,
|
|
185
|
+
passCount: 0,
|
|
186
|
+
encodeCount: 0,
|
|
187
|
+
bindGroupCount: 0,
|
|
188
|
+
uploadCount: 0,
|
|
189
|
+
residentChildBytes: 0,
|
|
190
|
+
generation: 0,
|
|
191
|
+
state: "off"
|
|
192
|
+
});
|
|
193
|
+
function projectBarrelDistortionInspection(input) {
|
|
194
|
+
const effectiveMapping = input.effectiveMapping === void 0 ? void 0 : freezeBarrelDistortionMapping(input.effectiveMapping);
|
|
195
|
+
return Object.freeze({
|
|
196
|
+
effectiveMapping,
|
|
197
|
+
extent: effectiveMapping === void 0 ? void 0 : Object.freeze({ width: effectiveMapping.width, height: effectiveMapping.height }),
|
|
198
|
+
frameId: input.frameId,
|
|
199
|
+
deviceGeneration: input.deviceGeneration,
|
|
200
|
+
graphGeneration: input.graphGeneration,
|
|
201
|
+
lastKnownGood: input.lastKnownGood
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/prepare/extended-lighting/state.ts
|
|
206
|
+
function createExtendedLightingState(generation) {
|
|
207
|
+
return {
|
|
208
|
+
enabled: false,
|
|
209
|
+
generation,
|
|
210
|
+
status: "empty",
|
|
211
|
+
candidate: void 0,
|
|
212
|
+
accepted: void 0,
|
|
213
|
+
lastKnownGood: void 0,
|
|
214
|
+
resourceCount: 0,
|
|
215
|
+
descriptorBytes: 0,
|
|
216
|
+
uploadCount: 0,
|
|
217
|
+
passCount: 0,
|
|
218
|
+
bindGroupCount: 0,
|
|
219
|
+
failure: void 0,
|
|
220
|
+
failureKeys: []
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function promoteExtendedLightingCandidate(state, candidate) {
|
|
224
|
+
if (candidate === void 0) return state;
|
|
225
|
+
return {
|
|
226
|
+
...state,
|
|
227
|
+
enabled: true,
|
|
228
|
+
generation: candidate.generation,
|
|
229
|
+
status: "accepted",
|
|
230
|
+
candidate: void 0,
|
|
231
|
+
accepted: candidate,
|
|
232
|
+
lastKnownGood: candidate,
|
|
233
|
+
resourceCount: 4,
|
|
234
|
+
descriptorBytes: candidate.descriptorBytes,
|
|
235
|
+
uploadCount: candidate.uploadCount
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function recordExtendedLightingFailure(state, failure2) {
|
|
239
|
+
const key = `${failure2.detail.entity}:${failure2.detail.feature}:${failure2.detail.generation}`;
|
|
240
|
+
if (state.failureKeys.includes(key)) return state;
|
|
241
|
+
return {
|
|
242
|
+
...state,
|
|
243
|
+
failure: failure2,
|
|
244
|
+
failureKeys: [...state.failureKeys, key]
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function projectExtendedLightingInspection(state) {
|
|
248
|
+
const identity = (candidate) => candidate === void 0 ? void 0 : lightInspectionIdentity(candidate.topology, candidate.generation);
|
|
249
|
+
return projectLightInspection({
|
|
250
|
+
generation: state.generation,
|
|
251
|
+
candidate: identity(state.candidate),
|
|
252
|
+
accepted: identity(state.accepted),
|
|
253
|
+
lastKnownGood: identity(state.lastKnownGood),
|
|
254
|
+
failure: state.failure?.code,
|
|
255
|
+
failureKeys: state.failureKeys,
|
|
256
|
+
resourceCount: state.resourceCount,
|
|
257
|
+
uploadBytes: state.descriptorBytes
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
var SSR_HISTORY_FORMAT = "rgba16float";
|
|
261
|
+
var HISTORY_USAGE = GPU_TEXTURE_USAGE_COPY_SRC | GPU_TEXTURE_USAGE_COPY_DST | GPU_TEXTURE_USAGE_TEXTURE_BINDING | GPU_TEXTURE_USAGE_STORAGE_BINDING | GPU_TEXTURE_USAGE_RENDER_ATTACHMENT;
|
|
262
|
+
function positiveDimension(value, field) {
|
|
263
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
264
|
+
throw new RangeError(`${field} must be a positive safe integer.`);
|
|
265
|
+
}
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
function createResources(device, scope, width, height) {
|
|
269
|
+
const halfWidth = Math.max(1, Math.floor(width / 2));
|
|
270
|
+
const halfHeight = Math.max(1, Math.floor(height / 2));
|
|
271
|
+
const descriptor = {
|
|
272
|
+
label: "ssr-history-rgba16float",
|
|
273
|
+
size: { width: halfWidth, height: halfHeight, depthOrArrayLayers: 1 },
|
|
274
|
+
format: SSR_HISTORY_FORMAT,
|
|
275
|
+
usage: HISTORY_USAGE,
|
|
276
|
+
mipLevelCount: 1,
|
|
277
|
+
sampleCount: 1,
|
|
278
|
+
dimension: "2d",
|
|
279
|
+
viewFormats: void 0,
|
|
280
|
+
textureBindingViewDimension: void 0
|
|
281
|
+
};
|
|
282
|
+
const childScope = scope.createChild(`${scope.owner}:ssr-history`);
|
|
283
|
+
const slots = [];
|
|
284
|
+
for (const index of [0, 1]) {
|
|
285
|
+
const texture2 = device.createTexture({
|
|
286
|
+
...descriptor,
|
|
287
|
+
label: `ssr-history-rgba16float-${index}`
|
|
288
|
+
});
|
|
289
|
+
if (!texture2.ok) {
|
|
290
|
+
childScope.abandon();
|
|
291
|
+
return texture2;
|
|
292
|
+
}
|
|
293
|
+
childScope._adopt("texture", texture2.value, (value) => {
|
|
294
|
+
device.destroyTexture(value);
|
|
295
|
+
});
|
|
296
|
+
const view = device.createTextureView(texture2.value, {
|
|
297
|
+
label: `ssr-history-rgba16float-${index}.view`,
|
|
298
|
+
dimension: "2d",
|
|
299
|
+
baseMipLevel: 0,
|
|
300
|
+
mipLevelCount: 1,
|
|
301
|
+
baseArrayLayer: 0,
|
|
302
|
+
arrayLayerCount: 1
|
|
303
|
+
});
|
|
304
|
+
if (!view.ok) {
|
|
305
|
+
childScope.abandon();
|
|
306
|
+
return view;
|
|
307
|
+
}
|
|
308
|
+
const surfaceTexture = device.createTexture({
|
|
309
|
+
...descriptor,
|
|
310
|
+
label: `ssr-history-surface-${index}`,
|
|
311
|
+
format: "rgba8unorm"
|
|
312
|
+
});
|
|
313
|
+
if (!surfaceTexture.ok) {
|
|
314
|
+
childScope.abandon();
|
|
315
|
+
return surfaceTexture;
|
|
316
|
+
}
|
|
317
|
+
childScope._adopt("texture", surfaceTexture.value, (value) => {
|
|
318
|
+
device.destroyTexture(value);
|
|
319
|
+
});
|
|
320
|
+
const surfaceView = device.createTextureView(surfaceTexture.value, { dimension: "2d" });
|
|
321
|
+
if (!surfaceView.ok) {
|
|
322
|
+
childScope.abandon();
|
|
323
|
+
return surfaceView;
|
|
324
|
+
}
|
|
325
|
+
slots.push({
|
|
326
|
+
texture: texture2.value,
|
|
327
|
+
view: view.value,
|
|
328
|
+
surfaceTexture: surfaceTexture.value,
|
|
329
|
+
surfaceView: surfaceView.value
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
const params = device.createBuffer({
|
|
333
|
+
label: "ssr-temporal-params",
|
|
334
|
+
size: 32,
|
|
335
|
+
usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
|
|
336
|
+
mappedAtCreation: false
|
|
337
|
+
});
|
|
338
|
+
if (!params.ok) {
|
|
339
|
+
childScope.abandon();
|
|
340
|
+
return params;
|
|
341
|
+
}
|
|
342
|
+
childScope._adopt("buffer", params.value, (value) => {
|
|
343
|
+
device.destroyBuffer(value);
|
|
344
|
+
});
|
|
345
|
+
const cleared = device.queue.writeBuffer(params.value, 0, new Uint8Array(32));
|
|
346
|
+
if (!cleared.ok) {
|
|
347
|
+
childScope.abandon();
|
|
348
|
+
return cleared;
|
|
349
|
+
}
|
|
350
|
+
return ok({
|
|
351
|
+
width,
|
|
352
|
+
height,
|
|
353
|
+
halfWidth,
|
|
354
|
+
halfHeight,
|
|
355
|
+
bytes: halfWidth * halfHeight * 12 * 2,
|
|
356
|
+
descriptor,
|
|
357
|
+
slots,
|
|
358
|
+
paramsBuffer: params.value,
|
|
359
|
+
childScope
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
function failure(code, expected, hint, detail) {
|
|
363
|
+
return { code, expected, hint, ...{} };
|
|
364
|
+
}
|
|
365
|
+
var SsrHistoryOwner = class _SsrHistoryOwner {
|
|
366
|
+
constructor(device, scope, resources) {
|
|
367
|
+
this.device = device;
|
|
368
|
+
this.scope = scope;
|
|
369
|
+
this.activeResources = resources;
|
|
370
|
+
}
|
|
371
|
+
device;
|
|
372
|
+
scope;
|
|
373
|
+
activeResources;
|
|
374
|
+
retiringResources = /* @__PURE__ */ new Set();
|
|
375
|
+
retirementFencePending = false;
|
|
376
|
+
activeCandidate;
|
|
377
|
+
readIndex = 0;
|
|
378
|
+
valid = false;
|
|
379
|
+
state = "first-frame";
|
|
380
|
+
resetReason = "first-enable";
|
|
381
|
+
lastFailure;
|
|
382
|
+
resetCount = 0;
|
|
383
|
+
static create(input) {
|
|
384
|
+
const width = positiveDimension(input.width, "width");
|
|
385
|
+
const height = positiveDimension(input.height, "height");
|
|
386
|
+
const resources = createResources(input.device, input.scope, width, height);
|
|
387
|
+
return resources.ok ? ok(new _SsrHistoryOwner(input.device, input.scope, resources.value)) : resources;
|
|
388
|
+
}
|
|
389
|
+
get resources() {
|
|
390
|
+
if (this.activeResources === void 0) throw new Error("SSR history resources are retired.");
|
|
391
|
+
return this.activeResources;
|
|
392
|
+
}
|
|
393
|
+
beginFrame() {
|
|
394
|
+
if (this.activeResources === void 0 || this.state === "retired" || this.state === "disposed") {
|
|
395
|
+
return err(
|
|
396
|
+
failure(
|
|
397
|
+
"ssr-history-retired",
|
|
398
|
+
"SSR history resources are active",
|
|
399
|
+
"recreate the renderer-owned SSR history after device recovery"
|
|
400
|
+
)
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
if (this.activeCandidate !== void 0) {
|
|
404
|
+
return err(
|
|
405
|
+
failure(
|
|
406
|
+
"ssr-history-active",
|
|
407
|
+
"one SSR history candidate is active",
|
|
408
|
+
"commit or abort the active SSR history candidate before beginning another"
|
|
409
|
+
)
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
const writeSlot = this.valid ? this.readIndex === 0 ? 1 : 0 : 0;
|
|
413
|
+
const candidate = Object.freeze({
|
|
414
|
+
readSlot: this.valid ? this.readIndex : null,
|
|
415
|
+
writeSlot,
|
|
416
|
+
historyValid: this.valid && this.resetReason === void 0
|
|
417
|
+
});
|
|
418
|
+
this.activeCandidate = candidate;
|
|
419
|
+
this.state = this.valid ? "stable" : this.state === "aborted" ? "aborted" : "first-frame";
|
|
420
|
+
this.lastFailure = void 0;
|
|
421
|
+
return ok(candidate);
|
|
422
|
+
}
|
|
423
|
+
commitFrame(candidate) {
|
|
424
|
+
if (this.activeCandidate !== candidate) {
|
|
425
|
+
return err(
|
|
426
|
+
failure(
|
|
427
|
+
"ssr-history-not-active",
|
|
428
|
+
"commit the candidate returned by beginFrame",
|
|
429
|
+
"keep one candidate from beginFrame until the shared queue submit succeeds"
|
|
430
|
+
)
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
this.readIndex = candidate.writeSlot;
|
|
434
|
+
this.valid = true;
|
|
435
|
+
this.activeCandidate = void 0;
|
|
436
|
+
this.state = "stable";
|
|
437
|
+
this.resetReason = void 0;
|
|
438
|
+
this.lastFailure = void 0;
|
|
439
|
+
return ok(void 0);
|
|
440
|
+
}
|
|
441
|
+
abortFrame(candidate, stage) {
|
|
442
|
+
if (this.activeCandidate !== candidate) return;
|
|
443
|
+
this.activeCandidate = void 0;
|
|
444
|
+
this.state = "aborted";
|
|
445
|
+
this.lastFailure = stage;
|
|
446
|
+
}
|
|
447
|
+
reset(reason) {
|
|
448
|
+
this.activeCandidate = void 0;
|
|
449
|
+
this.valid = false;
|
|
450
|
+
this.readIndex = 0;
|
|
451
|
+
this.resetReason = reason;
|
|
452
|
+
this.lastFailure = void 0;
|
|
453
|
+
this.resetCount += 1;
|
|
454
|
+
if (this.activeResources !== void 0) this.state = "reset";
|
|
455
|
+
}
|
|
456
|
+
resize(width, height) {
|
|
457
|
+
const next = createResources(
|
|
458
|
+
this.device,
|
|
459
|
+
this.scope,
|
|
460
|
+
positiveDimension(width, "width"),
|
|
461
|
+
positiveDimension(height, "height")
|
|
462
|
+
);
|
|
463
|
+
if (!next.ok) return next;
|
|
464
|
+
if (this.activeResources !== void 0) this.retiringResources.add(this.activeResources);
|
|
465
|
+
this.activeResources = next.value;
|
|
466
|
+
this.activeCandidate = void 0;
|
|
467
|
+
this.valid = false;
|
|
468
|
+
this.readIndex = 0;
|
|
469
|
+
this.resetReason = "resize";
|
|
470
|
+
this.lastFailure = void 0;
|
|
471
|
+
this.resetCount += 1;
|
|
472
|
+
this.state = "reset";
|
|
473
|
+
return ok(void 0);
|
|
474
|
+
}
|
|
475
|
+
/** Retire pending allocations after the queue's already-submitted work. */
|
|
476
|
+
retireAfterFence(queue, onFailure) {
|
|
477
|
+
if (this.retirementFencePending) return;
|
|
478
|
+
if (this.retiringResources.size === 0 && this.activeResources !== void 0) {
|
|
479
|
+
this.retiringResources.add(this.activeResources);
|
|
480
|
+
this.activeCandidate = void 0;
|
|
481
|
+
this.valid = false;
|
|
482
|
+
this.state = "retiring";
|
|
483
|
+
}
|
|
484
|
+
const pending = [...this.retiringResources];
|
|
485
|
+
for (const resources of pending) resources.childScope.beginRetire();
|
|
486
|
+
if (pending.length === 0) return;
|
|
487
|
+
this.retirementFencePending = true;
|
|
488
|
+
const finish = () => {
|
|
489
|
+
this.retirementFencePending = false;
|
|
490
|
+
for (const resources of pending) {
|
|
491
|
+
this.retiringResources.delete(resources);
|
|
492
|
+
resources.childScope.retire();
|
|
493
|
+
if (this.activeResources === resources) this.activeResources = void 0;
|
|
494
|
+
}
|
|
495
|
+
if (this.activeResources === void 0 && this.retiringResources.size === 0) {
|
|
496
|
+
this.state = "retired";
|
|
497
|
+
}
|
|
498
|
+
if (this.retiringResources.size > 0) this.retireAfterFence(queue, onFailure);
|
|
499
|
+
};
|
|
500
|
+
void queue.onSubmittedWorkDone().then(finish, (cause) => {
|
|
501
|
+
onFailure(cause);
|
|
502
|
+
finish();
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
retire() {
|
|
506
|
+
this.retirementFencePending = false;
|
|
507
|
+
this.activeCandidate = void 0;
|
|
508
|
+
if (this.activeResources !== void 0) {
|
|
509
|
+
this.activeResources.childScope.retire();
|
|
510
|
+
this.activeResources = void 0;
|
|
511
|
+
}
|
|
512
|
+
for (const resources of this.retiringResources) resources.childScope.retire();
|
|
513
|
+
this.retiringResources.clear();
|
|
514
|
+
this.valid = false;
|
|
515
|
+
this.state = "retired";
|
|
516
|
+
}
|
|
517
|
+
dispose() {
|
|
518
|
+
if (this.state === "disposed") return;
|
|
519
|
+
this.retire();
|
|
520
|
+
this.state = "disposed";
|
|
521
|
+
}
|
|
522
|
+
inspect() {
|
|
523
|
+
const active = this.activeResources;
|
|
524
|
+
return Object.freeze({
|
|
525
|
+
state: this.state,
|
|
526
|
+
width: active?.width ?? 0,
|
|
527
|
+
height: active?.height ?? 0,
|
|
528
|
+
halfWidth: active?.halfWidth ?? 0,
|
|
529
|
+
halfHeight: active?.halfHeight ?? 0,
|
|
530
|
+
format: SSR_HISTORY_FORMAT,
|
|
531
|
+
historyCount: active === void 0 ? 0 : 2,
|
|
532
|
+
historyValid: this.valid,
|
|
533
|
+
readSlot: active === void 0 ? null : this.valid ? this.readIndex : null,
|
|
534
|
+
writeSlot: active === void 0 ? 0 : this.valid ? this.readIndex === 0 ? 1 : 0 : 0,
|
|
535
|
+
activeBytes: active?.bytes ?? 0,
|
|
536
|
+
candidateBytes: 0,
|
|
537
|
+
retiringBytes: [...this.retiringResources].filter((entry) => entry !== active).reduce((sum, entry) => sum + entry.bytes, 0),
|
|
538
|
+
resetCount: this.resetCount,
|
|
539
|
+
resetReason: this.resetReason,
|
|
540
|
+
lastFailure: this.lastFailure
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
function createSsrHistoryOwner(input) {
|
|
545
|
+
return SsrHistoryOwner.create(input);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/scene/visibility/occlusion-query-pool.ts
|
|
549
|
+
var OCCLUSION_QUERY_PAGE_COUNT = 3;
|
|
550
|
+
var OCCLUSION_QUERY_PAGE_INDEX_LIMIT = 4096;
|
|
551
|
+
var OCCLUSION_QUERY_RESULT_BYTES = 8;
|
|
552
|
+
var OCCLUSION_QUERY_PAGE_BYTES = OCCLUSION_QUERY_PAGE_INDEX_LIMIT * OCCLUSION_QUERY_RESULT_BYTES;
|
|
553
|
+
function identityKey(identity) {
|
|
554
|
+
return [
|
|
555
|
+
identity.viewKey,
|
|
556
|
+
identity.attachmentId,
|
|
557
|
+
identity.deviceGeneration,
|
|
558
|
+
identity.worldGeneration,
|
|
559
|
+
identity.primitiveSlot,
|
|
560
|
+
identity.slotGeneration
|
|
561
|
+
].join("|");
|
|
562
|
+
}
|
|
563
|
+
var OcclusionQueryPool = class {
|
|
564
|
+
pages = Array.from(
|
|
565
|
+
{ length: OCCLUSION_QUERY_PAGE_COUNT },
|
|
566
|
+
(_, index) => ({ index, nextQueryIndex: 0, pendingCount: 0, inFlightCount: 0 })
|
|
567
|
+
);
|
|
568
|
+
pending = /* @__PURE__ */ new Map();
|
|
569
|
+
tickets = /* @__PURE__ */ new Map();
|
|
570
|
+
nextReservationId = 1;
|
|
571
|
+
disposed = false;
|
|
572
|
+
reserve(identity) {
|
|
573
|
+
if (this.disposed) return void 0;
|
|
574
|
+
const page = this.pages.find(
|
|
575
|
+
(candidate) => candidate.inFlightCount === 0 && candidate.nextQueryIndex < OCCLUSION_QUERY_PAGE_INDEX_LIMIT
|
|
576
|
+
);
|
|
577
|
+
if (page === void 0 || page.nextQueryIndex >= OCCLUSION_QUERY_PAGE_INDEX_LIMIT) {
|
|
578
|
+
return void 0;
|
|
579
|
+
}
|
|
580
|
+
const reservation = Object.freeze({
|
|
581
|
+
...identity,
|
|
582
|
+
pageIndex: page.index,
|
|
583
|
+
queryIndex: page.nextQueryIndex,
|
|
584
|
+
reservationId: this.nextReservationId++
|
|
585
|
+
});
|
|
586
|
+
page.nextQueryIndex += 1;
|
|
587
|
+
page.pendingCount += 1;
|
|
588
|
+
this.pending.set(reservation.reservationId, reservation);
|
|
589
|
+
return reservation;
|
|
590
|
+
}
|
|
591
|
+
publish(reservation, result) {
|
|
592
|
+
const pending = this.pending.get(reservation.reservationId);
|
|
593
|
+
if (pending === void 0 || this.disposed) {
|
|
594
|
+
return void 0;
|
|
595
|
+
}
|
|
596
|
+
if (!result.submitted) return void 0;
|
|
597
|
+
this.pending.delete(reservation.reservationId);
|
|
598
|
+
const page = this.pages[pending.pageIndex];
|
|
599
|
+
if (page !== void 0) {
|
|
600
|
+
page.pendingCount = Math.max(0, page.pendingCount - 1);
|
|
601
|
+
page.inFlightCount += 1;
|
|
602
|
+
}
|
|
603
|
+
const ticket = Object.freeze({ ...pending, submissionGeneration: result.submissionGeneration });
|
|
604
|
+
this.tickets.set(ticket.reservationId, ticket);
|
|
605
|
+
return ticket;
|
|
606
|
+
}
|
|
607
|
+
/** Return an unsubmitted reservation to its bounded page. */
|
|
608
|
+
cancel(reservation) {
|
|
609
|
+
const pending = this.pending.get(reservation.reservationId);
|
|
610
|
+
if (pending === void 0) return false;
|
|
611
|
+
this.pending.delete(reservation.reservationId);
|
|
612
|
+
const page = this.pages[pending.pageIndex];
|
|
613
|
+
if (page !== void 0) {
|
|
614
|
+
page.pendingCount = Math.max(0, page.pendingCount - 1);
|
|
615
|
+
this.releasePage(pending.pageIndex);
|
|
616
|
+
}
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
complete(ticket, _samples) {
|
|
620
|
+
const current = this.tickets.get(ticket.reservationId);
|
|
621
|
+
if (current !== void 0) {
|
|
622
|
+
this.tickets.delete(ticket.reservationId);
|
|
623
|
+
const page = this.pages[current.pageIndex];
|
|
624
|
+
if (page !== void 0) page.inFlightCount = Math.max(0, page.inFlightCount - 1);
|
|
625
|
+
this.releasePage(current.pageIndex);
|
|
626
|
+
}
|
|
627
|
+
if (this.disposed || current === void 0 || identityKey(current) !== identityKey(ticket)) {
|
|
628
|
+
return { status: "stale", visible: true };
|
|
629
|
+
}
|
|
630
|
+
return { status: "accepted", visible: true };
|
|
631
|
+
}
|
|
632
|
+
inspect() {
|
|
633
|
+
return {
|
|
634
|
+
pageCount: OCCLUSION_QUERY_PAGE_COUNT,
|
|
635
|
+
pageIndexLimit: OCCLUSION_QUERY_PAGE_INDEX_LIMIT,
|
|
636
|
+
resolveBytes: OCCLUSION_QUERY_PAGE_BYTES,
|
|
637
|
+
stagingBytes: OCCLUSION_QUERY_PAGE_BYTES,
|
|
638
|
+
availablePages: this.disposed ? 0 : this.pages.filter(
|
|
639
|
+
(page) => page.inFlightCount === 0 && page.nextQueryIndex < OCCLUSION_QUERY_PAGE_INDEX_LIMIT
|
|
640
|
+
).length,
|
|
641
|
+
inFlight: this.tickets.size,
|
|
642
|
+
disposed: this.disposed
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
dispose() {
|
|
646
|
+
this.disposed = true;
|
|
647
|
+
this.pending.clear();
|
|
648
|
+
this.tickets.clear();
|
|
649
|
+
for (const page of this.pages) {
|
|
650
|
+
page.pendingCount = 0;
|
|
651
|
+
page.inFlightCount = 0;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
releasePage(pageIndex) {
|
|
655
|
+
const page = this.pages[pageIndex];
|
|
656
|
+
if (page !== void 0 && page.pendingCount === 0 && page.inFlightCount === 0) {
|
|
657
|
+
page.nextQueryIndex = 0;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
// src/scene/visibility/occlusion-pass.ts
|
|
663
|
+
function createOcclusionQueryResources(device, pageIndex) {
|
|
664
|
+
const querySet = device.createQuerySet({
|
|
665
|
+
label: `occlusion-page-${pageIndex}`,
|
|
666
|
+
type: "occlusion",
|
|
667
|
+
count: OCCLUSION_QUERY_PAGE_INDEX_LIMIT
|
|
668
|
+
});
|
|
669
|
+
if (!querySet.ok) return querySet;
|
|
670
|
+
const resolveBuffer = device.createBuffer({
|
|
671
|
+
label: `occlusion-page-${pageIndex}-resolve`,
|
|
672
|
+
size: OCCLUSION_QUERY_PAGE_BYTES,
|
|
673
|
+
usage: GPU_BUFFER_USAGE_QUERY_RESOLVE | GPU_BUFFER_USAGE_COPY_SRC
|
|
674
|
+
});
|
|
675
|
+
if (!resolveBuffer.ok) {
|
|
676
|
+
device.destroyQuerySet(querySet.value);
|
|
677
|
+
return resolveBuffer;
|
|
678
|
+
}
|
|
679
|
+
const stagingBuffer = device.createBuffer({
|
|
680
|
+
label: `occlusion-page-${pageIndex}-staging`,
|
|
681
|
+
size: OCCLUSION_QUERY_PAGE_BYTES,
|
|
682
|
+
usage: GPU_BUFFER_USAGE_MAP_READ | GPU_BUFFER_USAGE_COPY_DST
|
|
683
|
+
});
|
|
684
|
+
if (!stagingBuffer.ok) {
|
|
685
|
+
device.destroyBuffer(resolveBuffer.value);
|
|
686
|
+
device.destroyQuerySet(querySet.value);
|
|
687
|
+
return stagingBuffer;
|
|
688
|
+
}
|
|
689
|
+
return ok$1({
|
|
690
|
+
pageIndex,
|
|
691
|
+
querySet: querySet.value,
|
|
692
|
+
resolveBuffer: resolveBuffer.value,
|
|
693
|
+
stagingBuffer: stagingBuffer.value
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
function recordOcclusionResolve(encoder, resources, firstQuery, queryCount) {
|
|
697
|
+
if (!Number.isSafeInteger(firstQuery) || !Number.isSafeInteger(queryCount) || firstQuery < 0 || queryCount <= 0 || firstQuery + queryCount > OCCLUSION_QUERY_PAGE_INDEX_LIMIT) {
|
|
698
|
+
return err$1(
|
|
699
|
+
new RhiError({
|
|
700
|
+
code: "webgpu-runtime-error",
|
|
701
|
+
expected: "query range stays within one 4096-index page",
|
|
702
|
+
hint: "allocate a bounded page before recording occlusion resolve"
|
|
703
|
+
})
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
const resolved = encoder.resolveQuerySet(
|
|
707
|
+
resources.querySet,
|
|
708
|
+
firstQuery,
|
|
709
|
+
queryCount,
|
|
710
|
+
resources.resolveBuffer,
|
|
711
|
+
0
|
|
712
|
+
);
|
|
713
|
+
if (!resolved.ok) return resolved;
|
|
714
|
+
encoder.copyBufferToBuffer(resources.resolveBuffer, resources.stagingBuffer, queryCount * 8);
|
|
715
|
+
return ok$1(void 0);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/scene/visibility/types.ts
|
|
719
|
+
function viewKeyId(key) {
|
|
720
|
+
return `${key.attachmentId}:${key.cameraEntity}:${key.viewRole}:${key.viewGeneration}`;
|
|
721
|
+
}
|
|
722
|
+
function primitiveKeyId(key) {
|
|
723
|
+
return `${key.attachmentId}:${key.worldGeneration}:${key.primitiveSlot}:${key.slotGeneration}`;
|
|
724
|
+
}
|
|
725
|
+
function viewKey(input) {
|
|
726
|
+
return Object.freeze({ ...input });
|
|
727
|
+
}
|
|
728
|
+
function primitiveKey(input) {
|
|
729
|
+
return Object.freeze({ ...input });
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// src/scene/visibility/occlusion-runtime.ts
|
|
733
|
+
var PROXY_VERTEX_FLOATS_PER_CANDIDATE = 36 * 4;
|
|
734
|
+
var PROXY_GEOMETRY_BYTES = OCCLUSION_QUERY_PAGE_INDEX_LIMIT * PROXY_VERTEX_FLOATS_PER_CANDIDATE * 4;
|
|
735
|
+
var runtimeTestHooks;
|
|
736
|
+
function setOcclusionRuntimeTestHooks(hooks) {
|
|
737
|
+
runtimeTestHooks = hooks;
|
|
738
|
+
}
|
|
739
|
+
var OcclusionRenderRuntime = class {
|
|
740
|
+
constructor(device, facets, shaderModuleFactory, budget = facets.visibilityBudget()) {
|
|
741
|
+
this.device = device;
|
|
742
|
+
this.facets = facets;
|
|
743
|
+
this.shaderModuleFactory = shaderModuleFactory;
|
|
744
|
+
this.budget = budget;
|
|
745
|
+
const runtime = this;
|
|
746
|
+
this.proxyScratch = new Float32Array(
|
|
747
|
+
Math.min(this.budget.effectiveQueryBudget, OCCLUSION_QUERY_PAGE_INDEX_LIMIT) * PROXY_VERTEX_FLOATS_PER_CANDIDATE
|
|
748
|
+
);
|
|
749
|
+
this.projection = {
|
|
750
|
+
get querySet() {
|
|
751
|
+
return runtime.pendingFrame?.resources.querySet;
|
|
752
|
+
},
|
|
753
|
+
get queryIndex() {
|
|
754
|
+
return runtime.pendingFrame?.reservations[0]?.queryIndex ?? 0;
|
|
755
|
+
},
|
|
756
|
+
get queryCount() {
|
|
757
|
+
return runtime.pendingFrame?.reservations.length ?? 0;
|
|
758
|
+
},
|
|
759
|
+
get sampleCount() {
|
|
760
|
+
return runtime.pendingFrame?.sampleCount ?? 1;
|
|
761
|
+
},
|
|
762
|
+
get pageIndex() {
|
|
763
|
+
return runtime.pendingFrame?.reservations[0]?.pageIndex ?? 0;
|
|
764
|
+
},
|
|
765
|
+
get reservationId() {
|
|
766
|
+
return runtime.pendingFrame?.reservations[0]?.reservationId ?? 0;
|
|
767
|
+
},
|
|
768
|
+
encodeProxyBounds: (pass) => {
|
|
769
|
+
const pending = runtime.pendingFrame;
|
|
770
|
+
return pending === void 0 ? err$1(runtimeError("the current RenderSystem frame owns the occlusion candidate")) : runtime.encodeProxyBounds(pass, pending);
|
|
771
|
+
},
|
|
772
|
+
beginCandidate: (pass) => {
|
|
773
|
+
const pending = runtime.pendingFrame;
|
|
774
|
+
if (pending === void 0 || pending.endedCount >= pending.reservations.length) {
|
|
775
|
+
return err$1(runtimeError("the current RenderSystem frame owns the occlusion candidate"));
|
|
776
|
+
}
|
|
777
|
+
return pass.beginOcclusionQuery(pending.reservations[pending.endedCount]?.queryIndex ?? 0);
|
|
778
|
+
},
|
|
779
|
+
endCandidate: (pass) => {
|
|
780
|
+
const pending = runtime.pendingFrame;
|
|
781
|
+
if (pending === void 0 || pending.endedCount >= pending.reservations.length) {
|
|
782
|
+
return err$1(runtimeError("the current RenderSystem frame owns the occlusion candidate"));
|
|
783
|
+
}
|
|
784
|
+
const ended = pass.endOcclusionQuery();
|
|
785
|
+
if (ended.ok) pending.endedCount += 1;
|
|
786
|
+
return ended;
|
|
787
|
+
},
|
|
788
|
+
recordResolve: (encoder) => {
|
|
789
|
+
const pending = runtime.pendingFrame;
|
|
790
|
+
if (pending === void 0 || pending.endedCount !== pending.reservations.length) {
|
|
791
|
+
return err$1(runtimeError("the real geometry pass must end its occlusion query first"));
|
|
792
|
+
}
|
|
793
|
+
return recordOcclusionResolve(
|
|
794
|
+
encoder,
|
|
795
|
+
pending.resources,
|
|
796
|
+
pending.reservations[0]?.queryIndex ?? 0,
|
|
797
|
+
pending.reservations.length
|
|
798
|
+
);
|
|
799
|
+
},
|
|
800
|
+
commit: (submitted, submissionGeneration, profilePhase) => {
|
|
801
|
+
const pending = runtime.pendingFrame;
|
|
802
|
+
if (pending !== void 0)
|
|
803
|
+
runtime.commit(pending, submitted, submissionGeneration, profilePhase);
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
device;
|
|
808
|
+
facets;
|
|
809
|
+
shaderModuleFactory;
|
|
810
|
+
budget;
|
|
811
|
+
pool = new OcclusionQueryPool();
|
|
812
|
+
resources = /* @__PURE__ */ new Map();
|
|
813
|
+
activeCompletions = /* @__PURE__ */ new Map();
|
|
814
|
+
retiredResources = /* @__PURE__ */ new Set();
|
|
815
|
+
/** Candidate descriptors are rebuilt only when the extracted frame changes. */
|
|
816
|
+
candidateSource;
|
|
817
|
+
validCandidates = [];
|
|
818
|
+
candidateByKey = /* @__PURE__ */ new Map();
|
|
819
|
+
lastPrepareUnavailable = false;
|
|
820
|
+
pendingFrame;
|
|
821
|
+
disposed = false;
|
|
822
|
+
proxyBuffer;
|
|
823
|
+
proxyScratch;
|
|
824
|
+
proxyPipeline;
|
|
825
|
+
proxyPipelineSampleCount;
|
|
826
|
+
queryLatencySamplesUs = [];
|
|
827
|
+
lastQueryLatencyUs = 0;
|
|
828
|
+
projection;
|
|
829
|
+
prepare(candidate) {
|
|
830
|
+
return this.prepareBatch([candidate], 1, false);
|
|
831
|
+
}
|
|
832
|
+
prepareBatch(candidates, sampleCount = 1, useReadyQueue = true) {
|
|
833
|
+
this.lastPrepareUnavailable = false;
|
|
834
|
+
if (this.disposed || candidates.length === 0) return void 0;
|
|
835
|
+
if (this.pendingFrame !== void 0) {
|
|
836
|
+
this.lastPrepareUnavailable = true;
|
|
837
|
+
return void 0;
|
|
838
|
+
}
|
|
839
|
+
if (this.candidateSource !== candidates) {
|
|
840
|
+
this.candidateSource = candidates;
|
|
841
|
+
this.candidateByKey.clear();
|
|
842
|
+
const valid2 = [];
|
|
843
|
+
for (const candidate of candidates) {
|
|
844
|
+
this.candidateByKey.set(candidateKey(candidate.view, candidate.primitive), candidate);
|
|
845
|
+
if (finiteBounds(candidate.bounds)) valid2.push(candidate);
|
|
846
|
+
}
|
|
847
|
+
this.validCandidates = valid2;
|
|
848
|
+
}
|
|
849
|
+
const valid = this.validCandidates;
|
|
850
|
+
if (valid.length === 0) return void 0;
|
|
851
|
+
let selectedCandidates;
|
|
852
|
+
if (!useReadyQueue) {
|
|
853
|
+
selectedCandidates = valid.slice(0, this.budget.effectiveQueryBudget);
|
|
854
|
+
} else {
|
|
855
|
+
const firstView = valid[0]?.view;
|
|
856
|
+
if (firstView === void 0) return void 0;
|
|
857
|
+
selectedCandidates = [];
|
|
858
|
+
for (const entry of this.facets.dequeueQueryCandidates(
|
|
859
|
+
firstView,
|
|
860
|
+
this.budget.effectiveQueryBudget
|
|
861
|
+
)) {
|
|
862
|
+
const candidate = this.candidateByKey.get(candidateKey(entry.view, entry.primitive));
|
|
863
|
+
if (candidate === void 0 || !finiteBounds(candidate.bounds)) {
|
|
864
|
+
this.facets.requeueQueryCandidate(entry.view, entry.primitive);
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
selectedCandidates.push(candidate);
|
|
868
|
+
}
|
|
869
|
+
if (selectedCandidates.length === 0) return void 0;
|
|
870
|
+
}
|
|
871
|
+
const reservations = [];
|
|
872
|
+
let selectedCount = 0;
|
|
873
|
+
while (selectedCount < selectedCandidates.length) {
|
|
874
|
+
const candidate = selectedCandidates[selectedCount];
|
|
875
|
+
if (candidate === void 0) break;
|
|
876
|
+
const reservation = this.pool.reserve({
|
|
877
|
+
viewKey: viewKeyId(candidate.view),
|
|
878
|
+
attachmentId: candidate.view.attachmentId,
|
|
879
|
+
deviceGeneration: candidate.deviceGeneration,
|
|
880
|
+
worldGeneration: candidate.primitive.worldGeneration,
|
|
881
|
+
primitiveSlot: candidate.primitive.primitiveSlot,
|
|
882
|
+
slotGeneration: candidate.primitive.slotGeneration
|
|
883
|
+
});
|
|
884
|
+
if (reservation === void 0) {
|
|
885
|
+
this.lastPrepareUnavailable = true;
|
|
886
|
+
break;
|
|
887
|
+
}
|
|
888
|
+
if (reservations.length > 0 && reservation.pageIndex !== reservations[0]?.pageIndex) {
|
|
889
|
+
this.pool.cancel(reservation);
|
|
890
|
+
this.lastPrepareUnavailable = true;
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
reservations.push(reservation);
|
|
894
|
+
selectedCount += 1;
|
|
895
|
+
if (reservations.length >= this.budget.effectiveQueryBudget) break;
|
|
896
|
+
}
|
|
897
|
+
if (reservations.length === 0) {
|
|
898
|
+
for (const candidate of selectedCandidates) {
|
|
899
|
+
if (useReadyQueue) this.facets.requeueQueryCandidate(candidate.view, candidate.primitive);
|
|
900
|
+
}
|
|
901
|
+
return void 0;
|
|
902
|
+
}
|
|
903
|
+
if (reservations.length < selectedCandidates.length) {
|
|
904
|
+
for (const reservation of reservations) this.pool.cancel(reservation);
|
|
905
|
+
for (const candidate of selectedCandidates) {
|
|
906
|
+
if (useReadyQueue) this.facets.requeueQueryCandidate(candidate.view, candidate.primitive);
|
|
907
|
+
}
|
|
908
|
+
return void 0;
|
|
909
|
+
}
|
|
910
|
+
const resources = this.ensureResources(reservations[0]?.pageIndex ?? 0);
|
|
911
|
+
if (resources === void 0) {
|
|
912
|
+
for (const reservation of reservations) this.pool.cancel(reservation);
|
|
913
|
+
this.lastPrepareUnavailable = true;
|
|
914
|
+
for (const candidate of selectedCandidates) {
|
|
915
|
+
if (useReadyQueue) this.facets.requeueQueryCandidate(candidate.view, candidate.primitive);
|
|
916
|
+
}
|
|
917
|
+
return void 0;
|
|
918
|
+
}
|
|
919
|
+
const selected = selectedCandidates;
|
|
920
|
+
let proxyFloatCount = 0;
|
|
921
|
+
for (const candidate of selected) {
|
|
922
|
+
const vertices = candidate.proxyVertices;
|
|
923
|
+
if (vertices === void 0) continue;
|
|
924
|
+
if (proxyFloatCount + vertices.length > this.proxyScratch.length) {
|
|
925
|
+
for (const reservation of reservations) this.pool.cancel(reservation);
|
|
926
|
+
for (const queuedCandidate of selectedCandidates) {
|
|
927
|
+
if (useReadyQueue) {
|
|
928
|
+
this.facets.requeueQueryCandidate(queuedCandidate.view, queuedCandidate.primitive);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
this.lastPrepareUnavailable = true;
|
|
932
|
+
return void 0;
|
|
933
|
+
}
|
|
934
|
+
this.proxyScratch.set(vertices, proxyFloatCount);
|
|
935
|
+
proxyFloatCount += vertices.length;
|
|
936
|
+
}
|
|
937
|
+
const pending = {
|
|
938
|
+
reservations,
|
|
939
|
+
resources,
|
|
940
|
+
candidates: selected,
|
|
941
|
+
sampleCount,
|
|
942
|
+
endedCount: 0,
|
|
943
|
+
proxyFloatCount
|
|
944
|
+
};
|
|
945
|
+
this.pendingFrame = pending;
|
|
946
|
+
return this.projection;
|
|
947
|
+
}
|
|
948
|
+
inspect() {
|
|
949
|
+
return this.pool.inspect();
|
|
950
|
+
}
|
|
951
|
+
get prepareUnavailable() {
|
|
952
|
+
return this.lastPrepareUnavailable;
|
|
953
|
+
}
|
|
954
|
+
/** Map-only latency from the submitted query page; queue wait is excluded. */
|
|
955
|
+
inspectQueryLatency() {
|
|
956
|
+
if (this.queryLatencySamplesUs.length === 0) {
|
|
957
|
+
return { median: 0, p95: 0, last: this.lastQueryLatencyUs };
|
|
958
|
+
}
|
|
959
|
+
const sorted = [...this.queryLatencySamplesUs].sort((left, right) => left - right);
|
|
960
|
+
const rank = (percentile) => sorted[Math.max(0, Math.ceil(sorted.length * percentile) - 1)] ?? 0;
|
|
961
|
+
return {
|
|
962
|
+
median: rank(0.5),
|
|
963
|
+
p95: rank(0.95),
|
|
964
|
+
last: this.lastQueryLatencyUs
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Wait until query readbacks submitted before the current observation have
|
|
969
|
+
* settled. The renderer receipt only fences command submission; query
|
|
970
|
+
* completion also includes the asynchronous map/unmap transition. Keeping
|
|
971
|
+
* that fence here makes `renderer.observe()` publish the latest visibility
|
|
972
|
+
* result and measured map latency instead of racing the readback promise.
|
|
973
|
+
*/
|
|
974
|
+
async waitForCompletions() {
|
|
975
|
+
for (; ; ) {
|
|
976
|
+
const completions = [...this.activeCompletions.values()];
|
|
977
|
+
if (completions.length === 0) return;
|
|
978
|
+
await Promise.all(completions);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
/** Count a successful frame for hidden rows that were not queried this frame. */
|
|
982
|
+
advanceSuccessfulSubmit(submissionGeneration) {
|
|
983
|
+
this.facets.advanceSuccessfulSubmits(submissionGeneration);
|
|
984
|
+
}
|
|
985
|
+
dispose() {
|
|
986
|
+
if (this.disposed) return;
|
|
987
|
+
this.disposed = true;
|
|
988
|
+
this.pool.dispose();
|
|
989
|
+
for (const resource of this.resources.values()) {
|
|
990
|
+
const completion = this.activeCompletions.get(resource);
|
|
991
|
+
if (completion === void 0) {
|
|
992
|
+
this.destroyResources(resource);
|
|
993
|
+
} else {
|
|
994
|
+
void completion.then(() => this.destroyResources(resource));
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
this.resources.clear();
|
|
998
|
+
if (this.proxyBuffer !== void 0) this.device.destroyBuffer(this.proxyBuffer);
|
|
999
|
+
this.proxyBuffer = void 0;
|
|
1000
|
+
this.proxyPipeline = void 0;
|
|
1001
|
+
this.proxyPipelineSampleCount = void 0;
|
|
1002
|
+
this.candidateSource = void 0;
|
|
1003
|
+
this.validCandidates = [];
|
|
1004
|
+
this.candidateByKey.clear();
|
|
1005
|
+
this.lastPrepareUnavailable = false;
|
|
1006
|
+
this.pendingFrame = void 0;
|
|
1007
|
+
}
|
|
1008
|
+
ensureResources(pageIndex) {
|
|
1009
|
+
const existing = this.resources.get(pageIndex);
|
|
1010
|
+
if (existing !== void 0) return existing;
|
|
1011
|
+
const created = createOcclusionQueryResources(this.device, pageIndex);
|
|
1012
|
+
if (!created.ok) return void 0;
|
|
1013
|
+
this.resources.set(pageIndex, created.value);
|
|
1014
|
+
return created.value;
|
|
1015
|
+
}
|
|
1016
|
+
commit(pending, submitted, submissionGeneration, profilePhase) {
|
|
1017
|
+
if (this.pendingFrame !== pending) return;
|
|
1018
|
+
this.pendingFrame = void 0;
|
|
1019
|
+
const tickets = [];
|
|
1020
|
+
const accepted = profilePhase === void 0 ? this.publishAndApplySubmit(pending, submitted, submissionGeneration, tickets) : profilePhase(
|
|
1021
|
+
"record/occlusion-query-submit",
|
|
1022
|
+
() => this.publishAndApplySubmit(pending, submitted, submissionGeneration, tickets)
|
|
1023
|
+
);
|
|
1024
|
+
if (!accepted) {
|
|
1025
|
+
for (const reservation of pending.reservations) this.pool.cancel(reservation);
|
|
1026
|
+
for (const candidate of pending.candidates) {
|
|
1027
|
+
this.facets.applyConfidence(candidate.view, candidate.primitive, {
|
|
1028
|
+
type: "failure",
|
|
1029
|
+
submissionGeneration
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if (profilePhase === void 0) {
|
|
1035
|
+
this.facets.advanceSuccessfulSubmits(submissionGeneration);
|
|
1036
|
+
} else {
|
|
1037
|
+
profilePhase(
|
|
1038
|
+
"record/occlusion-global-advance",
|
|
1039
|
+
() => this.facets.advanceSuccessfulSubmits(submissionGeneration)
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
const completion = this.completeAfterSubmit(tickets, pending);
|
|
1043
|
+
this.activeCompletions.set(pending.resources, completion);
|
|
1044
|
+
void completion.then(() => {
|
|
1045
|
+
if (this.activeCompletions.get(pending.resources) === completion) {
|
|
1046
|
+
this.activeCompletions.delete(pending.resources);
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
publishAndApplySubmit(pending, submitted, submissionGeneration, tickets) {
|
|
1051
|
+
for (const reservation of pending.reservations) {
|
|
1052
|
+
const ticket = this.pool.publish(reservation, { submitted, submissionGeneration });
|
|
1053
|
+
if (ticket !== void 0) tickets.push(ticket);
|
|
1054
|
+
}
|
|
1055
|
+
if (!submitted || tickets.length !== pending.reservations.length) return false;
|
|
1056
|
+
const submitEvent = Object.freeze({ type: "submit", submissionGeneration });
|
|
1057
|
+
for (const candidate of pending.candidates) {
|
|
1058
|
+
this.facets.applyConfidence(candidate.view, candidate.primitive, submitEvent);
|
|
1059
|
+
}
|
|
1060
|
+
return true;
|
|
1061
|
+
}
|
|
1062
|
+
destroyResources(resource) {
|
|
1063
|
+
if (this.retiredResources.has(resource)) return;
|
|
1064
|
+
this.retiredResources.add(resource);
|
|
1065
|
+
this.device.destroyBuffer(resource.resolveBuffer);
|
|
1066
|
+
this.device.destroyBuffer(resource.stagingBuffer);
|
|
1067
|
+
this.device.destroyQuerySet(resource.querySet);
|
|
1068
|
+
}
|
|
1069
|
+
async completeAfterSubmit(tickets, pending) {
|
|
1070
|
+
try {
|
|
1071
|
+
await this.device.queue.onSubmittedWorkDone();
|
|
1072
|
+
const mapStart = performance.now();
|
|
1073
|
+
await runtimeTestHooks?.beforeMap?.();
|
|
1074
|
+
const mapped = await pending.resources.stagingBuffer.mapAsync(
|
|
1075
|
+
GPU_BUFFER_USAGE_MAP_READ,
|
|
1076
|
+
0,
|
|
1077
|
+
pending.reservations.length * 8
|
|
1078
|
+
);
|
|
1079
|
+
if (!mapped.ok) throw mapped.error;
|
|
1080
|
+
this.lastQueryLatencyUs = Math.max(0, Math.round((performance.now() - mapStart) * 1e3));
|
|
1081
|
+
this.queryLatencySamplesUs.push(this.lastQueryLatencyUs);
|
|
1082
|
+
if (this.queryLatencySamplesUs.length > 64) this.queryLatencySamplesUs.shift();
|
|
1083
|
+
const bytes = mapped.value.getMappedRange(0, pending.reservations.length * 8);
|
|
1084
|
+
if (!bytes.ok) throw bytes.error;
|
|
1085
|
+
const data = new Uint8Array(bytes.value.slice(0));
|
|
1086
|
+
const dataView = new DataView(data.buffer);
|
|
1087
|
+
const firstQuery = pending.reservations[0]?.queryIndex ?? 0;
|
|
1088
|
+
mapped.value.unmap();
|
|
1089
|
+
for (let ticketIndex = tickets.length - 1; ticketIndex >= 0; ticketIndex -= 1) {
|
|
1090
|
+
const ticket = tickets[ticketIndex];
|
|
1091
|
+
if (ticket === void 0) continue;
|
|
1092
|
+
const candidateIndex = ticket.queryIndex - firstQuery;
|
|
1093
|
+
const samples = dataView.getUint32(candidateIndex * 8, true);
|
|
1094
|
+
const completion = this.pool.complete(ticket, samples);
|
|
1095
|
+
if (completion.status !== "accepted") continue;
|
|
1096
|
+
const candidate = pending.candidates[candidateIndex];
|
|
1097
|
+
if (candidate === void 0) continue;
|
|
1098
|
+
this.facets.applyConfidence(candidate.view, candidate.primitive, {
|
|
1099
|
+
type: "result",
|
|
1100
|
+
samples,
|
|
1101
|
+
submissionGeneration: ticket.submissionGeneration
|
|
1102
|
+
});
|
|
1103
|
+
this.facets.applyCompletion(
|
|
1104
|
+
candidate.view,
|
|
1105
|
+
candidate.primitive,
|
|
1106
|
+
candidate.epoch,
|
|
1107
|
+
candidate.candidate
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
} catch {
|
|
1111
|
+
for (const ticket of tickets) {
|
|
1112
|
+
this.pool.complete(ticket, Number.NaN);
|
|
1113
|
+
const candidate = pending.candidates[ticket.queryIndex - (pending.reservations[0]?.queryIndex ?? 0)];
|
|
1114
|
+
if (candidate !== void 0) {
|
|
1115
|
+
this.facets.applyConfidence(candidate.view, candidate.primitive, {
|
|
1116
|
+
type: "failure",
|
|
1117
|
+
submissionGeneration: ticket.submissionGeneration
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
encodeProxyBounds(pass, pending) {
|
|
1124
|
+
if (pending.proxyFloatCount === 0) {
|
|
1125
|
+
return err$1(runtimeError("every occlusion candidate needs conservative proxy geometry"));
|
|
1126
|
+
}
|
|
1127
|
+
const pipeline = this.ensureProxyPipeline(pending.sampleCount);
|
|
1128
|
+
if (pipeline === void 0) {
|
|
1129
|
+
for (const reservation of pending.reservations) {
|
|
1130
|
+
const began = pass.beginOcclusionQuery(reservation.queryIndex);
|
|
1131
|
+
if (!began.ok) return began;
|
|
1132
|
+
const ended = pass.endOcclusionQuery();
|
|
1133
|
+
if (!ended.ok) return ended;
|
|
1134
|
+
}
|
|
1135
|
+
pending.endedCount = pending.reservations.length;
|
|
1136
|
+
return ok$1(void 0);
|
|
1137
|
+
}
|
|
1138
|
+
const buffer = this.ensureProxyBuffer(pending.proxyFloatCount * Float32Array.BYTES_PER_ELEMENT);
|
|
1139
|
+
if (buffer === void 0) {
|
|
1140
|
+
return err$1(runtimeError("the backend must allocate the bounded proxy geometry buffer"));
|
|
1141
|
+
}
|
|
1142
|
+
const uploaded = this.device.queue.writeBuffer(
|
|
1143
|
+
buffer,
|
|
1144
|
+
0,
|
|
1145
|
+
this.proxyScratch.subarray(0, pending.proxyFloatCount)
|
|
1146
|
+
);
|
|
1147
|
+
if (!uploaded.ok) return uploaded;
|
|
1148
|
+
pass.setPipeline(pipeline);
|
|
1149
|
+
pass.setVertexBuffer(0, buffer);
|
|
1150
|
+
let vertexOffset = 0;
|
|
1151
|
+
for (let index = 0; index < pending.reservations.length; index += 1) {
|
|
1152
|
+
const vertices = pending.candidates[index]?.proxyVertices;
|
|
1153
|
+
if (vertices === void 0 || vertices.length === 0) continue;
|
|
1154
|
+
const began = pass.beginOcclusionQuery(pending.reservations[index]?.queryIndex ?? 0);
|
|
1155
|
+
if (!began.ok) return began;
|
|
1156
|
+
pass.draw(vertices.length / 4, 1, vertexOffset / 4, 0);
|
|
1157
|
+
const ended = pass.endOcclusionQuery();
|
|
1158
|
+
if (!ended.ok) return ended;
|
|
1159
|
+
vertexOffset += vertices.length;
|
|
1160
|
+
}
|
|
1161
|
+
pending.endedCount = pending.reservations.length;
|
|
1162
|
+
return ok$1(void 0);
|
|
1163
|
+
}
|
|
1164
|
+
ensureProxyBuffer(byteLength) {
|
|
1165
|
+
if (this.proxyBuffer !== void 0) return this.proxyBuffer;
|
|
1166
|
+
const created = this.device.createBuffer({
|
|
1167
|
+
label: "occlusion-bounds-proxy-geometry",
|
|
1168
|
+
size: Math.max(16, PROXY_GEOMETRY_BYTES, byteLength),
|
|
1169
|
+
usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST
|
|
1170
|
+
});
|
|
1171
|
+
if (!created.ok) return void 0;
|
|
1172
|
+
this.proxyBuffer = created.value;
|
|
1173
|
+
return created.value;
|
|
1174
|
+
}
|
|
1175
|
+
ensureProxyPipeline(sampleCount) {
|
|
1176
|
+
if (this.proxyPipelineSampleCount === sampleCount && this.proxyPipeline !== void 0) {
|
|
1177
|
+
return this.proxyPipeline;
|
|
1178
|
+
}
|
|
1179
|
+
if (this.shaderModuleFactory === void 0) return void 0;
|
|
1180
|
+
const module = this.shaderModuleFactory.createShaderModule({
|
|
1181
|
+
label: "occlusion-bounds-proxy",
|
|
1182
|
+
code: PROXY_BOUNDS_WGSL
|
|
1183
|
+
});
|
|
1184
|
+
if (!module.ok) return void 0;
|
|
1185
|
+
const created = this.device.createRenderPipeline({
|
|
1186
|
+
label: "occlusion-bounds-proxy",
|
|
1187
|
+
layout: "auto",
|
|
1188
|
+
vertex: {
|
|
1189
|
+
module: module.value,
|
|
1190
|
+
entryPoint: "vs_main",
|
|
1191
|
+
buffers: [
|
|
1192
|
+
{
|
|
1193
|
+
arrayStride: 16,
|
|
1194
|
+
stepMode: "vertex",
|
|
1195
|
+
attributes: [{ shaderLocation: 0, offset: 0, format: "float32x4" }]
|
|
1196
|
+
}
|
|
1197
|
+
]
|
|
1198
|
+
},
|
|
1199
|
+
// The proxy is recorded in the typed main pass, whose color attachment
|
|
1200
|
+
// remains RGBA16Float. Keep the pass compatible while suppressing all
|
|
1201
|
+
// color writes; occlusion only consumes depth samples.
|
|
1202
|
+
fragment: {
|
|
1203
|
+
module: module.value,
|
|
1204
|
+
entryPoint: "fs_main",
|
|
1205
|
+
targets: [{ format: "rgba16float", writeMask: 0 }]
|
|
1206
|
+
},
|
|
1207
|
+
primitive: { topology: "triangle-list" },
|
|
1208
|
+
depthStencil: {
|
|
1209
|
+
format: "depth24plus-stencil8",
|
|
1210
|
+
depthWriteEnabled: false,
|
|
1211
|
+
depthCompare: "less-equal"
|
|
1212
|
+
},
|
|
1213
|
+
multisample: { count: sampleCount }
|
|
1214
|
+
});
|
|
1215
|
+
if (!created.ok) return void 0;
|
|
1216
|
+
this.proxyPipeline = created.value;
|
|
1217
|
+
this.proxyPipelineSampleCount = sampleCount;
|
|
1218
|
+
return created.value;
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
var PROXY_BOUNDS_WGSL = `
|
|
1222
|
+
@vertex
|
|
1223
|
+
fn vs_main(@location(0) position: vec4f) -> @builtin(position) vec4f {
|
|
1224
|
+
return position;
|
|
1225
|
+
}
|
|
1226
|
+
@fragment
|
|
1227
|
+
fn fs_main() -> @location(0) vec4f {
|
|
1228
|
+
return vec4f(0.0);
|
|
1229
|
+
}
|
|
1230
|
+
`;
|
|
1231
|
+
function candidateKey(view, primitive) {
|
|
1232
|
+
return `${viewKeyId(view)}|${primitiveKeyId(primitive)}`;
|
|
1233
|
+
}
|
|
1234
|
+
function finiteBounds(bounds) {
|
|
1235
|
+
return [...bounds.min, ...bounds.max].every((value) => Number.isFinite(value));
|
|
1236
|
+
}
|
|
1237
|
+
function runtimeError(hint) {
|
|
1238
|
+
return new RhiError({
|
|
1239
|
+
code: "webgpu-runtime-error",
|
|
1240
|
+
expected: "RenderSystem owns one active occlusion candidate for the shared frame",
|
|
1241
|
+
hint
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
var PROBE_R_MIN = R_MIN;
|
|
1245
|
+
var PROBE_MAX_CONTRIBUTORS = PROBE_BLEND_RECORD_CAPACITY;
|
|
1246
|
+
var SH_C0 = 0.28209479177387814;
|
|
1247
|
+
var SH_C1 = 0.4886025119029199;
|
|
1248
|
+
var SH_C2 = 1.0925484305920792;
|
|
1249
|
+
var SH_C20 = 0.31539156525252005;
|
|
1250
|
+
var SH_C22 = 0.5462742152960396;
|
|
1251
|
+
function finiteVector(values, length) {
|
|
1252
|
+
if (values.length !== length) return false;
|
|
1253
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
1254
|
+
if (!Number.isFinite(values[index])) return false;
|
|
1255
|
+
}
|
|
1256
|
+
return true;
|
|
1257
|
+
}
|
|
1258
|
+
function contributorIsActive(probe, radiusMinimum) {
|
|
1259
|
+
return probe.admitted && Number.isFinite(probe.distance) && probe.distance >= 0 && Number.isFinite(probe.radius) && probe.radius >= radiusMinimum && probe.distance < probe.radius && finiteVector(probe.irradiance, 27);
|
|
1260
|
+
}
|
|
1261
|
+
function emptyReceipt(radiusMinimum) {
|
|
1262
|
+
return {
|
|
1263
|
+
activeIdentities: [],
|
|
1264
|
+
admittedIdentities: [],
|
|
1265
|
+
rejectedIdentities: [],
|
|
1266
|
+
stableOrder: [],
|
|
1267
|
+
capacity: PROBE_MAX_CONTRIBUTORS,
|
|
1268
|
+
scaleRadius: radiusMinimum,
|
|
1269
|
+
finite: true
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function admitProbeContributors(candidates, radiusMinimum = PROBE_R_MIN) {
|
|
1273
|
+
const active = candidates.filter((candidate) => contributorIsActive(candidate, radiusMinimum));
|
|
1274
|
+
const overflowed = active.length > PROBE_MAX_CONTRIBUTORS;
|
|
1275
|
+
const stableActive = active.slice().sort((left, right) => left.identity.localeCompare(right.identity));
|
|
1276
|
+
const admitted = stableActive.slice(0, PROBE_MAX_CONTRIBUTORS);
|
|
1277
|
+
const rejected = stableActive.slice(PROBE_MAX_CONTRIBUTORS);
|
|
1278
|
+
const scaleRadius = active.reduce(
|
|
1279
|
+
(minimum, candidate) => Math.min(minimum, candidate.radius),
|
|
1280
|
+
active[0]?.radius ?? radiusMinimum
|
|
1281
|
+
);
|
|
1282
|
+
const receipt = {
|
|
1283
|
+
activeIdentities: stableActive.map((candidate) => candidate.identity),
|
|
1284
|
+
admittedIdentities: admitted.map((candidate) => candidate.identity),
|
|
1285
|
+
rejectedIdentities: rejected.map((candidate) => candidate.identity),
|
|
1286
|
+
stableOrder: admitted.map((candidate) => candidate.identity),
|
|
1287
|
+
capacity: PROBE_MAX_CONTRIBUTORS,
|
|
1288
|
+
...overflowed ? { overflowReason: "active-count-exceeds-capacity" } : {},
|
|
1289
|
+
scaleRadius,
|
|
1290
|
+
finite: stableActive.every((candidate) => Number.isFinite(candidate.radius))
|
|
1291
|
+
};
|
|
1292
|
+
return {
|
|
1293
|
+
active: stableActive,
|
|
1294
|
+
admitted,
|
|
1295
|
+
rejected,
|
|
1296
|
+
receipt,
|
|
1297
|
+
...overflowed ? {
|
|
1298
|
+
error: {
|
|
1299
|
+
code: "capacity-exceeded",
|
|
1300
|
+
activeCount: active.length,
|
|
1301
|
+
capacity: PROBE_MAX_CONTRIBUTORS,
|
|
1302
|
+
receipt
|
|
1303
|
+
}
|
|
1304
|
+
} : {}
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
function invalidInputResult(radiusMinimum, code) {
|
|
1308
|
+
const receipt = emptyReceipt(radiusMinimum);
|
|
1309
|
+
return {
|
|
1310
|
+
terms: [],
|
|
1311
|
+
rStar: radiusMinimum,
|
|
1312
|
+
Q: 0,
|
|
1313
|
+
C: 0,
|
|
1314
|
+
S: 1,
|
|
1315
|
+
SHPreblend: new Array(27).fill(0),
|
|
1316
|
+
skyIrradiance: [0, 0, 0],
|
|
1317
|
+
diffuse: [0, 0, 0],
|
|
1318
|
+
finite: false,
|
|
1319
|
+
receipt,
|
|
1320
|
+
scaledQLogOffset: 0,
|
|
1321
|
+
error: { code, activeCount: 0, capacity: PROBE_MAX_CONTRIBUTORS, receipt }
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
function neumaierSum(values) {
|
|
1325
|
+
let sum = 0;
|
|
1326
|
+
let correction = 0;
|
|
1327
|
+
for (const value of values) {
|
|
1328
|
+
const next = sum + value;
|
|
1329
|
+
if (Math.abs(sum) >= Math.abs(value)) correction += sum - next + value;
|
|
1330
|
+
else correction += value - next + sum;
|
|
1331
|
+
sum = next;
|
|
1332
|
+
}
|
|
1333
|
+
return sum + correction;
|
|
1334
|
+
}
|
|
1335
|
+
function roundProbeToF32(value) {
|
|
1336
|
+
return Number.isFinite(value) ? new Float32Array([value])[0] : void 0;
|
|
1337
|
+
}
|
|
1338
|
+
function scaledProbeWeight(distance, radius, radiusMinimum = PROBE_R_MIN) {
|
|
1339
|
+
if (!Number.isFinite(distance) || distance < 0 || !Number.isFinite(radius) || radius < radiusMinimum || distance >= radius) {
|
|
1340
|
+
return { coverage: 0, q: 0 };
|
|
1341
|
+
}
|
|
1342
|
+
const ratio = distance / radius;
|
|
1343
|
+
const coverage = 1 - ratio * ratio;
|
|
1344
|
+
return { coverage, q: coverage / (radius * radius) };
|
|
1345
|
+
}
|
|
1346
|
+
function sh9Irradiance(coefficients, normal) {
|
|
1347
|
+
const x = normal[0];
|
|
1348
|
+
const y = normal[1];
|
|
1349
|
+
const z = normal[2];
|
|
1350
|
+
const basis = [
|
|
1351
|
+
SH_C0,
|
|
1352
|
+
SH_C1 * y,
|
|
1353
|
+
SH_C1 * z,
|
|
1354
|
+
SH_C1 * x,
|
|
1355
|
+
SH_C2 * x * y,
|
|
1356
|
+
SH_C2 * y * z,
|
|
1357
|
+
SH_C20 * (3 * z * z - 1),
|
|
1358
|
+
SH_C2 * x * z,
|
|
1359
|
+
SH_C22 * (x * x - y * y)
|
|
1360
|
+
];
|
|
1361
|
+
const result = [0, 0, 0];
|
|
1362
|
+
for (let band = 0; band < basis.length; band += 1) {
|
|
1363
|
+
const factor = basis[band] ?? 0;
|
|
1364
|
+
const offset = band * 3;
|
|
1365
|
+
result[0] = (result[0] ?? 0) + (coefficients[offset] ?? 0) * factor;
|
|
1366
|
+
result[1] = (result[1] ?? 0) + (coefficients[offset + 1] ?? 0) * factor;
|
|
1367
|
+
result[2] = (result[2] ?? 0) + (coefficients[offset + 2] ?? 0) * factor;
|
|
1368
|
+
}
|
|
1369
|
+
return result;
|
|
1370
|
+
}
|
|
1371
|
+
function vectorNeumaierSum(values, width) {
|
|
1372
|
+
return Array.from(
|
|
1373
|
+
{ length: width },
|
|
1374
|
+
(_, channel) => neumaierSum(values.map((value) => value[channel] ?? 0))
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
function blendProbeCoefficients(contributors, radiusMinimum) {
|
|
1378
|
+
if (contributors.length > PROBE_MAX_CONTRIBUTORS || contributors.some((contributor) => !contributorIsActive(contributor, radiusMinimum))) {
|
|
1379
|
+
return void 0;
|
|
1380
|
+
}
|
|
1381
|
+
const rStar = contributors.reduce(
|
|
1382
|
+
(minimum, contributor) => Math.min(minimum, contributor.radius),
|
|
1383
|
+
contributors[0]?.radius ?? radiusMinimum
|
|
1384
|
+
);
|
|
1385
|
+
const rawTerms = contributors.map((contributor) => {
|
|
1386
|
+
const { coverage } = scaledProbeWeight(contributor.distance, contributor.radius, radiusMinimum);
|
|
1387
|
+
const scaledRatio = rStar / contributor.radius;
|
|
1388
|
+
return {
|
|
1389
|
+
contributor,
|
|
1390
|
+
coverage,
|
|
1391
|
+
// Diagnostic q is never used for Q, alpha, C, or SH accumulation.
|
|
1392
|
+
q: coverage / (contributor.radius * contributor.radius),
|
|
1393
|
+
scaledQ: coverage * scaledRatio * scaledRatio
|
|
1394
|
+
};
|
|
1395
|
+
});
|
|
1396
|
+
const scaledQLogOffset = 0;
|
|
1397
|
+
const finiteTerms = rawTerms;
|
|
1398
|
+
if (finiteTerms.some((term) => !Number.isFinite(term.scaledQ))) return void 0;
|
|
1399
|
+
const Q = neumaierSum(finiteTerms.map((term) => term.scaledQ));
|
|
1400
|
+
const terms = finiteTerms.map((term) => ({
|
|
1401
|
+
identity: term.contributor.identity,
|
|
1402
|
+
distance: term.contributor.distance,
|
|
1403
|
+
radius: term.contributor.radius,
|
|
1404
|
+
coverage: term.coverage,
|
|
1405
|
+
q: term.q,
|
|
1406
|
+
scaledQ: term.scaledQ,
|
|
1407
|
+
qHat: term.scaledQ,
|
|
1408
|
+
alpha: Q > 0 ? term.scaledQ / Q : 0
|
|
1409
|
+
}));
|
|
1410
|
+
let product = 1;
|
|
1411
|
+
for (const term of finiteTerms) product *= 1 - term.coverage;
|
|
1412
|
+
const C = 1 - product;
|
|
1413
|
+
const S = 1 - C;
|
|
1414
|
+
const weightedSh = finiteTerms.map(
|
|
1415
|
+
(term) => Array.from(term.contributor.irradiance, (value) => value * (Q > 0 ? term.scaledQ / Q : 0))
|
|
1416
|
+
);
|
|
1417
|
+
const SHPreblend = vectorNeumaierSum(weightedSh, 27).map((value) => value * C);
|
|
1418
|
+
const finite = Number.isFinite(Q) && Number.isFinite(C) && Number.isFinite(S) && SHPreblend.every(Number.isFinite) && finiteTerms.every((term) => Number.isFinite(term.scaledQ));
|
|
1419
|
+
const receipt = {
|
|
1420
|
+
activeIdentities: contributors.map((contributor) => contributor.identity),
|
|
1421
|
+
admittedIdentities: contributors.map((contributor) => contributor.identity),
|
|
1422
|
+
rejectedIdentities: [],
|
|
1423
|
+
stableOrder: contributors.map((contributor) => contributor.identity),
|
|
1424
|
+
capacity: PROBE_MAX_CONTRIBUTORS,
|
|
1425
|
+
scaleRadius: rStar,
|
|
1426
|
+
finite
|
|
1427
|
+
};
|
|
1428
|
+
return {
|
|
1429
|
+
terms,
|
|
1430
|
+
rStar,
|
|
1431
|
+
Q,
|
|
1432
|
+
C,
|
|
1433
|
+
S,
|
|
1434
|
+
SHPreblend,
|
|
1435
|
+
finite,
|
|
1436
|
+
receipt,
|
|
1437
|
+
scaledQLogOffset
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
function blendLightProbes(input) {
|
|
1441
|
+
const radiusMinimum = input.radiusMinimum ?? PROBE_R_MIN;
|
|
1442
|
+
const normal = input.normal;
|
|
1443
|
+
const sky = input.skyIrradiance;
|
|
1444
|
+
if (!finiteVector(normal, 3) || !finiteVector(sky, 3)) {
|
|
1445
|
+
return invalidInputResult(radiusMinimum, "invalid-admitted-prefix");
|
|
1446
|
+
}
|
|
1447
|
+
const coefficients = blendProbeCoefficients(input.contributors, radiusMinimum);
|
|
1448
|
+
if (coefficients === void 0)
|
|
1449
|
+
return invalidInputResult(radiusMinimum, "invalid-admitted-prefix");
|
|
1450
|
+
const shIrradiance = sh9Irradiance(coefficients.SHPreblend, normal);
|
|
1451
|
+
const diffuse = [0, 1, 2].map(
|
|
1452
|
+
(channel) => Math.max(shIrradiance[channel] ?? 0, 0) + coefficients.S * (sky[channel] ?? 0)
|
|
1453
|
+
);
|
|
1454
|
+
return {
|
|
1455
|
+
...coefficients,
|
|
1456
|
+
skyIrradiance: [sky[0], sky[1], sky[2]],
|
|
1457
|
+
diffuse,
|
|
1458
|
+
finite: coefficients.finite && diffuse.every(Number.isFinite)
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
function vectorDistance(left, right) {
|
|
1462
|
+
const dx = left[0] - right[0];
|
|
1463
|
+
const dy = left[1] - right[1];
|
|
1464
|
+
const dz = left[2] - right[2];
|
|
1465
|
+
return Math.hypot(dx, dy, dz);
|
|
1466
|
+
}
|
|
1467
|
+
function encodeProbeRecord(objectKey, generation, localBlendFraction, shPreblend, candidate, accepted, lastKnownGood, sentinel) {
|
|
1468
|
+
const values = new Float32Array(PROBE_BLEND_RECORD_BYTE_SIZE / Float32Array.BYTES_PER_ELEMENT);
|
|
1469
|
+
values[0] = objectKey;
|
|
1470
|
+
values[1] = generation;
|
|
1471
|
+
values[2] = roundProbeToF32(localBlendFraction) ?? 0;
|
|
1472
|
+
values[3] = (1 ) | (accepted ? 2 : 0) | (lastKnownGood ? 4 : 0);
|
|
1473
|
+
for (let band = 0; band < 9; band += 1) {
|
|
1474
|
+
const lane = 4 + band * 4;
|
|
1475
|
+
values[lane] = roundProbeToF32(shPreblend[band * 3] ?? 0) ?? 0;
|
|
1476
|
+
values[lane + 1] = roundProbeToF32(shPreblend[band * 3 + 1] ?? 0) ?? 0;
|
|
1477
|
+
values[lane + 2] = roundProbeToF32(shPreblend[band * 3 + 2] ?? 0) ?? 0;
|
|
1478
|
+
values[lane + 3] = 0;
|
|
1479
|
+
}
|
|
1480
|
+
const packedSh = new Array(27);
|
|
1481
|
+
for (let band = 0; band < 9; band += 1) {
|
|
1482
|
+
packedSh[band * 3] = values[4 + band * 4] ?? 0;
|
|
1483
|
+
packedSh[band * 3 + 1] = values[5 + band * 4] ?? 0;
|
|
1484
|
+
packedSh[band * 3 + 2] = values[6 + band * 4] ?? 0;
|
|
1485
|
+
}
|
|
1486
|
+
return {
|
|
1487
|
+
objectKey,
|
|
1488
|
+
generation,
|
|
1489
|
+
localBlendFraction: values[2] ?? 0,
|
|
1490
|
+
shPreblend: packedSh,
|
|
1491
|
+
bytes: new Uint8Array(values.buffer),
|
|
1492
|
+
byteLength: values.byteLength,
|
|
1493
|
+
candidate,
|
|
1494
|
+
accepted,
|
|
1495
|
+
lastKnownGood,
|
|
1496
|
+
...sentinel === void 0 ? {} : { sentinel }
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
function sameList(left, right) {
|
|
1500
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1501
|
+
}
|
|
1502
|
+
function sameVector3(left, right) {
|
|
1503
|
+
return left[0] === right[0] && left[1] === right[1] && left[2] === right[2];
|
|
1504
|
+
}
|
|
1505
|
+
function sameProbeFacts(left, right) {
|
|
1506
|
+
if (left.length !== right.length) return false;
|
|
1507
|
+
return left.every((probe, index) => {
|
|
1508
|
+
const other = right[index];
|
|
1509
|
+
if (other === void 0 || probe.identity !== other.identity || probe.radius !== other.radius || probe.admitted !== other.admitted || !sameVector3(probe.position, other.position) || probe.irradiance.length !== other.irradiance.length)
|
|
1510
|
+
return false;
|
|
1511
|
+
for (let value = 0; value < probe.irradiance.length; value += 1) {
|
|
1512
|
+
if (probe.irradiance[value] !== other.irradiance[value]) return false;
|
|
1513
|
+
}
|
|
1514
|
+
return true;
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
function sameSkyFacts(left, right) {
|
|
1518
|
+
const leftSky = left.sky;
|
|
1519
|
+
const rightSky = right.sky;
|
|
1520
|
+
if (leftSky === void 0 !== (rightSky === void 0)) return false;
|
|
1521
|
+
if (leftSky !== void 0 && rightSky !== void 0 && (leftSky.available !== rightSky.available || leftSky.identity !== rightSky.identity || leftSky.sourceKey !== rightSky.sourceKey || leftSky.fallbackReason !== rightSky.fallbackReason || !sameVector3(leftSky.irradiance, rightSky.irradiance)))
|
|
1522
|
+
return false;
|
|
1523
|
+
if (left.skyIrradiance === void 0 !== (right.skyIrradiance === void 0)) return false;
|
|
1524
|
+
return left.skyIrradiance === void 0 || right.skyIrradiance !== void 0 && sameVector3(left.skyIrradiance, right.skyIrradiance);
|
|
1525
|
+
}
|
|
1526
|
+
var ProbeBlendSceneProjection = class {
|
|
1527
|
+
previous;
|
|
1528
|
+
records = /* @__PURE__ */ new Map();
|
|
1529
|
+
lastError;
|
|
1530
|
+
lastReceipt = emptyReceipt(PROBE_R_MIN);
|
|
1531
|
+
lastActiveContributorCount = 0;
|
|
1532
|
+
lastAdmittedProbeCount = 0;
|
|
1533
|
+
lastCoverage = 0;
|
|
1534
|
+
lastFinite = true;
|
|
1535
|
+
lastSky = {
|
|
1536
|
+
available: false,
|
|
1537
|
+
irradiance: [0, 0, 0],
|
|
1538
|
+
fallbackReason: "no-skylight"
|
|
1539
|
+
};
|
|
1540
|
+
lastDirtyVisitCount = 0;
|
|
1541
|
+
lastDirtyReasons = [];
|
|
1542
|
+
bufferProjectionSource = Object.freeze({});
|
|
1543
|
+
bufferProjection = Object.freeze({
|
|
1544
|
+
sourceIdentity: this.bufferProjectionSource,
|
|
1545
|
+
revision: 0,
|
|
1546
|
+
baseRevision: 0,
|
|
1547
|
+
capacity: 1,
|
|
1548
|
+
records: Object.freeze([]),
|
|
1549
|
+
dirtyRecords: Object.freeze([]),
|
|
1550
|
+
removedCacheKeys: Object.freeze([])
|
|
1551
|
+
});
|
|
1552
|
+
resultRecords = Object.freeze([]);
|
|
1553
|
+
recordFacts = /* @__PURE__ */ new Map();
|
|
1554
|
+
getRecord(objectKey) {
|
|
1555
|
+
return this.records.get(objectKey);
|
|
1556
|
+
}
|
|
1557
|
+
hasRecords() {
|
|
1558
|
+
return this.records.size > 0;
|
|
1559
|
+
}
|
|
1560
|
+
/** Stable upload projection; rebuilt only when probe/object facts change. */
|
|
1561
|
+
recordBufferProjection() {
|
|
1562
|
+
return this.bufferProjection;
|
|
1563
|
+
}
|
|
1564
|
+
inspect() {
|
|
1565
|
+
return {
|
|
1566
|
+
activeContributorCount: this.lastActiveContributorCount,
|
|
1567
|
+
admittedProbeCount: this.lastAdmittedProbeCount,
|
|
1568
|
+
coverage: this.lastCoverage,
|
|
1569
|
+
skyResidualFraction: 1 - this.lastCoverage,
|
|
1570
|
+
finite: this.lastFinite,
|
|
1571
|
+
errorCode: this.lastError?.code,
|
|
1572
|
+
records: [...this.records.values()].map((record) => {
|
|
1573
|
+
const facts = this.recordFacts.get(record.objectKey);
|
|
1574
|
+
return {
|
|
1575
|
+
objectKey: record.objectKey,
|
|
1576
|
+
generation: record.generation,
|
|
1577
|
+
localBlendFraction: record.localBlendFraction,
|
|
1578
|
+
shPreblend: [...record.shPreblend],
|
|
1579
|
+
byteLength: record.byteLength,
|
|
1580
|
+
candidate: record.candidate,
|
|
1581
|
+
accepted: record.accepted,
|
|
1582
|
+
lastKnownGood: record.lastKnownGood,
|
|
1583
|
+
sentinel: record.sentinel,
|
|
1584
|
+
probeBlendIndex: record.objectKey,
|
|
1585
|
+
contributors: (facts?.contributors ?? []).map((term) => ({
|
|
1586
|
+
identity: term.identity,
|
|
1587
|
+
distance: term.distance,
|
|
1588
|
+
radius: term.radius,
|
|
1589
|
+
coverage: term.coverage,
|
|
1590
|
+
q: term.q,
|
|
1591
|
+
qHat: term.qHat,
|
|
1592
|
+
alpha: term.alpha
|
|
1593
|
+
})),
|
|
1594
|
+
qHatSum: facts?.qHatSum ?? 0,
|
|
1595
|
+
rStar: facts?.rStar ?? PROBE_R_MIN,
|
|
1596
|
+
fallbackReason: facts?.fallbackReason
|
|
1597
|
+
};
|
|
1598
|
+
}),
|
|
1599
|
+
sky: {
|
|
1600
|
+
available: this.lastSky?.available ?? false,
|
|
1601
|
+
identity: this.lastSky?.identity,
|
|
1602
|
+
sourceKey: this.lastSky?.sourceKey,
|
|
1603
|
+
irradiance: [...this.lastSky?.irradiance ?? [0, 0, 0]],
|
|
1604
|
+
fallbackReason: this.lastSky?.fallbackReason
|
|
1605
|
+
},
|
|
1606
|
+
dirtyVisitCount: this.lastDirtyVisitCount,
|
|
1607
|
+
dirtyReasons: [...this.lastDirtyReasons],
|
|
1608
|
+
receipt: {
|
|
1609
|
+
activeIdentities: [...this.lastReceipt.activeIdentities],
|
|
1610
|
+
admittedIdentities: [...this.lastReceipt.admittedIdentities],
|
|
1611
|
+
rejectedIdentities: [...this.lastReceipt.rejectedIdentities],
|
|
1612
|
+
stableOrder: [...this.lastReceipt.stableOrder],
|
|
1613
|
+
capacity: this.lastReceipt.capacity,
|
|
1614
|
+
scaleRadius: this.lastReceipt.scaleRadius,
|
|
1615
|
+
finite: this.lastReceipt.finite,
|
|
1616
|
+
overflowReason: this.lastReceipt.overflowReason
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
apply(input) {
|
|
1621
|
+
const dirtyReasons = [];
|
|
1622
|
+
const affectedObjectKeys = /* @__PURE__ */ new Set();
|
|
1623
|
+
const previous = this.previous;
|
|
1624
|
+
if (previous !== void 0 && previous.objects === input.objects && input.worldRevision === previous.worldRevision && input.retainSkyResidualRecords === previous.retainSkyResidualRecords && sameProbeFacts(input.probes, previous.probes) && sameSkyFacts(input, previous)) {
|
|
1625
|
+
return {
|
|
1626
|
+
records: this.resultRecords,
|
|
1627
|
+
contributors: [],
|
|
1628
|
+
activeContributorCount: this.lastActiveContributorCount,
|
|
1629
|
+
admittedProbeCount: this.lastAdmittedProbeCount,
|
|
1630
|
+
coverage: this.lastCoverage,
|
|
1631
|
+
skyResidualFraction: 1 - this.lastCoverage,
|
|
1632
|
+
dirtyReasons: [],
|
|
1633
|
+
affectedObjectKeys: [],
|
|
1634
|
+
visits: 0,
|
|
1635
|
+
allocations: 0,
|
|
1636
|
+
uploads: 0,
|
|
1637
|
+
receipt: this.lastReceipt,
|
|
1638
|
+
...this.lastError === void 0 ? {} : { error: this.lastError }
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
const objectKeys = input.objects.map((object) => String(object.objectKey)).sort();
|
|
1642
|
+
const previousObjectKeys = previous?.objects.map((object) => String(object.objectKey)).sort() ?? [];
|
|
1643
|
+
if (previous === void 0 || !sameList(objectKeys, previousObjectKeys)) {
|
|
1644
|
+
dirtyReasons.push("object-boundary");
|
|
1645
|
+
for (const object of input.objects) affectedObjectKeys.add(object.objectKey);
|
|
1646
|
+
}
|
|
1647
|
+
if (previous !== void 0 && input.worldRevision !== previous.worldRevision)
|
|
1648
|
+
dirtyReasons.push("world");
|
|
1649
|
+
if (previous !== void 0 && (!sameProbeFacts(input.probes, previous.probes) || input.retainSkyResidualRecords !== previous.retainSkyResidualRecords))
|
|
1650
|
+
dirtyReasons.push("probe-fact");
|
|
1651
|
+
if (previous !== void 0 && !sameSkyFacts(input, previous)) dirtyReasons.push("sky");
|
|
1652
|
+
const previousObjects = new Map(previous?.objects.map((object) => [object.objectKey, object]));
|
|
1653
|
+
for (const object of input.objects) {
|
|
1654
|
+
const oldObject = previousObjects.get(object.objectKey);
|
|
1655
|
+
if (oldObject === void 0) {
|
|
1656
|
+
affectedObjectKeys.add(object.objectKey);
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
if (!sameVector3(object.position, oldObject.position)) {
|
|
1660
|
+
dirtyReasons.push("object-position");
|
|
1661
|
+
affectedObjectKeys.add(object.objectKey);
|
|
1662
|
+
}
|
|
1663
|
+
if (object.generation !== oldObject.generation) {
|
|
1664
|
+
dirtyReasons.push("generation");
|
|
1665
|
+
affectedObjectKeys.add(object.objectKey);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
if (dirtyReasons.includes("world") || dirtyReasons.includes("probe-fact") || dirtyReasons.includes("sky"))
|
|
1669
|
+
for (const object of input.objects) affectedObjectKeys.add(object.objectKey);
|
|
1670
|
+
const uniqueDirtyReasons = [...new Set(dirtyReasons)];
|
|
1671
|
+
if (previous !== void 0 && uniqueDirtyReasons.length === 0) {
|
|
1672
|
+
return {
|
|
1673
|
+
records: [...this.records.values()],
|
|
1674
|
+
contributors: [],
|
|
1675
|
+
activeContributorCount: this.lastActiveContributorCount,
|
|
1676
|
+
admittedProbeCount: this.lastAdmittedProbeCount,
|
|
1677
|
+
coverage: this.lastCoverage,
|
|
1678
|
+
skyResidualFraction: 1 - this.lastCoverage,
|
|
1679
|
+
dirtyReasons: [],
|
|
1680
|
+
affectedObjectKeys: [],
|
|
1681
|
+
visits: 0,
|
|
1682
|
+
allocations: 0,
|
|
1683
|
+
uploads: 0,
|
|
1684
|
+
receipt: this.lastReceipt,
|
|
1685
|
+
...this.lastError === void 0 ? {} : { error: this.lastError }
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
const nextRecords = new Map(this.records);
|
|
1689
|
+
const currentObjectKeys = new Set(input.objects.map((object) => object.objectKey));
|
|
1690
|
+
for (const objectKey of nextRecords.keys()) {
|
|
1691
|
+
if (!currentObjectKeys.has(objectKey)) {
|
|
1692
|
+
nextRecords.delete(objectKey);
|
|
1693
|
+
this.recordFacts.delete(objectKey);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
let firstTerms = [];
|
|
1697
|
+
let firstReceipt = emptyReceipt(PROBE_R_MIN);
|
|
1698
|
+
let firstError;
|
|
1699
|
+
let firstActiveCount = 0;
|
|
1700
|
+
let firstAdmittedCount = 0;
|
|
1701
|
+
let firstCoverage = 0;
|
|
1702
|
+
let firstObjectSeen = false;
|
|
1703
|
+
let visited = 0;
|
|
1704
|
+
for (const object of input.objects) {
|
|
1705
|
+
if (!affectedObjectKeys.has(object.objectKey)) continue;
|
|
1706
|
+
visited += 1;
|
|
1707
|
+
const candidates = input.probes.map((probe) => ({
|
|
1708
|
+
identity: probe.identity,
|
|
1709
|
+
admitted: probe.admitted,
|
|
1710
|
+
distance: vectorDistance(object.position, probe.position),
|
|
1711
|
+
radius: probe.radius,
|
|
1712
|
+
irradiance: probe.irradiance
|
|
1713
|
+
}));
|
|
1714
|
+
const admission = admitProbeContributors(candidates);
|
|
1715
|
+
if (!firstObjectSeen) {
|
|
1716
|
+
firstObjectSeen = true;
|
|
1717
|
+
firstActiveCount = admission.active.length;
|
|
1718
|
+
firstAdmittedCount = admission.admitted.length;
|
|
1719
|
+
firstReceipt = admission.receipt;
|
|
1720
|
+
}
|
|
1721
|
+
const previousRecord = this.records.get(object.objectKey);
|
|
1722
|
+
const lastKnownGood = input.lastKnownGood ?? previousRecord?.lastKnownGood ?? false;
|
|
1723
|
+
let record;
|
|
1724
|
+
if (admission.error !== void 0) firstError ??= admission.error;
|
|
1725
|
+
if (admission.admitted.length === 0) {
|
|
1726
|
+
if (input.probes.length === 0 && input.retainSkyResidualRecords !== true) {
|
|
1727
|
+
nextRecords.delete(object.objectKey);
|
|
1728
|
+
this.recordFacts.delete(object.objectKey);
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
const sentinel = lastKnownGood ? "no-active" : "no-lkg";
|
|
1732
|
+
record = encodeProbeRecord(
|
|
1733
|
+
object.objectKey,
|
|
1734
|
+
object.generation,
|
|
1735
|
+
0,
|
|
1736
|
+
new Array(27).fill(0),
|
|
1737
|
+
true,
|
|
1738
|
+
false,
|
|
1739
|
+
lastKnownGood,
|
|
1740
|
+
sentinel
|
|
1741
|
+
);
|
|
1742
|
+
this.recordFacts.set(object.objectKey, {
|
|
1743
|
+
contributors: [],
|
|
1744
|
+
qHatSum: 0,
|
|
1745
|
+
rStar: PROBE_R_MIN,
|
|
1746
|
+
fallbackReason: admission.error?.code ?? sentinel
|
|
1747
|
+
});
|
|
1748
|
+
} else {
|
|
1749
|
+
const blend = blendProbeCoefficients(admission.admitted, PROBE_R_MIN);
|
|
1750
|
+
if (blend === void 0) {
|
|
1751
|
+
firstError ??= {
|
|
1752
|
+
code: "invalid-admitted-prefix",
|
|
1753
|
+
activeCount: admission.active.length,
|
|
1754
|
+
capacity: PROBE_MAX_CONTRIBUTORS,
|
|
1755
|
+
receipt: admission.receipt
|
|
1756
|
+
};
|
|
1757
|
+
firstReceipt = admission.receipt;
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
record = encodeProbeRecord(
|
|
1761
|
+
object.objectKey,
|
|
1762
|
+
object.generation,
|
|
1763
|
+
blend.C,
|
|
1764
|
+
blend.SHPreblend,
|
|
1765
|
+
true,
|
|
1766
|
+
true,
|
|
1767
|
+
true,
|
|
1768
|
+
void 0
|
|
1769
|
+
);
|
|
1770
|
+
this.recordFacts.set(object.objectKey, {
|
|
1771
|
+
contributors: blend.terms,
|
|
1772
|
+
qHatSum: blend.Q,
|
|
1773
|
+
rStar: blend.rStar,
|
|
1774
|
+
fallbackReason: admission.error === void 0 ? void 0 : "capacity-exceeded-stable-prefix"
|
|
1775
|
+
});
|
|
1776
|
+
if (firstTerms.length === 0) firstTerms = blend.terms;
|
|
1777
|
+
firstCoverage = blend.C;
|
|
1778
|
+
}
|
|
1779
|
+
nextRecords.set(object.objectKey, record);
|
|
1780
|
+
firstError ??= admission.error;
|
|
1781
|
+
}
|
|
1782
|
+
const allocations = previous === void 0 ? nextRecords.size : 0;
|
|
1783
|
+
const resultRecords = Object.freeze([...nextRecords.values()]);
|
|
1784
|
+
const result = {
|
|
1785
|
+
records: resultRecords,
|
|
1786
|
+
contributors: firstTerms,
|
|
1787
|
+
activeContributorCount: firstActiveCount,
|
|
1788
|
+
admittedProbeCount: firstAdmittedCount,
|
|
1789
|
+
coverage: firstCoverage,
|
|
1790
|
+
skyResidualFraction: 1 - firstCoverage,
|
|
1791
|
+
dirtyReasons: uniqueDirtyReasons,
|
|
1792
|
+
affectedObjectKeys: [...affectedObjectKeys].sort((left, right) => left - right),
|
|
1793
|
+
visits: visited,
|
|
1794
|
+
allocations,
|
|
1795
|
+
uploads: nextRecords.size,
|
|
1796
|
+
receipt: firstReceipt,
|
|
1797
|
+
...firstError === void 0 ? {} : { error: firstError }
|
|
1798
|
+
};
|
|
1799
|
+
const projectedRecords = Object.freeze(
|
|
1800
|
+
[...nextRecords].map(([objectKey, record]) => Object.freeze({ cacheKey: objectKey, record }))
|
|
1801
|
+
);
|
|
1802
|
+
const dirtyRecords = Object.freeze(
|
|
1803
|
+
projectedRecords.filter(({ record }) => affectedObjectKeys.has(record.objectKey))
|
|
1804
|
+
);
|
|
1805
|
+
const removedCacheKeys = Object.freeze(
|
|
1806
|
+
[...this.records.keys()].filter((objectKey) => !nextRecords.has(objectKey))
|
|
1807
|
+
);
|
|
1808
|
+
const capacity = projectedRecords.reduce(
|
|
1809
|
+
(value, { record }) => Math.max(value, record.objectKey + 2),
|
|
1810
|
+
1
|
|
1811
|
+
);
|
|
1812
|
+
this.previous = input;
|
|
1813
|
+
this.records = nextRecords;
|
|
1814
|
+
this.resultRecords = resultRecords;
|
|
1815
|
+
const baseRevision = this.bufferProjection.revision;
|
|
1816
|
+
this.bufferProjection = Object.freeze({
|
|
1817
|
+
sourceIdentity: this.bufferProjectionSource,
|
|
1818
|
+
revision: baseRevision + 1,
|
|
1819
|
+
baseRevision,
|
|
1820
|
+
capacity,
|
|
1821
|
+
records: projectedRecords,
|
|
1822
|
+
dirtyRecords,
|
|
1823
|
+
removedCacheKeys
|
|
1824
|
+
});
|
|
1825
|
+
this.lastError = firstError;
|
|
1826
|
+
this.lastReceipt = firstReceipt;
|
|
1827
|
+
this.lastActiveContributorCount = firstActiveCount;
|
|
1828
|
+
this.lastAdmittedProbeCount = firstAdmittedCount;
|
|
1829
|
+
this.lastCoverage = firstCoverage;
|
|
1830
|
+
this.lastFinite = this.resultRecords.every(
|
|
1831
|
+
(record) => record.bytes.length === PROBE_BLEND_RECORD_BYTE_SIZE
|
|
1832
|
+
);
|
|
1833
|
+
this.lastSky = input.sky ?? (input.skyIrradiance === void 0 ? { available: false, irradiance: [0, 0, 0], fallbackReason: "no-skylight" } : { available: true, irradiance: input.skyIrradiance });
|
|
1834
|
+
this.lastDirtyVisitCount = visited;
|
|
1835
|
+
this.lastDirtyReasons = uniqueDirtyReasons;
|
|
1836
|
+
return result;
|
|
1837
|
+
}
|
|
1838
|
+
};
|
|
1839
|
+
|
|
1840
|
+
export { OcclusionRenderRuntime, PROBE_MAX_CONTRIBUTORS, ProbeBlendSceneProjection, SSR_HISTORY_FORMAT, SsrHistoryOwner, admitProbeContributors, blendLightProbes, createExtendedLightingState, createSsrHistoryOwner, createSsrSpatialResources, emptyBloomInspection, estimateSsrSpatialMemory, primitiveKey, primitiveKeyId, projectBarrelDistortionInspection, projectExtendedLightingInspection, promoteExtendedLightingCandidate, recordExtendedLightingFailure, scaledProbeWeight, setOcclusionRuntimeTestHooks, viewKey, viewKeyId };
|
|
1841
|
+
//# sourceMappingURL=chunk-D6WSBXJ2.mjs.map
|
|
1842
|
+
//# sourceMappingURL=chunk-D6WSBXJ2.mjs.map
|