Signatur
Beschreibung
Mit setVertexBuffer() bindest du beim Aufzeichnen eines Render-Bundles einen GPUBuffer an einen Vertex-Buffer-Slot. Welcher Slot welche Daten liefert, legt das Array vertex.buffers im Deskriptor der GPURenderPipeline fest: Der Index im Array entspricht dem slot-Parameter. Alle folgenden draw()- und drawIndexed()-Aufrufe im Bundle lesen ihre Vertex-Attribute aus diesem Buffer.
Render-Bundles (GPURenderBundle) sind vorab aufgezeichnete Befehlsfolgen, die du mit GPURenderPassEncoder.executeBundles() in beliebig vielen Render-Passes wiederverwenden kannst. Das spart CPU-Aufwand bei statischen Szenen, weil Validierung und Kodierung nur einmal stattfinden. Deshalb gehören die Vertex-Buffer-Bindungen direkt ins Bundle – ein Bundle erbt keinen Zustand aus dem umgebenden Render-Pass.
Mit offset und size kannst du nur einen Teilbereich des Buffers binden, etwa wenn mehrere Meshes in einem gemeinsamen großen Buffer liegen. Übergibst du null als buffer, wird die Bindung für diesen Slot entfernt.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $slot Pflicht | number | Index des Vertex-Buffer-Slots (GPUIndex32). Entspricht dem Index in vertex.buffers der Pipeline; muss kleiner als device.limits.maxVertexBuffers sein (meist 8). |
|
| $buffer Pflicht | GPUBuffer | null | Der Buffer mit den Vertex-Daten. Muss mit GPUBufferUsage.VERTEX erstellt worden sein. null entfernt die Bindung des Slots. |
|
| $offset | number | 0 | Byte-Offset im Buffer, ab dem die Vertex-Daten beginnen. Muss ein Vielfaches von 4 sein. |
| $size | number | buffer.size - offset | Größe des gebundenen Bereichs in Bytes. offset + size darf buffer.size nicht überschreiten; muss ein Vielfaches von 4 sein. |
Rückgabewert
Beispiele
Dreieck in ein Render-Bundle aufzeichnen und ausführen
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });
// Vertex-Daten: x, y pro Eckpunkt
const vertices = new Float32Array([0, 0.5, -0.5, -0.5, 0.5, -0.5]);
const vertexBuffer = device.createBuffer({
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(vertexBuffer, 0, vertices);
const module = device.createShaderModule({
code: `
@vertex fn vs(@location(0) pos: vec2f) -> @builtin(position) vec4f {
return vec4f(pos, 0.0, 1.0);
}
@fragment fn fs() -> @location(0) vec4f {
return vec4f(1.0, 0.4, 0.0, 1.0);
}`
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
buffers: [{ // Slot 0
arrayStride: 8,
attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }]
}]
},
fragment: { module, entryPoint: 'fs', targets: [{ format }] }
});
// Bundle aufzeichnen
const bundleEncoder = device.createRenderBundleEncoder({ colorFormats: [format] });
bundleEncoder.setPipeline(pipeline);
bundleEncoder.setVertexBuffer(0, vertexBuffer);
bundleEncoder.draw(3);
const bundle = bundleEncoder.finish();
// Bundle in einem Render-Pass ausführen
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: 'clear',
clearValue: { r: 0, g: 0, b: 0, a: 1 },
storeOp: 'store'
}]
});
pass.executeBundles([bundle]);
pass.end();
device.queue.submit([encoder.finish()]);
Mehrere Meshes aus einem gemeinsamen Buffer per offset/size
// Annahme: device, pipeline, format wie oben; zwei Dreiecke in einem Buffer
const data = new Float32Array([
// Mesh A (3 Vertices à 8 Byte = 24 Byte)
-0.9, 0.9, -0.9, 0.1, -0.1, 0.1,
// Mesh B
0.1, -0.1, 0.9, -0.1, 0.5, -0.9
]);
const sharedBuffer = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(sharedBuffer, 0, data);
const meshBytes = 3 * 2 * Float32Array.BYTES_PER_ELEMENT; // 24
const be = device.createRenderBundleEncoder({ colorFormats: [format] });
be.setPipeline(pipeline);
be.setVertexBuffer(0, sharedBuffer, 0, meshBytes); // Mesh A
be.draw(3);
be.setVertexBuffer(0, sharedBuffer, meshBytes, meshBytes); // Mesh B
be.draw(3);
const sceneBundle = be.finish();
console.log(sceneBundle instanceof GPURenderBundle);
Positionen und Farben in zwei Slots
// Pipeline mit zwei Vertex-Buffer-Layouts: Slot 0 = Position, Slot 1 = Farbe
const positions = new Float32Array([0, 0.5, -0.5, -0.5, 0.5, -0.5]);
const colors = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
const makeVB = (arr) => {
const buf = device.createBuffer({
size: arr.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(buf, 0, arr);
return buf;
};
const posBuffer = makeVB(positions);
const colorBuffer = makeVB(colors);
// colorPipeline: vertex.buffers = [
// { arrayStride: 8, attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }] },
// { arrayStride: 12, attributes: [{ shaderLocation: 1, offset: 0, format: 'float32x3' }] }
// ]
const enc = device.createRenderBundleEncoder({ colorFormats: [format] });
enc.setPipeline(colorPipeline);
enc.setVertexBuffer(0, posBuffer);
enc.setVertexBuffer(1, colorBuffer);
enc.draw(3);
const colorBundle = enc.finish();