Signatur
Beschreibung
GPURenderPipeline beschreibt den kompletten Ablauf, mit dem die GPU Geometrie in Pixel umsetzt: den Vertex-Shader samt Layout der Vertex-Puffer, die Primitiv-Zusammensetzung (z. B. triangle-list), das Culling, den Tiefen- und Stencil-Test, das Multisampling, den Fragment-Shader und das Format der Farbziele einschließlich Blending. Diesen ganzen Zustand legt man einmal beim Erstellen fest. Danach lässt er sich nicht mehr ändern.
Ein Objekt erzeugt man nicht über einen Konstruktor, sondern mit GPUDevice.createRenderPipeline() (synchron) oder GPUDevice.createRenderPipelineAsync() (gibt ein Promise zurück). Beim Zeichnen bindet man die Pipeline mit passEncoder.setPipeline(pipeline) und ruft dann draw() oder drawIndexed() auf.
Die Schnittstelle selbst ist schlank:
label(string): frei wählbare Bezeichnung für Fehlermeldungen und Debugging-Werkzeuge. Man kann sie lesen und schreiben.getBindGroupLayout(index): gibt dasGPUBindGroupLayoutfür die Bind-Gruppe@group(index)zurück. Das ist vor allem beilayout: "auto"nötig, weil das Layout dann aus dem Shader-Code abgeleitet wird.
Für jede Kombination aus Shadern und festem Zustand braucht man eine eigene Pipeline, zum Beispiel für Blending an und aus oder für ein anderes Farbformat. Da das Erstellen teuer ist, sollte man Pipelines beim Start anlegen und wiederverwenden, statt sie in jedem Frame neu zu erzeugen.
Rückgabewert
device.createRenderPipeline() direkt zurückgegeben, von createRenderPipelineAsync() als Promise<GPURenderPipeline>. getBindGroupLayout() liefert ein GPUBindGroupLayout.Beispiele
Pipeline erstellen und ein Dreieck zeichnen
const adapter = await navigator.gpu?.requestAdapter();
if (!adapter) throw new Error('WebGPU wird nicht unterstützt');
const device = await adapter.requestDevice();
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const format = navigator.gpu.getPreferredCanvasFormat();
context.configure({ device, format });
const module = device.createShaderModule({
code: /* wgsl */ `
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
var pos = array<vec2f, 3>(vec2f(0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5));
return vec4f(pos[i], 0, 1);
}
@fragment fn fs() -> @location(0) vec4f {
return vec4f(1, 0.4, 0, 1);
}
`
});
const pipeline = device.createRenderPipeline({
label: 'Dreieck-Pipeline',
layout: 'auto',
vertex: { module, entryPoint: 'vs' },
fragment: { module, entryPoint: 'fs', targets: [{ format }] },
primitive: { topology: 'triangle-list' }
});
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0.1, g: 0.1, b: 0.1, a: 1 },
loadOp: 'clear',
storeOp: 'store'
}]
});
pass.setPipeline(pipeline);
pass.draw(3);
pass.end();
device.queue.submit([encoder.finish()]);
console.log(pipeline.label);
Uniform-Puffer über getBindGroupLayout() binden
// Voraussetzung: device, format wie oben
const module = device.createShaderModule({
code: /* wgsl */ `
@group(0) @binding(0) var<uniform> farbe: vec4f;
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
var p = array<vec2f, 3>(vec2f(0, 1), vec2f(-1, -1), vec2f(1, -1));
return vec4f(p[i] * 0.5, 0, 1);
}
@fragment fn fs() -> @location(0) vec4f { return farbe; }
`
});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module, entryPoint: 'vs' },
fragment: { module, entryPoint: 'fs', targets: [{ format }] }
});
const uniformBuffer = device.createBuffer({
size: 16,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
});
device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([0, 0.6, 1, 1]));
// Layout aus der Pipeline holen, weil layout: 'auto' verwendet wurde
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: { buffer: uniformBuffer } }]
});
// Im Render-Pass:
// pass.setPipeline(pipeline);
// pass.setBindGroup(0, bindGroup);
// pass.draw(3);
Asynchron erstellen mit Vertex-Puffer, Tiefentest und Blending
const pipeline = await device.createRenderPipelineAsync({
label: 'Mesh-Pipeline',
layout: 'auto',
vertex: {
module: meshModule,
entryPoint: 'vs',
buffers: [{
arrayStride: 24, // 3 × float32 Position + 3 × float32 Normale
attributes: [
{ shaderLocation: 0, offset: 0, format: 'float32x3' },
{ shaderLocation: 1, offset: 12, format: 'float32x3' }
]
}]
},
fragment: {
module: meshModule,
entryPoint: 'fs',
targets: [{
format,
blend: {
color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' }
}
}]
},
primitive: { topology: 'triangle-list', cullMode: 'back' },
depthStencil: { format: 'depth24plus', depthWriteEnabled: true, depthCompare: 'less' }
}).catch((err) => {
// GPUPipelineError mit err.reason: 'validation' | 'internal'
console.error('Pipeline fehlgeschlagen:', err.reason, err.message);
});