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> </head>
<body> <body>
<canvas id="canvas" width="512" height="512"></canvas> <canvas id="canvas" width="512" height="512"></canvas>
<p id="fps">FPS:</p>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </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, device: GPUDevice,
cellPipeline: GPURenderPipeline, cellPipeline: GPURenderPipeline,
gridUniformBuffer: GPUBuffer, gridUniformBuffer: GPUBuffer,
timeBuffer: GPUBuffer, timeBuffer: GPUBuffer,
): GPUBindGroup { cellStateStorage: GPUBuffer[],
return device.createBindGroup({ ): GPUBindGroup[] {
label: "Cell renderer bind group", return [
device.createBindGroup({
label: "Cell renderer bind group A",
layout: cellPipeline.getBindGroupLayout(0), layout: cellPipeline.getBindGroupLayout(0),
entries: [ entries: [
{ binding: 0, resource: { buffer: gridUniformBuffer } }, { binding: 0, resource: { buffer: gridUniformBuffer } },
{ binding: 1, resource: { buffer: timeBuffer } }, { 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 { function createVertexBuffer(device: GPUDevice): GPUBuffer {
@@ -113,18 +145,44 @@ function createVertexBuffer(device: GPUDevice): GPUBuffer {
} }
class Renderer { class Renderer {
private lastFrameTime: number = 0
private frameCount: number = 0
private fps: number = 0
private bindGroupIndex: number = 0
private lastBindGroupSwitchTime: number = 0
constructor( constructor(
private device: GPUDevice, private device: GPUDevice,
private context: GPUCanvasContext, private context: GPUCanvasContext,
private cellPipeline: GPURenderPipeline, private cellPipeline: GPURenderPipeline,
private vertexBuffer: GPUBuffer, private vertexBuffer: GPUBuffer,
private bindGroup: GPUBindGroup, private bindGroups: GPUBindGroup[],
private timeBuffer: GPUBuffer, private timeBuffer: GPUBuffer,
private vertices: Float32Array, 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 const time = timeMs / 1000
this.updateFpsCount(timeMs)
this.updateBindGroupIndex(timeMs)
this.device.queue.writeBuffer(this.timeBuffer, 0, new Float32Array([time])) this.device.queue.writeBuffer(this.timeBuffer, 0, new Float32Array([time]))
const encoder = this.device.createCommandEncoder() const encoder = this.device.createCommandEncoder()
@@ -141,7 +199,9 @@ class Renderer {
pass.setPipeline(this.cellPipeline) pass.setPipeline(this.cellPipeline)
pass.setVertexBuffer(0, this.vertexBuffer) 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.draw(this.vertices.length / 2, GRID_SIZE * GRID_SIZE)
pass.end() pass.end()
@@ -158,11 +218,13 @@ async function init() {
const cellPipeline = await createCellPipeline(device, canvasFormat) const cellPipeline = await createCellPipeline(device, canvasFormat)
const gridUniformBuffer = createGridUniformBuffer(device) const gridUniformBuffer = createGridUniformBuffer(device)
const timeBuffer = createTimeBuffer(device) const timeBuffer = createTimeBuffer(device)
const bindGroup = createBindGroup( const cellStateStorage = createStateStorageBuffers(device)
const bindGroups = createBindGroups(
device, device,
cellPipeline, cellPipeline,
gridUniformBuffer, gridUniformBuffer,
timeBuffer, timeBuffer,
cellStateStorage,
) )
const vertexBuffer = createVertexBuffer(device) const vertexBuffer = createVertexBuffer(device)
const vertices = new Float32Array([ const vertices = new Float32Array([
@@ -174,7 +236,7 @@ async function init() {
context, context,
cellPipeline, cellPipeline,
vertexBuffer, vertexBuffer,
bindGroup, bindGroups,
timeBuffer, timeBuffer,
vertices, vertices,
) )
+4 -1
View File
@@ -1,5 +1,6 @@
@group(0) @binding(0) var<uniform> grid: vec2f; @group(0) @binding(0) var<uniform> grid: vec2f;
@group(0) @binding(1) var<uniform> time: f32; @group(0) @binding(1) var<uniform> time: f32;
@group(0) @binding(2) var<storage> cellState: array<u32>;
const TAU: f32 = 6.28318530718; const TAU: f32 = 6.28318530718;
@@ -17,8 +18,10 @@ struct VertexOutput {
fn vertexMain(input: VertexInput) -> VertexOutput { fn vertexMain(input: VertexInput) -> VertexOutput {
let i = f32(input.instance); let i = f32(input.instance);
let cell = vec2f(i % grid.x, floor(i / grid.x)); let cell = vec2f(i % grid.x, floor(i / grid.x));
let state = f32(cellState[input.instance]);
let cellOffset = cell / grid * 2.0; 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; var output: VertexOutput;
output.pos = vec4f(gridPos, 0.0, 1.0); output.pos = vec4f(gridPos, 0.0, 1.0);
+7
View File
@@ -1,11 +1,18 @@
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column;
height: 100vh; height: 100vh;
background-color: #222; background-color: #222;
color: #fff;
font-family: "Courier New", Courier, monospace;
} }
canvas { canvas {