WebGPU Rendering

Advanced

Modern GPU rendering with WebGPU support. Leverage compute shaders, storage buffers, and next-generation graphics features.

Web Engine supports both WebGL and WebGPU rendering backends. WebGPU provides modern GPU features including compute shaders, storage buffers, and improved performance. The engine automatically detects support and falls back to WebGL when needed.

WebGPU vs WebGL#

Compute Shaders

Run general-purpose GPU compute for culling, LOD selection, and Hi-Z pyramids

Storage Buffers

Direct GPU buffer access without vertex attribute limitations

Modern Pipeline

Improved shader compilation, better error messages, and reduced driver overhead

Better Performance

Lower CPU overhead, more efficient memory management, and explicit resource control

Feature Comparison#

FeatureWebGPUWebGL 2Notes
Compute ShadersWebGPU only feature
Storage BuffersWebGL limited to textures
Indirect DrawBoth support indirect rendering
Instance CullingGPUCPUWebGPU uses compute shaders
Hi-Z OcclusionRequires compute + storage textures
MSAABoth support antialiasing
Shadow MappingFeature parity
PBR MaterialsThree.js provides both

Renderer Selection

Web Engine automatically selects the best available renderer. WebGPU is preferred when available, with automatic fallback to WebGL 2 or WebGL 1. You can override this behavior with the rendererType option.

Browser Support#

WebGPU is a modern API with growing browser support. Check compatibility before deploying WebGPU-dependent features:

BrowserVersionStatusNotes
Chrome113+StableFull support since May 2023
Edge113+StableChromium-based, same as Chrome
FirefoxNightlyExperimentalBehind flag, targeting 2024
Safari18+PreviewTechnology Preview only
Mobile Chrome113+StableAndroid support
Mobile SafariNoneiOS support pending

Detecting WebGPU Support#

DetectWebGPU.ts
typescript
// Check WebGPU availability
const hasWebGPU = 'gpu' in navigator;
if (hasWebGPU) {
try {
const adapter = await navigator.gpu.requestAdapter();
if (adapter) {
console.log('WebGPU supported');
console.log('Adapter:', adapter.info);
// Request device to verify full support
const device = await adapter.requestDevice();
console.log('WebGPU device acquired');
// Check for specific features
const features = Array.from(device.features);
console.log('Supported features:', features);
device.destroy();
} else {
console.log('WebGPU adapter unavailable');
}
} catch (error) {
console.error('WebGPU initialization failed:', error);
}
} else {
console.log('WebGPU not available, using WebGL fallback');
}
// Web Engine provides a helper
import { isWebGPURenderer } from '@web-engine-dev/core';
const renderer = getRenderer();
if (isWebGPURenderer(renderer)) {
console.log('Using WebGPU renderer');
} else {
console.log('Using WebGL renderer');
}

Enabling WebGPU#

Web Engine supports WebGPU through Three.js's WebGPURenderer. Enable it by setting the renderer type during initialization:

Runtime Configuration#

EnableWebGPU.ts
typescript
import { createEngine, RendererType } from '@web-engine-dev/core';
// Create engine with WebGPU renderer
const engine = await createEngine({
rendererType: 'webgpu' as RendererType,
quality: 'high',
antialias: true,
});
// Engine will use WebGPU if available, fallback to WebGL otherwise
// Check active renderer
const ctx = engine.getContext();
if (ctx.renderer.isWebGPURenderer) {
console.log('WebGPU active');
// Access WebGPU-specific features
const device = ctx.renderer.backend.device;
console.log('GPU Device:', device);
}

Renderer Profiles#

RendererProfile.ts
typescript
import { RendererConfigRegistry } from '@web-engine-dev/core';
// Resolve renderer profile with WebGPU preference
const profile = RendererConfigRegistry.resolve('runtime', {
quality: 'high',
rendererType: 'webgpu',
init: {
antialias: true,
powerPreference: 'high-performance',
},
state: {
toneMapping: THREE.ACESFilmicToneMapping,
outputColorSpace: THREE.SRGBColorSpace,
},
});
console.log('Renderer config:', profile);
// Force WebGL fallback (for testing)
const webglProfile = RendererConfigRegistry.resolve('runtime', {
rendererType: 'webgl',
});

Graceful Degradation

Always implement fallback logic for WebGL. WebGPU is not universally supported, especially on older devices and iOS. Design features to work on both backends or provide alternative implementations.

Compute Shaders#

WebGPU's compute shaders enable GPU-accelerated operations beyond rendering. Web Engine uses compute for culling, LOD selection, and Hi-Z pyramid generation:

Hi-Z Pyramid Example#

HiZPyramid.ts
typescript
import { HiZPyramid } from '@web-engine-dev/core';
import { WebGPURenderer } from 'three/webgpu';
// Create Hi-Z pyramid for occlusion culling
const hizPyramid = new HiZPyramid(0.5); // 0.5 = half resolution
function render(scene, camera, renderer: WebGPURenderer) {
// Ensure pyramid matches viewport
hizPyramid.ensureSize(renderer);
// Render depth pre-pass
hizPyramid.renderDepthPass(scene, camera, renderer);
// Build mip chain via compute shaders
hizPyramid.build(renderer);
// Use pyramid for occlusion queries
const hizTexture = hizPyramid.texture;
const mipLevels = hizPyramid.mipLevels;
console.log(`Hi-Z pyramid: ${mipLevels} mip levels`);
// Main render pass
renderer.render(scene, camera);
}

GPU Frustum Culling#

ComputeCulling.ts
typescript
import { Fn, storageTexture, textureStore } from 'three/tsl';
import { StorageBufferAttribute } from 'three/webgpu';
// Create storage buffer for entity data
const entityCount = 100000;
const positionsBuffer = new StorageBufferAttribute(entityCount * 3, 3, Float32Array);
const radiiBuffer = new StorageBufferAttribute(entityCount, 1, Float32Array);
const visibilityBuffer = new StorageBufferAttribute(entityCount, 1, Uint32Array);
// Define compute shader using TSL (Three.js Shading Language)
const frustumCullCompute = Fn(() => {
// Compute node implementation
// Read entity positions and radii from storage buffers
// Test against frustum planes
// Write visibility mask to output buffer
})().compute(entityCount);
// Execute compute shader
renderer.compute(frustumCullCompute);
// Read results back to CPU (if needed)
const visibilityData = await renderer.backend.getArrayBufferAsync(visibilityBuffer);
const visibility = new Uint32Array(visibilityData);
console.log(`Visible entities: ${visibility.filter(v => v).length}`);

TSL Shading Language

Three.js provides TSL (Three.js Shading Language), a TypeScript-based shader authoring system for WebGPU. It offers type-safe shader development with proxy-based method chaining. See the HiZPyramid implementation for examples.

Storage Buffers#

Storage buffers provide direct GPU access to large data structures without vertex attribute limits:

StorageBuffers.ts
typescript
import { StorageBufferAttribute, StorageInstancedBufferAttribute } from 'three/webgpu';
// Create storage buffer for instance transforms
const instanceCount = 50000;
const instanceMatrices = new StorageInstancedBufferAttribute(
instanceCount * 16, // 16 floats per 4x4 matrix
16, // itemSize
Float32Array
);
// Populate buffer data
const matrices = new Float32Array(instanceCount * 16);
for (let i = 0; i < instanceCount; i++) {
// Fill matrix data
const offset = i * 16;
matrices.set([
1, 0, 0, 0, // column 0
0, 1, 0, 0, // column 1
0, 0, 1, 0, // column 2
x, y, z, 1 // column 3 (translation)
], offset);
}
// Upload to GPU
instanceMatrices.array.set(matrices);
instanceMatrices.needsUpdate = true;
// Use in instanced mesh
const mesh = new THREE.InstancedMesh(geometry, material, instanceCount);
mesh.instanceMatrix = instanceMatrices;
// Storage buffers bypass the vertex attribute count limit
// WebGL: max 16 attributes
// WebGPU: unlimited via storage buffers

GPU Readback#

GPUReadback.ts
typescript
import { isWebGPURendererWithBackend } from '@web-engine-dev/core';
// Read GPU buffer data back to CPU
if (isWebGPURendererWithBackend(renderer)) {
// Async readback (does not stall pipeline)
const arrayBuffer = await renderer.backend.getArrayBufferAsync(storageBuffer);
const data = new Float32Array(arrayBuffer);
console.log('GPU data:', data);
// Use for CPU-side logic
processData(data);
}
// Warning: Frequent CPU-GPU sync is slow
// Prefer keeping data on GPU when possible

Modern Rendering Features#

Indirect Drawing#

IndirectDraw.ts
typescript
import { IndirectDrawManager } from '@web-engine-dev/core';
// Create indirect draw manager
const indirectDrawManager = new IndirectDrawManager({
maxDrawCalls: 10000,
enableGPUCulling: true, // WebGPU only
});
// Register draw calls
indirectDrawManager.addDrawCall({
geometry,
material,
instanceCount: 5000,
baseInstance: 0,
});
// Execute all draws with single GPU command
indirectDrawManager.execute(renderer, scene, camera);
// WebGPU: Compute shader culls invisible instances
// WebGL: CPU-side culling + indirect draw

Storage Textures#

StorageTextures.ts
typescript
import { StorageTexture } from 'three/webgpu';
// Create storage texture for compute shader output
const storageTexture = new StorageTexture(1024, 1024);
storageTexture.name = 'ComputeOutput';
// Use in compute shader
import { storageTexture as storageTex, textureStore } from 'three/tsl';
const computeNode = Fn(() => {
const texNode = storageTex(storageTexture);
const coords = uvec2(instanceIndex.modInt(1024), instanceIndex.div(1024));
const color = vec4(1.0, 0.0, 0.0, 1.0);
textureStore(texNode, coords, color).toWriteOnly();
})().compute(1024 * 1024);
renderer.compute(computeNode);
// Storage textures support read/write access in compute shaders
// Regular textures are read-only in shaders

Performance Optimization#

Pipeline State Caching#

PipelineCache.ts
typescript
// WebGPU caches pipeline states automatically
// Minimize state changes for better performance
// ❌ Bad: Change state per object
objects.forEach(obj => {
material.transparent = obj.transparent;
renderer.render(scene, camera);
});
// ✅ Good: Batch by state
const opaque = objects.filter(o => !o.transparent);
const transparent = objects.filter(o => o.transparent);
renderBatch(opaque, material, false);
renderBatch(transparent, material, true);
function renderBatch(objects, material, transparent) {
material.transparent = transparent;
objects.forEach(obj => obj.visible = true);
renderer.render(scene, camera);
objects.forEach(obj => obj.visible = false);
}

Async Shader Compilation#

AsyncCompilation.ts
typescript
// WebGPU compiles shaders asynchronously
// Warm up pipelines during loading screen
async function warmupPipelines(materials: Material[]) {
const dummyScene = new THREE.Scene();
const dummyCamera = new THREE.PerspectiveCamera();
for (const material of materials) {
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(),
material
);
dummyScene.add(mesh);
// Trigger pipeline compilation
renderer.compile(dummyScene, dummyCamera);
await new Promise(resolve => setTimeout(resolve, 0));
dummyScene.remove(mesh);
}
console.log('Pipeline warmup complete');
}
// Call during loading screen
await warmupPipelines(allMaterials);

Fallback Handling#

Always provide WebGL fallbacks for WebGPU-only features:

Fallbacks.ts
typescript
import { isWebGPURenderer } from '@web-engine-dev/core';
class CullingSystem {
private gpuCulling: GPUCullingPipeline | null = null;
private cpuCulling: CPUCullingFallback;
constructor(renderer: CompatibleRenderer) {
if (isWebGPURenderer(renderer)) {
// Use GPU compute shaders
this.gpuCulling = new GPUCullingPipeline(renderer);
console.log('Using GPU culling');
} else {
// Fallback to CPU culling
this.cpuCulling = new CPUCullingFallback();
console.log('Using CPU culling fallback');
}
}
execute(entities: Entity[], camera: Camera) {
if (this.gpuCulling) {
return this.gpuCulling.cull(entities, camera);
} else {
return this.cpuCulling.cull(entities, camera);
}
}
}
// Feature detection pattern
function getOptimalImplementation() {
const renderer = getRenderer();
if (isWebGPURenderer(renderer)) {
return {
culling: 'gpu',
lodSelection: 'gpu',
occlusion: 'hiz',
};
} else {
return {
culling: 'cpu',
lodSelection: 'cpu',
occlusion: 'none',
};
}
}

Progressive Enhancement

Design your game to work on WebGL, then add WebGPU enhancements. Don't make WebGPU a hard requirement unless you control the deployment environment (e.g., internal tools, specific hardware).

Debugging WebGPU#

Validation Layers#

Validation.ts
typescript
// Enable WebGPU validation in development
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice({
// Enable validation features
requiredFeatures: [],
requiredLimits: {},
});
// Set up error handler
device.addEventListener('uncapturederror', (event) => {
console.error('WebGPU error:', event.error);
});
// Chrome DevTools provides WebGPU inspection
// 1. Open DevTools
// 2. Go to "Rendering" tab
// 3. Enable "WebGPU"
// 4. View pipeline states, buffers, and textures

Common Issues#

  • Shader compilation errors — Check TSL syntax and ensure proper type usage
  • Buffer size mismatches — Verify buffer sizes match shader expectations
  • Missing features — Check if required WebGPU features are supported
  • Validation errors — Enable validation layers to catch API misuse
  • Performance issues — Profile with Chrome DevTools Performance tab

Migrating to WebGPU#

If you're upgrading from WebGL-only to WebGPU support:

Migration.ts
typescript
// 1. Update renderer creation
// Old:
const renderer = new THREE.WebGLRenderer({ antialias: true });
// New:
import { createRenderer } from '@web-engine-dev/core';
const renderer = await createRenderer({
rendererType: 'webgpu', // Auto-fallback to WebGL
antialias: true,
});
// 2. Check renderer type before using WebGPU features
if (isWebGPURenderer(renderer)) {
// Use compute shaders
enableGPUCulling();
} else {
// Use CPU fallback
enableCPUCulling();
}
// 3. Replace vertex attributes with storage buffers (optional)
// Old (WebGL):
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
// New (WebGPU):
import { StorageBufferAttribute } from 'three/webgpu';
const storageAttr = new StorageBufferAttribute(positions.length, 3, Float32Array);
storageAttr.array.set(positions);
// 4. Update custom shaders to TSL (if using)
// Old (GLSL):
const material = new THREE.ShaderMaterial({
vertexShader: glslVertex,
fragmentShader: glslFragment,
});
// New (TSL):
import { MeshStandardNodeMaterial } from 'three/webgpu';
const material = new MeshStandardNodeMaterial();
material.colorNode = customColorNode; // TSL node

Best Practices#

  • Test on WebGL — Ensure fallback paths work correctly
  • Feature Detection — Check WebGPU support, don't assume availability
  • Progressive Enhancement — Add WebGPU features on top of working WebGL base
  • Profile Both Backends — Validate performance gains justify complexity
  • Monitor Browser Support — Check caniuse.com for latest WebGPU adoption
  • Use Compute Wisely — GPU dispatch has overhead, batch operations
  • Minimize Readback — CPU-GPU sync is slow, keep data on GPU
  • Cache Pipeline States — Reuse materials and geometries to avoid recompilation

Additional Resources#

Advanced | Web Engine Docs | Web Engine Docs