You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2649 lines
63 KiB
2649 lines
63 KiB
/* -- scan.c -- */
|
|
|
|
#include "x11vnc.h"
|
|
#include "xinerama.h"
|
|
#include "xwrappers.h"
|
|
#include "xdamage.h"
|
|
#include "xrandr.h"
|
|
#include "win_utils.h"
|
|
#include "8to24.h"
|
|
#include "screen.h"
|
|
#include "pointer.h"
|
|
#include "cleanup.h"
|
|
#include "unixpw.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<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) {
|
|
char *src_fb = main_fb;
|
|
int Bpp = bpp/8, fac = 1;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cmap8to24 && cmap8to24_fb) {
|
|
src_fb = cmap8to24_fb;
|
|
if (scaling && depth == 8) {
|
|
fac = 4;
|
|
}
|
|
}
|
|
|
|
scale_rect(scale_fac, scaling_blend, scaling_interpolate, fac * Bpp,
|
|
src_fb, fac * 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);
|
|
return;
|
|
}
|
|
|
|
if (cmap8to24) {
|
|
bpp8to24(x1, y1, x2, y2);
|
|
}
|
|
|
|
if (scaling) {
|
|
scale_and_mark_rect(x1, y1, x2, y2);
|
|
} else {
|
|
rfbMarkRectAsModified(screen, 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;
|
|
}
|
|
if (unixpw_in_progress) 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;
|
|
}
|
|
|
|
|
|
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 (unixpw_in_progress) return 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 (cmap8to24 && scan_count % 1 == 0) {
|
|
check_for_multivis();
|
|
}
|
|
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;
|
|
}
|
|
|
|
|