A complete guide to running a Windows 11 VM with GPU passthrough and Looking Glass on an Arch-based Linux system with a Wayland compositor. This setup allows you to run Windows-only software like Fusion 360 in a native-feeling window on your Linux desktop, with near-zero latency and no compression artifacts.
This guide was written from a real setup session, including every issue encountered and how it was resolved. If you're running into problems, there's a good chance the answer is in here.
| Component | Detail |
|---|---|
| Host OS | CachyOS (Arch-based) with Hyprland (Wayland) |
| Host GPU | NVIDIA GeForce RTX 3080 Ti |
| Guest GPU | NVIDIA GeForce GTX 1080 (passed through to VM) |
| CPU | Intel i9-12900K (16 cores, 24 threads) |
| RAM | 64 GB total, 32 GB allocated to VM |
| QEMU | 6.2+ (JSON-style configuration) |
| libvirt | 7.9+ |
| NVIDIA driver | 590.48.01 |
| Looking Glass | B7 stable release |
| Target resolution | 3440×1440 (ultrawide) |
Before diving into configuration, it helps to understand the architecture.
Looking Glass works by sharing GPU framebuffer data between the Windows VM and your Linux host through shared memory, completely bypassing the normal display output path.
The host application runs inside the Windows VM. It uses the Desktop Duplication API (D12 on Windows 10+) to capture frames the GPU is rendering — the same way screen recording works. Instead of encoding those frames into a video stream like RDP or VNC, it writes raw, uncompressed frame data directly into a shared memory region.
That shared memory region is the IVSHMEM device — a virtual PCI device that both the VM and the host can access simultaneously. It's a chunk of RAM that exists at the same physical memory addresses for both sides. The KVMFR kernel module manages this on the Linux side, and the IVSHMEM PCI driver handles it on the Windows side.
The client application runs on your Linux desktop. It maps that same shared memory region and reads the frame data directly. Because there's no encoding, decoding, or network transport involved, the latency is extremely low — essentially the time it takes to copy pixels from GPU memory into shared memory, plus one DMA transfer.
SPICE runs alongside as a separate channel purely for input — keyboard, mouse, clipboard sync, and audio. The actual video frames never touch SPICE.
This section covers everything needed to get the guest GPU claimed by VFIO and passed through to the VM. If you already have a working passthrough setup, skip to Part 1.
This is the number one thing people miss. The kernel parameter intel_iommu=on does nothing if the CPU feature isn't enabled in firmware.
Reboot into BIOS/UEFI and look for one of these settings (varies by motherboard manufacturer):
- VT-d
- Intel Virtualization Technology for Directed I/O
- IOMMU
Enable it, save, and exit. Also confirm VT-x (CPU virtualization) is enabled while you're there.
CachyOS uses systemd-boot. The correct way to set persistent kernel parameters is through /etc/sdboot-manage.conf, not by editing boot entries directly (those get overwritten on kernel updates).
sudo vim /etc/sdboot-manage.confFind the LINUX_OPTIONS line and set:
LINUX_OPTIONS="intel_iommu=on rd.driver.pre=vfio-pci video=efifb:off"Then regenerate the boot entries:
sudo sdboot-manage genReboot and verify:
cat /proc/cmdlineYou should see your parameters in the output.
Problem: ls /sys/kernel/iommu_groups/ shows an empty directory despite VT-d being enabled and intel_iommu=on in the kernel parameters.
Cause: iommu=pt (passthrough mode) can prevent IOMMU groups from being populated. The dmesg output will show Default domain type: Passthrough — meaning devices aren't being isolated.
Fix: Remove iommu=pt from your kernel parameters entirely, or change it to iommu=1. Despite being commonly recommended in guides, passthrough mode defeats the purpose of VFIO isolation. The rd.driver.pre=vfio-pci parameter is what actually matters for early VFIO binding.
Problem: After a system update, cat /proc/cmdline no longer shows your VFIO parameters.
Cause: CachyOS regenerates systemd-boot entries during kernel updates, overwriting manual edits to /boot/loader/entries/*.conf.
Fix: Use /etc/sdboot-manage.conf as described above — this is the CachyOS-specific mechanism for persistent kernel parameters. Never edit boot entries directly; always use LINUX_OPTIONS in the config file and run sudo sdboot-manage gen to apply.
lspci -nn | grep -E "VGA|Audio" | grep -i nvidiaExample output for a dual-GPU system:
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA102 [GeForce RTX 3080 Ti] [10de:2208] (rev a1)
01:00.1 Audio device [0403]: NVIDIA Corporation GA102 High Definition Audio Controller [10de:1aef] (rev a1)
08:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP104 [GeForce GTX 1080] [10de:1b80] (rev a1)
08:00.1 Audio device [0403]: NVIDIA Corporation GP104 High Definition Audio Controller [10de:10f0] (rev a1)
Note the device IDs in brackets. In this case, the GTX 1080 (guest GPU) has IDs 10de:1b80 (GPU) and 10de:10f0 (audio).
Create /etc/modprobe.d/vfio.conf:
options vfio-pci ids=10de:1b80,10de:10f0
softdep snd_hda_intel pre: vfio-pci
The softdep line ensures VFIO loads before the audio driver, which otherwise races to claim the GPU's audio device first.
Add VFIO modules to the initramfs so they load early enough. Edit /etc/mkinitcpio.conf:
MODULES=(vfio_pci vfio vfio_iommu_type1)
Rebuild the initramfs:
sudo mkinitcpio -PReboot, then verify both GPU functions are bound to vfio-pci:
lspci -nnk -s 08:00Expected output:
08:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP104 [GeForce GTX 1080] [10de:1b80] (rev a1)
Kernel driver in use: vfio-pci
08:00.1 Audio device [0403]: NVIDIA Corporation GP104 High Definition Audio Controller [10de:10f0] (rev a1)
Kernel driver in use: vfio-pci
Problem: After configuring everything, lspci -nnk shows no driver in use for the GPU.
Cause: The VFIO modules aren't in the initramfs, so they don't load early enough.
Fix: Verify the modules are actually present:
sudo lsinitcpio /boot/initramfs-linux-cachyos.img | grep vfioNote: CachyOS names its initramfs differently (linux-cachyos or linux-cachyos-lts). If the modules aren't listed, re-check /etc/mkinitcpio.conf and rebuild with sudo mkinitcpio -P.
Create a Windows 11 VM in virt-manager with Q35 machine type and UEFI firmware (OVMF). Then add the GPU:
- Add Hardware → PCI Host Device → select
0000:08:00.0(GPU) - Add Hardware → PCI Host Device → select
0000:08:00.1(Audio)
The CPU mode configuration is surprisingly important for VFIO + shared memory compatibility. Using the wrong mode causes QEMU crashes with vfio_container_region_add errors when shared memory devices are present.
Working configuration:
<cpu mode='host-model' check='none'>
<topology sockets='1' dies='1' cores='18' threads='1'/>
</cpu>What doesn't work:
host-passthroughwithmigratable='on'— causes memory mapping conflicts between the GPU passthrough and IVSHMEM device, crashing QEMUhost-passthroughwithoutmigratable— may also conflict depending on QEMU version
The host-model mode has libvirt select a compatible CPU model rather than exposing exact CPU features, which avoids the memory region overlap. The migratable attribute must be removed entirely (not set to 'off' — just absent).
Without the topology block, Windows will see an incorrect CPU layout (e.g., 2 sockets instead of 18 cores). The topology line tells Windows exactly how the vCPUs are organized. Note that vCPU allocation is shared, not reserved — your host can still use all 24 cores; the VM just won't use more than 18 at once.
For the VM to be accessible on your LAN (required for SPICE, and useful for general connectivity), set up a network bridge:
sudo nmcli connection add type bridge ifname br0 con-name br0
sudo nmcli connection add type ethernet ifname enp6s0 master br0
sudo nmcli connection up br0Replace enp6s0 with your actual ethernet interface (check with ip link).
Then configure the VM to use the bridge in its XML:
<interface type='bridge'>
<mac address='52:54:00:xx:xx:xx'/>
<source bridge='br0'/>
<model type='e1000e'/>
</interface>The VM will get its own IP from your router's DHCP server.
Once the VM boots with the passed-through GPU, install the NVIDIA drivers manually from nvidia.com. GeForce Experience often fails in VMs due to hardware detection checks, so download the standalone driver instead:
- Go to https://www.nvidia.com/Download/index.aspx
- Select GeForce → 10 Series → GTX 1080 → Windows 11
- Download and install the driver directly
Before arriving at Looking Glass, Moonlight/Sunshine was tested as an alternative. The results for this particular use case (CAD work at 3440×1440) were not satisfactory:
- At 165 FPS (native refresh rate), massive compression artifacts appeared — NVENC on the GTX 1080 can't keep up at that frame rate
- At 60 FPS with 150 Mbps bitrate, image quality was acceptable but mouse input felt sluggish — unacceptable for precision CAD work in Fusion 360
- HEVC encoding wasn't available (Moonlight client-side decoding issues on CachyOS)
- Even with optimized settings, the encode/decode pipeline adds perceptible latency compared to raw framebuffer sharing
For gaming or media consumption, Moonlight/Sunshine is excellent. For daily-driver CAD/productivity work where you need pixel-perfect rendering and zero input lag, Looking Glass is the correct tool.
GL.iNet Comet Pro (IP KVM): Hardware-based HDMI capture from the GTX 1080. Works for remote access and BIOS-level troubleshooting, but adds its own compression and latency. Useful as a fallback but not a primary display solution for productivity work.
Intel iGPU passthrough (GVT-d): The i9-12900K has a UHD 770 iGPU, but 12th gen Alder Lake does not support GVT-g (mediated/shared GPU). GVT-d requires full passthrough, meaning the host loses the iGPU entirely (must blacklist i915). Combined with known Code 43 errors, missing VBIOS issues, and special OpROM requirements, this was not worth pursuing when the GTX 1080 is vastly more capable for Fusion 360.
The KVMFR module creates a character device (/dev/kvmfr0) that provides a high-performance shared memory interface between host and guest.
Clone the Looking Glass B7 stable release (not git master — the client and host versions must match):
cd ~/source
git clone --branch B7 --single-branch https://github.com/gnif/LookingGlass.git looking-glass-B7
cd looking-glass-B7/moduleInstall via DKMS so the module rebuilds automatically on kernel updates:
sudo dkms install "."The formula from the Looking Glass docs:
width × height × 4 × 2 = frame bytes
round up to nearest power of 2 in MB
For 3440×1440:
3440 × 1440 × 4 × 2 = 39,628,800 bytes ≈ 37.8 MB
Round up to nearest power of 2 → 64 MB
However, 64 MB can be tight for ultrawide resolutions. We used 128 MB to provide headroom and avoid any frame corruption issues.
Create /etc/modprobe.d/kvmfr.conf:
options kvmfr static_size_mb=128
Create /etc/modules-load.d/kvmfr.conf to auto-load at boot:
kvmfr
Create /etc/udev/rules.d/99-kvmfr.rules:
SUBSYSTEM=="kvmfr", OWNER="your_username", GROUP="kvm", MODE="0660"
Replace your_username with your actual Linux username.
Reload and trigger:
sudo udevadm control --reload-rules
sudo udevadm triggersudo modprobe kvmfrVerify it's working:
ls -l /dev/kvmfr0You should see a character device (permissions start with c) owned by your user:
crw-rw---- 1 your_username kvm 508, 0 Feb 8 15:32 /dev/kvmfr0
Problem: After loading the module, /dev/kvmfr0 is owned by root with mode 600.
Cause: The udev rule didn't trigger, or the module was reloaded after creating the device (which creates a fresh device node without re-triggering udev).
Fix: Apply permissions manually for the current session, then fix the udev rule for persistence:
sudo chown your_username:kvm /dev/kvmfr0
sudo chmod 660 /dev/kvmfr0Problem: ls -l /dev/kvmfr0 shows a regular file (permissions start with -) instead of a character device (c).
Cause: The VM was started before the KVMFR module was loaded. QEMU creates a regular file as a fallback.
Fix: Stop the VM, remove the file, load the module, then start the VM:
virsh destroy win11
sudo rm /dev/kvmfr0
sudo modprobe kvmfr
virsh start win11The <domain> tag needs the QEMU namespace for the commandline passthrough:
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>This must be done in the same virsh edit session as adding the QEMU commandline block.
Add this block at the bottom of your VM XML, just before </domain>:
<qemu:commandline>
<qemu:arg value="-device"/>
<qemu:arg value="{'driver':'ivshmem-plain','id':'shmem0','memdev':'looking-glass'}"/>
<qemu:arg value="-object"/>
<qemu:arg value="{'qom-type':'memory-backend-file','id':'looking-glass','mem-path':'/dev/kvmfr0','size':134217728,'share':true}"/>
</qemu:commandline>The size is in bytes: 128 MB × 1024 × 1024 = 134,217,728.
Important: This is JSON-style syntax required for QEMU 6.2+ / libvirt 7.9+. Using the older comma-separated syntax on newer versions will cause a "PCI slot not available" error.
If you previously tried the standard shared memory approach, remove any <shmem> block like this:
<!-- REMOVE THIS if present -->
<shmem name='looking-glass'>
<model type='ivshmem-plain'/>
<size unit='M'>128</size>
</shmem>The QEMU commandline block replaces it. Having both will cause conflicts.
Change the memballoon device from virtio to none:
<memballoon model="none"/>The virtio balloon driver causes performance issues with VFIO passthrough.
Looking Glass is incompatible with absolute pointing devices. Remove the USB tablet:
<!-- REMOVE THIS -->
<input type="tablet" bus="usb">
<address type="usb" bus="0" port="1"/>
</input>Without removing this, you'll get mouse input issues and the Looking Glass FAQ explicitly calls this out.
SPICE provides keyboard, mouse, and clipboard sync to the VM (video is handled by Looking Glass, not SPICE):
<graphics type="spice" autoport="yes">
<listen type="address" address="127.0.0.1"/>
</graphics>Install SPICE guest tools inside Windows for clipboard sync and proper integration.
Edit /etc/libvirt/qemu.conf and find the cgroup_device_acl block. Uncomment it and add /dev/kvmfr0:
cgroup_device_acl = [
"/dev/null", "/dev/full", "/dev/zero",
"/dev/random", "/dev/urandom",
"/dev/ptmx", "/dev/kvm",
"/dev/kvmfr0"
]
Then restart libvirtd:
sudo systemctl restart libvirtdProblem: VM fails to start with can't open backing store /dev/kvmfr0 for guest RAM: Permission denied.
Cause: cgroups policy is blocking QEMU from accessing the device, even if file permissions are correct.
Fix: Ensure /dev/kvmfr0 is in the cgroup_device_acl list in /etc/libvirt/qemu.conf and restart libvirtd.
Critical: The client and host must be built from the same release. If you install the B7 host in Windows, you must build the B7 client on Linux. Mixing versions (e.g., a B7 host with a git master client, or vice versa) will cause connection failures or protocol mismatches. Always download the same tagged release for both sides.
sudo pacman -S cmake gcc libgl libegl fontconfig spice-protocol make nettle \
pkgconf binutils libxi libxinerama libxss libxcursor libxpresent \
libxkbcommon wayland-protocols ttf-dejavu libsamplerateBuild from the B7 stable release source (must match the Windows host version):
cd ~/source/looking-glass-B7/client
mkdir build
cd build
cmake ..
make -j$(nproc)Verify X11 support is enabled (required for XWayland rendering with OpenGL):
grep ENABLE_X11 CMakeCache.txt
# Should show: ENABLE_X11:BOOL=ONsudo cp looking-glass-client /usr/local/bin/Download the B7 host installer from the Looking Glass releases page and install it in the Windows VM.
The host runs as a Windows service under the SYSTEM account. This is important because it needs to capture the desktop even at the login screen.
Host application location: C:\Program Files\Looking Glass (host)\looking-glass-host.exe
Log file: %ProgramData%\Looking Glass (host)\looking-glass-host.txt
If you don't have a physical monitor connected to the passed-through GPU, you need a virtual display driver. IddSampleDriver creates a virtual monitor that the GPU renders to, which Looking Glass then captures.
Download IddSampleDriver from its GitHub repository and install it in the Windows VM. Configure it with your desired resolution (e.g., 3440×1440).
Critical: Do not set the IddSampleDriver display as the primary display if you're still using SPICE or RDP for initial setup. If the GPU can't output to the virtual display before Looking Glass is connected, you may lose access to the VM entirely. Set it as a secondary display first, then make it primary only after confirming Looking Glass captures it.
Problem: Set IddSampleDriver as primary display, can't see the login screen via SPICE or any other method.
Fix: Mount the VM's disk from the Linux host and remove the driver:
# Ensure VM is stopped
virsh destroy win11
# Mount the QCOW2 disk
sudo modprobe nbd max_part=8
sudo qemu-nbd --connect=/dev/nbd0 /path/to/win11.qcow2
sudo fdisk -l /dev/nbd0 # Identify the Windows NTFS partition (usually p3)
sudo mount -t ntfs-3g -o rw,remove_hiberfile /dev/nbd0p3 /mntThe remove_hiberfile option is needed because Windows Fast Startup leaves the filesystem dirty. Navigate to the IddSampleDriver installation directory and remove or rename the driver files:
# Remove or rename the driver directory
sudo mv /mnt/path/to/IddSampleDriver /mnt/path/to/IddSampleDriver.bakUnmount and disconnect:
sudo umount /mnt
sudo qemu-nbd --disconnect /dev/nbd0Start the VM again, and it should fall back to the SPICE display.
looking-glass-clientThe client will automatically find /dev/kvmfr0 and connect to the shared memory region. SPICE integration provides keyboard/mouse input.
This is the most important section in this guide. If you're running NVIDIA + Hyprland (or any Wayland compositor), you will almost certainly encounter flickering black rectangles in the Looking Glass output.
Problem: Occasional flickering black boxes/rectangles appearing in the Looking Glass window.
Root cause: The default EGL renderer has a compositing incompatibility with the NVIDIA driver under Wayland. This is not a Looking Glass bug, not a D12 capture issue, and not an IddSampleDriver problem.
Solution: Force the OpenGL renderer instead of EGL.
Create ~/.looking-glass-client.ini:
[app]
renderer=openglThat's it. The OpenGL renderer completely eliminates the flickering without any performance penalty.
What doesn't fix it (so you don't waste time):
- Disabling D12 damage tracking (
trackDamage=falsein the Windows host config) - Switching to DXGI capture
- Downgrading egl-wayland
- Disabling explicit sync (
__NV_DISABLE_EXPLICIT_SYNC=1) - Enabling double buffering (
egl:doubleBuffer=true) - Disabling DMA (
app:allowDMA=no) - Unsetting
WAYLAND_DISPLAY(this forces XWayland, which works but is unnecessary)
The OpenGL renderer is the clean, correct fix.
To have the Windows VM start automatically at boot:
virsh autostart win11To disable later:
virsh autostart --disable win11<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<name>win11</name>
<memory unit='GiB'>32</memory>
<vcpu placement='static'>18</vcpu>
<!-- CPU mode: host-model WITHOUT migratable (critical for VFIO + shmem) -->
<cpu mode='host-model' check='none'>
<topology sockets='1' dies='1' cores='18' threads='1'/>
</cpu>
<!-- GPU Passthrough -->
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x08' slot='0x00' function='0x0'/>
</source>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x08' slot='0x00' function='0x1'/>
</source>
</hostdev>
<!-- SPICE for input only -->
<graphics type="spice" autoport="yes">
<listen type="address" address="127.0.0.1"/>
</graphics>
<!-- No memory ballooning -->
<memballoon model="none"/>
<!-- NO USB tablet device -->
<!-- KVMFR shared memory -->
<qemu:commandline>
<qemu:arg value="-device"/>
<qemu:arg value="{'driver':'ivshmem-plain','id':'shmem0','memdev':'looking-glass'}"/>
<qemu:arg value="-object"/>
<qemu:arg value="{'qom-type':'memory-backend-file','id':'looking-glass','mem-path':'/dev/kvmfr0','size':134217728,'share':true}"/>
</qemu:commandline>
</domain>| File | Contents |
|---|---|
/etc/modprobe.d/kvmfr.conf |
options kvmfr static_size_mb=128 |
/etc/modules-load.d/kvmfr.conf |
kvmfr |
/etc/udev/rules.d/99-kvmfr.rules |
SUBSYSTEM=="kvmfr", OWNER="your_username", GROUP="kvm", MODE="0660" |
/etc/libvirt/qemu.conf |
Add /dev/kvmfr0 to cgroup_device_acl |
~/.looking-glass-client.ini |
[app] / renderer=opengl |
| Component | Location |
|---|---|
| Looking Glass host | C:\Program Files\Looking Glass (host)\looking-glass-host.exe |
| Host log | %ProgramData%\Looking Glass (host)\looking-glass-host.txt |
| Capture method | D12 (default, fastest for Windows 10+) |
| IddSampleDriver | Virtual display at 3440×1440 |
| SPICE guest tools | Installed for clipboard sync |
| Symptom | Cause | Fix |
|---|---|---|
VM won't start: "Permission denied" on /dev/kvmfr0 |
cgroups blocking access | Add /dev/kvmfr0 to cgroup_device_acl in qemu.conf, restart libvirtd |
/dev/kvmfr0 owned by root |
udev rule not applied | sudo chown user:kvm /dev/kvmfr0 && sudo chmod 660 /dev/kvmfr0; fix udev rule |
/dev/kvmfr0 is regular file, not char device |
VM started before module loaded | Stop VM, rm /dev/kvmfr0, modprobe kvmfr, start VM |
| VM won't start: "PCI slot not available" | Using legacy QEMU syntax on new QEMU | Use JSON-style qemu:commandline syntax |
| Flickering black boxes in LG output | EGL renderer + NVIDIA + Wayland | Set renderer=opengl in ~/.looking-glass-client.ini |
| No display in Looking Glass | Host not running / wrong display captured | Check host log; ensure IddSampleDriver is configured |
| Locked out of VM after display change | IddSampleDriver set as primary with no way to view it | Mount QCOW2 via qemu-nbd, remove driver files |
| Mouse issues in Looking Glass | USB tablet device present | Remove <input type="tablet"> from VM XML |
| No clipboard sync | SPICE tools not installed | Install SPICE guest tools in Windows |
# Check KVMFR module status
lsmod | grep kvmfr
dmesg | grep kvmfr
# Check device permissions
ls -la /dev/kvmfr0
# VM management
virsh start win11
virsh destroy win11 # force stop
virsh shutdown win11 # graceful shutdown
virsh autostart win11
# Mount VM disk for emergency access
sudo modprobe nbd max_part=8
sudo qemu-nbd --connect=/dev/nbd0 /path/to/win11.qcow2
sudo mount -t ntfs-3g -o rw,remove_hiberfile /dev/nbd0p3 /mnt
# ... do your work ...
sudo umount /mnt
sudo qemu-nbd --disconnect /dev/nbd0
# Launch Looking Glass with verbose output
looking-glass-client -d
Fantastic guide! I've spent hours finding information on how to set this up. I'll try this on my Ubuntu 24.04.3 LTS and will give you my feedback.