GUACAMOLE-377: Establish public guac_display API for efficient rendering via Guacamole protocol.

This commit is contained in:
Michael Jumper 2024-06-03 14:22:53 -07:00
parent c5702d1d4e
commit 104006c6e2
38 changed files with 7784 additions and 40 deletions

View File

@ -44,9 +44,14 @@ PKG_PROG_PKG_CONFIG()
AC_CHECK_HEADERS([fcntl.h stdlib.h string.h sys/socket.h time.h sys/time.h syslog.h unistd.h cairo/cairo.h pngstruct.h])
# Source characteristics
AC_DEFINE([_GNU_SOURCE], [1], [Uses GNU-specific APIs (if available)])
AC_DEFINE([_XOPEN_SOURCE], [700], [Uses X/Open and POSIX APIs])
AC_DEFINE([__BSD_VISIBLE], [1], [Uses BSD-specific APIs (if available)])
# Check for availability of non-portable sched_getaffinity() function (one of
# several possible routes for determining the number of available processors)
AC_CHECK_FUNCS([sched_getaffinity])
# Check for whether math library is required
AC_CHECK_LIB([m], [cos],
[MATH_LIBS=-lm],

View File

@ -39,16 +39,25 @@ libguacinc_HEADERS = \
guacamole/argv.h \
guacamole/argv-constants.h \
guacamole/argv-fntypes.h \
guacamole/assert.h \
guacamole/audio.h \
guacamole/audio-fntypes.h \
guacamole/audio-types.h \
guacamole/client-constants.h \
guacamole/client.h \
guacamole/client-constants.h \
guacamole/client-fntypes.h \
guacamole/client-types.h \
guacamole/display.h \
guacamole/display-constants.h \
guacamole/display-types.h \
guacamole/error.h \
guacamole/error-types.h \
guacamole/fifo.h \
guacamole/fifo-constants.h \
guacamole/fifo-types.h \
guacamole/fips.h \
guacamole/flag.h \
guacamole/flag-types.h \
guacamole/hash.h \
guacamole/layer.h \
guacamole/layer-types.h \
@ -66,9 +75,11 @@ libguacinc_HEADERS = \
guacamole/protocol-constants.h \
guacamole/protocol-types.h \
guacamole/recording.h \
guacamole/rect.h \
guacamole/rect-types.h \
guacamole/rwlock.h \
guacamole/socket-constants.h \
guacamole/socket.h \
guacamole/socket-constants.h \
guacamole/socket-fntypes.h \
guacamole/socket-types.h \
guacamole/stream.h \
@ -98,46 +109,63 @@ libguacprivinc_HEADERS = \
# Private, non-installed headers
#
noinst_HEADERS = \
id.h \
encode-jpeg.h \
encode-png.h \
palette.h \
user-handlers.h \
raw_encoder.h \
noinst_HEADERS = \
display-builtin-cursors.h \
display-plan.h \
display-priv.h \
encode-jpeg.h \
encode-png.h \
id.h \
palette.h \
raw_encoder.h \
user-handlers.h \
wait-fd.h
libguac_la_SOURCES = \
argv.c \
audio.c \
client.c \
encode-jpeg.c \
encode-png.c \
error.c \
fips.c \
hash.c \
id.c \
mem.c \
rwlock.c \
palette.c \
parser.c \
pool.c \
protocol.c \
raw_encoder.c \
recording.c \
socket.c \
socket-broadcast.c \
socket-fd.c \
socket-nest.c \
socket-tee.c \
string.c \
tcp.c \
timestamp.c \
unicode.c \
user.c \
user-handlers.c \
user-handshake.c \
wait-fd.c \
libguac_la_SOURCES = \
argv.c \
audio.c \
client.c \
display.c \
display-builtin-cursors.c \
display-cursor.c \
display-flush.c \
display-layer.c \
display-layer-list.c \
display-plan.c \
display-plan-combine.c \
display-plan-rect.c \
display-plan-search.c \
display-worker.c \
encode-jpeg.c \
encode-png.c \
error.c \
fifo.c \
fips.c \
flag.c \
hash.c \
id.c \
mem.c \
rwlock.c \
palette.c \
parser.c \
pool.c \
protocol.c \
raw_encoder.c \
recording.c \
rect.c \
socket.c \
socket-broadcast.c \
socket-fd.c \
socket-nest.c \
socket-tee.c \
string.c \
tcp.c \
timestamp.c \
unicode.c \
user.c \
user-handlers.c \
user-handshake.c \
wait-fd.c \
wol.c
# Compile WebP support if available

View File

@ -0,0 +1,149 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-builtin-cursors.h"
/**
* Opaque black. This macro evaluates to the 4 bytes of the single pixel of a
* 32-bit ARGB image that represent opaque black and is expected to be used
* only within this file to help make embedded cursor graphics more readable.
*/
#define X 0x00,0x00,0x00,0xFF
/**
* Opaque gray. This macro evaluates to the 4 bytes of the single pixel of a
* 32-bit ARGB image that represent opaque gray and is expected to be used only
* within this file to help make embedded cursor graphics more readable.
*/
#define U 0x80,0x80,0x80,0xFF
/**
* Opaque white. This macro evaluates to the 4 bytes of the single pixel of a
* 32-bit ARGB image that represent opaque white and is expected to be used
* only within this file to help make embedded cursor graphics more readable.
*/
#define O 0xFF,0xFF,0xFF,0xFF
/**
* Full transparency. This macro evaluates to the 4 bytes of the single pixel
* of a 32-bit ARGB image that represent full transparency and is expected to
* be used only within this file to help make embedded cursor graphics more
* readable.
*/
#define _ 0x00,0x00,0x00,0x00
const guac_display_builtin_cursor guac_display_cursor_none = {
.hotspot_x = 0,
.hotspot_y = 0,
.buffer = (unsigned char[]) {
_ /* Single, transparent pixel */
},
.width = 1,
.height = 1,
.stride = 4
};
const guac_display_builtin_cursor guac_display_cursor_dot = {
.hotspot_x = 2,
.hotspot_y = 2,
.buffer = (unsigned char[]) {
_,O,O,O,_,
O,X,X,X,O,
O,X,X,X,O,
O,X,X,X,O,
_,O,O,O,_
},
.width = 5,
.height = 5,
.stride = 20
};
const guac_display_builtin_cursor guac_display_cursor_ibar = {
.hotspot_x = 3,
.hotspot_y = 7,
.buffer = (unsigned char[]) {
X,X,X,X,X,X,X,
X,O,O,U,O,O,X,
X,X,X,O,X,X,X,
_,_,X,O,X,_,_,
_,_,X,O,X,_,_,
_,_,X,O,X,_,_,
_,_,X,O,X,_,_,
_,_,X,O,X,_,_,
_,_,X,O,X,_,_,
_,_,X,O,X,_,_,
_,_,X,O,X,_,_,
_,_,X,O,X,_,_,
_,_,X,O,X,_,_,
X,X,X,O,X,X,X,
X,O,O,U,O,O,X,
X,X,X,X,X,X,X
},
.width = 7,
.height = 16,
.stride = 28
};
const guac_display_builtin_cursor guac_display_cursor_pointer = {
.hotspot_x = 0,
.hotspot_y = 0,
.buffer = (unsigned char[]) {
O,_,_,_,_,_,_,_,_,_,_,
O,O,_,_,_,_,_,_,_,_,_,
O,X,O,_,_,_,_,_,_,_,_,
O,X,X,O,_,_,_,_,_,_,_,
O,X,X,X,O,_,_,_,_,_,_,
O,X,X,X,X,O,_,_,_,_,_,
O,X,X,X,X,X,O,_,_,_,_,
O,X,X,X,X,X,X,O,_,_,_,
O,X,X,X,X,X,X,X,O,_,_,
O,X,X,X,X,X,X,X,X,O,_,
O,X,X,X,X,X,O,O,O,O,O,
O,X,X,O,X,X,O,_,_,_,_,
O,X,O,_,O,X,X,O,_,_,_,
O,O,_,_,O,X,X,O,_,_,_,
O,_,_,_,_,O,X,X,O,_,_,
_,_,_,_,_,O,O,O,O,_,_
},
.width = 11,
.height = 16,
.stride = 44
};

View File

@ -0,0 +1,93 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_DISPLAY_BUILTIN_CURSORS_H
#define GUAC_DISPLAY_BUILTIN_CURSORS_H
#include <unistd.h>
/**
* Mouse cursor image that is built into libguac. Each actual instance of this
* structure will correspond to a value within the guac_display_cursor_type
* enum.
*/
typedef struct guac_display_builtin_cursor {
/**
* The raw, 32-bit ARGB image for this mouse cursor.
*/
const unsigned char* const buffer;
/**
* The width of this mouse cursor image, in pixels.
*/
const unsigned int width;
/**
* The height of this mouse cursor image, in pixels.
*/
const unsigned int height;
/**
* The size of each row of image data, in bytes.
*/
const size_t stride;
/**
* The X coordinate of the relative position of the pointer hotspot within
* the cursor image. The hotspot is the location that the mouse pointer is
* actually reported, with the cursor image visibly positioned relative to
* that location.
*/
int hotspot_x;
/**
* The Y coordinate of the relative position of the pointer hotspot within
* the cursor image. The hotspot is the location that the mouse pointer is
* actually reported, with the cursor image visibly positioned relative to
* that location.
*/
int hotspot_y;
} guac_display_builtin_cursor;
/**
* An empty (invisible/hidden) mouse cursor.
*/
extern const guac_display_builtin_cursor guac_display_cursor_none;
/**
* A small dot. This is typically used in situations where cursor information
* for the remote desktop is not available, thus all cursor rendering must
* happen remotely, but it's still important that the user be able to see the
* current location of their local mouse pointer.
*/
extern const guac_display_builtin_cursor guac_display_cursor_dot;
/**
* A vertical, I-shaped bar indicating text input or selection.
*/
extern const guac_display_builtin_cursor guac_display_cursor_ibar;
/**
* A standard, general-purpose pointer.
*/
extern const guac_display_builtin_cursor guac_display_cursor_pointer;
#endif

View File

@ -0,0 +1,96 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-builtin-cursors.h"
#include "display-priv.h"
#include "guacamole/assert.h"
#include "guacamole/display.h"
#include "guacamole/mem.h"
#include "guacamole/rect.h"
#include "guacamole/rwlock.h"
#include <string.h>
guac_display_layer* guac_display_cursor(guac_display* display) {
return display->cursor_buffer;
}
void guac_display_set_cursor_hotspot(guac_display* display, int x, int y) {
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
display->pending_frame.cursor_hotspot_x = x;
display->pending_frame.cursor_hotspot_y = y;
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_set_cursor(guac_display* display,
guac_display_cursor_type cursor_type) {
const guac_display_builtin_cursor* cursor;
switch (cursor_type) {
case GUAC_DISPLAY_CURSOR_NONE:
cursor = &guac_display_cursor_none;
break;
case GUAC_DISPLAY_CURSOR_DOT:
cursor = &guac_display_cursor_dot;
break;
case GUAC_DISPLAY_CURSOR_IBAR:
cursor = &guac_display_cursor_ibar;
break;
case GUAC_DISPLAY_CURSOR_POINTER:
default:
cursor = &guac_display_cursor_pointer;
break;
}
guac_display_layer* cursor_layer = guac_display_cursor(display);
guac_display_layer_resize(cursor_layer, cursor->width, cursor->height);
guac_display_layer_raw_context* context = guac_display_layer_open_raw(cursor_layer);
GUAC_ASSERT(!cursor_layer->pending_frame.buffer_is_external);
const unsigned char* src_cursor_row = cursor->buffer;
unsigned char* dst_cursor_row = context->buffer;
size_t row_length = guac_mem_ckd_mul_or_die(cursor->width, 4);
for (int y = 0; y < cursor->height; y++) {
memcpy(dst_cursor_row, src_cursor_row, row_length);
src_cursor_row += cursor->stride;
dst_cursor_row += context->stride;
}
context->dirty = (guac_rect) {
.left = 0,
.top = 0,
.right = cursor->width,
.bottom = cursor->height
};
guac_display_set_cursor_hotspot(display, cursor->hotspot_x, cursor->hotspot_y);
guac_display_layer_close_raw(cursor_layer, context);
}

298
src/libguac/display-flush.c Normal file
View File

@ -0,0 +1,298 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-plan.h"
#include "display-priv.h"
#include "guacamole/client.h"
#include "guacamole/display.h"
#include "guacamole/fifo.h"
#include "guacamole/flag.h"
#include "guacamole/mem.h"
#include "guacamole/protocol.h"
#include "guacamole/rect.h"
#include "guacamole/rwlock.h"
#include "guacamole/user.h"
#include <string.h>
/**
* Begins a section related to an optimization phase that should be tracked for
* performance at the "trace" log level.
*/
#define GUAC_DISPLAY_PLAN_BEGIN_PHASE() \
do { \
guac_timestamp phase_start = guac_timestamp_current();
/**
* Ends a section related to an optimization phase that should be tracked for
* performance at the "trace" log level.
*
* @param display
* The guac_display related to the optimizations being performed.
*
* @param phase
* A human-readable name for the optimization phase being tracked.
*
* @param n
* The ordinal number of this phase relative to other phases, where the
* first phase is phase 1.
*
* @param total
* The total number of optimization phases.
*/
#define GUAC_DISPLAY_PLAN_END_PHASE(display, phase, n, total) \
guac_timestamp phase_end = guac_timestamp_current(); \
guac_client_log(display->client, GUAC_LOG_TRACE, "Render planning " \
"phase %i/%i (%s): %ims", n, total, phase, \
(int) (phase_end - phase_start)); \
} while (0)
void guac_display_end_frame(guac_display* display) {
guac_display_end_multiple_frames(display, 0);
}
/**
* Callback for guac_client_foreach_user() which sends the current cursor
* position and button state to any given user except the user that moved the
* cursor last.
*
* @param data
* A pointer to the guac_display whose cursor state should be broadcast to
* all users except the user that moved the cursor last.
*
* @return
* Always NULL.
*/
static void* LFR_guac_display_broadcast_cursor_state(guac_user* user, void* data) {
guac_display* display = (guac_display*) data;
/* Send cursor state only if the user is not moving the cursor */
if (user != display->last_frame.cursor_user)
guac_protocol_send_mouse(user->socket,
display->last_frame.cursor_x, display->last_frame.cursor_y,
display->last_frame.cursor_mask, display->last_frame.timestamp);
return NULL;
}
/**
* Finalizes the current pending frame, storing that state as the copy of the
* last frame. All layer properties that have changed since the last frame will
* be sent out to connected clients.
*
* @param display
* The display whose pending frame should be finalized and persisted as the
* last frame.
*/
static void PFW_LFW_guac_display_frame_complete(guac_display* display) {
guac_client* client = display->client;
display->last_frame.layers = display->pending_frame.layers;
guac_display_layer* current = display->pending_frame.layers;
while (current != NULL) {
/* Copy over pending frame contents if actually changed */
if (!guac_rect_is_empty(&current->pending_frame.dirty)) {
unsigned char* pending_frame = current->pending_frame.buffer;
unsigned char* last_frame = current->last_frame.buffer;
size_t row_length = guac_mem_ckd_mul_or_die(current->pending_frame.width, 4);
for (int y = 0; y < current->pending_frame.height; y++) {
memcpy(last_frame, pending_frame, row_length);
last_frame += current->last_frame.buffer_stride;
pending_frame += current->pending_frame.buffer_stride;
}
current->last_frame.dirty = current->pending_frame.dirty;
current->pending_frame.dirty = (guac_rect) { 0 };
}
/* Commit any change in layer size */
if (current->pending_frame.width != current->last_frame.width
|| current->pending_frame.height != current->last_frame.height) {
guac_protocol_send_size(client->socket, current->layer,
current->pending_frame.width, current->pending_frame.height);
current->last_frame.width = current->pending_frame.width;
current->last_frame.height = current->pending_frame.height;
}
/* Commit any change in layer opacity */
if (current->pending_frame.opacity != current->last_frame.opacity) {
guac_protocol_send_shade(client->socket, current->layer,
current->pending_frame.opacity);
current->last_frame.opacity = current->pending_frame.opacity;
}
/* Commit any change in layer location/hierarchy */
if (current->pending_frame.x != current->last_frame.x
|| current->pending_frame.y != current->last_frame.y
|| current->pending_frame.z != current->last_frame.z
|| current->pending_frame.parent != current->last_frame.parent) {
guac_protocol_send_move(client->socket, current->layer,
current->pending_frame.parent,
current->pending_frame.x,
current->pending_frame.y,
current->pending_frame.z);
current->last_frame.x = current->pending_frame.x;
current->last_frame.y = current->pending_frame.y;
current->last_frame.z = current->pending_frame.z;
current->last_frame.parent = current->pending_frame.parent;
}
/* Commit any change in layer multitouch support */
if (current->pending_frame.touches != current->last_frame.touches) {
guac_protocol_send_set_int(client->socket, current->layer,
GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH,
current->pending_frame.touches);
current->last_frame.touches = current->pending_frame.touches;
}
/* Commit any change in lossless setting (no need to synchronize this
* to the client - it affects only how last_frame is interpreted) */
current->last_frame.lossless = current->pending_frame.lossless;
/* Duplicate layers from pending frame to last frame */
current->last_frame.prev = current->pending_frame.prev;
current->last_frame.next = current->pending_frame.next;
current = current->pending_frame.next;
}
display->last_frame.timestamp = display->pending_frame.timestamp;
display->last_frame.frames = display->pending_frame.frames;
display->pending_frame.frames = 0;
display->pending_dirty = 0;
/* Commit cursor hotspot */
display->last_frame.cursor_hotspot_x = display->pending_frame.cursor_hotspot_x;
display->last_frame.cursor_hotspot_y = display->pending_frame.cursor_hotspot_y;
/* Commit mouse cursor location and notify all other users of change in
* cursor state */
if (display->pending_frame.cursor_x != display->last_frame.cursor_x
|| display->pending_frame.cursor_y != display->last_frame.cursor_y
|| display->pending_frame.cursor_mask != display->last_frame.cursor_mask) {
display->last_frame.cursor_user = display->pending_frame.cursor_user;
display->last_frame.cursor_x = display->pending_frame.cursor_x;
display->last_frame.cursor_y = display->pending_frame.cursor_y;
display->last_frame.cursor_mask = display->pending_frame.cursor_mask;
guac_client_foreach_user(client, LFR_guac_display_broadcast_cursor_state, display);
}
}
void guac_display_end_multiple_frames(guac_display* display, int frames) {
guac_display_plan* plan = NULL;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
display->pending_frame.frames += frames;
/* Defer rendering of further frames until after any in-progress frame has
* finished. Graphical changes will meanwhile continue being accumulated in
* the pending frame. */
guac_fifo_lock(&display->ops);
if (display->ops.state.value & GUAC_FIFO_STATE_NONEMPTY || display->active_workers) {
goto finished_with_display_ops;
}
guac_rwlock_acquire_write_lock(&display->last_frame.lock);
/* PASS 0: Create naive plan, identify minimal dirty rects by comparing the
* changes between the pending and last frames.
*
* This plan will contain operations covering only the minimal parts of the
* display that have changed, but is naive in the sense that it only
* produces draw operations covering 64x64 cells. There is room for
* optimization of those operations, which will be performed by further
* passes. */
GUAC_DISPLAY_PLAN_BEGIN_PHASE();
plan = PFW_LFR_guac_display_plan_create(display);
if (plan == NULL)
goto finished_with_last_frame_lock;
GUAC_DISPLAY_PLAN_END_PHASE(display, "draft", 1, 5);
display->pending_frame.timestamp = plan->frame_end;
/* PASS 1: Identify draw operations that only apply a single color, and
* replace those operations with simple rectangle draws. */
GUAC_DISPLAY_PLAN_BEGIN_PHASE();
PFR_guac_display_plan_rewrite_as_rects(plan);
GUAC_DISPLAY_PLAN_END_PHASE(display, "rects", 2, 5);
/* PASS 2 (and 3): Index all modified cells by their graphical contents and
* search the previous frame for occurrences of the same content. Where any
* draws could instead be represented as copies from the previous frame, do
* so instead of sending new image data. */
GUAC_DISPLAY_PLAN_BEGIN_PHASE();
PFR_guac_display_plan_index_dirty_cells(plan);
PFR_LFR_guac_display_plan_rewrite_as_copies(plan);
GUAC_DISPLAY_PLAN_END_PHASE(display, "search", 3, 5);
/* PASS 4 (and 5): Combine adjacent updates in horizontal and vertical
* directions where doing so would be more efficient. The goal of these
* passes is to ensure that graphics can be encoded and decoded
* efficiently, without defeating the parralelism provided by providing the
* worker threads with many smaller operations. */
GUAC_DISPLAY_PLAN_BEGIN_PHASE();
PFW_guac_display_plan_combine_horizontally(plan);
PFW_guac_display_plan_combine_vertically(plan);
GUAC_DISPLAY_PLAN_END_PHASE(display, "combine", 4, 5);
/*
* With all optimizations now performed, finalize the pending frame. This
* sets the worker threads in motion and frees up the pending frame
* surfaces for writing. Drawing to the next pending frame can now occur
* without disturbing the encoding performed by the worker threads.
*/
GUAC_DISPLAY_PLAN_BEGIN_PHASE();
PFW_LFW_guac_display_frame_complete(display);
GUAC_DISPLAY_PLAN_END_PHASE(display, "commit", 5, 5);
finished_with_last_frame_lock:
guac_rwlock_release_lock(&display->last_frame.lock);
finished_with_display_ops:
guac_fifo_unlock(&display->ops);
guac_rwlock_release_lock(&display->pending_frame.lock);
if (plan != NULL) {
guac_display_plan_apply(plan);
guac_display_plan_free(plan);
}
}

View File

@ -0,0 +1,430 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-priv.h"
#include "guacamole/assert.h"
#include "guacamole/client.h"
#include "guacamole/display.h"
#include "guacamole/layer.h"
#include "guacamole/mem.h"
#include "guacamole/rwlock.h"
#include <cairo/cairo.h>
#include <stdlib.h>
#include <string.h>
/**
* Performs a bulk copy of image data from a source buffer to a destination
* buffer. The two buffers need not match in size and stride. If the
* destination is smaller than the desired source, the source dimensions will
* be adjusted to fit the available space.
*
* @param dst
* A pointer to the first byte of image data in the destination buffer.
*
* @param dst_stride
* The number of bytes in each row of image data in the destination buffer.
*
* @param dst_width
* The width of the destination buffer relative to the provided first byte,
* in pixels.
*
* @param dst_height
* The height of the destination buffer relative to the provided first byte,
* in pixels.
*
* @param src
* A pointer to the first byte of image data in the source buffer.
*
* @param src_stride
* The number of bytes in each row of image data in the source buffer.
*
* @param src_width
* The width of the source buffer relative to the provided first byte, in
* pixels. If this value is larger than dst_width, it will be adjusted to
* fit the available space.
*
* @param src_height
* The height of the source buffer relative to the provided first byte, in
* pixels. If this value is larger than dst_height, it will be adjusted to
* fit the available space.
*
* @param pixel_size
* The size of each pixel of image data, in bytes. The size of each pixel
* in both the destination and source buffers must be identical.
*/
static void guac_imgcpy(void* dst, size_t dst_stride, int dst_width, int dst_height,
void* src, size_t src_stride, int src_width, int src_height,
size_t pixel_size) {
int width = dst_width;
int height = dst_height;
if (src_width < width) width = src_width;
if (src_height < height) height = src_height;
GUAC_ASSERT(width >= 0);
GUAC_ASSERT(height >= 0);
size_t length = guac_mem_ckd_mul_or_die(width, pixel_size);
for (size_t i = 0; i < height; i++) {
memcpy(dst, src, length);
dst = ((char*) dst) + dst_stride;
src = ((char*) src) + src_stride;
}
}
/**
* Resizes the layer represented by the given pair of layer states to the given
* dimensions, allocating a larger underlying image buffer if necessary. If no
* image buffer has yet been allocated, an image buffer large enough to hold
* the given dimensions will be automatically allocated.
*
* This function DOES NOT resize the pending cells array, which is not stored
* on the guac_display_layer_state. When resizing a layer, the pending cells
* array must be separately resized with a call to
* PFW_guac_display_layer_pending_frame_cells_resize().
*
* @param last_frame
* The guac_display_layer_state representing the state of the layer at the
* end of the last frame sent to connected clients.
*
* @param pending_frame
* The guac_display_layer_state representing the current pending state of
* the layer for the upcoming frame to be eventually sent to connected
* clients.
*
* @param width
* The new width, in pixels.
*
* @param height
* The new height, in pixels.
*/
static void PFW_LFW_guac_display_layer_buffers_resize(guac_display_layer_state* last_frame,
guac_display_layer_state* pending_frame, int width, int height) {
GUAC_ASSERT(last_frame->buffer_width == pending_frame->buffer_width);
GUAC_ASSERT(last_frame->buffer_height == pending_frame->buffer_height);
/* Round up to nearest multiple of resize factor */
width = ((width + GUAC_DISPLAY_RESIZE_FACTOR - 1) / GUAC_DISPLAY_RESIZE_FACTOR) * GUAC_DISPLAY_RESIZE_FACTOR;
height = ((height + GUAC_DISPLAY_RESIZE_FACTOR - 1) / GUAC_DISPLAY_RESIZE_FACTOR) * GUAC_DISPLAY_RESIZE_FACTOR;
/* Do nothing if size isn't actually changing */
if (width == last_frame->buffer_width
&& height == last_frame->buffer_height)
return;
/* The request to resize applies only to the pending frame, but space for
* the last frame must be maintained. If either requested dimension is
* smaller than the last frame dimensions, the relevant dimension of the
* last frame must be used instead. */
int new_buffer_width = last_frame->buffer_width;
if (width > new_buffer_width)
new_buffer_width = width;
int new_buffer_height = last_frame->buffer_height;
if (height > new_buffer_height)
new_buffer_height = height;
/* Determine details of shared buffer space sufficient for both the
* established last frame and the resized pending frame. Allocate new
* shared buffer space for last and pending frames, interleaving their
* rows.
*
* NOTE: We interleave the rows of the last and pending frames to promote
* locality of reference. The comparisons performed between last and
* pending frames to determine what has changed are faster when the rows
* are interleaved, as data relevant to those comparisons will tend to be
* present in the CPU cache. */
int new_last_frame_offset = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, new_buffer_width);
int new_common_stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, new_last_frame_offset * 2);
unsigned char* new_buffer_base = guac_mem_zalloc(new_buffer_height, new_common_stride);
unsigned char* new_pending_frame_buffer = new_buffer_base;
unsigned char* new_last_frame_buffer = new_buffer_base + new_last_frame_offset;
/* Copy over data from old shared buffer, if that data exists and is
* relevant */
if (last_frame->buffer != NULL && pending_frame->buffer != NULL) {
guac_imgcpy(
/* Copy to newly-allocated pending frame buffer ... */
new_pending_frame_buffer, new_common_stride,
new_buffer_width, new_buffer_height,
/* ... from old pending frame buffer. */
pending_frame->buffer, pending_frame->buffer_stride,
pending_frame->buffer_width, pending_frame->buffer_height,
/* All pixels are 32-bit */
GUAC_DISPLAY_LAYER_RAW_BPP);
guac_imgcpy(
/* Copy to newly-allocated last frame buffer ... */
new_last_frame_buffer, new_common_stride,
last_frame->buffer_width, last_frame->buffer_height,
/* ... from old last frame buffer. */
last_frame->buffer, last_frame->buffer_stride,
last_frame->buffer_width, last_frame->buffer_height,
/* All pixels are 32-bit */
GUAC_DISPLAY_LAYER_RAW_BPP);
}
guac_mem_free(pending_frame->buffer);
last_frame->buffer = new_buffer_base + new_last_frame_offset;
pending_frame->buffer = new_buffer_base;
last_frame->buffer_width = pending_frame->buffer_width = new_buffer_width;
last_frame->buffer_height = pending_frame->buffer_height = new_buffer_height;
last_frame->buffer_stride = pending_frame->buffer_stride = new_common_stride;
}
/**
* Fully initializes the last and pending frame states for a newly-allocated
* layer, including its underlying image buffers.
*
* @param last_frame
* The guac_display_layer_state representing the state of the layer at the
* end of the last frame sent to connected clients.
*
* @param pending_frame
* The guac_display_layer_state representing the current pending state of
* the layer for the upcoming frame to be eventually sent to connected
* clients.
*/
static void PFW_LFW_guac_display_layer_state_init(guac_display_layer_state* last_frame,
guac_display_layer_state* pending_frame) {
last_frame->width = pending_frame->width = GUAC_DISPLAY_RESIZE_FACTOR;
last_frame->height = pending_frame->height = GUAC_DISPLAY_RESIZE_FACTOR;
last_frame->opacity = pending_frame->opacity = 0xFF;
last_frame->parent = pending_frame->parent = GUAC_DEFAULT_LAYER;
/* Allocate shared buffer space for last and pending frames, interleaving
* their rows */
PFW_LFW_guac_display_layer_buffers_resize(last_frame, pending_frame,
pending_frame->width, pending_frame->height);
}
/**
* Resizes the pending_frame_cells array of the given layer to the given
* dimensions.
*
* @param layer
* The layer whose pending_frame_cells array should be resized.
*
* @param width
* The new width, in pixels.
*
* @param height
* The new height, in pixels.
*/
static void PFW_guac_display_layer_pending_frame_cells_resize(guac_display_layer* layer,
int width, int height) {
int new_pending_frame_cells_width = GUAC_DISPLAY_CELL_DIMENSION(width);
int new_pending_frame_cells_height = GUAC_DISPLAY_CELL_DIMENSION(height);
/* Do nothing if size isn't actually changing */
if (new_pending_frame_cells_width == layer->pending_frame_cells_width
&& new_pending_frame_cells_height == layer->pending_frame_cells_height)
return;
guac_display_layer_cell* new_pending_frame_cells = guac_mem_zalloc(sizeof(guac_display_layer_cell),
new_pending_frame_cells_width, new_pending_frame_cells_height);
/* Copy existing cells over to new memory if present */
if (layer->pending_frame_cells != NULL) {
size_t new_stride = guac_mem_ckd_mul_or_die(new_pending_frame_cells_width, sizeof(guac_display_layer_cell));
size_t old_stride = guac_mem_ckd_mul_or_die(layer->pending_frame_cells_width, sizeof(guac_display_layer_cell));
guac_imgcpy(
/* Copy to newly-allocated pending frame cells ... */
new_pending_frame_cells, new_stride,
new_pending_frame_cells_width, new_pending_frame_cells_height,
/* ... from old pending frame cells. */
layer->pending_frame_cells, old_stride,
layer->pending_frame_cells_width, layer->pending_frame_cells_height,
/* All "pixels" are guac_display_layer_cell structures */
sizeof(guac_display_layer_cell));
}
guac_mem_free(layer->pending_frame_cells);
layer->pending_frame_cells = new_pending_frame_cells;
layer->pending_frame_cells_width = new_pending_frame_cells_width;
layer->pending_frame_cells_height = new_pending_frame_cells_height;
}
guac_display_layer* guac_display_add_layer(guac_display* display, guac_layer* layer, int opaque) {
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
/* Init core layer members */
guac_display_layer* display_layer = guac_mem_zalloc(sizeof(guac_display_layer));
display_layer->display = display;
display_layer->layer = layer;
display_layer->opaque = opaque;
/* Init tracking of pending and last frames (NOTE: We need not acquire the
* display-wide last_frame.lock here as this new layer will not actually be
* part of the last frame layer list until the pending frame is flushed) */
PFW_LFW_guac_display_layer_state_init(&display_layer->last_frame, &display_layer->pending_frame);
display_layer->last_frame_buffer = guac_client_alloc_buffer(display->client);
PFW_guac_display_layer_pending_frame_cells_resize(display_layer,
display_layer->pending_frame.width,
display_layer->pending_frame.height);
/* Insert list element as the new head */
guac_display_layer* old_head = display->pending_frame.layers;
display_layer->pending_frame.prev = NULL;
display_layer->pending_frame.next = old_head;
display->pending_frame.layers = display_layer;
/* Update old head to point to new element, if it existed */
if (old_head != NULL)
old_head->pending_frame.prev = display_layer;
guac_rwlock_release_lock(&display->pending_frame.lock);
return display_layer;
}
void guac_display_remove_layer(guac_display_layer* display_layer) {
guac_display* display = display_layer->display;
/*
* Remove layer from pending frame
*/
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
/* Update previous element, if it exists */
if (display_layer->pending_frame.prev != NULL)
display_layer->pending_frame.prev->pending_frame.next = display_layer->pending_frame.next;
/* If there is no previous element, then this element is the list head.
* Update the list head accordingly. */
else {
GUAC_ASSERT(display->pending_frame.layers == display_layer);
display->pending_frame.layers = display_layer->pending_frame.next;
}
/* Update next element, if it exists */
if (display_layer->pending_frame.next != NULL)
display_layer->pending_frame.next->pending_frame.prev = display_layer->pending_frame.prev;
guac_rwlock_release_lock(&display->pending_frame.lock);
/*
* Remove layer from last frame
*/
guac_rwlock_acquire_write_lock(&display->last_frame.lock);
/* Update previous element, if it exists */
if (display_layer->last_frame.prev != NULL)
display_layer->last_frame.prev->last_frame.next = display_layer->last_frame.next;
/* If there is no previous element, then this element is the list head.
* Update the list head accordingly. */
else {
GUAC_ASSERT(display->last_frame.layers == display_layer);
display->last_frame.layers = display_layer->last_frame.next;
}
/* Update next element, if it exists */
if (display_layer->last_frame.next != NULL)
display_layer->last_frame.next->last_frame.prev = display_layer->last_frame.prev;
guac_rwlock_release_lock(&display->last_frame.lock);
/*
* Layer has now been removed from both pending and last frame lists and
* can be safely freed
*/
guac_client* client = display->client;
guac_client_free_buffer(client, display_layer->last_frame_buffer);
/* Release any Cairo resources */
guac_display_layer_cairo_context* cairo_context = &(display_layer->pending_frame_cairo_context);
if (cairo_context->surface != NULL) {
cairo_surface_destroy(cairo_context->surface);
cairo_context->surface = NULL;
cairo_destroy(cairo_context->cairo);
cairo_context->cairo = NULL;
}
/* Free memory for underlying image surface and change tracking cells
* (NOTE: Freeing pending_frame.buffer inherently also frees
* last_frame.buffer because they are actually interleaved views of the
* same block) */
guac_mem_free(display_layer->pending_frame.buffer);
guac_mem_free(display_layer->pending_frame_cells);
guac_mem_free(display_layer);
}
void PFW_LFW_guac_display_layer_resize(guac_display_layer* layer, int width, int height) {
/* Flush and destroy any cached Cairo context */
guac_display_layer_cairo_context* cairo_context = &(layer->pending_frame_cairo_context);
if (cairo_context->surface != NULL) {
cairo_surface_flush(cairo_context->surface);
cairo_surface_destroy(cairo_context->surface);
cairo_destroy(cairo_context->cairo);
cairo_context->surface = NULL;
cairo_context->cairo = NULL;
}
PFW_LFW_guac_display_layer_buffers_resize(&layer->last_frame, &layer->pending_frame, width, height);
PFW_guac_display_layer_pending_frame_cells_resize(layer, width, height);
layer->pending_frame.width = width;
layer->pending_frame.height = height;
}

250
src/libguac/display-layer.c Normal file
View File

@ -0,0 +1,250 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-priv.h"
#include "guacamole/display.h"
#include "guacamole/rect.h"
#include "guacamole/rwlock.h"
#include <cairo/cairo.h>
#include <stdint.h>
#include <string.h>
void guac_display_layer_get_bounds(guac_display_layer* layer, guac_rect* bounds) {
guac_display* display = layer->display;
guac_rwlock_acquire_read_lock(&display->pending_frame.lock);
*bounds = (guac_rect) {
.left = 0,
.top = 0,
.right = layer->pending_frame.width,
.bottom = layer->pending_frame.height
};
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_move(guac_display_layer* layer, int x, int y) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.x = x;
layer->pending_frame.y = y;
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_stack(guac_display_layer* layer, int z) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.z = z;
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_set_parent(guac_display_layer* layer, const guac_display_layer* parent) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.parent = parent->layer;
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_set_opacity(guac_display_layer* layer, int opacity) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.opacity = opacity;
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_set_lossless(guac_display_layer* layer, int lossless) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.lossless = lossless;
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_set_multitouch(guac_display_layer* layer, int touches) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
layer->pending_frame.touches = touches;
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_resize(guac_display_layer* layer, int width, int height) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
guac_rwlock_acquire_write_lock(&display->last_frame.lock);
PFW_LFW_guac_display_layer_resize(layer, width, height);
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->last_frame.lock);
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_layer_raw_context_set(guac_display_layer_raw_context* context,
const guac_rect* dst, uint32_t color) {
size_t dst_stride = context->stride;
unsigned char* restrict dst_buffer = GUAC_DISPLAY_LAYER_RAW_BUFFER(context, *dst);
for (int dy = dst->top; dy < dst->bottom; dy++) {
uint32_t* dst_pixel = (uint32_t*) dst_buffer;
dst_buffer += dst_stride;
for (int dx = dst->left; dx < dst->right; dx++)
*(dst_pixel++) = color;
}
guac_rect_extend(&(context->dirty), dst);
}
void guac_display_layer_raw_context_put(guac_display_layer_raw_context* context,
const guac_rect* dst, const void* restrict buffer, size_t stride) {
size_t dst_stride = context->stride;
unsigned char* restrict dst_buffer = GUAC_DISPLAY_LAYER_RAW_BUFFER(context, *dst);
const unsigned char* restrict src_buffer = (const unsigned char*) buffer;
size_t copy_length = guac_mem_ckd_mul_or_die(guac_rect_width(dst),
GUAC_DISPLAY_LAYER_RAW_BPP);
for (int dy = dst->top; dy < dst->bottom; dy++) {
memcpy(dst_buffer, src_buffer, copy_length);
dst_buffer += dst_stride;
src_buffer += stride;
}
guac_rect_extend(&(context->dirty), dst);
}
guac_display_layer_raw_context* guac_display_layer_open_raw(guac_display_layer* layer) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
/* Flush any outstanding Cairo operations before directly accessing buffer */
guac_display_layer_cairo_context* cairo_context = &(layer->pending_frame_cairo_context);
if (cairo_context->surface != NULL)
cairo_surface_flush(cairo_context->surface);
layer->pending_frame_raw_context = (guac_display_layer_raw_context) {
.buffer = layer->pending_frame.buffer,
.stride = layer->pending_frame.buffer_stride,
.dirty = { 0 },
.bounds = {
.left = 0,
.top = 0,
.right = layer->pending_frame.buffer_width,
.bottom = layer->pending_frame.buffer_height
}
};
return &layer->pending_frame_raw_context;
}
void guac_display_layer_close_raw(guac_display_layer* layer, guac_display_layer_raw_context* context) {
guac_display* display = layer->display;
guac_rect_extend(&layer->pending_frame.dirty, &context->dirty);
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
guac_display_layer_cairo_context* guac_display_layer_open_cairo(guac_display_layer* layer) {
guac_display* display = layer->display;
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
guac_display_layer_cairo_context* context = &(layer->pending_frame_cairo_context);
context->dirty = (guac_rect) { 0 };
context->bounds = (guac_rect) {
.left = 0,
.top = 0,
.right = layer->pending_frame.buffer_width,
.bottom = layer->pending_frame.buffer_height
};
if (context->surface == NULL) {
context->surface = cairo_image_surface_create_for_data(
layer->pending_frame.buffer,
layer->opaque ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32,
layer->pending_frame.buffer_width,
layer->pending_frame.buffer_height,
layer->pending_frame.buffer_stride);
context->cairo = cairo_create(context->surface);
}
return context;
}
void guac_display_layer_close_cairo(guac_display_layer* layer, guac_display_layer_cairo_context* context) {
guac_display* display = layer->display;
guac_rect_extend(&layer->pending_frame.dirty, &context->dirty);
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
}

View File

@ -0,0 +1,322 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-plan.h"
#include "display-priv.h"
#include "guacamole/display.h"
#include "guacamole/rect.h"
/**
* Returns whether the given operation can be combined with others. Only
* operations of certain types can be combined, and only up to a certain size
* (to favor parallelism).
*
* @param op
* The operation to test.
*
* @return
* Non-zero if the operation can be combined with others, zero otherwise.
*/
static int guac_display_plan_is_combinable(const guac_display_plan_operation* op) {
switch (op->type) {
case GUAC_DISPLAY_PLAN_OPERATION_IMG:
return guac_rect_width(&op->dest) < GUAC_DISPLAY_MAX_COMBINED_WIDTH
&& guac_rect_height(&op->dest) < GUAC_DISPLAY_MAX_COMBINED_HEIGHT;
case GUAC_DISPLAY_PLAN_OPERATION_RECT:
case GUAC_DISPLAY_PLAN_OPERATION_COPY:
return 1;
default:
return 0;
}
}
/**
* Returns whether the two rectangles are adjacent and share exactly one common
* edge.
*
* @param op_a
* One of the rectangles to compare.
*
* @param op_b
* The rectangle to compare op_a with.
*
* @return
* Non-zero if the rectangles are adjacent and share exactly one common
* edge, zero otherwise.
*/
static int guac_display_plan_has_common_edge(const guac_display_plan_operation* op_a,
const guac_display_plan_operation* op_b) {
/* Two operations share a common edge if they are perfectly aligned
* vertically and have the same left/right or right/left edge */
if (op_a->dest.top == op_b->dest.top
&& op_a->dest.bottom == op_b->dest.bottom) {
return op_a->dest.right == op_b->dest.left
|| op_a->dest.left == op_b->dest.right;
}
/* Two operations share a common edge if they are perfectly aligned
* horizontally and have the same top/bottom or bottom/top edge */
else if (op_a->dest.left == op_b->dest.left
&& op_a->dest.right == op_b->dest.right) {
return op_a->dest.top == op_b->dest.bottom
|| op_a->dest.bottom == op_b->dest.top;
}
/* There are no other cases where two operations share a common edge */
return 0;
}
/**
* Returns whether the given pair of operations should be combined into a
* single operation.
*
* @param op_a
* The first operation to check.
*
* @param op_b
* The second operation to check.
*
* @return
* Non-zero if the operations would be better represented as a single,
* combined operation, zero otherwise.
*/
static int guac_display_plan_should_combine(const guac_display_plan_operation* op_a,
const guac_display_plan_operation* op_b) {
/* Consider only operations that have combinable types (draw to a
* particular rectangle in the layer) */
if (!guac_display_plan_is_combinable(op_a)
|| !guac_display_plan_is_combinable(op_b))
return 0;
/* Operations can only be combined within the same layer */
if (op_a->layer != op_b->layer)
return 0;
/* Operations of the same type can be trivially unified under specific
* circumstances */
if (op_a->type == op_b->type) {
switch (op_a->type) {
/* Copy operations can be combined if they are perfectly adjacent
* (exactly share an edge) and copy in the same direction */
case GUAC_DISPLAY_PLAN_OPERATION_COPY:
if (guac_display_plan_has_common_edge(op_a, op_b)) {
int delta_xa = op_a->dest.left - op_a->src.rect.left;
int delta_ya = op_a->dest.top - op_a->src.rect.top;
int delta_xb = op_b->dest.left - op_b->src.rect.left;
int delta_yb = op_b->dest.top - op_b->src.rect.top;
return delta_xa == delta_xb
&& delta_ya == delta_yb;
}
break;
/* Rectangle-drawing operations can be combined if they are
* perfectly adjacent (exactly share an edge) and draw the same
* color */
case GUAC_DISPLAY_PLAN_OPERATION_RECT:
return op_a->src.color == op_b->src.color
&& guac_display_plan_has_common_edge(op_a, op_b);
/* Other combinations require more complex logic... (see below) */
default:
break;
}
}
/* Simulate combination */
guac_rect combined = op_a->dest;
guac_rect_extend(&combined, &op_b->dest);
/* Combine if result is still small */
int combined_width = guac_rect_width(&combined);
int combined_height = guac_rect_height(&combined);
if (combined_width <= GUAC_DISPLAY_NEGLIGIBLE_WIDTH && combined_height <= GUAC_DISPLAY_NEGLIGIBLE_HEIGHT)
return 1;
/* Estimate costs of the existing update, new update, and both combined */
int cost_ab = GUAC_DISPLAY_BASE_COST + combined_width * combined_height;
int cost_a = GUAC_DISPLAY_BASE_COST + op_a->dirty_size;
int cost_b = GUAC_DISPLAY_BASE_COST + op_b->dirty_size;
/* Reduce cost if no image data */
if (op_a->type != GUAC_DISPLAY_PLAN_OPERATION_IMG) cost_a /= GUAC_DISPLAY_DATA_FACTOR;
if (op_b->type != GUAC_DISPLAY_PLAN_OPERATION_IMG) cost_b /= GUAC_DISPLAY_DATA_FACTOR;
/* Combine if cost estimate shows benefit or the increase in cost is
* negligible */
if ((cost_ab <= cost_b + cost_a)
|| (cost_ab - cost_a <= cost_a / GUAC_DISPLAY_NEGLIGIBLE_INCREASE)
|| (cost_ab - cost_b <= cost_b / GUAC_DISPLAY_NEGLIGIBLE_INCREASE))
return 1;
/* Otherwise, do not combine */
return 0;
}
/**
* Combines the given pair of operations into a single operation if doing so is
* advantageous (results in an operation of lesser or negligibly-worse cost).
*
* @param op_a
* The first of the pair of operations to be combined. If they operations
* are combined, the combined operation will be stored here.
*
* @param op_b
* The second of the pair of operations to be combined, which may
* potentially be identical to the first. If the operations are combined,
* this operation will be updated to be a GUAC_DISPLAY_PLAN_OPERATION_NOP
* operation.
*
* @return
* Non-zero if the operations were combined, zero otherwise.
*/
static int guac_display_plan_combine_if_improved(guac_display_plan_operation* op_a,
guac_display_plan_operation* op_b) {
if (op_a == op_b)
return 0;
/* Combine any adjacent operations that match the combination criteria
* (combining produces a net lower cost) */
if (guac_display_plan_should_combine(op_a, op_b)) {
guac_rect_extend(&op_a->dest, &op_b->dest);
/* Operations of different types can only be combined as images */
if (op_a->type != op_b->type)
op_a->type = GUAC_DISPLAY_PLAN_OPERATION_IMG;
/* When combining two copy operations, additionally combine their
* source rects (NOT just the destination rects) */
else if (op_a->type == GUAC_DISPLAY_PLAN_OPERATION_COPY)
guac_rect_extend(&op_a->src.rect, &op_b->src.rect);
op_a->dirty_size += op_b->dirty_size;
if (op_b->last_frame > op_a->last_frame)
op_a->last_frame = op_b->last_frame;
op_b->type = GUAC_DISPLAY_PLAN_OPERATION_NOP;
return 1;
}
return 0;
}
void PFW_guac_display_plan_combine_horizontally(guac_display_plan* plan) {
guac_display* display = plan->display;
guac_display_layer* current = display->pending_frame.layers;
while (current != NULL) {
/* Process only layers that have been modified */
if (!guac_rect_is_empty(&current->pending_frame.dirty)) {
/* Loop through all cells in left-to-right, top-to-bottom order,
* combining any operations that are combinable and horizontally
* adjacent. */
guac_display_layer_cell* cell = current->pending_frame_cells;
for (int y = 0; y < current->pending_frame_cells_height; y++) {
guac_display_layer_cell* previous = cell++;
for (int x = 1; x < current->pending_frame_cells_width; x++) {
/* Combine adjacent updates if doing so is advantageous */
if (previous->related_op != NULL && cell->related_op != NULL
&& guac_display_plan_combine_if_improved(previous->related_op, cell->related_op)) {
cell->related_op = previous->related_op;
}
previous++;
cell++;
}
}
}
current = current->pending_frame.next;
}
}
void PFW_guac_display_plan_combine_vertically(guac_display_plan* plan) {
guac_display* display = plan->display;
guac_display_layer* current = display->pending_frame.layers;
while (current != NULL) {
/* Process only layers that have been modified */
if (!guac_rect_is_empty(&current->pending_frame.dirty)) {
/* Loop through all cells in top-to-bottom, left-to-right order,
* combining any operations that are combinable and horizontally
* adjacent. */
guac_display_layer_cell* cell_col = current->pending_frame_cells;
for (int x = 0; x < current->pending_frame_cells_width; x++) {
guac_display_layer_cell* previous = cell_col;
guac_display_layer_cell* cell = cell_col + current->pending_frame_cells_width;
for (int y = 1; y < current->pending_frame_cells_height; y++) {
/* Combine adjacent updates if doing so is advantageous */
if (previous->related_op != NULL && cell->related_op != NULL
&& guac_display_plan_combine_if_improved(previous->related_op, cell->related_op)) {
cell->related_op = previous->related_op;
}
previous += current->pending_frame_cells_width;
cell += current->pending_frame_cells_width;
}
cell_col++;
}
}
current = current->pending_frame.next;
}
}

View File

@ -0,0 +1,235 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-plan.h"
#include "display-priv.h"
#include "guacamole/display.h"
#include "guacamole/mem.h"
#include "guacamole/rect.h"
#include <string.h>
#include <stdint.h>
/**
* Rounds the given value down to the nearest power of two.
*
* @param value
* The value to round.
*
* @return
* The power of two that is closest to the given value without exceeding
* that value.
*/
static size_t guac_display_plan_round_pot(size_t value) {
if (value <= 2)
return value;
size_t rounded = 1;
while (value >>= 1)
rounded <<= 1;
return rounded;
}
/**
* Returns whether the given buffer consists entirely of the same 32-bit
* quantity (ie: a single ARGB pixel), repeated throughout the buffer.
*
* This function attempts to perform a fast comparison leveraging memcmp() to
* reduce the search space, rather than simply looping through each pixel one
* at a time. Basic benchmarks show this approach to be roughly twice as fast
* as a simple loop for arbitrary buffer lengths and four times as fast for
* buffer lengths that are powers of two.
*
* @param buffer
* The buffer to check.
*
* @param length
* The number of bytes in the buffer.
*
* @param color
* A pointer to a uint32_t to receive the value of the 32-bit quantity that
* is repeated, if applicable.
*
* @return
* Non-zero if the same 32-bit quantity is repeated throughout the buffer,
* zero otherwise. If the same value is indeed repeated throughout the
* buffer, that value is stored in the variable pointed to by the "color"
* pointer. If the value is not repeated, the variable pointed to by the
* "color" pointer is left untouched.
*/
static int guac_display_plan_is_single_color(const unsigned char* restrict buffer,
size_t length, uint32_t* restrict color) {
/* It is vacuously true that all the 32-bit quantities in an empty buffer
* are the same */
if (length == 0) {
*color = 0x00000000;
return 1;
}
/* A single 32-bit value is the same as itself */
if (length == 4) {
*color = ((const uint32_t*) buffer)[0];
return 1;
}
/* Simply directly compare if there are only two values */
if (length == 8) {
uint32_t a = ((const uint32_t*) buffer)[0];
uint32_t b = ((const uint32_t*) buffer)[1];
if (a == b) {
*color = a;
return 1;
}
}
/* For all other lengths, avoid comparing if finding a match is impossible.
* A buffer can consist entirely of the same 32-bit (4-byte) quantity
* repeated throughout the buffer only if that buffer's length is a
* multiple of 4. */
if ((length % 4) != 0)
return 0;
/* A buffer consists entirely of the same 32-bit quantity repeated
* throughout if (1) the two halves of the buffer are the same and (2) one
* of those halves is known to consist entirely of the same 32-bit quantity
* repeated throughout. */
size_t pot_length = guac_display_plan_round_pot(guac_mem_ckd_sub_or_die(length, 1));
size_t remaining_length = guac_mem_ckd_sub_or_die(length, pot_length);
/* Easiest recursive case: the buffer is already a power of two and can be
* split into two very easy-to-compare halves */
if (pot_length == remaining_length) {
return !memcmp(buffer, buffer + pot_length, pot_length)
&& guac_display_plan_is_single_color(buffer, pot_length, color);
}
/* For buffers that can't be split into two power-of-two halves, decide
* based on one easy power-of-two case and one not-so-easy case of whatever
* remains */
uint32_t color_a = 0, color_b = 0;
if (guac_display_plan_is_single_color(buffer, pot_length, &color_a)
&& guac_display_plan_is_single_color(buffer + pot_length, remaining_length, &color_b)
&& color_a == color_b) {
*color = color_a;
return 1;
}
return 0;
}
/**
* Returns whether the given rectangle within given buffer consists entirely of
* the same 32-bit quantity (ie: a single ARGB pixel), repeated throughout the
* rectangular region.
*
* This function attempts to perform a fast comparison leveraging memcmp() to
* reduce the search space, rather than simply looping through each pixel one
* at a time. Basic benchmarks show this approach to be roughly twice as fast
* as a simple loop for arbitrary buffer lengths and four times as fast for
* buffer lengths that are powers of two.
*
* @param buffer
* The buffer to check.
*
* @param stride
* The number of bytes in each row of image data within the buffer.
*
* @param rect
* The rectangle representing the region to be checked within the buffer.
*
* @param color
* A pointer to a uint32_t to receive the value of the 32-bit quantity that
* is repeated, if applicable.
*
* @return
* Non-zero if the same 32-bit quantity is repeated throughout the
* rectangular region, zero otherwise. If the same value is indeed repeated
* throughout the rectangle, that value is stored in the variable pointed
* to by the "color" pointer. If the value is not repeated, the variable
* pointed to by the "color" pointer is left untouched.
*/
static int guac_display_plan_is_rect_single_color(const unsigned char* restrict buffer,
size_t stride, const guac_rect* restrict rect, uint32_t* restrict color) {
size_t row_length = guac_mem_ckd_mul_or_die(guac_rect_width(rect), GUAC_DISPLAY_LAYER_RAW_BPP);
buffer = GUAC_RECT_CONST_BUFFER(*rect, buffer, stride, GUAC_DISPLAY_LAYER_RAW_BPP);
/* Verify that the first row consists of a single color */
uint32_t first_color = 0x00000000;
if (!guac_display_plan_is_single_color(buffer, row_length, &first_color))
return 0;
/* The whole rectangle consists of a single color if each row is identical
* and it's already known that one of those rows consists of the a single
* color */
const unsigned char* previous = buffer;
for (int y = rect->top + 1; y < rect->bottom; y++) {
const unsigned char* current = previous + stride;
if (memcmp(previous, current, row_length))
return 0;
previous = current;
}
*color = first_color;
return 1;
}
void PFR_guac_display_plan_rewrite_as_rects(guac_display_plan* plan) {
uint32_t color = 0x00000000;
guac_display_plan_operation* op = plan->ops;
for (int i = 0; i < plan->length; i++) {
if (op->type == GUAC_DISPLAY_PLAN_OPERATION_IMG) {
guac_display_layer* layer = op->layer;
size_t stride = layer->pending_frame.buffer_stride;
const unsigned char* buffer = layer->pending_frame.buffer;
if (guac_display_plan_is_rect_single_color(buffer, stride, &op->dest, &color)) {
/* Ignore alpha channel for opaque layers */
if (layer->opaque)
color |= 0xFF000000;
op->type = GUAC_DISPLAY_PLAN_OPERATION_RECT;
op->src.color = color;
}
}
op++;
}
}

View File

@ -0,0 +1,428 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-plan.h"
#include "display-priv.h"
#include "guacamole/display.h"
#include "guacamole/rect.h"
#include <string.h>
#include <stdint.h>
/**
* Stores the given operation within the ops_by_hash table of the given display
* plan based on the given hash value. The hash function applied for storing
* the operation is GUAC_DISPLAY_PLAN_OPERATION_HASH(). If another operation is
* already stored at the same location within ops_by_hash, that operation will
* be replaced.
*
* @param plan
* The plan to store the operation within.
*
* @param hash
* The hash value to use to calculate the storage location. This value will
* be further hashed with GUAC_DISPLAY_PLAN_OPERATION_HASH().
*
* @param op
* The operation to store.
*/
static void guac_display_plan_store_indexed_op(guac_display_plan* plan, uint64_t hash,
guac_display_plan_operation* op) {
size_t index = GUAC_DISPLAY_PLAN_OPERATION_HASH(hash);
guac_display_plan_indexed_operation* entry = &(plan->ops_by_hash[index]);
if (entry->op == NULL) {
entry->hash = hash;
entry->op = op;
}
}
/**
* Removes and returns a pointer to the matching operation stored within the
* ops_by_hash table of the given display plan, if any. If no such operation is
* stored, NULL is returned.
*
* @param plan
* The plan to retrieve the operation from.
*
* @param hash
* The hash value to use to calculate the storage location. This value will
* be further hashed with GUAC_DISPLAY_PLAN_OPERATION_HASH().
*
* @return
* The operation that was stored under the given hash, if any, or NULL if
* no such operation was found.
*/
static guac_display_plan_operation* guac_display_plan_remove_indexed_op(guac_display_plan* plan, uint64_t hash) {
size_t index = GUAC_DISPLAY_PLAN_OPERATION_HASH(hash);
guac_display_plan_indexed_operation* entry = &(plan->ops_by_hash[index]);
/* NOTE: We verify the hash value here because the lookup performed is
* actually a hash of a hash. There's an additional chance of collisions
* between hash values at this second level of hashing. */
guac_display_plan_operation* op = entry->op;
if (op != NULL && entry->hash == hash) {
entry->op = NULL;
return op;
}
return NULL;
}
/**
* Callback invoked by guac_hash_foreach_image_rect() for each 64x64 rectangle
* of image data.
*
* @param plan
* The display plan related to the call to guac_hash_foreach_image_rect().
*
* @param x
* The X coordinate of the upper-left corner of the current 64x64 rectangle
* within the search region.
*
* @param y
* The Y coordinate of the upper-left corner of the current 64x64 rectangle
* within the search region.
*
* @param hash
* The hash value that applies to the current 64x64 rectangle.
*
* @param closure
* The closure value that was originally provided to the call to
* guac_hash_foreach_image_rect().
*/
typedef void guac_hash_callback(guac_display_plan* plan, int x, int y, uint64_t hash, void* closure);
/**
* Iterates through each 64x64 subrectangle within the given rectangular region
* of the underlying buffer of the given layer state, invoking the given
* callback for each such subrectangle. Each 64x64 subrectangle within the
* rectangular region is evaluated by sliding a 64x64 window over each pixel of
* the region such that every 64x64 subrectangle in the region is eventually
* covered.
*
* @param plan
* The display plan related to the search/indexing operation being
* performed.
*
* @param layer_state
* The layer state containing the image buffer to hash.
*
* @param rect
* The rectangular region within the image buffer that should be hashed.
*
* @param callback
* The callback to invoke for each 64x64 subrectangle of the given region.
*
* @param closure
* The arbitrary value to pass the given callback each time it is invoked
* through this function call.
*/
static int guac_hash_foreach_image_rect(guac_display_plan* plan,
const guac_display_layer_state* layer_state, const guac_rect* rect,
guac_hash_callback* callback, void* closure) {
size_t stride = layer_state->buffer_stride;
const unsigned char* data = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(*layer_state, *rect);
int x, y;
uint64_t cell_hash[GUAC_DISPLAY_MAX_WIDTH] = { 0 };
/* NOTE: Because the hash value of the sliding 64x64 window is available
* only upon reaching the bottom-right corner of that window, we offset the
* coordinates here by the relative location of the bottom-right corner
* (GUAC_DISPLAY_CELL_SIZE - 1) so that we have easy access to the
* coordinates of the upper-left corner of the sliding window, as required
* by the callback being invoked.
*
* This also allows us to easily determine when the hash is valid and it's
* safe to invoke the callback. Once the coordinates are within the given
* rect, we have evaluated a full 64x64 rectangle and have a valid hash. */
int start_x = rect->left - GUAC_DISPLAY_CELL_SIZE + 1;
int end_x = rect->right - GUAC_DISPLAY_CELL_SIZE + 1;
int start_y = rect->top - GUAC_DISPLAY_CELL_SIZE + 1;
int end_y = rect->bottom - GUAC_DISPLAY_CELL_SIZE + 1;
for (y = start_y; y < end_y; y++) {
uint64_t* current_cell_hash = cell_hash;
/* Get current row */
uint32_t* row = (uint32_t*) data;
data += stride;
/* Calculate row segment hashes for entire row */
uint64_t row_hash = 0;
for (x = start_x; x < end_x; x++) {
/* Get current pixel */
uint32_t pixel = *(row++);
/* Update hash value for current row segment */
row_hash = ((row_hash * 31) << 1) + pixel;
/* Incorporate row hash value into overall cell hash */
uint64_t cell_hash = ((*current_cell_hash * 31) << 1) + row_hash;
*(current_cell_hash++) = cell_hash;
/* Invoke callback for every hash generated, breaking out early if
* requested */
if (y >= rect->top && x >= rect->left)
callback(plan, x, y, cell_hash, closure);
}
} /* end for each row */
return 0;
}
/**
* Initializes the given rectangle with the bounds of the pending frame cell
* containing the given coordinate.
*
* @param rect
* The rectangle to initialize.
*
* @param x
* The X coordinate of the point that the rectangle must contain.
*
* @param y
* The Y coordinate of the point that the rectangle must contain.
*/
static void guac_display_cell_init_rect(guac_rect* rect, int x, int y) {
x = (x / GUAC_DISPLAY_CELL_SIZE) * GUAC_DISPLAY_CELL_SIZE;
y = (y / GUAC_DISPLAY_CELL_SIZE) * GUAC_DISPLAY_CELL_SIZE;
guac_rect_init(rect, x, y, GUAC_DISPLAY_CELL_SIZE, GUAC_DISPLAY_CELL_SIZE);
}
/**
* Callback for guac_hash_foreach_image_rect() which stores the given operation
* in the ops_by_hash table of the given display plan.
*
* @param plan
* The display plan to store the given operation in.
*
* @param x
* The X coordinate of the upper-left corner of the 64x64 rectangle
* modified by the given operation.
*
* @param y
* The Y coordinate of the upper-left corner of the 64x64 rectangle
* modified by the given operation.
*
* @param hash
* The hash value that applies to the 64x64 rectangle at the given
* coordinates.
*
* @param closure
* A pointer to the guac_display_plan_operation that should be stored
* within the ops_by_hash table of the given display plan.
*/
static void guac_display_plan_index_op_for_cell(guac_display_plan* plan, int x, int y, uint64_t hash, void* closure) {
guac_display_plan_store_indexed_op(plan, hash, (guac_display_plan_operation*) closure);
}
void PFR_guac_display_plan_index_dirty_cells(guac_display_plan* plan) {
memset(plan->ops_by_hash, 0, sizeof(plan->ops_by_hash));
guac_display_plan_operation* op = plan->ops;
for (int i = 0; i < plan->length; i++) {
if (op->type == GUAC_DISPLAY_PLAN_OPERATION_IMG) {
guac_display_layer* layer = op->layer;
guac_rect layer_bounds;
guac_display_layer_get_bounds(layer, &layer_bounds);
guac_rect cell;
guac_display_cell_init_rect(&cell, op->dest.left, op->dest.top);
guac_rect_constrain(&cell, &layer_bounds);
if (guac_rect_width(&cell) == GUAC_DISPLAY_CELL_SIZE
&& guac_rect_height(&cell) == GUAC_DISPLAY_CELL_SIZE) {
guac_hash_foreach_image_rect(plan, &layer->pending_frame,
&cell, guac_display_plan_index_op_for_cell, op);
}
}
op++;
}
}
/**
* Compares two rectangular regions of two arbitrary buffers, returning whether
* those regions contain identical data.
*
* @param data_a
* A pointer to the first byte of image data within the first region being
* compared.
*
* @param width_a
* The width of the first region, in pixels.
*
* @param height_a
* The height of the first region, in pixels.
*
* @param stride_a
* The number of bytes in each row of image data in the first region.
*
* @param data_b
* A pointer to the first byte of image data within the second region being
* compared.
*
* @param width_b
* The width of the second region, in pixels.
*
* @param height_b
* The height of the second region, in pixels.
*
* @param stride_b
* The number of bytes in each row of image data in the first region.
*
* @return
* Non-zero if the regions contain at least one differing pixel, zero
* otherwise.
*/
static int guac_image_cmp(const unsigned char* restrict data_a, int width_a, int height_a,
int stride_a, const unsigned char* restrict data_b, int width_b, int height_b,
int stride_b) {
int y;
/* If core dimensions differ, just compare those. Done. */
if (width_a != width_b) return width_a - width_b;
if (height_a != height_b) return height_a - height_b;
size_t length = guac_mem_ckd_mul_or_die(width_a, GUAC_DISPLAY_LAYER_RAW_BPP);
for (y = 0; y < height_a; y++) {
/* Compare row. If different, use that result. */
int cmp_result = memcmp(data_a, data_b, length);
if (cmp_result != 0)
return cmp_result;
/* Next row */
data_a += stride_a;
data_b += stride_b;
}
/* Otherwise, same. */
return 0;
}
/**
* Callback for guac_hash_foreach_image_rect() which searches the ops_by_hash
* table of the given display plan for occurrences of the given hash, replacing
* the matching operation with a copy operation if a match is found.
*
* NOTE: While this function will search for and optimize operations that copy
* existing data, it can only do so for distinct image data. Multiple
* operations that copy the same exact data (like a region tiled with multiple
* copies of some pattern) can only be stored in the table once, and therefore
* will only match once.
*
* @param plan
* The display plan to update with any copies found.
*
* @param x
* The X coordinate of the upper-left corner of the 64x64 region currently
* being checked.
*
* @param y
* The Y coordinate of the upper-left corner of the 64x64 region currently
* being checked.
*
* @param hash
* The hash value that applies to the 64x64 rectangle at the given
* coordinates.
*
* @param closure
* A pointer to the guac_display_layer that is being searched.
*/
static void PFR_LFR_guac_display_plan_find_copies(guac_display_plan* plan,
int x, int y, uint64_t hash, void* closure) {
guac_display_layer* layer = (guac_display_layer*) closure;
/* Transform the matching operation into a copy of the current region if
* any operations match, banning the underlying hash from further checks if
* a collision occurs */
guac_display_plan_operation* op = guac_display_plan_remove_indexed_op(plan, hash);
if (op != NULL && op->layer == layer) {
guac_rect src_rect;
guac_rect_init(&src_rect, x, y, GUAC_DISPLAY_CELL_SIZE, GUAC_DISPLAY_CELL_SIZE);
guac_rect dst_rect;
guac_display_cell_init_rect(&dst_rect, op->dest.left, op->dest.top);
const unsigned char* copy_from = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(layer->last_frame, src_rect);
const unsigned char* copy_to = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(layer->pending_frame, dst_rect);
/* Only transform into a copy if the image data is truly identical (not a collision) */
if (!guac_image_cmp(copy_from, GUAC_DISPLAY_CELL_SIZE, GUAC_DISPLAY_CELL_SIZE, layer->last_frame.buffer_stride,
copy_to, GUAC_DISPLAY_CELL_SIZE, GUAC_DISPLAY_CELL_SIZE, layer->pending_frame.buffer_stride)) {
op->type = GUAC_DISPLAY_PLAN_OPERATION_COPY;
op->src.rect = src_rect;
op->dest = dst_rect;
}
}
}
void PFR_LFR_guac_display_plan_rewrite_as_copies(guac_display_plan* plan) {
guac_display* display = plan->display;
guac_display_layer* current = display->last_frame.layers;
while (current != NULL) {
guac_rect search_region;
guac_rect_init(&search_region, 0, 0, current->last_frame.width, current->last_frame.height);
/* Avoid excessive computation by restricting the search region to only
* the area that was changed in the upcoming frame (in the case of
* scrolling, absolutely all data relevant to the scroll will have been
* modified) */
guac_rect_constrain(&search_region, &current->pending_frame.dirty);
guac_hash_foreach_image_rect(plan, &current->last_frame, &search_region,
PFR_LFR_guac_display_plan_find_copies, current);
current = current->last_frame.next;
}
}

275
src/libguac/display-plan.c Normal file
View File

@ -0,0 +1,275 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-plan.h"
#include "display-priv.h"
#include "guacamole/assert.h"
#include "guacamole/display.h"
#include "guacamole/fifo.h"
#include "guacamole/mem.h"
#include "guacamole/timestamp.h"
#include <string.h>
#include <cairo/cairo.h>
/**
* Updates the dirty rect in the given cell to note that a horizontal line of
* image data at the given location and having the given width has changed
* since the last frame. A provided counter of the overall number of changed
* cells is updated accordingly.
*
* @param layer
* The layer that changed.
*
* @param cell
* The cell containing the line of image data that changed.
*
* @param count
* A pointer to a counter that contains the current number of cells that
* have been marked as having changed since the last frame.
*
* @param x
* The X coordinate of the leftmost pixel of the horizontal line.
*
* @param y
* The Y coordinate of the leftmost pixel of the horizontal line.
*
* @param width
* The width of the line, in pixels.
*/
static void guac_display_plan_mark_dirty(guac_display_layer* layer,
guac_display_layer_cell* cell, size_t* count, int x, int y,
int width) {
if (!cell->dirty_size) {
guac_rect_init(&cell->dirty, x, y, width, 1);
cell->dirty_size = width;
(*count)++;
}
else {
guac_rect dirty;
guac_rect_init(&dirty, x, y, width, 1);
guac_rect_extend(&cell->dirty, &dirty);
cell->dirty_size += width;
}
}
guac_display_plan* PFW_LFR_guac_display_plan_create(guac_display* display) {
guac_display_layer* current;
guac_timestamp frame_end = guac_timestamp_current();
size_t op_count = 0;
/* Loop through each layer, searching for modified regions */
current = display->pending_frame.layers;
while (current != NULL) {
/* Check only within layer dirty region, skipping the layer if
* unmodified. This pass should reset and refine that region, but
* otherwise rely on proper reporting of modified regions by callers of
* the open/close layer functions. */
guac_rect dirty = current->pending_frame.dirty;
if (guac_rect_is_empty(&dirty)) {
current = current->pending_frame.next;
continue;
}
/* Flush any outstanding Cairo operations before directly accessing buffer */
guac_display_layer_cairo_context* cairo_context = &(current->pending_frame_cairo_context);
if (cairo_context->surface != NULL)
cairo_surface_flush(cairo_context->surface);
/* Re-align the dirty rect with nearest multiple of 64 to ensure each
* step of the dirty rect refinement loop starts at the topmost
* boundary of a cell */
guac_rect_align(&dirty, GUAC_DISPLAY_CELL_SIZE_EXPONENT);
guac_rect last_frame_bounds = {
.left = 0,
.top = 0,
.right = current->last_frame.width,
.bottom = current->last_frame.height
};
guac_rect pending_frame_bounds = {
.left = 0,
.top = 0,
.right = current->pending_frame.width,
.bottom = current->pending_frame.height
};
/* Limit size of dirty rect by bounds of backing surfaces */
guac_rect_constrain(&dirty, &last_frame_bounds);
guac_rect_constrain(&dirty, &pending_frame_bounds);
const unsigned char* flushed_row = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(current->last_frame, dirty);
unsigned char* buffer_row = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(current->pending_frame, dirty);
guac_display_layer_cell* cell_row = current->pending_frame_cells
+ guac_mem_ckd_mul_or_die(dirty.top / GUAC_DISPLAY_CELL_SIZE, current->pending_frame_cells_width)
+ dirty.left / GUAC_DISPLAY_CELL_SIZE;
/* Loop through the rough modified region, refining the dirty rects of
* each cell to more accurately contain only what has actually changed
* since last frame */
current->pending_frame.dirty = (guac_rect) { 0 };
for (int y = dirty.top; y < dirty.bottom; y += GUAC_DISPLAY_CELL_SIZE) {
int height = GUAC_DISPLAY_CELL_SIZE;
if (y + height > dirty.bottom)
height = dirty.bottom - y;
/* Iteration through the pending_frame_cells array and the image
* buffer is a bit complex here, as the pending_frame_cells array
* contains cells that represent 64x64 regions, while the image
* buffers contain absolutely all pixels. The outer loop goes
* through just the pending cells, while the following loop goes
* through the Y coordinates that make up that cell. */
for (int y_off = 0; y_off < height; y_off++) {
/* At this point, we need to loop through the horizontal
* dimension, comparing the 64-pixel rows of image data in the
* current line (y + y_off) that are in each applicable cell.
* We jump forward by one cell for each comparison. */
guac_display_layer_cell* current_cell = cell_row;
uint32_t* current_flushed = (uint32_t*) flushed_row;
uint32_t* current_buffer = (uint32_t*) buffer_row;
for (int x = dirty.left; x < dirty.right; x += GUAC_DISPLAY_CELL_SIZE) {
int width = GUAC_DISPLAY_CELL_SIZE;
if (x + width > dirty.right)
width = dirty.right - x;
/* Mark the relevant region of the cell as dirty if the
* current 64-pixel line has changed in any way */
size_t length = guac_mem_ckd_mul_or_die(width, GUAC_DISPLAY_LAYER_RAW_BPP);
if (memcmp(current_buffer, current_flushed, length)) {
guac_display_plan_mark_dirty(current, current_cell, &op_count, x, y + y_off, width);
guac_rect_extend(&current->pending_frame.dirty, &current_cell->dirty);
}
current_flushed += GUAC_DISPLAY_CELL_SIZE;
current_buffer += GUAC_DISPLAY_CELL_SIZE;
current_cell++;
}
flushed_row += current->last_frame.buffer_stride;
buffer_row += current->pending_frame.buffer_stride;
}
cell_row += current->pending_frame_cells_width;
}
current = current->pending_frame.next;
}
/* If no layer has been modified, there's no need to create a plan */
if (!op_count)
return NULL;
guac_display_plan* plan = guac_mem_alloc(sizeof(guac_display_plan));
plan->display = display;
plan->frame_end = frame_end;
plan->length = guac_mem_ckd_add_or_die(op_count, 1);
plan->ops = guac_mem_alloc(plan->length, sizeof(guac_display_plan_operation));
/* Convert the dirty rectangles stored in each layer's cells to individual
* image operations for later optimization */
size_t added_ops = 0;
guac_display_plan_operation* current_op = plan->ops;
current = display->pending_frame.layers;
while (current != NULL) {
guac_display_layer_cell* cell = current->pending_frame_cells;
for (int y = 0; y < current->pending_frame_cells_height; y++) {
for (int x = 0; x < current->pending_frame_cells_width; x++) {
if (cell->dirty_size) {
/* The overall number of ops that we try to add via these
* nested loops should always exactly align with the
* anticipated count produced earlier and therefore not
* overrun the ops array at any point unless there is a bug
* in the way the original operation count was calculated */
GUAC_ASSERT(added_ops < op_count);
current_op->layer = current;
current_op->type = GUAC_DISPLAY_PLAN_OPERATION_IMG;
current_op->dest = cell->dirty;
current_op->dirty_size = cell->dirty_size;
current_op->last_frame = cell->last_frame;
current_op->current_frame = frame_end;
cell->related_op = current_op;
cell->dirty_size = 0;
cell->last_frame = frame_end;
current_op++;
added_ops++;
}
else
cell->related_op = NULL;
cell++;
}
}
current = current->pending_frame.next;
}
/* At this point, the number of operations added should exactly match the
* predicted quantity */
GUAC_ASSERT(added_ops == op_count);
/* Worker threads must be aware of end-of-frame to know when to send sync,
* etc. Noticing that the operation queue is empty is insufficient, as the
* queue may become empty while a frame is in progress if the worker
* threads happen to be processing things quickly. */
current_op->type = GUAC_DISPLAY_PLAN_END_FRAME;
return plan;
}
void guac_display_plan_free(guac_display_plan* plan) {
guac_mem_free(plan->ops);
guac_mem_free(plan);
}
void guac_display_plan_apply(guac_display_plan* plan) {
guac_display* display = plan->display;
guac_display_plan_operation* op = plan->ops;
for (int i = 0; i < plan->length; i++) {
guac_fifo_enqueue(&display->ops, op++);
}
}

385
src/libguac/display-plan.h Normal file
View File

@ -0,0 +1,385 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_DISPLAY_PLAN_H
#define GUAC_DISPLAY_PLAN_H
#include "guacamole/display.h"
#include "guacamole/rect.h"
#include "guacamole/timestamp.h"
#include <stdint.h>
#include <unistd.h>
/**
* The width of an update which should be considered negible and thus
* trivial overhead compared to the cost of two updates.
*/
#define GUAC_DISPLAY_NEGLIGIBLE_WIDTH 64
/**
* The height of an update which should be considered negible and thus
* trivial overhead compared to the cost of two updates.
*/
#define GUAC_DISPLAY_NEGLIGIBLE_HEIGHT 64
/**
* The proportional increase in cost contributed by transfer and processing of
* image data, compared to processing an equivalent amount of client-side
* data.
*/
#define GUAC_DISPLAY_DATA_FACTOR 128
/**
* The maximum width to allow when combining any pair of rendering operations
* into a single operation, in pixels. This value is intended to be large
* enough to avoid unnecessarily increasing the number of drawing operations,
* yet also small enough to allow larger updates to be easily parallelized via
* the worker threads.
*/
#define GUAC_DISPLAY_MAX_COMBINED_WIDTH 512
/**
* The maximum height to allow when combining any pair of rendering operations
* into a single operation, in pixels. This value is intended to be large
* enough to avoid unnecessarily increasing the number of drawing operations,
* yet also small enough to allow larger updates to be easily parallelized via
* the worker threads.
*/
#define GUAC_DISPLAY_MAX_COMBINED_HEIGHT 512
/**
* The base cost of every update. Each update should be considered to have
* this starting cost, plus any additional cost estimated from its
* content.
*/
#define GUAC_DISPLAY_BASE_COST 4096
/**
* An increase in cost is negligible if it is less than
* 1/GUAC_DISPLAY_NEGLIGIBLE_INCREASE of the old cost.
*/
#define GUAC_DISPLAY_NEGLIGIBLE_INCREASE 4
/**
* The framerate which, if exceeded, indicates that JPEG is preferred.
*/
#define GUAC_DISPLAY_JPEG_FRAMERATE 3
/**
* Minimum JPEG bitmap size (area). If the bitmap is smaller than this threshold,
* it should be compressed as a PNG image to avoid the JPEG compression tax.
*/
#define GUAC_DISPLAY_JPEG_MIN_BITMAP_SIZE 4096
/**
* The JPEG compression min block size, as the exponent of a power of two. This
* defines the optimal rectangle block size factor for JPEG compression.
* Usually 8x8 would suffice, but we use 16x16 here to reduce the occurrence of
* ringing artifacts further.
*/
#define GUAC_SURFACE_JPEG_BLOCK_SIZE 4
/**
* The WebP compression min block size, as the exponent of a power of two. This
* defines the optimal rectangle block size factor for WebP compression. WebP
* does utilize variable block size, but ensuring a block size factor reduces
* any noise on the image edges.
*/
#define GUAC_SURFACE_WEBP_BLOCK_SIZE 3
/**
* The number of hash buckets within each guac_display_plan.
*/
#define GUAC_DISPLAY_PLAN_OPERATION_INDEX_SIZE 0x10000
/**
* Hash function which hashes a larger, 64-bit hash into a 16-bit hash that
* will fit within GUAC_DISPLAY_PLAN_OPERATION_INDEX_SIZE. Note that the random
* distribution of this hash relies entirely on the random distribution of the
* value being hashed.
*/
#define GUAC_DISPLAY_PLAN_OPERATION_HASH(hash) (\
( hash & 0xFFFF) \
^ ((hash >> 16) & 0xFFFF) \
^ ((hash >> 32) & 0xFFFF) \
^ ((hash >> 48) & 0xFFFF) \
)
/**
* The type of a graphical operation that may be part of a guac_display_plan.
*/
typedef enum guac_display_plan_operation_type {
/**
* Do nothing (no-op).
*/
GUAC_DISPLAY_PLAN_OPERATION_NOP = 0,
/**
* Copy image data from the associated source rect to the destination rect.
*/
GUAC_DISPLAY_PLAN_OPERATION_COPY,
/**
* Fill a rectangular region of the destination layer with the source
* color.
*/
GUAC_DISPLAY_PLAN_OPERATION_RECT,
/**
* Draw arbitrary image data to the destination rect.
*/
GUAC_DISPLAY_PLAN_OPERATION_IMG,
/**
* Finish the frame, sending the frame boundary to all connected users.
*/
GUAC_DISPLAY_PLAN_END_FRAME
} guac_display_plan_operation_type;
/**
* Any one of several operations that may be contained in a guac_display_plan.
*/
typedef struct guac_display_plan_operation {
/**
* The destination layer (recipient of graphical output/changes).
*/
guac_display_layer* layer;
/**
* The operation being performed on the destination layer.
*/
guac_display_plan_operation_type type;
/**
* The location within the destination layer that will receive these
* changes.
*/
guac_rect dest;
/**
* The approximate number of pixels that have actually changed as a result
* of this operation. This value will not necessarily be the same as the
* area of the destination rect if some pixels remain unchanged.
*/
size_t dirty_size;
/**
* The timestamp of the last frame that made any change within the
* destination rect of the destination layer.
*/
guac_timestamp last_frame;
/**
* The timestamp of the change being made. This will be the timestamp of
* the frame at the time the frame was ended, not the timestamp of the
* server at the time this operation was added to the plan.
*/
guac_timestamp current_frame;
union {
/**
* The color that should be used to fill the destination rect. This
* value applies only to GUAC_DISPLAY_PLAN_OPERATION_RECT operations.
*/
uint32_t color;
/**
* The rectangle that should be copied to the destination rect. This
* value applies only to GUAC_DISPLAY_PLAN_OPERATION_COPY operations.
*/
guac_rect rect;
} src;
} guac_display_plan_operation;
/**
* A guac_display_plan_operation that has been hashed and stored within a
* guac_display_plan.
*/
typedef struct guac_display_plan_indexed_operation {
/**
* The operation.
*/
guac_display_plan_operation* op;
/**
* The hash value associated with the operation. This hash value is derived
* from the actual image contents of the region that was changed, using the
* new contents of that region. The intent of this hash is to allow
* operations to be quickly located based on the output they will produce,
* such that image draw operations can be automatically replaced with
* simple copies if they reuse data from elsewhere in a layer.
*/
uint64_t hash;
} guac_display_plan_indexed_operation;
/**
* The set of operations required to transform the display state from what each
* user currently sees (the previous frame) to the current state of the
* guac_display (the current frame). The operations within a plan are quickly
* generated based on simple image comparisons, and are then refined by an
* optimizer based on estimated costs.
*/
typedef struct guac_display_plan {
/**
* The display that this plan was created for.
*/
guac_display* display;
/**
* The time that the frame ended.
*/
guac_timestamp frame_end;
/**
* Array of all operations that should be applied, in order. The operations
* in this array do not overlap nor depend on each other. They may be
* safely reordered without any impact on the image that results from
* applying those operations.
*/
guac_display_plan_operation* ops;
/**
* The number of operations stored in the ops array.
*/
size_t length;
/**
* Index of operations in the plan by their image contents. Only operations
* that can be easily stored without collisions will be represented here.
*/
guac_display_plan_indexed_operation ops_by_hash[GUAC_DISPLAY_PLAN_OPERATION_INDEX_SIZE];
} guac_display_plan;
/**
* Creates a new guac_display_plan representing the changes necessary to
* transform the current remote display state seen by each connected user (the
* previous frame) to the current local display state represented by the
* guac_display (the current frame). The actual operations within the plan are
* chosen based on the result of passing the naive set of operations through an
* optimizer.
*
* There are cases where no plan will be generated. If no changes have occurred
* since the last frame, or if the last frame is still being encoded by the
* guac_display, NULL is returned. In the event that NULL is returned but
* changes have been made, those changes will eventually be automatically
* picked up after the currently-pending frame has finished encoded.
*
* The returned guac_display_plan must eventually be manually freed by a call
* to guac_display_plan_free().
*
* IMPORTANT: The calling thread must already hold the write lock for the
* display's pending_frame.lock, and must at least hold the read lock for the
* display's last_frame.lock.
*
* @param display
* The guac_display to create a plan for.
*
* @return
* A newly-allocated guac_display_plan representing the changes necessary
* to transform the current remote display state to that of the local
* guac_display, or NULL if no plan could be created. If non-NULL, this
* value must eventually be freed by a call to guac_display_plan_free().
*/
guac_display_plan* PFW_LFR_guac_display_plan_create(guac_display* display);
/**
* Frees all memory associated with the given guac_display_plan.
*
* @param plan
* The plan to free.
*/
void guac_display_plan_free(guac_display_plan* plan);
/**
* Walks through all operations currently in the given guac_display_plan,
* replacing draw operations with simple rects wherever draws consist only of a
* single color.
*
* @param plan
* The guac_display_plan to modify.
*/
void PFR_guac_display_plan_rewrite_as_rects(guac_display_plan* plan);
/**
* Walks through all operations currently in the given guac_display_plan,
* storing the hashes of each outstanding draw operation within ops_by_hash.
* This function must be invoked before guac_display_plan_rewrite_as_copies()
* can be used for the current pending frame.
*
* @param plan
* The guac_display_plan to index.
*/
void PFR_guac_display_plan_index_dirty_cells(guac_display_plan* plan);
/**
* Walks through all operations currently in the given guac_display_plan,
* replacing draw operations with simple copies wherever draws can be rewritten
* as copies that pull image data from the previous frame. The display plan
* must first be indexed by guac_display_plan_index_dirty_cells() before this
* function can be used.
*
* @param plan
* The guac_display_plan to modify.
*/
void PFR_LFR_guac_display_plan_rewrite_as_copies(guac_display_plan* plan);
/**
* Walks through all operations currently in the given guac_display_plan,
* combining horizontally-adjacent operations wherever doing so appears to be
* more efficient than performing those operations separately.
*
* @param plan
* The guac_display_plan to modify.
*/
void PFW_guac_display_plan_combine_horizontally(guac_display_plan* plan);
/**
* Walks through all operations currently in the given guac_display_plan,
* combining vertically-adjacent operations wherever doing so appears to be
* more efficient than performing those operations separately.
*
* @param plan
* The guac_display_plan to modify.
*/
void PFW_guac_display_plan_combine_vertically(guac_display_plan* plan);
/**
* Enqueues all operations from the given plan within the operation FIFO used
* by the worker threads of the display associated with that plan. The
* display's worker threads will immediately begin picking up and performing
* these operations, with the final operation resulting in a frame boundary
* ("sync" instruction) being sent to connected users.
*
* @param plan
* The guac_display_plan to apply.
*/
void guac_display_plan_apply(guac_display_plan* plan);
#endif

704
src/libguac/display-priv.h Normal file
View File

@ -0,0 +1,704 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_DISPLAY_PRIV_H
#define GUAC_DISPLAY_PRIV_H
#include "display-plan.h"
#include "guacamole/client.h"
#include "guacamole/display.h"
#include "guacamole/fifo.h"
#include "guacamole/rect.h"
#include "guacamole/socket.h"
#include <pthread.h>
/**
* The maximum amount of time to wait after flushing a frame when compensating
* for client-side processing delays, in milliseconds. If a connected client is
* taking longer than this amount of additional time to process a received
* frame, processing lag compensation will be only partial (to avoid delaying
* further processing without bound for extremely slow clients).
*/
#define GUAC_DISPLAY_MAX_LAG_COMPENSATION 500
/*
* IMPORTANT: All functions defined within the internals of guac_display that
* DO NOT acquire locks on their own are given prefixes based on whether they
* access or modify the pending frame, last frame, or both. It is the
* responsibility of the caller of such functions to ensure that the required
* locks are either held or not relevant.
*
* The prefixes that may be added to function names are:
*
* "PFR_"
* The function reads (but does not write) the state of the pending frame.
* This prefix and "PFW_" are mutually-exclusive.
*
* "PFW_"
* The function writes (and possibly reads) the state of the pending frame.
* This prefix and "PFW_" are mutually-exclusive.
*
* "LFR_"
* The function reads (but does not write) the state of the last frame.
* This prefix and "LFW_" are mutually-exclusive.
*
* "LFW_"
* The function writes (and possibly reads) the state of the last frame.
* This prefix and "LFR_" are mutually-exclusive.
*
* Any functions lacking these prefixes either do not access last/pending
* frames in any way or take care of acquiring/releasing locks entirely on
* their own.
*
* These conventions are used for all functions in the internals of
* guac_display, not just those defined in this header.
*/
/*
* IMPORTANT: In cases where a single thread must acquire BOTH the pending
* frame lock and the last frame lock, the pending frame lock MUST be acquired
* first to maintain consistent lock order and avoid deadlock conditions. All
* functions within guac_display will follow this order.
*/
/**
* The size of the image tiles (cells) that will be used to track changes to
* each layer, including gathering framerate statistics and performing indexing
* based on contents. Each side of each cell will consist of this many pixels.
*
* IMPORTANT: The hashing algorithm used to search the previous frame for
* content in the pending frame that has been reused (ie: scrolling) strongly
* depends on this value being 64. Any adjustment to this value will require
* corresponding and careful changes to the hashing algorithm.
*/
#define GUAC_DISPLAY_CELL_SIZE 64
/**
* The exponent of the power-of-two value that dictates the size of the image
* tiles (cells) that will be used to track changes to each layer
* (GUAC_DISPLAY_CELL_SIZE).
*/
#define GUAC_DISPLAY_CELL_SIZE_EXPONENT 6
/**
* The amount that the width/height of internal storage for graphical data
* should be rounded up to avoid unnecessary reallocations and copying.
*/
#define GUAC_DISPLAY_RESIZE_FACTOR 64
/**
* Given the width (or height) of a layer in pixels, calculates the width (or
* height) of that layer's pending_frame_cells array in cells.
*
* NOTE: It is not necessary to recalculate these values except when resizing a
* layer. In all other cases, the width/height of a layer in cells can be found
* in the pending_frame_cells_width and pending_frame_cells_height members
* respectively.
*
* @param pixels
* The width or height of the layer, in pixels.
*
* @return
* The width or height of that layer's pending_frame_cells array, in cells.
*/
#define GUAC_DISPLAY_CELL_DIMENSION(pixels) \
((pixels + GUAC_DISPLAY_CELL_SIZE - 1) / GUAC_DISPLAY_CELL_SIZE)
/**
* The size of the operation FIFO read by the display worker threads. This
* value is the number of operation slots in the FIFO, not bytes. The amount of
* space currently specified here is roughly sufficient 8 worst-case frames
* worth of outstanding operations.
*/
#define GUAC_DISPLAY_WORKER_FIFO_SIZE ( \
GUAC_DISPLAY_MAX_WIDTH * GUAC_DISPLAY_MAX_HEIGHT \
/ GUAC_DISPLAY_CELL_SIZE \
/ GUAC_DISPLAY_CELL_SIZE \
* 8)
/**
* Returns the memory address of the given rectangle within the mutable image
* buffer of the given guac_display_layer_state, where the upper-left corner of
* the given buffer is (0, 0). If the memory address cannot be calculated
* because doing so would overflow the maximum value of a size_t, execution of
* the current process is automatically aborted.
*
* IMPORTANT: No checks are performed on whether the rectangle extends beyond
* the bounds of the buffer, including considering whether the left/top
* position of the rectangle is negative. If the rectangle has not already been
* contrained to be within the bounds of the buffer, such checks must be
* performed before dereferencing the value returned by this macro.
*
* @param layer_state
* The guac_display_layer_state associated with the image buffer within
* which the address of the given rectangle should be determined.
*
* @param rect
* The rectangle to determine the offset of.
*
* @return
* The memory address of the given rectangle within the buffer of the given
* layer state.
*/
#define GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(layer_state, rect) \
GUAC_RECT_MUTABLE_BUFFER(rect, (layer_state).buffer, (layer_state).buffer_stride, GUAC_DISPLAY_LAYER_RAW_BPP)
/**
* Returns the memory address of the given rectangle within the immutable
* (const) image buffer of the given guac_display_layer_state, where the
* upper-left corner of the given buffer is (0, 0). If the memory address
* cannot be calculated because doing so would overflow the maximum value of a
* size_t, execution of the current process is automatically aborted.
*
* IMPORTANT: No checks are performed on whether the rectangle extends beyond
* the bounds of the buffer, including considering whether the left/top
* position of the rectangle is negative. If the rectangle has not already been
* contrained to be within the bounds of the buffer, such checks must be
* performed before dereferencing the value returned by this macro.
*
* @param layer_state
* The guac_display_layer_state associated with the image buffer within
* which the address of the given rectangle should be determined.
*
* @param rect
* The rectangle to determine the offset of.
*
* @return
* The memory address of the given rectangle within the buffer of the given
* layer state.
*/
#define GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(layer_state, rect) \
GUAC_RECT_CONST_BUFFER(rect, (layer_state).buffer, (layer_state).buffer_stride, GUAC_DISPLAY_LAYER_RAW_BPP)
/**
* Approximation of how often a region of a layer is modified, as well as what
* changes have been made to that region since the last frame. This information
* is used to help advise future optimizations, such as whether lossy
* compression is appropriate and whether parts of the layer can be copied from
* other regions rather than resend image data.
*/
typedef struct guac_display_layer_cell {
/**
* The last time this particular cell was part of a frame (used to
* calculate framerate).
*/
guac_timestamp last_frame;
/**
* The region of this cell that has been modified since the last frame was
* flushed. If the cell has not been modified at all, this will be an empty
* rect.
*/
guac_rect dirty;
/**
* The rough number of pixels in the dirty rect that have been modified. If
* the cell has not been modified at all, this will be zero.
*/
size_t dirty_size;
/**
* The display plan operation that is associated with this cell. If a
* display plan is not currently being created or optimized, this will be
* NULL.
*/
guac_display_plan_operation* related_op;
} guac_display_layer_cell;
/**
* The state of a Guacamole layer or buffer at some point in time. Within
* guac_display_layer, copies of this structure are used to represent the
* previous frame and the current, in-progress frame. The previous and
* in-progress frames are compared during flush to determine what graphical
* operations need to be sent to connected clients to efficiently transform the
* remote display from its previous state to the now-current state.
*
* IMPORTANT: The lock of the corresponding guac_display_state must be acquired
* before reading or modifying the values of any member of this structure.
*/
typedef struct guac_display_layer_state {
/**
* The width of this layer in pixels.
*/
int width;
/**
* The height of this layer in pixels.
*/
int height;
/**
* The layer which contains this layer. This is only applicable to visible
* (non-buffer) layers which are not the default layer.
*/
const guac_layer* parent;
/**
* The X coordinate of the upper-left corner of this layer, in pixels,
* relative to its parent layer. This is only applicable to visible
* (non-buffer) layers which are not the default layer.
*/
int x;
/**
* The Y coordinate of the upper-left corner of this layer, in pixels,
* relative to its parent layer. This is only applicable to visible
* (non-buffer) layers which are not the default layer.
*/
int y;
/**
* The Z-order of this layer, relative to sibling layers. This is only
* applicable to visible (non-buffer) layers which are not the default
* layer.
*/
int z;
/**
* The level of opacity applied to this layer. Fully opaque is 255, while
* fully transparent is 0. This is only applicable to visible (non-buffer)
* layers which are not the default layer.
*/
int opacity;
/**
* The number of simultaneous touches that this surface can accept, where 0
* indicates that the surface does not support touch events at all.
*/
int touches;
/**
* Non-zero if all graphical updates for this surface should use lossless
* compression, 0 otherwise. By default, newly-created surfaces will use
* lossy compression when heuristics determine it is appropriate.
*/
int lossless;
/**
* The raw, 32-bit buffer of ARGB image data. If the layer was allocated as
* opaque, the alpha channel of each ARGB pixel will not be considered when
* compositing or when encoding images.
*
* So that large regions of image data can be easily compared, a consistent
* value for the alpha channel SHOULD be provided so that each 32-bit pixel
* can be compared without having to separately masking the channel.
* Optimizations within guac_display, including scroll detection, may
* assume that the alpha channel can always be considered when comparing
* pixel values for equivalence.
*/
unsigned char* buffer;
/**
* The width of the image data, in pixels. This is not necessarily the same
* as the width of the layer.
*/
int buffer_width;
/**
* The height of the image data, in pixels. This is not necessarily the
* same as the height of the layer.
*/
int buffer_height;
/**
* The number of bytes in each row of image data. This is not necessarily
* equivalent to 4 * width.
*/
size_t buffer_stride;
/**
* The approximate rectangular region containing all pixels within this
* layer that have been modified since the frame that occurred before this
* frame. If the layer was not modified, this will be an empty rect (zero
* width or zero height).
*/
guac_rect dirty;
/* ---------------- LAYER LIST POINTERS ---------------- */
/**
* The layer immediately prior to this layer within the list containing
* this layer, or NULL if this is the first layer/buffer in the list.
*/
guac_display_layer* prev;
/**
* The layer immediately following this layer within the list containing
* this layer, or NULL if this is the last layer/buffer in the list.
*/
guac_display_layer* next;
} guac_display_layer_state;
struct guac_display_layer {
/**
* The guac_display instance that allocated this layer/buffer.
*/
guac_display* display;
/**
* The Guacamole layer (or buffer) that this guac_display_layer will draw
* to when flushing a frame.
*
* NOTE: This value is set only during allocation and may safely be
* accessed without acquiring the overall layer lock.
*/
const guac_layer* layer;
/**
* Whether the graphical data that will be written to this layer/buffer
* will only ever be opaque (no alpha channel). Compositing of graphical
* updates can be faster when no alpha channel need be considered.
*/
int opaque;
/* ---------------- LAYER PREVIOUS FRAME STATE ---------------- */
/**
* The state of this layer when the last frame was flushed to connected clients.
*
* IMPORTANT: The display-level last_frame.lock MUST be acquired before
* modifying or reading this member.
*/
guac_display_layer_state last_frame;
/**
* Off-screen buffer storing the contents of the previously-rendered frame
* for later use. If graphical updates are recognized as reusing data from
* a previous frame, that data will be copied from this buffer. Doing this
* simplifies the copy operation (there is no longer any need to perform
* those copies in a specific order) and ensures the copies are efficient
* on the client side (copying from one part of a graphical surface to
* another part of the same surface can be inefficient, particularly if the
* regions overlap). In practice, there is ample time between frames for
* the client to copy a layer's current contents to an off-screen buffer
* while awaiting the next frame.
*
* NOTE: This value is set only during allocation and may safely be
* accessed without acquiring the display-level last_frame.lock.
*/
guac_layer* last_frame_buffer;
/* ---------------- LAYER PENDING FRAME STATE ---------------- */
/**
* The upcoming state of this layer when the current, in-progress frame is
* flushed to connected clients.
*
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
* modifying or reading this member.
*/
guac_display_layer_state pending_frame;
/**
* The Cairo context and surface containing the graphical data of the
* pending frame. The actual underlying buffer and details of the graphical
* surface are also available via pending_frame_raw_context.
*
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
* modifying or reading this member.
*/
guac_display_layer_cairo_context pending_frame_cairo_context;
/**
* The raw underlying buffer and details of the surface containing the
* graphical data of the pending frame. A Cairo context and surface backed
* by this buffer are also available via pending_frame_cairo_context.
*
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
* modifying or reading this member.
*/
guac_display_layer_raw_context pending_frame_raw_context;
/**
* A two-dimensional array of square tiles representing the nature of
* changes made to corresponding regions of the display. This is used both
* to track how frequently certain regions are being updated (to help
* inform whether lossy compression is appropriate), to track what parts of
* the frame have actually changed, and to aid in determining whether
* adjacent updated regions should be combined into a single update.
*
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
* modifying or reading this member.
*/
guac_display_layer_cell* pending_frame_cells;
/**
* The width of the pending_frame_cells array, in cells.
*
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
* modifying or reading this member.
*/
size_t pending_frame_cells_width;
/**
* The height of the pending_frame_cells array, in cells.
*
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
* modifying or reading this member.
*/
size_t pending_frame_cells_height;
};
typedef struct guac_display_state {
/**
* Lock that guards concurrent access to any member of ANY STRUCTURE that
* relates to this guac_display_state, including the members of this
* structure. Unless explicitly documented otherwise, this lock MUST be
* acquired before accessing or modifying the members of this
* guac_display_state or any nested structure.
*/
guac_rwlock lock;
/**
* The specific point in time that this guac_display_state represents.
*/
guac_timestamp timestamp;
/**
* All layers and buffers that were part of the display at the time that
* the frame/snapshot represented by this guac_display_state was updated.
*
* NOTE: For each guac_display, there are two distinct lists of layers: the
* last frame layer list and the pending frame layer list:
*
* LAST FRAME LAYER LIST
*
* - HEAD: display->last_frame.layers
* - NEXT: layer->last_frame.next
* - PREV: layer->last_frame.prev
*
* PENDING LAYER LIST
*
* - HEAD: display->pending_frame.layers
* - NEXT: layer->pending_frame.next
* - PREV: layer->pending_frame.prev
*
* Existing layers are deleted only at the time a frame is flushed when a
* layer in the last frame layer list is found to no longer exist in the
* pending frame layer list. The same goes for the addition of new layers:
* they are added only during flush when a layer that was not present in
* the last frame layer list is found to be present in the pending frame
* layer list.
*/
guac_display_layer* layers;
/**
* The X coordinate of the hotspot of the mouse cursor. The cursor image is
* stored/updated via the cursor_buffer member of guac_display.
*/
int cursor_hotspot_x;
/**
* The Y coordinate of the hotspot of the mouse cursor. The cursor image is
* stored/updated via the cursor_buffer member of guac_display.
*/
int cursor_hotspot_y;
/**
* The user that moved or clicked the mouse. This is used to ensure we
* don't attempt to synchronize an out-of-date mouse position to the user
* that is actively moving the mouse.
*/
guac_user* cursor_user;
/**
* The X coordinate of the mouse cursor.
*/
int cursor_x;
/**
* The Y coordinate of the mouse cursor.
*/
int cursor_y;
/**
* The mask representing the states of all mouse buttons.
*/
int cursor_mask;
/**
* The number of logical frames that have been rendered to this display
* state since the previous display state.
*/
unsigned int frames;
} guac_display_state;
struct guac_display {
/* NOTE: Any member of this structure that requires protection against
* concurrent access is protected by its own lock. The overall display does
* not have nor need a top-level lock. */
/**
* The client associated with this display.
*/
guac_client* client;
/* ---------------- DISPLAY FRAME STATES ---------------- */
/**
* The state of this display at the time the last frame was sent to
* connected users.
*/
guac_display_state last_frame;
/**
* The pending state of this display that will become the next frame once
* it is sent to connected users.
*/
guac_display_state pending_frame;
/**
* Whether the pending_frame has been modified in any way since the last
* frame.
*
* IMPORTANT: The pending_frame.lock MUST be acquired before modifying or
* reading this member.
*/
int pending_dirty;
/* ---------------- WELL-KNOWN LAYERS / BUFFERS ---------------- */
/**
* The default layer of the client display.
*/
guac_display_layer* default_layer;
/**
* The buffer storing the current mouse cursor. The hotspot position within
* the cursor is stored within cursor_hotspot_x and cursor_hotspot_y of
* guac_display_state.
*/
guac_display_layer* cursor_buffer;
/* ---------------- FRAME ENCODING WORKER THREADS ---------------- */
/**
* The number of worker threads in the worker_threads array.
*/
int worker_thread_count;
/**
* Pool of worker threads that automatically pull from the ops FIFO,
* sending corresponding Guacamole instructions to all connected clients.
*/
pthread_t* worker_threads;
/**
* FIFO of all graphical operations required to transform the remote
* display state from the previous frame to the next frame. Operations
* added to this FIFO will automatically be pulled and processed by a
* worker thread.
*/
guac_fifo ops;
/**
* Lock which ensures instructions that make use of a layer's current path
* (such as "rect" and "cfill") do not get inadvertently interleaved.
* Interleaving of path instructions can result in those paths not matching
* the expectatiosn of subsequent instructions, causing graphical
* artifacts.
*/
pthread_mutex_t op_path_lock;
/**
* Storage for any items within the ops fifo.
*/
guac_display_plan_operation ops_items[GUAC_DISPLAY_WORKER_FIFO_SIZE];
/**
* The current number of active worker threads.
*
* IMPORTANT: This member must only be accessed or modified while the ops
* FIFO is locked.
*/
unsigned int active_workers;
};
/**
* Allocates and inserts a new element into the given linked list of display
* layers, associating it with the given layer and surface.
*
* @param head
* A pointer to the head pointer of the list of layers. The head pointer
* will be updated by this function to point to the newly-allocated
* display layer.
*
* @param layer
* The Guacamole layer to associated with the new display layer.
*
* @param opaque
* Non-zero if the new layer will only ever contain opaque image contents
* (the alpha channel should be ignored), zero otherwise.
*
* @return
* The newly-allocated display layer, which has been associated with the
* provided layer and surface.
*/
guac_display_layer* guac_display_add_layer(guac_display* display, guac_layer* layer, int opaque);
/**
* Removes the given layer from all linked lists containing that layer and
* frees all associated memory.
*
* @param display_layer
* The layer to remove.
*/
void guac_display_remove_layer(guac_display_layer* display_layer);
/**
* Resizes the given layer to the given dimensions, including any underlying
* image buffers.
*
* @param layer
* The layer to resize.
*
* @param width
* The new width, in pixels.
*
* @param height
* The new height, in pixels.
*/
void PFW_LFW_guac_display_layer_resize(guac_display_layer* layer,
int width, int height);
/**
* Worker thread that continuously pulls operations from the operation FIFO of
* the given guac_display, applying those operations by seding corresponding
* instructions to connected clients.
*
* @param data
* A pointer to the guac_display.
*
* @return
* Always NULL.
*/
void* guac_display_worker_thread(void* data);
#endif

View File

@ -0,0 +1,602 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "display-plan.h"
#include "display-priv.h"
#include "guacamole/client.h"
#include "guacamole/display.h"
#include "guacamole/fifo.h"
#include "guacamole/layer.h"
#include "guacamole/protocol.h"
#include "guacamole/rect.h"
#include "guacamole/rwlock.h"
#include "guacamole/socket.h"
#include "guacamole/timestamp.h"
#include <inttypes.h>
#include <limits.h>
#include <cairo/cairo.h>
#include <pthread.h>
/**
* Sends the contents of the given dirty rectangle from the given layer using
* lossless PNG compression. The resulting instructions will be sent over the
* client-wide broadcast socket associated with the given layer. The graphical
* contents sent will be pulled from the layer's last_frame buffer. If sending
* the contents of a pending frame, that pending frame must have been copied
* over to the last_frame buffer before calling this function.
*
* @param display_layer
* The layer whose data should be sent to connected users.
*
* @param dirty
* The region of the layer that should be sent.
*/
static void LFR_guac_display_layer_flush_to_png(guac_display_layer* display_layer,
guac_rect* dirty) {
guac_display* display = display_layer->display;
guac_client* client = display->client;
guac_socket* socket = client->socket;
const guac_layer* layer = display_layer->layer;
/* Get Cairo layer for specified rect */
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(display_layer->last_frame, *dirty);
cairo_surface_t* rect;
/* Use RGB24 if the image is fully opaque */
if (display_layer->opaque)
rect = cairo_image_surface_create_for_data(buffer,
CAIRO_FORMAT_RGB24, guac_rect_width(dirty),
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
/* Otherwise ARGB32 is needed */
else {
rect = cairo_image_surface_create_for_data(buffer,
CAIRO_FORMAT_ARGB32, guac_rect_width(dirty),
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
/* Clear destination rect first */
pthread_mutex_lock(&display->op_path_lock);
guac_protocol_send_rect(socket, layer,
dirty->left, dirty->top,
guac_rect_width(dirty), guac_rect_height(dirty));
guac_protocol_send_cfill(socket, GUAC_COMP_ROUT, layer,
0x00, 0x00, 0x00, 0xFF);
pthread_mutex_unlock(&display->op_path_lock);
}
/* Send PNG for rect */
guac_client_stream_png(client, socket, GUAC_COMP_OVER,
layer, dirty->left, dirty->top, rect);
cairo_surface_destroy(rect);
}
/**
* Returns an appropriate quality between 0 and 100 for lossy encoding
* depending on the current processing lag calculated for the given client.
*
* @param client
* The client for which the lossy quality is being calculated.
*
* @return
* A value between 0 and 100 inclusive which seems appropriate for the
* client based on lag measurements.
*/
static int guac_display_suggest_quality(guac_client* client) {
int lag = guac_client_get_processing_lag(client);
/* Scale quality linearly from 90 to 30 as lag varies from 20ms to 80ms */
int quality = 90 - (lag - 20);
/* Do not exceed 90 for quality */
if (quality > 90)
return 90;
/* Do not go below 30 for quality */
if (quality < 30)
return 30;
return quality;
}
/**
* Sends the contents of the given dirty rectangle from the given layer using
* lossy JPEG compression. The resulting instructions will be sent over the
* client-wide broadcast socket associated with the given layer. The graphical
* contents sent will be pulled from the layer's last_frame buffer. If sending
* the contents of a pending frame, that pending frame must have been copied
* over to the last_frame buffer before calling this function.
*
* @param layer
* The layer whose data should be sent to connected users.
*
* @param dirty
* The region of the layer that should be sent.
*/
static void LFR_guac_display_layer_flush_to_jpeg(guac_display_layer* display_layer,
guac_rect* dirty) {
guac_display* display = display_layer->display;
guac_client* client = display->client;
guac_socket* socket = client->socket;
const guac_layer* layer = display_layer->layer;
guac_rect max = {
.left = 0,
.top = 0,
.right = display_layer->last_frame.width,
.bottom = display_layer->last_frame.height
};
/* Expand the dirty rect size to fit in a grid with cells equal to the
* minimum JPEG block size */
guac_rect_align(dirty, GUAC_SURFACE_JPEG_BLOCK_SIZE);
guac_rect_constrain(dirty, &max);
/* Get Cairo layer for specified rect */
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(display_layer->last_frame, *dirty);
cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer,
CAIRO_FORMAT_RGB24, guac_rect_width(dirty),
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
/* Send JPEG for rect */
guac_client_stream_jpeg(client, socket, GUAC_COMP_OVER, layer,
dirty->left, dirty->top, rect,
guac_display_suggest_quality(client));
cairo_surface_destroy(rect);
}
/**
* Sends the contents of the given dirty rectangle from the given layer using
* WebP compression. Whether that WebP compression is lossless depends on the
* lossless setting of the layer's last frame. The resulting instructions will
* be sent over the client-wide broadcast socket associated with the given
* layer. The graphical contents sent will be pulled from the layer's
* last_frame buffer. If sending the contents of a pending frame, that pending
* frame must have been copied over to the last_frame buffer before calling
* this function.
*
* @param layer
* The layer whose data should be sent to connected users.
*
* @param dirty
* The region of the layer that should be sent.
*/
static void LFR_guac_display_layer_flush_to_webp(guac_display_layer* display_layer,
guac_rect* dirty) {
guac_display* display = display_layer->display;
guac_client* client = display->client;
guac_socket* socket = client->socket;
const guac_layer* layer = display_layer->layer;
guac_rect max = {
.left = 0,
.top = 0,
.right = display_layer->last_frame.width,
.bottom = display_layer->last_frame.height
};
/* Expand the dirty rect size to fit in a grid with cells equal to the
* minimum WebP block size */
guac_rect_align(dirty, GUAC_SURFACE_WEBP_BLOCK_SIZE);
guac_rect_constrain(dirty, &max);
/* Get Cairo layer for specified rect */
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(display_layer->last_frame, *dirty);
cairo_surface_t* rect;
/* Use RGB24 if the image is fully opaque */
if (display_layer->opaque)
rect = cairo_image_surface_create_for_data(buffer,
CAIRO_FORMAT_RGB24, guac_rect_width(dirty),
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
/* Otherwise ARGB32 is needed */
else
rect = cairo_image_surface_create_for_data(buffer,
CAIRO_FORMAT_ARGB32, guac_rect_width(dirty),
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
/* Send WebP for rect */
guac_client_stream_webp(client, socket, GUAC_COMP_OVER, layer,
dirty->left, dirty->top, rect,
guac_display_suggest_quality(client),
display_layer->last_frame.lossless ? 1 : 0);
cairo_surface_destroy(rect);
}
/**
* Guesses whether a rectangle within a particular layer would be better
* compressed as PNG or using a lossy format like JPEG. Positive values
* indicate PNG is likely to be superior, while negative values indicate the
* opposite.
*
* @param layer
* The layer containing the image data to check.
*
* @param rect
* The rect to check within the given layer.
*
* @return
* Positive values if PNG compression is likely to perform better than
* lossy alternatives, or negative values if PNG is likely to perform
* worse.
*/
static int LFR_guac_display_layer_png_optimality(guac_display_layer* layer,
const guac_rect* rect) {
int x, y;
int num_same = 0;
int num_different = 1;
/* Get buffer from layer */
size_t stride = layer->last_frame.buffer_stride;
const unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(layer->last_frame, *rect);
/* Image must be at least 1x1 */
if (rect->right - rect->left < 1 || rect->bottom - rect->top< 1)
return 0;
/* For each row */
for (y = rect->top; y < rect->bottom; y++) {
uint32_t* row = (uint32_t*) buffer;
uint32_t last_pixel = *(row++) | 0xFF000000;
/* For each pixel in current row */
for (x = rect->left + 1; x < rect->right; x++) {
/* Get next pixel */
uint32_t current_pixel = *(row++) | 0xFF000000;
/* Update same/different counts according to pixel value */
if (current_pixel == last_pixel)
num_same++;
else
num_different++;
last_pixel = current_pixel;
}
/* Advance to next row */
buffer += stride;
}
/* Return rough approximation of optimality for PNG compression. As PNG
* leverages lossless DEFLATE compression (which works by reducing the
* number of bytes required to represent repeated data), an approximation
* of the amount of repeated image data within the image is a reasonable
* approximation for how well an image will compress. */
return 0x100 * num_same / num_different - 0x400;
}
/**
* Returns whether the given rectangle would be optimally encoded as JPEG
* rather than PNG.
*
* @param layer
* The layer to be queried.
*
* @param rect
* The rectangle to check.
*
* @param framerate
* The rate that the region covered by the given rectangle has historically
* been being updated within the given layer, in frames per second.
*
* @return
* Non-zero if the rectangle would be optimally encoded as JPEG, zero
* otherwise.
*/
static int LFR_guac_display_layer_should_use_jpeg(guac_display_layer* layer,
const guac_rect* rect, int framerate) {
/* Do not use JPEG if lossless quality is required */
if (layer->last_frame.lossless)
return 0;
int rect_width = rect->right - rect->left;
int rect_height = rect->bottom - rect->top;
int rect_size = rect_width * rect_height;
/* JPEG is preferred if:
* - frame rate is high enough
* - image size is large enough
* - PNG is not more optimal based on image contents */
return framerate >= GUAC_DISPLAY_JPEG_FRAMERATE
&& rect_size > GUAC_DISPLAY_JPEG_MIN_BITMAP_SIZE
&& LFR_guac_display_layer_png_optimality(layer, rect) < 0;
}
/**
* Returns whether the given rectangle would be optimally encoded as WebP
* rather than PNG.
*
* @param layer
* The layer to be queried.
*
* @param rect
* The rectangle to check.
*
* @param framerate
* The rate that the region covered by the given rectangle has historically
* been being updated within the given layer, in frames per second.
*
* @return
* Non-zero if the rectangle would be optimally encoded as WebP, zero
* otherwise.
*/
static int LFR_guac_display_layer_should_use_webp(guac_display_layer* layer,
const guac_rect* rect, int framerate) {
/* Do not use WebP if not supported */
if (!guac_client_supports_webp(layer->display->client))
return 0;
/* WebP is preferred if:
* - frame rate is high enough
* - PNG is not more optimal based on image contents */
return framerate >= GUAC_DISPLAY_JPEG_FRAMERATE
&& LFR_guac_display_layer_png_optimality(layer, rect) < 0;
}
void* guac_display_worker_thread(void* data) {
int framerate;
int has_outstanding_frames = 0;
guac_display* display = (guac_display*) data;
guac_client* client = display->client;
guac_display_plan_operation op;
while (guac_fifo_dequeue_and_lock(&display->ops, &op)) {
/* NOTE: Any thread that locks the operation queue can know that there
* are no pending operations in progress if the queue is empty and
* there are no active workers */
display->active_workers++;
guac_fifo_unlock(&display->ops);
guac_rwlock_acquire_read_lock(&display->last_frame.lock);
guac_display_layer* display_layer = op.layer;
switch (op.type) {
case GUAC_DISPLAY_PLAN_OPERATION_IMG:
framerate = INT_MAX;
if (op.current_frame > op.last_frame)
framerate = 1000 / (op.current_frame - op.last_frame);
guac_rect* dirty = &op.dest;
/* TODO: Determine whether to use PNG/WebP/JPEG purely
* based on whether lossless encoding is required, the
* expected time until another frame is received (time
* since last frame), and estimated encoding times. The
* time allowed per update should be divided up
* proportionately based on the dirty_size of the update. */
/* TODO: Stream PNG/WebP/JPEG using progressive encoding such
* that a frame that is currently being encoded can be
* preempted by the next frame, with the connected client then
* simply receiving a lower-quality intermediate frame. If
* necessary, progressive encoding can be achieved by manually
* dividing images into multiple reduced-resolution stages,
* such that each image streamed is actually only one quarter
* the size of the original image. Compositing via Guacamole
* protocol instructions can reassemble those stages. */
/* Prefer WebP when reasonable */
if (LFR_guac_display_layer_should_use_webp(display_layer, dirty, framerate))
LFR_guac_display_layer_flush_to_webp(display_layer, dirty);
/* If not WebP, JPEG is the next best (lossy) choice */
else if (display_layer->opaque && LFR_guac_display_layer_should_use_jpeg(display_layer, dirty, framerate))
LFR_guac_display_layer_flush_to_jpeg(display_layer, dirty);
/* Use PNG if no lossy formats are appropriate */
else
LFR_guac_display_layer_flush_to_png(display_layer, dirty);
break;
case GUAC_DISPLAY_PLAN_OPERATION_COPY:
guac_protocol_send_copy(client->socket, display_layer->last_frame_buffer,
op.src.rect.left, op.src.rect.top, guac_rect_width(&op.src.rect), guac_rect_height(&op.src.rect),
GUAC_COMP_OVER, display_layer->layer, op.dest.left, op.dest.top);
break;
case GUAC_DISPLAY_PLAN_OPERATION_RECT:
pthread_mutex_lock(&display->op_path_lock);
guac_protocol_send_rect(client->socket, display_layer->layer,
op.dest.left, op.dest.top, guac_rect_width(&op.dest), guac_rect_height(&op.dest));
int alpha = (op.src.color & 0xFF000000) >> 24;
int red = (op.src.color & 0x00FF0000) >> 16;
int green = (op.src.color & 0x0000FF00) >> 8;
int blue = (op.src.color & 0x000000FF);
/* Clear before drawing if layer is not opaque (transparency
* will not be copied correctly otherwise) */
if (!display_layer->opaque)
guac_protocol_send_cfill(client->socket, GUAC_COMP_ROUT, display_layer->layer,
0x00, 0x00, 0x00, 0xFF);
guac_protocol_send_cfill(client->socket, GUAC_COMP_OVER, display_layer->layer,
red, green, blue, alpha);
pthread_mutex_unlock(&display->op_path_lock);
break;
case GUAC_DISPLAY_PLAN_OPERATION_NOP:
break;
case GUAC_DISPLAY_PLAN_END_FRAME:
guac_fifo_lock(&display->ops);
int other_workers_busy = (display->active_workers > 1);
guac_fifo_unlock(&display->ops);
/* If other workers are still busy, push the frame boundary
* back on the queue so that it's picked up by one of those
* workers */
if (other_workers_busy) {
guac_fifo_enqueue(&display->ops, &op);
}
/* Otherwise, we've reached the end of the frame, and this is
* the worker that will be sending that boundary to connected
* users */
else {
/* Use the amount of time that the client has been waiting
* for a frame vs. the amount of time that it took the
* client to process the most recently acknowledged frame
* to calculate the amount of additional delay required to
* allow the client to catch up. This value is used later,
* after everything else related to the frame has been
* finalized. */
int time_since_last_frame = guac_timestamp_current() - client->last_sent_timestamp;
int processing_lag = guac_client_get_processing_lag(client);
int required_wait = processing_lag - time_since_last_frame;
/* Allow connected clients to move forward with rendering */
guac_client_end_multiple_frames(client, display->last_frame.frames);
/* Commit any changed contents to client-side backing
* buffer, while also determining whether any changes have
* been made to the mouse cursor graphic. */
int cursor_modified = 0;
guac_display_layer* cursor = display->cursor_buffer;
guac_display_layer* current = display->last_frame.layers;
while (current != NULL) {
/* Save a copy of the changed region if the layer has
* been modified since the last frame */
guac_rect* dirty = &current->last_frame.dirty;
if (!guac_rect_is_empty(dirty)) {
guac_protocol_send_copy(client->socket, current->layer,
0, 0, current->last_frame.width, current->last_frame.height,
GUAC_COMP_SRC, current->last_frame_buffer, 0, 0);
/* Additionally track whether the cursor is among
* the changed layers - we will later send a
* "cursor" instruction to actually update the
* remote cursor using the graphics from the cursor
* layer */
if (current == cursor)
cursor_modified = 1;
}
current = current->last_frame.next;
}
/* Update the mouse cursor if it's been changed since the
* last frame */
if (cursor_modified) {
guac_protocol_send_cursor(client->socket,
display->last_frame.cursor_hotspot_x,
display->last_frame.cursor_hotspot_y,
cursor->layer, 0, 0,
cursor->last_frame.width,
cursor->last_frame.height);
}
/* This is now absolutely everything for the current frame,
* and it's safe to flush any outstanding data */
guac_socket_flush(client->socket);
/* Exclude local, server-side frame processing latency from
* waiting period */
int latency = (int) (guac_timestamp_current() - display->last_frame.timestamp);
if (latency >= 0) {
guac_client_log(display->client, GUAC_LOG_TRACE,
"Rendering latency: %ims (%i:1 frame)\n",
latency, display->last_frame.frames);
required_wait -= latency;
}
/* Ensure we don't wait without bound when compensating for
* client-side processing delays */
if (required_wait > GUAC_DISPLAY_MAX_LAG_COMPENSATION)
required_wait = GUAC_DISPLAY_MAX_LAG_COMPENSATION;
/* Allow connected clients to catch up if they're taking
* longer to process frames than the server is taking to
* generate them */
if (required_wait > 0) {
guac_client_log(display->client, GUAC_LOG_TRACE,
"Waiting %ims to compensate for client-side "
"processing delays.\n", required_wait);
guac_timestamp_msleep(required_wait);
}
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
has_outstanding_frames = display->pending_dirty;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
break;
}
guac_rwlock_release_lock(&display->last_frame.lock);
guac_fifo_lock(&display->ops);
display->active_workers--;
guac_fifo_unlock(&display->ops);
/* Trigger additional flush if frames were completed while we were
* still processing the previous frame */
if (has_outstanding_frames) {
guac_display_end_multiple_frames(display, 0);
has_outstanding_frames = 0;
}
}
return NULL;
}

331
src/libguac/display.c Normal file
View File

@ -0,0 +1,331 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "config.h"
#include "display-plan.h"
#include "display-priv.h"
#include "guacamole/client.h"
#include "guacamole/display.h"
#include "guacamole/fifo.h"
#include "guacamole/layer.h"
#include "guacamole/mem.h"
#include "guacamole/protocol.h"
#include "guacamole/rect.h"
#include "guacamole/rwlock.h"
#include "guacamole/socket.h"
#include "guacamole/timestamp.h"
#include "guacamole/user.h"
#ifdef __MINGW32__
#include <winbase.h>
#endif
#include <cairo/cairo.h>
#include <pthread.h>
#include <sched.h>
#include <unistd.h>
/**
* Returns the number of processors available to this process. If possible,
* limits on otherwise available processors like CPU affinity will be taken
* into account. If the number of available processors cannot be determined,
* zero is returned.
*
* @return
* The number of available processors, or zero if this value cannot be
* determined for any reason.
*/
static unsigned long guac_display_nproc() {
#if defined(HAVE_SCHED_GETAFFINITY)
/* Linux, etc. implementation leveraging sched_getaffinity() (this is
* specific to glibc and MUSL libc and is non-portable) */
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
if (sched_getaffinity(0, sizeof(cpu_set), &cpu_set) == 0) {
long cpu_count = CPU_COUNT(&cpu_set);
if (cpu_count > 0)
return cpu_count;
}
#elif defined(_SC_NPROCESSORS_ONLN)
/* Linux, etc. implementation leveraging sysconf() and _SC_NPROCESSORS_ONLN
* (which is also non-portable) */
long cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
if (cpu_count > 0)
return cpu_count;
#elif defined(__MINGW32__)
/* Windows-specific implementation (clearly also non-portable) */
unsigned long cpu_count = 0;
DWORD_PTR process_mask, system_mask;
for (GetProcessAffinityMask(GetCurrentProcess(), &process_mask, &system_mask);
process_mask != 0; process_mask >>= 1) {
if (process_mask & 1)
cpu_count++;
}
if (cpu_count > 0)
return cpu_count;
#else
/* Fallback implementation that does not query the number of CPUs available
* at all, returning an error code (as portable as it gets) */
long cpu_count = 0;
#endif
return 0;
}
guac_display* guac_display_alloc(guac_client* client) {
/* Allocate and init core properties (really just the client pointer) */
guac_display* display = guac_mem_zalloc(sizeof(guac_display));
display->client = client;
/* Init last frame and pending frame tracking */
guac_rwlock_init(&display->last_frame.lock);
guac_rwlock_init(&display->pending_frame.lock);
display->last_frame.timestamp = display->pending_frame.timestamp = guac_timestamp_current();
/* It's safe to discard const of the default layer here, as
* guac_display_free_layer() function is specifically written to consider
* the default layer as const */
display->default_layer = guac_display_add_layer(display, (guac_layer*) GUAC_DEFAULT_LAYER, 1);
display->cursor_buffer = guac_display_alloc_buffer(display, 0);
/* Init operation FIFO used by worker threads */
guac_fifo_init(&display->ops, display->ops_items,
GUAC_DISPLAY_WORKER_FIFO_SIZE, sizeof(guac_display_plan_operation));
/* Init lock specific to the GUAC_DISPLAY_PLAN_OPERATION_RECT operation */
pthread_mutex_init(&display->op_path_lock, NULL);
int cpu_count = guac_display_nproc();
if (cpu_count <= 0) {
guac_client_log(client, GUAC_LOG_WARNING, "Number of available "
"processors could not be determined. Assuming single-processor.");
cpu_count = 1;
}
else {
guac_client_log(client, GUAC_LOG_INFO, "Local system reports %i "
"processor(s) are available.", cpu_count);
}
display->worker_thread_count = cpu_count;
display->worker_threads = guac_mem_alloc(display->worker_thread_count, sizeof(pthread_t));
guac_client_log(client, GUAC_LOG_INFO, "Graphical updates will be encoded "
"using %i worker thread(s).", display->worker_thread_count);
/* Now that the core of the display has been fully initialized, it's safe
* to start the worker threads */
for (int i = 0; i < display->worker_thread_count; i++)
pthread_create(&(display->worker_threads[i]), NULL, guac_display_worker_thread, display);
return display;
}
void guac_display_free(guac_display* display) {
/* Stop further use of the operation FIFO */
guac_fifo_invalidate(&display->ops);
/* Wait for all worker threads to terminate (they should nearly immediately
* terminate following invalidation of the FIFO) */
for (int i = 0; i < display->worker_thread_count; i++)
pthread_join(display->worker_threads[i], NULL);
/* Free all layers within the pending_frame list (NOTE: This will also free
* those layers from the last_frame list) */
while (display->pending_frame.layers != NULL)
guac_display_free_layer(display->pending_frame.layers);
/* Free any remaining layers that were present only on the last_frame list
* and not on the pending_frame list */
while (display->last_frame.layers != NULL)
guac_display_free_layer(display->last_frame.layers);
pthread_mutex_destroy(&display->op_path_lock);
guac_mem_free(display->worker_threads);
guac_mem_free(display);
}
void guac_display_dup(guac_display* display, guac_socket* socket) {
guac_client* client = display->client;
guac_rwlock_acquire_read_lock(&display->last_frame.lock);
guac_display_layer* current = display->last_frame.layers;
while (current != NULL) {
const guac_layer* layer = current->layer;
guac_rect layer_bounds;
guac_display_layer_get_bounds(current, &layer_bounds);
int width = guac_rect_width(&layer_bounds);
int height = guac_rect_height(&layer_bounds);
/* Get Cairo layer for specified rect */
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(current->last_frame, layer_bounds);
cairo_surface_t* rect;
/* Use RGB24 if the image is fully opaque */
if (current->opaque)
rect = cairo_image_surface_create_for_data(buffer,
CAIRO_FORMAT_RGB24, width, height,
current->last_frame.buffer_stride);
/* Otherwise ARGB32 is needed */
else
rect = cairo_image_surface_create_for_data(buffer,
CAIRO_FORMAT_ARGB32, width, height,
current->last_frame.buffer_stride);
/* Send PNG for rect */
guac_protocol_send_size(socket, layer, width, height);
guac_client_stream_png(client, socket, GUAC_COMP_OVER, layer, 0, 0, rect);
/* Resync copy of previous frame */
guac_protocol_send_copy(socket,
layer, 0, 0, width, height,
GUAC_COMP_SRC, current->last_frame_buffer, 0, 0);
/* Resync any properties that are specific to non-buffer layers */
if (current->layer->index > 0) {
/* Resync layer opacity */
guac_protocol_send_shade(socket, current->layer,
current->last_frame.opacity);
/* Resync layer position/hierarchy */
guac_protocol_send_move(socket, current->layer,
current->last_frame.parent,
current->last_frame.x,
current->last_frame.y,
current->last_frame.z);
}
/* Resync multitouch support */
if (current->layer->index >= 0) {
guac_protocol_send_set_int(socket, current->layer,
GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH,
current->last_frame.touches);
}
current = current->last_frame.next;
}
/* Synchronize mouse cursor */
guac_display_layer* cursor = display->cursor_buffer;
guac_protocol_send_cursor(socket,
display->last_frame.cursor_hotspot_x,
display->last_frame.cursor_hotspot_y,
cursor->layer, 0, 0,
cursor->last_frame.width,
cursor->last_frame.height);
/* Synchronize mouse location */
guac_protocol_send_mouse(socket, display->last_frame.cursor_x, display->last_frame.cursor_y,
display->last_frame.cursor_mask, client->last_sent_timestamp);
guac_protocol_send_sync(socket, client->last_sent_timestamp, display->last_frame.frames);
guac_rwlock_release_lock(&display->last_frame.lock);
guac_socket_flush(socket);
}
void guac_display_notify_user_left(guac_display* display, guac_user* user) {
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
/* Update to reflect leaving user, if necessary */
if (display->pending_frame.cursor_user == user)
display->pending_frame.cursor_user = NULL;
guac_rwlock_release_lock(&display->pending_frame.lock);
}
void guac_display_notify_user_moved_mouse(guac_display* display, guac_user* user, int x, int y, int mask) {
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
display->pending_frame.cursor_user = user;
display->pending_frame.cursor_x = x;
display->pending_frame.cursor_y = y;
display->pending_frame.cursor_mask = mask;
display->pending_dirty = 1;
guac_rwlock_release_lock(&display->pending_frame.lock);
guac_display_end_multiple_frames(display, 0);
}
guac_display_layer* guac_display_default_layer(guac_display* display) {
return display->default_layer;
}
guac_display_layer* guac_display_alloc_layer(guac_display* display, int opaque) {
return guac_display_add_layer(display, guac_client_alloc_layer(display->client), opaque);
}
guac_display_layer* guac_display_alloc_buffer(guac_display* display, int opaque) {
return guac_display_add_layer(display, guac_client_alloc_buffer(display->client), opaque);
}
void guac_display_free_layer(guac_display_layer* display_layer) {
guac_display* display = display_layer->display;
const guac_layer* layer = display_layer->layer;
guac_display_remove_layer(display_layer);
if (layer->index != 0) {
guac_client* client = display->client;
guac_protocol_send_dispose(client->socket, layer);
/* As long as this isn't the display layer, it's safe to cast away the
* constness and free the underlying layer/buffer. Only the default
* layer (layer #0) is truly const. */
if (layer->index > 0)
guac_client_free_layer(client, (guac_layer*) layer);
else
guac_client_free_buffer(client, (guac_layer*) layer);
}
}

210
src/libguac/fifo.c Normal file
View File

@ -0,0 +1,210 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "guacamole/fifo.h"
#include "guacamole/flag.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
void guac_fifo_init(guac_fifo* fifo, void* items,
size_t max_items, size_t item_size) {
/* Init values describing the memory structure of the items array */
fifo->items_offset = (char*) items - (char*) fifo;
fifo->max_items = max_items;
fifo->item_size = item_size;
/* The fifo is currently empty */
guac_flag_init(&fifo->state);
guac_flag_set(&fifo->state, GUAC_FIFO_STATE_READY);
fifo->head = 0;
fifo->item_count = 0;
}
void guac_fifo_destroy(guac_fifo* fifo) {
guac_flag_destroy(&fifo->state);
}
void guac_fifo_invalidate(guac_fifo* fifo) {
guac_flag_set(&fifo->state, GUAC_FIFO_STATE_INVALID);
}
void guac_fifo_lock(guac_fifo* fifo) {
guac_flag_lock(&fifo->state);
}
void guac_fifo_unlock(guac_fifo* fifo) {
guac_flag_unlock(&fifo->state);
}
int guac_fifo_is_valid(guac_fifo* fifo) {
/* We don't need to acquire the lock here as (1) we are only reading the
* flag and (2) the flag in question is a one-way, single-use signal (it's
* only set, never cleared) */
return !(fifo->state.value & GUAC_FIFO_STATE_INVALID);
}
int guac_fifo_enqueue(guac_fifo* fifo,
const void* item) {
if (!guac_fifo_enqueue_and_lock(fifo, item))
return 0;
guac_flag_unlock(&fifo->state);
return 1;
}
int guac_fifo_enqueue_and_lock(guac_fifo* fifo,
const void* item) {
/* Block until fifo is ready for further items OR until the fifo is
* invalidated */
guac_flag_wait_and_lock(&fifo->state,
GUAC_FIFO_STATE_INVALID | GUAC_FIFO_STATE_READY);
/* Bail out if the fifo has become invalid */
if (fifo->state.value & GUAC_FIFO_STATE_INVALID) {
guac_flag_unlock(&fifo->state);
return 0;
}
/* Abort program execution entirely if the fifo reports readiness but
* somehow actually does not have available space (this should never happen
* and indicates a bug) */
if (fifo->item_count >= fifo->max_items)
abort();
/* Update count of items within the fifo, clearing the readiness flag if
* there is no longer any space for further items */
fifo->item_count++;
if (fifo->item_count == fifo->max_items)
guac_flag_clear(&fifo->state, GUAC_FIFO_STATE_READY);
/* NOTE: At this point, there are `item_count - 1` items present in the
* fifo, and `item_count - 1` is the index of the space in the items array
* that should receive the item being added (relative to head) */
/* Copy data of item buffer into last item in fifo */
size_t tail = (fifo->head + fifo->item_count - 1) % fifo->max_items;
void* tail_item = ((char*) fifo) + fifo->items_offset + fifo->item_size * tail;
memcpy(tail_item, item, fifo->item_size);
/* Advise any waiting threads that the fifo is now non-empty */
guac_flag_set(&fifo->state, GUAC_FIFO_STATE_NONEMPTY);
/* Item enqueued successfully */
return 1;
}
/**
* Dequeues a single item from the given guac_fifo, storing a copy
* of that item in the provided buffer. The event fifo MUST be non-empty. The
* state flag of the fifo MUST already be locked.
*
* @param fifo
* The guac_fifo to dequeue an item from.
*
* @param item
* The buffer that should receive a copy of the dequeued item.
*/
static void dequeue(guac_fifo* fifo, void* item) {
/* Copy data of first item in fifo to provided output buffer */
void* head_item = ((char*) fifo) + fifo->items_offset + fifo->item_size * fifo->head;
memcpy(item, head_item, fifo->item_size);
/* Advance to next item in fifo, if any */
fifo->item_count--;
fifo->head = (fifo->head + 1) % fifo->max_items;
/* Keep state flag up-to-date with respect to non-emptiness ... */
if (fifo->item_count == 0)
guac_flag_clear(&fifo->state, GUAC_FIFO_STATE_NONEMPTY);
/* ... and readiness for further items */
guac_flag_set(&fifo->state, GUAC_FIFO_STATE_READY);
/* Item has been dequeued successfully */
}
int guac_fifo_dequeue(guac_fifo* fifo, void* item) {
if (!guac_fifo_dequeue_and_lock(fifo, item))
return 0;
guac_flag_unlock(&fifo->state);
return 1;
}
int guac_fifo_timed_dequeue(guac_fifo* fifo,
void* item, int msec_timeout) {
if (!guac_fifo_timed_dequeue_and_lock(fifo, item, msec_timeout))
return 0;
guac_flag_unlock(&fifo->state);
return 1;
}
int guac_fifo_dequeue_and_lock(guac_fifo* fifo, void* item) {
/* Block indefinitely while waiting for an item to be added, but bail out
* if the fifo becomes invalid */
guac_flag_wait_and_lock(&fifo->state,
GUAC_FIFO_STATE_NONEMPTY | GUAC_FIFO_STATE_INVALID);
if (fifo->state.value & GUAC_FIFO_STATE_INVALID) {
guac_flag_unlock(&fifo->state);
return 0;
}
dequeue(fifo, item);
return 1;
}
int guac_fifo_timed_dequeue_and_lock(guac_fifo* fifo,
void* item, int msec_timeout) {
/* Wait up to timeout for an item to be present in the fifo, failing if no
* items enter the fifo before the timeout lapses */
if (!guac_flag_timedwait_and_lock(&fifo->state,
GUAC_FIFO_STATE_NONEMPTY | GUAC_FIFO_STATE_INVALID,
msec_timeout)) {
return 0;
}
if (fifo->state.value & GUAC_FIFO_STATE_INVALID) {
guac_flag_unlock(&fifo->state);
return 0;
}
dequeue(fifo, item);
return 1;
}

167
src/libguac/flag.c Normal file
View File

@ -0,0 +1,167 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "guacamole/flag.h"
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
/**
* The number of nanoseconds in a whole second.
*/
#define NANOS_PER_SECOND 1000000000L
void guac_flag_init(guac_flag* event_flag) {
/* The condition used by guac_flag to signal changes in its
* value must be safe to share between processes, and must use the
* system-wide monotonic clock (not the realtime clock, which is subject to
* time changes) */
pthread_condattr_t cond_attr;
pthread_condattr_init(&cond_attr);
pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC);
pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(&event_flag->value_changed, &cond_attr);
/* In addition to being safe to share between processes, the mutex used by
* guac_flag to guard concurrent access to its value (AND to
* signal changes in its value) must be recursive (you can lock the mutex
* again even if the current thread has already locked it) */
pthread_mutexattr_t mutex_attr;
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&event_flag->value_mutex, &mutex_attr);
/* The initial value of all flags is unset (0) */
event_flag->value = 0;
}
void guac_flag_destroy(guac_flag* event_flag) {
pthread_cond_destroy(&event_flag->value_changed);
pthread_mutex_destroy(&event_flag->value_mutex);
}
void guac_flag_set_and_lock(guac_flag* event_flag,
unsigned int flags) {
guac_flag_lock(event_flag);
/* Set specific bits of flag, leaving other bits unaffected */
unsigned int old_value = event_flag->value;
event_flag->value |= flags;
/* Signal other threads only if flag has changed as a result of this call */
if (event_flag->value != old_value)
pthread_cond_broadcast(&event_flag->value_changed);
}
void guac_flag_set(guac_flag* event_flag,
unsigned int flags) {
guac_flag_set_and_lock(event_flag, flags);
guac_flag_unlock(event_flag);
}
void guac_flag_clear_and_lock(guac_flag* event_flag,
unsigned int flags) {
guac_flag_lock(event_flag);
/* Clear specific bits of flag, leaving other bits unaffected */
event_flag->value &= ~flags;
/* NOTE: Other threads are NOT signalled here. Threads wait only for flags
* to be set, not for flags to be cleared. */
}
void guac_flag_clear(guac_flag* event_flag,
unsigned int flags) {
guac_flag_clear_and_lock(event_flag, flags);
guac_flag_unlock(event_flag);
}
void guac_flag_lock(guac_flag* event_flag) {
pthread_mutex_lock(&event_flag->value_mutex);
}
void guac_flag_unlock(guac_flag* event_flag) {
pthread_mutex_unlock(&event_flag->value_mutex);
}
void guac_flag_wait_and_lock(guac_flag* event_flag,
unsigned int flags) {
guac_flag_lock(event_flag);
/* Continue waiting until at least one of the desired flags has been set */
while (!(event_flag->value & flags)) {
/* Wait for any change to any flags, bailing out if something is wrong
* that would prevent waiting from ever succeeding (such a failure
* would turn this into a busy loop) */
if (pthread_cond_wait(&event_flag->value_changed,
&event_flag->value_mutex)) {
abort(); /* This should not happen except due to a bug */
}
}
/* If we reach this point, at least one of the desired flags has been set,
* and it is intentional that we continue to hold the lock (acquired on
* behalf of the caller) */
}
int guac_flag_timedwait_and_lock(guac_flag* event_flag,
unsigned int flags, unsigned int msec_timeout) {
guac_flag_lock(event_flag);
struct timespec ts_timeout;
clock_gettime(CLOCK_MONOTONIC, &ts_timeout);
uint64_t nsec_timeout = msec_timeout * 1000000 + ts_timeout.tv_nsec;
ts_timeout.tv_sec += nsec_timeout / NANOS_PER_SECOND;
ts_timeout.tv_nsec = nsec_timeout % NANOS_PER_SECOND;
/* Continue waiting until at least one of the desired flags has been set */
while (!(event_flag->value & flags)) {
/* Wait for any change to any flags, failing if a timeout occurs */
if (pthread_cond_timedwait(&event_flag->value_changed,
&event_flag->value_mutex, &ts_timeout)) {
guac_flag_unlock(event_flag);
return 0;
}
}
/* If we reach this point, at least one of the desired flags has been set,
* and it is intentional that we continue to hold the lock (acquired on
* behalf of the caller) */
return 1;
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_ASSERT_H
#define GUAC_ASSERT_H
#include <stdio.h>
#include <stdlib.h>
/**
* Performs a runtime assertion that verifies the given condition evaluates to
* true (non-zero). If the condition evaluates to false (zero), execution is
* aborted with abort().
*
* This macro should be used only in cases where the performance impact of
* verifying the assertion is negligible and it is benificial to always verify
* the assertion. Unlike the standard assert(), this macro will never be
* omitted by the compiler.
*
* @param expression
* The condition to test.
*/
#define GUAC_ASSERT(expression) do { \
if (!(expression)) { \
fprintf(stderr, "GUAC_ASSERT in %s() failed at %s:%i.\n", \
__func__, __FILE__, __LINE__); \
abort(); \
} \
} while(0)
#endif

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_DISPLAY_CONSTANTS_H
#define GUAC_DISPLAY_CONSTANTS_H
/**
* Provides constants related to the abstract display implementation
* (guac_display).
*
* @file display-constants.h
*/
/**
* The maximum width of any guac_display, in pixels.
*/
#define GUAC_DISPLAY_MAX_WIDTH 8192
/**
* The maximum height of any guac_display, in pixels.
*/
#define GUAC_DISPLAY_MAX_HEIGHT 8192
/**
* The number of bytes in each pixel of raw image data within a
* guac_display_layer, as made accessible through a call to
* guac_display_layer_open_raw().
*/
#define GUAC_DISPLAY_LAYER_RAW_BPP 4
#endif

View File

@ -0,0 +1,91 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_DISPLAY_TYPES_H
#define GUAC_DISPLAY_TYPES_H
/**
* Provides type definitions related to the abstract display implementation
* (guac_display).
*
* @file display-types.h
*/
/**
* Opaque representation of the remote (client-side) display of a Guacamole
* connection (guac_client).
*/
typedef struct guac_display guac_display;
/**
* Opaque representation of a layer within a guac_display. This may be a
* visible layer or an off-screen buffer, and is effectively the guac_display
* equivalent of a guac_layer.
*/
typedef struct guac_display_layer guac_display_layer;
/**
* The current Cairo drawing context of a guac_display_layer, including a Cairo
* image surface wrapping the underlying drawing buffer of the
* guac_display_layer. After making graphical changes, the dirty rectangle of
* this context must be updated such that it includes the regions modified by
* those changes.
*/
typedef struct guac_display_layer_cairo_context guac_display_layer_cairo_context;
/**
* The current raw drawing context of a guac_display_layer, including the
* underlying drawing buffer of the guac_display_layer and memory layout
* information. After making graphical changes, the dirty rectangle of this
* context must be updated such that it includes the regions modified by those
* changes.
*/
typedef struct guac_display_layer_raw_context guac_display_layer_raw_context;
/**
* Pre-defined mouse cursor graphics.
*/
typedef enum guac_display_cursor_type {
/**
* An empty (invisible/hidden) mouse cursor.
*/
GUAC_DISPLAY_CURSOR_NONE,
/**
* A small dot. This is typically used in situations where cursor
* information for the remote desktop is not available, thus all cursor
* rendering must happen remotely, but it's still important that the user
* be able to see the current location of their local mouse pointer.
*/
GUAC_DISPLAY_CURSOR_DOT,
/**
* A vertical, I-shaped bar indicating text input or selection.
*/
GUAC_DISPLAY_CURSOR_IBAR,
/**
* A standard, general-purpose pointer.
*/
GUAC_DISPLAY_CURSOR_POINTER
} guac_display_cursor_type;
#endif

View File

@ -0,0 +1,616 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_DISPLAY_H
#define GUAC_DISPLAY_H
/**
* Provides an abstract display implementation (guac_display), which handles
* optimization automatically. Current optimizations include:
*
* - Scroll/copy detection
* - Solid color detection
* - Dirty rectangle reduction
* - Dynamic selection of PNG/JPEG/WebP compression depending on update content
* and frequency
* - Combining/rewriting of updates based on estimated cost
*
* @file display.h
*/
#include "client.h"
#include "display-constants.h"
#include "display-types.h"
#include "rect.h"
#include "socket.h"
#include <cairo/cairo.h>
#include <unistd.h>
/**
* Returns the memory address of the given rectangle within the image buffer of
* the given guac_display_layer_raw_context, where the upper-left corner of the
* given buffer is (0, 0). If the memory address cannot be calculated because
* doing so would overflow the maximum value of a size_t, execution of the
* current process is automatically aborted.
*
* IMPORTANT: No checks are performed on whether the rectangle extends beyond
* the bounds of the buffer, including considering whether the left/top
* position of the rectangle is negative. If the rectangle has not already been
* contrained to be within the bounds of the buffer, such checks must be
* performed before dereferencing the value returned by this macro.
*
* @param context
* The guac_display_layer_raw_context associated with the image buffer
* within which the address of the given rectangle should be determined.
*
* @param rect
* The rectangle to determine the offset of.
*/
#define GUAC_DISPLAY_LAYER_RAW_BUFFER(context, rect) \
GUAC_RECT_MUTABLE_BUFFER(rect, context->buffer, context->stride, GUAC_DISPLAY_LAYER_RAW_BPP)
struct guac_display_layer_cairo_context {
/**
* A Cairo context created for the Cairo surface. This Cairo context is
* persistent and will maintain its state between different calls to
* guac_display_layer_open_cairo() for the same layer.
*/
cairo_t* cairo;
/**
* A Cairo image surface wrapping the image buffer of this
* guac_display_layer.
*/
cairo_surface_t* surface;
/**
* A rectangle covering the current bounds of the graphical surface.
*/
guac_rect bounds;
/**
* A rectangle covering the region of the guac_display_layer that has
* changed since the last frame. This rectangle must be manually updated to
* cover any additional changed regions before closing the
* guac_display_layer_cairo_context.
*/
guac_rect dirty;
};
struct guac_display_layer_raw_context {
/**
* The raw, underlying image buffer of the guac_display_layer. If the layer
* was created as opaque, this image is 32-bit RGB with 8 bits per color
* component, where the lowest-order byte is the blue component and the
* highest-order byte is ignored. If the layer was not created as opaque,
* this image is 32-bit ARGB with 8 bits per color component, where the
* lowest-order byte is the blue component and the highest-order byte is
* alpha.
*/
unsigned char* buffer;
/**
* The number of bytes in each row of image data. This value is not
* necessarily the same as the width of the image multiplied by the size of
* each pixel. Additional space may be allocated to allow for memory
* alignment or to make future resize operations more efficient.
*/
size_t stride;
/**
* A rectangle covering the current bounds of the graphical surface. The
* buffer must not be addressed outside these bounds.
*/
guac_rect bounds;
/**
* A rectangle covering the region of the guac_display_layer that has
* changed since the last frame. This rectangle must be manually updated to
* cover any additional changed regions before closing the
* guac_display_layer_raw_context.
*/
guac_rect dirty;
};
/**
* Allocates a new guac_display representing the remote display shared by all
* connected users of the given guac_client. The dimensions of the display
* should be set with guac_display_defaulta_layer() and
* guac_display_layer_resize() once the desired display size is known. The
* guac_display must eventually be freed through a call to guac_display_free().
*
* @param client
* The guac_client whose remote display should be represented by the new
* guac_display.
*
* @return
* A newly-allocated guac_display representing the remote display of the
* given guac_client.
*/
guac_display* guac_display_alloc(guac_client* client);
/**
* Frees all resources associated with the given guac_display.
*
* @param display
* The guac_display to free.
*/
void guac_display_free(guac_display* display);
/**
* Replicates the current remote display state across the given socket. When
* new users join a particular guac_client, this function should be used to
* synchronize those users with the current display state.
*
* @param display
* The display that should be synchronized to all users at the other end of
* the given guac_socket.
*
* @param socket
* The socket to send the current remote display state over.
*/
void guac_display_dup(guac_display* display, guac_socket* socket);
/**
* Notifies the given guac_display that a specific user has left the connection
* and need no longer be considered for future updates/events. This SHOULD
* always be called when a user leaves the connection to ensure other future,
* user-related events are interpreted correctly.
*
* @param display
* The guac_display to notify.
*
* @param user
* The user that left the connection.
*/
void guac_display_notify_user_left(guac_display* display, guac_user* user);
/**
* Notifies the given guac_display that a specific user has changed the state
* of the mouse, such as through moving the pointer or pressing/releasing a
* mouse button.
*
* @param display
* The guac_display to notify.
*
* @param user
* The user that moved the mouse or pressed/released a mouse button.
*
* @param x
* The X position of the mouse, in pixels.
*
* @param y
* The Y position of the mouse, in pixels.
*
* @param mask
* An integer value representing the current state of each button, where
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
* button is currently pressed. The lowest-order bit is the left mouse
* button, followed by the middle button, right button, and finally the up
* and down buttons of the scroll wheel.
*
* @see GUAC_CLIENT_MOUSE_LEFT
* @see GUAC_CLIENT_MOUSE_MIDDLE
* @see GUAC_CLIENT_MOUSE_RIGHT
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
*/
void guac_display_notify_user_moved_mouse(guac_display* display, guac_user* user, int x, int y, int mask);
/**
* Ends the current frame, where the number of input frames that were
* considered in creating this frame is either unknown or inapplicable,
* allowing the guac_display to complete sending the frame to connected
* clients.
*
* @param display
* The guac_display that should send the current frame.
*/
void guac_display_end_frame(guac_display* display);
/**
* Ends the current frame, where that frame may combine or otherwise represent the
* changes of an arbitrary number of input frames, allowing the guac_display to
* complete sending the frame to connected clients.
*
* @param display
* The guac_display that should send the current frame.
*
* @param
* The number of distinct frames that were considered or combined when
* generating the current frame, or zero if the boundaries of relevant
* frames are unknown.
*/
void guac_display_end_multiple_frames(guac_display* display, int frames);
/**
* Returns the default layer for the given display. The default layer is the
* only layer that always exists and serves as the root-level layer for all
* other layers.
*
* @see GUAC_DEFAULT_LAYER
*
* @param display
* The guac_display to return the default layer from.
*
* @return
* A guac_display_layer representing the default layer for the given
* guac_display.
*/
guac_display_layer* guac_display_default_layer(guac_display* display);
/**
* Allocates a new layer for the given display. The new layer will initially be
* a direct child of the display's default layer. When the layer is no longer
* needed, it may be freed through calling guac_display_free_layer(). If not
* freed manually through a call to guac_display_free_layer(), it will be freed
* when the display is freed with guac_display_free().
*
* @param display
* The guac_display to allocate a new layer for.
*
* @param opaque
* Non-zero if the new layer will only ever contain opaque image contents
* (the alpha channel should be ignored), zero otherwise.
*
* @return
* A newly-allocated guac_display_layer that is initially a direct child of
* the default layer.
*/
guac_display_layer* guac_display_alloc_layer(guac_display* display, int opaque);
/**
* Allocates a new buffer (offscreen layer) for the given display. When the
* buffer is no longer needed, it may be freed through calling
* guac_display_free_layer(). If not freed manually through a call to
* guac_display_free_layer(), it will be freed when the display is freed with
* guac_display_free().
*
* @param display
* The guac_display to allocate a new buffer for.
*
* @param opaque
* Non-zero if the new buffer will only ever contain opaque image contents
* (the alpha channel should be ignored), zero otherwise.
*
* @return
* A newly-allocated guac_display_layer representing the new buffer.
*/
guac_display_layer* guac_display_alloc_buffer(guac_display* display, int opaque);
/**
* Frees the given layer, releasing any underlying memory. If the layer has
* already been used for rendering, it will be freed on the remote side, as
* well, when the current pending frame is complete.
*
* @param display_layer
* The layer to free.
*/
void guac_display_free_layer(guac_display_layer* display_layer);
/**
* Returns a layer representing the current mouse cursor icon. Changes to the
* contents of this layer will affect the remote mouse cursor after the current
* pending frame is complete.
*
* @param display
* The guac_display to return the cursor layer for.
*
* @return
* A guac_display_layer representing the mouse cursor of the given
* guac_display.
*/
guac_display_layer* guac_display_cursor(guac_display* display);
/**
* Sets the remote mouse cursor to the given built-in cursor icon. Changes to
* the remote mouse cursor will take effect after the current pending frame is
* complete.
*
* @param display
* The guac_display to set the cursor of.
*
* @param cursor_type
* The built-in cursor icon to set the remote cursor to.
*/
void guac_display_set_cursor(guac_display* display,
guac_display_cursor_type cursor_type);
/**
* Sets the hotspot location of the remote mouse cursor. The hotspot is the
* point within the mouse cursor where the click occurs. Changes to the hotspot
* of the remote mouse cursor will take effect after the current pending frame
* is complete.
*
* @param display
* The guac_display to set the cursor hotspot of.
*
* @param x
* The X coordinate of the cursor hotspot, in pixels.
*
* @param y
* The Y coordinate of the cursor hotspot, in pixels.
*/
void guac_display_set_cursor_hotspot(guac_display* display, int x, int y);
/**
* Stores the current bounding rectangle of the given layer in the given
* guac_rect. The boundary stored will be the boundary of the current pending
* frame.
*
* @oaram layer
* The layer to determine the dimensions of.
*
* @param bounds
* The guac_rect that should receive the bounding rectangle of the given
* layer.
*/
void guac_display_layer_get_bounds(guac_display_layer* layer, guac_rect* bounds);
/**
* Moves the given layer to the given coordinates. The changes to the given
* layer will be made as part of the current pending frame, and will not take
* effect on remote displays until the pending frame is complete.
*
* @param layer
* The layer to set the position of.
*
* @param x
* The X coordinate of the upper-left corner of the layer, in pixels.
*
* @param y
* The Y coordinate of the upper-left corner of the layer, in pixels.
*/
void guac_display_layer_move(guac_display_layer* layer, int x, int y);
/**
* Sets the stacking position of the given layer relative to all other sibling
* layers (direct children of the same parent). The change in relative layer
* stacking position will be made as part of the current pending frame, and
* will not take effect on remote displays until the pending frame is complete.
*
* @param layer
* The layer to set the stacking position of.
*
* #param z
* The relative order of this layer.
*/
void guac_display_layer_stack(guac_display_layer* layer, int z);
/**
* Reparents the given layer such that it is a direct child of the given parent
* layer. The change in layer hierarchy will be made as part of the current
* pending frame, and will not take effect on remote displays until the pending
* frame is complete.
*
* @param layer
* The layer to change the parent of.
*
* @param parent
* The layer that should be the new parent.
*/
void guac_display_layer_set_parent(guac_display_layer* layer, const guac_display_layer* parent);
/**
* Sets the opacity of the given layer. The change in layer opacity will be
* made as part of the current pending frame, and will not take effect on
* remote displays until the pending frame is complete.
*
* @param layer
* The layer to change the opacity of.
*
* @param opacity
* The opacity to assign to the given layer, as a value between 0 and 255
* inclusive, where 0 is completely transparent and 255 is completely
* opaque.
*/
void guac_display_layer_set_opacity(guac_display_layer* layer, int opacity);
/**
* Sets whether graphical changes to the given layer are allowed to be
* represented, updated, or sent using methods that can cause some loss of
* information, such as JPEG or WebP compression. By default, layers are
* allowed to use lossy methods. Changes to lossy vs. lossless behavior will
* affect the current pending frame, as well as any frames that follow.
*
* @param layer
* The layer to change the lossy behavior of.
*
* @param lossless
* Non-zero if the layer should be allowed to use lossy methods (the
* default behavior), zero if the layer should use strictly lossless
* methods.
*/
void guac_display_layer_set_lossless(guac_display_layer* layer, int lossless);
/**
* Sets the level of multitouch support available for the given layer. The
* change in layer multitouch support will be made as part of the current
* pending frame, and will not take effect on remote displays until the pending
* frame is complete. Setting multitouch support only has any effect on the
* default layer.
*
* @param layer
* The layer to set the multitouch support level of.
*
* @param touches
* The maximum number of simultaneous touches tracked by the layer, where 0
* represents no touch support.
*/
void guac_display_layer_set_multitouch(guac_display_layer* layer, int touches);
/**
* Resizes the given layer to the given dimensions. The change in layer size
* will be made as part of the current pending frame, and will not take effect
* on remote displays until the pending frame is complete.
*
* IMPORTANT: While it is safe to call this function while holding an open
* context (raw or Cairo), this should only be done if the underlying buffer is
* maintained externally or if the context is finished being used. Resizing a
* layer can result in the underlying buffer being replaced.
*
* @param layer
* The layer to set the size of.
*
* @param width
* The new width to assign to the layer, in pixels. Any values provided
* that are greater than GUAC_DISPLAY_MAX_WIDTH will instead be interpreted
* as equal to GUAC_DISPLAY_MAX_WIDTH.
*
* @param height
* The new height to assign to the layer, in pixels. Any values provided
* that are greater than GUAC_DISPLAY_MAX_HEIGHT will instead be
* interpreted as equal to GUAC_DISPLAY_MAX_HEIGHT.
*/
void guac_display_layer_resize(guac_display_layer* layer, int width, int height);
/**
* Begins a drawing operation for the given layer, returning a context that can
* be used to draw directly to the raw image buffer containing the layer's
* current pending frame.
*
* Starting a draw operation acquires exclusive access to the display for the
* current thread. When complete, the original calling thread must relinquish
* exclusive access and free the graphical context by calling
* guac_display_layer_close_raw(). It is the responsibility of the caller to
* ensure the dirty rect within the returned context is updated to contain the
* region modified, such as by calling guac_rect_expand().
*
* @param layer
* The layer to draw to.
*
* @return
* A mutable graphical context containing the current raw pending frame
* state of the given layer.
*/
guac_display_layer_raw_context* guac_display_layer_open_raw(guac_display_layer* layer);
/**
* Ends a drawing operation that was started with a call to
* guac_display_layer_open_raw() and relinquishes exclusive access to the
* display. All graphical changes made to the layer through the raw context
* will be committed to the layer and will be included in the current pending
* frame.
*
* This function MUST NOT be called by any thread other than the thread that called
* guac_display_layer_open_raw() to obtain the given context.
*
* @param layer
* The layer that finished being drawn to.
*
* @param context
* The raw context of the drawing operation that has completed, as returned
* by a previous call to guac_display_layer_open_raw().
*/
void guac_display_layer_close_raw(guac_display_layer* layer, guac_display_layer_raw_context* context);
/**
* Fills a rectangle of image data within the given raw context with a single
* color. All pixels within the rectangle are replaced with the given color. If
* applicable, this includes the alpha channel. Compositing is not performed by
* this function.
*
* @param context
* The raw context of the layer that is being drawn to.
*
* @param dst
* The rectangular area that should be filled with the given color.
*
* @param color
* The color that should replace all current pixel values within the given
* rectangular region.
*/
void guac_display_layer_raw_context_set(guac_display_layer_raw_context* context,
const guac_rect* dst, uint32_t color);
/**
* Copies a rectangle of image data from the given buffer to the given raw
* context, replacing all pixel values within the given rectangle. Compositing
* is not performed by this function.
*
* The size of the image data copied and the destination location of that data
* within the layer are dictated by the given rectangle. If any offset needs to
* be applied to the source image buffer, it is expected that this offset will
* already have been applied via the address of the buffer provided to this
* function, such as through an earlier call to GUAC_RECT_CONST_BUFFER().
*
* @param context
* The raw context of the layer that is being drawn to.
*
* @param dst
* The rectangular area that should be filled with the image data from the
* given buffer.
*
* @param buffer
* The containing the image data that should replace all current pixel
* values within the given rectangular region.
*
* @param stride
* The number of bytes in each row of image data within the given buffer.
*/
void guac_display_layer_raw_context_put(guac_display_layer_raw_context* context,
const guac_rect* dst, const void* restrict buffer, size_t stride);
/**
* Begins a drawing operation for the given layer, returning a context that can
* be used to draw to a Cairo surface containing the layer's current pending
* frame. The underlying Cairo state within the returned context will be
* preserved between calls to guac_display_layer_open_cairo().
*
* Starting a draw operation acquires exclusive access to the display for the
* current thread. When complete, the original calling thread must relinquish
* exclusive access and free the graphical context by calling
* guac_display_layer_close_cairo(). It is the responsibility of the caller to
* ensure the dirty rect within the returned context is updated to contain the
* region modified, such as by calling guac_rect_expand().
*
* @param layer
* The layer to draw to.
*
* @return
* A mutable graphical context containing the current pending frame state
* of the given layer in the form of a Cairo surface.
*/
guac_display_layer_cairo_context* guac_display_layer_open_cairo(guac_display_layer* layer);
/**
* Ends a drawing operation that was started with a call to
* guac_display_layer_open_cairo() and relinquishes exclusive access to the
* display. All graphical changes made to the layer through the Cairo context
* will be committed to the layer and will be included in the current pending
* frame.
*
* This function MUST NOT be called by any thread other than the thread that called
* guac_display_layer_open_cairo() to obtain the given context.
*
* @param layer
* The layer that finished being drawn to.
*
* @param context
* The Cairo context of the drawing operation that has completed, as
* returned by a previous call to guac_display_layer_open_cairo().
*/
void guac_display_layer_close_cairo(guac_display_layer* layer, guac_display_layer_cairo_context* context);
#endif

View File

@ -0,0 +1,57 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_FIFO_CONSTANTS_H
#define GUAC_FIFO_CONSTANTS_H
/**
* @addtogroup fifo
* @{
*/
/**
* Provides constants for the abstract FIFO implementation (guac_fifo).
*
* @file fifo-constants.h
*/
/**
* The bitwise flag used by the "state" member of guac_fifo to represent that
* the fifo has space for at least one item.
*/
#define GUAC_FIFO_STATE_READY 1
/**
* The bitwise flag used by the "state" member of guac_fifo to represent that
* the fifo contains at least one item.
*/
#define GUAC_FIFO_STATE_NONEMPTY 2
/**
* The bitwise flag used by the "state" member of guac_fifo to represent that
* the fifo is no longer valid and may not be used for any further operations.
*/
#define GUAC_FIFO_STATE_INVALID 4
/**
* @}
*/
#endif

View File

@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_FIFO_TYPES_H
#define GUAC_FIFO_TYPES_H
/**
* @addtogroup fifo
* @{
*/
/**
* Provides type definitions for the abstract FIFO implementation (guac_fifo).
*
* @file fifo-types.h
*/
/**
* Generic base structure for a FIFO of arbitrary events. The size of the FIFO
* and each event are up to the implementation. Each implementation must
* provide this base structure with a pointer to the underlying array of items,
* the maximum number of items supported, and the size in bytes of each item
* through a call to guac_fifo_init().
*
* This generic base may be safely included in shared memory, but
* implementations building off this base must ensure the base is initialized
* with a call to guac_fifo_init() and that any additional
* implementation-specific aspects are also safe for shared memory usage.
*/
typedef struct guac_fifo guac_fifo;
/**
* @}
*/
#endif

View File

@ -0,0 +1,314 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_FIFO_H
#define GUAC_FIFO_H
#include "fifo-constants.h"
#include "fifo-types.h"
#include "flag.h"
#include <stddef.h>
#include <sys/types.h>
/**
* Base FIFO implementation that allows arbitrary element sizes and arbitrary
* element storage.
*
* @defgroup fifo guac_fifo
* @{
*/
/**
* Provides an abstract FIFO implementation (guac_fifo), which can support
* arbitrary element sizes and storage.
*
* @file fifo.h
*/
struct guac_fifo {
/**
* The current state of this FIFO. This state primarily represents whether
* the FIFO contains at least one item (is non-empty), but it is also used
* to represent whether the FIFO is invalid (no longer permitted to contain
* any items).
*/
guac_flag state;
/**
* The maximum number of items that may be stored in this FIFO.
*/
size_t max_items;
/**
* The size of each individual item, in bytes. All FIFO items must have a
* constant size, though that size is implementation-dependent.
*/
size_t item_size;
/**
* The index of the first item within this FIFO. As items are
* added/removed, this value will advance as necessary to avoid needing to
* spend CPU time moving existing items around in memory.
*/
size_t head;
/**
* The current number of items stored within this FIFO.
*/
size_t item_count;
/**
* The offset of the first byte of the implementation-specific array of
* items within this FIFO, relative to the first byte of guac_fifo
* structure.
*/
ssize_t items_offset;
};
/**
* Initializes the given guac_fifo such that it may be safely included in
* shared memory and accessed by multiple processes. This function MUST be
* invoked once (and ONLY once) for each guac_fifo being used, and MUST be
* invoked before any such FIFO is used.
*
* The FIFO is empty upon initialization.
*
* @param fifo
* The FIFO to initialize.
*
* @param items
* The storage that the base implementation should use for queued items.
* This storage MUST be large enough to contain the maximum number of items
* as a contiguous array.
*
* @param max_items
* The maximum number of items supported by the provided storage.
*
* @param item_size
* The number of bytes required for each individual item in storage.
*/
void guac_fifo_init(guac_fifo* fifo, void* items,
size_t max_items, size_t item_size);
/**
* Releases all underlying resources used by the given guac_fifo, such as
* pthread mutexes and conditions. The given guac_fifo MAY NOT be used after
* this function has been called. This function MAY NOT be called while
* exclusive access to the underlying state flag is held by any thread.
*
* This function does NOT free() the given guac_fifo pointer. If the memory
* associated with the given guac_fifo has been manually allocated, it must be
* manually freed as necessary.
*
* @param fifo
* The FIFO to destroy.
*/
void guac_fifo_destroy(guac_fifo* fifo);
/**
* Marks the given FIFO as invalid, preventing any further additions or
* removals from the FIFO. Attempts to add/remove items from the FIFO from this
* point forward will fail immediately, as will any outstanding attempts to
* remove items that are currently blocked.
*
* This function is primarily necessary to allow for threadsafe cleanup of
* queues. Lacking this function, there is no guarantee that an outstanding
* call to guac_fifo_dequeue() won't still be indefinitely blocking.
* Internally, such a condition would mean that the mutex of the state flag is
* still held, which would mean that the FIFO can never be safely destroyed.
*
* @param fifo
* The FIFO to invalidate.
*/
void guac_fifo_invalidate(guac_fifo* fifo);
/**
* Returns whether the given FIFO is still valid. A FIFO is valid if it has not
* yet been invalidated through a call to guac_fifo_invalidate().
*
* @param fifo
* The FIFO to test.
*
* @return
* Non-zero if the given FIFO is still valid, zero otherwise.
*/
int guac_fifo_is_valid(guac_fifo* fifo);
/**
* Acquires exclusive access to this guac_fifo. When exclusive access is no
* longer required, it must be manually relinquished through a call to
* guac_fifo_unlock(). This function may be safely called while the current
* thread already has exclusive access, however every such call must eventually
* have a matching call to guac_fifo_unlock().
*
* NOTE: It is intended that locking/unlocking a guac_fifo may be used in lieu
* of a mutex to guard concurrent access to any number of shared resources
* related to the FIFO.
*
* @param fifo
* The guac_fifo to lock.
*/
void guac_fifo_lock(guac_fifo* fifo);
/**
* Relinquishes exclusive access to this guac_fifo. This function may only be
* called by a thread that currently has exclusive access to the guac_fifo.
*
* NOTE: It is intended that locking/unlocking a guac_fifo may be used in lieu
* of a mutex to guard concurrent access to any number of shared resources
* related to the FIFO.
*
* @param fifo
* The guac_fifo to unlock.
*/
void guac_fifo_unlock(guac_fifo* fifo);
/**
* Adds a copy of the given item to the end of the given FIFO, and signals any
* waiting threads that the FIFO is now non-empty. If there is insufficient
* space in the FIFO, this function will block until at space is available. If
* the FIFO is invalid or becomes invalid, this function returns immediately.
*
* @param fifo
* The FIFO to add an item to.
*
* @param item
* The item to add.
*
* @return
* Non-zero if the item was successfully added, zero if items cannot be
* added to the FIFO because the FIFO has been invalidated.
*/
int guac_fifo_enqueue(guac_fifo* fifo, const void* item);
/**
* Atomically adds a copy of the given item to the end of the given FIFO,
* signals any waiting threads that the FIFO is now non-empty, and leaves the
* given FIFO locked. If there is insufficient space in the FIFO, this function
* will block until at space is available. If the FIFO is invalid or becomes
* invalid, this function returns immediately and the FIFO is not locked.
*
* @param fifo
* The FIFO to add an item to.
*
* @param item
* The item to add.
*
* @return
* Non-zero if the item was successfully added, zero if items cannot be
* added to the FIFO because the FIFO has been invalidated.
*/
int guac_fifo_enqueue_and_lock(guac_fifo* fifo, const void* item);
/**
* Removes the oldest (first) item from the FIFO, storing a copy of that item
* within the provided buffer. If the FIFO is currently empty, this function
* will block until at least one item has been added to the FIFO or until the
* FIFO becomes invalid.
*
* @param fifo
* The FIFO to remove an item from.
*
* @param item
* The buffer that should receive a copy of the removed item.
*
* @return
* Non-zero if an item was successfully removed, zero if items cannot be
* removed from the FIFO because the FIFO has been invalidated.
*/
int guac_fifo_dequeue(guac_fifo* fifo, void* item);
/**
* Atomically removes the oldest (first) item from the FIFO, storing a copy of
* that item within the provided buffer. If this function successfully removes
* an item, the FIFO is left locked after this function returns. If the FIFO is
* currently empty, this function will block until at least one item has been
* added to the FIFO or until the FIFO becomes invalid.
*
* @param fifo
* The FIFO to remove an item from.
*
* @param item
* The buffer that should receive a copy of the removed item.
*
* @return
* Non-zero if an item was successfully removed, zero if items cannot be
* removed from the FIFO because the FIFO has been invalidated.
*/
int guac_fifo_dequeue_and_lock(guac_fifo* fifo, void* item);
/**
* Removes the oldest (first) item from the FIFO, storing a copy of that item
* within the provided buffer. If the FIFO is currently empty, this function
* will block until at least one item has been added to the FIFO, until the
* given timeout has elapsed, or until the FIFO becomes invalid.
*
* @param fifo
* The FIFO to remove an item from.
*
* @param item
* The buffer that should receive a copy of the removed item.
*
* @param msec_timeout
* The maximum number of milliseconds to wait for at least one item to be
* present within the FIFO (or for the FIFO to become invalid).
*
* @return
* Non-zero if an item was successfully removed, zero if the timeout has
* elapsed or if items cannot be removed from the FIFO because the FIFO has
* been invalidated.
*/
int guac_fifo_timed_dequeue(guac_fifo* fifo,
void* item, int msec_timeout);
/**
* Atomically removes the oldest (first) item from the FIFO, storing a copy of
* that item within the provided buffer. If this function successfully removes
* an item, the FIFO is left locked after this function returns. If the FIFO is
* currently empty, this function will block until at least one item has been
* added to the FIFO, until the given timeout has elapsed, or until the FIFO
* becomes invalid.
*
* @param fifo
* The FIFO to remove an item from.
*
* @param item
* The buffer that should receive a copy of the removed item.
*
* @param msec_timeout
* The maximum number of milliseconds to wait for at least one item to be
* present within the FIFO (or for the FIFO to become invalid).
*
* @return
* Non-zero if an item was successfully removed, zero if the timeout has
* elapsed or if items cannot be removed from the FIFO because the FIFO has
* been invalidated.
*/
int guac_fifo_timed_dequeue_and_lock(guac_fifo* fifo,
void* item, int msec_timeout);
/**
* @}
*/
#endif

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_FLAG_TYPES_H
#define GUAC_FLAG_TYPES_H
/**
* Generic integer flag intended for signalling of arbitrary events between
* processes. This flag may be safely included in shared memory, but must be
* initialized with guac_flag_init().
*
* In addition to basic signalling and tracking of flag values, it is intended
* that the locking/unlocking facilities of guac_flag may be used in
* lieu of a mutex to guard concurrent access to any number of shared resources
* related to the flag.
*/
typedef struct guac_flag guac_flag;
#endif

View File

@ -0,0 +1,244 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_FLAG_H
#define GUAC_FLAG_H
#include "flag-types.h"
#include <pthread.h>
struct guac_flag {
/**
* The mutex used to ensure concurrent changes to the value of this flag
* are threadsafe, as well as to satisfy the requirements of the pthread
* conditional used to signal changes to the value of this flag.
*/
pthread_mutex_t value_mutex;
/**
* Condition variable that signals when the value of this flag has changed.
*/
pthread_cond_t value_changed;
/**
* The current value of this flag. This value may be the bitwise OR'd value
* of any number of arbitrary flags, so long as those flags fit within an
* int. It is entirely up to the user of this guac_flag to
* define the meaning of any value(s) assigned.
*/
unsigned int value;
};
/**
* Initializes the given guac_flag such that it may be safely
* included in shared memory and accessed by multiple processes. This function
* MUST be invoked once (and ONLY once) for each guac_flag being
* used, and MUST be invoked before any such flag is used.
*
* The value of the flag upon initialization is 0 (no flags set).
*
* @param event_flag
* The flag to initialize.
*/
void guac_flag_init(guac_flag* event_flag);
/**
* Releases all underlying resources used by the given guac_flag,
* such as pthread mutexes and conditions. The given guac_flag MAY
* NOT be used after this function has been called. This function MAY NOT be
* called while exclusive access to the guac_flag is held by any
* thread.
*
* This function does NOT free() the given guac_flag pointer. If the
* memory associated with the given guac_flag has been manually
* allocated, it must be manually freed as necessary.
*
* @param event_flag
* The flag to destroy.
*/
void guac_flag_destroy(guac_flag* event_flag);
/**
* Sets the given bitwise flag(s) within the value of the given
* guac_flag, setting their corresponding bits to 1. The values of
* other bitwise flags are not affected. If other threads are waiting for any
* of these flags to be set, and at least one such flag has been set as a
* result of this call, they will be signalled accordingly.
*
* This function is threadsafe and will acquire exclusive access to the given
* guac_flag prior to changing the flag value. It is also safe to
* call this function if exclusive access has already been acquired through
* guac_flag_lock() or similar.
*
* @param event_flag
* The guac_flag to modify.
*
* @param flags
* The bitwise OR'd value of the flags to be set.
*/
void guac_flag_set(guac_flag* event_flag,
unsigned int flags);
/**
* Sets the given bitwise flag(s) within the value of the given guac_flag,
* setting their corresponding bits to 1, while also acquiring exclusive access
* to the guac_flag. The values of other bitwise flags are not affected. If
* other threads are waiting for any of these flags to be set, and at least one
* such flag has been set as a result of this call, they will be signalled
* accordingly.
*
* This function is threadsafe and will acquire exclusive access to the given
* guac_flag prior to changing the flag value. It is also safe to
* call this function if exclusive access has already been acquired through
* guac_flag_lock() or similar.
*
* @param event_flag
* The guac_flag to modify.
*
* @param flags
* The bitwise OR'd value of the flags to be set.
*/
void guac_flag_set_and_lock(guac_flag* event_flag,
unsigned int flags);
/**
* Clears the given bitwise flag(s) within the value of the given
* guac_flag, setting their corresponding bits to 0. The values of
* other bitwise flags are not affected. Unlike guac_flag_set(),
* no threads will be notified that these flag values have changed.
*
* This function is threadsafe and will acquire exclusive access to the given
* guac_flag prior to changing the flag value. It is also safe to
* call this function if exclusive access has already been acquired through
* guac_flag_lock() or similar.
*
* @param event_flag
* The guac_flag to modify.
*
* @param flags
* The bitwise OR'd value of the flags to be cleared. Each bit in this
* value that is set to 1 will be set to 0 in the value of the
* guac_flag.
*/
void guac_flag_clear(guac_flag* event_flag,
unsigned int flags);
/**
* Clears the given bitwise flag(s) within the value of the given guac_flag,
* setting their corresponding bits to 0, while also acquiring exclusive access
* to the guac_flag. The values of other bitwise flags are not affected. Unlike
* guac_flag_set(), no threads will be notified that these flag values have
* changed.
*
* This function is threadsafe and will acquire exclusive access to the given
* guac_flag prior to changing the flag value. It is also safe to
* call this function if exclusive access has already been acquired through
* guac_flag_lock() or similar.
*
* @param event_flag
* The guac_flag to modify.
*
* @param flags
* The bitwise OR'd value of the flags to be cleared. Each bit in this
* value that is set to 1 will be set to 0 in the value of the
* guac_flag.
*/
void guac_flag_clear_and_lock(guac_flag* event_flag,
unsigned int flags);
/**
* Acquires exclusive access to this guac_flag. When exclusive
* access is no longer required, it must be manually relinquished through a
* call to guac_flag_unlock(). This function may be safely called
* while the current thread already has exclusive access, however every such
* call must eventually have a matching call to guac_flag_unlock().
*
* NOTE: It is intended that locking/unlocking a guac_flag may be
* used in lieu of a mutex to guard concurrent access to any number of shared
* resources related to the flag.
*
* @param event_flag
* The guac_flag to lock.
*/
void guac_flag_lock(guac_flag* event_flag);
/**
* Relinquishes exclusive access to this guac_flag. This function
* may only be called by a thread that currently has exclusive access to the
* guac_flag.
*
* NOTE: It is intended that locking/unlocking a guac_flag may be
* used in lieu of a mutex to guard concurrent access to any number of shared
* resources related to the flag.
*
* @param event_flag
* The guac_flag to unlock.
*/
void guac_flag_unlock(guac_flag* event_flag);
/**
* Waits indefinitely for any of the given flags to be set within the given
* guac_flag. This function returns only after at least one of the
* given flags has been set. After this function returns, the current thread
* has exclusive access to the guac_flag and MUST relinquish that
* access with a call to guac_flag_unlock() when finished.
*
* @param event_flag
* The guac_flag to wait on.
*
* @param flags
* The bitwise OR'd value of the specific flag(s) to wait for.
*/
void guac_flag_wait_and_lock(guac_flag* event_flag,
unsigned int flags);
/**
* Waits no longer than the given number of milliseconds for any of the given
* flags to be set within the given guac_flag. This function returns
* after at least one of the given flags has been set, or after the provided
* time limit expires. After this function returns successfully, the current
* thread has exclusive access to the guac_flag and MUST relinquish
* that access with a call to guac_flag_unlock() when finished. If
* the time limit lapses before any of the given flags has been set, this
* function returns unsuccessfully without acquiring exclusive access.
*
* @param event_flag
* The guac_flag to wait on.
*
* @param flags
* The bitwise OR'd value of the specific flag(s) to wait for.
*
* @param msec_timeout
* The maximum number of milliseconds to wait for at least one of the
* desired flags to be set.
*
* @return
* Non-zero if at least one of the desired flags has been set and the
* current thread now has exclusive access to the guac_flag, zero if none
* of the desired flags were set within the time limit and the current
* thread DOES NOT have exclusive access.
*/
int guac_flag_timedwait_and_lock(guac_flag* event_flag,
unsigned int flags, unsigned int msec_timeout);
#endif

View File

@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RECT_TYPES_H
#define GUAC_RECT_TYPES_H
/**
* A rectangle defined by its upper-left and lower-right corners. The
* upper-left corner is inclusive (represents the start of the area contained
* by the guac_rect), while the lower-right corner is exclusive (represents the
* start of the area NOT contained by the guac_rect). All coordinates may be
* negative.
*/
typedef struct guac_rect guac_rect;
#endif

View File

@ -0,0 +1,271 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef GUAC_RECT_H
#define GUAC_RECT_H
#include "mem.h"
#include "rect-types.h"
/**
* Returns the memory address of the given rectangle within the given mutable
* buffer, where the upper-left corner of the given buffer is (0, 0). If the
* memory address cannot be calculated because doing so would overflow the
* maximum value of a size_t, execution of the current process is automatically
* aborted.
*
* IMPORTANT: No checks are performed on whether the rectangle extends beyond
* the bounds of the buffer, including considering whether the left/top
* position of the rectangle is negative. If the rectangle has not already been
* contrained to be within the bounds of the buffer, such checks must be
* performed before dereferencing the value returned by this macro.
*
* @param rect
* The rectangle to determine the offset of.
*
* @param buffer
* The mutable buffer within which the address of the given rectangle
* should be determined.
*
* @param stride
* The number of bytes in each row of image data within the buffer.
*
* @param bpp
* The number of bytes in each pixel of image data.
*
* @return
* The memory address of the given rectangle within the given buffer.
*/
#define GUAC_RECT_MUTABLE_BUFFER(rect, buffer, stride, bpp) ((void*) ( \
((unsigned char*) (buffer)) \
+ guac_mem_ckd_mul_or_die((rect).top, stride) \
+ guac_mem_ckd_mul_or_die((rect).left, bpp)))
/**
* Returns the memory address of the given rectangle within the given immutable
* (const) buffer, where the upper-left corner of the given buffer is (0, 0).
* If the memory address cannot be calculated because doing so would overflow
* the maximum value of a size_t, execution of the current process is
* automatically aborted.
*
* IMPORTANT: No checks are performed on whether the rectangle extends beyond
* the bounds of the buffer, including considering whether the left/top
* position of the rectangle is negative. If the rectangle has not already been
* contrained to be within the bounds of the buffer, such checks must be
* performed before dereferencing the value returned by this macro.
*
* @param rect
* The rectangle to determine the offset of.
*
* @param buffer
* The const buffer within which the address of the given rectangle should
* be determined.
*
* @param stride
* The number of bytes in each row of image data within the buffer.
*
* @param bpp
* The number of bytes in each pixel of image data.
*
* @return
* The memory address of the given rectangle within the given buffer.
*/
#define GUAC_RECT_CONST_BUFFER(rect, buffer, stride, bpp) ((const void*) ( \
((const unsigned char*) (buffer)) \
+ guac_mem_ckd_mul_or_die((rect).top, stride) \
+ guac_mem_ckd_mul_or_die((rect).left, bpp)))
struct guac_rect {
/**
* The X coordinate of the upper-left corner of this rectangle (inclusive).
* This value represents the least integer X coordinate that is part of
* this rectangle, with greater integer X coordinates being part of this
* rectangle up to but excluding the right boundary.
*
* This value MUST be less than or equal to the right boundary. If this
* value is equal to the right boundary, the rectangle is empty (has no
* width).
*/
int left;
/**
* The Y coordinate of the upper-left corner of this rectangle (inclusive).
* This value represents the least integer Y coordinate that is part of
* this rectangle, with greater integer Y coordinates being part of this
* rectangle up to but excluding the bottom boundary.
*
* This value MUST be less than or equal to the bottom boundary. If this
* value is equal to the bottom boundary, the rectangle is empty (has no
* height).
*/
int top;
/**
* The X coordinate of the lower-right corner of this rectangle
* (exclusive). This value represents the least integer X coordinate that
* is NOT part of this rectangle, with lesser integer X coordinates being
* part of this rectangle up to and including the left boundary.
*
* This value MUST be greater than or equal to the left boundary. If this
* value is equal to the left boundary, the rectangle is empty (has no
* width).
*/
int right;
/**
* The Y coordinate of the lower-right corner of this rectangle
* (exclusive). This value represents the least integer Y coordinate that
* is NOT part of this rectangle, with lesser integer Y coordinates being
* part of this rectangle up to and including the top boundary.
*
* This value MUST be greater than or equal to the top boundary. If this
* value is equal to the top boundary, the rectangle is empty (has no
* height).
*/
int bottom;
};
/**
* Initializes the given rectangle with the given coordinates and dimensions.
* If a dimenion is negative, it is interpreted as if zero.
*
* @param rect
* The rectangle to initialize.
*
* @param x
* The X coordinate of the upper-left corner of the rectangle.
*
* @param y
* The Y coordinate of the upper-left corner of the rectangle.
*
* @param width
* The width of the rectangle.
*
* @param height
* The height of the rectangle.
*/
void guac_rect_init(guac_rect* rect, int x, int y, int width, int height);
/**
* Extends the given rectangle such that each edge of the rectangle falls on
* the edge of an NxN cell in a regular grid anchored at the upper-left corner,
* where N is a power of two.
*
* @param rect
* The rectangle to adjust.
*
* @param bits
* The size of the cells in the grid, as the exponent of the power of two
* size of each grid cell edge. For example, to align the given rectangle
* to the edges of a grid containing 8x8 cells, use a value of 3.
*/
void guac_rect_align(guac_rect* rect, unsigned int bits);
/**
* Extends the given rectangle such that it contains at least the specified
* minimum rectangle.
*
* @param rect
* The rectangle to extend.
*
* @param min
* The minimum area which must be contained within the given rectangle.
*/
void guac_rect_extend(guac_rect* rect, const guac_rect* min);
/**
* Collapses the given rectangle such that it exists only within the bounds of
* the given maximum rectangle.
*
* @param rect
* The rectangle to collapse.
*
* @param max
* The maximum area in which the given rectangle can exist.
*/
void guac_rect_constrain(guac_rect* rect, const guac_rect* max);
/**
* Reduces the size of the given rectangle such that it does not exceed the
* given width and height. The aspect ratio of the given rectangle is
* preserved. If the original rectangle is already smaller than the given width
* and height, this function has no effect.
*
* @param rect
* The rectangle to shrink while preserving aspect ratio.
*
* @param max_width
* The maximum width that the given rectangle may have.
*
* @param max_height
* The maximum height that the given rectangle may have.
*/
void guac_rect_shrink(guac_rect* rect, int max_width, int max_height);
/**
* Returns whether the two given rectangles intersect.
*
* @param a
* One of the rectangles to check.
*
* @param b
* The other rectangle to check.
*
* @return
* Non-zero if the rectangles intersect, zero otherwise.
*/
int guac_rect_intersects(const guac_rect* a, const guac_rect* b);
/**
* Returns whether the given rectangle is empty. A rectangle is empty if it has
* no area (has an effective width or height of zero).
*
* @param rect
* The rectangle to test.
*
* @return
* Non-zero if the rectangle is empty, zero otherwise.
*/
int guac_rect_is_empty(const guac_rect* rect);
/**
* Returns the width of the given rectangle.
*
* @param rect
* The rectangle to determine the width of.
*
* @return
* The width of the given rectangle.
*/
int guac_rect_width(const guac_rect* rect);
/**
* Returns the height of the given rectangle.
*
* @param rect
* The rectangle to determine the height of.
*
* @return
* The height of the given rectangle.
*/
int guac_rect_height(const guac_rect* rect);
#endif

178
src/libguac/rect.c Normal file
View File

@ -0,0 +1,178 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "guacamole/rect.h"
/**
* Given a bitmask that is one less than a power of two (ie: 0xF, 0x1F, etc.),
* rounds the given value in the negative direction to the nearest multiple of
* that power of two. Positive values are rounded down towards zero while
* negative values are rounded up toward negative values of greater magnitude.
*
* @param value
* The value to round.
*
* @param mask
* A bitmask whose integer value is one less than a power of two.
*
* @return
* The given value, rounded to the nearest multiple of the power of two
* represented by the given mask, where that rounding is performed in the
* negative direction.
*/
#define GUAC_RECT_ROUND_NEG(value, mask) (value & ~mask)
/**
* Given a bitmask that is one less than a power of two (ie: 0xF, 0x1F, etc.),
* rounds the given value in the positive direction to the nearest multiple of
* that power of two. Negative values are rounded down towards zero while
* positive values are rounded up toward positive values of greater magnitude.
*
* @param value
* The value to round.
*
* @param mask
* A bitmask whose integer value is one less than a power of two.
*
* @return
* The given value, rounded to the nearest multiple of the power of two
* represented by the given mask, where that rounding is performed in the
* positive direction.
*/
#define GUAC_RECT_ROUND_POS(value, mask) ((value + mask) & ~mask)
void guac_rect_init(guac_rect* rect, int x, int y, int width, int height) {
*rect = (guac_rect) {
.left = x,
.top = y,
.right = width > 0 ? x + width : x,
.bottom = height > 0 ? y + height : y
};
}
void guac_rect_extend(guac_rect* rect, const guac_rect* min) {
/* The union of an empty rect and the provided rect should be that provided
* rect. Considering the garbage coordinates that may be present in an
* empty rect can otherwise produce incorrect results. */
if (guac_rect_is_empty(rect)) {
*rect = *min;
return;
}
/* Extend edges of rectangle such that it contains the provided minimum
* rectangle */
if (min->left < rect->left) rect->left = min->left;
if (min->top < rect->top) rect->top = min->top;
if (min->right > rect->right) rect->right = min->right;
if (min->bottom > rect->bottom) rect->bottom = min->bottom;
}
void guac_rect_constrain(guac_rect* rect, const guac_rect* max) {
/* Shrink edges of rectangle such that it is contained by the provided
* maximum rectangle */
if (max->left > rect->left) rect->left = max->left;
if (max->top > rect->top) rect->top = max->top;
if (max->right < rect->right) rect->right = max->right;
if (max->bottom < rect->bottom) rect->bottom = max->bottom;
}
void guac_rect_shrink(guac_rect* rect, int max_width, int max_height) {
int original_width = guac_rect_width(rect);
int original_height = guac_rect_height(rect);
/* Shrink only; do not _expand_ to reach the max width/height */
if (original_width < max_width) max_width = original_width;
if (original_height < max_height) max_height = original_height;
/* BOTH the width and height must be adjusted by the same factor in
* order to preserve aspect ratio. Choosing the smallest adjustment
* factor guarantees that the rectangle will be within bounds while
* preserving aspect ratio to the greatest degree possible (there
* is unavoidable integer rounding error). */
int scale_numerator, scale_denominator;
/* NOTE: The following test is mathematically equivalent to:
*
* if (max_width / original_width < max_height / original_height) {
* ...
* }
*
* but does not require floating point arithmetic. */
if (max_width * original_height < max_height * original_width) {
scale_numerator = max_width;
scale_denominator = original_width;
}
else {
scale_numerator = max_height;
scale_denominator = original_height;
}
rect->right = rect->left + original_width * scale_numerator / scale_denominator;
rect->bottom = rect->top + original_height * scale_numerator / scale_denominator;
}
void guac_rect_align(guac_rect* rect, unsigned int bits) {
if (bits == 0)
return;
int factor = 1 << bits;
int mask = factor - 1;
/* Expand and shift rectangle as necessary for its edges to be aligned
* along multiples of the given power of two */
rect->left = GUAC_RECT_ROUND_NEG(rect->left, mask);
rect->top = GUAC_RECT_ROUND_NEG(rect->top, mask);
rect->right = GUAC_RECT_ROUND_POS(rect->right, mask);
rect->bottom = GUAC_RECT_ROUND_POS(rect->bottom, mask);
}
int guac_rect_intersects(const guac_rect* a, const guac_rect* b) {
/* Two rectangles intersect if neither rectangle is wholly outside the
* other */
return !(
b->right <= a->left || a->right <= b->left
|| b->bottom <= a->top || a->bottom <= b->top
);
}
int guac_rect_is_empty(const guac_rect* rect) {
return rect->right <= rect->left || rect->bottom <= rect->top;
}
int guac_rect_width(const guac_rect* rect) {
int width = rect->right - rect->left;
return width > 0 ? width : 0;
}
int guac_rect_height(const guac_rect* rect) {
int height = rect->bottom - rect->top;
return height > 0 ? height : 0;
}

View File

@ -39,6 +39,8 @@ noinst_HEADERS = \
test_libguac_SOURCES = \
client/buffer_pool.c \
client/layer_pool.c \
fifo/fifo.c \
flag/flag.c \
id/generate.c \
mem/alloc.c \
mem/ckd_add.c \
@ -56,6 +58,11 @@ test_libguac_SOURCES = \
pool/next_free.c \
protocol/base64_decode.c \
protocol/guac_protocol_version.c \
rect/align.c \
rect/constrain.c \
rect/extend.c \
rect/init.c \
rect/intersects.c \
socket/fd_send_instruction.c \
socket/nested_send_instruction.c \
string/strdup.c \

View File

@ -0,0 +1,298 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <CUnit/CUnit.h>
#include <guacamole/fifo.h>
#include <guacamole/timestamp.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
/**
* The maximum number of milliseconds to wait for a test event to be added to a
* fifo.
*/
#define TEST_TIMEOUT 250
/**
* The maximum number of items permitted in test_fifo.
*/
#define TEST_FIFO_MAX_ITEMS 4
/**
* The rough amount of time to wait between fifo reads within the test thread,
* in milliseconds. A random delay between 0ms and this value will be added
* before each read. This is done to verify that the fifo behaves correctly
* for cases where the sending thread is producing data much faster than it's
* being read, slower than it's read, etc.
*/
#define TEST_READ_INTERVAL 10
/**
* Zero-terminated set of arbitrarily-chosen values that will be provided as
* the test_value of a sequence of test_events.
*/
unsigned int TEST_VALUES[] = {
32, 32, 226, 136, 167, 44, 44, 44,
226, 136, 167, 32, 32, 32, 32, 32,
65, 112, 97, 119, 99, 104, 101, 10,
32, 40, 226, 128, 162, 32, 226, 169,
138, 32, 226, 128, 162, 41, 32, 32,
71, 117, 97, 99, 97, 109, 101, 111,
119, 108, 101, 33, 10, /* END */ 0
};
/**
* Test event for an event fifo. This particular event contains a single
* integer for verifying that events are received in the order expected, and a
* chunk of arbitrary padding to ensure the base fifo is capable of supporting
* events of arbitrary size.
*/
typedef union test_event {
/**
* Arbitrary integer test value. This value is primarily intended to allow
* unit tests to verify the order of received events matches the order they
* were sent.
*/
unsigned int test_value;
/**
* Arbitrary padding. This member is entirely ignored and is used only to
* increase the storage size of this event. A wonky prime value is used
* here to help ensure the tests inherently verify that the base fifo
* implementation does not somehow depend on power-of-two alignment.
*/
char padding[73];
} test_event;
/**
* Test event fifo that extends the guac_fifo base. This event
* fifo differs from the base only in that it specifically stores test_events
* alongside an array of expected event values.
*/
typedef struct test_fifo {
/**
* The base fifo implementation.
*/
guac_fifo base;
/**
* Storage for all event items in this fifo.
*/
test_event items[TEST_FIFO_MAX_ITEMS];
/**
* A zero-terminated array of all integer values expected to be received as
* test events, in the order that they are expected to be received.
*/
unsigned int* expected_values;
} test_fifo;
/**
* Initializes the given test_fifo, assigning the given set of expected
* values for later reference by unit tests. The pointer to the expected values
* MUST remain valid until the text_fifo is destroyed.
*
* @param fifo
* The test_fifo to initialize.
*
* @param expected_values
* The zero-terminated set of expected values to be associated with the
* given test_fifo.
*/
void test_fifo_init(test_fifo* fifo, unsigned int* expected_values) {
guac_fifo_init((guac_fifo*) fifo, &fifo->items,
TEST_FIFO_MAX_ITEMS, sizeof(test_event));
fifo->expected_values = expected_values;
}
/**
* Destroys the given test_fifo, releasing any associated resources. It
* is safe to clean up the set of expected values originally provided to
* test_fifo_init() after this function has been invoked.
*
* @param fifo
* The test_fifo to destroy.
*/
void test_fifo_destroy(test_fifo* fifo) {
guac_fifo_destroy((guac_fifo*) fifo);
}
/**
* Thread that continuously reads events from the given test_fifo,
* verifying that each expected value is read in the correct order.
*
* @param data
* The test_fifo to read from.
*
* @return
* Always NULL.
*/
static void* queue_read_thread(void* data) {
test_fifo* fifo = (test_fifo*) data;
test_event event;
/* Continuously read values until zero (end of expected values) is reached */
for (unsigned int* current_expected_value = fifo->expected_values;
/* Exit condition checked in body of loop*/; current_expected_value++) {
/* Induce random delays in reading to simulate real-world conditions
* that may cause the fifo to fill */
guac_timestamp_msleep(rand() % TEST_READ_INTERVAL);
int retval = guac_fifo_timed_dequeue(
(guac_fifo*) fifo, &event, TEST_TIMEOUT);
/* A value of zero marks the end of the set of expected values, so the
* fifo SHOULD fail to read at this point */
if (*current_expected_value == 0) {
printf(" | END\n");
CU_ASSERT_FALSE(retval);
break;
}
/* For all other cases, the fifo should succeed in reading the next
* event, and the value of that event should match the current value
* from the set of expected values */
else {
printf(" | %i\n", event.test_value);
CU_ASSERT_TRUE(retval);
CU_ASSERT_EQUAL(event.test_value, *current_expected_value);
}
/* Do not continue waiting for more events if the fifo is timing out
* incorrectly */
if (!retval)
break;
}
return NULL;
}
/**
* Generic base test that sends all values in TEST_VALUES at the given
* interval. Values are read by a separate thread that instead reads at
* TEST_READ_INTERVAL, allowing the send/receive rates to differ. Timing
* between each send/receive attempt is varied randomly but is always bounded
* by the relevant interval.
*
* @param send_interval
* The rough number of milliseconds to wait between sending each event. The
* true number of milliseconds that elapse between each subsequent send
* attempt is varied randomly, with this provided value functioning as an
* upper bound.
*/
static void verify_send_receive(int send_interval) {
test_fifo fifo;
/* Create a test fifo that verifies each value within TEST_VALUES is
* received in order */
test_fifo_init(&fifo, TEST_VALUES);
/* Both this function and the thread it spawns will log sent/received event
* values to STDOUT for sake of debugging and verification */
printf("Sent | Received\n"
"---- | --------\n");
/* Spawn thread that can independently wait for events to be flagged */
pthread_t test_thread;
CU_ASSERT_FALSE_FATAL(pthread_create(&test_thread, NULL, queue_read_thread, &fifo));
/* Send all test values in order */
for (unsigned int* current = TEST_VALUES; *current != 0; current++) {
/* Pull next test value from TEST_VALUES array */
test_event event = {
.test_value = *current
};
/* Induce random delays in reading to simulate real-world conditions
* that may cause the fifo to fill */
if (send_interval)
guac_timestamp_msleep(rand() % send_interval);
printf("%4i |\n", event.test_value);
guac_fifo_enqueue((guac_fifo*) &fifo, &event);
}
/* All test values have now been sent */
printf(" END |\n");
/* Wait for thread to finish waiting for events */
CU_ASSERT_FALSE(pthread_join(test_thread, NULL));
test_fifo_destroy(&fifo);
}
/**
* Verify that the base fifo implementation functions correctly when events
* are sent slower than they are read.
*/
void test_fifo__slow_add() {
/* Add context for subsequent logging of sent/received values to STDOUT */
printf("-------- %s() --------\n", __func__);
/* Send at half the speed of the reading thread */
verify_send_receive(TEST_READ_INTERVAL * 2);
}
/**
* Verify that the base fifo implementation functions correctly when events
* are sent faster than they are read.
*/
void test_fifo__fast_add() {
/* Add context for subsequent logging of sent/received values to STDOUT */
printf("-------- %s() --------\n", __func__);
/* Send as quickly as possible (much faster than reading thread) */
verify_send_receive(0);
}
/**
* Verify that the base fifo implementation functions correctly when events
* are sent at roughly the same speed as the reading thread.
*/
void test_fifo__interleaved() {
/* Add context for subsequent logging of sent/received values to STDOUT */
printf("-------- %s() --------\n", __func__);
/* Send at roughly same speed as reading thread */
verify_send_receive(TEST_READ_INTERVAL);
}

View File

@ -0,0 +1,168 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <CUnit/CUnit.h>
#include <guacamole/flag.h>
#include <pthread.h>
#include <stdint.h>
/**
* The maximum number of milliseconds to wait for a test event to be flagged.
*/
#define TEST_TIMEOUT 250
/**
* Arbitrary test event #1.
*/
#define TEST_EVENT_A 1
/**
* Arbitrary test event #2.
*/
#define TEST_EVENT_B 2
/**
* Arbitrary test event #3.
*/
#define TEST_EVENT_C 16
/**
* Arbitrary test event #4.
*/
#define TEST_EVENT_D 64
/**
* Thread that waits up to TEST_TIMEOUT milliseconds for TEST_EVENT_B or
* TEST_EVENT_C to be flagged on a given guac_flag, returning the
* result of that wait.
*
* @param data
* The guac_flag to wait on.
*
* @return
* An intptr_t (NOT a pointer) containing the value returned by
* guac_flag_timedwait_and_lock().
*/
static void* flag_wait_thread(void* data) {
guac_flag* flag = (guac_flag*) data;
int retval = guac_flag_timedwait_and_lock(flag, TEST_EVENT_B | TEST_EVENT_C, TEST_TIMEOUT);
guac_flag_unlock(flag);
return (void*) ((intptr_t) retval);
}
/**
* Waits up to TEST_TIMEOUT milliseconds for TEST_EVENT_B or TEST_EVENT_C to be
* flagged on the given guac_flag, returning the result of that
* wait. If provided, optional sets of flags will be additionally set or
* cleared after the wait for the flag has started.
*
* @param flag
* The guac_flag to wait on.
*
* @param set_flags
* The flags that should be set, if any.
*
* @param clear_flags
* The flags that should be cleared, if any.
*
* @return
* The value returned by guac_flag_timedwait_and_lock() after
* waiting for TEST_EVENT_B or TEST_EVENT_C to be flagged.
*/
static int wait_for_flag(guac_flag* flag, int set_flags, int clear_flags) {
/* Spawn thread that can independently wait for events to be flagged */
pthread_t test_thread;
CU_ASSERT_FALSE_FATAL(pthread_create(&test_thread, NULL, flag_wait_thread, flag));
/* Set/clear any requested event flags */
if (set_flags) guac_flag_set(flag, set_flags);
if (clear_flags) guac_flag_clear(flag, clear_flags);
/* Wait for thread to finish waiting for events */
void* retval;
CU_ASSERT_FALSE(pthread_join(test_thread, &retval));
return (int) ((intptr_t) retval);
}
/**
* Verifies that a thread waiting on a particular event will NOT be notified if
* absolutely zero events ever occur.
*/
void test_flag__ignore_total_silence() {
guac_flag test_flag;
guac_flag_init(&test_flag);
/* Verify no interesting events occur if we set zero flags */
CU_ASSERT_FALSE(wait_for_flag(&test_flag, 0, 0));
guac_flag_destroy(&test_flag);
}
/**
* Verifies that a thread waiting on a particular event will NOT be notified if
* that event never occurs, even if other events are occurring.
*/
void test_flag__ignore_uninteresting_events() {
guac_flag test_flag;
guac_flag_init(&test_flag);
/* Verify no interesting events occurred if we only fire uninteresting
* events */
CU_ASSERT_FALSE(wait_for_flag(&test_flag, TEST_EVENT_A, 0));
CU_ASSERT_FALSE(wait_for_flag(&test_flag, TEST_EVENT_D, TEST_EVENT_C));
CU_ASSERT_FALSE(wait_for_flag(&test_flag, TEST_EVENT_A | TEST_EVENT_D, 0));
guac_flag_destroy(&test_flag);
}
/**
* Verifies that a thread waiting on a particular event will be notified when
* that event occurs.
*/
void test_flag__wake_for_interesting_events() {
guac_flag test_flag;
guac_flag_init(&test_flag);
/* Verify interesting events are reported if fired ... */
CU_ASSERT_TRUE(wait_for_flag(&test_flag, TEST_EVENT_B | TEST_EVENT_C, 0));
/* ... and continue to be reported if they remain set ... */
guac_flag_clear(&test_flag, TEST_EVENT_B);
CU_ASSERT_TRUE(wait_for_flag(&test_flag, 0, 0));
/* ... but not if all interesting events have since been cleared */
guac_flag_clear(&test_flag, TEST_EVENT_C);
CU_ASSERT_FALSE(wait_for_flag(&test_flag, 0, 0));
guac_flag_destroy(&test_flag);
}

View File

@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <CUnit/CUnit.h>
#include <guacamole/rect.h>
/**
* Test which verifies guac_rect_align() properly shifts and resizes rectangles
* to fit an NxN grid.
*/
void test_rect__align() {
/* A cell size of 4 is 2^4 (16) */
const int cell_size = 4;
guac_rect rect;
/* Simple case where only the rectangle dimensions need adjustment */
guac_rect_init(&rect, 0, 0, 25, 25);
guac_rect_align(&rect, cell_size);
CU_ASSERT_EQUAL(0, rect.left);
CU_ASSERT_EQUAL(0, rect.top);
CU_ASSERT_EQUAL(32, rect.right);
CU_ASSERT_EQUAL(32, rect.bottom);
/* More complex case where the rectangle location AND dimensions both need
* adjustment */
guac_rect_init(&rect, 75, 75, 25, 25);
guac_rect_align(&rect, cell_size);
CU_ASSERT_EQUAL(64, rect.left);
CU_ASSERT_EQUAL(64, rect.top);
CU_ASSERT_EQUAL(112, rect.right);
CU_ASSERT_EQUAL(112, rect.bottom);
/* Complex case where the rectangle location AND dimensions both need
* adjustment, and the rectangle location is negative */
guac_rect_init(&rect, -5, -5, 25, 25);
guac_rect_align(&rect, cell_size);
CU_ASSERT_EQUAL(-16, rect.left);
CU_ASSERT_EQUAL(-16, rect.top);
CU_ASSERT_EQUAL(32, rect.right);
CU_ASSERT_EQUAL(32, rect.bottom);
/* Complex case where the rectangle location AND dimensions both need
* adjustment, and all rectangle coordinates are negative */
guac_rect_init(&rect, -30, -30, 25, 25);
guac_rect_align(&rect, cell_size);
CU_ASSERT_EQUAL(-32, rect.left);
CU_ASSERT_EQUAL(-32, rect.top);
CU_ASSERT_EQUAL(0, rect.right);
CU_ASSERT_EQUAL(0, rect.bottom);
}

View File

@ -0,0 +1,42 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <CUnit/CUnit.h>
#include <guacamole/rect.h>
/**
* Test which verifies that guac_rect_constrain() restricts a given rectangle
* to arbitrary bounds.
*/
void test_rect__constrain() {
guac_rect max;
guac_rect rect;
guac_rect_init(&rect, -10, -10, 110, 110);
guac_rect_init(&max, 0, 0, 100, 100);
guac_rect_constrain(&rect, &max);
CU_ASSERT_EQUAL(0, rect.left);
CU_ASSERT_EQUAL(0, rect.top);
CU_ASSERT_EQUAL(100, rect.right);
CU_ASSERT_EQUAL(100, rect.bottom);
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <CUnit/CUnit.h>
#include <guacamole/rect.h>
/**
* Test which verifies that guac_rect_extend() expands the given rectangle as
* necessary to contain at least the given bounds.
*/
void test_rect__extend() {
guac_rect max;
guac_rect rect;
guac_rect_init(&rect, 10, 10, 90, 90);
guac_rect_init(&max, 0, 0, 100, 100);
guac_rect_extend(&rect, &max);
CU_ASSERT_EQUAL(0, rect.left);
CU_ASSERT_EQUAL(0, rect.top);
CU_ASSERT_EQUAL(100, rect.right);
CU_ASSERT_EQUAL(100, rect.bottom);
}

View File

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <CUnit/CUnit.h>
#include <guacamole/rect.h>
/**
* Test which verifies rectangle initialization via guac_rect_init().
*/
void test_rect__init() {
guac_rect max;
guac_rect_init(&max, 0, 0, 100, 100);
CU_ASSERT_EQUAL(0, max.left);
CU_ASSERT_EQUAL(0, max.top);
CU_ASSERT_EQUAL(100, max.right);
CU_ASSERT_EQUAL(100, max.bottom);
}

View File

@ -0,0 +1,85 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <CUnit/CUnit.h>
#include <guacamole/rect.h>
/**
* Test which verifies intersection testing via guac_rect_intersects().
*/
void test_rect__intersects() {
int res;
guac_rect min;
guac_rect rect;
/* NOTE: This rectangle will extend from (10, 10) inclusive to (20, 20) exclusive */
guac_rect_init(&min, 10, 10, 10, 10);
/* Rectangle that does not intersect by a fair margin */
guac_rect_init(&rect, 25, 25, 5, 5);
res = guac_rect_intersects(&rect, &min);
CU_ASSERT_FALSE(res);
/* Rectangle that barely does not intersect (one pixel away from intersecting) */
guac_rect_init(&rect, 20, 20, 5, 5);
res = guac_rect_intersects(&rect, &min);
CU_ASSERT_FALSE(res);
/* Rectangle that intersects by being entirely inside the other */
guac_rect_init(&rect, 11, 11, 5, 5);
res = guac_rect_intersects(&rect, &min);
CU_ASSERT_TRUE(res);
/* Rectangle that intersects with the upper-left corner */
guac_rect_init(&rect, 8, 8, 5, 5);
res = guac_rect_intersects(&rect, &min);
CU_ASSERT_TRUE(res);
/* Rectangle that intersects with the lower-right corner */
guac_rect_init(&rect, 18, 18, 5, 5);
res = guac_rect_intersects(&rect, &min);
CU_ASSERT_TRUE(res);
/* Rectangle that intersects with the uppper-left corner and shares both
* the upper and left edges */
guac_rect_init(&rect, 10, 10, 5, 5);
res = guac_rect_intersects(&rect, &min);
CU_ASSERT_TRUE(res);
/* Rectangle that barely fails to intersect the upper-left corner (one
* pixel away) */
guac_rect_init(&rect, 5, 10, 5, 5);
res = guac_rect_intersects(&rect, &min);
CU_ASSERT_FALSE(res);
/* Rectangle that barely fails to intersect the upper-right corner (one
* pixel away) */
guac_rect_init(&rect, 20, 10, 5, 5);
res = guac_rect_intersects(&rect, &min);
CU_ASSERT_FALSE(res);
/* Rectangle that intersects by entirely containing the other */
guac_rect_init(&rect, 5, 5, 20, 20);
res = guac_rect_intersects(&rect, &min);
CU_ASSERT_TRUE(res);
}