Documentation
    Preparing search index...

    The WebGPU backend (§62 backend 1).

    See wgpu-bindings.ts for the layout and the argument. The consequence here is the shape of a frame: the whole frame's uniform blocks are packed into one CPU staging array while the command encoder records, uploaded with a single queue.writeBuffer before submit, and read back by the recorded draws through their dynamic offsets. Queue ordering makes that safe — a writeBuffer enqueued before a submit is visible to it — and it means a frame issues exactly one buffer upload however many objects it draws.

    The staging array and the GPU buffer are sized before recording, from an upper bound (one block per view, plus one per view per item), so no reallocation can happen mid-frame and invalidate the bind group the pass has already been told to use. They only ever grow.

    WebGPU's scissor and viewport are pass commands, not ambient device state, so all the views of a frame are recorded into a single render pass with setViewport / setScissorRect between them. This is the one place the WebGPU backend is structurally safer than the GL one: there is no state mirror to keep, nothing to restore in a finally, and a draw that throws cannot leave state behind for the next frame — the pass is simply never submitted.

    Implements

    Index
    batching: WgpuRenderBatching | null = null

    §65 batching, or null (the default) to batch nothing — the opt-in seam R-9 recorded for the GL backend, restated here byte for byte: the field is read once per frame, the whole no-batching cost is one comparison per item, and this class names the uploader import type only, so a bundle that never calls createWgpuBatching never links it.

    import { createWgpuBatching } from "@fourjs/render-webgpu";
    renderer.batching = createWgpuBatching();

    The §6b channel required by Renderer — contextlost (see the module header).

    renderTime: number = 0

    §9 render time in seconds, read by §60 node graphs containing a time node (RFC 0001) — and by nothing else in this backend. 0 by default, so a time-less application pays nothing; the application (or Application's frame loop) writes it before render. Never simulation time (§42/§43) — WebglRenderer.renderTime's contract, verbatim.

    statistics: RenderStatistics | null = null

    §84's render counters (A-1). null until an application assigns a record; the frame reads this once and every call site is guarded by that one comparison, so a renderer with statistics off issues exactly the commands it always did.

    • Runs one §82 ComputePass (WP-R1.8): compiles the kernel on first use (cached on the source — wgpu-compute.ts), binds the storage buffers at @group(0) in array order, and submits the dispatch.

      This is Renderer.compute?() — the R-1 plan's Q3 promotion, executed 2026-08-29: the descriptor now lives in @fourjs/render (compute.ts there owns the story) and this method implements the optional interface member. Presence is the capability, the statistics/renderEffect pattern: WebGL 2 has no compute and never grows this member, and §62's computeShaders capability is how an application asks before reaching for it. The frame path never calls it — §82's "basic graphics … must not require compute support", held structurally. A buffer another backend minted is refused per binding (the promoted handle is structural; wgpu-compute.ts).

      Throws a FourError: INVALID_APPLICATION_STATE on a disposed or never-initialized renderer, for non-integer workgroup counts, or for a disposed buffer in bindings; DEVICE_LOST while lost; UNSUPPORTED_GPU_FEATURE on a device surface without the compute entry points. A kernel that fails to compile does not throw — WebGPU surfaces compilation failure through the device's error scopes and the dispatch does nothing, the pipeline cache's recorded reading of the error model.

      Parameters

      Returns void

    • Creates the device residency of one §36 simulation: "gpu" particle system (R-31 wiring, 2026-08-29) — see wgpu-particle-simulation.ts's header for the design: flat-lane storage buffers, the WP-R1.8 integrator kernel, moveSlot compaction mirroring, and the position buffer doubling as the draw's instance stream.

      The application binds the result to its emitter (emitter.bindGpuSimulation(...)) and owns its disposal (§83, the createPickingService ownership rule). This renderer registers it under options.systemId — the emitting node's id — and the §36 draw arm joins on that key: a frame drawing a particle item whose id has a live registered simulation binds the simulation's position buffer as the per-instance position stream (gpuInstances pipeline variant) instead of the interleaved CPU lanes; every other particle item, and every pre-existing scene, draws the landed CPU path byte-identically.

      One simulation per system id: a duplicate registration is refused — two residencies for one node would make the draw's join ambiguous — dispose the old one first.

      Throws a FourError: INVALID_APPLICATION_STATE on a disposed or never-initialized renderer, a duplicate systemId, or a non-positive capacity; DEVICE_LOST while lost; UNSUPPORTED_GPU_FEATURE on a device surface without the §82 compute entry points or copyBufferToBuffer — which is how a backendless §36 GPU emitter fails at authoring time rather than drawing a wrong picture (§85; §62's computeShaders is the capability to ask first).

      Parameters

      Returns WgpuParticleSimulation

    • Builds a PickingService over this renderer — §71's "gpu" tier (RFC 0005), gated on registerPickingPipeline() exactly as node-material draws are gated on registerWebgpuNodeMaterialPipeline(): this method resolves the registry slot and refuses (§85) when nothing registered, so the id pipeline, the service, and its mapAsync read-back live only in bundles that opted in (the pipeline-cost law; wgpu-picking-registry.ts).

      What the service receives is a live window onto exactly the renderer state an id pass needs — device, the three shared caches (geometry, particles, render targets), the surface size, and the two lifecycle flags — as accessors, so a §61 loss's dropped caches are seen rather than captured stale (PickingRendererHost). Each call builds an independent service; the caller owns and disposes it (§83). No GPU call is issued here — the id pipeline compiles on the service's first pass.

      Returns PickingService

      FourError INVALID_APPLICATION_STATE on a disposed renderer, or when no picking pipeline is registered.

    • Releases every GPU resource this renderer owns (§83).

      Idempotent and terminal, and succeeds while the device is lost — the caches drop their records without calling into a device that is gone. Every other method throws INVALID_APPLICATION_STATE afterwards, as the other two backends do.

      Returns void

    • Acquires an adapter, a device and the canvas's "webgpu" context (§61, §45).

      Rejects with a FourError carrying RENDERER_INITIALIZATION_FAILED (§89) when there is no canvas, no navigator.gpu, no adapter (which is what a browser without the WebGPU flag reports — navigator.gpu exists and requestAdapter() resolves null; see register.ts), no device, or when the canvas will not give up a WebGPU context. Calling it twice rejects with INVALID_APPLICATION_STATE.

      Nothing is compiled here. Pipelines are created lazily on first use, which is a deliberate departure from the WebGL backend's compile-at-init — wgpu-pipeline-cache.ts carries the measured argument.

      Parameters

      Returns Promise<void>

    • Reads a compute buffer back as a tightly packed ArrayBuffer (§82) — copyBufferToBuffer + mapAsync, readPixels' honestly-asynchronous contract for a buffer: rejects rather than skips, with the same codes (INVALID_APPLICATION_STATE for a disposed renderer, one never initialized, or a disposed buffer; DEVICE_LOST while lost; UNSUPPORTED_GPU_FEATURE on a device surface without the entry points).

      Parameters

      Returns Promise<ArrayBuffer>

    • Reads back target's colour attachment — or the region rectangle of it — as tightly packed RGBA8 bytes: §61's readPixels (WP-R1.6 shipped the whole-target form; the region form arrived 2026-08-29 with Rectangle2 in @fourjs/math, RFC 0005's named prerequisite, cleared).

      Asynchronous, honestly and permanently. WebGPU has no synchronous readback — copyTextureToBuffer + mapAsync is the only path (probe- verified; wgpu-readback.ts) — and §61's own sketch types the member Promise<ArrayBuffer>, which is the RFC 0005 argument this method is the standing evidence for. The result is width * height * 4 bytes (the region's, when given; the target's otherwise), rows bottom-to-top (§7a's Y-up; wgpu-readback.ts records the decision). region is measured in target texels from the bottom-left corner, the same space the rows are defined in; a region copies only its own rectangle.

      A target that was never rendered into reads back its zero-filled allocation — transparent black, the same defined answer sampling one gives. Unlike the frame methods this one rejects rather than skips: it is not called inside a frame, its caller is awaiting a value, and a silently empty buffer would be undefined content by another name. Rejections carry a FourError: INVALID_APPLICATION_STATE for a disposed renderer, one never initialized, or a disposed target; DEVICE_LOST while the device is lost (§89's "a caller asks for something that cannot be satisfied while lost"); UNSUPPORTED_GPU_FEATURE on a device double without the readback entry points (their presence is the capability — webgpu-device.ts). A malformed region — fractional, empty, or hanging off the target — rejects with validateReadbackRegion's RangeError (§85, @fourjs/render's shared check, so both backends refuse with the same words).

      Parameters

      Returns Promise<ArrayBuffer>

    • Draws root's subtree once per viewport, in array order (§61, §48, §64).

      The per-view sequence is: viewport rectangle, scissor rectangle, the clear draw (wgpu-unlit.ts explains why a clear is a draw here), then this view's items. §61's shared clear semantics hold unchanged — colour is cleared only where the view carries a clearColor, depth is cleared for every view, and both are confined to the rectangle.

      Items this tier has no pipeline for are skipped (see the module header), as is a draw whose geometry will not upload. Returns without drawing while the device is lost, when views is empty, and when target is a disposed render target (§83's "disposed resources still in use" — the frame must not draw into a released surface). An off-screen frame (WP-R1.6) attaches the target's own colour and depth views, resolves its viewport rectangles against the target's size, and bakes the target's formats into its pipelines; everything else — clears, culling, the draw arms — is the identical code path, which is what makes "the same scene draws the same way off screen" true by construction.

      Parameters

      Returns void

    • Draws one §70 full-screen effect (WP-R1.6) — pass.source's colour attachment over the whole of pass.target, or of the swap chain, through pass.effect. The normative contract is on @fourjs/render's Renderer.renderEffect; the GL backend's method documents the shared readings and this one differs in exactly two ways, both structural:

      • There is no state envelope. GL borrows the framebuffer binding, the rectangles, the depth test and a texture unit inside a try/finally; here the effect is its own render pass in its own command encoder, and a WebGPU pass has no ambient state to corrupt — the one place this backend is structurally safer, restated for §70. No scissor or viewport is set: a pass's defaults are the whole attachment, which is exactly what "an effect covers its destination surface" means.
      • The effect kind is pipeline identity, not uniform state — a lazy per-(kind × format) pipeline (wgpu-effect.ts carries the inverted R-19 argument), so a chain that only ever copies compiles one module and uploads no uniforms at all; only a grade touches the 16-byte block.

      Everything, on render's terms — a lost device, a source that is not a render-target texture (a caller that bypassed validateEffectRenderPass), a disposed source or destination, an effect kind this backend does not draw (RFC 0001's "graph" kind is drawn since WP-R1.9, through the registered node store; unregistered, it skips — the closed-union rule either way), and the feedback loop: a pass whose destination is the very surface it samples, refused here exactly as R-4 refuses it per draw, with RenderGraph.validate reporting it statically as "feedback" so the mistake is normally caught at setup.

      Parameters

      Returns void

    • Resizes the swap chain to width × height logical pixels at resolution device pixels per logical pixel (§61, §45).

      The depth attachment is not reallocated here: it is allocated lazily by the next frame that needs one, so a burst of resize events during a window drag costs one allocation rather than one per event. Returns quietly while the device is lost, recording the size for whenever a frame runs again.

      Parameters

      • width: number
      • height: number
      • resolution: number = 1

      Returns void

    • Uploads data into a compute buffer at byteOffset (§82) — the per-step params refresh the integrator tier needs, without reallocating.

      The error contract is WebgpuRenderer.createComputeBuffer's, plus INVALID_APPLICATION_STATE for a disposed buffer or an out-of-range or misaligned write.

      Parameters

      Returns void