A small, upstream-friendly patch for ValveSoftware/gamescope that lets you choose the DRM/KMS scanout (display) device independently of the Vulkan render device. This makes gamescope usable on hybrid / eGPU / muxless / split-SoC systems where the GPU you want to render on is not the GPU the monitor is attached to.
- Branch:
feature/prefer-drm - Patch file:
0001-prefer-drm.patch - Files touched:
src/main.cpp,src/main.hpp,src/backends.h,src/Backends/DRMBackend.cpp(+126 / −10) - Related upstream issue: #2070 (and #158, #1469, #1643)
gamescope's DRM backend derives the KMS (scanout) device from the selected Vulkan
physical device. In init_drm() it calls vulkan_primary_dev_id(), looks the device
up with drmGetDeviceFromDevId(), and opens that GPU's DRM_NODE_PRIMARY.
As a result, --prefer-vk-device effectively picks both the render GPU and the
scanout GPU. On a machine where the fast GPU is headless (no connectors) — a render-only
accelerator, an eGPU in some muxless modes, or an ARM SoC whose GPU and display controller
are separate DRM devices — selecting that GPU for rendering also forces scanout onto it.
It has no connected outputs, so you get the classic:
drm: opening DRM node '/dev/dri/card1'
drm: '/dev/dri/card1' is not a KMS device
vulkan_make_output failed
…i.e. a failed start / black screen. There was previously no way to say “render on GPU A, scan out on GPU B”.
A new option:
--prefer-drm <device>
It selects the DRM/KMS device used for scanout/output independently of the Vulkan device. The three selectors now compose cleanly:
| Option | Chooses |
|---|---|
--prefer-vk-device 1002:7360 |
the Vulkan render/compositor GPU |
--prefer-drm /dev/dri/by-path/... |
the DRM/KMS scanout device |
-O, --prefer-output HDMI-A-1 |
the connector within the chosen DRM device |
gamescope \
--prefer-drm /dev/dri/by-path/pci-0000:00:02.0-card \
--prefer-vk-device 1002:7360 \
-O HDMI-A-2 \
-- steam -gamepaduiAccepted forms for --prefer-drm:
- absolute primary node —
/dev/dri/card0 - short form —
card0(expanded to/dev/dri/card0) by-pathsymlink —/dev/dri/by-path/pci-0000:00:02.0-card(resolved viarealpath)
- Default behaviour is unchanged. If neither
--prefer-drmnor the env var is set, the original Vulkan-device-derived path is used verbatim. - Env fallback. When the flag is absent, the value is also read from
$GAMESCOPE_PREFER_DRM, so a session manager can configure a split render/scanout setup without per-game launch options (the flag takes precedence). - The value is resolved and validated up-front in
main()(DRMBackendCheckPreferredDevice()), before the backend and the heavy Vulkan/session init are brought up, so bad input fails fast with a clear message rather than tearing down a half-initialised backend. - Validation does not
open()the node. Under seatd/logind the compositor usually cannot open a primary node directly (that is the session manager's job), so a plainopen()would spuriously fail withEACCESon perfectly valid setups. Instead the device number is inspected (stat()): DRM primary minors are0–63, render nodes are128+, so render nodes are rejected. The authoritativedrmIsKMS()check still runs later, once the session has opened the fd.
drm: --prefer-drm: using DRM scanout device '/dev/dri/card0'
drm: Connectors:
drm: HDMI-A-2 (connected)
drm: DP-1 (disconnected)
drm: preferred output (--prefer-output): HDMI-A-2
drm: selecting connector HDMI-A-2
drm: selecting mode 1920x1200@60Hz
For a step-by-step, distribution-specific build guide, see
BUILD.md(CachyOS/Arch, Bazzite, generic). Build the patch against the same gamescope version already installed on the system — the WSI layer must match.
git clone --recursive https://github.com/ValveSoftware/gamescope
cd gamescope
git checkout <your installed version, e.g. 3.16.23> # match your system!
git apply /path/to/0001-prefer-drm.patch # or: git am
meson setup build
ninja -C build-
CachyOS / Arch (mutable,
pacman -U):cachyos/— a PKGBUILD- prebuilt packages (see Releases). CachyOS ships gamescope
3.16.24-1(extra) and3.16.20/gamescope-git(cachyosrepo) plus thegamescope-session-cachyosBig Picture session.
Quick start — run the UI on the external card (full step-by-step, incl. how to find your own device IDs, in
cachyos/README.md):# 1. install the patched gamescope matching your version (from Releases) sudo pacman -U ./gamescope-3.16.23-1-x86_64.pkg.tar.zst # 2. two GLOBAL env vars in ~/.config/environment.d/ — no session edits: mkdir -p ~/.config/environment.d # render UI + games on the external/fast GPU (vendor:device, e.g. AMD BC-160): echo 'MESA_VK_DEVICE_SELECT=1002:7360' \ > ~/.config/environment.d/05-gamescope-render-gpu.conf # output to the monitor through the display GPU (by-path of the connected card): echo 'GAMESCOPE_PREFER_DRM=/dev/dri/by-path/pci-0000:00:02.0-card' \ > ~/.config/environment.d/10-gamescope-prefer-drm.conf # 3. reboot — Game Mode picks them up
MESA_VK_DEVICE_SELECT= which GPU renders;GAMESCOPE_PREFER_DRM= which GPU outputs to the monitor. Edit both to your hardware (lspci -nn,ls -l /dev/dri/by-path/). - prebuilt packages (see Releases). CachyOS ships gamescope
-
Bazzite (immutable bootc):
bazzite/— the patch goes upstream;export-gpuis extended to write$GAMESCOPE_PREFER_DRM. A runtimerpm-ostree override replacedoes not apply on container-native images, so use a custom image or the userspaceGAMESCOPE_BINhook.
On an Intel HD 530 iGPU (card0, HDMI-A-2) + AMD BC-160 (1002:7360, headless) machine
running Bazzite / Steam Game Mode:
--helplists the new option.- Intel scanout + Intel render and Intel scanout + BC-160 render both bring the
output up on
HDMI-A-2at1920x1200@60, compositing onRADV NAVI12(BC-160), with noatomic/addfb2/ modifier / dmabuf errors. - Without the flag, gamescope still selects the BC-160 primary node and fails with
is not a KMS device— the exact black screen this fixes. - Invalid path and render-node input fail fast with a clear error and a clean exit.
- Real Steam Game Mode: with the patched binary wired in via
GAMESCOPE_BIN, Steam Big Picture comes up on the Intel-driven monitor while the compositor renders on the BC-160.
- This patch covers the compositor render device + scanout device split. It does not
force games onto a particular GPU — a game launched inside gamescope still selects its own
Vulkan device. Steering games onto the render GPU seamlessly (without per-game launch
options) is a separate follow-up using child-process environment
(
MESA_VK_DEVICE_SELECT/DRI_PRIME). - Cross-GPU scanout relies on the destination KMS device accepting the render GPU's buffers/modifiers. It worked without modifier fallbacks on the tested hardware; a linear/modifier-less fallback is a possible follow-up for stricter KMS drivers.
- The
minor < 64primary-node check mirrors libdrm's internal node-numbering convention.
--prefer-drm pci-0000:03:00.0PCI-address shorthand.- Optional linear / modifier-less framebuffer fallback for cross-GPU scanout.
- Seamless per-child game GPU selection on the render GPU.
Session integration is done: $GAMESCOPE_PREFER_DRM is read as a fallback, and
bazzite/ proposes the matching export-gpu change for Bazzite Game Mode.
DRM-бэкенд gamescope берёт KMS-устройство (для вывода) из выбранного Vulkan-устройства.
В init_drm() вызывается vulkan_primary_dev_id(), устройство ищется через
drmGetDeviceFromDevId(), и открывается его DRM_NODE_PRIMARY.
Из-за этого --prefer-vk-device фактически выбирает и GPU для рендера, и GPU для
вывода. На системе, где быстрый GPU безмониторный (нет коннекторов) — render-only
ускоритель, eGPU в некоторых muxless-режимах, или ARM-SoC, где GPU и контроллер дисплея —
разные DRM-устройства — выбор такого GPU для рендера принудительно отправляет на него же и
scanout. Подключённых выходов у него нет, и получается классическое:
drm: opening DRM node '/dev/dri/card1'
drm: '/dev/dri/card1' is not a KMS device
vulkan_make_output failed
…то есть провал старта / чёрный экран. Раньше не было способа сказать «рендерить на GPU A, выводить через GPU B».
Новый параметр:
--prefer-drm <device>
Он задаёт DRM/KMS-устройство для scanout/вывода независимо от Vulkan-устройства. Теперь три селектора аккуратно дополняют друг друга:
| Параметр | Что выбирает |
|---|---|
--prefer-vk-device 1002:7360 |
Vulkan-GPU для рендера/компоновки |
--prefer-drm /dev/dri/by-path/... |
DRM/KMS-устройство для вывода |
-O, --prefer-output HDMI-A-1 |
коннектор внутри выбранного DRM-устройства |
gamescope \
--prefer-drm /dev/dri/by-path/pci-0000:00:02.0-card \
--prefer-vk-device 1002:7360 \
-O HDMI-A-2 \
-- steam -gamepaduiПоддерживаемые формы --prefer-drm:
- абсолютный primary node —
/dev/dri/card0 - короткая форма —
card0(раскрывается в/dev/dri/card0) by-path-симлинк —/dev/dri/by-path/pci-0000:00:02.0-card(резолвится черезrealpath)
- Поведение по умолчанию не меняется. Если не задан ни флаг, ни env-переменная, используется прежний путь (устройство из Vulkan-девайса), без изменений.
- Env-fallback. Если флаг не задан, значение берётся из
$GAMESCOPE_PREFER_DRM— чтобы менеджер сессии мог настроить split render/scanout без per-game опций (флаг имеет приоритет). - Значение резолвится и проверяется заранее, в
main()(DRMBackendCheckPreferredDevice()), до создания бэкенда и тяжёлой инициализации Vulkan/сессии — поэтому невалидный ввод завершается сразу с понятным сообщением, а не ломает наполовину инициализированный бэкенд. - Валидация не делает
open()узла. Под seatd/logind компоновщик обычно не может открыть primary node напрямую (это задача менеджера сессий), так что обычныйopen()ложно падал бы сEACCESна полностью корректных конфигурациях. Вместо этого проверяется номер устройства (stat()): у DRM primary node младший номер0–63, у render-узлов128+, поэтому render-узлы отвергаются. Авторитетная проверкаdrmIsKMS()по-прежнему выполняется позже, когда сессия откроет дескриптор.
drm: --prefer-drm: using DRM scanout device '/dev/dri/card0'
drm: Connectors:
drm: HDMI-A-2 (connected)
drm: DP-1 (disconnected)
drm: preferred output (--prefer-output): HDMI-A-2
drm: selecting connector HDMI-A-2
drm: selecting mode 1920x1200@60Hz
Пошаговое руководство по сборке для конкретных дистрибутивов —
BUILD.md(CachyOS/Arch, Bazzite, generic). Патч собирается на той же версии gamescope, что установлена в системе — WSI-слой должен совпадать.
git clone --recursive https://github.com/ValveSoftware/gamescope
cd gamescope
git checkout <ваша установленная версия, напр. 3.16.23> # совпадение обязательно!
git apply /path/to/0001-prefer-drm.patch # или: git am
meson setup build
ninja -C build-
CachyOS / Arch (mutable,
pacman -U):cachyos/— PKGBUILD + готовые пакеты в Releases. В CachyOS gamescope идёт изextra(3.16.24-1) иcachyos(3.16.20/gamescope-git), сессия —gamescope-session-cachyos.Быстрый старт — UI на внешней карте (полный пошаговый гайд с поиском своих device ID — в
cachyos/README.md):# 1. поставить патченный gamescope нужной версии (из Releases) sudo pacman -U ./gamescope-3.16.23-1-x86_64.pkg.tar.zst # 2. две ГЛОБАЛЬНЫЕ переменные в ~/.config/environment.d/ — без правки сессии: mkdir -p ~/.config/environment.d # рендер UI + игр на внешней/мощной GPU (vendor:device, напр. AMD BC-160): echo 'MESA_VK_DEVICE_SELECT=1002:7360' \ > ~/.config/environment.d/05-gamescope-render-gpu.conf # вывод на монитор через дисплейную GPU (by-path подключённой карты): echo 'GAMESCOPE_PREFER_DRM=/dev/dri/by-path/pci-0000:00:02.0-card' \ > ~/.config/environment.d/10-gamescope-prefer-drm.conf # 3. перезагрузка — Game Mode подхватит переменные
MESA_VK_DEVICE_SELECT= какая GPU рендерит;GAMESCOPE_PREFER_DRM= какая GPU выводит на монитор. Подставьте свои значения (lspci -nn,ls -l /dev/dri/by-path/). -
Bazzite (immutable bootc):
bazzite/— патч идёт в upstream;export-gpuрасширен и пишет$GAMESCOPE_PREFER_DRM. Runtimerpm-ostree override replaceна container-native образах не применяется — используйте кастомный образ или userspace-хукGAMESCOPE_BIN.
На машине с Intel HD 530 iGPU (card0, HDMI-A-2) + AMD BC-160 (1002:7360, безмониторный)
под Bazzite / Steam Game Mode:
--helpпоказывает новый параметр.- Intel scanout + Intel render и Intel scanout + BC-160 render оба поднимают вывод
на
HDMI-A-2в1920x1200@60, с композитингом наRADV NAVI12(BC-160), без ошибокatomic/addfb2/ modifier / dmabuf. - Без флага gamescope по-прежнему выбирает primary node BC-160 и падает с
is not a KMS device— ровно тот чёрный экран, который это и чинит. - Несуществующий путь и render-node завершаются сразу с понятной ошибкой и чистым выходом.
- Реальный Steam Game Mode: с патченным бинарём, подключённым через
GAMESCOPE_BIN, Steam Big Picture выводится на монитор от Intel, а компоновщик рендерит на BC-160.
- Патч покрывает разделение GPU рендера компоновщика + устройство вывода. Он не
форсирует игры на конкретный GPU — игра, запущенная внутри gamescope, всё ещё выбирает
свой Vulkan-девайс сама. Бесшовное направление игр на render-GPU (без per-game опций) —
отдельный follow-up через окружение дочерних процессов (
MESA_VK_DEVICE_SELECT/DRI_PRIME). - Межгпушный scanout зависит от того, принимает ли целевое KMS-устройство буферы/модификаторы render-GPU. На тестовом железе это сработало без modifier-фолбэков; linear/modifier-less фолбэк — возможный follow-up для более строгих KMS-драйверов.
- Проверка primary-узла по
minor < 64повторяет внутреннее соглашение нумерации узлов в libdrm.
- Сокращение
--prefer-drm pci-0000:03:00.0по PCI-адресу. - Опциональный linear / modifier-less фреймбуфер-фолбэк для межгпушного scanout.
- Бесшовный выбор GPU для дочерних игровых процессов на render-GPU.
Интеграция с сессией сделана: читается fallback $GAMESCOPE_PREFER_DRM, а в
bazzite/ — предложение по правке export-gpu для Bazzite Game Mode.