Ported encoding optimizations from TurboVNC. The changes to the Tight parameters were determined through extensive low-level profiling (see http://www.virtualgl.org/pmwiki/uploads/About/turbototiger.pdf). The other enhancements involved: (1) porting the solid subrectangle pre-computation code from TightVNC/TurboVNC (it makes a pretty big difference-- see report), (2) encapsulating the JPEG encoder in its own class (this eliminates a buffer copy, and the JPEG buffer is now set to a decent size where it shouldn't ever need to be paged or re-allocated, except in rare corner cases), (3) adding support for last rect. encoding (necessary to support the solid rectangle pre-computation enhancements.
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4626 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/common/rfb/TightEncoder.cxx b/common/rfb/TightEncoder.cxx
index f9684b9..52673a6 100644
--- a/common/rfb/TightEncoder.cxx
+++ b/common/rfb/TightEncoder.cxx
@@ -1,4 +1,5 @@
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
+ * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,19 +31,19 @@
// Adjustable parameters.
// FIXME: Get rid of #defines
-#define TIGHT_JPEG_MIN_RECT_SIZE 1024
-#define TIGHT_DETECT_MIN_WIDTH 8
-#define TIGHT_DETECT_MIN_HEIGHT 8
+#define TIGHT_MAX_SPLIT_TILE_SIZE 16
+#define TIGHT_MIN_SPLIT_RECT_SIZE 4096
+#define TIGHT_MIN_SOLID_SUBRECT_SIZE 2048
//
// Compression level stuff. The following array contains various
// encoder parameters for each of 10 compression levels (0..9).
// Last three parameters correspond to JPEG quality levels (0..9).
//
-// NOTE: s_conf[9].maxRectSize should be >= s_conf[i].maxRectSize,
-// where i in [0..8]. RequiredBuffSize() method depends on this.
-// FIXME: Is this comment obsolete?
-//
+// NOTE: The parameters used in this encoder are the result of painstaking
+// research by The VirtualGL Project using RFB session captures from a variety
+// of both 2D and 3D applications. See http://www.VirtualGL.org for the full
+// reports.
// NOTE: The JPEG quality and subsampling levels below were obtained
// experimentally by the VirtualGL Project. They represent the approximate
@@ -63,18 +64,18 @@
// 0 = JPEG quality 15, 4:2:0 subsampling (ratio ~= 100:1)
const TIGHT_CONF TightEncoder::conf[10] = {
- { 512, 32, 6, 0, 0, 0, 4, 15, SUBSAMP_420 }, // 0
- { 2048, 64, 6, 1, 1, 1, 8, 29, SUBSAMP_420 }, // 1
- { 4096, 128, 8, 3, 3, 2, 24, 41, SUBSAMP_420 }, // 2
- { 8192, 256, 12, 5, 5, 2, 32, 42, SUBSAMP_422 }, // 3
- { 16384, 512, 12, 6, 7, 3, 32, 62, SUBSAMP_422 }, // 4
- { 32768, 512, 12, 7, 8, 4, 32, 77, SUBSAMP_422 }, // 5
- { 65536, 1024, 16, 7, 8, 5, 32, 79, SUBSAMP_NONE }, // 6
- { 65536, 1024, 16, 8, 9, 6, 64, 86, SUBSAMP_NONE }, // 7
- { 65536, 2048, 24, 9, 9, 7, 64, 92, SUBSAMP_NONE }, // 8
- { 65536, 2048, 32, 9, 9, 9, 96,100, SUBSAMP_NONE } // 9
+ { 65536, 2048, 6, 0, 0, 0, 4, 24, 15, SUBSAMP_420 }, // 0
+ { 65536, 2048, 6, 1, 1, 1, 8, 24, 29, SUBSAMP_420 }, // 1
+ { 65536, 2048, 8, 3, 3, 2, 24, 96, 41, SUBSAMP_420 }, // 2
+ { 65536, 2048, 12, 5, 5, 2, 32, 96, 42, SUBSAMP_422 }, // 3
+ { 65536, 2048, 12, 6, 7, 3, 32, 96, 62, SUBSAMP_422 }, // 4
+ { 65536, 2048, 12, 7, 8, 4, 32, 96, 77, SUBSAMP_422 }, // 5
+ { 65536, 2048, 16, 7, 8, 5, 32, 96, 79, SUBSAMP_NONE }, // 6
+ { 65536, 2048, 16, 8, 9, 6, 64, 96, 86, SUBSAMP_NONE }, // 7
+ { 65536, 2048, 24, 9, 9, 7, 64, 96, 92, SUBSAMP_NONE }, // 8
+ { 65536, 2048, 32, 9, 9, 9, 96, 96,100, SUBSAMP_NONE } // 9
};
-const int TightEncoder::defaultCompressLevel = 6;
+const int TightEncoder::defaultCompressLevel = 1;
// FIXME: Not good to mirror TightEncoder's members here.
static const TIGHT_CONF* s_pconf;
@@ -129,11 +130,114 @@
}
}
+bool TightEncoder::checkSolidTile(Rect& r, ImageGetter *ig, rdr::U32* colorPtr,
+ bool needSameColor)
+{
+ switch (writer->bpp()) {
+ case 32:
+ return checkSolidTile32(r, ig, writer, colorPtr, needSameColor);
+ case 16:
+ return checkSolidTile16(r, ig, writer, colorPtr, needSameColor);
+ default:
+ return checkSolidTile8(r, ig, writer, colorPtr, needSameColor);
+ }
+}
+
+void TightEncoder::findBestSolidArea(Rect& r, ImageGetter *ig,
+ rdr::U32 colorValue, Rect& bestr)
+{
+ int dx, dy, dw, dh;
+ int w_prev;
+ Rect sr;
+ int w_best = 0, h_best = 0;
+
+ bestr.tl.x = bestr.br.x = r.tl.x;
+ bestr.tl.y = bestr.br.y = r.tl.y;
+
+ w_prev = r.width();
+
+ for (dy = r.tl.y; dy < r.br.y; dy += TIGHT_MAX_SPLIT_TILE_SIZE) {
+
+ dh = (dy + TIGHT_MAX_SPLIT_TILE_SIZE <= r.br.y) ?
+ TIGHT_MAX_SPLIT_TILE_SIZE : (r.br.y - dy);
+ dw = (w_prev > TIGHT_MAX_SPLIT_TILE_SIZE) ?
+ TIGHT_MAX_SPLIT_TILE_SIZE : w_prev;
+
+ sr.setXYWH(r.tl.x, dy, dw, dh);
+ if (!checkSolidTile(sr, ig, &colorValue, true))
+ break;
+
+ for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
+ dw = (dx + TIGHT_MAX_SPLIT_TILE_SIZE <= r.tl.x + w_prev) ?
+ TIGHT_MAX_SPLIT_TILE_SIZE : (r.tl.x + w_prev - dx);
+ sr.setXYWH(dx, dy, dw, dh);
+ if (!checkSolidTile(sr, ig, &colorValue, true))
+ break;
+ dx += dw;
+ }
+
+ w_prev = dx - r.tl.x;
+ if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
+ w_best = w_prev;
+ h_best = dy + dh - r.tl.y;
+ }
+ }
+
+ bestr.br.x = bestr.tl.x + w_best;
+ bestr.br.y = bestr.tl.y + h_best;
+}
+
+void TightEncoder::extendSolidArea(const Rect& r, ImageGetter *ig,
+ rdr::U32 colorValue, Rect& er)
+{
+ int cx, cy;
+ Rect sr;
+
+ // Try to extend the area upwards.
+ for (cy = er.tl.y - 1; ; cy--) {
+ sr.setXYWH(er.tl.x, cy, er.width(), 1);
+ if (cy < r.tl.y || !checkSolidTile(sr, ig, &colorValue, true))
+ break;
+ }
+ er.tl.y = cy + 1;
+
+ // ... downwards.
+ for (cy = er.br.y; ; cy++) {
+ sr.setXYWH(er.tl.x, cy, er.width(), 1);
+ if (cy >= r.br.y || !checkSolidTile(sr, ig, &colorValue, true))
+ break;
+ }
+ er.br.y = cy;
+
+ // ... to the left.
+ for (cx = er.tl.x - 1; ; cx--) {
+ sr.setXYWH(cx, er.tl.y, 1, er.height());
+ if (cx < r.tl.x || !checkSolidTile(sr, ig, &colorValue, true))
+ break;
+ }
+ er.tl.x = cx + 1;
+
+ // ... to the right.
+ for (cx = er.br.x; ; cx++) {
+ sr.setXYWH(cx, er.tl.y, 1, er.height());
+ if (cx >= r.br.x || !checkSolidTile(sr, ig, &colorValue, true))
+ break;
+ }
+ er.br.x = cx;
+}
+
int TightEncoder::getNumRects(const Rect &r)
{
+ ConnParams* cp = writer->getConnParams();
const unsigned int w = r.width();
const unsigned int h = r.height();
+ // If last rect. encoding is enabled, we can use the higher-performance
+ // code that pre-computes solid rectangles. In that case, we don't care
+ // about the rectangle count.
+ if (cp->supportsLastRect && w * h >= TIGHT_MIN_SPLIT_RECT_SIZE)
+ return 0;
+
// Will this rectangle split into subrects?
bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
if (!rectTooBig)
@@ -150,7 +254,7 @@
((h - 1) / subrectMaxHeight + 1));
}
-bool TightEncoder::writeRect(const Rect& r, ImageGetter* ig, Rect* actual)
+void TightEncoder::sendRectSimple(const Rect& r, ImageGetter* ig)
{
// Shortcuts to rectangle coordinates and dimensions.
const int x = r.tl.x;
@@ -158,15 +262,11 @@
const unsigned int w = r.width();
const unsigned int h = r.height();
- // Copy members of current TightEncoder instance to static variables.
- s_pconf = pconf;
- s_pjconf = pjconf;
-
// Encode small rects as is.
bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
if (!rectTooBig) {
writeSubrect(r, ig);
- return true;
+ return;
}
// Compute max sub-rectangle size.
@@ -186,10 +286,110 @@
writeSubrect(sr, ig);
}
}
+}
+
+bool TightEncoder::writeRect(const Rect& _r, ImageGetter* ig, Rect* actual)
+{
+ ConnParams* cp = writer->getConnParams();
+
+ // Shortcuts to rectangle coordinates and dimensions.
+ Rect r = _r;
+ int x = r.tl.x;
+ int y = r.tl.y;
+ unsigned int w = r.width();
+ unsigned int h = r.height();
+
+ // Copy members of current TightEncoder instance to static variables.
+ s_pconf = pconf;
+ s_pjconf = pjconf;
+
+ // Encode small rects as is.
+ if (!cp->supportsLastRect || w * h < TIGHT_MIN_SPLIT_RECT_SIZE) {
+ sendRectSimple(r, ig);
+ return true;
+ }
+
+ // Split big rects into separately encoded subrects.
+ Rect sr, bestr;
+ unsigned int dx, dy, dw, dh;
+ rdr::U32 colorValue;
+ int maxRectSize = s_pconf->maxRectSize;
+ int maxRectWidth = s_pconf->maxRectWidth;
+ int nMaxWidth = (w > maxRectWidth) ? maxRectWidth : w;
+ int nMaxRows = s_pconf->maxRectSize / nMaxWidth;
+
+ // Try to find large solid-color areas and send them separately.
+ for (dy = y; dy < y + h; dy += TIGHT_MAX_SPLIT_TILE_SIZE) {
+
+ // If a rectangle becomes too large, send its upper part now.
+ if (dy - y >= nMaxRows) {
+ sr.setXYWH(x, y, w, nMaxRows);
+ sendRectSimple(sr, ig);
+ r.tl.y += nMaxRows;
+ y = r.tl.y;
+ h = r.height();
+ }
+
+ dh = (dy + TIGHT_MAX_SPLIT_TILE_SIZE <= y + h) ?
+ TIGHT_MAX_SPLIT_TILE_SIZE : (y + h - dy);
+
+ for (dx = x; dx < x + w; dx += TIGHT_MAX_SPLIT_TILE_SIZE) {
+
+ dw = (dx + TIGHT_MAX_SPLIT_TILE_SIZE <= x + w) ?
+ TIGHT_MAX_SPLIT_TILE_SIZE : (x + w - dx);
+
+ sr.setXYWH(dx, dy, dw, dh);
+ if (checkSolidTile(sr, ig, &colorValue, false)) {
+
+ // Get dimensions of solid-color area.
+ sr.setXYWH(dx, dy, r.br.x - dx, r.br.y - dy);
+ findBestSolidArea(sr, ig, colorValue, bestr);
+
+ // Make sure a solid rectangle is large enough
+ // (or the whole rectangle is of the same color).
+ if (bestr.area() != r.area()
+ && bestr.area() < TIGHT_MIN_SOLID_SUBRECT_SIZE)
+ continue;
+
+ // Try to extend solid rectangle to maximum size.
+ extendSolidArea(r, ig, colorValue, bestr);
+
+ // Send rectangles at top and left to solid-color area.
+ if (bestr.tl.y != y) {
+ sr.setXYWH(x, y, w, bestr.tl.y - y);
+ sendRectSimple(sr, ig);
+ }
+ if (bestr.tl.x != x) {
+ sr.setXYWH(x, bestr.tl.y, bestr.tl.x - x, bestr.height());
+ writeRect(sr, ig, NULL);
+ }
+
+ // Send solid-color rectangle.
+ writeSubrect(bestr, ig, true);
+
+ // Send remaining rectangles (at right and bottom).
+ if (bestr.br.x != r.br.x) {
+ sr.setXYWH(bestr.br.x, bestr.tl.y, r.br.x - bestr.br.x,
+ bestr.height());
+ writeRect(sr, ig, NULL);
+ }
+ if (bestr.br.y != r.br.y) {
+ sr.setXYWH(x, bestr.br.y, w, r.br.y - bestr.br.y);
+ writeRect(sr, ig, NULL);
+ }
+
+ return true;
+ }
+ }
+ }
+
+ // No suitable solid-color rectangles found.
+ sendRectSimple(r, ig);
return true;
}
-void TightEncoder::writeSubrect(const Rect& r, ImageGetter* ig)
+void TightEncoder::writeSubrect(const Rect& r, ImageGetter* ig,
+ bool forceSolid)
{
rdr::U8* imageBuf = writer->getImageBuf(r.area());
ConnParams* cp = writer->getConnParams();
@@ -197,11 +397,11 @@
switch (writer->bpp()) {
case 8:
- tightEncode8(r, &mos, zos, imageBuf, cp, ig); break;
+ tightEncode8(r, &mos, zos, jc, imageBuf, cp, ig, forceSolid); break;
case 16:
- tightEncode16(r, &mos, zos, imageBuf, cp, ig); break;
+ tightEncode16(r, &mos, zos, jc, imageBuf, cp, ig, forceSolid); break;
case 32:
- tightEncode32(r, &mos, zos, imageBuf, cp, ig); break;
+ tightEncode32(r, &mos, zos, jc, imageBuf, cp, ig, forceSolid); break;
}
writer->startRect(r, encodingTight);