Vertex Layouts
Vertex layouts are much like bind group layouts, in that they define the relationship between shaders and buffers. More precisely, they define what vertex attributes a shader expects, and how they are laid out in the corresponding vertex buffer.
Creating a vertex layout
To create a vertex layout, use the tgpu.vertexLayout
function. It takes an array schema constructor, i.e., a function that returns an array schema given the number of elements to render (vertices/instances). To determine what each element of the array corresponds to, you can pass an optional stepMode
argument, which can be either vertex
(default) or instance
.
import tgpu from 'typegpu';import * as d from 'typegpu/data';
const ParticleGeometry = d.struct({ tilt: d.f32, angle: d.f32, color: d.vec4f,});
const geometryLayout = tgpu .vertexLayout((n: number) => d.arrayOf(ParticleGeometry, n), 'instance');
Utilizing loose schemas with vertex layouts
If the vertex buffer is not required to function as a storage or uniform buffer, a loose schema may be used to define the vertex data layout. Loose schemas are not subject to alignment restrictions and allow the use of various vertex formats.
To define a loose schema:
- Use
d.unstruct
instead ofd.struct
. - Use
d.disarrayOf
instead ofd.arrayOf
.
Within a loose schema, both standard data types and vertex formats can be utilized.
const LooseParticleGeometry = d.unstruct({ tilt: d.f32, angle: d.f32, // four 8-bit values, unsigned & normalized // i.e., four integers in (0, 255) represent four floats in the range of (0.0, 1.0) color: d.unorm8x4,});
The size of LooseParticleGeometry
will be 12 bytes, compared to 32 bytes of ParticleGeometry
. This can be useful when you’re working with large amounts of vertex data and want to save memory.
Using vertex layouts
You can utilize root.unwrap
to get the raw GPUVertexBufferLayout
from a typed vertex layout. It will automatically calculate the stride and attributes for you, according to the vertex layout you provided.
const ParticleGeometry = d.struct({ tilt: d.location(0, d.f32), angle: d.location(1, d.f32), color: d.location(2, d.vec4f),});
const geometryLayout = tgpu .vertexLayout((n: number) => d.arrayOf(ParticleGeometry, n), 'instance');
const geometry = root.unwrap(geometryLayout);
console.log(geometry);//{// "arrayStride": 32,// "stepMode": "instance",// "attributes": [// {// "format": "float32",// "offset": 0,// "shaderLocation": 0// },// {// "format": "float32",// "offset": 4,// "shaderLocation": 1// },// {// "format": "float32x4",// "offset": 16,// "shaderLocation": 2// }// ]//}
This will return a GPUVertexBufferLayout
that can be used when creating a render pipeline.
const renderPipeline = device.createRenderPipeline({ layout: device.createPipelineLayout({ bindGroupLayouts: [root.unwrap(bindGroupLayout)], }), primitive: { topology: 'triangle-strip', }, vertex: { module: renderShader, buffers: [ { arrayStride: 32, stepMode: 'instance', attributes: [ { format: 'float32', offset: 0, shaderLocation: 0, }, { format: 'float32', offset: 4, shaderLocation: 1, }, { format: 'float32x4', offset: 16, shaderLocation: 2, }, ], }, ], buffers: [root.unwrap(geometryLayout)], }, fragment: { ... },});
Loose schemas can be interpreted in multiple ways within a shader. However, for convenience, they can be resolved to their default WGSL representation.
const LooseParticleGeometry = d.unstruct({ tilt: d.location(0, d.f32), angle: d.location(1, d.f32), color: d.location(2, d.unorm8x4),});
const sampleShader = ` @vertex fn main(particleGeometry: LooseParticleGeometry) -> @builtin(position) pos: vec4f { return vec4f( particleGeometry.tilt, particleGeometry.angle, particleGeometry.color.rgb, 1.0 ); }`;
const wgslDefinition = tgpu.resolve({ template: sampleShader, externals: { LooseParticleGeometry }});
console.log(wgslDefinition);// struct LooseParticleGeometry_0 {// @location(0) tilt: f32,// @location(1) angle: f32,// @location(2) color: vec4f,// }//// @vertex// fn main(particleGeometry: LooseParticleGeometry_0) -> @builtin(position) pos: vec4f {// return vec4f(// particleGeometry.tilt,// particleGeometry.angle,// particleGeometry.color.rgb,// 1.0// );// }