Merge branch 'main' into fix/mangoapp-drm-listing

This commit is contained in:
Kurt Himebauch authored and GitHub committed 2026-10-01 14:31:23 -04:00
commit 0d21e2f913
8 files changed
+45 -9

No files matched your search

+12 -2
View File
@@ -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 */
@@ -144,6 +144,7 @@ class MainActivity : ComponentActivity() {
private var directAudio by mutableStateOf(false)
private var clientDirectAudio by mutableStateOf(false)
private var forceFullscreen by mutableStateOf(true)
private var stretch16x9 by mutableStateOf(false)
private var launcherFullscreen by mutableStateOf(true)
private var storeEnabled by mutableStateOf(false)
private var appImagesEnabled by mutableStateOf(false)
@@ -958,6 +959,7 @@ class MainActivity : ComponentActivity() {
directAudio = if (mode == SessionService.MODE_STEAM) directAudio else null,
clientDirectAudio = clientDirectAudio,
forceFullscreen = if (mode == SessionService.MODE_STEAM) forceFullscreen else null,
stretch16x9 = if (mode == SessionService.MODE_STEAM) stretch16x9 else null,
mic = if (mode == SessionService.MODE_STEAM) mic else null,
renderer = if (mode == SessionService.MODE_DESKTOP) renderer else null,
gameStorage = if (mode == SessionService.MODE_STEAM) gameStorage else null,
@@ -993,6 +995,7 @@ class MainActivity : ComponentActivity() {
onDirectAudio = { on -> SessionPrefs.setDirectAudio(this, on); directAudio = on },
onClientDirectAudio = { on -> SessionPrefs.setClientDirectAudio(this, on); clientDirectAudio = on },
onForceFullscreen = { on -> SessionPrefs.setForceFullscreen(this, on); forceFullscreen = on },
onStretch16x9 = { on -> SessionPrefs.setStretch16x9(this, on); stretch16x9 = on },
onMic = { on ->
SessionPrefs.setMicEnabled(this, on)
mic = on
@@ -1119,6 +1122,7 @@ class MainActivity : ComponentActivity() {
directAudio = SessionPrefs.directAudio(this)
clientDirectAudio = SessionPrefs.clientDirectAudio(this)
forceFullscreen = SessionPrefs.forceFullscreen(this)
stretch16x9 = SessionPrefs.stretch16x9(this)
mic = SessionPrefs.micEnabled(this)
renderer = SessionPrefs.desktopRenderer(this)
gameStorage = SessionPrefs.gameStorage(this)
@@ -157,6 +157,7 @@ object DeviceReport {
k("Guest host name", SessionPrefs.guestHostname(context))
k("DirectAudio for games", SessionPrefs.directAudio(context))
k("Stretch games to fill", SessionPrefs.forceFullscreen(context))
k("Stretch 16:9 to panel", SessionPrefs.stretch16x9(context))
k("Client audio", if (SessionPrefs.clientDirectAudio(context)) "DirectAudio" else "classic")
k("Microphone", SessionPrefs.micEnabled(context))
k("On-screen controls", SessionPrefs.oscMode(context))
@@ -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))
}
}
@@ -184,6 +184,12 @@ object SessionPrefs {
writeForceFullscreenFlag(context)
}
fun stretch16x9(context: Context): Boolean = prefs(context).getBoolean("stretch16x9", false)
fun setStretch16x9(context: Context, on: Boolean) {
prefs(context).edit().putBoolean("stretch16x9", on).apply()
}
/**
* The same choice as a file the running session watches, so the drawer can change it live:
* the session hands every change to gamescope, which reads GAMESCOPE_FORCE_WINDOWS_FULLSCREEN
@@ -540,6 +540,7 @@ class SessionService : Service() {
if (steamHere) guest.add("BL_STEAM_CHANNEL=" + SessionPrefs.steamChannel(this))
if (SessionState.mode == MODE_STEAM) {
guest.add("BL_GAMESCOPE_FORCE_FULLSCREEN=" + (if (SessionPrefs.forceFullscreen(this)) "1" else "0"))
guest.add("BL_GAMESCOPE_STRETCH_16X9=" + (if (SessionPrefs.stretch16x9(this)) "1" else "0"))
SessionPrefs.writeForceFullscreenFlag(this)
}
// Proton's own gate for its xalia helper (its `proton` script reads this, and sets
@@ -68,6 +68,7 @@ class ModeSettings(
val fexPreset: String? = null,
/** Steam only: games are stretched to fill the screen (null = not a Steam page). */
val forceFullscreen: Boolean? = null,
val stretch16x9: Boolean? = null,
/** Steam only: the client branch forced on the command line. */
val steamChannel: String? = null,
/** Steam only: enable the SteamOS client interface and its performance controls. */
@@ -117,6 +118,7 @@ class ModeSettingsActions(
val onPickGameStorageFolder: () -> Unit = {},
val onFexPreset: (String) -> Unit = {},
val onForceFullscreen: (Boolean) -> Unit = {},
val onStretch16x9: (Boolean) -> Unit = {},
val onSteamChannel: (String) -> Unit = {},
val onSteamDeckMode: (Boolean) -> Unit = {},
val onMangoapp: (Boolean) -> Unit = {},
@@ -361,6 +363,11 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
"Keeps games that resize their own window (FlatOut) full screen. Turn it off if a game shows up small in a corner (Quake 3). Applies next session.",
s.forceFullscreen, onChange = a.onForceFullscreen,
)
if (s.stretch16x9 != null) ToggleRow(
host, "stretch169", "Stretch 16:9 games to a narrower screen",
"On a screen squarer than 16:9 (a foldable's inner panel), games render at 16:9 and are stretched to fill it: no bars, slightly wider-looking. Applies next session.",
s.stretch16x9, onChange = a.onStretch16x9,
)
}
if (steam && s.directAudio != null && s.mic != null) SettingsGroup("Audio") {
ToggleRow(host, "da", "DirectAudio for games", "Bypasses PulseAudio for lower latency in games.", s.directAudio, onChange = a.onDirectAudio)
@@ -140,7 +140,16 @@ if [ -z "${BL_INSIDE:-}" ]; then
fi
width=${BL_WIDTH:-1280}
height=${BL_HEIGHT:-720}
args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$width" -h "$height")
nested=$width
if [ "$mode" = steam ] && [ "${BL_GAMESCOPE_STRETCH_16X9:-0}" = 1 ]; then
wide=$(( (height * 16 + 4) / 9 / 2 * 2 ))
if [ "$wide" -gt "$width" ]; then
nested=$wide
echo "== gamescope: games at ${nested}x${height} stretched to the ${width}x${height} output"
fi
fi
args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$nested" -h "$height")
[ "$nested" != "$width" ] && args+=(-S stretch)
# -r is what gamescope advertises as the refresh rate, and what a game reads as the display's.
# Left unset it defaults to 60, so a 120 Hz panel offers only 60 Hz in game settings. The FPS
# limiter wins when it is on (it is a deliberate cap); otherwise use the panel's own rate.