---
title: Target
description: 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)`.
---

# Target



## Import

```ts
import type { Target, TargetOptions, TargetTextureOptions, PingPongTargets, PingPongStorage } from "vgpu";
```

## Signature

```ts
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

```ts
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));
});
```

```ts
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`.

```ts
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;
}
```

```ts
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 readback
```

```ts
import { 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 `Target` for offscreen intermediates that must be reused, sampled, read back, or ping-ponged; choose `Surface` only for the canvas swapchain (see `Surface`). A target can be rendered in multiple passes and sampled by later effects.
* Use simple `format` for one color attachment. Use `colors` when a pass writes multiple attachments (MRT/G-buffer), then consume `target.colors[i]` in later lighting/post passes.
* Set `depth: true` for ordinary z-testing; choose `depth: "depth24plus-stencil8"` when stencil masking is required. Enable `msaa: true`/`4` for anti-aliased 3D geometry, but do not combine MSAA with `clear: false` preservation or `depthReadOnly`: 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, use `pingPong(gpu, ...)` and swap targets instead of readback.
* There is no global resolution binding. Pass `target.size` or `target.texelSize` explicitly to shaders.
* `Surface.color` wraps the canvas current texture; offscreen target colors are stable until resize/destroy.
* `target.read()` and `surface.read()` return raw texel bytes in the target's own color format, with row padding removed and BGRA canvas formats swizzled to RGBA. For `rgba8unorm` targets that is exactly the previous RGBA byte layout.
* Float targets (`rgba16float`, `rgba32float`, `r16float`, `r32float`, `rg16float`, `rg32float`) read back through `target.readFloats()`, which decodes half/float texels into a `Float32Array` of components — HDR values above `1` and negatives are preserved. `readFloats()` also works on `unorm8` targets (normalized to `[0, 1]`), so tooling can stay format-agnostic.
* Custom `Target` implementers must provide `readFloats()`; delegating to `this.color.readFloats()` (as `target(gpu)` and `surface(gpu)` do) is enough.
* Size-dependent targets derived from a surface should be created from the real initial `surface.size` and resized from `surface.onResize(...)`.
* Custom `Target` implementers should honor the optional `renderPassDescriptor(opts?)` options-bag fields to participate in `Frame.pass({ clear: false })`, `FramePassOptions.clearDepth`, `FramePassOptions.clearStencil`, and `FramePassOptions.depthReadOnly`; implementations that ignore a field will clear (with depth `1`, stencil `0`) instead.
* Depth formats with a stencil aspect (`"depth24plus-stencil8"`, `"depth32float-stencil8"`) emit `stencilLoadOp`/`stencilStoreOp` on the pass depth-stencil attachment, mirroring the depth load/store behavior with `stencilClearValue` from `FramePassOptions.clearStencil` (default `0`), as WebGPU requires when the stencil aspect is writable.
* `target.depth` is created with `texture_binding` usage in addition to `render_attachment`, so it can be bound with `set()` as a sampled depth texture — including inside the same pass when it opened with `FramePassOptions.depthReadOnly`.
* **See also:** `Surface`, `FramePassOptions`, `Effect`, `Draw`, `Bundle`, `Compute` storage ping-pong.


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)