Reference4 min read3 sections

Frame texture graph

docs/RENDER_GRAPH.mdSource updated 2026-10-24
Markdown
On this page 3 sections

The production WebGPU renderer now encodes its shadow, scene and postprocessing passes from a compiled texture graph. Declaration order is deliberately post, scene, shadow; the compiler derives shadow → scene → post from resource reads and writes. This is the engine-owned forward renderer, with the existing shaders, geometry, culling and presentation protocol.

#Definition and compilation

packages/render/graph.ts is GPU/DOM independent. A definition contains at most 64 named textures and 32 named passes. A pass declares sampled inputs, up to eight color attachments, an optional depth attachment and optional explicit predecessor names. Texture extents are fixed or follow the current viewport; supported formats are the current renderer's RGBA/BGRA8, RGBA16F, depth24plus and depth32float formats. Resource versions use distinct logical names.

Each logical texture has at most one writer in a frame. Compilation rejects duplicate names/references, unknown dependencies/resources, uninitialized owned reads/loads, sampled discarded outputs, same-pass sampling of an attachment, attachment format mismatches, multiple writers, cycles, unused resources and descriptor/collection limits. An imported resource without a writer is a caller-supplied initialized input. Imported attachment loads likewise require initialized content supplied by the owner.

Dependencies include both explicit predecessor edges and producer-to-consumer edges. Topological order has deterministic name-based tie breaking. The immutable owned plan includes each texture's writer, sample/attachment usage and first/last pass index. The backend retains all targets across frames; lifetime intervals do not currently enable aliasing or transient allocation.

#Target ownership and encoding

WebGPURenderGraph owns only internal textures/views. Imported views are borrowed for one encode call and are never destroyed or retained by the graph. The canvas output remains owned by the canvas context. Every required imported view and pass handler is resolved before any pass begins. Handler reads must have been declared for that pass; their access function expires when the handler returns. A thrown handler ends its render pass and aborts encoding; callers must discard that command encoder rather than submitting a partial frame.

Viewport resizing validates dimensions against the device and the engine's 16,384 limit. It also checks a default 128 MiB estimated texel budget for both the accepted resource set and the temporary old-plus-new set. Fixed-size targets are reused. All viewport replacements and their views are prepared before swapping ownership; synchronous allocation/view failures destroy only newly created candidates and preserve the previous set. Generation increments only after accepting a changed target set. Identical dimensions allocate nothing.

The player resizes only while its single submission slot is idle. It replaces the postprocess bind group when viewport targets change; the fixed shadow target and its bind group remain valid. Disposal releases owned targets once. Asynchronous GPU validation/out-of-memory/device-loss handling remains the renderer's existing failure path; this graph does not claim transactional recovery from those faults.

The production plan owns a 2,048² shadow depth texture plus viewport HDR and depth textures. Logical footprint is 2048 × 2048 × 4 + width × height × 12 bytes: 18,505,216 bytes at 480 × 300, and 19,542,016 bytes at 640 × 360. The estimate excludes canvas output, buffers, shader/pipeline storage, driver padding and implementation-specific depth allocation. It is not a measurement of physical VRAM. Retaining old and new targets briefly during resize is included in admission accounting.

F2 and playtest exports show the compiled pass order and estimated target bytes. Observatory worker reports include the immutable graph and allocation census. GPU buffer count remains six; owned target count becomes three on the first frame and returns to zero on disposal.

#Verification and scope

The headless graph suite checks stable dependency/lifetime results, malformed graph rejection, ownership isolation, explicit and imported dependencies, thirty alternating resizes, fixed-target retention, peak-budget rejection, injected synchronous allocation/view failures, borrowed bindings, scoped reads and exactly-once cleanup. Mock GPU ownership checks establish CPU behavior; they do not simulate driver memory allocation.

The actual browser worker checks pass order, target dimensions/bytes/generations across resize and restoration, device loss and disposal, as well as the existing ECS/prefab rebuild controls and eighteen culling/reference pixel comparisons. Clean source bd0488f passes all 163 headless tests and 23 browser scenarios. The worker report records the actual target census and graph on that clean build, alongside the complete player/multiplayer regression acceptance.

This foundation does not implement resource aliasing, buffer/compute graph nodes, automatic device recreation, shader hot replacement, texture imports, transparent sorting, portals or a public material graph. Those and hardware-device acceptance remain part of the full renderer/graphics roadmap.

End of Render graph