Last active
December 18, 2022 15:37
-
-
Save ccincotti3/b1ba4f3adbbb0e90891ff3458c120dbd to your computer and use it in GitHub Desktop.
Rendering a Triangle with WebGPU as part of my series at https://carmencincotti.com/2022-12-05/how-to-render-a-webgpu-triangle-series-part-one-video/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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