/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
* Copyright (C) 2011 D. R. Commander. All Rights Reserved.
* Copyright 2014-2018 Pierre Ossman for Cendio AB
+ * Copyright 2018 Peter Astrand 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
// Don't bother with blocks smaller than this
static const int SolidBlockMinArea = 2048;
+// How long we consider a region recently changed (in ms)
+static const int RecentChangeTimeout = 50;
+
namespace rfb {
enum EncoderClass {
return "Unknown Encoder Type";
}
-EncodeManager::EncodeManager(SConnection* conn_) : conn(conn_)
+EncodeManager::EncodeManager(SConnection* conn_)
+ : conn(conn_), recentChangeTimer(this)
{
StatsVector::iterator iter;
return !lossyRegion.intersect(req).is_empty();
}
+int EncodeManager::getNextLosslessRefresh(const Region& req)
+{
+ // Do we have something we can send right away?
+ if (!pendingRefreshRegion.intersect(req).is_empty())
+ return 0;
+
+ assert(needsLosslessRefresh(req));
+ assert(recentChangeTimer.isStarted());
+
+ return recentChangeTimer.getNextTimeout();
+}
+
void EncodeManager::pruneLosslessRefresh(const Region& limits)
{
lossyRegion.assign_intersect(limits);
+ pendingRefreshRegion.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);
+ doUpdate(true, ui.changed, ui.copied, ui.copy_delta, pb, renderedCursor);
+
+ recentlyChangedRegion.assign_union(ui.changed);
+ recentlyChangedRegion.assign_union(ui.copied);
+ if (!recentChangeTimer.isStarted())
+ recentChangeTimer.start(RecentChangeTimeout);
}
void EncodeManager::writeLosslessRefresh(const Region& req, const PixelBuffer* pb,
const RenderedCursor* renderedCursor,
size_t maxUpdateSize)
{
- doUpdate(false, getLosslessRefresh(req, maxUpdateSize),
- Region(), Point(), pb, renderedCursor);
+ doUpdate(false, getLosslessRefresh(req, maxUpdateSize),
+ Region(), Point(), pb, renderedCursor);
+}
+
+bool EncodeManager::handleTimeout(Timer* t)
+{
+ if (t == &recentChangeTimer) {
+ // Any lossy region that wasn't recently updated can
+ // now be scheduled for a refresh
+ pendingRefreshRegion.assign_union(lossyRegion.subtract(recentlyChangedRegion));
+ recentlyChangedRegion.clear();
+
+ // Will there be more to do? (i.e. do we need another round)
+ if (!lossyRegion.subtract(pendingRefreshRegion).is_empty())
+ return true;
+ }
+
+ return false;
}
void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
maxUpdateSize *= 2;
area = 0;
- lossyRegion.intersect(req).get_rects(&rects);
+ pendingRefreshRegion.intersect(req).get_rects(&rects);
while (!rects.empty()) {
size_t idx;
Rect rect;
else
lossyRegion.assign_subtract(Region(rect));
+ // This was either a rect getting refreshed, or a rect that just got
+ // new content. Either way we should not try to refresh it anymore.
+ pendingRefreshRegion.assign_subtract(Region(rect));
+
return encoder;
}
lossyCopy.translate(delta);
lossyCopy.assign_intersect(copied);
lossyRegion.assign_union(lossyCopy);
+
+ // Stop any pending refresh as a copy is enough that we consider
+ // this region to be recently changed
+ pendingRefreshRegion.assign_subtract(copied);
}
void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
#include <rdr/types.h>
#include <rfb/PixelBuffer.h>
#include <rfb/Region.h>
+#include <rfb/Timer.h>
namespace rfb {
class SConnection;
struct RectInfo;
- class EncodeManager {
+ class EncodeManager : public Timer::Callback {
public:
EncodeManager(SConnection* conn);
~EncodeManager();
static bool supported(int encoding);
bool needsLosslessRefresh(const Region& req);
+ int getNextLosslessRefresh(const Region& req);
+
void pruneLosslessRefresh(const Region& limits);
void writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb,
size_t maxUpdateSize);
protected:
+ virtual bool handleTimeout(Timer* t);
+
void doUpdate(bool allowLossy, const Region& changed,
const Region& copied, const Point& copy_delta,
const PixelBuffer* pb,
std::vector<int> activeEncoders;
Region lossyRegion;
+ Region recentlyChangedRegion;
+ Region pendingRefreshRegion;
+
+ Timer recentChangeTimer;
struct EncoderStats {
unsigned rects;
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright 2009-2018 Pierre Ossman for Cendio AB
+ * Copyright 2018 Peter Astrand 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
inProcessMessages(false),
pendingSyncFence(false), syncFence(false), fenceFlags(0),
fenceDataLen(0), fenceData(NULL), congestionTimer(this),
- server(server_), updates(false),
+ losslessTimer(this), server(server_), updates(false),
updateRenderedCursor(false), removeRenderedCursor(false),
continuousUpdates(false), encodeManager(this), pointerEventTime(0),
clientHasCursor(false),
bool VNCSConnectionST::handleTimeout(Timer* t)
{
try {
- if (t == &congestionTimer)
+ if ((t == &congestionTimer) ||
+ (t == &losslessTimer))
writeFramebufferUpdate();
} catch (rdr::Exception& e) {
close(e.str());
}
// Return if there is nothing to send the client.
+ if (ui.is_empty() && !writer()->needFakeUpdate()) {
+ int eta;
- if (ui.is_empty() && !writer()->needFakeUpdate() &&
- !encodeManager.needsLosslessRefresh(req))
- return;
+ // Any lossless refresh that needs handling?
+ if (!encodeManager.needsLosslessRefresh(req))
+ return;
+
+ // Now? Or later?
+ eta = encodeManager.getNextLosslessRefresh(req);
+ if (eta > 0) {
+ losslessTimer.start(eta);
+ return;
+ }
+ }
writeRTTPing();