diff --git a/app/src/main/cpp/waylandcomp/src/compositor.c b/app/src/main/cpp/waylandcomp/src/compositor.c index 63f0eed8a..75a16311e 100644 --- a/app/src/main/cpp/waylandcomp/src/compositor.c +++ b/app/src/main/cpp/waylandcomp/src/compositor.c @@ -750,6 +750,8 @@ static void take_shm(struct surface *s, struct wl_shm_buffer *shm, struct wl_res static struct surface *g_hud_surface; /* When the HUD's window last presented a frame (now_ns), to let a replacement take over. */ static int64_t g_hud_last_ns; +/* That window committed a frame the screen has not drawn yet. */ +static int g_hud_fresh; extern void banner_on_game_surface(const char *window, const char *gpu); /* window NULL = gone */ extern void banner_on_game_frame(void); /* The program behind that window: its Linux pid (the Wayland client's credentials) and executable name @@ -852,7 +854,7 @@ static void take_dmabuf(struct surface *s, struct dmabuf_buffer *b, struct wl_re * Asking whether the compositor could import it, as this used to, is a question about the * copy path and not about whether a frame happened: a game whose buffers go straight to * the display layer draws on screen while the counter sat at 0.0 fps and 1000.0 ms. */ - if (s == g_hud_surface) { g_hud_last_ns = now_ns(); banner_on_game_frame(); } + if (s == g_hud_surface) { g_hud_last_ns = now_ns(); g_hud_fresh = 1; banner_on_game_frame(); } } /* ---- hooks for ahb_swapchain.c (zero-copy layers) */ @@ -2021,6 +2023,7 @@ static void render_scene(void) { int copy = 0; /* this scene went through the screen swapchain (the perf line's "copy") */ g_dirty = 0; + g_hud_fresh = 0; g_hdr_unimported = NULL; /* found again while the scene is built (HDR gate open only) */ wl_list_for_each(s, &g_surfaces, link) s->drawn = 0; scene_size(&w, &h); @@ -2236,6 +2239,13 @@ static void schedule_render(void) { wl_event_source_timer_update(g_fallback_timer, 8); } +/* With frame generation on, every scene is the engine's next source frame, so one drawn for + * another window (Steam's overlay) counted as a game frame and took its present slots. Such a + * change waits for the game's next frame, unless the game has been still for 100 ms. */ +static int scene_waits_for_game(int64_t now) { + return g_hud_surface && !g_hud_fresh && now - g_hud_last_ns < 100000000LL && vkp_framegen_active(); +} + /* A screen refresh (Choreographer tick): draw the newest state once. */ static void on_vsync(int64_t frame_time_ns) { int64_t now = now_ns(); @@ -2251,7 +2261,7 @@ static void on_vsync(int64_t frame_time_ns) { if (vkp_apply_window_request()) g_dirty = 1; /* A screen-effect setting changed (JNI, any thread): redraw so it shows on a static scene too. */ if (vkp_effects_sync()) g_dirty = 1; - if (g_dirty) render_scene(); + if (g_dirty && !scene_waits_for_game(now)) render_scene(); } /* ------------------------------------------------------------------ wl_seat */ diff --git a/app/src/main/java/com/droiddeck/launcher/session/PerfHud.kt b/app/src/main/java/com/droiddeck/launcher/session/PerfHud.kt index a9b5b0459..b3c3fd6ea 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/PerfHud.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/PerfHud.kt @@ -21,8 +21,9 @@ import java.util.concurrent.atomic.AtomicInteger * is on, what the screen is actually being shown - "60 → 118 fps" is the proof that the engine * is doing something, and "FG starting" or a reason is the proof that it is not. * - * The base rate is counted here from the compositor's per-frame callback, so it is right with or - * without an engine. The presented rate comes from the engine's own telemetry while it generates. + * The base rate is the game window's own frames, counted here from the compositor's per-frame + * callback with or without an engine - what Steam's overlay counts too. The presented rate comes + * from the engine's telemetry while it generates. */ class PerfHud(context: Context) { /** Empty when the HUD is off or not yet started. */ @@ -99,10 +100,7 @@ class PerfHud(context: Context) { return when { problem == 1 -> context.getString(R.string.hud_fg_unsupported, name) problem == 2 -> context.getString(R.string.hud_fg_failed, name) - stats != null && stats[3] > 1f -> { - val source = if (stats[2] > 1f) stats[2] else base - String.format(Locale.US, "%s %.0f → %.0f fps", label, source, stats[3]) - } + stats != null && stats[3] > 1f -> String.format(Locale.US, "%s %.0f → %.0f fps", label, base, stats[3]) else -> context.getString(R.string.hud_fg_starting, label, fps(base)) } }