@@ -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 |
@@ -4,6 +4,7 @@ add_library(rdr STATIC | |||
Exception.cxx | |||
FdInStream.cxx | |||
FdOutStream.cxx | |||
FileInStream.cxx | |||
HexInStream.cxx | |||
HexOutStream.cxx | |||
InStream.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; | |||
} |
@@ -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 |
@@ -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. |
@@ -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 |
@@ -74,3 +74,11 @@ void CMsgHandler::endOfContinuousUpdates() | |||
{ | |||
cp.supportsContinuousUpdates = true; | |||
} | |||
void CMsgHandler::framebufferUpdateStart() | |||
{ | |||
} | |||
void CMsgHandler::framebufferUpdateEnd() | |||
{ | |||
} |
@@ -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, |
@@ -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, |
@@ -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() {} |
@@ -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); |
@@ -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; | |||
@@ -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; | |||
}; | |||
} |
@@ -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; | |||
@@ -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; |
@@ -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) |
@@ -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; |
@@ -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), |
@@ -173,6 +173,7 @@ namespace rfb { | |||
network::Socket* sock; | |||
CharArray peerEndpoint; | |||
bool reverseConnection; | |||
Timer queryConnectTimer; | |||
@@ -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) |
@@ -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; | |||
} |
@@ -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; | |||
} |
@@ -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; |
@@ -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 |