restore FPS tracking via self-driven RAF in PerformanceOverlay

updateFrameMetrics had zero callers — the canvas2D RAF loop used to
invoke it per-frame, and that loop died with canvas2D. Tick metrics
were unaffected since GameRenderer.tick() still calls
updateTickLayerMetrics directly.

The WebGL renderer doesn't expose a per-frame hook for the overlay, so
the overlay now drives its own RAF, started/stopped with visibility so
it stays off the hot path when hidden.
This commit is contained in:
evanpelle
2026-05-17 13:48:35 -07:00
parent b27c2984fd
commit 69b5a9cba2
@@ -82,6 +82,7 @@ export class PerformanceOverlay extends LitElement implements Controller {
private fpsHistorySum: number = 0;
private lastSecondTime: number = 0;
private framesThisSecond: number = 0;
private fpsRafId: number | null = null;
private tickExecutionTimes: number[] = [];
private tickExecutionTimesSum: number = 0;
private tickDelayTimes: number[] = [];
@@ -519,6 +520,8 @@ export class PerformanceOverlay extends LitElement implements Controller {
disconnectedCallback(): void {
super.disconnectedCallback();
this.stopFpsLoop();
if (this.isUserSettingsListenerAttached) {
globalThis.removeEventListener(
`${USER_SETTINGS_CHANGED_EVENT}:${PERFORMANCE_OVERLAY_KEY}`,
@@ -561,6 +564,12 @@ export class PerformanceOverlay extends LitElement implements Controller {
this.isVisible = visible;
FrameProfiler.setEnabled(visible);
if (visible) {
this.startFpsLoop();
} else {
this.stopFpsLoop();
}
if (!visible && this.resizeState) {
globalThis.removeEventListener("pointermove", this.onResizePointerMove);
globalThis.removeEventListener("pointerup", this.onResizePointerUp);
@@ -583,6 +592,27 @@ export class PerformanceOverlay extends LitElement implements Controller {
this.userSettings.setPerformanceOverlay(nextVisible);
}
// FPS measurement runs on its own RAF — the WebGL renderer doesn't expose a
// per-frame hook for the overlay, and starting/stopping with visibility
// keeps the RAF cost off the hot path when the overlay is hidden.
private startFpsLoop(): void {
if (this.fpsRafId !== null) return;
const tick = () => {
this.updateFrameMetrics(0);
this.fpsRafId = requestAnimationFrame(tick);
};
this.fpsRafId = requestAnimationFrame(tick);
}
private stopFpsLoop(): void {
if (this.fpsRafId === null) return;
cancelAnimationFrame(this.fpsRafId);
this.fpsRafId = null;
this.lastTime = 0;
this.lastSecondTime = 0;
this.framesThisSecond = 0;
}
private onDragPointerMove = (e: PointerEvent) => {
if (!this.dragState || e.pointerId !== this.dragState.pointerId) return;