From 19af3bcdd25517b0764e7f74b5621eeafd2edce8 Mon Sep 17 00:00:00 2001 From: magartus Date: Thu, 1 Oct 2026 18:38:02 +0200 Subject: [PATCH 1/2] Expose the compositor's upscaler as a session setting The Wayland compositor already carries the X11 renderer's spatial upscalers (effects_chain.c: FSR 1 EASU+RCAS, Snapdragon GSR and its quality variant, NIS, RCAS sharpen) behind WaylandCompositor.nativeSetUpscaler / nativeSetUpscaleSharpness, but nothing calls them since the Winlator-era drawer was removed, so the scene is always enlarged to the panel with a plain linear blit. Add "Upscaler" and "Upscaler sharpness" to the session page's Display group (off by default, 75%), stored in SessionPrefs and applied next to nativeSetScaleMode when the session's surface is set up. The choice is also listed in device.txt. --- .../com/droiddeck/launcher/MainActivity.kt | 7 +++++++ .../com/droiddeck/launcher/SessionActivity.kt | 2 ++ .../droiddeck/launcher/core/DeviceReport.kt | 2 ++ .../launcher/session/SessionPrefs.kt | 20 +++++++++++++++++++ .../launcher/ui/ModeSettingsDialog.kt | 15 ++++++++++++++ 5 files changed, 46 insertions(+) diff --git a/app/src/main/java/com/droiddeck/launcher/MainActivity.kt b/app/src/main/java/com/droiddeck/launcher/MainActivity.kt index 3628375b5..abd38b87c 100644 --- a/app/src/main/java/com/droiddeck/launcher/MainActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/MainActivity.kt @@ -265,6 +265,8 @@ class MainActivity : ComponentActivity() { private var shapeMode by mutableStateOf(SessionPrefs.SHAPE_AUTO) private var hdrOn by mutableStateOf(false) private var fpsLimit by mutableStateOf(0) + private var upscaler by mutableStateOf(0) + private var upscaleSharpness by mutableStateOf(75) private var hdrReason by mutableStateOf(null) private var touchMode by mutableStateOf(SessionPrefs.TOUCH_AUTO) private var suspendPolicy by mutableStateOf(SessionPrefs.SUSPEND_MANUAL) @@ -950,6 +952,7 @@ class MainActivity : ComponentActivity() { ModeSettings( mode = mode, resolutionCap = resolutionCap, customResolution = customResolution, shapeMode = shapeMode, hdr = hdrOn, hdrReason = hdrReason, fpsLimit = fpsLimit, + upscaler = upscaler, upscaleSharpness = upscaleSharpness, gpuDrivers = drivers.summary(), touchMode = touchMode, suspendPolicy = suspendPolicy, @@ -983,6 +986,8 @@ class MainActivity : ComponentActivity() { onHdr = { on -> SessionPrefs.setHdr(this, mode, on); hdrOn = on }, onGpuDrivers = { openComponents(focusContent = true, tab = com.droiddeck.launcher.ui.GPU_TAB) }, onFpsLimit = { fps -> SessionPrefs.setFpsLimit(this, mode, fps); fpsLimit = fps }, + onUpscaler = { m -> SessionPrefs.setUpscaler(this, m); upscaler = m }, + onUpscaleSharpness = { pct -> SessionPrefs.setUpscaleSharpness(this, pct); upscaleSharpness = pct }, onTouch = { t -> SessionPrefs.setTouchMode(this, t); touchMode = t }, onSuspendPolicy = { policy -> SessionPrefs.setSuspendPolicy(this, mode, policy); suspendPolicy = policy }, onOsc = { o -> SessionPrefs.setOscMode(this, o); oscMode = o }, @@ -1111,6 +1116,8 @@ class MainActivity : ComponentActivity() { shapeMode = SessionPrefs.shapeMode(this) hdrOn = SessionPrefs.hdr(this, mode) fpsLimit = SessionPrefs.fpsLimit(this, mode) + upscaler = SessionPrefs.upscaler(this) + upscaleSharpness = SessionPrefs.upscaleSharpness(this) hdrReason = com.droiddeck.launcher.wayland.HdrSupport.probe(this).reason touchMode = SessionPrefs.touchMode(this) suspendPolicy = SessionPrefs.suspendPolicy(this, mode) diff --git a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt index 0f63ca6e6..004a4f71a 100644 --- a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt @@ -798,6 +798,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { // Letterbox, never stretch or crop: the output can be a different shape from the panel, // and a game's picture must keep its proportions with bars, not lose its edges. WaylandCompositor.nativeSetScaleMode(SCALE_FIT, ALIGN_CENTER) + WaylandCompositor.nativeSetUpscaler(SessionPrefs.upscaler(this)) + WaylandCompositor.nativeSetUpscaleSharpness(SessionPrefs.upscaleSharpness(this)) // The session's folder, claimed here because the compositor starts before the service and // opens its log once. The compositor reads the path from its environment; setting it after // it has started changes nothing, which is why the service copies the file in at teardown. diff --git a/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt b/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt index 16a9598e9..457e49cf1 100644 --- a/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt +++ b/app/src/main/java/com/droiddeck/launcher/core/DeviceReport.kt @@ -150,6 +150,8 @@ object DeviceReport { k("No GL error checks", SessionPrefs.noGlError(context)) k("Steam Deck mode", SessionPrefs.steamDeckMode(context)) k("Steam controller", SessionPrefs.steamController(context)) + k("Upscaler", (SessionPrefs.upscalerChoices.firstOrNull { it.first == SessionPrefs.upscaler(context) }?.second ?: "Off") + + " (sharpness ${SessionPrefs.upscaleSharpness(context)}%)") k("FEX preset", SessionPrefs.fexPreset(context).ifEmpty { "FEX defaults" }) k("Skip xalia", SessionPrefs.noXalia(context)) k("gamescope realtime", SessionPrefs.gamescopeRealtime(context)) diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt index 9eb8a1d35..c99943851 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt @@ -579,6 +579,26 @@ object SessionPrefs { val fpsLimitChoices = listOf(0 to "Off", 30 to "30", 40 to "40", 45 to "45", 60 to "60", 90 to "90", 120 to "120") + val upscalerChoices = listOf( + 0 to "Off", 4 to "AMD FSR 1", 3 to "Snapdragon GSR", 8 to "Snapdragon GSR (quality)", + 7 to "NVIDIA NIS", 6 to "Sharpen only", + ) + + fun upscaler(context: Context): Int = + prefs(context).getInt("upscaler", 0).takeIf { m -> upscalerChoices.any { it.first == m } } ?: 0 + + fun setUpscaler(context: Context, mode: Int) { + prefs(context).edit().putInt("upscaler", mode).apply() + } + + val upscaleSharpnessChoices = listOf(0 to "0%", 25 to "25%", 50 to "50%", 75 to "75%", 100 to "100%") + + fun upscaleSharpness(context: Context): Int = prefs(context).getInt("upscaleSharpness", 75).coerceIn(0, 100) + + fun setUpscaleSharpness(context: Context, pct: Int) { + prefs(context).edit().putInt("upscaleSharpness", pct.coerceIn(0, 100)).apply() + } + /** * The mode whose per-mode settings apply: a program run under gamescope (MODE_RUN) is a * fullscreen session like Steam's, so it takes Steam's display, driver and HDR choices. diff --git a/app/src/main/java/com/droiddeck/launcher/ui/ModeSettingsDialog.kt b/app/src/main/java/com/droiddeck/launcher/ui/ModeSettingsDialog.kt index d9f26e6b4..43494d9b8 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/ModeSettingsDialog.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/ModeSettingsDialog.kt @@ -53,6 +53,8 @@ class ModeSettings( val gpuDrivers: String = "Auto", /** Frames per second the session is capped at; 0 = none. */ val fpsLimit: Int = 0, + val upscaler: Int = 0, + val upscaleSharpness: Int = 75, val touchMode: String, val suspendPolicy: String, /** Steam only. */ @@ -105,6 +107,8 @@ class ModeSettingsActions( /** Opens the GPU drivers on the Components page: they are shared by every session. */ val onGpuDrivers: () -> Unit = {}, val onFpsLimit: (Int) -> Unit = {}, + val onUpscaler: (Int) -> Unit = {}, + val onUpscaleSharpness: (Int) -> Unit = {}, val onTouch: (String) -> Unit, val onSuspendPolicy: (String) -> Unit, val onOsc: (String) -> Unit, @@ -177,6 +181,17 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) { note = "Caps the whole session. The screen switches to a rate the cap divides evenly.", onPick = a.onFpsLimit, ) + ChoiceRow( + host, "upscaler", "Upscaler", "Sharpens the picture where it is enlarged to the screen.", + com.droiddeck.launcher.session.SessionPrefs.upscalerChoices, s.upscaler, + note = "Works only when the session is smaller than the screen; Sharpen only works at any size. Costs a little GPU time.", + onPick = a.onUpscaler, + ) + ChoiceRow( + host, "upscale-sharpness", "Upscaler sharpness", null, + com.droiddeck.launcher.session.SessionPrefs.upscaleSharpnessChoices, s.upscaleSharpness, + enabled = s.upscaler != 0, onPick = a.onUpscaleSharpness, + ) if (editCustom) CustomResolutionDialog( initial = custom, onSave = { size -> editCustom = false; a.onCustomResolution(size) }, From 61f31f502d2bf00d2abe98a4f5a1c3f36d77e377 Mon Sep 17 00:00:00 2001 From: magartus Date: Thu, 1 Oct 2026 21:21:52 +0200 Subject: [PATCH 2/2] Add the upscaler to the session drawer, applied live The drawer's Display page gets the same Upscaler and Upscaler sharpness choices as the session settings. A pick is saved and handed straight to the compositor, so it takes effect on the next frame without restarting the session. --- .../java/com/droiddeck/launcher/SessionActivity.kt | 13 +++++++++++++ .../com/droiddeck/launcher/ui/SessionOverlay.kt | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt index 004a4f71a..f41848a2c 100644 --- a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt @@ -190,6 +190,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private var pcKeyboardOpen by mutableStateOf(false) private var hudOn by mutableStateOf(true) private var fillScreen by mutableStateOf(true) + private var upscaler by mutableStateOf(0) + private var upscaleSharpness by mutableStateOf(75) private var lossless by mutableStateOf(Lossless.State.NONE) private val pickLossless = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { r -> if (r.resultCode == RESULT_OK) InAppFilePicker.pickedFile(r.data)?.let { importLossless(it) } @@ -417,6 +419,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { androidApps = androidApps, hudOn = hudOn, fillScreen = if (SessionState.mode == SessionService.MODE_STEAM) fillScreen else null, + upscaler = upscaler, upscaleSharpness = upscaleSharpness, frameGen = frameGen, lossless = lossless, oscMode = oscMode, suspendPolicy = suspendPolicy, touchMode = touchMode, @@ -427,6 +430,14 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { selectedSecondScreenDisplay = selectedSecondScreenDisplay, onHud = { on -> SessionPrefs.setHudEnabled(this@SessionActivity, on); hudOn = on; hud.refresh() }, onFillScreen = { on -> SessionPrefs.setForceFullscreen(this@SessionActivity, on); fillScreen = on }, + onUpscaler = { m -> + SessionPrefs.setUpscaler(this@SessionActivity, m); upscaler = m + WaylandCompositor.nativeSetUpscaler(m) + }, + onUpscaleSharpness = { pct -> + SessionPrefs.setUpscaleSharpness(this@SessionActivity, pct); upscaleSharpness = pct + WaylandCompositor.nativeSetUpscaleSharpness(pct) + }, onFrameGenPick = { mode -> FrameGen.set(this@SessionActivity, mode) readPrefs() @@ -642,6 +653,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private fun readPrefs() { hudOn = SessionPrefs.hudEnabled(this) fillScreen = SessionPrefs.forceFullscreen(this) + upscaler = SessionPrefs.upscaler(this) + upscaleSharpness = SessionPrefs.upscaleSharpness(this) touchMode = SessionPrefs.touchMode(this) frameGen = FrameGen.mode(this) fexPreset = SessionPrefs.fexPreset(this) diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt index af38940f0..5c9a363ce 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt @@ -170,6 +170,8 @@ class DrawerActions( val fexPreset: String, /** Steam only: games stretched to the screen's size, changed live (null = not Steam). */ val fillScreen: Boolean? = null, + val upscaler: Int = 0, + val upscaleSharpness: Int = 75, val secondScreenMode: SecondScreenMode, val secondScreenDisplays: List, val selectedSecondScreenDisplay: Int, @@ -191,6 +193,8 @@ class DrawerActions( val onShape: (String) -> Unit, val onFexPreset: (String) -> Unit, val onFillScreen: (Boolean) -> Unit = {}, + val onUpscaler: (Int) -> Unit = {}, + val onUpscaleSharpness: (Int) -> Unit = {}, val onSecondScreenMode: (SecondScreenMode) -> Unit, val onSecondScreenDisplay: (Int) -> Unit, val onLaunchAndroidApp: (HomeApp.LaunchableApp, Int?) -> Unit, @@ -346,6 +350,12 @@ fun SessionDrawer(open: Boolean, page: Int, controllerActive: Boolean, onPageCha host, "fill", "Stretch games to fill", null, a.fillScreen, chipModifier = focus.track(page, "fill"), onChange = a.onFillScreen, ) + ChoiceRow(host, "upscaler", "Upscaler", null, + SessionPrefs.upscalerChoices, a.upscaler, + chipModifier = focus.track(page, "upscaler"), onPick = a.onUpscaler) + ChoiceRow(host, "upscale-sharpness", "Upscaler sharpness", null, + SessionPrefs.upscaleSharpnessChoices, a.upscaleSharpness, enabled = a.upscaler != 0, + chipModifier = focus.track(page, "upscale-sharpness"), onPick = a.onUpscaleSharpness) val fgOpen = host.open == "fg" val fgLabel = FrameGen.label(LocalContext.current, a.frameGen) SettingsRow(stringResource(R.string.frame_gen_title), null, highlighted = fgOpen) {