Arch Linux + Hyprland on GPD Win4 with iGPU + eGPU

Document Objective
Written as a reference for anyone using both an iGPU and an eGPU and wanting to maximize their capability while switching between them frequently. The concrete use case is specific to the GPD Win4 — two AMD GPUs on Arch Linux + hyprland.lua, offloading rendering onto the eGPU — but the approach generalizes: other hardware stacks and configurations, such as two NVIDIA GPUs, follow the same principles for consideration.
- Configuration guidance (iGPU only / with eGPU) — Hardware Stack
- Self-check commands to verify current state — Verification
- All relevant commands in one place — distributed across sections
- Tweak log: what’s applied, what’s proposed — Current State / Proposed
- Searchable wiki for later reference — the whole doc
Hardware Stack
- OS: Arch Linux + Hyprland 0.56.1 (Lua config officially supported)
- 2 AMD GPUs: iGPU (Radeon 780M) + eGPU (RX 7600M XT, via OCULINK)
- BIOS: UMA frame buffer set to 8G (iGPU VRAM; Advanced > CBS > NBIO > GFX Configuration)
1. iGPU Only
- Both built-in and external monitors work
- Disabling any monitor works
- Single iGPU is primary
2. With eGPU
flowchart LR
subgraph GPD["GPD Win4"]
igpu["iGPU<br/>Radeon 780M"]
end
egpu["eGPU<br/>RX 7600M XT"]
monitor["External Monitor"]
igpu -- "OCULINK (PCIe)" --> egpu
egpu -- "HDMI" --> monitor
Expected:
- Turn on eGPU before powering on the machine
- Both monitors work
- iGPU is primary
- Run apps on the eGPU via command line
Verification (quick self-check, run first)
lspci | grep -i 7600M # eGPU detected? bus id?
# stable offload check (derived id, not DRI_PRIME=1 which is index-relative)
DRI_PRIME="pci-0000_$(lspci | awk '/7600M/{print $1}' | tr '.:' '__')" glxinfo | grep -i renderer # -> RX 7600M XT
egpu glxinfo | grep -i renderer # same via wrapper
vulkaninfo --summary # both GPUs visible (verified; --list-devices is not a valid flag in this vulkaninfo)
hyprctl monitors all # active monitors per output
readlink -f ~/.config/hypr/cards/{egpu,igpu} # symlink targets
Is the eGPU taking workload? While a game runs via egpu <game>, watch GPU busy %:
watch -n 1 'for g in egpu igpu; do n=/sys/class/drm/$(basename "$(readlink -f ~/.config/hypr/cards/$g)"); echo "$g: $(cat "$n/device/gpu_busy_percent")%"; done'
If the eGPU line is high (60%+ in a real game) while iGPU stays low, the eGPU is doing the work. Richer view (already installed): amdgpu_top or amdgpu_top --smi.
Example — a lightweight Steam game running on the eGPU (eGPU busy ~25%, below the 60% bar):

Verified Hardware Mapping (Aug 2026, this boot)
| GPU | PCI id | vendor:device | /dev/dri | outputs |
|---|---|---|---|---|
| eGPU RX 7600M XT (Navi 33) | 03:00.0 | 1002:7480 | card1 / renderD128 | HDMI-A-1, DP-1, DP-2 |
| iGPU Radeon 780M (Phoenix) | 66:00.0 | 1002:15bf | card2 / renderD129 | eDP-1 (built-in), DP-3..8 |
eDP-1(GPD G1618-04) on iGPU;HDMI-A-1(AOC AG322QWG3R3) on eGPU — both already work with the eGPU attached.
IDs are per-boot snapshots, not promises.
cardN/renderD*and even PCI bus IDs can change randomly between reboots. Always resolve dynamically (lspci,/dev/dri/by-path/) or use the symlinks~/.config/hypr/cards/{egpu,igpu}.Verified evidence: in 3 of 8 boot logs the iGPU appeared at
63:00.0(eGPU absent) instead of66:00.0. eGPU-present boots have been consistently03:00.0/66:00.0.
Regenerate the table (verified):
for c in /dev/dri/by-path/pci-*-card; do
pci=$(basename "${c%-card}"); pci=${pci#pci-0000:}
card=$(basename "$(readlink -f "$c")")
render=$(basename "$(readlink -f "${c%-card}-render")")
dev=$(lspci -nn -s "0000:$pci")
vid=$(grep -oE '\[[0-9a-f]{4}:[0-9a-f]{4}\]' <<<"$dev" | tr -d '[]')
name=$(sed -E 's/^[0-9:.]+ //; s/^VGA compatible controller \[0300\]: //; s/ \[[0-9a-f]{4}:[0-9a-f]{4}\] \(rev [0-9a-f]+\)$//' <<<"$dev")
outs=$(ls -d /sys/class/drm/${card}-* 2>/dev/null | grep -v Writeback | sed "s|.*/${card}-||" | paste -sd,)
printf "%s | %s | %s | %s / %s | %s\n" "$name" "$pci" "$vid" "$card" "$render" "$outs"
done
Current State
~/.bashrc has an egpu() launcher — GL/EGL-only offload, resolving the eGPU’s PCI id at call time (no hardcoded id):
# egpu
# Custom eGPU launcher shortcut
egpu() {
if [ -z "$1" ]; then
echo "Usage: egpu <command>"
echo "Example: egpu steam"
return 1
fi
local id
id=$(lspci | awk '/7600M/ {print $1}')
if [ -z "$id" ]; then
echo "eGPU (RX 7600M XT) not detected" >&2
return 1
fi
env DRI_PRIME="pci-0000_${id//[.:]/_}" "$@"
}
Verified: egpu glxinfo -> RX 7600M XT; plain glxinfo -> 780M. A stale hardcoded pci-0000_03_00_0 was replaced by this dynamic form — lspci emits 03:00.0 with a dot, so /7600M/ {print $1} output is rewritten with ${id//[.:]/_} (03:00.0 → 03_00_0). One lspci call per run; clear “not detected” error if the eGPU is absent.
How it works: DRI_PRIME=pci-0000_<bus>_<dev>_<func> renders GL/EGL on that GPU only; scanout is Hyprland’s job. Per-command only — desktop stays on iGPU.
Not covered:
- Vulkan — ignores
DRI_PRIME; games enumerate both GPUs themselves (see Proposed #1). - Display driving — compositor decides scanout; already works (see mapping).
Stable DRM Symlinks (Set Up)
mkdir -p ~/.config/hypr/cards
ln -s /dev/dri/by-path/pci-0000:03:00.0-card ~/.config/hypr/cards/egpu
ln -s /dev/dri/by-path/pci-0000:66:00.0-card ~/.config/hypr/cards/igpu
by-path is PCI-stable, so the symlinks survive cardN shifts. Verified: egpu->card1, igpu->card2.
Caveat:
igpu(pointing at66:00.0) dangles in eGPU-absent boots — the iGPU then sits at63:00.0. Harmless as long as nothing references it.
eGPU Display Ownership (Optional)
Env var for this stack is AQ_DRM_DEVICES (WLR_DRM_DEVICES is gone in Aquamarine 0.14+). In ~/.bash_profile, igpu first = primary:
export AQ_DRM_DEVICES="$HOME/.config/hypr/cards/igpu:$HOME/.config/hypr/cards/egpu"
- Set before Hyprland starts (login shell); effective after logout/login
- Currently unset and monitors already work — only makes ownership deterministic
Proposed egpu() Improvements (Not Applied)
-
Vulkan offload —
DRI_PRIMEis GL/EGL-only; Vulkan apps ignore it and enumerate both GPUs themselves (usually offering their own in-game adapter picker). Two options:MESA_VK_DEVICE_SELECT=1002:7480(layer installed). Verified: reorders the eGPU to GPU0 so apps that default to the first adapter pick it up, but it does not hide the other GPU. Name-picking apps (that list and let you choose) are unaffected.- Recommendation: skip it. Most games with a visible adapter picker already let you choose the eGPU;
MESA_VK_DEVICE_SELECTonly helps apps that blindly default to adapter 0 without letting you change it. Only add it if a specific game mis-defaults to the iGPU and has no in-game picker.
Open Questions (decided)
- Is
03:00.0stable? Stable in all eGPU-present boots (5/5); iGPU moves to63:00.0when eGPU absent. Theegpu()launcher now resolves dynamically at call time, so this no longer matters for offload. MESA_VK_DEVICE_SELECTinegpu()? Optional nice-to-have — only helps Vulkan games without an in-game adapter picker. Not required; skip unless a specific game mis-defaults.- Rename
egpu? Cosmetic only; keeping it is fine. - Enable
AQ_DRM_DEVICES? No — everything works, and theigpusymlink dangles in eGPU-absent boots, so enabling it adds risk with no current benefit. Minimalism wins.
btw, i use arch