Practical texture-format matrix
Choose a target format from the operations it must support, not just its channel precision. In vgpu, format is passed through to WebGPU for render-target capability; the API rejects a filtering binding for 32-bit float textures unless the device was initialized with float32-filterable support. target.read() returns raw texel bytes and target.readFloats() decodes them to f32 components, so HDR and scalar targets read back directly.
Common color and scalar formats
| Format | Render target | sampler with linear filtering | Readback | Practical use |
|---|---|---|---|---|
rgba8unorm | Yes | Yes | read() — 4 bytes/texel | Default target; screenshots, debug probes, and LDR color. |
rgba8unorm-srgb | Yes | Yes | read() — 4 bytes/texel | LDR color encoded as sRGB; do color math in linear space. |
rgba16float | Yes | Yes | readFloats() — 4 f32/texel | Filterable HDR color, bloom, and lighting intermediates. |
rgba32float | Yes | No by default | readFloats() — 4 f32/texel | Full-precision HDR/data; requires float32-filterable to bind to a filtering sampler. |
r16float | Yes | Yes | readFloats() — 1 f32/texel | Filterable scalar data such as a height or distance field. |
r32float | Yes | No by default | readFloats() — 1 f32/texel | Full-precision scalar data; requires float32-filterable for linear sampling. |
r8unorm | Yes | Yes | read() — 1 byte/texel | Compact normalized scalar/mask data. |
rg8unorm, rg16float, rg32float, and the bgra8unorm canvas formats read back too; read() always hands back the raw unpadded texel bytes of the format, and readFloats() decodes any of them to a Float32Array of components (unorm8 normalized to [0, 1]). Depth/stencil, packed (rgb10a2unorm, rg11b10ufloat), snorm/uint/sint, and compressed formats still throw VGPU-CORE-UNSUPPORTED-FORMAT.
“Render target” means the format can be requested through target(gpu, { format }) as a color attachment in the normal WebGPU profile; use an adapter that supports the requested format and feature set. rgba32float is renderable even though it is not linearly filterable by default.
Reading HDR and scalar targets
read() returns Uint8Array raw texel bytes for every supported format — 4 bytes per texel for rgba8unorm, 8 for rgba16float, 16 for rgba32float — with row padding removed and BGRA swizzled to RGBA. For anything but 8-bit formats, prefer readFloats(), which decodes half/float texels into a Float32Array of components and preserves values above 1 and below 0:
import { init, target } from "vgpu/node";
const gpu = await init();
const hdr = target(gpu, { size: [256, 256], format: "rgba16float" });
// ...render into hdr...
const floats = await hdr.readFloats(); // 256 * 256 * 4 components, HDR values intact
console.log(floats[0]);An encode pass into an rgba8unorm target (mapping values to [0, 1], for example direction * 0.5 + 0.5) is still the right tool when the goal is a PNG snapshot or a byte-exact visual diff. See Debugging shaders by extracting internal values for that recipe. For formats without a readback layout, blit into a supported format first, or write the data into a storage buffer and use StorageBuffer.read().
Filtering 32-bit float textures
A texture declaration and a sampler must agree. On the default device, bind rgba32float and r32float with a non-filtering sampler and use textureLoad, or select a lower-precision filterable format such as rgba16float/r16float. If the adapter exposes and the application requests float32-filterable, linear sampling becomes valid for the 32-bit float formats.
See also: Effects for chaining targets, and Debugging shaders by extracting internal values for readback workflows.