feat: add FPS display & grid state management

This commit is contained in:
Florian Sylvain
2025-04-06 02:51:45 +02:00
parent c6b9d1a9bc
commit 63420f39a1
4 changed files with 106 additions and 33 deletions
+1
View File
@@ -8,6 +8,7 @@
</head>
<body>
<canvas id="canvas" width="512" height="512"></canvas>
<p id="fps">FPS:</p>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+72 -10
View File
@@ -83,20 +83,52 @@ function createTimeBuffer(device: GPUDevice): GPUBuffer {
})
}
function createBindGroup(
function createStateStorageBuffers(device: GPUDevice): GPUBuffer[] {
const cellStateArray = new Uint32Array(GRID_SIZE * GRID_SIZE)
const size = cellStateArray.byteLength
const usage = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
const cellStateStorage = [
device.createBuffer({ label: "Cell State A", size, usage }),
device.createBuffer({ label: "Cell State B", size, usage }),
]
for (let i = 0; i < cellStateArray.length; i += 3) {
cellStateArray[i] = 1
}
device.queue.writeBuffer(cellStateStorage[0], 0, cellStateArray)
for (let i = 0; i < cellStateArray.length; i++) {
cellStateArray[i] = i % 2
}
device.queue.writeBuffer(cellStateStorage[1], 0, cellStateArray)
return cellStateStorage
}
function createBindGroups(
device: GPUDevice,
cellPipeline: GPURenderPipeline,
gridUniformBuffer: GPUBuffer,
timeBuffer: GPUBuffer,
): GPUBindGroup {
return device.createBindGroup({
label: "Cell renderer bind group",
cellStateStorage: GPUBuffer[],
): GPUBindGroup[] {
return [
device.createBindGroup({
label: "Cell renderer bind group A",
layout: cellPipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: gridUniformBuffer } },
{ binding: 1, resource: { buffer: timeBuffer } },
{ binding: 2, resource: { buffer: cellStateStorage[0] } },
],
})
}),
device.createBindGroup({
label: "Cell updater bind group B",
layout: cellPipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: gridUniformBuffer } },
{ binding: 1, resource: { buffer: timeBuffer } },
{ binding: 2, resource: { buffer: cellStateStorage[1] } },
],
}),
]
}
function createVertexBuffer(device: GPUDevice): GPUBuffer {
@@ -113,18 +145,44 @@ function createVertexBuffer(device: GPUDevice): GPUBuffer {
}
class Renderer {
private lastFrameTime: number = 0
private frameCount: number = 0
private fps: number = 0
private bindGroupIndex: number = 0
private lastBindGroupSwitchTime: number = 0
constructor(
private device: GPUDevice,
private context: GPUCanvasContext,
private cellPipeline: GPURenderPipeline,
private vertexBuffer: GPUBuffer,
private bindGroup: GPUBindGroup,
private bindGroups: GPUBindGroup[],
private timeBuffer: GPUBuffer,
private vertices: Float32Array,
) {}
public render(timeMs: number): void {
private updateFpsCount(timeMs: number): void {
if (this.lastFrameTime === 0) this.lastFrameTime = timeMs
this.frameCount++
if (timeMs - this.lastFrameTime >= 1000) {
this.fps = this.frameCount
this.frameCount = 0
this.lastFrameTime = timeMs
document.querySelector("#fps")!.textContent = `FPS: ${this.fps}`
}
}
private updateBindGroupIndex(timeMs: number): void {
if (timeMs - this.lastBindGroupSwitchTime >= 500) {
this.bindGroupIndex = (this.bindGroupIndex + 1) % this.bindGroups.length
this.lastBindGroupSwitchTime = timeMs
}
}
public async render(timeMs: number): Promise<void> {
const time = timeMs / 1000
this.updateFpsCount(timeMs)
this.updateBindGroupIndex(timeMs)
this.device.queue.writeBuffer(this.timeBuffer, 0, new Float32Array([time]))
const encoder = this.device.createCommandEncoder()
@@ -141,7 +199,9 @@ class Renderer {
pass.setPipeline(this.cellPipeline)
pass.setVertexBuffer(0, this.vertexBuffer)
pass.setBindGroup(0, this.bindGroup)
pass.setBindGroup(0, this.bindGroups[this.bindGroupIndex])
pass.draw(this.vertices.length / 2, GRID_SIZE * GRID_SIZE)
pass.end()
@@ -158,11 +218,13 @@ async function init() {
const cellPipeline = await createCellPipeline(device, canvasFormat)
const gridUniformBuffer = createGridUniformBuffer(device)
const timeBuffer = createTimeBuffer(device)
const bindGroup = createBindGroup(
const cellStateStorage = createStateStorageBuffers(device)
const bindGroups = createBindGroups(
device,
cellPipeline,
gridUniformBuffer,
timeBuffer,
cellStateStorage,
)
const vertexBuffer = createVertexBuffer(device)
const vertices = new Float32Array([
@@ -174,7 +236,7 @@ async function init() {
context,
cellPipeline,
vertexBuffer,
bindGroup,
bindGroups,
timeBuffer,
vertices,
)
+4 -1
View File
@@ -1,5 +1,6 @@
@group(0) @binding(0) var<uniform> grid: vec2f;
@group(0) @binding(1) var<uniform> time: f32;
@group(0) @binding(2) var<storage> cellState: array<u32>;
const TAU: f32 = 6.28318530718;
@@ -17,8 +18,10 @@ struct VertexOutput {
fn vertexMain(input: VertexInput) -> VertexOutput {
let i = f32(input.instance);
let cell = vec2f(i % grid.x, floor(i / grid.x));
let state = f32(cellState[input.instance]);
let cellOffset = cell / grid * 2.0;
let gridPos = (input.pos + 1.0) / grid - 1.0 + cellOffset;
let gridPos = (input.pos * state + 1.0) / grid - 1.0 + cellOffset;
var output: VertexOutput;
output.pos = vec4f(gridPos, 0.0, 1.0);
+7
View File
@@ -1,11 +1,18 @@
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100vh;
background-color: #222;
color: #fff;
font-family: "Courier New", Courier, monospace;
}
canvas {