Skip to content

Instantly share code, notes, and snippets.

@greggman
Last active January 29, 2022 01:48
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 greggman/906d8f21741840feb4fe075c5448cfc2 to your computer and use it in GitHub Desktop.
Save greggman/906d8f21741840feb4fe075c5448cfc2 to your computer and use it in GitHub Desktop.
Copy of Raw WebGPU article
canvas { border: 1px solid black; }
<canvas></canvas>
// Shaders
const vertWgsl = `
struct VSOut {
@builtin(position) Position: vec4<f32>;
@location(0) color: vec3<f32>;
};
@stage(vertex)
fn main(@location(0) inPos: vec3<f32>,
@location(1) inColor: vec3<f32>) -> VSOut {
var vsOut: VSOut;
vsOut.Position = vec4<f32>(inPos, 1.0);
vsOut.color = inColor;
return vsOut;
}`;
const fragWgsl = `
@stage(fragment)
fn main(@location(0) inColor: vec3<f32>) -> @location(0) vec4<f32> {
return vec4<f32>(inColor, 1.0);
}
`;
// 🌅 Renderer
// 📈 Position Vertex Buffer Data
const positions = new Float32Array([
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0,
0.0, 1.0, 0.0
]);
// 🎨 Color Vertex Buffer Data
const colors = new Float32Array([
1.0, 0.0, 0.0, // 🔴
0.0, 1.0, 0.0, // 🟢
0.0, 0.0, 1.0 // 🔵
]);
// 📇 Index Buffer Data
const indices = new Uint16Array([ 0, 1, 2 ]);
class Renderer {
constructor(canvas) {
this.canvas = canvas;
}
// 🏎️ Start the rendering engine
async start() {
if (await this.initializeAPI()) {
this.resizeBackings();
await this.initializeResources();
this.render();
}
else {
canvas.style.display = "none";
document.getElementById("error").innerHTML = `
<p>Doesn't look like your browser supports WebGPU.</p>
<p>Try using any chromium browser's canary build and go to <code>about:flags</code> to <code>enable-unsafe-webgpu</code>.</p>`
}
}
// 🌟 Initialize WebGPU
async initializeAPI() {
try {
// 🏭 Entry to WebGPU
const entry = navigator.gpu;
if (!entry) {
return false;
}
// 🔌 Physical Device Adapter
this.adapter = await entry.requestAdapter();
// 💻 Logical Device
this.device = await this.adapter.requestDevice();
// 📦 Queue
this.queue = this.device.queue;
} catch (e) {
console.error(e);
return false;
}
return true;
}
// 🍱 Initialize resources to render triangle (buffers, shaders, pipeline)
async initializeResources() {
// 🔺 Buffers
let createBuffer = (arr, usage) => {
let desc = {
size: (arr.byteLength + 3) & ~3,
usage,
mappedAtCreation: true
};
let buffer = this.device.createBuffer(desc);
const writeArray =
arr instanceof Uint16Array
? new Uint16Array(buffer.getMappedRange())
: new Float32Array(buffer.getMappedRange());
writeArray.set(arr);
buffer.unmap();
return buffer;
};
this.positionBuffer = createBuffer(positions, GPUBufferUsage.VERTEX);
this.colorBuffer = createBuffer(colors, GPUBufferUsage.VERTEX);
this.indexBuffer = createBuffer(indices, GPUBufferUsage.INDEX);
// 🖍️ Shaders
const vsmDesc = {
code: vertWgsl
};
this.vertModule = this.device.createShaderModule(vsmDesc);
const fsmDesc = {
code: fragWgsl
};
this.fragModule = this.device.createShaderModule(fsmDesc);
// ⚗️ Graphics Pipeline
// 🔣 Input Assembly
const positionAttribDesc = {
shaderLocation: 0, // @attribute(0)
offset: 0,
format: 'float32x3'
};
const colorAttribDesc = {
shaderLocation: 1, // @attribute(1)
offset: 0,
format: 'float32x3'
};
const positionBufferDesc = {
attributes: [positionAttribDesc],
arrayStride: 4 * 3, // sizeof(float) * 3
stepMode: 'vertex'
};
const colorBufferDesc = {
attributes: [colorAttribDesc],
arrayStride: 4 * 3, // sizeof(float) * 3
stepMode: 'vertex'
};
// 🌑 Depth
const depthStencil = {
depthWriteEnabled: true,
depthCompare: 'less',
format: 'depth24plus-stencil8'
};
// 🦄 Uniform Data
const pipelineLayoutDesc = { bindGroupLayouts: [] };
const layout = this.device.createPipelineLayout(pipelineLayoutDesc);
// 🎭 Shader Stages
const vertex = {
module: this.vertModule,
entryPoint: 'main',
buffers: [positionBufferDesc, colorBufferDesc]
};
// 🌀 Color/Blend State
const colorState = {
format: 'bgra8unorm',
writeMask: GPUColorWrite.ALL
};
const fragment = {
module: this.fragModule,
entryPoint: 'main',
targets: [colorState]
};
// 🟨 Rasterization
const primitive = {
frontFace: 'cw',
cullMode: 'none',
topology: 'triangle-list'
};
const pipelineDesc = {
layout,
vertex,
fragment,
primitive,
depthStencil
};
this.pipeline = this.device.createRenderPipeline(pipelineDesc);
}
// ↙️ Resize Canvas, frame buffer attachments
resizeBackings() {
// ⛓️ Canvas Context
if (!this.context) {
this.context = this.canvas.getContext('webgpu');
const canvasConfig = {
device: this.device,
format: 'bgra8unorm',
usage:
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
};
this.context.configure(canvasConfig);
}
const depthTextureDesc = {
size: [this.canvas.width, this.canvas.height, 1],
dimension: '2d',
format: 'depth24plus-stencil8',
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
};
this.depthTexture = this.device.createTexture(depthTextureDesc);
this.depthTextureView = this.depthTexture.createView();
}
// ✍️ Write commands to send to the GPU
encodeCommands() {
let colorAttachment = {
view: this.colorTextureView,
loadValue: { r: 0, g: 0, b: 0, a: 1 },
storeOp: 'store'
};
const depthAttachment = {
view: this.depthTextureView,
depthLoadValue: 1,
depthStoreOp: 'store',
stencilLoadValue: 'load',
stencilStoreOp: 'store'
};
const renderPassDesc = {
colorAttachments: [colorAttachment],
depthStencilAttachment: depthAttachment
};
this.commandEncoder = this.device.createCommandEncoder();
// 🖌️ Encode drawing commands
this.passEncoder = this.commandEncoder.beginRenderPass(renderPassDesc);
this.passEncoder.setPipeline(this.pipeline);
this.passEncoder.setViewport(
0,
0,
this.canvas.width,
this.canvas.height,
0,
1
);
this.passEncoder.setScissorRect(
0,
0,
this.canvas.width,
this.canvas.height
);
this.passEncoder.setVertexBuffer(0, this.positionBuffer);
this.passEncoder.setVertexBuffer(1, this.colorBuffer);
this.passEncoder.setIndexBuffer(this.indexBuffer, 'uint16');
this.passEncoder.drawIndexed(3, 1);
this.passEncoder.endPass();
this.queue.submit([this.commandEncoder.finish()]);
}
render = () => {
// ⏭ Acquire next image from context
this.colorTexture = this.context.getCurrentTexture();
this.colorTextureView = this.colorTexture.createView();
// 📦 Write and submit commands to queue
this.encodeCommands();
// ➿ Refresh canvas
requestAnimationFrame(this.render);
};
}
// Main
const canvas = document.querySelector('canvas');
const renderer = new Renderer(canvas);
renderer.start();
{"name":"Copy of Raw WebGPU article","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment