Skip to content

Latest commit

 

History

History
135 lines (103 loc) · 3.6 KB

File metadata and controls

135 lines (103 loc) · 3.6 KB

A typed particle pipeline

This tutorial follows programs/b04-particles.ts. The program defines host data, GPU bindings, a kernel, and one dispatch.

Define the schemas

Particle uses two Vec3f fields. The layout generator gives each value the required C and WGSL layout.

@CStruct
class Particle {
  pos: Vec3f;
  vel: Vec3f;

  constructor(pos: Vec3f, vel: Vec3f) {
    this.pos = pos;
    this.vel = vel;
  }
}

SimParams stores the time step and the active particle count.

@CStruct
class SimParams {
  dt: f32;
  count: u32;

  constructor(dt: f32, count: u32) {
    this.dt = dt;
    this.count = count;
  }
}

Define the resources

The layout class gives the uniform and storage bindings their script types.

class ParticleLayout {
  params!: Uniform<SimParams>;
  particles!: MutStorage<Particle>;
}

Write host-compatible logic

The helper has a real script body. The generator lowers the same typed operations to WGSL.

function integrate(particle: Particle, dt: f32): Particle {
  const speed: f32 = particle.vel.length();
  if (speed > 0.0) {
    const pos: Vec3f = particle.pos.add(particle.vel.scale(dt));
    return new Particle(pos, particle.vel);
  }
  return particle;
}

The kernel reads the uniform value and writes one storage element.

function particleKernel(res: ParticleLayout, ctx: ComputeInvocation): void {
  const settings: SimParams = res.params.$;
  const i: u32 = ctx.globalId.x;
  if (i < settings.count) {
    res.particles[i] = integrate(res.particles[i], settings.dt);
  }
}

simulateCompute calls this kernel over host wrapper storage. Its generated host-runnable constant prevents sequential simulation of kernels that require GPU synchronization.

Declare the pipeline

The declaration connects the layout type, the kernel function, and the workgroup size.

export const particles: ComputePipelineSpec = computePipeline<ParticleLayout>(particleKernel, {
  name: "particles",
  workgroupSize: [64, 1, 1],
});

Create the runtime resources

The program uses generated byte sizes for both buffers.

    using params = device.createBuffer({
      label: "b04-params",
      size: SimParams_SIZE as u64,
      usage: GPUBufferUsage.UNIFORM + GPUBufferUsage.COPY_DST,
    });

The generated WGSL text, entry name, layout, and workgroup constants create the runtime pipeline.

    using pipeline = createComputePipeline(
      device,
      particles_WGSL,
      particles_ENTRY,
      [particles_LAYOUT0],
      [particles_WORKGROUP_X, particles_WORKGROUP_Y, particles_WORKGROUP_Z],
    );

The typed factory joins each named resource to its layout binding. It returns the bindGroup value for the dispatch.

    const resources: ParticleLayoutResources = createParticleLayoutResources(
      params,
      particlesBuffer,
    );
    using bindGroup = createParticlesBindGroup0(device, pipeline, resources);

Dispatch the work

The typed pipeline converts the thread count into workgroup counts for all three axes.

    pipeline.dispatchThreads(encoder, [bindGroup], count, 1, 1);

The command buffer enters the device queue after the encoder finishes.

    using command = encoder.finishDefault();
    device.queue.submit([command]);