Skip to content

Comprehensive guide to Gamescope covering display stack, TTY modes, frame generation, and performance optimization

License

Notifications You must be signed in to change notification settings

dsrtfbbg379/gamescope-deep-dive

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

Github Gamescope Discussions

Glossary

Resources


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:

  • Intercepts vkQueuePresentKHR calls
  • Reads frames from the game
  • Uses Lossless.dll algorithm to generate interpolated frames
  • Presents multiple frames for each game frame (based on your multiplier)

Gamescope WSI layer:

  • Intercepts swapchain creation
  • Redirects presentation to gamescope's internal compositor instead of directly to your desktop
  • Enables gamescope to use async Vulkan compute for compositing, so you see frames quickly even if the GPU is busy with the next game frame

Mesa RADV driver:

  • Translates Vulkan commands to AMD GPU instructions
  • Handles MESA_VK_WSI_PRESENT_MODE (immediate/mailbox/fifo)

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:

  • Gamescope uses Wayland protocols to present to KDE Plasma
  • Creates a proper Wayland swapchain

With --backend sdl:

  • Gamescope uses SDL2 library for presentation
  • May bypass some Wayland overhead

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:

  • Gamescope can use DRM/KMS to directly flip game frames to the screen, removing compositor overhead
  • No desktop compositor (KDE Plasma) in between
  • Nested sessions incur performance overhead from running a compositor within a compositor
  • --immediate-flips actually WORKS in embedded mode

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:

  • ✅ Renders frames in GPU memory
  • ✅ Runs Vulkan/compositing as normal
  • ✅ Can encode frames for streaming (Moonlight, Steam Link, Sunshine)
  • ❌ Does NOT output to any TTY/monitor
  • ❌ Does NOT create a framebuffer you can see
# 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:

  • Keeps frames in GPU buffers
  • Optionally encodes them for streaming
  • Does NOT use DRM/KMS to display on a monitor

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:

  • If gamescope on TTY1 grabs DRM → TTY2 can't display anything
  • If KDE on TTY2 grabs DRM → TTY1's gamescope can't use embedded mode

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!)
  • NI = 0: Normal priority (CAP_SYS_NICE not working or not set)
  • NI = -10 or lower: 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:

  • CLS = TS: Normal time-sharing scheduler
  • CLS = FF: FIFO realtime scheduler (best for gamescope)
  • NI: Nice value (lower = higher priority)

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:

  • High framerate targets (120+ FPS)
  • VRR/Adaptive sync scenarios
  • CPU-bound games
  • Frame generation (like lsfg-vk!) - you want consistent scheduling

It matters less for:

  • 30-60 FPS targets
  • GPU-bound games
  • Single-threaded games

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:

  • gamescope-wl: Main compositor thread handling frame composition
  • gamescope: Core rendering and frame delivery
  • gamescope-pw: Audio synchronization (critical for AV sync)
  • gamescope-xwm: Window management (input handling)
  • gamescope-wait: Frame timing and vsync coordination

Threads that don't need high priority:

  • gamescope-eis: Input emulation (best effort is fine)
  • gamescope_img: Image processing (can be delayed)

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:

  • ✅ Gamescope process
  • ✅ Everything gamescope launches (inherited down the chain)

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]
  • Everything before -- → Gamescope configuration
  • Everything after -- → What gamescope should launch

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:

  • env is a command that runs another command with a modified environment
  • When used with no arguments like this, it just executes the command that follows
  • It's basically a no-op here (could be removed)

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:

  • Old habit from when people used env to set variables: env VAR=value command
  • Copy-pasted from example that used it differently
  • Harmless but unnecessary in your case

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:

  • ✅ Has variables from Steam (PROTON_LOG, SteamDeck, etc.)
  • ✅ Has variables from gamescope (DISPLAY, WAYLAND_DISPLAY if nested)
  • ✅ Adds new variables (DISABLE_VK_LAYER_*, LSFGVK_PROFILE)

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

  • ✅ Steam (child process) gets the variable
  • ✅ Games launched from Steam get the variable
  • ❌ If you close the terminal and open a new one, variable is GONE
  • ❌ Other terminals don't have the variable

Scenario B: Export in Shell RC File (Persistent)

Add to ~/.bashrc:

export LSFGVK_PROFILE=test

Persistence: Every new bash shell gets it

  • ✅ Every terminal you open has the variable
  • ✅ Logins on any TTY get the variable
  • ✅ Survives reboots (read from ~/.bashrc each time)

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

  • ✅ TTY logins get the variable
  • ✅ SSH sessions get the variable
  • ✅ Desktop Mode login gets it (if using bash)

Scenario D: Systemd Service Environment (Permanent)

For gamescope-session or custom systemd services:

[Service]
Environment="LSFGVK_PROFILE=test"

Persistence: Only for that specific service

  • ✅ Always set when service starts
  • ✅ Survives reboots
  • ❌ Only applies to that service's processes

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

  • What it does: Enables FSR/NIS upscaling controls in Steam's UI
  • Effect: Shows the "Performance" menu in Gaming Mode with FSR slider
  • Does it affect games? NO - it just shows/hides the UI controls
  • Do you need it? Only if you want Steam's UI to show upscaling options

STEAM_GAMESCOPE_COLOR_MANAGED=1

  • What it does: Enables HDR/color management controls in Steam's UI
  • Effect: Shows color profile options in Steam settings
  • Does it affect games? NO - just UI controls
  • Do you need it? Only if using HDR or color profiles

STEAM_MULTIPLE_XWAYLANDS=1

  • What it does: Tells Steam to support per-game Xwayland isolation
  • Effect: Each game gets its own Xwayland server (better stability)
  • Does it affect games? YES - improves game isolation, prevents crashes
  • Do you need it? YES - this is actually useful for stability

STEAM_GAMESCOPE_VRR_SUPPORTED=1 (commented out in your script)

  • What it does: Shows VRR (Variable Refresh Rate) toggle in Steam UI
  • Do you need it? Only if you have a VRR/FreeSync/G-Sync monitor

Variables That Affect Gamescope/Games

ENABLE_GAMESCOPE_WSI=1

  • What it does: Enables gamescope's Wayland/Vulkan WSI layer
  • Effect: Allows gamescope to intercept Vulkan swapchains
  • Does it affect games? YES - critical for gamescope to work
  • Do you need it? YES - you already discovered this!

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):

  • You're launching gamescope manually with your own flags
  • You're not using Steam's UI controls
  • You DON'T need STEAM_GAMESCOPE_FANCY_SCALING_SUPPORT=1

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:

  • 1900s: Electro-mechanical teletypewriters for stock tickers
  • 1960s-70s: Mainframe terminals - physical keyboards/screens connected via serial cables
  • 1970s: Unix introduced the /dev/tty abstraction to represent these devices
  • Present: Virtual TTYs - software emulation of physical terminals

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:

  • /dev/tty1 - Usually graphical login (GDM/SDDM)
  • /dev/tty2 - KDE Plasma Wayland session (your Desktop Mode)
  • /dev/tty3-tty6 - Text-only login prompts
  • /dev/tty7+ - Additional sessions if needed

How to access:

  • Ctrl+Alt+F1 → TTY1 (login screen)
  • Ctrl+Alt+F2 → TTY2 (your graphical desktop)
  • Ctrl+Alt+F3 → TTY3 (text console)
  • etc.

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:

  • Terminal emulator windows (Konsole, GNOME Terminal)
  • SSH sessions
  • tmux / screen virtual terminals
  • Any software-based terminal

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

3. Special TTY Devices

  • /dev/tty - Always refers to YOUR current TTY (whatever you're on)
  • /dev/tty0 - Currently active virtual console
  • /dev/console - System console (kernel messages, boot logs)
  • /dev/ttyS0, /dev/ttyUSB0 - Serial/USB TTYs (physical ports)

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

  • Network stack - WiFi, Ethernet, routing tables
  • systemd services - NetworkManager, PulseAudio/PipeWire, bluetooth, etc.
  • Hardware drivers - GPU, CPU, storage
  • Mounted filesystems - /home, /, all your files
  • Running daemons - SSH server, database servers, web servers

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)

  • Processes started with & (background jobs) continue running
  • Systemd user services (systemctl --user)
  • Session-wide services (PipeWire audio, if configured)

3. /tmp and Shared Memory

  • /tmp is the same across all TTYs
  • /dev/shm (shared memory) is global
  • Unix sockets can be accessed from any TTY

❌ 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:

  • LSFGVK_PROFILE=test set on TTY2 does NOT appear on TTY3
  • PATH, HOME, USER are usually the same (from /etc/profile)
  • Custom exports in ~/.bashrc only apply to that shell

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:

  • System-wide mode: Audio server runs globally, all TTYs can access it
  • Per-session mode (default on Steam Deck): Each login session gets its own audio

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:

  • When on TTY2 (KDE), your mouse/keyboard control KDE
  • Switch to TTY3, KDE loses input - TTY3 gets exclusive access
  • Gamepads connected via USB stay connected system-wide, but events go to active TTY

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:

  • ✅ Gamescope's low-latency DRM output (on TTY1)
  • ✅ Steam running from TTY2 (easier to debug/restart)
  • ✅ The display shows gamescope's output (from TTY1)

← 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:

  • /tmp/.X11-unix/X0 (Unix domain socket) - accessible from any TTY
  • Or TCP (localhost:6000) - also accessible anywhere

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:

  • Don't switch TTYs while a game is loading
  • Exit games cleanly before switching back to desktop
  • Use gamescopectl shutdown to close gamescope cleanly from desktop

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:

  • Fullscreen windows in Wayland/X11 typically use compositor redirection
  • The desktop compositor (KDE Plasma) enforces its own presentation timing
  • This adds a VSync layer that prevents immediate tearing flips

-f / --fullscreen

Makes gamescope's window fullscreen on your desktop

  • In nested mode (Desktop Mode), this tells your desktop compositor (KDE Plasma/Wayland) to make the gamescope window take over the full screen
  • When using -f, tearing is disabled and VSync is enforced
  • Think of it as "windowed fullscreen" mode

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

  • Enables immediate flips in embedded mode, which may result in tearing
  • Only works in embedded/DRM mode, NOT nested mode
  • Bypasses VSync to show frames immediately as they're ready
  • Does NOT work with -f because -f enforces VSync

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

  • Press Ctrl+Alt+F2 (usually Desktop Mode on Steam Deck)
  • Or Ctrl+Alt+F1 (login screen)

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):

  • Hold power button for 10+ seconds
  • Steam Deck will force shutdown
  • Your data is safe, but unsaved game progress will be lost

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:

  • Ctrl+Alt+F1 through F7 - Switch between TTYs
  • Ctrl+Alt+Backspace - Kill X server (some distros)
  • Alt+SysRq+K - Kill all on current console (if enabled)

4. Keep one TTY with a working shell:

  • Always have TTY3 logged in with a shell
  • From there you can kill stuck processes on other TTYs

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

  1. DRM already in use?
   # Check what's using DRM:
   lsof /dev/dri/card0
   
   # Kill conflicting process or switch to nested mode
  1. Missing dependencies?
   # Check gamescope can run:
   gamescope --help
   
   # Check for errors:
   gamescope -e -- vkcube
  1. 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
  • Best quality/performance balance
  • AMD's open-source upscaling
  • Recommended for most use cases

NIS (NVIDIA Image Scaling)

gamescope -w 1280 -h 800 -W 1920 -H 1080 -F nis -- game
  • NVIDIA's upscaling algorithm
  • Works on any GPU
  • Sharper than FSR, may show more artifacts

Linear (No Upscaling)

gamescope -w 1280 -h 800 -W 1920 -H 1080 -F linear -- game
  • Simple bilinear scaling
  • Fastest, but blurrier
  • Use when you don't want upscaling algorithms

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

  • Manual install for lsfg-vk pre build binaries can be found in
    https://github.com/PancakeTAS/lsfg-vk/releases

  • the code was recently updated to v2 and there have been some major changes
    Installation is quite straight‑forward

  • download the prebuild lsfg-vk 2.xx and extract

    • copy liblsfg-vk-layer.so to
      /home/deck/.local/lib/liblsfg-vk-layer.so
    • copy VkLayer_LSFGVK_frame_generation.json to
      /home/deck/.local/share/vulkan/implicit_layer.d/VkLayer_LSFGVK_frame_generation.json
      edit VkLayer_LSFGVK_frame_generation.json and change line 7 to correct directory path of liblsfg-vk-layer.so
    • copy lsfg-vk-ui to
      /home/deck/.local/bin/lsfg-vk-ui
    • copy lsfg-vk-cli to
      /home/deck/.local/bin/lsfg-vk-cli
  • restart terminal and run ./lsfg-vk-ui

  • adjust settings and point to the correct path of Lossless.dll

  • your primary adjustment is Multiplier, keep between 2-3

  • create a new profile in lsfg-vk-ui adjust settings and close and remember the name

  • on your steam launch options add add the profile name to LSFGVK_PROFILE=name of profile

  • start the game on steam and you should see a increase in fps

*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:

  • adaptive_sync: Whether or not adaptive sync is enabled if available
  • adaptive_sync_ignore_overlay: Whether or not to ignore overlay planes for pushing commits with adaptive sync
  • adaptive_sync_overlay_cycles: Number of vblank cycles to ignore overlay repaints before forcing a commit with adaptive sync

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:

  • MESA_VK_WSI_PRESENT_MODE=immediate - Forces immediate presentation (lowest latency)
  • --immediate-flips - Reduces latency in gamescope
  • MESA_VK_WSI_PRESENT_MODE=mailbox - Triple buffering, smooth without tearing
  • vk_xwayland_wait_ready=false - Reduces wait time
  • ENABLE_GAMESCOPE_WSI=0 - This disables gamescope's WSI layer and uses SDL backend instead - may improve performance in some games

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%
  • Use -r 60 since 20 FPS × 3 = 60 FPS target
  • Both game output and gamescope presentation are immediate
  • Expect: Lowest input lag, possible tearing

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%
  • Smooth game→gamescope handoff, fast gamescope→display
  • Expect: Good balance of smoothness and responsiveness

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%
  • Immediate outside gamescope, relaxed inside
  • -f enables fullscreen (removes --immediate-flips)
  • Expect: Smoothest framing, slight latency increase

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!!
# 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

Issues with the WSI layer, hdr, and steam overlay / input failing to hook

Stuttery input, but perfect frametiming

gamescope having CAP_SYS_NICE break the steam overlay in nested mode

Huge lag spikes while moving my cursor in Path of Exile 2


Glossary

Core Terms

  • DRM/KMS - Direct Rendering Manager / Kernel Mode Setting - Linux subsystem for controlling display hardware
  • WSI - Window System Integration - Layer that connects Vulkan to display systems (Wayland/X11)
  • Xwayland - X11 compatibility layer that runs on top of Wayland
  • PTY - Pseudo-TTY - Virtual terminal used by terminal emulators
  • TTY - TeleTypewriter - Virtual console you access with Ctrl+Alt+F1-F12

Gamescope Terms

  • Nested Mode - Gamescope runs as a window inside your desktop (uses --backend wayland/sdl)
  • Embedded Mode - Gamescope takes direct control of display hardware (uses -e or --backend drm)
  • Microcompositor - A lightweight compositor running inside another compositor

Performance Terms

  • CAP_SYS_NICE - Linux capability allowing processes to set high scheduling priority
  • VSync - Vertical Synchronization - Syncs frame delivery to display refresh rate
  • Immediate Flips - Present frames immediately without waiting for VSync
  • Frame Pacing - Consistent delivery of frames at regular intervals

← Back to top

Resources:

Official Documentation

Credits & Acknowledgments


About

Comprehensive guide to Gamescope covering display stack, TTY modes, frame generation, and performance optimization

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published