mirror of
https://github.com/Floriansylvain/WhateverWebGPU.git
synced 2026-08-19 11:43:26 +02:00
feat: add FPS display & grid state management
This commit is contained in:
@@ -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>
|
||||
|
||||
+77
-15
@@ -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",
|
||||
layout: cellPipeline.getBindGroupLayout(0),
|
||||
entries: [
|
||||
{ binding: 0, resource: { buffer: gridUniformBuffer } },
|
||||
{ binding: 1, resource: { buffer: timeBuffer } },
|
||||
],
|
||||
})
|
||||
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,
|
||||
)
|
||||
|
||||
+21
-18
@@ -1,40 +1,43 @@
|
||||
@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;
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) pos: vec2f,
|
||||
@builtin(instance_index) instance: u32,
|
||||
@location(0) pos: vec2f,
|
||||
@builtin(instance_index) instance: u32,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) pos: vec4f,
|
||||
@location(0) cell: vec2f,
|
||||
@builtin(position) pos: vec4f,
|
||||
@location(0) cell: vec2f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertexMain(input: VertexInput) -> VertexOutput {
|
||||
let i = f32(input.instance);
|
||||
let cell = vec2f(i % grid.x, floor(i / grid.x));
|
||||
let cellOffset = cell / grid * 2.0;
|
||||
let gridPos = (input.pos + 1.0) / grid - 1.0 + cellOffset;
|
||||
let i = f32(input.instance);
|
||||
let cell = vec2f(i % grid.x, floor(i / grid.x));
|
||||
let state = f32(cellState[input.instance]);
|
||||
|
||||
var output: VertexOutput;
|
||||
output.pos = vec4f(gridPos, 0.0, 1.0);
|
||||
output.cell = cell;
|
||||
return output;
|
||||
let cellOffset = cell / grid * 2.0;
|
||||
let gridPos = (input.pos * state + 1.0) / grid - 1.0 + cellOffset;
|
||||
|
||||
var output: VertexOutput;
|
||||
output.pos = vec4f(gridPos, 0.0, 1.0);
|
||||
output.cell = cell;
|
||||
return output;
|
||||
}
|
||||
|
||||
fn hueToRGB(hue: f32) -> vec3f {
|
||||
return 0.5 + 0.5 * sin(TAU * (vec3f(hue) + vec3f(0.0, 0.33, 0.66)));
|
||||
return 0.5 + 0.5 * sin(TAU * (vec3f(hue) + vec3f(0.0, 0.33, 0.66)));
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
|
||||
let delta = input.cell - grid * 0.5;
|
||||
let angle = atan2(delta.y, delta.x);
|
||||
let normalizedAngle = (angle / TAU) + 0.5;
|
||||
let hue = fract(normalizedAngle + time * 0.3);
|
||||
return vec4f(hueToRGB(hue), 1.0);
|
||||
let delta = input.cell - grid * 0.5;
|
||||
let angle = atan2(delta.y, delta.x);
|
||||
let normalizedAngle = (angle / TAU) + 0.5;
|
||||
let hue = fract(normalizedAngle + time * 0.3);
|
||||
return vec4f(hueToRGB(hue), 1.0);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user