blob: d783d54b8332510f15bab9b0d44bceee6895105b [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 Ossmanc0397262014-03-14 15:59:46 +0100337 if (conn->cp.supportsLastRect)
338 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 Ossmanc0397262014-03-14 15:59:46 +0100353 if (conn->cp.supportsLastRect)
354 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
367 rdr::S32 preferred;
368
369 std::vector<int>::iterator iter;
370
371 solid = bitmap = bitmapRLE = encoderRaw;
372 indexed = indexedRLE = fullColour = encoderRaw;
373
374 // Try to respect the client's wishes
Pierre Ossman48700812014-09-17 17:11:56 +0200375 preferred = conn->getPreferredEncoding();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100376 switch (preferred) {
377 case encodingRRE:
378 // Horrible for anything high frequency and/or lots of colours
379 bitmapRLE = indexedRLE = encoderRRE;
380 break;
381 case encodingHextile:
382 // Slightly less horrible
383 bitmapRLE = indexedRLE = fullColour = encoderHextile;
384 break;
385 case encodingTight:
386 if (encoders[encoderTightJPEG]->isSupported() &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100387 (conn->cp.pf().bpp >= 16) && allowLossy)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100388 fullColour = encoderTightJPEG;
389 else
390 fullColour = encoderTight;
391 indexed = indexedRLE = encoderTight;
392 bitmap = bitmapRLE = encoderTight;
393 break;
394 case encodingZRLE:
395 fullColour = encoderZRLE;
396 bitmapRLE = indexedRLE = encoderZRLE;
397 bitmap = indexed = encoderZRLE;
398 break;
399 }
400
401 // Any encoders still unassigned?
402
403 if (fullColour == encoderRaw) {
404 if (encoders[encoderTightJPEG]->isSupported() &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100405 (conn->cp.pf().bpp >= 16) && allowLossy)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100406 fullColour = encoderTightJPEG;
407 else if (encoders[encoderZRLE]->isSupported())
408 fullColour = encoderZRLE;
409 else if (encoders[encoderTight]->isSupported())
410 fullColour = encoderTight;
411 else if (encoders[encoderHextile]->isSupported())
412 fullColour = encoderHextile;
413 }
414
415 if (indexed == encoderRaw) {
416 if (encoders[encoderZRLE]->isSupported())
417 indexed = encoderZRLE;
418 else if (encoders[encoderTight]->isSupported())
419 indexed = encoderTight;
420 else if (encoders[encoderHextile]->isSupported())
421 indexed = encoderHextile;
422 }
423
424 if (indexedRLE == encoderRaw)
425 indexedRLE = indexed;
426
427 if (bitmap == encoderRaw)
428 bitmap = indexed;
429 if (bitmapRLE == encoderRaw)
430 bitmapRLE = bitmap;
431
432 if (solid == encoderRaw) {
433 if (encoders[encoderTight]->isSupported())
434 solid = encoderTight;
435 else if (encoders[encoderRRE]->isSupported())
436 solid = encoderRRE;
437 else if (encoders[encoderZRLE]->isSupported())
438 solid = encoderZRLE;
439 else if (encoders[encoderHextile]->isSupported())
440 solid = encoderHextile;
441 }
442
443 // JPEG is the only encoder that can reduce things to grayscale
444 if ((conn->cp.subsampling == subsampleGray) &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100445 encoders[encoderTightJPEG]->isSupported() && allowLossy) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100446 solid = bitmap = bitmapRLE = encoderTightJPEG;
447 indexed = indexedRLE = fullColour = encoderTightJPEG;
448 }
449
450 activeEncoders[encoderSolid] = solid;
451 activeEncoders[encoderBitmap] = bitmap;
452 activeEncoders[encoderBitmapRLE] = bitmapRLE;
453 activeEncoders[encoderIndexed] = indexed;
454 activeEncoders[encoderIndexedRLE] = indexedRLE;
455 activeEncoders[encoderFullColour] = fullColour;
456
457 for (iter = activeEncoders.begin(); iter != activeEncoders.end(); ++iter) {
458 Encoder *encoder;
459
460 encoder = encoders[*iter];
461
462 encoder->setCompressLevel(conn->cp.compressLevel);
463 encoder->setQualityLevel(conn->cp.qualityLevel);
464 encoder->setFineQualityLevel(conn->cp.fineQualityLevel,
465 conn->cp.subsampling);
466 }
467}
468
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100469Region EncodeManager::getLosslessRefresh(const Region& req,
470 size_t maxUpdateSize)
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100471{
472 std::vector<Rect> rects;
473 Region refresh;
474 size_t area;
475
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100476 // We make a conservative guess at the compression ratio at 2:1
477 maxUpdateSize *= 2;
478
Pierre Ossman10bea282018-09-19 16:27:56 +0200479 // We will measure pixels, not bytes (assume 32 bpp)
480 maxUpdateSize /= 4;
481
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100482 area = 0;
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200483 pendingRefreshRegion.intersect(req).get_rects(&rects);
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100484 while (!rects.empty()) {
485 size_t idx;
486 Rect rect;
487
488 // Grab a random rect so we don't keep damaging and restoring the
489 // same rect over and over
490 idx = rand() % rects.size();
491
492 rect = rects[idx];
493
494 // Add rects until we exceed the threshold, then include as much as
495 // possible of the final rect
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100496 if ((area + rect.area()) > maxUpdateSize) {
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100497 // Use the narrowest axis to avoid getting to thin rects
498 if (rect.width() > rect.height()) {
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100499 int width = (maxUpdateSize - area) / rect.height();
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100500 rect.br.x = rect.tl.x + __rfbmax(1, width);
501 } else {
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100502 int height = (maxUpdateSize - area) / rect.width();
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100503 rect.br.y = rect.tl.y + __rfbmax(1, height);
504 }
505 refresh.assign_union(Region(rect));
506 break;
507 }
508
509 area += rect.area();
510 refresh.assign_union(Region(rect));
511
512 rects.erase(rects.begin() + idx);
513 }
514
515 return refresh;
516}
517
Pierre Ossmanc0397262014-03-14 15:59:46 +0100518int EncodeManager::computeNumRects(const Region& changed)
519{
520 int numRects;
521 std::vector<Rect> rects;
522 std::vector<Rect>::const_iterator rect;
523
524 numRects = 0;
525 changed.get_rects(&rects);
526 for (rect = rects.begin(); rect != rects.end(); ++rect) {
527 int w, h, sw, sh;
528
529 w = rect->width();
530 h = rect->height();
531
532 // No split necessary?
533 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
534 numRects += 1;
535 continue;
536 }
537
538 if (w <= SubRectMaxWidth)
539 sw = w;
540 else
541 sw = SubRectMaxWidth;
542
543 sh = SubRectMaxArea / sw;
544
545 // ceil(w/sw) * ceil(h/sh)
546 numRects += (((w - 1)/sw) + 1) * (((h - 1)/sh) + 1);
547 }
548
549 return numRects;
550}
551
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100552Encoder *EncodeManager::startRect(const Rect& rect, int type)
553{
554 Encoder *encoder;
555 int klass, equiv;
556
557 activeType = type;
558 klass = activeEncoders[activeType];
559
560 beforeLength = conn->getOutStream()->length();
561
562 stats[klass][activeType].rects++;
563 stats[klass][activeType].pixels += rect.area();
Pierre Ossmanf81148c2018-07-25 20:44:32 +0200564 equiv = 12 + rect.area() * (conn->cp.pf().bpp/8);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100565 stats[klass][activeType].equivalent += equiv;
566
567 encoder = encoders[klass];
568 conn->writer()->startRect(rect, encoder->encoding);
569
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100570 if (encoder->flags & EncoderLossy)
571 lossyRegion.assign_union(Region(rect));
572 else
573 lossyRegion.assign_subtract(Region(rect));
574
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200575 // This was either a rect getting refreshed, or a rect that just got
576 // new content. Either way we should not try to refresh it anymore.
577 pendingRefreshRegion.assign_subtract(Region(rect));
578
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100579 return encoder;
580}
581
582void EncodeManager::endRect()
583{
584 int klass;
585 int length;
586
587 conn->writer()->endRect();
588
589 length = conn->getOutStream()->length() - beforeLength;
590
591 klass = activeEncoders[activeType];
592 stats[klass][activeType].bytes += length;
593}
594
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100595void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100596{
597 std::vector<Rect> rects;
598 std::vector<Rect>::const_iterator rect;
599
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100600 Region lossyCopy;
601
Pierre Ossmane539cb82015-09-22 11:09:00 +0200602 beforeLength = conn->getOutStream()->length();
603
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100604 copied.get_rects(&rects, delta.x <= 0, delta.y <= 0);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100605 for (rect = rects.begin(); rect != rects.end(); ++rect) {
Pierre Ossmane539cb82015-09-22 11:09:00 +0200606 int equiv;
607
608 copyStats.rects++;
609 copyStats.pixels += rect->area();
Pierre Ossmanf81148c2018-07-25 20:44:32 +0200610 equiv = 12 + rect->area() * (conn->cp.pf().bpp/8);
Pierre Ossmane539cb82015-09-22 11:09:00 +0200611 copyStats.equivalent += equiv;
612
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100613 conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x,
614 rect->tl.y - delta.y);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100615 }
Pierre Ossmane539cb82015-09-22 11:09:00 +0200616
617 copyStats.bytes += conn->getOutStream()->length() - beforeLength;
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100618
619 lossyCopy = lossyRegion;
620 lossyCopy.translate(delta);
621 lossyCopy.assign_intersect(copied);
622 lossyRegion.assign_union(lossyCopy);
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200623
624 // Stop any pending refresh as a copy is enough that we consider
625 // this region to be recently changed
626 pendingRefreshRegion.assign_subtract(copied);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100627}
628
629void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
630{
631 std::vector<Rect> rects;
632 std::vector<Rect>::const_iterator rect;
633
Pierre Ossmanc0397262014-03-14 15:59:46 +0100634 changed->get_rects(&rects);
Pierre Ossmaneef55162015-02-12 13:44:22 +0100635 for (rect = rects.begin(); rect != rects.end(); ++rect)
636 findSolidRect(*rect, changed, pb);
637}
Pierre Ossmanc0397262014-03-14 15:59:46 +0100638
Pierre Ossmaneef55162015-02-12 13:44:22 +0100639void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
640 const PixelBuffer* pb)
641{
642 Rect sr;
643 int dx, dy, dw, dh;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100644
Pierre Ossmaneef55162015-02-12 13:44:22 +0100645 // We start by finding a solid 16x16 block
646 for (dy = rect.tl.y; dy < rect.br.y; dy += SolidSearchBlock) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100647
Pierre Ossmaneef55162015-02-12 13:44:22 +0100648 dh = SolidSearchBlock;
649 if (dy + dh > rect.br.y)
650 dh = rect.br.y - dy;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100651
Pierre Ossmaneef55162015-02-12 13:44:22 +0100652 for (dx = rect.tl.x; dx < rect.br.x; dx += SolidSearchBlock) {
653 // We define it like this to guarantee alignment
654 rdr::U32 _buffer;
655 rdr::U8* colourValue = (rdr::U8*)&_buffer;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100656
Pierre Ossmaneef55162015-02-12 13:44:22 +0100657 dw = SolidSearchBlock;
658 if (dx + dw > rect.br.x)
659 dw = rect.br.x - dx;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100660
Pierre Ossmaneef55162015-02-12 13:44:22 +0100661 pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
Pierre Ossmanc0397262014-03-14 15:59:46 +0100662
Pierre Ossmaneef55162015-02-12 13:44:22 +0100663 sr.setXYWH(dx, dy, dw, dh);
664 if (checkSolidTile(sr, colourValue, pb)) {
665 Rect erb, erp;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100666
Pierre Ossmaneef55162015-02-12 13:44:22 +0100667 Encoder *encoder;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100668
Pierre Ossmaneef55162015-02-12 13:44:22 +0100669 // We then try extending the area by adding more blocks
670 // in both directions and pick the combination that gives
671 // the largest area.
672 sr.setXYWH(dx, dy, rect.br.x - dx, rect.br.y - dy);
673 extendSolidAreaByBlock(sr, colourValue, pb, &erb);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100674
Pierre Ossmaneef55162015-02-12 13:44:22 +0100675 // Did we end up getting the entire rectangle?
676 if (erb.equals(rect))
677 erp = erb;
678 else {
679 // Don't bother with sending tiny rectangles
680 if (erb.area() < SolidBlockMinArea)
681 continue;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100682
Pierre Ossmaneef55162015-02-12 13:44:22 +0100683 // Extend the area again, but this time one pixel
684 // row/column at a time.
685 extendSolidAreaByPixel(rect, erb, colourValue, pb, &erp);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100686 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100687
Pierre Ossmaneef55162015-02-12 13:44:22 +0100688 // Send solid-color rectangle.
689 encoder = startRect(erp, encoderSolid);
690 if (encoder->flags & EncoderUseNativePF) {
691 encoder->writeSolidRect(erp.width(), erp.height(),
692 pb->getPF(), colourValue);
693 } else {
694 rdr::U32 _buffer2;
695 rdr::U8* converted = (rdr::U8*)&_buffer2;
696
697 conn->cp.pf().bufferFromBuffer(converted, pb->getPF(),
698 colourValue, 1);
699
700 encoder->writeSolidRect(erp.width(), erp.height(),
701 conn->cp.pf(), converted);
702 }
703 endRect();
704
705 changed->assign_subtract(Region(erp));
706
707 // Search remaining areas by recursion
708 // FIXME: Is this the best way to divide things up?
709
710 // Left? (Note that we've already searched a SolidSearchBlock
711 // pixels high strip here)
712 if ((erp.tl.x != rect.tl.x) && (erp.height() > SolidSearchBlock)) {
713 sr.setXYWH(rect.tl.x, erp.tl.y + SolidSearchBlock,
714 erp.tl.x - rect.tl.x, erp.height() - SolidSearchBlock);
715 findSolidRect(sr, changed, pb);
716 }
717
718 // Right?
719 if (erp.br.x != rect.br.x) {
720 sr.setXYWH(erp.br.x, erp.tl.y, rect.br.x - erp.br.x, erp.height());
721 findSolidRect(sr, changed, pb);
722 }
723
724 // Below?
725 if (erp.br.y != rect.br.y) {
726 sr.setXYWH(rect.tl.x, erp.br.y, rect.width(), rect.br.y - erp.br.y);
727 findSolidRect(sr, changed, pb);
728 }
729
730 return;
731 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100732 }
733 }
734}
735
736void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb)
737{
738 std::vector<Rect> rects;
739 std::vector<Rect>::const_iterator rect;
740
741 changed.get_rects(&rects);
742 for (rect = rects.begin(); rect != rects.end(); ++rect) {
743 int w, h, sw, sh;
744 Rect sr;
745
746 w = rect->width();
747 h = rect->height();
748
749 // No split necessary?
750 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
751 writeSubRect(*rect, pb);
752 continue;
753 }
754
755 if (w <= SubRectMaxWidth)
756 sw = w;
757 else
758 sw = SubRectMaxWidth;
759
760 sh = SubRectMaxArea / sw;
761
762 for (sr.tl.y = rect->tl.y; sr.tl.y < rect->br.y; sr.tl.y += sh) {
763 sr.br.y = sr.tl.y + sh;
764 if (sr.br.y > rect->br.y)
765 sr.br.y = rect->br.y;
766
767 for (sr.tl.x = rect->tl.x; sr.tl.x < rect->br.x; sr.tl.x += sw) {
768 sr.br.x = sr.tl.x + sw;
769 if (sr.br.x > rect->br.x)
770 sr.br.x = rect->br.x;
771
772 writeSubRect(sr, pb);
773 }
774 }
775 }
776}
777
778void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
779{
780 PixelBuffer *ppb;
781
782 Encoder *encoder;
783
784 struct RectInfo info;
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100785 unsigned int divisor, maxColours;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100786
787 bool useRLE;
788 EncoderType type;
789
790 // FIXME: This is roughly the algorithm previously used by the Tight
791 // encoder. It seems a bit backwards though, that higher
792 // compression setting means spending less effort in building
793 // a palette. It might be that they figured the increase in
794 // zlib setting compensated for the loss.
795 if (conn->cp.compressLevel == -1)
796 divisor = 2 * 8;
797 else
798 divisor = conn->cp.compressLevel * 8;
799 if (divisor < 4)
800 divisor = 4;
801
802 maxColours = rect.area()/divisor;
803
804 // Special exception inherited from the Tight encoder
805 if (activeEncoders[encoderFullColour] == encoderTightJPEG) {
Pierre Ossman4daa7b12015-02-12 14:57:05 +0100806 if ((conn->cp.compressLevel != -1) && (conn->cp.compressLevel < 2))
Pierre Ossmanc0397262014-03-14 15:59:46 +0100807 maxColours = 24;
808 else
809 maxColours = 96;
810 }
811
812 if (maxColours < 2)
813 maxColours = 2;
814
815 encoder = encoders[activeEncoders[encoderIndexedRLE]];
816 if (maxColours > encoder->maxPaletteSize)
817 maxColours = encoder->maxPaletteSize;
818 encoder = encoders[activeEncoders[encoderIndexed]];
819 if (maxColours > encoder->maxPaletteSize)
820 maxColours = encoder->maxPaletteSize;
821
822 ppb = preparePixelBuffer(rect, pb, true);
823
824 if (!analyseRect(ppb, &info, maxColours))
825 info.palette.clear();
826
827 // Different encoders might have different RLE overhead, but
828 // here we do a guess at RLE being the better choice if reduces
829 // the pixel count by 50%.
830 useRLE = info.rleRuns <= (rect.area() * 2);
831
832 switch (info.palette.size()) {
833 case 0:
834 type = encoderFullColour;
835 break;
836 case 1:
837 type = encoderSolid;
838 break;
839 case 2:
840 if (useRLE)
841 type = encoderBitmapRLE;
842 else
843 type = encoderBitmap;
844 break;
845 default:
846 if (useRLE)
847 type = encoderIndexedRLE;
848 else
849 type = encoderIndexed;
850 }
851
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100852 encoder = startRect(rect, type);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100853
854 if (encoder->flags & EncoderUseNativePF)
855 ppb = preparePixelBuffer(rect, pb, false);
856
Pierre Ossmanc0397262014-03-14 15:59:46 +0100857 encoder->writeRect(ppb, info.palette);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100858
859 endRect();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100860}
861
862bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue,
863 const PixelBuffer *pb)
864{
865 switch (pb->getPF().bpp) {
866 case 32:
867 return checkSolidTile(r, *(const rdr::U32*)colourValue, pb);
868 case 16:
869 return checkSolidTile(r, *(const rdr::U16*)colourValue, pb);
870 default:
871 return checkSolidTile(r, *(const rdr::U8*)colourValue, pb);
872 }
873}
874
875void EncodeManager::extendSolidAreaByBlock(const Rect& r,
876 const rdr::U8* colourValue,
877 const PixelBuffer *pb, Rect* er)
878{
879 int dx, dy, dw, dh;
880 int w_prev;
881 Rect sr;
882 int w_best = 0, h_best = 0;
883
884 w_prev = r.width();
885
886 // We search width first, back off when we hit a different colour,
887 // and restart with a larger height. We keep track of the
888 // width/height combination that gives us the largest area.
889 for (dy = r.tl.y; dy < r.br.y; dy += SolidSearchBlock) {
890
891 dh = SolidSearchBlock;
892 if (dy + dh > r.br.y)
893 dh = r.br.y - dy;
894
895 // We test one block here outside the x loop in order to break
896 // the y loop right away.
897 dw = SolidSearchBlock;
898 if (dw > w_prev)
899 dw = w_prev;
900
901 sr.setXYWH(r.tl.x, dy, dw, dh);
902 if (!checkSolidTile(sr, colourValue, pb))
903 break;
904
905 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
906
907 dw = SolidSearchBlock;
908 if (dx + dw > r.tl.x + w_prev)
909 dw = r.tl.x + w_prev - dx;
910
911 sr.setXYWH(dx, dy, dw, dh);
912 if (!checkSolidTile(sr, colourValue, pb))
913 break;
914
915 dx += dw;
916 }
917
918 w_prev = dx - r.tl.x;
919 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
920 w_best = w_prev;
921 h_best = dy + dh - r.tl.y;
922 }
923 }
924
925 er->tl.x = r.tl.x;
926 er->tl.y = r.tl.y;
927 er->br.x = er->tl.x + w_best;
928 er->br.y = er->tl.y + h_best;
929}
930
931void EncodeManager::extendSolidAreaByPixel(const Rect& r, const Rect& sr,
932 const rdr::U8* colourValue,
933 const PixelBuffer *pb, Rect* er)
934{
935 int cx, cy;
936 Rect tr;
937
938 // Try to extend the area upwards.
939 for (cy = sr.tl.y - 1; cy >= r.tl.y; cy--) {
940 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
941 if (!checkSolidTile(tr, colourValue, pb))
942 break;
943 }
944 er->tl.y = cy + 1;
945
946 // ... downwards.
947 for (cy = sr.br.y; cy < r.br.y; cy++) {
948 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
949 if (!checkSolidTile(tr, colourValue, pb))
950 break;
951 }
952 er->br.y = cy;
953
954 // ... to the left.
955 for (cx = sr.tl.x - 1; cx >= r.tl.x; cx--) {
956 tr.setXYWH(cx, er->tl.y, 1, er->height());
957 if (!checkSolidTile(tr, colourValue, pb))
958 break;
959 }
960 er->tl.x = cx + 1;
961
962 // ... to the right.
963 for (cx = sr.br.x; cx < r.br.x; cx++) {
964 tr.setXYWH(cx, er->tl.y, 1, er->height());
965 if (!checkSolidTile(tr, colourValue, pb))
966 break;
967 }
968 er->br.x = cx;
969}
970
971PixelBuffer* EncodeManager::preparePixelBuffer(const Rect& rect,
972 const PixelBuffer *pb,
973 bool convert)
974{
975 const rdr::U8* buffer;
976 int stride;
977
978 // Do wo need to convert the data?
979 if (convert && !conn->cp.pf().equal(pb->getPF())) {
980 convertedPixelBuffer.setPF(conn->cp.pf());
981 convertedPixelBuffer.setSize(rect.width(), rect.height());
982
983 buffer = pb->getBuffer(rect, &stride);
984 convertedPixelBuffer.imageRect(pb->getPF(),
985 convertedPixelBuffer.getRect(),
986 buffer, stride);
987
988 return &convertedPixelBuffer;
989 }
990
991 // Otherwise we still need to shift the coordinates. We have our own
992 // abusive subclass of FullFramePixelBuffer for this.
993
994 buffer = pb->getBuffer(rect, &stride);
995
996 offsetPixelBuffer.update(pb->getPF(), rect.width(), rect.height(),
997 buffer, stride);
998
999 return &offsetPixelBuffer;
1000}
1001
1002bool EncodeManager::analyseRect(const PixelBuffer *pb,
1003 struct RectInfo *info, int maxColours)
1004{
1005 const rdr::U8* buffer;
1006 int stride;
1007
1008 buffer = pb->getBuffer(pb->getRect(), &stride);
1009
1010 switch (pb->getPF().bpp) {
1011 case 32:
1012 return analyseRect(pb->width(), pb->height(),
1013 (const rdr::U32*)buffer, stride,
1014 info, maxColours);
1015 case 16:
1016 return analyseRect(pb->width(), pb->height(),
1017 (const rdr::U16*)buffer, stride,
1018 info, maxColours);
1019 default:
1020 return analyseRect(pb->width(), pb->height(),
1021 (const rdr::U8*)buffer, stride,
1022 info, maxColours);
1023 }
1024}
1025
1026void EncodeManager::OffsetPixelBuffer::update(const PixelFormat& pf,
1027 int width, int height,
1028 const rdr::U8* data_,
1029 int stride_)
1030{
1031 format = pf;
1032 width_ = width;
1033 height_ = height;
1034 // Forced cast. We never write anything though, so it should be safe.
1035 data = (rdr::U8*)data_;
1036 stride = stride_;
1037}
1038
1039// Preprocessor generated, optimised methods
1040
1041#define BPP 8
1042#include "EncodeManagerBPP.cxx"
1043#undef BPP
1044#define BPP 16
1045#include "EncodeManagerBPP.cxx"
1046#undef BPP
1047#define BPP 32
1048#include "EncodeManagerBPP.cxx"
1049#undef BPP