mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-19 15:19:55 +00:00
Drop dead Hextile and standalone Zlib encoding paths
This commit is contained in:
@@ -330,57 +330,6 @@ func reverseBits(b byte) byte {
|
||||
return r
|
||||
}
|
||||
|
||||
// encodeZlibRect encodes a framebuffer region using Zlib compression.
|
||||
// The zlib stream is continuous for the entire VNC session: noVNC creates
|
||||
// one inflate context at startup and reuses it for all zlib-encoded rects.
|
||||
// We must NOT reset the zlib writer between calls.
|
||||
func encodeZlibRect(img *image.RGBA, pf clientPixelFormat, x, y, w, h int, z *zlibState) []byte {
|
||||
bytesPerPixel := max(int(pf.bpp)/8, 1)
|
||||
zw, zbuf := z.w, z.buf
|
||||
|
||||
// Clear the output buffer but keep the deflate dictionary intact.
|
||||
zbuf.Reset()
|
||||
|
||||
// Encode the full rect pixel stream into the session-lived scratch buffer
|
||||
// and feed zlib one row at a time. Row-granular writes amortise the per-
|
||||
// Write overhead that used to dominate this function when it wrote one
|
||||
// byte slice per pixel.
|
||||
rowBytes := w * bytesPerPixel
|
||||
total := rowBytes * h
|
||||
if cap(z.scratch) < total {
|
||||
z.scratch = make([]byte, total)
|
||||
}
|
||||
scratch := z.scratch[:total]
|
||||
writePixels(scratch, img, pf, rect{x, y, w, h}, bytesPerPixel)
|
||||
for row := 0; row < h; row++ {
|
||||
if _, err := zw.Write(scratch[row*rowBytes : (row+1)*rowBytes]); err != nil {
|
||||
log.Debugf("zlib write row %d: %v", row, err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := zw.Flush(); err != nil {
|
||||
log.Debugf("zlib flush: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
compressed := zbuf.Bytes()
|
||||
|
||||
// Build the FramebufferUpdate message.
|
||||
buf := make([]byte, 4+12+4+len(compressed))
|
||||
buf[0] = serverFramebufferUpdate
|
||||
buf[1] = 0
|
||||
binary.BigEndian.PutUint16(buf[2:4], 1) // 1 rectangle
|
||||
|
||||
binary.BigEndian.PutUint16(buf[4:6], uint16(x))
|
||||
binary.BigEndian.PutUint16(buf[6:8], uint16(y))
|
||||
binary.BigEndian.PutUint16(buf[8:10], uint16(w))
|
||||
binary.BigEndian.PutUint16(buf[10:12], uint16(h))
|
||||
binary.BigEndian.PutUint32(buf[12:16], uint32(encZlib))
|
||||
binary.BigEndian.PutUint32(buf[16:20], uint32(len(compressed)))
|
||||
copy(buf[20:], compressed)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
// diffTiles compares two RGBA images and returns a tile-ordered list of
|
||||
// dirty tiles, one entry per tile. Tile order is top-to-bottom, left-to-
|
||||
@@ -565,219 +514,6 @@ func encodePixel(dst []byte, pf clientPixelFormat, r, g, b byte) int {
|
||||
return bytesPerPixel
|
||||
}
|
||||
|
||||
// encodeHextileSolidRect emits a Hextile-encoded rectangle whose every pixel
|
||||
// is the same color. All sub-tiles after the first inherit the background
|
||||
// via a zero subencoding byte, collapsing a uniform 64×64 tile from ~16 KB
|
||||
// raw (or ~1-2 KB zlib) down to ~20 bytes on the wire.
|
||||
//
|
||||
// The returned buffer starts with the 12-byte rect header + the hextile
|
||||
// body. Callers assembling a multi-rect FramebufferUpdate append this after
|
||||
// their own message header.
|
||||
func encodeHextileSolidRect(r, g, b byte, pf clientPixelFormat, rc rect) []byte {
|
||||
bytesPerPixel := max(int(pf.bpp)/8, 1)
|
||||
|
||||
// Count sub-tiles. Right/bottom sub-tiles may be smaller than 16.
|
||||
cols := (rc.w + hextileSubSize - 1) / hextileSubSize
|
||||
rows := (rc.h + hextileSubSize - 1) / hextileSubSize
|
||||
subs := cols * rows
|
||||
|
||||
// Body: first sub-tile carries (subenc 0x02 + bg pixel); the rest are
|
||||
// subenc 0x00 (inherit the previously-emitted background).
|
||||
bodySize := 1 + bytesPerPixel + (subs - 1)
|
||||
buf := make([]byte, 12+bodySize)
|
||||
|
||||
binary.BigEndian.PutUint16(buf[0:2], uint16(rc.x))
|
||||
binary.BigEndian.PutUint16(buf[2:4], uint16(rc.y))
|
||||
binary.BigEndian.PutUint16(buf[4:6], uint16(rc.w))
|
||||
binary.BigEndian.PutUint16(buf[6:8], uint16(rc.h))
|
||||
binary.BigEndian.PutUint32(buf[8:12], uint32(encHextile))
|
||||
|
||||
buf[12] = hextileBackgroundSpecified
|
||||
encodePixel(buf[13:13+bytesPerPixel], pf, r, g, b)
|
||||
// Remaining sub-tiles are already zero-valued from make(): "same as
|
||||
// previous background", no pixel bytes.
|
||||
_ = subs
|
||||
return buf
|
||||
}
|
||||
|
||||
// encodeHextileRect emits a full Hextile-encoded rectangle. Each 16×16
|
||||
// sub-tile is classified as 1-color (background only), 2-color (background
|
||||
// + foreground subrects), or raw. The 1-color and 2-color paths are
|
||||
// significantly cheaper than zlib on UI content (text, icons, flat
|
||||
// backgrounds) and avoid the persistent zlib stream's inter-rect
|
||||
// serialization point, so they parallelize trivially.
|
||||
//
|
||||
// The returned buffer starts with the 12-byte rect header + hextile body.
|
||||
func encodeHextileRect(img *image.RGBA, pf clientPixelFormat, x, y, w, h int) []byte {
|
||||
bytesPerPixel := max(int(pf.bpp)/8, 1)
|
||||
|
||||
// Pre-size: worst case is every sub-tile raw → 1 header byte + raw
|
||||
// pixels per sub-tile.
|
||||
maxBody := 0
|
||||
for sy := 0; sy < h; sy += hextileSubSize {
|
||||
sh := min(hextileSubSize, h-sy)
|
||||
for sx := 0; sx < w; sx += hextileSubSize {
|
||||
sw := min(hextileSubSize, w-sx)
|
||||
maxBody += 1 + sw*sh*bytesPerPixel
|
||||
}
|
||||
}
|
||||
buf := make([]byte, 12, 12+maxBody)
|
||||
|
||||
binary.BigEndian.PutUint16(buf[0:2], uint16(x))
|
||||
binary.BigEndian.PutUint16(buf[2:4], uint16(y))
|
||||
binary.BigEndian.PutUint16(buf[4:6], uint16(w))
|
||||
binary.BigEndian.PutUint16(buf[6:8], uint16(h))
|
||||
binary.BigEndian.PutUint32(buf[8:12], uint32(encHextile))
|
||||
|
||||
var state hextileBgState
|
||||
|
||||
for sy := 0; sy < h; sy += hextileSubSize {
|
||||
sh := min(hextileSubSize, h-sy)
|
||||
for sx := 0; sx < w; sx += hextileSubSize {
|
||||
sw := min(hextileSubSize, w-sx)
|
||||
buf = appendHextileSubtile(buf, img, pf, rect{x + sx, y + sy, sw, sh}, &state, bytesPerPixel)
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// hextileBgState carries the running background across sub-tile encodes so
|
||||
// we can omit the BackgroundSpecified flag when it hasn't changed.
|
||||
type hextileBgState struct {
|
||||
prev uint32
|
||||
valid bool
|
||||
}
|
||||
|
||||
// appendHextileSubtile encodes a single 16×16 (or smaller edge) sub-tile
|
||||
// onto buf.
|
||||
func appendHextileSubtile(buf []byte, img *image.RGBA, pf clientPixelFormat, rc rect, state *hextileBgState, bytesPerPixel int) []byte {
|
||||
x, y, w, h := rc.x, rc.y, rc.w, rc.h
|
||||
c0, c1, only2, c0Count, c1Count := classifySubtile(img, x, y, w, h)
|
||||
|
||||
if !only2 {
|
||||
// >2 distinct colours: raw fallback.
|
||||
buf = append(buf, hextileRaw)
|
||||
buf = appendRawPixels(buf, img, pf, rc, bytesPerPixel)
|
||||
state.valid = false
|
||||
return buf
|
||||
}
|
||||
|
||||
if c1Count == 0 {
|
||||
// Single colour. Background only.
|
||||
if state.valid && state.prev == c0 {
|
||||
return append(buf, 0)
|
||||
}
|
||||
buf = append(buf, hextileBackgroundSpecified)
|
||||
buf = appendPackedPixelFromRGBA(buf, pf, c0, bytesPerPixel)
|
||||
state.prev = c0
|
||||
state.valid = true
|
||||
return buf
|
||||
}
|
||||
|
||||
// Two colours. Background = majority; foreground = minority,
|
||||
// emitted as 1-row subrects of fg runs.
|
||||
bg, fg := c0, c1
|
||||
if c1Count > c0Count {
|
||||
bg, fg = c1, c0
|
||||
}
|
||||
subrects := collectFgSubrects(img, x, y, w, h, bg)
|
||||
// Cap at 255 (the count is a uint8). On overflow fall through to
|
||||
// raw: that's the simplest correct fallback.
|
||||
if len(subrects) <= 255 {
|
||||
flags := byte(hextileForegroundSpecified | hextileAnySubrects)
|
||||
emitBg := !state.valid || state.prev != bg
|
||||
if emitBg {
|
||||
flags |= hextileBackgroundSpecified
|
||||
}
|
||||
buf = append(buf, flags)
|
||||
if emitBg {
|
||||
buf = appendPackedPixelFromRGBA(buf, pf, bg, bytesPerPixel)
|
||||
state.prev = bg
|
||||
state.valid = true
|
||||
}
|
||||
buf = appendPackedPixelFromRGBA(buf, pf, fg, bytesPerPixel)
|
||||
buf = append(buf, byte(len(subrects)))
|
||||
for _, sr := range subrects {
|
||||
buf = append(buf, byte((sr[0]<<4)|sr[1]), byte(((sr[2]-1)<<4)|(sr[3]-1)))
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// Raw fallback.
|
||||
buf = append(buf, hextileRaw)
|
||||
buf = appendRawPixels(buf, img, pf, rc, bytesPerPixel)
|
||||
// Raw sub-tiles invalidate the persistent background.
|
||||
state.valid = false
|
||||
return buf
|
||||
}
|
||||
|
||||
// classifySubtile scans the sub-tile and reports up to two distinct pixel
|
||||
// values plus their counts. only2 is false the moment a third distinct
|
||||
// colour is seen, in which case the caller falls back to raw.
|
||||
func classifySubtile(img *image.RGBA, x, y, w, h int) (c0, c1 uint32, only2 bool, c0Count, c1Count int) {
|
||||
stride := img.Stride
|
||||
base := y*stride + x*4
|
||||
c0 = *(*uint32)(unsafe.Pointer(&img.Pix[base]))
|
||||
only2 = true
|
||||
for row := 0; row < h; row++ {
|
||||
p := base + row*stride
|
||||
for col := 0; col < w; col++ {
|
||||
px := *(*uint32)(unsafe.Pointer(&img.Pix[p+col*4]))
|
||||
switch {
|
||||
case px == c0:
|
||||
c0Count++
|
||||
case c1Count == 0:
|
||||
c1 = px
|
||||
c1Count = 1
|
||||
case px == c1:
|
||||
c1Count++
|
||||
default:
|
||||
return c0, c1, false, 0, 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return c0, c1, only2, c0Count, c1Count
|
||||
}
|
||||
|
||||
// collectFgSubrects walks the sub-tile row by row, emitting one subrect per
|
||||
// horizontal run of pixels not equal to bg. Each subrect is [subX, subY,
|
||||
// width, height] with width/height in 1..16.
|
||||
func collectFgSubrects(img *image.RGBA, x, y, w, h int, bg uint32) [][4]int {
|
||||
stride := img.Stride
|
||||
var out [][4]int
|
||||
for row := 0; row < h; row++ {
|
||||
p := y*stride + x*4 + row*stride
|
||||
col := 0
|
||||
for col < w {
|
||||
if *(*uint32)(unsafe.Pointer(&img.Pix[p+col*4])) == bg {
|
||||
col++
|
||||
continue
|
||||
}
|
||||
start := col
|
||||
for col < w && *(*uint32)(unsafe.Pointer(&img.Pix[p+col*4])) != bg {
|
||||
col++
|
||||
}
|
||||
out = append(out, [4]int{start, row, col - start, 1})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func appendPackedPixelFromRGBA(buf []byte, pf clientPixelFormat, px uint32, bytesPerPixel int) []byte {
|
||||
r := byte(px)
|
||||
g := byte(px >> 8)
|
||||
b := byte(px >> 16)
|
||||
var tmp [4]byte
|
||||
encodePixel(tmp[:], pf, r, g, b)
|
||||
return append(buf, tmp[:bytesPerPixel]...)
|
||||
}
|
||||
|
||||
func appendRawPixels(buf []byte, img *image.RGBA, pf clientPixelFormat, rc rect, bytesPerPixel int) []byte {
|
||||
start := len(buf)
|
||||
buf = append(buf, make([]byte, rc.w*rc.h*bytesPerPixel)...)
|
||||
writePixels(buf[start:], img, pf, rc, bytesPerPixel)
|
||||
return buf
|
||||
}
|
||||
|
||||
// tightState holds the per-session JPEG scratch buffer and reused encoders
|
||||
// so per-rect encoding stays alloc-free in the steady state.
|
||||
|
||||
Reference in New Issue
Block a user