Skip to content

Commit 7831e90

Browse files
committed
cleanuo buffer usage
1 parent ed9ef28 commit 7831e90

File tree

1 file changed

+79
-88
lines changed

1 file changed

+79
-88
lines changed

packages/labs/src/folk-sand-webgpu.ts

Lines changed: 79 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,24 @@ export class FolkSandWebGPU extends FolkBaseSet {
113113
});
114114
}
115115

116-
#createBuffer(size: number, usage: GPUBufferUsageFlags): GPUBuffer {
116+
#createBuffer(size: number, type: 'uniform' | 'storage' | 'storage-rw'): GPUBuffer {
117+
const usage = {
118+
uniform: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
119+
storage: GPUBufferUsage.STORAGE,
120+
'storage-rw': GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
121+
}[type];
117122
const buffer = this.#device.createBuffer({ size, usage });
118123
this.#resources.push(buffer);
119124
return buffer;
120125
}
121126

127+
#createBindGroup(pipeline: GPUComputePipeline | GPURenderPipeline, buffers: GPUBuffer[]): GPUBindGroup {
128+
return this.#device.createBindGroup({
129+
layout: pipeline.getBindGroupLayout(0),
130+
entries: buffers.map((buffer, i) => ({ binding: i, resource: { buffer } })),
131+
});
132+
}
133+
122134
#stateBufferSize() {
123135
return this.#bufferWidth * this.#bufferHeight * 8; // Particle struct: 2 x u32
124136
}
@@ -196,48 +208,30 @@ export class FolkSandWebGPU extends FolkBaseSet {
196208
}
197209

198210
#createResources() {
199-
this.#paramsBuffer = this.#createBuffer(32, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);
200-
this.#mouseBuffer = this.#createBuffer(16, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);
201-
this.#collisionParamsBuffer = this.#createBuffer(16, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST);
211+
this.#paramsBuffer = this.#createBuffer(32, 'uniform');
212+
this.#mouseBuffer = this.#createBuffer(16, 'uniform');
213+
this.#collisionParamsBuffer = this.#createBuffer(16, 'uniform');
202214

203215
const stateSize = this.#stateBufferSize();
204-
this.#stateBuffers = [
205-
this.#createBuffer(stateSize, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC),
206-
this.#createBuffer(stateSize, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC),
207-
];
208-
this.#collisionBuffer = this.#createBuffer(stateSize, GPUBufferUsage.STORAGE);
216+
this.#stateBuffers = [this.#createBuffer(stateSize, 'storage'), this.#createBuffer(stateSize, 'storage')];
217+
this.#collisionBuffer = this.#createBuffer(stateSize, 'storage');
209218
}
210219

211220
#createBindGroups() {
212-
this.#initBindGroup = this.#device.createBindGroup({
213-
layout: this.#initPipeline.getBindGroupLayout(0),
214-
entries: [
215-
{ binding: 0, resource: { buffer: this.#stateBuffers[0] } },
216-
{ binding: 1, resource: { buffer: this.#paramsBuffer } },
217-
],
218-
});
221+
this.#initBindGroup = this.#createBindGroup(this.#initPipeline, [this.#stateBuffers[0], this.#paramsBuffer]);
219222

220223
this.#simBindGroups = [0, 1].map((i) =>
221-
this.#device.createBindGroup({
222-
layout: this.#simulationPipeline.getBindGroupLayout(0),
223-
entries: [
224-
{ binding: 0, resource: { buffer: this.#stateBuffers[i] } },
225-
{ binding: 1, resource: { buffer: this.#stateBuffers[1 - i] } },
226-
{ binding: 2, resource: { buffer: this.#collisionBuffer } },
227-
{ binding: 3, resource: { buffer: this.#paramsBuffer } },
228-
{ binding: 4, resource: { buffer: this.#mouseBuffer } },
229-
],
230-
}),
224+
this.#createBindGroup(this.#simulationPipeline, [
225+
this.#stateBuffers[i],
226+
this.#stateBuffers[1 - i],
227+
this.#collisionBuffer,
228+
this.#paramsBuffer,
229+
this.#mouseBuffer,
230+
]),
231231
) as [GPUBindGroup, GPUBindGroup];
232232

233233
this.#renderBindGroups = [0, 1].map((i) =>
234-
this.#device.createBindGroup({
235-
layout: this.#renderPipeline.getBindGroupLayout(0),
236-
entries: [
237-
{ binding: 0, resource: { buffer: this.#stateBuffers[i] } },
238-
{ binding: 1, resource: { buffer: this.#paramsBuffer } },
239-
],
240-
}),
234+
this.#createBindGroup(this.#renderPipeline, [this.#stateBuffers[i], this.#paramsBuffer]),
241235
) as [GPUBindGroup, GPUBindGroup];
242236

243237
this.#updateCollisionBindGroup();
@@ -249,14 +243,11 @@ export class FolkSandWebGPU extends FolkBaseSet {
249243
this.#collisionBindGroup = undefined;
250244
return;
251245
}
252-
this.#collisionBindGroup = this.#device.createBindGroup({
253-
layout: this.#collisionPipeline.getBindGroupLayout(0),
254-
entries: [
255-
{ binding: 0, resource: { buffer: this.#collisionBuffer } },
256-
{ binding: 1, resource: { buffer: this.#collisionParamsBuffer } },
257-
{ binding: 2, resource: { buffer: this.#shapeDataBuffer } },
258-
],
259-
});
246+
this.#collisionBindGroup = this.#createBindGroup(this.#collisionPipeline, [
247+
this.#collisionBuffer,
248+
this.#collisionParamsBuffer,
249+
this.#shapeDataBuffer,
250+
]);
260251
}
261252

262253
// === Uniform Updates ===
@@ -344,11 +335,8 @@ export class FolkSandWebGPU extends FolkBaseSet {
344335

345336
// Recreate buffers (old ones stay in #resources for cleanup)
346337
const stateSize = this.#stateBufferSize();
347-
this.#stateBuffers = [
348-
this.#createBuffer(stateSize, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC),
349-
this.#createBuffer(stateSize, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC),
350-
];
351-
this.#collisionBuffer = this.#createBuffer(stateSize, GPUBufferUsage.STORAGE);
338+
this.#stateBuffers = [this.#createBuffer(stateSize, 'storage'), this.#createBuffer(stateSize, 'storage')];
339+
this.#collisionBuffer = this.#createBuffer(stateSize, 'storage');
352340
this.#currentStateIndex = 0;
353341

354342
this.#createBindGroups();
@@ -384,10 +372,7 @@ export class FolkSandWebGPU extends FolkBaseSet {
384372
// Resize buffer if needed, and update collision bind group
385373
const requiredSize = shapeData.length * 4;
386374
if (!this.#shapeDataBuffer || this.#shapeDataBuffer.size < requiredSize) {
387-
this.#shapeDataBuffer = this.#createBuffer(
388-
Math.max(requiredSize, 64),
389-
GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
390-
);
375+
this.#shapeDataBuffer = this.#createBuffer(Math.max(requiredSize, 64), 'storage-rw');
391376
this.#updateCollisionBindGroup();
392377
}
393378

@@ -473,17 +458,6 @@ struct Mouse {
473458
prevY: f32,
474459
}`;
475460

476-
const particleTypes = /*wgsl*/ `
477-
const AIR: u32 = 0u;
478-
const SMOKE: u32 = 1u;
479-
const WATER: u32 = 2u;
480-
const LAVA: u32 = 3u;
481-
const SAND: u32 = 4u;
482-
const STONE: u32 = 5u;
483-
const WALL: u32 = 6u;
484-
const COLLISION: u32 = 99u;
485-
`;
486-
487461
const hashFunctions = /*wgsl*/ `
488462
fn hash12(p: vec2f) -> f32 {
489463
var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);
@@ -504,26 +478,45 @@ fn hash44(p: vec4f) -> vec4f {
504478
}
505479
`;
506480

507-
const particleStruct = /*wgsl*/ `
508-
struct Particle {
509-
ptype: u32, // particle type (only needs ~4 bits, but u32 is WGSL's smallest)
510-
rand: u32, // visual variation (only needs 8 bits)
511-
}
512-
481+
const wgslUtils = /*wgsl*/ `
513482
fn getIndex(x: u32, y: u32, width: u32) -> u32 {
514483
return y * width + x;
515484
}
516485
486+
fn getIndexI(p: vec2i, width: u32) -> u32 {
487+
return u32(p.y) * width + u32(p.x);
488+
}
489+
490+
fn inBounds(p: vec2i, width: u32, height: u32) -> bool {
491+
return p.x >= 0 && p.y >= 0 && p.x < i32(width) && p.y < i32(height);
492+
}
493+
`;
494+
495+
const particleDefs = /*wgsl*/ `
496+
const AIR: u32 = 0u;
497+
const SMOKE: u32 = 1u;
498+
const WATER: u32 = 2u;
499+
const LAVA: u32 = 3u;
500+
const SAND: u32 = 4u;
501+
const STONE: u32 = 5u;
502+
const WALL: u32 = 6u;
503+
const COLLISION: u32 = 99u;
504+
505+
struct Particle {
506+
ptype: u32,
507+
rand: u32,
508+
}
509+
517510
fn particle(ptype: u32, rand: u32) -> Particle {
518511
return Particle(ptype, rand);
519512
}
520513
`;
521514

522515
const initShader = /*wgsl*/ `
523516
${paramsStruct}
524-
${particleTypes}
517+
${particleDefs}
525518
${hashFunctions}
526-
${particleStruct}
519+
${wgslUtils}
527520
528521
@group(0) @binding(0) var<storage, read_write> output: array<Particle>;
529522
@group(0) @binding(1) var<uniform> params: Params;
@@ -576,9 +569,9 @@ fn main(@builtin(global_invocation_id) gid: vec3u) {
576569
const simulationShader = /*wgsl*/ `
577570
${paramsStruct}
578571
${mouseStruct}
579-
${particleTypes}
572+
${particleDefs}
580573
${hashFunctions}
581-
${particleStruct}
574+
${wgslUtils}
582575
583576
@group(0) @binding(0) var<storage, read> input: array<Particle>;
584577
@group(0) @binding(1) var<storage, read_write> output: array<Particle>;
@@ -601,13 +594,9 @@ fn getOffset(frame: u32) -> vec2i {
601594
}
602595
603596
fn getData(p: vec2i) -> Particle {
604-
if (p.x < 0 || p.y < 0 || p.x >= i32(params.width) || p.y >= i32(params.height)) {
605-
return particle(WALL, 0u);
606-
}
607-
let idx = getIndex(u32(p.x), u32(p.y), params.width);
608-
if (collision[idx] > 0u) {
609-
return particle(COLLISION, 0u);
610-
}
597+
if (!inBounds(p, params.width, params.height)) { return particle(WALL, 0u); }
598+
let idx = getIndexI(p, params.width);
599+
if (collision[idx] > 0u) { return particle(COLLISION, 0u); }
611600
return input[idx];
612601
}
613602
@@ -617,12 +606,14 @@ fn newParticle(ptype: u32, coord: vec2i, frame: u32) -> Particle {
617606
}
618607
619608
fn isCollision(p: vec2i) -> bool {
620-
if (p.x < 0 || p.y < 0 || p.x >= i32(params.width) || p.y >= i32(params.height)) { return false; }
621-
return collision[getIndex(u32(p.x), u32(p.y), params.width)] > 0u;
609+
if (!inBounds(p, params.width, params.height)) { return false; }
610+
return collision[getIndexI(p, params.width)] > 0u;
622611
}
623612
624-
fn inBounds(p: vec2i) -> bool {
625-
return p.x >= 0 && p.y >= 0 && p.x < i32(params.width) && p.y < i32(params.height);
613+
fn writeIfInBounds(p: vec2i, val: Particle) {
614+
if (inBounds(p, params.width, params.height)) {
615+
output[getIndexI(p, params.width)] = val;
616+
}
626617
}
627618
628619
// Block-based: each thread processes one 2x2 Margolus block
@@ -741,17 +732,17 @@ fn main(@builtin(global_invocation_id) gid: vec3u) {
741732
if (t11.ptype == COLLISION && !isCollision(p + vec2i(1, 1))) { t11 = newParticle(AIR, p + vec2i(1, 1), params.frame); }
742733
743734
// Write only in-bounds cells
744-
if (inBounds(p)) { output[getIndex(u32(p.x), u32(p.y), params.width)] = t00; }
745-
if (inBounds(p + vec2i(1, 0))) { output[getIndex(u32(p.x + 1), u32(p.y), params.width)] = t10; }
746-
if (inBounds(p + vec2i(0, 1))) { output[getIndex(u32(p.x), u32(p.y + 1), params.width)] = t01; }
747-
if (inBounds(p + vec2i(1, 1))) { output[getIndex(u32(p.x + 1), u32(p.y + 1), params.width)] = t11; }
735+
writeIfInBounds(p, t00);
736+
writeIfInBounds(p + vec2i(1, 0), t10);
737+
writeIfInBounds(p + vec2i(0, 1), t01);
738+
writeIfInBounds(p + vec2i(1, 1), t11);
748739
}
749740
`;
750741

751742
const renderShader = /*wgsl*/ `
752743
${paramsStruct}
753-
${particleTypes}
754-
${particleStruct}
744+
${particleDefs}
745+
${wgslUtils}
755746
756747
struct VertexOutput {
757748
@builtin(position) position: vec4f,

0 commit comments

Comments
 (0)