# Display v2 — build plan (pluggable scanout: GOP floor + virtio-gpu native) The ordered, checkpointable build-out for [display-v2.md](display-v2.md). Each milestone lands on its own and ends in a **verifiable gate** — shaped for a `/loop` run, like [display-plan.md](display-plan.md). Read display-v2.md first for the *why*. ## Locked decisions (do not relitigate) - **First native backend = virtio-gpu** (VM standard: mode-set + present/flush + vsync). - **Dynamic hot-attach**: boot on GOP, upgrade to native when the driver **announces** (push, not polling); re-attach across driver restarts; GOP is the floor for "no driver ever," not a live fall-back after a reprogram. - **v2 builds the `shm` capability** (endpoints → memory objects), shared with the future client-surface path. - The compositor's layers/back-buffer/damage are **unchanged**; only scanout is pluggable. ## Conventions Follow [coding-standards.md](coding-standards.md): spell out non-acronym abbreviations, kebab-case file names, no `Co-Authored-By` trailers. New user binaries go through `addUserBinary` and get packed into the initial-ramdisk; protocols are `b.addModule("…-protocol", …)` imported into `runtime`; new syscalls extend [abi.zig](../system/abi.zig) `SystemCall` + a `library/runtime` wrapper. ## How to verify along the way **Every gate is serial-checkable — no screenshots** (this plan is built to run unattended). Where "does it actually display" would otherwise need a human eyeball, the code **reads its own pixels back**: the scanout resource is CPU-visible RAM (shm-backed) and the back buffer is cacheable, so a driver/compositor can write a known value, read it back, and log a pass/fail — and a virtio `resource_flush` is confirmed by the device **acking it on the used ring**. Those two together (pixel-readback + flush-ack) are the automated stand-in for "it's on screen." - `zig build test` — host unit tests (backend selection, virtio struct sizes/encodings, pixel-check helpers). - `python3 test/qemu_test.py ` — boots the kernel in QEMU; asserts on serial markers. The virtio cases boot with `-device virtio-gpu` (a per-case `qemu_extra`). - `run-x86-64` renders to a window — for the human's own satisfaction, **not** a gate. --- ## V1 — The scanout backend seam (refactor, no behaviour change) ✅ Extract scanout from the compositor so today's path becomes one backend among future ones. - [x] `system/services/display/backend.zig`: a `Backend` tagged union with `info()`, `surface()` (the cacheable compose target), `present(damage)`, and capability flags (`canModeSet`/`hasVsync`, both false for GOP). - [x] The v1 GOP path is now `backend.Gop` (claims the `display` node, WC-maps the LFB, keeps the cacheable back buffer, `present` = the damage-rect WC copy). display.zig composes into `backend.surface()` and calls `backend.present(damage)` — no LFB or framebuffer geometry left in the compositor core. - [x] The selection decision is the pure `chooseKind(native_available)` (gop unless a native driver announced), split from the syscall-bound `select()`/`Gop.init()`. **Gate (met):** `display-service` + `display-demo` pass **unchanged** (pure refactor; GOP is the only backend), and `zig build test` stays green. ## V2 — The `shm` cross-process memory capability (kernel) ✅ - [x] [abi.zig](../system/abi.zig): `shm_create` (34) / `shm_map` (35) syscalls + a `shm_test` service id. Handlers in process.zig: `shm_create(len)` allocates contiguous, zeroed, **cacheable** frames, wraps them in a refcounted object, installs a capability handle, maps them into the caller's shm arena → returns vaddr + handle; `shm_map(cap)` maps the same physical pages into the receiver. Reclaimed on death (see below). - [x] The capability core (ipc-synchronous.zig) is now **kind-tagged**: `scheduler.Task`'s handle table holds `HandleObject{kind, ptr}`; `closeHandles` and `shareCapability` dispatch by kind, so an `ShmObject` rides an `ipc_call` `send_cap` exactly like an endpoint and frees only when its last capability drops. `mapUserSharedInto` (paging) maps WB-cacheable + `device_grant`, so a sharer's teardown never frees the shared frames — the object owns them. - [x] `library/runtime/shm.zig` (+ barrel export): `create(len) -> Region{ptr, handle, len}`, `map(handle) -> ptr`. **Gate (met):** `python3 test/qemu_test.py shm` — `shm-client` creates a region, writes a pattern, and passes its capability to `shm-server` as an `ipc_call` send_cap; the server `shm_map`s it and reads the **same bytes** back → `shm: shared 4096 bytes ok`. Guardrail: `ipc`/`ipc-call`/`ipc-cap`, `supervision`, `dma`, `usermem`, `display-service`, and host tests all still pass — the handle-table change broke no existing IPC. ## V3 — The virtio-gpu driver: bring-up + a frame on screen ✅ - [x] `system/drivers/virtio-gpu/`: claim the virtio-gpu PCI function (device-manager match on the display/other class triple, driver self-confirms vendor 0x1AF4/device 0x1050 from config space), enable memory-space + bus-master, walk the vendor capabilities in config space to find common-config + notify, map the BAR, negotiate VERSION_1, and stand up the control virtqueue in coherent DMA. `virtio-gpu-protocol.zig` + `virtio-pci.zig` for the control/transport structs (host-tested sizes). - [x] Create a 2D scanout resource backed by a coherent DMA region (V4 swaps this for the shm-shared surface), `attach_backing`, `set_scanout` to scanout 0, `transfer_to_host_2d` + `resource_flush` of a test pattern, and wait on the used ring. - [x] Register a `scanout` service (`ServiceId.scanout` = 11). **Gate (met):** the `virtio-gpu` case (QEMU `-device virtio-gpu-pci`) boots the device-manager stack, which discovers the function and spawns the driver; the driver writes a known test pattern into the scanout backing, `transfer_to_host_2d` + `resource_flush`es it, and **waits for the device's used-ring ack**, then reads the backing back and checks the pattern — logging `virtio-gpu: scanout 640x480 online` and `virtio-gpu: flush acked, pixel check ok`. That proves virtqueue + resource + attach + set_scanout + transfer + flush end to end without a screenshot (the used-ring ack is the device confirming it consumed the frame). ## V4 — The native backend + hot-attach - [ ] `VirtioGpuBackend` in the compositor: `surface()` = the shared scanout resource, `present(damage)` = `resource_flush` of the damaged rect. - [ ] The driver **announces** to `.display` (looks it up, sends *attach-scanout* with its `scanout` endpoint + the shared surface as capabilities). The compositor switches backends and re-presents the current frame full-screen. - [ ] Boot still starts on `GopBackend`; the upgrade happens on announce. **Gate (automated):** boot with virtio-gpu + `display-demo`; the compositor logs `display: scanout upgraded to virtio-gpu`, drives frames through the native backend, and **reads a pixel back** from the shared scanout resource after a present to confirm the composited frame landed (`display: native present verified`), while `display-demo: ok` still fires. Without `-device virtio-gpu`, no `scanout` is announced and it stays on GOP — the v1 `display-service`/`display-demo` gates still pass unchanged. ## V5 — Mode-setting, EDID, and vsync - [ ] virtio-gpu `GET_EDID` → a mode list; `set_scanout` at a chosen mode = runtime resolution change. `runtime.display` gains `modes()` / `setMode(m)`. - [ ] A vsync/fenced `resource_flush` present path → genuinely tear-free. - [ ] The compositor reports the native backend's `canModeSet`/`hasVsync` = true. **Gate (automated):** a `display-modeset` case reads the EDID mode list, calls `setMode` to a different resolution, and confirms the change by reading the driver's scanout geometry back (`display: mode set to {w}x{h}, verified`); the vsync/fenced present path is exercised and confirmed by the flush **fence completing** (`display: vsync present ok`) — both from serial, no eyeballing. ## V6 — Resilience (restart + re-attach) + tests + docs - [ ] The virtio-gpu driver is supervised (device-manager / init) and restartable; on driver loss the compositor freezes the last frame and **re-attaches** when the driver re-announces. Only a permanent give-up (crash-loop cap) attempts GOP again. - [ ] `test/qemu_test.py` cases: `virtio-gpu`, hot-attach, `display-modeset`, and a driver-kill/re-attach case. Update [display-v2.md](display-v2.md) status; README index. **Gate:** kill the virtio-gpu driver mid-run; the compositor survives and re-attaches on restart (`display: scanout re-attached`); all v1 + v2 cases pass; default `zig build` clean. --- ## Deferred (explicitly not in this plan) - **Client-rendered surfaces** — now unblocked by the `shm` capability (V2): an app renders its own bitmap and hands the compositor a reference. A natural follow-on. - **Bochs DISPI backend** — a simpler second native backend (mode-set only, dumb scanout); slots behind the same interface if wanted. - **Real-GPU (NVIDIA/AMD/Intel) drivers** — out of scope; those devices stay on the GOP floor by design. - **Hardware-accelerated compositing / multiple heads** — future.