webgpu-app/index.js

252 lines
6.0 KiB
JavaScript

if (!navigator.gpu) {
throw new Error("WebGPU not supported on this browser.");
}
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
throw new Error("No WebGPU adapter");
}
const device = await adapter.requestDevice();
const canvas = document.querySelector("canvas");
canvas.width = canvas.clientWidth
canvas.height = canvas.clientHeight
const context = canvas.getContext("webgpu");
const canvasFormat = navigator.gpu.getPreferredCanvasFormat();
//console.log(canvasFormat);
context.configure({
device: device,
format: canvasFormat,
});
const encoder = device.createCommandEncoder();
const renderPass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: "clear",
clearValue: { r: 0.9, g: 0.5, b: 0.2, a: 1 },
storeOp: "store",
}]
});
renderPass.end();
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
const vertices = new Float32Array([
// X, Y,
-0.8, -0.8, // Triangle 1 (Blue)
0.8, -0.8,
0.8, 0.8,
-0.8, -0.8, // Triangle 2 (Red)
0.8, 0.8,
-0.8, 0.8,
]);
const vertexBuffer = device.createBuffer({
label: "cell vertices",
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(vertexBuffer, 0, vertices);
const vertexBufferLayout = {
arrayStride: 8,
attributes: [{
format: "float32x2",
offset: 0,
shaderLocation: 0,
}],
stepMode: "vertex",
};
function getPath(path)
{
return fetch(path).then((response) => {
if (!response.ok) {
throw new Error(`${path}: ${response.status}`)
}
return response.blob()
}).then((blob) => { return blob.text() })
}
const cellShaderModule = device.createShaderModule({
label: "cell shader",
code: await getPath("index.wgsl")
});
const bindGroupLayout = device.createBindGroupLayout({
label: "bind group layout",
entries: [{
binding: 0,
visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
buffer: { type: "uniform" }
}, {
binding: 1,
visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
buffer: { type: "read-only-storage" }
}, {
binding: 2,
visibility: GPUShaderStage.COMPUTE,
buffer: { type: "storage" }
}]
});
const pipelineLayout = device.createPipelineLayout({
label: "pipeline layout",
bindGroupLayouts: [ bindGroupLayout ],
});
const cellPipeline = device.createRenderPipeline({
label: "cell pipeline",
layout: pipelineLayout,
vertex: {
module: cellShaderModule,
entryPoint: "vertexMain",
buffers: [vertexBufferLayout],
},
fragment: {
module: cellShaderModule,
entryPoint: "fragmentMain",
targets: [{
format: canvasFormat,
}]
}
});
const computeShaderModule = device.createShaderModule({
label: "compute shader",
code: await getPath("compute.wgsl")
});
const computePipeline = device.createComputePipeline({
label: "compute pipeline",
layout: pipelineLayout,
compute: {
module: computeShaderModule,
entryPoint: "computeMain",
},
});
const gridSize = 32;
const workGroupSize = 16;
const uniformArray = new Float32Array([gridSize, gridSize, 1, 1]);
const uniformBuffer = device.createBuffer({
label: "grid uniform",
size: uniformArray.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(uniformBuffer, 0, uniformArray);
const cellStateArray = new Uint32Array(gridSize * gridSize);
const cellStateStorage = [
device.createBuffer({
label: "cell state 0",
size: cellStateArray.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
}),
device.createBuffer({
label: "cell state 1",
size: cellStateArray.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
}),
];
for (let i = 0; i < cellStateArray.length; i += 1) {
cellStateArray[i] = Math.random() > 0.6 ? 1 : 0;
}
device.queue.writeBuffer(cellStateStorage[0], 0, cellStateArray);
device.queue.writeBuffer(cellStateStorage[1], 0, cellStateArray);
const bindGroups = [
device.createBindGroup({
label: "cell bind group 0",
layout: bindGroupLayout,
entries: [{
binding: 0,
resource: { buffer: uniformBuffer },
}, {
binding: 1,
resource: { buffer: cellStateStorage[0] },
}, {
binding: 2,
resource: { buffer: cellStateStorage[1] },
}],
}),
device.createBindGroup({
label: "cell bind group 1",
layout: bindGroupLayout,
entries: [{
binding: 0,
resource: { buffer: uniformBuffer },
}, {
binding: 1,
resource: { buffer: cellStateStorage[1] },
}, {
binding: 2,
resource: { buffer: cellStateStorage[0] },
}],
})
];
let tick = 0;
let which = 0;
function render()
{
const encoder = device.createCommandEncoder();
const computePass = encoder.beginComputePass();
computePass.setPipeline(computePipeline);
computePass.setBindGroup(0, bindGroups[which]);
const workgroupCount = Math.ceil((gridSize * gridSize) / workGroupSize);
computePass.dispatchWorkgroups(workgroupCount);
computePass.end();
tick += 1;
if ((tick % 5) == 0) {
which ^= 1;
}
canvas.width = canvas.clientWidth
canvas.height = canvas.clientHeight
const aspect = canvas.height / canvas.width;
if (canvas.width > canvas.height) {
uniformArray[2] = aspect;
uniformArray[3] = 1;
} else {
uniformArray[2] = 1;
uniformArray[3] = 1 / aspect;
}
device.queue.writeBuffer(uniformBuffer, 0, uniformArray, 0);
const renderPass = encoder.beginRenderPass({
colorAttachments: [{
view: context.getCurrentTexture().createView(),
loadOp: "clear",
clearValue: { r: 0.2, g: 0.2, b: 0.4, a: 1 },
storeOp: "store",
}]
});
renderPass.setPipeline(cellPipeline);
renderPass.setVertexBuffer(0, vertexBuffer);
renderPass.setBindGroup(0, bindGroups[which]);
renderPass.draw(vertices.length / 2, gridSize * gridSize);
renderPass.end();
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
requestAnimationFrame(render)
}
requestAnimationFrame(render)