diff --git a/src/client/graphics/layers/PerformanceOverlay.ts b/src/client/graphics/layers/PerformanceOverlay.ts index 2f0e4134c..78871dde5 100644 --- a/src/client/graphics/layers/PerformanceOverlay.ts +++ b/src/client/graphics/layers/PerformanceOverlay.ts @@ -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;