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.
This commit is contained in:
magartus committed 2026-10-01 18:38:02 +02:00
1 parent 6140c5af35
commit 19af3bcdd2
5 files changed
+46

No files matched your search

@@ -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<String?>(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)
@@ -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.
@@ -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))
@@ -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.
@@ -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) },