mirror of
https://github.com/Floriansylvain/WhateverWebGPU.git
synced 2026-08-19 11:43:26 +02:00
refac: split code into classes & add reset button
This commit is contained in:
@@ -24,6 +24,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button id="reset-button">Reset Grid</button>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+247
-253
@@ -3,19 +3,25 @@ import "./style.css"
|
|||||||
const GRID_SIZE = 64
|
const GRID_SIZE = 64
|
||||||
let COMPUTE_MS_INTERVAL = 100
|
let COMPUTE_MS_INTERVAL = 100
|
||||||
|
|
||||||
function setupSliders() {
|
class UIController {
|
||||||
const computeIntervalSlider = document.getElementById(
|
static onReset(callback: () => void) {
|
||||||
|
document.getElementById("reset-button")?.addEventListener("click", callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
static setup() {
|
||||||
|
const slider = document.getElementById(
|
||||||
"compute-interval-slider",
|
"compute-interval-slider",
|
||||||
) as HTMLInputElement
|
) as HTMLInputElement
|
||||||
const computeIntervalValue = document.getElementById(
|
const valueLabel = document.getElementById(
|
||||||
"compute-interval-value",
|
"compute-interval-value",
|
||||||
) as HTMLElement
|
) as HTMLElement
|
||||||
|
|
||||||
computeIntervalSlider.addEventListener("input", () => {
|
slider.addEventListener("input", () => {
|
||||||
COMPUTE_MS_INTERVAL = parseInt(computeIntervalSlider.value)
|
COMPUTE_MS_INTERVAL = parseInt(slider.value)
|
||||||
computeIntervalValue.textContent = computeIntervalSlider.value
|
valueLabel.textContent = slider.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getCanvas(): Promise<HTMLCanvasElement> {
|
async function getCanvas(): Promise<HTMLCanvasElement> {
|
||||||
const canvas = document.querySelector<HTMLCanvasElement>("#canvas")
|
const canvas = document.querySelector<HTMLCanvasElement>("#canvas")
|
||||||
@@ -23,102 +29,32 @@ async function getCanvas(): Promise<HTMLCanvasElement> {
|
|||||||
return canvas
|
return canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAdapter(): Promise<GPUAdapter> {
|
async function getDevice(): Promise<GPUDevice> {
|
||||||
if (!navigator.gpu) throw new Error("WebGPU not supported on this browser.")
|
if (!navigator.gpu) throw new Error("WebGPU not supported")
|
||||||
const adapter = await navigator.gpu.requestAdapter()
|
const adapter = await navigator.gpu.requestAdapter()
|
||||||
if (!adapter) throw new Error("No appropriate GPUAdapter found.")
|
if (!adapter) throw new Error("No GPUAdapter found")
|
||||||
return adapter
|
return adapter.requestDevice()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDevice(adapter: GPUAdapter): Promise<GPUDevice> {
|
class Grid {
|
||||||
return await adapter.requestDevice()
|
readonly uniformBuffer: GPUBuffer
|
||||||
}
|
|
||||||
|
|
||||||
function configureContext(
|
constructor(
|
||||||
canvas: HTMLCanvasElement,
|
readonly size: number,
|
||||||
device: GPUDevice,
|
device: GPUDevice,
|
||||||
): GPUCanvasContext {
|
) {
|
||||||
const context = canvas.getContext("webgpu")
|
const data = new Float32Array([size, size])
|
||||||
if (!context) throw new Error("Failed to get WebGPU context.")
|
this.uniformBuffer = device.createBuffer({
|
||||||
const canvasFormat = navigator.gpu.getPreferredCanvasFormat()
|
label: "Grid Uniform",
|
||||||
context.configure({ device, format: canvasFormat })
|
size: data.byteLength,
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
function createVertexBufferLayout(): GPUVertexBufferLayout {
|
|
||||||
return {
|
|
||||||
arrayStride: 8,
|
|
||||||
attributes: [
|
|
||||||
{ format: "float32x2" as GPUVertexFormat, offset: 0, shaderLocation: 0 },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPipelineLayout(
|
|
||||||
device: GPUDevice,
|
|
||||||
bindGroupLayout: GPUBindGroupLayout,
|
|
||||||
): GPUPipelineLayout {
|
|
||||||
return device.createPipelineLayout({
|
|
||||||
label: "Cell Pipeline Layout",
|
|
||||||
bindGroupLayouts: [bindGroupLayout],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createComputePipeline(
|
|
||||||
device: GPUDevice,
|
|
||||||
pipelineLayout: GPUPipelineLayout,
|
|
||||||
): Promise<GPUComputePipeline> {
|
|
||||||
return device.createComputePipeline({
|
|
||||||
label: "Simulation pipeline",
|
|
||||||
layout: pipelineLayout,
|
|
||||||
compute: {
|
|
||||||
module: device.createShaderModule({
|
|
||||||
label: "Game of Life simulation shader",
|
|
||||||
code: (await import("./shaders/simulation.wgsl?raw")).default,
|
|
||||||
}),
|
|
||||||
entryPoint: "computeMain",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createCellPipeline(
|
|
||||||
device: GPUDevice,
|
|
||||||
canvasFormat: GPUTextureFormat,
|
|
||||||
pipelineLayout: GPUPipelineLayout,
|
|
||||||
): Promise<GPURenderPipeline> {
|
|
||||||
const cellShaderModule = device.createShaderModule({
|
|
||||||
label: "Cell shader",
|
|
||||||
code: (await import("./shaders/cell.wgsl?raw")).default,
|
|
||||||
})
|
|
||||||
|
|
||||||
return device.createRenderPipeline({
|
|
||||||
label: "Cell pipeline",
|
|
||||||
layout: pipelineLayout,
|
|
||||||
vertex: {
|
|
||||||
module: cellShaderModule,
|
|
||||||
entryPoint: "vertexMain",
|
|
||||||
buffers: [createVertexBufferLayout()],
|
|
||||||
},
|
|
||||||
fragment: {
|
|
||||||
module: cellShaderModule,
|
|
||||||
entryPoint: "fragmentMain",
|
|
||||||
targets: [{ format: canvasFormat }],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function createGridUniformBuffer(device: GPUDevice): GPUBuffer {
|
|
||||||
const gridUniformArray = new Float32Array([GRID_SIZE, GRID_SIZE])
|
|
||||||
const gridUniformBuffer = device.createBuffer({
|
|
||||||
label: "Grid Uniforms",
|
|
||||||
size: gridUniformArray.byteLength,
|
|
||||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||||
})
|
})
|
||||||
device.queue.writeBuffer(gridUniformBuffer, 0, gridUniformArray)
|
device.queue.writeBuffer(this.uniformBuffer, 0, data)
|
||||||
return gridUniformBuffer
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTimeUniformBuffer(device: GPUDevice): GPUBuffer {
|
class Buffers {
|
||||||
|
static createTimeBuffer(device: GPUDevice): GPUBuffer {
|
||||||
return device.createBuffer({
|
return device.createBuffer({
|
||||||
label: "Time Uniform",
|
label: "Time Uniform",
|
||||||
size: 4,
|
size: 4,
|
||||||
@@ -126,216 +62,274 @@ function createTimeUniformBuffer(device: GPUDevice): GPUBuffer {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStateStorageBuffers(device: GPUDevice): GPUBuffer[] {
|
static createVertexBuffer(device: GPUDevice): [GPUBuffer, Float32Array] {
|
||||||
const cellStateArray = new Uint32Array(GRID_SIZE * GRID_SIZE)
|
const data = new Float32Array([
|
||||||
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) {
|
|
||||||
cellStateArray[i] = Math.random() > 0.6 ? 1 : 0
|
|
||||||
}
|
|
||||||
device.queue.writeBuffer(cellStateStorage[0], 0, cellStateArray)
|
|
||||||
return cellStateStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBindGroupLayout(device: GPUDevice): GPUBindGroupLayout {
|
|
||||||
return device.createBindGroupLayout({
|
|
||||||
label: "Cell Bind Group Layout",
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
binding: 0,
|
|
||||||
visibility:
|
|
||||||
GPUShaderStage.FRAGMENT |
|
|
||||||
GPUShaderStage.VERTEX |
|
|
||||||
GPUShaderStage.COMPUTE,
|
|
||||||
buffer: { type: "uniform" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
binding: 1,
|
|
||||||
visibility: GPUShaderStage.FRAGMENT,
|
|
||||||
buffer: { type: "uniform" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
binding: 2,
|
|
||||||
visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
|
|
||||||
buffer: { type: "read-only-storage" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
binding: 3,
|
|
||||||
visibility: GPUShaderStage.COMPUTE,
|
|
||||||
buffer: { type: "storage" },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBindGroups(
|
|
||||||
device: GPUDevice,
|
|
||||||
gridUniformBuffer: GPUBuffer,
|
|
||||||
timeUniformBuffer: GPUBuffer,
|
|
||||||
cellStateStorage: GPUBuffer[],
|
|
||||||
bindGroupLayout: GPUBindGroupLayout,
|
|
||||||
): GPUBindGroup[] {
|
|
||||||
return [
|
|
||||||
device.createBindGroup({
|
|
||||||
label: "Cell renderer bind group A",
|
|
||||||
layout: bindGroupLayout,
|
|
||||||
entries: [
|
|
||||||
{ binding: 0, resource: { buffer: gridUniformBuffer } },
|
|
||||||
{ binding: 1, resource: { buffer: timeUniformBuffer } },
|
|
||||||
{ binding: 2, resource: { buffer: cellStateStorage[0] } },
|
|
||||||
{ binding: 3, resource: { buffer: cellStateStorage[1] } },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
device.createBindGroup({
|
|
||||||
label: "Cell updater bind group B",
|
|
||||||
layout: bindGroupLayout,
|
|
||||||
entries: [
|
|
||||||
{ binding: 0, resource: { buffer: gridUniformBuffer } },
|
|
||||||
{ binding: 1, resource: { buffer: timeUniformBuffer } },
|
|
||||||
{ binding: 2, resource: { buffer: cellStateStorage[1] } },
|
|
||||||
{ binding: 3, resource: { buffer: cellStateStorage[0] } },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
function createVertexBuffer(device: GPUDevice): GPUBuffer {
|
|
||||||
const vertices = new Float32Array([
|
|
||||||
-0.8, -0.8, 0.8, -0.8, 0.8, 0.8, -0.8, -0.8, 0.8, 0.8, -0.8, 0.8,
|
-0.8, -0.8, 0.8, -0.8, 0.8, 0.8, -0.8, -0.8, 0.8, 0.8, -0.8, 0.8,
|
||||||
])
|
])
|
||||||
const vertexBuffer = device.createBuffer({
|
const buffer = device.createBuffer({
|
||||||
label: "Cell vertices",
|
label: "Cell vertices",
|
||||||
size: vertices.byteLength,
|
size: data.byteLength,
|
||||||
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
||||||
})
|
})
|
||||||
device.queue.writeBuffer(vertexBuffer, 0, vertices)
|
device.queue.writeBuffer(buffer, 0, data)
|
||||||
return vertexBuffer
|
return [buffer, data]
|
||||||
|
}
|
||||||
|
|
||||||
|
static createStateBuffers(device: GPUDevice, size: number): GPUBuffer[] {
|
||||||
|
const data = new Uint32Array(size * size).map(() =>
|
||||||
|
Math.random() > 0.6 ? 1 : 0,
|
||||||
|
)
|
||||||
|
const usage = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
||||||
|
const buffers = [
|
||||||
|
device.createBuffer({ label: "State A", size: data.byteLength, usage }),
|
||||||
|
device.createBuffer({ label: "State B", size: data.byteLength, usage }),
|
||||||
|
]
|
||||||
|
device.queue.writeBuffer(buffers[0], 0, data)
|
||||||
|
device.queue.writeBuffer(buffers[1], 0, data)
|
||||||
|
return buffers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PipelineFactory {
|
||||||
|
static createBindGroupLayout(device: GPUDevice): GPUBindGroupLayout {
|
||||||
|
return device.createBindGroupLayout({
|
||||||
|
label: "BindGroupLayout",
|
||||||
|
entries: [
|
||||||
|
{ binding: 0, visibility: 7, buffer: { type: "uniform" } },
|
||||||
|
{ binding: 1, visibility: 2, buffer: { type: "uniform" } },
|
||||||
|
{ binding: 2, visibility: 5, buffer: { type: "read-only-storage" } },
|
||||||
|
{ binding: 3, visibility: 4, buffer: { type: "storage" } },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static createPipelineLayout(device: GPUDevice, layout: GPUBindGroupLayout) {
|
||||||
|
return device.createPipelineLayout({ bindGroupLayouts: [layout] })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createCompute(
|
||||||
|
device: GPUDevice,
|
||||||
|
layout: GPUPipelineLayout,
|
||||||
|
): Promise<GPUComputePipeline> {
|
||||||
|
const module = device.createShaderModule({
|
||||||
|
label: "Compute shader",
|
||||||
|
code: (await import("./shaders/simulation.wgsl?raw")).default,
|
||||||
|
})
|
||||||
|
return device.createComputePipeline({
|
||||||
|
label: "Simulation pipeline",
|
||||||
|
layout,
|
||||||
|
compute: { module, entryPoint: "computeMain" },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createRender(
|
||||||
|
device: GPUDevice,
|
||||||
|
format: GPUTextureFormat,
|
||||||
|
layout: GPUPipelineLayout,
|
||||||
|
): Promise<GPURenderPipeline> {
|
||||||
|
const module = device.createShaderModule({
|
||||||
|
label: "Cell shader",
|
||||||
|
code: (await import("./shaders/cell.wgsl?raw")).default,
|
||||||
|
})
|
||||||
|
return device.createRenderPipeline({
|
||||||
|
label: "Cell pipeline",
|
||||||
|
layout,
|
||||||
|
vertex: {
|
||||||
|
module,
|
||||||
|
entryPoint: "vertexMain",
|
||||||
|
buffers: [
|
||||||
|
{
|
||||||
|
arrayStride: 8,
|
||||||
|
attributes: [{ format: "float32x2", offset: 0, shaderLocation: 0 }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fragment: {
|
||||||
|
module,
|
||||||
|
entryPoint: "fragmentMain",
|
||||||
|
targets: [{ format }],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Simulation {
|
||||||
|
public bindGroups: GPUBindGroup[]
|
||||||
|
private stateBuffers: GPUBuffer[]
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private device: GPUDevice,
|
||||||
|
grid: Grid,
|
||||||
|
private time: GPUBuffer,
|
||||||
|
state: GPUBuffer[],
|
||||||
|
layout: GPUBindGroupLayout,
|
||||||
|
) {
|
||||||
|
this.stateBuffers = state
|
||||||
|
this.bindGroups = this.createBindGroups(grid, layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(size: number) {
|
||||||
|
const data = new Uint32Array(size * size).map(() =>
|
||||||
|
Math.random() > 0.6 ? 1 : 0,
|
||||||
|
)
|
||||||
|
for (const buffer of this.stateBuffers) {
|
||||||
|
this.device.queue.writeBuffer(buffer, 0, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createBindGroups(
|
||||||
|
grid: Grid,
|
||||||
|
layout: GPUBindGroupLayout,
|
||||||
|
): GPUBindGroup[] {
|
||||||
|
return [
|
||||||
|
this.device.createBindGroup({
|
||||||
|
layout,
|
||||||
|
entries: [
|
||||||
|
{ binding: 0, resource: { buffer: grid.uniformBuffer } },
|
||||||
|
{ binding: 1, resource: { buffer: this.time } },
|
||||||
|
{ binding: 2, resource: { buffer: this.stateBuffers[0] } },
|
||||||
|
{ binding: 3, resource: { buffer: this.stateBuffers[1] } },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
this.device.createBindGroup({
|
||||||
|
layout,
|
||||||
|
entries: [
|
||||||
|
{ binding: 0, resource: { buffer: grid.uniformBuffer } },
|
||||||
|
{ binding: 1, resource: { buffer: this.time } },
|
||||||
|
{ binding: 2, resource: { buffer: this.stateBuffers[1] } },
|
||||||
|
{ binding: 3, resource: { buffer: this.stateBuffers[0] } },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
private lastFrameTime: number = 0
|
private lastFrame = 0
|
||||||
private frameCount: number = 0
|
private frameCount = 0
|
||||||
private fps: number = 0
|
private fps = 0
|
||||||
private bindGroupIndex: number = 0
|
private bindGroupIndex = 0
|
||||||
private lastBindGroupSwitchTime: number = 0
|
private lastSwitch = 0
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private device: GPUDevice,
|
private device: GPUDevice,
|
||||||
private context: GPUCanvasContext,
|
private context: GPUCanvasContext,
|
||||||
private cellPipeline: GPURenderPipeline,
|
private renderPipeline: GPURenderPipeline,
|
||||||
private simulationPipeline: GPUComputePipeline,
|
private computePipeline: GPUComputePipeline,
|
||||||
private vertexBuffer: GPUBuffer,
|
private vertexBuffer: GPUBuffer,
|
||||||
private bindGroups: GPUBindGroup[],
|
|
||||||
private timeBuffer: GPUBuffer,
|
|
||||||
private vertices: Float32Array,
|
private vertices: Float32Array,
|
||||||
|
private simulation: Simulation,
|
||||||
|
private timeBuffer: GPUBuffer,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private updateFpsCount(timeMs: number): void {
|
render(timeMs: number) {
|
||||||
if (this.lastFrameTime === 0) this.lastFrameTime = timeMs
|
this.updateFPS(timeMs)
|
||||||
|
|
||||||
|
const encoder = this.device.createCommandEncoder()
|
||||||
|
this.runComputePass(encoder, timeMs)
|
||||||
|
this.runRenderPass(encoder)
|
||||||
|
this.updateTimeBuffer(timeMs)
|
||||||
|
|
||||||
|
this.device.queue.submit([encoder.finish()])
|
||||||
|
requestAnimationFrame(this.render.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFPS(timeMs: number) {
|
||||||
|
if (!this.lastFrame) this.lastFrame = timeMs
|
||||||
this.frameCount++
|
this.frameCount++
|
||||||
if (timeMs - this.lastFrameTime >= 1000) {
|
|
||||||
|
if (timeMs - this.lastFrame >= 1000) {
|
||||||
this.fps = this.frameCount
|
this.fps = this.frameCount
|
||||||
this.frameCount = 0
|
this.frameCount = 0
|
||||||
this.lastFrameTime = timeMs
|
this.lastFrame = timeMs
|
||||||
document.querySelector("#fps")!.textContent = `FPS: ${this.fps}`
|
document.querySelector("#fps")!.textContent = `FPS: ${this.fps}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateBindGroupIndex(timeMs: number): void {
|
private updateTimeBuffer(timeMs: number) {
|
||||||
if (timeMs - this.lastBindGroupSwitchTime >= COMPUTE_MS_INTERVAL) {
|
const seconds = timeMs / 1000
|
||||||
this.bindGroupIndex = (this.bindGroupIndex + 1) % this.bindGroups.length
|
this.device.queue.writeBuffer(
|
||||||
this.lastBindGroupSwitchTime = timeMs
|
this.timeBuffer,
|
||||||
}
|
0,
|
||||||
|
new Float32Array([seconds]),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async render(timeMs: number): Promise<void> {
|
private runComputePass(encoder: GPUCommandEncoder, timeMs: number) {
|
||||||
const encoder = this.device.createCommandEncoder()
|
if (timeMs - this.lastSwitch >= COMPUTE_MS_INTERVAL) {
|
||||||
|
this.bindGroupIndex = (this.bindGroupIndex + 1) % 2
|
||||||
const computePass = encoder.beginComputePass()
|
this.lastSwitch = timeMs
|
||||||
computePass.setPipeline(this.simulationPipeline)
|
}
|
||||||
computePass.setBindGroup(0, this.bindGroups[this.bindGroupIndex])
|
const pass = encoder.beginComputePass()
|
||||||
|
pass.setPipeline(this.computePipeline)
|
||||||
const workgroupCount = Math.ceil(GRID_SIZE / 8)
|
pass.setBindGroup(0, this.simulation.bindGroups[this.bindGroupIndex])
|
||||||
computePass.dispatchWorkgroups(workgroupCount, workgroupCount)
|
pass.dispatchWorkgroups(Math.ceil(GRID_SIZE / 8), Math.ceil(GRID_SIZE / 8))
|
||||||
|
pass.end()
|
||||||
computePass.end()
|
}
|
||||||
|
|
||||||
const time = timeMs / 1000
|
|
||||||
this.updateFpsCount(timeMs)
|
|
||||||
this.device.queue.writeBuffer(this.timeBuffer, 0, new Float32Array([time]))
|
|
||||||
this.updateBindGroupIndex(timeMs)
|
|
||||||
|
|
||||||
|
private runRenderPass(encoder: GPUCommandEncoder) {
|
||||||
const pass = encoder.beginRenderPass({
|
const pass = encoder.beginRenderPass({
|
||||||
colorAttachments: [
|
colorAttachments: [
|
||||||
{
|
{
|
||||||
view: this.context.getCurrentTexture().createView(),
|
view: this.context.getCurrentTexture().createView(),
|
||||||
loadOp: "clear",
|
loadOp: "clear",
|
||||||
clearValue: [0.0, 0.0, 0.4, 1.0],
|
clearValue: [0.0, 0.0, 0.4, 1],
|
||||||
storeOp: "store",
|
storeOp: "store",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
pass.setPipeline(this.renderPipeline)
|
||||||
pass.setPipeline(this.cellPipeline)
|
|
||||||
pass.setVertexBuffer(0, this.vertexBuffer)
|
pass.setVertexBuffer(0, this.vertexBuffer)
|
||||||
|
pass.setBindGroup(0, this.simulation.bindGroups[this.bindGroupIndex])
|
||||||
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()
|
||||||
|
|
||||||
this.device.queue.submit([encoder.finish()])
|
|
||||||
requestAnimationFrame((time) => this.render(time))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
class Engine {
|
||||||
setupSliders()
|
async start() {
|
||||||
|
UIController.setup()
|
||||||
|
|
||||||
const device = await getDevice(await getAdapter())
|
const device = await getDevice()
|
||||||
const context = configureContext(await getCanvas(), device)
|
const canvas = await getCanvas()
|
||||||
const canvasFormat = navigator.gpu.getPreferredCanvasFormat()
|
const context = canvas.getContext("webgpu")!
|
||||||
|
const format = navigator.gpu.getPreferredCanvasFormat()
|
||||||
|
context.configure({ device, format })
|
||||||
|
|
||||||
const gridUniformBuffer = createGridUniformBuffer(device)
|
const grid = new Grid(GRID_SIZE, device)
|
||||||
const timeBuffer = createTimeUniformBuffer(device)
|
const timeBuffer = Buffers.createTimeBuffer(device)
|
||||||
const cellStateStorage = createStateStorageBuffers(device)
|
const [vertexBuffer, vertices] = Buffers.createVertexBuffer(device)
|
||||||
const bindGroupLayout = createBindGroupLayout(device)
|
const stateBuffers = Buffers.createStateBuffers(device, GRID_SIZE)
|
||||||
const bindGroups = createBindGroups(
|
const layout = PipelineFactory.createBindGroupLayout(device)
|
||||||
device,
|
const pipelineLayout = PipelineFactory.createPipelineLayout(device, layout)
|
||||||
gridUniformBuffer,
|
|
||||||
timeBuffer,
|
const [renderPipeline, computePipeline] = await Promise.all([
|
||||||
cellStateStorage,
|
PipelineFactory.createRender(device, format, pipelineLayout),
|
||||||
bindGroupLayout,
|
PipelineFactory.createCompute(device, pipelineLayout),
|
||||||
)
|
|
||||||
const pipelineLayout = createPipelineLayout(device, bindGroupLayout)
|
|
||||||
const cellPipeline = await createCellPipeline(
|
|
||||||
device,
|
|
||||||
canvasFormat,
|
|
||||||
pipelineLayout,
|
|
||||||
)
|
|
||||||
const simulationPipeline = await createComputePipeline(device, pipelineLayout)
|
|
||||||
const vertexBuffer = createVertexBuffer(device)
|
|
||||||
const vertices = new Float32Array([
|
|
||||||
-0.8, -0.8, 0.8, -0.8, 0.8, 0.8, -0.8, -0.8, 0.8, 0.8, -0.8, 0.8,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const simulation = new Simulation(
|
||||||
|
device,
|
||||||
|
grid,
|
||||||
|
timeBuffer,
|
||||||
|
stateBuffers,
|
||||||
|
layout,
|
||||||
|
)
|
||||||
const renderer = new Renderer(
|
const renderer = new Renderer(
|
||||||
device,
|
device,
|
||||||
context,
|
context,
|
||||||
cellPipeline,
|
renderPipeline,
|
||||||
simulationPipeline,
|
computePipeline,
|
||||||
vertexBuffer,
|
vertexBuffer,
|
||||||
bindGroups,
|
|
||||||
timeBuffer,
|
|
||||||
vertices,
|
vertices,
|
||||||
|
simulation,
|
||||||
|
timeBuffer,
|
||||||
)
|
)
|
||||||
requestAnimationFrame((time) => renderer.render(time))
|
|
||||||
|
UIController.onReset(() => simulation.reset(GRID_SIZE))
|
||||||
|
|
||||||
|
requestAnimationFrame(renderer.render.bind(renderer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init().catch((error) => {
|
new Engine().start().catch(console.error)
|
||||||
console.error("Error initializing WebGPU:", error)
|
|
||||||
})
|
|
||||||
|
|||||||
Reference in New Issue
Block a user