diff options
author | Pierre Ossman <ossman@cendio.se> | 2015-02-13 13:46:16 +0100 |
---|---|---|
committer | Pierre Ossman <ossman@cendio.se> | 2015-02-13 13:46:16 +0100 |
commit | f481203dc078287447b4367769c890ab7815d7cf (patch) | |
tree | 9966c58ac0bae953c99c0502f2b563b331eeda8e | |
parent | c4bc5a8f0eeacbebb50a2ef79c7b03b64586475a (diff) | |
parent | 4daa7b1172cd4606b60f8245831bc5aa155c4b9e (diff) | |
download | tigervnc-f481203dc078287447b4367769c890ab7815d7cf.tar.gz tigervnc-f481203dc078287447b4367769c890ab7815d7cf.zip |
Merge branch 'perf' of https://github.com/CendioOssman/tigervnc
-rw-r--r-- | cmake/StaticBuild.cmake | 8 | ||||
-rw-r--r-- | common/rdr/CMakeLists.txt | 1 | ||||
-rw-r--r-- | common/rdr/FileInStream.cxx | 87 | ||||
-rw-r--r-- | common/rdr/FileInStream.h | 50 | ||||
-rw-r--r-- | common/rfb/CConnection.h | 3 | ||||
-rw-r--r-- | common/rfb/CMakeLists.txt | 2 | ||||
-rw-r--r-- | common/rfb/CMsgHandler.cxx | 8 | ||||
-rw-r--r-- | common/rfb/CMsgHandler.h | 4 | ||||
-rw-r--r-- | common/rfb/EncodeManager.cxx | 321 | ||||
-rw-r--r-- | common/rfb/EncodeManager.h | 19 | ||||
-rw-r--r-- | common/rfb/PixelBuffer.cxx | 32 | ||||
-rw-r--r-- | common/rfb/SConnection.cxx | 5 | ||||
-rw-r--r-- | common/rfb/SConnection.h | 10 | ||||
-rw-r--r-- | common/rfb/SMsgHandler.cxx | 2 | ||||
-rw-r--r-- | common/rfb/SMsgHandler.h | 2 | ||||
-rw-r--r-- | common/rfb/SMsgWriter.cxx | 30 | ||||
-rw-r--r-- | common/rfb/SMsgWriter.h | 13 | ||||
-rw-r--r-- | common/rfb/VNCSConnectionST.cxx | 2 | ||||
-rw-r--r-- | common/rfb/VNCSConnectionST.h | 1 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 6 | ||||
-rw-r--r-- | tests/decperf.cxx | 217 | ||||
-rw-r--r-- | tests/encperf.cxx | 439 | ||||
-rw-r--r-- | tests/util.cxx | 79 | ||||
-rw-r--r-- | tests/util.h | 10 |
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 |