vnc_worker: add ZRLE and a configurable tile size, reduce redundant screen-update work, identify clients#3665
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR improves VNC console efficiency and observability by plumbing a bidirectional synth-video control channel, reducing VRAM reads to dirty spans, and adding richer negotiation/dirty-path diagnostics.
Changes:
- Bundle synth-video dirty-rect + “updates needed” channels into a single optional struct and plumb it through OpenVMM/Underhill/device wiring.
- Optimize update collection to avoid unnecessary full VRAM rereads and support partial scanline reads (dirty columns only).
- Add diagnostic logging (security type, pixel format, encodings) plus tests and updated guide docs.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| workers/vnc_worker_defs/src/lib.rs | Introduces SynthVideoChannels and updates VncParameters to carry both channels together. |
| workers/vnc_worker/vnc/src/update_state.rs | Refines full-refresh vs stale-buffer logic and reads only dirty columns; adds tests. |
| workers/vnc_worker/vnc/src/traits.rs | Extends Framebuffer::read_line to include an x offset for partial reads. |
| workers/vnc_worker/vnc/src/server.rs | Adds debug logging for security type, negotiated pixel format, and encoding negotiation. |
| workers/vnc_worker/vnc/src/rfb.rs | Adds encoding_name() helper for readable encoding diagnostics plus unit tests. |
| workers/vnc_worker/vnc/examples/vncserver.rs | Updates example framebuffer implementation for new read_line(line, x, ...) signature. |
| workers/vnc_worker/src/lib.rs | Wires SynthVideoChannels, client-id tracing spans, dirty backlog draining/coalescing, and updates-needed signaling. |
| workers/vnc_worker/Cargo.toml | Adds tracelimit dependency used for rate-limited logging. |
| vm/devices/uidevices_resources/src/lib.rs | Introduces SynthVideoDeviceChannels and updates SynthVideoHandle to carry paired channels. |
| vm/devices/uidevices/src/video/protocol.rs | Removes unused FeatureChangeMessageV2 definition. |
| vm/devices/uidevices/src/video/mod.rs | Adds “updates needed” receiver, sends FeatureChange, gates dirt forwarding, and adds tests. |
| vm/devices/uidevices/src/resolver.rs | Unpacks SynthVideoHandle.channels into device constructor parameters. |
| vm/devices/framebuffer/src/lib.rs | Adds View::read_line_at() to support partial scanline reads. |
| petri/src/vm/openvmm/construct.rs | Updates synth-video handle construction for new channels field. |
| openvmm/openvmm_entry/src/lib.rs | Adds reverse “updates needed” channel wiring when --gfx is enabled and passes synth-video channels to VNC worker. |
| openhcl/underhill_core/src/worker.rs | Updates Underhill remote console channel wiring to use paired dirty/updates channels. |
| openhcl/underhill_core/src/lib.rs | Refactors remote console setup return type and wires paired channels into VncParameters. |
| Guide/src/reference/devices/vnc/what-we-are-proud-of.md | Updates documentation to reflect partial dirty-column reads and guest update suppression. |
| Guide/src/reference/devices/vnc/architecture.md | Updates architecture docs for new channel bundling, stale-buffer semantics, and backlog coalescing. |
75b3554 to
2d8045d
Compare
for performance reasons i propose to change it to a non-rate limited debug message instead. |
2d8045d to
29f6dc0
Compare
|
@smalis-msft - maybe You can take a look, I tested and refractored again. I acknowledge jstarks concerns but the channel is unbound by behaviour - we consume the dirty rects much faster then the device can are produce, so we never where able to provoke more then a few rectangles piling up (single digits) |
29f6dc0 to
a481297
Compare
a481297 to
18e3ce0
Compare
|
@smalis-msft @jstarks would be great if you could skim this and merge it in. There's still a lot left to do. Next up I want to tackle ZRLE encoding so remote viewers aren't stuck on just ZLIB or RAW. That's the biggest win for remote video performance right now. |
|
I will take a pass on both of your current PRs sometime today. |
|
let me sneak in ZRLE compression - almost finished |
18e3ce0 to
b689f5e
Compare
b689f5e to
64228ee
Compare
|
@smalis-msft : adopted the minimal docstring style and took out quemu references - except in the VNC server feature comparison documentation - it should be ready for review now. maybe You discuss internally if we need Tight encoding or not (mostly for jpeg picture frames where ZRLE falls back to ZLIB compression performance) - or what to tackle next ? Authentication ? Mouse Pointer ? |
|
I think our SNP runner broke... |
thanks - was just about looking into it. |
64228ee to
2c28b72
Compare
2c28b72 to
e54fada
Compare
…creen-update work, identify clients Combines several vnc_worker changes. The dirty-rect ones started as separate branches that overlapped heavily and conflicted on every rebase, and they were largely reviewed already, so they are folded in here together with the new ZRLE encoding. ZRLE: a framebuffer encoding (RFB encoding 16) that splits a rectangle into 64x64 tiles and sends each tile as the smallest applicable subencoding, all through one continuous per-connection zlib stream: solid (one CPIXEL), raw (one CPIXEL per pixel), packed palette (up to 16 colours, indices packed MSB-first and padded to a byte per row), and plain or palette RLE (runs of CPIXELs or palette indices, using the 255-continuation run-length scheme). A CPIXEL drops the unused byte of a 32bpp true-colour pixel (3 bytes instead of 4) in the client's byte order, falling back to the full pixel for other formats. The server prefers ZRLE over Zlib whenever a client offers it (ZRLE, then Zlib, then Raw), which also stops TigerVNC, which offers no plain zlib, from falling back to uncompressed Raw. An in-test reference decoder exercises every subencoding, including big-endian and high-3-byte CPIXEL layouts, packed rows spanning multiple index bytes, RLE runs longer than 255, and two updates decoded through one continuous stream. Idle suppression: when no VNC client is connected, ask the synthetic video device to stop reporting screen and pointer updates, and re-assert that if the device re-handshakes while idle. The device-side synth-video channels (dirty rects plus the updates-needed signal) are bundled into one handle so they stay paired across worker restart and OpenHCL paravisor wiring. Partial-width dirty reads: read only the dirty columns of each scanline rather than the whole row when building an update. Full-refresh reuse: when a client requests a non-incremental update and the device is already keeping our framebuffer copy current, send that copy instead of re-reading all of VRAM. A first frame, a resolution change, or a dropped broadcast still forces a fresh full read. Configurable tile size: --vnc-tile-size sets the dirty-tracking tile edge length (4, 8, or 16), replacing the hardcoded 16 and defaulting to 8, which measured best for bandwidth and CPU across resolutions and workloads. The dirty bitmap and update state hold the size as a field rather than a constant; a resolution change keeps it, and the paravisor console uses the same default. A cycle value rotates through 2, 4, 8, 16, 32 every 30 seconds, logging the bytes and CPU at each for re-measuring; 2 and 32 are otherwise left out of the offered set as dominated. The flag and the measurements behind the default are in the VNC guide. Diagnostics: drain the whole dirty-rect backlog each wakeup, coalesce it into a single broadcast, track its high-water mark, and rate-limit an error if the unbounded mesh queue ever climbs past a ceiling, which would mean a wedged consumer. Each client connection runs in a span tagged with its id so the log lines from concurrent clients can be told apart. Client identification: RFB carries no client name, but the SetEncodings list is characteristic of each viewer. guess_client matches the offered set against known signatures (noVNC, TigerVNC, RealVNC, MobaXterm, UltraVNC) and logs the result as client_guess on the client encodings line, or "unknown" when nothing matches. The peer address is logged on that line too, and the full decoded offered list stays at debug. A reference page under the VNC guide records the encodings each client advertises next to what the server implements. Encoding numbers: the RFB encoding and pseudo-encoding numbers now live in one EncodingType open_enum instead of a few u32 constants sitting beside a separate integer match in encoding_name, so a number and its name cannot drift apart. The wire field stays unsigned big-endian; the handful of wire sites reinterpret the bits through a single helper. The worker's test client shares the type instead of keeping its own copies of the numbers, and ZRLE adds its number to the same open_enum. Also reject a --vnc-max-clients of zero, which would otherwise open the port but refuse every connection.
e54fada to
bbbdcaa
Compare
|
@smalis-msft gh runners look a bit flaky today |
|
Yeah was probably https://www.githubstatus.com/incidents/fcj3088jg1wx |
This is the existing vnc_worker improvements PR, now also carrying the ZRLE
encoding and a configurable dirty-tracking tile size. It is squashed to one commit
and rebased on current
main.Screen-update work
told to stop reporting screen and pointer updates, re-asserted on a
re-handshake. The device-side synth-video channels are bundled into one handle
so they stay paired across worker restart and OpenHCL paravisor wiring.
not the whole row.
framebuffer copy instead of re-reading all of VRAM, except on a first frame, a
resolution change, or a dropped broadcast.
Configurable tile size
--vnc-tile-sizesets the dirty-tracking tile edge length (4, 8, or 16),replacing the hardcoded 16. It defaults to 8, which measured best for bandwidth
and CPU across resolutions and workloads. The dirty bitmap and update state hold
the size as a field; a resolution change keeps it, and the paravisor console uses
the same default. A
cyclevalue rotates through 2, 4, 8, 16, 32 every 30 secondsand logs the bytes and CPU at each, for re-measuring; 2 and 32 are left out of the
offered set as dominated.
ZRLE encoding
A new framebuffer encoding (RFB encoding 16). Each rectangle is split into 64x64
tiles, and each tile is sent as the smallest applicable subencoding (solid, raw,
packed palette, plain RLE, palette RLE) through one continuous per-connection
zlib stream. CPIXELs drop the unused byte of a 32bpp true-colour pixel in the
client's byte order. The server prefers ZRLE over Zlib for any client that
offers it, then Zlib, then Raw, so a client that offers no plain zlib (TigerVNC)
no longer falls back to uncompressed Raw.
Client identification and diagnostics
guess_clientmatches the offered SetEncodings list against known viewersignatures and logs a best-effort
client_guess(or "unknown"), with the peeraddress.
wakeup, with a high-water-mark gauge and a rate-limited error if the queue
climbs past a ceiling. Each client connection runs in a span tagged with its
id.
EncodingTypeopen_enum so a numberand its name cannot drift apart.
--vnc-max-clientsof zero is now rejected.Docs
Reference pages under the VNC guide: a client-capability matrix, a comparison
with other VNC servers, and the dirty-tracking tile-size measurements behind the
default.
Testing
vnccrate, including an in-test ZRLE reference decoderthat round-trips every subencoding (big-endian and high-3-byte CPIXEL layouts,
multi-byte packed rows, RLE runs over 255, multi-update continuous-stream
cases), and tile-to-pixel reconstruction at non-default tile sizes.
MobaXterm, UltraVNC): each selects ZRLE and renders correctly, including video
playback and text-heavy screens. The tile-size default was set from per-size
bandwidth and CPU measurements at four resolutions, under static and
full-screen-video workloads.