From b3f0f53a23204b305ef4a139e5dfdb276330ab8e Mon Sep 17 00:00:00 2001 From: Viktor Liu Date: Tue, 19 May 2026 12:42:38 +0200 Subject: [PATCH] Collapse dirty rects to their bounding box when the bbox is densely dirty --- client/vnc/server/session.go | 15 ++++++++++ client/vnc/server/session_encode.go | 44 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/client/vnc/server/session.go b/client/vnc/server/session.go index c4d066f73..0228701c6 100644 --- a/client/vnc/server/session.go +++ b/client/vnc/server/session.go @@ -32,6 +32,21 @@ const ( fullFramePromoteDen = 100 ) +// bboxPromoteDensityPct collapses the coalesced rect list down to its +// bounding box when the dirty pixels occupy at least this fraction of the +// bbox. Catches the "windowed video" case where the player area dirties as +// a dense block but is split into many sibling rects by overlays or by +// non-uniform tile coverage. Sending one JPEG over the bbox beats sending +// dozens of small JPEGs that each carry their own header and Tight stream +// restart. +const ( + bboxPromoteDensityPct = 70 + // bboxPromoteMinArea avoids promoting a handful of small scattered + // rects whose bbox would span most of the screen and pull in mostly + // clean pixels. + bboxPromoteMinArea = tileSize * tileSize * 16 +) + type session struct { conn net.Conn capturer ScreenCapturer diff --git a/client/vnc/server/session_encode.go b/client/vnc/server/session_encode.go index f34a58400..4d6501591 100644 --- a/client/vnc/server/session_encode.go +++ b/client/vnc/server/session_encode.go @@ -120,6 +120,11 @@ func (s *session) processIncremental(img *image.RGBA) error { s.refreshCopyRectIndex() return nil } + if len(moves) == 0 { + if bb, ok := promoteToBoundingBox(rects); ok { + rects = bb + } + } if err := s.sendDirtyAndMoves(img, moves, rects); err != nil { return err } @@ -311,6 +316,45 @@ func (s *session) captureFrame() (*image.RGBA, error) { return s.curFrame, nil } +// promoteToBoundingBox replaces the rect list with a single rect covering +// the bounding box of all inputs, provided the bbox is at least +// bboxPromoteMinArea and the dirty pixels fill at least +// bboxPromoteDensityPct of it. Returns the new rect list and true when the +// promotion fires; otherwise returns nil, false and the caller keeps the +// original list. +func promoteToBoundingBox(rects [][4]int) ([][4]int, bool) { + if len(rects) < 2 { + return nil, false + } + x0, y0 := rects[0][0], rects[0][1] + x1, y1 := x0+rects[0][2], y0+rects[0][3] + dirty := 0 + for _, r := range rects { + if r[0] < x0 { + x0 = r[0] + } + if r[1] < y0 { + y0 = r[1] + } + if r[0]+r[2] > x1 { + x1 = r[0] + r[2] + } + if r[1]+r[3] > y1 { + y1 = r[1] + r[3] + } + dirty += r[2] * r[3] + } + w, h := x1-x0, y1-y0 + bbox := w * h + if bbox < bboxPromoteMinArea { + return nil, false + } + if dirty*100 < bbox*bboxPromoteDensityPct { + return nil, false + } + return [][4]int{{x0, y0, w, h}}, true +} + // shouldPromoteToFullFrame returns true when the dirty rect set covers a // large enough fraction of the screen that a single full-frame zlib rect // beats per-tile encoding on both CPU time and wire bytes. The crossover