252 lines
6.0 KiB
JavaScript
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)
|