Move image encoding logic into a central EncodeManager class
This allows us to apply a lot more server logic
independently of which encoder is in use.
Most of this class are things moved over from the
Tight encoder.
diff --git a/common/rfb/TightEncoder.cxx b/common/rfb/TightEncoder.cxx
index cdc23c4..3846ae0 100644
--- a/common/rfb/TightEncoder.cxx
+++ b/common/rfb/TightEncoder.cxx
@@ -1,5 +1,6 @@
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
* Copyright (C) 2011 D. R. Commander. All Rights Reserved.
+ * Copyright 2014 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,399 +17,202 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
+#include <assert.h>
+
#include <rdr/OutStream.h>
#include <rfb/PixelBuffer.h>
+#include <rfb/Palette.h>
#include <rfb/encodings.h>
#include <rfb/ConnParams.h>
-#include <rfb/SMsgWriter.h>
#include <rfb/SConnection.h>
#include <rfb/TightEncoder.h>
+#include <rfb/TightConstants.h>
using namespace rfb;
-// Minimum amount of data to be compressed. This value should not be
-// changed, doing so will break compatibility with existing clients.
-#define TIGHT_MIN_TO_COMPRESS 12
-
-// Adjustable parameters.
-// FIXME: Get rid of #defines
-#define TIGHT_MAX_SPLIT_TILE_SIZE 16
-#define TIGHT_MIN_SPLIT_RECT_SIZE 4096
-#define TIGHT_MIN_SOLID_SUBRECT_SIZE 2048
+struct TightConf {
+ int idxZlibLevel, monoZlibLevel, rawZlibLevel;
+};
//
-// 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).
+// Compression level stuff. The following array contains zlib
+// settings for each of 10 compression levels (0..9).
//
// 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
-// average compression ratios listed below, as measured across the set of
-// every 10th frame in the SPECviewperf 9 benchmark suite.
-//
-// 9 = JPEG quality 100, no subsampling (ratio ~= 10:1)
-// [this should be lossless, except for round-off error]
-// 8 = JPEG quality 92, no subsampling (ratio ~= 20:1)
-// [this should be perceptually lossless, based on current research]
-// 7 = JPEG quality 86, no subsampling (ratio ~= 25:1)
-// 6 = JPEG quality 79, no subsampling (ratio ~= 30:1)
-// 5 = JPEG quality 77, 4:2:2 subsampling (ratio ~= 40:1)
-// 4 = JPEG quality 62, 4:2:2 subsampling (ratio ~= 50:1)
-// 3 = JPEG quality 42, 4:2:2 subsampling (ratio ~= 60:1)
-// 2 = JPEG quality 41, 4:2:0 subsampling (ratio ~= 70:1)
-// 1 = JPEG quality 29, 4:2:0 subsampling (ratio ~= 80:1)
-// 0 = JPEG quality 15, 4:2:0 subsampling (ratio ~= 100:1)
-
-const TIGHT_CONF TightEncoder::conf[10] = {
- { 65536, 2048, 6, 0, 0, 0, 4, 24, 15, subsample4X }, // 0
- { 65536, 2048, 6, 1, 1, 1, 8, 24, 29, subsample4X }, // 1
- { 65536, 2048, 8, 3, 3, 2, 24, 96, 41, subsample4X }, // 2
- { 65536, 2048, 12, 5, 5, 2, 32, 96, 42, subsample2X }, // 3
- { 65536, 2048, 12, 6, 7, 3, 32, 96, 62, subsample2X }, // 4
- { 65536, 2048, 12, 7, 8, 4, 32, 96, 77, subsample2X }, // 5
- { 65536, 2048, 16, 7, 8, 5, 32, 96, 79, subsampleNone }, // 6
- { 65536, 2048, 16, 8, 9, 6, 64, 96, 86, subsampleNone }, // 7
- { 65536, 2048, 24, 9, 9, 7, 64, 96, 92, subsampleNone }, // 8
- { 65536, 2048, 32, 9, 9, 9, 96, 96,100, subsampleNone } // 9
+static const TightConf conf[10] = {
+ { 0, 0, 0 }, // 0
+ { 1, 1, 1 }, // 1
+ { 3, 3, 2 }, // 2
+ { 5, 5, 2 }, // 3
+ { 6, 7, 3 }, // 4
+ { 7, 8, 4 }, // 5
+ { 7, 8, 5 }, // 6
+ { 8, 9, 6 }, // 7
+ { 9, 9, 7 }, // 8
+ { 9, 9, 9 } // 9
};
-const int TightEncoder::defaultCompressLevel = 2;
-
-//
-// Including BPP-dependent implementation of the encoder.
-//
-
-#define BPP 8
-#include <rfb/tightEncode.h>
-#undef BPP
-#define BPP 16
-#include <rfb/tightEncode.h>
-#undef BPP
-#define BPP 32
-#include <rfb/tightEncode.h>
-#undef BPP
-
-TightEncoder::TightEncoder(SConnection* conn) : Encoder(conn)
+TightEncoder::TightEncoder(SConnection* conn) :
+ Encoder(conn, encodingTight, EncoderPlain, 256)
{
- setCompressLevel(defaultCompressLevel);
- setQualityLevel(-1);
+ setCompressLevel(-1);
}
TightEncoder::~TightEncoder()
{
}
+bool TightEncoder::isSupported()
+{
+ return conn->cp.supportsEncoding(encodingTight);
+}
+
void TightEncoder::setCompressLevel(int level)
{
- if (level >= 0 && level <= 9) {
- pconf = &conf[level];
- } else {
- pconf = &conf[defaultCompressLevel];
- }
+ if (level < 0 || level > 9)
+ level = 2;
+
+ idxZlibLevel = conf[level].idxZlibLevel;
+ monoZlibLevel = conf[level].idxZlibLevel;
+ rawZlibLevel = conf[level].rawZlibLevel;
}
-void TightEncoder::setQualityLevel(int level)
+void TightEncoder::writeRect(const PixelBuffer* pb, const Palette& palette)
{
- if (level >= 0 && level <= 9) {
- jpegQuality = conf[level].jpegQuality;
- jpegSubsampling = conf[level].jpegSubsampling;
- } else {
- jpegQuality = -1;
- jpegSubsampling = subsampleUndefined;
- }
-}
-
-void TightEncoder::setFineQualityLevel(int quality, int subsampling)
-{
- jpegQuality = quality;
- jpegSubsampling = subsampling;
-}
-
-bool TightEncoder::checkSolidTile(Rect& r, rdr::U32* colorPtr,
- bool needSameColor)
-{
- switch (serverpf.bpp) {
- case 32:
- return checkSolidTile32(r, colorPtr, needSameColor);
- case 16:
- return checkSolidTile16(r, colorPtr, needSameColor);
+ switch (palette.size()) {
+ case 0:
+ writeFullColourRect(pb, palette);
+ break;
+ case 1:
+ Encoder::writeSolidRect(pb, palette);
+ break;
+ case 2:
+ writeMonoRect(pb, palette);
+ break;
default:
- return checkSolidTile8(r, colorPtr, needSameColor);
+ writeIndexedRect(pb, palette);
}
}
-void TightEncoder::findBestSolidArea(Rect& r, rdr::U32 colorValue, Rect& bestr)
+void TightEncoder::writeSolidRect(int width, int height,
+ const PixelFormat& pf,
+ const rdr::U8* colour)
{
- int dx, dy, dw, dh;
- int w_prev;
- Rect sr;
- int w_best = 0, h_best = 0;
+ rdr::OutStream* os;
- bestr.tl.x = bestr.br.x = r.tl.x;
- bestr.tl.y = bestr.br.y = r.tl.y;
+ os = conn->getOutStream();
- 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, &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, &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;
+ os->writeU8(tightFill << 4);
+ writePixels(colour, pf, 1, os);
}
-void TightEncoder::extendSolidArea(const Rect& r, rdr::U32 colorValue,
- Rect& er)
+void TightEncoder::writeMonoRect(const PixelBuffer* pb, const Palette& palette)
{
- int cx, cy;
- Rect sr;
+ const rdr::U8* buffer;
+ int stride;
- // 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, &colorValue, true))
- break;
- }
- er.tl.y = cy + 1;
+ buffer = pb->getBuffer(pb->getRect(), &stride);
- // ... downwards.
- for (cy = er.br.y; ; cy++) {
- sr.setXYWH(er.tl.x, cy, er.width(), 1);
- if (cy >= r.br.y || !checkSolidTile(sr, &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, &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, &colorValue, true))
- break;
- }
- er.br.x = cx;
-}
-
-int TightEncoder::getNumRects(const Rect &r)
-{
- ConnParams* cp = &conn->cp;
- 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)
- return 1;
-
- // Compute max sub-rectangle size.
- const unsigned int subrectMaxWidth =
- (w > pconf->maxRectWidth) ? pconf->maxRectWidth : w;
- const unsigned int subrectMaxHeight =
- pconf->maxRectSize / subrectMaxWidth;
-
- // Return the number of subrects.
- return (((w - 1) / pconf->maxRectWidth + 1) *
- ((h - 1) / subrectMaxHeight + 1));
-}
-
-void TightEncoder::sendRectSimple(const Rect& r)
-{
- // Shortcuts to rectangle coordinates and dimensions.
- const int x = r.tl.x;
- const int y = r.tl.y;
- const unsigned int w = r.width();
- const unsigned int h = r.height();
-
- // Encode small rects as is.
- bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
- if (!rectTooBig) {
- writeSubrect(r);
- return;
- }
-
- // Compute max sub-rectangle size.
- const unsigned int subrectMaxWidth =
- (w > pconf->maxRectWidth) ? pconf->maxRectWidth : w;
- const unsigned int subrectMaxHeight =
- pconf->maxRectSize / subrectMaxWidth;
-
- // Split big rects into separately encoded subrects.
- Rect sr;
- unsigned int dx, dy, sw, sh;
- for (dy = 0; dy < h; dy += subrectMaxHeight) {
- for (dx = 0; dx < w; dx += pconf->maxRectWidth) {
- sw = (dx + pconf->maxRectWidth < w) ? pconf->maxRectWidth : w - dx;
- sh = (dy + subrectMaxHeight < h) ? subrectMaxHeight : h - dy;
- sr.setXYWH(x + dx, y + dy, sw, sh);
- writeSubrect(sr);
- }
- }
-}
-
-void TightEncoder::writeRect(const Rect& _r, PixelBuffer* _pb)
-{
- pb = _pb;
- serverpf = pb->getPF();
- ConnParams* cp = &conn->cp;
- clientpf = cp->pf();
-
- // Shortcuts to rectangle coordinates and dimensions.
- Rect r = _r;
- int x = r.tl.x;
- int y = r.tl.y;
- int w = r.width();
- int h = r.height();
-
- // Encode small rects as is.
- if (!cp->supportsLastRect || w * h < TIGHT_MIN_SPLIT_RECT_SIZE) {
- sendRectSimple(r);
- return;
- }
-
- // Split big rects into separately encoded subrects.
- Rect sr, bestr;
- int dx, dy, dw, dh;
- rdr::U32 colorValue;
- int maxRectWidth = pconf->maxRectWidth;
- int nMaxWidth = (w > maxRectWidth) ? maxRectWidth : w;
- int nMaxRows = 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);
- 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, &colorValue, false)) {
-
- if (jpegSubsampling == subsampleGray && jpegQuality != -1) {
- rdr::U16 r, g, b;
- serverpf.rgbFromPixel(colorValue, &r, &g, &b);
- rdr::U32 lum = ((257 * r) + (504 * g) + (98 * b)
- + 16500) / 1000;
- colorValue = lum + (lum << 8) + (lum << 16);
- }
-
- // Get dimensions of solid-color area.
- sr.setXYWH(dx, dy, r.br.x - dx, r.br.y - dy);
- findBestSolidArea(sr, 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, 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);
- }
- if (bestr.tl.x != x) {
- sr.setXYWH(x, bestr.tl.y, bestr.tl.x - x, bestr.height());
- writeRect(sr, _pb);
- }
-
- // Send solid-color rectangle.
- writeSubrect(bestr, 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, _pb);
- }
- if (bestr.br.y != r.br.y) {
- sr.setXYWH(x, bestr.br.y, w, r.br.y - bestr.br.y);
- writeRect(sr, _pb);
- }
-
- return;
- }
- }
- }
-
- // No suitable solid-color rectangles found.
- sendRectSimple(r);
- return;
-}
-
-void TightEncoder::writeSubrect(const Rect& r, bool forceSolid)
-{
- mos.clear();
-
- switch (clientpf.bpp) {
- case 8:
- tightEncode8(r, &mos, forceSolid); break;
- case 16:
- tightEncode16(r, &mos, forceSolid); break;
+ switch (pb->getPF().bpp) {
case 32:
- tightEncode32(r, &mos, forceSolid); break;
+ writeMonoRect(pb->width(), pb->height(), (rdr::U32*)buffer, stride,
+ pb->getPF(), palette);
+ break;
+ case 16:
+ writeMonoRect(pb->width(), pb->height(), (rdr::U16*)buffer, stride,
+ pb->getPF(), palette);
+ break;
+ default:
+ writeMonoRect(pb->width(), pb->height(), (rdr::U8*)buffer, stride,
+ pb->getPF(), palette);
+ }
+}
+
+void TightEncoder::writeIndexedRect(const PixelBuffer* pb, const Palette& palette)
+{
+ const rdr::U8* buffer;
+ int stride;
+
+ buffer = pb->getBuffer(pb->getRect(), &stride);
+
+ switch (pb->getPF().bpp) {
+ case 32:
+ writeIndexedRect(pb->width(), pb->height(), (rdr::U32*)buffer, stride,
+ pb->getPF(), palette);
+ break;
+ case 16:
+ writeIndexedRect(pb->width(), pb->height(), (rdr::U16*)buffer, stride,
+ pb->getPF(), palette);
+ break;
+ default:
+ // It's more efficient to just do raw pixels
+ writeFullColourRect(pb, palette);
+ }
+}
+
+void TightEncoder::writeFullColourRect(const PixelBuffer* pb, const Palette& palette)
+{
+ const int streamId = 0;
+
+ rdr::OutStream* os;
+ rdr::OutStream* zos;
+ int length;
+
+ const rdr::U8* buffer;
+ int stride, w, h;
+
+ os = conn->getOutStream();
+
+ os->writeU8(streamId << 4);
+
+ // Set up compression
+ if ((pb->getPF().bpp != 32) || !pb->getPF().is888())
+ length = pb->getRect().area() * pb->getPF().bpp/8;
+ else
+ length = pb->getRect().area() * 3;
+
+ zos = getZlibOutStream(streamId, rawZlibLevel, length);
+
+ // And then just dump all the raw pixels
+ buffer = pb->getBuffer(pb->getRect(), &stride);
+ h = pb->height();
+
+ while (h--) {
+ writePixels(buffer, pb->getPF(), pb->width(), zos);
+ buffer += stride * pb->getPF().bpp/8;
}
- conn->writer()->startRect(r, encodingTight);
- rdr::OutStream* os = conn->getOutStream();
- os->writeBytes(mos.data(), mos.length());
- conn->writer()->endRect();
+ // Finish the zlib stream
+ flushZlibOutStream(zos);
+}
+
+void TightEncoder::writePixels(const rdr::U8* buffer, const PixelFormat& pf,
+ unsigned int count, rdr::OutStream* os)
+{
+ rdr::U8 rgb[2048];
+
+ if ((pf.bpp != 32) || !pf.is888()) {
+ os->writeBytes(buffer, count * pf.bpp/8);
+ return;
+ }
+
+ while (count) {
+ int iter_count;
+
+ iter_count = sizeof(rgb)/3;
+ if (iter_count > count)
+ iter_count = count;
+
+ pf.rgbFromBuffer(rgb, buffer, iter_count);
+ os->writeBytes(rgb, iter_count * 3);
+
+ buffer += iter_count * pf.bpp/8;
+ count -= iter_count;
+ }
}
void TightEncoder::writeCompact(rdr::OutStream* os, rdr::U32 value)
@@ -428,3 +232,52 @@
}
}
}
+
+rdr::OutStream* TightEncoder::getZlibOutStream(int streamId, int level, size_t length)
+{
+ // Minimum amount of data to be compressed. This value should not be
+ // changed, doing so will break compatibility with existing clients.
+ if (length < 12)
+ return conn->getOutStream();
+
+ assert(streamId >= 0);
+ assert(streamId < 4);
+
+ zlibStreams[streamId].setUnderlying(&memStream);
+ zlibStreams[streamId].setCompressionLevel(level);
+
+ return &zlibStreams[streamId];
+}
+
+void TightEncoder::flushZlibOutStream(rdr::OutStream* os_)
+{
+ rdr::OutStream* os;
+ rdr::ZlibOutStream* zos;
+
+ zos = dynamic_cast<rdr::ZlibOutStream*>(os_);
+ if (zos == NULL)
+ return;
+
+ zos->flush();
+ zos->setUnderlying(NULL);
+
+ os = conn->getOutStream();
+
+ writeCompact(os, memStream.length());
+ os->writeBytes(memStream.data(), memStream.length());
+ memStream.clear();
+}
+
+//
+// Including BPP-dependent implementation of the encoder.
+//
+
+#define BPP 8
+#include <rfb/TightEncoderBPP.cxx>
+#undef BPP
+#define BPP 16
+#include <rfb/TightEncoderBPP.cxx>
+#undef BPP
+#define BPP 32
+#include <rfb/TightEncoderBPP.cxx>
+#undef BPP