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.
537 lines
12 KiB
537 lines
12 KiB
/* -- xdamage.c -- */
|
|
|
|
#include "x11vnc.h"
|
|
#include "xwrappers.h"
|
|
#include "userinput.h"
|
|
|
|
#if LIBVNCSERVER_HAVE_LIBXDAMAGE
|
|
Damage xdamage = 0;
|
|
#endif
|
|
#ifndef XDAMAGE
|
|
#define XDAMAGE 1
|
|
#endif
|
|
int use_xdamage = XDAMAGE; /* use the xdamage rects for scanline hints */
|
|
int xdamage_present = 0;
|
|
int xdamage_max_area = 20000; /* pixels */
|
|
double xdamage_memory = 1.0; /* in units of NSCAN */
|
|
int xdamage_tile_count = 0, xdamage_direct_count = 0;
|
|
double xdamage_scheduled_mark = 0.0;
|
|
sraRegionPtr xdamage_scheduled_mark_region = NULL;
|
|
sraRegionPtr *xdamage_regions = NULL;
|
|
int xdamage_ticker = 0;
|
|
int XD_skip = 0, XD_tot = 0, XD_des = 0; /* for stats */
|
|
|
|
void add_region_xdamage(sraRegionPtr new_region);
|
|
void clear_xdamage_mark_region(sraRegionPtr markregion, int flush);
|
|
int collect_xdamage(int scancnt, int call);
|
|
int xdamage_hint_skip(int y);
|
|
void initialize_xdamage(void);
|
|
void create_xdamage_if_needed(void);
|
|
void destroy_xdamage_if_needed(void);
|
|
void check_xdamage_state(void);
|
|
|
|
|
|
static void record_desired_xdamage_rect(int x, int y, int w, int h);
|
|
|
|
|
|
static void record_desired_xdamage_rect(int x, int y, int w, int h) {
|
|
/*
|
|
* Unfortunately we currently can't trust an xdamage event
|
|
* to correspond to real screen damage. E.g. focus-in for
|
|
* mozilla (depending on wm) will mark the whole toplevel
|
|
* area as damaged, when only the border has changed.
|
|
* Similar things for terminal windows.
|
|
*
|
|
* This routine uses some heuristics to detect small enough
|
|
* damage regions that we will not have a performance problem
|
|
* if we believe them even though they are wrong. We record
|
|
* the corresponding tiles the damage regions touch.
|
|
*/
|
|
int dt_x, dt_y, nt_x1, nt_y1, nt_x2, nt_y2, nt;
|
|
int ix, iy, cnt = 0;
|
|
int area = w*h, always_accept = 0;
|
|
/*
|
|
* XXX: not working yet, slow and overlaps with scan_display()
|
|
* probably slow because tall skinny rectangles very inefficient
|
|
* in general and in direct_fb_copy() (100X slower then horizontal).
|
|
*/
|
|
int use_direct_fb_copy = 0;
|
|
int wh_min, wh_max;
|
|
static int first = 1, udfb = 0;
|
|
|
|
/* compiler warning: */
|
|
nt_x1 = 0; nt_y1 = 0; nt_x2 = 0; nt_y2 = 0;
|
|
|
|
if (first) {
|
|
if (getenv("XD_DFC")) {
|
|
udfb = 1;
|
|
}
|
|
first = 0;
|
|
}
|
|
if (udfb) {
|
|
use_direct_fb_copy = 1;
|
|
}
|
|
|
|
if (xdamage_max_area <= 0) {
|
|
always_accept = 1;
|
|
}
|
|
|
|
if (!always_accept && area > xdamage_max_area) {
|
|
return;
|
|
}
|
|
|
|
dt_x = w / tile_x;
|
|
dt_y = h / tile_y;
|
|
|
|
if (w < h) {
|
|
wh_min = w;
|
|
wh_max = h;
|
|
} else {
|
|
wh_min = h;
|
|
wh_max = w;
|
|
}
|
|
|
|
if (!always_accept && dt_y >= 3 && area > 4000) {
|
|
/*
|
|
* if it is real it should be caught by a normal scanline
|
|
* poll, but we might as well keep if small (tall line?).
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (use_direct_fb_copy) {
|
|
X_UNLOCK;
|
|
direct_fb_copy(x, y, x + w, y + h, 1);
|
|
xdamage_direct_count++;
|
|
X_LOCK;
|
|
} else if (0 && wh_min < tile_x/4 && wh_max > 30 * wh_min) {
|
|
/* try it for long, skinny rects, XXX still no good */
|
|
X_UNLOCK;
|
|
direct_fb_copy(x, y, x + w, y + h, 1);
|
|
xdamage_direct_count++;
|
|
X_LOCK;
|
|
} else {
|
|
nt_x1 = nfix( (x)/tile_x, ntiles_x);
|
|
nt_x2 = nfix((x+w)/tile_x, ntiles_x);
|
|
nt_y1 = nfix( (y)/tile_y, ntiles_y);
|
|
nt_y2 = nfix((y+h)/tile_y, ntiles_y);
|
|
|
|
/*
|
|
* loop over the rectangle of tiles (1 tile for a small
|
|
* input rect).
|
|
*/
|
|
for (ix = nt_x1; ix <= nt_x2; ix++) {
|
|
for (iy = nt_y1; iy <= nt_y2; iy++) {
|
|
nt = ix + iy * ntiles_x;
|
|
cnt++;
|
|
if (! tile_has_xdamage_diff[nt]) {
|
|
XD_des++;
|
|
tile_has_xdamage_diff[nt] = 1;
|
|
}
|
|
/* not used: */
|
|
tile_row_has_xdamage_diff[iy] = 1;
|
|
xdamage_tile_count++;
|
|
}
|
|
}
|
|
}
|
|
if (debug_xdamage > 1) {
|
|
fprintf(stderr, "xdamage: desired: %dx%d+%d+%d\tA: %6d tiles="
|
|
"%02d-%02d/%02d-%02d tilecnt: %d\n", w, h, x, y,
|
|
w * h, nt_x1, nt_x2, nt_y1, nt_y2, cnt);
|
|
}
|
|
}
|
|
|
|
void add_region_xdamage(sraRegionPtr new_region) {
|
|
sraRegionPtr reg;
|
|
int prev_tick, nreg;
|
|
|
|
if (! xdamage_regions) {
|
|
return;
|
|
}
|
|
|
|
nreg = (xdamage_memory * NSCAN) + 1;
|
|
prev_tick = xdamage_ticker - 1;
|
|
if (prev_tick < 0) {
|
|
prev_tick = nreg - 1;
|
|
}
|
|
|
|
reg = xdamage_regions[prev_tick];
|
|
if (reg != NULL) {
|
|
if (0) fprintf(stderr, "add_region_xdamage: prev_tick: %d reg %p\n", prev_tick, (void *)reg);
|
|
sraRgnOr(reg, new_region);
|
|
}
|
|
}
|
|
|
|
void clear_xdamage_mark_region(sraRegionPtr markregion, int flush) {
|
|
#if LIBVNCSERVER_HAVE_LIBXDAMAGE
|
|
XEvent ev;
|
|
sraRegionPtr tmpregion;
|
|
int count = 0;
|
|
|
|
if (! xdamage_present || ! use_xdamage) {
|
|
return;
|
|
}
|
|
if (! xdamage) {
|
|
return;
|
|
}
|
|
if (! xdamage_base_event_type) {
|
|
return;
|
|
}
|
|
|
|
X_LOCK;
|
|
if (flush) {
|
|
XFlush(dpy);
|
|
}
|
|
while (XCheckTypedEvent(dpy, xdamage_base_event_type+XDamageNotify, &ev)) {
|
|
count++;
|
|
}
|
|
/* clear the whole damage region */
|
|
XDamageSubtract(dpy, xdamage, None, None);
|
|
X_UNLOCK;
|
|
|
|
if (debug_tiles || debug_xdamage) {
|
|
fprintf(stderr, "clear_xdamage_mark_region: %d\n", count);
|
|
}
|
|
|
|
if (! markregion) {
|
|
/* NULL means mark the whole display */
|
|
tmpregion = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
|
|
add_region_xdamage(tmpregion);
|
|
sraRgnDestroy(tmpregion);
|
|
} else {
|
|
add_region_xdamage(markregion);
|
|
}
|
|
#else
|
|
if (0) flush++; /* compiler warnings */
|
|
if (0) markregion = NULL;
|
|
#endif
|
|
}
|
|
|
|
int collect_xdamage(int scancnt, int call) {
|
|
#if LIBVNCSERVER_HAVE_LIBXDAMAGE
|
|
XDamageNotifyEvent *dev;
|
|
XEvent ev;
|
|
sraRegionPtr tmpregion;
|
|
sraRegionPtr reg;
|
|
static int rect_count = 0;
|
|
int nreg, ccount = 0, dcount = 0, ecount = 0;
|
|
static time_t last_rpt = 0;
|
|
time_t now;
|
|
int x, y, w, h, x2, y2;
|
|
int i, dup, next = 0, dup_max = 0;
|
|
#define DUPSZ 32
|
|
int dup_x[DUPSZ], dup_y[DUPSZ], dup_w[DUPSZ], dup_h[DUPSZ];
|
|
double tm, dt;
|
|
|
|
if (scancnt) {} /* unused vars warning: */
|
|
|
|
if (! xdamage_present || ! use_xdamage) {
|
|
return 0;
|
|
}
|
|
if (! xdamage) {
|
|
return 0;
|
|
}
|
|
if (! xdamage_base_event_type) {
|
|
return 0;
|
|
}
|
|
|
|
dtime0(&tm);
|
|
|
|
nreg = (xdamage_memory * NSCAN) + 1;
|
|
|
|
if (call == 0) {
|
|
xdamage_ticker = (xdamage_ticker+1) % nreg;
|
|
xdamage_direct_count = 0;
|
|
reg = xdamage_regions[xdamage_ticker];
|
|
sraRgnMakeEmpty(reg);
|
|
} else {
|
|
reg = xdamage_regions[xdamage_ticker];
|
|
}
|
|
|
|
|
|
X_LOCK;
|
|
if (0) XFlush(dpy);
|
|
if (0) XEventsQueued(dpy, QueuedAfterFlush);
|
|
while (XCheckTypedEvent(dpy, xdamage_base_event_type+XDamageNotify, &ev)) {
|
|
/*
|
|
* TODO max cut off time in this loop?
|
|
* Could check QLength and if huge just mark the whole
|
|
* screen.
|
|
*/
|
|
ecount++;
|
|
if (ev.type != xdamage_base_event_type + XDamageNotify) {
|
|
break;
|
|
}
|
|
dev = (XDamageNotifyEvent *) &ev;
|
|
if (dev->damage != xdamage) {
|
|
continue; /* not ours! */
|
|
}
|
|
|
|
x = dev->area.x;
|
|
y = dev->area.y;
|
|
w = dev->area.width;
|
|
h = dev->area.height;
|
|
|
|
/*
|
|
* we try to manually remove some duplicates because
|
|
* certain activities can lead to many 10's of dups
|
|
* in a row. The region work can be costly and reg is
|
|
* later used in xdamage_hint_skip loops, so it is good
|
|
* to skip them if possible.
|
|
*/
|
|
dup = 0;
|
|
for (i=0; i < dup_max; i++) {
|
|
if (dup_x[i] == x && dup_y[i] == y && dup_w[i] == w &&
|
|
dup_h[i] == h) {
|
|
dup = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (dup) {
|
|
dcount++;
|
|
continue;
|
|
}
|
|
if (dup_max < DUPSZ) {
|
|
next = dup_max;
|
|
dup_max++;
|
|
} else {
|
|
next = (next+1) % DUPSZ;
|
|
}
|
|
dup_x[next] = x;
|
|
dup_y[next] = y;
|
|
dup_w[next] = w;
|
|
dup_h[next] = h;
|
|
|
|
/* translate if needed */
|
|
if (clipshift) {
|
|
/* set coords relative to fb origin */
|
|
if (0 && rootshift) {
|
|
/*
|
|
* Note: not needed because damage is
|
|
* relative to subwin, not rootwin.
|
|
*/
|
|
x = x - off_x;
|
|
y = y - off_y;
|
|
}
|
|
if (clipshift) {
|
|
x = x - coff_x;
|
|
y = y - coff_y;
|
|
}
|
|
|
|
x2 = x + w; /* upper point */
|
|
x = nfix(x, dpy_x); /* place both in fb area */
|
|
x2 = nfix(x2, dpy_x+1);
|
|
w = x2 - x; /* recompute w */
|
|
|
|
y2 = y + h;
|
|
y = nfix(y, dpy_y);
|
|
y2 = nfix(y2, dpy_y+1);
|
|
h = y2 - y;
|
|
|
|
if (w <= 0 || h <= 0) {
|
|
continue;
|
|
}
|
|
}
|
|
if (debug_xdamage > 2) {
|
|
fprintf(stderr, "xdamage: -> event %dx%d+%d+%d area:"
|
|
" %d dups: %d %s\n", w, h, x, y, w*h, dcount,
|
|
(w*h > xdamage_max_area) ? "TOO_BIG" : "");
|
|
}
|
|
|
|
record_desired_xdamage_rect(x, y, w, h);
|
|
|
|
tmpregion = sraRgnCreateRect(x, y, x + w, y + h);
|
|
sraRgnOr(reg, tmpregion);
|
|
sraRgnDestroy(tmpregion);
|
|
rect_count++;
|
|
ccount++;
|
|
}
|
|
/* clear the whole damage region for next time. XXX check */
|
|
if (call == 1) {
|
|
XDamageSubtract(dpy, xdamage, None, None);
|
|
}
|
|
X_UNLOCK;
|
|
|
|
if (0 && xdamage_direct_count) {
|
|
fb_push();
|
|
}
|
|
|
|
dt = dtime(&tm);
|
|
if ((debug_tiles > 1 && ecount) || (debug_tiles && ecount > 200)
|
|
|| debug_xdamage > 1) {
|
|
fprintf(stderr, "collect_xdamage(%d): %.4f t: %.4f ev/dup/accept"
|
|
"/direct %d/%d/%d/%d\n", call, dt, tm - x11vnc_start, ecount,
|
|
dcount, ccount, xdamage_direct_count);
|
|
}
|
|
now = time(0);
|
|
if (! last_rpt) {
|
|
last_rpt = now;
|
|
}
|
|
if (now > last_rpt + 15) {
|
|
double rat = -1.0;
|
|
|
|
if (XD_tot) {
|
|
rat = ((double) XD_skip)/XD_tot;
|
|
}
|
|
if (debug_tiles || debug_xdamage) {
|
|
fprintf(stderr, "xdamage: == scanline skip/tot: "
|
|
"%04d/%04d =%.3f rects: %d desired: %d\n",
|
|
XD_skip, XD_tot, rat, rect_count, XD_des);
|
|
}
|
|
|
|
XD_skip = 0;
|
|
XD_tot = 0;
|
|
XD_des = 0;
|
|
rect_count = 0;
|
|
last_rpt = now;
|
|
}
|
|
#else
|
|
if (0) scancnt++; /* compiler warnings */
|
|
if (0) call++;
|
|
if (0) record_desired_xdamage_rect(0, 0, 0, 0);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int xdamage_hint_skip(int y) {
|
|
static sraRegionPtr scanline = NULL;
|
|
sraRegionPtr reg, tmpl;
|
|
int ret, i, n, nreg;
|
|
|
|
if (! xdamage_present || ! use_xdamage) {
|
|
return 0; /* cannot skip */
|
|
}
|
|
if (! xdamage_regions) {
|
|
return 0; /* cannot skip */
|
|
}
|
|
|
|
if (! scanline) {
|
|
/* keep it around to avoid malloc etc, recreate */
|
|
scanline = sraRgnCreate();
|
|
}
|
|
|
|
tmpl = sraRgnCreateRect(0, y, dpy_x, y+1);
|
|
|
|
nreg = (xdamage_memory * NSCAN) + 1;
|
|
ret = 1;
|
|
for (i=0; i<nreg; i++) {
|
|
/* go back thru the history starting at most recent */
|
|
n = (xdamage_ticker + nreg - i) % nreg;
|
|
reg = xdamage_regions[n];
|
|
if (sraRgnEmpty(reg)) {
|
|
/* checking for emptiness is very fast */
|
|
continue;
|
|
}
|
|
sraRgnMakeEmpty(scanline);
|
|
sraRgnOr(scanline, tmpl);
|
|
if (sraRgnAnd(scanline, reg)) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
sraRgnDestroy(tmpl);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void initialize_xdamage(void) {
|
|
sraRegionPtr *ptr;
|
|
int i, nreg;
|
|
|
|
if (! xdamage_present) {
|
|
use_xdamage = 0;
|
|
}
|
|
if (xdamage_regions) {
|
|
ptr = xdamage_regions;
|
|
while (*ptr != NULL) {
|
|
sraRgnDestroy(*ptr);
|
|
ptr++;
|
|
}
|
|
free(xdamage_regions);
|
|
xdamage_regions = NULL;
|
|
}
|
|
if (use_xdamage) {
|
|
nreg = (xdamage_memory * NSCAN) + 2;
|
|
xdamage_regions = (sraRegionPtr *)
|
|
malloc(nreg * sizeof(sraRegionPtr));
|
|
for (i = 0; i < nreg; i++) {
|
|
ptr = xdamage_regions+i;
|
|
if (i == nreg - 1) {
|
|
*ptr = NULL;
|
|
} else {
|
|
*ptr = sraRgnCreate();
|
|
sraRgnMakeEmpty(*ptr);
|
|
}
|
|
}
|
|
/* set so will be 0 in first collect_xdamage call */
|
|
xdamage_ticker = -1;
|
|
}
|
|
}
|
|
|
|
void create_xdamage_if_needed(void) {
|
|
|
|
if (raw_fb && ! dpy) return; /* raw_fb hack */
|
|
|
|
#if LIBVNCSERVER_HAVE_LIBXDAMAGE
|
|
if (! xdamage) {
|
|
X_LOCK;
|
|
xdamage = XDamageCreate(dpy, window, XDamageReportRawRectangles);
|
|
XDamageSubtract(dpy, xdamage, None, None);
|
|
X_UNLOCK;
|
|
rfbLog("created xdamage object: 0x%lx\n", xdamage);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void destroy_xdamage_if_needed(void) {
|
|
|
|
if (raw_fb && ! dpy) return; /* raw_fb hack */
|
|
|
|
#if LIBVNCSERVER_HAVE_LIBXDAMAGE
|
|
if (xdamage) {
|
|
XEvent ev;
|
|
X_LOCK;
|
|
XDamageDestroy(dpy, xdamage);
|
|
XFlush(dpy);
|
|
if (xdamage_base_event_type) {
|
|
while (XCheckTypedEvent(dpy,
|
|
xdamage_base_event_type+XDamageNotify, &ev)) {
|
|
;
|
|
}
|
|
}
|
|
X_UNLOCK;
|
|
rfbLog("destroyed xdamage object: 0x%lx\n", xdamage);
|
|
xdamage = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void check_xdamage_state(void) {
|
|
if (! xdamage_present) {
|
|
return;
|
|
}
|
|
/*
|
|
* Create or destroy the Damage object as needed, we don't want
|
|
* one if no clients are connected.
|
|
*/
|
|
if (client_count && use_xdamage) {
|
|
create_xdamage_if_needed();
|
|
if (xdamage_scheduled_mark > 0.0 && dnow() >
|
|
xdamage_scheduled_mark) {
|
|
if (xdamage_scheduled_mark_region) {
|
|
mark_region_for_xdamage(
|
|
xdamage_scheduled_mark_region);
|
|
sraRgnDestroy(xdamage_scheduled_mark_region);
|
|
xdamage_scheduled_mark_region = NULL;
|
|
} else {
|
|
mark_for_xdamage(0, 0, dpy_x, dpy_y);
|
|
}
|
|
xdamage_scheduled_mark = 0.0;
|
|
}
|
|
} else {
|
|
destroy_xdamage_if_needed();
|
|
}
|
|
}
|
|
|
|
|