From 04b138dc9991fdaa9f30ce1ffa1053c3827506a4 Mon Sep 17 00:00:00 2001 From: Sebastian Macke Date: Wed, 26 Jun 2024 23:41:50 +0200 Subject: [PATCH] Switch to asynchronous handling of computation and rendering --- src/scripts/AbstractGPURunner.ts | 8 +++- src/scripts/GPURenderRunner.ts | 6 ++- src/scripts/RunGPURunner.ts | 48 ++++++++++++++++++- src/scripts/diffuse/diffuse.ts | 4 +- src/scripts/diffuse/diffuse.wgsl | 2 +- src/scripts/fluid/fluid.ts | 12 +++-- src/scripts/light/light.ts | 10 ++-- src/scripts/light/scene/scene.ts | 1 - src/scripts/light2/light.ts | 10 ++-- .../light_monte_carlo_path_tracing/light.ts | 10 ++-- .../propagate.wgsl | 2 +- src/scripts/raytrace/raytrace.ts | 3 +- src/scripts/raytrace/smallpt.wgsl | 2 +- src/scripts/sdf/sdf.ts | 2 +- src/scripts/ui.ts | 19 ++++++++ 15 files changed, 111 insertions(+), 28 deletions(-) diff --git a/src/scripts/AbstractGPURunner.ts b/src/scripts/AbstractGPURunner.ts index 133d734..eb5955d 100644 --- a/src/scripts/AbstractGPURunner.ts +++ b/src/scripts/AbstractGPURunner.ts @@ -4,7 +4,8 @@ export enum RunnerType { HTML = 1, GRAPHIC = 2, ANIM= 3, - BENCHMARK= 4, + ASYNCANIM= 4, + BENCHMARK= 5, } export interface GPURunner { @@ -12,6 +13,7 @@ export interface GPURunner { getRenderInfo(): {textures: Texture[], fragmentShaderFilenames: string[]}; getCommandBuffer(): GPUCommandBuffer; Run(): Promise; + Render(): Promise; Init(): Promise; Destroy(): Promise; } @@ -22,6 +24,10 @@ export abstract class GPUAbstractRunner implements GPURunner { public abstract Init(): Promise public abstract Run(): Promise + Render(): Promise { + throw new Error("Method not implemented."); + } + getCommandBuffer(): GPUCommandBuffer { throw new Error("Method not implemented."); } diff --git a/src/scripts/GPURenderRunner.ts b/src/scripts/GPURenderRunner.ts index 0c07ef8..f318c9b 100644 --- a/src/scripts/GPURenderRunner.ts +++ b/src/scripts/GPURenderRunner.ts @@ -11,6 +11,10 @@ export class GPURenderRunner implements GPURunner { this.runner = runner } + async Render() { + GPU.device.queue.submit([this.render.getCommandBuffer()]) + } + getType(): RunnerType { return this.runner.getType() } @@ -28,7 +32,7 @@ export class GPURenderRunner implements GPURunner { } async Run() { - GPU.device.queue.submit([this.runner.getCommandBuffer(), this.render.getCommandBuffer()]) + await this.runner.Run() } getCommandBuffer(): GPUCommandBuffer { diff --git a/src/scripts/RunGPURunner.ts b/src/scripts/RunGPURunner.ts index 4e32d8d..2ed603d 100644 --- a/src/scripts/RunGPURunner.ts +++ b/src/scripts/RunGPURunner.ts @@ -1,6 +1,6 @@ import {GPU} from "./webgpu/gpu"; import {GPURunner, RunnerType} from "./AbstractGPURunner"; -import {MeasureFrame, ShowError} from "./ui"; +import {MeasureFrame, MeasureIteration, ShowError} from "./ui"; let stop_immediately = true; @@ -89,6 +89,8 @@ async function HandleAnimation(runner: GPURunner) { let frame = async () => { try { await runner.Run() + await runner.Render() + await GPU.device.queue.onSubmittedWorkDone() } catch (e) { ShowError("GPU error", e as Error) await runner.Destroy() @@ -109,6 +111,48 @@ async function HandleAnimation(runner: GPURunner) { }) } +async function HandleAsyncAnimation(runner: GPURunner) { + await SwitchToGraphic() + + // never return from this function unless the animation is stopped + await new Promise(async resolve => { + let nIter = 0 + let queuePartFinished = async() => { + if (stop_immediately) { + return + } + await runner.Run() + nIter++ + GPU.device.queue.onSubmittedWorkDone().then(() => queuePartFinished()) + } + // fill the queue + queuePartFinished().then(r => {}) + queuePartFinished().then(r => {}) + + let frame = async () => { + try { + await runner.Render() + } catch (e) { + ShowError("GPU error", e as Error) + await runner.Destroy() + resolve(0) + throw e + } + MeasureIteration(nIter) + if (stop_immediately) { + await GPU.device.queue.onSubmittedWorkDone() + await runner.Destroy() + document.getElementById("textFps").innerHTML = "" + resolve(0) + return; + } + requestAnimationFrame(frame) + } + requestAnimationFrame(frame) + }) +} + + async function HandleBenchmark(runner: GPURunner) { await SwitchToHTML() @@ -160,6 +204,8 @@ export async function HandleRunner(runner: GPURunner) { return HandleGraphic(runner) case RunnerType.ANIM: return HandleAnimation(runner) + case RunnerType.ASYNCANIM: + return HandleAsyncAnimation(runner) case RunnerType.BENCHMARK: return HandleBenchmark(runner) } diff --git a/src/scripts/diffuse/diffuse.ts b/src/scripts/diffuse/diffuse.ts index f2d4750..102c1c1 100644 --- a/src/scripts/diffuse/diffuse.ts +++ b/src/scripts/diffuse/diffuse.ts @@ -2,7 +2,6 @@ import {GPU} from "../webgpu/gpu"; import {Texture} from "../webgpu/texture"; import {Buffer} from "../webgpu/buffer"; import {GPUAbstractRunner, RunnerType} from "../AbstractGPURunner"; -import {Render} from "../render/render"; export class Diffuse extends GPUAbstractRunner { width: number; @@ -47,7 +46,7 @@ export class Diffuse extends GPUAbstractRunner { } override getType(): RunnerType { - return RunnerType.ANIM + return RunnerType.ASYNCANIM } override async Destroy() { @@ -275,7 +274,6 @@ export class Diffuse extends GPUAbstractRunner { override async Run() { GPU.device.queue.submit([this.getCommandBuffer()]) - await GPU.device.queue.onSubmittedWorkDone() } override getRenderInfo(): { textures: Texture[]; fragmentShaderFilenames: string[] } { diff --git a/src/scripts/diffuse/diffuse.wgsl b/src/scripts/diffuse/diffuse.wgsl index e39b06c..b654875 100644 --- a/src/scripts/diffuse/diffuse.wgsl +++ b/src/scripts/diffuse/diffuse.wgsl @@ -9,7 +9,7 @@ struct StagingBuffer { @group(0) @binding(2) var staging: StagingBuffer; const PI = 3.14159265359; -const SAMPLES = 40; +const SAMPLES = 10; const MAXDEPTH = 4; struct Ray { diff --git a/src/scripts/fluid/fluid.ts b/src/scripts/fluid/fluid.ts index 8ab658d..3b2bdfe 100644 --- a/src/scripts/fluid/fluid.ts +++ b/src/scripts/fluid/fluid.ts @@ -29,7 +29,7 @@ import {GPUAbstractRunner, RunnerType} from "../AbstractGPURunner"; export class Fluid extends GPUAbstractRunner { public getType(): RunnerType { - return RunnerType.ANIM + return RunnerType.ASYNCANIM } public async Destroy() { } @@ -118,15 +118,19 @@ export class Fluid extends GPUAbstractRunner { this.div.GetCommandBuffer(), this.poisson.GetCommandBuffer(), this.project.GetCommandBuffer(), - this.render.GetCommandBuffer() ]) - await GPU.device.queue.onSubmittedWorkDone(); //await GPU.Render(this.transport.texturea); //await GPU.Render(this.transport.texturea); //await GPU.Render(this.poisson.pressurea); } - async InitVelocity() { + async Render() { + GPU.device.queue.submit([ + this.render.GetCommandBuffer() + ]) + } + + async InitVelocity() { let vel = new Uint16Array(this.width * this.height * 4) /* for (let j = 0; j < this.height; j++) diff --git a/src/scripts/light/light.ts b/src/scripts/light/light.ts index 3a1dad3..6e4f0e8 100644 --- a/src/scripts/light/light.ts +++ b/src/scripts/light/light.ts @@ -42,7 +42,7 @@ export class LightPropagation extends GPUAbstractRunner { } getType(): RunnerType { - return RunnerType.ANIM + return RunnerType.ASYNCANIM } async Destroy() { @@ -153,7 +153,7 @@ export class LightPropagation extends GPUAbstractRunner { GPU.device.queue.writeBuffer(this.stagingBuffer.buffer, 0, this.stagingData) let encoder: GPUCommandEncoder = GPU.CreateCommandEncoder(); - for(let i = 0; i < 40; i++) { + for(let i = 0; i < 10; i++) { let pass: GPUComputePassEncoder = encoder.beginComputePass(); pass.setBindGroup(0, this.bind_group); pass.setBindGroup(1, this.scene_bind_group); @@ -170,8 +170,10 @@ export class LightPropagation extends GPUAbstractRunner { } async Run() { - GPU.device.queue.submit([this.scene.GetCommandBuffer(), this.GetCommandBuffer(), this.render.getCommandBuffer()]); - await GPU.device.queue.onSubmittedWorkDone(); + GPU.device.queue.submit([this.scene.GetCommandBuffer(), this.GetCommandBuffer()]); } + async Render() { + GPU.device.queue.submit([this.render.getCommandBuffer()]); + } } diff --git a/src/scripts/light/scene/scene.ts b/src/scripts/light/scene/scene.ts index bdae364..157f306 100755 --- a/src/scripts/light/scene/scene.ts +++ b/src/scripts/light/scene/scene.ts @@ -120,6 +120,5 @@ export class LightScene { async Run() { GPU.device.queue.submit([this.GetCommandBuffer()]); - await GPU.device.queue.onSubmittedWorkDone() } } diff --git a/src/scripts/light2/light.ts b/src/scripts/light2/light.ts index 33dbc7e..6f55bb3 100644 --- a/src/scripts/light2/light.ts +++ b/src/scripts/light2/light.ts @@ -46,7 +46,7 @@ export class LightPropagation2 extends GPUAbstractRunner { } getType(): RunnerType { - return RunnerType.ANIM + return RunnerType.ASYNCANIM } async Destroy() { @@ -171,7 +171,7 @@ export class LightPropagation2 extends GPUAbstractRunner { GPU.device.queue.writeBuffer(this.stagingBuffer.buffer, 0, this.stagingData) let encoder: GPUCommandEncoder = GPU.CreateCommandEncoder(); - for(let i = 0; i < 80; i++) { + for(let i = 0; i < 10; i++) { let pass: GPUComputePassEncoder = encoder.beginComputePass(); pass.setBindGroup(0, this.bind_group_atob); pass.setBindGroup(1, this.scene_bind_group); @@ -196,8 +196,10 @@ export class LightPropagation2 extends GPUAbstractRunner { } async Run() { - GPU.device.queue.submit([this.scene.GetCommandBuffer(), this.GetCommandBuffer(), this.render.getCommandBuffer()]); - await GPU.device.queue.onSubmittedWorkDone(); + GPU.device.queue.submit([this.scene.GetCommandBuffer(), this.GetCommandBuffer()]); } + async Render() { + GPU.device.queue.submit([this.render.getCommandBuffer()]); + } } diff --git a/src/scripts/light_monte_carlo_path_tracing/light.ts b/src/scripts/light_monte_carlo_path_tracing/light.ts index e10df10..6a260c5 100644 --- a/src/scripts/light_monte_carlo_path_tracing/light.ts +++ b/src/scripts/light_monte_carlo_path_tracing/light.ts @@ -37,7 +37,7 @@ export class LightMonteCarloPathTracing extends GPUAbstractRunner { } getType(): RunnerType { - return RunnerType.ANIM + return RunnerType.ASYNCANIM } async Destroy() { @@ -192,8 +192,12 @@ export class LightMonteCarloPathTracing extends GPUAbstractRunner { } async Run() { - GPU.device.queue.submit([this.scene.GetCommandBuffer(), this.GetCommandBuffer(), this.render.getCommandBuffer()]); - await GPU.device.queue.onSubmittedWorkDone(); + GPU.device.queue.submit([this.scene.GetCommandBuffer(), this.GetCommandBuffer()]); } + async Render() { + GPU.device.queue.submit([this.render.getCommandBuffer()]); + } + + } diff --git a/src/scripts/light_monte_carlo_path_tracing/propagate.wgsl b/src/scripts/light_monte_carlo_path_tracing/propagate.wgsl index 6d54fcb..ef935ae 100644 --- a/src/scripts/light_monte_carlo_path_tracing/propagate.wgsl +++ b/src/scripts/light_monte_carlo_path_tracing/propagate.wgsl @@ -11,7 +11,7 @@ struct StagingBuffer { const PI = 3.14159265359; -const SAMPLES = 10; +const SAMPLES = 2; const MAXDEPTH = 700; var seed: u32 = 0u; diff --git a/src/scripts/raytrace/raytrace.ts b/src/scripts/raytrace/raytrace.ts index 2074df4..97401f3 100755 --- a/src/scripts/raytrace/raytrace.ts +++ b/src/scripts/raytrace/raytrace.ts @@ -34,7 +34,7 @@ export class Raytrace extends GPUAbstractRunner { } override getType(): RunnerType { - return RunnerType.ANIM + return RunnerType.ASYNCANIM } override async Destroy() { @@ -143,6 +143,5 @@ export class Raytrace extends GPUAbstractRunner { override async Run() { GPU.device.queue.submit([this.getCommandBuffer()]) - await GPU.device.queue.onSubmittedWorkDone() } } diff --git a/src/scripts/raytrace/smallpt.wgsl b/src/scripts/raytrace/smallpt.wgsl index b47887c..13c8f98 100755 --- a/src/scripts/raytrace/smallpt.wgsl +++ b/src/scripts/raytrace/smallpt.wgsl @@ -10,7 +10,7 @@ struct StagingBuffer { // Play with the two following values to change quality. // You want as many samples as your GPU can bear. :) -const SAMPLES = 60; +const SAMPLES = 10; const MAXDEPTH = 4; const PI = 3.14159265359; diff --git a/src/scripts/sdf/sdf.ts b/src/scripts/sdf/sdf.ts index 026db69..7e9b5e1 100755 --- a/src/scripts/sdf/sdf.ts +++ b/src/scripts/sdf/sdf.ts @@ -24,7 +24,7 @@ export class SDF extends GPUAbstractRunner { } getType(): RunnerType { - return RunnerType.ANIM + return RunnerType.ASYNCANIM } diff --git a/src/scripts/ui.ts b/src/scripts/ui.ts index 23b04ad..f47d61a 100755 --- a/src/scripts/ui.ts +++ b/src/scripts/ui.ts @@ -3,6 +3,7 @@ import {PrepareSidebar, ShowFeatures} from "./sidebar"; let lastframeTime = 0 as number let nFrame = 0 as number +let lastIteration = 0 as number export function MeasureFrame() { if (lastframeTime == 0) { @@ -19,6 +20,24 @@ export function MeasureFrame() { } } +export function MeasureIteration(currentIteration: number) { + if (lastframeTime == 0) { + lastframeTime = performance.now() + nFrame = 0 + lastIteration = 0 + } + nFrame++ + if (nFrame >= 20) { + let currentFrameTime = performance.now() + let fps = (currentIteration - lastIteration) / (currentFrameTime - lastframeTime) * 1000 + lastframeTime = currentFrameTime + lastIteration = currentIteration + nFrame = 0 + document.getElementById("textFps").innerHTML = fps.toFixed(2) + " fps" + } +} + + export function ShowError(message: string, e: Error) { document.getElementById("screen").style.visibility = "hidden"