import { Orton, Data } from "https://cdn.jsdelivr.net/npm/orton@0.0.0/dist/Orton.min.js"
import { vert, frag, comp } from "./shaderSource.js"
const random = (min, max) => min + (Math.random() * (max - min));
const GRID_SIZE = 16 * Math.ceil(random(0, 32))
const DELAY_TIME = 1000 / 30
const canvas = document.querySelector("canvas")
const O = await Orton.init(canvas)
format: O.preferredFormat,
const cellVerticesData = Data(cellVertices, "f32")
const cellVertexBuffer = O.Buffer(device, {
size: cellVerticesData.byteLength,
usage: ["VERTEX", "COPY_DST"],
O.queue.writeBuffer(cellVertexBuffer, 0, cellVerticesData)
const positionAttribute = O.createVertexAttribute({
const cellVertexBufferLayout = O.createVertexBufferLayout({
attributes: [positionAttribute],
const gridData = Data([GRID_SIZE, GRID_SIZE])
const gridUniformBuffer = O.Buffer(device, {
size: gridData.byteLength,
usage: ["VERTEX", "UNIFORM", "COPY_DST"],
O.queue.writeBuffer(gridUniformBuffer, 0, gridData)
const gridUniformBinding = O.Binding({
visibility: ["VERTEX", "COMPUTE"],
buffer: gridUniformBuffer,
const cellStateData = Data(GRID_SIZE * GRID_SIZE, "u32")
const cellStateStorage = [
size: cellStateData.byteLength,
usage: ["STORAGE", "COPY_DST"],
size: cellStateData.byteLength,
usage: ["STORAGE", "COPY_DST"],
for (let i = 0; i < cellStateData.length; i++) {
cellStateData[i] = Math.random() >= 0.5 ? 0 : 1
O.queue.writeBuffer(cellStateStorage[0], 0, cellStateData)
const cellStateReadStorageBindingA = O.Binding({
type: "read-only-storage",
visibility: ["VERTEX", "COMPUTE"],
buffer: cellStateStorage[0],
const cellStateWriteStorageBindingA = O.Binding({
buffer: cellStateStorage[1],
const cellStateReadStorageBindingB = O.Binding({
...cellStateReadStorageBindingA,
buffer: cellStateStorage[1],
const cellStateWriteStorageBindingB = O.Binding({
...cellStateWriteStorageBindingA,
buffer: cellStateStorage[0],
let BindGroupA = O.BindGroup(device, [
cellStateReadStorageBindingA,
cellStateWriteStorageBindingA,
let BindGroupB = O.BindGroup(device, [
cellStateReadStorageBindingB,
cellStateWriteStorageBindingB,
const pipelineLayout = O.PipelineLayout(device, [BindGroupA.layout])
const computePipeline = O.ComputePipeline(device, {
compute: O.Shader.Compute(device, comp),
const renderPipeline = O.RenderPipeline(device, {
vertex: O.Shader.Vertex(device, {
buffers: [cellVertexBufferLayout],
fragment: O.Shader.Fragment(device, {
targets: [{ format: O.preferredFormat }],
const WORKGROUP_COUNT = Math.ceil(GRID_SIZE / WORKGROUP_SIZE)
const commandEncoder = O.CommandEncoder(device)
.beginComputePass(commandEncoder)
.setPipeline(computePipeline)
.setBindGroup(0, BindGroupA.bindGroup)
.dispatchWorkgroups(WORKGROUP_COUNT, WORKGROUP_COUNT, 1)
const renderPassDescriptor = O.createRenderPassDescriptor({
clearValue: [0, 0, 0, 1],
.beginRenderPass(commandEncoder, renderPassDescriptor)
.setPipeline(renderPipeline)
.setVertexBuffer(0, cellVertexBuffer)
.setBindGroup(0, BindGroupB.bindGroup)
.draw(cellVertices.length, GRID_SIZE * GRID_SIZE)
O.queue.submit([commandEncoder.finish()])
;[BindGroupA, BindGroupB] = [BindGroupB, BindGroupA]
setInterval(render, DELAY_TIME)