Symbols in this topic
Target
Offscreen render target abstraction used by passes, draws, bundles, and ping-pong resources. Targets own size, color formats, optional depth, MSAA resolve textures, and readback. Canvas-backed targets are Surface instances created with surface(gpu, canvas).
Import
import type { Target, TargetOptions, TargetTextureOptions, PingPongTargets, PingPongStorage } from "vgpu";Signature
import type { ClearColor } from "vgpu";
import type { ResourceDestroyCallback, ResourceIdentity, Texture, UnsubscribeResourceDestroy } from "vgpu/core";
interface TargetTextureOptions {
readonly format?: GPUTextureFormat;
readonly colors?: readonly { readonly format: GPUTextureFormat }[];
readonly depth?: boolean | GPUTextureFormat;
readonly msaa?: boolean | 4;
readonly label?: string;
}
interface TargetOptions extends TargetTextureOptions {
readonly size: readonly [number, number];
}
interface Target {
readonly gpu: unknown;
readonly size: readonly [number, number];
readonly texelSize: readonly [number, number];
readonly color: Texture;
readonly colors: readonly [Texture, ...Texture[]];
readonly depth?: Texture;
readonly format: GPUTextureFormat;
readonly sampleCount: 1 | 4;
readonly resourceIdentity: ResourceIdentity;
resize(size: readonly [number, number]): void;
read(): Promise<Uint8Array>;
readFloats(): Promise<Float32Array>;
onDestroy(cb: ResourceDestroyCallback<Target>): UnsubscribeResourceDestroy;
renderPassDescriptor(opts?: {
readonly clear?: ClearColor;
readonly preserve?: boolean;
readonly clearDepth?: number;
readonly clearStencil?: number;
readonly depthReadOnly?: boolean;
}): GPURenderPassDescriptor;
}
interface PingPongTargets { readonly read: Target; readonly write: Target; swap(): void; }
interface PingPongStorage { readonly read: import("vgpu").StorageBuffer; readonly write: import("vgpu").StorageBuffer; swap(): void; }Parameters
| Param | Type | Required | Default | Notes |
|---|---|---|---|---|
| target.opts | TargetOptions | ✔ | — | Creates an offscreen target. size is mandatory. |
| opts.size | readonly [number, number] | ✔ | — | Initial offscreen texture size in physical pixels. |
| opts.format | GPUTextureFormat | ✖ | "rgba8unorm" | Used for single-color targets when colors is omitted. |
| opts.colors | readonly { format: GPUTextureFormat }[] | ✖ | [{ format: opts.format ?? "rgba8unorm" }] | Multiple render targets (MRT): one attachment per entry, all written by one pass — the G-buffer layout for deferred shading. target.color is colors[0]. |
| opts.depth | boolean | GPUTextureFormat | ✖ | undefined | true means "depth24plus"; a string uses that depth format; omitted means no depth. Combined depth-stencil formats such as "depth24plus-stencil8" are supported; stencil-only "stencil8" is rejected. |
| opts.msaa | boolean | 4 | ✖ | false / sample count 1 | Only true or 4 enables MSAA, creating color/depth attachments with sample count 4 and resolving to sampleable .color(s). |
| opts.clearColor | ClearColor | ✖ | [0, 0, 0, 1] | Default clear color of this target, used by passes that clear without naming one. Writable at runtime as target.clearColor; a pass clear color still wins for that pass. Four finite numbers, or a GPUColor object. |
| opts.label | string | ✖ | undefined | Prefix for created texture labels. |
| target.resize.size | readonly [number, number] | ✔ | — | Recreates offscreen textures unless size is unchanged. |
| target.read | — | — | — | No parameters; reads target.color and returns its raw unpadded texel bytes (4 per texel for rgba8unorm, 8 for rgba16float, 16 for rgba32float). bgra8unorm / bgra8unorm-srgb are supported and swizzled to RGBA, matching canvas preferred formats on platforms such as macOS. |
| target.readFloats | — | — | — | No parameters; reads target.color and decodes it to one f32 per component — the HDR readback for rgba16float / rgba32float targets. unorm8 formats decode to [0, 1]. |
| target.onDestroy.cb | ResourceDestroyCallback<Target> | ✔ | — | Subscribes to target destruction. |
| target.renderPassDescriptor.clear | ClearColor | ✖ | [0, 0, 0, 1] | Clear color for all color attachments unless preserve is true. Frame.pass supplies target.clearColor for omitted/true clears and a per-pass color when provided. |
| target.renderPassDescriptor.preserve | boolean | ✖ | false | Optional implementer hook used by Frame.pass({ clear: false }); when true, color and depth attachments should load existing contents and omit clear values. |
| target.renderPassDescriptor.clearDepth | number | ✖ | 1 | Depth clear value used when the pass clears. Frame.pass supplies FramePassOptions.clearDepth; ignored while preserving and on targets without depth. |
| target.renderPassDescriptor.clearStencil | number | ✖ | 0 | Stencil clear value used when the pass clears. Frame.pass supplies FramePassOptions.clearStencil; ignored while preserving and on targets whose depth format has no stencil aspect. |
| target.renderPassDescriptor.depthReadOnly | boolean | ✖ | false | When true, the depth-stencil attachment is built with depthReadOnly: true and omits depthLoadOp/depthStoreOp, as WebGPU requires for read-only aspects; formats with a stencil aspect also set stencilReadOnly: true and omit the stencil ops. Frame.pass supplies FramePassOptions.depthReadOnly. |
| pingPong.width | number | ✔ | — | Floored and clamped to at least 1. |
| pingPong.height | number | ✔ | — | Floored and clamped to at least 1. |
| pingPong.opts | TargetTextureOptions | ✖ | {} | Texture options for both targets. Size is intentionally not accepted; positional width/height win. |
| pingPongStorage.bytes | number | ✔ | — | Creates two "read-write" storage buffers. |
Returns: target(gpu) returns Target; resize() returns void; read() returns Promise<Uint8Array>; readFloats() returns Promise<Float32Array>; renderPassDescriptor(opts?) returns a WebGPU render pass descriptor; pingPong(gpu) returns PingPongTargets; pingPongStorage(gpu) returns PingPongStorage.
Throws: VGPU-CORE-UNSUPPORTED-FORMAT when read() / readFloats() runs on a color format outside the readback table (see Texture); VGPU-TARGET-SIZE-REQUIRED when runtime JS calls target(gpu) without size; VGPU-TARGET-MSAA-INVALID when runtime JS passes an unsupported msaa value (only true / 4 are accepted); VGPU-TARGET-DEPTH-STENCIL-ONLY when depth receives the stencil-only "stencil8" format (stencil-only depth targets are not supported yet); VGPU-RING1-UNSUPPORTED when msaa: true / 4 with rgba16float is used on a Dawn compatibility-mode device; underlying core texture/readback operations can throw native WebGPU validation errors.
Examples
import { init, effect, frame, target } from "vgpu/mock";
const gpu = await init();
const scene = target(gpu, { size: [128, 128], format: "rgba16float", depth: true, msaa: true });
const post = effect(gpu, `
@fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f { return vec4f(uv, 0, 1); }
`);
frame(gpu, (currentFrame) => {
currentFrame.pass({ target: scene, clear: [0, 0, 0, 1] }, (pass) => pass.draw(post));
});import { init, draw, frame, target } from "vgpu/mock";
const gpu = await init();
// G-buffer for deferred shading: albedo, normals, material parameters.
const gbuffer = target(gpu, {
size: [512, 512],
colors: [{ format: "rgba8unorm" }, { format: "rgba16float" }, { format: "rgba8unorm" }],
depth: true,
});
const fill = draw(gpu, {
shader: `
@vertex fn vs_main(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
var p = array<vec2f, 3>(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));
return vec4f(p[vi], 0, 1);
}
struct GBuffer { @location(0) albedo: vec4f, @location(1) normal: vec4f, @location(2) material: vec4f }
@fragment fn fs_main() -> GBuffer {
return GBuffer(vec4f(0.8, 0.2, 0.2, 1), vec4f(0, 0, 1, 0), vec4f(0.5, 0.1, 0, 0));
}
`,
});
frame(gpu, (currentFrame) => {
currentFrame.pass(gbuffer, fill); // one draw fills all three attachments
});One geometry pass fills every G-buffer attachment; a later lighting effect samples them as gbuffer.colors[0]–[2]. Per-attachment blend/write-mask overrides for MRT draws live on DrawOptions.colors.
import { init, effect, surface, target } from "vgpu/mock";
const gpu = await init();
const canvasSurface = surface(gpu, mockCanvas());
const bloomSize = (w: number, h: number): [number, number] => [w / 2, h / 2];
const bloom = target(gpu, { size: bloomSize(canvasSurface.size[0], canvasSurface.size[1]) });
const bright = effect(gpu, `
struct Params { resolution: vec2f }
@group(0) @binding(0) var<uniform> params: Params;
@fragment fn fs_main() -> @location(0) vec4f { return vec4f(1); }
`, { set: { params: { resolution: bloom.size } } });
canvasSurface.onResize(({ width, height }) => {
bloom.resize(bloomSize(width, height));
bright.set({ params: { resolution: bloom.size } });
});
function mockCanvas(): HTMLCanvasElement {
return {
width: 10,
height: 10,
clientWidth: 10,
clientHeight: 10,
getContext() { return { configure() {}, unconfigure() {}, getCurrentTexture() { return { createView: () => ({}) }; } }; },
} as unknown as HTMLCanvasElement;
}import { init, effect, frame, target } from "vgpu/mock";
const gpu = await init();
// HDR target: readFloats() decodes the half-float texels, read() would hand back raw bytes.
const hdr = target(gpu, { size: [64, 64], format: "rgba16float" });
const bloom = effect(gpu, `@fragment fn fs_main() -> @location(0) vec4f { return vec4f(4.0, 2.0, 1.0, 1.0); }`);
frame(gpu, (currentFrame) => currentFrame.pass(hdr, bloom));
const floats = await hdr.readFloats(); // Float32Array, 64 * 64 * 4 components
console.log(floats[0]); // 4 — values above 1 survive the readbackimport { init, effect, frame, pingPong } from "vgpu/mock";
const gpu = await init();
const pair = pingPong(gpu, 32.9, 32.1, { format: "rgba8unorm" });
const blur = effect(gpu, `@fragment fn fs_main() -> @location(0) vec4f { return vec4f(1); }`);
frame(gpu, (currentFrame) => {
currentFrame.pass({ target: pair.write, clear: false }, (pass) => pass.draw(blur));
});
pair.swap();Notes
- Choose
Targetfor offscreen intermediates that must be reused, sampled, read back, or ping-ponged; chooseSurfaceonly for the canvas swapchain (seeSurface). A target can be rendered in multiple passes and sampled by later effects. - Use simple
formatfor one color attachment. Usecolorswhen a pass writes multiple attachments (MRT/G-buffer), then consumetarget.colors[i]in later lighting/post passes. - Set
depth: truefor ordinary z-testing; choosedepth: "depth24plus-stencil8"when stencil masking is required. Enablemsaa: true/4for anti-aliased 3D geometry, but do not combine MSAA withclear: falsepreservation ordepthReadOnly: the internal multisample render attachments (including depth) are discarded, while the resolved.color(s)remain sampleable/readable. target.read()/target.readFloats()are intended for tests, snapshots, and diagnostics—not a per-frame hot path. For iterative simulation or post-processing, usepingPong(gpu, ...)and swap targets instead of readback.- There is no global resolution binding. Pass
target.sizeortarget.texelSizeexplicitly to shaders. Surface.colorwraps the canvas current texture; offscreen target colors are stable until resize/destroy.target.read()andsurface.read()return raw texel bytes in the target's own color format, with row padding removed and BGRA canvas formats swizzled to RGBA. Forrgba8unormtargets that is exactly the previous RGBA byte layout.- Float targets (
rgba16float,rgba32float,r16float,r32float,rg16float,rg32float) read back throughtarget.readFloats(), which decodes half/float texels into aFloat32Arrayof components — HDR values above1and negatives are preserved.readFloats()also works onunorm8targets (normalized to[0, 1]), so tooling can stay format-agnostic. - Custom
Targetimplementers must providereadFloats(); delegating tothis.color.readFloats()(astarget(gpu)andsurface(gpu)do) is enough. - Size-dependent targets derived from a surface should be created from the real initial
surface.sizeand resized fromsurface.onResize(...). - Custom
Targetimplementers should honor the optionalrenderPassDescriptor(opts?)options-bag fields to participate inFrame.pass({ clear: false }),FramePassOptions.clearDepth,FramePassOptions.clearStencil, andFramePassOptions.depthReadOnly; implementations that ignore a field will clear (with depth1, stencil0) instead. - Depth formats with a stencil aspect (
"depth24plus-stencil8","depth32float-stencil8") emitstencilLoadOp/stencilStoreOpon the pass depth-stencil attachment, mirroring the depth load/store behavior withstencilClearValuefromFramePassOptions.clearStencil(default0), as WebGPU requires when the stencil aspect is writable. target.depthis created withtexture_bindingusage in addition torender_attachment, so it can be bound withset()as a sampled depth texture — including inside the same pass when it opened withFramePassOptions.depthReadOnly.- See also:
Surface,FramePassOptions,Effect,Draw,Bundle,Computestorage ping-pong.