Public
Edited
Mar 22, 2024
1 fork
Insert cell
Insert cell
{
let device;
let adapter;
try {
adapter = await navigator.gpu?.requestAdapter();
device = await adapter?.requestDevice();
} catch (e) {
return 'WebGPU not supported'
}

// ==================================================
// create shader modules
// ==================================================
const shaders = `
struct VertexOut {
@builtin(position) position : vec4f,
@location(0) color : vec4f
}
@vertex
fn vertex_main(@location(0) position: vec4f,
@location(1) color: vec4f) -> VertexOut
{
var output : VertexOut;
output.position = position;
output.color = color;
return output;
}
@fragment
fn fragment_main(fragData: VertexOut) -> @location(0) vec4f
{
return fragData.color;
}
`;

// make code available to GPU
const shaderModule = device.createShaderModule({
code: shaders,
})

// ==================================================
// configure canvas context
// ==================================================
const canvas = DOM.canvas(width, 512);
const context = canvas.getContext('webgpu');
context.configure({
device: device,
format: navigator.gpu.getPreferredCanvasFormat(),
alphaMode: 'premultiplied',
})

// ==================================================
// create buffer and write triangle data to it
// ==================================================

const vertices = new Float32Array([
0.0, 0.6, 0, 1, // XYZW
1, 0, 0, 1, // RGBA
-0.5, -0.6, 0, 1, // XYZW
0, 1, 0, 1, // RGBA
0.5, -0.6, 0, 1, // XYZW
0, 0, 1, 1, // RGBA
])

// pass data to in-memory buffer (GPUBuffer) which is tightly coupled
// with the GPU for perf
// (we cannot access this from the host system, however)
const vertexBuffer = device.createBuffer({
size: vertices.byteLength,
// used as vertex buffer and destination of copy operations
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});

// put data in GPUBuffer
// buffer, bufferOffset, data, dataOffset, size
device.queue.writeBuffer(vertexBuffer, 0, vertices, 0, vertices.length);

// ==================================================
// create render pipeline
// ==================================================
// vertex data layout
const vertexBuffers = [
{
// each vert has a position and a color
attributes: [
{
shaderLocation: 0, // position (`@location(0) position: vec4f` in shader)
offset: 0,
format: 'float32x4', // maps to WGSL `vec4<f32>` type
},
{
shaderLocation: 1, // color (`@location(1) color: vec4f` in shader)
offset: 16,
format: 'float32x4',
},
],
arrayStride: 32,
// fetch data per-vertex
stepMode: 'vertex',
},
];

// config for render pipeline stages
const pipelineDescriptor = {
vertex: {
module: shaderModule,
entryPoint: 'vertex_main',
buffers: vertexBuffers,
},
fragment: {
module: shaderModule,
entryPoint: 'fragment_main',
targets: [
// an array of color target states that indicate the specified rendering
// format (this matches the format specified in our canvas context config earlier)
{
format: navigator.gpu.getPreferredCanvasFormat(),
},
],
},
// type of primitive we're drawing
primitive: {
topology: 'triangle-list',
},
// 'auto' will make pipeline to generate an implicit bind group layout
// based on any bindings defined in the shader code. will need to update
// this to use GPUPipelineLayout object (created using GPUDevice.createPipelineLayout()
// for more complex pipelines with e.g. compute shaders)
layout: 'auto',
};

const renderPipeline = device.createRenderPipeline(pipelineDescriptor);

// ==================================================
// run render pass
// ==================================================
// encode commands which will be issued to GPU
const commandEncoder = device.createCommandEncoder();

const clearColor = { r: 0.0, g: 0.5, b: 1.0, a: 1.0 };

const renderPassDescriptor = {
// colorAttachments is mandatory
colorAttachments: [
{
clearValue: clearColor,
// clear view to to color before drawing (gives us a background color)
loadOp: 'clear',
// store value of current render pass for this color attachment
storeOp: 'store',
// texture 'view' to render into - our <canvas>
view: context.getCurrentTexture().createView(),
},
],
};

// start render pass
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);

// pipeline to use for render pass
passEncoder.setPipeline(renderPipeline);
// data source to pass to pipeline to render
// first param is the "slot to set vertex buffer for" (ref to index of element in
// vertexBuffers[] array that describes the buffer's layout)
passEncoder.setVertexBuffer(0, vertexBuffer);
// start drawing! we have data for 3 verts, so set vertex count param to 3
passEncoder.draw(3);

// finish encoding sequence of commands
// invoke end of render pass command list
passEncoder.end();
// finish() to complete recording of issued command seq and encapsulate it
// into a GPUCommandBuffer object instance.
// submit command buffer to device's command queue to be sent to GPU.
device.queue.submit([commandEncoder.finish()]);

return canvas;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more