aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2015-02-13 13:46:16 +0100
committerPierre Ossman <ossman@cendio.se>2015-02-13 13:46:16 +0100
commitf481203dc078287447b4367769c890ab7815d7cf (patch)
tree9966c58ac0bae953c99c0502f2b563b331eeda8e
parentc4bc5a8f0eeacbebb50a2ef79c7b03b64586475a (diff)
parent4daa7b1172cd4606b60f8245831bc5aa155c4b9e (diff)
downloadtigervnc-f481203dc078287447b4367769c890ab7815d7cf.tar.gz
tigervnc-f481203dc078287447b4367769c890ab7815d7cf.zip
Merge branch 'perf' of https://github.com/CendioOssman/tigervnc
-rw-r--r--cmake/StaticBuild.cmake8
-rw-r--r--common/rdr/CMakeLists.txt1
-rw-r--r--common/rdr/FileInStream.cxx87
-rw-r--r--common/rdr/FileInStream.h50
-rw-r--r--common/rfb/CConnection.h3
-rw-r--r--common/rfb/CMakeLists.txt2
-rw-r--r--common/rfb/CMsgHandler.cxx8
-rw-r--r--common/rfb/CMsgHandler.h4
-rw-r--r--common/rfb/EncodeManager.cxx321
-rw-r--r--common/rfb/EncodeManager.h19
-rw-r--r--common/rfb/PixelBuffer.cxx32
-rw-r--r--common/rfb/SConnection.cxx5
-rw-r--r--common/rfb/SConnection.h10
-rw-r--r--common/rfb/SMsgHandler.cxx2
-rw-r--r--common/rfb/SMsgHandler.h2
-rw-r--r--common/rfb/SMsgWriter.cxx30
-rw-r--r--common/rfb/SMsgWriter.h13
-rw-r--r--common/rfb/VNCSConnectionST.cxx2
-rw-r--r--common/rfb/VNCSConnectionST.h1
-rw-r--r--tests/CMakeLists.txt6
-rw-r--r--tests/decperf.cxx217
-rw-r--r--tests/encperf.cxx439
-rw-r--r--tests/util.cxx79
-rw-r--r--tests/util.h10
24 files changed, 1193 insertions, 158 deletions
diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake
index 1ac4f6e3..0ec3361e 100644
--- a/cmake/StaticBuild.cmake
+++ b/cmake/StaticBuild.cmake
@@ -47,14 +47,18 @@ if(BUILD_STATIC)
set(GNUTLS_LIBRARIES "${GNUTLS_LIBRARIES} -Wl,-Bdynamic")
- # GnuTLS uses various crypto-api stuff
if (WIN32)
+ # GnuTLS uses various crypto-api stuff
set(GNUTLS_LIBRARIES "${GNUTLS_LIBRARIES} -lcrypt32")
+ # And sockets
+ set(GNUTLS_LIBRARIES "${GNUTLS_LIBRARIES} -lws2_32")
endif()
- # nanosleep() lives here on Solaris
if(${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
+ # nanosleep() lives here on Solaris
set(GNUTLS_LIBRARIES "${GNUTLS_LIBRARIES} -lrt")
+ # and socket functions are hidden here
+ set(GNUTLS_LIBRARIES "${GNUTLS_LIBRARIES} -lsocket")
endif()
# GnuTLS uses gettext and zlib, so make sure those are always
diff --git a/common/rdr/CMakeLists.txt b/common/rdr/CMakeLists.txt
index cc45f911..989ba2f4 100644
--- a/common/rdr/CMakeLists.txt
+++ b/common/rdr/CMakeLists.txt
@@ -4,6 +4,7 @@ add_library(rdr STATIC
Exception.cxx
FdInStream.cxx
FdOutStream.cxx
+ FileInStream.cxx
HexInStream.cxx
HexOutStream.cxx
InStream.cxx
diff --git a/common/rdr/FileInStream.cxx b/common/rdr/FileInStream.cxx
new file mode 100644
index 00000000..18a9169d
--- /dev/null
+++ b/common/rdr/FileInStream.cxx
@@ -0,0 +1,87 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright (C) 2013 D. R. Commander. All Rights Reserved.
+ * Copyright 2015 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#include <errno.h>
+
+#include <rdr/Exception.h>
+#include <rdr/FileInStream.h>
+
+using namespace rdr;
+
+FileInStream::FileInStream(const char *fileName)
+{
+ file = fopen(fileName, "rb");
+ if (!file)
+ throw SystemException("fopen", errno);
+ ptr = end = b;
+}
+
+FileInStream::~FileInStream(void) {
+ if (file) {
+ fclose(file);
+ file = NULL;
+ }
+}
+
+void FileInStream::reset(void) {
+ if (!file)
+ throw Exception("File is not open");
+ if (fseek(file, 0, SEEK_SET) != 0)
+ throw SystemException("fseek", errno);
+ ptr = end = b;
+}
+
+int FileInStream::pos()
+{
+ if (!file)
+ throw Exception("File is not open");
+
+ return ftell(file) + ptr - b;
+}
+
+int FileInStream::overrun(int itemSize, int nItems, bool wait)
+{
+ if (itemSize > sizeof(b))
+ throw Exception("FileInStream overrun: max itemSize exceeded");
+
+ if (end - ptr != 0)
+ memmove(b, ptr, end - ptr);
+
+ end -= ptr - b;
+ ptr = b;
+
+
+ while (end < b + itemSize) {
+ size_t n = fread((U8 *)end, b + sizeof(b) - end, 1, file);
+ if (n < 1) {
+ if (n < 0 || ferror(file))
+ throw Exception(strerror(errno));
+ if (feof(file))
+ throw EndOfStream();
+ if (n == 0) { return 0; }
+ }
+ end += b + sizeof(b) - end;
+ }
+
+ if (itemSize * nItems > end - ptr)
+ nItems = (end - ptr) / itemSize;
+
+ return nItems;
+}
diff --git a/common/rdr/FileInStream.h b/common/rdr/FileInStream.h
new file mode 100644
index 00000000..ace04f37
--- /dev/null
+++ b/common/rdr/FileInStream.h
@@ -0,0 +1,50 @@
+/* Copyright (C) 2013 D. R. Commander. All Rights Reserved.
+ * Copyright 2015 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+#ifndef __RDR_FILEINSTREAM_H__
+#define __RDR_FILEINSTREAM_H__
+
+#include <stdio.h>
+
+#include <rdr/InStream.h>
+
+namespace rdr {
+
+ class FileInStream : public InStream {
+
+ public:
+
+ FileInStream(const char *fileName);
+ ~FileInStream(void);
+
+ void reset(void);
+
+ int pos();
+
+ protected:
+ int overrun(int itemSize, int nItems, bool wait = true);
+
+ private:
+ U8 b[131072];
+ FILE *file;
+ };
+
+} // end of namespace rdr
+
+#endif
diff --git a/common/rfb/CConnection.h b/common/rfb/CConnection.h
index 8bd886aa..0109fe86 100644
--- a/common/rfb/CConnection.h
+++ b/common/rfb/CConnection.h
@@ -136,6 +136,9 @@ namespace rfb {
protected:
void setState(stateEnum s) { state_ = s; }
+ void setReader(CMsgReader *r) { reader_ = r; }
+ void setWriter(CMsgWriter *w) { writer_ = w; }
+
private:
// This is a default implementation of fences that automatically
// responds to requests, stating no support for synchronisation.
diff --git a/common/rfb/CMakeLists.txt b/common/rfb/CMakeLists.txt
index 6cd321e4..e9b027a1 100644
--- a/common/rfb/CMakeLists.txt
+++ b/common/rfb/CMakeLists.txt
@@ -67,7 +67,7 @@ if(WIN32)
set(RFB_SOURCES ${RFB_SOURCES} WinPasswdValidator.cxx)
endif(WIN32)
-set(RFB_LIBRARIES ${JPEG_LIBRARIES} os rdr)
+set(RFB_LIBRARIES ${JPEG_LIBRARIES} os rdr Xregion)
if(HAVE_PAM)
set(RFB_SOURCES ${RFB_SOURCES} UnixPasswordValidator.cxx
diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx
index eff47766..ce8b271f 100644
--- a/common/rfb/CMsgHandler.cxx
+++ b/common/rfb/CMsgHandler.cxx
@@ -74,3 +74,11 @@ void CMsgHandler::endOfContinuousUpdates()
{
cp.supportsContinuousUpdates = true;
}
+
+void CMsgHandler::framebufferUpdateStart()
+{
+}
+
+void CMsgHandler::framebufferUpdateEnd()
+{
+}
diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h
index 8e3c84ed..5e333d27 100644
--- a/common/rfb/CMsgHandler.h
+++ b/common/rfb/CMsgHandler.h
@@ -57,8 +57,8 @@ namespace rfb {
virtual void endOfContinuousUpdates();
virtual void serverInit() = 0;
- virtual void framebufferUpdateStart() = 0;
- virtual void framebufferUpdateEnd() = 0;
+ virtual void framebufferUpdateStart();
+ virtual void framebufferUpdateEnd();
virtual void dataRect(const Rect& r, int encoding) = 0;
virtual void setColourMapEntries(int firstColour, int nColours,
diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx
index ca60da48..e6323870 100644
--- a/common/rfb/EncodeManager.cxx
+++ b/common/rfb/EncodeManager.cxx
@@ -23,6 +23,7 @@
#include <rfb/SConnection.h>
#include <rfb/SMsgWriter.h>
#include <rfb/UpdateTracker.h>
+#include <rfb/LogWriter.h>
#include <rfb/RawEncoder.h>
#include <rfb/RREEncoder.h>
@@ -33,6 +34,8 @@
using namespace rfb;
+static LogWriter vlog("EncodeManager");
+
// Split each rectangle into smaller ones no larger than this area,
// and no wider than this width.
static const int SubRectMaxArea = 65536;
@@ -73,8 +76,50 @@ struct RectInfo {
};
+static const char *encoderClassName(EncoderClass klass)
+{
+ switch (klass) {
+ case encoderRaw:
+ return "Raw";
+ case encoderRRE:
+ return "RRE";
+ case encoderHextile:
+ return "Hextile";
+ case encoderTight:
+ return "Tight";
+ case encoderTightJPEG:
+ return "Tight (JPEG)";
+ case encoderZRLE:
+ return "ZRLE";
+ }
+
+ return "Unknown Encoder Class";
+}
+
+static const char *encoderTypeName(EncoderType type)
+{
+ switch (type) {
+ case encoderSolid:
+ return "Solid";
+ case encoderBitmap:
+ return "Bitmap";
+ case encoderBitmapRLE:
+ return "Bitmap RLE";
+ case encoderIndexed:
+ return "Indexed";
+ case encoderIndexedRLE:
+ return "Indexed RLE";
+ case encoderFullColour:
+ return "Full Colour";
+ }
+
+ return "Unknown Encoder Type";
+}
+
EncodeManager::EncodeManager(SConnection* conn_) : conn(conn_)
{
+ StatsVector::iterator iter;
+
encoders.resize(encoderClassMax, NULL);
activeEncoders.resize(encoderTypeMax, encoderRaw);
@@ -84,16 +129,78 @@ EncodeManager::EncodeManager(SConnection* conn_) : conn(conn_)
encoders[encoderTight] = new TightEncoder(conn);
encoders[encoderTightJPEG] = new TightJPEGEncoder(conn);
encoders[encoderZRLE] = new ZRLEEncoder(conn);
+
+ updates = 0;
+ stats.resize(encoderClassMax);
+ for (iter = stats.begin();iter != stats.end();++iter) {
+ StatsVector::value_type::iterator iter2;
+ iter->resize(encoderTypeMax);
+ for (iter2 = iter->begin();iter2 != iter->end();++iter2)
+ memset(&*iter2, 0, sizeof(EncoderStats));
+ }
}
EncodeManager::~EncodeManager()
{
std::vector<Encoder*>::iterator iter;
+ logStats();
+
for (iter = encoders.begin();iter != encoders.end();iter++)
delete *iter;
}
+void EncodeManager::logStats()
+{
+ int i, j;
+
+ unsigned rects;
+ unsigned long long pixels, bytes, equivalent;
+
+ double ratio;
+
+ rects = 0;
+ pixels = bytes = equivalent = 0;
+
+ vlog.info("Framebuffer updates: %u", updates);
+
+ for (i = 0;i < stats.size();i++) {
+ // Did this class do anything at all?
+ for (j = 0;j < stats[i].size();j++) {
+ if (stats[i][j].rects != 0)
+ break;
+ }
+ if (j == stats[i].size())
+ continue;
+
+ vlog.info(" %s:", encoderClassName((EncoderClass)i));
+
+ for (j = 0;j < stats[i].size();j++) {
+ if (stats[i][j].rects == 0)
+ continue;
+
+ rects += stats[i][j].rects;
+ pixels += stats[i][j].pixels;
+ bytes += stats[i][j].bytes;
+ equivalent += stats[i][j].equivalent;
+
+ ratio = (double)stats[i][j].equivalent / stats[i][j].bytes;
+
+ vlog.info(" %s: %u rects, %llu pixels",
+ encoderTypeName((EncoderType)j),
+ stats[i][j].rects, stats[i][j].pixels);
+ vlog.info(" %*s %llu bytes (%g ratio)",
+ strlen(encoderTypeName((EncoderType)j)), "",
+ stats[i][j].bytes, ratio);
+ }
+ }
+
+ ratio = (double)equivalent / bytes;
+
+ vlog.info(" Total: %u rects, %llu pixels", rects, pixels);
+ vlog.info(" %llu bytes (%g ratio)", bytes, ratio);
+}
+
bool EncodeManager::supported(int encoding)
{
switch (encoding) {
@@ -114,6 +221,8 @@ void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb,
int nRects;
Region changed;
+ updates++;
+
prepareEncoders();
if (conn->cp.supportsLastRect)
@@ -292,6 +401,40 @@ int EncodeManager::computeNumRects(const Region& changed)
return numRects;
}
+Encoder *EncodeManager::startRect(const Rect& rect, int type)
+{
+ Encoder *encoder;
+ int klass, equiv;
+
+ activeType = type;
+ klass = activeEncoders[activeType];
+
+ beforeLength = conn->getOutStream()->length();
+
+ stats[klass][activeType].rects++;
+ stats[klass][activeType].pixels += rect.area();
+ equiv = 12 + rect.area() * conn->cp.pf().bpp/8;
+ stats[klass][activeType].equivalent += equiv;
+
+ encoder = encoders[klass];
+ conn->writer()->startRect(rect, encoder->encoding);
+
+ return encoder;
+}
+
+void EncodeManager::endRect()
+{
+ int klass;
+ int length;
+
+ conn->writer()->endRect();
+
+ length = conn->getOutStream()->length() - beforeLength;
+
+ klass = activeEncoders[activeType];
+ stats[klass][activeType].bytes += length;
+}
+
void EncodeManager::writeCopyRects(const UpdateInfo& ui)
{
std::vector<Rect> rects;
@@ -309,84 +452,104 @@ void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
std::vector<Rect> rects;
std::vector<Rect>::const_iterator rect;
- // FIXME: This gives up after the first rect it finds. A large update
- // (like a whole screen refresh) might have lots of large solid
- // areas.
-
changed->get_rects(&rects);
- for (rect = rects.begin(); rect != rects.end(); ++rect) {
- Rect sr;
- int dx, dy, dw, dh;
-
- // We start by finding a solid 16x16 block
- for (dy = rect->tl.y; dy < rect->br.y; dy += SolidSearchBlock) {
-
- dh = SolidSearchBlock;
- if (dy + dh > rect->br.y)
- dh = rect->br.y - dy;
-
- for (dx = rect->tl.x; dx < rect->br.x; dx += SolidSearchBlock) {
- // We define it like this to guarantee alignment
- rdr::U32 _buffer;
- rdr::U8* colourValue = (rdr::U8*)&_buffer;
-
- dw = SolidSearchBlock;
- if (dx + dw > rect->br.x)
- dw = rect->br.x - dx;
-
- pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
-
- sr.setXYWH(dx, dy, dw, dh);
- if (checkSolidTile(sr, colourValue, pb)) {
- Rect erb, erp;
-
- Encoder *encoder;
-
- // We then try extending the area by adding more blocks
- // in both directions and pick the combination that gives
- // the largest area.
- sr.setXYWH(dx, dy, rect->br.x - dx, rect->br.y - dy);
- extendSolidAreaByBlock(sr, colourValue, pb, &erb);
-
- // Did we end up getting the entire rectangle?
- if (erb.equals(*rect))
- erp = erb;
- else {
- // Don't bother with sending tiny rectangles
- if (erb.area() < SolidBlockMinArea)
- continue;
-
- // Extend the area again, but this time one pixel
- // row/column at a time.
- extendSolidAreaByPixel(*rect, erb, colourValue, pb, &erp);
- }
-
- // Send solid-color rectangle.
- encoder = encoders[activeEncoders[encoderSolid]];
- conn->writer()->startRect(erp, encoder->encoding);
- if (encoder->flags & EncoderUseNativePF) {
- encoder->writeSolidRect(erp.width(), erp.height(),
- pb->getPF(), colourValue);
- } else {
- rdr::U32 _buffer2;
- rdr::U8* converted = (rdr::U8*)&_buffer2;
-
- conn->cp.pf().bufferFromBuffer(converted, pb->getPF(),
- colourValue, 1);
-
- encoder->writeSolidRect(erp.width(), erp.height(),
- conn->cp.pf(), converted);
- }
- conn->writer()->endRect();
-
- changed->assign_subtract(Region(erp));
-
- break;
+ for (rect = rects.begin(); rect != rects.end(); ++rect)
+ findSolidRect(*rect, changed, pb);
+}
+
+void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
+ const PixelBuffer* pb)
+{
+ Rect sr;
+ int dx, dy, dw, dh;
+
+ // We start by finding a solid 16x16 block
+ for (dy = rect.tl.y; dy < rect.br.y; dy += SolidSearchBlock) {
+
+ dh = SolidSearchBlock;
+ if (dy + dh > rect.br.y)
+ dh = rect.br.y - dy;
+
+ for (dx = rect.tl.x; dx < rect.br.x; dx += SolidSearchBlock) {
+ // We define it like this to guarantee alignment
+ rdr::U32 _buffer;
+ rdr::U8* colourValue = (rdr::U8*)&_buffer;
+
+ dw = SolidSearchBlock;
+ if (dx + dw > rect.br.x)
+ dw = rect.br.x - dx;
+
+ pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
+
+ sr.setXYWH(dx, dy, dw, dh);
+ if (checkSolidTile(sr, colourValue, pb)) {
+ Rect erb, erp;
+
+ Encoder *encoder;
+
+ // We then try extending the area by adding more blocks
+ // in both directions and pick the combination that gives
+ // the largest area.
+ sr.setXYWH(dx, dy, rect.br.x - dx, rect.br.y - dy);
+ extendSolidAreaByBlock(sr, colourValue, pb, &erb);
+
+ // Did we end up getting the entire rectangle?
+ if (erb.equals(rect))
+ erp = erb;
+ else {
+ // Don't bother with sending tiny rectangles
+ if (erb.area() < SolidBlockMinArea)
+ continue;
+
+ // Extend the area again, but this time one pixel
+ // row/column at a time.
+ extendSolidAreaByPixel(rect, erb, colourValue, pb, &erp);
}
- }
- if (dx < rect->br.x)
- break;
+ // Send solid-color rectangle.
+ encoder = startRect(erp, encoderSolid);
+ if (encoder->flags & EncoderUseNativePF) {
+ encoder->writeSolidRect(erp.width(), erp.height(),
+ pb->getPF(), colourValue);
+ } else {
+ rdr::U32 _buffer2;
+ rdr::U8* converted = (rdr::U8*)&_buffer2;
+
+ conn->cp.pf().bufferFromBuffer(converted, pb->getPF(),
+ colourValue, 1);
+
+ encoder->writeSolidRect(erp.width(), erp.height(),
+ conn->cp.pf(), converted);
+ }
+ endRect();
+
+ changed->assign_subtract(Region(erp));
+
+ // Search remaining areas by recursion
+ // FIXME: Is this the best way to divide things up?
+
+ // Left? (Note that we've already searched a SolidSearchBlock
+ // pixels high strip here)
+ if ((erp.tl.x != rect.tl.x) && (erp.height() > SolidSearchBlock)) {
+ sr.setXYWH(rect.tl.x, erp.tl.y + SolidSearchBlock,
+ erp.tl.x - rect.tl.x, erp.height() - SolidSearchBlock);
+ findSolidRect(sr, changed, pb);
+ }
+
+ // Right?
+ if (erp.br.x != rect.br.x) {
+ sr.setXYWH(erp.br.x, erp.tl.y, rect.br.x - erp.br.x, erp.height());
+ findSolidRect(sr, changed, pb);
+ }
+
+ // Below?
+ if (erp.br.y != rect.br.y) {
+ sr.setXYWH(rect.tl.x, erp.br.y, rect.width(), rect.br.y - erp.br.y);
+ findSolidRect(sr, changed, pb);
+ }
+
+ return;
+ }
}
}
}
@@ -461,7 +624,7 @@ void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
// Special exception inherited from the Tight encoder
if (activeEncoders[encoderFullColour] == encoderTightJPEG) {
- if (conn->cp.compressLevel < 2)
+ if ((conn->cp.compressLevel != -1) && (conn->cp.compressLevel < 2))
maxColours = 24;
else
maxColours = 96;
@@ -507,14 +670,14 @@ void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
type = encoderIndexed;
}
- encoder = encoders[activeEncoders[type]];
+ encoder = startRect(rect, type);
if (encoder->flags & EncoderUseNativePF)
ppb = preparePixelBuffer(rect, pb, false);
- conn->writer()->startRect(rect, encoder->encoding);
encoder->writeRect(ppb, info.palette);
- conn->writer()->endRect();
+
+ endRect();
}
bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue,
diff --git a/common/rfb/EncodeManager.h b/common/rfb/EncodeManager.h
index df0275ce..a3df8f74 100644
--- a/common/rfb/EncodeManager.h
+++ b/common/rfb/EncodeManager.h
@@ -41,6 +41,8 @@ namespace rfb {
EncodeManager(SConnection* conn);
~EncodeManager();
+ void logStats();
+
// Hack to let ConnParams calculate the client's preferred encoding
static bool supported(int encoding);
@@ -52,8 +54,12 @@ namespace rfb {
int computeNumRects(const Region& changed);
+ Encoder *startRect(const Rect& rect, int type);
+ void endRect();
+
void writeCopyRects(const UpdateInfo& ui);
void writeSolidRects(Region *changed, const PixelBuffer* pb);
+ void findSolidRect(const Rect& rect, Region *changed, const PixelBuffer* pb);
void writeRects(const Region& changed, const PixelBuffer* pb);
void writeSubRect(const Rect& rect, const PixelBuffer *pb);
@@ -97,6 +103,19 @@ namespace rfb {
std::vector<Encoder*> encoders;
std::vector<int> activeEncoders;
+ struct EncoderStats {
+ unsigned rects;
+ unsigned long long bytes;
+ unsigned long long pixels;
+ unsigned long long equivalent;
+ };
+ typedef std::vector< std::vector<struct EncoderStats> > StatsVector;
+
+ unsigned updates;
+ StatsVector stats;
+ int activeType;
+ int beforeLength;
+
class OffsetPixelBuffer : public FullFramePixelBuffer {
public:
OffsetPixelBuffer() {}
diff --git a/common/rfb/PixelBuffer.cxx b/common/rfb/PixelBuffer.cxx
index b1359c2e..b03af1af 100644
--- a/common/rfb/PixelBuffer.cxx
+++ b/common/rfb/PixelBuffer.cxx
@@ -99,23 +99,43 @@ ModifiablePixelBuffer::~ModifiablePixelBuffer()
void ModifiablePixelBuffer::fillRect(const Rect& r, Pixel pix)
{
int stride;
- U8 *buf, pixbuf[4];
+ U8 *buf;
int w, h, b;
- buf = getBufferRW(r, &stride);
w = r.width();
h = r.height();
b = format.bpp/8;
- format.bufferFromPixel(pixbuf, pix);
+ if (h == 0)
+ return;
+
+ buf = getBufferRW(r, &stride);
- while (h--) {
- int w_ = w;
- while (w_--) {
+ if (b == 1) {
+ while (h--) {
+ memset(buf, pix, w);
+ buf += stride * b;
+ }
+ } else {
+ U8 pixbuf[4], *start;
+ int w1;
+
+ start = buf;
+
+ format.bufferFromPixel(pixbuf, pix);
+
+ w1 = w;
+ while (w1--) {
memcpy(buf, pixbuf, b);
buf += b;
}
buf += (stride - w) * b;
+ h--;
+
+ while (h--) {
+ memcpy(buf, start, w * b);
+ buf += stride * b;
+ }
}
commitBufferRW(r);
diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx
index 99a4850c..bfff70cf 100644
--- a/common/rfb/SConnection.cxx
+++ b/common/rfb/SConnection.cxx
@@ -48,11 +48,10 @@ const SConnection::AccessRights SConnection::AccessNoQuery = 0x0400;
const SConnection::AccessRights SConnection::AccessFull = 0xffff;
-SConnection::SConnection(bool reverseConnection_)
+SConnection::SConnection()
: readyForSetColourMapEntries(false),
is(0), os(0), reader_(0), writer_(0),
security(0), ssecurity(0), state_(RFBSTATE_UNINITIALISED),
- reverseConnection(reverseConnection_),
preferredEncoding(encodingRaw)
{
defaultMajorVersion = 3;
@@ -271,7 +270,7 @@ void SConnection::writeConnFailedFromScratch(const char* msg,
os->flush();
}
-void SConnection::setEncodings(int nEncodings, rdr::S32* encodings)
+void SConnection::setEncodings(int nEncodings, const rdr::S32* encodings)
{
int i;
diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h
index 005a7a85..ef1de2b3 100644
--- a/common/rfb/SConnection.h
+++ b/common/rfb/SConnection.h
@@ -38,7 +38,7 @@ namespace rfb {
class SConnection : public SMsgHandler {
public:
- SConnection(bool reverseConnection_);
+ SConnection();
virtual ~SConnection();
// Methods to initialise the connection
@@ -71,7 +71,7 @@ namespace rfb {
// Overridden from SMsgHandler
- virtual void setEncodings(int nEncodings, rdr::S32* encodings);
+ virtual void setEncodings(int nEncodings, const rdr::S32* encodings);
// Methods to be overridden in a derived class
@@ -183,6 +183,11 @@ namespace rfb {
protected:
void setState(stateEnum s) { state_ = s; }
+
+ void setReader(SMsgReader *r) { reader_ = r; }
+ void setWriter(SMsgWriter *w) { writer_ = w; }
+
+ private:
void writeFakeColourMap(void);
bool readyForSetColourMapEntries;
@@ -201,7 +206,6 @@ namespace rfb {
SecurityServer *security;
SSecurity* ssecurity;
stateEnum state_;
- bool reverseConnection;
rdr::S32 preferredEncoding;
};
}
diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx
index d4046b1b..388b21f8 100644
--- a/common/rfb/SMsgHandler.cxx
+++ b/common/rfb/SMsgHandler.cxx
@@ -39,7 +39,7 @@ void SMsgHandler::setPixelFormat(const PixelFormat& pf)
cp.setPF(pf);
}
-void SMsgHandler::setEncodings(int nEncodings, rdr::S32* encodings)
+void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings)
{
bool firstFence, firstContinuousUpdates;
diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h
index c21cf528..74509e0b 100644
--- a/common/rfb/SMsgHandler.h
+++ b/common/rfb/SMsgHandler.h
@@ -46,7 +46,7 @@ namespace rfb {
virtual void clientInit(bool shared);
virtual void setPixelFormat(const PixelFormat& pf);
- virtual void setEncodings(int nEncodings, rdr::S32* encodings);
+ virtual void setEncodings(int nEncodings, const rdr::S32* encodings);
virtual void framebufferUpdateRequest(const Rect& r, bool incremental) = 0;
virtual void setDesktopSize(int fb_width, int fb_height,
const ScreenSet& layout) = 0;
diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx
index 0d61292c..9aee96d2 100644
--- a/common/rfb/SMsgWriter.cxx
+++ b/common/rfb/SMsgWriter.cxx
@@ -33,31 +33,15 @@ using namespace rfb;
static LogWriter vlog("SMsgWriter");
SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_)
- : cp(cp_), os(os_), currentEncoding(0),
+ : cp(cp_), os(os_),
nRectsInUpdate(0), nRectsInHeader(0),
needSetDesktopSize(false), needExtendedDesktopSize(false),
- needSetDesktopName(false), needSetCursor(false), needSetXCursor(false),
- lenBeforeRect(0), updatesSent(0), rawBytesEquivalent(0)
+ needSetDesktopName(false), needSetCursor(false), needSetXCursor(false)
{
- for (int i = 0; i <= encodingMax; i++) {
- bytesSent[i] = 0;
- rectsSent[i] = 0;
- }
}
SMsgWriter::~SMsgWriter()
{
- vlog.info("framebuffer updates %d",updatesSent);
- int bytes = 0;
- for (int i = 0; i <= encodingMax; i++) {
- if (i != encodingCopyRect)
- bytes += bytesSent[i];
- if (rectsSent[i])
- vlog.info(" %s rects %d, bytes %d",
- encodingName(i), rectsSent[i], bytesSent[i]);
- }
- vlog.info(" raw bytes equivalent %llu, compression ratio %f",
- rawBytesEquivalent, (double)rawBytesEquivalent / bytes);
}
void SMsgWriter::writeServerInit()
@@ -276,7 +260,6 @@ void SMsgWriter::writeFramebufferUpdateEnd()
os->writeU32(pseudoEncodingLastRect);
}
- updatesSent++;
endMsg();
}
@@ -293,11 +276,6 @@ void SMsgWriter::startRect(const Rect& r, int encoding)
if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader)
throw Exception("SMsgWriter::startRect: nRects out of sync");
- currentEncoding = encoding;
- lenBeforeRect = os->length();
- if (encoding != encodingCopyRect)
- rawBytesEquivalent += 12 + r.width() * r.height() * (cp->pf().bpp/8);
-
os->writeS16(r.tl.x);
os->writeS16(r.tl.y);
os->writeU16(r.width());
@@ -307,10 +285,6 @@ void SMsgWriter::startRect(const Rect& r, int encoding)
void SMsgWriter::endRect()
{
- if (currentEncoding <= encodingMax) {
- bytesSent[currentEncoding] += os->length() - lenBeforeRect;
- rectsSent[currentEncoding]++;
- }
}
void SMsgWriter::startMsg(int type)
diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h
index deddd3cd..917b933c 100644
--- a/common/rfb/SMsgWriter.h
+++ b/common/rfb/SMsgWriter.h
@@ -109,11 +109,6 @@ namespace rfb {
void startRect(const Rect& r, int enc);
void endRect();
- int getUpdatesSent() { return updatesSent; }
- int getRectsSent(int encoding) { return rectsSent[encoding]; }
- int getBytesSent(int encoding) { return bytesSent[encoding]; }
- rdr::U64 getRawBytesEquivalent() { return rawBytesEquivalent; }
-
protected:
void startMsg(int type);
void endMsg();
@@ -137,8 +132,6 @@ namespace rfb {
ConnParams* cp;
rdr::OutStream* os;
- int currentEncoding;
-
int nRectsInUpdate;
int nRectsInHeader;
@@ -149,12 +142,6 @@ namespace rfb {
bool needSetCursor;
bool needSetXCursor;
- int lenBeforeRect;
- int updatesSent;
- int bytesSent[encodingMax+1];
- int rectsSent[encodingMax+1];
- rdr::U64 rawBytesEquivalent;
-
typedef struct {
rdr::U16 reason, result;
int fb_width, fb_height;
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index 746bb901..e261cfba 100644
--- a/common/rfb/VNCSConnectionST.cxx
+++ b/common/rfb/VNCSConnectionST.cxx
@@ -66,7 +66,7 @@ struct RTTInfo {
VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
bool reverse)
- : SConnection(reverse), sock(s),
+ : sock(s), reverseConnection(reverse),
queryConnectTimer(this), inProcessMessages(false),
pendingSyncFence(false), syncFence(false), fenceFlags(0),
fenceDataLen(0), fenceData(NULL),
diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h
index 7b25570d..978879f8 100644
--- a/common/rfb/VNCSConnectionST.h
+++ b/common/rfb/VNCSConnectionST.h
@@ -173,6 +173,7 @@ namespace rfb {
network::Socket* sock;
CharArray peerEndpoint;
+ bool reverseConnection;
Timer queryConnectTimer;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index afab3e26..e99c825a 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -7,3 +7,9 @@ target_link_libraries(convperf test_util rfb)
add_executable(conv conv.cxx)
target_link_libraries(conv rfb)
+
+add_executable(decperf decperf.cxx)
+target_link_libraries(decperf test_util rfb)
+
+add_executable(encperf encperf.cxx)
+target_link_libraries(encperf test_util rfb)
diff --git a/tests/decperf.cxx b/tests/decperf.cxx
new file mode 100644
index 00000000..6714cb10
--- /dev/null
+++ b/tests/decperf.cxx
@@ -0,0 +1,217 @@
+/* Copyright 2015 Pierre Ossman <ossman@cendio.se> 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*
+ * This program reads files produced by TightVNC's/TurboVNC's
+ * compare-encodings. It is basically a dump of the RFB protocol
+ * from the server side from the ServerInit message and forward.
+ * It is assumed that the client is using a bgr888 (LE) pixel
+ * format.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <rdr/Exception.h>
+#include <rdr/FileInStream.h>
+
+#include <rfb/CConnection.h>
+#include <rfb/CMsgReader.h>
+#include <rfb/Decoder.h>
+#include <rfb/PixelBuffer.h>
+#include <rfb/PixelFormat.h>
+
+#include "util.h"
+
+// FIXME: Files are always in this format
+static const rfb::PixelFormat filePF(32, 24, false, true, 255, 255, 255, 0, 8, 16);
+
+class CConn : public rfb::CConnection {
+public:
+ CConn(const char *filename);
+ ~CConn();
+
+ virtual void setDesktopSize(int w, int h);
+ virtual void setPixelFormat(const rfb::PixelFormat& pf);
+ virtual void setCursor(int, int, const rfb::Point&, void*, void*);
+ virtual void dataRect(const rfb::Rect&, int);
+ virtual void setColourMapEntries(int, int, rdr::U16*);
+ virtual void bell();
+ virtual void serverCutText(const char*, rdr::U32);
+
+public:
+ double cpuTime;
+
+protected:
+ rdr::FileInStream *in;
+ rfb::Decoder *decoders[rfb::encodingMax+1];
+ rfb::ManagedPixelBuffer pb;
+};
+
+CConn::CConn(const char *filename)
+{
+ int i;
+
+ cpuTime = 0.0;
+
+ in = new rdr::FileInStream(filename);
+ setStreams(in, NULL);
+
+ memset(decoders, 0, sizeof(decoders));
+ for (i = 0;i < rfb::encodingMax;i++) {
+ if (!rfb::Decoder::supported(i))
+ continue;
+
+ decoders[i] = rfb::Decoder::createDecoder(i, this);
+ }
+
+ // Need to skip the initial handshake
+ setState(RFBSTATE_INITIALISATION);
+ // That also means that the reader and writer weren't setup
+ setReader(new rfb::CMsgReader(this, in));
+}
+
+CConn::~CConn()
+{
+ int i;
+
+ delete in;
+
+ for (i = 0;i < rfb::encodingMax;i++)
+ delete decoders[i];
+}
+
+void CConn::setDesktopSize(int w, int h)
+{
+ CConnection::setDesktopSize(w, h);
+
+ pb.setSize(cp.width, cp.height);
+}
+
+void CConn::setPixelFormat(const rfb::PixelFormat& pf)
+{
+ // Override format
+ CConnection::setPixelFormat(filePF);
+
+ pb.setPF(cp.pf());
+}
+
+void CConn::setCursor(int, int, const rfb::Point&, void*, void*)
+{
+}
+
+void CConn::dataRect(const rfb::Rect &r, int encoding)
+{
+ if (!decoders[encoding])
+ throw rdr::Exception("Unknown encoding");
+
+ startCpuCounter();
+ decoders[encoding]->readRect(r, &pb);
+ endCpuCounter();
+
+ cpuTime += getCpuCounter();
+}
+
+void CConn::setColourMapEntries(int, int, rdr::U16*)
+{
+}
+
+void CConn::bell()
+{
+}
+
+void CConn::serverCutText(const char*, rdr::U32)
+{
+}
+
+static double runTest(const char *fn)
+{
+ CConn *cc;
+ double time;
+
+ cc = new CConn(fn);
+
+ try {
+ while (true)
+ cc->processMsg();
+ } catch (rdr::EndOfStream e) {
+ } catch (rdr::Exception e) {
+ fprintf(stderr, "Failed to run rfb file: %s\n", e.str());
+ exit(1);
+ }
+
+ time = cc->cpuTime;
+
+ delete cc;
+
+ return time;
+}
+
+static void sort(double *array, int count)
+{
+ bool sorted;
+ int i;
+ do {
+ sorted = true;
+ for (i = 1;i < count;i++) {
+ if (array[i-1] > array[i]) {
+ double d;
+ d = array[i];
+ array[i] = array[i-1];
+ array[i-1] = d;
+ sorted = false;
+ }
+ }
+ } while (!sorted);
+}
+
+static const int runCount = 9;
+
+int main(int argc, char **argv)
+{
+ int i;
+ double times[runCount], dev[runCount];
+ double median, meddev;
+
+ if (argc != 2) {
+ printf("Syntax: %s <rfb file>\n", argv[0]);
+ return 1;
+ }
+
+ // Warmup
+ runTest(argv[1]);
+
+ // Multiple runs to get a good average
+ for (i = 0;i < runCount;i++)
+ times[i] = runTest(argv[1]);
+
+ // Calculate median and median deviation
+ sort(times, runCount);
+ median = times[runCount/2];
+
+ for (i = 0;i < runCount;i++)
+ dev[i] = fabs((times[i] - median) / median) * 100;
+
+ sort(dev, runCount);
+ meddev = dev[runCount/2];
+
+ printf("CPU time: %g s (+/- %g %)\n", median, meddev);
+
+ return 0;
+}
diff --git a/tests/encperf.cxx b/tests/encperf.cxx
new file mode 100644
index 00000000..60905c83
--- /dev/null
+++ b/tests/encperf.cxx
@@ -0,0 +1,439 @@
+/* Copyright 2015 Pierre Ossman <ossman@cendio.se> 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*
+ * This program reads files produced by TightVNC's/TurboVNC's
+ * fbs-dump, which in turn takes files from rfbproxy. It is
+ * basically a dump of the RFB protocol from the server side after
+ * the ServerInit message. Mostly this consists of FramebufferUpdate
+ * message using the HexTile encoding. Screen size and pixel format
+ * are not encoded in the file and must be specified by the user.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <rdr/Exception.h>
+#include <rdr/OutStream.h>
+#include <rdr/FileInStream.h>
+
+#include <rfb/PixelFormat.h>
+
+#include <rfb/CConnection.h>
+#include <rfb/CMsgReader.h>
+#include <rfb/Decoder.h>
+#include <rfb/UpdateTracker.h>
+
+#include <rfb/EncodeManager.h>
+#include <rfb/SConnection.h>
+#include <rfb/SMsgWriter.h>
+
+#include "util.h"
+
+static rfb::IntParameter width("width", "Frame buffer width", 0);
+static rfb::IntParameter height("height", "Frame buffer height", 0);
+
+static rfb::StringParameter format("format", "Pixel format (e.g. bgr888)", "");
+
+// The frame buffer (and output) is always this format
+static const rfb::PixelFormat fbPF(32, 24, false, true, 255, 255, 255, 0, 8, 16);
+
+// Encodings to use
+static const rdr::S32 encodings[] = {
+ rfb::encodingTight, rfb::encodingCopyRect, rfb::encodingRRE,
+ rfb::encodingHextile, rfb::encodingZRLE, rfb::pseudoEncodingLastRect,
+ rfb::pseudoEncodingQualityLevel0 + 8 };
+
+class DummyOutStream : public rdr::OutStream {
+public:
+ DummyOutStream();
+
+ virtual int length();
+ virtual void flush();
+
+private:
+ virtual int overrun(int itemSize, int nItems);
+
+ int offset;
+ rdr::U8 buf[131072];
+};
+
+class CConn : public rfb::CConnection {
+public:
+ CConn(const char *filename);
+ ~CConn();
+
+ double getRatio();
+
+ virtual void setDesktopSize(int w, int h);
+ virtual void setCursor(int, int, const rfb::Point&, void*, void*);
+ virtual void framebufferUpdateStart();
+ virtual void framebufferUpdateEnd();
+ virtual void dataRect(const rfb::Rect&, int);
+ virtual void setColourMapEntries(int, int, rdr::U16*);
+ virtual void bell();
+ virtual void serverCutText(const char*, rdr::U32);
+
+public:
+ double decodeTime;
+ double encodeTime;
+
+protected:
+ rdr::FileInStream *in;
+ rfb::Decoder *decoders[rfb::encodingMax+1];
+ rfb::ManagedPixelBuffer pb;
+ rfb::SimpleUpdateTracker updates;
+ class SConn *sc;
+};
+
+class Manager : public rfb::EncodeManager {
+public:
+ Manager(class rfb::SConnection *conn);
+
+ double getRatio();
+};
+
+class SConn : public rfb::SConnection {
+public:
+ SConn();
+ ~SConn();
+
+ void writeUpdate(const rfb::UpdateInfo& ui, const rfb::PixelBuffer* pb);
+
+ double getRatio();
+
+ virtual void setAccessRights(AccessRights ar);
+
+ virtual void setDesktopSize(int fb_width, int fb_height,
+ const rfb::ScreenSet& layout);
+
+protected:
+ DummyOutStream *out;
+ Manager *manager;
+};
+
+DummyOutStream::DummyOutStream()
+{
+ offset = 0;
+ ptr = buf;
+ end = buf + sizeof(buf);
+}
+
+int DummyOutStream::length()
+{
+ flush();
+ return offset;
+}
+
+void DummyOutStream::flush()
+{
+ offset += ptr - buf;
+ ptr = buf;
+}
+
+int DummyOutStream::overrun(int itemSize, int nItems)
+{
+ flush();
+}
+
+CConn::CConn(const char *filename)
+{
+ int i;
+
+ decodeTime = 0.0;
+ encodeTime = 0.0;
+
+ in = new rdr::FileInStream(filename);
+ setStreams(in, NULL);
+
+ memset(decoders, 0, sizeof(decoders));
+ for (i = 0;i < rfb::encodingMax;i++) {
+ if (!rfb::Decoder::supported(i))
+ continue;
+
+ decoders[i] = rfb::Decoder::createDecoder(i, this);
+ }
+
+ pb.setPF(fbPF);
+
+ // Need to skip the initial handshake and ServerInit
+ setState(RFBSTATE_NORMAL);
+ // That also means that the reader and writer weren't setup
+ setReader(new rfb::CMsgReader(this, in));
+ // Nor the frame buffer size and format
+ setDesktopSize(width, height);
+ rfb::PixelFormat pf;
+ pf.parse(format);
+ setPixelFormat(pf);
+
+ sc = new SConn();
+ sc->cp.setPF(pb.getPF());
+ sc->setEncodings(sizeof(encodings)/sizeof(*encodings), encodings);
+}
+
+CConn::~CConn()
+{
+ int i;
+
+ delete sc;
+
+ delete in;
+
+ for (i = 0;i < rfb::encodingMax;i++)
+ delete decoders[i];
+}
+
+double CConn::getRatio()
+{
+ return sc->getRatio();
+}
+
+void CConn::setDesktopSize(int w, int h)
+{
+ CConnection::setDesktopSize(w, h);
+
+ pb.setSize(cp.width, cp.height);
+}
+
+void CConn::setCursor(int, int, const rfb::Point&, void*, void*)
+{
+}
+
+void CConn::framebufferUpdateStart()
+{
+ updates.clear();
+}
+
+void CConn::framebufferUpdateEnd()
+{
+ rfb::UpdateInfo ui;
+ rfb::Region clip(pb.getRect());
+
+ updates.getUpdateInfo(&ui, clip);
+
+ startCpuCounter();
+ sc->writeUpdate(ui, &pb);
+ endCpuCounter();
+
+ encodeTime += getCpuCounter();
+}
+
+void CConn::dataRect(const rfb::Rect &r, int encoding)
+{
+ if (!decoders[encoding])
+ throw rdr::Exception("Unknown encoding");
+
+ startCpuCounter();
+ decoders[encoding]->readRect(r, &pb);
+ endCpuCounter();
+
+ decodeTime += getCpuCounter();
+
+ if (encoding != rfb::encodingCopyRect) // FIXME
+ updates.add_changed(rfb::Region(r));
+}
+
+void CConn::setColourMapEntries(int, int, rdr::U16*)
+{
+}
+
+void CConn::bell()
+{
+}
+
+void CConn::serverCutText(const char*, rdr::U32)
+{
+}
+
+Manager::Manager(class rfb::SConnection *conn) :
+ EncodeManager(conn)
+{
+}
+
+double Manager::getRatio()
+{
+ StatsVector::iterator iter;
+ unsigned long long bytes, equivalent;
+
+ bytes = equivalent = 0;
+ for (iter = stats.begin();iter != stats.end();++iter) {
+ StatsVector::value_type::iterator iter2;
+ for (iter2 = iter->begin();iter2 != iter->end();++iter2) {
+ bytes += iter2->bytes;
+ equivalent += iter2->equivalent;
+ }
+ }
+
+ return (double)equivalent / bytes;
+}
+
+SConn::SConn()
+{
+ out = new DummyOutStream;
+ setStreams(NULL, out);
+
+ setWriter(new rfb::SMsgWriter(&cp, out));
+
+ manager = new Manager(this);
+}
+
+SConn::~SConn()
+{
+ delete manager;
+ delete out;
+}
+
+void SConn::writeUpdate(const rfb::UpdateInfo& ui, const rfb::PixelBuffer* pb)
+{
+ manager->writeUpdate(ui, pb, NULL);
+}
+
+double SConn::getRatio()
+{
+ return manager->getRatio();
+}
+
+void SConn::setAccessRights(AccessRights ar)
+{
+}
+
+void SConn::setDesktopSize(int fb_width, int fb_height,
+ const rfb::ScreenSet& layout)
+{
+}
+
+static double runTest(const char *fn, double *ratio)
+{
+ CConn *cc;
+ double time;
+
+ cc = new CConn(fn);
+
+ try {
+ while (true)
+ cc->processMsg();
+ } catch (rdr::EndOfStream e) {
+ } catch (rdr::Exception e) {
+ fprintf(stderr, "Failed to run rfb file: %s\n", e.str());
+ exit(1);
+ }
+
+ time = cc->encodeTime;
+ *ratio = cc->getRatio();
+
+ delete cc;
+
+ return time;
+}
+
+static void sort(double *array, int count)
+{
+ bool sorted;
+ int i;
+ do {
+ sorted = true;
+ for (i = 1;i < count;i++) {
+ if (array[i-1] > array[i]) {
+ double d;
+ d = array[i];
+ array[i] = array[i-1];
+ array[i-1] = d;
+ sorted = false;
+ }
+ }
+ } while (!sorted);
+}
+
+static const int runCount = 9;
+
+static void usage(const char *argv0)
+{
+ fprintf(stderr, "Syntax: %s [options] <rfb file>\n", argv0);
+ fprintf(stderr, "Options:\n");
+ rfb::Configuration::listParams(79, 14);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+
+ const char *fn;
+
+ double times[runCount], dev[runCount];
+ double median, meddev, ratio;
+
+ fn = NULL;
+ for (i = 1; i < argc; i++) {
+ if (rfb::Configuration::setParam(argv[i]))
+ continue;
+
+ if (argv[i][0] == '-') {
+ if (i+1 < argc) {
+ if (rfb::Configuration::setParam(&argv[i][1], argv[i+1])) {
+ i++;
+ continue;
+ }
+ }
+ usage(argv[0]);
+ }
+
+ if (fn != NULL)
+ usage(argv[0]);
+
+ fn = argv[i];
+ }
+
+ if (fn == NULL) {
+ fprintf(stderr, "No file specified!\n\n");
+ usage(argv[0]);
+ }
+
+ if (!format.hasBeenSet()) {
+ fprintf(stderr, "Pixel format not specified!\n\n");
+ usage(argv[0]);
+ }
+
+ if (!width.hasBeenSet() || !height.hasBeenSet()) {
+ fprintf(stderr, "Frame buffer size not specified!\n\n");
+ usage(argv[0]);
+ }
+
+ // Warmup
+ runTest(fn, &ratio);
+
+ // Multiple runs to get a good average
+ for (i = 0;i < runCount;i++)
+ times[i] = runTest(fn, &ratio);
+
+ // Calculate median and median deviation
+ sort(times, runCount);
+ median = times[runCount/2];
+
+ for (i = 0;i < runCount;i++)
+ dev[i] = fabs((times[i] - median) / median) * 100;
+
+ sort(dev, runCount);
+ meddev = dev[runCount/2];
+
+ printf("CPU time: %g s (+/- %g %)\n", median, meddev);
+ printf("Ratio: %g\n", ratio);
+
+ return 0;
+}
diff --git a/tests/util.cxx b/tests/util.cxx
index c2685abb..52a2c610 100644
--- a/tests/util.cxx
+++ b/tests/util.cxx
@@ -17,6 +17,8 @@
*/
#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
#ifdef WIN32
#include <windows.h>
@@ -24,52 +26,93 @@
#include <sys/resource.h>
#endif
+#include "util.h"
+
#ifdef WIN32
-static FILETIME cpuCounters[2];
+typedef FILETIME syscounter_t;
#else
-struct rusage cpuCounters[2];
+typedef struct rusage syscounter_t;
#endif
-static void measureCpu(void *counter)
+static syscounter_t _globalCounter[2];
+static cpucounter_t globalCounter = _globalCounter;
+
+void startCpuCounter(void)
+{
+ startCpuCounter(globalCounter);
+}
+
+void endCpuCounter(void)
+{
+ endCpuCounter(globalCounter);
+}
+
+double getCpuCounter(void)
+{
+ return getCpuCounter(globalCounter);
+}
+
+cpucounter_t newCpuCounter(void)
+{
+ syscounter_t *c;
+
+ c = (syscounter_t*)malloc(sizeof(syscounter_t) * 2);
+ if (c == NULL)
+ return NULL;
+
+ memset(c, 0, sizeof(syscounter_t) * 2);
+
+ return c;
+}
+
+void freeCpuCounter(cpucounter_t c)
+{
+ free(c);
+}
+
+static void measureCpu(syscounter_t *counter)
{
#ifdef WIN32
FILETIME dummy1, dummy2, dummy3;
GetProcessTimes(GetCurrentProcess(), &dummy1, &dummy2,
- &dummy3, (FILETIME*)counter);
+ &dummy3, counter);
#else
- getrusage(RUSAGE_SELF, (struct rusage*)counter);
+ getrusage(RUSAGE_SELF, counter);
#endif
}
-void startCpuCounter(void)
+void startCpuCounter(cpucounter_t c)
{
- measureCpu(&cpuCounters[0]);
+ syscounter_t *s = (syscounter_t*)c;
+ measureCpu(&s[0]);
}
-void endCpuCounter(void)
+void endCpuCounter(cpucounter_t c)
{
- measureCpu(&cpuCounters[1]);
+ syscounter_t *s = (syscounter_t*)c;
+ measureCpu(&s[1]);
}
-double getCpuCounter(void)
+double getCpuCounter(cpucounter_t c)
{
+ syscounter_t *s = (syscounter_t*)c;
double seconds;
#ifdef WIN32
uint64_t counters[2];
- counters[0] = (uint64_t)cpuCounters[0].dwHighDateTime << 32 |
- cpuCounters[0].dwLowDateTime;
- counters[1] = (uint64_t)cpuCounters[1].dwHighDateTime << 32 |
- cpuCounters[1].dwLowDateTime;
+ counters[0] = (uint64_t)s[0].dwHighDateTime << 32 |
+ s[0].dwLowDateTime;
+ counters[1] = (uint64_t)s[1].dwHighDateTime << 32 |
+ s[1].dwLowDateTime;
seconds = (double)(counters[1] - counters[2]) / 10000000.0;
#else
- seconds = (double)(cpuCounters[1].ru_utime.tv_sec -
- cpuCounters[0].ru_utime.tv_sec);
- seconds += (double)(cpuCounters[1].ru_utime.tv_usec -
- cpuCounters[0].ru_utime.tv_usec) / 1000000.0;
+ seconds = (double)(s[1].ru_utime.tv_sec -
+ s[0].ru_utime.tv_sec);
+ seconds += (double)(s[1].ru_utime.tv_usec -
+ s[0].ru_utime.tv_usec) / 1000000.0;
#endif
return seconds;
diff --git a/tests/util.h b/tests/util.h
index ebeadeba..16f2ba2f 100644
--- a/tests/util.h
+++ b/tests/util.h
@@ -19,9 +19,19 @@
#ifndef __TESTS_UTIL_H__
#define __TESTS_UTIL_H__
+typedef void* cpucounter_t;
+
void startCpuCounter(void);
void endCpuCounter(void);
double getCpuCounter(void);
+cpucounter_t newCpuCounter(void);
+void freeCpuCounter(cpucounter_t c);
+
+void startCpuCounter(cpucounter_t c);
+void endCpuCounter(cpucounter_t c);
+
+double getCpuCounter(cpucounter_t c);
+
#endif