Phaser AE

WebGPU renderer

Free packv3.0.0-62D and 3DRenderers

The WebGPU backend. There is nothing for game code to call: it draws what the world orders in a handful of instanced draws a frame, and every object gets alpha, blend modes, tiling, scrolling and deforms as part of its spawn.

This is a Free pack. Every phaser.io account can use it in their games.

Released October 14, 2026 · updated October 20, 2026

WebGPU renderer: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

webgpu

function webgpu(): Pack<{ gpu: WebGpuApi; preloader: PreloaderApi }>

The WebGPU backend pack. Draws the worlds' sorted instance tables from the kernel atlas — and each big picture from its texture of its own, uploaded once with mips — world-space tables in the sprites pass (with world-screen's underlay among them, through the screen camera) and screen-space ones in screen, publishes a WebGpuBag on ctx.backend.webgpu for passes between them, hands runs of drawBy objects to their registered drawer in place, splices every registered deform definition into its grid shader, survives a lost device (boot.contextLost → rebuild → boot.contextRestored), and claims the screenshot, startRecording, stopRecording and loseDevice commands. No options. Headless (no document) it attaches and draws nothing.

Functions

installWebGpu

function installWebGpu(k: Kernel, prepared?: GPUDevice, deferRegistration = false): { gpu: WebGpuApi; initialise(): Promise<void>; register(): void }

Install one backend's passes and commands; the caller controls device initialisation.

mipLevels

function mipLevels(width: number, height: number): number

Mip levels for a texture of its own, down to 1×1: a 1584×2816 picture has 12.

placeFrame

function placeFrame(assets: Pick<KernelAssets, 'size' | 'framePlace' | 'ownTexture'>, frame: number, out: Float32Array, o: number): number

Write a frame's UV rect at out[o..o+3], half-texel inset, in the texture the frame lives in — the atlas, or a picture's own texture, whose UVs run over the picture rather than the atlas — and return that texture's id (0 the atlas). The one place both backends turn a frame index into texture coordinates.

runsOf

function runsOf(blend: Uint8Array, deformKind: Uint8Array, flags: Uint8Array, count: number, base: number, tex?: Uint32Array): Run[]

Split a table into runs of consecutive instances sharing (blend, deformed, drawBy, texture) — draw order is untouched, draw calls grow only where modes mix. tex is each instance's texture (framePlace(frame).tex; absent, all atlas): atlas sprites batch as they always did, and a picture in a texture of its own is a run of its own. base offsets first.

spriteParts

function spriteParts(tables: ReadonlyArray<InstanceTable>, splits: ReadonlyArray<WorldSplit>, floors: ReadonlyArray<Pick<WorldFloor, 'through'>>): SpritePart[]

Each table's part: its space and pass, a 'world-split' (the world layers above it are 'over') and a 'world-floor' (the world layers up to its own are 'floor').

worldSplit

function worldSplit(tables: ReadonlyArray<InstanceTable>, splits: ReadonlyArray<WorldSplit>): number

Which sprite pass draws each table: the index in tables of the first world layer a split names; -1 for none.

Types

KernelAssets

interface KernelAssets

The kernel atlas surface a backend reads. Mirrors KernelAssets without importing the implementation.

  • readonly size: number — Side of the square atlas texture in pixels.
  • readonly image: OffscreenCanvas | HTMLCanvasElement | null — The canvas the atlas is drawn on; null when there is none (headless).
  • readonly version: number — Counter that moves whenever the atlas pixels change; the backend uploads the atlas again when it differs from the one it last saw.
  • frameRect(index: number): { x: number; y: number; w: number; h: number } — A frame's rect in the atlas; a frame in a texture of its own answers the white square (and warns once).
  • framePlace(index: number): { x: number; y: number; w: number; h: number; tex: number } — Where a frame's pixels are: tex 0 the atlas, else ownTexture(tex); the rect is in that texture's pixels.
  • readonly ownTextures: ReadonlyArray<{ readonly id: number; readonly key: string; readonly width: number; readonly height: number; readonly image: ImageBitmap | OffscreenCanvas | HTMLCanvasElement | null; readonly live: boolean; readonly version: number; readonly page?: boolean }> — Later atlas pages (page) and big pictures in textures of their own, in the order they landed; id = position + 1. image is null headless and after destroy. A live one (every page) is a canvas to upload again whenever version moves.
  • ownTexture(id: number): { readonly width: number; readonly height: number; readonly page?: boolean } | null — A texture of its own by id; null for 0, the atlas's first page.
  • takeDirty(): { x: number; y: number; w: number; h: number }[] — The first page's rectangles (pixels) changed since the last call; reading them clears them, so each is uploaded once.
  • takeDirtyPages?(): ReadonlyArray<{ readonly tex: number; readonly since: number; readonly version: number; readonly rects: ReadonlyArray<{ x: number; y: number; w: number; h: number }> }> — Each later page's rectangles drawn since the last call, with the page version they start from and the one they bring it to; reading them clears them. A copy at since takes the rects; any other waits for the whole page. Absent from a kernel older than atlas pages.

Run

type Run = [number, number, number, boolean, number, number]

A run of consecutive instances sharing (blend, deformed, drawer, texture): [first, count, blend, deformed, drawBy, tex]. tex 0 is the atlas.

SpritePart

type SpritePart = 'floor' | 'world' | 'over' | 'screen'

The sprite pass a table is drawn in: 'floor' and 'world' both in 'sprites' (floor first), 'over' in 'sprites-over', 'screen' in 'screen'.

WebGpuApi

interface WebGpuApi

game.gpu: the backend's readable facts. Nothing to call.

  • readonly ready: boolean — True once the device and pipeline exist; false before boot, in a browser without WebGPU, and while a lost device is being replaced.
  • readonly drawCalls: number — Instanced draws issued last frame: one per run of a layer sharing a blend mode, deform flag, drawer and texture.
  • readonly losses: number — How many times the device has been lost and rebuilt this session. Every loss emits boot.contextLost, every rebuild boot.contextRestored.

WebGpuBag

interface WebGpuBag

What this backend publishes on the pass context for packs that draw after it (lighting, post, effects).

  • device: GPUDevice — The GPU device every later pass creates its pipelines and buffers on. It CHANGES after a device loss: a pack keyed on it must rebuild (listen to boot.contextLost).
  • format: GPUTextureFormat — The swapchain's texture format; a pass's colour target must match it.
  • context: GPUCanvasContext — The canvas context; context.canvas is the element (for toBlob, captureStream).
  • encoder: GPUCommandEncoder — This frame's command encoder; each pack opens its own render pass on it with loadOp 'load'. Submitted at present.
  • view: GPUTextureView — This frame's colour target view; render passes attach to it.
  • texture: GPUTexture — The swapchain texture itself (COPY_SRC | TEXTURE_BINDING), for a post chain that reads the frame back.
  • atlas: GPUTextureView | null — The kernel atlas as a texture view, for packs that sample sprites themselves (effects, a background image). Null until the atlas has been uploaded.
  • textureOf(tex: number): GPUTextureView | null — The texture a frame lives in, by its framePlace(i).tex: 0 is atlas, anything else a big picture's texture of its own. Null until that texture is uploaded. A pack that samples a loaded image itself binds this rather than atlas, and then draws pictures as well as sprites.
  • sampler: GPUSampler — Linear atlas sampler; pixelArt uses texel snapping with a derivative-sized antialias seam.
  • width: number — Physical pixels of the target, wide.
  • height: number — Physical pixels of the target, high.
  • logicalWidth: number — Logical pixels wide (the screen policy). Fragment coords are physical: scale = width / logicalWidth.
  • logicalHeight: number — Logical pixels high (the screen policy).
  • stats: { drawCalls: number; instances: number; textures: number; textureBytes: number } — What this backend spent last frame, for anything that profiles: draws, instances, textures bound and their bytes.

WorldFloor

interface WorldFloor

A registry entry (kind 'world-floor') from a pack that acts between a 2D world's floor and the pieces on it, as lighting-2d's floor does for its under lights: inside 'sprites', the world-pass tables up to and including the 2D world layer through are drawn first, then between runs, then the rest. Both renderers list the kind 'render-feature' entry { name: 'world-floor' } when they honour it.

  • readonly name: string — The pack that asked, for messages: 'lighting-2d'.
  • readonly through: string — The last 2D world layer that is floor.
  • between(ctx: PassContext): void — Called every frame between the floor and the pieces, with the frame's context (its backend bag).

WorldSplit

interface WorldSplit

A registry entry (kind 'world-split') from a pack that composites over only part of the world's stack, as lighting-2d's unlitAbove does: the world-pass tables after the 2D world layer above are drawn in the 'sprites-over' pass, after that pack's own pass and before post, instead of in 'sprites'.

  • readonly name: string — The pack that asked for the split, for messages: 'lighting-2d'.
  • readonly above: string — The last 2D world layer drawn in 'sprites'; the world layers above it are drawn in 'sprites-over'.