This commit is contained in:
Andras Schmelczer 2026-05-21 07:43:10 +01:00
commit 6bc125be1c
104 changed files with 3084 additions and 2410 deletions

View file

@ -1,10 +1,13 @@
import { appConfig } from '../config';
import { AgentPipeline } from '../pipelines/agents/agent-pipeline';
import { BrushPipeline } from '../pipelines/brush/brush-pipeline';
import { DiffusionPipeline } from '../pipelines/diffusion/diffusion-pipeline';
import { EraserAgentPipeline } from '../pipelines/eraser/eraser-agent-pipeline';
import { EraserTexturePipeline } from '../pipelines/eraser/eraser-texture-pipeline';
import { RenderPipeline } from '../pipelines/render/render-pipeline';
import { settings } from '../settings';
import { CanvasReadbackRequest } from './game-loop-types';
import { GpuProfiler } from './gpu-profiler';
import { SimulationTextures } from './simulation-textures';
interface SimulationFramePipelines {
@ -17,15 +20,14 @@ interface SimulationFramePipelines {
}
export class SimulationFrameRenderer {
private static readonly SOURCE_ACTIVE_FRAMES_AFTER_WRITE = 600;
private sourceActiveFramesRemaining = 0;
private sourceMapsCleared = true;
public constructor(
private readonly device: GPUDevice,
private readonly textures: SimulationTextures,
private readonly pipelines: SimulationFramePipelines
private readonly pipelines: SimulationFramePipelines,
private readonly gpuProfiler: GpuProfiler | null = null
) {}
public resetSourceMapActivity(): void {
@ -33,11 +35,16 @@ export class SimulationFrameRenderer {
this.sourceMapsCleared = true;
}
public get isSourceMapActive(): boolean {
return this.sourceActiveFramesRemaining > 0;
}
public execute(
isErasing: boolean,
canvasReadbackRequest?: CanvasReadbackRequest | null
): void {
const commandEncoder = this.device.createCommandEncoder();
this.gpuProfiler?.beginFrame();
this.textures.copyTrailMapAToB(commandEncoder);
let wroteSourceMap = false;
@ -48,24 +55,29 @@ export class SimulationFrameRenderer {
commandEncoder,
eraserMask,
this.textures.sourceMapA.getTextureView(),
this.textures.trailMapB.getTextureView()
this.textures.trailMapB.getTextureView(),
this.gpuProfiler?.timestampWrites('eraserTexture')
);
this.pipelines.eraserAgentPipeline.execute(
commandEncoder,
eraserMask,
this.gpuProfiler?.timestampWrites('eraserAgent')
);
this.pipelines.eraserAgentPipeline.execute(commandEncoder, eraserMask);
}
} else {
wroteSourceMap = this.pipelines.brushPipeline.executeMultiTarget(
commandEncoder,
this.textures.sourceMapA.getTextureView()
this.textures.sourceMapA.getTextureView(),
this.gpuProfiler?.timestampWrites('brush')
);
}
if (wroteSourceMap) {
this.sourceActiveFramesRemaining =
SimulationFrameRenderer.SOURCE_ACTIVE_FRAMES_AFTER_WRITE;
this.sourceActiveFramesRemaining = getSourceActiveFrameCount();
this.sourceMapsCleared = false;
}
const useSourceMap = this.sourceActiveFramesRemaining > 0;
const useSourceMap = this.isSourceMapActive;
if (!useSourceMap && !this.sourceMapsCleared) {
this.textures.clearSourceMaps(commandEncoder);
this.sourceMapsCleared = true;
@ -74,19 +86,22 @@ export class SimulationFrameRenderer {
this.pipelines.agentPipeline.execute(
commandEncoder,
this.textures.trailMapA.getTextureView(),
this.textures.trailMapB.getTextureView()
this.textures.trailMapB.getTextureView(),
this.gpuProfiler?.timestampWrites('agent')
);
this.pipelines.diffusionPipeline.execute(
commandEncoder,
this.textures.trailMapB.getTextureView(),
this.textures.trailMapA.getTextureView(),
this.textures.trailMapA.getSize()
this.textures.trailMapA.getSize(),
this.gpuProfiler?.timestampWrites('trailDiffusion')
);
const canvasTexture = this.pipelines.renderPipeline.execute(
commandEncoder,
this.textures.trailMapA.getTextureView(),
this.textures.sourceMapA.getTextureView(),
useSourceMap
useSourceMap,
this.gpuProfiler?.timestampWrites('render')
);
canvasReadbackRequest?.encode(commandEncoder, canvasTexture);
@ -95,10 +110,13 @@ export class SimulationFrameRenderer {
commandEncoder,
this.textures.sourceMapA.getTextureView(),
this.textures.sourceMapB.getTextureView(),
this.textures.sourceMapB.getSize()
this.textures.sourceMapB.getSize(),
this.gpuProfiler?.timestampWrites('sourceDiffusion')
);
}
const afterGpuProfileSubmit = this.gpuProfiler?.resolve(commandEncoder);
this.device.queue.submit([commandEncoder.finish()]);
afterGpuProfileSubmit?.();
canvasReadbackRequest?.afterSubmit();
if (useSourceMap) {
this.textures.swapSourceMaps();
@ -106,3 +124,12 @@ export class SimulationFrameRenderer {
}
}
}
const getSourceActiveFrameCount = (): number => {
const frameCount =
settings.brushEffectDuration * appConfig.simulation.brushEffectFramesPerSecond;
if (Number.isFinite(frameCount) && frameCount > 0) {
return Math.ceil(frameCount);
}
return Math.max(1, appConfig.simulation.sourceActiveFramesAfterWrite);
};