diff --git a/app/src/main/java/app/gamenative/powercontrol/metrics/PerformanceMetricsCollector.kt b/app/src/main/java/app/gamenative/powercontrol/metrics/PerformanceMetricsCollector.kt index e620b467e0..b5b2df1b5a 100644 --- a/app/src/main/java/app/gamenative/powercontrol/metrics/PerformanceMetricsCollector.kt +++ b/app/src/main/java/app/gamenative/powercontrol/metrics/PerformanceMetricsCollector.kt @@ -221,8 +221,11 @@ object PerformanceMetricsCollector { sampleCount++ if (sampleCount % LOG_EVERY_N_SAMPLES == 0L) { Timber.tag(TAG).i( - "fps=%.1f p95=%.1fms slow=%d/%d repeated=%s in 10s cpu=%s%%(%s) gpu=%s%% cpuTemp=%s gpuTemp=%s", + // fps is the game's (frames produced, FrameRating's ticks); displayed is it + // clamped to the panel's rate, an estimate of the frames the panel showed. + "fps=%.1f displayed=%.1f p95=%.1fms slow=%d/%d repeated=%s in 10s cpu=%s%%(%s) gpu=%s%% cpuTemp=%s gpuTemp=%s", snapshot.fps, + com.winlator.widget.FrameRating.displayedFps(snapshot.fps), snapshot.frameTimeP95Ms, snapshot.slowFrameCount, snapshot.totalFrameCount, diff --git a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt index 0e2530537b..03895111b1 100644 --- a/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt +++ b/app/src/main/java/app/gamenative/ui/screen/xserver/XServerScreen.kt @@ -844,6 +844,8 @@ fun XServerScreen( fun refreshFpsLimit(view: View?, isX11Display: Boolean) { val refreshHz = detectRefreshRateHz(context, view) + // The ceiling of the displayed-fps estimate, on both display servers. + FrameRating.setPanelRefreshHz(refreshHz.toFloat()) ui.detectedMaxRefreshRateHz = refreshHz * FPS_LIMITER_MAX_MULTIPLIER if (!isX11Display) @@ -5435,7 +5437,12 @@ private fun exit( "game_name" to ContainerUtils.resolveGameName(appId), "game_store" to ContainerUtils.extractGameSourceFromContainerId(appId).name, "session_length" to (frameRating?.sessionLengthSec ?: 0), + // avg_fps is the game's fps (frames it produced; see FrameRating). game_fps repeats it + // under an explicit name, and displayed_fps is the same readings clamped to the panel's + // rate, an estimate of the frames that reached the panel. "avg_fps" to (frameRating?.avgFPS ?: 0.0), + "game_fps" to (frameRating?.avgFPS ?: 0.0), + "displayed_fps" to (frameRating?.avgDisplayedFPS ?: 0.0), "container_config" to container.containerJson, ) + runCatching { PluviaApp.xServerView?.getxServer()?.effectiveScreenInfo @@ -5456,6 +5463,7 @@ private fun exit( // Store session data in container metadata frameRating?.let { rating -> container.putSessionMetadata("avg_fps", rating.avgFPS) + container.putSessionMetadata("displayed_fps", rating.avgDisplayedFPS) container.putSessionMetadata("session_length_sec", rating.sessionLengthSec.toInt()) container.putSessionMetadata("total_frames", rating.totalFrames) container.saveData() diff --git a/app/src/main/java/app/gamenative/ui/widget/PerformanceHudText.kt b/app/src/main/java/app/gamenative/ui/widget/PerformanceHudText.kt index 453762d40a..4bc94ebce4 100644 --- a/app/src/main/java/app/gamenative/ui/widget/PerformanceHudText.kt +++ b/app/src/main/java/app/gamenative/ui/widget/PerformanceHudText.kt @@ -9,6 +9,25 @@ import kotlin.math.roundToInt internal object PerformanceHudText { private const val MISSING = "--" + /** + * FPS row: the frames that reached the panel, then in brackets the frames the game produced + * when the two differ. "FPS 60 (287)" is a game rendering 287 frames a second on a 60 Hz + * panel; "FPS 59.8" is a game at or below the panel's rate, where the two are the same. + * + * [gameFps] is what the session counts on either display server (see FrameRating). The + * displayed figure is an estimate: [gameFps] clamped to [refreshHz]; no renderer reports the + * frames the panel actually latched. With no refresh rate known the row is the game's fps. + */ + fun fps(gameFps: Float, refreshHz: Float): String { + val game = if (gameFps.isFinite()) gameFps.coerceAtLeast(0f) else 0f + val hz = refreshHz.takeIf { it.isFinite() && it > 0f } + return if (hz != null && game > hz + 0.5f) { + String.format(Locale.US, "FPS %d (%d)", hz.roundToInt(), game.roundToInt()) + } else { + String.format(Locale.US, "FPS %.1f", game) + } + } + /** * GPU row. The percent is always the active usage source's reading (sysfs, Mali counters * or the shim); a measured GPU frame time is appended, never substituted: diff --git a/app/src/main/java/app/gamenative/ui/widget/PerformanceHudView.kt b/app/src/main/java/app/gamenative/ui/widget/PerformanceHudView.kt index ff01234c7a..b325b700ae 100644 --- a/app/src/main/java/app/gamenative/ui/widget/PerformanceHudView.kt +++ b/app/src/main/java/app/gamenative/ui/widget/PerformanceHudView.kt @@ -347,7 +347,7 @@ class PerformanceHudView( fpsValue = currentFps, cpuValue = cpuPercent?.toFloat(), gpuValue = gpuPercent?.toFloat(), - fps = String.format(Locale.US, "FPS %.1f", currentFps), + fps = PerformanceHudText.fps(currentFps, refreshHz), cpu = cpuPercent?.let { "CPU $it%" }, gpu = PerformanceHudText.gpu(gpuPercent, gpuFrameMs), gpuCompact = PerformanceHudText.gpu(gpuPercent, gpuFrameMs, compact = true), diff --git a/app/src/main/java/com/winlator/widget/FrameRating.java b/app/src/main/java/com/winlator/widget/FrameRating.java index 2015b5283a..ae266ac8e3 100644 --- a/app/src/main/java/com/winlator/widget/FrameRating.java +++ b/app/src/main/java/com/winlator/widget/FrameRating.java @@ -23,7 +23,37 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +/** + * Counts the frames the game produced, on either display server. + * + *