Public
Edited
Jun 2, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof interval = Inputs.range([0.1, 1], { value: 1, label: 'Frame length (s)', step: 0.05})
Insert cell
currentStep = {
let step = 0
while (true) {
// In updateGrid()
// Move the encoder creation to the top of the function.
const encoder = gpu.device.createCommandEncoder();
const computePass = encoder.beginComputePass();
// New lines
computePass.setPipeline(pipeline.simulationPipeline);
computePass.setBindGroup(0, bindGroups[step % 2]);
// New lines
const workgroupCount = Math.ceil(settings.GRID_SIZE / settings.WORKGROUP_SIZE);
computePass.dispatchWorkgroups(workgroupCount, workgroupCount);
computePass.end();

step = (step + 1) % 2
// Start a render pass
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: canvas.context.getCurrentTexture().createView(),
loadOp: "clear",
clearValue: backgroundSetting.color,
storeOp: "store",
}]
});

// Draw the grid.
pass.setPipeline(pipeline.cellPipeline);
pass.setBindGroup(0, bindGroups[step % 2]); // Updated!
pass.setVertexBuffer(0, geodata.vertexBuffer);
pass.draw(geodata.vertexBuffer.size /4 /2, settings.GRID_SIZE * settings.GRID_SIZE);

// End the render pass and submit the command buffer
pass.end();
gpu.device.queue.submit([encoder.finish()]);
yield Promises.tick(interval*1000, step)
}
}
Insert cell
bindGroupLayout = {
// Create the bind group layout and pipeline layout.
const bindGroupLayout = gpu.device.createBindGroupLayout({
label: "Cell Bind Group Layout",
entries: [{
binding: 0,
// Add GPUShaderStage.FRAGMENT here if you are using the `grid` uniform in the fragment shader.
visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
buffer: {} // Grid uniform buffer
}, {
binding: 1,
visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
buffer: { type: "read-only-storage"} // Cell state input buffer
}, {
binding: 2,
visibility: GPUShaderStage.COMPUTE,
buffer: { type: "storage"} // Cell state output buffer
}]
});
return bindGroupLayout
}
Insert cell
bindGroups = {
// Create a uniform buffer that describes the grid.
const uniformArray = new Float32Array([settings.GRID_SIZE, settings.GRID_SIZE]);
const uniformBuffer = gpu.device.createBuffer({
label: "Grid Uniforms",
size: uniformArray.byteLength,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
gpu.device.queue.writeBuffer(uniformBuffer, 0, uniformArray);
const bindGroups = [
gpu.device.createBindGroup({
label: "Cell renderer bind group A",
layout: bindGroupLayout,
entries: [{
binding: 0,
resource: { buffer: uniformBuffer }
}, {
binding: 1,
resource: { buffer: state.cellStateStorage[0] }
}, {
binding: 2, // New Entry
resource: { buffer: state.cellStateStorage[1] }
}],
}),
gpu.device.createBindGroup({
label: "Cell renderer bind group B",
layout: bindGroupLayout,
entries: [{
binding: 0,
resource: { buffer: uniformBuffer }
}, {
binding: 1,
resource: { buffer: state.cellStateStorage[1] }
}, {
binding: 2, // New Entry
resource: { buffer: state.cellStateStorage[0] }
}],
})
];
return bindGroups
}
Insert cell
settings = {
return {
GRID_SIZE: 32,
WORKGROUP_SIZE: 8,
}
}
Insert cell
geodata = {
const square = 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 = gpu.device.createBuffer({
label: "Cell vertices",
size: square.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
gpu.device.queue.writeBuffer(vertexBuffer, /*bufferOffset=*/0, square);
const vertexBufferLayout = {
arrayStride: 8,
attributes: [{
format: "float32x2",
offset: 0,
shaderLocation: 0, // Position, see vertex shader
}],
};
return {
vertexBuffer,
vertexBufferLayout,
}
}
Insert cell
shaderModule = {
const cellShaderModule = gpu.device.createShaderModule({
label: "Cell shader",
code: `
struct VertexInput {
@location(0) pos: vec2f,
@builtin(instance_index) instance: u32,
};

struct VertexOutput {
@builtin(position) pos: vec4f,
@location(0) cell: vec2f, // New line!
};

@group(0) @binding(0) var<uniform> grid: vec2f;
@group(0) @binding(1) var<storage> cellStateIn: array<u32>;
@group(0) @binding(2) var<storage, read_write> cellStateOut: array<u32>;

@vertex
fn vertexMain(input: VertexInput) -> VertexOutput {
let i = f32(input.instance);
let cell = vec2f(i % grid.x, floor(i / grid.x));
let state = f32(cellStateIn[input.instance]); // New line!

let cellOffset = cell / grid * 2;
let gridPos = (input.pos*state+1) / grid - 1 + cellOffset;
var output: VertexOutput;
output.pos = vec4f(gridPos, 0, 1);
output.cell = cell; // New line!
return output;
}

@fragment
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
let c = input.cell/grid;
return vec4f(c.x+(1-c.x)*0.5, c.y+(1-c.x)*0.5, 1.0-(c.x+c.y)*0.5, 1);
}

// New function
fn cellIndex(cell: vec2u) -> u32 {
return (cell.y % u32(grid.y)) * u32(grid.x) +
(cell.x % u32(grid.x));
}
fn cellActive(x: u32, y: u32) -> u32 {
return cellStateIn[cellIndex(vec2(x, y))];
}
@compute
@workgroup_size(${settings.WORKGROUP_SIZE}, ${settings.WORKGROUP_SIZE}) // New line
fn computeMain(@builtin(global_invocation_id) cell: vec3u) {
let activeNeighbors = cellActive(cell.x+1, cell.y+1) +
cellActive(cell.x+1, cell.y) +
cellActive(cell.x+1, cell.y-1) +
cellActive(cell.x, cell.y-1) +
cellActive(cell.x-1, cell.y-1) +
cellActive(cell.x-1, cell.y) +
cellActive(cell.x-1, cell.y+1) +
cellActive(cell.x, cell.y+1);

let i = cellIndex(cell.xy);
// Conway's game of life rules:
switch activeNeighbors {
case 2: { // Active cells with 2 neighbors stay active.
cellStateOut[i] = cellStateIn[i];
}
case 3: { // Cells with 3 neighbors become or stay active.
cellStateOut[i] = 1;
}
default: { // Cells with < 2 or > 3 neighbors become inactive.
cellStateOut[i] = 0;
}
}
}
`
});
return cellShaderModule
}
Insert cell
pipeline = {
const pipelineLayout = gpu.device.createPipelineLayout({
label: "Cell Pipeline Layout",
bindGroupLayouts: [ bindGroupLayout ],
});
const cellPipeline = gpu.device.createRenderPipeline({
label: "Cell pipeline",
layout: pipelineLayout,
vertex: {
module: shaderModule,
entryPoint: "vertexMain",
buffers: [geodata.vertexBufferLayout]
},
fragment: {
module: shaderModule,
entryPoint: "fragmentMain",
targets: [{
format: navigator.gpu.getPreferredCanvasFormat()
}]
}
});
// Create a compute pipeline that updates the game state.
const simulationPipeline = gpu.device.createComputePipeline({
label: "Simulation pipeline",
layout: pipelineLayout,
compute: {
module: shaderModule,
entryPoint: "computeMain",
}
});
return {
cellPipeline,
simulationPipeline,
}
}
Insert cell
state = {
button
// Create an array representing the active state of each cell.
const cellStateArray = new Uint32Array(settings.GRID_SIZE * settings.GRID_SIZE);

// Create a storage buffer to hold the cell state.
const cellStateStorage = [
gpu.device.createBuffer({
label: "Cell State A",
size: cellStateArray.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
}),
gpu.device.createBuffer({
label: "Cell State B",
size: cellStateArray.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
})
];
// Mark every third cell of the first grid as active.
for (let i = 0; i < cellStateArray.length; i+=3) {
cellStateArray[i] = Math.random() > 0.3 ? 1 : 0;
}
gpu.device.queue.writeBuffer(cellStateStorage[0], 0, cellStateArray);
// Mark every other cell of the second grid as active.
// for (let i = 0; i < cellStateArray.length; i++) {
// cellStateArray[i] = i % 2;
// }
// gpu.device.queue.writeBuffer(cellStateStorage[1], 0, cellStateArray);
return {
cellStateStorage,
}
}
Insert cell
gpu = {
if (!navigator.gpu) {
return new Error('WebGPU is not supported by your browser.')
}
const adapter = await navigator.gpu.requestAdapter()
if (!adapter) {
throw new Error("No appropriate GPUAdapter found.")
}
const device = await adapter.requestDevice();
return {
device
}
}
Insert cell
canvas = {
const canvas = DOM.canvas(320, 320)
const context = canvas.getContext("webgpu")
const format = navigator.gpu.getPreferredCanvasFormat()
context.configure({
alphaMode: backgroundSetting.alphaMode,
device: gpu.device,
format,
})
return {
canvas,
context,
format,
}
}
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