Skip to content

VNCCanvas: fix faint ghost edges left by a moving cursor - #303

Open
michael-adler wants to merge 1 commit into
bk138:masterfrom
michael-adler:cursor_artifacts
Open

VNCCanvas: fix faint ghost edges left by a moving cursor#303
michael-adler wants to merge 1 commit into
bk138:masterfrom
michael-adler:cursor_artifacts

Conversation

@michael-adler

Copy link
Copy Markdown

Solid-color content (e.g. a block text cursor) advancing across the screen left faint stale borders behind at its previous positions, worst in 1:1 view and still present when scaled. Not a compression artifact -- reproduced regardless of encoding/quality settings.

Tested on MacOS.

Two separate bugs were stacked here:

  1. onPaint drew each dirty rect as its own small, independently composited bitmap. Every such tile leaves a visible seam at its own boundary (blending of a scaled edge, or HiDPI content-scale interpolation even at 1:1). Fix: composite a single whole-canvas bitmap in onPaint instead of many per-rect tiles, removing any internal edge that could leave a seam.

  2. Even after (1), stale content remained in some spots, and a full forced repaint (e.g. from a resize) always cleared it -- meaning the pixel data was correct, but some Refresh(false, &rect) calls for just the changed sub-rect just never resulted in that area being repainted on this wx port. Tried coalescing all of a tick's changes into one bounding-box Refresh() call instead of one call per rect, in case it was multiple overlapping calls confusing the OS's dirty tracking -- still lost updates. So partial-rect invalidation itself isn't reliable here, not just how it's split into calls. Fix: always invalidate the whole canvas on any change, removing the dependency on that tracking entirely.

Invalidating and redrawing everything on every update would normally be wasteful, so VNCCanvas now keeps its own native-resolution copy of the framebuffer (fb_cache), updated incrementally: only the changed rect is re-fetched from the connection (the expensive per-pixel copy, unchanged in cost from before this fix), while onPaint just blits the already-current cache in one cheap, GPU-composited call. The always-redraw-everything requirement from (2) ends up nearly free.

Root cause of the wx-level partial-invalidation unreliability itself is still unknown.

Solid-color content (e.g. a block text cursor) advancing across the
screen left faint stale borders behind at its previous positions,
worst in 1:1 view and still present when scaled. Not a compression
artifact -- reproduced regardless of encoding/quality settings.

Two separate bugs were stacked here:

1. onPaint drew each dirty rect as its own small, independently
   composited bitmap. Every such tile leaves a visible seam at its own
   boundary (blending of a scaled edge, or HiDPI content-scale
   interpolation even at 1:1). Fix: composite a single whole-canvas
   bitmap in onPaint instead of many per-rect tiles, removing any
   internal edge that could leave a seam.

2. Even after (1), stale content remained in some spots, and a full
   forced repaint (e.g. from a resize) always cleared it -- meaning
   the pixel data was correct, but some Refresh(false, &rect) calls
   for just the changed sub-rect just never resulted in that area
   being repainted on this wx port. Tried coalescing all of a tick's
   changes into one bounding-box Refresh() call instead of one call
   per rect, in case it was multiple overlapping calls confusing the
   OS's dirty tracking -- still lost updates. So partial-rect
   invalidation itself isn't reliable here, not just how it's split
   into calls. Fix: always invalidate the whole canvas on any change,
   removing the dependency on that tracking entirely.

Invalidating and redrawing everything on every update would normally
be wasteful, so VNCCanvas now keeps its own native-resolution copy of
the framebuffer (fb_cache), updated incrementally: only the changed
rect is re-fetched from the connection (the expensive per-pixel copy,
unchanged in cost from before this fix), while onPaint just blits the
already-current cache in one cheap, GPU-composited call. The
always-redraw-everything requirement from (2) ends up nearly free.

Root cause of the wx-level partial-invalidation unreliability itself
is still unknown.

Signed-off-by: Michael Adler <madler@tapil.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant