@multiplekex/shallot 0.2.3 → 0.2.5

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 (42) hide show
  1. package/package.json +1 -1
  2. package/src/extras/arrows/index.ts +3 -3
  3. package/src/extras/caustic.ts +37 -0
  4. package/src/extras/gradient/index.ts +63 -69
  5. package/src/extras/index.ts +3 -0
  6. package/src/extras/lines/index.ts +3 -3
  7. package/src/extras/skylab/index.ts +314 -0
  8. package/src/extras/text/font.ts +69 -14
  9. package/src/extras/text/index.ts +15 -7
  10. package/src/extras/text/sdf.ts +13 -2
  11. package/src/extras/water.ts +64 -0
  12. package/src/standard/defaults.ts +2 -0
  13. package/src/standard/index.ts +2 -0
  14. package/src/standard/raster/index.ts +517 -0
  15. package/src/standard/{render → raytracing}/bvh/blas.ts +3 -3
  16. package/src/standard/{render → raytracing}/bvh/tlas.ts +3 -0
  17. package/src/standard/{render → raytracing}/depth.ts +9 -9
  18. package/src/standard/raytracing/index.ts +380 -0
  19. package/src/standard/{render → raytracing}/instance.ts +3 -0
  20. package/src/standard/raytracing/shaders.ts +815 -0
  21. package/src/standard/{render → raytracing}/triangle.ts +1 -1
  22. package/src/standard/render/camera.ts +88 -80
  23. package/src/standard/render/index.ts +68 -208
  24. package/src/standard/render/indirect.ts +9 -10
  25. package/src/standard/render/mesh/index.ts +35 -166
  26. package/src/standard/render/overlay.ts +4 -4
  27. package/src/standard/render/pass.ts +1 -1
  28. package/src/standard/render/postprocess.ts +75 -50
  29. package/src/standard/render/scene.ts +28 -16
  30. package/src/standard/render/surface/compile.ts +6 -8
  31. package/src/standard/render/surface/noise.ts +15 -2
  32. package/src/standard/render/surface/shaders.ts +257 -0
  33. package/src/standard/render/surface/structs.ts +13 -6
  34. package/src/standard/render/forward/index.ts +0 -259
  35. package/src/standard/render/forward/raster.ts +0 -228
  36. package/src/standard/render/shaders.ts +0 -484
  37. package/src/standard/render/surface/wgsl.ts +0 -573
  38. /package/src/standard/{render → raytracing}/bvh/radix.ts +0 -0
  39. /package/src/standard/{render → raytracing}/bvh/structs.ts +0 -0
  40. /package/src/standard/{render → raytracing}/bvh/traverse.ts +0 -0
  41. /package/src/standard/{render → raytracing}/intersection.ts +0 -0
  42. /package/src/standard/{render → raytracing}/ray.ts +0 -0
@@ -0,0 +1,380 @@
1
+ import type { Plugin, State } from "../../core";
2
+ import { MAX_ENTITIES, resource } from "../../core";
3
+ import { Compute } from "../compute";
4
+ import type { ComputeNode, ExecutionContext } from "../compute";
5
+ import { WorldTransform } from "../transforms";
6
+ import { Activity } from "../activity";
7
+ import { Camera, Raytracing, getClearColor, hasSkyComponent } from "../render/camera";
8
+ import { Mesh, type Batch } from "../render/mesh";
9
+ import { getDefaultAllSurfaces } from "../render/surface";
10
+ import type { SurfaceData } from "../render/surface";
11
+ import { COLOR_FORMAT } from "../render/scene";
12
+ import { Render, RenderPlugin } from "../render";
13
+ import {
14
+ RasterPlugin,
15
+ RasterResource,
16
+ createRasterPipeline,
17
+ createSkyPipeline,
18
+ executeRasterPass,
19
+ type RasterPassConfig,
20
+ } from "../raster";
21
+ import { createBLASAtlas, type BLASAtlas } from "./bvh/blas";
22
+ import { createTLASBuffers, createTLASNode, type TLASBuffers } from "./bvh/tlas";
23
+ import { getMesh } from "../render/mesh";
24
+ import { createInstanceNode } from "./instance";
25
+ import { createDepthConvertNode } from "./depth";
26
+ import { compileUberShader } from "./shaders";
27
+
28
+ export { compileUberShader, compileRTSurface, compileForwardShader } from "./shaders";
29
+ export { createDepthConvertNode } from "./depth";
30
+ export { createInstanceNode } from "./instance";
31
+ export { generateRay } from "./ray";
32
+ export type { Ray } from "./ray";
33
+ export { intersectRayTriangle } from "./intersection";
34
+ export type { RayTriangleResult } from "./intersection";
35
+ export { extractTriangles } from "./triangle";
36
+ export type { Triangle, FlatTriangle } from "./triangle";
37
+
38
+ interface RTState {
39
+ instanceAABBs: GPUBuffer;
40
+ instanceInverses: GPUBuffer;
41
+ instanceCount: GPUBuffer;
42
+ tlas: TLASBuffers;
43
+ blasAtlas: BLASAtlas;
44
+ }
45
+
46
+ export const RTResource = resource<RTState>("raytracing");
47
+
48
+ function createRTForwardNode(config: {
49
+ scene: GPUBuffer;
50
+ sky: GPUBuffer;
51
+ data: GPUBuffer;
52
+ matrices: GPUBuffer;
53
+ sizes: GPUBuffer;
54
+ indirect: GPUBuffer;
55
+ getSurfaces: () => SurfaceData[];
56
+ getRaytracing: () => boolean;
57
+ getClearColor: () => { r: number; g: number; b: number };
58
+ getSky: () => boolean;
59
+ acquire?: (message?: string) => (() => void) | undefined;
60
+ batches: () => (Batch | null)[];
61
+ tlasNodes: GPUBuffer;
62
+ tlasInstanceIds: GPUBuffer;
63
+ blasNodes: GPUBuffer;
64
+ blasTriIds: GPUBuffer;
65
+ blasTriangles: GPUBuffer;
66
+ blasMeta: GPUBuffer;
67
+ instanceInverses: GPUBuffer;
68
+ }): ComputeNode {
69
+ let rasterPipeline: GPURenderPipeline | null = null;
70
+ let rtPipeline: GPUComputePipeline | null = null;
71
+ let rtCompiling = false;
72
+ let skyPipeline: GPURenderPipeline | null = null;
73
+
74
+ let bindGroup0: GPUBindGroup | null = null;
75
+ let bindGroup1: GPUBindGroup | null = null;
76
+ let cachedWidth = 0;
77
+ let cachedHeight = 0;
78
+
79
+ let skyBindGroup: GPUBindGroup | null = null;
80
+ let batchBindGroups: GPUBindGroup[] = [];
81
+
82
+ function executeRT(ctx: ExecutionContext): void {
83
+ const { device, encoder } = ctx;
84
+
85
+ const colorView = ctx.getTextureView("color");
86
+ const depthView = ctx.getTextureView("depth");
87
+ const eidView = ctx.getTextureView("eid");
88
+
89
+ if (!colorView || !depthView || !eidView || !rtPipeline) {
90
+ return;
91
+ }
92
+
93
+ const colorTexture = ctx.getTexture("color");
94
+ if (!colorTexture) return;
95
+
96
+ const width = colorTexture.width;
97
+ const height = colorTexture.height;
98
+
99
+ if (width !== cachedWidth || height !== cachedHeight) {
100
+ bindGroup0 = device.createBindGroup({
101
+ layout: rtPipeline.getBindGroupLayout(0),
102
+ entries: [
103
+ { binding: 0, resource: { buffer: config.scene } },
104
+ { binding: 1, resource: { buffer: config.data } },
105
+ { binding: 2, resource: colorView },
106
+ { binding: 3, resource: depthView },
107
+ { binding: 4, resource: eidView },
108
+ { binding: 5, resource: { buffer: config.sky } },
109
+ ],
110
+ });
111
+
112
+ bindGroup1 = device.createBindGroup({
113
+ layout: rtPipeline.getBindGroupLayout(1),
114
+ entries: [
115
+ { binding: 0, resource: { buffer: config.tlasNodes } },
116
+ { binding: 1, resource: { buffer: config.tlasInstanceIds } },
117
+ { binding: 2, resource: { buffer: config.blasNodes } },
118
+ { binding: 3, resource: { buffer: config.blasTriIds } },
119
+ { binding: 4, resource: { buffer: config.blasTriangles } },
120
+ { binding: 5, resource: { buffer: config.blasMeta } },
121
+ { binding: 6, resource: { buffer: config.instanceInverses } },
122
+ ],
123
+ });
124
+
125
+ cachedWidth = width;
126
+ cachedHeight = height;
127
+ }
128
+
129
+ const pass = encoder.beginComputePass();
130
+ pass.setPipeline(rtPipeline);
131
+ pass.setBindGroup(0, bindGroup0!);
132
+ pass.setBindGroup(1, bindGroup1!);
133
+ pass.dispatchWorkgroups(Math.ceil(width / 8), Math.ceil(height / 8));
134
+ pass.end();
135
+ }
136
+
137
+ function executeRasterFallback(ctx: ExecutionContext): void {
138
+ if (!rasterPipeline) return;
139
+
140
+ const depthView = ctx.getTextureView("depth");
141
+ if (depthView) {
142
+ const clearPass = ctx.encoder.beginRenderPass({
143
+ colorAttachments: [
144
+ {
145
+ view: depthView as GPUTextureView,
146
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
147
+ loadOp: "clear" as const,
148
+ storeOp: "store" as const,
149
+ },
150
+ ],
151
+ });
152
+ clearPass.end();
153
+ }
154
+
155
+ const passConfig: RasterPassConfig = {
156
+ scene: config.scene,
157
+ sky: config.sky,
158
+ data: config.data,
159
+ matrices: config.matrices,
160
+ sizes: config.sizes,
161
+ indirect: config.indirect,
162
+ rasterPipeline,
163
+ skyPipeline,
164
+ getClearColor: config.getClearColor,
165
+ getSky: config.getSky,
166
+ batches: config.batches,
167
+ skyBindGroup,
168
+ batchBindGroups,
169
+ };
170
+ executeRasterPass(ctx, passConfig);
171
+ skyBindGroup = passConfig.skyBindGroup ?? null;
172
+ batchBindGroups = passConfig.batchBindGroups ?? [];
173
+ }
174
+
175
+ async function createRTPipeline(
176
+ device: GPUDevice,
177
+ surfaces: SurfaceData[]
178
+ ): Promise<GPUComputePipeline> {
179
+ const code = compileUberShader(surfaces);
180
+ const module = device.createShaderModule({ code });
181
+ return device.createComputePipelineAsync({
182
+ layout: "auto",
183
+ compute: { module, entryPoint: "main" },
184
+ });
185
+ }
186
+
187
+ return {
188
+ id: "forward",
189
+ inputs: [{ id: "tlas-bvh-nodes", access: "read" as const }],
190
+ outputs: [
191
+ { id: "color", access: "write" },
192
+ { id: "depth", access: "write" },
193
+ { id: "eid", access: "write" },
194
+ ],
195
+
196
+ async prepare(device: GPUDevice) {
197
+ const surfaces = config.getSurfaces();
198
+ rasterPipeline = await createRasterPipeline(device, surfaces, COLOR_FORMAT);
199
+ skyPipeline = await createSkyPipeline(device, COLOR_FORMAT);
200
+
201
+ if (config.getRaytracing()) {
202
+ rtCompiling = true;
203
+ const release = config.acquire?.("compiling shaders");
204
+ createRTPipeline(device, surfaces)
205
+ .then((p) => {
206
+ rtPipeline = p;
207
+ })
208
+ .finally(() => {
209
+ rtCompiling = false;
210
+ release?.();
211
+ });
212
+ }
213
+ },
214
+
215
+ execute(ctx: ExecutionContext) {
216
+ const wantRT = config.getRaytracing();
217
+
218
+ if (wantRT && !rtPipeline && !rtCompiling) {
219
+ rtCompiling = true;
220
+ const release = config.acquire?.("compiling shaders");
221
+ const surfaces = config.getSurfaces();
222
+ createRTPipeline(ctx.device, surfaces)
223
+ .then((p) => {
224
+ rtPipeline = p;
225
+ })
226
+ .finally(() => {
227
+ rtCompiling = false;
228
+ release?.();
229
+ });
230
+ }
231
+
232
+ if (wantRT && rtPipeline) {
233
+ executeRT(ctx);
234
+ } else {
235
+ executeRasterFallback(ctx);
236
+ }
237
+ },
238
+ };
239
+ }
240
+
241
+ export const RaytracingPlugin: Plugin = {
242
+ systems: [],
243
+ dependencies: [RasterPlugin],
244
+
245
+ async initialize(state: State) {
246
+ const compute = Compute.from(state);
247
+ const render = Render.from(state);
248
+ if (!compute || !render) return;
249
+
250
+ const { device } = compute;
251
+
252
+ const createPropertyBuffer = (size: number, label?: string) =>
253
+ device.createBuffer({
254
+ label: label ?? "property",
255
+ size,
256
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
257
+ });
258
+
259
+ const blasAtlas = createBLASAtlas(device, getMesh);
260
+
261
+ const rtState: RTState = {
262
+ instanceAABBs: createPropertyBuffer(MAX_ENTITIES * 32, "instanceAABBs"),
263
+ instanceInverses: createPropertyBuffer(MAX_ENTITIES * 64, "instanceInverses"),
264
+ instanceCount: device.createBuffer({
265
+ label: "instanceCount",
266
+ size: 4,
267
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
268
+ }),
269
+ tlas: createTLASBuffers(device),
270
+ blasAtlas,
271
+ };
272
+
273
+ state.setResource(RTResource, rtState);
274
+
275
+ const getRaytracing = () => {
276
+ for (const eid of state.query([Camera])) {
277
+ if (Camera.active[eid]) {
278
+ return state.hasComponent(eid, Raytracing);
279
+ }
280
+ }
281
+ return false;
282
+ };
283
+
284
+ const entityIds = new Uint32Array(MAX_ENTITIES);
285
+ const countBuf = new Uint32Array(1);
286
+
287
+ const uploadNode: ComputeNode = {
288
+ id: "rt-upload",
289
+ inputs: [],
290
+ outputs: [{ id: "instance-count", access: "write" }],
291
+ execute(ctx) {
292
+ if (!getRaytracing()) return;
293
+ const meshEntities = state.query([Mesh, WorldTransform]);
294
+ let meshCount = 0;
295
+ for (const eid of meshEntities) {
296
+ entityIds[meshCount] = eid;
297
+ meshCount++;
298
+ }
299
+ ctx.device.queue.writeBuffer(
300
+ rtState.tlas.entityIds,
301
+ 0,
302
+ entityIds,
303
+ 0,
304
+ Math.max(meshCount, 1)
305
+ );
306
+ countBuf[0] = meshCount;
307
+ ctx.device.queue.writeBuffer(rtState.instanceCount, 0, countBuf);
308
+ },
309
+ };
310
+ compute.graph.add(uploadNode);
311
+
312
+ const instanceNode = createInstanceNode({
313
+ matrices: render.matrices,
314
+ sizes: render.sizes,
315
+ shapes: render.shapes,
316
+ shapeAABBs: rtState.blasAtlas.shapeAABBs,
317
+ entityCount: render.entityCountBuffer,
318
+ instanceAABBs: rtState.instanceAABBs,
319
+ instanceInverses: rtState.instanceInverses,
320
+ getEntityCount: () => render.entityCount,
321
+ getRaytracing,
322
+ });
323
+ compute.graph.add(instanceNode);
324
+
325
+ const tlasNode = createTLASNode({
326
+ instanceAABBs: rtState.instanceAABBs,
327
+ instanceCount: rtState.instanceCount,
328
+ tlas: rtState.tlas,
329
+ getEntityCount: () => render.entityCount,
330
+ getRaytracing,
331
+ });
332
+ compute.graph.add(tlasNode);
333
+
334
+ compute.graph.add(
335
+ createDepthConvertNode({
336
+ scene: render.scene,
337
+ getRaytracing,
338
+ })
339
+ );
340
+
341
+ const getActiveClearColor = () => {
342
+ for (const eid of state.query([Camera])) {
343
+ if (Camera.active[eid]) return getClearColor(eid);
344
+ }
345
+ return { r: 0, g: 0, b: 0 };
346
+ };
347
+
348
+ const getSky = () => {
349
+ for (const eid of state.query([Camera])) {
350
+ if (Camera.active[eid]) return hasSkyComponent(state, eid);
351
+ }
352
+ return false;
353
+ };
354
+
355
+ const rasterState = RasterResource.from(state);
356
+
357
+ const forwardNode = createRTForwardNode({
358
+ scene: render.scene,
359
+ sky: render.sky,
360
+ data: render.data,
361
+ matrices: render.matrices,
362
+ sizes: render.sizes,
363
+ indirect: rasterState!.indirect,
364
+ getSurfaces: getDefaultAllSurfaces,
365
+ getRaytracing,
366
+ getClearColor: getActiveClearColor,
367
+ getSky,
368
+ acquire: (message) => Activity.from(state)?.acquire(message),
369
+ batches: () => rasterState?.batches ?? [],
370
+ tlasNodes: rtState.tlas.bvhNodes,
371
+ tlasInstanceIds: rtState.tlas.instanceIds,
372
+ blasNodes: rtState.blasAtlas.nodesBuffer,
373
+ blasTriIds: rtState.blasAtlas.triIdsBuffer,
374
+ blasTriangles: rtState.blasAtlas.trianglesBuffer,
375
+ blasMeta: rtState.blasAtlas.metaBuffer,
376
+ instanceInverses: rtState.instanceInverses,
377
+ });
378
+ compute.graph.set("forward", forwardNode);
379
+ },
380
+ };
@@ -165,6 +165,7 @@ export interface InstanceConfig {
165
165
  instanceAABBs: GPUBuffer;
166
166
  instanceInverses: GPUBuffer;
167
167
  getEntityCount: () => number;
168
+ getRaytracing?: () => boolean;
168
169
  }
169
170
 
170
171
  export function createInstanceNode(config: InstanceConfig): ComputeNode {
@@ -202,6 +203,8 @@ export function createInstanceNode(config: InstanceConfig): ComputeNode {
202
203
  },
203
204
 
204
205
  execute(ctx: ExecutionContext) {
206
+ if (config.getRaytracing && !config.getRaytracing()) return;
207
+
205
208
  const workgroups = Math.ceil(config.getEntityCount() / WORKGROUP_SIZE);
206
209
 
207
210
  const pass = ctx.encoder.beginComputePass();