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.
The demos provide:
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);
Basic WebGPU rendering fundamentals
A minimal example demonstrating the core WebGPU rendering pipeline with a simple colored triangle.
Features:
Key Concepts Demonstrated:
Perfect for:
GPU-based particle system
A particle simulation demonstrating compute shaders and large-scale object rendering.
Features:
Key Concepts Demonstrated:
Technical Details:
Advanced particle diffusion simulation
A more complex particle system demonstrating diffusion algorithms and advanced compute techniques.
Features:
Key Concepts Demonstrated:
Use Cases:
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:
// 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();
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);
}
// 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>
);
}
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
}
}
// 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
};
}
}
// 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;
}
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;
}
}
When creating a new demo:
// 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);
}
// 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]);
}
Render Bundle Not Updating
needsRebundle flagPerformance Issues
WebGPU Errors
// 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());
Each demo teaches specific concepts:
When adding new demos:
New demos should demonstrate novel techniques, serve as educational examples, or provide useful development tools for the WebGPU system.