Automatic lossless refresh
Resend pixel perfect copies of areas that were previously sent
using a lossy encoder. This is done when there is no normal update
to send, and no congestion.
diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx
index 0ceec8f..ca7c730 100644
--- a/common/rfb/EncodeManager.cxx
+++ b/common/rfb/EncodeManager.cxx
@@ -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>
@@ -47,6 +50,8 @@
// Don't bother with blocks smaller than this
static const int SolidBlockMinArea = 2048;
+static const int LosslessRefreshMaxArea = 4096;
+
namespace rfb {
enum EncoderClass {
@@ -245,17 +250,42 @@
}
}
+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)
+{
+ doUpdate(false, getLosslessRefresh(req),
+ 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, cursorRegion;
updates++;
- prepareEncoders();
+ prepareEncoders(allowLossy);
- changed = ui.changed;
+ changed = changed_;
/*
* We need to render the cursor seperately as it has its own
@@ -269,14 +299,14 @@
if (conn->cp.supportsLastRect)
nRects = 0xFFFF;
else {
- nRects = ui.copied.numRects();
+ 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
@@ -291,7 +321,7 @@
conn->writer()->writeFramebufferUpdateEnd();
}
-void EncodeManager::prepareEncoders()
+void EncodeManager::prepareEncoders(bool allowLossy)
{
enum EncoderClass solid, bitmap, bitmapRLE;
enum EncoderClass indexed, indexedRLE, fullColour;
@@ -316,7 +346,7 @@
break;
case encodingTight:
if (encoders[encoderTightJPEG]->isSupported() &&
- (conn->cp.pf().bpp >= 16))
+ (conn->cp.pf().bpp >= 16) && allowLossy)
fullColour = encoderTightJPEG;
else
fullColour = encoderTight;
@@ -334,7 +364,7 @@
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;
@@ -374,7 +404,7 @@
// 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;
}
@@ -398,6 +428,48 @@
}
}
+Region EncodeManager::getLosslessRefresh(const Region& req)
+{
+ std::vector<Rect> rects;
+ Region refresh;
+ size_t area;
+
+ 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()) > LosslessRefreshMaxArea) {
+ // Use the narrowest axis to avoid getting to thin rects
+ if (rect.width() > rect.height()) {
+ int width = (LosslessRefreshMaxArea - area) / rect.height();
+ rect.br.x = rect.tl.x + __rfbmax(1, width);
+ } else {
+ int height = (LosslessRefreshMaxArea - 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;
@@ -450,6 +522,11 @@
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;
}
@@ -466,14 +543,16 @@
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;
@@ -482,11 +561,16 @@
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)