Skip to content

Compute Shader Design

Deep dive into the GPU physics simulation using WebGPU Compute Shaders.

Overview

The compute shader handles all physics calculations in parallel on the GPU. Each particle is processed by a single GPU thread, allowing thousands of particles to update simultaneously.

Shader Structure

wgsl
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
    // Thread safety: bounds check
    if (id.x >= arrayLength(&particles)) { return; }

    // Physics pipeline
    let particle = particles[id.x];
    var velocity = particle.velocity;
    var position = particle.position;

    // 1. Apply gravity
    // 2. Apply mouse repulsion
    // 3. Clamp velocity
    // 4. Update position
    // 5. Boundary bounce

    particles[id.x] = particle;
}

Physics Pipeline

Step-by-Step Breakdown

1. Gravity Application

wgsl
velocity.x += GRAVITY.x * deltaTime;
velocity.y += GRAVITY.y * deltaTime;

Default: {x: 0, y: 600} px/s² (downward acceleration)

2. Mouse Repulsion

wgsl
let dx = position.x - mouseX;
let dy = position.y - mouseY;
let dist = sqrt(dx * dx + dy * dy);

if (dist < REPULSION_RADIUS && dist > 0.0) {
    let strength = REPULSION_STRENGTH / dist;
    velocity.x += (dx / dist) * strength * deltaTime;
    velocity.y += (dy / dist) * strength * deltaTime;
}

Inverse distance falloff creates natural push-away effect.

3. Velocity Clamping

wgsl
let speed = sqrt(velocity.x * velocity.x + velocity.y * velocity.y);
if (speed > MAX_SPEED) {
    velocity = velocity * (MAX_SPEED / speed);
}

Prevents particles from moving too fast, maintaining visual coherence.

4. Position Update

wgsl
position.x += velocity.x * deltaTime;
position.y += velocity.y * deltaTime;

Delta-time based movement ensures consistent physics regardless of frame rate.

5. Boundary Bounce

wgsl
if (position.x < 0.0) {
    position.x = 0.0;
    velocity.x = -velocity.x * DAMPING;
}
// Similar for all four boundaries

Elastic collision with DAMPING = 0.9 (90% energy retention).

Constants Configuration

ConstantValueUnitPurpose
GRAVITY{x: 0, y: 600}px/s²Downward acceleration
REPULSION_RADIUS200pxMouse influence area
REPULSION_STRENGTH3000px/sPush force magnitude
MAX_SPEED800px/sVelocity ceiling
DAMPING0.9ratioBounce energy retention

Workgroup Sizing

Why 64?

  • Optimal for most GPU architectures
  • Good balance of parallelism and register usage
  • Matches common GPU warp/wavefront sizes

CPU Reference Implementation

The TypeScript implementation in src/core/physics.ts mirrors the WGSL logic exactly:

typescript
function updateParticle(
  particle: Particle,
  canvasSize: Vec2,
  mousePos: Vec2,
  deltaTime: number,
  gravity: Vec2
): Particle {
  // 1. Apply gravity
  particle.vx += gravity.x * deltaTime;
  particle.vy += gravity.y * deltaTime;

  // 2. Mouse repulsion
  // 3. Clamp velocity
  // 4. Update position
  // 5. Boundary bounce

  return particle;
}

This enables property-based testing to validate GPU behavior.

Performance Characteristics

Metric10K Particles2.5K Particles
Compute dispatches157 workgroups40 workgroups
Typical GPU time2-4 ms0.5-1 ms
Memory bandwidth~320 KB/frame~80 KB/frame

Common Pitfalls

IssueSymptomFix
Race conditionsParticles flickerUse single buffer, no atomics needed
Delta time overflowPhysics explosionClamp deltaTime to MAX_DELTA_TIME
Division by zeroNaN propagationCheck dist > 0 before division
Buffer misalignmentGPU crashEnsure 16-byte particle alignment

Source Files

FilePurpose
src/shaders/compute.wgslGPU shader code
src/core/physics.tsCPU reference implementation
src/config/sim.tsConstants definition
src/core/pipelines.tsPipeline creation

Next Steps

Built with VitePress