---
title: Uniform
description: Low-level user-owned uniform buffer with a stable bind group at binding `0`. Prefer main API (`vgpu`) `set({ params: ... })` for ordinary values; use `Uniform` when you need byte-level writes or one buffer shared by many draws.
---

# Uniform



## Import

```ts
import { Uniform } from "vgpu/core";
import type { UniformOptions } from "vgpu/core";
```

## Signature

```ts
import type { Device } from "vgpu/core";

declare interface UniformOptions {
  readonly size: number;
  readonly label?: string;
  readonly visibility?: GPUShaderStageFlags;
  readonly bindGroupLayout?: GPUBindGroupLayout;
}

declare class Uniform {
  readonly device: Device;
  readonly size: number;
  readonly buffer: import("vgpu/core").Buffer;
  readonly bindGroupLayout: GPUBindGroupLayout;
  readonly bindGroup: GPUBindGroup;
  constructor(device: Device, opts: UniformOptions);
  get gpu(): GPUBuffer;
  write(data: BufferSource, offset?: number): void;
  destroy(): void;
  dispose(): void;
}
```

## Parameters

| Param                | Type                  | Required | Default                                                                           | Notes                                                                                                   |
| -------------------- | --------------------- | -------: | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| device               | `Device`              |        ✔ | —                                                                                 | Core device, usually `gpu.device` from the main API (`vgpu`) `init()`.                                  |
| opts                 | `UniformOptions`      |        ✔ | —                                                                                 | Buffer/layout options.                                                                                  |
| opts.size            | `number`              |        ✔ | —                                                                                 | Byte size. Used for `device.createBuffer({ usage: ["uniform", "copy_dst"] })` and `minBindingSize`.     |
| opts.label           | `string`              |        ✖ | `undefined`                                                                       | Forwarded to buffer label; layout label becomes `${label}.bgl`; bind group label becomes `${label}.bg`. |
| opts.visibility      | `GPUShaderStageFlags` |        ✖ | `GPUShaderStage.VERTEX \| GPUShaderStage.FRAGMENT` with numeric fallback `1 \| 2` | Ignored when `opts.bindGroupLayout` is supplied.                                                        |
| opts.bindGroupLayout | `GPUBindGroupLayout`  |        ✖ | A new binding-0 uniform layout                                                    | Reuse a pipeline/draw-owned layout. Binding `0` must be a compatible uniform buffer.                    |
| uniform.write.data   | `BufferSource`        |        ✔ | —                                                                                 | Bytes uploaded with `queue.writeBuffer`.                                                                |
| uniform.write.offset | `number`              |        ✖ | `0`                                                                               | Destination byte offset in the buffer.                                                                  |

**Returns:** Constructor returns `Uniform`; `gpu` returns the underlying `GPUBuffer`; `write()`, `destroy()`, and `dispose()` return `void`.

**Throws:** No main API (`vgpu`) `VGPU-*` errors are thrown directly by `Uniform`; invalid sizes, incompatible reused layouts, out-of-range writes, or destroyed-buffer usage can surface as core/native WebGPU validation errors. Binding a `Uniform` through main API (`vgpu`) can still trigger `VGPU-R1-OWNERSHIP-FLIP` if the same shader binding was first set with JS values.

## Examples

```ts
import { init, draw } from "vgpu/mock";
import { Uniform } from "vgpu/core";

const gpu = await init();
const camera = new Uniform(gpu.device, { size: 64, label: "camera" });
camera.write(new Float32Array(16));

const drawable = draw(gpu, { shader: `
  struct Camera { viewProjection: mat4x4f }
  @group(0) @binding(0) var<uniform> camera: Camera;
  @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 camera.viewProjection * vec4f(p[vi], 0, 1);
  }
  @fragment fn fs_main() -> @location(0) vec4f { return vec4f(1); }
` });
drawable.set({ camera });
```

```ts
import { init, draw } from "vgpu/mock";
import { Uniform } from "vgpu/core";

const gpu = await init();
const drawable = draw(gpu, { shader: `
  struct Params { value: f32 }
  @group(0) @binding(0) var<uniform> params: Params;
  @vertex fn vs_main() -> @builtin(position) vec4f { return vec4f(0, 0, 0, 1); }
  @fragment fn fs_main() -> @location(0) vec4f { return vec4f(params.value); }
` });
const params = new Uniform(gpu.device, { size: 16, bindGroupLayout: drawable.layout(0) });
params.write(new Float32Array([1, 0, 0, 0]));
drawable.set({ params });
```

## Notes

* `Uniform` is user-owned from the first `draw.set({ name: uniform })`; vgpu binds its identity and never packs JS values into it.
* It creates a non-dynamic bind group. For many per-object uniforms with dynamic offsets, use `UniformPool` instead.
* Call `destroy()` / `dispose()` when the buffer lifetime ends.
* **See also:** `SharedUniforms`, `StructuredUniform`, `UniformPool`, `Draw.set`, `Effect.set`.


---

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)