At a time where most of us are rooting for the year of the Linux desktop, I’m already rooting for the year of the hardware accelerated virtual Linux desktop. Cause, let’s face it, the virtualized Windows desktop has gotten a lot more TLC. With gamers moving over to Linux, let’s hope that’ll all change (next year).
Setting the scene
This guide assumes Arch with Hyprland on both the host and guest. I used archinstall with Profile type minimal. Systemd-boot (default), Pipewire for audio.
The choice of Hyprland was easy, it’s built from the ground up with the hardware accelerated use case in mind. The combination of Hyprland with UWSM is a first class experience. With just a couple of lines added to the end of .bash_profile you launch right into your Hyprland session, without a login screen. Something to keep in mind when setting up the guest.
if uwsm check may-start; then
exec uwsm start hyprland.desktop
fi
To keep the scope and size of this guide (somewhat) manageable, we won’t expand further into setting up Arch with Hyprland.
I’ll try to outline the minimum setup to get things going in the main flow of the guide. Feel free to expand the details for some side quests along the way.
After installing Hyprland on the host, ref detail to install virt-manager with its dependencies.
Pro tip, set up paru in the VMs. Makes trying out new stuff a breeze, with the focus remaining on the (package’s) functionality.
Virt-manager
$ sudo pacman -S qemu virt-manager virt-viewer dnsmasq vde2 bridge-utils openbsd-netcat ebtables iptables libguestfs
:: There are 3 providers available for qemu:
:: Repository extra
1) qemu-base 2) qemu-desktop 3) qemu-full
Enter a number (default=1): 3
warning: iptables-1:1.8.11-2 is up to date -- reinstalling
resolving dependencies...
looking for conflicting packages...
warning: removing 'iptables-1:1.8.11-2' from target list because it conflicts with 'iptables-nft-1:1.8.11-2'
:: iptables-nft-1:1.8.11-2 and iptables-1:1.8.11-2 are in conflict. Remove iptables? [y/N] y
...
Add your user to the libvirt user group.
$ sudo usermod -aG libvirt $USER
Verify
$ groups $USER
Add your user to /etc/libvirt/qemu.conf. Otherwise, QEMU will give a permission denied error when trying to access local drives. Search for the section below and update “username”.
$ sudo nvim /etc/libvirt/qemu.conf
# Some examples of valid values are:
#
# user = "qemu" # A user named "qemu"
# user = "+0" # Super user (uid=0)
# user = "100" # A user named "100" or a user with uid=100
#
user = "username"
$ sudo systemctl enable libvirtd
$ sudo systemctl status libvirtd
Reboot
$ virt-manager
Virtiofs
As said, I didn’t want to go too far into the weeds when it comes to setup, but I do want to share one more tidbit named virtiofs, which can be used to share a folder between host and guest. Sort of a bridge between both worlds. Just as with Docker, you want to separate your runtime processes from your state. I used it mainly to store my notes, progress, benchmark results, so when my VM went horrendously wrong (again), at least my research was preserved.
Create shared directory on host
$ mkdir -p ~/virt/share
Add virtiofs device
Virtiofs needs shared memory to work. This can be enabled in the VM’s hardware configuration window.
Virt-manager > VM > Show virtual hardware details > Memory > check Enable shared memory > Apply.
After that, click on Add Hardware (button at the bottom) and select Filesystem:
driver: virtiofs
source path: browse to /home/$USER/virt/share (replace $USER)
target path: share
The target path is a bit of a misnomer. It’s not actually a path, just an identifier that we use as a mount point in our guest file system. This will become clear when we mount the file system in the guest later.
Ref File Sharing with Qemu and Virt-Manager
Inside VM run:
$ mkdir ~/share
$ sudo mount -t virtiofs share "/home/$USER/share"
Create first script
$ mkdir -p ~/scripts/share
$ vi ~/scripts/share/mount.sh
#!/bin/bash
sudo mount -t virtiofs share "/home/$USER/share"
$ chmod u+x ~/scripts/share/mount.sh
$ ~/scripts/share/mount.sh
Optional check, installed by default:
$ sudo pacman -S qemu-guest-agent
Of course, we should be mindful of the security ramifications of these kinds of bridges. For my R&D rig, I did find it quite helpful. In fact, after a while, I found I couldn’t do much without mounting the share first.
Mount share on boot
Allow user to mount without password prompt. Execute on guest:
$ sudoedit /etc/sudoers
Add to the end of file, be sure to replace $USER
# User is allowed to mount share
$USER ALL=NOPASSWD: /usr/bin/mount
Execute the script when Hyprland starts
$ nvim .config/hypr/hyprland.conf
Add to the end of exec-once lines
exec-once = bash ~/scripts/share/mount.sh
Wrapping up, it pays to keep the VM setup similar to the host’s.
For example, the next section discusses glmark2, which I built from git source in the VM using paru.
To use the bench on the host, I only had to copy these preserving the absolute paths:
- /usr/bin/glmark2
- /usr/share/glmark2
Tooling
For the OG’s in the audience, Wayland uses EGL instead of GLX for rendering. So instead of glxinfo, you can use eglinfo to get information about the OpenGL rendering context.
$ paru -S mesa-utils
$ eglinfo -p wayland
I’ll be using the glmark2 benchmark throughout this guide, to compare different setups.
$ paru -Ss glmark
extra/glmark2 2023.01-2 [7.23 MiB 13.31 MiB]
An OpenGL 2.0 and ES 2.0 benchmark
aur/glmark2-git r971.af498f4-1 [+37 ~0.00]
An OpenGL 2.0 and ES 2.0 benchmark (X11, Wayland, DRM)
Looking at Pull requests · glmark2/glmark2 · GitHub, already quite some PRs have been added since last Release (2023).
$ paru -S glmark2-git
$ glmark2
A sneak preview of the output with the B50 on the host (1440p with no VFs).
glmark2 score 10031
=======================================================
glmark2 2023.01
=======================================================
OpenGL Information
GL_VENDOR: Intel
GL_RENDERER: Mesa Intel(R) Arc(tm) Pro B50 Graphics (BMG G21)
GL_VERSION: 4.6 (Compatibility Profile) Mesa 25.3.3-arch1.3
Surface Config: buf=32 r=8 g=8 b=8 a=8 depth=24 stencil=0 samples=0
Surface Size: 800x600 windowed
=======================================================
[build] use-vbo=false: FPS: 8680 FrameTime: 0.115 ms
[build] use-vbo=true: FPS: 12535 FrameTime: 0.080 ms
[texture] texture-filter=nearest: FPS: 12192 FrameTime: 0.082 ms
[texture] texture-filter=linear: FPS: 12110 FrameTime: 0.083 ms
[texture] texture-filter=mipmap: FPS: 12152 FrameTime: 0.082 ms
[shading] shading=gouraud: FPS: 12096 FrameTime: 0.083 ms
[shading] shading=blinn-phong-inf: FPS: 11975 FrameTime: 0.084 ms
[shading] shading=phong: FPS: 11867 FrameTime: 0.084 ms
[shading] shading=cel: FPS: 11678 FrameTime: 0.086 ms
[bump] bump-render=high-poly: FPS: 9801 FrameTime: 0.102 ms
[bump] bump-render=normals: FPS: 12618 FrameTime: 0.079 ms
[bump] bump-render=height: FPS: 12344 FrameTime: 0.081 ms
[effect2d] kernel=0,1,0;1,-4,1;0,1,0;: FPS: 10982 FrameTime: 0.091 ms
[effect2d] kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;: FPS: 10287 FrameTime: 0.097 ms
[pulsar] light=false:quads=5:texture=false: FPS: 12236 FrameTime: 0.082 ms
[desktop] blur-radius=5:effect=blur:passes=1:separable=true:windows=4: FPS: 7243 FrameTime: 0.138 ms
[desktop] effect=shadow:windows=4: FPS: 8850 FrameTime: 0.113 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 1548 FrameTime: 0.646 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=subdata: FPS: 1656 FrameTime: 0.604 ms
[buffer] columns=200:interleave=true:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 1919 FrameTime: 0.521 ms
[ideas] speed=duration: FPS: 6012 FrameTime: 0.166 ms
[jellyfish] <default>: FPS: 10336 FrameTime: 0.097 ms
[terrain] <default>: FPS: 2214 FrameTime: 0.452 ms
[shadow] <default>: FPS: 11699 FrameTime: 0.085 ms
[refract] <default>: FPS: 6212 FrameTime: 0.161 ms
[conditionals] fragment-steps=0:vertex-steps=0: FPS: 15229 FrameTime: 0.066 ms
[conditionals] fragment-steps=5:vertex-steps=0: FPS: 12264 FrameTime: 0.082 ms
[conditionals] fragment-steps=0:vertex-steps=5: FPS: 12092 FrameTime: 0.083 ms
[function] fragment-complexity=low:fragment-steps=5: FPS: 12075 FrameTime: 0.083 ms
[function] fragment-complexity=medium:fragment-steps=5: FPS: 12019 FrameTime: 0.083 ms
[loop] fragment-loop=false:fragment-steps=5:vertex-steps=5: FPS: 12100 FrameTime: 0.083 ms
[loop] fragment-steps=5:fragment-uniform=false:vertex-steps=5: FPS: 12088 FrameTime: 0.083 ms
[loop] fragment-steps=5:fragment-uniform=true:vertex-steps=5: FPS: 11974 FrameTime: 0.084 ms
=======================================================
glmark2 Score: 10031
=======================================================
In other words, best case scenario will be a glmark2 score of 10,000. Worth pointing out I’m on a 3rd gen ThreadRipper system, so with PCIe gen 4 instead of gen 5.
Fun fact, even though the rendering is done in a 800x600 window, the resolution of the screen does seem to impact the score.
The Phantom Menace
To counter with a worst case scenario, a VM with no hardware acceleration of any kind (virtio Video driver).
At this point the only intel package explicitly installed is:
$ sudo paru -S linux-firmware-intel
glmark2 score 322
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
=======================================================
glmark2 2023.01
=======================================================
OpenGL Information
GL_VENDOR: Mesa
GL_RENDERER: llvmpipe (LLVM 21.1.6, 256 bits)
GL_VERSION: 4.5 (Compatibility Profile) Mesa 25.3.4-arch1.1
Surface Config: buf=32 r=8 g=8 b=8 a=8 depth=24 stencil=0 samples=0
Surface Size: 800x600 windowed
=======================================================
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[build] use-vbo=false: FPS: 344 FrameTime: 2.913 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[build] use-vbo=true: FPS: 338 FrameTime: 2.961 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[texture] texture-filter=nearest: FPS: 545 FrameTime: 1.836 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[texture] texture-filter=linear: FPS: 572 FrameTime: 1.751 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[texture] texture-filter=mipmap: FPS: 532 FrameTime: 1.881 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[shading] shading=gouraud: FPS: 273 FrameTime: 3.675 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[shading] shading=blinn-phong-inf: FPS: 268 FrameTime: 3.741 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[shading] shading=phong: FPS: 250 FrameTime: 4.001 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[shading] shading=cel: FPS: 247 FrameTime: 4.063 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[bump] bump-render=high-poly: FPS: 154 FrameTime: 6.533 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[bump] bump-render=normals: FPS: 548 FrameTime: 1.827 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[bump] bump-render=height: FPS: 532 FrameTime: 1.882 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[effect2d] kernel=0,1,0;1,-4,1;0,1,0;: FPS: 500 FrameTime: 2.000 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[effect2d] kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;: FPS: 426 FrameTime: 2.351 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[pulsar] light=false:quads=5:texture=false: FPS: 521 FrameTime: 1.923 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[desktop] blur-radius=5:effect=blur:passes=1:separable=true:windows=4: FPS: 87 FrameTime: 11.503 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[desktop] effect=shadow:windows=4: FPS: 102 FrameTime: 9.866 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 212 FrameTime: 4.719 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=subdata: FPS: 217 FrameTime: 4.627 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[buffer] columns=200:interleave=true:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 219 FrameTime: 4.578 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[ideas] speed=duration: FPS: 299 FrameTime: 3.349 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[jellyfish] <default>: FPS: 228 FrameTime: 4.402 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[terrain] <default>: FPS: 24 FrameTime: 42.958 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[shadow] <default>: FPS: 183 FrameTime: 5.488 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[refract] <default>: FPS: 40 FrameTime: 25.411 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[conditionals] fragment-steps=0:vertex-steps=0: FPS: 386 FrameTime: 2.591 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[conditionals] fragment-steps=5:vertex-steps=0: FPS: 385 FrameTime: 2.601 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[conditionals] fragment-steps=0:vertex-steps=5: FPS: 373 FrameTime: 2.687 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[function] fragment-complexity=low:fragment-steps=5: FPS: 386 FrameTime: 2.594 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[function] fragment-complexity=medium:fragment-steps=5: FPS: 374 FrameTime: 2.675 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[loop] fragment-loop=false:fragment-steps=5:vertex-steps=5: FPS: 383 FrameTime: 2.616 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[loop] fragment-steps=5:fragment-uniform=false:vertex-steps=5: FPS: 379 FrameTime: 2.645 ms
** GLX does not support GLX_EXT_swap_control or GLX_MESA_swap_control!
** Failed to set swap interval. Results may be bounded above by refresh rate.
[loop] fragment-steps=5:fragment-uniform=true:vertex-steps=5: FPS: 346 FrameTime: 2.892 ms
=======================================================
glmark2 Score: 322
=======================================================
A couple of takeaways:
- Falling back to the virtio implementation. Relying fully on emulation.
- GL_REMDERER is llvmpipe, which is software based.
- The same warnings are repeated with each update.
With mesa-utils we uncover another piece of the puzzle.
$ eglinfo -p wayland
libEGL warning: egl: failed to create dri2 screen
libEGL warning: egl: failed to create dri2 screen
...
This usually means a lack of drivers or otherwise faulty setup. Which makes sense given there’s no hardware to support hardware acceleration.
Attack of the clones
Host prep
$ sudo pacman -S intel-compute-runtime
If you’re not already there, this would be a good time to complete the first guide in this wiki.
Since last time, I bumped the driver version to the latest Pro driver, at the cost of Virtual Functions. The new driver allowed me to use another GPU for the host, without the B50’s fan ramping up to 100%. In other words, with the old driver, if the Physical Function was not used, the fan went bananas.
If you bump the driver be sure to set NUM_VFS to 2 in the script, as that is the new max for the latest driver (AToW).
As per Intel recommendation, best enable ASPM in BIOS if your system config can handle it (some hardware doesn’t like it).
Output with ASPM Disabled:
Device 0 [Battlemage G21 (Arc Pro B50)] PCIe GEN 4@ 8x RX: N/A TX: N/A
GPU 1200MHz MEM N/A MHz TEMP 51°C FAN 1243RPM POW 21 W
Output with ASPM set to Auto (I only have the two options in BIOS)
Device 0 [Battlemage G21 (Arc Pro B50)] PCIe GEN 4@ 8x RX: N/A TX: N/A
GPU 1200MHz MEM N/A MHz TEMP 40°C FAN 1033RPM POW 9 W
So it does impact the temps at idle quite a bit.
VF and VM for the win
Time to add our VF to our Linux VM. In virt-manager, Add hardware > PCI Host Device
Don’t select the PF ending in 0.
Select a VF eg 0000:63:00:1 Intel Corporation Battlemage G21 [Arc Pro B50] > Finish
Install required package for hardware acceleration (VA-API) and friends
$ sudo pacman -S libva-intel-driver intel-media-driver libva-utils
Virtualized Hyprland
There are a lot of landmines to trip over. In the end, it’s deceptively simple.
There are two settings, in the hyprland.conf, to rule them all.
$ nvim ~/.config/hypr/hyprland.conf
Monitor setup
monitor=, , auto, 1
#monitor=, 2560x1440, auto, 1
#monitor= Virtual-1, 2560x1440, auto, 1
The first line (active) is the default. Both the monitor name and resolution are left to be “best guessed”. With only one GPU and monitor this usually pans out well. In the virtualized env, you want to be as specific as possible. It doesn’t help that I like to keep the default virt-manager VGA device around for troubleshooting.
To list the monitors (and get their names) from within Hyprland (session needs to be active):
$ hyprctl monitors
To list inactive monitors as well
$ hyprctl monitors all
Provide a fully specified monitor definition, but leave it commented out for now. Leave the generic default as active.
Card setup
# See https://wiki.hyprland.org/Configuring/Environment-variables/
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
#env = WLR_DRM_DEVICES,/dev/dri/card1
#env = AQ_DRM_DEVICES,/dev/dri/card1
#env = AQ_DRM_DEVICES,/home/vit/.config/hypr/intel_gpu
First and foremost, the WLR_DRM_DEVICES syntax has been deprecated, use AQ_DRM_DEVICES instead.
You can get the current card identifiers by executing:
$ ls -lah /dev/dri/by-path
total 0
drwxr-xr-x 2 root root 120 Jan 31 11:26 .
drwxr-xr-x 3 root root 140 Jan 31 11:26 ..
lrwxrwxrwx 1 root root 8 Jan 31 11:26 pci-0000:00:01.0-card -> ../card1
lrwxrwxrwx 1 root root 13 Jan 31 11:26 pci-0000:00:01.0-render -> ../renderD128
lrwxrwxrwx 1 root root 8 Jan 31 11:26 pci-0000:08:00.0-card -> ../card0
lrwxrwxrwx 1 root root 13 Jan 31 11:26 pci-0000:08:00.0-render -> ../renderD129
Verify with lspci
$ lspci | grep VGA
00:01.0 VGA compatible controller: Red Hat, Inc. Virtio 1.0 GPU (rev 01)
08:00.0 VGA compatible controller: Intel Corporation Battlemage G21 [Arc Pro B50]
card0 matches the B50. However, the card with index approach is especially troublesome when virtualized. I often had them bumped between boots, resulting in the wrong card being picked.
Prepare hyprland conf with gpu link (update the paths accordingly)
$ sudo ln -s /dev/dri/by-path/pci-0000\:08\:00.0-card ~/.config/hypr/intel_gpu
$ nvim ~/.config/hypr/hyprland.conf
Add (leave commented out for now)
#env = AQ_DRM_DEVICES,/home/diam/.config/hypr/intel_gpu
Poweroff VM and take a snapshot in virt-manager.
Start VM, activate fully specified config settings, disable previous ones, and reboot, but don’t do it in fullscreen mode.
You’ll see uwsm trying to initialize Hyprland but it ends with a bang, or as Berta would put it, with three pumps and an apology.
In the virt-manger menu, go to Send key and select another tty (eg Ctrl+Alt+F2).
To verify gpu link is still correct
$ ls -lah /dev/dri/by-path
To view Hyprland crash reports
$ ls -lah ~/.cache/hyprland
View the last one, go to the end of the file. Your output should be similar to:
DEBUG from aquamarine ]: Creating an Aquamarine backend!
DEBUG from aquamarine ]: [libseat] [libseat/backend/seatd.c:64] Could not connect to socket /run/seatd.sock: No such file or directory
DEBUG from aquamarine ]: [libseat] [libseat/libseat.c:76] Backend 'seatd' failed to open seat, skipping
DEBUG from aquamarine ]: [libseat] [libseat/libseat.c:73] Seat opened with backend 'logind'
DEBUG from aquamarine ]: drm: Enumerated device /sys/devices/pci0000:00/0000:00:01.0/drm/card0
DEBUG from aquamarine ]: libseat: Device /dev/dri/card0 supports kms
DEBUG from aquamarine ]: drm: Enumerated device /sys/devices/pci0000:00/0000:00:02.7/0000:08:00.0/drm/card1
DEBUG from aquamarine ]: libseat: Device /dev/dri/card1 does not support kms
ERR from aquamarine ]: drm: Skipping device /sys/devices/pci0000:00/0000:00:02.7/0000:08:00.0/drm/card1, not a KMS device
DEBUG from aquamarine ]: drm: Explicit device list ~/.config/hypr/vf-card
ERR from aquamarine ]: drm: Failed to canonicalize path ~/.config/hypr/vf-card
ERR from aquamarine ]: drm: Explicit device ~/.config/hypr/vf-card not found
ERR from aquamarine ]: drm: Found no gpus to use, cannot continue
ERR from aquamarine ]: DRM Backend failed
...
Take your seats
FYI this is not the reason for the crash, logind can take over without seatd being enabled.
Hyprland (or any WM) needs early access to the GPU. Ref Sway - ArchWiki
Before a WM can be started, it needs access to your hardware devices such as your keyboard, mouse, and graphics card.
The collection of these hardware devices is called a seat.
On Arch Linux, a WM can get access to your seat using either:
- systemd-logind(8) and polkit
- seatd, which will be installed alongside Hyprland
If polkit is already installed on your system, the WM should automatically get access to your seat.
Alternatively, if polkit is not installed on your system and you want to use seatd instead, add yourself to the
seat user group and enable/start seatd.service, then re-log.
$ which seatd
/usr/bin/seatd
$ systemctl status seatd
○ seatd.service - Seat management daemon
Loaded: loaded (/usr/lib/systemd/system/seatd.service;
disabled; preset: disabled
)
Active: inactive (dead)
Docs: man:seatd(1)
$ sudo usermod -aG seat $USER
$ systemctl enable seatd
Reboot
The crash report indicates the VF does not support kms.
$ ls -lah /sys/class/drm
total 0
drwxr-xr-x 2 root root 0 Jan 31 12:19 .
drwxr-xr-x 63 root root 0 Jan 31 12:19 ..
lrwxrwxrwx 1 root root 0 Jan 31 12:19 card0 -> ../../devices/pci0000:00/0000:00:02.7/0000:08:00.0/drm/card0
lrwxrwxrwx 1 root root 0 Jan 31 12:19 card1 -> ../../devices/pci0000:00/0000:00:01.0/drm/card1
lrwxrwxrwx 1 root root 0 Jan 31 12:19 card1-Virtual-1 -> ../../devices/pci0000:00/0000:00:01.0/drm/card1/card1-Virtual-1
lrwxrwxrwx 1 root root 0 Jan 31 12:19 renderD128 -> ../../devices/pci0000:00/0000:00:01.0/drm/renderD128
lrwxrwxrwx 1 root root 0 Jan 31 12:19 renderD129 -> ../../devices/pci0000:00/0000:00:02.7/0000:08:00.0/drm/renderD129
-r--r--r-- 1 root root 4.0K Jan 31 12:19 version
card0 → …08:00.0 is the VF of the B50.
What we would expect to see here is a second line with the virtual monitor appended, as is done for card1.
Turns out the VFs on the B50 are exposed as render only cards,
ref community.intel.com/t5/Graphics/Intel-Arc-Pro-B50-SR-IOV-desktop-acceleration/m-p/1734262#M148627:
think I’ve identified what the problem is here.
The issue stems from the fact that the SR-IOV Virtual Function (VF) driver only exposes a render-only node (e.g., /dev/dri/renderD128) and does not provide any DRM connectors in /sys/class/drm/.
As it stands, the Virtual Function seems to be completely stripped of its “Display Engine” capabilities, leaving only the “Render Engine” accessible. Most Wayland compositors (like KWin or Mutter) require at least one active DRM connector to initialize the session, even in a headless/remote setup. Without it, the compositor simply fails to start.
I’m now certain that this “render-only” limitation of the VF is exactly what’s preventing the Wayland session from booting.
With that in mind, I have a few questions regarding the driver’s roadmap:
Is there any plan to allow the SR-IOV VF driver to expose “Virtual Connectors” in the future? This would allow Wayland to initialize properly on headless VFs by providing a dummy/virtual output directly at the DRM level.
Are there any current undocumented module parameters or firmware settings that could force the driver to expose a virtual connector for a VF?
So with the current state of things, you can not use these VFs as you would with regular passthrough.
Reverting back to the snapshot to do the benchmark (virtio with VF combo).
glmark2 score 4033
=======================================================
glmark2 2023.01
=======================================================
OpenGL Information
GL_VENDOR: Intel
GL_RENDERER: Mesa Intel(R) Arc(tm) Pro B50 Graphics (BMG G21)
GL_VERSION: 4.6 (Compatibility Profile) Mesa 25.3.4-arch1.1
Surface Config: buf=32 r=8 g=8 b=8 a=8 depth=24 stencil=0 samples=0
Surface Size: 800x600 windowed
=======================================================
[build] use-vbo=false: FPS: 2796 FrameTime: 0.358 ms
[build] use-vbo=true: FPS: 7888 FrameTime: 0.127 ms
[texture] texture-filter=nearest: FPS: 9432 FrameTime: 0.106 ms
[texture] texture-filter=linear: FPS: 8846 FrameTime: 0.113 ms
[texture] texture-filter=mipmap: FPS: 3705 FrameTime: 0.270 ms
[shading] shading=gouraud: FPS: 3623 FrameTime: 0.276 ms
[shading] shading=blinn-phong-inf: FPS: 3527 FrameTime: 0.284 ms
[shading] shading=phong: FPS: 3448 FrameTime: 0.290 ms
[shading] shading=cel: FPS: 3472 FrameTime: 0.288 ms
[bump] bump-render=high-poly: FPS: 5619 FrameTime: 0.178 ms
[bump] bump-render=normals: FPS: 3527 FrameTime: 0.284 ms
[bump] bump-render=height: FPS: 3607 FrameTime: 0.277 ms
[effect2d] kernel=0,1,0;1,-4,1;0,1,0;: FPS: 6230 FrameTime: 0.161 ms
[effect2d] kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;: FPS: 3295 FrameTime: 0.304 ms
[pulsar] light=false:quads=5:texture=false: FPS: 5264 FrameTime: 0.190 ms
[desktop] blur-radius=5:effect=blur:passes=1:separable=true:windows=4: FPS: 2212 FrameTime: 0.452 ms
[desktop] effect=shadow:windows=4: FPS: 2541 FrameTime: 0.394 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 660 FrameTime: 1.515 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=subdata: FPS: 783 FrameTime: 1.278 ms
[buffer] columns=200:interleave=true:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 770 FrameTime: 1.300 ms
[ideas] speed=duration: FPS: 1854 FrameTime: 0.539 ms
[jellyfish] <default>: FPS: 3154 FrameTime: 0.317 ms
[terrain] <default>: FPS: 896 FrameTime: 1.117 ms
[shadow] <default>: FPS: 2913 FrameTime: 0.343 ms
[refract] <default>: FPS: 1860 FrameTime: 0.538 ms
[conditionals] fragment-steps=0:vertex-steps=0: FPS: 3434 FrameTime: 0.291 ms
[conditionals] fragment-steps=5:vertex-steps=0: FPS: 5709 FrameTime: 0.175 ms
[conditionals] fragment-steps=0:vertex-steps=5: FPS: 4543 FrameTime: 0.220 ms
[function] fragment-complexity=low:fragment-steps=5: FPS: 6896 FrameTime: 0.145 ms
[function] fragment-complexity=medium:fragment-steps=5: FPS: 6874 FrameTime: 0.145 ms
[loop] fragment-loop=false:fragment-steps=5:vertex-steps=5: FPS: 6900 FrameTime: 0.145 ms
[loop] fragment-steps=5:fragment-uniform=false:vertex-steps=5: FPS: 3355 FrameTime: 0.298 ms
[loop] fragment-steps=5:fragment-uniform=true:vertex-steps=5: FPS: 3502 FrameTime: 0.286 ms
=======================================================
glmark2 Score: 4033
=======================================================
Given only the renderer is exposed. You could think you might as well remove the VF PCI Host Device from the VM in virt-manager and pass the renderer instead.
To do so, in virt-manager go to:
Video _ > Select Virtio and check 3D acceleration > Apply
Display Spice > Listen type: None > Check OpenGL and select VF from dropdown (not the PF ending in :0)
glmark2 score 151
=======================================================
glmark2 2023.01
=======================================================
OpenGL Information
GL_VENDOR: Mesa
GL_RENDERER: virgl (Mesa Intel(R) Arc(tm) Pro B50 Graphics (BMG G21))
GL_VERSION: 4.3 (Compatibility Profile) Mesa 25.3.4-arch1.1
Surface Config: buf=32 r=8 g=8 b=8 a=8 depth=32 stencil=0 samples=0
Surface Size: 800x600 windowed
=======================================================
[build] use-vbo=false: FPS: 159 FrameTime: 6.307 ms
[build] use-vbo=true: FPS: 165 FrameTime: 6.066 ms
[texture] texture-filter=nearest: FPS: 165 FrameTime: 6.080 ms
[texture] texture-filter=linear: FPS: 164 FrameTime: 6.124 ms
[texture] texture-filter=mipmap: FPS: 167 FrameTime: 6.010 ms
[shading] shading=gouraud: FPS: 166 FrameTime: 6.059 ms
[shading] shading=blinn-phong-inf: FPS: 161 FrameTime: 6.222 ms
[shading] shading=phong: FPS: 163 FrameTime: 6.151 ms
[shading] shading=cel: FPS: 164 FrameTime: 6.133 ms
[bump] bump-render=high-poly: FPS: 162 FrameTime: 6.209 ms
[bump] bump-render=normals: FPS: 163 FrameTime: 6.144 ms
[bump] bump-render=height: FPS: 162 FrameTime: 6.208 ms
[effect2d] kernel=0,1,0;1,-4,1;0,1,0;: FPS: 166 FrameTime: 6.058 ms
[effect2d] kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;: FPS: 165 FrameTime: 6.088 ms
[pulsar] light=false:quads=5:texture=false: FPS: 164 FrameTime: 6.132 ms
[desktop] blur-radius=5:effect=blur:passes=1:separable=true:windows=4: FPS: 156 FrameTime: 6.412 ms
[desktop] effect=shadow:windows=4: FPS: 159 FrameTime: 6.301 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 58 FrameTime: 17.358 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=subdata: FPS: 107 FrameTime: 9.426 ms
[buffer] columns=200:interleave=true:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 56 FrameTime: 18.054 ms
[ideas] speed=duration: FPS: 141 FrameTime: 7.134 ms
[jellyfish] <default>: FPS: 160 FrameTime: 6.261 ms
[terrain] <default>: FPS: 118 FrameTime: 8.477 ms
[shadow] <default>: FPS: 162 FrameTime: 6.183 ms
[refract] <default>: FPS: 148 FrameTime: 6.776 ms
[conditionals] fragment-steps=0:vertex-steps=0: FPS: 164 FrameTime: 6.111 ms
[conditionals] fragment-steps=5:vertex-steps=0: FPS: 165 FrameTime: 6.065 ms
[conditionals] fragment-steps=0:vertex-steps=5: FPS: 161 FrameTime: 6.223 ms
[function] fragment-complexity=low:fragment-steps=5: FPS: 166 FrameTime: 6.045 ms
[function] fragment-complexity=medium:fragment-steps=5: FPS: 162 FrameTime: 6.203 ms
[loop] fragment-loop=false:fragment-steps=5:vertex-steps=5: FPS: 160 FrameTime: 6.258 ms
[loop] fragment-steps=5:fragment-uniform=false:vertex-steps=5: FPS: 161 FrameTime: 6.216 ms
[loop] fragment-steps=5:fragment-uniform=true:vertex-steps=5: FPS: 165 FrameTime: 6.070 ms
=======================================================
glmark2 Score: 151
=======================================================
Despite the very low score, watching videos in 1080p seems to be ok (unlike with just the default virtio driver).
Return to hyprspace
Let’s create a headless monitor named vnc-monitor with hyprctl (from within a Hyprland session).
$ hyprctl output create headless vnc-monitor
Verify
$ hyprctl monitors all
Now add it on startup.
$ nvim .config/hypr/hyprland.conf
Add to exec-once lines:
exec-once = hyprctl output create headless vnc-monitor
We don’t just want to add a new monitor entry:
monitor= vnc-monitor, 2560x1440, auto, 1
But also set them as mirrors of each other:
monitor= Virtual-1, 2560x1440, auto, 1, mirror, vnc-monitor
monitor= vnc-monitor, 2560x1440, auto, 1, mirror, Virtual-1
To be sure, these should be the only 2 active monitor lines.
Rustdesk
Even though Rustdesk claims experimental Wayland support, like with my last attempts, I still get the dreaded x11 error.
Remmina with wayvnc
On the guest
$ paru -S wayvnc
Update hyprland config for wayvnc
Replace last exec-once lines with:
exec-once = hyprctl output create headless vnc-monitor
exec-once = wayvncctl output-set vnc-monitor
exec-once = bash ~/scripts/hyprland/spawn_kitties.sh
exec-once = wayvnc 0.0.0.0 5900
On the host
$ sudo pacman -S remmina
Launch remmina and connect to guest. This is the only case where I got the headless setup to work.
Sunshine / Moonlight
Ensure you have enough RAM assigned to the guest. 8GB won’t cut it, you’ll get build errors.
On the guest
$ paru -S sunshine
:: Resolving dependencies...
:: There are 3 providers available for sunshine:
:: Repository AUR:
1) sunshine 2) sunshine-bin 3) sunshine-git
Enter a number (default=1): 1
...
Reminder, install required package for hardware acceleration (VA-API) and friends
$ sudo pacman -S libva-intel-driver intel-media-driver libva-utils
$ sunshine
Open https://localhost:47990 in browser
Go to Advanced > Proceed to localhost (steps for Brave YMMV)
Username: sunshine
Password:
Enter again in login dialog
For sunshine to launch on boot, you can add an exec-once line.
Sunshine doesn’t seem to pick up on the headless monitor, ref Troubleshooting tab > Logs > Always bound to Virtual-1.
Under Configuration tab > Audio/Video subtab there’s a config.output_name_unix setting, but I couldn’t get the mapping to work.
On the host:
$ sudo pacman -S moonlight-qt
Required package for hardware acceleration (VA-API) and friends
$ sudo pacman -S libva-intel-driver intel-media-driver libva-utils
$ moonlight
Go to Settings (gear icon)
Basic settings > 1440p
Mind your thoughts Anakin
There’s no QuickSync support yet with Sunshine on Linux.
The auto headless setup of Apollo (Sunshine fork) swayed me. However, AToW it’s only supported on Windows.
This issue tracker does expose some valuable insights:
The lack of an universal Virtual Display on Linux seems to be what’s holding back progress in this field.
Revenge of the sith
Persauded by the other side, and the promise of NVENC, I dug up my trusty old Geforce GTX 1650.
Small intermezzo, the GTX 16 series uses the Turing architecture and were marketed towards live streamers. Even though they weren’t priced towards the higher end of the market, the GTX 1650’s bigger brother, the GTX 1660 Super was nothing to sneeze at. Remember these were still the days before the bandwidth caps.
To be sure, there are a lot of use cases that would warrant a higher end GPU. For me, I just want to use one or more Linux VMs for productivity, preferably with a user experience similar to that of Looking-glass on Windows.
What does warrant a mention in this guide though, is the glmark2 score for the GTX 1650 on the host.
glmark2 score 12168
=======================================================
glmark2 2023.01
=======================================================
OpenGL Information
GL_VENDOR: NVIDIA Corporation
GL_RENDERER: NVIDIA GeForce GTX 1650/PCIe/SSE2
GL_VERSION: 4.6.0 NVIDIA 580.126.09
Surface Config: buf=32 r=8 g=8 b=8 a=8 depth=24 stencil=0 samples=0
Surface Size: 800x600 windowed
=======================================================
[build] use-vbo=false: FPS: 8906 FrameTime: 0.112 ms
[build] use-vbo=true: FPS: 17113 FrameTime: 0.058 ms
[texture] texture-filter=nearest: FPS: 16535 FrameTime: 0.060 ms
[texture] texture-filter=linear: FPS: 16812 FrameTime: 0.059 ms
[texture] texture-filter=mipmap: FPS: 16887 FrameTime: 0.059 ms
[shading] shading=gouraud: FPS: 14971 FrameTime: 0.067 ms
[shading] shading=blinn-phong-inf: FPS: 15066 FrameTime: 0.066 ms
[shading] shading=phong: FPS: 14368 FrameTime: 0.070 ms
[shading] shading=cel: FPS: 13198 FrameTime: 0.076 ms
[bump] bump-render=high-poly: FPS: 10698 FrameTime: 0.093 ms
[bump] bump-render=normals: FPS: 16092 FrameTime: 0.062 ms
[bump] bump-render=height: FPS: 16007 FrameTime: 0.062 ms
[effect2d] kernel=0,1,0;1,-4,1;0,1,0;: FPS: 11535 FrameTime: 0.087 ms
[effect2d] kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;: FPS: 7727 FrameTime: 0.129 ms
[pulsar] light=false:quads=5:texture=false: FPS: 15737 FrameTime: 0.064 ms
[desktop] blur-radius=5:effect=blur:passes=1:separable=true:windows=4: FPS: 5456 FrameTime: 0.183 ms
[desktop] effect=shadow:windows=4: FPS: 7558 FrameTime: 0.132 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 1744 FrameTime: 0.574 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=subdata: FPS: 2101 FrameTime: 0.476 ms
[buffer] columns=200:interleave=true:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 2110 FrameTime: 0.474 ms
[ideas] speed=duration: FPS: 9024 FrameTime: 0.111 ms
[jellyfish] <default>: FPS: 11121 FrameTime: 0.090 ms
[terrain] <default>: FPS: 1276 FrameTime: 0.784 ms
[shadow] <default>: FPS: 11210 FrameTime: 0.089 ms
[refract] <default>: FPS: 2407 FrameTime: 0.416 ms
[conditionals] fragment-steps=0:vertex-steps=0: FPS: 17024 FrameTime: 0.059 ms
[conditionals] fragment-steps=5:vertex-steps=0: FPS: 17187 FrameTime: 0.058 ms
[conditionals] fragment-steps=0:vertex-steps=5: FPS: 16808 FrameTime: 0.059 ms
[function] fragment-complexity=low:fragment-steps=5: FPS: 17164 FrameTime: 0.058 ms
[function] fragment-complexity=medium:fragment-steps=5: FPS: 16954 FrameTime: 0.059 ms
[loop] fragment-loop=false:fragment-steps=5:vertex-steps=5: FPS: 17232 FrameTime: 0.058 ms
[loop] fragment-steps=5:fragment-uniform=false:vertex-steps=5: FPS: 16805 FrameTime: 0.060 ms
[loop] fragment-steps=5:fragment-uniform=true:vertex-steps=5: FPS: 16758 FrameTime: 0.060 ms
=======================================================
glmark2 Score: 12168
=======================================================
It can still hold its own with its 16 lanes of PCIe gen 3.
Guest setup with NVIDIA
If you’re on old hardware, you’ll probably want to fall back to the 580 driver version. Newer versions (are) no longer (guaranteed to) support them.
Set up on the host
$ lspci -nnk | grep 'NVIDIA'
61:00.0 VGA compatible controller [0300]: NVIDIA Corporation TU106 [GeForce GTX 1650] [10de:1f0a] (rev a1)
61:00.1 Audio device [0403]: NVIDIA Corporation TU106 High Definition Audio Controller [10de:10f9] (rev a1)
$ sudo lspci -vvv -s 61:00
61:00.0 VGA compatible controller: NVIDIA Corporation TU106 [GeForce GTX 1650] (rev a1) (prog-if 00 [VGA controller])
...
Kernel driver in use: nouveau
Kernel modules: nouveau
61:00.1 Audio device: NVIDIA Corporation TU106 High Definition Audio Controller (rev a1) (prog-if 00 [HDA compatible])
...
$ lspci -n -s 01:00
61:00.0 0300: 10de:1f0a (rev a1)
61:00.1 0403: 10de:10f9 (rev a1)
Ref PCI passthrough via OVMF - ArchWiki
Load vfio
Load vfio when udev loads GPU drivers.
$ sudo v /etc/modprobe.d/vfio.conf
softdep nouveau pre: vfio-pci
Add kernel parameters
We need to specify the vfio_pci ids that we want to bind the vfio driver to. The vfio driver then acts as a placeholder, so the VM can bind to the card later.
-
Using GRUB
$ sudo nvim /etc/default/grubAdd “… vfio_pci.ids=10de:1f0a,10de:10f9 vfio_pci.disable_vga=1”
Arch
$ sudo grub-mkconfig -o /boot/grub/grub.cfgDebian based
$ sudo update-grub -
Using Systemd
$ ls /boot/loader/entries 2025-12-14_11-23-01_linux.conf 2025-12-14_11-23-01_linux-fallback.conf $ sudo nvim /boot/loader/entries/2025-12-14_11-23-01_linux.confAdd options … vfio_pci.ids=10de:1f0a,10de:10f9 vfio_pci.disable_vga=1
Reboot and verify the active driver for the GPU
$ sudo lspci -vvv -s 61:00
61:00.0 VGA compatible controller: NVIDIA Corporation TU106 [GeForce GTX 1650] (rev a1) (prog-if 00 [VGA controller])
...
Kernel driver in use: vfio-pci
Kernel modules: nouveau
61:00.1 Audio device: NVIDIA Corporation TU106 High Definition Audio Controller (rev a1) (prog-if 00 [HDA compatible])
...
Driver in use is vfio-pci, ready for passthrough.
If this method didn’t work for you, the arch wiki describes alternatives (eg mkinitcpio).
Pass through the functions to the Linux VM. In virt-manager, Add hardware > PCI Host Device > Finish
Do this for both the Video and Audio device linked to the NVIDIA card.
Set up on the guest
$ ls -lah /dev/dri/by-path
Prepare hyprland conf with gpu link (update the paths accordingly)
$ sudo ln -s /dev/dri/by-path/pci-0000\:08\:00.0-card ~/.config/hypr/nvidia_gpu
$ nvim ~/.config/hypr/hyprland.conf
Add (leave commented out for now)
#env = AQ_DRM_DEVICES,/home/diam/.config/hypr/nvidia_gpu
Ref Wayland NVIDIA : Your Complete 2025 Fix for a Broken Desktop | Kextcache
On a rolling-release distribution like Arch Linux, the kernel is updated frequently. To prevent system breakage, it is highly recommended to use a DKMS (Dynamic Kernel Module Support) driver package. DKMS automatically recompiles the NVIDIA kernel module every time the kernel is updated, ensuring compatibility.
$ sudo pacman -S --needed git base-devel linux-headers
GPU families Turing, Ampere and Lovelace (ref CodeNames · freedesktop.org)
Supported both by:
- nvidia-open with no RTD3 Power Management on Turing2,
possible crashes on Ampere-equiped laptops2 - nvidia-580xx-dkms (AUR)
The nvidia driver 580.126 is the last driver version to support the older Turing cards.
$ paru -S nvidia-580xx-dkms nvidia-580xx-utils opencl-nvidia-580xx
Reboot
$ nvidia-smi
Tue Jan 27 20:30:21 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.126.09 Driver Version: 580.126.09 CUDA Version: 13.0 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GeForce GTX 1650 Off | 00000000:08:00.0 Off | N/A |
| 28% 37C P8 11W / 90W | 4MiB / 4096MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| 0 N/A N/A 642 G Hyprland 2MiB |
+-----------------------------------------------------------------------------------------+
GSP firmware
Ref wiki and [SOLVED] Disabling GSP firmware on nvidia-open / Kernel & Hardware / Arch Linux Forums
$ nvidia-smi -q | grep GSP
GSP Firmware Version : 580.126.09
$ sudo nvim /etc/modprobe.d/nvidia.conf
options nvidia NVreg_EnableGpuFirmware=0
options nvidia_drm modeset=1
Regenerate the initramfs to apply changes
$ sudo mkinitcpio -P
Reboot
$ nvidia-smi -q | grep GSP
GSP Firmware Version : N/A
mkinitcpio
$ sudo nvim /etc/mkinitcpio.conf
Change
MODULES=()
To
MODULES=(nvidia nvidia_modeset nvidia_uvm nvidia_drm)
Regenerate the initramfs
$ sudo mkinitcpio -P
Reboot
hyprland.conf
At least for the initial configuration, it helps if you can attach a physical monitor to the card ouput. Both on bare metal as when setting up a VM. Look up GPU output.
On the host (radeon and geforce):
$ ls /sys/class/drm
card0 card1 renderD128
card0-DP-2 card1-DP-1 renderD129
card0-HDMI-A-2 card1-DVI-D-1 version
card0-Writeback-1 card1-HDMI-A-1
On the guest (geforce and virtio):
$ ls /sys/class/drm
card0 card0-DP-1 card0-DVI-D-1 card0-HDMI-A-1 card1 card1-Virtual-1 renderD128 renderD129 version
Update config accordingly
$ nvim .config/hypr/hyprland.conf
monitor=HDMI-A-1, 2560x1440, auto, 1
env = AQ_DRM_DEVICES,/dev/dri/card1
Or even better, enable the symlink
env = AQ_DRM_DEVICES,/home/diam/.config/hypr/nvidia_gpu
Additionally, add these env variables
env = LIBVA_DRIVER_NAME,nvidia
env = __GLX_VENDOR_LIBRARY_NAME,nvidia
Optional environment variables
# Nvidia-specific environment variables
env = LIBVA_DRIVER_NAME,nvidia
env = XDG_SESSION_TYPE,wayland
env = GBM_BACKEND,nvidia-drm
env = __GLX_VENDOR_LIBRARY_NAME,nvidia
env = WLR_NO_HARDWARE_CURSORS,1
# Optional: Disable DRM modifiers (fixes most issues)
env = AQ_NO_MODIFIERS,1
# Optional: Enable VRR/G-SYNC
env = __GL_GSYNC_ALLOWED,1
#env = __GL_VRR_ALLOWED,1
glmark2 score 7517
=======================================================
glmark2 2023.01
=======================================================
OpenGL Information
GL_VENDOR: NVIDIA Corporation
GL_RENDERER: NVIDIA GeForce GTX 1650/PCIe/SSE2
GL_VERSION: 4.6.0 NVIDIA 580.126.09
Surface Config: buf=32 r=8 g=8 b=8 a=8 depth=24 stencil=0 samples=0
Surface Size: 800x600 windowed
=======================================================
[build] use-vbo=false: FPS: 5720 FrameTime: 0.175 ms
[build] use-vbo=true: FPS: 9866 FrameTime: 0.101 ms
[texture] texture-filter=nearest: FPS: 9746 FrameTime: 0.103 ms
[texture] texture-filter=linear: FPS: 9667 FrameTime: 0.103 ms
[texture] texture-filter=mipmap: FPS: 9815 FrameTime: 0.102 ms
[shading] shading=gouraud: FPS: 9318 FrameTime: 0.107 ms
[shading] shading=blinn-phong-inf: FPS: 9562 FrameTime: 0.105 ms
[shading] shading=phong: FPS: 9243 FrameTime: 0.108 ms
[shading] shading=cel: FPS: 9323 FrameTime: 0.107 ms
[bump] bump-render=high-poly: FPS: 7953 FrameTime: 0.126 ms
[bump] bump-render=normals: FPS: 9826 FrameTime: 0.102 ms
[bump] bump-render=height: FPS: 9652 FrameTime: 0.104 ms
[effect2d] kernel=0,1,0;1,-4,1;0,1,0;: FPS: 8500 FrameTime: 0.118 ms
[effect2d] kernel=1,1,1,1,1;1,1,1,1,1;1,1,1,1,1;: FPS: 5853 FrameTime: 0.171 ms
[pulsar] light=false:quads=5:texture=false: FPS: 9315 FrameTime: 0.107 ms
[desktop] blur-radius=5:effect=blur:passes=1:separable=true:windows=4: FPS: 3888 FrameTime: 0.257 ms
[desktop] effect=shadow:windows=4: FPS: 4694 FrameTime: 0.213 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 1345 FrameTime: 0.744 ms
[buffer] columns=200:interleave=false:update-dispersion=0.9:update-fraction=0.5:update-method=subdata: FPS: 1709 FrameTime: 0.585 ms
[buffer] columns=200:interleave=true:update-dispersion=0.9:update-fraction=0.5:update-method=map: FPS: 1612 FrameTime: 0.621 ms
[ideas] speed=duration: FPS: 5768 FrameTime: 0.173 ms
[jellyfish] <default>: FPS: 7460 FrameTime: 0.134 ms
[terrain] <default>: FPS: 967 FrameTime: 1.035 ms
[shadow] <default>: FPS: 6640 FrameTime: 0.151 ms
[refract] <default>: FPS: 1788 FrameTime: 0.559 ms
[conditionals] fragment-steps=0:vertex-steps=0: FPS: 9725 FrameTime: 0.103 ms
[conditionals] fragment-steps=5:vertex-steps=0: FPS: 9978 FrameTime: 0.100 ms
[conditionals] fragment-steps=0:vertex-steps=5: FPS: 9949 FrameTime: 0.101 ms
[function] fragment-complexity=low:fragment-steps=5: FPS: 10130 FrameTime: 0.099 ms
[function] fragment-complexity=medium:fragment-steps=5: FPS: 9803 FrameTime: 0.102 ms
[loop] fragment-loop=false:fragment-steps=5:vertex-steps=5: FPS: 9705 FrameTime: 0.103 ms
[loop] fragment-steps=5:fragment-uniform=false:vertex-steps=5: FPS: 9743 FrameTime: 0.103 ms
[loop] fragment-steps=5:fragment-uniform=true:vertex-steps=5: FPS: 9846 FrameTime: 0.102 ms
=======================================================
glmark2 Score: 7517
=======================================================
A new hope
The future looks promising. With a bit more polish, the driver should allow the B50’s VFs to be usable for light productivity tasks on Linux. AToW I couldn’t get it stable enough to consider using it on the clock (which could still be a skill issue).
@mathew2214 This should allow you to further investigate your use case. Unlike with passthrough, it’s still the host that manages the card’s outputs with the B50. You could make it work though. For example, by setting up a multi screen environment on the host and monitoring a single VM on each.
The Hyprland wiki describes the multi screen setup here. By default, you’ll likely end up with each screen having it’s own workspaces. You can do a lot with Workspace Rules, but I came across this plugin, that would create a single workspace spanning screens.
Next up is the B50 in combination with Windows guests. Will it be possible to use the B50 both on the host and guest with Looking-glass? Hope to see you soon in Ra Ra Rant.
