@@ -1,4 +1,4 @@ | |||
/* Copyright 2009-2015 Pierre Ossman for Cendio AB | |||
/* Copyright 2009-2018 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 | |||
@@ -73,7 +73,7 @@ static LogWriter vlog("Congestion"); | |||
Congestion::Congestion() : | |||
lastPosition(0), extraBuffer(0), | |||
baseRTT(-1), congWindow(INITIAL_WINDOW), inSlowStart(true), | |||
measurements(0), minRTT(-1), minCongestedRTT(-1) | |||
safeBaseRTT(-1), measurements(0), minRTT(-1), minCongestedRTT(-1) | |||
{ | |||
gettimeofday(&lastUpdate, NULL); | |||
gettimeofday(&lastSent, NULL); | |||
@@ -170,7 +170,7 @@ void Congestion::gotPong() | |||
// Try to estimate wire latency by tracking lowest seen latency | |||
if (rtt < baseRTT) | |||
baseRTT = rtt; | |||
safeBaseRTT = baseRTT = rtt; | |||
// Pings sent before the last adjustment aren't interesting as they | |||
// aren't a measurement of the current congestion window | |||
@@ -284,6 +284,15 @@ int Congestion::getUncongestedETA() | |||
} | |||
} | |||
size_t Congestion::getBandwidth() | |||
{ | |||
// No measurements yet? Guess RTT of 60 ms | |||
if (safeBaseRTT == (unsigned)-1) | |||
return congWindow * 1000 / 60; | |||
return congWindow * 1000 / safeBaseRTT; | |||
} | |||
void Congestion::debugTrace(const char* filename, int fd) | |||
{ | |||
#ifdef CONGESTION_TRACE |
@@ -1,4 +1,4 @@ | |||
/* Copyright 2009-2015 Pierre Ossman for Cendio AB | |||
/* Copyright 2009-2018 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 | |||
@@ -47,6 +47,10 @@ namespace rfb { | |||
// longer be congested. | |||
int getUncongestedETA(); | |||
// getBandwidth() returns the current bandwidth estimation in bytes | |||
// per second. | |||
size_t getBandwidth(); | |||
// debugTrace() writes the current congestion window, as well as the | |||
// congestion window of the underlying TCP layer, to the specified | |||
// file | |||
@@ -68,6 +72,8 @@ namespace rfb { | |||
unsigned congWindow; | |||
bool inSlowStart; | |||
unsigned safeBaseRTT; | |||
struct RTTInfo { | |||
struct timeval tv; | |||
unsigned pos; |
@@ -1,6 +1,6 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* Copyright 2014-2018 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 | |||
@@ -17,6 +17,9 @@ | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#include <stdlib.h> | |||
#include <rfb/EncodeManager.h> | |||
#include <rfb/Encoder.h> | |||
#include <rfb/Palette.h> | |||
@@ -245,52 +248,79 @@ bool EncodeManager::supported(int encoding) | |||
} | |||
} | |||
bool EncodeManager::needsLosslessRefresh(const Region& req) | |||
{ | |||
return !lossyRegion.intersect(req).is_empty(); | |||
} | |||
void EncodeManager::pruneLosslessRefresh(const Region& limits) | |||
{ | |||
lossyRegion.assign_intersect(limits); | |||
} | |||
void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, | |||
const RenderedCursor* renderedCursor) | |||
{ | |||
doUpdate(true, ui.changed, ui.copied, ui.copy_delta, pb, renderedCursor); | |||
} | |||
void EncodeManager::writeLosslessRefresh(const Region& req, const PixelBuffer* pb, | |||
const RenderedCursor* renderedCursor, | |||
size_t maxUpdateSize) | |||
{ | |||
doUpdate(false, getLosslessRefresh(req, maxUpdateSize), | |||
Region(), Point(), pb, renderedCursor); | |||
} | |||
void EncodeManager::doUpdate(bool allowLossy, const Region& changed_, | |||
const Region& copied, const Point& copyDelta, | |||
const PixelBuffer* pb, | |||
const RenderedCursor* renderedCursor) | |||
{ | |||
int nRects; | |||
Region changed; | |||
Region changed, cursorRegion; | |||
updates++; | |||
prepareEncoders(); | |||
prepareEncoders(allowLossy); | |||
changed = changed_; | |||
/* | |||
* We need to render the cursor seperately as it has its own | |||
* magical pixel buffer, so split it out from the changed region. | |||
*/ | |||
if (renderedCursor != NULL) { | |||
cursorRegion = changed.intersect(renderedCursor->getEffectiveRect()); | |||
changed.assign_subtract(renderedCursor->getEffectiveRect()); | |||
} | |||
if (conn->cp.supportsLastRect) | |||
nRects = 0xFFFF; | |||
else { | |||
nRects = ui.copied.numRects(); | |||
nRects += computeNumRects(ui.changed); | |||
if (renderedCursor != NULL) | |||
nRects += 1; | |||
nRects = copied.numRects(); | |||
nRects += computeNumRects(changed); | |||
nRects += computeNumRects(cursorRegion); | |||
} | |||
conn->writer()->writeFramebufferUpdateStart(nRects); | |||
writeCopyRects(ui); | |||
writeCopyRects(copied, copyDelta); | |||
/* | |||
* We start by searching for solid rects, which are then removed | |||
* from the changed region. | |||
*/ | |||
changed.copyFrom(ui.changed); | |||
if (conn->cp.supportsLastRect) | |||
writeSolidRects(&changed, pb); | |||
writeRects(changed, pb); | |||
if (renderedCursor != NULL) { | |||
Rect renderedCursorRect; | |||
renderedCursorRect = renderedCursor->getEffectiveRect(); | |||
writeSubRect(renderedCursorRect, renderedCursor); | |||
} | |||
writeRects(cursorRegion, renderedCursor); | |||
conn->writer()->writeFramebufferUpdateEnd(); | |||
} | |||
void EncodeManager::prepareEncoders() | |||
void EncodeManager::prepareEncoders(bool allowLossy) | |||
{ | |||
enum EncoderClass solid, bitmap, bitmapRLE; | |||
enum EncoderClass indexed, indexedRLE, fullColour; | |||
@@ -315,7 +345,7 @@ void EncodeManager::prepareEncoders() | |||
break; | |||
case encodingTight: | |||
if (encoders[encoderTightJPEG]->isSupported() && | |||
(conn->cp.pf().bpp >= 16)) | |||
(conn->cp.pf().bpp >= 16) && allowLossy) | |||
fullColour = encoderTightJPEG; | |||
else | |||
fullColour = encoderTight; | |||
@@ -333,7 +363,7 @@ void EncodeManager::prepareEncoders() | |||
if (fullColour == encoderRaw) { | |||
if (encoders[encoderTightJPEG]->isSupported() && | |||
(conn->cp.pf().bpp >= 16)) | |||
(conn->cp.pf().bpp >= 16) && allowLossy) | |||
fullColour = encoderTightJPEG; | |||
else if (encoders[encoderZRLE]->isSupported()) | |||
fullColour = encoderZRLE; | |||
@@ -373,7 +403,7 @@ void EncodeManager::prepareEncoders() | |||
// JPEG is the only encoder that can reduce things to grayscale | |||
if ((conn->cp.subsampling == subsampleGray) && | |||
encoders[encoderTightJPEG]->isSupported()) { | |||
encoders[encoderTightJPEG]->isSupported() && allowLossy) { | |||
solid = bitmap = bitmapRLE = encoderTightJPEG; | |||
indexed = indexedRLE = fullColour = encoderTightJPEG; | |||
} | |||
@@ -397,6 +427,52 @@ void EncodeManager::prepareEncoders() | |||
} | |||
} | |||
Region EncodeManager::getLosslessRefresh(const Region& req, | |||
size_t maxUpdateSize) | |||
{ | |||
std::vector<Rect> rects; | |||
Region refresh; | |||
size_t area; | |||
// We make a conservative guess at the compression ratio at 2:1 | |||
maxUpdateSize *= 2; | |||
area = 0; | |||
lossyRegion.intersect(req).get_rects(&rects); | |||
while (!rects.empty()) { | |||
size_t idx; | |||
Rect rect; | |||
// Grab a random rect so we don't keep damaging and restoring the | |||
// same rect over and over | |||
idx = rand() % rects.size(); | |||
rect = rects[idx]; | |||
// Add rects until we exceed the threshold, then include as much as | |||
// possible of the final rect | |||
if ((area + rect.area()) > maxUpdateSize) { | |||
// Use the narrowest axis to avoid getting to thin rects | |||
if (rect.width() > rect.height()) { | |||
int width = (maxUpdateSize - area) / rect.height(); | |||
rect.br.x = rect.tl.x + __rfbmax(1, width); | |||
} else { | |||
int height = (maxUpdateSize - area) / rect.width(); | |||
rect.br.y = rect.tl.y + __rfbmax(1, height); | |||
} | |||
refresh.assign_union(Region(rect)); | |||
break; | |||
} | |||
area += rect.area(); | |||
refresh.assign_union(Region(rect)); | |||
rects.erase(rects.begin() + idx); | |||
} | |||
return refresh; | |||
} | |||
int EncodeManager::computeNumRects(const Region& changed) | |||
{ | |||
int numRects; | |||
@@ -449,6 +525,11 @@ Encoder *EncodeManager::startRect(const Rect& rect, int type) | |||
encoder = encoders[klass]; | |||
conn->writer()->startRect(rect, encoder->encoding); | |||
if (encoder->flags & EncoderLossy) | |||
lossyRegion.assign_union(Region(rect)); | |||
else | |||
lossyRegion.assign_subtract(Region(rect)); | |||
return encoder; | |||
} | |||
@@ -465,14 +546,16 @@ void EncodeManager::endRect() | |||
stats[klass][activeType].bytes += length; | |||
} | |||
void EncodeManager::writeCopyRects(const UpdateInfo& ui) | |||
void EncodeManager::writeCopyRects(const Region& copied, const Point& delta) | |||
{ | |||
std::vector<Rect> rects; | |||
std::vector<Rect>::const_iterator rect; | |||
Region lossyCopy; | |||
beforeLength = conn->getOutStream()->length(); | |||
ui.copied.get_rects(&rects, ui.copy_delta.x <= 0, ui.copy_delta.y <= 0); | |||
copied.get_rects(&rects, delta.x <= 0, delta.y <= 0); | |||
for (rect = rects.begin(); rect != rects.end(); ++rect) { | |||
int equiv; | |||
@@ -481,11 +564,16 @@ void EncodeManager::writeCopyRects(const UpdateInfo& ui) | |||
equiv = 12 + rect->area() * conn->cp.pf().bpp/8; | |||
copyStats.equivalent += equiv; | |||
conn->writer()->writeCopyRect(*rect, rect->tl.x - ui.copy_delta.x, | |||
rect->tl.y - ui.copy_delta.y); | |||
conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x, | |||
rect->tl.y - delta.y); | |||
} | |||
copyStats.bytes += conn->getOutStream()->length() - beforeLength; | |||
lossyCopy = lossyRegion; | |||
lossyCopy.translate(delta); | |||
lossyCopy.assign_intersect(copied); | |||
lossyRegion.assign_union(lossyCopy); | |||
} | |||
void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb) |
@@ -1,6 +1,6 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* Copyright 2014-2018 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 | |||
@@ -24,6 +24,7 @@ | |||
#include <rdr/types.h> | |||
#include <rfb/PixelBuffer.h> | |||
#include <rfb/Region.h> | |||
namespace rfb { | |||
class SConnection; | |||
@@ -31,7 +32,6 @@ namespace rfb { | |||
class UpdateInfo; | |||
class PixelBuffer; | |||
class RenderedCursor; | |||
class Region; | |||
struct Rect; | |||
struct RectInfo; | |||
@@ -46,18 +46,31 @@ namespace rfb { | |||
// Hack to let ConnParams calculate the client's preferred encoding | |||
static bool supported(int encoding); | |||
bool needsLosslessRefresh(const Region& req); | |||
void pruneLosslessRefresh(const Region& limits); | |||
void writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, | |||
const RenderedCursor* renderedCursor); | |||
void writeLosslessRefresh(const Region& req, const PixelBuffer* pb, | |||
const RenderedCursor* renderedCursor, | |||
size_t maxUpdateSize); | |||
protected: | |||
void prepareEncoders(); | |||
void doUpdate(bool allowLossy, const Region& changed, | |||
const Region& copied, const Point& copy_delta, | |||
const PixelBuffer* pb, | |||
const RenderedCursor* renderedCursor); | |||
void prepareEncoders(bool allowLossy); | |||
Region getLosslessRefresh(const Region& req, size_t maxUpdateSize); | |||
int computeNumRects(const Region& changed); | |||
Encoder *startRect(const Rect& rect, int type); | |||
void endRect(); | |||
void writeCopyRects(const UpdateInfo& ui); | |||
void writeCopyRects(const Region& copied, const Point& delta); | |||
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); | |||
@@ -103,6 +116,8 @@ namespace rfb { | |||
std::vector<Encoder*> encoders; | |||
std::vector<int> activeEncoders; | |||
Region lossyRegion; | |||
struct EncoderStats { | |||
unsigned rects; | |||
unsigned long long bytes; |
@@ -35,6 +35,8 @@ namespace rfb { | |||
// Give us the raw frame buffer, and not something converted to | |||
// the what the client is asking for. | |||
EncoderUseNativePF = 1 << 0, | |||
// Encoder does not encode pixels perfectly accurate | |||
EncoderLossy = 1 << 1, | |||
}; | |||
class Encoder { |
@@ -143,10 +143,6 @@ void rfb::Region::setExtentsAndOrderedRects(const ShortRect* extents, | |||
} | |||
} | |||
void rfb::Region::copyFrom(const rfb::Region& r) { | |||
XUnionRegion(r.xrgn, r.xrgn, xrgn); | |||
} | |||
void rfb::Region::assign_intersect(const rfb::Region& r) { | |||
XIntersectRegion(xrgn, r.xrgn, xrgn); | |||
} |
@@ -52,7 +52,6 @@ namespace rfb { | |||
void setOrderedRects(const std::vector<Rect>& rects); | |||
void setExtentsAndOrderedRects(const ShortRect* extents, int nRects, | |||
const ShortRect* rects); | |||
void copyFrom(const Region& r); | |||
void assign_intersect(const Region& r); | |||
void assign_union(const Region& r); |
@@ -64,7 +64,7 @@ static const struct TightJPEGConfiguration conf[10] = { | |||
TightJPEGEncoder::TightJPEGEncoder(SConnection* conn) : | |||
Encoder(conn, encodingTight, EncoderUseNativePF, -1), | |||
Encoder(conn, encodingTight, (EncoderFlags)(EncoderUseNativePF | EncoderLossy), -1), | |||
qualityLevel(-1), fineQuality(-1), fineSubsampling(subsampleUndefined) | |||
{ | |||
} |
@@ -1,5 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2016 Pierre Ossman for Cendio AB | |||
* Copyright 2016-2018 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 | |||
@@ -94,7 +94,7 @@ int Timer::checkTimeouts() { | |||
int Timer::getNextTimeout() { | |||
timeval now; | |||
gettimeofday(&now, 0); | |||
int toWait = __rfbmax(1, diffTimeMillis(pending.front()->dueTime, now)); | |||
int toWait = __rfbmax(1, pending.front()->getRemainingMs()); | |||
if (toWait > pending.front()->timeoutMs) { | |||
if (toWait - pending.front()->timeoutMs < 1000) { | |||
vlog.info("gettimeofday is broken..."); | |||
@@ -148,6 +148,12 @@ int Timer::getTimeoutMs() { | |||
return timeoutMs; | |||
} | |||
int Timer::getRemainingMs() { | |||
timeval now; | |||
gettimeofday(&now, 0); | |||
return __rfbmax(0, diffTimeMillis(pending.front()->dueTime, now)); | |||
} | |||
bool Timer::isBefore(timeval other) { | |||
return (dueTime.tv_sec < other.tv_sec) || | |||
((dueTime.tv_sec == other.tv_sec) && |
@@ -1,4 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2018 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 | |||
@@ -85,6 +86,11 @@ namespace rfb { | |||
// Usually used with isStarted() to get the _current_ timeout. | |||
int getTimeoutMs(); | |||
// getRemainingMs | |||
// Determines how many milliseconds are left before the Timer | |||
// will timeout. Only valid for an active timer. | |||
int getRemainingMs(); | |||
// isBefore | |||
// Determine whether the Timer will timeout before the specified time. | |||
bool isBefore(timeval other); |
@@ -1,5 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2009-2016 Pierre Ossman for Cendio AB | |||
* Copyright 2009-2018 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 | |||
@@ -223,6 +223,9 @@ void VNCSConnectionST::pixelBufferChange() | |||
} | |||
} | |||
} | |||
// Drop any lossy tracking that is now outside the framebuffer | |||
encodeManager.pruneLosslessRefresh(Region(server->pb->getRect())); | |||
} | |||
// Just update the whole screen at the moment because we're too lazy to | |||
// work out what's actually changed. | |||
@@ -962,18 +965,13 @@ void VNCSConnectionST::writeNoDataUpdate() | |||
void VNCSConnectionST::writeDataUpdate() | |||
{ | |||
Region req; | |||
Region req, pending; | |||
UpdateInfo ui; | |||
bool needNewUpdateInfo; | |||
const RenderedCursor *cursor; | |||
updates.enable_copyrect(cp.useCopyRect); | |||
// See if we are allowed to send anything right now (the framebuffer | |||
// might have changed in ways we haven't yet been informed of). | |||
if (!server->checkUpdate()) | |||
return; | |||
// See what the client has requested (if anything) | |||
if (continuousUpdates) | |||
req = cuRegion.union_(requested); | |||
@@ -983,6 +981,9 @@ void VNCSConnectionST::writeDataUpdate() | |||
if (req.is_empty()) | |||
return; | |||
// Get any framebuffer changes we haven't yet been informed of | |||
pending = server->getPendingRegion(); | |||
// Get the lists of updates. Prior to exporting the data to the `ui' object, | |||
// getUpdateInfo() will normalize the `updates' object such way that its | |||
// `changed' and `copied' regions would not intersect. | |||
@@ -996,7 +997,7 @@ void VNCSConnectionST::writeDataUpdate() | |||
if (!ui.copied.is_empty() && !damagedCursorRegion.is_empty()) { | |||
Region bogusCopiedCursor; | |||
bogusCopiedCursor.copyFrom(damagedCursorRegion); | |||
bogusCopiedCursor = damagedCursorRegion; | |||
bogusCopiedCursor.translate(ui.copy_delta); | |||
bogusCopiedCursor.assign_intersect(server->pb->getRect()); | |||
if (!ui.copied.intersect(bogusCopiedCursor).is_empty()) { | |||
@@ -1015,53 +1016,78 @@ void VNCSConnectionST::writeDataUpdate() | |||
removeRenderedCursor = false; | |||
} | |||
// Return if there is nothing to send the client. | |||
// If we need a full cursor update then make sure its entire region | |||
// is marked as changed. | |||
if (updates.is_empty() && !writer()->needFakeUpdate() && !updateRenderedCursor) | |||
return; | |||
if (updateRenderedCursor) { | |||
updates.add_changed(server->getRenderedCursor()->getEffectiveRect()); | |||
needNewUpdateInfo = true; | |||
updateRenderedCursor = false; | |||
} | |||
// The `updates' object could change, make sure we have valid update info. | |||
if (needNewUpdateInfo) | |||
updates.getUpdateInfo(&ui, req); | |||
// If the client needs a server-side rendered cursor, work out the cursor | |||
// rectangle. If it's empty then don't bother drawing it, but if it overlaps | |||
// with the update region, we need to draw the rendered cursor regardless of | |||
// whether it has changed. | |||
// If there are queued updates then we cannot safely send an update | |||
// without risking a partially updated screen | |||
if (!pending.is_empty()) { | |||
// However we might still be able to send a lossless refresh | |||
req.assign_subtract(pending); | |||
req.assign_subtract(ui.changed); | |||
req.assign_subtract(ui.copied); | |||
ui.changed.clear(); | |||
ui.copied.clear(); | |||
} | |||
// Does the client need a server-side rendered cursor? | |||
cursor = NULL; | |||
if (needRenderedCursor()) { | |||
Rect renderedCursorRect; | |||
cursor = server->getRenderedCursor(); | |||
renderedCursorRect | |||
= cursor->getEffectiveRect().intersect(req.get_bounding_rect()); | |||
if (renderedCursorRect.is_empty()) { | |||
cursor = NULL; | |||
} else if (!updateRenderedCursor && | |||
ui.changed.union_(ui.copied) | |||
.intersect(renderedCursorRect).is_empty()) { | |||
cursor = NULL; | |||
renderedCursorRect = cursor->getEffectiveRect(); | |||
// Check that we don't try to copy over the cursor area, and | |||
// if that happens we need to treat it as changed so that we can | |||
// re-render it | |||
if (!ui.copied.intersect(renderedCursorRect).is_empty()) { | |||
ui.changed.assign_union(ui.copied.intersect(renderedCursorRect)); | |||
ui.copied.assign_subtract(renderedCursorRect); | |||
} | |||
if (cursor) { | |||
updates.subtract(renderedCursorRect); | |||
updates.getUpdateInfo(&ui, req); | |||
} | |||
damagedCursorRegion.assign_union(renderedCursorRect); | |||
updateRenderedCursor = false; | |||
// Track where we've rendered the cursor | |||
damagedCursorRegion.assign_union(ui.changed.intersect(renderedCursorRect)); | |||
} | |||
if (ui.is_empty() && !writer()->needFakeUpdate() && !cursor) | |||
// Return if there is nothing to send the client. | |||
if (ui.is_empty() && !writer()->needFakeUpdate() && | |||
!encodeManager.needsLosslessRefresh(req)) | |||
return; | |||
writeRTTPing(); | |||
encodeManager.writeUpdate(ui, server->getPixelBuffer(), cursor); | |||
if (!ui.is_empty()) | |||
encodeManager.writeUpdate(ui, server->getPixelBuffer(), cursor); | |||
else { | |||
size_t maxUpdateSize; | |||
// FIXME: If continuous updates aren't used then the client might | |||
// be slower than frameRate in its requests and we could | |||
// afford a larger update size | |||
// FIXME: Bandwidth estimation without congestion control | |||
maxUpdateSize = congestion.getBandwidth() * | |||
server->msToNextUpdate() / 1000; | |||
encodeManager.writeLosslessRefresh(req, server->getPixelBuffer(), | |||
cursor, maxUpdateSize); | |||
} | |||
writeRTTPing(); | |||
@@ -1,5 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2009-2017 Pierre Ossman for Cendio AB | |||
* Copyright 2009-2018 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 | |||
@@ -105,10 +105,7 @@ VNCServerST::~VNCServerST() | |||
} | |||
// Stop the desktop object if active, *only* after deleting all clients! | |||
if (desktopStarted) { | |||
desktopStarted = false; | |||
desktop->stop(); | |||
} | |||
stopDesktop(); | |||
if (comparer) | |||
comparer->logStats(); | |||
@@ -154,12 +151,8 @@ void VNCServerST::removeSocket(network::Socket* sock) { | |||
delete *ci; | |||
// - Check that the desktop object is still required | |||
if (authClientCount() == 0 && desktopStarted) { | |||
slog.debug("no authenticated clients - stopping desktop"); | |||
desktopStarted = false; | |||
desktop->stop(); | |||
stopFrameClock(); | |||
} | |||
if (authClientCount() == 0) | |||
stopDesktop(); | |||
if (comparer) | |||
comparer->logStats(); | |||
@@ -552,6 +545,16 @@ void VNCServerST::startDesktop() | |||
} | |||
} | |||
void VNCServerST::stopDesktop() | |||
{ | |||
if (desktopStarted) { | |||
slog.debug("stopping desktop"); | |||
desktopStarted = false; | |||
desktop->stop(); | |||
stopFrameClock(); | |||
} | |||
} | |||
int VNCServerST::authClientCount() { | |||
int count = 0; | |||
std::list<VNCSConnectionST*>::iterator ci; | |||
@@ -576,6 +579,8 @@ void VNCServerST::startFrameClock() | |||
return; | |||
if (blockCounter > 0) | |||
return; | |||
if (!desktopStarted) | |||
return; | |||
// The first iteration will be just half a frame as we get a very | |||
// unstable update rate if we happen to be perfectly in sync with | |||
@@ -588,6 +593,17 @@ void VNCServerST::stopFrameClock() | |||
frameTimer.stop(); | |||
} | |||
int VNCServerST::msToNextUpdate() | |||
{ | |||
// FIXME: If the application is updating slower than frameRate then | |||
// we could allow the clients more time here | |||
if (!frameTimer.isStarted()) | |||
return 1000/rfb::Server::frameRate/2; | |||
else | |||
return frameTimer.getRemainingMs(); | |||
} | |||
// writeUpdate() is called on a regular interval in order to see what | |||
// updates are pending and propagates them to the update tracker for | |||
// each client. It uses the ComparingUpdateTracker's compare() method | |||
@@ -603,6 +619,7 @@ void VNCServerST::writeUpdate() | |||
std::list<VNCSConnectionST*>::iterator ci, ci_next; | |||
assert(blockCounter == 0); | |||
assert(desktopStarted); | |||
comparer->getUpdateInfo(&ui, pb->getRect()); | |||
toCheck = ui.changed.union_(ui.copied); | |||
@@ -639,17 +656,21 @@ void VNCServerST::writeUpdate() | |||
// checkUpdate() is called by clients to see if it is safe to read from | |||
// the framebuffer at this time. | |||
bool VNCServerST::checkUpdate() | |||
Region VNCServerST::getPendingRegion() | |||
{ | |||
UpdateInfo ui; | |||
// Block clients as the frame buffer cannot be safely accessed | |||
if (blockCounter > 0) | |||
return false; | |||
return pb->getRect(); | |||
// Block client from updating if there are pending updates | |||
if (!comparer->is_empty()) | |||
return false; | |||
if (comparer->is_empty()) | |||
return Region(); | |||
comparer->getUpdateInfo(&ui, pb->getRect()); | |||
return true; | |||
return ui.changed.union_(ui.copied); | |||
} | |||
const RenderedCursor* VNCServerST::getRenderedCursor() |
@@ -195,6 +195,7 @@ namespace rfb { | |||
// - Internal methods | |||
void startDesktop(); | |||
void stopDesktop(); | |||
static LogWriter connectionsLog; | |||
Blacklist blacklist; | |||
@@ -226,8 +227,9 @@ namespace rfb { | |||
bool needRenderedCursor(); | |||
void startFrameClock(); | |||
void stopFrameClock(); | |||
int msToNextUpdate(); | |||
void writeUpdate(); | |||
bool checkUpdate(); | |||
Region getPendingRegion(); | |||
const RenderedCursor* getRenderedCursor(); | |||
void notifyScreenLayoutChange(VNCSConnectionST *requester); |