SOP: Create a Render Pipeline
Fresh 🌱Version: 1.0 | Chrome: 113+
Overview
A WebGPU render pipeline encapsulates all rendering state in one immutable object: shaders, vertex layout, color targets, depth/stencil, and blend state. This replaces WebGL's error-prone global state.
Pipeline Flowchart
Step-by-Step
Step 1 — Write WGSL Shaders
wgsl
// Vertex shader: outputs clip-space position
@vertex
fn vertexMain(@builtin(vertex_index) i : u32) -> @builtin(position) vec4f {
// Triangle positions in clip space
const pos = array(
vec2f( 0.0, 0.5), // top center
vec2f(-0.5, -0.5), // bottom left
vec2f( 0.5, -0.5) // bottom right
);
return vec4f(pos[i], 0.0, 1.0);
}
// Fragment shader: outputs RGBA color
@fragment
fn fragmentMain() -> @location(0) vec4f {
return vec4f(0.31, 0.19, 0.89, 1.0); // Indigo
}Step 2 — Create Shader Module
javascript
const shaderModule = device.createShaderModule({
label: 'Triangle shader',
code: wgslCode // the WGSL string above
});Step 3 — Create Render Pipeline
javascript
const pipeline = device.createRenderPipeline({
label: 'Triangle pipeline',
layout: 'auto', // auto-generate bind group layouts
vertex: {
module: shaderModule,
entryPoint: 'vertexMain'
},
fragment: {
module: shaderModule,
entryPoint: 'fragmentMain',
targets: [{
format: navigator.gpu.getPreferredCanvasFormat()
}]
},
primitive: {
topology: 'triangle-list' // or 'triangle-strip', 'line-list', 'point-list'
}
});Step 4 — Render Frame
javascript
function renderFrame() {
// Create fresh encoder each frame
const commandEncoder = device.createCommandEncoder({ label: 'Frame encoder' });
const renderPass = commandEncoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: 'clear',
clearValue: { r: 0.05, g: 0.05, b: 0.05, a: 1.0 },
storeOp: 'store'
}]
});
renderPass.setPipeline(pipeline);
renderPass.draw(3); // 3 vertices = 1 triangle
renderPass.end();
// Submit to GPU queue
device.queue.submit([commandEncoder.finish()]);
requestAnimationFrame(renderFrame);
}
requestAnimationFrame(renderFrame);Step 5 — Full Triangle Example
javascript
async function main() {
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 });
const shaderCode = `
@vertex
fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
const p = array(vec2f(0,0.5), vec2f(-0.5,-0.5), vec2f(0.5,-0.5));
return vec4f(p[i], 0, 1);
}
@fragment fn fs() -> @location(0) vec4f {
return vec4f(0.31, 0.19, 0.89, 1.0);
}
`;
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: { module: device.createShaderModule({ code: shaderCode }), entryPoint: 'vs' },
fragment: { module: device.createShaderModule({ code: shaderCode }), entryPoint: 'fs', targets: [{ format }] },
primitive: { topology: 'triangle-list' }
});
function frame() {
const enc = device.createCommandEncoder();
const pass = enc.beginRenderPass({
colorAttachments: [{ view: context.getCurrentTexture().createView(), loadOp: 'clear', clearValue: [0,0,0,1], storeOp: 'store' }]
});
pass.setPipeline(pipeline);
pass.draw(3);
pass.end();
device.queue.submit([enc.finish()]);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
main();Verification Checklist
- [ ] Shader module created without compilation errors
- [ ] Pipeline
layout: 'auto'or explicit layout defined - [ ] Canvas format matches
getPreferredCanvasFormat() - [ ]
loadOpandstoreOpspecified on all attachments - [ ]
renderPass.end()called beforecommandEncoder.finish() - [ ] Commands submitted via
device.queue.submit()
Topology Options
| Topology | Use Case |
|---|---|
triangle-list | Standard mesh rendering |
triangle-strip | Efficient quad rendering |
line-list | Debug wireframes |
line-strip | Curves and paths |
point-list | Particle systems |