blob: 7589cb6a8495a8da448a4ce2cac81861c1696ec5 [file] [log] [blame]
Pierre Ossmanc0397262014-03-14 15:59:46 +01001/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
2 * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
Pierre Ossman8c3bd692018-03-22 15:58:54 +01003 * Copyright 2014-2018 Pierre Ossman for Cendio AB
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +02004 * Copyright 2018 Peter Astrand for Cendio AB
Pierre Ossmanc0397262014-03-14 15:59:46 +01005 *
6 * This is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This software is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this software; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
19 * USA.
20 */
Pierre Ossman6b2f1132016-11-30 08:03:35 +010021
22#include <stdlib.h>
23
Pierre Ossmanc0397262014-03-14 15:59:46 +010024#include <rfb/EncodeManager.h>
25#include <rfb/Encoder.h>
26#include <rfb/Palette.h>
27#include <rfb/SConnection.h>
28#include <rfb/SMsgWriter.h>
29#include <rfb/UpdateTracker.h>
Pierre Ossman20dd2a92015-02-11 17:43:15 +010030#include <rfb/LogWriter.h>
Pierre Ossmanc0397262014-03-14 15:59:46 +010031
32#include <rfb/RawEncoder.h>
33#include <rfb/RREEncoder.h>
34#include <rfb/HextileEncoder.h>
35#include <rfb/ZRLEEncoder.h>
36#include <rfb/TightEncoder.h>
37#include <rfb/TightJPEGEncoder.h>
38
39using namespace rfb;
40
Pierre Ossman20dd2a92015-02-11 17:43:15 +010041static LogWriter vlog("EncodeManager");
42
Pierre Ossmanc0397262014-03-14 15:59:46 +010043// Split each rectangle into smaller ones no larger than this area,
44// and no wider than this width.
45static const int SubRectMaxArea = 65536;
46static const int SubRectMaxWidth = 2048;
47
48// The size in pixels of either side of each block tested when looking
49// for solid blocks.
50static const int SolidSearchBlock = 16;
51// Don't bother with blocks smaller than this
52static const int SolidBlockMinArea = 2048;
53
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +020054// How long we consider a region recently changed (in ms)
55static const int RecentChangeTimeout = 50;
56
Pierre Ossmanc0397262014-03-14 15:59:46 +010057namespace rfb {
58
59enum EncoderClass {
60 encoderRaw,
61 encoderRRE,
62 encoderHextile,
63 encoderTight,
64 encoderTightJPEG,
65 encoderZRLE,
66 encoderClassMax,
67};
68
69enum EncoderType {
70 encoderSolid,
71 encoderBitmap,
72 encoderBitmapRLE,
73 encoderIndexed,
74 encoderIndexedRLE,
75 encoderFullColour,
76 encoderTypeMax,
77};
78
79struct RectInfo {
80 int rleRuns;
81 Palette palette;
82};
83
84};
85
Pierre Ossman20dd2a92015-02-11 17:43:15 +010086static const char *encoderClassName(EncoderClass klass)
87{
88 switch (klass) {
89 case encoderRaw:
90 return "Raw";
91 case encoderRRE:
92 return "RRE";
93 case encoderHextile:
94 return "Hextile";
95 case encoderTight:
96 return "Tight";
97 case encoderTightJPEG:
98 return "Tight (JPEG)";
99 case encoderZRLE:
100 return "ZRLE";
Pierre Ossman620dd952015-03-03 16:28:54 +0100101 case encoderClassMax:
102 break;
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100103 }
104
105 return "Unknown Encoder Class";
106}
107
108static const char *encoderTypeName(EncoderType type)
109{
110 switch (type) {
111 case encoderSolid:
112 return "Solid";
113 case encoderBitmap:
114 return "Bitmap";
115 case encoderBitmapRLE:
116 return "Bitmap RLE";
117 case encoderIndexed:
118 return "Indexed";
119 case encoderIndexedRLE:
120 return "Indexed RLE";
121 case encoderFullColour:
122 return "Full Colour";
Pierre Ossman620dd952015-03-03 16:28:54 +0100123 case encoderTypeMax:
124 break;
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100125 }
126
127 return "Unknown Encoder Type";
128}
129
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200130EncodeManager::EncodeManager(SConnection* conn_)
131 : conn(conn_), recentChangeTimer(this)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100132{
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100133 StatsVector::iterator iter;
134
Pierre Ossmanc0397262014-03-14 15:59:46 +0100135 encoders.resize(encoderClassMax, NULL);
136 activeEncoders.resize(encoderTypeMax, encoderRaw);
137
138 encoders[encoderRaw] = new RawEncoder(conn);
139 encoders[encoderRRE] = new RREEncoder(conn);
140 encoders[encoderHextile] = new HextileEncoder(conn);
141 encoders[encoderTight] = new TightEncoder(conn);
142 encoders[encoderTightJPEG] = new TightJPEGEncoder(conn);
143 encoders[encoderZRLE] = new ZRLEEncoder(conn);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100144
145 updates = 0;
Pierre Ossmane539cb82015-09-22 11:09:00 +0200146 memset(&copyStats, 0, sizeof(copyStats));
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100147 stats.resize(encoderClassMax);
148 for (iter = stats.begin();iter != stats.end();++iter) {
149 StatsVector::value_type::iterator iter2;
150 iter->resize(encoderTypeMax);
151 for (iter2 = iter->begin();iter2 != iter->end();++iter2)
152 memset(&*iter2, 0, sizeof(EncoderStats));
153 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100154}
155
156EncodeManager::~EncodeManager()
157{
158 std::vector<Encoder*>::iterator iter;
159
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100160 logStats();
161
Pierre Ossmanc0397262014-03-14 15:59:46 +0100162 for (iter = encoders.begin();iter != encoders.end();iter++)
163 delete *iter;
164}
165
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100166void EncodeManager::logStats()
167{
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100168 size_t i, j;
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100169
170 unsigned rects;
171 unsigned long long pixels, bytes, equivalent;
172
173 double ratio;
174
Pierre Ossman64624342015-03-03 16:30:13 +0100175 char a[1024], b[1024];
176
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100177 rects = 0;
178 pixels = bytes = equivalent = 0;
179
180 vlog.info("Framebuffer updates: %u", updates);
181
Pierre Ossmane539cb82015-09-22 11:09:00 +0200182 if (copyStats.rects != 0) {
183 vlog.info(" %s:", "CopyRect");
184
185 rects += copyStats.rects;
186 pixels += copyStats.pixels;
187 bytes += copyStats.bytes;
188 equivalent += copyStats.equivalent;
189
190 ratio = (double)copyStats.equivalent / copyStats.bytes;
191
192 siPrefix(copyStats.rects, "rects", a, sizeof(a));
193 siPrefix(copyStats.pixels, "pixels", b, sizeof(b));
194 vlog.info(" %s: %s, %s", "Copies", a, b);
195 iecPrefix(copyStats.bytes, "B", a, sizeof(a));
196 vlog.info(" %*s %s (1:%g ratio)",
197 (int)strlen("Copies"), "",
198 a, ratio);
199 }
200
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100201 for (i = 0;i < stats.size();i++) {
202 // Did this class do anything at all?
203 for (j = 0;j < stats[i].size();j++) {
204 if (stats[i][j].rects != 0)
205 break;
206 }
207 if (j == stats[i].size())
208 continue;
209
210 vlog.info(" %s:", encoderClassName((EncoderClass)i));
211
212 for (j = 0;j < stats[i].size();j++) {
213 if (stats[i][j].rects == 0)
214 continue;
215
216 rects += stats[i][j].rects;
217 pixels += stats[i][j].pixels;
218 bytes += stats[i][j].bytes;
219 equivalent += stats[i][j].equivalent;
220
221 ratio = (double)stats[i][j].equivalent / stats[i][j].bytes;
222
Pierre Ossman64624342015-03-03 16:30:13 +0100223 siPrefix(stats[i][j].rects, "rects", a, sizeof(a));
224 siPrefix(stats[i][j].pixels, "pixels", b, sizeof(b));
225 vlog.info(" %s: %s, %s", encoderTypeName((EncoderType)j), a, b);
226 iecPrefix(stats[i][j].bytes, "B", a, sizeof(a));
227 vlog.info(" %*s %s (1:%g ratio)",
228 (int)strlen(encoderTypeName((EncoderType)j)), "",
229 a, ratio);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100230 }
231 }
232
233 ratio = (double)equivalent / bytes;
234
Pierre Ossman64624342015-03-03 16:30:13 +0100235 siPrefix(rects, "rects", a, sizeof(a));
236 siPrefix(pixels, "pixels", b, sizeof(b));
237 vlog.info(" Total: %s, %s", a, b);
238 iecPrefix(bytes, "B", a, sizeof(a));
239 vlog.info(" %s (1:%g ratio)", a, ratio);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100240}
241
Pierre Ossmanc0397262014-03-14 15:59:46 +0100242bool EncodeManager::supported(int encoding)
243{
244 switch (encoding) {
245 case encodingRaw:
246 case encodingRRE:
247 case encodingHextile:
248 case encodingZRLE:
249 case encodingTight:
250 return true;
251 default:
252 return false;
253 }
254}
255
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100256bool EncodeManager::needsLosslessRefresh(const Region& req)
257{
258 return !lossyRegion.intersect(req).is_empty();
259}
260
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200261int EncodeManager::getNextLosslessRefresh(const Region& req)
262{
263 // Do we have something we can send right away?
264 if (!pendingRefreshRegion.intersect(req).is_empty())
265 return 0;
266
267 assert(needsLosslessRefresh(req));
268 assert(recentChangeTimer.isStarted());
269
270 return recentChangeTimer.getNextTimeout();
271}
272
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100273void EncodeManager::pruneLosslessRefresh(const Region& limits)
274{
275 lossyRegion.assign_intersect(limits);
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200276 pendingRefreshRegion.assign_intersect(limits);
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100277}
278
Pierre Ossmanc0397262014-03-14 15:59:46 +0100279void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb,
280 const RenderedCursor* renderedCursor)
281{
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200282 doUpdate(true, ui.changed, ui.copied, ui.copy_delta, pb, renderedCursor);
283
284 recentlyChangedRegion.assign_union(ui.changed);
285 recentlyChangedRegion.assign_union(ui.copied);
286 if (!recentChangeTimer.isStarted())
287 recentChangeTimer.start(RecentChangeTimeout);
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100288}
289
290void EncodeManager::writeLosslessRefresh(const Region& req, const PixelBuffer* pb,
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100291 const RenderedCursor* renderedCursor,
292 size_t maxUpdateSize)
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100293{
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200294 doUpdate(false, getLosslessRefresh(req, maxUpdateSize),
295 Region(), Point(), pb, renderedCursor);
296}
297
298bool EncodeManager::handleTimeout(Timer* t)
299{
300 if (t == &recentChangeTimer) {
301 // Any lossy region that wasn't recently updated can
302 // now be scheduled for a refresh
303 pendingRefreshRegion.assign_union(lossyRegion.subtract(recentlyChangedRegion));
304 recentlyChangedRegion.clear();
305
306 // Will there be more to do? (i.e. do we need another round)
307 if (!lossyRegion.subtract(pendingRefreshRegion).is_empty())
308 return true;
309 }
310
311 return false;
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100312}
313
314void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
315 const Region& copied, const Point& copyDelta,
316 const PixelBuffer* pb,
317 const RenderedCursor* renderedCursor)
318{
Pierre Ossmanc0397262014-03-14 15:59:46 +0100319 int nRects;
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100320 Region changed, cursorRegion;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100321
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100322 updates++;
323
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100324 prepareEncoders(allowLossy);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100325
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100326 changed = changed_;
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100327
328 /*
329 * We need to render the cursor seperately as it has its own
330 * magical pixel buffer, so split it out from the changed region.
331 */
332 if (renderedCursor != NULL) {
333 cursorRegion = changed.intersect(renderedCursor->getEffectiveRect());
334 changed.assign_subtract(renderedCursor->getEffectiveRect());
335 }
336
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200337 if (conn->client.supportsLastRect)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100338 nRects = 0xFFFF;
339 else {
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100340 nRects = copied.numRects();
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100341 nRects += computeNumRects(changed);
342 nRects += computeNumRects(cursorRegion);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100343 }
344
345 conn->writer()->writeFramebufferUpdateStart(nRects);
346
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100347 writeCopyRects(copied, copyDelta);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100348
349 /*
350 * We start by searching for solid rects, which are then removed
351 * from the changed region.
352 */
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200353 if (conn->client.supportsLastRect)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100354 writeSolidRects(&changed, pb);
355
356 writeRects(changed, pb);
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100357 writeRects(cursorRegion, renderedCursor);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100358
359 conn->writer()->writeFramebufferUpdateEnd();
360}
361
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100362void EncodeManager::prepareEncoders(bool allowLossy)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100363{
364 enum EncoderClass solid, bitmap, bitmapRLE;
365 enum EncoderClass indexed, indexedRLE, fullColour;
366
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200367 bool allowJPEG;
368
Pierre Ossmanc0397262014-03-14 15:59:46 +0100369 rdr::S32 preferred;
370
371 std::vector<int>::iterator iter;
372
373 solid = bitmap = bitmapRLE = encoderRaw;
374 indexed = indexedRLE = fullColour = encoderRaw;
375
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200376 allowJPEG = conn->client.pf().bpp >= 16;
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200377 if (!allowLossy) {
378 if (encoders[encoderTightJPEG]->losslessQuality == -1)
379 allowJPEG = false;
380 }
381
Pierre Ossmanc0397262014-03-14 15:59:46 +0100382 // Try to respect the client's wishes
Pierre Ossman48700812014-09-17 17:11:56 +0200383 preferred = conn->getPreferredEncoding();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100384 switch (preferred) {
385 case encodingRRE:
386 // Horrible for anything high frequency and/or lots of colours
387 bitmapRLE = indexedRLE = encoderRRE;
388 break;
389 case encodingHextile:
390 // Slightly less horrible
391 bitmapRLE = indexedRLE = fullColour = encoderHextile;
392 break;
393 case encodingTight:
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200394 if (encoders[encoderTightJPEG]->isSupported() && allowJPEG)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100395 fullColour = encoderTightJPEG;
396 else
397 fullColour = encoderTight;
398 indexed = indexedRLE = encoderTight;
399 bitmap = bitmapRLE = encoderTight;
400 break;
401 case encodingZRLE:
402 fullColour = encoderZRLE;
403 bitmapRLE = indexedRLE = encoderZRLE;
404 bitmap = indexed = encoderZRLE;
405 break;
406 }
407
408 // Any encoders still unassigned?
409
410 if (fullColour == encoderRaw) {
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200411 if (encoders[encoderTightJPEG]->isSupported() && allowJPEG)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100412 fullColour = encoderTightJPEG;
413 else if (encoders[encoderZRLE]->isSupported())
414 fullColour = encoderZRLE;
415 else if (encoders[encoderTight]->isSupported())
416 fullColour = encoderTight;
417 else if (encoders[encoderHextile]->isSupported())
418 fullColour = encoderHextile;
419 }
420
421 if (indexed == encoderRaw) {
422 if (encoders[encoderZRLE]->isSupported())
423 indexed = encoderZRLE;
424 else if (encoders[encoderTight]->isSupported())
425 indexed = encoderTight;
426 else if (encoders[encoderHextile]->isSupported())
427 indexed = encoderHextile;
428 }
429
430 if (indexedRLE == encoderRaw)
431 indexedRLE = indexed;
432
433 if (bitmap == encoderRaw)
434 bitmap = indexed;
435 if (bitmapRLE == encoderRaw)
436 bitmapRLE = bitmap;
437
438 if (solid == encoderRaw) {
439 if (encoders[encoderTight]->isSupported())
440 solid = encoderTight;
441 else if (encoders[encoderRRE]->isSupported())
442 solid = encoderRRE;
443 else if (encoders[encoderZRLE]->isSupported())
444 solid = encoderZRLE;
445 else if (encoders[encoderHextile]->isSupported())
446 solid = encoderHextile;
447 }
448
449 // JPEG is the only encoder that can reduce things to grayscale
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200450 if ((conn->client.subsampling == subsampleGray) &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100451 encoders[encoderTightJPEG]->isSupported() && allowLossy) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100452 solid = bitmap = bitmapRLE = encoderTightJPEG;
453 indexed = indexedRLE = fullColour = encoderTightJPEG;
454 }
455
456 activeEncoders[encoderSolid] = solid;
457 activeEncoders[encoderBitmap] = bitmap;
458 activeEncoders[encoderBitmapRLE] = bitmapRLE;
459 activeEncoders[encoderIndexed] = indexed;
460 activeEncoders[encoderIndexedRLE] = indexedRLE;
461 activeEncoders[encoderFullColour] = fullColour;
462
463 for (iter = activeEncoders.begin(); iter != activeEncoders.end(); ++iter) {
464 Encoder *encoder;
465
466 encoder = encoders[*iter];
467
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200468 encoder->setCompressLevel(conn->client.compressLevel);
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200469
470 if (allowLossy) {
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200471 encoder->setQualityLevel(conn->client.qualityLevel);
472 encoder->setFineQualityLevel(conn->client.fineQualityLevel,
473 conn->client.subsampling);
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200474 } else {
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200475 int level = __rfbmax(conn->client.qualityLevel,
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200476 encoder->losslessQuality);
477 encoder->setQualityLevel(level);
478 encoder->setFineQualityLevel(-1, subsampleUndefined);
479 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100480 }
481}
482
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100483Region EncodeManager::getLosslessRefresh(const Region& req,
484 size_t maxUpdateSize)
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100485{
486 std::vector<Rect> rects;
487 Region refresh;
488 size_t area;
489
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100490 // We make a conservative guess at the compression ratio at 2:1
491 maxUpdateSize *= 2;
492
Pierre Ossman10bea282018-09-19 16:27:56 +0200493 // We will measure pixels, not bytes (assume 32 bpp)
494 maxUpdateSize /= 4;
495
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100496 area = 0;
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200497 pendingRefreshRegion.intersect(req).get_rects(&rects);
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100498 while (!rects.empty()) {
499 size_t idx;
500 Rect rect;
501
502 // Grab a random rect so we don't keep damaging and restoring the
503 // same rect over and over
504 idx = rand() % rects.size();
505
506 rect = rects[idx];
507
508 // Add rects until we exceed the threshold, then include as much as
509 // possible of the final rect
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100510 if ((area + rect.area()) > maxUpdateSize) {
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100511 // Use the narrowest axis to avoid getting to thin rects
512 if (rect.width() > rect.height()) {
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100513 int width = (maxUpdateSize - area) / rect.height();
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100514 rect.br.x = rect.tl.x + __rfbmax(1, width);
515 } else {
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100516 int height = (maxUpdateSize - area) / rect.width();
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100517 rect.br.y = rect.tl.y + __rfbmax(1, height);
518 }
519 refresh.assign_union(Region(rect));
520 break;
521 }
522
523 area += rect.area();
524 refresh.assign_union(Region(rect));
525
526 rects.erase(rects.begin() + idx);
527 }
528
529 return refresh;
530}
531
Pierre Ossmanc0397262014-03-14 15:59:46 +0100532int EncodeManager::computeNumRects(const Region& changed)
533{
534 int numRects;
535 std::vector<Rect> rects;
536 std::vector<Rect>::const_iterator rect;
537
538 numRects = 0;
539 changed.get_rects(&rects);
540 for (rect = rects.begin(); rect != rects.end(); ++rect) {
541 int w, h, sw, sh;
542
543 w = rect->width();
544 h = rect->height();
545
546 // No split necessary?
547 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
548 numRects += 1;
549 continue;
550 }
551
552 if (w <= SubRectMaxWidth)
553 sw = w;
554 else
555 sw = SubRectMaxWidth;
556
557 sh = SubRectMaxArea / sw;
558
559 // ceil(w/sw) * ceil(h/sh)
560 numRects += (((w - 1)/sw) + 1) * (((h - 1)/sh) + 1);
561 }
562
563 return numRects;
564}
565
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100566Encoder *EncodeManager::startRect(const Rect& rect, int type)
567{
568 Encoder *encoder;
569 int klass, equiv;
570
571 activeType = type;
572 klass = activeEncoders[activeType];
573
574 beforeLength = conn->getOutStream()->length();
575
576 stats[klass][activeType].rects++;
577 stats[klass][activeType].pixels += rect.area();
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200578 equiv = 12 + rect.area() * (conn->client.pf().bpp/8);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100579 stats[klass][activeType].equivalent += equiv;
580
581 encoder = encoders[klass];
582 conn->writer()->startRect(rect, encoder->encoding);
583
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200584 if ((encoder->flags & EncoderLossy) &&
585 ((encoder->losslessQuality == -1) ||
586 (encoder->getQualityLevel() < encoder->losslessQuality)))
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100587 lossyRegion.assign_union(Region(rect));
588 else
589 lossyRegion.assign_subtract(Region(rect));
590
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200591 // This was either a rect getting refreshed, or a rect that just got
592 // new content. Either way we should not try to refresh it anymore.
593 pendingRefreshRegion.assign_subtract(Region(rect));
594
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100595 return encoder;
596}
597
598void EncodeManager::endRect()
599{
600 int klass;
601 int length;
602
603 conn->writer()->endRect();
604
605 length = conn->getOutStream()->length() - beforeLength;
606
607 klass = activeEncoders[activeType];
608 stats[klass][activeType].bytes += length;
609}
610
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100611void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100612{
613 std::vector<Rect> rects;
614 std::vector<Rect>::const_iterator rect;
615
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100616 Region lossyCopy;
617
Pierre Ossmane539cb82015-09-22 11:09:00 +0200618 beforeLength = conn->getOutStream()->length();
619
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100620 copied.get_rects(&rects, delta.x <= 0, delta.y <= 0);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100621 for (rect = rects.begin(); rect != rects.end(); ++rect) {
Pierre Ossmane539cb82015-09-22 11:09:00 +0200622 int equiv;
623
624 copyStats.rects++;
625 copyStats.pixels += rect->area();
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200626 equiv = 12 + rect->area() * (conn->client.pf().bpp/8);
Pierre Ossmane539cb82015-09-22 11:09:00 +0200627 copyStats.equivalent += equiv;
628
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100629 conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x,
630 rect->tl.y - delta.y);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100631 }
Pierre Ossmane539cb82015-09-22 11:09:00 +0200632
633 copyStats.bytes += conn->getOutStream()->length() - beforeLength;
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100634
635 lossyCopy = lossyRegion;
636 lossyCopy.translate(delta);
637 lossyCopy.assign_intersect(copied);
638 lossyRegion.assign_union(lossyCopy);
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200639
640 // Stop any pending refresh as a copy is enough that we consider
641 // this region to be recently changed
642 pendingRefreshRegion.assign_subtract(copied);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100643}
644
645void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
646{
647 std::vector<Rect> rects;
648 std::vector<Rect>::const_iterator rect;
649
Pierre Ossmanc0397262014-03-14 15:59:46 +0100650 changed->get_rects(&rects);
Pierre Ossmaneef55162015-02-12 13:44:22 +0100651 for (rect = rects.begin(); rect != rects.end(); ++rect)
652 findSolidRect(*rect, changed, pb);
653}
Pierre Ossmanc0397262014-03-14 15:59:46 +0100654
Pierre Ossmaneef55162015-02-12 13:44:22 +0100655void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
656 const PixelBuffer* pb)
657{
658 Rect sr;
659 int dx, dy, dw, dh;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100660
Pierre Ossmaneef55162015-02-12 13:44:22 +0100661 // We start by finding a solid 16x16 block
662 for (dy = rect.tl.y; dy < rect.br.y; dy += SolidSearchBlock) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100663
Pierre Ossmaneef55162015-02-12 13:44:22 +0100664 dh = SolidSearchBlock;
665 if (dy + dh > rect.br.y)
666 dh = rect.br.y - dy;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100667
Pierre Ossmaneef55162015-02-12 13:44:22 +0100668 for (dx = rect.tl.x; dx < rect.br.x; dx += SolidSearchBlock) {
669 // We define it like this to guarantee alignment
670 rdr::U32 _buffer;
671 rdr::U8* colourValue = (rdr::U8*)&_buffer;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100672
Pierre Ossmaneef55162015-02-12 13:44:22 +0100673 dw = SolidSearchBlock;
674 if (dx + dw > rect.br.x)
675 dw = rect.br.x - dx;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100676
Pierre Ossmaneef55162015-02-12 13:44:22 +0100677 pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
Pierre Ossmanc0397262014-03-14 15:59:46 +0100678
Pierre Ossmaneef55162015-02-12 13:44:22 +0100679 sr.setXYWH(dx, dy, dw, dh);
680 if (checkSolidTile(sr, colourValue, pb)) {
681 Rect erb, erp;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100682
Pierre Ossmaneef55162015-02-12 13:44:22 +0100683 Encoder *encoder;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100684
Pierre Ossmaneef55162015-02-12 13:44:22 +0100685 // We then try extending the area by adding more blocks
686 // in both directions and pick the combination that gives
687 // the largest area.
688 sr.setXYWH(dx, dy, rect.br.x - dx, rect.br.y - dy);
689 extendSolidAreaByBlock(sr, colourValue, pb, &erb);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100690
Pierre Ossmaneef55162015-02-12 13:44:22 +0100691 // Did we end up getting the entire rectangle?
692 if (erb.equals(rect))
693 erp = erb;
694 else {
695 // Don't bother with sending tiny rectangles
696 if (erb.area() < SolidBlockMinArea)
697 continue;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100698
Pierre Ossmaneef55162015-02-12 13:44:22 +0100699 // Extend the area again, but this time one pixel
700 // row/column at a time.
701 extendSolidAreaByPixel(rect, erb, colourValue, pb, &erp);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100702 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100703
Pierre Ossmaneef55162015-02-12 13:44:22 +0100704 // Send solid-color rectangle.
705 encoder = startRect(erp, encoderSolid);
706 if (encoder->flags & EncoderUseNativePF) {
707 encoder->writeSolidRect(erp.width(), erp.height(),
708 pb->getPF(), colourValue);
709 } else {
710 rdr::U32 _buffer2;
711 rdr::U8* converted = (rdr::U8*)&_buffer2;
712
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200713 conn->client.pf().bufferFromBuffer(converted, pb->getPF(),
Pierre Ossmaneef55162015-02-12 13:44:22 +0100714 colourValue, 1);
715
716 encoder->writeSolidRect(erp.width(), erp.height(),
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200717 conn->client.pf(), converted);
Pierre Ossmaneef55162015-02-12 13:44:22 +0100718 }
719 endRect();
720
721 changed->assign_subtract(Region(erp));
722
723 // Search remaining areas by recursion
724 // FIXME: Is this the best way to divide things up?
725
726 // Left? (Note that we've already searched a SolidSearchBlock
727 // pixels high strip here)
728 if ((erp.tl.x != rect.tl.x) && (erp.height() > SolidSearchBlock)) {
729 sr.setXYWH(rect.tl.x, erp.tl.y + SolidSearchBlock,
730 erp.tl.x - rect.tl.x, erp.height() - SolidSearchBlock);
731 findSolidRect(sr, changed, pb);
732 }
733
734 // Right?
735 if (erp.br.x != rect.br.x) {
736 sr.setXYWH(erp.br.x, erp.tl.y, rect.br.x - erp.br.x, erp.height());
737 findSolidRect(sr, changed, pb);
738 }
739
740 // Below?
741 if (erp.br.y != rect.br.y) {
742 sr.setXYWH(rect.tl.x, erp.br.y, rect.width(), rect.br.y - erp.br.y);
743 findSolidRect(sr, changed, pb);
744 }
745
746 return;
747 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100748 }
749 }
750}
751
752void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb)
753{
754 std::vector<Rect> rects;
755 std::vector<Rect>::const_iterator rect;
756
757 changed.get_rects(&rects);
758 for (rect = rects.begin(); rect != rects.end(); ++rect) {
759 int w, h, sw, sh;
760 Rect sr;
761
762 w = rect->width();
763 h = rect->height();
764
765 // No split necessary?
766 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
767 writeSubRect(*rect, pb);
768 continue;
769 }
770
771 if (w <= SubRectMaxWidth)
772 sw = w;
773 else
774 sw = SubRectMaxWidth;
775
776 sh = SubRectMaxArea / sw;
777
778 for (sr.tl.y = rect->tl.y; sr.tl.y < rect->br.y; sr.tl.y += sh) {
779 sr.br.y = sr.tl.y + sh;
780 if (sr.br.y > rect->br.y)
781 sr.br.y = rect->br.y;
782
783 for (sr.tl.x = rect->tl.x; sr.tl.x < rect->br.x; sr.tl.x += sw) {
784 sr.br.x = sr.tl.x + sw;
785 if (sr.br.x > rect->br.x)
786 sr.br.x = rect->br.x;
787
788 writeSubRect(sr, pb);
789 }
790 }
791 }
792}
793
794void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
795{
796 PixelBuffer *ppb;
797
798 Encoder *encoder;
799
800 struct RectInfo info;
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100801 unsigned int divisor, maxColours;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100802
803 bool useRLE;
804 EncoderType type;
805
806 // FIXME: This is roughly the algorithm previously used by the Tight
807 // encoder. It seems a bit backwards though, that higher
808 // compression setting means spending less effort in building
809 // a palette. It might be that they figured the increase in
810 // zlib setting compensated for the loss.
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200811 if (conn->client.compressLevel == -1)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100812 divisor = 2 * 8;
813 else
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200814 divisor = conn->client.compressLevel * 8;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100815 if (divisor < 4)
816 divisor = 4;
817
818 maxColours = rect.area()/divisor;
819
820 // Special exception inherited from the Tight encoder
821 if (activeEncoders[encoderFullColour] == encoderTightJPEG) {
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200822 if ((conn->client.compressLevel != -1) && (conn->client.compressLevel < 2))
Pierre Ossmanc0397262014-03-14 15:59:46 +0100823 maxColours = 24;
824 else
825 maxColours = 96;
826 }
827
828 if (maxColours < 2)
829 maxColours = 2;
830
831 encoder = encoders[activeEncoders[encoderIndexedRLE]];
832 if (maxColours > encoder->maxPaletteSize)
833 maxColours = encoder->maxPaletteSize;
834 encoder = encoders[activeEncoders[encoderIndexed]];
835 if (maxColours > encoder->maxPaletteSize)
836 maxColours = encoder->maxPaletteSize;
837
838 ppb = preparePixelBuffer(rect, pb, true);
839
840 if (!analyseRect(ppb, &info, maxColours))
841 info.palette.clear();
842
843 // Different encoders might have different RLE overhead, but
844 // here we do a guess at RLE being the better choice if reduces
845 // the pixel count by 50%.
846 useRLE = info.rleRuns <= (rect.area() * 2);
847
848 switch (info.palette.size()) {
849 case 0:
850 type = encoderFullColour;
851 break;
852 case 1:
853 type = encoderSolid;
854 break;
855 case 2:
856 if (useRLE)
857 type = encoderBitmapRLE;
858 else
859 type = encoderBitmap;
860 break;
861 default:
862 if (useRLE)
863 type = encoderIndexedRLE;
864 else
865 type = encoderIndexed;
866 }
867
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100868 encoder = startRect(rect, type);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100869
870 if (encoder->flags & EncoderUseNativePF)
871 ppb = preparePixelBuffer(rect, pb, false);
872
Pierre Ossmanc0397262014-03-14 15:59:46 +0100873 encoder->writeRect(ppb, info.palette);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100874
875 endRect();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100876}
877
878bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue,
879 const PixelBuffer *pb)
880{
881 switch (pb->getPF().bpp) {
882 case 32:
883 return checkSolidTile(r, *(const rdr::U32*)colourValue, pb);
884 case 16:
885 return checkSolidTile(r, *(const rdr::U16*)colourValue, pb);
886 default:
887 return checkSolidTile(r, *(const rdr::U8*)colourValue, pb);
888 }
889}
890
891void EncodeManager::extendSolidAreaByBlock(const Rect& r,
892 const rdr::U8* colourValue,
893 const PixelBuffer *pb, Rect* er)
894{
895 int dx, dy, dw, dh;
896 int w_prev;
897 Rect sr;
898 int w_best = 0, h_best = 0;
899
900 w_prev = r.width();
901
902 // We search width first, back off when we hit a different colour,
903 // and restart with a larger height. We keep track of the
904 // width/height combination that gives us the largest area.
905 for (dy = r.tl.y; dy < r.br.y; dy += SolidSearchBlock) {
906
907 dh = SolidSearchBlock;
908 if (dy + dh > r.br.y)
909 dh = r.br.y - dy;
910
911 // We test one block here outside the x loop in order to break
912 // the y loop right away.
913 dw = SolidSearchBlock;
914 if (dw > w_prev)
915 dw = w_prev;
916
917 sr.setXYWH(r.tl.x, dy, dw, dh);
918 if (!checkSolidTile(sr, colourValue, pb))
919 break;
920
921 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
922
923 dw = SolidSearchBlock;
924 if (dx + dw > r.tl.x + w_prev)
925 dw = r.tl.x + w_prev - dx;
926
927 sr.setXYWH(dx, dy, dw, dh);
928 if (!checkSolidTile(sr, colourValue, pb))
929 break;
930
931 dx += dw;
932 }
933
934 w_prev = dx - r.tl.x;
935 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
936 w_best = w_prev;
937 h_best = dy + dh - r.tl.y;
938 }
939 }
940
941 er->tl.x = r.tl.x;
942 er->tl.y = r.tl.y;
943 er->br.x = er->tl.x + w_best;
944 er->br.y = er->tl.y + h_best;
945}
946
947void EncodeManager::extendSolidAreaByPixel(const Rect& r, const Rect& sr,
948 const rdr::U8* colourValue,
949 const PixelBuffer *pb, Rect* er)
950{
951 int cx, cy;
952 Rect tr;
953
954 // Try to extend the area upwards.
955 for (cy = sr.tl.y - 1; cy >= r.tl.y; cy--) {
956 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
957 if (!checkSolidTile(tr, colourValue, pb))
958 break;
959 }
960 er->tl.y = cy + 1;
961
962 // ... downwards.
963 for (cy = sr.br.y; cy < r.br.y; cy++) {
964 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
965 if (!checkSolidTile(tr, colourValue, pb))
966 break;
967 }
968 er->br.y = cy;
969
970 // ... to the left.
971 for (cx = sr.tl.x - 1; cx >= r.tl.x; cx--) {
972 tr.setXYWH(cx, er->tl.y, 1, er->height());
973 if (!checkSolidTile(tr, colourValue, pb))
974 break;
975 }
976 er->tl.x = cx + 1;
977
978 // ... to the right.
979 for (cx = sr.br.x; cx < r.br.x; cx++) {
980 tr.setXYWH(cx, er->tl.y, 1, er->height());
981 if (!checkSolidTile(tr, colourValue, pb))
982 break;
983 }
984 er->br.x = cx;
985}
986
987PixelBuffer* EncodeManager::preparePixelBuffer(const Rect& rect,
988 const PixelBuffer *pb,
989 bool convert)
990{
991 const rdr::U8* buffer;
992 int stride;
993
994 // Do wo need to convert the data?
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200995 if (convert && !conn->client.pf().equal(pb->getPF())) {
996 convertedPixelBuffer.setPF(conn->client.pf());
Pierre Ossmanc0397262014-03-14 15:59:46 +0100997 convertedPixelBuffer.setSize(rect.width(), rect.height());
998
999 buffer = pb->getBuffer(rect, &stride);
1000 convertedPixelBuffer.imageRect(pb->getPF(),
1001 convertedPixelBuffer.getRect(),
1002 buffer, stride);
1003
1004 return &convertedPixelBuffer;
1005 }
1006
1007 // Otherwise we still need to shift the coordinates. We have our own
1008 // abusive subclass of FullFramePixelBuffer for this.
1009
1010 buffer = pb->getBuffer(rect, &stride);
1011
1012 offsetPixelBuffer.update(pb->getPF(), rect.width(), rect.height(),
1013 buffer, stride);
1014
1015 return &offsetPixelBuffer;
1016}
1017
1018bool EncodeManager::analyseRect(const PixelBuffer *pb,
1019 struct RectInfo *info, int maxColours)
1020{
1021 const rdr::U8* buffer;
1022 int stride;
1023
1024 buffer = pb->getBuffer(pb->getRect(), &stride);
1025
1026 switch (pb->getPF().bpp) {
1027 case 32:
1028 return analyseRect(pb->width(), pb->height(),
1029 (const rdr::U32*)buffer, stride,
1030 info, maxColours);
1031 case 16:
1032 return analyseRect(pb->width(), pb->height(),
1033 (const rdr::U16*)buffer, stride,
1034 info, maxColours);
1035 default:
1036 return analyseRect(pb->width(), pb->height(),
1037 (const rdr::U8*)buffer, stride,
1038 info, maxColours);
1039 }
1040}
1041
1042void EncodeManager::OffsetPixelBuffer::update(const PixelFormat& pf,
1043 int width, int height,
1044 const rdr::U8* data_,
1045 int stride_)
1046{
1047 format = pf;
1048 width_ = width;
1049 height_ = height;
1050 // Forced cast. We never write anything though, so it should be safe.
1051 data = (rdr::U8*)data_;
1052 stride = stride_;
1053}
1054
1055// Preprocessor generated, optimised methods
1056
1057#define BPP 8
1058#include "EncodeManagerBPP.cxx"
1059#undef BPP
1060#define BPP 16
1061#include "EncodeManagerBPP.cxx"
1062#undef BPP
1063#define BPP 32
1064#include "EncodeManagerBPP.cxx"
1065#undef BPP