@@ -6,8 +6,13 @@
### Environment
- Host: Ubuntu 24.04.4 on WSL2 (kernel 6.18), 20 cores, `/dev/kvm` present, no `/dev/dri`
(so no VAAPI/DMABUF here; the shm path and software encoding only).
- Host: Ubuntu 24.04.4 on WSL2 (kernel 6.18), 20 cores, `/dev/kvm` present, NVIDIA RTX 4090
(Windows driver 610.88, exposed through `/dev/dxg` and `/usr/lib/wsl/lib`, which include
`libcuda` and `libnvidia-encode`). CUDA toolkits 12.8 and 13.2 are in `/usr/local`.
- WSL2 has **no DRM render node** (`/dev/dri` is absent): no VAAPI, no GBM, and no DMABUF export.
QEMU refuses `-device virtio-vga-gl -display dbus,gl=on` with "egl: no drm render node
available". So the DMABUF zero-copy path (#3) can't run here; NVENC encoding of the shm path can.
An earlier version of these notes wrongly said this host has no GPU because `/dev/dri` was missing.
- Toolchain: upstream needs **gcc-14** on Ubuntu 24.04 (`scripts/linux_build.sh` picks it;
`src/platform/linux/kmsgrab.cpp` uses `std::ranges::to`). The default gcc 13 fails to build a
clean upstream tree. Configure with `CC=gcc-14 CXX=g++-14`.
@@ -65,18 +70,35 @@
### REQ-NFR-001 measurements (host processing latency: QEMU call receipt → encoded frame sent)
Release build, gcc-14, 20-core host under WSL2, KVM, `encoder = software` (libx264), guest
framebuffer 640x400 scaled by Sunshine, ~330 samples per run over 18 s:
Release build, gcc-14, 20-core host under WSL2, KVM, guest framebuffer 640x400 scaled by
Sunshine, ~330 samples per run over 18 s. NVENC runs use a CUDA build
(`-DCMAKE_CUDA_COMPILER=/usr/local/cuda-13.2/bin/nvcc`, `encoder = nvenc`, `h264_nvenc` with the
RAM→CUDA upload) on the RTX 4090.
| Transport (QEMU) | Stream | p50 | p95 |
|---------------------------------|-------------|---------|---------|
| Shared memory map (11.1.1) | 1920x1080@60 | 14.1 ms | 21.4 ms |
| D-Bus messages (8.2.2) | 1920x1080@60 | 14.1 ms | 21.1 ms |
| Shared memory map, Debug build | 1280x800@30 | 17.2 ms | 32.9 ms |
| D-Bus messages, Debug build | 1280x800@30 | 20.2 ms | 34.6 ms |
| Transport (QEMU) | Encoder | Stream | p50 | p95 |
|---------------------------------|-----------------|--------------|---------|---------|
| Shared memory map (11.1.1) | NVENC | 1920x1080@60 | 11.1 ms | 20.4 ms |
| D-Bus messages (8.2.2) | NVENC | 1920x1080@60 | 12.2 ms | 19.5 ms |
| Shared memory map (11.1.1) | software x264 | 1920x1080@60 | 14.1 ms | 21.4 ms |
| D-Bus messages (8.2.2) | software x264 | 1920x1080@60 | 14.1 ms | 21.1 ms |
| Shared memory map, Debug build | software x264 | 1280x800@30 | 17.2 ms | 32.9 ms |
| D-Bus messages, Debug build | software x264 | 1280x800@30 | 20.2 ms | 34.6 ms |
The transports tie because the guest framebuffer is small; software scaling and x264 dominate.
No VAAPI/NVENC or DMABUF on this host, so the draft "p95 ≤ 10 ms on the DMABUF path" can't be
confirmed here; it stays for #3 on a GPU host.
The transports tie because the guest framebuffer is small.
**Capture pacing dominates, not encoding.** The capture loop wakes on a fixed tick of one frame
interval (16.7 ms at 60 fps) and copies whatever damage arrived since the last tick. Damage lands
uniformly within the interval, so it waits 0–16.7 ms (p50 ≈ 8 ms, p95 ≈ 16 ms) before the copy.
Add ~3 ms for NVENC and that predicts p50 ≈ 11 ms and p95 ≈ 19–20 ms, which matches the
measurements. Hardware encoding only saves the 2–3 ms that x264 spends. Waking the capture thread on
damage (a condition variable with the next frame deadline as timeout) should remove most of the
wait. That belongs with the pacing work in #3.
The draft "p95 ≤ 10 ms on the DMABUF path" can't be measured here, because WSL2 has no DRM render
node for QEMU's GL display. It stays for #3 on native Linux.
Seen during encoder probing with the CUDA build, not caused by this backend: "cuda::cuda_t doesn't
support any format other than AV_PIX_FMT_NV12 and AV_PIX_FMT_YUV444P" (the RAM→CUDA upload rejects
10-bit probes, as it does for x11 capture). The 8-bit H.264 stream is unaffected.
### Observations for later phases