blob: 9288668434665bc1c58d987999558573b30cd684 [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
Pierre Ossmana114c342018-06-18 16:29:55 +0200328 if (!conn->client.useCopyRect)
329 changed.assign_union(copied);
330
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100331 /*
332 * We need to render the cursor seperately as it has its own
333 * magical pixel buffer, so split it out from the changed region.
334 */
335 if (renderedCursor != NULL) {
336 cursorRegion = changed.intersect(renderedCursor->getEffectiveRect());
337 changed.assign_subtract(renderedCursor->getEffectiveRect());
338 }
339
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200340 if (conn->client.supportsLastRect)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100341 nRects = 0xFFFF;
342 else {
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100343 nRects = copied.numRects();
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100344 nRects += computeNumRects(changed);
345 nRects += computeNumRects(cursorRegion);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100346 }
347
348 conn->writer()->writeFramebufferUpdateStart(nRects);
349
Pierre Ossmana114c342018-06-18 16:29:55 +0200350 if (conn->client.useCopyRect)
351 writeCopyRects(copied, copyDelta);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100352
353 /*
354 * We start by searching for solid rects, which are then removed
355 * from the changed region.
356 */
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200357 if (conn->client.supportsLastRect)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100358 writeSolidRects(&changed, pb);
359
360 writeRects(changed, pb);
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100361 writeRects(cursorRegion, renderedCursor);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100362
363 conn->writer()->writeFramebufferUpdateEnd();
364}
365
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100366void EncodeManager::prepareEncoders(bool allowLossy)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100367{
368 enum EncoderClass solid, bitmap, bitmapRLE;
369 enum EncoderClass indexed, indexedRLE, fullColour;
370
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200371 bool allowJPEG;
372
Pierre Ossmanc0397262014-03-14 15:59:46 +0100373 rdr::S32 preferred;
374
375 std::vector<int>::iterator iter;
376
377 solid = bitmap = bitmapRLE = encoderRaw;
378 indexed = indexedRLE = fullColour = encoderRaw;
379
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200380 allowJPEG = conn->client.pf().bpp >= 16;
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200381 if (!allowLossy) {
382 if (encoders[encoderTightJPEG]->losslessQuality == -1)
383 allowJPEG = false;
384 }
385
Pierre Ossmanc0397262014-03-14 15:59:46 +0100386 // Try to respect the client's wishes
Pierre Ossman48700812014-09-17 17:11:56 +0200387 preferred = conn->getPreferredEncoding();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100388 switch (preferred) {
389 case encodingRRE:
390 // Horrible for anything high frequency and/or lots of colours
391 bitmapRLE = indexedRLE = encoderRRE;
392 break;
393 case encodingHextile:
394 // Slightly less horrible
395 bitmapRLE = indexedRLE = fullColour = encoderHextile;
396 break;
397 case encodingTight:
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200398 if (encoders[encoderTightJPEG]->isSupported() && allowJPEG)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100399 fullColour = encoderTightJPEG;
400 else
401 fullColour = encoderTight;
402 indexed = indexedRLE = encoderTight;
403 bitmap = bitmapRLE = encoderTight;
404 break;
405 case encodingZRLE:
406 fullColour = encoderZRLE;
407 bitmapRLE = indexedRLE = encoderZRLE;
408 bitmap = indexed = encoderZRLE;
409 break;
410 }
411
412 // Any encoders still unassigned?
413
414 if (fullColour == encoderRaw) {
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200415 if (encoders[encoderTightJPEG]->isSupported() && allowJPEG)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100416 fullColour = encoderTightJPEG;
417 else if (encoders[encoderZRLE]->isSupported())
418 fullColour = encoderZRLE;
419 else if (encoders[encoderTight]->isSupported())
420 fullColour = encoderTight;
421 else if (encoders[encoderHextile]->isSupported())
422 fullColour = encoderHextile;
423 }
424
425 if (indexed == encoderRaw) {
426 if (encoders[encoderZRLE]->isSupported())
427 indexed = encoderZRLE;
428 else if (encoders[encoderTight]->isSupported())
429 indexed = encoderTight;
430 else if (encoders[encoderHextile]->isSupported())
431 indexed = encoderHextile;
432 }
433
434 if (indexedRLE == encoderRaw)
435 indexedRLE = indexed;
436
437 if (bitmap == encoderRaw)
438 bitmap = indexed;
439 if (bitmapRLE == encoderRaw)
440 bitmapRLE = bitmap;
441
442 if (solid == encoderRaw) {
443 if (encoders[encoderTight]->isSupported())
444 solid = encoderTight;
445 else if (encoders[encoderRRE]->isSupported())
446 solid = encoderRRE;
447 else if (encoders[encoderZRLE]->isSupported())
448 solid = encoderZRLE;
449 else if (encoders[encoderHextile]->isSupported())
450 solid = encoderHextile;
451 }
452
453 // JPEG is the only encoder that can reduce things to grayscale
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200454 if ((conn->client.subsampling == subsampleGray) &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100455 encoders[encoderTightJPEG]->isSupported() && allowLossy) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100456 solid = bitmap = bitmapRLE = encoderTightJPEG;
457 indexed = indexedRLE = fullColour = encoderTightJPEG;
458 }
459
460 activeEncoders[encoderSolid] = solid;
461 activeEncoders[encoderBitmap] = bitmap;
462 activeEncoders[encoderBitmapRLE] = bitmapRLE;
463 activeEncoders[encoderIndexed] = indexed;
464 activeEncoders[encoderIndexedRLE] = indexedRLE;
465 activeEncoders[encoderFullColour] = fullColour;
466
467 for (iter = activeEncoders.begin(); iter != activeEncoders.end(); ++iter) {
468 Encoder *encoder;
469
470 encoder = encoders[*iter];
471
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200472 encoder->setCompressLevel(conn->client.compressLevel);
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200473
474 if (allowLossy) {
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200475 encoder->setQualityLevel(conn->client.qualityLevel);
476 encoder->setFineQualityLevel(conn->client.fineQualityLevel,
477 conn->client.subsampling);
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200478 } else {
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200479 int level = __rfbmax(conn->client.qualityLevel,
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200480 encoder->losslessQuality);
481 encoder->setQualityLevel(level);
482 encoder->setFineQualityLevel(-1, subsampleUndefined);
483 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100484 }
485}
486
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100487Region EncodeManager::getLosslessRefresh(const Region& req,
488 size_t maxUpdateSize)
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100489{
490 std::vector<Rect> rects;
491 Region refresh;
492 size_t area;
493
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100494 // We make a conservative guess at the compression ratio at 2:1
495 maxUpdateSize *= 2;
496
Pierre Ossman10bea282018-09-19 16:27:56 +0200497 // We will measure pixels, not bytes (assume 32 bpp)
498 maxUpdateSize /= 4;
499
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100500 area = 0;
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200501 pendingRefreshRegion.intersect(req).get_rects(&rects);
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100502 while (!rects.empty()) {
503 size_t idx;
504 Rect rect;
505
506 // Grab a random rect so we don't keep damaging and restoring the
507 // same rect over and over
508 idx = rand() % rects.size();
509
510 rect = rects[idx];
511
512 // Add rects until we exceed the threshold, then include as much as
513 // possible of the final rect
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100514 if ((area + rect.area()) > maxUpdateSize) {
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100515 // Use the narrowest axis to avoid getting to thin rects
516 if (rect.width() > rect.height()) {
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100517 int width = (maxUpdateSize - area) / rect.height();
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100518 rect.br.x = rect.tl.x + __rfbmax(1, width);
519 } else {
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100520 int height = (maxUpdateSize - area) / rect.width();
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100521 rect.br.y = rect.tl.y + __rfbmax(1, height);
522 }
523 refresh.assign_union(Region(rect));
524 break;
525 }
526
527 area += rect.area();
528 refresh.assign_union(Region(rect));
529
530 rects.erase(rects.begin() + idx);
531 }
532
533 return refresh;
534}
535
Pierre Ossmanc0397262014-03-14 15:59:46 +0100536int EncodeManager::computeNumRects(const Region& changed)
537{
538 int numRects;
539 std::vector<Rect> rects;
540 std::vector<Rect>::const_iterator rect;
541
542 numRects = 0;
543 changed.get_rects(&rects);
544 for (rect = rects.begin(); rect != rects.end(); ++rect) {
545 int w, h, sw, sh;
546
547 w = rect->width();
548 h = rect->height();
549
550 // No split necessary?
551 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
552 numRects += 1;
553 continue;
554 }
555
556 if (w <= SubRectMaxWidth)
557 sw = w;
558 else
559 sw = SubRectMaxWidth;
560
561 sh = SubRectMaxArea / sw;
562
563 // ceil(w/sw) * ceil(h/sh)
564 numRects += (((w - 1)/sw) + 1) * (((h - 1)/sh) + 1);
565 }
566
567 return numRects;
568}
569
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100570Encoder *EncodeManager::startRect(const Rect& rect, int type)
571{
572 Encoder *encoder;
573 int klass, equiv;
574
575 activeType = type;
576 klass = activeEncoders[activeType];
577
578 beforeLength = conn->getOutStream()->length();
579
580 stats[klass][activeType].rects++;
581 stats[klass][activeType].pixels += rect.area();
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200582 equiv = 12 + rect.area() * (conn->client.pf().bpp/8);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100583 stats[klass][activeType].equivalent += equiv;
584
585 encoder = encoders[klass];
586 conn->writer()->startRect(rect, encoder->encoding);
587
Pierre Ossmanbeab2b62018-09-20 10:52:15 +0200588 if ((encoder->flags & EncoderLossy) &&
589 ((encoder->losslessQuality == -1) ||
590 (encoder->getQualityLevel() < encoder->losslessQuality)))
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100591 lossyRegion.assign_union(Region(rect));
592 else
593 lossyRegion.assign_subtract(Region(rect));
594
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200595 // This was either a rect getting refreshed, or a rect that just got
596 // new content. Either way we should not try to refresh it anymore.
597 pendingRefreshRegion.assign_subtract(Region(rect));
598
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100599 return encoder;
600}
601
602void EncodeManager::endRect()
603{
604 int klass;
605 int length;
606
607 conn->writer()->endRect();
608
609 length = conn->getOutStream()->length() - beforeLength;
610
611 klass = activeEncoders[activeType];
612 stats[klass][activeType].bytes += length;
613}
614
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100615void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100616{
617 std::vector<Rect> rects;
618 std::vector<Rect>::const_iterator rect;
619
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100620 Region lossyCopy;
621
Pierre Ossmane539cb82015-09-22 11:09:00 +0200622 beforeLength = conn->getOutStream()->length();
623
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100624 copied.get_rects(&rects, delta.x <= 0, delta.y <= 0);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100625 for (rect = rects.begin(); rect != rects.end(); ++rect) {
Pierre Ossmane539cb82015-09-22 11:09:00 +0200626 int equiv;
627
628 copyStats.rects++;
629 copyStats.pixels += rect->area();
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200630 equiv = 12 + rect->area() * (conn->client.pf().bpp/8);
Pierre Ossmane539cb82015-09-22 11:09:00 +0200631 copyStats.equivalent += equiv;
632
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100633 conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x,
634 rect->tl.y - delta.y);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100635 }
Pierre Ossmane539cb82015-09-22 11:09:00 +0200636
637 copyStats.bytes += conn->getOutStream()->length() - beforeLength;
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100638
639 lossyCopy = lossyRegion;
640 lossyCopy.translate(delta);
641 lossyCopy.assign_intersect(copied);
642 lossyRegion.assign_union(lossyCopy);
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200643
644 // Stop any pending refresh as a copy is enough that we consider
645 // this region to be recently changed
646 pendingRefreshRegion.assign_subtract(copied);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100647}
648
649void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
650{
651 std::vector<Rect> rects;
652 std::vector<Rect>::const_iterator rect;
653
Pierre Ossmanc0397262014-03-14 15:59:46 +0100654 changed->get_rects(&rects);
Pierre Ossmaneef55162015-02-12 13:44:22 +0100655 for (rect = rects.begin(); rect != rects.end(); ++rect)
656 findSolidRect(*rect, changed, pb);
657}
Pierre Ossmanc0397262014-03-14 15:59:46 +0100658
Pierre Ossmaneef55162015-02-12 13:44:22 +0100659void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
660 const PixelBuffer* pb)
661{
662 Rect sr;
663 int dx, dy, dw, dh;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100664
Pierre Ossmaneef55162015-02-12 13:44:22 +0100665 // We start by finding a solid 16x16 block
666 for (dy = rect.tl.y; dy < rect.br.y; dy += SolidSearchBlock) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100667
Pierre Ossmaneef55162015-02-12 13:44:22 +0100668 dh = SolidSearchBlock;
669 if (dy + dh > rect.br.y)
670 dh = rect.br.y - dy;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100671
Pierre Ossmaneef55162015-02-12 13:44:22 +0100672 for (dx = rect.tl.x; dx < rect.br.x; dx += SolidSearchBlock) {
673 // We define it like this to guarantee alignment
674 rdr::U32 _buffer;
675 rdr::U8* colourValue = (rdr::U8*)&_buffer;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100676
Pierre Ossmaneef55162015-02-12 13:44:22 +0100677 dw = SolidSearchBlock;
678 if (dx + dw > rect.br.x)
679 dw = rect.br.x - dx;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100680
Pierre Ossmaneef55162015-02-12 13:44:22 +0100681 pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
Pierre Ossmanc0397262014-03-14 15:59:46 +0100682
Pierre Ossmaneef55162015-02-12 13:44:22 +0100683 sr.setXYWH(dx, dy, dw, dh);
684 if (checkSolidTile(sr, colourValue, pb)) {
685 Rect erb, erp;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100686
Pierre Ossmaneef55162015-02-12 13:44:22 +0100687 Encoder *encoder;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100688
Pierre Ossmaneef55162015-02-12 13:44:22 +0100689 // We then try extending the area by adding more blocks
690 // in both directions and pick the combination that gives
691 // the largest area.
692 sr.setXYWH(dx, dy, rect.br.x - dx, rect.br.y - dy);
693 extendSolidAreaByBlock(sr, colourValue, pb, &erb);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100694
Pierre Ossmaneef55162015-02-12 13:44:22 +0100695 // Did we end up getting the entire rectangle?
696 if (erb.equals(rect))
697 erp = erb;
698 else {
699 // Don't bother with sending tiny rectangles
700 if (erb.area() < SolidBlockMinArea)
701 continue;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100702
Pierre Ossmaneef55162015-02-12 13:44:22 +0100703 // Extend the area again, but this time one pixel
704 // row/column at a time.
705 extendSolidAreaByPixel(rect, erb, colourValue, pb, &erp);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100706 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100707
Pierre Ossmaneef55162015-02-12 13:44:22 +0100708 // Send solid-color rectangle.
709 encoder = startRect(erp, encoderSolid);
710 if (encoder->flags & EncoderUseNativePF) {
711 encoder->writeSolidRect(erp.width(), erp.height(),
712 pb->getPF(), colourValue);
713 } else {
714 rdr::U32 _buffer2;
715 rdr::U8* converted = (rdr::U8*)&_buffer2;
716
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200717 conn->client.pf().bufferFromBuffer(converted, pb->getPF(),
Pierre Ossmaneef55162015-02-12 13:44:22 +0100718 colourValue, 1);
719
720 encoder->writeSolidRect(erp.width(), erp.height(),
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200721 conn->client.pf(), converted);
Pierre Ossmaneef55162015-02-12 13:44:22 +0100722 }
723 endRect();
724
725 changed->assign_subtract(Region(erp));
726
727 // Search remaining areas by recursion
728 // FIXME: Is this the best way to divide things up?
729
730 // Left? (Note that we've already searched a SolidSearchBlock
731 // pixels high strip here)
732 if ((erp.tl.x != rect.tl.x) && (erp.height() > SolidSearchBlock)) {
733 sr.setXYWH(rect.tl.x, erp.tl.y + SolidSearchBlock,
734 erp.tl.x - rect.tl.x, erp.height() - SolidSearchBlock);
735 findSolidRect(sr, changed, pb);
736 }
737
738 // Right?
739 if (erp.br.x != rect.br.x) {
740 sr.setXYWH(erp.br.x, erp.tl.y, rect.br.x - erp.br.x, erp.height());
741 findSolidRect(sr, changed, pb);
742 }
743
744 // Below?
745 if (erp.br.y != rect.br.y) {
746 sr.setXYWH(rect.tl.x, erp.br.y, rect.width(), rect.br.y - erp.br.y);
747 findSolidRect(sr, changed, pb);
748 }
749
750 return;
751 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100752 }
753 }
754}
755
756void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb)
757{
758 std::vector<Rect> rects;
759 std::vector<Rect>::const_iterator rect;
760
761 changed.get_rects(&rects);
762 for (rect = rects.begin(); rect != rects.end(); ++rect) {
763 int w, h, sw, sh;
764 Rect sr;
765
766 w = rect->width();
767 h = rect->height();
768
769 // No split necessary?
770 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
771 writeSubRect(*rect, pb);
772 continue;
773 }
774
775 if (w <= SubRectMaxWidth)
776 sw = w;
777 else
778 sw = SubRectMaxWidth;
779
780 sh = SubRectMaxArea / sw;
781
782 for (sr.tl.y = rect->tl.y; sr.tl.y < rect->br.y; sr.tl.y += sh) {
783 sr.br.y = sr.tl.y + sh;
784 if (sr.br.y > rect->br.y)
785 sr.br.y = rect->br.y;
786
787 for (sr.tl.x = rect->tl.x; sr.tl.x < rect->br.x; sr.tl.x += sw) {
788 sr.br.x = sr.tl.x + sw;
789 if (sr.br.x > rect->br.x)
790 sr.br.x = rect->br.x;
791
792 writeSubRect(sr, pb);
793 }
794 }
795 }
796}
797
798void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
799{
800 PixelBuffer *ppb;
801
802 Encoder *encoder;
803
804 struct RectInfo info;
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100805 unsigned int divisor, maxColours;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100806
807 bool useRLE;
808 EncoderType type;
809
810 // FIXME: This is roughly the algorithm previously used by the Tight
811 // encoder. It seems a bit backwards though, that higher
812 // compression setting means spending less effort in building
813 // a palette. It might be that they figured the increase in
814 // zlib setting compensated for the loss.
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200815 if (conn->client.compressLevel == -1)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100816 divisor = 2 * 8;
817 else
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200818 divisor = conn->client.compressLevel * 8;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100819 if (divisor < 4)
820 divisor = 4;
821
822 maxColours = rect.area()/divisor;
823
824 // Special exception inherited from the Tight encoder
825 if (activeEncoders[encoderFullColour] == encoderTightJPEG) {
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200826 if ((conn->client.compressLevel != -1) && (conn->client.compressLevel < 2))
Pierre Ossmanc0397262014-03-14 15:59:46 +0100827 maxColours = 24;
828 else
829 maxColours = 96;
830 }
831
832 if (maxColours < 2)
833 maxColours = 2;
834
835 encoder = encoders[activeEncoders[encoderIndexedRLE]];
836 if (maxColours > encoder->maxPaletteSize)
837 maxColours = encoder->maxPaletteSize;
838 encoder = encoders[activeEncoders[encoderIndexed]];
839 if (maxColours > encoder->maxPaletteSize)
840 maxColours = encoder->maxPaletteSize;
841
842 ppb = preparePixelBuffer(rect, pb, true);
843
844 if (!analyseRect(ppb, &info, maxColours))
845 info.palette.clear();
846
847 // Different encoders might have different RLE overhead, but
848 // here we do a guess at RLE being the better choice if reduces
849 // the pixel count by 50%.
850 useRLE = info.rleRuns <= (rect.area() * 2);
851
852 switch (info.palette.size()) {
853 case 0:
854 type = encoderFullColour;
855 break;
856 case 1:
857 type = encoderSolid;
858 break;
859 case 2:
860 if (useRLE)
861 type = encoderBitmapRLE;
862 else
863 type = encoderBitmap;
864 break;
865 default:
866 if (useRLE)
867 type = encoderIndexedRLE;
868 else
869 type = encoderIndexed;
870 }
871
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100872 encoder = startRect(rect, type);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100873
874 if (encoder->flags & EncoderUseNativePF)
875 ppb = preparePixelBuffer(rect, pb, false);
876
Pierre Ossmanc0397262014-03-14 15:59:46 +0100877 encoder->writeRect(ppb, info.palette);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100878
879 endRect();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100880}
881
882bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue,
883 const PixelBuffer *pb)
884{
885 switch (pb->getPF().bpp) {
886 case 32:
887 return checkSolidTile(r, *(const rdr::U32*)colourValue, pb);
888 case 16:
889 return checkSolidTile(r, *(const rdr::U16*)colourValue, pb);
890 default:
891 return checkSolidTile(r, *(const rdr::U8*)colourValue, pb);
892 }
893}
894
895void EncodeManager::extendSolidAreaByBlock(const Rect& r,
896 const rdr::U8* colourValue,
897 const PixelBuffer *pb, Rect* er)
898{
899 int dx, dy, dw, dh;
900 int w_prev;
901 Rect sr;
902 int w_best = 0, h_best = 0;
903
904 w_prev = r.width();
905
906 // We search width first, back off when we hit a different colour,
907 // and restart with a larger height. We keep track of the
908 // width/height combination that gives us the largest area.
909 for (dy = r.tl.y; dy < r.br.y; dy += SolidSearchBlock) {
910
911 dh = SolidSearchBlock;
912 if (dy + dh > r.br.y)
913 dh = r.br.y - dy;
914
915 // We test one block here outside the x loop in order to break
916 // the y loop right away.
917 dw = SolidSearchBlock;
918 if (dw > w_prev)
919 dw = w_prev;
920
921 sr.setXYWH(r.tl.x, dy, dw, dh);
922 if (!checkSolidTile(sr, colourValue, pb))
923 break;
924
925 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
926
927 dw = SolidSearchBlock;
928 if (dx + dw > r.tl.x + w_prev)
929 dw = r.tl.x + w_prev - dx;
930
931 sr.setXYWH(dx, dy, dw, dh);
932 if (!checkSolidTile(sr, colourValue, pb))
933 break;
934
935 dx += dw;
936 }
937
938 w_prev = dx - r.tl.x;
939 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
940 w_best = w_prev;
941 h_best = dy + dh - r.tl.y;
942 }
943 }
944
945 er->tl.x = r.tl.x;
946 er->tl.y = r.tl.y;
947 er->br.x = er->tl.x + w_best;
948 er->br.y = er->tl.y + h_best;
949}
950
951void EncodeManager::extendSolidAreaByPixel(const Rect& r, const Rect& sr,
952 const rdr::U8* colourValue,
953 const PixelBuffer *pb, Rect* er)
954{
955 int cx, cy;
956 Rect tr;
957
958 // Try to extend the area upwards.
959 for (cy = sr.tl.y - 1; cy >= r.tl.y; cy--) {
960 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
961 if (!checkSolidTile(tr, colourValue, pb))
962 break;
963 }
964 er->tl.y = cy + 1;
965
966 // ... downwards.
967 for (cy = sr.br.y; cy < r.br.y; cy++) {
968 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
969 if (!checkSolidTile(tr, colourValue, pb))
970 break;
971 }
972 er->br.y = cy;
973
974 // ... to the left.
975 for (cx = sr.tl.x - 1; cx >= r.tl.x; cx--) {
976 tr.setXYWH(cx, er->tl.y, 1, er->height());
977 if (!checkSolidTile(tr, colourValue, pb))
978 break;
979 }
980 er->tl.x = cx + 1;
981
982 // ... to the right.
983 for (cx = sr.br.x; cx < r.br.x; cx++) {
984 tr.setXYWH(cx, er->tl.y, 1, er->height());
985 if (!checkSolidTile(tr, colourValue, pb))
986 break;
987 }
988 er->br.x = cx;
989}
990
991PixelBuffer* EncodeManager::preparePixelBuffer(const Rect& rect,
992 const PixelBuffer *pb,
993 bool convert)
994{
995 const rdr::U8* buffer;
996 int stride;
997
998 // Do wo need to convert the data?
Pierre Ossman0d3ce872018-06-18 15:59:00 +0200999 if (convert && !conn->client.pf().equal(pb->getPF())) {
1000 convertedPixelBuffer.setPF(conn->client.pf());
Pierre Ossmanc0397262014-03-14 15:59:46 +01001001 convertedPixelBuffer.setSize(rect.width(), rect.height());
1002
1003 buffer = pb->getBuffer(rect, &stride);
1004 convertedPixelBuffer.imageRect(pb->getPF(),
1005 convertedPixelBuffer.getRect(),
1006 buffer, stride);
1007
1008 return &convertedPixelBuffer;
1009 }
1010
1011 // Otherwise we still need to shift the coordinates. We have our own
1012 // abusive subclass of FullFramePixelBuffer for this.
1013
1014 buffer = pb->getBuffer(rect, &stride);
1015
1016 offsetPixelBuffer.update(pb->getPF(), rect.width(), rect.height(),
1017 buffer, stride);
1018
1019 return &offsetPixelBuffer;
1020}
1021
1022bool EncodeManager::analyseRect(const PixelBuffer *pb,
1023 struct RectInfo *info, int maxColours)
1024{
1025 const rdr::U8* buffer;
1026 int stride;
1027
1028 buffer = pb->getBuffer(pb->getRect(), &stride);
1029
1030 switch (pb->getPF().bpp) {
1031 case 32:
1032 return analyseRect(pb->width(), pb->height(),
1033 (const rdr::U32*)buffer, stride,
1034 info, maxColours);
1035 case 16:
1036 return analyseRect(pb->width(), pb->height(),
1037 (const rdr::U16*)buffer, stride,
1038 info, maxColours);
1039 default:
1040 return analyseRect(pb->width(), pb->height(),
1041 (const rdr::U8*)buffer, stride,
1042 info, maxColours);
1043 }
1044}
1045
1046void EncodeManager::OffsetPixelBuffer::update(const PixelFormat& pf,
1047 int width, int height,
1048 const rdr::U8* data_,
1049 int stride_)
1050{
1051 format = pf;
1052 width_ = width;
1053 height_ = height;
1054 // Forced cast. We never write anything though, so it should be safe.
1055 data = (rdr::U8*)data_;
1056 stride = stride_;
1057}
1058
1059// Preprocessor generated, optimised methods
1060
1061#define BPP 8
1062#include "EncodeManagerBPP.cxx"
1063#undef BPP
1064#define BPP 16
1065#include "EncodeManagerBPP.cxx"
1066#undef BPP
1067#define BPP 32
1068#include "EncodeManagerBPP.cxx"
1069#undef BPP