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,49 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { createTexturePixelProvider } from "./texture-pixels.js";
|
|
3
|
+
/** Borrows loaded assets, owns decoded CPU pixels only; never returns Three/GPU objects. */
|
|
4
|
+
export function createComputeTextureAssets(textures) {
|
|
5
|
+
const pixels = createTexturePixelProvider(), cache = new Map();
|
|
6
|
+
let disposed = false;
|
|
7
|
+
const fail = (id, detail) => { throw new Error("TC_COMPUTE_TEXTURE_ASSET: " + id + ": " + detail + " Repair: Use a declared loaded RGBA texture with a supported color space and readable pixels."); };
|
|
8
|
+
return {
|
|
9
|
+
resolve(id) {
|
|
10
|
+
if (disposed)
|
|
11
|
+
return fail(id, "Asset provider is disposed.");
|
|
12
|
+
const texture = textures.get(id) ?? fail(id, "Unknown texture asset.");
|
|
13
|
+
const signature = [texture, texture.version, texture.source, texture.source.version, texture.image, texture.colorSpace, texture.flipY];
|
|
14
|
+
const old = cache.get(id);
|
|
15
|
+
if (old && old.signature.every((v, i) => v === signature[i]))
|
|
16
|
+
return old.value;
|
|
17
|
+
if (![THREE.SRGBColorSpace, THREE.LinearSRGBColorSpace, THREE.NoColorSpace].includes(texture.colorSpace) || texture.format !== THREE.RGBAFormat || texture.premultiplyAlpha || texture.isRenderTargetTexture)
|
|
18
|
+
return fail(id, "Unsupported format, color space or premultiplication.");
|
|
19
|
+
const native = texture;
|
|
20
|
+
const decoded = native.isDataTexture ? undefined : pixels.decode(texture);
|
|
21
|
+
const width = decoded?.width ?? native.image?.width, height = decoded?.height ?? native.image?.height;
|
|
22
|
+
const source = decoded?.data ?? (native.isDataTexture ? native.image?.data : undefined);
|
|
23
|
+
const float = decoded?.encoding === "float32" || (native.isDataTexture && texture.type === THREE.FloatType);
|
|
24
|
+
if (!Number.isSafeInteger(width) || !Number.isSafeInteger(height) || width < 1 || height < 1 || width * height > 2 ** 24 || !source || source.length !== width * height * 4 || (native.isDataTexture && ![THREE.FloatType, THREE.UnsignedByteType].includes(texture.type)))
|
|
25
|
+
return fail(id, "Expected readable RGBA8 or float32 pixels and valid dimensions.");
|
|
26
|
+
const flip = decoded?.origin === "image" && texture.flipY, srgb = texture.colorSpace === THREE.SRGBColorSpace;
|
|
27
|
+
const data = [];
|
|
28
|
+
for (let y = 0; y < height; y++)
|
|
29
|
+
for (let x = 0; x < width; x++) {
|
|
30
|
+
const offset = ((flip ? height - 1 - y : y) * width + x) * 4, rgba = [];
|
|
31
|
+
for (let c = 0; c < 4; c++) {
|
|
32
|
+
let value = Number(source[offset + c]) / (float ? 1 : 255);
|
|
33
|
+
if (srgb && c < 3)
|
|
34
|
+
value = value <= 0.04045 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
|
|
35
|
+
value = Math.fround(value);
|
|
36
|
+
if (!Number.isFinite(value))
|
|
37
|
+
return fail(id, "Decoded texels must fit finite f32.");
|
|
38
|
+
rgba.push(value);
|
|
39
|
+
}
|
|
40
|
+
data.push(Object.freeze(rgba));
|
|
41
|
+
}
|
|
42
|
+
const value = Object.freeze({ width, height, data: Object.freeze(data) });
|
|
43
|
+
cache.set(id, { signature, value });
|
|
44
|
+
return value;
|
|
45
|
+
},
|
|
46
|
+
dispose() { if (disposed)
|
|
47
|
+
return; disposed = true; cache.clear(); pixels.dispose(); },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// Dammertz et al., HPG 2010, DOI 10.2312/EGGH/HPG10/067-075.
|
|
2
|
+
// B3-spline a-trous, with conservative distance/material/original-luma guides.
|
|
3
|
+
const SHADER = /* wgsl */ `
|
|
4
|
+
struct Parameters { width:u32, height:u32, step:u32, padding:u32 }
|
|
5
|
+
@group(0) @binding(0) var<storage, read> source:array<vec4f>;
|
|
6
|
+
@group(0) @binding(1) var<storage, read> normals:array<vec4f>;
|
|
7
|
+
@group(0) @binding(2) var<storage, read> albedo:array<vec4f>;
|
|
8
|
+
@group(0) @binding(3) var<storage, read> original:array<vec4f>;
|
|
9
|
+
@group(0) @binding(4) var<storage, read_write> destination:array<vec4f>;
|
|
10
|
+
@group(0) @binding(5) var<uniform> parameters:Parameters;
|
|
11
|
+
|
|
12
|
+
fn finite4(v:vec4f)->bool {
|
|
13
|
+
return all((bitcast<vec4u>(v) & vec4u(0x7f800000u)) != vec4u(0x7f800000u));
|
|
14
|
+
}
|
|
15
|
+
fn validSample(v:vec4f)->bool {
|
|
16
|
+
return finite4(v) && v.w > 0.0 && all(abs(v.rgb) <= vec3f(1e20));
|
|
17
|
+
}
|
|
18
|
+
fn clean(v:vec4f)->vec4f {
|
|
19
|
+
var rgb=vec3f(0.0);
|
|
20
|
+
var count=0.0;
|
|
21
|
+
if (finite4(vec4f(v.rgb,0.0)) && all(abs(v.rgb)<=vec3f(1e20))) {
|
|
22
|
+
rgb=max(v.rgb,vec3f(0.0));
|
|
23
|
+
}
|
|
24
|
+
if (finite4(vec4f(v.w)) && v.w>=0.0) { count=v.w; }
|
|
25
|
+
if (count==0.0) { rgb=vec3f(0.0); }
|
|
26
|
+
return vec4f(rgb,count);
|
|
27
|
+
}
|
|
28
|
+
fn validGuide(n:vec4f,a:vec4f)->bool {
|
|
29
|
+
return finite4(n) && finite4(a) && n.w>=0.0 && n.w<=1e20
|
|
30
|
+
&& dot(n.xyz,n.xyz)>1e-12 && dot(n.xyz,n.xyz)<1e12
|
|
31
|
+
&& all(a.rgb>=vec3f(0.0)) && all(a.rgb<=vec3f(1.0))
|
|
32
|
+
&& a.w>=0.0 && a.w<=1.0;
|
|
33
|
+
}
|
|
34
|
+
fn luma(v:vec3f)->f32 { return dot(v,vec3f(0.2126,0.7152,0.0722)); }
|
|
35
|
+
fn spline(i:i32)->f32 {
|
|
36
|
+
if (i==0) { return 0.375; }
|
|
37
|
+
if (abs(i)==1) { return 0.25; }
|
|
38
|
+
return 0.0625;
|
|
39
|
+
}
|
|
40
|
+
@compute @workgroup_size(8,8)
|
|
41
|
+
fn main(@builtin(global_invocation_id) id:vec3u) {
|
|
42
|
+
if (id.x>=parameters.width || id.y>=parameters.height) { return; }
|
|
43
|
+
let index=id.y*parameters.width+id.x;
|
|
44
|
+
let raw=original[index];
|
|
45
|
+
let center=clean(source[index]);
|
|
46
|
+
let n=normals[index];
|
|
47
|
+
let a=albedo[index];
|
|
48
|
+
// Invalid originals never become contributors in subsequent iterations.
|
|
49
|
+
// Misses and invalid guides are isolated, not blended into surface hits.
|
|
50
|
+
if (!validSample(raw) || !validGuide(n,a)) {
|
|
51
|
+
destination[index]=clean(raw);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
let cn=normalize(n.xyz);
|
|
55
|
+
let cl=luma(max(raw.rgb,vec3f(0.0)));
|
|
56
|
+
var total=vec3f(0.0);
|
|
57
|
+
var weights=0.0;
|
|
58
|
+
for (var y=-2; y<=2; y++) {
|
|
59
|
+
for (var x=-2; x<=2; x++) {
|
|
60
|
+
let q=vec2i(id.xy)+vec2i(x,y)*i32(parameters.step);
|
|
61
|
+
if (any(q<vec2i(0)) || q.x>=i32(parameters.width) || q.y>=i32(parameters.height)) { continue; }
|
|
62
|
+
let qi=u32(q.y)*parameters.width+u32(q.x);
|
|
63
|
+
let r=original[qi];
|
|
64
|
+
let nn=normals[qi];
|
|
65
|
+
let aa=albedo[qi];
|
|
66
|
+
if (!validSample(r) || !validGuide(nn,aa)) { continue; }
|
|
67
|
+
let cosine=clamp(dot(cn,normalize(nn.xyz)),-1.0,1.0);
|
|
68
|
+
let depth=abs(n.w-nn.w);
|
|
69
|
+
let depthLimit=0.002+0.03*max(min(n.w,nn.w),0.01);
|
|
70
|
+
let rough=abs(a.w-aa.w);
|
|
71
|
+
if (cosine<0.85 || depth>depthLimit || rough>0.2) { continue; }
|
|
72
|
+
let ad=a.rgb-aa.rgb;
|
|
73
|
+
let al=luma(a.rgb)-luma(aa.rgb);
|
|
74
|
+
let ql=luma(max(r.rgb,vec3f(0.0)));
|
|
75
|
+
// Original, not progressively blurred, luma protects same-material lighting.
|
|
76
|
+
let sigma=0.03+(0.12+0.4*min(a.w,aa.w))*max(cl,ql)
|
|
77
|
+
/sqrt(clamp(min(raw.w,r.w),1.0,16.0));
|
|
78
|
+
let dl=(cl-ql)/sigma;
|
|
79
|
+
let w=spline(x)*spline(y)*exp(-64.0*(1.0-cosine)
|
|
80
|
+
-4.0*(depth/depthLimit)*(depth/depthLimit)
|
|
81
|
+
-50.0*dot(ad,ad)-50.0*al*al-64.0*rough*rough-0.5*dl*dl);
|
|
82
|
+
total+=clean(source[qi]).rgb*w;
|
|
83
|
+
weights+=w;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
var rgb=center.rgb;
|
|
87
|
+
if (weights>1e-8) { rgb=total/weights; }
|
|
88
|
+
destination[index]=vec4f(rgb,raw.w);
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
function fail(code, message) {
|
|
92
|
+
throw new Error(`TC_DENOISER_${code}: ${message}`);
|
|
93
|
+
}
|
|
94
|
+
async function creationDeadline(work, device) {
|
|
95
|
+
let timer;
|
|
96
|
+
try {
|
|
97
|
+
return await Promise.race([
|
|
98
|
+
work,
|
|
99
|
+
device.lost.then((info) => fail("DEVICE_LOST", `Device lost during creation: ${info.message}`)),
|
|
100
|
+
new Promise((_, reject) => {
|
|
101
|
+
timer = setTimeout(() => reject(new Error("TC_DENOISER_TIMEOUT: Creation exceeded 10000 ms.")), 10_000);
|
|
102
|
+
}),
|
|
103
|
+
]);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
class SpatialDenoiser {
|
|
110
|
+
device;
|
|
111
|
+
pipeline;
|
|
112
|
+
layout;
|
|
113
|
+
allocation;
|
|
114
|
+
disposed = false;
|
|
115
|
+
lost = false;
|
|
116
|
+
passes = 0;
|
|
117
|
+
lastPasses = 0;
|
|
118
|
+
constructor(device, pipeline, layout) {
|
|
119
|
+
this.device = device;
|
|
120
|
+
this.pipeline = pipeline;
|
|
121
|
+
this.layout = layout;
|
|
122
|
+
const reference = new WeakRef(this);
|
|
123
|
+
void device.lost.then(() => {
|
|
124
|
+
const self = reference.deref();
|
|
125
|
+
if (!self)
|
|
126
|
+
return;
|
|
127
|
+
self.lost = true;
|
|
128
|
+
self.release();
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
release() {
|
|
132
|
+
this.allocation?.buffers.forEach((buffer) => buffer.destroy());
|
|
133
|
+
this.allocation = undefined;
|
|
134
|
+
}
|
|
135
|
+
allocate(width, height, size) {
|
|
136
|
+
const buffers = [];
|
|
137
|
+
const stride = Math.ceil(16 / this.device.limits.minUniformBufferOffsetAlignment) *
|
|
138
|
+
this.device.limits.minUniformBufferOffsetAlignment;
|
|
139
|
+
try {
|
|
140
|
+
for (let index = 0; index < 3; index++) {
|
|
141
|
+
const buffer = this.device.createBuffer({
|
|
142
|
+
label: `pelican spatial denoiser ${index}`,
|
|
143
|
+
size: index === 2 ? stride * 5 : size,
|
|
144
|
+
usage: index === 2
|
|
145
|
+
? GPUBufferUsage.UNIFORM
|
|
146
|
+
: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
|
147
|
+
mappedAtCreation: true,
|
|
148
|
+
});
|
|
149
|
+
buffers.push(buffer);
|
|
150
|
+
// Mapping also makes synchronous allocation failure transactional.
|
|
151
|
+
const mapped = buffer.getMappedRange();
|
|
152
|
+
if (index === 2) {
|
|
153
|
+
for (let iteration = 0; iteration < 5; iteration++)
|
|
154
|
+
new Uint32Array(mapped, iteration * stride, 4).set([
|
|
155
|
+
width,
|
|
156
|
+
height,
|
|
157
|
+
1 << iteration,
|
|
158
|
+
0,
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
buffer.unmap();
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
width,
|
|
165
|
+
height,
|
|
166
|
+
size,
|
|
167
|
+
stride,
|
|
168
|
+
buffers: buffers,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
buffers.forEach((buffer) => buffer.destroy());
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
encode(encoder, input, normalDepth, albedoRoughness, width, height, options = {}) {
|
|
177
|
+
if (this.disposed)
|
|
178
|
+
fail("DISPOSED", "Create a new denoiser after dispose().");
|
|
179
|
+
if (this.lost)
|
|
180
|
+
fail("DEVICE_LOST", "Create a denoiser on a new device.");
|
|
181
|
+
if (!options || typeof options !== "object" || Array.isArray(options))
|
|
182
|
+
fail("OPTIONS", "Expected an options object.");
|
|
183
|
+
const iterations = options.iterations === undefined ? 3 : options.iterations;
|
|
184
|
+
if (!Number.isInteger(iterations) ||
|
|
185
|
+
iterations < 0 ||
|
|
186
|
+
iterations > 5 ||
|
|
187
|
+
(options.enabled !== undefined && typeof options.enabled !== "boolean"))
|
|
188
|
+
fail("OPTIONS", "iterations must be an integer in [0,5]; enabled must be boolean.");
|
|
189
|
+
const size = width * height * 16;
|
|
190
|
+
const limits = this.device.limits;
|
|
191
|
+
if (!Number.isSafeInteger(width) ||
|
|
192
|
+
!Number.isSafeInteger(height) ||
|
|
193
|
+
width < 1 ||
|
|
194
|
+
height < 1 ||
|
|
195
|
+
width > 0x7fffffff ||
|
|
196
|
+
height > 0x7fffffff ||
|
|
197
|
+
!Number.isSafeInteger(size) ||
|
|
198
|
+
width * height > 0xffffffff ||
|
|
199
|
+
size > limits.maxBufferSize ||
|
|
200
|
+
size > limits.maxStorageBufferBindingSize ||
|
|
201
|
+
Math.ceil(width / 8) > limits.maxComputeWorkgroupsPerDimension ||
|
|
202
|
+
Math.ceil(height / 8) > limits.maxComputeWorkgroupsPerDimension)
|
|
203
|
+
fail("DIMENSION", "Positive integer dimensions must fit storage and dispatch limits.");
|
|
204
|
+
for (const [label, buffer] of [
|
|
205
|
+
["input", input],
|
|
206
|
+
["normalDepth", normalDepth],
|
|
207
|
+
["albedoRoughness", albedoRoughness],
|
|
208
|
+
]) {
|
|
209
|
+
if (!buffer ||
|
|
210
|
+
!Number.isSafeInteger(buffer.size) ||
|
|
211
|
+
buffer.size < size ||
|
|
212
|
+
buffer.size % 16 !== 0 ||
|
|
213
|
+
!(buffer.usage & GPUBufferUsage.STORAGE) ||
|
|
214
|
+
buffer.mapState !== "unmapped")
|
|
215
|
+
fail("BUFFER", `${label} needs unmapped STORAGE vec4f capacity >= ${size} bytes.`);
|
|
216
|
+
if (options.enabled !== false &&
|
|
217
|
+
iterations !== 0 &&
|
|
218
|
+
this.allocation?.buffers.includes(buffer))
|
|
219
|
+
fail("ALIAS", `${label} cannot alias denoiser-owned output; copy it first.`);
|
|
220
|
+
}
|
|
221
|
+
if (!encoder || typeof encoder.beginComputePass !== "function")
|
|
222
|
+
fail("ENCODER", "Provide an open command encoder on the same device.");
|
|
223
|
+
if (options.enabled === false || iterations === 0) {
|
|
224
|
+
this.lastPasses = 0;
|
|
225
|
+
return input;
|
|
226
|
+
}
|
|
227
|
+
const old = this.allocation;
|
|
228
|
+
const next = old?.width === width && old.height === height
|
|
229
|
+
? old
|
|
230
|
+
: this.allocate(width, height, size);
|
|
231
|
+
const groups = [];
|
|
232
|
+
try {
|
|
233
|
+
for (let iteration = 0; iteration < iterations; iteration++) {
|
|
234
|
+
const buffers = [
|
|
235
|
+
iteration === 0 ? input : next.buffers[(iteration - 1) % 2],
|
|
236
|
+
normalDepth,
|
|
237
|
+
albedoRoughness,
|
|
238
|
+
input,
|
|
239
|
+
next.buffers[iteration % 2],
|
|
240
|
+
];
|
|
241
|
+
groups.push(this.device.createBindGroup({
|
|
242
|
+
label: `pelican spatial denoiser step ${1 << iteration}`,
|
|
243
|
+
layout: this.layout,
|
|
244
|
+
entries: [
|
|
245
|
+
...buffers.map((buffer, binding) => ({
|
|
246
|
+
binding,
|
|
247
|
+
resource: { buffer, offset: 0, size },
|
|
248
|
+
})),
|
|
249
|
+
{
|
|
250
|
+
binding: 5,
|
|
251
|
+
resource: {
|
|
252
|
+
buffer: next.buffers[2],
|
|
253
|
+
offset: iteration * next.stride,
|
|
254
|
+
size: 16,
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (next !== old)
|
|
263
|
+
next.buffers.forEach((buffer) => buffer.destroy());
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
// No error scope here: synchronous encode cannot consume async GPU errors.
|
|
267
|
+
// Caller must submit previous commands BEFORE a resize destroys old buffers.
|
|
268
|
+
try {
|
|
269
|
+
for (const group of groups) {
|
|
270
|
+
const pass = encoder.beginComputePass({
|
|
271
|
+
label: "pelican spatial denoiser",
|
|
272
|
+
});
|
|
273
|
+
pass.setPipeline(this.pipeline);
|
|
274
|
+
pass.setBindGroup(0, group);
|
|
275
|
+
pass.dispatchWorkgroups(Math.ceil(width / 8), Math.ceil(height / 8));
|
|
276
|
+
pass.end();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
if (next !== old)
|
|
281
|
+
next.buffers.forEach((buffer) => buffer.destroy());
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
if (next !== old) {
|
|
285
|
+
this.allocation = next;
|
|
286
|
+
old?.buffers.forEach((buffer) => buffer.destroy());
|
|
287
|
+
}
|
|
288
|
+
this.passes += iterations;
|
|
289
|
+
this.lastPasses = iterations;
|
|
290
|
+
return next.buffers[(iterations - 1) % 2];
|
|
291
|
+
}
|
|
292
|
+
inspect() {
|
|
293
|
+
const a = this.allocation;
|
|
294
|
+
return {
|
|
295
|
+
entries: a ? 3 : 0,
|
|
296
|
+
bytes: a ? a.size * 2 + a.stride * 5 : 0,
|
|
297
|
+
passes: this.passes,
|
|
298
|
+
lastPasses: this.lastPasses,
|
|
299
|
+
dimension: { width: a?.width ?? 0, height: a?.height ?? 0 },
|
|
300
|
+
disposed: this.disposed,
|
|
301
|
+
lost: this.lost,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
dispose() {
|
|
305
|
+
if (this.disposed)
|
|
306
|
+
return;
|
|
307
|
+
this.release();
|
|
308
|
+
this.disposed = true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
export async function createSurfaceDenoiser(device) {
|
|
312
|
+
let pending;
|
|
313
|
+
let scope;
|
|
314
|
+
let layout;
|
|
315
|
+
device.pushErrorScope("validation");
|
|
316
|
+
try {
|
|
317
|
+
layout = device.createBindGroupLayout({
|
|
318
|
+
label: "pelican spatial denoiser readonly ABI",
|
|
319
|
+
entries: [0, 1, 2, 3, 4, 5].map((binding) => ({
|
|
320
|
+
binding,
|
|
321
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
322
|
+
buffer: {
|
|
323
|
+
type: binding === 5
|
|
324
|
+
? "uniform"
|
|
325
|
+
: binding === 4
|
|
326
|
+
? "storage"
|
|
327
|
+
: "read-only-storage",
|
|
328
|
+
minBindingSize: 16,
|
|
329
|
+
},
|
|
330
|
+
})),
|
|
331
|
+
});
|
|
332
|
+
pending = device.createComputePipelineAsync({
|
|
333
|
+
label: "pelican spatial a-trous denoiser",
|
|
334
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
|
|
335
|
+
compute: {
|
|
336
|
+
module: device.createShaderModule({ code: SHADER }),
|
|
337
|
+
entryPoint: "main",
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
// Pop even when setup throws; consume a possibly rejected pop without hiding setup failure.
|
|
343
|
+
void device.popErrorScope().catch(() => { });
|
|
344
|
+
throw new Error("TC_DENOISER_PIPELINE: Pipeline setup failed.", {
|
|
345
|
+
cause: error,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
scope = device.popErrorScope();
|
|
349
|
+
try {
|
|
350
|
+
const [pipeline, validation] = await creationDeadline(Promise.all([pending, scope]), device);
|
|
351
|
+
if (validation)
|
|
352
|
+
fail("VALIDATION", validation.message);
|
|
353
|
+
return new SpatialDenoiser(device, pipeline, layout);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
// No GPU buffers exist before successful creation; pipelines/layouts have no destroy API.
|
|
357
|
+
throw new Error("TC_DENOISER_PIPELINE: Cannot create spatial denoiser.", {
|
|
358
|
+
cause: error,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|