WebGPU Rendering
AdvancedModern 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#
| Feature | WebGPU | WebGL 2 | Notes |
|---|---|---|---|
| Compute Shaders | ✓ | ✗ | WebGPU only feature |
| Storage Buffers | ✓ | ✗ | WebGL limited to textures |
| Indirect Draw | ✓ | ✓ | Both support indirect rendering |
| Instance Culling | GPU | CPU | WebGPU uses compute shaders |
| Hi-Z Occlusion | ✓ | ✗ | Requires compute + storage textures |
| MSAA | ✓ | ✓ | Both support antialiasing |
| Shadow Mapping | ✓ | ✓ | Feature parity |
| PBR Materials | ✓ | ✓ | Three.js provides both |
Renderer Selection
rendererType option.Browser Support#
WebGPU is a modern API with growing browser support. Check compatibility before deploying WebGPU-dependent features:
| Browser | Version | Status | Notes |
|---|---|---|---|
| Chrome | 113+ | Stable | Full support since May 2023 |
| Edge | 113+ | Stable | Chromium-based, same as Chrome |
| Firefox | Nightly | Experimental | Behind flag, targeting 2024 |
| Safari | 18+ | Preview | Technology Preview only |
| Mobile Chrome | 113+ | Stable | Android support |
| Mobile Safari | — | None | iOS support pending |
Detecting WebGPU Support#
// Check WebGPU availabilityconst 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 helperimport { 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#
import { createEngine, RendererType } from '@web-engine-dev/core'; // Create engine with WebGPU rendererconst engine = await createEngine({ rendererType: 'webgpu' as RendererType, quality: 'high', antialias: true,}); // Engine will use WebGPU if available, fallback to WebGL otherwise // Check active rendererconst 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#
import { RendererConfigRegistry } from '@web-engine-dev/core'; // Resolve renderer profile with WebGPU preferenceconst 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
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#
import { HiZPyramid } from '@web-engine-dev/core';import { WebGPURenderer } from 'three/webgpu'; // Create Hi-Z pyramid for occlusion cullingconst 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#
import { Fn, storageTexture, textureStore } from 'three/tsl';import { StorageBufferAttribute } from 'three/webgpu'; // Create storage buffer for entity dataconst 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 shaderrenderer.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
HiZPyramid implementation for examples.Storage Buffers#
Storage buffers provide direct GPU access to large data structures without vertex attribute limits:
import { StorageBufferAttribute, StorageInstancedBufferAttribute } from 'three/webgpu'; // Create storage buffer for instance transformsconst instanceCount = 50000;const instanceMatrices = new StorageInstancedBufferAttribute( instanceCount * 16, // 16 floats per 4x4 matrix 16, // itemSize Float32Array); // Populate buffer dataconst 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 GPUinstanceMatrices.array.set(matrices);instanceMatrices.needsUpdate = true; // Use in instanced meshconst 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 buffersGPU Readback#
import { isWebGPURendererWithBackend } from '@web-engine-dev/core'; // Read GPU buffer data back to CPUif (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 possibleModern Rendering Features#
Indirect Drawing#
import { IndirectDrawManager } from '@web-engine-dev/core'; // Create indirect draw managerconst indirectDrawManager = new IndirectDrawManager({ maxDrawCalls: 10000, enableGPUCulling: true, // WebGPU only}); // Register draw callsindirectDrawManager.addDrawCall({ geometry, material, instanceCount: 5000, baseInstance: 0,}); // Execute all draws with single GPU commandindirectDrawManager.execute(renderer, scene, camera); // WebGPU: Compute shader culls invisible instances// WebGL: CPU-side culling + indirect drawStorage Textures#
import { StorageTexture } from 'three/webgpu'; // Create storage texture for compute shader outputconst storageTexture = new StorageTexture(1024, 1024);storageTexture.name = 'ComputeOutput'; // Use in compute shaderimport { 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 shadersPerformance Optimization#
Pipeline State Caching#
// WebGPU caches pipeline states automatically// Minimize state changes for better performance // ❌ Bad: Change state per objectobjects.forEach(obj => { material.transparent = obj.transparent; renderer.render(scene, camera);}); // ✅ Good: Batch by stateconst 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#
// 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 screenawait warmupPipelines(allMaterials);Fallback Handling#
Always provide WebGL fallbacks for WebGPU-only features:
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 patternfunction getOptimalImplementation() { const renderer = getRenderer(); if (isWebGPURenderer(renderer)) { return { culling: 'gpu', lodSelection: 'gpu', occlusion: 'hiz', }; } else { return { culling: 'cpu', lodSelection: 'cpu', occlusion: 'none', }; }}Progressive Enhancement
Debugging WebGPU#
Validation Layers#
// Enable WebGPU validation in developmentconst adapter = await navigator.gpu.requestAdapter();const device = await adapter.requestDevice({ // Enable validation features requiredFeatures: [], requiredLimits: {},}); // Set up error handlerdevice.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 texturesCommon 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:
// 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 featuresif (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 nodeBest 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#
- WebGPU Spec — w3.org/TR/webgpu
- Three.js WebGPU — threejs.org/examples
- Browser Support — caniuse.com/webgpu
- TSL Documentation — Three.js Shading Language examples and API
- WebGPU Fundamentals — webgpufundamentals.org