@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.
- package/package.json +1 -1
- package/src/extras/arrows/index.ts +3 -3
- package/src/extras/caustic.ts +37 -0
- package/src/extras/gradient/index.ts +63 -69
- package/src/extras/index.ts +3 -0
- package/src/extras/lines/index.ts +3 -3
- package/src/extras/skylab/index.ts +314 -0
- package/src/extras/text/font.ts +69 -14
- package/src/extras/text/index.ts +15 -7
- package/src/extras/text/sdf.ts +13 -2
- package/src/extras/water.ts +64 -0
- package/src/standard/defaults.ts +2 -0
- package/src/standard/index.ts +2 -0
- package/src/standard/raster/index.ts +517 -0
- package/src/standard/{render → raytracing}/bvh/blas.ts +3 -3
- package/src/standard/{render → raytracing}/bvh/tlas.ts +3 -0
- package/src/standard/{render → raytracing}/depth.ts +9 -9
- package/src/standard/raytracing/index.ts +380 -0
- package/src/standard/{render → raytracing}/instance.ts +3 -0
- package/src/standard/raytracing/shaders.ts +815 -0
- package/src/standard/{render → raytracing}/triangle.ts +1 -1
- package/src/standard/render/camera.ts +88 -80
- package/src/standard/render/index.ts +68 -208
- package/src/standard/render/indirect.ts +9 -10
- package/src/standard/render/mesh/index.ts +35 -166
- package/src/standard/render/overlay.ts +4 -4
- package/src/standard/render/pass.ts +1 -1
- package/src/standard/render/postprocess.ts +75 -50
- package/src/standard/render/scene.ts +28 -16
- package/src/standard/render/surface/compile.ts +6 -8
- package/src/standard/render/surface/noise.ts +15 -2
- package/src/standard/render/surface/shaders.ts +257 -0
- package/src/standard/render/surface/structs.ts +13 -6
- package/src/standard/render/forward/index.ts +0 -259
- package/src/standard/render/forward/raster.ts +0 -228
- package/src/standard/render/shaders.ts +0 -484
- package/src/standard/render/surface/wgsl.ts +0 -573
- /package/src/standard/{render → raytracing}/bvh/radix.ts +0 -0
- /package/src/standard/{render → raytracing}/bvh/structs.ts +0 -0
- /package/src/standard/{render → raytracing}/bvh/traverse.ts +0 -0
- /package/src/standard/{render → raytracing}/intersection.ts +0 -0
- /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();
|