blob: 8f5427c828377a21c566ea312ffd4ceaecd344a1 [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 Ossman6b2f1132016-11-30 08:03:35 +0100479 area = 0;
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200480 pendingRefreshRegion.intersect(req).get_rects(&rects);
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100481 while (!rects.empty()) {
482 size_t idx;
483 Rect rect;
484
485 // Grab a random rect so we don't keep damaging and restoring the
486 // same rect over and over
487 idx = rand() % rects.size();
488
489 rect = rects[idx];
490
491 // Add rects until we exceed the threshold, then include as much as
492 // possible of the final rect
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100493 if ((area + rect.area()) > maxUpdateSize) {
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100494 // Use the narrowest axis to avoid getting to thin rects
495 if (rect.width() > rect.height()) {
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100496 int width = (maxUpdateSize - area) / rect.height();
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100497 rect.br.x = rect.tl.x + __rfbmax(1, width);
498 } else {
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100499 int height = (maxUpdateSize - area) / rect.width();
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100500 rect.br.y = rect.tl.y + __rfbmax(1, height);
501 }
502 refresh.assign_union(Region(rect));
503 break;
504 }
505
506 area += rect.area();
507 refresh.assign_union(Region(rect));
508
509 rects.erase(rects.begin() + idx);
510 }
511
512 return refresh;
513}
514
Pierre Ossmanc0397262014-03-14 15:59:46 +0100515int EncodeManager::computeNumRects(const Region& changed)
516{
517 int numRects;
518 std::vector<Rect> rects;
519 std::vector<Rect>::const_iterator rect;
520
521 numRects = 0;
522 changed.get_rects(&rects);
523 for (rect = rects.begin(); rect != rects.end(); ++rect) {
524 int w, h, sw, sh;
525
526 w = rect->width();
527 h = rect->height();
528
529 // No split necessary?
530 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
531 numRects += 1;
532 continue;
533 }
534
535 if (w <= SubRectMaxWidth)
536 sw = w;
537 else
538 sw = SubRectMaxWidth;
539
540 sh = SubRectMaxArea / sw;
541
542 // ceil(w/sw) * ceil(h/sh)
543 numRects += (((w - 1)/sw) + 1) * (((h - 1)/sh) + 1);
544 }
545
546 return numRects;
547}
548
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100549Encoder *EncodeManager::startRect(const Rect& rect, int type)
550{
551 Encoder *encoder;
552 int klass, equiv;
553
554 activeType = type;
555 klass = activeEncoders[activeType];
556
557 beforeLength = conn->getOutStream()->length();
558
559 stats[klass][activeType].rects++;
560 stats[klass][activeType].pixels += rect.area();
Pierre Ossmanf81148c2018-07-25 20:44:32 +0200561 equiv = 12 + rect.area() * (conn->cp.pf().bpp/8);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100562 stats[klass][activeType].equivalent += equiv;
563
564 encoder = encoders[klass];
565 conn->writer()->startRect(rect, encoder->encoding);
566
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100567 if (encoder->flags & EncoderLossy)
568 lossyRegion.assign_union(Region(rect));
569 else
570 lossyRegion.assign_subtract(Region(rect));
571
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200572 // This was either a rect getting refreshed, or a rect that just got
573 // new content. Either way we should not try to refresh it anymore.
574 pendingRefreshRegion.assign_subtract(Region(rect));
575
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100576 return encoder;
577}
578
579void EncodeManager::endRect()
580{
581 int klass;
582 int length;
583
584 conn->writer()->endRect();
585
586 length = conn->getOutStream()->length() - beforeLength;
587
588 klass = activeEncoders[activeType];
589 stats[klass][activeType].bytes += length;
590}
591
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100592void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100593{
594 std::vector<Rect> rects;
595 std::vector<Rect>::const_iterator rect;
596
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100597 Region lossyCopy;
598
Pierre Ossmane539cb82015-09-22 11:09:00 +0200599 beforeLength = conn->getOutStream()->length();
600
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100601 copied.get_rects(&rects, delta.x <= 0, delta.y <= 0);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100602 for (rect = rects.begin(); rect != rects.end(); ++rect) {
Pierre Ossmane539cb82015-09-22 11:09:00 +0200603 int equiv;
604
605 copyStats.rects++;
606 copyStats.pixels += rect->area();
Pierre Ossmanf81148c2018-07-25 20:44:32 +0200607 equiv = 12 + rect->area() * (conn->cp.pf().bpp/8);
Pierre Ossmane539cb82015-09-22 11:09:00 +0200608 copyStats.equivalent += equiv;
609
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100610 conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x,
611 rect->tl.y - delta.y);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100612 }
Pierre Ossmane539cb82015-09-22 11:09:00 +0200613
614 copyStats.bytes += conn->getOutStream()->length() - beforeLength;
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100615
616 lossyCopy = lossyRegion;
617 lossyCopy.translate(delta);
618 lossyCopy.assign_intersect(copied);
619 lossyRegion.assign_union(lossyCopy);
Peter Åstrand (astrand)7a368c92018-09-19 12:45:17 +0200620
621 // Stop any pending refresh as a copy is enough that we consider
622 // this region to be recently changed
623 pendingRefreshRegion.assign_subtract(copied);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100624}
625
626void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
627{
628 std::vector<Rect> rects;
629 std::vector<Rect>::const_iterator rect;
630
Pierre Ossmanc0397262014-03-14 15:59:46 +0100631 changed->get_rects(&rects);
Pierre Ossmaneef55162015-02-12 13:44:22 +0100632 for (rect = rects.begin(); rect != rects.end(); ++rect)
633 findSolidRect(*rect, changed, pb);
634}
Pierre Ossmanc0397262014-03-14 15:59:46 +0100635
Pierre Ossmaneef55162015-02-12 13:44:22 +0100636void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
637 const PixelBuffer* pb)
638{
639 Rect sr;
640 int dx, dy, dw, dh;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100641
Pierre Ossmaneef55162015-02-12 13:44:22 +0100642 // We start by finding a solid 16x16 block
643 for (dy = rect.tl.y; dy < rect.br.y; dy += SolidSearchBlock) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100644
Pierre Ossmaneef55162015-02-12 13:44:22 +0100645 dh = SolidSearchBlock;
646 if (dy + dh > rect.br.y)
647 dh = rect.br.y - dy;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100648
Pierre Ossmaneef55162015-02-12 13:44:22 +0100649 for (dx = rect.tl.x; dx < rect.br.x; dx += SolidSearchBlock) {
650 // We define it like this to guarantee alignment
651 rdr::U32 _buffer;
652 rdr::U8* colourValue = (rdr::U8*)&_buffer;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100653
Pierre Ossmaneef55162015-02-12 13:44:22 +0100654 dw = SolidSearchBlock;
655 if (dx + dw > rect.br.x)
656 dw = rect.br.x - dx;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100657
Pierre Ossmaneef55162015-02-12 13:44:22 +0100658 pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
Pierre Ossmanc0397262014-03-14 15:59:46 +0100659
Pierre Ossmaneef55162015-02-12 13:44:22 +0100660 sr.setXYWH(dx, dy, dw, dh);
661 if (checkSolidTile(sr, colourValue, pb)) {
662 Rect erb, erp;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100663
Pierre Ossmaneef55162015-02-12 13:44:22 +0100664 Encoder *encoder;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100665
Pierre Ossmaneef55162015-02-12 13:44:22 +0100666 // We then try extending the area by adding more blocks
667 // in both directions and pick the combination that gives
668 // the largest area.
669 sr.setXYWH(dx, dy, rect.br.x - dx, rect.br.y - dy);
670 extendSolidAreaByBlock(sr, colourValue, pb, &erb);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100671
Pierre Ossmaneef55162015-02-12 13:44:22 +0100672 // Did we end up getting the entire rectangle?
673 if (erb.equals(rect))
674 erp = erb;
675 else {
676 // Don't bother with sending tiny rectangles
677 if (erb.area() < SolidBlockMinArea)
678 continue;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100679
Pierre Ossmaneef55162015-02-12 13:44:22 +0100680 // Extend the area again, but this time one pixel
681 // row/column at a time.
682 extendSolidAreaByPixel(rect, erb, colourValue, pb, &erp);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100683 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100684
Pierre Ossmaneef55162015-02-12 13:44:22 +0100685 // Send solid-color rectangle.
686 encoder = startRect(erp, encoderSolid);
687 if (encoder->flags & EncoderUseNativePF) {
688 encoder->writeSolidRect(erp.width(), erp.height(),
689 pb->getPF(), colourValue);
690 } else {
691 rdr::U32 _buffer2;
692 rdr::U8* converted = (rdr::U8*)&_buffer2;
693
694 conn->cp.pf().bufferFromBuffer(converted, pb->getPF(),
695 colourValue, 1);
696
697 encoder->writeSolidRect(erp.width(), erp.height(),
698 conn->cp.pf(), converted);
699 }
700 endRect();
701
702 changed->assign_subtract(Region(erp));
703
704 // Search remaining areas by recursion
705 // FIXME: Is this the best way to divide things up?
706
707 // Left? (Note that we've already searched a SolidSearchBlock
708 // pixels high strip here)
709 if ((erp.tl.x != rect.tl.x) && (erp.height() > SolidSearchBlock)) {
710 sr.setXYWH(rect.tl.x, erp.tl.y + SolidSearchBlock,
711 erp.tl.x - rect.tl.x, erp.height() - SolidSearchBlock);
712 findSolidRect(sr, changed, pb);
713 }
714
715 // Right?
716 if (erp.br.x != rect.br.x) {
717 sr.setXYWH(erp.br.x, erp.tl.y, rect.br.x - erp.br.x, erp.height());
718 findSolidRect(sr, changed, pb);
719 }
720
721 // Below?
722 if (erp.br.y != rect.br.y) {
723 sr.setXYWH(rect.tl.x, erp.br.y, rect.width(), rect.br.y - erp.br.y);
724 findSolidRect(sr, changed, pb);
725 }
726
727 return;
728 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100729 }
730 }
731}
732
733void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb)
734{
735 std::vector<Rect> rects;
736 std::vector<Rect>::const_iterator rect;
737
738 changed.get_rects(&rects);
739 for (rect = rects.begin(); rect != rects.end(); ++rect) {
740 int w, h, sw, sh;
741 Rect sr;
742
743 w = rect->width();
744 h = rect->height();
745
746 // No split necessary?
747 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
748 writeSubRect(*rect, pb);
749 continue;
750 }
751
752 if (w <= SubRectMaxWidth)
753 sw = w;
754 else
755 sw = SubRectMaxWidth;
756
757 sh = SubRectMaxArea / sw;
758
759 for (sr.tl.y = rect->tl.y; sr.tl.y < rect->br.y; sr.tl.y += sh) {
760 sr.br.y = sr.tl.y + sh;
761 if (sr.br.y > rect->br.y)
762 sr.br.y = rect->br.y;
763
764 for (sr.tl.x = rect->tl.x; sr.tl.x < rect->br.x; sr.tl.x += sw) {
765 sr.br.x = sr.tl.x + sw;
766 if (sr.br.x > rect->br.x)
767 sr.br.x = rect->br.x;
768
769 writeSubRect(sr, pb);
770 }
771 }
772 }
773}
774
775void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
776{
777 PixelBuffer *ppb;
778
779 Encoder *encoder;
780
781 struct RectInfo info;
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100782 unsigned int divisor, maxColours;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100783
784 bool useRLE;
785 EncoderType type;
786
787 // FIXME: This is roughly the algorithm previously used by the Tight
788 // encoder. It seems a bit backwards though, that higher
789 // compression setting means spending less effort in building
790 // a palette. It might be that they figured the increase in
791 // zlib setting compensated for the loss.
792 if (conn->cp.compressLevel == -1)
793 divisor = 2 * 8;
794 else
795 divisor = conn->cp.compressLevel * 8;
796 if (divisor < 4)
797 divisor = 4;
798
799 maxColours = rect.area()/divisor;
800
801 // Special exception inherited from the Tight encoder
802 if (activeEncoders[encoderFullColour] == encoderTightJPEG) {
Pierre Ossman4daa7b12015-02-12 14:57:05 +0100803 if ((conn->cp.compressLevel != -1) && (conn->cp.compressLevel < 2))
Pierre Ossmanc0397262014-03-14 15:59:46 +0100804 maxColours = 24;
805 else
806 maxColours = 96;
807 }
808
809 if (maxColours < 2)
810 maxColours = 2;
811
812 encoder = encoders[activeEncoders[encoderIndexedRLE]];
813 if (maxColours > encoder->maxPaletteSize)
814 maxColours = encoder->maxPaletteSize;
815 encoder = encoders[activeEncoders[encoderIndexed]];
816 if (maxColours > encoder->maxPaletteSize)
817 maxColours = encoder->maxPaletteSize;
818
819 ppb = preparePixelBuffer(rect, pb, true);
820
821 if (!analyseRect(ppb, &info, maxColours))
822 info.palette.clear();
823
824 // Different encoders might have different RLE overhead, but
825 // here we do a guess at RLE being the better choice if reduces
826 // the pixel count by 50%.
827 useRLE = info.rleRuns <= (rect.area() * 2);
828
829 switch (info.palette.size()) {
830 case 0:
831 type = encoderFullColour;
832 break;
833 case 1:
834 type = encoderSolid;
835 break;
836 case 2:
837 if (useRLE)
838 type = encoderBitmapRLE;
839 else
840 type = encoderBitmap;
841 break;
842 default:
843 if (useRLE)
844 type = encoderIndexedRLE;
845 else
846 type = encoderIndexed;
847 }
848
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100849 encoder = startRect(rect, type);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100850
851 if (encoder->flags & EncoderUseNativePF)
852 ppb = preparePixelBuffer(rect, pb, false);
853
Pierre Ossmanc0397262014-03-14 15:59:46 +0100854 encoder->writeRect(ppb, info.palette);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100855
856 endRect();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100857}
858
859bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue,
860 const PixelBuffer *pb)
861{
862 switch (pb->getPF().bpp) {
863 case 32:
864 return checkSolidTile(r, *(const rdr::U32*)colourValue, pb);
865 case 16:
866 return checkSolidTile(r, *(const rdr::U16*)colourValue, pb);
867 default:
868 return checkSolidTile(r, *(const rdr::U8*)colourValue, pb);
869 }
870}
871
872void EncodeManager::extendSolidAreaByBlock(const Rect& r,
873 const rdr::U8* colourValue,
874 const PixelBuffer *pb, Rect* er)
875{
876 int dx, dy, dw, dh;
877 int w_prev;
878 Rect sr;
879 int w_best = 0, h_best = 0;
880
881 w_prev = r.width();
882
883 // We search width first, back off when we hit a different colour,
884 // and restart with a larger height. We keep track of the
885 // width/height combination that gives us the largest area.
886 for (dy = r.tl.y; dy < r.br.y; dy += SolidSearchBlock) {
887
888 dh = SolidSearchBlock;
889 if (dy + dh > r.br.y)
890 dh = r.br.y - dy;
891
892 // We test one block here outside the x loop in order to break
893 // the y loop right away.
894 dw = SolidSearchBlock;
895 if (dw > w_prev)
896 dw = w_prev;
897
898 sr.setXYWH(r.tl.x, dy, dw, dh);
899 if (!checkSolidTile(sr, colourValue, pb))
900 break;
901
902 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
903
904 dw = SolidSearchBlock;
905 if (dx + dw > r.tl.x + w_prev)
906 dw = r.tl.x + w_prev - dx;
907
908 sr.setXYWH(dx, dy, dw, dh);
909 if (!checkSolidTile(sr, colourValue, pb))
910 break;
911
912 dx += dw;
913 }
914
915 w_prev = dx - r.tl.x;
916 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
917 w_best = w_prev;
918 h_best = dy + dh - r.tl.y;
919 }
920 }
921
922 er->tl.x = r.tl.x;
923 er->tl.y = r.tl.y;
924 er->br.x = er->tl.x + w_best;
925 er->br.y = er->tl.y + h_best;
926}
927
928void EncodeManager::extendSolidAreaByPixel(const Rect& r, const Rect& sr,
929 const rdr::U8* colourValue,
930 const PixelBuffer *pb, Rect* er)
931{
932 int cx, cy;
933 Rect tr;
934
935 // Try to extend the area upwards.
936 for (cy = sr.tl.y - 1; cy >= r.tl.y; cy--) {
937 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
938 if (!checkSolidTile(tr, colourValue, pb))
939 break;
940 }
941 er->tl.y = cy + 1;
942
943 // ... downwards.
944 for (cy = sr.br.y; cy < r.br.y; cy++) {
945 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
946 if (!checkSolidTile(tr, colourValue, pb))
947 break;
948 }
949 er->br.y = cy;
950
951 // ... to the left.
952 for (cx = sr.tl.x - 1; cx >= r.tl.x; cx--) {
953 tr.setXYWH(cx, er->tl.y, 1, er->height());
954 if (!checkSolidTile(tr, colourValue, pb))
955 break;
956 }
957 er->tl.x = cx + 1;
958
959 // ... to the right.
960 for (cx = sr.br.x; cx < r.br.x; cx++) {
961 tr.setXYWH(cx, er->tl.y, 1, er->height());
962 if (!checkSolidTile(tr, colourValue, pb))
963 break;
964 }
965 er->br.x = cx;
966}
967
968PixelBuffer* EncodeManager::preparePixelBuffer(const Rect& rect,
969 const PixelBuffer *pb,
970 bool convert)
971{
972 const rdr::U8* buffer;
973 int stride;
974
975 // Do wo need to convert the data?
976 if (convert && !conn->cp.pf().equal(pb->getPF())) {
977 convertedPixelBuffer.setPF(conn->cp.pf());
978 convertedPixelBuffer.setSize(rect.width(), rect.height());
979
980 buffer = pb->getBuffer(rect, &stride);
981 convertedPixelBuffer.imageRect(pb->getPF(),
982 convertedPixelBuffer.getRect(),
983 buffer, stride);
984
985 return &convertedPixelBuffer;
986 }
987
988 // Otherwise we still need to shift the coordinates. We have our own
989 // abusive subclass of FullFramePixelBuffer for this.
990
991 buffer = pb->getBuffer(rect, &stride);
992
993 offsetPixelBuffer.update(pb->getPF(), rect.width(), rect.height(),
994 buffer, stride);
995
996 return &offsetPixelBuffer;
997}
998
999bool EncodeManager::analyseRect(const PixelBuffer *pb,
1000 struct RectInfo *info, int maxColours)
1001{
1002 const rdr::U8* buffer;
1003 int stride;
1004
1005 buffer = pb->getBuffer(pb->getRect(), &stride);
1006
1007 switch (pb->getPF().bpp) {
1008 case 32:
1009 return analyseRect(pb->width(), pb->height(),
1010 (const rdr::U32*)buffer, stride,
1011 info, maxColours);
1012 case 16:
1013 return analyseRect(pb->width(), pb->height(),
1014 (const rdr::U16*)buffer, stride,
1015 info, maxColours);
1016 default:
1017 return analyseRect(pb->width(), pb->height(),
1018 (const rdr::U8*)buffer, stride,
1019 info, maxColours);
1020 }
1021}
1022
1023void EncodeManager::OffsetPixelBuffer::update(const PixelFormat& pf,
1024 int width, int height,
1025 const rdr::U8* data_,
1026 int stride_)
1027{
1028 format = pf;
1029 width_ = width;
1030 height_ = height;
1031 // Forced cast. We never write anything though, so it should be safe.
1032 data = (rdr::U8*)data_;
1033 stride = stride_;
1034}
1035
1036// Preprocessor generated, optimised methods
1037
1038#define BPP 8
1039#include "EncodeManagerBPP.cxx"
1040#undef BPP
1041#define BPP 16
1042#include "EncodeManagerBPP.cxx"
1043#undef BPP
1044#define BPP 32
1045#include "EncodeManagerBPP.cxx"
1046#undef BPP