WebGPU
TODO
Hello Triangle
async function main() {
// request adapter
const adapter = await navigator.gpu?.requestAdapter();
// request device
const device = await adapter?.requestDevice();
if (!device) {
fail("need a browser that supports WebGPU");
return;
}
// get canvas
const canvas = document.querySelector("canvas");
// get webgpu context from canvas
const context = canvas.getContext("webgpu");
// set get recommended """ideal/recommended""" format (should be faster)
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device: device,
format: presentationFormat,
});
// create shader
const module = device.createShaderModule({
label: "our hardcoded red triangle shaders",
code: `
@vertex fn vs( // vertex shader function
@builtin(vertex_index) vertexIndex : u32 // builtin vertex iterator
) -> @builtin(position) vec4f // returns vertex positon [-1.0..1.0]
{
let 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[vertexIndex], 0.0, 1.0); // position
}
@fragment fn fs() -> @location(0) vec4f { // fragment shader function
// returns vec4 at location 0
// this will write to the first render target
return vec4f(0.0, 0.0, 0.0, 1.0); // color
}
`,
});
const pipeline = device.createRenderPipeline({ // define pipeline
label: "our hardcoded red triangle pipeline",
layout: "auto", // data layout (not data in this example)
vertex: {
module, // set vertex shader
},
fragment: {
module, // set fragmaent shader
targets: [{
format: presentationFormat // use presentation format
}],
},
});
const renderPassDescriptor = { // define render pass
label: "our basic canvas renderPass",
colorAttachments: [ // color step
{
clearValue: [1.0, 1.0, 1.0, 1.0], // background color
loadOp: "clear", // load operation
storeOp: "store", // store operation
},
],
};
function render() {
// Get the current texture from the canvas context and
// set it as the texture to render to.
renderPassDescriptor.colorAttachments[0].view = context.getCurrentTexture();
// make a command encoder to start encoding commands
const encoder = device.createCommandEncoder({
label: "our encoder"
});
// make a render pass encoder to encode render specific commands
const pass = encoder.beginRenderPass(renderPassDescriptor);
pass.setPipeline(pipeline);
pass.draw(3); // call our vertex shader 3 times
pass.end();
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
}
render();
}
Compute Shader
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
if (!device) {
fail("need a browser that supports WebGPU");
return;
}
const module = device.createShaderModule({
lable: 'doubling compute module',
code: `
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
@compute @workgroup_size(1) fn computeSomething(
@builtin(global_invocation_id) id: vec3u
) {
let i = id.x;
data[i] = data[i] * 2;
}
`
})
const pipeline = device.createComputePipeline({
label: 'doubling compute pipeline',
layout: 'auto',
compute: {
module,
}
})
const input = new Float32Array([1,2,3]); // host araray
// create a buffer on the GPU to hold our computation
// input and output
const workBuffer = device.createBuffer({
label: 'work buffer',
size: input.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
});
// Copy our input data to that buffer
device.queue.writeBuffer(workBuffer, 0, input);
// create a buffer on the GPU to hold our computation
// input and output
const resultBuffer = device.createBuffer({
label: 'work buffer',
size: input.byteLength,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
// setup bind group
// tell the shader which buffer to use
const bindGroup = device.createBindGroup({
label: 'bindGroup for work buffer',
layout: pipeline.getBindGroupLayout(0), // '@group(0)' in the shader
entries: [{
binding: 0, // '@group(0) @binding(0)' in the shader
resource: workBuffer
},],
});
// encode commands
const encoder = device.createCommandEncoder({
label = 'doubling encoder',
})
const pass = encoder.beginComputePass({
label = 'doubling compute pass',
})
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup); // '@group(0)' in the shader
pass.dispatchWorkgroups(input.length);
pass.end();
// copy results to mappable buffer
encoder.copyToBuffer(workBuffer, 0, resultBuffer, 0, resultBuffer.size);
// finish encoding and submit the commands
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
// read the results
await resultBuffer.mapAsync(GPUMapMode.READ);
const result = new Float32Array(resultBuffer.getMappedRange());
console.log('input', input);
console.log('result', result);
resultBuffer.unmap();
}