Created
November 11, 2024 17:44
-
-
Save greggman/a42d67ebc7f13e30759f3efd16963308 to your computer and use it in GitHub Desktop.
WebGPU Node/Edges (webgpu-utils)
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
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; | |
} |
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
<canvas></canvas> | |
<div id="fail" style="display: none"> | |
<div class="content"></div> | |
</div> | |
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
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(); | |
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
{"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