RFC 0001: Core Architecture β WebGPU Particle Fluid Simulation
Status: Stable β Maintenance Mode Last Updated: 2026-04-16 Version: 2.0.0 Related Specs: Product Requirements
Overview
This document describes the architecture and design decisions for a high-performance particle fluid simulation using WebGPU. The system leverages GPU parallel computing via Compute Shaders to achieve real-time physics simulation for thousands of particles.
Architecture
Heterogeneous Computing Model
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CPU (TypeScript) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β’ WebGPU initialization and canvas configuration β
β β’ Runtime quality selection based on device capabilities β
β β’ Uniform buffer updates (canvas size, mouse position, dt) β
β β’ Input handling (mouse/touch events β HiDPI coordinates) β
β β’ Frame loop orchestration via requestAnimationFrame β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
writeBuffer / commandEncoder
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GPU (WGSL) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Compute Pass β β
β β β’ Parallel particle physics (workgroup_size: 64) β β
β β β’ Gravity, repulsion, velocity clamp, boundary bounce β β
β β β’ Reads/writes particle storage buffer β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Trail Pass β β
β β β’ Fade persistent offscreen texture (alpha: 0.05) β β
β β β’ Fullscreen quad (triangle strip) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Render Pass β β
β β β’ Draw particles as colored points β β
β β β’ Velocity-to-color mapping (cyan β purple) β β
β β β’ Output to offscreen texture β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Present Pass β β
β β β’ Composite offscreen texture to swapchain canvas β β
β β β’ Bilinear sampling for smooth appearance β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Key Design Decisions
| Decision | Rationale |
|---|---|
| Offscreen trail texture | More portable than relying on swapchain persistence |
| Shared constants via preamble | Single source of truth for TypeScript and WGSL |
| CPU reference implementations | Enables property testing of GPU-equivalent logic |
| Adaptive particle count | Graceful degradation on low-end devices |
Data Layout
Particle Buffer (Storage Buffer)
Each particle: 16 bytes (4 Γ float32)
βββββββββββ¬ββββββββββ¬ββββββββββ¬ββββββββββ
β x (f32)β y (f32)β vx (f32)β vy (f32)β
βββββββββββΌββββββββββΌββββββββββΌββββββββββ€
β Offset: β +4 β +8 β +12 β
β 0 β β β β
βββββββββββ΄ββββββββββ΄ββββββββββ΄ββββββββββ
Total size: particleCount Γ 16 bytes
Uniform Buffer
Total: 32 bytes (8 Γ float32, 16-byte aligned)
ββββββββββββ¬ββββββββββββ¬ββββββββββββ¬ββββββββββββ
β width β height β mouseX β mouseY β
β (f32) β (f32) β (f32) β (f32) β
ββββββββββββΌββββββββββββΌββββββββββββΌββββββββββββ€
β deltaTimeβ _pad1 β _pad2 β _pad3 β
β (f32) β (f32) β (f32) β (f32) β
ββββββββββββ΄ββββββββββββ΄ββββββββββββ΄ββββββββββββ
WGSL Shader Design
Compute Shader (compute.wgsl)
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
// 1. Bounds check
// 2. Apply gravity: velocity += GRAVITY * dt
// 3. Apply mouse repulsion (inverse distance)
// 4. Clamp velocity to MAX_SPEED
// 5. Update position: position += velocity * dt
// 6. Boundary bounce with damping
}
Render Shader (render.wgsl)
@vertex fn vertexMain(vertexIndex: u32) -> VertexOutput {
// Convert pixel position to NDC (-1 to 1)
// Pass speed to fragment shader
}
@fragment fn fragmentMain(speed: f32) -> vec4f {
// Color interpolation: mix(CYAN, PURPLE, speed/MAX_SPEED)
// Brightness: 0.5 + t * 0.5
}
Trail Shader (trail.wgsl)
@fragment fn fragmentMain() -> vec4f {
return vec4f(0.0, 0.0, 0.0, TRAIL_FADE_ALPHA);
}
Present Shader (present.wgsl)
@fragment fn fragmentMain(uv: vec2f) -> vec4f {
return textureSample(sceneTexture, sceneSampler, uv);
}
Adaptive Quality System
The quality system adjusts particle count at startup based on:
interface SimulationHeuristicsInput {
hardwareConcurrency?: number; // CPU cores
deviceMemory?: number; // Device RAM (GB)
isFallbackAdapter: boolean; // Software rendering?
maxStorageBufferBindingSize: number;
viewportPixels: number; // Width Γ Height Γ DPRΒ²
}
Scaling Rules
| Condition | Max Scale |
|---|---|
| Fallback adapter | 40% |
| RAM β€ 2 GB | 45% |
| RAM β€ 4 GB | 65% |
| Cores β€ 2 | 45% |
| Cores β€ 4 | 70% |
| 4K viewport | 65% |
| QHD viewport | 85% |
Typical output: 2,500 β 10,000 particles
Error Handling
Initialization Errors
| Error | Handling |
|---|---|
| WebGPU not supported | Show message, suggest Chrome/Edge 113+ |
| Adapter unavailable | Show GPU compatibility message |
| Device unavailable | Show fallback message |
| Context creation failed | Log error, halt |
Runtime Errors
| Error | Handling |
|---|---|
| Device lost | Log message, suggest page refresh |
| Out of memory | Not handled (quality system prevents) |
Testing Strategy
Property-Based Tests
Using fast-check with minimum 100 runs per property:
| Property | Module | Validates |
|---|---|---|
| Particle Bounds | buffers.test.ts |
All particles initialized within canvas |
| Physics Integration | physics.test.ts |
Position/velocity updates match formula |
| Boundary Bounce | physics.test.ts |
Correct reflection with damping |
| Repulsion Falloff | physics.test.ts |
Inverse distance relationship |
| Velocity Clamping | physics.test.ts |
Speed β€ MAX_SPEED |
| Color Mapping | color.test.ts |
RGB in [0, 1] range |
Test Coverage
src/core/
βββ buffers.ts β buffers.test.ts
βββ color.ts β color.test.ts
βββ physics.ts β physics.test.ts
βββ quality.ts β quality.test.ts
βββ types.ts β types.test.ts
Performance Considerations
GPU Optimization
- Workgroup size 64: Optimal for most GPUs
- Single buffer for particles: Minimizes memory bandwidth
- Offscreen texture reuse: Avoids reallocation per frame
CPU Optimization
- requestAnimationFrame timing: Natural frame pacing
- Minimal per-frame allocation: Reuse Float32Array for uniforms
- Event delegation: Single resize handler
Quality Scaling
The adaptive system prevents:
- Frame drops on low-end devices
- Memory allocation failures
- GPU timeout/crash on constrained hardware
Future Considerations
Potential enhancements for future versions:
High Effort / High Impact
| Enhancement | Description | Effort |
|---|---|---|
| SPH (Smoothed Particle Hydrodynamics) | Realistic fluid dynamics simulation | High |
| WebXR Support | Immersive VR/AR viewing | High |
| Multi-threaded Initialization | Web Workers for particle setup | Medium |
Medium Effort / Medium Impact
| Enhancement | Description | Effort |
|---|---|---|
| Custom Particle Shaders | User-provided behavior shaders | Medium |
| State Persistence | Save/load simulation state | Medium |
| Interactive Controls | UI for adjusting parameters | Medium |
Low Effort / Nice to Have
| Enhancement | Description | Effort |
|---|---|---|
| Screenshot/Recording | Export canvas as image/video | Low |
| Color Themes | Alternative color schemes | Low |
| Particle Shapes | Non-point primitives | Low |
Architecture Notes for Future Development
- Shader Modifications: Any shader changes must be reflected in corresponding TypeScript CPU implementations for testing
- Buffer Layout Changes: Require updates to both WGSL structs and TypeScript interfaces
- New Pipelines: Follow the existing 4-pass pattern for consistency
- Performance Testing: Benchmark on multiple devices before merging performance-sensitive changes