vgpu1 symbolView source ↗

Symbols in this topic

geometry()

Creates an immutable vertex/index layout plus mutable GPU buffers for draw(gpu). The v2 descriptor form accepts named attributes, multiple vertex streams, instancing streams, indices, topology, writes, and slices. Existing scene geometry descriptors such as box() continue to work as primitive sugar.

Import

TypeScript
1
2
import type { Geometry, GeometryOptions, GeometryBufferOptions, GeometrySliceOptions } from "vgpu";
import { box } from "vgpu/scene";

Signature

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
interface Gpu {
  geometry(geometry: import("vgpu/scene").SceneGeometry): Geometry;
  geometry(options: GeometryOptions): Geometry;
}
 
type GeometryData = ArrayBuffer | ArrayBufferView;
type GeometryAttributes = {
  readonly [name: string]: GPUVertexFormat | GeometryAttributeOverride;
};
 
interface GeometryAttributeOverride {
  readonly format: GPUVertexFormat;
  readonly offset?: number;
  readonly location?: number;
}
 
interface GeometryBufferOptions {
  readonly attributes: GeometryAttributes;
  readonly data?: GeometryData;
  readonly buffer?: GPUBuffer;
  readonly stride?: number;
  readonly stepMode?: GPUVertexStepMode;
  readonly label?: string;
}
 
interface GeometryOptions {
  readonly buffers: readonly GeometryBufferOptions[];
  readonly vertexCount?: number;
  readonly instanceCount?: number;
  readonly indices?: Uint16Array | Uint32Array | readonly number[];
  readonly indexBuffer?: GPUBuffer;
  readonly indexFormat?: GPUIndexFormat;
  readonly indexCount?: number;
  readonly topology?: GPUPrimitiveTopology;
  readonly label?: string;
}
 
interface GeometryBuffer {
  readonly gpu: GPUBuffer;
  readonly stride: number;
  readonly stepMode: GPUVertexStepMode;
  write(data: GeometryData, byteOffset?: number): void;
}
 
interface GeometrySliceOptions {
  readonly firstIndex?: number;
  readonly indexCount?: number;
  readonly baseVertex?: number;
  readonly firstVertex?: number;
  readonly vertexCount?: number;
  readonly instanceCount?: number;
  readonly label?: string;
}
 
interface Geometry {
  readonly vertexCount?: number;
  readonly indexCount?: number;
  readonly instanceCount?: number;
  readonly vertexBuffers?: readonly GPUBuffer[];
  readonly indexBuffer?: GPUBuffer;
  readonly indexFormat?: GPUIndexFormat;
  readonly vertexBufferLayouts?: readonly GPUVertexBufferLayout[];
  readonly topology: GPUPrimitiveTopology;
  readonly buffers: readonly GeometryBuffer[];
  slice(opts?: GeometrySliceOptions): GeometrySlice;
  write(data: GeometryData, byteOffset?: number): void;
  writeIndices(data: Uint16Array | Uint32Array, byteOffset?: number): void;
  destroy(): void;
}
 
interface GeometrySlice extends GeometryLike {
  readonly geometry: Geometry;
  readonly firstIndex?: number;
  readonly indexCount?: number;
  readonly baseVertex?: number;
  readonly firstVertex?: number;
  readonly vertexCount?: number;
  readonly instanceCount?: number;
}
 
interface DrawCallOptions {
  /** Non-indexed count. Precedence: call > slice > geometry > DrawOptions > 3. */
  readonly vertices?: number;
  /** Non-indexed start. Precedence: call > slice > 0. */
  readonly firstVertex?: number;
  /** Indexed count. Precedence: call > slice > geometry. */
  readonly indices?: number;
  /** Indexed start. Precedence: call > slice > 0. */
  readonly firstIndex?: number;
  /** Indexed base vertex. Precedence: call > slice > 0. */
  readonly baseVertex?: number;
}

Parameters

FieldTypeRequiredDefaultNotes
geometrySceneGeometryv1 primitive sugar path. Emits pinned locations: position@location(0), normal@location(1), uv@location(2).
options.buffersreadonly GeometryBufferOptions[]Vertex buffer streams. Maximum 8.
buffer.attributesGeometryAttributesRecord form only. Key is the WGSL vertex input name unless location is specified. Maximum 16 attributes total.
attribute formatGPUVertexFormatShorthand value: { position: "float32x3" }.
attribute.offsetnumbertight-packed orderByte offset within the stream. Integer-like attribute keys are rejected to avoid JavaScript key reordering.
attribute.locationnumbershader name matchExplicit shader location. When present, the record key is only a label.
buffer.dataGeometryDataCreates an owned ["vertex", "copy_dst"] buffer and uploads the initial data. Mutually exclusive with buffer.
buffer.bufferGPUBufferCaller-owned escape hatch. Mutually exclusive with data; not destroyed by geometry.destroy().
buffer.stridenumberroundUp4(sum(format sizes))Explicit stride for padded/interleaved data. Must be valid for WebGPU vertex buffers.
buffer.stepMode"vertex" | "instance""vertex"Instance streams derive geometry.instanceCount from the first instance buffer with data.
options.vertexCountnumberderivedDerived from the first vertex-step buffer with data.
options.instanceCountnumberderivedDraw default after DrawCallOptions.instances and DrawOptions.instances.
options.indicesUint16Array | Uint32Array | readonly number[]Creates an owned ["index", "copy_dst"] index buffer. Uint16Array infers "uint16"; otherwise "uint32".
options.indexBufferGPUBufferCaller-owned index buffer escape hatch. Pair with indexFormat and indexCount.
options.topologyGPUPrimitiveTopology"triangle-list"Pipeline-affecting geometry topology. Strip topologies derive stripIndexFormat from indexFormat.
geometry.slice.optsGeometrySliceOptionsfull rangeFrozen range view sharing buffers and layout identity with the parent geometry.
geometry.write.dataGeometryDataWrites to buffer 0 using queue.writeBuffer. No resize.
geometry.writeIndices.dataUint16Array | Uint32ArrayWrites to an index buffer owned from options.indices. Write caller-owned indexBuffer objects directly. No resize.

Returns: geometry(gpu) returns Geometry; geometry.slice() returns GeometrySlice; write(), writeIndices(), and destroy() return void.

Error codes

CodeWhenFix
VGPU-MESH-LAYOUT-INVALIDInvalid stride/offset/format, both data and buffer, or integer-like attribute key.Use record keys that are names, align offsets/strides, and choose one data source.
VGPU-MESH-LIMIT-EXCEEDEDMore than 8 buffers or 16 attributes.Split draws or reduce streams/attributes.
VGPU-MESH-LOCATION-CONFLICTDuplicate explicit location values.Give each explicit shader location once.
VGPU-MESH-DATA-MISALIGNEDData byte length is not divisible by stride, or index bytes do not match format.Repack data or pass an explicit stride.
VGPU-MESH-RANGE-INVALIDSlice or draw-time range is negative, non-integer, outside parent counts, or uses index ranges on non-indexed geometries.Clamp ranges and use indexed fields only with indexed geometries.
VGPU-MESH-WRITE-RANGEwrite() or writeIndices() would overflow the fixed buffer.Create a larger geometry; writes do not resize buffers.
VGPU-MESH-ATTRIBUTE-UNMATCHEDNamed geometry attribute has no vertex-stage shader input.Rename the attribute or specify location.
VGPU-MESH-INPUT-MISSINGShader declares an uncovered @location input.Add the geometry attribute or remove the shader input.
VGPU-MESH-FORMAT-MISMATCHVertex format base type does not match the WGSL input base type.Use a compatible GPUVertexFormat; width differences are allowed by WebGPU.

Examples

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { init, geometry } from "vgpu/mock";
 
const gpu = await init();
const positions = new Float32Array([
  -1, -1, 0,
   1, -1, 0,
   0,  1, 0,
]);
 
const triangle = geometry(gpu, {
  buffers: [{
    data: positions,
    attributes: { position: "float32x3" },
  }],
});
TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { init, geometry } from "vgpu/mock";
 
const gpu = await init();
const ledVertices = new Float32Array(6 * 6);
const ledGeometry = geometry(gpu, {
  label: "triangle-led-front-led-emitters",
  buffers: [{
    data: ledVertices,
    stride: 24,
    attributes: {
      position: "float32x2",
      local: "float32x2",
      led_index: "float32",
    },
  }],
});
TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { init, geometry } from "vgpu/mock";
 
const gpu = await init();
const quadCorners = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
const instanceData = new Float32Array(4 * 10);
const particles = geometry(gpu, {
  topology: "triangle-strip",
  buffers: [
    { data: quadCorners, attributes: { corner: "float32x2" } },
    { stepMode: "instance", data: instanceData, attributes: {
      i_pos: "float32x3",
      i_color: { format: "unorm8x4", location: 5 },
    } },
  ],
});
TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { init, draw, geometry } from "vgpu/mock";
 
const gpu = await init();
const vertexData = new Float32Array(3 * 4500);
const allIndices = new Uint32Array(4500);
const gltfGeometry = geometry(gpu, {
  buffers: [{ data: vertexData, attributes: { position: "float32x3" } }],
  indices: allIndices,
});
const hull = gltfGeometry.slice({ firstIndex: 0, indexCount: 3600 });
const glass = gltfGeometry.slice({ firstIndex: 3600, indexCount: 900, label: "glass" });
const pbrWgsl = "@vertex fn vs_main(@location(0) position: vec3f) -> @builtin(position) vec4f { return vec4f(position, 1); }";
 
draw(gpu, { shader: pbrWgsl, geometry: hull });
draw(gpu, { shader: pbrWgsl, geometry: glass, blend: "alpha" });
TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { init, draw, geometry, target } from "vgpu/mock";
 
const gpu = await init();
const glyphQuads = new Float32Array(4 * 4);
const quadIndices = new Uint16Array([0, 1, 2, 2, 1, 3]);
const text = geometry(gpu, {
  buffers: [{ data: glyphQuads, attributes: { pos: "float32x2", uv: "float32x2" } }],
  indices: quadIndices,
});
const sdfTextWgsl = "@vertex fn vs_main(@location(0) pos: vec2f, @location(1) uv: vec2f) -> @builtin(position) vec4f { return vec4f(pos, 0, 1); }";
const textDraw = draw(gpu, { shader: sdfTextWgsl, geometry: text });
const colorTarget = target(gpu, { size: [640, 480] });
 
text.write(new Float32Array(4 * 4));
text.writeIndices(new Uint16Array([0, 1, 2, 2, 1, 3]));
textDraw.draw({ target: colorTarget, indices: 6 });
TypeScript
1
2
3
4
5
import { init, geometry } from "vgpu/mock";
import { box } from "vgpu/scene";
 
const gpu = await init();
const cube = geometry(gpu, box({ size: 2 }));

Notes

  • Layout is immutable; data is mutable. Changing formats, strides, topology, index format, or buffer identities requires a new geometry and a new draw.
  • Auto stride is tight-packed and rounded up to 4 bytes. It never guesses padded data; pass stride when a writer emits padding.
  • Slices share parent buffers and the same vertexBufferLayouts array identity so pipelines are shared.
  • Draw-time range overrides use DrawCallOptions.indices, firstIndex, and baseVertex; non-indexed draws use existing vertices and firstVertex.
  • Bundles bake counts and ranges at record time. Dynamic per-frame ranges need direct draws or bundle re-recording.
  • destroy() only destroys buffers owned from data/indices; caller-owned buffer and indexBuffer remain caller-owned.