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.
Files changed (192) hide show
  1. package/AGENTS.md +147 -0
  2. package/LICENSE +22 -0
  3. package/README.md +129 -2
  4. package/distribution.json +197 -0
  5. package/docs/AI-WORKFLOW.md +517 -0
  6. package/docs/ARCHITECTURE.md +268 -0
  7. package/docs/CAPABILITIES.md +264 -0
  8. package/docs/DECISIONS.md +107 -0
  9. package/docs/M3-USAGE.md +192 -0
  10. package/docs/PELICAN-MIGRATION.md +38 -0
  11. package/docs/RENDER-BACKENDS.md +50 -0
  12. package/docs/ROADMAP.md +239 -0
  13. package/docs/THIRD-PARTY-NOTICES.md +151 -0
  14. package/docs/api/REFERENCE.md +1435 -0
  15. package/docs/api/index.json +1139 -0
  16. package/docs/npm/DISTRIBUTION.md +130 -0
  17. package/docs/npm/GAME-AUTHOR.md +147 -0
  18. package/engine.manifest.json +15 -0
  19. package/lib/assets/audio.js +67 -0
  20. package/lib/assets/errors.js +36 -0
  21. package/lib/assets/glb.js +277 -0
  22. package/lib/assets/index.js +53 -0
  23. package/lib/assets/jpeg.js +199 -0
  24. package/lib/assets/model.js +558 -0
  25. package/lib/assets/optimize.js +95 -0
  26. package/lib/assets/texture.js +187 -0
  27. package/lib/compiler/distribution.js +17 -0
  28. package/lib/compiler/extract.js +425 -0
  29. package/lib/compiler/graph.js +366 -0
  30. package/lib/compiler/index.js +574 -0
  31. package/lib/compiler/normalize.js +1194 -0
  32. package/lib/compiler/source-map.js +25 -0
  33. package/lib/compiler/types.js +2 -0
  34. package/lib/compiler/version.js +93 -0
  35. package/lib/experiments/simulation/index.js +2 -0
  36. package/lib/experiments/simulation/math.js +41 -0
  37. package/lib/experiments/simulation/probe.js +215 -0
  38. package/lib/experiments/simulation/scenes.js +192 -0
  39. package/lib/experiments/simulation/solver.js +346 -0
  40. package/lib/experiments/simulation/types.js +1 -0
  41. package/lib/experiments/world/adapters.js +30 -0
  42. package/lib/experiments/world/geometry.js +180 -0
  43. package/lib/experiments/world/index.js +2 -0
  44. package/lib/experiments/world/probe.js +288 -0
  45. package/lib/experiments/world/types.js +1 -0
  46. package/lib/experiments/world/world.js +599 -0
  47. package/lib/navigation/index.js +157 -0
  48. package/lib/physics/index.js +909 -0
  49. package/lib/protocol/index.js +92 -0
  50. package/lib/renderer/acceleration.js +609 -0
  51. package/lib/renderer/browser.js +2015 -0
  52. package/lib/renderer/bsdf.js +260 -0
  53. package/lib/renderer/checkpoint.js +88 -0
  54. package/lib/renderer/compute-executor.js +1034 -0
  55. package/lib/renderer/compute-texture-assets.js +49 -0
  56. package/lib/renderer/compute-view-contract.js +1 -0
  57. package/lib/renderer/denoiser.js +361 -0
  58. package/lib/renderer/dielectric.js +360 -0
  59. package/lib/renderer/embedded-image-pixels.js +368 -0
  60. package/lib/renderer/emissive-lights.js +67 -0
  61. package/lib/renderer/environment-importance.js +55 -0
  62. package/lib/renderer/environment.js +346 -0
  63. package/lib/renderer/gameplay.js +464 -0
  64. package/lib/renderer/geometry.js +103 -0
  65. package/lib/renderer/graph-expressions.js +485 -0
  66. package/lib/renderer/graph-tsl.js +141 -0
  67. package/lib/renderer/graph.js +214 -0
  68. package/lib/renderer/managed-texture-sampling.js +29 -0
  69. package/lib/renderer/material-graphs.js +297 -0
  70. package/lib/renderer/medium.js +348 -0
  71. package/lib/renderer/models.js +1053 -0
  72. package/lib/renderer/motion-shader.js +40 -0
  73. package/lib/renderer/path-shader.js +196 -0
  74. package/lib/renderer/pathtrace.js +324 -0
  75. package/lib/renderer/postprocess-contract.js +1 -0
  76. package/lib/renderer/postprocess.js +494 -0
  77. package/lib/renderer/render-buffers.js +132 -0
  78. package/lib/renderer/render-textures.js +155 -0
  79. package/lib/renderer/startup.js +50 -0
  80. package/lib/renderer/surface-capture.js +199 -0
  81. package/lib/renderer/surface-gpu.js +270 -0
  82. package/lib/renderer/surface-motion.js +84 -0
  83. package/lib/renderer/surface-ray-shader.js +91 -0
  84. package/lib/renderer/surface-shader.js +88 -0
  85. package/lib/renderer/surface-tracer.js +1720 -0
  86. package/lib/renderer/surface.js +1425 -0
  87. package/lib/renderer/temporal-filter.js +512 -0
  88. package/lib/renderer/texture-pixels.js +207 -0
  89. package/lib/renderer/traversal-nodes.js +54 -0
  90. package/lib/renderer/ui.js +446 -0
  91. package/lib/renderer/vertex-bounds.js +127 -0
  92. package/lib/renderer/vertex-contract.js +1 -0
  93. package/lib/renderer/vertex-graphs.js +197 -0
  94. package/lib/renderer/volume-shader.js +50 -0
  95. package/lib/runtime/compute-contract.js +1 -0
  96. package/lib/runtime/compute-service.js +674 -0
  97. package/lib/runtime/contracts.js +1 -0
  98. package/lib/runtime/error.js +37 -0
  99. package/lib/runtime/events.js +189 -0
  100. package/lib/runtime/game-services.js +772 -0
  101. package/lib/runtime/host.js +839 -0
  102. package/lib/runtime/material-parameters.js +145 -0
  103. package/lib/runtime/plugins.js +1 -0
  104. package/lib/runtime/postprocess-contract.js +1 -0
  105. package/lib/runtime/postprocess.js +189 -0
  106. package/lib/runtime/prefabs.js +13 -0
  107. package/lib/runtime/random.js +88 -0
  108. package/lib/runtime/render-buffer-contract.js +1 -0
  109. package/lib/runtime/render-buffers.js +243 -0
  110. package/lib/runtime/render-capabilities.js +17 -0
  111. package/lib/runtime/render-settings.js +249 -0
  112. package/lib/runtime/render-texture-contract.js +1 -0
  113. package/lib/runtime/render-textures.js +271 -0
  114. package/lib/runtime/resources.js +648 -0
  115. package/lib/runtime/scheduler.js +765 -0
  116. package/lib/runtime/storage.js +169 -0
  117. package/lib/runtime/world.js +594 -0
  118. package/lib/schema/index.js +24 -0
  119. package/lib/sdk/graph-values.js +16 -0
  120. package/lib/sdk/index.d.ts +800 -0
  121. package/lib/sdk/index.js +82 -0
  122. package/lib/tools/api-index.js +113 -0
  123. package/lib/tools/browser-session.js +89 -0
  124. package/lib/tools/cli.js +513 -0
  125. package/lib/tools/compress-dist.js +59 -0
  126. package/lib/tools/control-client.js +153 -0
  127. package/lib/tools/control-server.js +633 -0
  128. package/lib/tools/diagnostic-code.js +3 -0
  129. package/lib/tools/doctor.js +496 -0
  130. package/lib/tools/npm/browser-cli.js +6 -0
  131. package/lib/tools/npm/entry.js +40 -0
  132. package/lib/tools/npm/layout.js +195 -0
  133. package/lib/tools/setup.js +153 -0
  134. package/licenses/@dimforge/rapier2d-compat/LICENSE +201 -0
  135. package/licenses/@dimforge/rapier3d-compat/LICENSE +201 -0
  136. package/licenses/@gltf-transform/core/LICENSE.md +21 -0
  137. package/licenses/@gltf-transform/extensions/LICENSE.md +21 -0
  138. package/licenses/@gltf-transform/functions/LICENSE.md +21 -0
  139. package/licenses/@img/colour/LICENSE.md +82 -0
  140. package/licenses/@img/sharp-win32-x64/LICENSE +191 -0
  141. package/licenses/@oxc-project/types/LICENSE +22 -0
  142. package/licenses/@rolldown/pluginutils/LICENSE +21 -0
  143. package/licenses/@tweenjs/tween.js/LICENSE +23 -0
  144. package/licenses/@types/ndarray/LICENSE +21 -0
  145. package/licenses/@types/node/LICENSE +21 -0
  146. package/licenses/@types/stats.js/LICENSE +21 -0
  147. package/licenses/@types/three/@dimforge/rapier3d-compat/LICENSE +201 -0
  148. package/licenses/@types/three/LICENSE +21 -0
  149. package/licenses/@types/webxr/LICENSE +21 -0
  150. package/licenses/@webgpu/types/LICENSE +26 -0
  151. package/licenses/THIRD-PARTY-NOTICES.md +139 -0
  152. package/licenses/cwise-compiler/LICENSE +22 -0
  153. package/licenses/detect-libc/LICENSE +201 -0
  154. package/licenses/esbuild/LICENSE.md +21 -0
  155. package/licenses/fast-png/LICENSE +21 -0
  156. package/licenses/fdir/LICENSE +7 -0
  157. package/licenses/fflate/LICENSE +21 -0
  158. package/licenses/iobuffer/LICENSE +22 -0
  159. package/licenses/iota-array/LICENSE +22 -0
  160. package/licenses/is-buffer/LICENSE +21 -0
  161. package/licenses/ktx-parse/LICENSE +21 -0
  162. package/licenses/lightningcss/LICENSE +373 -0
  163. package/licenses/lightningcss-win32-x64-msvc/LICENSE +373 -0
  164. package/licenses/meshoptimizer/LICENSE.md +21 -0
  165. package/licenses/nanoid/LICENSE +20 -0
  166. package/licenses/ndarray/LICENSE +22 -0
  167. package/licenses/ndarray-lanczos/LICENSE +21 -0
  168. package/licenses/ndarray-ops/LICENSE +22 -0
  169. package/licenses/ndarray-pixels/LICENSE +21 -0
  170. package/licenses/ngraph.path/LICENSE +21 -0
  171. package/licenses/picocolors/LICENSE +15 -0
  172. package/licenses/picomatch/LICENSE +21 -0
  173. package/licenses/playwright/LICENSE +202 -0
  174. package/licenses/playwright/NOTICE +5 -0
  175. package/licenses/playwright-core/LICENSE +202 -0
  176. package/licenses/playwright-core/NOTICE +5 -0
  177. package/licenses/postcss/LICENSE +20 -0
  178. package/licenses/prettier/LICENSE +7 -0
  179. package/licenses/property-graph/LICENSE +21 -0
  180. package/licenses/rolldown/LICENSE +25 -0
  181. package/licenses/semver/LICENSE +15 -0
  182. package/licenses/sharp/LICENSE +191 -0
  183. package/licenses/source-map-js/LICENSE +28 -0
  184. package/licenses/three/LICENSE +21 -0
  185. package/licenses/tinyglobby/LICENSE +21 -0
  186. package/licenses/tsx/LICENSE +21 -0
  187. package/licenses/typescript/LICENSE.txt +55 -0
  188. package/licenses/undici-types/LICENSE +21 -0
  189. package/licenses/uniq/LICENSE +22 -0
  190. package/licenses/vite/LICENSE.md +2084 -0
  191. package/package.json +49 -4
  192. 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
+ }