pelican-engine 0.0.0-stage → 0.3.1
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/AGENTS.md +147 -0
- package/LICENSE +22 -0
- package/README.md +129 -2
- package/distribution.json +197 -0
- package/docs/AI-WORKFLOW.md +517 -0
- package/docs/ARCHITECTURE.md +268 -0
- package/docs/CAPABILITIES.md +264 -0
- package/docs/DECISIONS.md +107 -0
- package/docs/M3-USAGE.md +192 -0
- package/docs/PELICAN-MIGRATION.md +38 -0
- package/docs/RENDER-BACKENDS.md +50 -0
- package/docs/ROADMAP.md +239 -0
- package/docs/THIRD-PARTY-NOTICES.md +151 -0
- package/docs/api/REFERENCE.md +1435 -0
- package/docs/api/index.json +1139 -0
- package/docs/npm/DISTRIBUTION.md +130 -0
- package/docs/npm/GAME-AUTHOR.md +147 -0
- package/engine.manifest.json +15 -0
- package/lib/assets/audio.js +67 -0
- package/lib/assets/errors.js +36 -0
- package/lib/assets/glb.js +277 -0
- package/lib/assets/index.js +53 -0
- package/lib/assets/jpeg.js +199 -0
- package/lib/assets/model.js +558 -0
- package/lib/assets/optimize.js +95 -0
- package/lib/assets/texture.js +187 -0
- package/lib/compiler/distribution.js +17 -0
- package/lib/compiler/extract.js +425 -0
- package/lib/compiler/graph.js +366 -0
- package/lib/compiler/index.js +574 -0
- package/lib/compiler/normalize.js +1194 -0
- package/lib/compiler/source-map.js +25 -0
- package/lib/compiler/types.js +2 -0
- package/lib/compiler/version.js +93 -0
- package/lib/experiments/simulation/index.js +2 -0
- package/lib/experiments/simulation/math.js +41 -0
- package/lib/experiments/simulation/probe.js +215 -0
- package/lib/experiments/simulation/scenes.js +192 -0
- package/lib/experiments/simulation/solver.js +346 -0
- package/lib/experiments/simulation/types.js +1 -0
- package/lib/experiments/world/adapters.js +30 -0
- package/lib/experiments/world/geometry.js +180 -0
- package/lib/experiments/world/index.js +2 -0
- package/lib/experiments/world/probe.js +288 -0
- package/lib/experiments/world/types.js +1 -0
- package/lib/experiments/world/world.js +599 -0
- package/lib/navigation/index.js +157 -0
- package/lib/physics/index.js +909 -0
- package/lib/protocol/index.js +92 -0
- package/lib/renderer/acceleration.js +609 -0
- package/lib/renderer/browser.js +2015 -0
- package/lib/renderer/bsdf.js +260 -0
- package/lib/renderer/checkpoint.js +88 -0
- package/lib/renderer/compute-executor.js +1034 -0
- package/lib/renderer/compute-texture-assets.js +49 -0
- package/lib/renderer/compute-view-contract.js +1 -0
- package/lib/renderer/denoiser.js +361 -0
- package/lib/renderer/dielectric.js +360 -0
- package/lib/renderer/embedded-image-pixels.js +368 -0
- package/lib/renderer/emissive-lights.js +67 -0
- package/lib/renderer/environment-importance.js +55 -0
- package/lib/renderer/environment.js +346 -0
- package/lib/renderer/gameplay.js +464 -0
- package/lib/renderer/geometry.js +103 -0
- package/lib/renderer/graph-expressions.js +485 -0
- package/lib/renderer/graph-tsl.js +141 -0
- package/lib/renderer/graph.js +214 -0
- package/lib/renderer/managed-texture-sampling.js +29 -0
- package/lib/renderer/material-graphs.js +297 -0
- package/lib/renderer/medium.js +348 -0
- package/lib/renderer/models.js +1053 -0
- package/lib/renderer/motion-shader.js +40 -0
- package/lib/renderer/path-shader.js +196 -0
- package/lib/renderer/pathtrace.js +324 -0
- package/lib/renderer/postprocess-contract.js +1 -0
- package/lib/renderer/postprocess.js +494 -0
- package/lib/renderer/render-buffers.js +132 -0
- package/lib/renderer/render-textures.js +155 -0
- package/lib/renderer/startup.js +50 -0
- package/lib/renderer/surface-capture.js +199 -0
- package/lib/renderer/surface-gpu.js +270 -0
- package/lib/renderer/surface-motion.js +84 -0
- package/lib/renderer/surface-ray-shader.js +91 -0
- package/lib/renderer/surface-shader.js +88 -0
- package/lib/renderer/surface-tracer.js +1720 -0
- package/lib/renderer/surface.js +1425 -0
- package/lib/renderer/temporal-filter.js +512 -0
- package/lib/renderer/texture-pixels.js +207 -0
- package/lib/renderer/traversal-nodes.js +54 -0
- package/lib/renderer/ui.js +446 -0
- package/lib/renderer/vertex-bounds.js +127 -0
- package/lib/renderer/vertex-contract.js +1 -0
- package/lib/renderer/vertex-graphs.js +197 -0
- package/lib/renderer/volume-shader.js +50 -0
- package/lib/runtime/compute-contract.js +1 -0
- package/lib/runtime/compute-service.js +674 -0
- package/lib/runtime/contracts.js +1 -0
- package/lib/runtime/error.js +37 -0
- package/lib/runtime/events.js +189 -0
- package/lib/runtime/game-services.js +772 -0
- package/lib/runtime/host.js +839 -0
- package/lib/runtime/material-parameters.js +145 -0
- package/lib/runtime/plugins.js +1 -0
- package/lib/runtime/postprocess-contract.js +1 -0
- package/lib/runtime/postprocess.js +189 -0
- package/lib/runtime/prefabs.js +13 -0
- package/lib/runtime/random.js +88 -0
- package/lib/runtime/render-buffer-contract.js +1 -0
- package/lib/runtime/render-buffers.js +243 -0
- package/lib/runtime/render-capabilities.js +17 -0
- package/lib/runtime/render-settings.js +249 -0
- package/lib/runtime/render-texture-contract.js +1 -0
- package/lib/runtime/render-textures.js +271 -0
- package/lib/runtime/resources.js +648 -0
- package/lib/runtime/scheduler.js +765 -0
- package/lib/runtime/storage.js +169 -0
- package/lib/runtime/world.js +594 -0
- package/lib/schema/index.js +24 -0
- package/lib/sdk/graph-values.js +16 -0
- package/lib/sdk/index.d.ts +800 -0
- package/lib/sdk/index.js +82 -0
- package/lib/tools/api-index.js +113 -0
- package/lib/tools/browser-session.js +89 -0
- package/lib/tools/cli.js +513 -0
- package/lib/tools/compress-dist.js +59 -0
- package/lib/tools/control-client.js +153 -0
- package/lib/tools/control-server.js +633 -0
- package/lib/tools/diagnostic-code.js +3 -0
- package/lib/tools/doctor.js +496 -0
- package/lib/tools/npm/browser-cli.js +6 -0
- package/lib/tools/npm/entry.js +40 -0
- package/lib/tools/npm/layout.js +195 -0
- package/lib/tools/setup.js +153 -0
- package/licenses/@dimforge/rapier2d-compat/LICENSE +201 -0
- package/licenses/@dimforge/rapier3d-compat/LICENSE +201 -0
- package/licenses/@gltf-transform/core/LICENSE.md +21 -0
- package/licenses/@gltf-transform/extensions/LICENSE.md +21 -0
- package/licenses/@gltf-transform/functions/LICENSE.md +21 -0
- package/licenses/@img/colour/LICENSE.md +82 -0
- package/licenses/@img/sharp-win32-x64/LICENSE +191 -0
- package/licenses/@oxc-project/types/LICENSE +22 -0
- package/licenses/@rolldown/pluginutils/LICENSE +21 -0
- package/licenses/@tweenjs/tween.js/LICENSE +23 -0
- package/licenses/@types/ndarray/LICENSE +21 -0
- package/licenses/@types/node/LICENSE +21 -0
- package/licenses/@types/stats.js/LICENSE +21 -0
- package/licenses/@types/three/@dimforge/rapier3d-compat/LICENSE +201 -0
- package/licenses/@types/three/LICENSE +21 -0
- package/licenses/@types/webxr/LICENSE +21 -0
- package/licenses/@webgpu/types/LICENSE +26 -0
- package/licenses/THIRD-PARTY-NOTICES.md +139 -0
- package/licenses/cwise-compiler/LICENSE +22 -0
- package/licenses/detect-libc/LICENSE +201 -0
- package/licenses/esbuild/LICENSE.md +21 -0
- package/licenses/fast-png/LICENSE +21 -0
- package/licenses/fdir/LICENSE +7 -0
- package/licenses/fflate/LICENSE +21 -0
- package/licenses/iobuffer/LICENSE +22 -0
- package/licenses/iota-array/LICENSE +22 -0
- package/licenses/is-buffer/LICENSE +21 -0
- package/licenses/ktx-parse/LICENSE +21 -0
- package/licenses/lightningcss/LICENSE +373 -0
- package/licenses/lightningcss-win32-x64-msvc/LICENSE +373 -0
- package/licenses/meshoptimizer/LICENSE.md +21 -0
- package/licenses/nanoid/LICENSE +20 -0
- package/licenses/ndarray/LICENSE +22 -0
- package/licenses/ndarray-lanczos/LICENSE +21 -0
- package/licenses/ndarray-ops/LICENSE +22 -0
- package/licenses/ndarray-pixels/LICENSE +21 -0
- package/licenses/ngraph.path/LICENSE +21 -0
- package/licenses/picocolors/LICENSE +15 -0
- package/licenses/picomatch/LICENSE +21 -0
- package/licenses/playwright/LICENSE +202 -0
- package/licenses/playwright/NOTICE +5 -0
- package/licenses/playwright-core/LICENSE +202 -0
- package/licenses/playwright-core/NOTICE +5 -0
- package/licenses/postcss/LICENSE +20 -0
- package/licenses/prettier/LICENSE +7 -0
- package/licenses/property-graph/LICENSE +21 -0
- package/licenses/rolldown/LICENSE +25 -0
- package/licenses/semver/LICENSE +15 -0
- package/licenses/sharp/LICENSE +191 -0
- package/licenses/source-map-js/LICENSE +28 -0
- package/licenses/three/LICENSE +21 -0
- package/licenses/tinyglobby/LICENSE +21 -0
- package/licenses/tsx/LICENSE +21 -0
- package/licenses/typescript/LICENSE.txt +55 -0
- package/licenses/undici-types/LICENSE +21 -0
- package/licenses/uniq/LICENSE +22 -0
- package/licenses/vite/LICENSE.md +2084 -0
- package/package.json +49 -4
- package/web/index.html +34 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
const SHADER = /* wgsl */ `
|
|
2
|
+
struct Parameters { width:u32, height:u32, history:u32, padding:u32 }
|
|
3
|
+
@group(0) @binding(0) var<storage, read> current:array<vec4f>;
|
|
4
|
+
@group(0) @binding(1) var<storage, read> normals:array<vec4f>;
|
|
5
|
+
@group(0) @binding(2) var<storage, read> albedo:array<vec4f>;
|
|
6
|
+
@group(0) @binding(3) var<storage, read> motion:array<vec4f>;
|
|
7
|
+
@group(0) @binding(4) var<storage, read> previous:array<vec4f>;
|
|
8
|
+
@group(0) @binding(5) var<storage, read> previousNormals:array<vec4f>;
|
|
9
|
+
@group(0) @binding(6) var<storage, read> previousAlbedo:array<vec4f>;
|
|
10
|
+
@group(0) @binding(7) var<storage, read_write> destination:array<vec4f>;
|
|
11
|
+
@group(0) @binding(8) var<uniform> parameters:Parameters;
|
|
12
|
+
|
|
13
|
+
fn finite4(v:vec4f)->bool {
|
|
14
|
+
return all((bitcast<vec4u>(v) & vec4u(0x7f800000u)) != vec4u(0x7f800000u));
|
|
15
|
+
}
|
|
16
|
+
fn validSample(r:vec4f)->bool { return finite4(r) && r.w>0.0; }
|
|
17
|
+
fn validGuide(n:vec4f,a:vec4f)->bool {
|
|
18
|
+
return finite4(n) && finite4(a) && n.w>=0.0
|
|
19
|
+
&& dot(n.xyz,n.xyz)>1e-12 && dot(n.xyz,n.xyz)<1e12
|
|
20
|
+
&& all(a.rgb>=vec3f(0.0)) && all(a.rgb<=vec3f(1.0))
|
|
21
|
+
&& a.w>=0.0 && a.w<=1.0;
|
|
22
|
+
}
|
|
23
|
+
fn compatible(n:vec4f,a:vec4f,pn:vec4f,pa:vec4f,expected:f32)->bool {
|
|
24
|
+
return validGuide(pn,pa)
|
|
25
|
+
&& abs(pn.w-expected)<=0.002+0.02*expected
|
|
26
|
+
&& dot(normalize(n.xyz),normalize(pn.xyz))>=0.9
|
|
27
|
+
&& all(abs(a.rgb-pa.rgb)<=vec3f(0.1)) && abs(a.w-pa.w)<=0.1;
|
|
28
|
+
}
|
|
29
|
+
fn toYCoCg(v:vec3f)->vec3f {
|
|
30
|
+
return vec3f(0.25*v.r+0.5*v.g+0.25*v.b,0.5*v.r-0.5*v.b,
|
|
31
|
+
-0.25*v.r+0.5*v.g-0.25*v.b);
|
|
32
|
+
}
|
|
33
|
+
fn fromYCoCg(v:vec3f)->vec3f { return vec3f(v.x+v.y-v.z,v.x+v.z,v.x-v.y-v.z); }
|
|
34
|
+
|
|
35
|
+
@compute @workgroup_size(8,8)
|
|
36
|
+
fn main(@builtin(global_invocation_id) id:vec3u) {
|
|
37
|
+
if (id.x>=parameters.width || id.y>=parameters.height) { return; }
|
|
38
|
+
let index=id.y*parameters.width+id.x;
|
|
39
|
+
let raw=current[index];
|
|
40
|
+
// Exact copy paths preserve invalid data for the caller to diagnose.
|
|
41
|
+
destination[index]=raw;
|
|
42
|
+
if (parameters.history==0u || !validSample(raw)) { return; }
|
|
43
|
+
let n=normals[index];
|
|
44
|
+
let a=albedo[index];
|
|
45
|
+
let m=motion[index];
|
|
46
|
+
if (!validGuide(n,a) || !finite4(m) || m.w<=0.0 || m.z<0.0
|
|
47
|
+
|| any(m.xy<vec2f(0.0)) || any(m.xy>=vec2f(1.0))) { return; }
|
|
48
|
+
// UVs refer to texel centers: (pixel + 0.5) / dimensions.
|
|
49
|
+
let p=m.xy*vec2f(f32(parameters.width),f32(parameters.height))-vec2f(0.5);
|
|
50
|
+
let base=vec2i(floor(p));
|
|
51
|
+
let f=fract(p);
|
|
52
|
+
var history=vec3f(0.0);
|
|
53
|
+
var count=0.0;
|
|
54
|
+
var support=0.0;
|
|
55
|
+
for (var y=0; y<2; y++) {
|
|
56
|
+
for (var x=0; x<2; x++) {
|
|
57
|
+
let q=base+vec2i(x,y);
|
|
58
|
+
if (any(q<vec2i(0)) || q.x>=i32(parameters.width) || q.y>=i32(parameters.height)) { continue; }
|
|
59
|
+
let qi=u32(q.y)*parameters.width+u32(q.x);
|
|
60
|
+
let r=previous[qi];
|
|
61
|
+
if (!validSample(r) || !compatible(n,a,previousNormals[qi],previousAlbedo[qi],m.z)) { continue; }
|
|
62
|
+
let w=select(1.0-f.x,f.x,x==1)*select(1.0-f.y,f.y,y==1);
|
|
63
|
+
history+=r.rgb*w;
|
|
64
|
+
count+=min(r.w,32.0)*w;
|
|
65
|
+
support+=w;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (support<0.01) { return; }
|
|
69
|
+
history/=support;
|
|
70
|
+
count/=support;
|
|
71
|
+
if (!finite4(vec4f(history,count))) { return; }
|
|
72
|
+
var lo=toYCoCg(raw.rgb);
|
|
73
|
+
var hi=lo;
|
|
74
|
+
for (var y=-1; y<=1; y++) {
|
|
75
|
+
for (var x=-1; x<=1; x++) {
|
|
76
|
+
let q=vec2i(id.xy)+vec2i(x,y);
|
|
77
|
+
if (any(q<vec2i(0)) || q.x>=i32(parameters.width) || q.y>=i32(parameters.height)) { continue; }
|
|
78
|
+
let qi=u32(q.y)*parameters.width+u32(q.x);
|
|
79
|
+
let r=current[qi];
|
|
80
|
+
if (!validSample(r) || !compatible(n,a,normals[qi],albedo[qi],n.w)) { continue; }
|
|
81
|
+
let c=toYCoCg(r.rgb);
|
|
82
|
+
lo=min(lo,c);
|
|
83
|
+
hi=max(hi,c);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
let clipped=fromYCoCg(clamp(toYCoCg(history),lo,hi));
|
|
87
|
+
let weight=count/(raw.w+count);
|
|
88
|
+
let result=raw.rgb*(1.0-weight)+clipped*weight;
|
|
89
|
+
// Invalid history must not contaminate a valid current sample.
|
|
90
|
+
if (finite4(vec4f(result,count))) { destination[index]=vec4f(result,raw.w+count); }
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
function fail(code, message) {
|
|
94
|
+
throw new Error(`TC_TEMPORAL_${code}: ${message}`);
|
|
95
|
+
}
|
|
96
|
+
function validateOptions(options) {
|
|
97
|
+
if (!options || typeof options !== "object" || Array.isArray(options))
|
|
98
|
+
fail("OPTIONS", "Expected an options object.");
|
|
99
|
+
for (const key of ["enabled", "reset", "progressive"])
|
|
100
|
+
if (options[key] !== undefined && typeof options[key] !== "boolean")
|
|
101
|
+
fail("OPTIONS", `${key} must be boolean.`);
|
|
102
|
+
}
|
|
103
|
+
async function creationDeadline(work, device) {
|
|
104
|
+
let timer;
|
|
105
|
+
try {
|
|
106
|
+
return await Promise.race([
|
|
107
|
+
work,
|
|
108
|
+
device.lost.then((info) => fail("DEVICE_LOST", `Device lost during creation: ${info.message}`)),
|
|
109
|
+
new Promise((_, reject) => {
|
|
110
|
+
timer = setTimeout(() => reject(new Error("TC_TEMPORAL_TIMEOUT: Creation exceeded 10000 ms.")), 10_000);
|
|
111
|
+
}),
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const OWNED_BUDGET = 512 * 1024 * 1024;
|
|
119
|
+
class GpuTemporalFilter {
|
|
120
|
+
device;
|
|
121
|
+
pipeline;
|
|
122
|
+
layout;
|
|
123
|
+
allocation;
|
|
124
|
+
historyValid = false;
|
|
125
|
+
disposed = false;
|
|
126
|
+
lost = false;
|
|
127
|
+
passes = 0;
|
|
128
|
+
lastPasses = 0;
|
|
129
|
+
constructor(device, pipeline, layout) {
|
|
130
|
+
this.device = device;
|
|
131
|
+
this.pipeline = pipeline;
|
|
132
|
+
this.layout = layout;
|
|
133
|
+
const reference = new WeakRef(this);
|
|
134
|
+
void device.lost.then(() => {
|
|
135
|
+
const self = reference.deref();
|
|
136
|
+
if (!self)
|
|
137
|
+
return;
|
|
138
|
+
self.lost = true;
|
|
139
|
+
self.release();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
release() {
|
|
143
|
+
this.allocation?.buffers.forEach((buffer) => buffer.destroy());
|
|
144
|
+
this.allocation = undefined;
|
|
145
|
+
this.historyValid = false;
|
|
146
|
+
}
|
|
147
|
+
allocate(width, height, size, stride) {
|
|
148
|
+
const buffers = [];
|
|
149
|
+
try {
|
|
150
|
+
for (let index = 0; index < 5; index++) {
|
|
151
|
+
const buffer = this.device.createBuffer({
|
|
152
|
+
label: `pelican temporal filter ${index}`,
|
|
153
|
+
size: index === 4 ? stride * 2 : size,
|
|
154
|
+
usage: index === 4
|
|
155
|
+
? GPUBufferUsage.UNIFORM
|
|
156
|
+
: GPUBufferUsage.STORAGE |
|
|
157
|
+
(index < 2 ? GPUBufferUsage.COPY_SRC : GPUBufferUsage.COPY_DST),
|
|
158
|
+
mappedAtCreation: true,
|
|
159
|
+
});
|
|
160
|
+
buffers.push(buffer);
|
|
161
|
+
const mapped = buffer.getMappedRange();
|
|
162
|
+
if (index === 4) {
|
|
163
|
+
for (let history = 0; history < 2; history++)
|
|
164
|
+
new Uint32Array(mapped, history * stride, 4).set([
|
|
165
|
+
width,
|
|
166
|
+
height,
|
|
167
|
+
history,
|
|
168
|
+
0,
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
171
|
+
buffer.unmap();
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
width,
|
|
175
|
+
height,
|
|
176
|
+
size,
|
|
177
|
+
stride,
|
|
178
|
+
buffers: buffers,
|
|
179
|
+
previous: 1,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
buffers.forEach((buffer) => buffer.destroy());
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
encode(encoder, inputRadiance, normalDepth, albedoRoughness, reprojection, width, height, options = {}) {
|
|
188
|
+
if (this.disposed)
|
|
189
|
+
fail("DISPOSED", "Create a new temporal filter after dispose().");
|
|
190
|
+
if (this.lost)
|
|
191
|
+
fail("DEVICE_LOST", "Create a temporal filter on a new device.");
|
|
192
|
+
validateOptions(options);
|
|
193
|
+
const size = width * height * 16;
|
|
194
|
+
const limits = this.device.limits;
|
|
195
|
+
const alignment = limits.minUniformBufferOffsetAlignment;
|
|
196
|
+
const stride = Math.ceil(16 / alignment) * alignment;
|
|
197
|
+
if (!Number.isSafeInteger(width) ||
|
|
198
|
+
!Number.isSafeInteger(height) ||
|
|
199
|
+
width < 1 ||
|
|
200
|
+
height < 1 ||
|
|
201
|
+
width > 0x7fffffff ||
|
|
202
|
+
height > 0x7fffffff ||
|
|
203
|
+
!Number.isSafeInteger(size) ||
|
|
204
|
+
width * height > 0xffffffff ||
|
|
205
|
+
size > limits.maxBufferSize ||
|
|
206
|
+
size > limits.maxStorageBufferBindingSize ||
|
|
207
|
+
Math.ceil(width / 8) > limits.maxComputeWorkgroupsPerDimension ||
|
|
208
|
+
Math.ceil(height / 8) > limits.maxComputeWorkgroupsPerDimension)
|
|
209
|
+
fail("DIMENSION", "Positive integer dimensions must fit storage and dispatch limits.");
|
|
210
|
+
if (!Number.isSafeInteger(stride) ||
|
|
211
|
+
stride < 16 ||
|
|
212
|
+
stride * 2 > limits.maxBufferSize ||
|
|
213
|
+
size * 4 + stride * 2 > OWNED_BUDGET)
|
|
214
|
+
fail("BUDGET", "Owned buffers must fit device limits and the 512 MiB temporal budget.");
|
|
215
|
+
for (const [label, buffer, copy] of [
|
|
216
|
+
["inputRadiance", inputRadiance, false],
|
|
217
|
+
["normalDepth", normalDepth, true],
|
|
218
|
+
["albedoRoughness", albedoRoughness, true],
|
|
219
|
+
["reprojection", reprojection, false],
|
|
220
|
+
]) {
|
|
221
|
+
if (!buffer ||
|
|
222
|
+
!Number.isSafeInteger(buffer.size) ||
|
|
223
|
+
buffer.size < size ||
|
|
224
|
+
buffer.size % 16 !== 0 ||
|
|
225
|
+
!(buffer.usage & GPUBufferUsage.STORAGE) ||
|
|
226
|
+
(copy &&
|
|
227
|
+
options.enabled !== false &&
|
|
228
|
+
!(buffer.usage & GPUBufferUsage.COPY_SRC)) ||
|
|
229
|
+
buffer.mapState !== "unmapped")
|
|
230
|
+
fail("BUFFER", `${label} needs unmapped STORAGE vec4f capacity >= ${size} bytes; enabled guides also need COPY_SRC.`);
|
|
231
|
+
if (options.enabled !== false &&
|
|
232
|
+
this.allocation?.buffers.includes(buffer))
|
|
233
|
+
fail("ALIAS", `${label} cannot alias temporal-owned buffers; copy it first.`);
|
|
234
|
+
}
|
|
235
|
+
if (!encoder ||
|
|
236
|
+
typeof encoder.beginComputePass !== "function" ||
|
|
237
|
+
(options.enabled !== false &&
|
|
238
|
+
typeof encoder.copyBufferToBuffer !== "function"))
|
|
239
|
+
fail("ENCODER", "Provide an open command encoder on the same device with copy support.");
|
|
240
|
+
if (options.enabled === false) {
|
|
241
|
+
this.historyValid = false;
|
|
242
|
+
this.lastPasses = 0;
|
|
243
|
+
return inputRadiance;
|
|
244
|
+
}
|
|
245
|
+
const old = this.allocation;
|
|
246
|
+
const next = old?.width === width && old.height === height
|
|
247
|
+
? old
|
|
248
|
+
: this.allocate(width, height, size, stride);
|
|
249
|
+
const useHistory = next === old &&
|
|
250
|
+
this.historyValid &&
|
|
251
|
+
!options.reset &&
|
|
252
|
+
!options.progressive;
|
|
253
|
+
const dest = 1 - next.previous;
|
|
254
|
+
try {
|
|
255
|
+
const buffers = [
|
|
256
|
+
inputRadiance,
|
|
257
|
+
normalDepth,
|
|
258
|
+
albedoRoughness,
|
|
259
|
+
reprojection,
|
|
260
|
+
next.buffers[next.previous],
|
|
261
|
+
next.buffers[2],
|
|
262
|
+
next.buffers[3],
|
|
263
|
+
next.buffers[dest],
|
|
264
|
+
];
|
|
265
|
+
const group = this.device.createBindGroup({
|
|
266
|
+
label: "pelican temporal filter",
|
|
267
|
+
layout: this.layout,
|
|
268
|
+
entries: [
|
|
269
|
+
...buffers.map((buffer, binding) => ({
|
|
270
|
+
binding,
|
|
271
|
+
resource: { buffer, offset: 0, size },
|
|
272
|
+
})),
|
|
273
|
+
{
|
|
274
|
+
binding: 8,
|
|
275
|
+
resource: {
|
|
276
|
+
buffer: next.buffers[4],
|
|
277
|
+
offset: useHistory ? stride : 0,
|
|
278
|
+
size: 16,
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
});
|
|
283
|
+
// No async error scopes in synchronous encode. Caller owns submission validation.
|
|
284
|
+
const pass = encoder.beginComputePass({
|
|
285
|
+
label: "pelican temporal filter",
|
|
286
|
+
});
|
|
287
|
+
pass.setPipeline(this.pipeline);
|
|
288
|
+
pass.setBindGroup(0, group);
|
|
289
|
+
pass.dispatchWorkgroups(Math.ceil(width / 8), Math.ceil(height / 8));
|
|
290
|
+
pass.end();
|
|
291
|
+
encoder.copyBufferToBuffer(normalDepth, 0, next.buffers[2], 0, size);
|
|
292
|
+
encoder.copyBufferToBuffer(albedoRoughness, 0, next.buffers[3], 0, size);
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
if (next !== old)
|
|
296
|
+
next.buffers.forEach((buffer) => buffer.destroy());
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
if (next !== old) {
|
|
300
|
+
this.allocation = next;
|
|
301
|
+
old?.buffers.forEach((buffer) => buffer.destroy());
|
|
302
|
+
}
|
|
303
|
+
next.previous = dest;
|
|
304
|
+
this.historyValid = true;
|
|
305
|
+
this.passes++;
|
|
306
|
+
this.lastPasses = 1;
|
|
307
|
+
return next.buffers[dest];
|
|
308
|
+
}
|
|
309
|
+
reset() {
|
|
310
|
+
this.historyValid = false;
|
|
311
|
+
}
|
|
312
|
+
inspect() {
|
|
313
|
+
const a = this.allocation;
|
|
314
|
+
return {
|
|
315
|
+
entries: a ? 5 : 0,
|
|
316
|
+
bytes: a ? a.size * 4 + a.stride * 2 : 0,
|
|
317
|
+
passes: this.passes,
|
|
318
|
+
lastPasses: this.lastPasses,
|
|
319
|
+
dimension: { width: a?.width ?? 0, height: a?.height ?? 0 },
|
|
320
|
+
historyValid: this.historyValid,
|
|
321
|
+
disposed: this.disposed,
|
|
322
|
+
lost: this.lost,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
dispose() {
|
|
326
|
+
if (this.disposed)
|
|
327
|
+
return;
|
|
328
|
+
this.release();
|
|
329
|
+
this.disposed = true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
export async function createTemporalFilter(device) {
|
|
333
|
+
let pending;
|
|
334
|
+
let layout;
|
|
335
|
+
device.pushErrorScope("validation");
|
|
336
|
+
try {
|
|
337
|
+
layout = device.createBindGroupLayout({
|
|
338
|
+
label: "pelican temporal readonly ABI",
|
|
339
|
+
entries: Array.from({ length: 9 }, (_, binding) => ({
|
|
340
|
+
binding,
|
|
341
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
342
|
+
buffer: {
|
|
343
|
+
type: binding === 8
|
|
344
|
+
? "uniform"
|
|
345
|
+
: binding === 7
|
|
346
|
+
? "storage"
|
|
347
|
+
: "read-only-storage",
|
|
348
|
+
minBindingSize: 16,
|
|
349
|
+
},
|
|
350
|
+
})),
|
|
351
|
+
});
|
|
352
|
+
pending = device.createComputePipelineAsync({
|
|
353
|
+
label: "pelican temporal reprojection filter",
|
|
354
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
|
|
355
|
+
compute: {
|
|
356
|
+
module: device.createShaderModule({ code: SHADER }),
|
|
357
|
+
entryPoint: "main",
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
void device.popErrorScope().catch(() => { });
|
|
363
|
+
throw new Error("TC_TEMPORAL_PIPELINE: Pipeline setup failed.", {
|
|
364
|
+
cause: error,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
const scope = device.popErrorScope();
|
|
368
|
+
try {
|
|
369
|
+
const [pipeline, validation] = await creationDeadline(Promise.all([pending, scope]), device);
|
|
370
|
+
if (validation)
|
|
371
|
+
fail("VALIDATION", validation.message);
|
|
372
|
+
return new GpuTemporalFilter(device, pipeline, layout);
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
throw new Error("TC_TEMPORAL_PIPELINE: Cannot create temporal filter.", {
|
|
376
|
+
cause: error,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const pixel = (data, i) => [
|
|
381
|
+
data[i * 4],
|
|
382
|
+
data[i * 4 + 1],
|
|
383
|
+
data[i * 4 + 2],
|
|
384
|
+
data[i * 4 + 3],
|
|
385
|
+
];
|
|
386
|
+
const finite = (v) => v.every((x) => Number.isFinite(x) && Math.abs(x) <= 3.4028234663852886e38);
|
|
387
|
+
const sample = (r) => finite(r) && r[3] > 0;
|
|
388
|
+
const length2 = (n) => n[0] * n[0] + n[1] * n[1] + n[2] * n[2];
|
|
389
|
+
const guide = (n, a) => finite(n) &&
|
|
390
|
+
finite(a) &&
|
|
391
|
+
n[3] >= 0 &&
|
|
392
|
+
length2(n) > 1e-12 &&
|
|
393
|
+
length2(n) < 1e12 &&
|
|
394
|
+
a.every((v) => v >= 0 && v <= 1);
|
|
395
|
+
function compatible(n, a, pn, pa, expected) {
|
|
396
|
+
return (guide(pn, pa) &&
|
|
397
|
+
Math.abs(pn[3] - expected) <= 0.002 + 0.02 * expected &&
|
|
398
|
+
(n[0] * pn[0] + n[1] * pn[1] + n[2] * pn[2]) /
|
|
399
|
+
Math.sqrt(length2(n) * length2(pn)) >=
|
|
400
|
+
0.9 &&
|
|
401
|
+
a.every((v, i) => Math.abs(v - pa[i]) <= 0.1));
|
|
402
|
+
}
|
|
403
|
+
function toYCoCg(v) {
|
|
404
|
+
return [
|
|
405
|
+
0.25 * v[0] + 0.5 * v[1] + 0.25 * v[2],
|
|
406
|
+
0.5 * v[0] - 0.5 * v[2],
|
|
407
|
+
-0.25 * v[0] + 0.5 * v[1] - 0.25 * v[2],
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
function fromYCoCg(v) {
|
|
411
|
+
return [v[0] + v[1] - v[2], v[0] + v[2], v[0] - v[1] - v[2]];
|
|
412
|
+
}
|
|
413
|
+
/** Stateless CPU oracle; double intermediates, f32 output, not bit-exact GPU arithmetic. */
|
|
414
|
+
export function temporalFilterReference(input) {
|
|
415
|
+
validateOptions(input);
|
|
416
|
+
const { width, height, inputRadiance, normalDepth, albedoRoughness, reprojection, previousRadiance, previousNormalDepth, previousAlbedoRoughness, } = input;
|
|
417
|
+
const size = width * height * 4;
|
|
418
|
+
if (!Number.isSafeInteger(width) ||
|
|
419
|
+
!Number.isSafeInteger(height) ||
|
|
420
|
+
width < 1 ||
|
|
421
|
+
height < 1 ||
|
|
422
|
+
!Number.isSafeInteger(size) ||
|
|
423
|
+
size > 0xffffffff)
|
|
424
|
+
fail("DIMENSION", "CPU reference needs positive bounded integer dimensions.");
|
|
425
|
+
for (const data of [
|
|
426
|
+
inputRadiance,
|
|
427
|
+
normalDepth,
|
|
428
|
+
albedoRoughness,
|
|
429
|
+
reprojection,
|
|
430
|
+
])
|
|
431
|
+
if (!data || data.length < size)
|
|
432
|
+
fail("BUFFER", "CPU input arrays need four floats per pixel.");
|
|
433
|
+
for (const data of [
|
|
434
|
+
previousRadiance,
|
|
435
|
+
previousNormalDepth,
|
|
436
|
+
previousAlbedoRoughness,
|
|
437
|
+
])
|
|
438
|
+
if (data && data.length < size)
|
|
439
|
+
fail("BUFFER", "CPU history arrays need four floats per pixel.");
|
|
440
|
+
const output = new Float32Array(size);
|
|
441
|
+
for (let i = 0; i < size; i++)
|
|
442
|
+
output[i] = inputRadiance[i];
|
|
443
|
+
if (input.enabled === false ||
|
|
444
|
+
input.reset ||
|
|
445
|
+
input.progressive ||
|
|
446
|
+
!previousRadiance ||
|
|
447
|
+
!previousNormalDepth ||
|
|
448
|
+
!previousAlbedoRoughness)
|
|
449
|
+
return output;
|
|
450
|
+
for (let i = 0; i < width * height; i++) {
|
|
451
|
+
const r = pixel(inputRadiance, i), n = pixel(normalDepth, i), a = pixel(albedoRoughness, i), m = pixel(reprojection, i);
|
|
452
|
+
if (!sample(r) ||
|
|
453
|
+
!guide(n, a) ||
|
|
454
|
+
!finite(m) ||
|
|
455
|
+
m[3] <= 0 ||
|
|
456
|
+
m[2] < 0 ||
|
|
457
|
+
m[0] < 0 ||
|
|
458
|
+
m[1] < 0 ||
|
|
459
|
+
m[0] >= 1 ||
|
|
460
|
+
m[1] >= 1)
|
|
461
|
+
continue;
|
|
462
|
+
const px = m[0] * width - 0.5, py = m[1] * height - 0.5;
|
|
463
|
+
const bx = Math.floor(px), by = Math.floor(py), fx = px - bx, fy = py - by;
|
|
464
|
+
const history = [0, 0, 0];
|
|
465
|
+
let support = 0, count = 0;
|
|
466
|
+
for (let y = 0; y < 2; y++)
|
|
467
|
+
for (let x = 0; x < 2; x++) {
|
|
468
|
+
const qx = bx + x, qy = by + y;
|
|
469
|
+
if (qx < 0 || qy < 0 || qx >= width || qy >= height)
|
|
470
|
+
continue;
|
|
471
|
+
const qi = qy * width + qx, pr = pixel(previousRadiance, qi);
|
|
472
|
+
if (!sample(pr) ||
|
|
473
|
+
!compatible(n, a, pixel(previousNormalDepth, qi), pixel(previousAlbedoRoughness, qi), m[2]))
|
|
474
|
+
continue;
|
|
475
|
+
const w = (x === 1 ? fx : 1 - fx) * (y === 1 ? fy : 1 - fy);
|
|
476
|
+
for (let k = 0; k < 3; k++)
|
|
477
|
+
history[k] += pr[k] * w;
|
|
478
|
+
count += Math.min(pr[3], 32) * w;
|
|
479
|
+
support += w;
|
|
480
|
+
}
|
|
481
|
+
if (support < 0.01)
|
|
482
|
+
continue;
|
|
483
|
+
for (let k = 0; k < 3; k++)
|
|
484
|
+
history[k] /= support;
|
|
485
|
+
count /= support;
|
|
486
|
+
if (!finite([...history, count]))
|
|
487
|
+
continue;
|
|
488
|
+
const lo = toYCoCg(r), hi = [...lo];
|
|
489
|
+
for (let y = -1; y <= 1; y++)
|
|
490
|
+
for (let x = -1; x <= 1; x++) {
|
|
491
|
+
const qx = (i % width) + x, qy = Math.floor(i / width) + y;
|
|
492
|
+
if (qx < 0 || qy < 0 || qx >= width || qy >= height)
|
|
493
|
+
continue;
|
|
494
|
+
const qi = qy * width + qx, nr = pixel(inputRadiance, qi);
|
|
495
|
+
if (!sample(nr) ||
|
|
496
|
+
!compatible(n, a, pixel(normalDepth, qi), pixel(albedoRoughness, qi), n[3]))
|
|
497
|
+
continue;
|
|
498
|
+
const c = toYCoCg(nr);
|
|
499
|
+
for (let k = 0; k < 3; k++) {
|
|
500
|
+
lo[k] = Math.min(lo[k], c[k]);
|
|
501
|
+
hi[k] = Math.max(hi[k], c[k]);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const h = toYCoCg(history);
|
|
505
|
+
const clipped = fromYCoCg(h.map((v, k) => Math.max(lo[k], Math.min(hi[k], v))));
|
|
506
|
+
const w = count / (r[3] + count);
|
|
507
|
+
const result = clipped.map((v, k) => r[k] * (1 - w) + v * w);
|
|
508
|
+
if (finite([...result, count]))
|
|
509
|
+
output.set([...result, r[3] + count], i * 4);
|
|
510
|
+
}
|
|
511
|
+
return output;
|
|
512
|
+
}
|