GUAC-240: Simplify and clarify update history tracking. Remove lossless refresh of previously-lossy updates for now.

This commit is contained in:
Michael Jumper 2015-08-17 01:08:58 -07:00
parent 254a0dded0
commit 807e3a39a5
2 changed files with 78 additions and 189 deletions

View File

@ -261,55 +261,6 @@ static void __guac_common_mark_dirty(guac_common_surface* surface, const guac_co
}
/**
* Flush a surface's lossy area to the dirty rectangle. This will make the
* rectangle refresh through the normal non-lossy refresh path.
*
* @param surface
* The surface whose lossy area will be moved to the dirty refresh
* queue.
*
* @param x
* The x coordinate of the area to move.
*
* @param y
* The y coordinate of the area to move.
*/
static void __guac_common_surface_flush_lossy_rect_to_dirty_rect(
guac_common_surface* surface, int x, int y) {
/* Get the heat map index. */
int hx = x / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
int hy = y / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
/* Don't update if this rect was not previously sent as a lossy refresh. */
if (!surface->lossy_rect[hy][hx]) {
return;
}
/* Clear the lossy status for this heat map rectangle. */
surface->lossy_rect[hy][hx] = 0;
guac_common_rect lossy_rect;
guac_common_rect_init(&lossy_rect, x, y,
GUAC_COMMON_SURFACE_HEAT_MAP_CELL, GUAC_COMMON_SURFACE_HEAT_MAP_CELL);
int sx = 0;
int sy = 0;
/* Clip operation */
__guac_common_clip_rect(surface, &lossy_rect, &sx, &sy);
if (lossy_rect.width <= 0 || lossy_rect.height <= 0)
return;
/* Flush the rectangle if not combining. */
if (!__guac_common_should_combine(surface, &lossy_rect, 0))
guac_common_surface_flush_deferred(surface);
/* Always defer draws */
__guac_common_mark_dirty(surface, &lossy_rect);
}
/**
* Actual method which flushes a bitmap described by the dirty rectangle
* on the socket associated with the surface.
@ -508,107 +459,55 @@ static void __guac_common_surface_flush_lossy_bitmap(
* @return
* The average refresh frequency.
*/
static unsigned int __guac_common_surface_calculate_refresh_frequency(
guac_common_surface* surface,
int x, int y, int w, int h)
{
static unsigned int __guac_common_surface_calculate_framerate(
guac_common_surface* surface, guac_common_rect* rect) {
w = (x + w) / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
h = (y + h) / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
x /= GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
y /= GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
int x, y;
unsigned int sum_frequency = 0;
/* Calculate minimum X/Y coordinates intersecting given rect */
int min_x = rect->x / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
int min_y = rect->y / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
/* Calculate maximum X/Y coordinates intersecting given rect */
int max_x = min_x + (rect->width - 1) / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
int max_y = min_y + (rect->height - 1) / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
unsigned int sum_framerate = 0;
unsigned int count = 0;
/* Iterate over all the heat map cells for the area
* and calculate the average refresh frequency. */
for (int hy = y; hy <= h; hy++) {
for (int hx = x; hx <= w; hx++) {
const guac_common_surface_heat_rect* heat_rect = &surface->heat_map[hy][hx];
sum_frequency += heat_rect->frequency;
/* Iterate over all the heat map cells for the area
* and calculate the average framerate */
for (y = min_y; y < max_y; y++) {
for (x = min_x; x < max_x; x++) {
const guac_common_surface_heat_rect* heat_rect =
&surface->heat_map[y][x];
/* Calculate indicies for latest and oldest history entries */
int oldest_entry = heat_rect->oldest_entry;
int latest_entry = oldest_entry - 1;
if (latest_entry < 0)
latest_entry = GUAC_COMMON_SURFACE_HEAT_MAP_HISTORY_SIZE;
/* Calculate elapsed time covering entire history for this cell */
int elapsed_time = heat_rect->history[latest_entry]
- heat_rect->history[oldest_entry];
/* Calculate and add framerate */
if (elapsed_time)
sum_framerate += GUAC_COMMON_SURFACE_HEAT_MAP_HISTORY_SIZE
* 1000 / elapsed_time;
count++;
}
}
/* Calculate the average. */
if (count) {
return sum_frequency / count;
}
else {
return 0;
}
}
/* Calculate the average framerate over entire rect */
if (count)
return sum_framerate / count;
/**
* Update the heat map for the surface and re-calculate the refresh frequencies.
*
* Any areas of the surface which have not been updated within a given threshold
* will be moved from the lossy to the normal refresh path.
*
* @param surface
* The surface on which the heat map will be refreshed.
*
* @param now
* The current time.
*/
static void __guac_common_surface_update_heat_map(guac_common_surface* surface,
guac_timestamp now)
{
/* Only update the heat map at the given interval. */
if (now - surface->last_heat_map_update < GUAC_COMMON_SURFACE_HEAT_MAP_UPDATE_FREQ) {
return;
}
surface->last_heat_map_update = now;
const int width = surface->width / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
const int height = surface->height / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
int hx, hy;
for (hy = 0; hy < height; hy++) {
for (hx = 0; hx < width; hx++) {
guac_common_surface_heat_rect* heat_rect = &surface->heat_map[hy][hx];
const int last_update_index = (heat_rect->index + GUAC_COMMON_SURFACE_HEAT_UPDATE_ARRAY_SZ - 1) % GUAC_COMMON_SURFACE_HEAT_UPDATE_ARRAY_SZ;
const guac_timestamp last_update = heat_rect->updates[last_update_index];
const guac_timestamp time_since_last = now - last_update;
/* If the time between the last 2 refreshes is larger than the
* threshold, move this rectangle back to the non-lossy
* refresh pipe. */
if (time_since_last > GUAC_COMMON_SURFACE_NON_LOSSY_REFRESH_THRESHOLD) {
/* Send this lossy rectangle to the normal update queue. */
const int x = hx * GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
const int y = hy * GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
__guac_common_surface_flush_lossy_rect_to_dirty_rect(surface,
x, y);
/* Clear the frequency and refresh times for this square. */
heat_rect->frequency = 0;
memset(heat_rect->updates, 0, sizeof(heat_rect->updates));
continue ;
}
/* Only calculate frequency after N updates to this heat
* rectangle. */
if (heat_rect->updates[GUAC_COMMON_SURFACE_HEAT_UPDATE_ARRAY_SZ - 1] == 0) {
continue;
}
/* Calculate refresh frequency. */
const guac_timestamp first_update = heat_rect->updates[heat_rect->index];
int elapsed_time = last_update - first_update;
if (elapsed_time)
heat_rect->frequency = GUAC_COMMON_SURFACE_HEAT_UPDATE_ARRAY_SZ * 1000 / elapsed_time;
else
heat_rect->frequency = 0;
}
}
return 0;
}
@ -626,22 +525,33 @@ static void __guac_common_surface_update_heat_map(guac_common_surface* surface,
* The time stamp of this update.
*/
static void __guac_common_surface_touch_rect(guac_common_surface* surface,
guac_common_rect* rect, guac_timestamp time)
{
guac_common_rect* rect, guac_timestamp time) {
const int w = (rect->x + rect->width) / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
const int h = (rect->y + rect->height) / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
int hx = rect->x / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
int hy = rect->y / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
int x, y;
for (; hy <= h; hy++) {
for (; hx <= w; hx++) {
/* Calculate minimum X/Y coordinates intersecting given rect */
int min_x = rect->x / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
int min_y = rect->y / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
guac_common_surface_heat_rect* heat_rect = &surface->heat_map[hy][hx];
heat_rect->updates[heat_rect->index] = time;
/* Calculate maximum X/Y coordinates intersecting given rect */
int max_x = min_x + (rect->width - 1) / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
int max_y = min_y + (rect->height - 1) / GUAC_COMMON_SURFACE_HEAT_MAP_CELL;
/* Move the heat index to the next. */
heat_rect->index = (heat_rect->index + 1) % GUAC_COMMON_SURFACE_HEAT_UPDATE_ARRAY_SZ;
/* Update all heat map cells which intersect with rectangle */
for (y = min_y; y <= max_y; y++) {
for (x = min_x; x <= max_x; x++) {
/* Get heat map cell at current location */
guac_common_surface_heat_rect* heat_rect = &surface->heat_map[y][x];
/* Replace oldest entry with new timestamp */
heat_rect->history[heat_rect->oldest_entry] = time;
/* Update to next oldest entry */
heat_rect->oldest_entry++;
if (heat_rect->oldest_entry >=
GUAC_COMMON_SURFACE_HEAT_MAP_HISTORY_SIZE)
heat_rect->oldest_entry = 0;
}
}
@ -1116,14 +1026,12 @@ guac_common_surface* guac_common_surface_alloc(guac_client* client,
guac_socket* socket, const guac_layer* layer, int w, int h) {
/* Init surface */
guac_common_surface* surface = malloc(sizeof(guac_common_surface));
guac_common_surface* surface = calloc(1, sizeof(guac_common_surface));
surface->client = client;
surface->socket = socket;
surface->layer = layer;
surface->width = w;
surface->height = h;
surface->dirty = 0;
surface->bitmap_queue_length = 0;
/* Create corresponding Cairo surface */
surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
@ -1142,22 +1050,6 @@ guac_common_surface* guac_common_surface_alloc(guac_client* client,
else
surface->realized = 0;
/* Initialize heat map and adaptive coding bits. */
surface->lossy_dirty = 0;
surface->last_heat_map_update = 0;
for (int y = 0; y < GUAC_COMMON_SURFACE_HEAT_MAP_ROWS; y++) {
for (int x = 0; x < GUAC_COMMON_SURFACE_HEAT_MAP_COLS; x++) {
guac_common_surface_heat_rect *rect= & surface->heat_map[y][x];
memset(rect->updates, 0, sizeof(rect->updates));
rect->frequency = 0;
rect->index = 0;
surface->lossy_rect[y][x] = 0;
}
}
return surface;
}
@ -1246,8 +1138,8 @@ void guac_common_surface_draw(guac_common_surface* surface, int x, int y, cairo_
guac_timestamp time = guac_timestamp_current();
__guac_common_surface_touch_rect(surface, &rect, time);
/* Calculate the update frequency for this rectangle. */
freq = __guac_common_surface_calculate_refresh_frequency(surface, x, y, w, h);
/* Calculate the average framerate for this rectangle. */
freq = __guac_common_surface_calculate_framerate(surface, &rect);
/* If this rectangle is hot, mark lossy dirty rectangle. */
if (freq >= GUAC_COMMON_SURFACE_LOSSY_REFRESH_FREQUENCY) {
@ -1486,10 +1378,6 @@ static int __guac_common_surface_bitmap_rect_compare(const void* a, const void*
void guac_common_surface_flush(guac_common_surface* surface) {
/* Update heat map. */
guac_timestamp time = guac_timestamp_current();
__guac_common_surface_update_heat_map(surface, time);
/* Flush final dirty rectangle to queue. */
__guac_common_surface_flush_to_queue(surface);

View File

@ -63,10 +63,11 @@
#define GUAC_COMMON_SURFACE_HEAT_MAP_ROWS (GUAC_COMMON_SURFACE_MAX_HEIGHT / GUAC_COMMON_SURFACE_HEAT_MAP_CELL)
/**
* The number of time stamps to collect to be able to calculate the refresh
* frequency for a heat map cell.
* The number of entries to collect within each heat map cell. Collected
* history entries are used to determine the framerate of the region associated
* with that cell.
*/
#define GUAC_COMMON_SURFACE_HEAT_UPDATE_ARRAY_SZ 5
#define GUAC_COMMON_SURFACE_HEAT_MAP_HISTORY_SIZE 5
/**
* Representation of a rectangle or cell in the refresh heat map. This rectangle
@ -75,19 +76,19 @@
typedef struct guac_common_surface_heat_rect {
/**
* Time of the last N updates, used to calculate the refresh frequency.
* Timestamps of each of the last N updates covering the location
* associated with this heat map cell. This is used to calculate the
* framerate. This array is structured as a ring buffer containing history
* entries in chronologically-ascending order, starting at the entry
* pointed to by oldest_entry and proceeding through all other entries,
* wrapping around if the end of the array is reached.
*/
guac_timestamp updates[GUAC_COMMON_SURFACE_HEAT_UPDATE_ARRAY_SZ];
guac_timestamp history[GUAC_COMMON_SURFACE_HEAT_MAP_HISTORY_SIZE];
/**
* Index of the next update slot in the updates array.
* Index of the oldest entry within the history.
*/
int index;
/**
* The current update frequency.
*/
unsigned int frequency;
int oldest_entry;
} guac_common_surface_heat_rect;