/* -- 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=0, ddy=0; /* for interpolation expansion */ char *src, *dest; /* pointers to the two framebuffers */ unsigned short us = 0; unsigned char uc = 0; unsigned int ui = 0; 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 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 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; bserverFormat.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 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 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; }