Backend comparison
Compare backend behavior and decide what to ship.
Backend comparison
ExoJS renders through one of two backends: WebGL2 or WebGPU. The choice is automatic by default — the engine picks WebGPU when navigator.gpu is available, WebGL2 otherwise. You can override this with backend: { type: 'webgpu' } or backend: { type: 'webgl2' } in ApplicationOptions.
Selection
import { Application } from '@codexo/exojs';
// Auto-select (prefers WebGPU, falls back to WebGL2)
const app = new Application();
// Pin to WebGPU — throws if unavailable
const gpuApp = new Application({ backend: { type: 'webgpu' } });
// Pin to WebGL2
const glApp = new Application({ backend: { type: 'webgl2' } });
At any point, check app.backend.backendType (a RenderBackendType enum with values WebGl2 and WebGpu) to branch backend-specific code.
Feature parity
The core rendering pipeline — sprites, meshes, graphics, text, containers, masks, filters, render-targets, views, culling — works identically on both backends. The API is the same. You write one scene and it renders on both.
Where differences exist, they are performance characteristics or rendering-path specifics, not API gaps:
| Area | WebGL2 | WebGPU |
|---|---|---|
| Sprite batching | Multi-texture batched (up to 8) | Multi-texture batched (up to 8) |
| Particle simulation | CPU | CPU (default); optional GPU compute update path when all update modules implement wgsl() |
| MeshMaterial | GLSL ES 3.00 | WGSL |
| ShaderFilter | WebGl2ShaderFilter (GLSL) | WebGpuShaderFilter (WGSL) |
| GPU compute | Not available | Raw backend.device access for compute + custom pipelines |
| Engine-emitted debug pass labels | Not currently emitted | Emitted on key passes (e.g. WebGpuShaderFilter pass) |
Both backends batch sprites from up to 8 different textures into a single draw call. For most projects with a single texture atlas, the batching difference is irrelevant — the practical distinction is only visible in multi-atlas scenes.
Geometric clip parity is also aligned: RenderNode.clip with a Geometry clipShape works on both backends for Sprite (default/custom material), Mesh (default/custom material), Graphics, Text / BitmapText, and ParticleSystem. Rectangle / bounds clips remain on the scissor path.
Particles and GPU
Particle simulation (spawn, integration, module updates) runs on the CPU by default on both backends. On WebGPU, when every registered update module is GPU-eligible (implements wgsl()), the system compiles a composite WGSL compute shader and runs the full update pipeline on the GPU in one dispatch. This is the GPU path: simulation moves to the GPU, and the renderer reads instance data from a shared buffer — no CPU readback.
On WebGL2, or when any update module lacks wgsl(), the system runs on CPU. The particle rendering path (instanced draw calls) is the same on both backends. Check system.gpuMode to know which path is active.
Custom shaders
A ShaderSource accepts both glsl: { vertex, fragment } and wgsl source; wrap it in a MeshMaterial to bind uniforms and attach it to a Mesh. The renderer picks the appropriate language for the active backend. Provide both for cross-backend portability. ShaderSource.detectUniformDrift() compares declared uniforms across languages for CI-style verification.
For screen-space effects, use WebGl2ShaderFilter on WebGL2 and WebGpuShaderFilter on WebGPU. Both accept the same uniform-value types — only the shader language differs. For cross-backend filters, construct the appropriate variant based on app.backend.backendType at init time.
Direct GPU access
The WebGPU backend exposes backend.device (GPUDevice), backend.context (GPUCanvasContext), and backend.format (GPUTextureFormat). You can create custom pipelines, vertex buffers, and command encoders directly — the custom-triangle-renderer example demonstrates this. On WebGL2, backend.context is also publicly available as a WebGL2RenderingContext, but the direct compute-style escape hatch exists only on WebGPU.
Device loss
Both backends handle loss events. On WebGL2, the engine attempts context restore automatically. On WebGPU, ExoJS also attempts automatic device recovery and emits onBackendLost / onBackendRestored on the Application.
Choosing for production
- Ship with
auto(the default). Most users get WebGPU, older browsers get WebGL2. You write one codebase, both paths work. - Pin to
webgpuif you depend on features only available through direct backend access (compute shaders, raw GPU pipelines) and can accept the browser-support trade-off. - Pin to
webgl2if you are targeting a specific environment where WebGPU is unreliable or unavailable. This is uncommon — auto-selection covers this case.
The backend-comparison example lets you toggle backends at runtime (press B) to compare performance and visual output with the same scene.
Examples
2200 bouncing sprites with a performance overlay. Press B to toggle between WebGL2 and WebGPU backends and compare frame rates.
Where to go next
The next chapter, Custom renderers, covers extending the render pipeline with your own passes — no-op passes, full-screen triangles, and bridging a custom renderer into the engine’s frame.