WebGPU Rendering System

This directory contains a complete WebGPU-based rendering system built around modern architectural patterns including Entity-Component-System (ECS) scene management, modular mesh rendering, and efficient resource management.

Overview

The WebGPU system provides:

Architecture

Core Components

WebGPU System Architecture

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   Scene.ts      │    │ ResourceManager  │    │ BufferManager   │
│                 │    │                  │    │                 │
│ • ECS Scene     │◄──►│ • GPU Resources  │◄──►│ • Ring Buffers  │
│ • Transforms    │    │ • Pipeline Cache │    │ • Memory Alloc  │
│ • Behaviors     │    │ • Uniform Mgmt   │    │ • Alignment     │
└─────────────────┘    └──────────────────┘    └─────────────────┘
         │                       │                       │
         ▼                       ▼                       ▼
┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   Mesh System   │    │   Render Utils   │    │   Demos         │
│                 │    │                  │    │                 │
│ • CubeMesh      │    │ • Render Bundles │    │ • WebGPUSketch  │
│ • TriangleMesh  │    │ • Pipeline Mgmt  │    │ • Particle Sim  │
│ • MeshRegistry  │    │ • Batch Rendering│    │ • Examples      │
└─────────────────┘    └──────────────────┘    └─────────────────┘

Directory Structure

Core Files

Directories

Utility Files

Key Concepts

1. ECS Scene Management

The scene system uses an Entity-Component-System pattern:

// Components are stored in separate maps
interface SceneState {
  nodes: Map<NodeID, HierarchyComponent>;      // Scene graph structure
  transforms: Map<NodeID, TransformComponent>; // Position, rotation, scale
  renderables: Map<NodeID, RenderableComponent>; // Mesh type, render bundles
  behaviors: Map<NodeID, BehaviorComponent[]>; // Animation, instancing
}

// Systems operate on components
Scene.updateTransforms(sceneState);  // Transform system
Scene.applyBehaviors(sceneState, dt); // Behavior system

2. Resource Management

The ResourceManager centralizes GPU resource lifecycle:

// Automatic pipeline caching
const pipeline = resourceManager.getOrCreateSharedPipeline(key, factory);

// Dynamic buffer allocation
const offset = resourceManager.allocRenderUniformBlock(transformData);

// Named buffer management
const buffer = resourceManager.getOrCreateBuffer(name, descriptor);

3. Buffer Management

Ring buffers provide efficient dynamic allocation:

// Reset each frame
resourceManager.resetAllRingBuffers();

// Allocate with alignment
const { offset } = ringBuffer.allocData(data, alignment);

// Automatic wraparound when buffer fills

4. Mesh System

Modular mesh architecture with shared resources:

// Register mesh types
getRenderableRegistry().register("mesh", "cube", new CubeMesh());

// Create render bundles
const bundle = createMeshRenderBundle(
  "cube", device, resourceManager, renderable, colorFormat, depthFormat
);

Performance Features

Render Bundle Caching

Render bundles are cached until geometry or shaders change:

if (renderable.needsRebundle || !renderable.bundle) {
  renderable.bundle = createMeshRenderBundle(/* ... */);
  renderable.needsRebundle = false;
}

Uniform Buffer Optimization

Automatic uniform offset reorganization eliminates memory fragmentation:

// Called after scene modifications
Scene.reorganizeUniformOffsets(sceneState, resourceManager);

GPU Instancing

Getting Started

Basic Scene Setup

import * as Scene from "./scene/scene.ts";
import { ResourceManager } from "./ResourceManager.ts";

// Create scene state
const sceneState = Scene.createSceneState();

// Add objects
Scene.addNode(sceneState, parentId, "My Cube", {
  renderable: { type: "cube" },
  behavior: { type: "spinning", rotationSpeed: [1, 0, 0] }
}, resourceManager);

// Update loop
function render(deltaTime: number) {
  Scene.applyBehaviors(sceneState, deltaTime);
  Scene.updateTransforms(sceneState);
  // ... render logic
}

Creating Custom Meshes

import { getRenderableRegistry, MeshInterface } from "./renderable/index.ts";

class CustomMesh implements MeshInterface {
  readonly meshType = "custom";

  ensureResources(device: GPUDevice, resourceManager: ResourceManager) {
    // Create vertex buffers, shaders
  }

  createRenderBundle(/* ... */): GPURenderBundle {
    // Create optimized render bundle
  }
}

// Register the mesh
getRenderableRegistry().register("mesh", "custom", new CustomMesh());

Debugging and Profiling

Render Statistics

const stats = calculateRenderStats(renderables, getVertexCount, getTriangleCount);
console.log(`Rendering ${stats.totalTriangles} triangles in ${stats.totalBundles} bundles`);

Buffer Usage Tracking

// Monitor ring buffer usage
const usage = ringBuffer.head / ringBuffer.size;
if (usage > 0.8) console.warn("Ring buffer nearly full");

Pipeline Cache Statistics

const cacheStats = renderBundleCache.getStats();
console.log(`Cache hit rate: ${(cacheStats.hitRate * 100).toFixed(1)}%`);

Best Practices

Memory Management

  1. Reset ring buffers each frame: resourceManager.resetAllRingBuffers()
  2. Batch similar objects: Use instancing for multiple similar objects
  3. Cache render bundles: Only recreate when geometry changes
  4. Cleanup resources: Call destroy() methods when done

Performance Optimization

  1. Use render bundles: Pre-record GPU commands for efficiency
  2. Minimize pipeline changes: Group objects by material/shader
  3. Leverage instancing: Render many objects with one draw call
  4. Profile regularly: Monitor vertex count, draw calls, and memory usage

Architecture Guidelines

  1. Separate concerns: Keep mesh logic separate from scene logic
  2. Use the registry: Register all mesh types with MeshRegistry
  3. Handle errors gracefully: Check for null returns from resource creation
  4. Document custom components: Follow the existing README patterns

Common Patterns

Dynamic Object Creation

// Runtime object creation
const nodeId = Scene.addNode(sceneState, parentId, name, {
  renderable: { type: meshType },
  transform: { position: [x, y, z] }
}, resourceManager);

// Reorganize buffers after modifications
Scene.reorganizeUniformOffsets(sceneState, resourceManager);

Animation System

// Add spinning behavior
Scene.addBehavior(sceneState, nodeId, {
  type: "spinning",
  rotationSpeed: [0, Math.PI, 0], // Y-axis rotation
  rotationEnabled: [false, true, false]
});

// Apply in render loop
Scene.applyBehaviors(sceneState, deltaTime);

Custom Rendering Pipeline

// Custom render pass
const bundles: GPURenderBundle[] = [];
for (const [id, renderable] of sceneState.renderables.entries()) {
  if (renderable.bundle) {
    mainPass.setBindGroup(0, uniformBindGroup, [renderable.uniformOffset]);
    mainPass.executeBundles([renderable.bundle]);
  }
}

Migration Guide

From Legacy Renderers

// OLD: Direct renderer calls
const bundle = getCubeRenderBundle(device, resourceManager, /* ... */);

// NEW: Mesh system
getRenderableRegistry().register("mesh", "cube", new CubeMesh());
const bundle = createMeshRenderBundle("cube", device, resourceManager, /* ... */);

Adding New Components

// Extend existing interfaces
interface ExtendedRenderableComponent extends RenderableComponent {
  customProperty: string;
}

// Create new component maps
const customComponents = new Map<NodeID, CustomComponent>();

API Reference

See individual README files in subdirectories for detailed API documentation:

Contributing

When adding new features:

  1. Follow existing patterns: Use the established architecture
  2. Add documentation: Update README files for new components
  3. Write tests: Include example usage in demos
  4. Profile performance: Ensure changes don't degrade performance
  5. Update type definitions: Keep TypeScript types current

Troubleshooting

Common Issues

Render bundles not updating: Check needsRebundle flag Uniform offset errors: Call reorganizeUniformOffsets after scene changes Pipeline creation fails: Verify shader compatibility and buffer layouts Memory leaks: Ensure destroy() methods are called on cleanup

Performance Issues

Low frame rate: Check triangle count and draw call batching Memory usage: Monitor ring buffer usage and reset frequency GPU stalls: Verify uniform buffer alignment and usage patterns