Skip to content

Instantly share code, notes, and snippets.

@ccincotti3
Last active December 18, 2022 15:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ccincotti3/b1ba4f3adbbb0e90891ff3458c120dbd to your computer and use it in GitHub Desktop.
Save ccincotti3/b1ba4f3adbbb0e90891ff3458c120dbd to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Carmen Cincotti - WebGPU Triangle</title>
<style>
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="canvas-container"></canvas>
<script>
const init = async () => {
// ~~ INITIALIZE ~~ Make sure we can initialize WebGPU in the browser
// https://carmencincotti.com/2022-04-18/drawing-a-webgpu-triangle/#adapter-and-device
if (!navigator.gpu) {
console.error(
"WebGPU cannot be initialized - navigator.gpu not found"
);
return null;
}
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
console.error("WebGPU cannot be initialized - Adapter not found");
return null;
}
const device = await adapter.requestDevice();
device.lost.then(() => {
console.error("WebGPU cannot be initialized - Device has been lost");
return null;
});
const canvas = document.getElementById("canvas-container");
const context = canvas.getContext("webgpu");
if (!context) {
console.error(
"WebGPU cannot be initialized - Canvas does not support WebGPU"
);
return null;
}
// ~~ CONFIGURE THE SWAP CHAIN ~~
// https://carmencincotti.com/2022-04-18/drawing-a-webgpu-triangle/#swap-chain
// https://www.w3.org/TR/webgpu/#dom-gpu-getpreferredcanvasformat
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device, // Create link between GPU and canvas.
format: presentationFormat,
alphaMode: "opaque"
});
// ~~ SETUP VERTICES (position (vec3<f32>), color(vec4<i32>)) ~~
// https://carmencincotti.com/2022-04-18/drawing-a-webgpu-triangle/#vertices
// Pack them all into one array
// Each vertex has a position and a color packed in memory in X Y Z W R G B A order
const vertices = new Float32Array([
-1.0, -1.0, 0, 1, 1, 0, 0, 1, // Bottom left, red
-0.0, 1.0, 0, 1, 0, 1, 0, 1, // Top, green
1.0, -1.0, 0, 1, 0, 0, 1, 1, // Bottom right, blue
]);
const vertexBuffer = device.createBuffer({
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
new Float32Array(vertexBuffer.getMappedRange()).set(vertices);
vertexBuffer.unmap();
const vertexBuffersDescriptors = [
{
attributes: [
{
shaderLocation: 0,
offset: 0,
format: "float32x4",
},
{
shaderLocation: 1,
offset: 16,
format: "float32x4",
},
],
arrayStride: 32,
stepMode: "vertex",
},
];
// ~~ DEFINE BASIC SHADERS ~~
// https://carmencincotti.com/2022-04-18/drawing-a-webgpu-triangle/#the-vertex-and-fragment-shader
const shaderModule = device.createShaderModule({
code: `
struct VertexOut {
@builtin(position) position : vec4<f32>,
@location(0) color : vec4<f32>,
};
@vertex
fn vertex_main(@location(0) position: vec4<f32>,
@location(1) color: vec4<f32>) -> VertexOut
{
var output : VertexOut;
output.position = position;
output.color = color;
return output;
}
@fragment
fn fragment_main(fragData: VertexOut) -> @location(0) vec4<f32>
{
return fragData.color;
}
`,
});
// ~~ CREATE RENDER PIPELINE ~~
// https://carmencincotti.com/2022-04-18/drawing-a-webgpu-triangle/#the-rendering-pipeline-1
const pipeline = device.createRenderPipeline({
layout: "auto",
vertex: {
module: shaderModule,
entryPoint: "vertex_main",
buffers: vertexBuffersDescriptors,
},
fragment: {
module: shaderModule,
entryPoint: "fragment_main",
targets: [
{
format: presentationFormat,
},
],
},
primitive: {
topology: "triangle-list",
},
});
// ~~ CREATE RENDER PASS DESCRIPTOR ~~
// Object contains a collection of attachments used as the rendering destination
// for pixels generated by a rendering pass
// We will set colorAttachment[0].view in render loop
const renderPassDescriptor = {
colorAttachments: [
{
loadOp: "clear", // Clear image on each load
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, // Clear image with this color
storeOp: "store", // Write result to the view
},
],
};
// ~~ Define render loop ~~
// https://carmencincotti.com/2022-04-18/drawing-a-webgpu-triangle/#the-animation-frame-and-command-buffers
function frame() {
// Get the latest swap chain image, set as our output color attachment image
renderPassDescriptor.colorAttachments[0].view = context
.getCurrentTexture()
.createView();
// Create command encoder to record rendering commands
const commandEncoder = device.createCommandEncoder();
// Pass render pass descriptor to get back a GPURenderPassEncorder
// So that we can record rendering commands
const passEncoder =
commandEncoder.beginRenderPass(renderPassDescriptor);
// Configure the pass encoder
passEncoder.setPipeline(pipeline);
passEncoder.setVertexBuffer(0, vertexBuffer);
// Draw the triangle
passEncoder.draw(3);
// End the render pass
passEncoder.end();
// Get command buffer to submit to GPU, by calling commandEncoder.finish()
device.queue.submit([commandEncoder.finish()]);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
};
init();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment