vnc: fix blue channel shift using red_max instead of blue_max in convert_pixels#3395
vnc: fix blue channel shift using red_max instead of blue_max in convert_pixels#3395
Conversation
…red_max Agent-Logs-Url: https://github.com/microsoft/openvmm/sessions/94357e2c-6dcd-4962-a64d-181c35521667 Co-authored-by: benhillis <17727402+benhillis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/microsoft/openvmm/sessions/94357e2c-6dcd-4962-a64d-181c35521667 Co-authored-by: benhillis <17727402+benhillis@users.noreply.github.com>
|
Nice catch on the blue-channel shift, we hit the same bug while doing a broader VNC overhaul on our fork (vnc-multiclient-dirtydirtydirty branch), and ended up fixing it as part of a small refactor of convert_pixels. Sharing in case it's useful for follow-up: The same fix, factored out: Instead of patching the shift in place, we extracted a PixelConversion struct that's built once when the client's pixel format is set (lib.rs:103-162). convert_pixels itself then just does shift/mask in the hot loop, no per-call recomputation of red_bits/green_bits/blue_bits. For VGA at 60 Hz this is a small but free win, and it makes the channel-width logic exist in exactly one place, which is what made the original red_max/blue_max typo possible in the first place. Passthrough fast-path: Most real clients negotiate native 32bpp BGRX, where every shift/mask is a no-op. We detect that case in the constructor (passthrough: bool) and skip the whole loop. Worth considering as a follow up here too, it's maybe 15 lines and pays for itself on every frame for the common case. Bit width via leading_zeros rather than count_ones: Equivalent for any 2^n - 1 mask (so identical for every spec-compliant client), but robust to non contiguous masks like red_max = 5 (0b101) where count_ones would underreport the spanned width. Pure defensive; costs nothing. Tests: We landed the same three cases you have (32bpp identity, RGB565, 8bpp 3:3:2 asymmetric) plus an empty_input edge case and a dedicated regression named after the bug, so a future bisect lands on a test whose name says exactly what broke. only fixing the blue_max gave us wrong grey colour on gray windows decorators with real-vnc viewer (check visually !!) |
VGA output via VNC showed severe color degradation (posterization, channel bleed) when VNC clients requested sub-32bpp pixel formats such as 8bpp RGB 3:3:2.
Root cause
Copy-paste bug in
convert_pixels:shift_bwas computed usingred_maxinstead ofblue_max:Source pixels are
0x00RRGGBB. The shift for each channel is derived from how many significant bits the destination format allocates to that channel:24 - count_ones(red_max)16 - count_ones(green_max)8 - count_ones(blue_max)← wasred_maxThis is invisible for symmetric formats (32bpp, RGB565) where
red_max == blue_max. It breaks 8bpp RGB 3:3:2 (red_max=7,blue_max=3):shift_bis 5 instead of 6, producing out-of-range blue values (0–7 instead of 0–3) that overflow into the adjacent green channel.Changes
workers/vnc_worker/vnc/src/lib.rs: One-character fix toshift_bcalculation.convert_pixelsunit tests covering the 32bpp no-convert fast path, 8bpp 3:3:2 asymmetric channel depths (regression case), and 16bpp RGB565.