summaryrefslogtreecommitdiffstats
path: root/x11vnc/scan.c
diff options
context:
space:
mode:
authorrunge <runge>2006-01-09 01:54:38 +0000
committerrunge <runge>2006-01-09 01:54:38 +0000
commit71f2ec79180185a6c3db0c87f9d53c491dc31e76 (patch)
tree67c341571cbeb1bd9a0744cc8eb03b30ef04f381 /x11vnc/scan.c
parentdef301266373e462f4a5e90eab443087ccfc7ccc (diff)
downloadlibtdevnc-71f2ec79180185a6c3db0c87f9d53c491dc31e76.tar.gz
libtdevnc-71f2ec79180185a6c3db0c87f9d53c491dc31e76.zip
x11vnc: the big split.
Diffstat (limited to 'x11vnc/scan.c')
-rw-r--r--x11vnc/scan.c2622
1 files changed, 2622 insertions, 0 deletions
diff --git a/x11vnc/scan.c b/x11vnc/scan.c
new file mode 100644
index 0000000..ad5f032
--- /dev/null
+++ b/x11vnc/scan.c
@@ -0,0 +1,2622 @@
+/* -- scan.c -- */
+
+#include "x11vnc.h"
+#include "xinerama.h"
+#include "xwrappers.h"
+#include "xdamage.h"
+#include "xrandr.h"
+#include "win_utils.h"
+#include "screen.h"
+#include "pointer.h"
+#include "cleanup.h"
+
+/*
+ * routines for scanning and reading the X11 display for changes, and
+ * for doing all the tile work (shm, etc).
+ */
+void initialize_tiles(void);
+void free_tiles(void);
+void shm_delete(XShmSegmentInfo *shm);
+void shm_clean(XShmSegmentInfo *shm, XImage *xim);
+void initialize_polling_images(void);
+void scale_rect(double factor, int blend, int interpolate, int Bpp,
+ char *src_fb, int src_bytes_per_line, char *dst_fb, int dst_bytes_per_line,
+ int Nx, int Ny, int nx, int ny, int X1, int Y1, int X2, int Y2, int mark);
+void scale_and_mark_rect(int X1, int Y1, int X2, int Y2);
+void mark_rect_as_modified(int x1, int y1, int x2, int y2, int force);
+int copy_screen(void);
+int copy_snap(void);
+void nap_sleep(int ms, int split);
+void set_offset(void);
+int scan_for_updates(int count_only);
+
+
+static void set_fs_factor(int max);
+static char *flip_ximage_byte_order(XImage *xim);
+static int shm_create(XShmSegmentInfo *shm, XImage **ximg_ptr, int w, int h,
+ char *name);
+static void create_tile_hint(int x, int y, int tw, int th, hint_t *hint);
+static void extend_tile_hint(int x, int y, int tw, int th, hint_t *hint);
+static void save_hint(hint_t hint, int loc);
+static void hint_updates(void);
+static void mark_hint(hint_t hint);
+static int copy_tiles(int tx, int ty, int nt);
+static int copy_all_tiles(void);
+static int copy_all_tile_runs(void);
+static int copy_tiles_backward_pass(void);
+static int copy_tiles_additional_pass(void);
+static int gap_try(int x, int y, int *run, int *saw, int along_x);
+static int fill_tile_gaps(void);
+static int island_try(int x, int y, int u, int v, int *run);
+static int grow_islands(void);
+static void blackout_regions(void);
+static void nap_set(int tile_cnt);
+static void nap_check(int tile_cnt);
+static void ping_clients(int tile_cnt);
+static int blackout_line_skip(int n, int x, int y, int rescan,
+ int *tile_count);
+static int blackout_line_cmpskip(int n, int x, int y, char *dst, char *src,
+ int w, int pixelsize);
+static int scan_display(int ystart, int rescan);
+
+
+/* array to hold the hints: */
+static hint_t *hint_list;
+
+/* nap state */
+int nap_ok = 0;
+static int nap_diff_count = 0;
+
+static int scan_count = 0; /* indicates which scan pattern we are on */
+static int scan_in_progress = 0;
+
+
+typedef struct tile_change_region {
+ /* start and end lines, along y, of the changed area inside a tile. */
+ unsigned short first_line, last_line;
+ short first_x, last_x;
+ /* info about differences along edges. */
+ unsigned short left_diff, right_diff;
+ unsigned short top_diff, bot_diff;
+} region_t;
+
+/* array to hold the tiles region_t-s. */
+static region_t *tile_region;
+
+
+
+
+/*
+ * setup tile numbers and allocate the tile and hint arrays:
+ */
+void initialize_tiles(void) {
+
+ ntiles_x = (dpy_x - 1)/tile_x + 1;
+ ntiles_y = (dpy_y - 1)/tile_y + 1;
+ ntiles = ntiles_x * ntiles_y;
+
+ tile_has_diff = (unsigned char *)
+ malloc((size_t) (ntiles * sizeof(unsigned char)));
+ tile_has_xdamage_diff = (unsigned char *)
+ malloc((size_t) (ntiles * sizeof(unsigned char)));
+ tile_row_has_xdamage_diff = (unsigned char *)
+ malloc((size_t) (ntiles_y * sizeof(unsigned char)));
+ tile_tried = (unsigned char *)
+ malloc((size_t) (ntiles * sizeof(unsigned char)));
+ tile_copied = (unsigned char *)
+ malloc((size_t) (ntiles * sizeof(unsigned char)));
+ tile_blackout = (tile_blackout_t *)
+ malloc((size_t) (ntiles * sizeof(tile_blackout_t)));
+ tile_region = (region_t *) malloc((size_t) (ntiles * sizeof(region_t)));
+
+ tile_row = (XImage **)
+ malloc((size_t) ((ntiles_x + 1) * sizeof(XImage *)));
+ tile_row_shm = (XShmSegmentInfo *)
+ malloc((size_t) ((ntiles_x + 1) * sizeof(XShmSegmentInfo)));
+
+ /* there will never be more hints than tiles: */
+ hint_list = (hint_t *) malloc((size_t) (ntiles * sizeof(hint_t)));
+}
+
+void free_tiles(void) {
+ if (tile_has_diff) {
+ free(tile_has_diff);
+ tile_has_diff = NULL;
+ }
+ if (tile_has_xdamage_diff) {
+ free(tile_has_xdamage_diff);
+ tile_has_xdamage_diff = NULL;
+ }
+ if (tile_row_has_xdamage_diff) {
+ free(tile_row_has_xdamage_diff);
+ tile_row_has_xdamage_diff = NULL;
+ }
+ if (tile_tried) {
+ free(tile_tried);
+ tile_tried = NULL;
+ }
+ if (tile_copied) {
+ free(tile_copied);
+ tile_copied = NULL;
+ }
+ if (tile_blackout) {
+ free(tile_blackout);
+ tile_blackout = NULL;
+ }
+ if (tile_region) {
+ free(tile_region);
+ tile_region = NULL;
+ }
+ if (tile_row) {
+ free(tile_row);
+ tile_row = NULL;
+ }
+ if (tile_row_shm) {
+ free(tile_row_shm);
+ tile_row_shm = NULL;
+ }
+ if (hint_list) {
+ free(hint_list);
+ hint_list = NULL;
+ }
+}
+
+/*
+ * silly function to factor dpy_y until fullscreen shm is not bigger than max.
+ * should always work unless dpy_y is a large prime or something... under
+ * failure fs_factor remains 0 and no fullscreen updates will be tried.
+ */
+static int fs_factor = 0;
+
+static void set_fs_factor(int max) {
+ int f, fac = 1, n = dpy_y;
+
+ fs_factor = 0;
+ if ((bpp/8) * dpy_x * dpy_y <= max) {
+ fs_factor = 1;
+ return;
+ }
+ for (f=2; f <= 101; f++) {
+ while (n % f == 0) {
+ n = n / f;
+ fac = fac * f;
+ if ( (bpp/8) * dpy_x * (dpy_y/fac) <= max ) {
+ fs_factor = fac;
+ return;
+ }
+ }
+ }
+}
+
+static char *flip_ximage_byte_order(XImage *xim) {
+ char *order;
+ if (xim->byte_order == LSBFirst) {
+ order = "MSBFirst";
+ xim->byte_order = MSBFirst;
+ xim->bitmap_bit_order = MSBFirst;
+ } else {
+ order = "LSBFirst";
+ xim->byte_order = LSBFirst;
+ xim->bitmap_bit_order = LSBFirst;
+ }
+ return order;
+}
+
+/*
+ * set up an XShm image, or if not using shm just create the XImage.
+ */
+static int shm_create(XShmSegmentInfo *shm, XImage **ximg_ptr, int w, int h,
+ char *name) {
+
+ XImage *xim;
+ static int reported_flip = 0;
+
+ shm->shmid = -1;
+ shm->shmaddr = (char *) -1;
+ *ximg_ptr = NULL;
+
+ if (nofb) {
+ return 1;
+ }
+
+ X_LOCK;
+
+ if (! using_shm) {
+ /* we only need the XImage created */
+ xim = XCreateImage_wr(dpy, default_visual, depth, ZPixmap,
+ 0, NULL, w, h, raw_fb ? 32 : BitmapPad(dpy), 0);
+
+ X_UNLOCK;
+
+ if (xim == NULL) {
+ rfbErr("XCreateImage(%s) failed.\n", name);
+ if (quiet) {
+ fprintf(stderr, "XCreateImage(%s) failed.\n",
+ name);
+ }
+ return 0;
+ }
+ xim->data = (char *) malloc(xim->bytes_per_line * xim->height);
+ if (xim->data == NULL) {
+ rfbErr("XCreateImage(%s) data malloc failed.\n", name);
+ if (quiet) {
+ fprintf(stderr, "XCreateImage(%s) data malloc"
+ " failed.\n", name);
+ }
+ return 0;
+ }
+ if (flip_byte_order) {
+ char *order = flip_ximage_byte_order(xim);
+ if (! reported_flip && ! quiet) {
+ rfbLog("Changing XImage byte order"
+ " to %s\n", order);
+ reported_flip = 1;
+ }
+ }
+
+ *ximg_ptr = xim;
+ return 1;
+ }
+
+ xim = XShmCreateImage_wr(dpy, default_visual, depth, ZPixmap, NULL,
+ shm, w, h);
+
+ if (xim == NULL) {
+ rfbErr("XShmCreateImage(%s) failed.\n", name);
+ if (quiet) {
+ fprintf(stderr, "XShmCreateImage(%s) failed.\n", name);
+ }
+ X_UNLOCK;
+ return 0;
+ }
+
+ *ximg_ptr = xim;
+
+#if LIBVNCSERVER_HAVE_XSHM
+ shm->shmid = shmget(IPC_PRIVATE,
+ xim->bytes_per_line * xim->height, IPC_CREAT | 0777);
+
+ if (shm->shmid == -1) {
+ rfbErr("shmget(%s) failed.\n", name);
+ rfbLogPerror("shmget");
+
+ XDestroyImage(xim);
+ *ximg_ptr = NULL;
+
+ X_UNLOCK;
+ return 0;
+ }
+
+ shm->shmaddr = xim->data = (char *) shmat(shm->shmid, 0, 0);
+
+ if (shm->shmaddr == (char *)-1) {
+ rfbErr("shmat(%s) failed.\n", name);
+ rfbLogPerror("shmat");
+
+ XDestroyImage(xim);
+ *ximg_ptr = NULL;
+
+ shmctl(shm->shmid, IPC_RMID, 0);
+ shm->shmid = -1;
+
+ X_UNLOCK;
+ return 0;
+ }
+
+ shm->readOnly = False;
+
+ if (! XShmAttach_wr(dpy, shm)) {
+ rfbErr("XShmAttach(%s) failed.\n", name);
+ XDestroyImage(xim);
+ *ximg_ptr = NULL;
+
+ shmdt(shm->shmaddr);
+ shm->shmaddr = (char *) -1;
+
+ shmctl(shm->shmid, IPC_RMID, 0);
+ shm->shmid = -1;
+
+ X_UNLOCK;
+ return 0;
+ }
+#endif
+
+ X_UNLOCK;
+ return 1;
+}
+
+void shm_delete(XShmSegmentInfo *shm) {
+#if LIBVNCSERVER_HAVE_XSHM
+ if (shm != NULL && shm->shmaddr != (char *) -1) {
+ shmdt(shm->shmaddr);
+ }
+ if (shm != NULL && shm->shmid != -1) {
+ shmctl(shm->shmid, IPC_RMID, 0);
+ }
+#endif
+}
+
+void shm_clean(XShmSegmentInfo *shm, XImage *xim) {
+
+ X_LOCK;
+#if LIBVNCSERVER_HAVE_XSHM
+ if (shm != NULL && shm->shmid != -1 && dpy) { /* raw_fb hack */
+ XShmDetach_wr(dpy, shm);
+ }
+#endif
+ if (xim != NULL) {
+ XDestroyImage(xim);
+ xim = NULL;
+ }
+ X_UNLOCK;
+
+ shm_delete(shm);
+}
+
+void initialize_polling_images(void) {
+ int i, MB = 1024 * 1024;
+
+ /* set all shm areas to "none" before trying to create any */
+ scanline_shm.shmid = -1;
+ scanline_shm.shmaddr = (char *) -1;
+ scanline = NULL;
+ fullscreen_shm.shmid = -1;
+ fullscreen_shm.shmaddr = (char *) -1;
+ fullscreen = NULL;
+ snaprect_shm.shmid = -1;
+ snaprect_shm.shmaddr = (char *) -1;
+ snaprect = NULL;
+ for (i=1; i<=ntiles_x; i++) {
+ tile_row_shm[i].shmid = -1;
+ tile_row_shm[i].shmaddr = (char *) -1;
+ tile_row[i] = NULL;
+ }
+
+ /* the scanline (e.g. 1280x1) shared memory area image: */
+
+ if (! shm_create(&scanline_shm, &scanline, dpy_x, 1, "scanline")) {
+ clean_up_exit(1);
+ }
+
+ /*
+ * the fullscreen (e.g. 1280x1024/fs_factor) shared memory area image:
+ * (we cut down the size of the shm area to try avoid and shm segment
+ * limits, e.g. the default 1MB on Solaris)
+ */
+ if (UT.sysname && strstr(UT.sysname, "Linux")) {
+ set_fs_factor(10 * MB);
+ } else {
+ set_fs_factor(1 * MB);
+ }
+ if (fs_frac >= 1.0) {
+ fs_frac = 1.1;
+ fs_factor = 0;
+ }
+ if (! fs_factor) {
+ rfbLog("warning: fullscreen updates are disabled.\n");
+ } else {
+ if (! shm_create(&fullscreen_shm, &fullscreen, dpy_x,
+ dpy_y/fs_factor, "fullscreen")) {
+ clean_up_exit(1);
+ }
+ }
+ if (use_snapfb) {
+ if (! fs_factor) {
+ rfbLog("warning: disabling -snapfb mode.\n");
+ use_snapfb = 0;
+ } else if (! shm_create(&snaprect_shm, &snaprect, dpy_x,
+ dpy_y/fs_factor, "snaprect")) {
+ clean_up_exit(1);
+ }
+ }
+
+ /*
+ * for copy_tiles we need a lot of shared memory areas, one for
+ * each possible run length of changed tiles. 32 for 1024x768
+ * and 40 for 1280x1024, etc.
+ */
+
+ tile_shm_count = 0;
+ for (i=1; i<=ntiles_x; i++) {
+ if (! shm_create(&tile_row_shm[i], &tile_row[i], tile_x * i,
+ tile_y, "tile_row")) {
+ if (i == 1) {
+ clean_up_exit(1);
+ }
+ rfbLog("shm: Error creating shared memory tile-row for"
+ " len=%d,\n", i);
+ rfbLog("shm: reverting to -onetile mode. If this"
+ " problem persists\n");
+ rfbLog("shm: try using the -onetile or -noshm options"
+ " to limit\n");
+ rfbLog("shm: shared memory usage, or run ipcrm(1)"
+ " to manually\n");
+ rfbLog("shm: delete unattached shm segments.\n");
+ single_copytile_count = i;
+ single_copytile = 1;
+ }
+ tile_shm_count++;
+ if (single_copytile && i >= 1) {
+ /* only need 1x1 tiles */
+ break;
+ }
+ }
+ if (!quiet) {
+ if (using_shm) {
+ rfbLog("created %d tile_row shm polling images.\n",
+ tile_shm_count);
+ } else {
+ rfbLog("created %d tile_row polling images.\n",
+ tile_shm_count);
+ }
+ }
+}
+
+/*
+ * A hint is a rectangular region built from 1 or more adjacent tiles
+ * glued together. Ultimately, this information in a single hint is sent
+ * to libvncserver rather than sending each tile separately.
+ */
+static void create_tile_hint(int x, int y, int tw, int th, hint_t *hint) {
+ int w = dpy_x - x;
+ int h = dpy_y - y;
+
+ if (w > tw) {
+ w = tw;
+ }
+ if (h > th) {
+ h = th;
+ }
+
+ hint->x = x;
+ hint->y = y;
+ hint->w = w;
+ hint->h = h;
+}
+
+static void extend_tile_hint(int x, int y, int tw, int th, hint_t *hint) {
+ int w = dpy_x - x;
+ int h = dpy_y - y;
+
+ if (w > tw) {
+ w = tw;
+ }
+ if (h > th) {
+ h = th;
+ }
+
+ if (hint->x > x) { /* extend to the left */
+ hint->w += hint->x - x;
+ hint->x = x;
+ }
+ if (hint->y > y) { /* extend upward */
+ hint->h += hint->y - y;
+ hint->y = y;
+ }
+
+ if (hint->x + hint->w < x + w) { /* extend to the right */
+ hint->w = x + w - hint->x;
+ }
+ if (hint->y + hint->h < y + h) { /* extend downward */
+ hint->h = y + h - hint->y;
+ }
+}
+
+static void save_hint(hint_t hint, int loc) {
+ /* simply copy it to the global array for later use. */
+ hint_list[loc].x = hint.x;
+ hint_list[loc].y = hint.y;
+ hint_list[loc].w = hint.w;
+ hint_list[loc].h = hint.h;
+}
+
+/*
+ * Glue together horizontal "runs" of adjacent changed tiles into one big
+ * rectangle change "hint" to be passed to the vnc machinery.
+ */
+static void hint_updates(void) {
+ hint_t hint;
+ int x, y, i, n, ty, th, tx, tw;
+ int hint_count = 0, in_run = 0;
+
+ for (y=0; y < ntiles_y; y++) {
+ for (x=0; x < ntiles_x; x++) {
+ n = x + y * ntiles_x;
+
+ if (tile_has_diff[n]) {
+ ty = tile_region[n].first_line;
+ th = tile_region[n].last_line - ty + 1;
+
+ tx = tile_region[n].first_x;
+ tw = tile_region[n].last_x - tx + 1;
+ if (tx < 0) {
+ tx = 0;
+ tw = tile_x;
+ }
+
+ if (! in_run) {
+ create_tile_hint( x * tile_x + tx,
+ y * tile_y + ty, tw, th, &hint);
+ in_run = 1;
+ } else {
+ extend_tile_hint( x * tile_x + tx,
+ y * tile_y + ty, tw, th, &hint);
+ }
+ } else {
+ if (in_run) {
+ /* end of a row run of altered tiles: */
+ save_hint(hint, hint_count++);
+ in_run = 0;
+ }
+ }
+ }
+ if (in_run) { /* save the last row run */
+ save_hint(hint, hint_count++);
+ in_run = 0;
+ }
+ }
+
+ for (i=0; i < hint_count; i++) {
+ /* pass update info to vnc: */
+ mark_hint(hint_list[i]);
+ }
+}
+
+/*
+ * kludge, simple ceil+floor for non-negative doubles:
+ */
+#define CEIL(x) ( (double) ((int) (x)) == (x) ? \
+ (double) ((int) (x)) : (double) ((int) (x) + 1) )
+#define FLOOR(x) ( (double) ((int) (x)) )
+
+/*
+ * Scaling:
+ *
+ * For shrinking, a destination (scaled) pixel will correspond to more
+ * than one source (i.e. main fb) pixel. Think of an x-y plane made with
+ * graph paper. Each unit square in the graph paper (i.e. collection of
+ * points (x,y) such that N < x < N+1 and M < y < M+1, N and M integers)
+ * corresponds to one pixel in the unscaled fb. There is a solid
+ * color filling the inside of such a square. A scaled pixel has width
+ * 1/scale_fac, e.g. for "-scale 3/4" the width of the scaled pixel
+ * is 1.333. The area of this scaled pixel is 1.333 * 1.333 (so it
+ * obviously overlaps more than one source pixel, each which have area 1).
+ *
+ * We take the weight an unscaled pixel (source) contributes to a
+ * scaled pixel (destination) as simply proportional to the overlap area
+ * between the two pixels. One can then think of the value of the scaled
+ * pixel as an integral over the portion of the graph paper it covers.
+ * The thing being integrated is the color value of the unscaled source.
+ * That color value is constant over a graph paper square (source pixel),
+ * and changes discontinuously from one unit square to the next.
+ *
+
+Here is an example for -scale 3/4, the solid lines are the source pixels
+(graph paper unit squares), while the dotted lines denote the scaled
+pixels (destination pixels):
+
+ 0 1 4/3 2 8/3 3 4=12/3
+ |---------|--.------|------.--|---------|.
+ | | . | . | |.
+ | A | . B | . | |.
+ | | . | . | |.
+ | | . | . | |.
+ 1 |---------|--.------|------.--|---------|.
+ 4/3|.........|.........|.........|.........|.
+ | | . | . | |.
+ | C | . D | . | |.
+ | | . | . | |.
+ 2 |---------|--.------|------.--|---------|.
+ | | . | . | |.
+ | | . | . | |.
+ 8/3|.........|.........|.........|.........|.
+ | | . | . | |.
+ 3 |---------|--.------|------.--|---------|.
+
+So we see the first scaled pixel (0 < x < 4/3 and 0 < y < 4/3) mostly
+overlaps with unscaled source pixel "A". The integration (averaging)
+weights for this scaled pixel are:
+
+ A 1
+ B 1/3
+ C 1/3
+ D 1/9
+
+ *
+ * The Red, Green, and Blue color values must be averaged over separately
+ * otherwise you can get a complete mess (except in solid regions),
+ * because high order bits are averaged differently from the low order bits.
+ *
+ * So the algorithm is roughly:
+ *
+ * - Given as input a rectangle in the unscaled source fb with changes,
+ * find the rectangle of pixels this affects in the scaled destination fb.
+ *
+ * - For each of the affected scaled (dest) pixels, determine all of the
+ * unscaled (source) pixels it overlaps with.
+ *
+ * - Average those unscaled source values together, weighted by the area
+ * overlap with the destination pixel. Average R, G, B separately.
+ *
+ * - Take this average value and convert to a valid pixel value if
+ * necessary (e.g. rounding, shifting), and then insert it into the
+ * destination framebuffer as the pixel value.
+ *
+ * - On to the next destination pixel...
+ *
+ * ========================================================================
+ *
+ * For expanding, e.g. -scale 1.1 (which we don't think people will do
+ * very often... or at least so we hope, the framebuffer can become huge)
+ * the situation is reversed and the destination pixel is smaller than a
+ * "graph paper" unit square (source pixel). Some destination pixels
+ * will be completely within a single unscaled source pixel.
+ *
+ * What we do here is a simple 4 point interpolation scheme:
+ *
+ * Let P00 be the source pixel closest to the destination pixel but with
+ * x and y values less than or equal to those of the destination pixel.
+ * (for simplicity, think of the upper left corner of a pixel defining the
+ * x,y location of the pixel, the center would work just as well). So it
+ * is the source pixel immediately to the upper left of the destination
+ * pixel. Let P10 be the source pixel one to the right of P00. Let P01
+ * be one down from P00. And let P11 be one down and one to the right
+ * of P00. They form a 2x2 square we will interpolate inside of.
+ *
+ * Let V00, V10, V01, and V11 be the color values of those 4 source
+ * pixels. Let dx be the displacement along x the destination pixel is
+ * from P00. Note: 0 <= dx < 1 by definition of P00. Similarly let
+ * dy be the displacement along y. The weighted average for the
+ * interpolation is:
+ *
+ * V_ave = V00 * (1 - dx) * (1 - dy)
+ * + V10 * dx * (1 - dy)
+ * + V01 * (1 - dx) * dy
+ * + V11 * dx * dy
+ *
+ * Note that the weights (1-dx)*(1-dy) + dx*(1-dy) + (1-dx)*dy + dx*dy
+ * automatically add up to 1. It is also nice that all the weights are
+ * positive (unsigned char stays unsigned char). The above formula can
+ * be motivated by doing two 1D interpolations along x:
+ *
+ * VA = V00 * (1 - dx) + V10 * dx
+ * VB = V01 * (1 - dx) + V11 * dx
+ *
+ * and then interpolating VA and VB along y:
+ *
+ * V_ave = VA * (1 - dy) + VB * dy
+ *
+ * VA
+ * v |<-dx->|
+ * -- V00 ------ V10
+ * dy | |
+ * -- | o...|... "o" denotes the position of the desired
+ * ^ | . | . destination pixel relative to the P00
+ * | . | . source pixel.
+ * V10 ----.- V11 .
+ * ........
+ * |
+ * VB
+ *
+ *
+ * Of course R, G, B averages are done separately as in the shrinking
+ * case. This gives reasonable results, and the implementation for
+ * shrinking can simply be used with different choices for weights for
+ * the loop over the 4 pixels.
+ */
+
+void scale_rect(double factor, int blend, int interpolate, int Bpp,
+ char *src_fb, int src_bytes_per_line, char *dst_fb, int dst_bytes_per_line,
+ int Nx, int Ny, int nx, int ny, int X1, int Y1, int X2, int Y2, int mark) {
+/*
+ * Notation:
+ * "i" an x pixel index in the destination (scaled) framebuffer
+ * "j" a y pixel index in the destination (scaled) framebuffer
+ * "I" an x pixel index in the source (un-scaled, i.e. main) framebuffer
+ * "J" a y pixel index in the source (un-scaled, i.e. main) framebuffer
+ *
+ * Similarly for nx, ny, Nx, Ny, etc. Lowercase: dest, Uppercase: source.
+ */
+ int i, j, i1, i2, j1, j2; /* indices for scaled fb (dest) */
+ int I, J, I1, I2, J1, J2; /* indices for main fb (source) */
+
+ double w, wx, wy, wtot; /* pixel weights */
+
+ double x1, y1, x2, y2; /* x-y coords for destination pixels edges */
+ double dx, dy; /* size of destination pixel */
+ double ddx, ddy; /* for interpolation expansion */
+
+ char *src, *dest; /* pointers to the two framebuffers */
+
+
+ unsigned short us;
+ unsigned char uc;
+ unsigned int ui;
+
+ int use_noblend_shortcut = 1;
+ int shrink; /* whether shrinking or expanding */
+ static int constant_weights = -1, mag_int = -1;
+ static int last_Nx = -1, last_Ny = -1, cnt = 0;
+ static double last_factor = -1.0;
+ int b, k;
+ double pixave[4]; /* for averaging pixel values */
+
+ if (factor <= 1.0) {
+ shrink = 1;
+ } else {
+ shrink = 0;
+ }
+
+ /*
+ * N.B. width and height (real numbers) of a scaled pixel.
+ * both are > 1 (e.g. 1.333 for -scale 3/4)
+ * they should also be equal but we don't assume it.
+ *
+ * This new way is probably the best we can do, take the inverse
+ * of the scaling factor to double precision.
+ */
+ dx = 1.0/factor;
+ dy = 1.0/factor;
+
+ /*
+ * There is some speedup if the pixel weights are constant, so
+ * let's special case these.
+ *
+ * If scale = 1/n and n divides Nx and Ny, the pixel weights
+ * are constant (e.g. 1/2 => equal on 2x2 square).
+ */
+ if (factor != last_factor || Nx != last_Nx || Ny != last_Ny) {
+ constant_weights = -1;
+ mag_int = -1;
+ last_Nx = Nx;
+ last_Ny = Ny;
+ last_factor = factor;
+ }
+
+ if (constant_weights < 0) {
+ int n = 0;
+
+ constant_weights = 0;
+ mag_int = 0;
+
+ for (i = 2; i<=128; i++) {
+ double test = ((double) 1)/ i;
+ double diff, eps = 1.0e-7;
+ diff = factor - test;
+ if (-eps < diff && diff < eps) {
+ n = i;
+ break;
+ }
+ }
+ if (! blend || ! shrink || interpolate) {
+ ;
+ } else if (n != 0) {
+ if (Nx % n == 0 && Ny % n == 0) {
+ static int didmsg = 0;
+ if (mark && ! didmsg) {
+ didmsg = 1;
+ rfbLog("scale_and_mark_rect: using "
+ "constant pixel weight speedup "
+ "for 1/%d\n", n);
+ }
+ constant_weights = 1;
+ }
+ }
+
+ n = 0;
+ for (i = 2; i<=32; i++) {
+ double test = (double) i;
+ double diff, eps = 1.0e-7;
+ diff = factor - test;
+ if (-eps < diff && diff < eps) {
+ n = i;
+ break;
+ }
+ }
+ if (! blend && factor > 1.0 && n) {
+ mag_int = n;
+ }
+ }
+
+ if (mark && factor > 1.0 && blend) {
+ /*
+ * kludge: correct for interpolating blurring leaking
+ * up or left 1 destination pixel.
+ */
+ if (X1 > 0) X1--;
+ if (Y1 > 0) Y1--;
+ }
+
+ /*
+ * find the extent of the change the input rectangle induces in
+ * the scaled framebuffer.
+ */
+
+ /* Left edges: find largest i such that i * dx <= X1 */
+ i1 = FLOOR(X1/dx);
+
+ /* Right edges: find smallest i such that (i+1) * dx >= X2+1 */
+ i2 = CEIL( (X2+1)/dx ) - 1;
+
+ /* To be safe, correct any overflows: */
+ i1 = nfix(i1, nx);
+ i2 = nfix(i2, nx) + 1; /* add 1 to make a rectangle upper boundary */
+
+ /* Repeat above for y direction: */
+ j1 = FLOOR(Y1/dy);
+ j2 = CEIL( (Y2+1)/dy ) - 1;
+
+ j1 = nfix(j1, ny);
+ j2 = nfix(j2, ny) + 1;
+
+ /* special case integer magnification with no blending */
+ if (mark && ! blend && mag_int && Bpp != 3) {
+ int jmin, jmax, imin, imax;
+
+ /* outer loop over *source* pixels */
+ for (J=Y1; J < Y2; J++) {
+ jmin = J * mag_int;
+ jmax = jmin + mag_int;
+ for (I=X1; I < X2; I++) {
+ /* extract value */
+ src = src_fb + J*src_bytes_per_line + I*Bpp;
+ if (Bpp == 4) {
+ ui = *((unsigned int *)src);
+ } else if (Bpp == 2) {
+ us = *((unsigned short *)src);
+ } else if (Bpp == 1) {
+ uc = *((unsigned char *)src);
+ }
+ imin = I * mag_int;
+ imax = imin + mag_int;
+ /* inner loop over *dest* pixels */
+ for (j=jmin; j<jmax; j++) {
+ dest = dst_fb + j*dst_bytes_per_line + imin*Bpp;
+ for (i=imin; i<imax; i++) {
+ if (Bpp == 4) {
+ *((unsigned int *)dest) = ui;
+ } else if (Bpp == 2) {
+ *((unsigned short *)dest) = us;
+ } else if (Bpp == 1) {
+ *((unsigned char *)dest) = uc;
+ }
+ dest += Bpp;
+ }
+ }
+ }
+ }
+ goto markit;
+ }
+
+ /* set these all to 1.0 to begin with */
+ wx = 1.0;
+ wy = 1.0;
+ w = 1.0;
+
+ /*
+ * Loop over destination pixels in scaled fb:
+ */
+ for (j=j1; j<j2; j++) {
+ y1 = j * dy; /* top edge */
+ if (y1 > Ny - 1) {
+ /* can go over with dy = 1/scale_fac */
+ y1 = Ny - 1;
+ }
+ y2 = y1 + dy; /* bottom edge */
+
+ /* Find main fb indices covered by this dest pixel: */
+ J1 = (int) FLOOR(y1);
+ J1 = nfix(J1, Ny);
+
+ if (shrink && ! interpolate) {
+ J2 = (int) CEIL(y2) - 1;
+ J2 = nfix(J2, Ny);
+ } else {
+ J2 = J1 + 1; /* simple interpolation */
+ ddy = y1 - J1;
+ }
+
+ /* destination char* pointer: */
+ dest = dst_fb + j*dst_bytes_per_line + i1*Bpp;
+
+ for (i=i1; i<i2; i++) {
+
+ x1 = i * dx; /* left edge */
+ if (x1 > Nx - 1) {
+ /* can go over with dx = 1/scale_fac */
+ x1 = Nx - 1;
+ }
+ x2 = x1 + dx; /* right edge */
+
+ cnt++;
+
+ /* Find main fb indices covered by this dest pixel: */
+ I1 = (int) FLOOR(x1);
+ if (I1 >= Nx) I1 = Nx - 1;
+
+ if (! blend && use_noblend_shortcut) {
+ /*
+ * The noblend case involves no weights,
+ * and 1 pixel, so just copy the value
+ * directly.
+ */
+ src = src_fb + J1*src_bytes_per_line + I1*Bpp;
+ if (Bpp == 4) {
+ *((unsigned int *)dest)
+ = *((unsigned int *)src);
+ } else if (Bpp == 2) {
+ *((unsigned short *)dest)
+ = *((unsigned short *)src);
+ } else if (Bpp == 1) {
+ *(dest) = *(src);
+ } else if (Bpp == 3) {
+ /* rare case */
+ for (k=0; k<=2; k++) {
+ *(dest+k) = *(src+k);
+ }
+ }
+ dest += Bpp;
+ continue;
+ }
+
+ if (shrink && ! interpolate) {
+ I2 = (int) CEIL(x2) - 1;
+ if (I2 >= Nx) I2 = Nx - 1;
+ } else {
+ I2 = I1 + 1; /* simple interpolation */
+ ddx = x1 - I1;
+ }
+
+ /* Zero out accumulators for next pixel average: */
+ for (b=0; b<4; b++) {
+ pixave[b] = 0.0; /* for RGB weighted sums */
+ }
+
+ /*
+ * wtot is for accumulating the total weight.
+ * It should always sum to 1/(scale_fac * scale_fac).
+ */
+ wtot = 0.0;
+
+ /*
+ * Loop over source pixels covered by this dest pixel.
+ *
+ * These "extra" loops over "J" and "I" make
+ * the cache/cacheline performance unclear.
+ * For example, will the data brought in from
+ * src for j, i, and J=0 still be in the cache
+ * after the J > 0 data have been accessed and
+ * we are at j, i+1, J=0? The stride in J is
+ * main_bytes_per_line, and so ~4 KB.
+ *
+ * Typical case when shrinking are 2x2 loop, so
+ * just two lines to worry about.
+ */
+ for (J=J1; J<=J2; J++) {
+ /* see comments for I, x1, x2, etc. below */
+ if (constant_weights) {
+ ;
+ } else if (! blend) {
+ if (J != J1) {
+ continue;
+ }
+ wy = 1.0;
+
+ /* interpolation scheme: */
+ } else if (! shrink || interpolate) {
+ if (J >= Ny) {
+ continue;
+ } else if (J == J1) {
+ wy = 1.0 - ddy;
+ } else if (J != J1) {
+ wy = ddy;
+ }
+
+ /* integration scheme: */
+ } else if (J < y1) {
+ wy = J+1 - y1;
+ } else if (J+1 > y2) {
+ wy = y2 - J;
+ } else {
+ wy = 1.0;
+ }
+
+ src = src_fb + J*src_bytes_per_line + I1*Bpp;
+
+ for (I=I1; I<=I2; I++) {
+
+ /* Work out the weight: */
+
+ if (constant_weights) {
+ ;
+ } else if (! blend) {
+ /*
+ * Ugh, PseudoColor colormap is
+ * bad news, to avoid random
+ * colors just take the first
+ * pixel. Or user may have
+ * specified :nb to fraction.
+ * The :fb will force blending
+ * for this case.
+ */
+ if (I != I1) {
+ continue;
+ }
+ wx = 1.0;
+
+ /* interpolation scheme: */
+ } else if (! shrink || interpolate) {
+ if (I >= Nx) {
+ continue; /* off edge */
+ } else if (I == I1) {
+ wx = 1.0 - ddx;
+ } else if (I != I1) {
+ wx = ddx;
+ }
+
+ /* integration scheme: */
+ } else if (I < x1) {
+ /*
+ * source left edge (I) to the
+ * left of dest left edge (x1):
+ * fractional weight
+ */
+ wx = I+1 - x1;
+ } else if (I+1 > x2) {
+ /*
+ * source right edge (I+1) to the
+ * right of dest right edge (x2):
+ * fractional weight
+ */
+ wx = x2 - I;
+ } else {
+ /*
+ * source edges (I and I+1) completely
+ * inside dest edges (x1 and x2):
+ * full weight
+ */
+ wx = 1.0;
+ }
+
+ w = wx * wy;
+ wtot += w;
+
+ /*
+ * We average the unsigned char value
+ * instead of char value: otherwise
+ * the minimum (char 0) is right next
+ * to the maximum (char -1)! This way
+ * they are spread between 0 and 255.
+ */
+ if (Bpp == 4) {
+ /* unroll the loops, can give 20% */
+ pixave[0] += w *
+ ((unsigned char) *(src ));
+ pixave[1] += w *
+ ((unsigned char) *(src+1));
+ pixave[2] += w *
+ ((unsigned char) *(src+2));
+ pixave[3] += w *
+ ((unsigned char) *(src+3));
+ } else if (Bpp == 2) {
+ /*
+ * 16bpp: trickier with green
+ * split over two bytes, so we
+ * use the masks:
+ */
+ us = *((unsigned short *) src);
+ pixave[0] += w*(us & main_red_mask);
+ pixave[1] += w*(us & main_green_mask);
+ pixave[2] += w*(us & main_blue_mask);
+ } else if (Bpp == 1) {
+ pixave[0] += w *
+ ((unsigned char) *(src));
+ } else {
+ for (b=0; b<Bpp; b++) {
+ pixave[b] += w *
+ ((unsigned char) *(src+b));
+ }
+ }
+ src += Bpp;
+ }
+ }
+
+ if (wtot <= 0.0) {
+ wtot = 1.0;
+ }
+ wtot = 1.0/wtot; /* normalization factor */
+
+ /* place weighted average pixel in the scaled fb: */
+ if (Bpp == 4) {
+ *(dest ) = (char) (wtot * pixave[0]);
+ *(dest+1) = (char) (wtot * pixave[1]);
+ *(dest+2) = (char) (wtot * pixave[2]);
+ *(dest+3) = (char) (wtot * pixave[3]);
+ } else if (Bpp == 2) {
+ /* 16bpp / 565 case: */
+ pixave[0] *= wtot;
+ pixave[1] *= wtot;
+ pixave[2] *= wtot;
+ us = (main_red_mask & (int) pixave[0])
+ | (main_green_mask & (int) pixave[1])
+ | (main_blue_mask & (int) pixave[2]);
+ *( (unsigned short *) dest ) = us;
+ } else if (Bpp == 1) {
+ *(dest) = (char) (wtot * pixave[0]);
+ } else {
+ for (b=0; b<Bpp; b++) {
+ *(dest+b) = (char) (wtot * pixave[b]);
+ }
+ }
+ dest += Bpp;
+ }
+ }
+ markit:
+ if (mark) {
+ mark_rect_as_modified(i1, j1, i2, j2, 1);
+ }
+}
+
+void scale_and_mark_rect(int X1, int Y1, int X2, int Y2) {
+
+ if (!screen || !rfb_fb || !main_fb) {
+ return;
+ }
+ if (! screen->serverFormat.trueColour) {
+ /*
+ * PseudoColor colormap... blending leads to random colors.
+ * User can override with ":fb"
+ */
+ if (scaling_blend == 1) {
+ /* :fb option sets it to 2 */
+ if (default_visual->class == StaticGray) {
+ /*
+ * StaticGray can be blended OK, otherwise
+ * user can disable with :nb
+ */
+ ;
+ } else {
+ scaling_blend = 0;
+ }
+ }
+ }
+
+ scale_rect(scale_fac, scaling_blend, scaling_interpolate, bpp/8,
+ main_fb, main_bytes_per_line, rfb_fb, rfb_bytes_per_line,
+ dpy_x, dpy_y, scaled_x, scaled_y, X1, Y1, X2, Y2, 1);
+}
+
+void mark_rect_as_modified(int x1, int y1, int x2, int y2, int force) {
+
+ if (damage_time != 0) {
+ /*
+ * This is not XDAMAGE, rather a hack for testing
+ * where we allow the framebuffer to be corrupted for
+ * damage_delay seconds.
+ */
+ int debug = 0;
+ if (time(0) > damage_time + damage_delay) {
+ if (! quiet) {
+ rfbLog("damaging turned off.\n");
+ }
+ damage_time = 0;
+ damage_delay = 0;
+ } else {
+ if (debug) {
+ rfbLog("damaging viewer fb by not marking "
+ "rect: %d,%d,%d,%d\n", x1, y1, x2, y2);
+ }
+ return;
+ }
+ }
+
+ if (rfb_fb == main_fb || force) {
+ rfbMarkRectAsModified(screen, x1, y1, x2, y2);
+ } else if (scaling) {
+ scale_and_mark_rect(x1, y1, x2, y2);
+ }
+}
+
+/*
+ * Notifies libvncserver of a changed hint rectangle.
+ */
+static void mark_hint(hint_t hint) {
+ int x = hint.x;
+ int y = hint.y;
+ int w = hint.w;
+ int h = hint.h;
+
+ mark_rect_as_modified(x, y, x + w, y + h, 0);
+}
+
+/*
+ * copy_tiles() gives a slight improvement over copy_tile() since
+ * adjacent runs of tiles are done all at once there is some savings
+ * due to contiguous memory access. Not a great speedup, but in some
+ * cases it can be up to 2X. Even more on a SunRay or ShadowFB where
+ * no graphics hardware is involved in the read. Generally, graphics
+ * devices are optimized for write, not read, so we are limited by the
+ * read bandwidth, sometimes only 5 MB/sec on otherwise fast hardware.
+ */
+static int *first_line = NULL, *last_line;
+static unsigned short *left_diff, *right_diff;
+
+static int copy_tiles(int tx, int ty, int nt) {
+ int x, y, line;
+ int size_x, size_y, width1, width2;
+ int off, len, n, dw, dx, t;
+ int w1, w2, dx1, dx2; /* tmps for normal and short tiles */
+ int pixelsize = bpp/8;
+ int first_min, last_max;
+ int first_x = -1, last_x = -1;
+
+ char *src, *dst, *s_src, *s_dst, *m_src, *m_dst;
+ char *h_src, *h_dst;
+ if (! first_line) {
+ /* allocate arrays first time in. */
+ int n = ntiles_x + 1;
+ first_line = (int *) malloc((size_t) (n * sizeof(int)));
+ last_line = (int *) malloc((size_t) (n * sizeof(int)));
+ left_diff = (unsigned short *)
+ malloc((size_t) (n * sizeof(unsigned short)));
+ right_diff = (unsigned short *)
+ malloc((size_t) (n * sizeof(unsigned short)));
+ }
+
+ x = tx * tile_x;
+ y = ty * tile_y;
+
+ size_x = dpy_x - x;
+ if ( size_x > tile_x * nt ) {
+ size_x = tile_x * nt;
+ width1 = tile_x;
+ width2 = tile_x;
+ } else {
+ /* short tile */
+ width1 = tile_x; /* internal tile */
+ width2 = size_x - (nt - 1) * tile_x; /* right hand tile */
+ }
+
+ size_y = dpy_y - y;
+ if ( size_y > tile_y ) {
+ size_y = tile_y;
+ }
+
+ n = tx + ty * ntiles_x; /* number of the first tile */
+
+ if (blackouts && tile_blackout[n].cover == 2) {
+ /*
+ * If there are blackouts and this tile is completely covered
+ * no need to poll screen or do anything else..
+ * n.b. we are in single copy_tile mode: nt=1
+ */
+ tile_has_diff[n] = 0;
+ return(0);
+ }
+
+ X_LOCK;
+ XRANDR_SET_TRAP_RET(-1, "copy_tile-set");
+ /* read in the whole tile run at once: */
+ copy_image(tile_row[nt], x, y, size_x, size_y);
+ XRANDR_CHK_TRAP_RET(-1, "copy_tile-chk");
+
+ X_UNLOCK;
+
+ if (blackouts && tile_blackout[n].cover == 1) {
+ /*
+ * If there are blackouts and this tile is partially covered
+ * we should re-black-out the portion.
+ * n.b. we are in single copy_tile mode: nt=1
+ */
+ int x1, x2, y1, y2, b;
+ int w, s, fill = 0;
+
+ for (b=0; b < tile_blackout[n].count; b++) {
+ char *b_dst = tile_row[nt]->data;
+
+ x1 = tile_blackout[n].bo[b].x1 - x;
+ y1 = tile_blackout[n].bo[b].y1 - y;
+ x2 = tile_blackout[n].bo[b].x2 - x;
+ y2 = tile_blackout[n].bo[b].y2 - y;
+
+ w = (x2 - x1) * pixelsize;
+ s = x1 * pixelsize;
+
+ for (line = 0; line < size_y; line++) {
+ if (y1 <= line && line < y2) {
+ memset(b_dst + s, fill, (size_t) w);
+ }
+ b_dst += tile_row[nt]->bytes_per_line;
+ }
+ }
+ }
+
+ src = tile_row[nt]->data;
+ dst = main_fb + y * main_bytes_per_line + x * pixelsize;
+
+ s_src = src;
+ s_dst = dst;
+
+ for (t=1; t <= nt; t++) {
+ first_line[t] = -1;
+ }
+
+ /* find the first line with difference: */
+ w1 = width1 * pixelsize;
+ w2 = width2 * pixelsize;
+
+ /* foreach line: */
+ for (line = 0; line < size_y; line++) {
+ /* foreach horizontal tile: */
+ for (t=1; t <= nt; t++) {
+ if (first_line[t] != -1) {
+ continue;
+ }
+
+ off = (t-1) * w1;
+ if (t == nt) {
+ len = w2; /* possible short tile */
+ } else {
+ len = w1;
+ }
+
+ if (memcmp(s_dst + off, s_src + off, len)) {
+ first_line[t] = line;
+ }
+ }
+ s_src += tile_row[nt]->bytes_per_line;
+ s_dst += main_bytes_per_line;
+ }
+
+ /* see if there were any differences for any tile: */
+ first_min = -1;
+ for (t=1; t <= nt; t++) {
+ tile_tried[n+(t-1)] = 1;
+ if (first_line[t] != -1) {
+ if (first_min == -1 || first_line[t] < first_min) {
+ first_min = first_line[t];
+ }
+ }
+ }
+ if (first_min == -1) {
+ /* no tile has a difference, note this and get out: */
+ for (t=1; t <= nt; t++) {
+ tile_has_diff[n+(t-1)] = 0;
+ }
+ return(0);
+ } else {
+ /*
+ * at least one tile has a difference. make sure info
+ * is recorded (e.g. sometimes we guess tiles and they
+ * came in with tile_has_diff 0)
+ */
+ for (t=1; t <= nt; t++) {
+ if (first_line[t] == -1) {
+ tile_has_diff[n+(t-1)] = 0;
+ } else {
+ tile_has_diff[n+(t-1)] = 1;
+ }
+ }
+ }
+
+ m_src = src + (tile_row[nt]->bytes_per_line * size_y);
+ m_dst = dst + (main_bytes_per_line * size_y);
+
+ for (t=1; t <= nt; t++) {
+ last_line[t] = first_line[t];
+ }
+
+ /* find the last line with difference: */
+ w1 = width1 * pixelsize;
+ w2 = width2 * pixelsize;
+
+ /* foreach line: */
+ for (line = size_y - 1; line > first_min; line--) {
+
+ m_src -= tile_row[nt]->bytes_per_line;
+ m_dst -= main_bytes_per_line;
+
+ /* foreach tile: */
+ for (t=1; t <= nt; t++) {
+ if (first_line[t] == -1
+ || last_line[t] != first_line[t]) {
+ /* tile has no changes or already done */
+ continue;
+ }
+
+ off = (t-1) * w1;
+ if (t == nt) {
+ len = w2; /* possible short tile */
+ } else {
+ len = w1;
+ }
+ if (memcmp(m_dst + off, m_src + off, len)) {
+ last_line[t] = line;
+ }
+ }
+ }
+
+ /*
+ * determine the farthest down last changed line
+ * will be used below to limit our memcpy() to the framebuffer.
+ */
+ last_max = -1;
+ for (t=1; t <= nt; t++) {
+ if (first_line[t] == -1) {
+ continue;
+ }
+ if (last_max == -1 || last_line[t] > last_max) {
+ last_max = last_line[t];
+ }
+ }
+
+ /* look for differences on left and right hand edges: */
+ for (t=1; t <= nt; t++) {
+ left_diff[t] = 0;
+ right_diff[t] = 0;
+ }
+
+ h_src = src;
+ h_dst = dst;
+
+ w1 = width1 * pixelsize;
+ w2 = width2 * pixelsize;
+
+ dx1 = (width1 - tile_fuzz) * pixelsize;
+ dx2 = (width2 - tile_fuzz) * pixelsize;
+ dw = tile_fuzz * pixelsize;
+
+ /* foreach line: */
+ for (line = 0; line < size_y; line++) {
+ /* foreach tile: */
+ for (t=1; t <= nt; t++) {
+ if (first_line[t] == -1) {
+ /* tile has no changes at all */
+ continue;
+ }
+
+ off = (t-1) * w1;
+ if (t == nt) {
+ dx = dx2; /* possible short tile */
+ if (dx <= 0) {
+ break;
+ }
+ } else {
+ dx = dx1;
+ }
+
+ if (! left_diff[t] && memcmp(h_dst + off,
+ h_src + off, dw)) {
+ left_diff[t] = 1;
+ }
+ if (! right_diff[t] && memcmp(h_dst + off + dx,
+ h_src + off + dx, dw) ) {
+ right_diff[t] = 1;
+ }
+ }
+ h_src += tile_row[nt]->bytes_per_line;
+ h_dst += main_bytes_per_line;
+ }
+
+ /* now finally copy the difference to the rfb framebuffer: */
+ s_src = src + tile_row[nt]->bytes_per_line * first_min;
+ s_dst = dst + main_bytes_per_line * first_min;
+
+ for (line = first_min; line <= last_max; line++) {
+ /* for I/O speed we do not do this tile by tile */
+ memcpy(s_dst, s_src, size_x * pixelsize);
+ if (nt == 1) {
+ /*
+ * optimization for tall skinny lines, e.g. wm
+ * frame. try to find first_x and last_x to limit
+ * the size of the hint. could help for a slow
+ * link. Unfortunately we spent a lot of time
+ * reading in the many tiles.
+ *
+ * BTW, we like to think the above memcpy leaves
+ * the data we use below in the cache... (but
+ * it could be two 128 byte segments at 32bpp)
+ * so this inner loop is not as bad as it seems.
+ */
+ int k, kx;
+ kx = pixelsize;
+ for (k=0; k<size_x; k++) {
+ if (memcmp(s_dst + k*kx, s_src + k*kx, kx)) {
+ if (first_x == -1 || k < first_x) {
+ first_x = k;
+ }
+ if (last_x == -1 || k > last_x) {
+ last_x = k;
+ }
+ }
+ }
+ }
+ s_src += tile_row[nt]->bytes_per_line;
+ s_dst += main_bytes_per_line;
+ }
+
+ /* record all the info in the region array for this tile: */
+ for (t=1; t <= nt; t++) {
+ int s = t - 1;
+
+ if (first_line[t] == -1) {
+ /* tile unchanged */
+ continue;
+ }
+ tile_region[n+s].first_line = first_line[t];
+ tile_region[n+s].last_line = last_line[t];
+
+ tile_region[n+s].first_x = first_x;
+ tile_region[n+s].last_x = last_x;
+
+ tile_region[n+s].top_diff = 0;
+ tile_region[n+s].bot_diff = 0;
+ if ( first_line[t] < tile_fuzz ) {
+ tile_region[n+s].top_diff = 1;
+ }
+ if ( last_line[t] > (size_y - 1) - tile_fuzz ) {
+ tile_region[n+s].bot_diff = 1;
+ }
+
+ tile_region[n+s].left_diff = left_diff[t];
+ tile_region[n+s].right_diff = right_diff[t];
+
+ tile_copied[n+s] = 1;
+ }
+
+ return(1);
+}
+
+/*
+ * The copy_tile() call in the loop below copies the changed tile into
+ * the rfb framebuffer. Note that copy_tile() sets the tile_region
+ * struct to have info about the y-range of the changed region and also
+ * whether the tile edges contain diffs (within distance tile_fuzz).
+ *
+ * We use this tile_region info to try to guess if the downward and right
+ * tiles will have diffs. These tiles will be checked later in the loop
+ * (since y+1 > y and x+1 > x).
+ *
+ * See copy_tiles_backward_pass() for analogous checking upward and
+ * left tiles.
+ */
+static int copy_all_tiles(void) {
+ int x, y, n, m;
+ int diffs = 0, ct;
+
+ for (y=0; y < ntiles_y; y++) {
+ for (x=0; x < ntiles_x; x++) {
+ n = x + y * ntiles_x;
+
+ if (tile_has_diff[n]) {
+ ct = copy_tiles(x, y, 1);
+ if (ct < 0) return ct; /* fatal */
+ }
+ if (! tile_has_diff[n]) {
+ /*
+ * n.b. copy_tiles() may have detected
+ * no change and reset tile_has_diff to 0.
+ */
+ continue;
+ }
+ diffs++;
+
+ /* neighboring tile downward: */
+ if ( (y+1) < ntiles_y && tile_region[n].bot_diff) {
+ m = x + (y+1) * ntiles_x;
+ if (! tile_has_diff[m]) {
+ tile_has_diff[m] = 2;
+ }
+ }
+ /* neighboring tile to right: */
+ if ( (x+1) < ntiles_x && tile_region[n].right_diff) {
+ m = (x+1) + y * ntiles_x;
+ if (! tile_has_diff[m]) {
+ tile_has_diff[m] = 2;
+ }
+ }
+ }
+ }
+ return diffs;
+}
+
+/*
+ * Routine analogous to copy_all_tiles() above, but for horizontal runs
+ * of adjacent changed tiles.
+ */
+static int copy_all_tile_runs(void) {
+ int x, y, n, m, i;
+ int diffs = 0, ct;
+ int in_run = 0, run = 0;
+ int ntave = 0, ntcnt = 0;
+
+ for (y=0; y < ntiles_y; y++) {
+ for (x=0; x < ntiles_x + 1; x++) {
+ n = x + y * ntiles_x;
+
+ if (x != ntiles_x && tile_has_diff[n]) {
+ in_run = 1;
+ run++;
+ } else {
+ if (! in_run) {
+ in_run = 0;
+ run = 0;
+ continue;
+ }
+ ct = copy_tiles(x - run, y, run);
+ if (ct < 0) return ct; /* fatal */
+
+ ntcnt++;
+ ntave += run;
+ diffs += run;
+
+ /* neighboring tile downward: */
+ for (i=1; i <= run; i++) {
+ if ((y+1) < ntiles_y
+ && tile_region[n-i].bot_diff) {
+ m = (x-i) + (y+1) * ntiles_x;
+ if (! tile_has_diff[m]) {
+ tile_has_diff[m] = 2;
+ }
+ }
+ }
+
+ /* neighboring tile to right: */
+ if (((x-1)+1) < ntiles_x
+ && tile_region[n-1].right_diff) {
+ m = ((x-1)+1) + y * ntiles_x;
+ if (! tile_has_diff[m]) {
+ tile_has_diff[m] = 2;
+ }
+
+ /* note that this starts a new run */
+ in_run = 1;
+ run = 1;
+ } else {
+ in_run = 0;
+ run = 0;
+ }
+ }
+ }
+ /*
+ * Could some activity go here, to emulate threaded
+ * behavior by servicing some libvncserver tasks?
+ */
+ }
+ return diffs;
+}
+
+/*
+ * Here starts a bunch of heuristics to guess/detect changed tiles.
+ * They are:
+ * copy_tiles_backward_pass, fill_tile_gaps/gap_try, grow_islands/island_try
+ */
+
+/*
+ * Try to predict whether the upward and/or leftward tile has been modified.
+ * copy_all_tiles() has already done downward and rightward tiles.
+ */
+static int copy_tiles_backward_pass(void) {
+ int x, y, n, m;
+ int diffs = 0, ct;
+
+ for (y = ntiles_y - 1; y >= 0; y--) {
+ for (x = ntiles_x - 1; x >= 0; x--) {
+ n = x + y * ntiles_x; /* number of this tile */
+
+ if (! tile_has_diff[n]) {
+ continue;
+ }
+
+ m = x + (y-1) * ntiles_x; /* neighboring tile upward */
+
+ if (y >= 1 && ! tile_has_diff[m] && tile_region[n].top_diff) {
+ if (! tile_tried[m]) {
+ tile_has_diff[m] = 2;
+ ct = copy_tiles(x, y-1, 1);
+ if (ct < 0) return ct; /* fatal */
+ }
+ }
+
+ m = (x-1) + y * ntiles_x; /* neighboring tile to left */
+
+ if (x >= 1 && ! tile_has_diff[m] && tile_region[n].left_diff) {
+ if (! tile_tried[m]) {
+ tile_has_diff[m] = 2;
+ ct = copy_tiles(x-1, y, 1);
+ if (ct < 0) return ct; /* fatal */
+ }
+ }
+ }
+ }
+ for (n=0; n < ntiles; n++) {
+ if (tile_has_diff[n]) {
+ diffs++;
+ }
+ }
+ return diffs;
+}
+
+static int copy_tiles_additional_pass(void) {
+ int x, y, n;
+ int diffs = 0, ct;
+
+ for (y=0; y < ntiles_y; y++) {
+ for (x=0; x < ntiles_x; x++) {
+ n = x + y * ntiles_x; /* number of this tile */
+
+ if (! tile_has_diff[n]) {
+ continue;
+ }
+ if (tile_copied[n]) {
+ continue;
+ }
+
+ ct = copy_tiles(x, y, 1);
+ if (ct < 0) return ct; /* fatal */
+ }
+ }
+ for (n=0; n < ntiles; n++) {
+ if (tile_has_diff[n]) {
+ diffs++;
+ }
+ }
+ return diffs;
+}
+
+static int gap_try(int x, int y, int *run, int *saw, int along_x) {
+ int n, m, i, xt, yt, ct;
+
+ n = x + y * ntiles_x;
+
+ if (! tile_has_diff[n]) {
+ if (*saw) {
+ (*run)++; /* extend the gap run. */
+ }
+ return 0;
+ }
+ if (! *saw || *run == 0 || *run > gaps_fill) {
+ *run = 0; /* unacceptable run. */
+ *saw = 1;
+ return 0;
+ }
+
+ for (i=1; i <= *run; i++) { /* iterate thru the run. */
+ if (along_x) {
+ xt = x - i;
+ yt = y;
+ } else {
+ xt = x;
+ yt = y - i;
+ }
+
+ m = xt + yt * ntiles_x;
+ if (tile_tried[m]) { /* do not repeat tiles */
+ continue;
+ }
+
+ ct = copy_tiles(xt, yt, 1);
+ if (ct < 0) return ct; /* fatal */
+ }
+ *run = 0;
+ *saw = 1;
+ return 1;
+}
+
+/*
+ * Look for small gaps of unchanged tiles that may actually contain changes.
+ * E.g. when paging up and down in a web broswer or terminal there can
+ * be a distracting delayed filling in of such gaps. gaps_fill is the
+ * tweak parameter that sets the width of the gaps that are checked.
+ *
+ * BTW, grow_islands() is actually pretty successful at doing this too...
+ */
+static int fill_tile_gaps(void) {
+ int x, y, run, saw;
+ int n, diffs = 0, ct;
+
+ /* horizontal: */
+ for (y=0; y < ntiles_y; y++) {
+ run = 0;
+ saw = 0;
+ for (x=0; x < ntiles_x; x++) {
+ ct = gap_try(x, y, &run, &saw, 1);
+ if (ct < 0) return ct; /* fatal */
+ }
+ }
+
+ /* vertical: */
+ for (x=0; x < ntiles_x; x++) {
+ run = 0;
+ saw = 0;
+ for (y=0; y < ntiles_y; y++) {
+ ct = gap_try(x, y, &run, &saw, 0);
+ if (ct < 0) return ct; /* fatal */
+ }
+ }
+
+ for (n=0; n < ntiles; n++) {
+ if (tile_has_diff[n]) {
+ diffs++;
+ }
+ }
+ return diffs;
+}
+
+static int island_try(int x, int y, int u, int v, int *run) {
+ int n, m, ct;
+
+ n = x + y * ntiles_x;
+ m = u + v * ntiles_x;
+
+ if (tile_has_diff[n]) {
+ (*run)++;
+ } else {
+ *run = 0;
+ }
+
+ if (tile_has_diff[n] && ! tile_has_diff[m]) {
+ /* found a discontinuity */
+
+ if (tile_tried[m]) {
+ return 0;
+ } else if (*run < grow_fill) {
+ return 0;
+ }
+
+ ct = copy_tiles(u, v, 1);
+ if (ct < 0) return ct; /* fatal */
+ }
+ return 1;
+}
+
+/*
+ * Scan looking for discontinuities in tile_has_diff[]. Try to extend
+ * the boundary of the discontinuity (i.e. make the island larger).
+ * Vertical scans are skipped since they do not seem to yield much...
+ */
+static int grow_islands(void) {
+ int x, y, n, run;
+ int diffs = 0, ct;
+
+ /*
+ * n.b. the way we scan here should keep an extension going,
+ * and so also fill in gaps effectively...
+ */
+
+ /* left to right: */
+ for (y=0; y < ntiles_y; y++) {
+ run = 0;
+ for (x=0; x <= ntiles_x - 2; x++) {
+ ct = island_try(x, y, x+1, y, &run);
+ if (ct < 0) return ct; /* fatal */
+ }
+ }
+ /* right to left: */
+ for (y=0; y < ntiles_y; y++) {
+ run = 0;
+ for (x = ntiles_x - 1; x >= 1; x--) {
+ ct = island_try(x, y, x-1, y, &run);
+ if (ct < 0) return ct; /* fatal */
+ }
+ }
+ for (n=0; n < ntiles; n++) {
+ if (tile_has_diff[n]) {
+ diffs++;
+ }
+ }
+ return diffs;
+}
+
+/*
+ * Fill the framebuffer with zeros for each blackout region
+ */
+static void blackout_regions(void) {
+ int i;
+ for (i=0; i < blackouts; i++) {
+ zero_fb(blackr[i].x1, blackr[i].y1, blackr[i].x2, blackr[i].y2);
+ }
+}
+
+/*
+ * copy the whole X screen to the rfb framebuffer. For a large enough
+ * number of changed tiles, this is faster than tiles scheme at retrieving
+ * the info from the X server. Bandwidth to client and compression time
+ * are other issues... use -fs 1.0 to disable.
+ */
+int copy_screen(void) {
+ int pixelsize = bpp/8;
+ char *fbp;
+ int i, y, block_size;
+
+ if (! fs_factor) {
+ return 0;
+ }
+
+ block_size = (dpy_x * (dpy_y/fs_factor) * pixelsize);
+
+ if (! main_fb) {
+ return 0;
+ }
+ fbp = main_fb;
+ y = 0;
+
+ X_LOCK;
+
+ /* screen may be too big for 1 shm area, so broken into fs_factor */
+ for (i=0; i < fs_factor; i++) {
+ XRANDR_SET_TRAP_RET(-1, "copy_screen-set");
+ copy_image(fullscreen, 0, y, 0, 0);
+ XRANDR_CHK_TRAP_RET(-1, "copy_screen-chk");
+
+ memcpy(fbp, fullscreen->data, (size_t) block_size);
+
+ y += dpy_y / fs_factor;
+ fbp += block_size;
+ }
+
+ X_UNLOCK;
+
+ if (blackouts) {
+ blackout_regions();
+ }
+
+ mark_rect_as_modified(0, 0, dpy_x, dpy_y, 0);
+ return 0;
+}
+
+int copy_snap(void) {
+ int pixelsize = bpp/8;
+ char *fbp;
+ int i, y, block_size;
+ double dt;
+ static int first = 1;
+
+ if (! fs_factor) {
+ return 0;
+ }
+
+ block_size = (dpy_x * (dpy_y/fs_factor) * pixelsize);
+
+ if (! snap_fb || ! snap || ! snaprect) {
+ return 0;
+ }
+ fbp = snap_fb;
+ y = 0;
+
+ dtime0(&dt);
+ X_LOCK;
+
+ /* screen may be too big for 1 shm area, so broken into fs_factor */
+ for (i=0; i < fs_factor; i++) {
+ XRANDR_SET_TRAP_RET(-1, "copy_snap-set");
+ copy_image(snaprect, 0, y, 0, 0);
+ XRANDR_CHK_TRAP_RET(-1, "copy_snap-chk");
+
+ memcpy(fbp, snaprect->data, (size_t) block_size);
+
+ y += dpy_y / fs_factor;
+ fbp += block_size;
+ }
+
+ X_UNLOCK;
+ dt = dtime(&dt);
+ if (first) {
+ rfbLog("copy_snap: time for -snapfb snapshot: %.3f sec\n", dt);
+ first = 0;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Utilities for managing the "naps" to cut down on amount of polling.
+ */
+static void nap_set(int tile_cnt) {
+ int nap_in = nap_ok;
+ time_t now = time(0);
+
+ if (scan_count == 0) {
+ /* roll up check for all NSCAN scans */
+ nap_ok = 0;
+ if (naptile && nap_diff_count < 2 * NSCAN * naptile) {
+ /* "2" is a fudge to permit a bit of bg drawing */
+ nap_ok = 1;
+ }
+ nap_diff_count = 0;
+ }
+ if (nap_ok && ! nap_in && use_xdamage) {
+ if (XD_skip > 0.8 * XD_tot) {
+ /* X DAMAGE is keeping load low, so skip nap */
+ nap_ok = 0;
+ }
+ }
+ if (! nap_ok && client_count) {
+ if(now > last_fb_bytes_sent + no_fbu_blank) {
+ if (debug_tiles > 1) {
+ printf("nap_set: nap_ok=1: now: %d last: %d\n",
+ (int) now, (int) last_fb_bytes_sent);
+ }
+ nap_ok = 1;
+ }
+ }
+
+ if (show_cursor) {
+ /* kludge for the up to 4 tiles the mouse patch could occupy */
+ if ( tile_cnt > 4) {
+ last_event = now;
+ }
+ } else if (tile_cnt != 0) {
+ last_event = now;
+ }
+}
+
+/*
+ * split up a long nap to improve the wakeup time
+ */
+void nap_sleep(int ms, int split) {
+ int i, input = got_user_input;
+
+ for (i=0; i<split; i++) {
+ usleep(ms * 1000 / split);
+ if (! use_threads && i != split - 1) {
+ rfbPE(-1);
+ }
+ if (input != got_user_input) {
+ break;
+ }
+ }
+}
+
+/*
+ * see if we should take a nap of some sort between polls
+ */
+static void nap_check(int tile_cnt) {
+ time_t now;
+
+ nap_diff_count += tile_cnt;
+
+ if (! take_naps) {
+ return;
+ }
+
+ now = time(0);
+
+ if (screen_blank > 0) {
+ int dt_ev, dt_fbu, ms = 2000;
+
+ /* if no activity, pause here for a second or so. */
+ dt_ev = (int) (now - last_event);
+ dt_fbu = (int) (now - last_fb_bytes_sent);
+ if (dt_fbu > screen_blank) {
+ /* sleep longer for no fb requests */
+ nap_sleep(2 * ms, 16);
+ return;
+ }
+ if (dt_ev > screen_blank) {
+ nap_sleep(ms, 8);
+ return;
+ }
+ }
+ if (naptile && nap_ok && tile_cnt < naptile) {
+ int ms = napfac * waitms;
+ ms = ms > napmax ? napmax : ms;
+ if (now - last_input <= 2) {
+ nap_ok = 0;
+ } else {
+ nap_sleep(ms, 1);
+ }
+ }
+}
+
+/*
+ * This is called to avoid a ~20 second timeout in libvncserver.
+ * May no longer be needed.
+ */
+static void ping_clients(int tile_cnt) {
+ static time_t last_send = 0;
+ time_t now = time(0);
+
+ if (rfbMaxClientWait < 20000) {
+ rfbMaxClientWait = 20000;
+ rfbLog("reset rfbMaxClientWait to %d msec.\n",
+ rfbMaxClientWait);
+ }
+ if (tile_cnt) {
+ last_send = now;
+ } else if (now - last_send > 1) {
+ /* Send small heartbeat to client */
+ mark_rect_as_modified(0, 0, 1, 1, 1);
+ last_send = now;
+ }
+}
+
+/*
+ * scan_display() wants to know if this tile can be skipped due to
+ * blackout regions: (no data compare is done, just a quick geometric test)
+ */
+static int blackout_line_skip(int n, int x, int y, int rescan,
+ int *tile_count) {
+
+ if (tile_blackout[n].cover == 2) {
+ tile_has_diff[n] = 0;
+ return 1; /* skip it */
+
+ } else if (tile_blackout[n].cover == 1) {
+ int w, x1, y1, x2, y2, b, hit = 0;
+ if (x + NSCAN > dpy_x) {
+ w = dpy_x - x;
+ } else {
+ w = NSCAN;
+ }
+
+ for (b=0; b < tile_blackout[n].count; b++) {
+
+ /* n.b. these coords are in full display space: */
+ x1 = tile_blackout[n].bo[b].x1;
+ x2 = tile_blackout[n].bo[b].x2;
+ y1 = tile_blackout[n].bo[b].y1;
+ y2 = tile_blackout[n].bo[b].y2;
+
+ if (x2 - x1 < w) {
+ /* need to cover full width */
+ continue;
+ }
+ if (y1 <= y && y < y2) {
+ hit = 1;
+ break;
+ }
+ }
+ if (hit) {
+ if (! rescan) {
+ tile_has_diff[n] = 0;
+ } else {
+ *tile_count += tile_has_diff[n];
+ }
+ return 1; /* skip */
+ }
+ }
+ return 0; /* do not skip */
+}
+
+static int blackout_line_cmpskip(int n, int x, int y, char *dst, char *src,
+ int w, int pixelsize) {
+
+ int i, x1, y1, x2, y2, b, hit = 0;
+ int beg = -1, end = -1;
+
+ if (tile_blackout[n].cover == 0) {
+ return 0; /* 0 means do not skip it. */
+ } else if (tile_blackout[n].cover == 2) {
+ return 1; /* 1 means skip it. */
+ }
+
+ /* tile has partial coverage: */
+
+ for (i=0; i < w * pixelsize; i++) {
+ if (*(dst+i) != *(src+i)) {
+ beg = i/pixelsize; /* beginning difference */
+ break;
+ }
+ }
+ for (i = w * pixelsize - 1; i >= 0; i--) {
+ if (*(dst+i) != *(src+i)) {
+ end = i/pixelsize; /* ending difference */
+ break;
+ }
+ }
+ if (beg < 0 || end < 0) {
+ /* problem finding range... */
+ return 0;
+ }
+
+ /* loop over blackout rectangles: */
+ for (b=0; b < tile_blackout[n].count; b++) {
+
+ /* y in full display space: */
+ y1 = tile_blackout[n].bo[b].y1;
+ y2 = tile_blackout[n].bo[b].y2;
+
+ /* x relative to tile origin: */
+ x1 = tile_blackout[n].bo[b].x1 - x;
+ x2 = tile_blackout[n].bo[b].x2 - x;
+
+ if (y1 > y || y >= y2) {
+ continue;
+ }
+ if (x1 <= beg && end <= x2) {
+ hit = 1;
+ break;
+ }
+ }
+ if (hit) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*
+ * For the subwin case follows the window if it is moved.
+ */
+void set_offset(void) {
+ Window w;
+ if (! subwin) {
+ return;
+ }
+ X_LOCK;
+ xtranslate(window, rootwin, 0, 0, &off_x, &off_y, &w, 0);
+ X_UNLOCK;
+}
+
+/*
+ * Loop over 1-pixel tall horizontal scanlines looking for changes.
+ * Record the changes in tile_has_diff[]. Scanlines in the loop are
+ * equally spaced along y by NSCAN pixels, but have a slightly random
+ * starting offset ystart ( < NSCAN ) from scanlines[].
+ */
+static int scan_display(int ystart, int rescan) {
+ char *src, *dst;
+ int pixelsize = bpp/8;
+ int x, y, w, n;
+ int tile_count = 0;
+ int nodiffs = 0, diff_hint;
+
+ y = ystart;
+
+ if (! main_fb) {
+ rfbLog("scan_display: no main_fb!\n");
+ return 0;
+ }
+
+ while (y < dpy_y) {
+
+ if (use_xdamage) {
+ XD_tot++;
+ if (xdamage_hint_skip(y)) {
+ XD_skip++;
+ y += NSCAN;
+ continue;
+ }
+ }
+
+ /* grab the horizontal scanline from the display: */
+ X_LOCK;
+ XRANDR_SET_TRAP_RET(-1, "scan_display-set");
+ copy_image(scanline, 0, y, 0, 0);
+ XRANDR_CHK_TRAP_RET(-1, "scan_display-chk");
+ X_UNLOCK;
+
+ /* for better memory i/o try the whole line at once */
+ src = scanline->data;
+ dst = main_fb + y * main_bytes_per_line;
+
+ if (! memcmp(dst, src, main_bytes_per_line)) {
+ /* no changes anywhere in scan line */
+ nodiffs = 1;
+ if (! rescan) {
+ y += NSCAN;
+ continue;
+ }
+ }
+
+ x = 0;
+ while (x < dpy_x) {
+ n = (x/tile_x) + (y/tile_y) * ntiles_x;
+ diff_hint = 0;
+
+ if (blackouts) {
+ if (blackout_line_skip(n, x, y, rescan,
+ &tile_count)) {
+ x += NSCAN;
+ continue;
+ }
+ }
+
+ if (rescan) {
+ if (nodiffs || tile_has_diff[n]) {
+ tile_count += tile_has_diff[n];
+ x += NSCAN;
+ continue;
+ }
+ } else if (xdamage_tile_count &&
+ tile_has_xdamage_diff[n]) {
+ tile_has_xdamage_diff[n] = 2;
+ diff_hint = 1;
+ }
+
+ /* set ptrs to correspond to the x offset: */
+ src = scanline->data + x * pixelsize;
+ dst = main_fb + y * main_bytes_per_line + x * pixelsize;
+
+ /* compute the width of data to be compared: */
+ if (x + NSCAN > dpy_x) {
+ w = dpy_x - x;
+ } else {
+ w = NSCAN;
+ }
+
+ if (diff_hint || memcmp(dst, src, w * pixelsize)) {
+ /* found a difference, record it: */
+ if (! blackouts) {
+ tile_has_diff[n] = 1;
+ tile_count++;
+ } else {
+ if (blackout_line_cmpskip(n, x, y,
+ dst, src, w, pixelsize)) {
+ tile_has_diff[n] = 0;
+ } else {
+ tile_has_diff[n] = 1;
+ tile_count++;
+ }
+ }
+ }
+ x += NSCAN;
+ }
+ y += NSCAN;
+ }
+ return tile_count;
+}
+
+
+static int scanlines[NSCAN] = {
+ 0, 16, 8, 24, 4, 20, 12, 28,
+ 10, 26, 18, 2, 22, 6, 30, 14,
+ 1, 17, 9, 25, 7, 23, 15, 31,
+ 19, 3, 27, 11, 29, 13, 5, 21
+};
+
+/*
+ * toplevel for the scanning, rescanning, and applying the heuristics.
+ * returns number of changed tiles.
+ */
+int scan_for_updates(int count_only) {
+ int i, tile_count, tile_diffs;
+ int old_copy_tile;
+ double frac1 = 0.1; /* tweak parameter to try a 2nd scan_display() */
+ double frac2 = 0.35; /* or 3rd */
+ double frac3 = 0.02; /* do scan_display() again after copy_tiles() */
+ static double last_poll = 0.0;
+
+ if (slow_fb > 0.0) {
+ double now = dnow();
+ if (now < last_poll + slow_fb) {
+ return 0;
+ }
+ last_poll = now;
+ }
+
+ for (i=0; i < ntiles; i++) {
+ tile_has_diff[i] = 0;
+ tile_has_xdamage_diff[i] = 0;
+ tile_tried[i] = 0;
+ tile_copied[i] = 0;
+ }
+ for (i=0; i < ntiles_y; i++) {
+ /* could be useful, currently not used */
+ tile_row_has_xdamage_diff[i] = 0;
+ }
+ xdamage_tile_count = 0;
+
+ /*
+ * n.b. this program has only been tested so far with
+ * tile_x = tile_y = NSCAN = 32!
+ */
+
+ if (!count_only) {
+ scan_count++;
+ scan_count %= NSCAN;
+
+ /* some periodic maintenance */
+ if (subwin) {
+ set_offset(); /* follow the subwindow */
+ }
+ if (indexed_color && scan_count % 4 == 0) {
+ /* check for changed colormap */
+ set_colormap(0);
+ }
+ if (use_xdamage) {
+ /* first pass collecting DAMAGE events: */
+ collect_xdamage(scan_count, 0);
+ }
+ }
+
+#define SCAN_FATAL(x) \
+ if (x < 0) { \
+ scan_in_progress = 0; \
+ fb_copy_in_progress = 0; \
+ return 0; \
+ }
+
+ /* scan with the initial y to the jitter value from scanlines: */
+ scan_in_progress = 1;
+ tile_count = scan_display(scanlines[scan_count], 0);
+ SCAN_FATAL(tile_count);
+
+ /*
+ * we do the XDAMAGE here too since after scan_display()
+ * there is a better chance we have received the events from
+ * the X server (otherwise the DAMAGE events will be processed
+ * in the *next* call, usually too late and wasteful since
+ * the unchanged tiles are read in again).
+ */
+ if (use_xdamage) {
+ collect_xdamage(scan_count, 1);
+ }
+ if (count_only) {
+ scan_in_progress = 0;
+ fb_copy_in_progress = 0;
+ return tile_count;
+ }
+
+ if (xdamage_tile_count) {
+ /* pick up "known" damaged tiles we missed in scan_display() */
+ for (i=0; i < ntiles; i++) {
+ if (tile_has_diff[i]) {
+ continue;
+ }
+ if (tile_has_xdamage_diff[i]) {
+ tile_has_diff[i] = 1;
+ if (tile_has_xdamage_diff[i] == 1) {
+ tile_has_xdamage_diff[i] = 2;
+ tile_count++;
+ }
+ }
+ }
+ }
+
+ nap_set(tile_count);
+
+ if (fs_factor && frac1 >= fs_frac) {
+ /* make frac1 < fs_frac if fullscreen updates are enabled */
+ frac1 = fs_frac/2.0;
+ }
+
+ if (tile_count > frac1 * ntiles) {
+ /*
+ * many tiles have changed, so try a rescan (since it should
+ * be short compared to the many upcoming copy_tiles() calls)
+ */
+
+ /* this check is done to skip the extra scan_display() call */
+ if (! fs_factor || tile_count <= fs_frac * ntiles) {
+ int cp, tile_count_old = tile_count;
+
+ /* choose a different y shift for the 2nd scan: */
+ cp = (NSCAN - scan_count) % NSCAN;
+
+ tile_count = scan_display(scanlines[cp], 1);
+ SCAN_FATAL(tile_count);
+
+ if (tile_count >= (1 + frac2) * tile_count_old) {
+ /* on a roll... do a 3rd scan */
+ cp = (NSCAN - scan_count + 7) % NSCAN;
+ tile_count = scan_display(scanlines[cp], 1);
+ SCAN_FATAL(tile_count);
+ }
+ }
+ scan_in_progress = 0;
+
+ /*
+ * At some number of changed tiles it is better to just
+ * copy the full screen at once. I.e. time = c1 + m * r1
+ * where m is number of tiles, r1 is the copy_tiles()
+ * time, and c1 is the scan_display() time: for some m
+ * it crosses the full screen update time.
+ *
+ * We try to predict that crossover with the fs_frac
+ * fudge factor... seems to be about 1/2 the total number
+ * of tiles. n.b. this ignores network bandwidth,
+ * compression time etc...
+ *
+ * Use -fs 1.0 to disable on slow links.
+ */
+ if (fs_factor && tile_count > fs_frac * ntiles) {
+ int cs;
+ fb_copy_in_progress = 1;
+ cs = copy_screen();
+ fb_copy_in_progress = 0;
+ SCAN_FATAL(cs);
+ if (use_threads && pointer_mode != 1) {
+ pointer(-1, 0, 0, NULL);
+ }
+ nap_check(tile_count);
+ return tile_count;
+ }
+ }
+ scan_in_progress = 0;
+
+ /* copy all tiles with differences from display to rfb framebuffer: */
+ fb_copy_in_progress = 1;
+
+ if (single_copytile || tile_shm_count < ntiles_x) {
+ /*
+ * Old way, copy I/O one tile at a time.
+ */
+ old_copy_tile = 1;
+ } else {
+ /*
+ * New way, does runs of horizontal tiles at once.
+ * Note that below, for simplicity, the extra tile finding
+ * (e.g. copy_tiles_backward_pass) is done the old way.
+ */
+ old_copy_tile = 0;
+ }
+ if (old_copy_tile) {
+ tile_diffs = copy_all_tiles();
+ } else {
+ tile_diffs = copy_all_tile_runs();
+ }
+ SCAN_FATAL(tile_diffs);
+
+ /*
+ * This backward pass for upward and left tiles complements what
+ * was done in copy_all_tiles() for downward and right tiles.
+ */
+ tile_diffs = copy_tiles_backward_pass();
+ SCAN_FATAL(tile_diffs);
+
+ if (tile_diffs > frac3 * ntiles) {
+ /*
+ * we spent a lot of time in those copy_tiles, run
+ * another scan, maybe more of the screen changed.
+ */
+ int cp = (NSCAN - scan_count + 13) % NSCAN;
+
+ scan_in_progress = 1;
+ tile_count = scan_display(scanlines[cp], 1);
+ SCAN_FATAL(tile_count);
+ scan_in_progress = 0;
+
+ tile_diffs = copy_tiles_additional_pass();
+ SCAN_FATAL(tile_diffs);
+ }
+
+ /* Given enough tile diffs, try the islands: */
+ if (grow_fill && tile_diffs > 4) {
+ tile_diffs = grow_islands();
+ }
+ SCAN_FATAL(tile_diffs);
+
+ /* Given enough tile diffs, try the gaps: */
+ if (gaps_fill && tile_diffs > 4) {
+ tile_diffs = fill_tile_gaps();
+ }
+ SCAN_FATAL(tile_diffs);
+
+ fb_copy_in_progress = 0;
+ if (use_threads && pointer_mode != 1) {
+ /*
+ * tell the pointer handler it can process any queued
+ * pointer events:
+ */
+ pointer(-1, 0, 0, NULL);
+ }
+
+ if (blackouts) {
+ /* ignore any diffs in completely covered tiles */
+ int x, y, n;
+ for (y=0; y < ntiles_y; y++) {
+ for (x=0; x < ntiles_x; x++) {
+ n = x + y * ntiles_x;
+ if (tile_blackout[n].cover == 2) {
+ tile_has_diff[n] = 0;
+ }
+ }
+ }
+ }
+
+ hint_updates(); /* use x0rfbserver hints algorithm */
+
+ /* Work around threaded rfbProcessClientMessage() calls timeouts */
+ if (use_threads) {
+ ping_clients(tile_diffs);
+ }
+
+
+ nap_check(tile_diffs);
+ return tile_diffs;
+}
+
+