mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 08:38:10 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user