View on GitHub

Gamescope Deep Dive

Comprehensive guide to Gamescope on Steam Deck

Steam Deck: Gamescope Deep Dive

Last Updated License

Introduction

Comprehensive look into user-facing side of Gamescope with commands and examples. This is being primarily used-tested on Steam Deck on Wayland display‑server protocol. If you are using an X11 display‑server protocol, some of these commands might not function.

All examples and commands are produced with help of Claude, please note that I have not gone through and tested all of the commands listed here.

This is first and foremost used as a learning document. You are welcome to correct any errors found within it. I will try to keep this updated with any new info. Gamescope Version: 3.16.14.5

Table of Contents

TL;DR Section

Display Stack & Architecture

DRM and Hardware Control

Gamescope Headless Mode

Gamescope Capabilities: CAP_SYS_NICE

Execution Flow

Environment Variables

TTY Deep Dive

Using TTY to utilize Direct Mode

Network Transparency & Backend Modes

Setup & Configuration

Emergency Recovery: Stuck TTY or Gamescope

VSync & Performance

FSR/NIS Upscaling

Frame Generation (lsfg-vk)

Testing & Debugging


Quick Start / TL;DR

If you just want to:

Get lowest latency gaming (Competitive)

# Switch to empty TTY (Ctrl+Alt+F3), then:
gamescope -e -W 1920 -H 1080 --immediate-flips -- steam -gamepadui

Full explanation - Complete Execution Flow with Locations

Use frame generation (lsfg-vk)

# In Steam launch options:
ENABLE_GAMESCOPE_WSI=1 gamescope -W 1920 -H 1080 -r 120 -- /home/deck/lsfg-vk %command%

Full explanation: Execution Flow

Debug why gamescope isn’t working

  1. Check your backend mode
  2. Verify environment variables
  3. Check CAP_SYS_NICE

Common issues:


← Back to top

First: The Display Stack

The Complete Display Stack: Steam Deck Desktop Mode

Here’s what happens when you click “Play”

Layer 1: You Click “Play” in Steam

Steam Client (Desktop Mode)
  ↓
Reads launch options
  ↓
Executes: gamescope -W 1920 -H 1080 -f --backend wayland --mangoapp -- env /home/deck/lsfg-vk %command%

Layer 2: Gamescope Initializes (Nested Mode)

Gamescope starts in nested mode because you’re in Desktop Mode:

KDE Plasma Wayland Compositor (Your Desktop)
  ↓
Creates a Wayland window for gamescope
  ↓
Gamescope runs as a "microcompositor" INSIDE this window
  ↓
Gamescope creates its own internal Wayland server: "gamescope-0"
  ↓
Gamescope spawns Xwayland server (for X11 app compatibility): ":0" or ":1"

Layer 3: Your Wrapper Script Executes

env /home/deck/lsfg-vk %command%
  ↓
Runs your wrapper script which exports:
  - DISABLE_VK_LAYER_VALVE_steam_fossilize_1=1
  - DISABLE_VK_LAYER_VALVE_steam_overlay_1=1
  - LSFGVK_PROFILE=test
  ↓
Then executes whatever %command% expands to (Proton + game exe)

Layer 4: Proton Initializes

Proton (Steam's Wine-based compatibility layer)
  ↓
Sets up Wine prefix
  ↓
Loads DXVK (DirectX → Vulkan translation) or VKD3D (DX12 → Vulkan)
  ↓
Initializes Vulkan with environment variables from your wrapper

Layer 5: Vulkan Layer Stack Loads

This is where things get interesting! The Vulkan loader builds a chain of layers:

Application (MOTORSLICE.exe)
  ↓
DXVK/VKD3D (DirectX → Vulkan translation)
  ↓
VK_LAYER_LSFGVK_frame_generation (lsfg-vk layer - YOUR FRAME GENERATION)
  ↓
VK_LAYER_FROG_gamescope_wsi_x86_64 (Gamescope's WSI layer)
  ↓
Mesa Vulkan Driver (RADV for AMD)
  ↓
Linux Kernel DRM/KMS
  ↓
AMD GPU Hardware

What each layer does:

lsfg-vk layer:

Gamescope WSI layer:

Mesa RADV driver:

Layer 6: Gamescope Composites the Frame

Game renders frame → DXVK/VKD3D → lsfg-vk generates 3x frames → Gamescope WSI layer
  ↓
Gamescope's internal compositor receives frames
  ↓
Gamescope composites (if needed):
  - Upscales (from 1920x1080 game to 1920x1080 output in your case)
  - Overlays MangoHud (via --mangoapp)
  - Applies FSR/NIS if specified
  ↓
Gamescope presents to its Wayland buffer

With --backend wayland:

With --backend sdl:

Layer 7: Desktop Compositor Displays

Gamescope's Wayland surface
  ↓
KDE Plasma Wayland Compositor (KWin)
  ↓
Composites gamescope window with desktop elements (taskbar, etc.)
  ↓
Presents to DRM/KMS
  ↓
Linux Kernel sends buffer to display controller
  ↓
Steam Deck LCD Panel (800p @ 60Hz)
  ↓
Your eyeballs!

What Changes in Gaming Mode (Embedded Mode)

In Gaming Mode, the stack is MUCH simpler:

Application
  ↓
DXVK/VKD3D
  ↓
lsfg-vk layer
  ↓
Mesa RADV
  ↓
Gamescope (runs in EMBEDDED MODE - no nested window)
  ↓
DRM/KMS DIRECTLY (no desktop compositor!)
  ↓
LCD Panel

Why embedded mode is faster:


Essential Read 1:

Before you move on I recommend you read Gamescope Compatibility section from the maker of Lossless Scaling for linux
https://github.com/PancakeTAS/lsfg-vk/wiki/Gamescope-Compatibility/760be621450d283c0ac73b6e65e9bc0d9fb6a14e#understanding-how-gamescope-works


← Back to top

How DRM Actually Works

The Direct Rendering Manager resides in kernel space and controls the physical display hardware directly. Here’s what actually happens:

DRM Master Process (gamescope on TTY1)
  ↓
Takes EXCLUSIVE control of /dev/dri/card0
  ↓
Sends frames directly to GPU → Display Pipeline → Your LCD Panel

Critical point: DRM doesn’t render “to a TTY” - it renders to the physical display hardware. TTYs are just sessions that can request control of that hardware.

Why You Can’t Send DRM Output from TTY1 to Display on TTY2

When gamescope runs in DRM mode (embedded mode with -e or --backend drm):

  1. It becomes the DRM master - exclusive control of the GPU
  2. It controls the physical display - not a virtual framebuffer
  3. Only ONE process can be DRM master at a time
TTY1: gamescope -e (DRM master)
  ↓
Controls /dev/dri/card0 EXCLUSIVELY
  ↓
GPU output goes to PHYSICAL DISPLAY
  ↓
The display shows whatever TTY1's DRM master is rendering

TTY2: KDE Plasma
  ↓
CANNOT be DRM master (TTY1 already has it)
  ↓
CANNOT see the display (TTY1 controls it)
  ↓
Is effectively "blind" while TTY1 is active

Only ONE TTY can control the display at a time!

What “Switching TTYs” Actually Does

When you press Ctrl+Alt+F2:

  1. Kernel switches active VT (virtual terminal)
  2. TTY2 becomes active and takes control of:
    • Input devices (keyboard/mouse)
    • DRM master (if it has a compositor)
    • Physical display output
  3. TTY1 becomes inactive and:
    • Loses input
    • Loses DRM master
    • Its processes keep running but can’t display anything

The display hardware can only show ONE TTY at a time!


← Back to top

What is Gamescope Headless Mode?

Headless mode allows gamescope to work without a display, useful for remote gaming scenarios where you stream the output to another device.

What it actually does:

Game → Gamescope (--backend headless) → NO DISPLAY OUTPUT
                                        ↓
                                   Encoders only (for streaming)

Headless mode:

# On a headless server (no monitor connected)
gamescope --backend headless -e -W 1920 -H 1080 -- steam -gamepadui

# From another device, stream via Steam Link or Moonlight
# You see the game on your phone/tablet, server has no display

Why It Won’t Work for TTY1 → TTY2

Your idea seems to be:

  1. Run gamescope headless on TTY1
  2. Somehow “pipe” or “display” the output on TTY2

This fundamentally doesn’t work because:

Problem 1: Headless = No Display Output

TTY1: gamescope --backend headless -- game
      ↓
      Renders to GPU memory
      ↓
      NO OUTPUT TO ANY TTY (not even TTY1!)

Headless mode intentionally doesn’t draw to the screen. It only:

Problem 2: TTYs Don’t Share Framebuffers

Each TTY has its own exclusive framebuffer:

TTY1: /dev/fb0 (controlled by TTY1's session)
TTY2: /dev/fb0 (controlled by TTY2's session when active)

When you switch TTYs, the kernel switches which TTY owns the display. There’s no mechanism to “share” or “pipe” framebuffer output between TTYs.

Problem 3: DRM/KMS is Exclusive

Gamescope embedded mode uses DRM/KMS, which requires exclusive access to the display connector. Only one application can control DRM at a time:

Note: Headless mode could be experimental and buggy. Not recommended for local gaming.


← Back to top

Gamescope Capabilities: CAP_SYS_NICE

What is CAP_SYS_NICE?

CAP_SYS_NICE is a Linux capability that allows a process to set higher scheduling priorities for itself. For gamescope, this means it can run critical threads with realtime FIFO scheduling, which significantly reduces latency and improves frame pacing.

Quick links:


Step 1: Check If Gamescope Has CAP_SYS_NICE Capability

Run this command:

getcap $(which gamescope)

Expected outputs:

If CAP_SYS_NICE is set:

/usr/bin/gamescope cap_sys_nice=eip

If NOT set (most common in Desktop Mode):

(no output or "= cap_sys_nice+eip")

Next steps:


Step 2: Check Gamescope’s Runtime Messages

When you launch gamescope, watch the console output:

gamescope -W 1920 -H 1080 -f --backend wayland -- vkcube

If CAP_SYS_NICE is missing, you’ll see:

No CAP_SYS_NICE, falling back to regular-priority compute and threads.
Performance will be affected.

If CAP_SYS_NICE is present:

(no warning about CAP_SYS_NICE)

Step 3: Check Gamescope’s Actual Process Priority While Running

While gamescope is running, open another terminal and run:

ps -eo pid,ni,comm | grep gamescope

Output explanation:

  PID  NI COMMAND
 12345   0 gamescope         ← Nice value 0 (normal priority, CAP_SYS_NICE not working)
 12345 -10 gamescope         ← Nice value -10 (elevated priority, CAP_SYS_NICE working!)

Want more detail? See Thread Priorities (Advanced)


Step 4: Check Thread Priorities (Advanced)

For a more detailed view of gamescope’s thread scheduling:

ps -eLo pid,tid,class,ni,comm | grep gamescope

Expected output with CAP_SYS_NICE working:

  PID   TID CLS  NI COMMAND
12345 12345  TS -10 gamescope        ← Main thread with nice -10
12345 12346  FF   0 gamescope:cs0    ← Realtime FIFO thread (compute)
12345 12347  FF   0 gamescope:cs1    ← Realtime FIFO thread

Without CAP_SYS_NICE:

  PID   TID CLS  NI COMMAND
12345 12345  TS   0 gamescope        ← Normal priority
12345 12346  TS   0 gamescope:cs0    ← No realtime priority

Legend:

Learn more: Understanding Thread Breakdown


Step 5: Check Systemd Service Configuration (Gaming Mode)

systemctl cat gamescope-session.service

You’ll likely see something like:

[Service]
AmbientCapabilities=CAP_SYS_NICE CAP_SYS_RESOURCE
CapabilityBoundingSet=CAP_SYS_NICE CAP_SYS_RESOURCE

This shows that in Gaming Mode, CAP_SYS_NICE is enabled by default through systemd.


When CAP_SYS_NICE Matters Most

You NEED it for:

It matters less for:

Ready to enable it? Jump to How to Grant CAP_SYS_NICE


How to Grant CAP_SYS_NICE Properly

If you want to enable it:

sudo setcap 'CAP_SYS_NICE=+eip' $(which gamescope)

Verify it worked:

getcap $(which gamescope)

Should show: /usr/bin/gamescope cap_sys_nice=eip

⚠️ IMPORTANT: Read the caveats below before enabling!

Verify it’s working: Check Process Priority


⚠️ Important Caveats

Using setcap causes some Vulkan environment variables to be ignored, such as MESA_VK_DEVICE_SELECT. Also, it can stop the Steam In-Game Overlay from working.

Workaround for overlay:

You can restore Steam functionality by bypassing LD_PRELOAD on gamescope and passing it to the command:

gamescope -- env LD_PRELOAD="$LD_PRELOAD" %command%

Note: Your current wrapper already has LD_PRELOAD="" which clears it, so you might lose Steam overlay if you enable CAP_SYS_NICE.

Related: See Verification section for checking environment variables


Quick Diagnostic Script

Save this as ~/check-gamescope-priority.sh:

#!/bin/bash
echo "=== Gamescope CAP_SYS_NICE Check ==="
echo ""
echo "1. Checking binary capabilities:"
getcap $(which gamescope)
echo ""
echo "2. Gamescope running processes:"
ps -eo pid,ni,comm | grep gamescope
echo ""
echo "3. Gamescope thread scheduling:"
ps -eLo pid,tid,class,ni,comm | grep gamescope | head -5
echo ""
echo "=== Interpretation ==="
echo "NI = 0: Normal priority (no CAP_SYS_NICE)"
echo "NI < 0: Elevated priority (CAP_SYS_NICE working)"
echo "CLS = TS: Normal scheduler"
echo "CLS = FF: Realtime FIFO scheduler (best)"

Make it executable:

chmod +x ~/check-gamescope-priority.sh

Run it:

~/check-gamescope-priority.sh

What the output means: Understanding Thread Breakdown


Understanding Thread Breakdown

Example output with CAP_SYS_NICE enabled:

  PID   TID CLS  NI COMMAND
45061 45061  TS -20 gamescope-wl     ← Main Wayland compositor thread (MAX PRIORITY)
45061 45062  TS   0 gamescope-eis    ← EIS (input emulation) - normal priority
45061 45063  TS   0 gamescope_img    ← Image processing - normal priority
45061 45064  TS -20 gamescope        ← Core gamescope thread (MAX PRIORITY)
45061 45086  TS -20 gamescope-pw     ← PipeWire audio thread (MAX PRIORITY)
45061 45087  TS -20 gamescope-xwm    ← X Window Manager thread (MAX PRIORITY)
45061 45089  TS -20 gamescope-wait   ← Wait/sync thread (MAX PRIORITY)

Key threads that benefit from high priority:

Threads that don’t need high priority:

Back to top: CAP_SYS_NICE Table of Contents


← Back to top

Next: Execution Flow

Command Breakdown: Execution Flow

Your command:

PROTON_LOG=1 LD_PRELOAD="" SteamDeck=0 gamescope -W 1920 -H 1080 -f --backend wayland --mangoapp -- env /home/deck/lsfg-vk %command%

Part 1: Environment Variables (Before gamescope)

PROTON_LOG=1 LD_PRELOAD="" SteamDeck=0

Where these are set: In the parent process (Steam client) before launching gamescope

What happens:

  1. Steam reads your launch options
  2. Steam’s shell sets these environment variables
  3. Steam then executes the gamescope command
  4. Gamescope inherits these variables

Scope: These variables are available to:

Part 2: Gamescope and Its Arguments

gamescope -W 1920 -H 1080 -f --backend wayland --mangoapp

Where this runs: Steam client executes gamescope as a subprocess

What happens:

  1. Steam spawns gamescope with these arguments
  2. Gamescope parses the arguments:
    • -W 1920 -H 1080 → Internal resolution
    • -f → Fullscreen mode
    • --backend wayland → Use Wayland nested mode
    • --mangoapp → Load MangoHud overlay

Initiated by: Steam client (the parent process)

Part 3: The Separator --

--

What this means: “End of gamescope arguments, everything after this is THE COMMAND TO RUN INSIDE GAMESCOPE”

This is a standard Unix convention for separating program arguments from the command it should execute.

Example breakdown:

gamescope [gamescope args] -- [command to run inside gamescope]

Part 4: env Command

env /home/deck/lsfg-vk %command%

Where this runs: Inside gamescope’s Xwayland session

What happens:

  1. Gamescope finishes initializing
  2. Gamescope creates its internal Xwayland server (e.g., DISPLAY=:1)
  3. Gamescope executes whatever comes after --, which is: env /home/deck/lsfg-vk %command%

What env does:

Initiated by: Gamescope (after it’s set up its environment)

Process hierarchy at this point:

Steam (TTY2, your desktop session)
  └─ gamescope (TTY2, nested in Plasma)
      └─ Xwayland server (DISPLAY=:1, created by gamescope)
          └─ env (running inside gamescope's X server)
              └─ /home/deck/lsfg-vk (your wrapper script)
                  └─ %command% (expanded by Steam to: proton run game.exe)

Key Insight: The env Command Is Redundant

In your command:

gamescope ... -- env /home/deck/lsfg-vk %command%

The env command here is doing nothing useful. You could simplify to:

gamescope ... -- /home/deck/lsfg-vk %command%

Why env might have been included:

Part 5: Your Wrapper Script

/home/deck/lsfg-vk

Where this runs: Inside gamescope’s Xwayland session

What it does:

#!/bin/bash
export DISABLE_VK_LAYER_VALVE_steam_fossilize_1=1
export DISABLE_VK_LAYER_VALVE_steam_overlay_1=1
export LSFGVK_PROFILE=test
exec "$@"  # Executes whatever arguments were passed (the %command% part)

Initiated by: The env command (which was initiated by gamescope)

Environment at this point:

Part 6: %command% Expansion

%command%

What this is: A Steam placeholder that gets replaced by Steam

Where expansion happens: In the Steam client, BEFORE launching anything

What it expands to: The actual game launch command, something like:

/home/deck/.local/share/Steam/compatibilitytools.d/proton-ge/proton run /path/to/game.exe

Example:

# What you write in Steam:
env /home/deck/lsfg-vk %command%

# What Steam actually executes:
env /home/deck/lsfg-vk /home/deck/.local/share/Steam/compatibilitytools.d/proton-ge/proton run /home/deck/.local/share/Steam/steamapps/common/MOTORSLICE/motorslice.exe

Initiated by: Your wrapper script’s exec "$@" runs this expanded command


← Back to top

Complete Execution Flow with Locations

Let me trace the full path of execution:

Step 1: Steam Client (TTY2, in KDE Plasma)

Steam reads launch options
  ↓
Sets environment: PROTON_LOG=1 LD_PRELOAD="" SteamDeck=0
  ↓
Expands %command% to actual game path
  ↓
Executes: gamescope -W 1920 -H 1080 -f --backend wayland --mangoapp -- env /home/deck/lsfg-vk [full game command]

Step 2: Gamescope (TTY2, nested in Plasma’s Wayland)

Gamescope receives command and arguments
  ↓
Parses its own arguments (-W, -H, -f, --backend, --mangoapp)
  ↓
Initializes Wayland backend (connects to Plasma's wayland-0)
  ↓
Creates nested Xwayland server (DISPLAY=:1 or :2)
  ↓
Launches command after '--': env /home/deck/lsfg-vk [full game command]

Step 3: env (Inside gamescope’s Xwayland, TTY2)

env command runs
  ↓
No environment modifications (env used without args)
  ↓
Executes: /home/deck/lsfg-vk [full game command]

Step 4: Your Wrapper (Inside gamescope’s Xwayland, TTY2)

/home/deck/lsfg-vk script runs
  ↓
Exports additional environment variables:
  - DISABLE_VK_LAYER_VALVE_steam_fossilize_1=1
  - DISABLE_VK_LAYER_VALVE_steam_overlay_1=1
  - LSFGVK_PROFILE=test
  ↓
exec "$@" replaces itself with: proton run motorslice.exe

Step 5: Proton (Inside gamescope’s Xwayland, TTY2)

Proton initializes
  ↓
Sets up Wine prefix
  ↓
Loads DXVK/VKD3D
  ↓
Loads Vulkan layers (including lsfg-vk because LSFGVK_PROFILE=test)
  ↓
Executes: motorslice.exe

Step 6: Your Game (Inside gamescope’s Xwayland, TTY2)

MOTORSLICE.exe runs
  ↓
Renders frames via DXVK → Vulkan
  ↓
lsfg-vk intercepts and generates frames
  ↓
Presents to gamescope's Xwayland
  ↓
Gamescope composites and presents to Plasma
  ↓
Plasma displays on your monitor

Physical Location: Where Each Process Actually Runs

All of these processes are running on TTY2 (your KDE Plasma session):

TTY2 (KDE Plasma Desktop Session)
├─ Steam client
│   └─ gamescope (nested window in Plasma)
│       └─ Xwayland server (created by gamescope)
│           └─ env
│               └─ lsfg-vk wrapper
│                   └─ proton
│                       └─ MOTORSLICE.exe

They’re all on the same TTY! Gamescope is running in nested mode as a window inside your Plasma desktop.

Environment Variable Inheritance Table

Here’s what each process sees:

Variable Set By Steam Gamescope Wrapper Proton Game
PROTON_LOG=1 Launch options
LD_PRELOAD=”” Launch options
SteamDeck=0 Launch options
DISPLAY=:1 Gamescope
ENABLE_GAMESCOPE_WSI=1 Gamescope
DISABLE_VK_LAYER_* Wrapper
LSFGVK_PROFILE=test Wrapper

← Back to top

Next: Learn About Variables

Do Exported Variables Get Passed to Games? Do They Persist?

Great question! Let me break this down:

Do Environment Variables Pass to Child Processes?

Yes! Environment variables are inherited by child processes.

When you do this:

export LSFGVK_PROFILE=test
steam -gamepadui

The inheritance chain:

bash (has LSFGVK_PROFILE=test)
  └─ steam (inherits LSFGVK_PROFILE=test)
      └─ game launcher (inherits LSFGVK_PROFILE=test)
          └─ proton (inherits LSFGVK_PROFILE=test)
              └─ game.exe (inherits LSFGVK_PROFILE=test!)

Every child process inherits the parent’s environment!

You Can Verify This

While a game is running, check its environment:

# Find the game process
ps aux | grep -i motorslice

# Check its environment (replace PID with actual PID)
cat /proc/<PID>/environ | tr '\0' '\n' | grep LSFGVK

You’ll see LSFGVK_PROFILE=test in the game’s environment!

How Steam Launch Options Interact

When you set launch options in Steam:

/home/deck/lsfg-vk %command%

What Steam does:

# Steam internally runs:
/home/deck/lsfg-vk /path/to/proton run game.exe

# Your wrapper script exports more variables:
export LSFGVK_PROFILE=test  # (already inherited from parent, or set here)
exec "$@"  # Runs: /path/to/proton run game.exe

# The game inherits EVERYTHING:
# - Variables from your login shell
# - Variables exported in the TTY session
# - Variables exported in wrapper scripts

Bottom line: Variables “stack” and get inherited all the way down!

Do Exported Variables Persist?

This depends on WHERE you export them:

Scenario A: Export in Terminal/Script (Temporary)

# In a terminal or script
export LSFGVK_PROFILE=test
steam

Persistence: Only for that shell session and its children

Scenario B: Export in Shell RC File (Persistent)

Add to ~/.bashrc:

export LSFGVK_PROFILE=test

Persistence: Every new bash shell gets it

Scenario C: Export in Login Script (Persistent, All Sessions)

Add to ~/.bash_profile or ~/.profile:

export LSFGVK_PROFILE=test

Persistence: Every login session gets it

Scenario D: Systemd Service Environment (Permanent)

For gamescope-session or custom systemd services:

[Service]
Environment="LSFGVK_PROFILE=test"

Persistence: Only for that specific service


Q1: Do the “steamscope launcher variables” only affect Steam?

Yes, mostly! Let me break down each variable:

Variables That Only Affect Steam Client

These variables tell the Steam client to enable certain UI features and functionality:

STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT=1

STEAM_GAMESCOPE_COLOR_MANAGED=1

STEAM_MULTIPLE_XWAYLANDS=1

STEAM_GAMESCOPE_VRR_SUPPORTED=1 (commented out in your script)

Variables That Affect Gamescope/Games

ENABLE_GAMESCOPE_WSI=1

How to Actually Enable FSR/NIS Upscaling

FSR/NIS is controlled by gamescope command-line flags, not environment variables:

# Enable FSR upscaling
gamescope -W 1920 -H 1080 -w 1280 -h 800 -F fsr -- game

# Enable NIS upscaling
gamescope -W 1920 -H 1080 -w 1280 -h 800 -F nis -- game

# No upscaling (linear)
gamescope -W 1920 -H 1080 -w 1280 -h 800 -F linear -- game

The -F (filtering) flag controls the upscaling algorithm. This works whether or not STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT is set!

Your use case (TTY with manual gamescope):

Simplified Script Example

#!/bin/bash

# ===== CRITICAL VARIABLES (Actually needed) =====

# Disable conflicting Steam Vulkan layers
export DISABLE_VK_LAYER_VALVE_steam_fossilize_1=1
export DISABLE_VK_LAYER_VALVE_steam_overlay_1=1

# Enable Gamescope WSI (REQUIRED for gamescope to hook Vulkan)
export ENABLE_GAMESCOPE_WSI=1

# Enable per-game Xwayland isolation (RECOMMENDED for stability)
export STEAM_MULTIPLE_XWAYLANDS=1

# lsfg-vk profile
export LSFGVK_PROFILE=test

# ===== OPTIONAL VARIABLES (Only if using Steam UI features) =====

# Uncomment ONLY if you want Steam's FSR UI controls:
# export STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT=1

# Uncomment ONLY if you want Steam's HDR UI controls:
# export STEAM_GAMESCOPE_COLOR_MANAGED=1

# ===== LAUNCH GAMESCOPE =====

gamescope -e \
  -W 1280 -H 800 \
  -r 60 \
  --immediate-flips \
  --adaptive-sync \
  --mangoapp \
  -- steam -gamepadui

Verification

While gamescope + vkcube is running, check what environment it actually sees:

cat /proc/$(pgrep gamescope)/environ | tr '\0' '\n' | grep -E 'LSFGVK|MESA|ENABLE'

You should see your variables like:

LSFGVK_PROFILE=test
DISABLE_VK_LAYER_VALVE_steam_fossilize_1=1
MESA_VK_WSI_PRESENT_MODE=immediate

If you DON’T see them, it means CAP_SYS_NICE is causing environment variable sanitization.


← Back to top

What is a TTY?

Historical Context → Modern Implementation

Evolution:

Types of TTYs in Modern Linux

1. Virtual Console TTYs (/dev/tty1 through /dev/tty63)

These are virtual text consoles accessible from the keyboard, emulated in hardware using the screen and keyboard connected to your computer.

On Steam Deck / Modern Linux:

How to access:

2. Pseudo-TTYs (PTY) (/dev/pts/0, /dev/pts/1, etc.)

A PTY enables bi-directional communication similar to pipes, but provides a terminal interface to any process that requires it.

Used for:

When you open Konsole in Desktop Mode and type tty, you’ll see something like /dev/pts/0.

3. Special TTY Devices

Critical Question: What Do TTYs Inherit and Share?

This is the KEY question for your gamescope use case! Here’s the breakdown:

✅ SHARED Across ALL TTYs (System-Wide)

These things are global and available to all TTYs:

1. Kernel and System Services

Example: If you connect to WiFi on TTY2 (Desktop Mode), TTY3 will also have network access. NetworkManager is a system service, not per-TTY.

2. User Processes (Within Same User)

3. /tmp and Shared Memory

❌ NOT SHARED (TTY-Specific)

These things are isolated per TTY session:

1. Login Session

Each TTY gets its own login session managed by systemd-logind.

2. Environment Variables

Each TTY session has its own environment:

# TTY2 (Desktop Mode with KDE)
$ echo $XDG_SESSION_TYPE
wayland
$ echo $DISPLAY
:0

# TTY3 (Text console)
$ echo $XDG_SESSION_TYPE
tty
$ echo $DISPLAY
(empty - no X11/Wayland)

This means:

3. Graphics/Display Server

CRITICAL FOR YOUR USE CASE:

Each TTY can run its own independent graphics session:

TTY2 (KDE Plasma Desktop Mode):

Wayland Compositor (KWin) running
Xwayland server for X11 apps
Display: :0 or wayland-0
All GPU resources allocated to this session

TTY3 (Text-only):

NO graphical server
Framebuffer console only (text mode)
Direct DRM/KMS access available

TTY4 (If you run gamescope there):

Gamescope running in embedded mode
Direct DRM/KMS access
Independent from TTY2's compositor

Key insight: The main difference between the Linux console and graphical terminal emulators is the shells in the Linux console are attached directly to TTY devices, whereas shells in a graphical terminal emulator are attached to pseudo-TTYs.

4. Audio Session (Depends on Configuration)

Modern Linux uses PipeWire or PulseAudio:

On Steam Deck, systemd spawns getty services on demand as you switch to VTs, and each gets its own PipeWire session.

Test this: Play music on TTY2, switch to TTY3 - you might lose audio depending on PipeWire config.

5. Input Devices

The keyboard and mouse are grabbed by whichever TTY has focus:


Practical Example: What Happens When You Switch from TTY2 → TTY3

You’re on TTY2 (KDE Plasma Desktop Mode):

TTY2 Active:
├─ KDE Plasma Wayland Compositor (running)
├─ Steam Client (running)
├─ Discord (running)
├─ Firefox (running)
└─ Input: Keyboard/Mouse → KDE

System Services (always running):
├─ NetworkManager (WiFi connected)
├─ PipeWire (audio server)
├─ systemd-logind (session manager)
└─ Steam downloads (background daemon)

You Press Ctrl+Alt+F3 → Switch to TTY3:

TTY3 Now Active:
├─ Text login prompt OR shell (if already logged in)
├─ Framebuffer console (80x25 text mode or higher with KMS)
└─ Input: Keyboard only (no mouse in text mode)

TTY2 Still Running But Inactive:
├─ KDE Plasma (frozen, no redraws)
├─ Steam Client (running but can't display)
├─ Discord (running, still receives messages)
├─ Firefox (running, tabs still load)
└─ Input: NONE (switched to TTY3)

System Services (still running):
├─ NetworkManager ✅ (WiFi still connected!)
├─ PipeWire ✅ (might mute TTY2's audio, depends on config)
├─ systemd-logind ✅ (managing both sessions)
└─ Steam downloads ✅ (continue in background)

Key insight: TTY2 applications keep running but can’t display anything because the GPU’s output is now showing TTY3.


← Back to top

What This Means for Your Gamescope Setup

Running Gamescope on TTY2 (Current Setup):

TTY2:
├─ KDE Plasma Wayland (base compositor)
│   └─ Gamescope (nested window)
│       └─ Steam + Game
└─ Networking: ✅ Shared
└─ GPU: ✅ Shared (but KDE owns the display)

Display stack:

Game → Gamescope → KDE Plasma → DRM/KMS → LCD

Latency: ~20-40ms extra from KDE compositor

Running Gamescope on TTY3 (Your Proposed Solution):

TTY3:
└─ Gamescope (embedded mode, direct DRM)
    └─ Steam + Game
└─ Networking: ✅ STILL AVAILABLE!
└─ GPU: ✅ Exclusive access (no compositor overhead)

Display stack:

Game → Gamescope → DRM/KMS → LCD

Latency: Minimal, direct to hardware


Smart Two-TTY Approach

TTY1 Script (Starts Gamescope):

#!/usr/bin/bash
export XDG_SESSION_TYPE=x11

tmpdir="$(mktemp -p "$XDG_RUNTIME_DIR" -d -t gamescope.XXXXXXX)"
socket="${tmpdir:+$tmpdir/startup.socket}"

/usr/bin/gamescope -W 2560 -H 1440 --default-touch-mode 4 --hide-cursor-delay 3000 --fade-out-duration 200 -R $socket

TTY2 (Opens Steam and Connects to TTY1’s Gamescope):

DISPLAY=:0 steam -gamepadui -steamos3 -steampal -steamdeck

What’s Happening Here:

This is NOT headless mode - it’s actually:

  1. TTY1: Runs gamescope in embedded DRM mode (takes over the display)
  2. TTY2: Runs Steam which connects to gamescope’s X server via DISPLAY=:0
  3. Result: Gamescope displays on screen (via TTY1’s DRM), Steam UI runs from TTY2

The magic: DISPLAY=:0 tells Steam on TTY2 to connect to the X server that gamescope created on TTY1!

This is brilliant! You get:


← Back to top

Key Insight: X11/Xwayland is Network-Transparent

X11 (and Xwayland) uses a client-server architecture:

X Server (gamescope on TTY1, :0)
    ↑
    | Unix socket: /tmp/.X11-unix/X0
    |
X Clients (Steam on TTY2, connects via DISPLAY=:0)

The X Window System is an architecture that was designed at its core to run over a network. X11’s client-server model uses network-transparent protocols:

X Server (gamescope on TTY1)
    ↕ Unix socket or TCP
X Clients (Steam on TTY2)

The client and server communicate via:

This is why DISPLAY=:0 steam on TTY2 can connect to gamescope’s Xwayland server on TTY1!

Why This Does NOT Work with Pure Wayland

Wayland does not offer network transparency by itself. Wayland’s design is fundamentally different:

Wayland Compositor (runs on TTY1)
    ↕ Direct shared memory buffers (wl_buffer)
Wayland Clients (must run in same session)

Key differences:

  1. Wayland uses shared memory buffers, not network protocols
    • The internal type of wl_buffer object is implementation dependent, but the content data must be shareable between the client and the compositor
    • Shared memory (/dev/shm) is per-session, not global
  2. No socket-based communication like X11
    • Wayland socket: $XDG_RUNTIME_DIR/wayland-0 (e.g., /run/user/1000/wayland-0)
    • This socket is session-specific and typically only accessible to processes in that session
  3. Security by design prevents cross-session access
    • Wayland, by design, addresses security flaws by sandboxing application access to both graphical output and input, ensuring that no process outside the compositor can view or control other applications unless explicitly permitted

Gamescope Backend Modes

--backend wayland or --backend sdl (Nested Mode)

Requires: Existing compositor (KDE Plasma, GNOME, etc.)

What it does:

Gamescope connects to existing Wayland/X11 compositor
    ↓
Creates a window inside that compositor
    ↓
Becomes a "nested compositor" (compositor within compositor)

Example: Your TTY1 test - Plasma is the host compositor, gamescope is a client

--backend drm or -e (Embedded Mode)

Requires: Direct access to GPU via DRM/KMS (no existing compositor)

What it does:

Gamescope talks DIRECTLY to GPU via /dev/dri/card0
    ↓
Takes exclusive control of display
    ↓
No other compositor needed

Complete Comparison

TTY Graphical Session Command Backend Result
TTY1 ✅ KDE Plasma gamescope --backend wayland vkcube Wayland (nested) ✅ Works - creates window in Plasma
TTY1 ✅ KDE Plasma gamescope -e vkcube DRM (embedded) ❌ Fails - Plasma already owns DRM
TTY2 ❌ None gamescope --backend wayland vkcube Wayland (nested) ❌ Fails - no compositor to connect to
TTY2 ❌ None gamescope -e vkcube DRM (embedded) ✅ Works - direct GPU access

Auto-Detection Logic

When you run gamescope without specifying --backend, it:

  1. Checks for $WAYLAND_DISPLAY → If set, use --backend wayland
  2. Checks for $DISPLAY → If set, use --backend x11
  3. Otherwise → Use --backend drm (embedded mode)

On TTY1 (Plasma):

$ echo $WAYLAND_DISPLAY
wayland-0
# gamescope auto-uses --backend wayland (nested)

On TTY2 (text console):

$ echo $WAYLAND_DISPLAY
(empty)
$ echo $DISPLAY
(empty)
# gamescope auto-uses --backend drm (embedded)

← Back to top

Setting Up Auto-Login on TTY2

Set up autologin so TTY2 automatically runs gamescope when you switch to it:

sudo systemctl edit getty@tty2

Add these lines:

[Service]
ExecStart=
ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin deck %I $TERM

Save and exit. Then create ~/.bash_profile or edit existing one:

# Auto-start gamescope on TTY2
if [[ "$(tty)" == "/dev/tty2" ]]; then
    exec ~/.local/bin/steamscope
fi

Known Issues & Limitations

Issues with TTY gamescope mode include: game overlay does not work, switching to your DE while a game is running can cause gamescope to crash, giving gamescope CAP_SYS_NICE causes some environment variables to be ignored, and simultaneous HDR and VRR can cause stuttering.

Workarounds:


Potential Issues & Solutions

Issue 1: “DISPLAY=:0 connection refused”

Gamescope might create :1 instead of :0

Issue 2: No input on Steam

You need to be on TTY1 for keyboard/mouse input to work:

Ctrl+Alt+F1

Issue 3: Audio might not work

PipeWire sessions are per-TTY. Export the correct PipeWire socket:

# On TTY2 before launching Steam
export PIPEWIRE_RUNTIME_DIR=/run/user/1000
DISPLAY=:0 steam -gamepadui

Your Error Message Explained

Gamescope fails to create Wayland objects

This error means:

  1. Gamescope tried to use --backend wayland (nested mode)
  2. It looked for a Wayland compositor to connect to
  3. It tried to create Wayland client objects (windows, surfaces)
  4. No Wayland socket exists on TTY2
  5. Object creation fails → error

Solution: Use -e or --backend drm on TTY2!


← Back to top

Gamescope + VSync Clarification

Gamescope does its own compositing and presentation, so in-game VSync settings are often ignored. Gamescope controls the final presentation to your display.

Key points:

-f / --fullscreen

Makes gamescope’s window fullscreen on your desktop

WITH -f:

Gamescope requests fullscreen → KDE Plasma makes the window cover entire screen → VSync enforced → --immediate-flips ignored

The behavior of -f is to use the entire display as the output resolution regardless of -W and -H, which means your specified resolution becomes suggestions rather than hard limits.

WITHOUT -f:

Gamescope creates a regular window → KDE Plasma treats it like any window → You can resize, minimize, etc.

--immediate-flips

The two flags are mutually exclusive in nested mode - using them together results in tearing being disabled


← Back to top

Emergency Recovery: Stuck TTY or Gamescope

If Gamescope is Frozen/Stuck

Method 1: gamescopectl (Preferred)

From another TTY or SSH:

# Switch to TTY with working terminal (Ctrl+Alt+F3)
gamescopectl shutdown

More gamescopectl commands

Method 2: Kill Process

# Find gamescope PID
ps aux | grep gamescope

# Kill it (replace PID)
kill -9 [PID]

# Or kill all gamescope instances
pkill -9 gamescope

Method 3: Force TTY Switch

Sometimes switching TTYs multiple times helps:

Ctrl+Alt+F1  # Try desktop
Ctrl+Alt+F2  # Try another TTY
Ctrl+Alt+F7  # Traditional graphical login

If You’re Completely Stuck on a TTY

Recovery Steps:

1. Try to get to a working TTY

2. If display is frozen but you can type:

# Blind typing - you won't see output but it might work:
# Type carefully:
sudo systemctl restart gdm
# Or on Steam Deck:
sudo systemctl restart sddm

3. SSH from another device (if enabled):

# From another computer on same network:
ssh deck@steamdeck.local

# Then kill gamescope:
pkill -9 gamescope

# Restart display manager:
sudo systemctl restart sddm

4. Hard reset (last resort):


Preventing TTY Lock-ups

1. Test in nested mode first:

# Safe - runs in window, easy to close
gamescope --backend wayland -- vkcube

2. Set up SSH access (highly recommended):

# Enable SSH
sudo systemctl enable sshd
sudo systemctl start sshd

# Set password if not already set
passwd

3. Learn the TTY switching shortcuts:

4. Keep one TTY with a working shell:


If Gamescope Won’t Start

Check these in order:

  1. Backend mode issue?
    # Are you on Desktop Mode (TTY with Plasma)?
    # Use nested mode:
    gamescope --backend wayland -- game
       
    # Are you on empty TTY?
    # Use embedded mode:
    gamescope -e -- game
    

    Full backend explanation

  2. DRM already in use?
    # Check what's using DRM:
    lsof /dev/dri/card0
       
    # Kill conflicting process or switch to nested mode
    
  3. Missing dependencies?
    # Check gamescope can run:
    gamescope --help
       
    # Check for errors:
    gamescope -e -- vkcube
    
  4. Permissions issue?
    # Check you're in video group:
    groups
       
    # Add yourself if missing:
    sudo usermod -a -G video $USER
    # Log out and back in
    

← Back to top

FSR/NIS Upscaling

How FSR/NIS Works in Gamescope

Gamescope can upscale games from a lower internal resolution to your display resolution, improving performance while maintaining visual quality.

Basic syntax:

gamescope -w [internal width] -h [internal height] -W [output width] -H [output height] -F [filter] -- game

Upscaling Filters

FSR (FidelityFX Super Resolution)

gamescope -w 1280 -h 800 -W 1920 -H 1080 -F fsr -- game

NIS (NVIDIA Image Scaling)

gamescope -w 1280 -h 800 -W 1920 -H 1080 -F nis -- game

Linear (No Upscaling)

gamescope -w 1280 -h 800 -W 1920 -H 1080 -F linear -- game

Common Upscaling Ratios

Internal Res Output Res Scaling Factor Use Case
1280x720 1920x1080 1.5x Balanced performance
960x540 1920x1080 2.0x Maximum performance
1280x800 2560x1600 2.0x Steam Deck OLED
800x600 1600x1200 2.0x Retro games

Steam UI Controls

To enable FSR controls in Steam’s Gaming Mode UI:

export STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT=1

Note: This only shows/hides the UI controls. The actual upscaling is controlled by gamescope flags.

Related: Environment Variables


← Back to top

Frame Generation (lsfg-vk)

Essential Read 2

Decky plugin or Manual install

Decky plug-in

Manual install

*if you are in steam deck you might need to use DISABLE_VK_LAYER_VALVE_steam_fossilize_1=1 to make lsfg-vk active


← Back to top

Testing and Debug

Logging to the console

If you are using nested desktop mode in steam deck and want to use gamescope but also need game boot info, launch gamescope in a seperate terminal and use DISPLAY=:0 %command% as launch option in steam
[use echo $DISPLAY to find the correct output]

MANGOHUD

Using mangohud in gamescope require you to use –mangoapp flag, you can pass the config file by using MANGOHUD_CONFIGFILE=/home/deck/.config/MangoHud/mangoapp.conf

Process monitoring

# Find gamescope processes
ps -eo pid,comm,cmd | grep -iE 'gamescope'

# Use pstree to get an idea of the tree
pstree

# Use strings to check environment variables for the found process
# (replace [] with PID obtained from ps command above)
sudo strings /proc/[PID]/environ | sort | uniq > ~/Desktop/gamescope-wl_envs.txt

gamescopectl (must have an active gamescope session to utilize)

# Dump debug info about the backend state
gamescopectl backend_info

# More accurate way to limit rate than -r flag but more work involved
gamescopectl debug_set_fps_limit

# Not sure if this will work
gamescopectl paint_steam_overlay_plane

# Cleanly shutdown gamescope
gamescopectl shutdown

# Enable or disable adaptive sync
gamescopectl adaptive_sync 1

Adaptive sync options:

Debug Tips

Keep VK_LOADER_DEBUG=all enabled temporarily to verify the layer stack. Once everything works smoothly, remove it from your wrapper to reduce log spam.

# Can pull FPS and controller data with this method
--stats-path=/home/deck/gamescope_stat.txt

PROTON_LOG=1 display proton based game info on a file created in your home directory.


How MESA_VK_WSI_PRESENT_MODE Works with Gamescope

Game → (Proton/DXVK) → Mesa Vulkan WSI → Gamescope WSI Layer → Gamescope Compositor → Display

Available modes:

Test Configurations

Test 1: Immediate + Immediate (Lowest Latency)

LD_PRELOAD="" MESA_VK_WSI_PRESENT_MODE=immediate gamescope -W 1920 -H 1080 -r 60 --immediate-flips --backend wayland --mangoapp -- env /home/deck/lsfg-vk %command%

Test 2: Mailbox + Immediate (Balanced)

LD_PRELOAD="" MESA_VK_WSI_PRESENT_MODE=mailbox vk_xwayland_wait_ready=false gamescope -W 1920 -H 1080 -r 60 --immediate-flips --backend wayland --mangoapp -- env /home/deck/lsfg-vk %command%

Test 3: Relaxed Inside Gamescope (Smooth Priority)

LD_PRELOAD="" MESA_VK_WSI_PRESENT_MODE=immediate gamescope -W 1920 -H 1080 -r 60 -f --backend wayland --mangoapp -- env MESA_VK_WSI_PRESENT_MODE=relaxed /home/deck/lsfg-vk %command%

Current Call

LD_PRELOAD="" SteamDeck=0 ENABLE_GAMESCOPE_WSI=1 DISABLE_VK_LAYER_VALVE_steam_overlay_1=1
MESA_VK_WSI_PRESENT_MODE=immediate gamescope --rt --immediate-flips -W 1920 -H 1080 -r 120 -f --mangoapp -- %command%

**Note:** `-f` might invalidate `--immediate-flips`

Environment Variables and Flags

!!NOT TESTED!! ```bash # WSI Reported better framelimiting with this enabled/ export ENABLE_GAMESCOPE_WSI=0/ # Enable support for xwayland isolation per-game in Steam/ export STEAM_MULTIPLE_XWAYLANDS=1 # sdl video driver export SDL_VIDEODRIVER=x11 # hdr support in steam #export STEAM_GAMESCOPE_HDR_SUPPORTED=1 # Show VRR controls in Steam #export STEAM_GAMESCOPE_VRR_SUPPORTED=1 # Enable Mangoapp #export STEAM_MANGOAPP_PRESETS_SUPPORTED=1 #export STEAM_USE_MANGOAPP=1 #export STEAM_DISABLE_MANGOAPP_ATOM_WORKAROUND=1 # Enable horizontal mangoapp bar #export STEAM_MANGOAPP_HORIZONTAL_SUPPORTED=1 # Scaling support export STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT=1 # Color management support export STEAM_GAMESCOPE_COLOR_MANAGED=1 export STEAM_GAMESCOPE_VIRTUAL_WHITE=1 # Fix intel color corruption. might come with some performance degradation #export INTEL_DEBUG=norbc #export mesa_glthread=true # Set input method modules for Qt/GTK that will show the Steam keyboard #export QT_IM_MODULE=steam #export GTK_IM_MODULE=Steam # Enable volume key management via steam for this session #export STEAM_ENABLE_VOLUME_HANDLER=1 # Disable automatic audio device switching in steam, handled by wireplumber export STEAM_DISABLE_AUDIO_DEVICE_SWITCHING=1 # Force Qt applications to run under xwayland export QT_QPA_PLATFORM=xcb # Don't wait for buffers to idle on the client side before sending them export vk_xwayland_wait_ready=false # Some environment variables by default (taken from Deck session) export SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS=0 # nv12 color fix export GAMESCOPE_NV12_COLORSPACE=k_EStreamColorspace_BT601 # To expose vram info from radv export WINEDLLOVERRIDES=dxgi=n # radv stuff? requires more configuration? #export STEAM_USE_DYNAMIC_VRS=1 # Workaround older versions of vkd3d-proton #export VKD3D_SWAPCHAIN_LATENCY_FRAMES=3 # Have SteamRT's xdg-open send http:// and https:// URLs to Steam #export SRT_URLOPEN_PREFER_STEAM=1 # To play nice with the short term callback-based limiter for now export GAMESCOPE_LIMITER_FILE=$(mktemp /tmp/gamescope-limiter.XXXXXXXX) # We have the Mesa integration for the fifo-based dynamic fps-limiter #export STEAM_GAMESCOPE_DYNAMIC_FPSLIMITER=1 # We have NIS support export STEAM_GAMESCOPE_NIS_SUPPORTED=1 # Let steam know it can unmount drives without superuser privileges #export STEAM_ALLOW_DRIVE_UNMOUNT=1 ### Tearing ### # Temporary crutch until dummy plane interactions / etc are figured out #export GAMESCOPE_DISABLE_ASYNC_FLIPS=1 # Support for gamescope tearing with GAMESCOPE_ALLOW_TEARING atom export GAMESCOPE_ALLOW_TEARING=1 export STEAM_GAMESCOPE_HAS_TEARING_SUPPORT=1 # Enable tearing controls in steam export STEAM_GAMESCOPE_TEARING_SUPPORTED=1 # nvidia low latency export __GL_MaxFramesAllowed=1 ```

← Back to top

Github Gamescope Discussions

Wayland backend: add host-to-client clipboard sync


Glossary

Core Terms

Gamescope Terms

Performance Terms


← Back to top

Resources:

Official Documentation