@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,517 @@
|
|
|
1
|
+
import type { Plugin, State, System } from "../../core";
|
|
2
|
+
import { resource } from "../../core";
|
|
3
|
+
import { Compute } from "../compute";
|
|
4
|
+
import type { ComputeNode, ExecutionContext } from "../compute";
|
|
5
|
+
import { WorldTransform } from "../transforms";
|
|
6
|
+
import { Camera, getClearColor, hasSkyComponent } from "../render/camera";
|
|
7
|
+
import {
|
|
8
|
+
Mesh,
|
|
9
|
+
collectBatches,
|
|
10
|
+
updateBatches,
|
|
11
|
+
MAX_BATCH_SLOTS,
|
|
12
|
+
type Batch,
|
|
13
|
+
type MeshBuffers,
|
|
14
|
+
type BatchEntities,
|
|
15
|
+
} from "../render/mesh";
|
|
16
|
+
import { Surface, SurfaceType, getDefaultAllSurfaces } from "../render/surface";
|
|
17
|
+
import type { SurfaceData } from "../render/surface";
|
|
18
|
+
import { SCENE_STRUCT_WGSL, SKY_STRUCT_WGSL, WGSL_STRUCTS } from "../render/surface/structs";
|
|
19
|
+
import {
|
|
20
|
+
compileVertexBody,
|
|
21
|
+
WGSL_LIGHTING_CALC,
|
|
22
|
+
SKY_WGSL,
|
|
23
|
+
SKY_DIR_WGSL,
|
|
24
|
+
NOISE_WGSL,
|
|
25
|
+
} from "../render/surface/shaders";
|
|
26
|
+
import { COLOR_FORMAT } from "../render/scene";
|
|
27
|
+
import { createIndirectBuffer, INDIRECT_SIZE } from "../render/indirect";
|
|
28
|
+
import { Render, RenderPlugin } from "../render";
|
|
29
|
+
|
|
30
|
+
export { createRasterPipeline, createSkyPipeline, executeRasterPass };
|
|
31
|
+
export type { RasterPassConfig };
|
|
32
|
+
|
|
33
|
+
interface RasterState {
|
|
34
|
+
indirect: GPUBuffer;
|
|
35
|
+
batches: (Batch | null)[];
|
|
36
|
+
batchEntities: BatchEntities;
|
|
37
|
+
buffers: Map<number, MeshBuffers>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const RasterResource = resource<RasterState>("raster");
|
|
41
|
+
|
|
42
|
+
const RasterUploadSystem: System = {
|
|
43
|
+
group: "draw",
|
|
44
|
+
|
|
45
|
+
update(state: State) {
|
|
46
|
+
const render = Render.from(state);
|
|
47
|
+
const raster = RasterResource.from(state);
|
|
48
|
+
const compute = Compute.from(state);
|
|
49
|
+
if (!render || !raster || !compute) return;
|
|
50
|
+
|
|
51
|
+
const { device } = compute;
|
|
52
|
+
const meshEntities = state.query([Mesh, WorldTransform]);
|
|
53
|
+
|
|
54
|
+
collectBatches(
|
|
55
|
+
meshEntities,
|
|
56
|
+
(eid) => Surface.type[eid] ?? SurfaceType.Default,
|
|
57
|
+
raster.batchEntities
|
|
58
|
+
);
|
|
59
|
+
updateBatches(device, raster.batchEntities, raster, raster.indirect);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function compileSurfaceVariant(id: number, data: SurfaceData): string {
|
|
64
|
+
const vertexBody = compileVertexBody(data.vertex);
|
|
65
|
+
const fragmentBody = data.fragment ?? "";
|
|
66
|
+
const lit = data.lit !== false;
|
|
67
|
+
|
|
68
|
+
return `
|
|
69
|
+
fn userVertexTransform_${id}(worldPos: vec3<f32>, normal: vec3<f32>, eid: u32) -> vec3<f32> {
|
|
70
|
+
${vertexBody}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn userFragment_${id}(surface: ptr<function, SurfaceData>, position: vec4<f32>) {
|
|
74
|
+
${fragmentBody}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn applyLighting_${id}(surface: SurfaceData) -> vec3<f32> {
|
|
78
|
+
${
|
|
79
|
+
lit
|
|
80
|
+
? `${WGSL_LIGHTING_CALC}
|
|
81
|
+
return surface.baseColor * lighting + surface.emission;`
|
|
82
|
+
: "return surface.baseColor;"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function compileDispatchFunctions(surfaceCount: number): string {
|
|
89
|
+
const vertexCases = Array.from(
|
|
90
|
+
{ length: surfaceCount },
|
|
91
|
+
(_, i) => ` case ${i}u: { return userVertexTransform_${i}(worldPos, normal, eid); }`
|
|
92
|
+
).join("\n");
|
|
93
|
+
|
|
94
|
+
const fragmentCases = Array.from(
|
|
95
|
+
{ length: surfaceCount },
|
|
96
|
+
(_, i) => ` case ${i}u: { userFragment_${i}(surface, position); }`
|
|
97
|
+
).join("\n");
|
|
98
|
+
|
|
99
|
+
const lightingCases = Array.from(
|
|
100
|
+
{ length: surfaceCount },
|
|
101
|
+
(_, i) => ` case ${i}u: { return applyLighting_${i}(surface); }`
|
|
102
|
+
).join("\n");
|
|
103
|
+
|
|
104
|
+
return `
|
|
105
|
+
fn dispatchVertexTransform(surfaceId: u32, worldPos: vec3<f32>, normal: vec3<f32>, eid: u32) -> vec3<f32> {
|
|
106
|
+
switch surfaceId {
|
|
107
|
+
${vertexCases}
|
|
108
|
+
default: { return userVertexTransform_0(worldPos, normal, eid); }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn dispatchFragment(surfaceId: u32, surface: ptr<function, SurfaceData>, position: vec4<f32>) {
|
|
113
|
+
switch surfaceId {
|
|
114
|
+
${fragmentCases}
|
|
115
|
+
default: { userFragment_0(surface, position); }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn dispatchLighting(surfaceId: u32, surface: SurfaceData) -> vec3<f32> {
|
|
120
|
+
switch surfaceId {
|
|
121
|
+
${lightingCases}
|
|
122
|
+
default: { return applyLighting_0(surface); }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function compileRasterShader(surfaces: SurfaceData[]): string {
|
|
129
|
+
const surfaceVariants = surfaces.map((s, i) => compileSurfaceVariant(i, s)).join("\n");
|
|
130
|
+
const dispatchFunctions = compileDispatchFunctions(surfaces.length);
|
|
131
|
+
|
|
132
|
+
return /* wgsl */ `
|
|
133
|
+
${WGSL_STRUCTS}
|
|
134
|
+
${NOISE_WGSL}
|
|
135
|
+
|
|
136
|
+
${surfaceVariants}
|
|
137
|
+
${dispatchFunctions}
|
|
138
|
+
|
|
139
|
+
@vertex
|
|
140
|
+
fn vs(input: VertexInput) -> VertexOutput {
|
|
141
|
+
let eid = entityIds[input.instance];
|
|
142
|
+
let world = matrices[eid];
|
|
143
|
+
let scaledPos = input.position * sizes[eid].xyz;
|
|
144
|
+
let baseWorldPos = (world * vec4<f32>(scaledPos, 1.0)).xyz;
|
|
145
|
+
let worldNormal = normalize((world * vec4<f32>(input.normal, 0.0)).xyz);
|
|
146
|
+
let d = data[eid];
|
|
147
|
+
let surfaceId = d.flags & 0xFFu;
|
|
148
|
+
let finalWorldPos = dispatchVertexTransform(surfaceId, baseWorldPos, worldNormal, eid);
|
|
149
|
+
|
|
150
|
+
var output: VertexOutput;
|
|
151
|
+
output.position = scene.viewProj * vec4<f32>(finalWorldPos, 1.0);
|
|
152
|
+
output.color = d.baseColor;
|
|
153
|
+
output.worldNormal = worldNormal;
|
|
154
|
+
output.entityId = eid;
|
|
155
|
+
output.worldPos = finalWorldPos;
|
|
156
|
+
return output;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@fragment
|
|
160
|
+
fn fs(input: VertexOutput) -> FragmentOutput {
|
|
161
|
+
let eid = input.entityId;
|
|
162
|
+
let d = data[eid];
|
|
163
|
+
let surfaceId = d.flags & 0xFFu;
|
|
164
|
+
|
|
165
|
+
var surface: SurfaceData;
|
|
166
|
+
surface.baseColor = input.color.rgb;
|
|
167
|
+
surface.roughness = d.pbr.x;
|
|
168
|
+
surface.metallic = d.pbr.y;
|
|
169
|
+
surface.emission = d.emission.rgb * d.emission.a;
|
|
170
|
+
surface.normal = normalize(input.worldNormal);
|
|
171
|
+
surface.worldPos = input.worldPos;
|
|
172
|
+
|
|
173
|
+
dispatchFragment(surfaceId, &surface, input.position);
|
|
174
|
+
|
|
175
|
+
let litColor = dispatchLighting(surfaceId, surface);
|
|
176
|
+
|
|
177
|
+
var output: FragmentOutput;
|
|
178
|
+
output.color = vec4<f32>(litColor, input.color.a);
|
|
179
|
+
output.entityId = input.entityId;
|
|
180
|
+
return output;
|
|
181
|
+
}
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function createRasterPipeline(
|
|
186
|
+
device: GPUDevice,
|
|
187
|
+
surfaces: SurfaceData[],
|
|
188
|
+
colorFormat: GPUTextureFormat
|
|
189
|
+
): Promise<GPURenderPipeline> {
|
|
190
|
+
const code = compileRasterShader(surfaces);
|
|
191
|
+
const module = device.createShaderModule({ code });
|
|
192
|
+
|
|
193
|
+
return device.createRenderPipelineAsync({
|
|
194
|
+
layout: "auto",
|
|
195
|
+
vertex: {
|
|
196
|
+
module,
|
|
197
|
+
entryPoint: "vs",
|
|
198
|
+
buffers: [
|
|
199
|
+
{
|
|
200
|
+
arrayStride: 24,
|
|
201
|
+
attributes: [
|
|
202
|
+
{ shaderLocation: 0, offset: 0, format: "float32x3" },
|
|
203
|
+
{ shaderLocation: 1, offset: 12, format: "float32x3" },
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
fragment: {
|
|
209
|
+
module,
|
|
210
|
+
entryPoint: "fs",
|
|
211
|
+
targets: [{ format: colorFormat }, { format: "r32uint" }],
|
|
212
|
+
},
|
|
213
|
+
depthStencil: {
|
|
214
|
+
format: "depth24plus",
|
|
215
|
+
depthWriteEnabled: true,
|
|
216
|
+
depthCompare: "less",
|
|
217
|
+
},
|
|
218
|
+
primitive: {
|
|
219
|
+
topology: "triangle-list",
|
|
220
|
+
cullMode: "back",
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function compileSkyShader(): string {
|
|
226
|
+
return /* wgsl */ `
|
|
227
|
+
${SCENE_STRUCT_WGSL}
|
|
228
|
+
${SKY_STRUCT_WGSL}
|
|
229
|
+
|
|
230
|
+
@group(0) @binding(0) var<uniform> scene: Scene;
|
|
231
|
+
@group(0) @binding(1) var<uniform> sky: Sky;
|
|
232
|
+
|
|
233
|
+
${SKY_DIR_WGSL}
|
|
234
|
+
${NOISE_WGSL}
|
|
235
|
+
${SKY_WGSL}
|
|
236
|
+
|
|
237
|
+
struct VertexOutput {
|
|
238
|
+
@builtin(position) position: vec4<f32>,
|
|
239
|
+
@location(0) uv: vec2<f32>,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@vertex
|
|
243
|
+
fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
244
|
+
var positions = array<vec2<f32>, 3>(
|
|
245
|
+
vec2(-1.0, -1.0),
|
|
246
|
+
vec2(3.0, -1.0),
|
|
247
|
+
vec2(-1.0, 3.0)
|
|
248
|
+
);
|
|
249
|
+
var output: VertexOutput;
|
|
250
|
+
output.position = vec4(positions[vertexIndex], 0.0, 1.0);
|
|
251
|
+
output.uv = (positions[vertexIndex] + 1.0) * 0.5;
|
|
252
|
+
output.uv.y = 1.0 - output.uv.y;
|
|
253
|
+
return output;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@fragment
|
|
257
|
+
fn fs(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
258
|
+
let dir = computeSkyDir(input.uv.x, input.uv.y);
|
|
259
|
+
let color = sampleSky(dir);
|
|
260
|
+
return vec4(color, 1.0);
|
|
261
|
+
}
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function createSkyPipeline(
|
|
266
|
+
device: GPUDevice,
|
|
267
|
+
colorFormat: GPUTextureFormat
|
|
268
|
+
): Promise<GPURenderPipeline> {
|
|
269
|
+
const code = compileSkyShader();
|
|
270
|
+
const module = device.createShaderModule({ code });
|
|
271
|
+
|
|
272
|
+
return device.createRenderPipelineAsync({
|
|
273
|
+
layout: "auto",
|
|
274
|
+
vertex: { module, entryPoint: "vs" },
|
|
275
|
+
fragment: {
|
|
276
|
+
module,
|
|
277
|
+
entryPoint: "fs",
|
|
278
|
+
targets: [{ format: colorFormat }],
|
|
279
|
+
},
|
|
280
|
+
depthStencil: {
|
|
281
|
+
format: "depth24plus",
|
|
282
|
+
depthWriteEnabled: false,
|
|
283
|
+
depthCompare: "always",
|
|
284
|
+
},
|
|
285
|
+
primitive: { topology: "triangle-list" },
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
interface RasterPassConfig {
|
|
290
|
+
scene: GPUBuffer;
|
|
291
|
+
sky: GPUBuffer;
|
|
292
|
+
data: GPUBuffer;
|
|
293
|
+
matrices: GPUBuffer;
|
|
294
|
+
sizes: GPUBuffer;
|
|
295
|
+
indirect: GPUBuffer;
|
|
296
|
+
rasterPipeline: GPURenderPipeline;
|
|
297
|
+
skyPipeline: GPURenderPipeline | null;
|
|
298
|
+
getClearColor: () => { r: number; g: number; b: number };
|
|
299
|
+
getSky: () => boolean;
|
|
300
|
+
batches: () => (Batch | null)[];
|
|
301
|
+
skyBindGroup?: GPUBindGroup | null;
|
|
302
|
+
batchBindGroups?: GPUBindGroup[];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function executeRasterPass(ctx: ExecutionContext, config: RasterPassConfig): void {
|
|
306
|
+
const { device, encoder } = ctx;
|
|
307
|
+
|
|
308
|
+
const colorView = ctx.getTextureView("color");
|
|
309
|
+
const eidView = ctx.getTextureView("eid");
|
|
310
|
+
const zView = ctx.getTextureView("z");
|
|
311
|
+
|
|
312
|
+
if (!colorView || !eidView || !zView) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const batches = config.batches();
|
|
317
|
+
const colorTexture = ctx.getTexture("color");
|
|
318
|
+
if (!colorTexture) return;
|
|
319
|
+
|
|
320
|
+
const clearColor = config.getClearColor();
|
|
321
|
+
const hasSky = config.getSky() && config.skyPipeline;
|
|
322
|
+
|
|
323
|
+
if (hasSky) {
|
|
324
|
+
const skyPass = encoder.beginRenderPass({
|
|
325
|
+
colorAttachments: [
|
|
326
|
+
{
|
|
327
|
+
view: colorView as GPUTextureView,
|
|
328
|
+
clearValue: { r: clearColor.r, g: clearColor.g, b: clearColor.b, a: 1 },
|
|
329
|
+
loadOp: "clear" as const,
|
|
330
|
+
storeOp: "store" as const,
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
depthStencilAttachment: {
|
|
334
|
+
view: zView as GPUTextureView,
|
|
335
|
+
depthClearValue: 1.0,
|
|
336
|
+
depthLoadOp: "clear" as const,
|
|
337
|
+
depthStoreOp: "store" as const,
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
if (!config.skyBindGroup) {
|
|
342
|
+
config.skyBindGroup = device.createBindGroup({
|
|
343
|
+
layout: config.skyPipeline!.getBindGroupLayout(0),
|
|
344
|
+
entries: [
|
|
345
|
+
{ binding: 0, resource: { buffer: config.scene } },
|
|
346
|
+
{ binding: 1, resource: { buffer: config.sky } },
|
|
347
|
+
],
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
skyPass.setPipeline(config.skyPipeline!);
|
|
352
|
+
skyPass.setBindGroup(0, config.skyBindGroup);
|
|
353
|
+
skyPass.draw(3);
|
|
354
|
+
skyPass.end();
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const pass = encoder.beginRenderPass({
|
|
358
|
+
colorAttachments: [
|
|
359
|
+
{
|
|
360
|
+
view: colorView as GPUTextureView,
|
|
361
|
+
clearValue: { r: clearColor.r, g: clearColor.g, b: clearColor.b, a: 1 },
|
|
362
|
+
loadOp: hasSky ? ("load" as const) : ("clear" as const),
|
|
363
|
+
storeOp: "store" as const,
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
view: eidView as GPUTextureView,
|
|
367
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
368
|
+
loadOp: "clear" as const,
|
|
369
|
+
storeOp: "store" as const,
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
depthStencilAttachment: {
|
|
373
|
+
view: zView as GPUTextureView,
|
|
374
|
+
depthClearValue: 1.0,
|
|
375
|
+
depthLoadOp: hasSky ? ("load" as const) : ("clear" as const),
|
|
376
|
+
depthStoreOp: "store" as const,
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
pass.setPipeline(config.rasterPipeline);
|
|
381
|
+
|
|
382
|
+
if (!config.batchBindGroups) {
|
|
383
|
+
config.batchBindGroups = [];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
for (let i = 0; i < batches.length; i++) {
|
|
387
|
+
const batch = batches[i];
|
|
388
|
+
if (!batch || batch.count === 0) continue;
|
|
389
|
+
|
|
390
|
+
if (!config.batchBindGroups[i]) {
|
|
391
|
+
config.batchBindGroups[i] = device.createBindGroup({
|
|
392
|
+
layout: config.rasterPipeline.getBindGroupLayout(0),
|
|
393
|
+
entries: [
|
|
394
|
+
{ binding: 0, resource: { buffer: config.scene } },
|
|
395
|
+
{ binding: 1, resource: { buffer: batch.entityIds } },
|
|
396
|
+
{ binding: 2, resource: { buffer: config.matrices } },
|
|
397
|
+
{ binding: 3, resource: { buffer: config.sizes } },
|
|
398
|
+
{ binding: 4, resource: { buffer: config.data } },
|
|
399
|
+
],
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
pass.setBindGroup(0, config.batchBindGroups[i]);
|
|
404
|
+
pass.setVertexBuffer(0, batch.buffers.vertex);
|
|
405
|
+
pass.setIndexBuffer(batch.buffers.index, "uint16");
|
|
406
|
+
pass.drawIndexedIndirect(config.indirect, i * INDIRECT_SIZE);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
pass.end();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
interface RasterForwardConfig {
|
|
413
|
+
scene: GPUBuffer;
|
|
414
|
+
sky: GPUBuffer;
|
|
415
|
+
data: GPUBuffer;
|
|
416
|
+
indirect: GPUBuffer;
|
|
417
|
+
matrices: GPUBuffer;
|
|
418
|
+
sizes: GPUBuffer;
|
|
419
|
+
getSurfaces: () => SurfaceData[];
|
|
420
|
+
getClearColor: () => { r: number; g: number; b: number };
|
|
421
|
+
getSky: () => boolean;
|
|
422
|
+
batches: () => (Batch | null)[];
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function createRasterForwardNode(config: RasterForwardConfig): ComputeNode {
|
|
426
|
+
let rasterPipeline: GPURenderPipeline | null = null;
|
|
427
|
+
let skyPipeline: GPURenderPipeline | null = null;
|
|
428
|
+
let skyBindGroup: GPUBindGroup | null = null;
|
|
429
|
+
let batchBindGroups: GPUBindGroup[] = [];
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
id: "forward",
|
|
433
|
+
inputs: [{ id: "data", access: "read" }],
|
|
434
|
+
outputs: [
|
|
435
|
+
{ id: "color", access: "write" },
|
|
436
|
+
{ id: "eid", access: "write" },
|
|
437
|
+
],
|
|
438
|
+
|
|
439
|
+
async prepare(device: GPUDevice) {
|
|
440
|
+
const surfaces = config.getSurfaces();
|
|
441
|
+
rasterPipeline = await createRasterPipeline(device, surfaces, COLOR_FORMAT);
|
|
442
|
+
skyPipeline = await createSkyPipeline(device, COLOR_FORMAT);
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
execute(ctx: ExecutionContext) {
|
|
446
|
+
if (!rasterPipeline) return;
|
|
447
|
+
const passConfig: RasterPassConfig = {
|
|
448
|
+
scene: config.scene,
|
|
449
|
+
sky: config.sky,
|
|
450
|
+
data: config.data,
|
|
451
|
+
matrices: config.matrices,
|
|
452
|
+
sizes: config.sizes,
|
|
453
|
+
indirect: config.indirect,
|
|
454
|
+
rasterPipeline,
|
|
455
|
+
skyPipeline,
|
|
456
|
+
getClearColor: config.getClearColor,
|
|
457
|
+
getSky: config.getSky,
|
|
458
|
+
batches: config.batches,
|
|
459
|
+
skyBindGroup,
|
|
460
|
+
batchBindGroups,
|
|
461
|
+
};
|
|
462
|
+
executeRasterPass(ctx, passConfig);
|
|
463
|
+
skyBindGroup = passConfig.skyBindGroup ?? null;
|
|
464
|
+
batchBindGroups = passConfig.batchBindGroups ?? [];
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export const RasterPlugin: Plugin = {
|
|
470
|
+
systems: [RasterUploadSystem],
|
|
471
|
+
dependencies: [RenderPlugin],
|
|
472
|
+
|
|
473
|
+
async initialize(state: State) {
|
|
474
|
+
const compute = Compute.from(state);
|
|
475
|
+
const render = Render.from(state);
|
|
476
|
+
if (!compute || !render) return;
|
|
477
|
+
|
|
478
|
+
const { device } = compute;
|
|
479
|
+
|
|
480
|
+
const rasterState: RasterState = {
|
|
481
|
+
indirect: createIndirectBuffer(device, MAX_BATCH_SLOTS),
|
|
482
|
+
batches: Array(MAX_BATCH_SLOTS).fill(null),
|
|
483
|
+
batchEntities: Array(MAX_BATCH_SLOTS).fill(null),
|
|
484
|
+
buffers: new Map(),
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
state.setResource(RasterResource, rasterState);
|
|
488
|
+
|
|
489
|
+
const getActiveClearColor = () => {
|
|
490
|
+
for (const eid of state.query([Camera])) {
|
|
491
|
+
if (Camera.active[eid]) return getClearColor(eid);
|
|
492
|
+
}
|
|
493
|
+
return { r: 0, g: 0, b: 0 };
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
const getSky = () => {
|
|
497
|
+
for (const eid of state.query([Camera])) {
|
|
498
|
+
if (Camera.active[eid]) return hasSkyComponent(state, eid);
|
|
499
|
+
}
|
|
500
|
+
return false;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const forwardNode = createRasterForwardNode({
|
|
504
|
+
scene: render.scene,
|
|
505
|
+
sky: render.sky,
|
|
506
|
+
data: render.data,
|
|
507
|
+
indirect: rasterState.indirect,
|
|
508
|
+
matrices: render.matrices,
|
|
509
|
+
sizes: render.sizes,
|
|
510
|
+
getSurfaces: getDefaultAllSurfaces,
|
|
511
|
+
getClearColor: getActiveClearColor,
|
|
512
|
+
getSky,
|
|
513
|
+
batches: () => rasterState.batches,
|
|
514
|
+
});
|
|
515
|
+
compute.graph.add(forwardNode);
|
|
516
|
+
},
|
|
517
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Vec3, BVHNode, MortonPair, AABB } from "./structs";
|
|
2
2
|
import { LEAF_FLAG, isLeaf, leafIndex } from "./structs";
|
|
3
|
-
import type { MeshData } from "
|
|
3
|
+
import type { MeshData } from "../../render/mesh";
|
|
4
4
|
|
|
5
5
|
export interface BLASTriangle {
|
|
6
6
|
v0: Vec3;
|
|
@@ -503,7 +503,7 @@ export function buildShapeBLAS(triangles: BLASTriangle[]): BLASData {
|
|
|
503
503
|
};
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
-
|
|
506
|
+
function validateBLASBounds(blas: BLASData, triangles: BLASTriangle[]): boolean {
|
|
507
507
|
if (blas.triCount === 0) return true;
|
|
508
508
|
|
|
509
509
|
const epsilon = 1e-5;
|
|
@@ -530,7 +530,7 @@ export function validateBLASBounds(blas: BLASData, triangles: BLASTriangle[]): b
|
|
|
530
530
|
return true;
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
-
|
|
533
|
+
function validateBLASNodes(blas: BLASData): { valid: boolean; errors: string[] } {
|
|
534
534
|
const errors: string[] = [];
|
|
535
535
|
|
|
536
536
|
if (blas.triCount <= 1) {
|
|
@@ -721,6 +721,7 @@ export interface TLASConfig {
|
|
|
721
721
|
instanceCount: GPUBuffer;
|
|
722
722
|
tlas: TLASBuffers;
|
|
723
723
|
getEntityCount: () => number;
|
|
724
|
+
getRaytracing?: () => boolean;
|
|
724
725
|
}
|
|
725
726
|
|
|
726
727
|
export function createTLASNode(config: TLASConfig): ComputeNode {
|
|
@@ -836,6 +837,8 @@ export function createTLASNode(config: TLASConfig): ComputeNode {
|
|
|
836
837
|
},
|
|
837
838
|
|
|
838
839
|
execute(ctx: ExecutionContext) {
|
|
840
|
+
if (config.getRaytracing && !config.getRaytracing()) return;
|
|
841
|
+
|
|
839
842
|
const { device, encoder } = ctx;
|
|
840
843
|
|
|
841
844
|
const workgroups = Math.ceil(MAX_ENTITIES / WORKGROUP_SIZE);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ComputeNode, ExecutionContext } from "../compute";
|
|
2
|
-
import { SCENE_STRUCT_WGSL } from "
|
|
2
|
+
import { SCENE_STRUCT_WGSL } from "../render/surface/structs";
|
|
3
3
|
|
|
4
4
|
const depthConvertShader = /* wgsl */ `
|
|
5
5
|
${SCENE_STRUCT_WGSL}
|
|
6
6
|
|
|
7
7
|
@group(0) @binding(0) var<uniform> scene: Scene;
|
|
8
|
-
@group(0) @binding(1) var
|
|
8
|
+
@group(0) @binding(1) var depthTex: texture_2d<f32>;
|
|
9
9
|
|
|
10
10
|
struct VertexOutput {
|
|
11
11
|
@builtin(position) position: vec4<f32>,
|
|
@@ -29,7 +29,7 @@ fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
|
|
|
29
29
|
@fragment
|
|
30
30
|
fn fs(input: VertexOutput) -> @builtin(frag_depth) f32 {
|
|
31
31
|
let coords = vec2<i32>(input.position.xy);
|
|
32
|
-
let t = textureLoad(
|
|
32
|
+
let t = textureLoad(depthTex, coords, 0).r;
|
|
33
33
|
|
|
34
34
|
let near = scene.near;
|
|
35
35
|
let far = scene.far;
|
|
@@ -59,8 +59,8 @@ export function createDepthConvertNode(config: DepthConvertConfig): ComputeNode
|
|
|
59
59
|
|
|
60
60
|
return {
|
|
61
61
|
id: "depth-convert",
|
|
62
|
-
inputs: [{ id: "
|
|
63
|
-
outputs: [{ id: "
|
|
62
|
+
inputs: [{ id: "depth", access: "read" }],
|
|
63
|
+
outputs: [{ id: "z", access: "write" }],
|
|
64
64
|
|
|
65
65
|
async prepare(device: GPUDevice) {
|
|
66
66
|
const module = device.createShaderModule({ code: depthConvertShader });
|
|
@@ -84,10 +84,10 @@ export function createDepthConvertNode(config: DepthConvertConfig): ComputeNode
|
|
|
84
84
|
|
|
85
85
|
const { device, encoder } = ctx;
|
|
86
86
|
|
|
87
|
-
const linearDepthView = ctx.getTextureView("linear-depth");
|
|
88
87
|
const depthView = ctx.getTextureView("depth");
|
|
88
|
+
const zView = ctx.getTextureView("z");
|
|
89
89
|
|
|
90
|
-
if (!
|
|
90
|
+
if (!depthView || !zView) {
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -95,14 +95,14 @@ export function createDepthConvertNode(config: DepthConvertConfig): ComputeNode
|
|
|
95
95
|
layout: pipeline!.getBindGroupLayout(0),
|
|
96
96
|
entries: [
|
|
97
97
|
{ binding: 0, resource: { buffer: config.scene } },
|
|
98
|
-
{ binding: 1, resource:
|
|
98
|
+
{ binding: 1, resource: depthView },
|
|
99
99
|
],
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
const pass = encoder.beginRenderPass({
|
|
103
103
|
colorAttachments: [],
|
|
104
104
|
depthStencilAttachment: {
|
|
105
|
-
view:
|
|
105
|
+
view: zView,
|
|
106
106
|
depthClearValue: 1.0,
|
|
107
107
|
depthLoadOp: "clear",
|
|
108
108
|
depthStoreOp: "store",
|