Limit lossless refresh update to safe size
We don't want to waste bandwidth on the lossless refresh if we might
need that bandwidth for a normal update. Try to estimate how much
data we can safely send without interfering.
diff --git a/common/rfb/Congestion.cxx b/common/rfb/Congestion.cxx
index a2f7a25..4d36d9f 100644
--- a/common/rfb/Congestion.cxx
+++ b/common/rfb/Congestion.cxx
@@ -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 @@
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 @@
// 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 @@
}
}
+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
diff --git a/common/rfb/Congestion.h b/common/rfb/Congestion.h
index fd57c22..d293512 100644
--- a/common/rfb/Congestion.h
+++ b/common/rfb/Congestion.h
@@ -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 @@
// 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 @@
unsigned congWindow;
bool inSlowStart;
+ unsigned safeBaseRTT;
+
struct RTTInfo {
struct timeval tv;
unsigned pos;
diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx
index ca7c730..0ce611e 100644
--- a/common/rfb/EncodeManager.cxx
+++ b/common/rfb/EncodeManager.cxx
@@ -50,8 +50,6 @@
// Don't bother with blocks smaller than this
static const int SolidBlockMinArea = 2048;
-static const int LosslessRefreshMaxArea = 4096;
-
namespace rfb {
enum EncoderClass {
@@ -267,9 +265,10 @@
}
void EncodeManager::writeLosslessRefresh(const Region& req, const PixelBuffer* pb,
- const RenderedCursor* renderedCursor)
+ const RenderedCursor* renderedCursor,
+ size_t maxUpdateSize)
{
- doUpdate(false, getLosslessRefresh(req),
+ doUpdate(false, getLosslessRefresh(req, maxUpdateSize),
Region(), Point(), pb, renderedCursor);
}
@@ -428,12 +427,16 @@
}
}
-Region EncodeManager::getLosslessRefresh(const Region& req)
+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()) {
@@ -448,13 +451,13 @@
// Add rects until we exceed the threshold, then include as much as
// possible of the final rect
- if ((area + rect.area()) > LosslessRefreshMaxArea) {
+ if ((area + rect.area()) > maxUpdateSize) {
// Use the narrowest axis to avoid getting to thin rects
if (rect.width() > rect.height()) {
- int width = (LosslessRefreshMaxArea - area) / rect.height();
+ int width = (maxUpdateSize - area) / rect.height();
rect.br.x = rect.tl.x + __rfbmax(1, width);
} else {
- int height = (LosslessRefreshMaxArea - area) / rect.width();
+ int height = (maxUpdateSize - area) / rect.width();
rect.br.y = rect.tl.y + __rfbmax(1, height);
}
refresh.assign_union(Region(rect));
diff --git a/common/rfb/EncodeManager.h b/common/rfb/EncodeManager.h
index bdff04b..a91c544 100644
--- a/common/rfb/EncodeManager.h
+++ b/common/rfb/EncodeManager.h
@@ -53,7 +53,8 @@
const RenderedCursor* renderedCursor);
void writeLosslessRefresh(const Region& req, const PixelBuffer* pb,
- const RenderedCursor* renderedCursor);
+ const RenderedCursor* renderedCursor,
+ size_t maxUpdateSize);
protected:
void doUpdate(bool allowLossy, const Region& changed,
@@ -62,7 +63,7 @@
const RenderedCursor* renderedCursor);
void prepareEncoders(bool allowLossy);
- Region getLosslessRefresh(const Region& req);
+ Region getLosslessRefresh(const Region& req, size_t maxUpdateSize);
int computeNumRects(const Region& changed);
diff --git a/common/rfb/Timer.cxx b/common/rfb/Timer.cxx
index 7179cd8..fd1a87a 100644
--- a/common/rfb/Timer.cxx
+++ b/common/rfb/Timer.cxx
@@ -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::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 @@
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) &&
diff --git a/common/rfb/Timer.h b/common/rfb/Timer.h
index 78687d1..15b5d03 100644
--- a/common/rfb/Timer.h
+++ b/common/rfb/Timer.h
@@ -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 @@
// 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);
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index 77a6058..3f92a42 100644
--- a/common/rfb/VNCSConnectionST.cxx
+++ b/common/rfb/VNCSConnectionST.cxx
@@ -1067,8 +1067,20 @@
if (!ui.is_empty())
encodeManager.writeUpdate(ui, server->getPixelBuffer(), cursor);
- else
- encodeManager.writeLosslessRefresh(req, 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();
diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx
index 83f2b7e..15df71b 100644
--- a/common/rfb/VNCServerST.cxx
+++ b/common/rfb/VNCServerST.cxx
@@ -593,6 +593,17 @@
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
diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h
index 584c48c..9a1a44d 100644
--- a/common/rfb/VNCServerST.h
+++ b/common/rfb/VNCServerST.h
@@ -227,6 +227,7 @@
bool needRenderedCursor();
void startFrameClock();
void stopFrameClock();
+ int msToNextUpdate();
void writeUpdate();
bool checkUpdate();
const RenderedCursor* getRenderedCursor();