Enhance performance metrics in rendering layers

- Added new metrics to PerformanceOverlay for tracking render submissions, noops, and various CPU timings.
- Updated TerritoryLayer to optimize user settings synchronization and reduce unnecessary refresh calls.
- Enhanced Worker components to collect and report detailed rendering performance metrics, including frame compute and territory pass timings.
- Improved WorkerTerritoryRenderer to profile rendering phases
This commit is contained in:
scamiv
2026-02-04 19:47:45 +01:00
parent f134e5ba1a
commit dbf2d34452
5 changed files with 472 additions and 55 deletions
@@ -549,6 +549,20 @@ export class PerformanceOverlay extends LitElement implements Layer {
rfQueueMax: number | null;
rfHandlerAvg: number | null;
rfHandlerMax: number | null;
renderSubmittedCount: number | null;
renderNoopCount: number | null;
renderCpuTotalAvg: number | null;
renderCpuTotalMax: number | null;
renderGetTextureAvg: number | null;
renderGetTextureMax: number | null;
renderFrameComputeAvg: number | null;
renderFrameComputeMax: number | null;
renderTerritoryPassAvg: number | null;
renderTerritoryPassMax: number | null;
renderTemporalResolveAvg: number | null;
renderTemporalResolveMax: number | null;
renderSubmitAvg: number | null;
renderSubmitMax: number | null;
traceLines: string[];
topMsgs: Array<{
type: string;
@@ -572,6 +586,20 @@ export class PerformanceOverlay extends LitElement implements Layer {
rfQueueMax: null,
rfHandlerAvg: null,
rfHandlerMax: null,
renderSubmittedCount: null,
renderNoopCount: null,
renderCpuTotalAvg: null,
renderCpuTotalMax: null,
renderGetTextureAvg: null,
renderGetTextureMax: null,
renderFrameComputeAvg: null,
renderFrameComputeMax: null,
renderTerritoryPassAvg: null,
renderTerritoryPassMax: null,
renderTemporalResolveAvg: null,
renderTemporalResolveMax: null,
renderSubmitAvg: null,
renderSubmitMax: null,
traceLines: [],
topMsgs: [],
};
@@ -620,6 +648,62 @@ export class PerformanceOverlay extends LitElement implements Layer {
rfQueueMax: typeof rfQueueMax === "number" ? rfQueueMax : null,
rfHandlerAvg: typeof rfHandlerAvg === "number" ? rfHandlerAvg : null,
rfHandlerMax: typeof rfHandlerMax === "number" ? rfHandlerMax : null,
renderSubmittedCount:
typeof metrics.renderSubmittedCount === "number"
? metrics.renderSubmittedCount
: null,
renderNoopCount:
typeof metrics.renderNoopCount === "number"
? metrics.renderNoopCount
: null,
renderCpuTotalAvg:
typeof metrics.renderCpuTotalMsAvg === "number"
? metrics.renderCpuTotalMsAvg
: null,
renderCpuTotalMax:
typeof metrics.renderCpuTotalMsMax === "number"
? metrics.renderCpuTotalMsMax
: null,
renderGetTextureAvg:
typeof metrics.renderGetTextureMsAvg === "number"
? metrics.renderGetTextureMsAvg
: null,
renderGetTextureMax:
typeof metrics.renderGetTextureMsMax === "number"
? metrics.renderGetTextureMsMax
: null,
renderFrameComputeAvg:
typeof metrics.renderFrameComputeMsAvg === "number"
? metrics.renderFrameComputeMsAvg
: null,
renderFrameComputeMax:
typeof metrics.renderFrameComputeMsMax === "number"
? metrics.renderFrameComputeMsMax
: null,
renderTerritoryPassAvg:
typeof metrics.renderTerritoryPassMsAvg === "number"
? metrics.renderTerritoryPassMsAvg
: null,
renderTerritoryPassMax:
typeof metrics.renderTerritoryPassMsMax === "number"
? metrics.renderTerritoryPassMsMax
: null,
renderTemporalResolveAvg:
typeof metrics.renderTemporalResolveMsAvg === "number"
? metrics.renderTemporalResolveMsAvg
: null,
renderTemporalResolveMax:
typeof metrics.renderTemporalResolveMsMax === "number"
? metrics.renderTemporalResolveMsMax
: null,
renderSubmitAvg:
typeof metrics.renderSubmitMsAvg === "number"
? metrics.renderSubmitMsAvg
: null,
renderSubmitMax:
typeof metrics.renderSubmitMsMax === "number"
? metrics.renderSubmitMsMax
: null,
traceLines,
topMsgs,
};
@@ -813,6 +897,63 @@ export class PerformanceOverlay extends LitElement implements Layer {
${this.formatMs(worker.rfHandlerMax, 0)}</span
>
</div>
${worker.renderSubmittedCount !== null ||
worker.renderNoopCount !== null
? html`<div class="layer-row">
<span class="layer-name">render submits / noops</span>
<span class="layer-metrics"
>${worker.renderSubmittedCount ?? 0} /
${worker.renderNoopCount ?? 0}</span
>
</div>`
: html``}
${worker.renderCpuTotalAvg !== null
? html`<div class="performance-line" style="margin-top: 6px;">
render CPU breakdown (avg/max, submitted frames)
</div>
<div class="layer-row">
<span class="layer-name">cpu total</span>
<span class="layer-metrics"
>${this.formatMs(worker.renderCpuTotalAvg)} /
${this.formatMs(worker.renderCpuTotalMax, 0)}</span
>
</div>
<div class="layer-row">
<span class="layer-name">getCurrentTexture</span>
<span class="layer-metrics"
>${this.formatMs(worker.renderGetTextureAvg)} /
${this.formatMs(worker.renderGetTextureMax, 0)}</span
>
</div>
<div class="layer-row">
<span class="layer-name">frame compute</span>
<span class="layer-metrics"
>${this.formatMs(worker.renderFrameComputeAvg)} /
${this.formatMs(worker.renderFrameComputeMax, 0)}</span
>
</div>
<div class="layer-row">
<span class="layer-name">territory pass</span>
<span class="layer-metrics"
>${this.formatMs(worker.renderTerritoryPassAvg)} /
${this.formatMs(worker.renderTerritoryPassMax, 0)}</span
>
</div>
<div class="layer-row">
<span class="layer-name">temporal resolve</span>
<span class="layer-metrics"
>${this.formatMs(worker.renderTemporalResolveAvg)} /
${this.formatMs(worker.renderTemporalResolveMax, 0)}</span
>
</div>
<div class="layer-row">
<span class="layer-name">submit</span>
<span class="layer-metrics"
>${this.formatMs(worker.renderSubmitAvg)} /
${this.formatMs(worker.renderSubmitMax, 0)}</span
>
</div>`
: html``}
${worker.topMsgs.length
? html`<div class="performance-line" style="margin-top: 6px;">
top msgs (count | queue avg/max | handler avg/max)
+44 -49
View File
@@ -1,6 +1,5 @@
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { UnitType } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameView } from "../../../core/game/GameView";
import { UserSettings } from "../../../core/game/UserSettings";
@@ -49,12 +48,14 @@ export class TerritoryLayer implements Layer {
private lastPaletteSignature: string | null = null;
private lastPatternsEnabled: boolean | null = null;
private lastDefensePostsSignature: string | null = null;
private lastTerrainShaderSignature: string | null = null;
private lastTerritoryShaderSignature: string | null = null;
private lastPreSmoothingSignature: string | null = null;
private lastPostSmoothingSignature: string | null = null;
private lastPaletteSyncMs = 0;
private lastUserSettingsSyncMs = 0;
private lastMousePosition: { x: number; y: number } | null = null;
private hoveredOwnerSmallId: number | null = null;
private lastHoverUpdateMs = 0;
@@ -97,11 +98,9 @@ export class TerritoryLayer implements Layer {
this.redraw();
}
this.refreshPaletteIfNeeded();
this.refreshDefensePostsIfNeeded();
this.applyTerrainShaderSettings();
this.applyTerritoryShaderSettings();
this.applyTerritorySmoothingSettings();
const now = performance.now();
this.syncUserSettingsMaybe(now);
this.syncPaletteMaybe(now);
// Renderer tick and dirty-tile marking are driven in the worker from
// simulation-derived tile updates (tileUpdateSink). The main thread only
@@ -151,10 +150,6 @@ export class TerritoryLayer implements Layer {
this.territoryRenderer.refreshPalette();
this.lastPaletteSignature = this.computePaletteSignature();
this.lastDefensePostsSignature = this.computeDefensePostsSignature();
// Ensure defense posts buffer is uploaded on first tick.
this.territoryRenderer.markDefensePostsDirty();
// Run an initial tick to upload state and build the colour texture. Without
// this, the first render call may occur before the initial compute pass
// has been executed, resulting in undefined colours.
@@ -174,12 +169,11 @@ export class TerritoryLayer implements Layer {
this.redraw();
}
// Apply user settings even while the game is paused (settings modal).
this.refreshPaletteIfNeeded();
this.refreshDefensePostsIfNeeded();
this.applyTerrainShaderSettings();
this.applyTerritoryShaderSettings();
this.applyTerritorySmoothingSettings();
// Apply user settings even while the game is paused (settings modal), but
// avoid heavy polling work in the RAF hot path.
const now = performance.now();
this.syncUserSettingsMaybe(now);
this.syncPaletteMaybe(now);
this.ensureTerritoryCanvasAttached(context.canvas);
this.updateHoverHighlight();
@@ -377,7 +371,6 @@ export class TerritoryLayer implements Layer {
}
private computePaletteSignature(): string {
const patternsEnabled = this.userSettings.territoryPatterns();
const players = this.game.playerViews();
const fnvByte = (hash: number, byte: number): number =>
@@ -391,7 +384,7 @@ export class TerritoryLayer implements Layer {
return hash;
};
let hash = patternsEnabled ? 2166136261 : 2166136262;
let hash = 2166136261;
hash = fnv32(hash, players.length);
let maxSmallId = 0;
@@ -416,15 +409,19 @@ export class TerritoryLayer implements Layer {
return `${hash}`;
}
private refreshPaletteIfNeeded() {
private syncPaletteMaybe(nowMs: number, force: boolean = false) {
if (!this.territoryRenderer) {
return;
}
const patternsEnabled = this.userSettings.territoryPatterns();
if (patternsEnabled !== this.lastPatternsEnabled) {
this.lastPatternsEnabled = patternsEnabled;
this.territoryRenderer.setPatternsEnabled(patternsEnabled);
// Palette rebuild is relatively expensive (builds & transfers full rows),
// so only check periodically. The worker handles most palette dirtiness,
// but the main thread still owns the theme-derived colour mapping.
if (!force && nowMs - this.lastPaletteSyncMs < 500) {
return;
}
this.lastPaletteSyncMs = nowMs;
const signature = this.computePaletteSignature();
if (signature !== this.lastPaletteSignature) {
this.lastPaletteSignature = signature;
@@ -432,6 +429,29 @@ export class TerritoryLayer implements Layer {
}
}
private syncUserSettingsMaybe(nowMs: number, force: boolean = false) {
if (!this.territoryRenderer) {
return;
}
// Shader settings are user-driven and change rarely; avoid allocating
// signatures and arrays every RAF.
if (!force && nowMs - this.lastUserSettingsSyncMs < 250) {
return;
}
this.lastUserSettingsSyncMs = nowMs;
const patternsEnabled = this.userSettings.territoryPatterns();
if (patternsEnabled !== this.lastPatternsEnabled) {
this.lastPatternsEnabled = patternsEnabled;
this.territoryRenderer.setPatternsEnabled(patternsEnabled);
}
this.applyTerrainShaderSettings();
this.applyTerritoryShaderSettings();
this.applyTerritorySmoothingSettings();
}
private applyTerritoryShaderSettings(force: boolean = false) {
if (!this.territoryRenderer) {
return;
@@ -507,29 +527,4 @@ export class TerritoryLayer implements Layer {
);
}
}
private computeDefensePostsSignature(): string {
// Active + completed posts only.
const parts: string[] = [];
for (const u of this.game.units(UnitType.DefensePost)) {
if (!u.isActive() || u.isUnderConstruction()) continue;
const tile = u.tile();
parts.push(
`${u.owner().smallID()},${this.game.x(tile)},${this.game.y(tile)}`,
);
}
parts.sort();
return parts.join("|");
}
private refreshDefensePostsIfNeeded() {
if (!this.territoryRenderer) {
return;
}
const signature = this.computeDefensePostsSignature();
if (signature !== this.lastDefensePostsSignature) {
this.lastDefensePostsSignature = signature;
this.territoryRenderer.markDefensePostsDirty();
}
}
}
+156 -1
View File
@@ -85,6 +85,21 @@ class WorkerProfiler {
private traceHead = 0;
private readonly traceCap = 160;
private renderSubmittedCount = 0;
private renderNoopCount = 0;
private renderGetTextureSum = 0;
private renderGetTextureMax = 0;
private renderFrameComputeSum = 0;
private renderFrameComputeMax = 0;
private renderTerritoryPassSum = 0;
private renderTerritoryPassMax = 0;
private renderTemporalResolveSum = 0;
private renderTemporalResolveMax = 0;
private renderSubmitSum = 0;
private renderSubmitMax = 0;
private renderCpuTotalSum = 0;
private renderCpuTotalMax = 0;
start(): void {
if (this.reportTimer) return;
this.lastReportWallMs = Date.now();
@@ -178,6 +193,60 @@ class WorkerProfiler {
}
}
recordRenderBreakdown(b: {
submitted: boolean;
getTextureMs?: number;
frameComputeMs?: number;
territoryPassMs?: number;
temporalResolveMs?: number;
submitMs?: number;
cpuTotalMs?: number;
}): void {
if (!this.config.enabled) return;
if (!b.submitted) {
this.renderNoopCount++;
return;
}
this.renderSubmittedCount++;
if (typeof b.getTextureMs === "number") {
this.renderGetTextureSum += b.getTextureMs;
this.renderGetTextureMax = Math.max(
this.renderGetTextureMax,
b.getTextureMs,
);
}
if (typeof b.frameComputeMs === "number") {
this.renderFrameComputeSum += b.frameComputeMs;
this.renderFrameComputeMax = Math.max(
this.renderFrameComputeMax,
b.frameComputeMs,
);
}
if (typeof b.territoryPassMs === "number") {
this.renderTerritoryPassSum += b.territoryPassMs;
this.renderTerritoryPassMax = Math.max(
this.renderTerritoryPassMax,
b.territoryPassMs,
);
}
if (typeof b.temporalResolveMs === "number") {
this.renderTemporalResolveSum += b.temporalResolveMs;
this.renderTemporalResolveMax = Math.max(
this.renderTemporalResolveMax,
b.temporalResolveMs,
);
}
if (typeof b.submitMs === "number") {
this.renderSubmitSum += b.submitMs;
this.renderSubmitMax = Math.max(this.renderSubmitMax, b.submitMs);
}
if (typeof b.cpuTotalMs === "number") {
this.renderCpuTotalSum += b.cpuTotalMs;
this.renderCpuTotalMax = Math.max(this.renderCpuTotalMax, b.cpuTotalMs);
}
}
trace(line: string): void {
if (!this.config.enabled || !this.config.includeTrace) return;
if (this.traceRing.length < this.traceCap) {
@@ -234,6 +303,10 @@ class WorkerProfiler {
msgCountsObj[k] = c;
}
const renderTotal = this.renderSubmittedCount + this.renderNoopCount;
const rAvg = (sum: number): number =>
this.renderSubmittedCount > 0 ? sum / this.renderSubmittedCount : 0;
const metrics: WorkerMetricsMessage = {
type: "worker_metrics",
intervalMs,
@@ -248,6 +321,45 @@ class WorkerProfiler {
simPumpExecMsAvg:
this.simExecCount > 0 ? this.simExecSum / this.simExecCount : 0,
simPumpExecMsMax: this.simExecMax,
renderSubmittedCount:
renderTotal > 0 ? this.renderSubmittedCount : undefined,
renderNoopCount: renderTotal > 0 ? this.renderNoopCount : undefined,
renderGetTextureMsAvg:
this.renderSubmittedCount > 0
? rAvg(this.renderGetTextureSum)
: undefined,
renderGetTextureMsMax:
this.renderSubmittedCount > 0 ? this.renderGetTextureMax : undefined,
renderFrameComputeMsAvg:
this.renderSubmittedCount > 0
? rAvg(this.renderFrameComputeSum)
: undefined,
renderFrameComputeMsMax:
this.renderSubmittedCount > 0 ? this.renderFrameComputeMax : undefined,
renderTerritoryPassMsAvg:
this.renderSubmittedCount > 0
? rAvg(this.renderTerritoryPassSum)
: undefined,
renderTerritoryPassMsMax:
this.renderSubmittedCount > 0 ? this.renderTerritoryPassMax : undefined,
renderTemporalResolveMsAvg:
this.renderSubmittedCount > 0
? rAvg(this.renderTemporalResolveSum)
: undefined,
renderTemporalResolveMsMax:
this.renderSubmittedCount > 0
? this.renderTemporalResolveMax
: undefined,
renderSubmitMsAvg:
this.renderSubmittedCount > 0 ? rAvg(this.renderSubmitSum) : undefined,
renderSubmitMsMax:
this.renderSubmittedCount > 0 ? this.renderSubmitMax : undefined,
renderCpuTotalMsAvg:
this.renderSubmittedCount > 0
? rAvg(this.renderCpuTotalSum)
: undefined,
renderCpuTotalMsMax:
this.renderSubmittedCount > 0 ? this.renderCpuTotalMax : undefined,
msgCounts: msgCountsObj,
msgHandlerMsAvg: toAvgRecord(this.msgHandlerSum, this.msgCounts),
msgHandlerMsMax: toMaxRecord(this.msgHandlerMax),
@@ -268,6 +380,20 @@ class WorkerProfiler {
this.simExecSum = 0;
this.simExecCount = 0;
this.simExecMax = 0;
this.renderSubmittedCount = 0;
this.renderNoopCount = 0;
this.renderGetTextureSum = 0;
this.renderGetTextureMax = 0;
this.renderFrameComputeSum = 0;
this.renderFrameComputeMax = 0;
this.renderTerritoryPassSum = 0;
this.renderTerritoryPassMax = 0;
this.renderTemporalResolveSum = 0;
this.renderTemporalResolveMax = 0;
this.renderSubmitSum = 0;
this.renderSubmitMax = 0;
this.renderCpuTotalSum = 0;
this.renderCpuTotalMax = 0;
this.msgCounts.clear();
this.msgHandlerSum.clear();
this.msgHandlerMax.clear();
@@ -856,6 +982,12 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
let renderGpuWaitMs: number | undefined;
let renderWaitPrevGpuTimedOut: boolean | undefined;
let renderGpuWaitTimedOut: boolean | undefined;
let renderSubmitted: boolean | undefined;
let renderFrameComputeMs: number | undefined;
let renderTerritoryPassMs: number | undefined;
let renderTemporalResolveMs: number | undefined;
let renderSubmitMs: number | undefined;
let renderCpuTotalMs: number | undefined;
try {
if ("viewSize" in message && message.viewSize) {
renderer.setViewSize(
@@ -872,7 +1004,7 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
}
const r: any = renderer as any;
if (typeof r.renderAsync === "function") {
const breakdown = await r.renderAsync();
const breakdown = await r.renderAsync(!!profiler.config.enabled);
if (breakdown) {
renderWaitPrevGpuMs = breakdown.waitPrevGpuMs;
renderCpuMs = breakdown.cpuMs;
@@ -880,6 +1012,12 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
renderGpuWaitMs = breakdown.gpuWaitMs;
renderWaitPrevGpuTimedOut = breakdown.waitPrevGpuTimedOut;
renderGpuWaitTimedOut = breakdown.gpuWaitTimedOut;
renderSubmitted = breakdown.submitted;
renderFrameComputeMs = breakdown.frameComputeMs;
renderTerritoryPassMs = breakdown.territoryPassMs;
renderTemporalResolveMs = breakdown.temporalResolveMs;
renderSubmitMs = breakdown.submitMs;
renderCpuTotalMs = breakdown.cpuTotalMs;
}
} else {
renderer.render();
@@ -890,6 +1028,17 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
const endedAt = performance.now();
const endedAtWallMs = Date.now();
if (id) {
if (typeof renderSubmitted === "boolean") {
profiler.recordRenderBreakdown({
submitted: renderSubmitted,
getTextureMs: renderGetTextureMs,
frameComputeMs: renderFrameComputeMs,
territoryPassMs: renderTerritoryPassMs,
temporalResolveMs: renderTemporalResolveMs,
submitMs: renderSubmitMs,
cpuTotalMs: renderCpuTotalMs,
});
}
sendMessage({
type: "render_done",
id,
@@ -907,6 +1056,12 @@ ctx.addEventListener("message", async (e: MessageEvent<MainThreadMessage>) => {
renderGpuWaitMs,
renderWaitPrevGpuTimedOut,
renderGpuWaitTimedOut,
renderSubmitted,
renderFrameComputeMs,
renderTerritoryPassMs,
renderTemporalResolveMs,
renderSubmitMs,
renderCpuTotalMs,
} as RenderDoneMessage);
}
}
+30
View File
@@ -301,6 +301,16 @@ export interface RenderDoneMessage extends BaseWorkerMessage {
renderGpuWaitMs?: number;
renderWaitPrevGpuTimedOut?: boolean;
renderGpuWaitTimedOut?: boolean;
/**
* Additional optional breakdown for CPU-side render encoding.
*/
renderSubmitted?: boolean;
renderFrameComputeMs?: number;
renderTerritoryPassMs?: number;
renderTemporalResolveMs?: number;
renderSubmitMs?: number;
renderCpuTotalMs?: number;
}
export interface RendererReadyMessage extends BaseWorkerMessage {
@@ -330,6 +340,26 @@ export interface WorkerMetricsMessage extends BaseWorkerMessage {
simPumpDelayMsMax: number;
simPumpExecMsAvg: number;
simPumpExecMsMax: number;
/**
* Optional render_frame breakdown collected inside the worker renderer.
* Values are based on the last metrics interval.
*/
renderSubmittedCount?: number;
renderNoopCount?: number;
renderGetTextureMsAvg?: number;
renderGetTextureMsMax?: number;
renderFrameComputeMsAvg?: number;
renderFrameComputeMsMax?: number;
renderTerritoryPassMsAvg?: number;
renderTerritoryPassMsMax?: number;
renderTemporalResolveMsAvg?: number;
renderTemporalResolveMsMax?: number;
renderSubmitMsAvg?: number;
renderSubmitMsMax?: number;
renderCpuTotalMsAvg?: number;
renderCpuTotalMsMax?: number;
msgCounts: Record<string, number>;
msgHandlerMsAvg: Record<string, number>;
msgHandlerMsMax: Record<string, number>;
+101 -5
View File
@@ -746,7 +746,16 @@ export class WorkerTerritoryRenderer {
* Render one frame.
* Runs render passes to draw to the canvas.
*/
render(onGetTextureMs?: (ms: number) => void): boolean {
render(
onGetTextureMs?: (ms: number) => void,
profile?: {
cpuTotalMs: number;
frameComputeMs: number;
territoryPassMs: number;
temporalResolveMs: number;
submitMs: number;
},
): boolean {
if (
!this.ready ||
!this.device ||
@@ -756,10 +765,13 @@ export class WorkerTerritoryRenderer {
return false;
}
// Without post-smoothing, stable frames can simply be skipped.
if (!this.frameDirty && !this.postSmoothingEnabled) {
return false;
}
const cpuStart = profile ? performance.now() : 0;
const nowSec = performance.now() / 1000;
this.resources.writeTemporalUniformBuffer(nowSec);
@@ -770,10 +782,12 @@ export class WorkerTerritoryRenderer {
onGetTextureMs(performance.now() - getTexStart);
}
let frameComputeMs = 0;
if (
this.preSmoothingEnabled &&
this.resources.consumeVisualStateSyncNeeded()
) {
const start = profile ? performance.now() : 0;
const visualStateTexture = this.resources.getVisualStateTexture();
if (visualStateTexture) {
encoder.copyTextureToTexture(
@@ -786,20 +800,36 @@ export class WorkerTerritoryRenderer {
},
);
}
if (profile) {
frameComputeMs += performance.now() - start;
}
}
for (const pass of this.frameComputePasses) {
if (!pass.needsUpdate()) {
continue;
}
const start = profile ? performance.now() : 0;
pass.execute(encoder, this.resources);
if (profile) {
frameComputeMs += performance.now() - start;
}
}
if (profile) {
profile.frameComputeMs = frameComputeMs;
}
// Execute render passes in dependency order
let territoryPassMs = 0;
let temporalResolveMs = 0;
// Execute render passes in dependency order.
for (const pass of this.renderPassOrder) {
if (!pass.needsUpdate()) {
continue;
}
const passStart = profile ? performance.now() : 0;
if (pass === this.territoryRenderPass && this.postSmoothingEnabled) {
if (!this.resources.getCurrentColorTexture()) {
const viewWidth = this.canvas?.width ?? 1;
@@ -810,27 +840,56 @@ export class WorkerTerritoryRenderer {
this.device.canvasFormat,
);
}
const currentTexture = this.resources.getCurrentColorTexture();
if (currentTexture) {
pass.execute(encoder, this.resources, currentTexture.createView());
}
if (profile) {
territoryPassMs += performance.now() - passStart;
}
continue;
}
pass.execute(encoder, this.resources, swapchainView);
if (profile) {
const dt = performance.now() - passStart;
if (pass === this.territoryRenderPass) {
territoryPassMs += dt;
} else if (pass === this.temporalResolvePass) {
temporalResolveMs += dt;
}
}
}
const submitStart = profile ? performance.now() : 0;
this.device.device.queue.submit([encoder.finish()]);
if (profile) {
profile.territoryPassMs = territoryPassMs;
profile.temporalResolveMs = temporalResolveMs;
profile.submitMs = performance.now() - submitStart;
profile.cpuTotalMs = performance.now() - cpuStart;
}
if (!this.postSmoothingEnabled) {
this.frameDirty = false;
}
return true;
}
async renderAsync(): Promise<{
async renderAsync(profilePhases: boolean = false): Promise<{
waitPrevGpuMs: number;
cpuMs: number;
getTextureMs: number;
submitted: boolean;
frameComputeMs?: number;
territoryPassMs?: number;
temporalResolveMs?: number;
submitMs?: number;
cpuTotalMs?: number;
gpuWaitMs: number;
waitPrevGpuTimedOut: boolean;
gpuWaitTimedOut: boolean;
@@ -842,6 +901,12 @@ export class WorkerTerritoryRenderer {
const waitPrevGpuMs = 0;
let cpuMs = 0;
let getTextureMs = 0;
let submitted = false;
let frameComputeMs: number | undefined;
let territoryPassMs: number | undefined;
let temporalResolveMs: number | undefined;
let submitMs: number | undefined;
let cpuTotalMs: number | undefined;
const gpuWaitMs = 0;
const waitPrevGpuTimedOut = false;
const gpuWaitTimedOut = false;
@@ -851,9 +916,19 @@ export class WorkerTerritoryRenderer {
this.lastGpuWork = null;
const cpuStart = performance.now();
const submitted = this.render((ms) => {
const profile = profilePhases
? {
cpuTotalMs: 0,
frameComputeMs: 0,
territoryPassMs: 0,
temporalResolveMs: 0,
submitMs: 0,
}
: undefined;
submitted = this.render((ms) => {
getTextureMs = ms;
});
}, profile);
cpuMs = performance.now() - cpuStart;
if (!submitted) {
@@ -862,6 +937,7 @@ export class WorkerTerritoryRenderer {
waitPrevGpuMs,
cpuMs,
getTextureMs,
submitted,
gpuWaitMs,
waitPrevGpuTimedOut,
gpuWaitTimedOut,
@@ -875,12 +951,26 @@ export class WorkerTerritoryRenderer {
waitPrevGpuMs,
cpuMs,
getTextureMs,
submitted,
frameComputeMs,
territoryPassMs,
temporalResolveMs,
submitMs,
cpuTotalMs,
gpuWaitMs,
waitPrevGpuTimedOut,
gpuWaitTimedOut,
};
}
if (profile) {
frameComputeMs = profile.frameComputeMs;
territoryPassMs = profile.territoryPassMs;
temporalResolveMs = profile.temporalResolveMs;
submitMs = profile.submitMs;
cpuTotalMs = profile.cpuTotalMs;
}
const p = q.onSubmittedWorkDone() as Promise<void>;
this.lastGpuWork = p.catch(() => {});
@@ -888,6 +978,12 @@ export class WorkerTerritoryRenderer {
waitPrevGpuMs,
cpuMs,
getTextureMs,
submitted,
frameComputeMs,
territoryPassMs,
temporalResolveMs,
submitMs,
cpuTotalMs,
gpuWaitMs,
waitPrevGpuTimedOut,
gpuWaitTimedOut,