@multiplekex/shallot 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/package.json +1 -1
  2. package/src/core/component.ts +1 -1
  3. package/src/core/index.ts +1 -13
  4. package/src/core/math.ts +186 -0
  5. package/src/core/state.ts +1 -1
  6. package/src/core/xml.ts +56 -41
  7. package/src/extras/arrows/index.ts +3 -3
  8. package/src/extras/caustic.ts +37 -0
  9. package/src/extras/gradient/index.ts +63 -69
  10. package/src/extras/index.ts +3 -0
  11. package/src/extras/lines/index.ts +3 -3
  12. package/src/extras/orbit/index.ts +1 -1
  13. package/src/extras/skylab/index.ts +314 -0
  14. package/src/extras/text/font.ts +69 -14
  15. package/src/extras/text/index.ts +17 -69
  16. package/src/extras/text/sdf.ts +13 -2
  17. package/src/extras/water/index.ts +119 -0
  18. package/src/standard/defaults.ts +2 -0
  19. package/src/standard/index.ts +2 -0
  20. package/src/standard/raster/batch.ts +149 -0
  21. package/src/standard/raster/forward.ts +832 -0
  22. package/src/standard/raster/index.ts +191 -0
  23. package/src/standard/raster/shadow.ts +408 -0
  24. package/src/standard/{render → raytracing}/bvh/blas.ts +336 -88
  25. package/src/standard/raytracing/bvh/radix.ts +473 -0
  26. package/src/standard/raytracing/bvh/refit.ts +711 -0
  27. package/src/standard/{render → raytracing}/bvh/structs.ts +0 -55
  28. package/src/standard/{render → raytracing}/bvh/tlas.ts +155 -140
  29. package/src/standard/{render → raytracing}/bvh/traverse.ts +72 -64
  30. package/src/standard/{render → raytracing}/depth.ts +9 -9
  31. package/src/standard/raytracing/index.ts +409 -0
  32. package/src/standard/{render → raytracing}/instance.ts +31 -16
  33. package/src/standard/{render → raytracing}/ray.ts +1 -1
  34. package/src/standard/raytracing/shaders.ts +798 -0
  35. package/src/standard/{render → raytracing}/triangle.ts +1 -1
  36. package/src/standard/render/camera.ts +96 -106
  37. package/src/standard/render/data.ts +1 -1
  38. package/src/standard/render/index.ts +136 -220
  39. package/src/standard/render/indirect.ts +9 -10
  40. package/src/standard/render/light.ts +2 -2
  41. package/src/standard/render/mesh.ts +404 -0
  42. package/src/standard/render/overlay.ts +8 -5
  43. package/src/standard/render/pass.ts +1 -1
  44. package/src/standard/render/postprocess.ts +263 -242
  45. package/src/standard/render/scene.ts +28 -16
  46. package/src/standard/render/surface/index.ts +81 -12
  47. package/src/standard/render/surface/shaders.ts +511 -0
  48. package/src/standard/render/surface/structs.ts +23 -6
  49. package/src/standard/tween/tween.ts +44 -115
  50. package/src/standard/render/bvh/radix.ts +0 -476
  51. package/src/standard/render/forward/index.ts +0 -259
  52. package/src/standard/render/forward/raster.ts +0 -228
  53. package/src/standard/render/mesh/box.ts +0 -20
  54. package/src/standard/render/mesh/index.ts +0 -446
  55. package/src/standard/render/mesh/plane.ts +0 -11
  56. package/src/standard/render/mesh/sphere.ts +0 -40
  57. package/src/standard/render/mesh/unified.ts +0 -96
  58. package/src/standard/render/shaders.ts +0 -484
  59. package/src/standard/render/surface/compile.ts +0 -67
  60. package/src/standard/render/surface/noise.ts +0 -45
  61. package/src/standard/render/surface/wgsl.ts +0 -573
  62. /package/src/standard/{render → raytracing}/intersection.ts +0 -0
@@ -0,0 +1,409 @@
1
+ import type { Plugin, State } from "../../core";
2
+ import { MAX_ENTITIES, resource } from "../../core";
3
+ import { setTraits } from "../../core/component";
4
+ import { Compute } from "../compute";
5
+ import type { ComputeNode, ExecutionContext } from "../compute";
6
+ import { WorldTransform } from "../transforms";
7
+ import { Activity } from "../activity";
8
+ import { Camera } from "../render/camera";
9
+ import { Mesh, getMesh, getMeshVersion } from "../render/mesh";
10
+ import { getDefaultAllSurfaces, Surface } from "../render/surface";
11
+ import type { SurfaceData } from "../render/surface";
12
+ import { Render, registerShadowSettings } from "../render";
13
+ import { RasterPlugin, RasterResource } from "../raster";
14
+ import { createBLASAtlas, type BLASAtlas } from "./bvh/blas";
15
+ import { createTLASBuffers, createTLASNode, type TLASBuffers } from "./bvh/tlas";
16
+ import { createBLASRefitNode } from "./bvh/refit";
17
+ import { createInstanceNode } from "./instance";
18
+ import { createDepthConvertNode } from "./depth";
19
+ import { compileUberShader } from "./shaders";
20
+
21
+ export const Raytracing = {};
22
+ export const Dynamic = {};
23
+
24
+ export const RaytracingShadowSettings = {
25
+ softness: [] as number[],
26
+ samples: [] as number[],
27
+ };
28
+
29
+ setTraits(RaytracingShadowSettings, {
30
+ defaults: () => ({
31
+ softness: 0,
32
+ samples: 1,
33
+ }),
34
+ });
35
+
36
+ export { compileUberShader, compileRTSurface } from "./shaders";
37
+ export { createDepthConvertNode } from "./depth";
38
+ export { createInstanceNode } from "./instance";
39
+ export { generateRay } from "./ray";
40
+ export type { Ray } from "./ray";
41
+ export { intersectRayTriangle } from "./intersection";
42
+ export type { RayTriangleResult } from "./intersection";
43
+ export { extractTriangles } from "./triangle";
44
+ export type { Triangle, FlatTriangle } from "./triangle";
45
+
46
+ interface RTState {
47
+ instanceAABBs: GPUBuffer;
48
+ instanceInverses: GPUBuffer;
49
+ instanceCount: GPUBuffer;
50
+ tlas: TLASBuffers;
51
+ blasAtlas: BLASAtlas;
52
+ blasVersion: number;
53
+ rendered: boolean;
54
+ }
55
+
56
+ export const RTResource = resource<RTState>("raytracing");
57
+
58
+ interface RTRenderConfig {
59
+ scene: GPUBuffer;
60
+ sky: GPUBuffer;
61
+ data: GPUBuffer;
62
+ tlasNodes: GPUBuffer;
63
+ tlasInstanceIds: GPUBuffer;
64
+ instanceInverses: GPUBuffer;
65
+ getBlasAtlas: () => BLASAtlas;
66
+ getSurfaces: () => SurfaceData[];
67
+ getRaytracing: () => boolean;
68
+ setRendered: (value: boolean) => void;
69
+ acquire?: (message?: string) => (() => void) | undefined;
70
+ }
71
+
72
+ interface RTRenderGPU {
73
+ pipeline: GPUComputePipeline | null;
74
+ compiling: boolean;
75
+ bindGroup0: GPUBindGroup | null;
76
+ bindGroup1: GPUBindGroup | null;
77
+ cachedWidth: number;
78
+ cachedHeight: number;
79
+ cachedBlasAtlas: BLASAtlas | null;
80
+ }
81
+
82
+ async function compileRTPipeline(
83
+ device: GPUDevice,
84
+ surfaces: SurfaceData[]
85
+ ): Promise<GPUComputePipeline> {
86
+ const code = compileUberShader(surfaces);
87
+ const module = device.createShaderModule({ code });
88
+ return device.createComputePipelineAsync({
89
+ layout: "auto",
90
+ compute: { module, entryPoint: "main" },
91
+ });
92
+ }
93
+
94
+ function triggerCompile(gpu: RTRenderGPU, device: GPUDevice, config: RTRenderConfig): void {
95
+ gpu.compiling = true;
96
+ const release = config.acquire?.("compiling shaders");
97
+ const surfaces = config.getSurfaces();
98
+ compileRTPipeline(device, surfaces)
99
+ .then((p) => {
100
+ gpu.pipeline = p;
101
+ })
102
+ .finally(() => {
103
+ gpu.compiling = false;
104
+ release?.();
105
+ });
106
+ }
107
+
108
+ function executeRTRender(gpu: RTRenderGPU, ctx: ExecutionContext, config: RTRenderConfig): void {
109
+ const wantRT = config.getRaytracing();
110
+
111
+ if (!wantRT) {
112
+ config.setRendered(false);
113
+ return;
114
+ }
115
+
116
+ if (!gpu.pipeline && !gpu.compiling) {
117
+ triggerCompile(gpu, ctx.device, config);
118
+ }
119
+
120
+ if (!gpu.pipeline) {
121
+ config.setRendered(false);
122
+ return;
123
+ }
124
+
125
+ const { device, encoder } = ctx;
126
+
127
+ const colorView = ctx.getTextureView("color");
128
+ const depthView = ctx.getTextureView("depth");
129
+ const eidView = ctx.getTextureView("eid");
130
+
131
+ if (!colorView || !depthView || !eidView) {
132
+ config.setRendered(false);
133
+ return;
134
+ }
135
+
136
+ const colorTexture = ctx.getTexture("color");
137
+ if (!colorTexture) {
138
+ config.setRendered(false);
139
+ return;
140
+ }
141
+
142
+ const width = colorTexture.width;
143
+ const height = colorTexture.height;
144
+
145
+ const blas = config.getBlasAtlas();
146
+
147
+ const sizeChanged = width !== gpu.cachedWidth || height !== gpu.cachedHeight;
148
+ const blasChanged = blas !== gpu.cachedBlasAtlas;
149
+
150
+ if (sizeChanged) {
151
+ gpu.bindGroup0 = device.createBindGroup({
152
+ layout: gpu.pipeline.getBindGroupLayout(0),
153
+ entries: [
154
+ { binding: 0, resource: { buffer: config.scene } },
155
+ { binding: 1, resource: { buffer: config.data } },
156
+ { binding: 2, resource: colorView },
157
+ { binding: 3, resource: depthView },
158
+ { binding: 4, resource: eidView },
159
+ { binding: 5, resource: { buffer: config.sky } },
160
+ ],
161
+ });
162
+ gpu.cachedWidth = width;
163
+ gpu.cachedHeight = height;
164
+ }
165
+
166
+ if (sizeChanged || blasChanged) {
167
+ gpu.bindGroup1 = device.createBindGroup({
168
+ layout: gpu.pipeline.getBindGroupLayout(1),
169
+ entries: [
170
+ { binding: 0, resource: { buffer: config.tlasNodes } },
171
+ { binding: 1, resource: { buffer: config.tlasInstanceIds } },
172
+ { binding: 2, resource: { buffer: blas.nodesBuffer } },
173
+ { binding: 3, resource: { buffer: blas.triIdsBuffer } },
174
+ { binding: 4, resource: { buffer: blas.trianglesBuffer } },
175
+ { binding: 5, resource: { buffer: blas.metaBuffer } },
176
+ { binding: 6, resource: { buffer: config.instanceInverses } },
177
+ ],
178
+ });
179
+ gpu.cachedBlasAtlas = blas;
180
+ }
181
+
182
+ const pass = encoder.beginComputePass();
183
+ pass.setPipeline(gpu.pipeline);
184
+ pass.setBindGroup(0, gpu.bindGroup0!);
185
+ pass.setBindGroup(1, gpu.bindGroup1!);
186
+ pass.dispatchWorkgroups(Math.ceil(width / 8), Math.ceil(height / 8));
187
+ pass.end();
188
+
189
+ config.setRendered(true);
190
+ }
191
+
192
+ function createRTRenderNode(config: RTRenderConfig): ComputeNode {
193
+ const gpu: RTRenderGPU = {
194
+ pipeline: null,
195
+ compiling: false,
196
+ bindGroup0: null,
197
+ bindGroup1: null,
198
+ cachedWidth: 0,
199
+ cachedHeight: 0,
200
+ cachedBlasAtlas: null,
201
+ };
202
+
203
+ return {
204
+ id: "rt-render",
205
+ inputs: [{ id: "tlas-bvh-nodes", access: "read" as const }],
206
+ outputs: [
207
+ { id: "rt-output", access: "write" },
208
+ { id: "depth", access: "write" },
209
+ ],
210
+
211
+ async prepare(device: GPUDevice) {
212
+ if (config.getRaytracing()) {
213
+ triggerCompile(gpu, device, config);
214
+ }
215
+ },
216
+
217
+ execute(ctx: ExecutionContext) {
218
+ executeRTRender(gpu, ctx, config);
219
+ },
220
+ };
221
+ }
222
+
223
+ export const RaytracingPlugin: Plugin = {
224
+ systems: [],
225
+ components: {
226
+ Raytracing,
227
+ RaytracingShadowSettings,
228
+ Dynamic,
229
+ },
230
+ dependencies: [RasterPlugin],
231
+
232
+ async initialize(state: State) {
233
+ const compute = Compute.from(state);
234
+ const render = Render.from(state);
235
+ if (!compute || !render) return;
236
+
237
+ registerShadowSettings({
238
+ softness: RaytracingShadowSettings.softness,
239
+ samples: RaytracingShadowSettings.samples,
240
+ isActive: (s, eid) => s.hasComponent(eid, RaytracingShadowSettings),
241
+ });
242
+
243
+ const { device } = compute;
244
+
245
+ const createPropertyBuffer = (size: number, label?: string) =>
246
+ device.createBuffer({
247
+ label: label ?? "property",
248
+ size,
249
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
250
+ });
251
+
252
+ const blasAtlas = createBLASAtlas(device, getMesh);
253
+
254
+ const rtState: RTState = {
255
+ instanceAABBs: createPropertyBuffer(MAX_ENTITIES * 32, "instanceAABBs"),
256
+ instanceInverses: createPropertyBuffer(MAX_ENTITIES * 64, "instanceInverses"),
257
+ instanceCount: device.createBuffer({
258
+ label: "instanceCount",
259
+ size: 4,
260
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
261
+ }),
262
+ tlas: createTLASBuffers(device),
263
+ blasAtlas,
264
+ blasVersion: getMeshVersion(),
265
+ rendered: false,
266
+ };
267
+
268
+ state.setResource(RTResource, rtState);
269
+
270
+ const getRaytracing = () => {
271
+ for (const eid of state.query([Camera])) {
272
+ if (Camera.active[eid]) {
273
+ return state.hasComponent(eid, Raytracing);
274
+ }
275
+ }
276
+ return false;
277
+ };
278
+
279
+ const entityIds = new Uint32Array(MAX_ENTITIES);
280
+ const countBuf = new Uint32Array(1);
281
+
282
+ const uploadNode: ComputeNode = {
283
+ id: "rt-upload",
284
+ inputs: [],
285
+ outputs: [{ id: "instance-count", access: "write" }],
286
+ execute(ctx) {
287
+ if (!getRaytracing()) return;
288
+
289
+ const currentMeshVersion = getMeshVersion();
290
+ if (currentMeshVersion !== rtState.blasVersion) {
291
+ rtState.blasAtlas.nodesBuffer.destroy();
292
+ rtState.blasAtlas.triIdsBuffer.destroy();
293
+ rtState.blasAtlas.metaBuffer.destroy();
294
+ rtState.blasAtlas.trianglesBuffer.destroy();
295
+ rtState.blasAtlas.shapeAABBs.destroy();
296
+ rtState.blasAtlas.treeNodesBuffer.destroy();
297
+ rtState.blasAtlas.parentIndicesBuffer.destroy();
298
+ rtState.blasAtlas.baseTrianglesBuffer.destroy();
299
+ rtState.blasAtlas.boundsFlagsBuffer.destroy();
300
+ rtState.blasAtlas = createBLASAtlas(ctx.device, getMesh);
301
+ rtState.blasVersion = currentMeshVersion;
302
+ }
303
+
304
+ const meshEntities = state.query([Mesh, WorldTransform]);
305
+ let entityCount = 0;
306
+ for (const eid of meshEntities) {
307
+ entityIds[entityCount] = eid;
308
+ entityCount++;
309
+ }
310
+ ctx.device.queue.writeBuffer(
311
+ rtState.tlas.entityIds,
312
+ 0,
313
+ entityIds,
314
+ 0,
315
+ Math.max(entityCount, 1)
316
+ );
317
+ countBuf[0] = entityCount;
318
+ ctx.device.queue.writeBuffer(rtState.instanceCount, 0, countBuf);
319
+ },
320
+ };
321
+ compute.graph.add(uploadNode);
322
+
323
+ const getDynamicShapes = () => {
324
+ const result = new Map<number, SurfaceData>();
325
+ const surfaces = getDefaultAllSurfaces();
326
+ for (const eid of state.query([Mesh, Dynamic])) {
327
+ const shapeId = Mesh.shape[eid];
328
+ const surfaceType = Surface.type[eid] ?? 0;
329
+ const data = surfaces[surfaceType];
330
+ if (data?.vertex && !result.has(shapeId)) {
331
+ result.set(shapeId, data);
332
+ }
333
+ }
334
+ return result;
335
+ };
336
+
337
+ const blasRefitNode = createBLASRefitNode({
338
+ getBlasAtlas: () => rtState.blasAtlas,
339
+ scene: render.scene,
340
+ sky: render.sky,
341
+ getDynamicShapes,
342
+ getRaytracing,
343
+ });
344
+ compute.graph.add(blasRefitNode);
345
+
346
+ const instanceNode = createInstanceNode({
347
+ matrices: render.matrices,
348
+ sizes: render.sizes,
349
+ shapes: render.shapes,
350
+ entityCount: render.entityCountBuffer,
351
+ instanceAABBs: rtState.instanceAABBs,
352
+ instanceInverses: rtState.instanceInverses,
353
+ getShapeAABBs: () => rtState.blasAtlas.shapeAABBs,
354
+ getEntityCount: () => render.entityCount,
355
+ getRaytracing,
356
+ inputs: [{ id: "blas-nodes", access: "read" }],
357
+ });
358
+ compute.graph.add(instanceNode);
359
+
360
+ const tlasNode = createTLASNode({
361
+ instanceAABBs: rtState.instanceAABBs,
362
+ instanceCount: rtState.instanceCount,
363
+ tlas: rtState.tlas,
364
+ getEntityCount: () => render.entityCount,
365
+ getRaytracing,
366
+ });
367
+ compute.graph.add(tlasNode);
368
+
369
+ compute.graph.add(
370
+ createDepthConvertNode({
371
+ scene: render.scene,
372
+ getRaytracing,
373
+ })
374
+ );
375
+
376
+ const rasterState = RasterResource.from(state);
377
+
378
+ const rtRenderNode = createRTRenderNode({
379
+ scene: render.scene,
380
+ sky: render.sky,
381
+ data: render.data,
382
+ tlasNodes: rtState.tlas.bvhNodes,
383
+ tlasInstanceIds: rtState.tlas.instanceIds,
384
+ instanceInverses: rtState.instanceInverses,
385
+ getBlasAtlas: () => rtState.blasAtlas,
386
+ getSurfaces: getDefaultAllSurfaces,
387
+ getRaytracing,
388
+ setRendered: (v: boolean) => {
389
+ rtState.rendered = v;
390
+ if (rasterState) rasterState.rtRendered = v;
391
+ },
392
+ acquire: (message) => Activity.from(state)?.acquire(message),
393
+ });
394
+ compute.graph.add(rtRenderNode);
395
+
396
+ const existingForward = compute.graph.nodes.get("forward");
397
+ if (existingForward) {
398
+ const wrappedForward: ComputeNode = {
399
+ id: "forward",
400
+ inputs: [...existingForward.inputs, { id: "rt-output", access: "read" as const }],
401
+ outputs: existingForward.outputs,
402
+ sync: existingForward.sync,
403
+ prepare: existingForward.prepare,
404
+ execute: existingForward.execute,
405
+ };
406
+ compute.graph.set("forward", wrappedForward);
407
+ }
408
+ },
409
+ };
@@ -1,4 +1,4 @@
1
- import type { ComputeNode, ExecutionContext } from "../compute";
1
+ import type { ComputeNode, ExecutionContext, ResourceRef } from "../compute";
2
2
 
3
3
  const WORKGROUP_SIZE = 64;
4
4
 
@@ -160,48 +160,63 @@ export interface InstanceConfig {
160
160
  matrices: GPUBuffer;
161
161
  sizes: GPUBuffer;
162
162
  shapes: GPUBuffer;
163
- shapeAABBs: GPUBuffer;
164
163
  entityCount: GPUBuffer;
165
164
  instanceAABBs: GPUBuffer;
166
165
  instanceInverses: GPUBuffer;
166
+ getShapeAABBs: () => GPUBuffer;
167
167
  getEntityCount: () => number;
168
+ getRaytracing: () => boolean;
169
+ inputs?: ResourceRef[];
168
170
  }
169
171
 
170
172
  export function createInstanceNode(config: InstanceConfig): ComputeNode {
171
173
  let pipeline: GPUComputePipeline | null = null;
172
174
  let bindGroup: GPUBindGroup | null = null;
175
+ let cachedShapeAABBs: GPUBuffer | null = null;
176
+
177
+ function rebuildBindGroup(device: GPUDevice, shapeAABBs: GPUBuffer): void {
178
+ bindGroup = device.createBindGroup({
179
+ layout: pipeline!.getBindGroupLayout(0),
180
+ entries: [
181
+ { binding: 0, resource: { buffer: config.matrices } },
182
+ { binding: 1, resource: { buffer: config.sizes } },
183
+ { binding: 2, resource: { buffer: config.shapes } },
184
+ { binding: 3, resource: { buffer: shapeAABBs } },
185
+ { binding: 4, resource: { buffer: config.entityCount } },
186
+ { binding: 5, resource: { buffer: config.instanceAABBs } },
187
+ { binding: 6, resource: { buffer: config.instanceInverses } },
188
+ ],
189
+ });
190
+ cachedShapeAABBs = shapeAABBs;
191
+ }
173
192
 
174
193
  return {
175
194
  id: "instance",
176
- inputs: [],
195
+ inputs: config.inputs ?? [],
177
196
  outputs: [
178
197
  { id: "instance-aabbs", access: "write" },
179
198
  { id: "instance-inverses", access: "write" },
180
199
  ],
181
200
 
182
201
  async prepare(device: GPUDevice) {
183
- const module = await device.createShaderModule({ code: shader });
202
+ const module = device.createShaderModule({ code: shader });
184
203
 
185
204
  pipeline = await device.createComputePipelineAsync({
186
205
  layout: "auto",
187
206
  compute: { module, entryPoint: "main" },
188
207
  });
189
208
 
190
- bindGroup = device.createBindGroup({
191
- layout: pipeline.getBindGroupLayout(0),
192
- entries: [
193
- { binding: 0, resource: { buffer: config.matrices } },
194
- { binding: 1, resource: { buffer: config.sizes } },
195
- { binding: 2, resource: { buffer: config.shapes } },
196
- { binding: 3, resource: { buffer: config.shapeAABBs } },
197
- { binding: 4, resource: { buffer: config.entityCount } },
198
- { binding: 5, resource: { buffer: config.instanceAABBs } },
199
- { binding: 6, resource: { buffer: config.instanceInverses } },
200
- ],
201
- });
209
+ rebuildBindGroup(device, config.getShapeAABBs());
202
210
  },
203
211
 
204
212
  execute(ctx: ExecutionContext) {
213
+ if (!config.getRaytracing()) return;
214
+
215
+ const currentAABBs = config.getShapeAABBs();
216
+ if (currentAABBs !== cachedShapeAABBs) {
217
+ rebuildBindGroup(ctx.device, currentAABBs);
218
+ }
219
+
205
220
  const workgroups = Math.ceil(config.getEntityCount() / WORKGROUP_SIZE);
206
221
 
207
222
  const pass = ctx.encoder.beginComputePass();
@@ -1,4 +1,4 @@
1
- import type { Vec3, Ray } from "./bvh/structs";
1
+ import type { Ray } from "./bvh/structs";
2
2
 
3
3
  export type { Ray };
4
4