From 104006c6e24ccde1c99bc8e39a0264cf33609017 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Mon, 3 Jun 2024 14:22:53 -0700 Subject: [PATCH] GUACAMOLE-377: Establish public guac_display API for efficient rendering via Guacamole protocol. --- configure.ac | 5 + src/libguac/Makefile.am | 108 ++-- src/libguac/display-builtin-cursors.c | 149 +++++ src/libguac/display-builtin-cursors.h | 93 +++ src/libguac/display-cursor.c | 96 +++ src/libguac/display-flush.c | 298 +++++++++ src/libguac/display-layer-list.c | 430 +++++++++++++ src/libguac/display-layer.c | 250 ++++++++ src/libguac/display-plan-combine.c | 322 ++++++++++ src/libguac/display-plan-rect.c | 235 ++++++++ src/libguac/display-plan-search.c | 428 +++++++++++++ src/libguac/display-plan.c | 275 +++++++++ src/libguac/display-plan.h | 385 ++++++++++++ src/libguac/display-priv.h | 704 ++++++++++++++++++++++ src/libguac/display-worker.c | 602 ++++++++++++++++++ src/libguac/display.c | 331 ++++++++++ src/libguac/fifo.c | 210 +++++++ src/libguac/flag.c | 167 +++++ src/libguac/guacamole/assert.h | 47 ++ src/libguac/guacamole/display-constants.h | 47 ++ src/libguac/guacamole/display-types.h | 91 +++ src/libguac/guacamole/display.h | 616 +++++++++++++++++++ src/libguac/guacamole/fifo-constants.h | 57 ++ src/libguac/guacamole/fifo-types.h | 53 ++ src/libguac/guacamole/fifo.h | 314 ++++++++++ src/libguac/guacamole/flag-types.h | 36 ++ src/libguac/guacamole/flag.h | 244 ++++++++ src/libguac/guacamole/rect-types.h | 33 + src/libguac/guacamole/rect.h | 271 +++++++++ src/libguac/rect.c | 178 ++++++ src/libguac/tests/Makefile.am | 7 + src/libguac/tests/fifo/fifo.c | 298 +++++++++ src/libguac/tests/flag/flag.c | 168 ++++++ src/libguac/tests/rect/align.c | 70 +++ src/libguac/tests/rect/constrain.c | 42 ++ src/libguac/tests/rect/extend.c | 41 ++ src/libguac/tests/rect/init.c | 38 ++ src/libguac/tests/rect/intersects.c | 85 +++ 38 files changed, 7784 insertions(+), 40 deletions(-) create mode 100644 src/libguac/display-builtin-cursors.c create mode 100644 src/libguac/display-builtin-cursors.h create mode 100644 src/libguac/display-cursor.c create mode 100644 src/libguac/display-flush.c create mode 100644 src/libguac/display-layer-list.c create mode 100644 src/libguac/display-layer.c create mode 100644 src/libguac/display-plan-combine.c create mode 100644 src/libguac/display-plan-rect.c create mode 100644 src/libguac/display-plan-search.c create mode 100644 src/libguac/display-plan.c create mode 100644 src/libguac/display-plan.h create mode 100644 src/libguac/display-priv.h create mode 100644 src/libguac/display-worker.c create mode 100644 src/libguac/display.c create mode 100644 src/libguac/fifo.c create mode 100644 src/libguac/flag.c create mode 100644 src/libguac/guacamole/assert.h create mode 100644 src/libguac/guacamole/display-constants.h create mode 100644 src/libguac/guacamole/display-types.h create mode 100644 src/libguac/guacamole/display.h create mode 100644 src/libguac/guacamole/fifo-constants.h create mode 100644 src/libguac/guacamole/fifo-types.h create mode 100644 src/libguac/guacamole/fifo.h create mode 100644 src/libguac/guacamole/flag-types.h create mode 100644 src/libguac/guacamole/flag.h create mode 100644 src/libguac/guacamole/rect-types.h create mode 100644 src/libguac/guacamole/rect.h create mode 100644 src/libguac/rect.c create mode 100644 src/libguac/tests/fifo/fifo.c create mode 100644 src/libguac/tests/flag/flag.c create mode 100644 src/libguac/tests/rect/align.c create mode 100644 src/libguac/tests/rect/constrain.c create mode 100644 src/libguac/tests/rect/extend.c create mode 100644 src/libguac/tests/rect/init.c create mode 100644 src/libguac/tests/rect/intersects.c diff --git a/configure.ac b/configure.ac index fb7612f3..a66f5362 100644 --- a/configure.ac +++ b/configure.ac @@ -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], diff --git a/src/libguac/Makefile.am b/src/libguac/Makefile.am index 2386f9c3..6aedb404 100644 --- a/src/libguac/Makefile.am +++ b/src/libguac/Makefile.am @@ -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 diff --git a/src/libguac/display-builtin-cursors.c b/src/libguac/display-builtin-cursors.c new file mode 100644 index 00000000..a5d12fa9 --- /dev/null +++ b/src/libguac/display-builtin-cursors.c @@ -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 + +}; diff --git a/src/libguac/display-builtin-cursors.h b/src/libguac/display-builtin-cursors.h new file mode 100644 index 00000000..01ec3818 --- /dev/null +++ b/src/libguac/display-builtin-cursors.h @@ -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 + +/** + * 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 diff --git a/src/libguac/display-cursor.c b/src/libguac/display-cursor.c new file mode 100644 index 00000000..682da3b5 --- /dev/null +++ b/src/libguac/display-cursor.c @@ -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 + +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); + +} diff --git a/src/libguac/display-flush.c b/src/libguac/display-flush.c new file mode 100644 index 00000000..57777b41 --- /dev/null +++ b/src/libguac/display-flush.c @@ -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 + +/** + * 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(¤t->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); + } + +} diff --git a/src/libguac/display-layer-list.c b/src/libguac/display-layer-list.c new file mode 100644 index 00000000..698cf2c1 --- /dev/null +++ b/src/libguac/display-layer-list.c @@ -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 +#include +#include + +/** + * 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; + +} diff --git a/src/libguac/display-layer.c b/src/libguac/display-layer.c new file mode 100644 index 00000000..0bde123b --- /dev/null +++ b/src/libguac/display-layer.c @@ -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 +#include +#include + +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); + +} diff --git a/src/libguac/display-plan-combine.c b/src/libguac/display-plan-combine.c new file mode 100644 index 00000000..517a02dc --- /dev/null +++ b/src/libguac/display-plan-combine.c @@ -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(¤t->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(¤t->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; + + } + +} diff --git a/src/libguac/display-plan-rect.c b/src/libguac/display-plan-rect.c new file mode 100644 index 00000000..bad1253a --- /dev/null +++ b/src/libguac/display-plan-rect.c @@ -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 +#include + +/** + * 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++; + + } + +} diff --git a/src/libguac/display-plan-search.c b/src/libguac/display-plan-search.c new file mode 100644 index 00000000..fef119cb --- /dev/null +++ b/src/libguac/display-plan-search.c @@ -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 +#include + +/** + * 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, ¤t->pending_frame.dirty); + + guac_hash_foreach_image_rect(plan, ¤t->last_frame, &search_region, + PFR_LFR_guac_display_plan_find_copies, current); + + current = current->last_frame.next; + + } + +} diff --git a/src/libguac/display-plan.c b/src/libguac/display-plan.c new file mode 100644 index 00000000..85f6a19c --- /dev/null +++ b/src/libguac/display-plan.c @@ -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 +#include + +/** + * 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(¤t->pending_frame.dirty, ¤t_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++); + } + +} diff --git a/src/libguac/display-plan.h b/src/libguac/display-plan.h new file mode 100644 index 00000000..3e844505 --- /dev/null +++ b/src/libguac/display-plan.h @@ -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 +#include + +/** + * 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 diff --git a/src/libguac/display-priv.h b/src/libguac/display-priv.h new file mode 100644 index 00000000..62558f61 --- /dev/null +++ b/src/libguac/display-priv.h @@ -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 + +/** + * 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 diff --git a/src/libguac/display-worker.c b/src/libguac/display-worker.c new file mode 100644 index 00000000..7a578b72 --- /dev/null +++ b/src/libguac/display-worker.c @@ -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 +#include +#include +#include + +/** + * 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 = ¤t->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; + +} diff --git a/src/libguac/display.c b/src/libguac/display.c new file mode 100644 index 00000000..ecda756d --- /dev/null +++ b/src/libguac/display.c @@ -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 +#endif + +#include +#include +#include +#include + +/** + * 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); + + } + +} diff --git a/src/libguac/fifo.c b/src/libguac/fifo.c new file mode 100644 index 00000000..30aef7c6 --- /dev/null +++ b/src/libguac/fifo.c @@ -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 +#include +#include + +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; + +} + diff --git a/src/libguac/flag.c b/src/libguac/flag.c new file mode 100644 index 00000000..5f44ac31 --- /dev/null +++ b/src/libguac/flag.c @@ -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 +#include +#include +#include +#include + +/** + * 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; + +} + diff --git a/src/libguac/guacamole/assert.h b/src/libguac/guacamole/assert.h new file mode 100644 index 00000000..f8fd246c --- /dev/null +++ b/src/libguac/guacamole/assert.h @@ -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 +#include + +/** + * 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 diff --git a/src/libguac/guacamole/display-constants.h b/src/libguac/guacamole/display-constants.h new file mode 100644 index 00000000..56eba86c --- /dev/null +++ b/src/libguac/guacamole/display-constants.h @@ -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 diff --git a/src/libguac/guacamole/display-types.h b/src/libguac/guacamole/display-types.h new file mode 100644 index 00000000..6014e9b8 --- /dev/null +++ b/src/libguac/guacamole/display-types.h @@ -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 diff --git a/src/libguac/guacamole/display.h b/src/libguac/guacamole/display.h new file mode 100644 index 00000000..23eaafcf --- /dev/null +++ b/src/libguac/guacamole/display.h @@ -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 +#include + +/** + * 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 diff --git a/src/libguac/guacamole/fifo-constants.h b/src/libguac/guacamole/fifo-constants.h new file mode 100644 index 00000000..ff90d71c --- /dev/null +++ b/src/libguac/guacamole/fifo-constants.h @@ -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 + diff --git a/src/libguac/guacamole/fifo-types.h b/src/libguac/guacamole/fifo-types.h new file mode 100644 index 00000000..7ab78b46 --- /dev/null +++ b/src/libguac/guacamole/fifo-types.h @@ -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 + diff --git a/src/libguac/guacamole/fifo.h b/src/libguac/guacamole/fifo.h new file mode 100644 index 00000000..bfc3dda7 --- /dev/null +++ b/src/libguac/guacamole/fifo.h @@ -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 +#include + +/** + * 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 + diff --git a/src/libguac/guacamole/flag-types.h b/src/libguac/guacamole/flag-types.h new file mode 100644 index 00000000..e67fa5e9 --- /dev/null +++ b/src/libguac/guacamole/flag-types.h @@ -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 + diff --git a/src/libguac/guacamole/flag.h b/src/libguac/guacamole/flag.h new file mode 100644 index 00000000..00f2f1af --- /dev/null +++ b/src/libguac/guacamole/flag.h @@ -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 + +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 + diff --git a/src/libguac/guacamole/rect-types.h b/src/libguac/guacamole/rect-types.h new file mode 100644 index 00000000..73a1bdc9 --- /dev/null +++ b/src/libguac/guacamole/rect-types.h @@ -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 + diff --git a/src/libguac/guacamole/rect.h b/src/libguac/guacamole/rect.h new file mode 100644 index 00000000..477fec6b --- /dev/null +++ b/src/libguac/guacamole/rect.h @@ -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 diff --git a/src/libguac/rect.c b/src/libguac/rect.c new file mode 100644 index 00000000..2d372fe6 --- /dev/null +++ b/src/libguac/rect.c @@ -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; +} diff --git a/src/libguac/tests/Makefile.am b/src/libguac/tests/Makefile.am index dba84241..1d2f45f1 100644 --- a/src/libguac/tests/Makefile.am +++ b/src/libguac/tests/Makefile.am @@ -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 \ diff --git a/src/libguac/tests/fifo/fifo.c b/src/libguac/tests/fifo/fifo.c new file mode 100644 index 00000000..67617e2f --- /dev/null +++ b/src/libguac/tests/fifo/fifo.c @@ -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 +#include +#include +#include +#include +#include +#include + +/** + * 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); + +} + diff --git a/src/libguac/tests/flag/flag.c b/src/libguac/tests/flag/flag.c new file mode 100644 index 00000000..c822a6a7 --- /dev/null +++ b/src/libguac/tests/flag/flag.c @@ -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 +#include +#include +#include + +/** + * 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); + +} + diff --git a/src/libguac/tests/rect/align.c b/src/libguac/tests/rect/align.c new file mode 100644 index 00000000..bed19726 --- /dev/null +++ b/src/libguac/tests/rect/align.c @@ -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 +#include + +/** + * 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); + +} + diff --git a/src/libguac/tests/rect/constrain.c b/src/libguac/tests/rect/constrain.c new file mode 100644 index 00000000..18ddc7a8 --- /dev/null +++ b/src/libguac/tests/rect/constrain.c @@ -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 +#include + +/** + * 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); + +} + diff --git a/src/libguac/tests/rect/extend.c b/src/libguac/tests/rect/extend.c new file mode 100644 index 00000000..264ab3bf --- /dev/null +++ b/src/libguac/tests/rect/extend.c @@ -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 +#include + +/** + * 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); + +} + diff --git a/src/libguac/tests/rect/init.c b/src/libguac/tests/rect/init.c new file mode 100644 index 00000000..c91efec2 --- /dev/null +++ b/src/libguac/tests/rect/init.c @@ -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 +#include + +/** + * 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); + +} + diff --git a/src/libguac/tests/rect/intersects.c b/src/libguac/tests/rect/intersects.c new file mode 100644 index 00000000..e6b005e7 --- /dev/null +++ b/src/libguac/tests/rect/intersects.c @@ -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 +#include + +/** + * 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); + +} +