Skip to content

Instantly share code, notes, and snippets.

@greggman
Created November 11, 2024 17:44
Show Gist options
  • Save greggman/a42d67ebc7f13e30759f3efd16963308 to your computer and use it in GitHub Desktop.
Save greggman/a42d67ebc7f13e30759f3efd16963308 to your computer and use it in GitHub Desktop.
WebGPU Node/Edges (webgpu-utils)
html, body { margin: 0; height: 100% }
canvas { width: 100%; height: 100%; display: block; }
#fail {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background: red;
color: white;
font-weight: bold;
font-family: monospace;
font-size: 16pt;
text-align: center;
}
<canvas></canvas>
<div id="fail" style="display: none">
<div class="content"></div>
</div>
import {vec3, mat4} from 'https://webgpufundamentals.org/3rdparty/wgpu-matrix.module.js';
import {
makeShaderDataDefinitions,
makeStructuredView,
getSizeAndAlignmentOfUnsizedArrayElement,
} from 'https://greggman.github.io/webgpu-utils/dist/1.x/webgpu-utils.module.js';
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
if (!device) {
fail('need webgpu');
return;
}
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const presentationFormat = navigator.gpu.getPreferredCanvasFormat(adapter);
context.configure({
alphaMode: "opaque",
format: presentationFormat,
device,
});
const shaderSrc = `
struct Node {
@location(0) position : vec2f,
@location(1) size : f32,
};
struct Edge {
srouceNodeIndex : u32,
targetNodeIndex : u32,
};
struct MyVSOutput {
@builtin(position) position: vec4f,
@location(0) size: f32,
};
@vertex fn vsLines(v: Node) -> MyVSOutput {
var vsOut: MyVSOutput;
vsOut.position = vec4f(v.position, 0, 1);
vsOut.size = v.size;
return vsOut;
}
@vertex fn vsPoints(
@builtin(vertex_index) vNdx: u32,
@builtin(instance_index) iNdx: u32,
) -> MyVSOutput {
let pos = array(
vec2f(-0.5, -0.5),
vec2f( 0.5, -0.5),
vec2f(-0.5, 0.5),
vec2f(-0.5, 0.5),
vec2f( 0.5, -0.5),
vec2f( 0.5, 0.5),
);
let node = nodes[iNdx];
var vsOut: MyVSOutput;
vsOut.position = vec4f(pos[vNdx] * node.size + node.position, 0, 1);
return vsOut;
}
@group(0) @binding(0) var<storage> nodes: array<Node>;
@group(0) @binding(1) var<storage> edges: array<Edge>;
@fragment
fn fsLines(v: MyVSOutput) -> @location(0) vec4<f32> {
return vec4f(1,0,0,1);
}
@fragment
fn fsPoints(v: MyVSOutput) -> @location(0) vec4<f32> {
return vec4f(0,1,1,1);
}
`;
const shaderModule = device.createShaderModule({code: shaderSrc});
const defs = makeShaderDataDefinitions(shaderSrc);
const {size: nodeSize} = getSizeAndAlignmentOfUnsizedArrayElement(defs.storages.nodes);
const {size: edgeSize} = getSizeAndAlignmentOfUnsizedArrayElement(defs.storages.edges);
const numNodes = 6;
const numEdges = 5;
const nodeArrayBuffer = new ArrayBuffer(numNodes * nodeSize);
const edgeArrayBuffer = new ArrayBuffer(numEdges * edgeSize);
const nodes = makeStructuredView(defs.storages.nodes, nodeArrayBuffer);
const edges = makeStructuredView(defs.storages.edges, edgeArrayBuffer);
nodes.set([
{ position: [-0.5, -0.5], size: 0.1 },
{ position: [ 0.5, -0.5], size: 0.2 },
{ position: [-0.5, 0 ], size: 0.3 },
{ position: [ 0.5, 0 ], size: 0.2 },
{ position: [-0.5, 0.5], size: 0.1 },
{ position: [ 0.5, 0.5], size: 0.2 },
]);
edges.set([
{ srouceNodeIndex: 0, targetNodeIndex: 1 },
{ srouceNodeIndex: 1, targetNodeIndex: 3 },
{ srouceNodeIndex: 3, targetNodeIndex: 2 },
{ srouceNodeIndex: 2, targetNodeIndex: 4 },
{ srouceNodeIndex: 4, targetNodeIndex: 5 },
]);
const nodeBuffer = device.createBuffer({
label: 'nodes',
size: nodes.arrayBuffer.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
const edgeBuffer = device.createBuffer({
label: 'edges',
size: edges.arrayBuffer.byteLength,
usage: GPUBufferUsage.INDEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(nodeBuffer, 0, nodes.arrayBuffer);
device.queue.writeBuffer(edgeBuffer, 0, edges.arrayBuffer);
const linesPipeline = device.createRenderPipeline({
label: 'lines',
layout: 'auto',
vertex: {
module: shaderModule,
entryPoint: 'vsLines',
buffers: [
{
arrayStride: nodeSize,
attributes: [
{ shaderLocation: 0, format: 'float32x2', offset: nodes.views[0].position.byteOffset },
{ shaderLocation: 1, format: 'float32', offset: nodes.views[0].size.byteOffset },
],
}
]
},
fragment: {
module: shaderModule,
entryPoint: 'fsLines',
targets: [
{format: presentationFormat},
],
},
primitive: {
topology: 'line-list',
},
});
const pointsPipeline = device.createRenderPipeline({
label: 'points',
layout: 'auto',
vertex: {
module: shaderModule,
entryPoint: 'vsPoints',
},
fragment: {
module: shaderModule,
entryPoint: 'fsPoints',
targets: [
{format: presentationFormat},
],
},
});
const bindGroup = device.createBindGroup({
layout: pointsPipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: nodeBuffer } },
],
});
const renderPassDescriptor = {
colorAttachments: [
{
// view: undefined, // Assigned later
// resolveTarget: undefined, // Assigned Later
clearValue: [0.2, 0.2, 0.2, 1],
loadOp: 'clear',
storeOp: 'store',
},
],
};
function render(time) {
time *= 0.001;
const canvasTexture = context.getCurrentTexture();
renderPassDescriptor.colorAttachments[0].view = canvasTexture.createView();
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
passEncoder.setPipeline(linesPipeline);
passEncoder.setVertexBuffer(0, nodeBuffer);
passEncoder.setIndexBuffer(edgeBuffer, 'uint32');
passEncoder.drawIndexed(numEdges * 2);
passEncoder.setPipeline(pointsPipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.draw(6, numNodes);
passEncoder.end();
device.queue.submit([commandEncoder.finish()]);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const canvas = entry.target;
const width = entry.contentBoxSize[0].inlineSize;
const height = entry.contentBoxSize[0].blockSize;
canvas.width = Math.max(1, Math.min(width, device.limits.maxTextureDimension2D));
canvas.height = Math.max(1, Math.min(height, device.limits.maxTextureDimension2D));
}
});
observer.observe(canvas);
}
function fail(msg) {
const elem = document.querySelector('#fail');
const contentElem = elem.querySelector('.content');
elem.style.display = '';
contentElem.textContent = msg;
}
main();
{"name":"WebGPU Node/Edges (webgpu-utils)","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