@multiplekex/shallot 0.2.5 → 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 (41) 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/orbit/index.ts +1 -1
  8. package/src/extras/text/index.ts +10 -65
  9. package/src/extras/{water.ts → water/index.ts} +59 -4
  10. package/src/standard/raster/batch.ts +149 -0
  11. package/src/standard/raster/forward.ts +832 -0
  12. package/src/standard/raster/index.ts +146 -472
  13. package/src/standard/raster/shadow.ts +408 -0
  14. package/src/standard/raytracing/bvh/blas.ts +335 -87
  15. package/src/standard/raytracing/bvh/radix.ts +225 -228
  16. package/src/standard/raytracing/bvh/refit.ts +711 -0
  17. package/src/standard/raytracing/bvh/structs.ts +0 -55
  18. package/src/standard/raytracing/bvh/tlas.ts +153 -141
  19. package/src/standard/raytracing/bvh/traverse.ts +72 -64
  20. package/src/standard/raytracing/index.ts +233 -204
  21. package/src/standard/raytracing/instance.ts +30 -18
  22. package/src/standard/raytracing/ray.ts +1 -1
  23. package/src/standard/raytracing/shaders.ts +23 -40
  24. package/src/standard/render/camera.ts +10 -28
  25. package/src/standard/render/data.ts +1 -1
  26. package/src/standard/render/index.ts +68 -12
  27. package/src/standard/render/light.ts +2 -2
  28. package/src/standard/render/mesh.ts +404 -0
  29. package/src/standard/render/overlay.ts +5 -2
  30. package/src/standard/render/postprocess.ts +263 -267
  31. package/src/standard/render/surface/index.ts +81 -12
  32. package/src/standard/render/surface/shaders.ts +265 -11
  33. package/src/standard/render/surface/structs.ts +10 -0
  34. package/src/standard/tween/tween.ts +44 -115
  35. package/src/standard/render/mesh/box.ts +0 -20
  36. package/src/standard/render/mesh/index.ts +0 -315
  37. package/src/standard/render/mesh/plane.ts +0 -11
  38. package/src/standard/render/mesh/sphere.ts +0 -40
  39. package/src/standard/render/mesh/unified.ts +0 -96
  40. package/src/standard/render/surface/compile.ts +0 -65
  41. package/src/standard/render/surface/noise.ts +0 -58
@@ -1,31 +1,39 @@
1
1
  import type { Plugin, State } from "../../core";
2
2
  import { MAX_ENTITIES, resource } from "../../core";
3
+ import { setTraits } from "../../core/component";
3
4
  import { Compute } from "../compute";
4
5
  import type { ComputeNode, ExecutionContext } from "../compute";
5
6
  import { WorldTransform } from "../transforms";
6
7
  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";
8
+ import { Camera } from "../render/camera";
9
+ import { Mesh, getMesh, getMeshVersion } from "../render/mesh";
10
+ import { getDefaultAllSurfaces, Surface } from "../render/surface";
10
11
  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";
12
+ import { Render, registerShadowSettings } from "../render";
13
+ import { RasterPlugin, RasterResource } from "../raster";
21
14
  import { createBLASAtlas, type BLASAtlas } from "./bvh/blas";
22
15
  import { createTLASBuffers, createTLASNode, type TLASBuffers } from "./bvh/tlas";
23
- import { getMesh } from "../render/mesh";
16
+ import { createBLASRefitNode } from "./bvh/refit";
24
17
  import { createInstanceNode } from "./instance";
25
18
  import { createDepthConvertNode } from "./depth";
26
19
  import { compileUberShader } from "./shaders";
27
20
 
28
- export { compileUberShader, compileRTSurface, compileForwardShader } from "./shaders";
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";
29
37
  export { createDepthConvertNode } from "./depth";
30
38
  export { createInstanceNode } from "./instance";
31
39
  export { generateRay } from "./ray";
@@ -41,205 +49,184 @@ interface RTState {
41
49
  instanceCount: GPUBuffer;
42
50
  tlas: TLASBuffers;
43
51
  blasAtlas: BLASAtlas;
52
+ blasVersion: number;
53
+ rendered: boolean;
44
54
  }
45
55
 
46
56
  export const RTResource = resource<RTState>("raytracing");
47
57
 
48
- function createRTForwardNode(config: {
58
+ interface RTRenderConfig {
49
59
  scene: GPUBuffer;
50
60
  sky: GPUBuffer;
51
61
  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
62
  tlasNodes: GPUBuffer;
62
63
  tlasInstanceIds: GPUBuffer;
63
- blasNodes: GPUBuffer;
64
- blasTriIds: GPUBuffer;
65
- blasTriangles: GPUBuffer;
66
- blasMeta: GPUBuffer;
67
64
  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;
65
+ getBlasAtlas: () => BLASAtlas;
66
+ getSurfaces: () => SurfaceData[];
67
+ getRaytracing: () => boolean;
68
+ setRendered: (value: boolean) => void;
69
+ acquire?: (message?: string) => (() => void) | undefined;
70
+ }
73
71
 
74
- let bindGroup0: GPUBindGroup | null = null;
75
- let bindGroup1: GPUBindGroup | null = null;
76
- let cachedWidth = 0;
77
- let cachedHeight = 0;
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
+ }
78
81
 
79
- let skyBindGroup: GPUBindGroup | null = null;
80
- let batchBindGroups: GPUBindGroup[] = [];
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
+ }
81
93
 
82
- function executeRT(ctx: ExecutionContext): void {
83
- const { device, encoder } = ctx;
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
+ }
84
107
 
85
- const colorView = ctx.getTextureView("color");
86
- const depthView = ctx.getTextureView("depth");
87
- const eidView = ctx.getTextureView("eid");
108
+ function executeRTRender(gpu: RTRenderGPU, ctx: ExecutionContext, config: RTRenderConfig): void {
109
+ const wantRT = config.getRaytracing();
88
110
 
89
- if (!colorView || !depthView || !eidView || !rtPipeline) {
90
- return;
91
- }
111
+ if (!wantRT) {
112
+ config.setRendered(false);
113
+ return;
114
+ }
92
115
 
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
- });
116
+ if (!gpu.pipeline && !gpu.compiling) {
117
+ triggerCompile(gpu, ctx.device, config);
118
+ }
111
119
 
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
- });
120
+ if (!gpu.pipeline) {
121
+ config.setRendered(false);
122
+ return;
123
+ }
124
124
 
125
- cachedWidth = width;
126
- cachedHeight = height;
127
- }
125
+ const { device, encoder } = ctx;
126
+
127
+ const colorView = ctx.getTextureView("color");
128
+ const depthView = ctx.getTextureView("depth");
129
+ const eidView = ctx.getTextureView("eid");
128
130
 
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();
131
+ if (!colorView || !depthView || !eidView) {
132
+ config.setRendered(false);
133
+ return;
135
134
  }
136
135
 
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
- }
136
+ const colorTexture = ctx.getTexture("color");
137
+ if (!colorTexture) {
138
+ config.setRendered(false);
139
+ return;
140
+ }
154
141
 
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 ?? [];
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;
173
164
  }
174
165
 
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" },
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
+ ],
184
178
  });
179
+ gpu.cachedBlasAtlas = blas;
185
180
  }
186
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
+
187
203
  return {
188
- id: "forward",
204
+ id: "rt-render",
189
205
  inputs: [{ id: "tlas-bvh-nodes", access: "read" as const }],
190
206
  outputs: [
191
- { id: "color", access: "write" },
207
+ { id: "rt-output", access: "write" },
192
208
  { id: "depth", access: "write" },
193
- { id: "eid", access: "write" },
194
209
  ],
195
210
 
196
211
  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
212
  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
- });
213
+ triggerCompile(gpu, device, config);
212
214
  }
213
215
  },
214
216
 
215
217
  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
- }
218
+ executeRTRender(gpu, ctx, config);
237
219
  },
238
220
  };
239
221
  }
240
222
 
241
223
  export const RaytracingPlugin: Plugin = {
242
224
  systems: [],
225
+ components: {
226
+ Raytracing,
227
+ RaytracingShadowSettings,
228
+ Dynamic,
229
+ },
243
230
  dependencies: [RasterPlugin],
244
231
 
245
232
  async initialize(state: State) {
@@ -247,6 +234,12 @@ export const RaytracingPlugin: Plugin = {
247
234
  const render = Render.from(state);
248
235
  if (!compute || !render) return;
249
236
 
237
+ registerShadowSettings({
238
+ softness: RaytracingShadowSettings.softness,
239
+ samples: RaytracingShadowSettings.samples,
240
+ isActive: (s, eid) => s.hasComponent(eid, RaytracingShadowSettings),
241
+ });
242
+
250
243
  const { device } = compute;
251
244
 
252
245
  const createPropertyBuffer = (size: number, label?: string) =>
@@ -268,6 +261,8 @@ export const RaytracingPlugin: Plugin = {
268
261
  }),
269
262
  tlas: createTLASBuffers(device),
270
263
  blasAtlas,
264
+ blasVersion: getMeshVersion(),
265
+ rendered: false,
271
266
  };
272
267
 
273
268
  state.setResource(RTResource, rtState);
@@ -290,35 +285,75 @@ export const RaytracingPlugin: Plugin = {
290
285
  outputs: [{ id: "instance-count", access: "write" }],
291
286
  execute(ctx) {
292
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
+
293
304
  const meshEntities = state.query([Mesh, WorldTransform]);
294
- let meshCount = 0;
305
+ let entityCount = 0;
295
306
  for (const eid of meshEntities) {
296
- entityIds[meshCount] = eid;
297
- meshCount++;
307
+ entityIds[entityCount] = eid;
308
+ entityCount++;
298
309
  }
299
310
  ctx.device.queue.writeBuffer(
300
311
  rtState.tlas.entityIds,
301
312
  0,
302
313
  entityIds,
303
314
  0,
304
- Math.max(meshCount, 1)
315
+ Math.max(entityCount, 1)
305
316
  );
306
- countBuf[0] = meshCount;
317
+ countBuf[0] = entityCount;
307
318
  ctx.device.queue.writeBuffer(rtState.instanceCount, 0, countBuf);
308
319
  },
309
320
  };
310
321
  compute.graph.add(uploadNode);
311
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
+
312
346
  const instanceNode = createInstanceNode({
313
347
  matrices: render.matrices,
314
348
  sizes: render.sizes,
315
349
  shapes: render.shapes,
316
- shapeAABBs: rtState.blasAtlas.shapeAABBs,
317
350
  entityCount: render.entityCountBuffer,
318
351
  instanceAABBs: rtState.instanceAABBs,
319
352
  instanceInverses: rtState.instanceInverses,
353
+ getShapeAABBs: () => rtState.blasAtlas.shapeAABBs,
320
354
  getEntityCount: () => render.entityCount,
321
355
  getRaytracing,
356
+ inputs: [{ id: "blas-nodes", access: "read" }],
322
357
  });
323
358
  compute.graph.add(instanceNode);
324
359
 
@@ -338,43 +373,37 @@ export const RaytracingPlugin: Plugin = {
338
373
  })
339
374
  );
340
375
 
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
376
  const rasterState = RasterResource.from(state);
356
377
 
357
- const forwardNode = createRTForwardNode({
378
+ const rtRenderNode = createRTRenderNode({
358
379
  scene: render.scene,
359
380
  sky: render.sky,
360
381
  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
382
  tlasNodes: rtState.tlas.bvhNodes,
371
383
  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
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),
377
393
  });
378
- compute.graph.set("forward", forwardNode);
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
+ }
379
408
  },
380
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,50 +160,62 @@ 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;
168
+ getRaytracing: () => boolean;
169
+ inputs?: ResourceRef[];
169
170
  }
170
171
 
171
172
  export function createInstanceNode(config: InstanceConfig): ComputeNode {
172
173
  let pipeline: GPUComputePipeline | null = null;
173
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
+ }
174
192
 
175
193
  return {
176
194
  id: "instance",
177
- inputs: [],
195
+ inputs: config.inputs ?? [],
178
196
  outputs: [
179
197
  { id: "instance-aabbs", access: "write" },
180
198
  { id: "instance-inverses", access: "write" },
181
199
  ],
182
200
 
183
201
  async prepare(device: GPUDevice) {
184
- const module = await device.createShaderModule({ code: shader });
202
+ const module = device.createShaderModule({ code: shader });
185
203
 
186
204
  pipeline = await device.createComputePipelineAsync({
187
205
  layout: "auto",
188
206
  compute: { module, entryPoint: "main" },
189
207
  });
190
208
 
191
- bindGroup = device.createBindGroup({
192
- layout: pipeline.getBindGroupLayout(0),
193
- entries: [
194
- { binding: 0, resource: { buffer: config.matrices } },
195
- { binding: 1, resource: { buffer: config.sizes } },
196
- { binding: 2, resource: { buffer: config.shapes } },
197
- { binding: 3, resource: { buffer: config.shapeAABBs } },
198
- { binding: 4, resource: { buffer: config.entityCount } },
199
- { binding: 5, resource: { buffer: config.instanceAABBs } },
200
- { binding: 6, resource: { buffer: config.instanceInverses } },
201
- ],
202
- });
209
+ rebuildBindGroup(device, config.getShapeAABBs());
203
210
  },
204
211
 
205
212
  execute(ctx: ExecutionContext) {
206
- if (config.getRaytracing && !config.getRaytracing()) return;
213
+ if (!config.getRaytracing()) return;
214
+
215
+ const currentAABBs = config.getShapeAABBs();
216
+ if (currentAABBs !== cachedShapeAABBs) {
217
+ rebuildBindGroup(ctx.device, currentAABBs);
218
+ }
207
219
 
208
220
  const workgroups = Math.ceil(config.getEntityCount() / WORKGROUP_SIZE);
209
221
 
@@ -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