summaryrefslogtreecommitdiffstats
path: root/docs/05-operations/06-guest-frame-path-and-looking-glass.md
blob: bc5447afe8ba8eb1ba3aab8fe4d1a7d03de1fea7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# Guest frame path and Looking Glass status (2026-10-21)

This page records the state of the "show the guest's GPU output on the host"
goal: what is proven, what is built, and exactly which pieces remain. It is
written from live evidence on the vGPU-IO stack (guest CUDA green, ivshmem
devices present, kvmfr module loaded on the host).

## 1. What is proven

* **Guest GPU rendering works through CUDA.** `tools/cuda-ab/frame_producer.cu`
  renders an animated Julia set on the guest GPU (800x600 RGBA, ~10 fps),
  copies the frame to host memory and publishes it into the ivshmem BAR
  (`/sys/bus/pci/devices/0000:00:03.0/resource2`, the 128 MiB kvmfr device
  QEMU backs with `/dev/shm/kvmfr-<uuid>`), with a small header
  (`magic/version/width/height/stride/format/seq/timestamp`) at offset 1 MiB.
* **The host can read those frames.** `tools/lg/frame_reader.py` maps the same
  shm file and writes each new sequence number out as a PPM, i.e. a real
  GPU-rendered guest frame lands on the host. This is the full transport path
  (guest GPU -> guest sysmem -> ivshmem -> host), independent of any display
  protocol.
* **The ivshmem devices are visible in the guest** (`lspci`: two
  "Inter-VM shared memory" functions; `resource2` of `0000:00:03.0` is the
  128 MiB window, `0000:00:04.0` the 2 MiB cursor window), and the guest can
  mmap them (root required for the sysfs resource files).
* **EGL enumerates the NVIDIA device in the guest.** With
  `EGL_EXT_platform_device`/`EGL_EXT_device_enumeration` present,
  `eglQueryDevicesEXT` returns 3 devices and `eglInitialize` on the NVIDIA
  device succeeds (`vendor=NVIDIA version=1.5`).

## 2. What is blocked, and why

* **Guest GL contexts do not allocate.** `eglMakeCurrent` on an offscreen
  pbuffer fails with `EGL_BAD_ALLOC` (0x3003) on every platform tried
  (device, surfaceless, default). Xorg's own failure is the same class:
  `(EE) NVIDIA(GPU-0): Failed to allocate VASpace object` then
  `Failing initialization of X screen`. The driver cannot allocate GPU virtual
  address space for a GL context/surface through the vGPU, so both the X
  display path and the EGL offscreen path stop at the same wall. This is the
  single remaining blocker for guest OpenGL (and therefore for GZDOOM /
  doomsday rendering in the guest).
* **The Looking Glass client (B4) cannot consume our frames directly.**
  `looking-glass-client` (installed at `/usr/local/bin/looking-glass-client`,
  runs under Xvfb with the `libbfd` shim in `/tmp/opencode/lgcompat`) supports
  two transports, LGMP and SPICE. Its `app:shmFile` option still expects an
  **LGMP session** (the `KVMFR` session header plus the shared-memory message
  queues), not a raw framebuffer. Upstream's frame *producer* (`host/`) is the
  Windows DXGI/IDD path. So a Linux guest-side producer must either implement
  the LGMP session protocol or we keep the frame reader as the host-side
  substitute (which is what `frame_reader.py` is).
* **GZDOOM is not packaged for Ubuntu noble** (`apt-cache policy gzdoom` has
  no candidate); `doomsday` + `freedoom` are installed as the fallback, but
  doomsday cannot start a renderer without a working GL context (blocker
  above).

## 3. Exact remaining pieces (in order of leverage)

1. **Fix the guest GL VASpace allocation.** This is an RM/vGPU-side gap: the
   driver's `nv_vgpu` VASpace allocation for a GL context fails. Fixing it
   unlocks Xorg, EGL offscreen GL, doomsday/GZDOOM, and the standard Looking
   Glass guest path (the guest's GL capture driver would then work as
   upstream expects).
2. **Implement the LGMP session producer** (or a small Linux capture client)
   that writes frames into the ivshmem region with the KVMFR session header
   and the frame queue protocol the client expects. The transport half is
   already proven; this is protocol work in the guest (or a host-side
   substitute that speaks LGMP).
3. **Packaging**: GZDOOM would need a source build or a flatpak/snap; the
   doomsday fallback is installed and only blocked by (1).

## 4. Reproduce

```bash
# guest (root, because the sysfs resource is root-only):
sudo /tmp/frame_producer 60 800 600 10        # 60 frames, 800x600, 10 fps

# host:
python3 tools/lg/frame_reader.py --frames 5 --out /tmp/opencode/frames
# -> SAVED seq=N 800x600 fmt=0 -> /tmp/opencode/frames/frame-NNNN.ppm

# guest EGL probe (shows the enumeration success and the MakeCurrent wall):
gcc -O2 -o egl_frame tools/guest-diag/egl_frame.c -lEGL -lGLESv2
./egl_frame
```

## 5. Session-13 update (2026-10-21, r432→r441)

* The single-frame path is unchanged and still proven: guest `frame_producer`
  (CUDA Julia) -> kvmfr ivshmem BAR -> host `frame_reader.py` ->
  `/tmp/opencode/frames/frame-0001.png` (320x240, 1431 colours).
* `tools/lg/frame_sequence.sh` captures an **animated** sequence end to end
  (reader first, then N frames from the guest). It is blocked by the
  GR no-execute wedge: when the lane is wedged the render launches write zero
  pages and the producer republishes stale identical frames (observed: 6/6
  identical MD5s on r438; r439 published 5 identical plus one frame left in
  the BAR by the previous boot). A multi-frame animation needs a healthy
  lane; the frame pipeline itself is not the blocker.
* The GL wall is untouched: `Xorg :0` -> `(EE) NVIDIA(GPU-0): Failed to
  allocate VASpace object`, guest dmesg `NVRM: vaspaceapiConstruct_IMPL:
  Could not construct VA space. Status ffff`, i.e. `vmmCreateVaspace` inside
  the guest's own RM. Next step: reproduce with the guest RM's
  `ResmanDebugLevel` raised (a guest module reload, the service's m177 path)
  to capture which inner call returns the generic error.