/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace rfb; static LogWriter vlog("ComparingUpdateTracker"); static uint32_t ispow(const uint32_t in) { if (in < 2) return 0; return !(in & (in - 1)); } static uint32_t npow(uint32_t in) { if (ispow(in)) return in; in |= in >> 1; in |= in >> 2; in |= in >> 4; in |= in >> 8; in |= in >> 16; return in + 1; } static uint32_t pow2shift(const uint32_t in) { return __builtin_ffs(in) - 1; } #define SCROLLBLOCK_SIZE 64 #define NUM_TOTALS (1024 * 256) #define MAX_CHECKS 8 class scrollHasher_t { protected: struct hashdata_t { uint32_t hash; bool operator ==(const hashdata_t &other) const { return hash == other.hash; } bool operator <(const hashdata_t &other) const { return hash < other.hash; } }; struct hasher { size_t operator()(const hashdata_t &in) const { return in.hash; } }; struct match_t { uint32_t hash, idx; }; uint_fast32_t w, h, d, lineBytes, blockBytes; hashdata_t *hashtable; uint_fast32_t hashw, hashAnd, hashShift; mutable int_fast16_t lastOffX, lastOffY; const uint8_t *olddata; uint32_t *totals, *starts, *idxtable, *curs; public: scrollHasher_t(): w(0), h(0), d(0), lineBytes(0), blockBytes(0), hashtable(NULL), hashw(0), hashAnd(0), hashShift(0), lastOffX(0), lastOffY(0), olddata(NULL), totals(NULL), starts(NULL), idxtable(NULL) { assert(sizeof(hashdata_t) == sizeof(uint32_t)); } virtual ~scrollHasher_t() { free(totals); free(starts); free(curs); free(hashtable); free(idxtable); free((void *) olddata); } virtual void calcHashes(const uint8_t *ptr, const uint32_t w_, const uint32_t h_, const uint32_t d_) = 0; virtual void invalidate(const uint_fast32_t x, uint_fast32_t y, uint_fast32_t h) = 0; virtual void findBestMatch(const uint8_t * const ptr, const uint_fast32_t maxLines, const uint_fast32_t inx, const uint_fast32_t iny, uint_fast32_t *outx, uint_fast32_t *outy, uint_fast32_t *outlines) const = 0; virtual void findBlock(const uint8_t * const ptr, const uint_fast32_t inx, const uint_fast32_t iny, uint_fast32_t *outx, uint_fast32_t *outy, uint_fast32_t *outlines) const = 0; }; class scrollHasher_vert_t: public scrollHasher_t { public: scrollHasher_vert_t(): scrollHasher_t() { totals = (uint32_t *) malloc(sizeof(uint32_t) * NUM_TOTALS); starts = (uint32_t *) malloc(sizeof(uint32_t) * NUM_TOTALS); curs = (uint32_t *) malloc(sizeof(uint32_t) * NUM_TOTALS); } void calcHashes(const uint8_t *ptr, const uint32_t w_, const uint32_t h_, const uint32_t d_) { if (w != w_ || h != h_) { // Reallocate w = w_; h = h_; d = d_; lineBytes = w * d; blockBytes = SCROLLBLOCK_SIZE * d; hashw = npow(w / SCROLLBLOCK_SIZE); hashAnd = hashw - 1; hashShift = pow2shift(hashw); hashtable = (hashdata_t *) realloc(hashtable, hashw * h * sizeof(uint32_t)); idxtable = (uint32_t *) realloc(idxtable, hashw * h * sizeof(uint32_t)); olddata = (const uint8_t *) realloc((void *) olddata, w * h * d); } // We need to make a copy, since the comparer incrementally updates its copy memcpy((uint8_t *) olddata, ptr, w * h * d); //memset(hashtable, 0, hashw * h * sizeof(uint32_t)); //memset(idxtable, 0, w * h * sizeof(uint32_t)); memset(totals, 0, NUM_TOTALS * sizeof(uint32_t)); //memset(starts, 0, NUM_TOTALS * sizeof(uint32_t)); //memset(curs, 0, NUM_TOTALS * sizeof(uint32_t)); for (uint_fast32_t y = 0; y < h; y++) { const uint8_t *inptr0 = olddata; inptr0 += y * lineBytes; for (uint_fast32_t x = 0; x < w; x += SCROLLBLOCK_SIZE) { if (w - x < SCROLLBLOCK_SIZE) break; const uint_fast32_t idx = (y << hashShift) + x / SCROLLBLOCK_SIZE; hashtable[idx].hash = XXH64(inptr0, blockBytes, 0); totals[hashtable[idx].hash % NUM_TOTALS]++; inptr0 += blockBytes; } } // calculate number of unique 21-bit hashes /*uint_fast32_t uniqHashes = 0; for (uint_fast32_t i = 0; i < NUM_TOTALS; i++) { if (totals[i]) uniqHashes++; } printf("%lu unique hashes\n", uniqHashes);*/ // Update starting positions uint_fast32_t sum = 0; for (uint_fast32_t i = 0; i < NUM_TOTALS; i++) { if (!totals[i]) continue; starts[i] = curs[i] = sum; sum += totals[i]; } // update index table const hashdata_t *src = hashtable; for (uint_fast32_t y = 0; y < h; y++) { uint_fast32_t ybase = (y << hashShift); for (uint_fast32_t x = 0; x < w; x += SCROLLBLOCK_SIZE, ybase++) { if (w - x < SCROLLBLOCK_SIZE) break; const uint_fast32_t val = src[x / SCROLLBLOCK_SIZE].hash; const uint_fast32_t smallIdx = val % NUM_TOTALS; const uint_fast32_t newpos = curs[smallIdx]++; // this assert is very heavy, uncomment only for debugging //assert(curs[smallIdx] - starts[smallIdx] <= totals[smallIdx]); idxtable[newpos] = ybase; } src += hashw; } lastOffX = lastOffY = 0; } void invalidate(const uint_fast32_t x, uint_fast32_t y, uint_fast32_t h) { h += y; for (; y < h; y++) { memset(&hashtable[(y << hashShift) + x / SCROLLBLOCK_SIZE], 0, sizeof(uint32_t)); } } void findBestMatch(const uint8_t * const ptr, const uint_fast32_t maxLines, const uint_fast32_t inx, const uint_fast32_t iny, uint_fast32_t *outx, uint_fast32_t *outy, uint_fast32_t *outlines) const { const uint_fast32_t starthash = (uint32_t) XXH64(ptr, blockBytes, 0); const uint_fast32_t smallIdx = starthash % NUM_TOTALS; match_t matches[MAX_CHECKS]; *outlines = 0; uint_fast32_t i, upto, curidx, curhash, found = 0, inc; upto = totals[smallIdx] + starts[smallIdx]; if (!totals[smallIdx]) return; inc = totals[smallIdx] / 32; if (!inc) inc = 1; //printf("target hash %lx, it has %u matches\n", // starthash, totals[smallIdx]); // First, try the last good offset. If this was a scroll, // and we have a good offset, it should match almost everything const uint_fast16_t tryX = inx + lastOffX; const uint_fast16_t tryY = iny + lastOffY; if ((lastOffX || lastOffY) && tryX < w - (SCROLLBLOCK_SIZE - 1) && tryY < h - maxLines) { //printf("Trying good offset %ld,%ld for in %lu,%lu, try %lu,%lu\n", // lastOffX, lastOffY, inx, iny, tryX, tryY); curidx = (tryY << hashShift) + tryX / SCROLLBLOCK_SIZE; curhash = hashtable[curidx].hash; if (curhash == starthash && memcmp(ptr, &olddata[tryY * lineBytes + tryX * d], blockBytes) == 0) { matches[0].hash = curhash; matches[0].idx = curidx; found++; } /*else printf("Nope, hashes %u %lx %lx, mem %u, maxlines %lu\n", curhash == starthash, curhash, starthash, memcmp(ptr, &olddata[tryY * lineBytes + tryX * d], blockBytes) == 0, maxLines);*/ } for (i = starts[smallIdx]; i < upto; i += inc) { curidx = idxtable[i]; curhash = hashtable[curidx].hash; if (curhash != starthash) continue; // Convert to olddata position const uint_fast32_t oldy = curidx >> hashShift; const uint_fast32_t oldx = curidx & hashAnd; if (memcmp(ptr, &olddata[oldy * lineBytes + oldx * blockBytes], blockBytes)) continue; matches[found].hash = curhash; matches[found].idx = curidx; found++; if (found >= MAX_CHECKS) break; } if (!found) return; //printf("%lu of those were suitable for further checks\n", found); // Find best of them uint_fast32_t best = 0, bestmatches = 0; for (i = 0; i < found; i++) { const uint_fast32_t oldy = matches[i].idx >> hashShift; const uint_fast32_t oldx = matches[i].idx & hashAnd; uint_fast32_t k, bothMaxLines; bothMaxLines = maxLines; if (bothMaxLines > h - oldy) bothMaxLines = h - oldy; for (k = 1; k < bothMaxLines; k++) { /* curhash = adler32(adler32(0, NULL, 0), ptr + lineBytes * k, blockBytes); if (curhash != hashtable[matches[i].idx + hashw * k].hash) break;*/ if (!hashtable[matches[i].idx + (k << hashShift)].hash) break; // Invalidated if (memcmp(ptr + lineBytes * k, &olddata[(oldy + k) * lineBytes + oldx * blockBytes], blockBytes)) break; } if (k > bestmatches) { bestmatches = k; best = i; } if (k == maxLines) break; } //printf("Best had %lu matching lines of allowed %lu\n", bestmatches, maxLines); *outlines = bestmatches; *outx = (matches[best].idx & hashAnd) * SCROLLBLOCK_SIZE; *outy = matches[best].idx >> hashShift; // Was it a good match? If so, store for later if (*outx == inx && bestmatches >= maxLines / 2 && totals[smallIdx] < 4 && *outy != iny) { lastOffX = 0; lastOffY = *outy - iny; } } void findBlock(const uint8_t * const ptr, const uint_fast32_t inx, const uint_fast32_t iny, uint_fast32_t *outx, uint_fast32_t *outy, uint_fast32_t *outlines) const { uint_fast32_t i, lowest = 0, tmpx = 0, tmpy = 0, tmplines, searchHash, lowestTotal = 10000, tmpTotal; *outlines = 0; for (i = 0; i < SCROLLBLOCK_SIZE; i++) { searchHash = (uint32_t) XXH64(ptr + lineBytes * i, blockBytes, 0); const uint_fast32_t smallIdx = searchHash % NUM_TOTALS; tmpTotal = totals[smallIdx]; if (!tmpTotal) return; if (tmpTotal < lowestTotal) { lowest = i; lowestTotal = tmpTotal; } } // If the lowest number of matches is too high, we probably can't find // a full block if (lowestTotal > MAX_CHECKS) return; //printf("Lowest was %lu, %lu totals\n", lowest, lowestTotal); findBestMatch(ptr + lineBytes * lowest, SCROLLBLOCK_SIZE - lowest, inx, iny + lowest, &tmpx, &tmpy, &tmplines); // The end didn't match if (tmplines != SCROLLBLOCK_SIZE - lowest) return; if (tmpx != inx) return; // Source too high? if (tmpy < lowest) return; // Try to see if the beginning matches for (i = 0; i < lowest; i++) { if (!hashtable[((tmpy - lowest + i) << hashShift) + inx / SCROLLBLOCK_SIZE].hash) return; // Invalidated if (memcmp(ptr + lineBytes * i, &olddata[(tmpy - lowest + i) * lineBytes + tmpx * d], blockBytes)) return; } *outlines = 64; *outx = tmpx; *outy = tmpy - lowest; } }; #undef NUM_TOTALS #define NUM_TOTALS (1024 * 1024 * 2) class scrollHasher_bothDir_t: public scrollHasher_t { public: scrollHasher_bothDir_t(): scrollHasher_t() { totals = (uint32_t *) malloc(sizeof(uint32_t) * NUM_TOTALS); starts = (uint32_t *) malloc(sizeof(uint32_t) * NUM_TOTALS); curs = (uint32_t *) malloc(sizeof(uint32_t) * NUM_TOTALS); } void calcHashes(const uint8_t *ptr, const uint32_t w_, const uint32_t h_, const uint32_t d_) { if (w != w_ || h != h_) { // Reallocate w = w_; h = h_; d = d_; lineBytes = w * d; blockBytes = SCROLLBLOCK_SIZE * d; hashw = npow(w - (SCROLLBLOCK_SIZE - 1)); hashAnd = hashw - 1; hashShift = pow2shift(hashw); hashtable = (hashdata_t *) realloc(hashtable, hashw * h * sizeof(uint32_t)); idxtable = (uint32_t *) realloc(idxtable, w * h * sizeof(uint32_t)); olddata = (const uint8_t *) realloc((void *) olddata, w * h * d); } // We need to make a copy, since the comparer incrementally updates its copy memcpy((uint8_t *) olddata, ptr, w * h * d); Adler32 rolling(blockBytes); //memset(hashtable, 0, hashw * h * sizeof(uint32_t)); //memset(idxtable, 0, w * h * sizeof(uint32_t)); memset(totals, 0, NUM_TOTALS * sizeof(uint32_t)); //memset(starts, 0, NUM_TOTALS * sizeof(uint32_t)); //memset(curs, 0, NUM_TOTALS * sizeof(uint32_t)); const uint8_t *prevptr = NULL; for (uint_fast32_t y = 0; y < h; y++) { const uint8_t *inptr0 = olddata; inptr0 += y * lineBytes; for (uint_fast32_t x = 0; x < w - (SCROLLBLOCK_SIZE - 1); x++) { if (!x) { rolling.reset(); uint_fast32_t g; for (g = 0; g < SCROLLBLOCK_SIZE; g++) { for (uint_fast32_t di = 0; di < d; di++) { rolling.eat(inptr0[g * d + di]); } } } else { for (uint_fast32_t di = 0; di < d; di++) { rolling.update(prevptr[di], inptr0[(SCROLLBLOCK_SIZE - 1) * d + di]); } } const uint_fast32_t idx = (y << hashShift) + x; hashtable[idx].hash = rolling.hash; totals[rolling.hash % NUM_TOTALS]++; prevptr = inptr0; inptr0 += d; } } // calculate number of unique 21-bit hashes /*uint_fast32_t uniqHashes = 0; for (uint_fast32_t i = 0; i < NUM_TOTALS; i++) { if (totals[i]) uniqHashes++; } printf("%lu unique hashes\n", uniqHashes);*/ // Update starting positions uint_fast32_t sum = 0; for (uint_fast32_t i = 0; i < NUM_TOTALS; i++) { if (!totals[i]) continue; starts[i] = curs[i] = sum; sum += totals[i]; } // update index table const hashdata_t *src = hashtable; for (uint_fast32_t y = 0; y < h; y++) { uint_fast32_t ybase = (y << hashShift); for (uint_fast32_t x = 0; x < w - (SCROLLBLOCK_SIZE - 1); x++, ybase++) { const uint_fast32_t val = src[x].hash; const uint_fast32_t smallIdx = val % NUM_TOTALS; const uint_fast32_t newpos = curs[smallIdx]++; // this assert is very heavy, uncomment only for debugging //assert(curs[smallIdx] - starts[smallIdx] <= totals[smallIdx]); idxtable[newpos] = ybase; } src += hashw; } lastOffX = lastOffY = 0; } void invalidate(const uint_fast32_t x, uint_fast32_t y, uint_fast32_t h) { const uint_fast32_t nw = SCROLLBLOCK_SIZE; const uint_fast32_t left = x > (SCROLLBLOCK_SIZE - 1) ? (SCROLLBLOCK_SIZE - 1) : x; const uint_fast32_t right = x + nw + (SCROLLBLOCK_SIZE - 1) < w ? (SCROLLBLOCK_SIZE - 1) : w - x - nw; h += y; for (; y < h; y++) { memset(&hashtable[(y << hashShift) + x - left], 0, sizeof(uint32_t) * (nw + left + right)); } } void findBestMatch(const uint8_t * const ptr, const uint_fast32_t maxLines, const uint_fast32_t inx, const uint_fast32_t iny, uint_fast32_t *outx, uint_fast32_t *outy, uint_fast32_t *outlines) const { const uint_fast32_t starthash = adler32(adler32(0, NULL, 0), ptr, blockBytes); const uint_fast32_t smallIdx = starthash % NUM_TOTALS; match_t matches[MAX_CHECKS]; *outlines = 0; uint_fast32_t i, upto, curidx, curhash, found = 0, inc; upto = totals[smallIdx] + starts[smallIdx]; if (!totals[smallIdx]) return; inc = totals[smallIdx] / 32; if (!inc) inc = 1; //printf("target hash %lx, it has %u matches\n", // starthash, totals[smallIdx]); // First, try the last good offset. If this was a scroll, // and we have a good offset, it should match almost everything const uint_fast16_t tryX = inx + lastOffX; const uint_fast16_t tryY = iny + lastOffY; if ((lastOffX || lastOffY) && tryX < w - (SCROLLBLOCK_SIZE - 1) && tryY < h - maxLines) { //printf("Trying good offset %ld,%ld for in %lu,%lu, try %lu,%lu\n", // lastOffX, lastOffY, inx, iny, tryX, tryY); curidx = (tryY << hashShift) + tryX; curhash = hashtable[curidx].hash; if (curhash == starthash && memcmp(ptr, &olddata[tryY * lineBytes + tryX * d], blockBytes) == 0) { matches[0].hash = curhash; matches[0].idx = curidx; found++; } /*else printf("Nope, hashes %u %lx %lx, mem %u, maxlines %lu\n", curhash == starthash, curhash, starthash, memcmp(ptr, &olddata[tryY * lineBytes + tryX * d], blockBytes) == 0, maxLines);*/ } for (i = starts[smallIdx]; i < upto; i += inc) { curidx = idxtable[i]; curhash = hashtable[curidx].hash; if (curhash != starthash) continue; // Convert to olddata position const uint_fast32_t oldy = curidx >> hashShift; const uint_fast32_t oldx = curidx & hashAnd; if (memcmp(ptr, &olddata[oldy * lineBytes + oldx * d], blockBytes)) continue; matches[found].hash = curhash; matches[found].idx = curidx; found++; if (found >= MAX_CHECKS) break; } if (!found) return; //printf("%lu of those were suitable for further checks\n", found); // Find best of them uint_fast32_t best = 0, bestmatches = 0; for (i = 0; i < found; i++) { const uint_fast32_t oldy = matches[i].idx >> hashShift; const uint_fast32_t oldx = matches[i].idx & hashAnd; uint_fast32_t k, bothMaxLines; bothMaxLines = maxLines; if (bothMaxLines > h - oldy) bothMaxLines = h - oldy; for (k = 1; k < bothMaxLines; k++) { /* curhash = adler32(adler32(0, NULL, 0), ptr + lineBytes * k, blockBytes); if (curhash != hashtable[matches[i].idx + hashw * k].hash) break;*/ if (!hashtable[matches[i].idx + (k << hashShift)].hash) break; // Invalidated if (memcmp(ptr + lineBytes * k, &olddata[(oldy + k) * lineBytes + oldx * d], blockBytes)) break; } if (k > bestmatches) { bestmatches = k; best = i; } if (k == maxLines) break; } //printf("Best had %lu matching lines of allowed %lu\n", bestmatches, maxLines); *outlines = bestmatches; *outx = matches[best].idx & hashAnd; *outy = matches[best].idx >> hashShift; // Was it a good match? If so, store for later if (bestmatches >= maxLines / 2 && totals[smallIdx] < 4) { lastOffX = *outx - inx; lastOffY = *outy - iny; } } void findBlock(const uint8_t * const ptr, const uint_fast32_t inx, const uint_fast32_t iny, uint_fast32_t *outx, uint_fast32_t *outy, uint_fast32_t *outlines) const { *outlines = 0; // Not implemented for horizontal } }; ComparingUpdateTracker::ComparingUpdateTracker(PixelBuffer* buffer) : fb(buffer), oldFb(fb->getPF(), 0, 0), firstCompare(true), enabled(true), detectScroll(false), totalPixels(0), missedPixels(0), scrollHasher(NULL) { changed.assign_union(fb->getRect()); if (Server::detectHorizontal) scrollHasher = new scrollHasher_bothDir_t; else scrollHasher = new scrollHasher_vert_t; } ComparingUpdateTracker::~ComparingUpdateTracker() { delete scrollHasher; } #define BLOCK_SIZE 64 bool ComparingUpdateTracker::compare(bool skipScrollDetection, const Region &skipCursorArea) { std::vector rects; std::vector::iterator i; if (!enabled) return false; if (firstCompare) { // NB: We leave the change region untouched on this iteration, // since in effect the entire framebuffer has changed. oldFb.setSize(fb->width(), fb->height()); for (int y=0; yheight(); y+=BLOCK_SIZE) { Rect pos(0, y, fb->width(), __rfbmin(fb->height(), y+BLOCK_SIZE)); int srcStride; const rdr::U8* srcData = fb->getBuffer(pos, &srcStride); oldFb.imageRect(pos, srcData, srcStride); } firstCompare = false; return false; } copied.get_rects(&rects, copy_delta.x<=0, copy_delta.y<=0); for (i = rects.begin(); i != rects.end(); i++) oldFb.copyRect(*i, copy_delta); changed.get_rects(&rects); uint32_t changedArea = 0; bool atLeast64 = false; detectScroll = false; for (i = rects.begin(); i != rects.end(); i++) { if (!atLeast64 && i->width() >= 64) atLeast64 = true; changedArea += i->area(); } if (atLeast64 && Server::detectScrolling && !skipScrollDetection && (changedArea * 100) / (fb->width() * fb->height()) > (unsigned) Server::scrollDetectLimit) { detectScroll = true; Rect pos(0, 0, oldFb.width(), oldFb.height()); int unused; scrollHasher->calcHashes(oldFb.getBuffer(pos, &unused), oldFb.width(), oldFb.height(), oldFb.getPF().bpp / 8); // Invalidating lossy areas is not needed, the lossy region tracking tracks copies too } copyPassRects.clear(); Region newChanged; for (i = rects.begin(); i != rects.end(); i++) compareRect(*i, &newChanged, skipCursorArea); changed.get_rects(&rects); for (i = rects.begin(); i != rects.end(); i++) totalPixels += i->area(); newChanged.get_rects(&rects); for (i = rects.begin(); i != rects.end(); i++) missedPixels += i->area(); if (changed.equals(newChanged)) return false; changed = newChanged; return true; } void ComparingUpdateTracker::enable() { enabled = true; } void ComparingUpdateTracker::disable() { enabled = false; // Make sure we update the framebuffer next time we get enabled firstCompare = true; } static void tryMerge(std::vector ©PassRects, const int y, const int blockLeft, const int blockRight, const uint_fast32_t outlines, const uint_fast32_t outx, const uint_fast32_t outy) { if (copyPassRects.size() && copyPassRects.back().rect.tl.y == y && copyPassRects.back().rect.br.x == blockLeft && (unsigned) copyPassRects.back().rect.br.y == y + outlines && copyPassRects.back().src_x + copyPassRects.back().rect.width() == outx && copyPassRects.back().src_y == outy) { CopyPassRect &prev = copyPassRects.back(); prev.rect.br.x += SCROLLBLOCK_SIZE; //merged++; } else { // Before adding this new rect as a non-mergeable one, try to vertically merge the // previous two if (copyPassRects.size() > 1) { CopyPassRect &prev = *(copyPassRects.end() - 2); const CopyPassRect &cur = copyPassRects.back(); if (prev.rect.br.y == cur.rect.tl.y && prev.rect.tl.x == cur.rect.tl.x && prev.rect.br.x == cur.rect.br.x && prev.src_x == cur.src_x && prev.src_y + prev.rect.height() == cur.src_y) { prev.rect.br.y += cur.rect.height(); copyPassRects.pop_back(); } } const CopyPassRect cp = {Rect(blockLeft, y, blockRight, y + outlines), (unsigned) outx, (unsigned) outy}; copyPassRects.push_back(cp); } } void ComparingUpdateTracker::compareRect(const Rect& inr, Region* newChanged, const Region &skipCursorArea) { Rect r = inr; if (detectScroll && !Server::detectHorizontal) r.tl.x &= ~(BLOCK_SIZE - 1); if (!r.enclosed_by(fb->getRect())) { Rect safe; // Crop the rect and try again safe = r.intersect(fb->getRect()); if (!safe.is_empty()) compareRect(safe, newChanged, skipCursorArea); return; } int bytesPerPixel = fb->getPF().bpp/8; int oldStride; rdr::U8* oldData = oldFb.getBufferRW(r, &oldStride); int oldStrideBytes = oldStride * bytesPerPixel; std::vector changedBlocks; for (int blockTop = r.tl.y; blockTop < r.br.y; blockTop += BLOCK_SIZE) { // Get a strip of the source buffer Rect pos(r.tl.x, blockTop, r.br.x, __rfbmin(r.br.y, blockTop+BLOCK_SIZE)); int fbStride; const rdr::U8* newBlockPtr = fb->getBuffer(pos, &fbStride); int newStrideBytes = fbStride * bytesPerPixel; rdr::U8* oldBlockPtr = oldData; int blockBottom = __rfbmin(blockTop+BLOCK_SIZE, r.br.y); for (int blockLeft = r.tl.x; blockLeft < r.br.x; blockLeft += BLOCK_SIZE) { const rdr::U8* newPtr = newBlockPtr; rdr::U8* oldPtr = oldBlockPtr; int blockRight = __rfbmin(blockLeft+BLOCK_SIZE, r.br.x); int blockWidthInBytes = (blockRight-blockLeft) * bytesPerPixel; bool changed = false; int y; for (y = blockTop; y < blockBottom; y++) { if (memcmp(oldPtr, newPtr, blockWidthInBytes) != 0) { // A block has changed - copy the remainder to the oldFb changed = true; const rdr::U8* savedPtr = newPtr; for (int y2 = y; y2 < blockBottom; y2++) { memcpy(oldPtr, newPtr, blockWidthInBytes); newPtr += newStrideBytes; oldPtr += oldStrideBytes; } newPtr = savedPtr; break; } newPtr += newStrideBytes; oldPtr += oldStrideBytes; } if (!changed || (changed && !detectScroll) || (skipCursorArea.numRects() && !skipCursorArea.intersect(Rect(blockLeft, blockTop, blockRight, blockBottom)).is_empty())) { if (changed || skipCursorArea.numRects()) changedBlocks.push_back(Rect(blockLeft, blockTop, blockRight, blockBottom)); oldBlockPtr += blockWidthInBytes; newBlockPtr += blockWidthInBytes; continue; } uint_fast32_t outx, outy, outlines; if (blockRight - blockLeft < SCROLLBLOCK_SIZE) { // Block too small, put it out outright as changed changedBlocks.push_back(Rect(blockLeft, blockTop, blockRight, blockBottom)); } else { // First, try to find a full block outlines = 0; if (blockBottom - blockTop == SCROLLBLOCK_SIZE) scrollHasher->findBlock(newBlockPtr, blockLeft, blockTop, &outx, &outy, &outlines); if (outlines == SCROLLBLOCK_SIZE) { // Perfect match! // success += outlines; tryMerge(copyPassRects, blockTop, blockLeft, blockRight, outlines, outx, outy); scrollHasher->invalidate(blockLeft, blockTop, outlines); oldBlockPtr += blockWidthInBytes; newBlockPtr += blockWidthInBytes; continue; } for (; y < blockBottom; y += outlines) { // We have the first changed line. Find the best match, if any scrollHasher->findBestMatch(newPtr, blockBottom - y, blockLeft, y, &outx, &outy, &outlines); if (!outlines) { // Heuristic, if a line did not match, probably // the next few won't either changedBlocks.push_back(Rect(blockLeft, y, blockRight, __rfbmin(y + 4, blockBottom))); y += 4; newPtr += newStrideBytes * 4; // unfound += 4; continue; } // success += outlines; // Try to merge it with the last rect tryMerge(copyPassRects, y, blockLeft, blockRight, outlines, outx, outy); scrollHasher->invalidate(blockLeft, y, outlines); newPtr += newStrideBytes * outlines; } } oldBlockPtr += blockWidthInBytes; newBlockPtr += blockWidthInBytes; } oldData += oldStrideBytes * BLOCK_SIZE; } oldFb.commitBufferRW(r); if (!changedBlocks.empty()) { Region temp; temp.setOrderedRects(changedBlocks); newChanged->assign_union(temp); } } void ComparingUpdateTracker::logStats() { double ratio; char a[1024], b[1024]; siPrefix(totalPixels, "pixels", a, sizeof(a)); siPrefix(missedPixels, "pixels", b, sizeof(b)); ratio = (double)totalPixels / missedPixels; vlog.info("%s in / %s out", a, b); vlog.info("(1:%g ratio)", ratio); totalPixels = missedPixels = 0; } void ComparingUpdateTracker::getUpdateInfo(UpdateInfo* info, const Region& cliprgn) { info->copypassed = copyPassRects; SimpleUpdateTracker::getUpdateInfo(info, cliprgn); } void ComparingUpdateTracker::clear() { copyPassRects.clear(); SimpleUpdateTracker::clear(); }