WebGPU Demos

This directory contains interactive demo applications that showcase the capabilities of the WebGPU rendering system. Each demo serves as both a working example and a testing ground for different rendering techniques, performance optimizations, and system features.

Overview

The demos provide:

Demo Applications

WebGPUSketch.tsx

Interactive 3D scene editor and mesh rendering showcase

The primary demo application featuring a complete 3D scene editor with real-time manipulation capabilities.

Features:

Key Concepts Demonstrated:

// Example: Adding objects to the scene
Scene.addNode(sceneState, parentId, "My Cube", {
  renderable: { type: "cube" },
  behavior: { 
    type: "spinning", 
    rotationSpeed: [0, Math.PI, 0] 
  }
}, resourceManager);

SimpleTriangleDemo.tsx

Basic WebGPU rendering fundamentals

A minimal example demonstrating the core WebGPU rendering pipeline with a simple colored triangle.

Features:

Key Concepts Demonstrated:

Perfect for:

ParticleDemo.tsx

GPU-based particle system

A particle simulation demonstrating compute shaders and large-scale object rendering.

Features:

Key Concepts Demonstrated:

Technical Details:

ParticleDiffusionDemo.tsx

Advanced particle diffusion simulation

A more complex particle system demonstrating diffusion algorithms and advanced compute techniques.

Features:

Key Concepts Demonstrated:

Use Cases:

ColorSpacePointCloudDemo.tsx

3D point cloud rendering and color space exploration

A visualization tool for working with 3D point clouds and color space transformations.

Features:

Key Concepts Demonstrated:

Applications:

Running the Demos

Prerequisites

// Ensure WebGPU support
if (!navigator.gpu) {
  throw new Error("WebGPU not supported");
}

// Initialize WebGPU device
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();

Basic Demo Setup

import { WebGPUSketchDemo } from "./WebGPUSketch.tsx";

// Create demo instance
const demo = new WebGPUSketchDemo();

// Initialize with canvas
await demo.init(canvas, hostUpdateCallback);

// Run render loop
function renderLoop(timestamp: number) {
  demo.render(device, context, commandEncoder, textureView, timestamp);
  requestAnimationFrame(renderLoop);
}

Integration with React/UI

// React component integration
export function DemoWrapper() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const [demo, setDemo] = useState<WebGPUDemo | null>(null);

  useEffect(() => {
    if (canvasRef.current) {
      const demoInstance = new WebGPUSketchDemo();
      demoInstance.init(canvasRef.current, () => {
        // Handle updates
      });
      setDemo(demoInstance);
    }
  }, []);

  return (
    <div>
      <canvas ref={canvasRef} />
      {demo && demo.getControls()}
    </div>
  );
}

Common Patterns

Demo Structure

All demos follow a consistent structure:

export class MyDemo {
  // Core properties
  public name = "My Demo";
  public description = "Demo description";
  public supportedInputs = ["mouse", "keyboard"];

  // WebGPU resources
  private device: GPUDevice;
  private resourceManager: ResourceManager;
  private isInitialized = false;

  // Initialization
  async init(canvas: HTMLCanvasElement, updateCallback?: () => void) {
    // Setup WebGPU device and resources
    // Initialize demo-specific components
  }

  // Render loop
  render(device: GPUDevice, context: GPUCanvasContext, 
         commandEncoder: GPUCommandEncoder, textureView: GPUTextureView,
         timestamp: number) {
    // Update simulation/animation
    // Record render commands
    // Execute rendering
  }

  // Input handling
  onMouseDown(event: MouseEvent) { /* ... */ }
  onKeyDown(event: KeyboardEvent) { /* ... */ }

  // UI controls
  getControls() {
    return <DemoControls />; // React component
  }

  // Cleanup
  destroy() {
    // Clean up resources
  }
}

Performance Monitoring

// Standard performance tracking pattern
class PerformanceMonitor {
  private frameTime = 0;
  private smoothedFrameTime = 0;
  private readonly frameTimeAlpha = 0.1;

  updateFrameTime(deltaTime: number) {
    this.frameTime = deltaTime;
    this.smoothedFrameTime = this.frameTimeAlpha * deltaTime + 
                           (1.0 - this.frameTimeAlpha) * this.smoothedFrameTime;
  }

  getStats() {
    return {
      fps: 1000 / this.smoothedFrameTime,
      frameTime: this.smoothedFrameTime,
      instantFps: 1000 / this.frameTime
    };
  }
}

Resource Management

// Standard resource cleanup pattern
destroy() {
  // Clean up demo-specific resources
  this.demoSpecificBuffer?.destroy();
  
  // Clean up shared resources
  this.resourceManager?.destroy();
  
  // Clean up UI components
  this.msdfTextRenderer?.destroy();
  
  this.isInitialized = false;
}

Creating New Demos

Basic Demo Template

import type { GPUDevice, GPUCanvasContext } from "@webgpu/types";
import { ResourceManager } from "../ResourceManager.ts";

export class NewDemo {
  public name = "New Demo";
  public description = "Description of what this demo does";
  public supportedInputs = ["mouse", "keyboard", "touch"];

  private device!: GPUDevice;
  private resourceManager!: ResourceManager;
  private isInitialized = false;

  async init(
    canvas: HTMLCanvasElement,
    hostUpdateCallback?: () => void
  ): Promise<void> {
    // Initialize WebGPU
    const adapter = await navigator.gpu?.requestAdapter();
    if (!adapter) throw new Error("WebGPU not supported");
    
    this.device = await adapter.requestDevice();
    
    // Create resource manager
    this.resourceManager = new ResourceManager(
      this.device,
      "bgra8unorm", // preferred format
      "depth24plus" // depth format
    );

    // Demo-specific initialization
    this.setupDemoResources();
    
    this.isInitialized = true;
  }

  private setupDemoResources() {
    // Create demo-specific buffers, textures, pipelines
  }

  render(
    device: GPUDevice,
    context: GPUCanvasContext,
    commandEncoder: GPUCommandEncoder,
    textureView: GPUTextureView,
    timestamp: number
  ): void {
    if (!this.isInitialized) return;

    // Update logic
    this.update(timestamp);

    // Render logic
    const renderPass = commandEncoder.beginRenderPass({
      colorAttachments: [{
        view: textureView,
        clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
        loadOp: "clear",
        storeOp: "store"
      }]
    });

    // Record render commands
    this.recordRenderCommands(renderPass);

    renderPass.end();
  }

  private update(timestamp: number) {
    // Animation and simulation updates
  }

  private recordRenderCommands(renderPass: GPURenderPassEncoder) {
    // WebGPU render commands
  }

  // Input handlers
  onMouseDown(event: MouseEvent) { /* ... */ }
  onMouseMove(event: MouseEvent) { /* ... */ }
  onKeyDown(event: KeyboardEvent) { /* ... */ }

  // UI controls (React component)
  getControls() {
    return null; // Return React component if needed
  }

  destroy() {
    this.resourceManager?.destroy();
    this.isInitialized = false;
  }
}

Integration Checklist

When creating a new demo:

Performance Guidelines

Optimization Strategies

  1. Use Render Bundles: Pre-record GPU commands for repeated operations
  2. Batch Similar Operations: Group objects with same material/shader
  3. Monitor Memory Usage: Track buffer allocation and ring buffer usage
  4. Profile Regularly: Use browser dev tools and custom performance counters
  5. Optimize Shaders: Keep vertex/fragment shaders as simple as possible

Common Performance Patterns

// Efficient render loop pattern
render(/* ... */) {
  // Reset ring buffers each frame
  this.resourceManager.resetAllRingBuffers();
  
  // Update dynamic data
  this.updateDynamicData(timestamp);
  
  // Batch render operations
  this.executeBatchedRenders(renderPass);
  
  // Update performance metrics
  this.updatePerformanceStats(timestamp);
}

Memory Management

// Efficient buffer usage
private updateDynamicData(timestamp: number) {
  // Allocate from ring buffer
  const uniformOffset = this.resourceManager.allocRenderUniformBlock(
    new Float32Array(transformMatrix)
  );
  
  // Use allocated offset
  renderPass.setBindGroup(0, bindGroup, [uniformOffset]);
}

Debugging and Troubleshooting

Common Issues

Render Bundle Not Updating

Performance Issues

WebGPU Errors

Debugging Tools

// Performance profiling
const debugInfo = {
  fps: performanceMonitor.getFPS(),
  triangles: calculateTriangleCount(),
  drawCalls: renderBatches.length,
  bufferUsage: ringBuffer.head / ringBuffer.size
};

// Resource tracking
console.log("Active buffers:", resourceManager.getActiveBuffers());
console.log("Pipeline cache:", resourceManager.getPipelineStats());

Educational Value

Each demo teaches specific concepts:

Contributing

When adding new demos:

  1. Follow the template structure for consistency
  2. Include comprehensive documentation in comments
  3. Add interactive controls where appropriate
  4. Implement proper error handling and resource cleanup
  5. Provide educational value - teach specific concepts
  6. Test performance under different conditions
  7. Update this README with demo description

New demos should demonstrate novel techniques, serve as educational examples, or provide useful development tools for the WebGPU system.