Skip to content

WebGPU 2D Renderer

@shell/webgpu renders ECS sprite components directly through WebGPU. It extracts each frame into reusable typed arrays, conservatively culls sprites outside the active camera, sorts visible transparent sprites deterministically, batches compatible runs, and submits one command buffer without creating a renderer display object for every entity.

import { World } from '@shell/ecs';
import { Position, Size } from '@shell/transform';
import { Image, Texture, WebGPUPlugin } from '@shell/webgpu';
const world = new World();
world.addPlugin(
WebGPUPlugin.config({
canvas,
clearColor: [0.05, 0.06, 0.08, 1],
spawnCamera: true,
powerPreference: 'high-performance',
}),
);
await world.mount();
world
.get('commands')
.spawn(
Image.with(Texture, { source: '/player.png' })
.with(Position, { x: 100, y: 50 })
.with(Size, { width: 64, height: 64 }),
);

The plugin accepts either a canvas or container. A container causes the renderer to create and own a responsive canvas. Importing the package is SSR-safe; mounting requires navigator.gpu and an available adapter.

ExportDescription
SpriteSprite renderable marker.
TextureAsset source and source rectangle.
SamplerNearest/linear filtering and clamp/repeat address.
BlendSource-alpha or additive blending.
TintPacked RGB tint and alpha.
RenderOrderLayer and explicit order.
CameraActive state and zoom.
ImageComplete sprite bundle.
CameraPrefabCamera, position, and rotation prefab.
defineTextureAtlasValidated named frame metadata.

Transform components are owned by @shell/transform and are not re-exported by @shell/webgpu. WebGPUPlugin installs TransformPlugin as a dependency, while application code imports the components it authors directly from @shell/transform.

const atlas = defineTextureAtlas({
name: 'actors',
source: '/actors.png',
frames: {
idle: { x: 0, y: 0, width: 32, height: 48 },
run: { x: 32, y: 0, width: 32, height: 48 },
},
});
commands.spawn(
Image.with(atlas.frame('run'))
.with(Sampler, { filter: SamplerFilter.Linear })
.with(Blend, { mode: BlendMode.Additive }),
);

Atlas frames resolve to the existing serializable Texture fields and share one resident image. Frame metadata supports untrimmed, unrotated origins and dimensions. Cached frame data and bundles avoid selection-time allocation.

Sprites sort by layer, Z, explicit order, pipeline, sampler, blend, texture, and entity index. Transparency ordering takes priority over reducing state switches. The default canvas alpha mode is premultiplied; sprite colors are straight alpha. Additive blending weights source color by alpha and adds it to the destination.

world.get('webgpuTextures') exposes unload, reload, retry, and replace. Unloading invalidates in-flight completion and makes stale IDs use the fallback. Replacement accepts a decoded image source under the existing logical key. Statistics include resident count, estimated bytes, failures, retries, and replacements. Automatic eviction is intentionally not implemented.

const device = world.get('webgpuDevice');
const frame = world.get('webgpuFrame');
const renderer = world.get('webgpuRenderer');
  • device.status and device.error report unsupported browsers, acquisition failures, and device loss.
  • frame.camera.diagnostic reports missing, multiple, or zero-sized camera states.
  • frame.stats reports camera extraction, sprite extraction, preparation CPU time, and candidate, visible, and culled sprite counts.
  • renderer.stats reports the visibility counts, CPU encoding time, draw calls, one-frame total and instance-only upload bytes, queue submissions, estimated texture/buffer memory, resident textures, and texture lifecycle counters.

Culling tests the transformed quad corners against the same clip-space viewport used for rendering. It includes camera translation, rotation, zoom, CSS canvas size, sprite rotation, scale, anchor, and pivot. Invalid camera frames, non-finite bounds, and pending natural texture dimensions remain visible conservatively. Offscreen sprites still request their textures deterministically.

Failed image loads use a shared white fallback texture and retain their error. Device loss suspends rendering and is not automatically recovered. Unmounting destroys all renderer-owned GPU resources and browser observers.

The renderer supports one canvas, one active camera, flat transforms, textured quads, nearest/linear and clamp/repeat sampling, and alpha/additive blending. It does not yet support atlas trimming or rotation, hierarchy transforms, text, masks, filters, particles, multiple cameras, render textures, custom shaders, spatial or occlusion culling, automatic device recovery, or a non-WebGPU fallback.

See packages/2d/webgpu/README.md for detailed semantics and examples/webgpu/benchmark.html for the Pixi comparison harness.