GUACAMOLE-377: Establish public guac_display API for efficient rendering via Guacamole protocol.
This commit is contained in:
parent
c5702d1d4e
commit
104006c6e2
@ -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],
|
||||
|
||||
@ -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
|
||||
|
||||
149
src/libguac/display-builtin-cursors.c
Normal file
149
src/libguac/display-builtin-cursors.c
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "display-builtin-cursors.h"
|
||||
|
||||
/**
|
||||
* Opaque black. This macro evaluates to the 4 bytes of the single pixel of a
|
||||
* 32-bit ARGB image that represent opaque black and is expected to be used
|
||||
* only within this file to help make embedded cursor graphics more readable.
|
||||
*/
|
||||
#define X 0x00,0x00,0x00,0xFF
|
||||
|
||||
/**
|
||||
* Opaque gray. This macro evaluates to the 4 bytes of the single pixel of a
|
||||
* 32-bit ARGB image that represent opaque gray and is expected to be used only
|
||||
* within this file to help make embedded cursor graphics more readable.
|
||||
*/
|
||||
#define U 0x80,0x80,0x80,0xFF
|
||||
|
||||
/**
|
||||
* Opaque white. This macro evaluates to the 4 bytes of the single pixel of a
|
||||
* 32-bit ARGB image that represent opaque white and is expected to be used
|
||||
* only within this file to help make embedded cursor graphics more readable.
|
||||
*/
|
||||
#define O 0xFF,0xFF,0xFF,0xFF
|
||||
|
||||
/**
|
||||
* Full transparency. This macro evaluates to the 4 bytes of the single pixel
|
||||
* of a 32-bit ARGB image that represent full transparency and is expected to
|
||||
* be used only within this file to help make embedded cursor graphics more
|
||||
* readable.
|
||||
*/
|
||||
#define _ 0x00,0x00,0x00,0x00
|
||||
|
||||
const guac_display_builtin_cursor guac_display_cursor_none = {
|
||||
|
||||
.hotspot_x = 0,
|
||||
.hotspot_y = 0,
|
||||
|
||||
.buffer = (unsigned char[]) {
|
||||
_ /* Single, transparent pixel */
|
||||
},
|
||||
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
.stride = 4
|
||||
|
||||
};
|
||||
|
||||
const guac_display_builtin_cursor guac_display_cursor_dot = {
|
||||
|
||||
.hotspot_x = 2,
|
||||
.hotspot_y = 2,
|
||||
|
||||
.buffer = (unsigned char[]) {
|
||||
|
||||
_,O,O,O,_,
|
||||
O,X,X,X,O,
|
||||
O,X,X,X,O,
|
||||
O,X,X,X,O,
|
||||
_,O,O,O,_
|
||||
|
||||
},
|
||||
|
||||
.width = 5,
|
||||
.height = 5,
|
||||
.stride = 20
|
||||
|
||||
};
|
||||
|
||||
const guac_display_builtin_cursor guac_display_cursor_ibar = {
|
||||
|
||||
.hotspot_x = 3,
|
||||
.hotspot_y = 7,
|
||||
|
||||
.buffer = (unsigned char[]) {
|
||||
|
||||
X,X,X,X,X,X,X,
|
||||
X,O,O,U,O,O,X,
|
||||
X,X,X,O,X,X,X,
|
||||
_,_,X,O,X,_,_,
|
||||
_,_,X,O,X,_,_,
|
||||
_,_,X,O,X,_,_,
|
||||
_,_,X,O,X,_,_,
|
||||
_,_,X,O,X,_,_,
|
||||
_,_,X,O,X,_,_,
|
||||
_,_,X,O,X,_,_,
|
||||
_,_,X,O,X,_,_,
|
||||
_,_,X,O,X,_,_,
|
||||
_,_,X,O,X,_,_,
|
||||
X,X,X,O,X,X,X,
|
||||
X,O,O,U,O,O,X,
|
||||
X,X,X,X,X,X,X
|
||||
|
||||
},
|
||||
|
||||
.width = 7,
|
||||
.height = 16,
|
||||
.stride = 28
|
||||
|
||||
};
|
||||
|
||||
const guac_display_builtin_cursor guac_display_cursor_pointer = {
|
||||
|
||||
.hotspot_x = 0,
|
||||
.hotspot_y = 0,
|
||||
|
||||
.buffer = (unsigned char[]) {
|
||||
|
||||
O,_,_,_,_,_,_,_,_,_,_,
|
||||
O,O,_,_,_,_,_,_,_,_,_,
|
||||
O,X,O,_,_,_,_,_,_,_,_,
|
||||
O,X,X,O,_,_,_,_,_,_,_,
|
||||
O,X,X,X,O,_,_,_,_,_,_,
|
||||
O,X,X,X,X,O,_,_,_,_,_,
|
||||
O,X,X,X,X,X,O,_,_,_,_,
|
||||
O,X,X,X,X,X,X,O,_,_,_,
|
||||
O,X,X,X,X,X,X,X,O,_,_,
|
||||
O,X,X,X,X,X,X,X,X,O,_,
|
||||
O,X,X,X,X,X,O,O,O,O,O,
|
||||
O,X,X,O,X,X,O,_,_,_,_,
|
||||
O,X,O,_,O,X,X,O,_,_,_,
|
||||
O,O,_,_,O,X,X,O,_,_,_,
|
||||
O,_,_,_,_,O,X,X,O,_,_,
|
||||
_,_,_,_,_,O,O,O,O,_,_
|
||||
|
||||
},
|
||||
|
||||
.width = 11,
|
||||
.height = 16,
|
||||
.stride = 44
|
||||
|
||||
};
|
||||
93
src/libguac/display-builtin-cursors.h
Normal file
93
src/libguac/display-builtin-cursors.h
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_DISPLAY_BUILTIN_CURSORS_H
|
||||
#define GUAC_DISPLAY_BUILTIN_CURSORS_H
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Mouse cursor image that is built into libguac. Each actual instance of this
|
||||
* structure will correspond to a value within the guac_display_cursor_type
|
||||
* enum.
|
||||
*/
|
||||
typedef struct guac_display_builtin_cursor {
|
||||
|
||||
/**
|
||||
* The raw, 32-bit ARGB image for this mouse cursor.
|
||||
*/
|
||||
const unsigned char* const buffer;
|
||||
|
||||
/**
|
||||
* The width of this mouse cursor image, in pixels.
|
||||
*/
|
||||
const unsigned int width;
|
||||
|
||||
/**
|
||||
* The height of this mouse cursor image, in pixels.
|
||||
*/
|
||||
const unsigned int height;
|
||||
|
||||
/**
|
||||
* The size of each row of image data, in bytes.
|
||||
*/
|
||||
const size_t stride;
|
||||
|
||||
/**
|
||||
* The X coordinate of the relative position of the pointer hotspot within
|
||||
* the cursor image. The hotspot is the location that the mouse pointer is
|
||||
* actually reported, with the cursor image visibly positioned relative to
|
||||
* that location.
|
||||
*/
|
||||
int hotspot_x;
|
||||
|
||||
/**
|
||||
* The Y coordinate of the relative position of the pointer hotspot within
|
||||
* the cursor image. The hotspot is the location that the mouse pointer is
|
||||
* actually reported, with the cursor image visibly positioned relative to
|
||||
* that location.
|
||||
*/
|
||||
int hotspot_y;
|
||||
|
||||
} guac_display_builtin_cursor;
|
||||
|
||||
/**
|
||||
* An empty (invisible/hidden) mouse cursor.
|
||||
*/
|
||||
extern const guac_display_builtin_cursor guac_display_cursor_none;
|
||||
|
||||
/**
|
||||
* A small dot. This is typically used in situations where cursor information
|
||||
* for the remote desktop is not available, thus all cursor rendering must
|
||||
* happen remotely, but it's still important that the user be able to see the
|
||||
* current location of their local mouse pointer.
|
||||
*/
|
||||
extern const guac_display_builtin_cursor guac_display_cursor_dot;
|
||||
|
||||
/**
|
||||
* A vertical, I-shaped bar indicating text input or selection.
|
||||
*/
|
||||
extern const guac_display_builtin_cursor guac_display_cursor_ibar;
|
||||
|
||||
/**
|
||||
* A standard, general-purpose pointer.
|
||||
*/
|
||||
extern const guac_display_builtin_cursor guac_display_cursor_pointer;
|
||||
|
||||
#endif
|
||||
96
src/libguac/display-cursor.c
Normal file
96
src/libguac/display-cursor.c
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "display-builtin-cursors.h"
|
||||
#include "display-priv.h"
|
||||
#include "guacamole/assert.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/mem.h"
|
||||
#include "guacamole/rect.h"
|
||||
#include "guacamole/rwlock.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
guac_display_layer* guac_display_cursor(guac_display* display) {
|
||||
return display->cursor_buffer;
|
||||
}
|
||||
|
||||
void guac_display_set_cursor_hotspot(guac_display* display, int x, int y) {
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
display->pending_frame.cursor_hotspot_x = x;
|
||||
display->pending_frame.cursor_hotspot_y = y;
|
||||
display->pending_dirty = 1;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
}
|
||||
|
||||
void guac_display_set_cursor(guac_display* display,
|
||||
guac_display_cursor_type cursor_type) {
|
||||
|
||||
const guac_display_builtin_cursor* cursor;
|
||||
switch (cursor_type) {
|
||||
|
||||
case GUAC_DISPLAY_CURSOR_NONE:
|
||||
cursor = &guac_display_cursor_none;
|
||||
break;
|
||||
|
||||
case GUAC_DISPLAY_CURSOR_DOT:
|
||||
cursor = &guac_display_cursor_dot;
|
||||
break;
|
||||
|
||||
case GUAC_DISPLAY_CURSOR_IBAR:
|
||||
cursor = &guac_display_cursor_ibar;
|
||||
break;
|
||||
|
||||
case GUAC_DISPLAY_CURSOR_POINTER:
|
||||
default:
|
||||
cursor = &guac_display_cursor_pointer;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
guac_display_layer* cursor_layer = guac_display_cursor(display);
|
||||
guac_display_layer_resize(cursor_layer, cursor->width, cursor->height);
|
||||
|
||||
guac_display_layer_raw_context* context = guac_display_layer_open_raw(cursor_layer);
|
||||
GUAC_ASSERT(!cursor_layer->pending_frame.buffer_is_external);
|
||||
|
||||
const unsigned char* src_cursor_row = cursor->buffer;
|
||||
unsigned char* dst_cursor_row = context->buffer;
|
||||
size_t row_length = guac_mem_ckd_mul_or_die(cursor->width, 4);
|
||||
|
||||
for (int y = 0; y < cursor->height; y++) {
|
||||
memcpy(dst_cursor_row, src_cursor_row, row_length);
|
||||
src_cursor_row += cursor->stride;
|
||||
dst_cursor_row += context->stride;
|
||||
}
|
||||
|
||||
context->dirty = (guac_rect) {
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = cursor->width,
|
||||
.bottom = cursor->height
|
||||
};
|
||||
|
||||
guac_display_set_cursor_hotspot(display, cursor->hotspot_x, cursor->hotspot_y);
|
||||
|
||||
guac_display_layer_close_raw(cursor_layer, context);
|
||||
|
||||
}
|
||||
298
src/libguac/display-flush.c
Normal file
298
src/libguac/display-flush.c
Normal file
@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "display-plan.h"
|
||||
#include "display-priv.h"
|
||||
#include "guacamole/client.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/fifo.h"
|
||||
#include "guacamole/flag.h"
|
||||
#include "guacamole/mem.h"
|
||||
#include "guacamole/protocol.h"
|
||||
#include "guacamole/rect.h"
|
||||
#include "guacamole/rwlock.h"
|
||||
#include "guacamole/user.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Begins a section related to an optimization phase that should be tracked for
|
||||
* performance at the "trace" log level.
|
||||
*/
|
||||
#define GUAC_DISPLAY_PLAN_BEGIN_PHASE() \
|
||||
do { \
|
||||
guac_timestamp phase_start = guac_timestamp_current();
|
||||
|
||||
/**
|
||||
* Ends a section related to an optimization phase that should be tracked for
|
||||
* performance at the "trace" log level.
|
||||
*
|
||||
* @param display
|
||||
* The guac_display related to the optimizations being performed.
|
||||
*
|
||||
* @param phase
|
||||
* A human-readable name for the optimization phase being tracked.
|
||||
*
|
||||
* @param n
|
||||
* The ordinal number of this phase relative to other phases, where the
|
||||
* first phase is phase 1.
|
||||
*
|
||||
* @param total
|
||||
* The total number of optimization phases.
|
||||
*/
|
||||
#define GUAC_DISPLAY_PLAN_END_PHASE(display, phase, n, total) \
|
||||
guac_timestamp phase_end = guac_timestamp_current(); \
|
||||
guac_client_log(display->client, GUAC_LOG_TRACE, "Render planning " \
|
||||
"phase %i/%i (%s): %ims", n, total, phase, \
|
||||
(int) (phase_end - phase_start)); \
|
||||
} while (0)
|
||||
|
||||
void guac_display_end_frame(guac_display* display) {
|
||||
guac_display_end_multiple_frames(display, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for guac_client_foreach_user() which sends the current cursor
|
||||
* position and button state to any given user except the user that moved the
|
||||
* cursor last.
|
||||
*
|
||||
* @param data
|
||||
* A pointer to the guac_display whose cursor state should be broadcast to
|
||||
* all users except the user that moved the cursor last.
|
||||
*
|
||||
* @return
|
||||
* Always NULL.
|
||||
*/
|
||||
static void* LFR_guac_display_broadcast_cursor_state(guac_user* user, void* data) {
|
||||
|
||||
guac_display* display = (guac_display*) data;
|
||||
|
||||
/* Send cursor state only if the user is not moving the cursor */
|
||||
if (user != display->last_frame.cursor_user)
|
||||
guac_protocol_send_mouse(user->socket,
|
||||
display->last_frame.cursor_x, display->last_frame.cursor_y,
|
||||
display->last_frame.cursor_mask, display->last_frame.timestamp);
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the current pending frame, storing that state as the copy of the
|
||||
* last frame. All layer properties that have changed since the last frame will
|
||||
* be sent out to connected clients.
|
||||
*
|
||||
* @param display
|
||||
* The display whose pending frame should be finalized and persisted as the
|
||||
* last frame.
|
||||
*/
|
||||
static void PFW_LFW_guac_display_frame_complete(guac_display* display) {
|
||||
|
||||
guac_client* client = display->client;
|
||||
|
||||
display->last_frame.layers = display->pending_frame.layers;
|
||||
guac_display_layer* current = display->pending_frame.layers;
|
||||
while (current != NULL) {
|
||||
|
||||
/* Copy over pending frame contents if actually changed */
|
||||
if (!guac_rect_is_empty(¤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);
|
||||
}
|
||||
|
||||
}
|
||||
430
src/libguac/display-layer-list.c
Normal file
430
src/libguac/display-layer-list.c
Normal file
@ -0,0 +1,430 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "display-priv.h"
|
||||
#include "guacamole/assert.h"
|
||||
#include "guacamole/client.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/layer.h"
|
||||
#include "guacamole/mem.h"
|
||||
#include "guacamole/rwlock.h"
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Performs a bulk copy of image data from a source buffer to a destination
|
||||
* buffer. The two buffers need not match in size and stride. If the
|
||||
* destination is smaller than the desired source, the source dimensions will
|
||||
* be adjusted to fit the available space.
|
||||
*
|
||||
* @param dst
|
||||
* A pointer to the first byte of image data in the destination buffer.
|
||||
*
|
||||
* @param dst_stride
|
||||
* The number of bytes in each row of image data in the destination buffer.
|
||||
*
|
||||
* @param dst_width
|
||||
* The width of the destination buffer relative to the provided first byte,
|
||||
* in pixels.
|
||||
*
|
||||
* @param dst_height
|
||||
* The height of the destination buffer relative to the provided first byte,
|
||||
* in pixels.
|
||||
*
|
||||
* @param src
|
||||
* A pointer to the first byte of image data in the source buffer.
|
||||
*
|
||||
* @param src_stride
|
||||
* The number of bytes in each row of image data in the source buffer.
|
||||
*
|
||||
* @param src_width
|
||||
* The width of the source buffer relative to the provided first byte, in
|
||||
* pixels. If this value is larger than dst_width, it will be adjusted to
|
||||
* fit the available space.
|
||||
*
|
||||
* @param src_height
|
||||
* The height of the source buffer relative to the provided first byte, in
|
||||
* pixels. If this value is larger than dst_height, it will be adjusted to
|
||||
* fit the available space.
|
||||
*
|
||||
* @param pixel_size
|
||||
* The size of each pixel of image data, in bytes. The size of each pixel
|
||||
* in both the destination and source buffers must be identical.
|
||||
*/
|
||||
static void guac_imgcpy(void* dst, size_t dst_stride, int dst_width, int dst_height,
|
||||
void* src, size_t src_stride, int src_width, int src_height,
|
||||
size_t pixel_size) {
|
||||
|
||||
int width = dst_width;
|
||||
int height = dst_height;
|
||||
|
||||
if (src_width < width) width = src_width;
|
||||
if (src_height < height) height = src_height;
|
||||
|
||||
GUAC_ASSERT(width >= 0);
|
||||
GUAC_ASSERT(height >= 0);
|
||||
|
||||
size_t length = guac_mem_ckd_mul_or_die(width, pixel_size);
|
||||
|
||||
for (size_t i = 0; i < height; i++) {
|
||||
memcpy(dst, src, length);
|
||||
dst = ((char*) dst) + dst_stride;
|
||||
src = ((char*) src) + src_stride;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the layer represented by the given pair of layer states to the given
|
||||
* dimensions, allocating a larger underlying image buffer if necessary. If no
|
||||
* image buffer has yet been allocated, an image buffer large enough to hold
|
||||
* the given dimensions will be automatically allocated.
|
||||
*
|
||||
* This function DOES NOT resize the pending cells array, which is not stored
|
||||
* on the guac_display_layer_state. When resizing a layer, the pending cells
|
||||
* array must be separately resized with a call to
|
||||
* PFW_guac_display_layer_pending_frame_cells_resize().
|
||||
*
|
||||
* @param last_frame
|
||||
* The guac_display_layer_state representing the state of the layer at the
|
||||
* end of the last frame sent to connected clients.
|
||||
*
|
||||
* @param pending_frame
|
||||
* The guac_display_layer_state representing the current pending state of
|
||||
* the layer for the upcoming frame to be eventually sent to connected
|
||||
* clients.
|
||||
*
|
||||
* @param width
|
||||
* The new width, in pixels.
|
||||
*
|
||||
* @param height
|
||||
* The new height, in pixels.
|
||||
*/
|
||||
static void PFW_LFW_guac_display_layer_buffers_resize(guac_display_layer_state* last_frame,
|
||||
guac_display_layer_state* pending_frame, int width, int height) {
|
||||
|
||||
GUAC_ASSERT(last_frame->buffer_width == pending_frame->buffer_width);
|
||||
GUAC_ASSERT(last_frame->buffer_height == pending_frame->buffer_height);
|
||||
|
||||
/* Round up to nearest multiple of resize factor */
|
||||
width = ((width + GUAC_DISPLAY_RESIZE_FACTOR - 1) / GUAC_DISPLAY_RESIZE_FACTOR) * GUAC_DISPLAY_RESIZE_FACTOR;
|
||||
height = ((height + GUAC_DISPLAY_RESIZE_FACTOR - 1) / GUAC_DISPLAY_RESIZE_FACTOR) * GUAC_DISPLAY_RESIZE_FACTOR;
|
||||
|
||||
/* Do nothing if size isn't actually changing */
|
||||
if (width == last_frame->buffer_width
|
||||
&& height == last_frame->buffer_height)
|
||||
return;
|
||||
|
||||
/* The request to resize applies only to the pending frame, but space for
|
||||
* the last frame must be maintained. If either requested dimension is
|
||||
* smaller than the last frame dimensions, the relevant dimension of the
|
||||
* last frame must be used instead. */
|
||||
|
||||
int new_buffer_width = last_frame->buffer_width;
|
||||
if (width > new_buffer_width)
|
||||
new_buffer_width = width;
|
||||
|
||||
int new_buffer_height = last_frame->buffer_height;
|
||||
if (height > new_buffer_height)
|
||||
new_buffer_height = height;
|
||||
|
||||
/* Determine details of shared buffer space sufficient for both the
|
||||
* established last frame and the resized pending frame. Allocate new
|
||||
* shared buffer space for last and pending frames, interleaving their
|
||||
* rows.
|
||||
*
|
||||
* NOTE: We interleave the rows of the last and pending frames to promote
|
||||
* locality of reference. The comparisons performed between last and
|
||||
* pending frames to determine what has changed are faster when the rows
|
||||
* are interleaved, as data relevant to those comparisons will tend to be
|
||||
* present in the CPU cache. */
|
||||
|
||||
int new_last_frame_offset = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, new_buffer_width);
|
||||
int new_common_stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, new_last_frame_offset * 2);
|
||||
unsigned char* new_buffer_base = guac_mem_zalloc(new_buffer_height, new_common_stride);
|
||||
unsigned char* new_pending_frame_buffer = new_buffer_base;
|
||||
unsigned char* new_last_frame_buffer = new_buffer_base + new_last_frame_offset;
|
||||
|
||||
/* Copy over data from old shared buffer, if that data exists and is
|
||||
* relevant */
|
||||
|
||||
if (last_frame->buffer != NULL && pending_frame->buffer != NULL) {
|
||||
|
||||
guac_imgcpy(
|
||||
|
||||
/* Copy to newly-allocated pending frame buffer ... */
|
||||
new_pending_frame_buffer, new_common_stride,
|
||||
new_buffer_width, new_buffer_height,
|
||||
|
||||
/* ... from old pending frame buffer. */
|
||||
pending_frame->buffer, pending_frame->buffer_stride,
|
||||
pending_frame->buffer_width, pending_frame->buffer_height,
|
||||
|
||||
/* All pixels are 32-bit */
|
||||
GUAC_DISPLAY_LAYER_RAW_BPP);
|
||||
|
||||
guac_imgcpy(
|
||||
|
||||
/* Copy to newly-allocated last frame buffer ... */
|
||||
new_last_frame_buffer, new_common_stride,
|
||||
last_frame->buffer_width, last_frame->buffer_height,
|
||||
|
||||
/* ... from old last frame buffer. */
|
||||
last_frame->buffer, last_frame->buffer_stride,
|
||||
last_frame->buffer_width, last_frame->buffer_height,
|
||||
|
||||
/* All pixels are 32-bit */
|
||||
GUAC_DISPLAY_LAYER_RAW_BPP);
|
||||
|
||||
}
|
||||
|
||||
guac_mem_free(pending_frame->buffer);
|
||||
last_frame->buffer = new_buffer_base + new_last_frame_offset;
|
||||
pending_frame->buffer = new_buffer_base;
|
||||
|
||||
last_frame->buffer_width = pending_frame->buffer_width = new_buffer_width;
|
||||
last_frame->buffer_height = pending_frame->buffer_height = new_buffer_height;
|
||||
last_frame->buffer_stride = pending_frame->buffer_stride = new_common_stride;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully initializes the last and pending frame states for a newly-allocated
|
||||
* layer, including its underlying image buffers.
|
||||
*
|
||||
* @param last_frame
|
||||
* The guac_display_layer_state representing the state of the layer at the
|
||||
* end of the last frame sent to connected clients.
|
||||
*
|
||||
* @param pending_frame
|
||||
* The guac_display_layer_state representing the current pending state of
|
||||
* the layer for the upcoming frame to be eventually sent to connected
|
||||
* clients.
|
||||
*/
|
||||
static void PFW_LFW_guac_display_layer_state_init(guac_display_layer_state* last_frame,
|
||||
guac_display_layer_state* pending_frame) {
|
||||
|
||||
last_frame->width = pending_frame->width = GUAC_DISPLAY_RESIZE_FACTOR;
|
||||
last_frame->height = pending_frame->height = GUAC_DISPLAY_RESIZE_FACTOR;
|
||||
last_frame->opacity = pending_frame->opacity = 0xFF;
|
||||
last_frame->parent = pending_frame->parent = GUAC_DEFAULT_LAYER;
|
||||
|
||||
/* Allocate shared buffer space for last and pending frames, interleaving
|
||||
* their rows */
|
||||
|
||||
PFW_LFW_guac_display_layer_buffers_resize(last_frame, pending_frame,
|
||||
pending_frame->width, pending_frame->height);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the pending_frame_cells array of the given layer to the given
|
||||
* dimensions.
|
||||
*
|
||||
* @param layer
|
||||
* The layer whose pending_frame_cells array should be resized.
|
||||
*
|
||||
* @param width
|
||||
* The new width, in pixels.
|
||||
*
|
||||
* @param height
|
||||
* The new height, in pixels.
|
||||
*/
|
||||
static void PFW_guac_display_layer_pending_frame_cells_resize(guac_display_layer* layer,
|
||||
int width, int height) {
|
||||
|
||||
int new_pending_frame_cells_width = GUAC_DISPLAY_CELL_DIMENSION(width);
|
||||
int new_pending_frame_cells_height = GUAC_DISPLAY_CELL_DIMENSION(height);
|
||||
|
||||
/* Do nothing if size isn't actually changing */
|
||||
if (new_pending_frame_cells_width == layer->pending_frame_cells_width
|
||||
&& new_pending_frame_cells_height == layer->pending_frame_cells_height)
|
||||
return;
|
||||
|
||||
guac_display_layer_cell* new_pending_frame_cells = guac_mem_zalloc(sizeof(guac_display_layer_cell),
|
||||
new_pending_frame_cells_width, new_pending_frame_cells_height);
|
||||
|
||||
/* Copy existing cells over to new memory if present */
|
||||
if (layer->pending_frame_cells != NULL) {
|
||||
|
||||
size_t new_stride = guac_mem_ckd_mul_or_die(new_pending_frame_cells_width, sizeof(guac_display_layer_cell));
|
||||
size_t old_stride = guac_mem_ckd_mul_or_die(layer->pending_frame_cells_width, sizeof(guac_display_layer_cell));
|
||||
|
||||
guac_imgcpy(
|
||||
|
||||
/* Copy to newly-allocated pending frame cells ... */
|
||||
new_pending_frame_cells, new_stride,
|
||||
new_pending_frame_cells_width, new_pending_frame_cells_height,
|
||||
|
||||
/* ... from old pending frame cells. */
|
||||
layer->pending_frame_cells, old_stride,
|
||||
layer->pending_frame_cells_width, layer->pending_frame_cells_height,
|
||||
|
||||
/* All "pixels" are guac_display_layer_cell structures */
|
||||
sizeof(guac_display_layer_cell));
|
||||
|
||||
}
|
||||
|
||||
guac_mem_free(layer->pending_frame_cells);
|
||||
layer->pending_frame_cells = new_pending_frame_cells;
|
||||
layer->pending_frame_cells_width = new_pending_frame_cells_width;
|
||||
layer->pending_frame_cells_height = new_pending_frame_cells_height;
|
||||
|
||||
}
|
||||
|
||||
guac_display_layer* guac_display_add_layer(guac_display* display, guac_layer* layer, int opaque) {
|
||||
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
/* Init core layer members */
|
||||
guac_display_layer* display_layer = guac_mem_zalloc(sizeof(guac_display_layer));
|
||||
display_layer->display = display;
|
||||
display_layer->layer = layer;
|
||||
display_layer->opaque = opaque;
|
||||
|
||||
/* Init tracking of pending and last frames (NOTE: We need not acquire the
|
||||
* display-wide last_frame.lock here as this new layer will not actually be
|
||||
* part of the last frame layer list until the pending frame is flushed) */
|
||||
PFW_LFW_guac_display_layer_state_init(&display_layer->last_frame, &display_layer->pending_frame);
|
||||
display_layer->last_frame_buffer = guac_client_alloc_buffer(display->client);
|
||||
PFW_guac_display_layer_pending_frame_cells_resize(display_layer,
|
||||
display_layer->pending_frame.width,
|
||||
display_layer->pending_frame.height);
|
||||
|
||||
/* Insert list element as the new head */
|
||||
guac_display_layer* old_head = display->pending_frame.layers;
|
||||
display_layer->pending_frame.prev = NULL;
|
||||
display_layer->pending_frame.next = old_head;
|
||||
display->pending_frame.layers = display_layer;
|
||||
|
||||
/* Update old head to point to new element, if it existed */
|
||||
if (old_head != NULL)
|
||||
old_head->pending_frame.prev = display_layer;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
return display_layer;
|
||||
|
||||
}
|
||||
|
||||
void guac_display_remove_layer(guac_display_layer* display_layer) {
|
||||
|
||||
guac_display* display = display_layer->display;
|
||||
|
||||
/*
|
||||
* Remove layer from pending frame
|
||||
*/
|
||||
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
/* Update previous element, if it exists */
|
||||
if (display_layer->pending_frame.prev != NULL)
|
||||
display_layer->pending_frame.prev->pending_frame.next = display_layer->pending_frame.next;
|
||||
|
||||
/* If there is no previous element, then this element is the list head.
|
||||
* Update the list head accordingly. */
|
||||
else {
|
||||
GUAC_ASSERT(display->pending_frame.layers == display_layer);
|
||||
display->pending_frame.layers = display_layer->pending_frame.next;
|
||||
}
|
||||
|
||||
/* Update next element, if it exists */
|
||||
if (display_layer->pending_frame.next != NULL)
|
||||
display_layer->pending_frame.next->pending_frame.prev = display_layer->pending_frame.prev;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
/*
|
||||
* Remove layer from last frame
|
||||
*/
|
||||
|
||||
guac_rwlock_acquire_write_lock(&display->last_frame.lock);
|
||||
|
||||
/* Update previous element, if it exists */
|
||||
if (display_layer->last_frame.prev != NULL)
|
||||
display_layer->last_frame.prev->last_frame.next = display_layer->last_frame.next;
|
||||
|
||||
/* If there is no previous element, then this element is the list head.
|
||||
* Update the list head accordingly. */
|
||||
else {
|
||||
GUAC_ASSERT(display->last_frame.layers == display_layer);
|
||||
display->last_frame.layers = display_layer->last_frame.next;
|
||||
}
|
||||
|
||||
/* Update next element, if it exists */
|
||||
if (display_layer->last_frame.next != NULL)
|
||||
display_layer->last_frame.next->last_frame.prev = display_layer->last_frame.prev;
|
||||
|
||||
guac_rwlock_release_lock(&display->last_frame.lock);
|
||||
|
||||
/*
|
||||
* Layer has now been removed from both pending and last frame lists and
|
||||
* can be safely freed
|
||||
*/
|
||||
|
||||
guac_client* client = display->client;
|
||||
guac_client_free_buffer(client, display_layer->last_frame_buffer);
|
||||
|
||||
/* Release any Cairo resources */
|
||||
guac_display_layer_cairo_context* cairo_context = &(display_layer->pending_frame_cairo_context);
|
||||
if (cairo_context->surface != NULL) {
|
||||
|
||||
cairo_surface_destroy(cairo_context->surface);
|
||||
cairo_context->surface = NULL;
|
||||
|
||||
cairo_destroy(cairo_context->cairo);
|
||||
cairo_context->cairo = NULL;
|
||||
|
||||
}
|
||||
|
||||
/* Free memory for underlying image surface and change tracking cells
|
||||
* (NOTE: Freeing pending_frame.buffer inherently also frees
|
||||
* last_frame.buffer because they are actually interleaved views of the
|
||||
* same block) */
|
||||
guac_mem_free(display_layer->pending_frame.buffer);
|
||||
guac_mem_free(display_layer->pending_frame_cells);
|
||||
|
||||
guac_mem_free(display_layer);
|
||||
|
||||
}
|
||||
|
||||
void PFW_LFW_guac_display_layer_resize(guac_display_layer* layer, int width, int height) {
|
||||
|
||||
/* Flush and destroy any cached Cairo context */
|
||||
guac_display_layer_cairo_context* cairo_context = &(layer->pending_frame_cairo_context);
|
||||
if (cairo_context->surface != NULL) {
|
||||
|
||||
cairo_surface_flush(cairo_context->surface);
|
||||
cairo_surface_destroy(cairo_context->surface);
|
||||
cairo_destroy(cairo_context->cairo);
|
||||
|
||||
cairo_context->surface = NULL;
|
||||
cairo_context->cairo = NULL;
|
||||
|
||||
}
|
||||
|
||||
PFW_LFW_guac_display_layer_buffers_resize(&layer->last_frame, &layer->pending_frame, width, height);
|
||||
PFW_guac_display_layer_pending_frame_cells_resize(layer, width, height);
|
||||
|
||||
layer->pending_frame.width = width;
|
||||
layer->pending_frame.height = height;
|
||||
|
||||
}
|
||||
250
src/libguac/display-layer.c
Normal file
250
src/libguac/display-layer.c
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "display-priv.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/rect.h"
|
||||
#include "guacamole/rwlock.h"
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
void guac_display_layer_get_bounds(guac_display_layer* layer, guac_rect* bounds) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
guac_rwlock_acquire_read_lock(&display->pending_frame.lock);
|
||||
|
||||
*bounds = (guac_rect) {
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = layer->pending_frame.width,
|
||||
.bottom = layer->pending_frame.height
|
||||
};
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_move(guac_display_layer* layer, int x, int y) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
layer->pending_frame.x = x;
|
||||
layer->pending_frame.y = y;
|
||||
display->pending_dirty = 1;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_stack(guac_display_layer* layer, int z) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
layer->pending_frame.z = z;
|
||||
display->pending_dirty = 1;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_set_parent(guac_display_layer* layer, const guac_display_layer* parent) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
layer->pending_frame.parent = parent->layer;
|
||||
display->pending_dirty = 1;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_set_opacity(guac_display_layer* layer, int opacity) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
layer->pending_frame.opacity = opacity;
|
||||
display->pending_dirty = 1;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_set_lossless(guac_display_layer* layer, int lossless) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
layer->pending_frame.lossless = lossless;
|
||||
display->pending_dirty = 1;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_set_multitouch(guac_display_layer* layer, int touches) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
layer->pending_frame.touches = touches;
|
||||
display->pending_dirty = 1;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_resize(guac_display_layer* layer, int width, int height) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
guac_rwlock_acquire_write_lock(&display->last_frame.lock);
|
||||
|
||||
PFW_LFW_guac_display_layer_resize(layer, width, height);
|
||||
display->pending_dirty = 1;
|
||||
|
||||
guac_rwlock_release_lock(&display->last_frame.lock);
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_raw_context_set(guac_display_layer_raw_context* context,
|
||||
const guac_rect* dst, uint32_t color) {
|
||||
|
||||
size_t dst_stride = context->stride;
|
||||
unsigned char* restrict dst_buffer = GUAC_DISPLAY_LAYER_RAW_BUFFER(context, *dst);
|
||||
|
||||
for (int dy = dst->top; dy < dst->bottom; dy++) {
|
||||
|
||||
uint32_t* dst_pixel = (uint32_t*) dst_buffer;
|
||||
dst_buffer += dst_stride;
|
||||
|
||||
for (int dx = dst->left; dx < dst->right; dx++)
|
||||
*(dst_pixel++) = color;
|
||||
|
||||
}
|
||||
|
||||
guac_rect_extend(&(context->dirty), dst);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_raw_context_put(guac_display_layer_raw_context* context,
|
||||
const guac_rect* dst, const void* restrict buffer, size_t stride) {
|
||||
|
||||
size_t dst_stride = context->stride;
|
||||
unsigned char* restrict dst_buffer = GUAC_DISPLAY_LAYER_RAW_BUFFER(context, *dst);
|
||||
const unsigned char* restrict src_buffer = (const unsigned char*) buffer;
|
||||
|
||||
size_t copy_length = guac_mem_ckd_mul_or_die(guac_rect_width(dst),
|
||||
GUAC_DISPLAY_LAYER_RAW_BPP);
|
||||
|
||||
for (int dy = dst->top; dy < dst->bottom; dy++) {
|
||||
memcpy(dst_buffer, src_buffer, copy_length);
|
||||
dst_buffer += dst_stride;
|
||||
src_buffer += stride;
|
||||
}
|
||||
|
||||
guac_rect_extend(&(context->dirty), dst);
|
||||
|
||||
}
|
||||
|
||||
guac_display_layer_raw_context* guac_display_layer_open_raw(guac_display_layer* layer) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
/* Flush any outstanding Cairo operations before directly accessing buffer */
|
||||
guac_display_layer_cairo_context* cairo_context = &(layer->pending_frame_cairo_context);
|
||||
if (cairo_context->surface != NULL)
|
||||
cairo_surface_flush(cairo_context->surface);
|
||||
|
||||
layer->pending_frame_raw_context = (guac_display_layer_raw_context) {
|
||||
.buffer = layer->pending_frame.buffer,
|
||||
.stride = layer->pending_frame.buffer_stride,
|
||||
.dirty = { 0 },
|
||||
.bounds = {
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = layer->pending_frame.buffer_width,
|
||||
.bottom = layer->pending_frame.buffer_height
|
||||
}
|
||||
};
|
||||
|
||||
return &layer->pending_frame_raw_context;
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_close_raw(guac_display_layer* layer, guac_display_layer_raw_context* context) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
|
||||
guac_rect_extend(&layer->pending_frame.dirty, &context->dirty);
|
||||
display->pending_dirty = 1;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
}
|
||||
|
||||
guac_display_layer_cairo_context* guac_display_layer_open_cairo(guac_display_layer* layer) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
guac_display_layer_cairo_context* context = &(layer->pending_frame_cairo_context);
|
||||
|
||||
context->dirty = (guac_rect) { 0 };
|
||||
context->bounds = (guac_rect) {
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = layer->pending_frame.buffer_width,
|
||||
.bottom = layer->pending_frame.buffer_height
|
||||
};
|
||||
|
||||
if (context->surface == NULL) {
|
||||
|
||||
context->surface = cairo_image_surface_create_for_data(
|
||||
layer->pending_frame.buffer,
|
||||
layer->opaque ? CAIRO_FORMAT_RGB24 : CAIRO_FORMAT_ARGB32,
|
||||
layer->pending_frame.buffer_width,
|
||||
layer->pending_frame.buffer_height,
|
||||
layer->pending_frame.buffer_stride);
|
||||
|
||||
context->cairo = cairo_create(context->surface);
|
||||
|
||||
}
|
||||
|
||||
return context;
|
||||
|
||||
}
|
||||
|
||||
void guac_display_layer_close_cairo(guac_display_layer* layer, guac_display_layer_cairo_context* context) {
|
||||
|
||||
guac_display* display = layer->display;
|
||||
|
||||
guac_rect_extend(&layer->pending_frame.dirty, &context->dirty);
|
||||
display->pending_dirty = 1;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
}
|
||||
322
src/libguac/display-plan-combine.c
Normal file
322
src/libguac/display-plan-combine.c
Normal file
@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "display-plan.h"
|
||||
#include "display-priv.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/rect.h"
|
||||
|
||||
/**
|
||||
* Returns whether the given operation can be combined with others. Only
|
||||
* operations of certain types can be combined, and only up to a certain size
|
||||
* (to favor parallelism).
|
||||
*
|
||||
* @param op
|
||||
* The operation to test.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the operation can be combined with others, zero otherwise.
|
||||
*/
|
||||
static int guac_display_plan_is_combinable(const guac_display_plan_operation* op) {
|
||||
switch (op->type) {
|
||||
|
||||
case GUAC_DISPLAY_PLAN_OPERATION_IMG:
|
||||
return guac_rect_width(&op->dest) < GUAC_DISPLAY_MAX_COMBINED_WIDTH
|
||||
&& guac_rect_height(&op->dest) < GUAC_DISPLAY_MAX_COMBINED_HEIGHT;
|
||||
|
||||
case GUAC_DISPLAY_PLAN_OPERATION_RECT:
|
||||
case GUAC_DISPLAY_PLAN_OPERATION_COPY:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the two rectangles are adjacent and share exactly one common
|
||||
* edge.
|
||||
*
|
||||
* @param op_a
|
||||
* One of the rectangles to compare.
|
||||
*
|
||||
* @param op_b
|
||||
* The rectangle to compare op_a with.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the rectangles are adjacent and share exactly one common
|
||||
* edge, zero otherwise.
|
||||
*/
|
||||
static int guac_display_plan_has_common_edge(const guac_display_plan_operation* op_a,
|
||||
const guac_display_plan_operation* op_b) {
|
||||
|
||||
/* Two operations share a common edge if they are perfectly aligned
|
||||
* vertically and have the same left/right or right/left edge */
|
||||
if (op_a->dest.top == op_b->dest.top
|
||||
&& op_a->dest.bottom == op_b->dest.bottom) {
|
||||
|
||||
return op_a->dest.right == op_b->dest.left
|
||||
|| op_a->dest.left == op_b->dest.right;
|
||||
|
||||
}
|
||||
|
||||
/* Two operations share a common edge if they are perfectly aligned
|
||||
* horizontally and have the same top/bottom or bottom/top edge */
|
||||
else if (op_a->dest.left == op_b->dest.left
|
||||
&& op_a->dest.right == op_b->dest.right) {
|
||||
|
||||
return op_a->dest.top == op_b->dest.bottom
|
||||
|| op_a->dest.bottom == op_b->dest.top;
|
||||
|
||||
}
|
||||
|
||||
/* There are no other cases where two operations share a common edge */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given pair of operations should be combined into a
|
||||
* single operation.
|
||||
*
|
||||
* @param op_a
|
||||
* The first operation to check.
|
||||
*
|
||||
* @param op_b
|
||||
* The second operation to check.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the operations would be better represented as a single,
|
||||
* combined operation, zero otherwise.
|
||||
*/
|
||||
static int guac_display_plan_should_combine(const guac_display_plan_operation* op_a,
|
||||
const guac_display_plan_operation* op_b) {
|
||||
|
||||
/* Consider only operations that have combinable types (draw to a
|
||||
* particular rectangle in the layer) */
|
||||
if (!guac_display_plan_is_combinable(op_a)
|
||||
|| !guac_display_plan_is_combinable(op_b))
|
||||
return 0;
|
||||
|
||||
/* Operations can only be combined within the same layer */
|
||||
if (op_a->layer != op_b->layer)
|
||||
return 0;
|
||||
|
||||
/* Operations of the same type can be trivially unified under specific
|
||||
* circumstances */
|
||||
if (op_a->type == op_b->type) {
|
||||
switch (op_a->type) {
|
||||
|
||||
/* Copy operations can be combined if they are perfectly adjacent
|
||||
* (exactly share an edge) and copy in the same direction */
|
||||
case GUAC_DISPLAY_PLAN_OPERATION_COPY:
|
||||
if (guac_display_plan_has_common_edge(op_a, op_b)) {
|
||||
|
||||
int delta_xa = op_a->dest.left - op_a->src.rect.left;
|
||||
int delta_ya = op_a->dest.top - op_a->src.rect.top;
|
||||
int delta_xb = op_b->dest.left - op_b->src.rect.left;
|
||||
int delta_yb = op_b->dest.top - op_b->src.rect.top;
|
||||
|
||||
return delta_xa == delta_xb
|
||||
&& delta_ya == delta_yb;
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
/* Rectangle-drawing operations can be combined if they are
|
||||
* perfectly adjacent (exactly share an edge) and draw the same
|
||||
* color */
|
||||
case GUAC_DISPLAY_PLAN_OPERATION_RECT:
|
||||
return op_a->src.color == op_b->src.color
|
||||
&& guac_display_plan_has_common_edge(op_a, op_b);
|
||||
|
||||
/* Other combinations require more complex logic... (see below) */
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Simulate combination */
|
||||
guac_rect combined = op_a->dest;
|
||||
guac_rect_extend(&combined, &op_b->dest);
|
||||
|
||||
/* Combine if result is still small */
|
||||
int combined_width = guac_rect_width(&combined);
|
||||
int combined_height = guac_rect_height(&combined);
|
||||
if (combined_width <= GUAC_DISPLAY_NEGLIGIBLE_WIDTH && combined_height <= GUAC_DISPLAY_NEGLIGIBLE_HEIGHT)
|
||||
return 1;
|
||||
|
||||
/* Estimate costs of the existing update, new update, and both combined */
|
||||
int cost_ab = GUAC_DISPLAY_BASE_COST + combined_width * combined_height;
|
||||
int cost_a = GUAC_DISPLAY_BASE_COST + op_a->dirty_size;
|
||||
int cost_b = GUAC_DISPLAY_BASE_COST + op_b->dirty_size;
|
||||
|
||||
/* Reduce cost if no image data */
|
||||
if (op_a->type != GUAC_DISPLAY_PLAN_OPERATION_IMG) cost_a /= GUAC_DISPLAY_DATA_FACTOR;
|
||||
if (op_b->type != GUAC_DISPLAY_PLAN_OPERATION_IMG) cost_b /= GUAC_DISPLAY_DATA_FACTOR;
|
||||
|
||||
/* Combine if cost estimate shows benefit or the increase in cost is
|
||||
* negligible */
|
||||
if ((cost_ab <= cost_b + cost_a)
|
||||
|| (cost_ab - cost_a <= cost_a / GUAC_DISPLAY_NEGLIGIBLE_INCREASE)
|
||||
|| (cost_ab - cost_b <= cost_b / GUAC_DISPLAY_NEGLIGIBLE_INCREASE))
|
||||
return 1;
|
||||
|
||||
/* Otherwise, do not combine */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the given pair of operations into a single operation if doing so is
|
||||
* advantageous (results in an operation of lesser or negligibly-worse cost).
|
||||
*
|
||||
* @param op_a
|
||||
* The first of the pair of operations to be combined. If they operations
|
||||
* are combined, the combined operation will be stored here.
|
||||
*
|
||||
* @param op_b
|
||||
* The second of the pair of operations to be combined, which may
|
||||
* potentially be identical to the first. If the operations are combined,
|
||||
* this operation will be updated to be a GUAC_DISPLAY_PLAN_OPERATION_NOP
|
||||
* operation.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the operations were combined, zero otherwise.
|
||||
*/
|
||||
static int guac_display_plan_combine_if_improved(guac_display_plan_operation* op_a,
|
||||
guac_display_plan_operation* op_b) {
|
||||
|
||||
if (op_a == op_b)
|
||||
return 0;
|
||||
|
||||
/* Combine any adjacent operations that match the combination criteria
|
||||
* (combining produces a net lower cost) */
|
||||
if (guac_display_plan_should_combine(op_a, op_b)) {
|
||||
|
||||
guac_rect_extend(&op_a->dest, &op_b->dest);
|
||||
|
||||
/* Operations of different types can only be combined as images */
|
||||
if (op_a->type != op_b->type)
|
||||
op_a->type = GUAC_DISPLAY_PLAN_OPERATION_IMG;
|
||||
|
||||
/* When combining two copy operations, additionally combine their
|
||||
* source rects (NOT just the destination rects) */
|
||||
else if (op_a->type == GUAC_DISPLAY_PLAN_OPERATION_COPY)
|
||||
guac_rect_extend(&op_a->src.rect, &op_b->src.rect);
|
||||
|
||||
op_a->dirty_size += op_b->dirty_size;
|
||||
|
||||
if (op_b->last_frame > op_a->last_frame)
|
||||
op_a->last_frame = op_b->last_frame;
|
||||
|
||||
op_b->type = GUAC_DISPLAY_PLAN_OPERATION_NOP;
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
void PFW_guac_display_plan_combine_horizontally(guac_display_plan* plan) {
|
||||
|
||||
guac_display* display = plan->display;
|
||||
guac_display_layer* current = display->pending_frame.layers;
|
||||
while (current != NULL) {
|
||||
|
||||
/* Process only layers that have been modified */
|
||||
if (!guac_rect_is_empty(¤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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
235
src/libguac/display-plan-rect.c
Normal file
235
src/libguac/display-plan-rect.c
Normal file
@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "display-plan.h"
|
||||
#include "display-priv.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/mem.h"
|
||||
#include "guacamole/rect.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Rounds the given value down to the nearest power of two.
|
||||
*
|
||||
* @param value
|
||||
* The value to round.
|
||||
*
|
||||
* @return
|
||||
* The power of two that is closest to the given value without exceeding
|
||||
* that value.
|
||||
*/
|
||||
static size_t guac_display_plan_round_pot(size_t value) {
|
||||
|
||||
if (value <= 2)
|
||||
return value;
|
||||
|
||||
size_t rounded = 1;
|
||||
while (value >>= 1)
|
||||
rounded <<= 1;
|
||||
|
||||
return rounded;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given buffer consists entirely of the same 32-bit
|
||||
* quantity (ie: a single ARGB pixel), repeated throughout the buffer.
|
||||
*
|
||||
* This function attempts to perform a fast comparison leveraging memcmp() to
|
||||
* reduce the search space, rather than simply looping through each pixel one
|
||||
* at a time. Basic benchmarks show this approach to be roughly twice as fast
|
||||
* as a simple loop for arbitrary buffer lengths and four times as fast for
|
||||
* buffer lengths that are powers of two.
|
||||
*
|
||||
* @param buffer
|
||||
* The buffer to check.
|
||||
*
|
||||
* @param length
|
||||
* The number of bytes in the buffer.
|
||||
*
|
||||
* @param color
|
||||
* A pointer to a uint32_t to receive the value of the 32-bit quantity that
|
||||
* is repeated, if applicable.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the same 32-bit quantity is repeated throughout the buffer,
|
||||
* zero otherwise. If the same value is indeed repeated throughout the
|
||||
* buffer, that value is stored in the variable pointed to by the "color"
|
||||
* pointer. If the value is not repeated, the variable pointed to by the
|
||||
* "color" pointer is left untouched.
|
||||
*/
|
||||
static int guac_display_plan_is_single_color(const unsigned char* restrict buffer,
|
||||
size_t length, uint32_t* restrict color) {
|
||||
|
||||
/* It is vacuously true that all the 32-bit quantities in an empty buffer
|
||||
* are the same */
|
||||
if (length == 0) {
|
||||
*color = 0x00000000;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* A single 32-bit value is the same as itself */
|
||||
if (length == 4) {
|
||||
*color = ((const uint32_t*) buffer)[0];
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Simply directly compare if there are only two values */
|
||||
if (length == 8) {
|
||||
uint32_t a = ((const uint32_t*) buffer)[0];
|
||||
uint32_t b = ((const uint32_t*) buffer)[1];
|
||||
if (a == b) {
|
||||
*color = a;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* For all other lengths, avoid comparing if finding a match is impossible.
|
||||
* A buffer can consist entirely of the same 32-bit (4-byte) quantity
|
||||
* repeated throughout the buffer only if that buffer's length is a
|
||||
* multiple of 4. */
|
||||
if ((length % 4) != 0)
|
||||
return 0;
|
||||
|
||||
/* A buffer consists entirely of the same 32-bit quantity repeated
|
||||
* throughout if (1) the two halves of the buffer are the same and (2) one
|
||||
* of those halves is known to consist entirely of the same 32-bit quantity
|
||||
* repeated throughout. */
|
||||
|
||||
size_t pot_length = guac_display_plan_round_pot(guac_mem_ckd_sub_or_die(length, 1));
|
||||
size_t remaining_length = guac_mem_ckd_sub_or_die(length, pot_length);
|
||||
|
||||
/* Easiest recursive case: the buffer is already a power of two and can be
|
||||
* split into two very easy-to-compare halves */
|
||||
if (pot_length == remaining_length) {
|
||||
return !memcmp(buffer, buffer + pot_length, pot_length)
|
||||
&& guac_display_plan_is_single_color(buffer, pot_length, color);
|
||||
}
|
||||
|
||||
/* For buffers that can't be split into two power-of-two halves, decide
|
||||
* based on one easy power-of-two case and one not-so-easy case of whatever
|
||||
* remains */
|
||||
uint32_t color_a = 0, color_b = 0;
|
||||
if (guac_display_plan_is_single_color(buffer, pot_length, &color_a)
|
||||
&& guac_display_plan_is_single_color(buffer + pot_length, remaining_length, &color_b)
|
||||
&& color_a == color_b) {
|
||||
|
||||
*color = color_a;
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given rectangle within given buffer consists entirely of
|
||||
* the same 32-bit quantity (ie: a single ARGB pixel), repeated throughout the
|
||||
* rectangular region.
|
||||
*
|
||||
* This function attempts to perform a fast comparison leveraging memcmp() to
|
||||
* reduce the search space, rather than simply looping through each pixel one
|
||||
* at a time. Basic benchmarks show this approach to be roughly twice as fast
|
||||
* as a simple loop for arbitrary buffer lengths and four times as fast for
|
||||
* buffer lengths that are powers of two.
|
||||
*
|
||||
* @param buffer
|
||||
* The buffer to check.
|
||||
*
|
||||
* @param stride
|
||||
* The number of bytes in each row of image data within the buffer.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle representing the region to be checked within the buffer.
|
||||
*
|
||||
* @param color
|
||||
* A pointer to a uint32_t to receive the value of the 32-bit quantity that
|
||||
* is repeated, if applicable.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the same 32-bit quantity is repeated throughout the
|
||||
* rectangular region, zero otherwise. If the same value is indeed repeated
|
||||
* throughout the rectangle, that value is stored in the variable pointed
|
||||
* to by the "color" pointer. If the value is not repeated, the variable
|
||||
* pointed to by the "color" pointer is left untouched.
|
||||
*/
|
||||
static int guac_display_plan_is_rect_single_color(const unsigned char* restrict buffer,
|
||||
size_t stride, const guac_rect* restrict rect, uint32_t* restrict color) {
|
||||
|
||||
size_t row_length = guac_mem_ckd_mul_or_die(guac_rect_width(rect), GUAC_DISPLAY_LAYER_RAW_BPP);
|
||||
buffer = GUAC_RECT_CONST_BUFFER(*rect, buffer, stride, GUAC_DISPLAY_LAYER_RAW_BPP);
|
||||
|
||||
/* Verify that the first row consists of a single color */
|
||||
uint32_t first_color = 0x00000000;
|
||||
if (!guac_display_plan_is_single_color(buffer, row_length, &first_color))
|
||||
return 0;
|
||||
|
||||
/* The whole rectangle consists of a single color if each row is identical
|
||||
* and it's already known that one of those rows consists of the a single
|
||||
* color */
|
||||
const unsigned char* previous = buffer;
|
||||
for (int y = rect->top + 1; y < rect->bottom; y++) {
|
||||
|
||||
const unsigned char* current = previous + stride;
|
||||
if (memcmp(previous, current, row_length))
|
||||
return 0;
|
||||
|
||||
previous = current;
|
||||
|
||||
}
|
||||
|
||||
*color = first_color;
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
void PFR_guac_display_plan_rewrite_as_rects(guac_display_plan* plan) {
|
||||
|
||||
uint32_t color = 0x00000000;
|
||||
|
||||
guac_display_plan_operation* op = plan->ops;
|
||||
for (int i = 0; i < plan->length; i++) {
|
||||
|
||||
if (op->type == GUAC_DISPLAY_PLAN_OPERATION_IMG) {
|
||||
|
||||
guac_display_layer* layer = op->layer;
|
||||
size_t stride = layer->pending_frame.buffer_stride;
|
||||
const unsigned char* buffer = layer->pending_frame.buffer;
|
||||
|
||||
if (guac_display_plan_is_rect_single_color(buffer, stride, &op->dest, &color)) {
|
||||
|
||||
/* Ignore alpha channel for opaque layers */
|
||||
if (layer->opaque)
|
||||
color |= 0xFF000000;
|
||||
|
||||
op->type = GUAC_DISPLAY_PLAN_OPERATION_RECT;
|
||||
op->src.color = color;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
op++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
428
src/libguac/display-plan-search.c
Normal file
428
src/libguac/display-plan-search.c
Normal file
@ -0,0 +1,428 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "display-plan.h"
|
||||
#include "display-priv.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/rect.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Stores the given operation within the ops_by_hash table of the given display
|
||||
* plan based on the given hash value. The hash function applied for storing
|
||||
* the operation is GUAC_DISPLAY_PLAN_OPERATION_HASH(). If another operation is
|
||||
* already stored at the same location within ops_by_hash, that operation will
|
||||
* be replaced.
|
||||
*
|
||||
* @param plan
|
||||
* The plan to store the operation within.
|
||||
*
|
||||
* @param hash
|
||||
* The hash value to use to calculate the storage location. This value will
|
||||
* be further hashed with GUAC_DISPLAY_PLAN_OPERATION_HASH().
|
||||
*
|
||||
* @param op
|
||||
* The operation to store.
|
||||
*/
|
||||
static void guac_display_plan_store_indexed_op(guac_display_plan* plan, uint64_t hash,
|
||||
guac_display_plan_operation* op) {
|
||||
|
||||
size_t index = GUAC_DISPLAY_PLAN_OPERATION_HASH(hash);
|
||||
guac_display_plan_indexed_operation* entry = &(plan->ops_by_hash[index]);
|
||||
|
||||
if (entry->op == NULL) {
|
||||
entry->hash = hash;
|
||||
entry->op = op;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes and returns a pointer to the matching operation stored within the
|
||||
* ops_by_hash table of the given display plan, if any. If no such operation is
|
||||
* stored, NULL is returned.
|
||||
*
|
||||
* @param plan
|
||||
* The plan to retrieve the operation from.
|
||||
*
|
||||
* @param hash
|
||||
* The hash value to use to calculate the storage location. This value will
|
||||
* be further hashed with GUAC_DISPLAY_PLAN_OPERATION_HASH().
|
||||
*
|
||||
* @return
|
||||
* The operation that was stored under the given hash, if any, or NULL if
|
||||
* no such operation was found.
|
||||
*/
|
||||
static guac_display_plan_operation* guac_display_plan_remove_indexed_op(guac_display_plan* plan, uint64_t hash) {
|
||||
|
||||
size_t index = GUAC_DISPLAY_PLAN_OPERATION_HASH(hash);
|
||||
guac_display_plan_indexed_operation* entry = &(plan->ops_by_hash[index]);
|
||||
|
||||
/* NOTE: We verify the hash value here because the lookup performed is
|
||||
* actually a hash of a hash. There's an additional chance of collisions
|
||||
* between hash values at this second level of hashing. */
|
||||
|
||||
guac_display_plan_operation* op = entry->op;
|
||||
if (op != NULL && entry->hash == hash) {
|
||||
entry->op = NULL;
|
||||
return op;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked by guac_hash_foreach_image_rect() for each 64x64 rectangle
|
||||
* of image data.
|
||||
*
|
||||
* @param plan
|
||||
* The display plan related to the call to guac_hash_foreach_image_rect().
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the upper-left corner of the current 64x64 rectangle
|
||||
* within the search region.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the upper-left corner of the current 64x64 rectangle
|
||||
* within the search region.
|
||||
*
|
||||
* @param hash
|
||||
* The hash value that applies to the current 64x64 rectangle.
|
||||
*
|
||||
* @param closure
|
||||
* The closure value that was originally provided to the call to
|
||||
* guac_hash_foreach_image_rect().
|
||||
*/
|
||||
typedef void guac_hash_callback(guac_display_plan* plan, int x, int y, uint64_t hash, void* closure);
|
||||
|
||||
/**
|
||||
* Iterates through each 64x64 subrectangle within the given rectangular region
|
||||
* of the underlying buffer of the given layer state, invoking the given
|
||||
* callback for each such subrectangle. Each 64x64 subrectangle within the
|
||||
* rectangular region is evaluated by sliding a 64x64 window over each pixel of
|
||||
* the region such that every 64x64 subrectangle in the region is eventually
|
||||
* covered.
|
||||
*
|
||||
* @param plan
|
||||
* The display plan related to the search/indexing operation being
|
||||
* performed.
|
||||
*
|
||||
* @param layer_state
|
||||
* The layer state containing the image buffer to hash.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangular region within the image buffer that should be hashed.
|
||||
*
|
||||
* @param callback
|
||||
* The callback to invoke for each 64x64 subrectangle of the given region.
|
||||
*
|
||||
* @param closure
|
||||
* The arbitrary value to pass the given callback each time it is invoked
|
||||
* through this function call.
|
||||
*/
|
||||
static int guac_hash_foreach_image_rect(guac_display_plan* plan,
|
||||
const guac_display_layer_state* layer_state, const guac_rect* rect,
|
||||
guac_hash_callback* callback, void* closure) {
|
||||
|
||||
size_t stride = layer_state->buffer_stride;
|
||||
const unsigned char* data = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(*layer_state, *rect);
|
||||
|
||||
int x, y;
|
||||
uint64_t cell_hash[GUAC_DISPLAY_MAX_WIDTH] = { 0 };
|
||||
|
||||
/* NOTE: Because the hash value of the sliding 64x64 window is available
|
||||
* only upon reaching the bottom-right corner of that window, we offset the
|
||||
* coordinates here by the relative location of the bottom-right corner
|
||||
* (GUAC_DISPLAY_CELL_SIZE - 1) so that we have easy access to the
|
||||
* coordinates of the upper-left corner of the sliding window, as required
|
||||
* by the callback being invoked.
|
||||
*
|
||||
* This also allows us to easily determine when the hash is valid and it's
|
||||
* safe to invoke the callback. Once the coordinates are within the given
|
||||
* rect, we have evaluated a full 64x64 rectangle and have a valid hash. */
|
||||
|
||||
int start_x = rect->left - GUAC_DISPLAY_CELL_SIZE + 1;
|
||||
int end_x = rect->right - GUAC_DISPLAY_CELL_SIZE + 1;
|
||||
int start_y = rect->top - GUAC_DISPLAY_CELL_SIZE + 1;
|
||||
int end_y = rect->bottom - GUAC_DISPLAY_CELL_SIZE + 1;
|
||||
|
||||
for (y = start_y; y < end_y; y++) {
|
||||
|
||||
uint64_t* current_cell_hash = cell_hash;
|
||||
|
||||
/* Get current row */
|
||||
uint32_t* row = (uint32_t*) data;
|
||||
data += stride;
|
||||
|
||||
/* Calculate row segment hashes for entire row */
|
||||
uint64_t row_hash = 0;
|
||||
for (x = start_x; x < end_x; x++) {
|
||||
|
||||
/* Get current pixel */
|
||||
uint32_t pixel = *(row++);
|
||||
|
||||
/* Update hash value for current row segment */
|
||||
row_hash = ((row_hash * 31) << 1) + pixel;
|
||||
|
||||
/* Incorporate row hash value into overall cell hash */
|
||||
uint64_t cell_hash = ((*current_cell_hash * 31) << 1) + row_hash;
|
||||
*(current_cell_hash++) = cell_hash;
|
||||
|
||||
/* Invoke callback for every hash generated, breaking out early if
|
||||
* requested */
|
||||
if (y >= rect->top && x >= rect->left)
|
||||
callback(plan, x, y, cell_hash, closure);
|
||||
|
||||
}
|
||||
|
||||
} /* end for each row */
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the given rectangle with the bounds of the pending frame cell
|
||||
* containing the given coordinate.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to initialize.
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the point that the rectangle must contain.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the point that the rectangle must contain.
|
||||
*/
|
||||
static void guac_display_cell_init_rect(guac_rect* rect, int x, int y) {
|
||||
x = (x / GUAC_DISPLAY_CELL_SIZE) * GUAC_DISPLAY_CELL_SIZE;
|
||||
y = (y / GUAC_DISPLAY_CELL_SIZE) * GUAC_DISPLAY_CELL_SIZE;
|
||||
guac_rect_init(rect, x, y, GUAC_DISPLAY_CELL_SIZE, GUAC_DISPLAY_CELL_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for guac_hash_foreach_image_rect() which stores the given operation
|
||||
* in the ops_by_hash table of the given display plan.
|
||||
*
|
||||
* @param plan
|
||||
* The display plan to store the given operation in.
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the upper-left corner of the 64x64 rectangle
|
||||
* modified by the given operation.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the upper-left corner of the 64x64 rectangle
|
||||
* modified by the given operation.
|
||||
*
|
||||
* @param hash
|
||||
* The hash value that applies to the 64x64 rectangle at the given
|
||||
* coordinates.
|
||||
*
|
||||
* @param closure
|
||||
* A pointer to the guac_display_plan_operation that should be stored
|
||||
* within the ops_by_hash table of the given display plan.
|
||||
*/
|
||||
static void guac_display_plan_index_op_for_cell(guac_display_plan* plan, int x, int y, uint64_t hash, void* closure) {
|
||||
guac_display_plan_store_indexed_op(plan, hash, (guac_display_plan_operation*) closure);
|
||||
}
|
||||
|
||||
void PFR_guac_display_plan_index_dirty_cells(guac_display_plan* plan) {
|
||||
|
||||
memset(plan->ops_by_hash, 0, sizeof(plan->ops_by_hash));
|
||||
|
||||
guac_display_plan_operation* op = plan->ops;
|
||||
for (int i = 0; i < plan->length; i++) {
|
||||
|
||||
if (op->type == GUAC_DISPLAY_PLAN_OPERATION_IMG) {
|
||||
|
||||
guac_display_layer* layer = op->layer;
|
||||
|
||||
guac_rect layer_bounds;
|
||||
guac_display_layer_get_bounds(layer, &layer_bounds);
|
||||
|
||||
guac_rect cell;
|
||||
guac_display_cell_init_rect(&cell, op->dest.left, op->dest.top);
|
||||
|
||||
guac_rect_constrain(&cell, &layer_bounds);
|
||||
if (guac_rect_width(&cell) == GUAC_DISPLAY_CELL_SIZE
|
||||
&& guac_rect_height(&cell) == GUAC_DISPLAY_CELL_SIZE) {
|
||||
guac_hash_foreach_image_rect(plan, &layer->pending_frame,
|
||||
&cell, guac_display_plan_index_op_for_cell, op);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
op++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two rectangular regions of two arbitrary buffers, returning whether
|
||||
* those regions contain identical data.
|
||||
*
|
||||
* @param data_a
|
||||
* A pointer to the first byte of image data within the first region being
|
||||
* compared.
|
||||
*
|
||||
* @param width_a
|
||||
* The width of the first region, in pixels.
|
||||
*
|
||||
* @param height_a
|
||||
* The height of the first region, in pixels.
|
||||
*
|
||||
* @param stride_a
|
||||
* The number of bytes in each row of image data in the first region.
|
||||
*
|
||||
* @param data_b
|
||||
* A pointer to the first byte of image data within the second region being
|
||||
* compared.
|
||||
*
|
||||
* @param width_b
|
||||
* The width of the second region, in pixels.
|
||||
*
|
||||
* @param height_b
|
||||
* The height of the second region, in pixels.
|
||||
*
|
||||
* @param stride_b
|
||||
* The number of bytes in each row of image data in the first region.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the regions contain at least one differing pixel, zero
|
||||
* otherwise.
|
||||
*/
|
||||
static int guac_image_cmp(const unsigned char* restrict data_a, int width_a, int height_a,
|
||||
int stride_a, const unsigned char* restrict data_b, int width_b, int height_b,
|
||||
int stride_b) {
|
||||
|
||||
int y;
|
||||
|
||||
/* If core dimensions differ, just compare those. Done. */
|
||||
if (width_a != width_b) return width_a - width_b;
|
||||
if (height_a != height_b) return height_a - height_b;
|
||||
|
||||
size_t length = guac_mem_ckd_mul_or_die(width_a, GUAC_DISPLAY_LAYER_RAW_BPP);
|
||||
|
||||
for (y = 0; y < height_a; y++) {
|
||||
|
||||
/* Compare row. If different, use that result. */
|
||||
int cmp_result = memcmp(data_a, data_b, length);
|
||||
if (cmp_result != 0)
|
||||
return cmp_result;
|
||||
|
||||
/* Next row */
|
||||
data_a += stride_a;
|
||||
data_b += stride_b;
|
||||
|
||||
}
|
||||
|
||||
/* Otherwise, same. */
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for guac_hash_foreach_image_rect() which searches the ops_by_hash
|
||||
* table of the given display plan for occurrences of the given hash, replacing
|
||||
* the matching operation with a copy operation if a match is found.
|
||||
*
|
||||
* NOTE: While this function will search for and optimize operations that copy
|
||||
* existing data, it can only do so for distinct image data. Multiple
|
||||
* operations that copy the same exact data (like a region tiled with multiple
|
||||
* copies of some pattern) can only be stored in the table once, and therefore
|
||||
* will only match once.
|
||||
*
|
||||
* @param plan
|
||||
* The display plan to update with any copies found.
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the upper-left corner of the 64x64 region currently
|
||||
* being checked.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the upper-left corner of the 64x64 region currently
|
||||
* being checked.
|
||||
*
|
||||
* @param hash
|
||||
* The hash value that applies to the 64x64 rectangle at the given
|
||||
* coordinates.
|
||||
*
|
||||
* @param closure
|
||||
* A pointer to the guac_display_layer that is being searched.
|
||||
*/
|
||||
static void PFR_LFR_guac_display_plan_find_copies(guac_display_plan* plan,
|
||||
int x, int y, uint64_t hash, void* closure) {
|
||||
|
||||
guac_display_layer* layer = (guac_display_layer*) closure;
|
||||
|
||||
/* Transform the matching operation into a copy of the current region if
|
||||
* any operations match, banning the underlying hash from further checks if
|
||||
* a collision occurs */
|
||||
guac_display_plan_operation* op = guac_display_plan_remove_indexed_op(plan, hash);
|
||||
if (op != NULL && op->layer == layer) {
|
||||
|
||||
guac_rect src_rect;
|
||||
guac_rect_init(&src_rect, x, y, GUAC_DISPLAY_CELL_SIZE, GUAC_DISPLAY_CELL_SIZE);
|
||||
|
||||
guac_rect dst_rect;
|
||||
guac_display_cell_init_rect(&dst_rect, op->dest.left, op->dest.top);
|
||||
|
||||
const unsigned char* copy_from = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(layer->last_frame, src_rect);
|
||||
const unsigned char* copy_to = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(layer->pending_frame, dst_rect);
|
||||
|
||||
/* Only transform into a copy if the image data is truly identical (not a collision) */
|
||||
if (!guac_image_cmp(copy_from, GUAC_DISPLAY_CELL_SIZE, GUAC_DISPLAY_CELL_SIZE, layer->last_frame.buffer_stride,
|
||||
copy_to, GUAC_DISPLAY_CELL_SIZE, GUAC_DISPLAY_CELL_SIZE, layer->pending_frame.buffer_stride)) {
|
||||
op->type = GUAC_DISPLAY_PLAN_OPERATION_COPY;
|
||||
op->src.rect = src_rect;
|
||||
op->dest = dst_rect;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PFR_LFR_guac_display_plan_rewrite_as_copies(guac_display_plan* plan) {
|
||||
|
||||
guac_display* display = plan->display;
|
||||
guac_display_layer* current = display->last_frame.layers;
|
||||
while (current != NULL) {
|
||||
|
||||
guac_rect search_region;
|
||||
guac_rect_init(&search_region, 0, 0, current->last_frame.width, current->last_frame.height);
|
||||
|
||||
/* Avoid excessive computation by restricting the search region to only
|
||||
* the area that was changed in the upcoming frame (in the case of
|
||||
* scrolling, absolutely all data relevant to the scroll will have been
|
||||
* modified) */
|
||||
guac_rect_constrain(&search_region, ¤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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
275
src/libguac/display-plan.c
Normal file
275
src/libguac/display-plan.c
Normal file
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "display-plan.h"
|
||||
#include "display-priv.h"
|
||||
#include "guacamole/assert.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/fifo.h"
|
||||
#include "guacamole/mem.h"
|
||||
#include "guacamole/timestamp.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <cairo/cairo.h>
|
||||
|
||||
/**
|
||||
* Updates the dirty rect in the given cell to note that a horizontal line of
|
||||
* image data at the given location and having the given width has changed
|
||||
* since the last frame. A provided counter of the overall number of changed
|
||||
* cells is updated accordingly.
|
||||
*
|
||||
* @param layer
|
||||
* The layer that changed.
|
||||
*
|
||||
* @param cell
|
||||
* The cell containing the line of image data that changed.
|
||||
*
|
||||
* @param count
|
||||
* A pointer to a counter that contains the current number of cells that
|
||||
* have been marked as having changed since the last frame.
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the leftmost pixel of the horizontal line.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the leftmost pixel of the horizontal line.
|
||||
*
|
||||
* @param width
|
||||
* The width of the line, in pixels.
|
||||
*/
|
||||
static void guac_display_plan_mark_dirty(guac_display_layer* layer,
|
||||
guac_display_layer_cell* cell, size_t* count, int x, int y,
|
||||
int width) {
|
||||
|
||||
if (!cell->dirty_size) {
|
||||
guac_rect_init(&cell->dirty, x, y, width, 1);
|
||||
cell->dirty_size = width;
|
||||
(*count)++;
|
||||
}
|
||||
|
||||
else {
|
||||
guac_rect dirty;
|
||||
guac_rect_init(&dirty, x, y, width, 1);
|
||||
guac_rect_extend(&cell->dirty, &dirty);
|
||||
cell->dirty_size += width;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
guac_display_plan* PFW_LFR_guac_display_plan_create(guac_display* display) {
|
||||
|
||||
guac_display_layer* current;
|
||||
guac_timestamp frame_end = guac_timestamp_current();
|
||||
size_t op_count = 0;
|
||||
|
||||
/* Loop through each layer, searching for modified regions */
|
||||
current = display->pending_frame.layers;
|
||||
while (current != NULL) {
|
||||
|
||||
/* Check only within layer dirty region, skipping the layer if
|
||||
* unmodified. This pass should reset and refine that region, but
|
||||
* otherwise rely on proper reporting of modified regions by callers of
|
||||
* the open/close layer functions. */
|
||||
guac_rect dirty = current->pending_frame.dirty;
|
||||
if (guac_rect_is_empty(&dirty)) {
|
||||
current = current->pending_frame.next;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Flush any outstanding Cairo operations before directly accessing buffer */
|
||||
guac_display_layer_cairo_context* cairo_context = &(current->pending_frame_cairo_context);
|
||||
if (cairo_context->surface != NULL)
|
||||
cairo_surface_flush(cairo_context->surface);
|
||||
|
||||
/* Re-align the dirty rect with nearest multiple of 64 to ensure each
|
||||
* step of the dirty rect refinement loop starts at the topmost
|
||||
* boundary of a cell */
|
||||
guac_rect_align(&dirty, GUAC_DISPLAY_CELL_SIZE_EXPONENT);
|
||||
|
||||
guac_rect last_frame_bounds = {
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = current->last_frame.width,
|
||||
.bottom = current->last_frame.height
|
||||
};
|
||||
|
||||
guac_rect pending_frame_bounds = {
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = current->pending_frame.width,
|
||||
.bottom = current->pending_frame.height
|
||||
};
|
||||
|
||||
/* Limit size of dirty rect by bounds of backing surfaces */
|
||||
guac_rect_constrain(&dirty, &last_frame_bounds);
|
||||
guac_rect_constrain(&dirty, &pending_frame_bounds);
|
||||
|
||||
const unsigned char* flushed_row = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(current->last_frame, dirty);
|
||||
unsigned char* buffer_row = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(current->pending_frame, dirty);
|
||||
|
||||
guac_display_layer_cell* cell_row = current->pending_frame_cells
|
||||
+ guac_mem_ckd_mul_or_die(dirty.top / GUAC_DISPLAY_CELL_SIZE, current->pending_frame_cells_width)
|
||||
+ dirty.left / GUAC_DISPLAY_CELL_SIZE;
|
||||
|
||||
/* Loop through the rough modified region, refining the dirty rects of
|
||||
* each cell to more accurately contain only what has actually changed
|
||||
* since last frame */
|
||||
current->pending_frame.dirty = (guac_rect) { 0 };
|
||||
for (int y = dirty.top; y < dirty.bottom; y += GUAC_DISPLAY_CELL_SIZE) {
|
||||
|
||||
int height = GUAC_DISPLAY_CELL_SIZE;
|
||||
if (y + height > dirty.bottom)
|
||||
height = dirty.bottom - y;
|
||||
|
||||
/* Iteration through the pending_frame_cells array and the image
|
||||
* buffer is a bit complex here, as the pending_frame_cells array
|
||||
* contains cells that represent 64x64 regions, while the image
|
||||
* buffers contain absolutely all pixels. The outer loop goes
|
||||
* through just the pending cells, while the following loop goes
|
||||
* through the Y coordinates that make up that cell. */
|
||||
|
||||
for (int y_off = 0; y_off < height; y_off++) {
|
||||
|
||||
/* At this point, we need to loop through the horizontal
|
||||
* dimension, comparing the 64-pixel rows of image data in the
|
||||
* current line (y + y_off) that are in each applicable cell.
|
||||
* We jump forward by one cell for each comparison. */
|
||||
|
||||
guac_display_layer_cell* current_cell = cell_row;
|
||||
uint32_t* current_flushed = (uint32_t*) flushed_row;
|
||||
uint32_t* current_buffer = (uint32_t*) buffer_row;
|
||||
for (int x = dirty.left; x < dirty.right; x += GUAC_DISPLAY_CELL_SIZE) {
|
||||
|
||||
int width = GUAC_DISPLAY_CELL_SIZE;
|
||||
if (x + width > dirty.right)
|
||||
width = dirty.right - x;
|
||||
|
||||
/* Mark the relevant region of the cell as dirty if the
|
||||
* current 64-pixel line has changed in any way */
|
||||
size_t length = guac_mem_ckd_mul_or_die(width, GUAC_DISPLAY_LAYER_RAW_BPP);
|
||||
if (memcmp(current_buffer, current_flushed, length)) {
|
||||
guac_display_plan_mark_dirty(current, current_cell, &op_count, x, y + y_off, width);
|
||||
guac_rect_extend(¤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++);
|
||||
}
|
||||
|
||||
}
|
||||
385
src/libguac/display-plan.h
Normal file
385
src/libguac/display-plan.h
Normal file
@ -0,0 +1,385 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_DISPLAY_PLAN_H
|
||||
#define GUAC_DISPLAY_PLAN_H
|
||||
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/rect.h"
|
||||
#include "guacamole/timestamp.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* The width of an update which should be considered negible and thus
|
||||
* trivial overhead compared to the cost of two updates.
|
||||
*/
|
||||
#define GUAC_DISPLAY_NEGLIGIBLE_WIDTH 64
|
||||
|
||||
/**
|
||||
* The height of an update which should be considered negible and thus
|
||||
* trivial overhead compared to the cost of two updates.
|
||||
*/
|
||||
#define GUAC_DISPLAY_NEGLIGIBLE_HEIGHT 64
|
||||
|
||||
/**
|
||||
* The proportional increase in cost contributed by transfer and processing of
|
||||
* image data, compared to processing an equivalent amount of client-side
|
||||
* data.
|
||||
*/
|
||||
#define GUAC_DISPLAY_DATA_FACTOR 128
|
||||
|
||||
/**
|
||||
* The maximum width to allow when combining any pair of rendering operations
|
||||
* into a single operation, in pixels. This value is intended to be large
|
||||
* enough to avoid unnecessarily increasing the number of drawing operations,
|
||||
* yet also small enough to allow larger updates to be easily parallelized via
|
||||
* the worker threads.
|
||||
*/
|
||||
#define GUAC_DISPLAY_MAX_COMBINED_WIDTH 512
|
||||
|
||||
/**
|
||||
* The maximum height to allow when combining any pair of rendering operations
|
||||
* into a single operation, in pixels. This value is intended to be large
|
||||
* enough to avoid unnecessarily increasing the number of drawing operations,
|
||||
* yet also small enough to allow larger updates to be easily parallelized via
|
||||
* the worker threads.
|
||||
*/
|
||||
#define GUAC_DISPLAY_MAX_COMBINED_HEIGHT 512
|
||||
|
||||
/**
|
||||
* The base cost of every update. Each update should be considered to have
|
||||
* this starting cost, plus any additional cost estimated from its
|
||||
* content.
|
||||
*/
|
||||
#define GUAC_DISPLAY_BASE_COST 4096
|
||||
|
||||
/**
|
||||
* An increase in cost is negligible if it is less than
|
||||
* 1/GUAC_DISPLAY_NEGLIGIBLE_INCREASE of the old cost.
|
||||
*/
|
||||
#define GUAC_DISPLAY_NEGLIGIBLE_INCREASE 4
|
||||
|
||||
/**
|
||||
* The framerate which, if exceeded, indicates that JPEG is preferred.
|
||||
*/
|
||||
#define GUAC_DISPLAY_JPEG_FRAMERATE 3
|
||||
|
||||
/**
|
||||
* Minimum JPEG bitmap size (area). If the bitmap is smaller than this threshold,
|
||||
* it should be compressed as a PNG image to avoid the JPEG compression tax.
|
||||
*/
|
||||
#define GUAC_DISPLAY_JPEG_MIN_BITMAP_SIZE 4096
|
||||
|
||||
/**
|
||||
* The JPEG compression min block size, as the exponent of a power of two. This
|
||||
* defines the optimal rectangle block size factor for JPEG compression.
|
||||
* Usually 8x8 would suffice, but we use 16x16 here to reduce the occurrence of
|
||||
* ringing artifacts further.
|
||||
*/
|
||||
#define GUAC_SURFACE_JPEG_BLOCK_SIZE 4
|
||||
|
||||
/**
|
||||
* The WebP compression min block size, as the exponent of a power of two. This
|
||||
* defines the optimal rectangle block size factor for WebP compression. WebP
|
||||
* does utilize variable block size, but ensuring a block size factor reduces
|
||||
* any noise on the image edges.
|
||||
*/
|
||||
#define GUAC_SURFACE_WEBP_BLOCK_SIZE 3
|
||||
|
||||
/**
|
||||
* The number of hash buckets within each guac_display_plan.
|
||||
*/
|
||||
#define GUAC_DISPLAY_PLAN_OPERATION_INDEX_SIZE 0x10000
|
||||
|
||||
/**
|
||||
* Hash function which hashes a larger, 64-bit hash into a 16-bit hash that
|
||||
* will fit within GUAC_DISPLAY_PLAN_OPERATION_INDEX_SIZE. Note that the random
|
||||
* distribution of this hash relies entirely on the random distribution of the
|
||||
* value being hashed.
|
||||
*/
|
||||
#define GUAC_DISPLAY_PLAN_OPERATION_HASH(hash) (\
|
||||
( hash & 0xFFFF) \
|
||||
^ ((hash >> 16) & 0xFFFF) \
|
||||
^ ((hash >> 32) & 0xFFFF) \
|
||||
^ ((hash >> 48) & 0xFFFF) \
|
||||
)
|
||||
|
||||
/**
|
||||
* The type of a graphical operation that may be part of a guac_display_plan.
|
||||
*/
|
||||
typedef enum guac_display_plan_operation_type {
|
||||
|
||||
/**
|
||||
* Do nothing (no-op).
|
||||
*/
|
||||
GUAC_DISPLAY_PLAN_OPERATION_NOP = 0,
|
||||
|
||||
/**
|
||||
* Copy image data from the associated source rect to the destination rect.
|
||||
*/
|
||||
GUAC_DISPLAY_PLAN_OPERATION_COPY,
|
||||
|
||||
/**
|
||||
* Fill a rectangular region of the destination layer with the source
|
||||
* color.
|
||||
*/
|
||||
GUAC_DISPLAY_PLAN_OPERATION_RECT,
|
||||
|
||||
/**
|
||||
* Draw arbitrary image data to the destination rect.
|
||||
*/
|
||||
GUAC_DISPLAY_PLAN_OPERATION_IMG,
|
||||
|
||||
/**
|
||||
* Finish the frame, sending the frame boundary to all connected users.
|
||||
*/
|
||||
GUAC_DISPLAY_PLAN_END_FRAME
|
||||
|
||||
} guac_display_plan_operation_type;
|
||||
|
||||
/**
|
||||
* Any one of several operations that may be contained in a guac_display_plan.
|
||||
*/
|
||||
typedef struct guac_display_plan_operation {
|
||||
|
||||
/**
|
||||
* The destination layer (recipient of graphical output/changes).
|
||||
*/
|
||||
guac_display_layer* layer;
|
||||
|
||||
/**
|
||||
* The operation being performed on the destination layer.
|
||||
*/
|
||||
guac_display_plan_operation_type type;
|
||||
|
||||
/**
|
||||
* The location within the destination layer that will receive these
|
||||
* changes.
|
||||
*/
|
||||
guac_rect dest;
|
||||
|
||||
/**
|
||||
* The approximate number of pixels that have actually changed as a result
|
||||
* of this operation. This value will not necessarily be the same as the
|
||||
* area of the destination rect if some pixels remain unchanged.
|
||||
*/
|
||||
size_t dirty_size;
|
||||
|
||||
/**
|
||||
* The timestamp of the last frame that made any change within the
|
||||
* destination rect of the destination layer.
|
||||
*/
|
||||
guac_timestamp last_frame;
|
||||
|
||||
/**
|
||||
* The timestamp of the change being made. This will be the timestamp of
|
||||
* the frame at the time the frame was ended, not the timestamp of the
|
||||
* server at the time this operation was added to the plan.
|
||||
*/
|
||||
guac_timestamp current_frame;
|
||||
|
||||
union {
|
||||
|
||||
/**
|
||||
* The color that should be used to fill the destination rect. This
|
||||
* value applies only to GUAC_DISPLAY_PLAN_OPERATION_RECT operations.
|
||||
*/
|
||||
uint32_t color;
|
||||
|
||||
/**
|
||||
* The rectangle that should be copied to the destination rect. This
|
||||
* value applies only to GUAC_DISPLAY_PLAN_OPERATION_COPY operations.
|
||||
*/
|
||||
guac_rect rect;
|
||||
|
||||
} src;
|
||||
|
||||
} guac_display_plan_operation;
|
||||
|
||||
/**
|
||||
* A guac_display_plan_operation that has been hashed and stored within a
|
||||
* guac_display_plan.
|
||||
*/
|
||||
typedef struct guac_display_plan_indexed_operation {
|
||||
|
||||
/**
|
||||
* The operation.
|
||||
*/
|
||||
guac_display_plan_operation* op;
|
||||
|
||||
/**
|
||||
* The hash value associated with the operation. This hash value is derived
|
||||
* from the actual image contents of the region that was changed, using the
|
||||
* new contents of that region. The intent of this hash is to allow
|
||||
* operations to be quickly located based on the output they will produce,
|
||||
* such that image draw operations can be automatically replaced with
|
||||
* simple copies if they reuse data from elsewhere in a layer.
|
||||
*/
|
||||
uint64_t hash;
|
||||
|
||||
} guac_display_plan_indexed_operation;
|
||||
|
||||
/**
|
||||
* The set of operations required to transform the display state from what each
|
||||
* user currently sees (the previous frame) to the current state of the
|
||||
* guac_display (the current frame). The operations within a plan are quickly
|
||||
* generated based on simple image comparisons, and are then refined by an
|
||||
* optimizer based on estimated costs.
|
||||
*/
|
||||
typedef struct guac_display_plan {
|
||||
|
||||
/**
|
||||
* The display that this plan was created for.
|
||||
*/
|
||||
guac_display* display;
|
||||
|
||||
/**
|
||||
* The time that the frame ended.
|
||||
*/
|
||||
guac_timestamp frame_end;
|
||||
|
||||
/**
|
||||
* Array of all operations that should be applied, in order. The operations
|
||||
* in this array do not overlap nor depend on each other. They may be
|
||||
* safely reordered without any impact on the image that results from
|
||||
* applying those operations.
|
||||
*/
|
||||
guac_display_plan_operation* ops;
|
||||
|
||||
/**
|
||||
* The number of operations stored in the ops array.
|
||||
*/
|
||||
size_t length;
|
||||
|
||||
/**
|
||||
* Index of operations in the plan by their image contents. Only operations
|
||||
* that can be easily stored without collisions will be represented here.
|
||||
*/
|
||||
guac_display_plan_indexed_operation ops_by_hash[GUAC_DISPLAY_PLAN_OPERATION_INDEX_SIZE];
|
||||
|
||||
} guac_display_plan;
|
||||
|
||||
/**
|
||||
* Creates a new guac_display_plan representing the changes necessary to
|
||||
* transform the current remote display state seen by each connected user (the
|
||||
* previous frame) to the current local display state represented by the
|
||||
* guac_display (the current frame). The actual operations within the plan are
|
||||
* chosen based on the result of passing the naive set of operations through an
|
||||
* optimizer.
|
||||
*
|
||||
* There are cases where no plan will be generated. If no changes have occurred
|
||||
* since the last frame, or if the last frame is still being encoded by the
|
||||
* guac_display, NULL is returned. In the event that NULL is returned but
|
||||
* changes have been made, those changes will eventually be automatically
|
||||
* picked up after the currently-pending frame has finished encoded.
|
||||
*
|
||||
* The returned guac_display_plan must eventually be manually freed by a call
|
||||
* to guac_display_plan_free().
|
||||
*
|
||||
* IMPORTANT: The calling thread must already hold the write lock for the
|
||||
* display's pending_frame.lock, and must at least hold the read lock for the
|
||||
* display's last_frame.lock.
|
||||
*
|
||||
* @param display
|
||||
* The guac_display to create a plan for.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated guac_display_plan representing the changes necessary
|
||||
* to transform the current remote display state to that of the local
|
||||
* guac_display, or NULL if no plan could be created. If non-NULL, this
|
||||
* value must eventually be freed by a call to guac_display_plan_free().
|
||||
*/
|
||||
guac_display_plan* PFW_LFR_guac_display_plan_create(guac_display* display);
|
||||
|
||||
/**
|
||||
* Frees all memory associated with the given guac_display_plan.
|
||||
*
|
||||
* @param plan
|
||||
* The plan to free.
|
||||
*/
|
||||
void guac_display_plan_free(guac_display_plan* plan);
|
||||
|
||||
/**
|
||||
* Walks through all operations currently in the given guac_display_plan,
|
||||
* replacing draw operations with simple rects wherever draws consist only of a
|
||||
* single color.
|
||||
*
|
||||
* @param plan
|
||||
* The guac_display_plan to modify.
|
||||
*/
|
||||
void PFR_guac_display_plan_rewrite_as_rects(guac_display_plan* plan);
|
||||
|
||||
/**
|
||||
* Walks through all operations currently in the given guac_display_plan,
|
||||
* storing the hashes of each outstanding draw operation within ops_by_hash.
|
||||
* This function must be invoked before guac_display_plan_rewrite_as_copies()
|
||||
* can be used for the current pending frame.
|
||||
*
|
||||
* @param plan
|
||||
* The guac_display_plan to index.
|
||||
*/
|
||||
void PFR_guac_display_plan_index_dirty_cells(guac_display_plan* plan);
|
||||
|
||||
/**
|
||||
* Walks through all operations currently in the given guac_display_plan,
|
||||
* replacing draw operations with simple copies wherever draws can be rewritten
|
||||
* as copies that pull image data from the previous frame. The display plan
|
||||
* must first be indexed by guac_display_plan_index_dirty_cells() before this
|
||||
* function can be used.
|
||||
*
|
||||
* @param plan
|
||||
* The guac_display_plan to modify.
|
||||
*/
|
||||
void PFR_LFR_guac_display_plan_rewrite_as_copies(guac_display_plan* plan);
|
||||
|
||||
/**
|
||||
* Walks through all operations currently in the given guac_display_plan,
|
||||
* combining horizontally-adjacent operations wherever doing so appears to be
|
||||
* more efficient than performing those operations separately.
|
||||
*
|
||||
* @param plan
|
||||
* The guac_display_plan to modify.
|
||||
*/
|
||||
void PFW_guac_display_plan_combine_horizontally(guac_display_plan* plan);
|
||||
|
||||
/**
|
||||
* Walks through all operations currently in the given guac_display_plan,
|
||||
* combining vertically-adjacent operations wherever doing so appears to be
|
||||
* more efficient than performing those operations separately.
|
||||
*
|
||||
* @param plan
|
||||
* The guac_display_plan to modify.
|
||||
*/
|
||||
void PFW_guac_display_plan_combine_vertically(guac_display_plan* plan);
|
||||
|
||||
/**
|
||||
* Enqueues all operations from the given plan within the operation FIFO used
|
||||
* by the worker threads of the display associated with that plan. The
|
||||
* display's worker threads will immediately begin picking up and performing
|
||||
* these operations, with the final operation resulting in a frame boundary
|
||||
* ("sync" instruction) being sent to connected users.
|
||||
*
|
||||
* @param plan
|
||||
* The guac_display_plan to apply.
|
||||
*/
|
||||
void guac_display_plan_apply(guac_display_plan* plan);
|
||||
|
||||
#endif
|
||||
704
src/libguac/display-priv.h
Normal file
704
src/libguac/display-priv.h
Normal file
@ -0,0 +1,704 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_DISPLAY_PRIV_H
|
||||
#define GUAC_DISPLAY_PRIV_H
|
||||
|
||||
#include "display-plan.h"
|
||||
#include "guacamole/client.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/fifo.h"
|
||||
#include "guacamole/rect.h"
|
||||
#include "guacamole/socket.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
/**
|
||||
* The maximum amount of time to wait after flushing a frame when compensating
|
||||
* for client-side processing delays, in milliseconds. If a connected client is
|
||||
* taking longer than this amount of additional time to process a received
|
||||
* frame, processing lag compensation will be only partial (to avoid delaying
|
||||
* further processing without bound for extremely slow clients).
|
||||
*/
|
||||
#define GUAC_DISPLAY_MAX_LAG_COMPENSATION 500
|
||||
|
||||
/*
|
||||
* IMPORTANT: All functions defined within the internals of guac_display that
|
||||
* DO NOT acquire locks on their own are given prefixes based on whether they
|
||||
* access or modify the pending frame, last frame, or both. It is the
|
||||
* responsibility of the caller of such functions to ensure that the required
|
||||
* locks are either held or not relevant.
|
||||
*
|
||||
* The prefixes that may be added to function names are:
|
||||
*
|
||||
* "PFR_"
|
||||
* The function reads (but does not write) the state of the pending frame.
|
||||
* This prefix and "PFW_" are mutually-exclusive.
|
||||
*
|
||||
* "PFW_"
|
||||
* The function writes (and possibly reads) the state of the pending frame.
|
||||
* This prefix and "PFW_" are mutually-exclusive.
|
||||
*
|
||||
* "LFR_"
|
||||
* The function reads (but does not write) the state of the last frame.
|
||||
* This prefix and "LFW_" are mutually-exclusive.
|
||||
*
|
||||
* "LFW_"
|
||||
* The function writes (and possibly reads) the state of the last frame.
|
||||
* This prefix and "LFR_" are mutually-exclusive.
|
||||
*
|
||||
* Any functions lacking these prefixes either do not access last/pending
|
||||
* frames in any way or take care of acquiring/releasing locks entirely on
|
||||
* their own.
|
||||
*
|
||||
* These conventions are used for all functions in the internals of
|
||||
* guac_display, not just those defined in this header.
|
||||
*/
|
||||
|
||||
/*
|
||||
* IMPORTANT: In cases where a single thread must acquire BOTH the pending
|
||||
* frame lock and the last frame lock, the pending frame lock MUST be acquired
|
||||
* first to maintain consistent lock order and avoid deadlock conditions. All
|
||||
* functions within guac_display will follow this order.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The size of the image tiles (cells) that will be used to track changes to
|
||||
* each layer, including gathering framerate statistics and performing indexing
|
||||
* based on contents. Each side of each cell will consist of this many pixels.
|
||||
*
|
||||
* IMPORTANT: The hashing algorithm used to search the previous frame for
|
||||
* content in the pending frame that has been reused (ie: scrolling) strongly
|
||||
* depends on this value being 64. Any adjustment to this value will require
|
||||
* corresponding and careful changes to the hashing algorithm.
|
||||
*/
|
||||
#define GUAC_DISPLAY_CELL_SIZE 64
|
||||
|
||||
/**
|
||||
* The exponent of the power-of-two value that dictates the size of the image
|
||||
* tiles (cells) that will be used to track changes to each layer
|
||||
* (GUAC_DISPLAY_CELL_SIZE).
|
||||
*/
|
||||
#define GUAC_DISPLAY_CELL_SIZE_EXPONENT 6
|
||||
|
||||
/**
|
||||
* The amount that the width/height of internal storage for graphical data
|
||||
* should be rounded up to avoid unnecessary reallocations and copying.
|
||||
*/
|
||||
#define GUAC_DISPLAY_RESIZE_FACTOR 64
|
||||
|
||||
/**
|
||||
* Given the width (or height) of a layer in pixels, calculates the width (or
|
||||
* height) of that layer's pending_frame_cells array in cells.
|
||||
*
|
||||
* NOTE: It is not necessary to recalculate these values except when resizing a
|
||||
* layer. In all other cases, the width/height of a layer in cells can be found
|
||||
* in the pending_frame_cells_width and pending_frame_cells_height members
|
||||
* respectively.
|
||||
*
|
||||
* @param pixels
|
||||
* The width or height of the layer, in pixels.
|
||||
*
|
||||
* @return
|
||||
* The width or height of that layer's pending_frame_cells array, in cells.
|
||||
*/
|
||||
#define GUAC_DISPLAY_CELL_DIMENSION(pixels) \
|
||||
((pixels + GUAC_DISPLAY_CELL_SIZE - 1) / GUAC_DISPLAY_CELL_SIZE)
|
||||
|
||||
/**
|
||||
* The size of the operation FIFO read by the display worker threads. This
|
||||
* value is the number of operation slots in the FIFO, not bytes. The amount of
|
||||
* space currently specified here is roughly sufficient 8 worst-case frames
|
||||
* worth of outstanding operations.
|
||||
*/
|
||||
#define GUAC_DISPLAY_WORKER_FIFO_SIZE ( \
|
||||
GUAC_DISPLAY_MAX_WIDTH * GUAC_DISPLAY_MAX_HEIGHT \
|
||||
/ GUAC_DISPLAY_CELL_SIZE \
|
||||
/ GUAC_DISPLAY_CELL_SIZE \
|
||||
* 8)
|
||||
|
||||
/**
|
||||
* Returns the memory address of the given rectangle within the mutable image
|
||||
* buffer of the given guac_display_layer_state, where the upper-left corner of
|
||||
* the given buffer is (0, 0). If the memory address cannot be calculated
|
||||
* because doing so would overflow the maximum value of a size_t, execution of
|
||||
* the current process is automatically aborted.
|
||||
*
|
||||
* IMPORTANT: No checks are performed on whether the rectangle extends beyond
|
||||
* the bounds of the buffer, including considering whether the left/top
|
||||
* position of the rectangle is negative. If the rectangle has not already been
|
||||
* contrained to be within the bounds of the buffer, such checks must be
|
||||
* performed before dereferencing the value returned by this macro.
|
||||
*
|
||||
* @param layer_state
|
||||
* The guac_display_layer_state associated with the image buffer within
|
||||
* which the address of the given rectangle should be determined.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to determine the offset of.
|
||||
*
|
||||
* @return
|
||||
* The memory address of the given rectangle within the buffer of the given
|
||||
* layer state.
|
||||
*/
|
||||
#define GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(layer_state, rect) \
|
||||
GUAC_RECT_MUTABLE_BUFFER(rect, (layer_state).buffer, (layer_state).buffer_stride, GUAC_DISPLAY_LAYER_RAW_BPP)
|
||||
|
||||
/**
|
||||
* Returns the memory address of the given rectangle within the immutable
|
||||
* (const) image buffer of the given guac_display_layer_state, where the
|
||||
* upper-left corner of the given buffer is (0, 0). If the memory address
|
||||
* cannot be calculated because doing so would overflow the maximum value of a
|
||||
* size_t, execution of the current process is automatically aborted.
|
||||
*
|
||||
* IMPORTANT: No checks are performed on whether the rectangle extends beyond
|
||||
* the bounds of the buffer, including considering whether the left/top
|
||||
* position of the rectangle is negative. If the rectangle has not already been
|
||||
* contrained to be within the bounds of the buffer, such checks must be
|
||||
* performed before dereferencing the value returned by this macro.
|
||||
*
|
||||
* @param layer_state
|
||||
* The guac_display_layer_state associated with the image buffer within
|
||||
* which the address of the given rectangle should be determined.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to determine the offset of.
|
||||
*
|
||||
* @return
|
||||
* The memory address of the given rectangle within the buffer of the given
|
||||
* layer state.
|
||||
*/
|
||||
#define GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(layer_state, rect) \
|
||||
GUAC_RECT_CONST_BUFFER(rect, (layer_state).buffer, (layer_state).buffer_stride, GUAC_DISPLAY_LAYER_RAW_BPP)
|
||||
|
||||
/**
|
||||
* Approximation of how often a region of a layer is modified, as well as what
|
||||
* changes have been made to that region since the last frame. This information
|
||||
* is used to help advise future optimizations, such as whether lossy
|
||||
* compression is appropriate and whether parts of the layer can be copied from
|
||||
* other regions rather than resend image data.
|
||||
*/
|
||||
typedef struct guac_display_layer_cell {
|
||||
|
||||
/**
|
||||
* The last time this particular cell was part of a frame (used to
|
||||
* calculate framerate).
|
||||
*/
|
||||
guac_timestamp last_frame;
|
||||
|
||||
/**
|
||||
* The region of this cell that has been modified since the last frame was
|
||||
* flushed. If the cell has not been modified at all, this will be an empty
|
||||
* rect.
|
||||
*/
|
||||
guac_rect dirty;
|
||||
|
||||
/**
|
||||
* The rough number of pixels in the dirty rect that have been modified. If
|
||||
* the cell has not been modified at all, this will be zero.
|
||||
*/
|
||||
size_t dirty_size;
|
||||
|
||||
/**
|
||||
* The display plan operation that is associated with this cell. If a
|
||||
* display plan is not currently being created or optimized, this will be
|
||||
* NULL.
|
||||
*/
|
||||
guac_display_plan_operation* related_op;
|
||||
|
||||
} guac_display_layer_cell;
|
||||
|
||||
/**
|
||||
* The state of a Guacamole layer or buffer at some point in time. Within
|
||||
* guac_display_layer, copies of this structure are used to represent the
|
||||
* previous frame and the current, in-progress frame. The previous and
|
||||
* in-progress frames are compared during flush to determine what graphical
|
||||
* operations need to be sent to connected clients to efficiently transform the
|
||||
* remote display from its previous state to the now-current state.
|
||||
*
|
||||
* IMPORTANT: The lock of the corresponding guac_display_state must be acquired
|
||||
* before reading or modifying the values of any member of this structure.
|
||||
*/
|
||||
typedef struct guac_display_layer_state {
|
||||
|
||||
/**
|
||||
* The width of this layer in pixels.
|
||||
*/
|
||||
int width;
|
||||
|
||||
/**
|
||||
* The height of this layer in pixels.
|
||||
*/
|
||||
int height;
|
||||
|
||||
/**
|
||||
* The layer which contains this layer. This is only applicable to visible
|
||||
* (non-buffer) layers which are not the default layer.
|
||||
*/
|
||||
const guac_layer* parent;
|
||||
|
||||
/**
|
||||
* The X coordinate of the upper-left corner of this layer, in pixels,
|
||||
* relative to its parent layer. This is only applicable to visible
|
||||
* (non-buffer) layers which are not the default layer.
|
||||
*/
|
||||
int x;
|
||||
|
||||
/**
|
||||
* The Y coordinate of the upper-left corner of this layer, in pixels,
|
||||
* relative to its parent layer. This is only applicable to visible
|
||||
* (non-buffer) layers which are not the default layer.
|
||||
*/
|
||||
int y;
|
||||
|
||||
/**
|
||||
* The Z-order of this layer, relative to sibling layers. This is only
|
||||
* applicable to visible (non-buffer) layers which are not the default
|
||||
* layer.
|
||||
*/
|
||||
int z;
|
||||
|
||||
/**
|
||||
* The level of opacity applied to this layer. Fully opaque is 255, while
|
||||
* fully transparent is 0. This is only applicable to visible (non-buffer)
|
||||
* layers which are not the default layer.
|
||||
*/
|
||||
int opacity;
|
||||
|
||||
/**
|
||||
* The number of simultaneous touches that this surface can accept, where 0
|
||||
* indicates that the surface does not support touch events at all.
|
||||
*/
|
||||
int touches;
|
||||
|
||||
/**
|
||||
* Non-zero if all graphical updates for this surface should use lossless
|
||||
* compression, 0 otherwise. By default, newly-created surfaces will use
|
||||
* lossy compression when heuristics determine it is appropriate.
|
||||
*/
|
||||
int lossless;
|
||||
|
||||
/**
|
||||
* The raw, 32-bit buffer of ARGB image data. If the layer was allocated as
|
||||
* opaque, the alpha channel of each ARGB pixel will not be considered when
|
||||
* compositing or when encoding images.
|
||||
*
|
||||
* So that large regions of image data can be easily compared, a consistent
|
||||
* value for the alpha channel SHOULD be provided so that each 32-bit pixel
|
||||
* can be compared without having to separately masking the channel.
|
||||
* Optimizations within guac_display, including scroll detection, may
|
||||
* assume that the alpha channel can always be considered when comparing
|
||||
* pixel values for equivalence.
|
||||
*/
|
||||
unsigned char* buffer;
|
||||
|
||||
/**
|
||||
* The width of the image data, in pixels. This is not necessarily the same
|
||||
* as the width of the layer.
|
||||
*/
|
||||
int buffer_width;
|
||||
|
||||
/**
|
||||
* The height of the image data, in pixels. This is not necessarily the
|
||||
* same as the height of the layer.
|
||||
*/
|
||||
int buffer_height;
|
||||
|
||||
/**
|
||||
* The number of bytes in each row of image data. This is not necessarily
|
||||
* equivalent to 4 * width.
|
||||
*/
|
||||
size_t buffer_stride;
|
||||
|
||||
/**
|
||||
* The approximate rectangular region containing all pixels within this
|
||||
* layer that have been modified since the frame that occurred before this
|
||||
* frame. If the layer was not modified, this will be an empty rect (zero
|
||||
* width or zero height).
|
||||
*/
|
||||
guac_rect dirty;
|
||||
|
||||
/* ---------------- LAYER LIST POINTERS ---------------- */
|
||||
|
||||
/**
|
||||
* The layer immediately prior to this layer within the list containing
|
||||
* this layer, or NULL if this is the first layer/buffer in the list.
|
||||
*/
|
||||
guac_display_layer* prev;
|
||||
|
||||
/**
|
||||
* The layer immediately following this layer within the list containing
|
||||
* this layer, or NULL if this is the last layer/buffer in the list.
|
||||
*/
|
||||
guac_display_layer* next;
|
||||
|
||||
} guac_display_layer_state;
|
||||
|
||||
struct guac_display_layer {
|
||||
|
||||
/**
|
||||
* The guac_display instance that allocated this layer/buffer.
|
||||
*/
|
||||
guac_display* display;
|
||||
|
||||
/**
|
||||
* The Guacamole layer (or buffer) that this guac_display_layer will draw
|
||||
* to when flushing a frame.
|
||||
*
|
||||
* NOTE: This value is set only during allocation and may safely be
|
||||
* accessed without acquiring the overall layer lock.
|
||||
*/
|
||||
const guac_layer* layer;
|
||||
|
||||
/**
|
||||
* Whether the graphical data that will be written to this layer/buffer
|
||||
* will only ever be opaque (no alpha channel). Compositing of graphical
|
||||
* updates can be faster when no alpha channel need be considered.
|
||||
*/
|
||||
int opaque;
|
||||
|
||||
/* ---------------- LAYER PREVIOUS FRAME STATE ---------------- */
|
||||
|
||||
/**
|
||||
* The state of this layer when the last frame was flushed to connected clients.
|
||||
*
|
||||
* IMPORTANT: The display-level last_frame.lock MUST be acquired before
|
||||
* modifying or reading this member.
|
||||
*/
|
||||
guac_display_layer_state last_frame;
|
||||
|
||||
/**
|
||||
* Off-screen buffer storing the contents of the previously-rendered frame
|
||||
* for later use. If graphical updates are recognized as reusing data from
|
||||
* a previous frame, that data will be copied from this buffer. Doing this
|
||||
* simplifies the copy operation (there is no longer any need to perform
|
||||
* those copies in a specific order) and ensures the copies are efficient
|
||||
* on the client side (copying from one part of a graphical surface to
|
||||
* another part of the same surface can be inefficient, particularly if the
|
||||
* regions overlap). In practice, there is ample time between frames for
|
||||
* the client to copy a layer's current contents to an off-screen buffer
|
||||
* while awaiting the next frame.
|
||||
*
|
||||
* NOTE: This value is set only during allocation and may safely be
|
||||
* accessed without acquiring the display-level last_frame.lock.
|
||||
*/
|
||||
guac_layer* last_frame_buffer;
|
||||
|
||||
/* ---------------- LAYER PENDING FRAME STATE ---------------- */
|
||||
|
||||
/**
|
||||
* The upcoming state of this layer when the current, in-progress frame is
|
||||
* flushed to connected clients.
|
||||
*
|
||||
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
|
||||
* modifying or reading this member.
|
||||
*/
|
||||
guac_display_layer_state pending_frame;
|
||||
|
||||
/**
|
||||
* The Cairo context and surface containing the graphical data of the
|
||||
* pending frame. The actual underlying buffer and details of the graphical
|
||||
* surface are also available via pending_frame_raw_context.
|
||||
*
|
||||
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
|
||||
* modifying or reading this member.
|
||||
*/
|
||||
guac_display_layer_cairo_context pending_frame_cairo_context;
|
||||
|
||||
/**
|
||||
* The raw underlying buffer and details of the surface containing the
|
||||
* graphical data of the pending frame. A Cairo context and surface backed
|
||||
* by this buffer are also available via pending_frame_cairo_context.
|
||||
*
|
||||
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
|
||||
* modifying or reading this member.
|
||||
*/
|
||||
guac_display_layer_raw_context pending_frame_raw_context;
|
||||
|
||||
/**
|
||||
* A two-dimensional array of square tiles representing the nature of
|
||||
* changes made to corresponding regions of the display. This is used both
|
||||
* to track how frequently certain regions are being updated (to help
|
||||
* inform whether lossy compression is appropriate), to track what parts of
|
||||
* the frame have actually changed, and to aid in determining whether
|
||||
* adjacent updated regions should be combined into a single update.
|
||||
*
|
||||
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
|
||||
* modifying or reading this member.
|
||||
*/
|
||||
guac_display_layer_cell* pending_frame_cells;
|
||||
|
||||
/**
|
||||
* The width of the pending_frame_cells array, in cells.
|
||||
*
|
||||
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
|
||||
* modifying or reading this member.
|
||||
*/
|
||||
size_t pending_frame_cells_width;
|
||||
|
||||
/**
|
||||
* The height of the pending_frame_cells array, in cells.
|
||||
*
|
||||
* IMPORTANT: The display-level pending_frame.lock MUST be acquired before
|
||||
* modifying or reading this member.
|
||||
*/
|
||||
size_t pending_frame_cells_height;
|
||||
|
||||
};
|
||||
|
||||
typedef struct guac_display_state {
|
||||
|
||||
/**
|
||||
* Lock that guards concurrent access to any member of ANY STRUCTURE that
|
||||
* relates to this guac_display_state, including the members of this
|
||||
* structure. Unless explicitly documented otherwise, this lock MUST be
|
||||
* acquired before accessing or modifying the members of this
|
||||
* guac_display_state or any nested structure.
|
||||
*/
|
||||
guac_rwlock lock;
|
||||
|
||||
/**
|
||||
* The specific point in time that this guac_display_state represents.
|
||||
*/
|
||||
guac_timestamp timestamp;
|
||||
|
||||
/**
|
||||
* All layers and buffers that were part of the display at the time that
|
||||
* the frame/snapshot represented by this guac_display_state was updated.
|
||||
*
|
||||
* NOTE: For each guac_display, there are two distinct lists of layers: the
|
||||
* last frame layer list and the pending frame layer list:
|
||||
*
|
||||
* LAST FRAME LAYER LIST
|
||||
*
|
||||
* - HEAD: display->last_frame.layers
|
||||
* - NEXT: layer->last_frame.next
|
||||
* - PREV: layer->last_frame.prev
|
||||
*
|
||||
* PENDING LAYER LIST
|
||||
*
|
||||
* - HEAD: display->pending_frame.layers
|
||||
* - NEXT: layer->pending_frame.next
|
||||
* - PREV: layer->pending_frame.prev
|
||||
*
|
||||
* Existing layers are deleted only at the time a frame is flushed when a
|
||||
* layer in the last frame layer list is found to no longer exist in the
|
||||
* pending frame layer list. The same goes for the addition of new layers:
|
||||
* they are added only during flush when a layer that was not present in
|
||||
* the last frame layer list is found to be present in the pending frame
|
||||
* layer list.
|
||||
*/
|
||||
guac_display_layer* layers;
|
||||
|
||||
/**
|
||||
* The X coordinate of the hotspot of the mouse cursor. The cursor image is
|
||||
* stored/updated via the cursor_buffer member of guac_display.
|
||||
*/
|
||||
int cursor_hotspot_x;
|
||||
|
||||
/**
|
||||
* The Y coordinate of the hotspot of the mouse cursor. The cursor image is
|
||||
* stored/updated via the cursor_buffer member of guac_display.
|
||||
*/
|
||||
int cursor_hotspot_y;
|
||||
|
||||
/**
|
||||
* The user that moved or clicked the mouse. This is used to ensure we
|
||||
* don't attempt to synchronize an out-of-date mouse position to the user
|
||||
* that is actively moving the mouse.
|
||||
*/
|
||||
guac_user* cursor_user;
|
||||
|
||||
/**
|
||||
* The X coordinate of the mouse cursor.
|
||||
*/
|
||||
int cursor_x;
|
||||
|
||||
/**
|
||||
* The Y coordinate of the mouse cursor.
|
||||
*/
|
||||
int cursor_y;
|
||||
|
||||
/**
|
||||
* The mask representing the states of all mouse buttons.
|
||||
*/
|
||||
int cursor_mask;
|
||||
|
||||
/**
|
||||
* The number of logical frames that have been rendered to this display
|
||||
* state since the previous display state.
|
||||
*/
|
||||
unsigned int frames;
|
||||
|
||||
} guac_display_state;
|
||||
|
||||
struct guac_display {
|
||||
|
||||
/* NOTE: Any member of this structure that requires protection against
|
||||
* concurrent access is protected by its own lock. The overall display does
|
||||
* not have nor need a top-level lock. */
|
||||
|
||||
/**
|
||||
* The client associated with this display.
|
||||
*/
|
||||
guac_client* client;
|
||||
|
||||
/* ---------------- DISPLAY FRAME STATES ---------------- */
|
||||
|
||||
/**
|
||||
* The state of this display at the time the last frame was sent to
|
||||
* connected users.
|
||||
*/
|
||||
guac_display_state last_frame;
|
||||
|
||||
/**
|
||||
* The pending state of this display that will become the next frame once
|
||||
* it is sent to connected users.
|
||||
*/
|
||||
guac_display_state pending_frame;
|
||||
|
||||
/**
|
||||
* Whether the pending_frame has been modified in any way since the last
|
||||
* frame.
|
||||
*
|
||||
* IMPORTANT: The pending_frame.lock MUST be acquired before modifying or
|
||||
* reading this member.
|
||||
*/
|
||||
int pending_dirty;
|
||||
|
||||
/* ---------------- WELL-KNOWN LAYERS / BUFFERS ---------------- */
|
||||
|
||||
/**
|
||||
* The default layer of the client display.
|
||||
*/
|
||||
guac_display_layer* default_layer;
|
||||
|
||||
/**
|
||||
* The buffer storing the current mouse cursor. The hotspot position within
|
||||
* the cursor is stored within cursor_hotspot_x and cursor_hotspot_y of
|
||||
* guac_display_state.
|
||||
*/
|
||||
guac_display_layer* cursor_buffer;
|
||||
|
||||
/* ---------------- FRAME ENCODING WORKER THREADS ---------------- */
|
||||
|
||||
/**
|
||||
* The number of worker threads in the worker_threads array.
|
||||
*/
|
||||
int worker_thread_count;
|
||||
|
||||
/**
|
||||
* Pool of worker threads that automatically pull from the ops FIFO,
|
||||
* sending corresponding Guacamole instructions to all connected clients.
|
||||
*/
|
||||
pthread_t* worker_threads;
|
||||
|
||||
/**
|
||||
* FIFO of all graphical operations required to transform the remote
|
||||
* display state from the previous frame to the next frame. Operations
|
||||
* added to this FIFO will automatically be pulled and processed by a
|
||||
* worker thread.
|
||||
*/
|
||||
guac_fifo ops;
|
||||
|
||||
/**
|
||||
* Lock which ensures instructions that make use of a layer's current path
|
||||
* (such as "rect" and "cfill") do not get inadvertently interleaved.
|
||||
* Interleaving of path instructions can result in those paths not matching
|
||||
* the expectatiosn of subsequent instructions, causing graphical
|
||||
* artifacts.
|
||||
*/
|
||||
pthread_mutex_t op_path_lock;
|
||||
|
||||
/**
|
||||
* Storage for any items within the ops fifo.
|
||||
*/
|
||||
guac_display_plan_operation ops_items[GUAC_DISPLAY_WORKER_FIFO_SIZE];
|
||||
|
||||
/**
|
||||
* The current number of active worker threads.
|
||||
*
|
||||
* IMPORTANT: This member must only be accessed or modified while the ops
|
||||
* FIFO is locked.
|
||||
*/
|
||||
unsigned int active_workers;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocates and inserts a new element into the given linked list of display
|
||||
* layers, associating it with the given layer and surface.
|
||||
*
|
||||
* @param head
|
||||
* A pointer to the head pointer of the list of layers. The head pointer
|
||||
* will be updated by this function to point to the newly-allocated
|
||||
* display layer.
|
||||
*
|
||||
* @param layer
|
||||
* The Guacamole layer to associated with the new display layer.
|
||||
*
|
||||
* @param opaque
|
||||
* Non-zero if the new layer will only ever contain opaque image contents
|
||||
* (the alpha channel should be ignored), zero otherwise.
|
||||
*
|
||||
* @return
|
||||
* The newly-allocated display layer, which has been associated with the
|
||||
* provided layer and surface.
|
||||
*/
|
||||
guac_display_layer* guac_display_add_layer(guac_display* display, guac_layer* layer, int opaque);
|
||||
|
||||
/**
|
||||
* Removes the given layer from all linked lists containing that layer and
|
||||
* frees all associated memory.
|
||||
*
|
||||
* @param display_layer
|
||||
* The layer to remove.
|
||||
*/
|
||||
void guac_display_remove_layer(guac_display_layer* display_layer);
|
||||
|
||||
/**
|
||||
* Resizes the given layer to the given dimensions, including any underlying
|
||||
* image buffers.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to resize.
|
||||
*
|
||||
* @param width
|
||||
* The new width, in pixels.
|
||||
*
|
||||
* @param height
|
||||
* The new height, in pixels.
|
||||
*/
|
||||
void PFW_LFW_guac_display_layer_resize(guac_display_layer* layer,
|
||||
int width, int height);
|
||||
|
||||
/**
|
||||
* Worker thread that continuously pulls operations from the operation FIFO of
|
||||
* the given guac_display, applying those operations by seding corresponding
|
||||
* instructions to connected clients.
|
||||
*
|
||||
* @param data
|
||||
* A pointer to the guac_display.
|
||||
*
|
||||
* @return
|
||||
* Always NULL.
|
||||
*/
|
||||
void* guac_display_worker_thread(void* data);
|
||||
|
||||
#endif
|
||||
602
src/libguac/display-worker.c
Normal file
602
src/libguac/display-worker.c
Normal file
@ -0,0 +1,602 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "display-plan.h"
|
||||
#include "display-priv.h"
|
||||
#include "guacamole/client.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/fifo.h"
|
||||
#include "guacamole/layer.h"
|
||||
#include "guacamole/protocol.h"
|
||||
#include "guacamole/rect.h"
|
||||
#include "guacamole/rwlock.h"
|
||||
#include "guacamole/socket.h"
|
||||
#include "guacamole/timestamp.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <cairo/cairo.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/**
|
||||
* Sends the contents of the given dirty rectangle from the given layer using
|
||||
* lossless PNG compression. The resulting instructions will be sent over the
|
||||
* client-wide broadcast socket associated with the given layer. The graphical
|
||||
* contents sent will be pulled from the layer's last_frame buffer. If sending
|
||||
* the contents of a pending frame, that pending frame must have been copied
|
||||
* over to the last_frame buffer before calling this function.
|
||||
*
|
||||
* @param display_layer
|
||||
* The layer whose data should be sent to connected users.
|
||||
*
|
||||
* @param dirty
|
||||
* The region of the layer that should be sent.
|
||||
*/
|
||||
static void LFR_guac_display_layer_flush_to_png(guac_display_layer* display_layer,
|
||||
guac_rect* dirty) {
|
||||
|
||||
guac_display* display = display_layer->display;
|
||||
guac_client* client = display->client;
|
||||
guac_socket* socket = client->socket;
|
||||
const guac_layer* layer = display_layer->layer;
|
||||
|
||||
/* Get Cairo layer for specified rect */
|
||||
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(display_layer->last_frame, *dirty);
|
||||
|
||||
cairo_surface_t* rect;
|
||||
|
||||
/* Use RGB24 if the image is fully opaque */
|
||||
if (display_layer->opaque)
|
||||
rect = cairo_image_surface_create_for_data(buffer,
|
||||
CAIRO_FORMAT_RGB24, guac_rect_width(dirty),
|
||||
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
|
||||
|
||||
/* Otherwise ARGB32 is needed */
|
||||
else {
|
||||
|
||||
rect = cairo_image_surface_create_for_data(buffer,
|
||||
CAIRO_FORMAT_ARGB32, guac_rect_width(dirty),
|
||||
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
|
||||
|
||||
/* Clear destination rect first */
|
||||
pthread_mutex_lock(&display->op_path_lock);
|
||||
guac_protocol_send_rect(socket, layer,
|
||||
dirty->left, dirty->top,
|
||||
guac_rect_width(dirty), guac_rect_height(dirty));
|
||||
guac_protocol_send_cfill(socket, GUAC_COMP_ROUT, layer,
|
||||
0x00, 0x00, 0x00, 0xFF);
|
||||
pthread_mutex_unlock(&display->op_path_lock);
|
||||
|
||||
}
|
||||
|
||||
/* Send PNG for rect */
|
||||
guac_client_stream_png(client, socket, GUAC_COMP_OVER,
|
||||
layer, dirty->left, dirty->top, rect);
|
||||
|
||||
cairo_surface_destroy(rect);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriate quality between 0 and 100 for lossy encoding
|
||||
* depending on the current processing lag calculated for the given client.
|
||||
*
|
||||
* @param client
|
||||
* The client for which the lossy quality is being calculated.
|
||||
*
|
||||
* @return
|
||||
* A value between 0 and 100 inclusive which seems appropriate for the
|
||||
* client based on lag measurements.
|
||||
*/
|
||||
static int guac_display_suggest_quality(guac_client* client) {
|
||||
|
||||
int lag = guac_client_get_processing_lag(client);
|
||||
|
||||
/* Scale quality linearly from 90 to 30 as lag varies from 20ms to 80ms */
|
||||
int quality = 90 - (lag - 20);
|
||||
|
||||
/* Do not exceed 90 for quality */
|
||||
if (quality > 90)
|
||||
return 90;
|
||||
|
||||
/* Do not go below 30 for quality */
|
||||
if (quality < 30)
|
||||
return 30;
|
||||
|
||||
return quality;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the contents of the given dirty rectangle from the given layer using
|
||||
* lossy JPEG compression. The resulting instructions will be sent over the
|
||||
* client-wide broadcast socket associated with the given layer. The graphical
|
||||
* contents sent will be pulled from the layer's last_frame buffer. If sending
|
||||
* the contents of a pending frame, that pending frame must have been copied
|
||||
* over to the last_frame buffer before calling this function.
|
||||
*
|
||||
* @param layer
|
||||
* The layer whose data should be sent to connected users.
|
||||
*
|
||||
* @param dirty
|
||||
* The region of the layer that should be sent.
|
||||
*/
|
||||
static void LFR_guac_display_layer_flush_to_jpeg(guac_display_layer* display_layer,
|
||||
guac_rect* dirty) {
|
||||
|
||||
guac_display* display = display_layer->display;
|
||||
guac_client* client = display->client;
|
||||
guac_socket* socket = client->socket;
|
||||
const guac_layer* layer = display_layer->layer;
|
||||
|
||||
guac_rect max = {
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = display_layer->last_frame.width,
|
||||
.bottom = display_layer->last_frame.height
|
||||
};
|
||||
|
||||
/* Expand the dirty rect size to fit in a grid with cells equal to the
|
||||
* minimum JPEG block size */
|
||||
guac_rect_align(dirty, GUAC_SURFACE_JPEG_BLOCK_SIZE);
|
||||
guac_rect_constrain(dirty, &max);
|
||||
|
||||
/* Get Cairo layer for specified rect */
|
||||
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(display_layer->last_frame, *dirty);
|
||||
|
||||
cairo_surface_t* rect = cairo_image_surface_create_for_data(buffer,
|
||||
CAIRO_FORMAT_RGB24, guac_rect_width(dirty),
|
||||
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
|
||||
|
||||
/* Send JPEG for rect */
|
||||
guac_client_stream_jpeg(client, socket, GUAC_COMP_OVER, layer,
|
||||
dirty->left, dirty->top, rect,
|
||||
guac_display_suggest_quality(client));
|
||||
|
||||
cairo_surface_destroy(rect);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the contents of the given dirty rectangle from the given layer using
|
||||
* WebP compression. Whether that WebP compression is lossless depends on the
|
||||
* lossless setting of the layer's last frame. The resulting instructions will
|
||||
* be sent over the client-wide broadcast socket associated with the given
|
||||
* layer. The graphical contents sent will be pulled from the layer's
|
||||
* last_frame buffer. If sending the contents of a pending frame, that pending
|
||||
* frame must have been copied over to the last_frame buffer before calling
|
||||
* this function.
|
||||
*
|
||||
* @param layer
|
||||
* The layer whose data should be sent to connected users.
|
||||
*
|
||||
* @param dirty
|
||||
* The region of the layer that should be sent.
|
||||
*/
|
||||
static void LFR_guac_display_layer_flush_to_webp(guac_display_layer* display_layer,
|
||||
guac_rect* dirty) {
|
||||
|
||||
guac_display* display = display_layer->display;
|
||||
guac_client* client = display->client;
|
||||
guac_socket* socket = client->socket;
|
||||
const guac_layer* layer = display_layer->layer;
|
||||
|
||||
guac_rect max = {
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = display_layer->last_frame.width,
|
||||
.bottom = display_layer->last_frame.height
|
||||
};
|
||||
|
||||
/* Expand the dirty rect size to fit in a grid with cells equal to the
|
||||
* minimum WebP block size */
|
||||
guac_rect_align(dirty, GUAC_SURFACE_WEBP_BLOCK_SIZE);
|
||||
guac_rect_constrain(dirty, &max);
|
||||
|
||||
/* Get Cairo layer for specified rect */
|
||||
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(display_layer->last_frame, *dirty);
|
||||
|
||||
cairo_surface_t* rect;
|
||||
|
||||
/* Use RGB24 if the image is fully opaque */
|
||||
if (display_layer->opaque)
|
||||
rect = cairo_image_surface_create_for_data(buffer,
|
||||
CAIRO_FORMAT_RGB24, guac_rect_width(dirty),
|
||||
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
|
||||
|
||||
/* Otherwise ARGB32 is needed */
|
||||
else
|
||||
rect = cairo_image_surface_create_for_data(buffer,
|
||||
CAIRO_FORMAT_ARGB32, guac_rect_width(dirty),
|
||||
guac_rect_height(dirty), display_layer->last_frame.buffer_stride);
|
||||
|
||||
/* Send WebP for rect */
|
||||
guac_client_stream_webp(client, socket, GUAC_COMP_OVER, layer,
|
||||
dirty->left, dirty->top, rect,
|
||||
guac_display_suggest_quality(client),
|
||||
display_layer->last_frame.lossless ? 1 : 0);
|
||||
|
||||
cairo_surface_destroy(rect);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Guesses whether a rectangle within a particular layer would be better
|
||||
* compressed as PNG or using a lossy format like JPEG. Positive values
|
||||
* indicate PNG is likely to be superior, while negative values indicate the
|
||||
* opposite.
|
||||
*
|
||||
* @param layer
|
||||
* The layer containing the image data to check.
|
||||
*
|
||||
* @param rect
|
||||
* The rect to check within the given layer.
|
||||
*
|
||||
* @return
|
||||
* Positive values if PNG compression is likely to perform better than
|
||||
* lossy alternatives, or negative values if PNG is likely to perform
|
||||
* worse.
|
||||
*/
|
||||
static int LFR_guac_display_layer_png_optimality(guac_display_layer* layer,
|
||||
const guac_rect* rect) {
|
||||
|
||||
int x, y;
|
||||
|
||||
int num_same = 0;
|
||||
int num_different = 1;
|
||||
|
||||
/* Get buffer from layer */
|
||||
size_t stride = layer->last_frame.buffer_stride;
|
||||
const unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_CONST_BUFFER(layer->last_frame, *rect);
|
||||
|
||||
/* Image must be at least 1x1 */
|
||||
if (rect->right - rect->left < 1 || rect->bottom - rect->top< 1)
|
||||
return 0;
|
||||
|
||||
/* For each row */
|
||||
for (y = rect->top; y < rect->bottom; y++) {
|
||||
|
||||
uint32_t* row = (uint32_t*) buffer;
|
||||
uint32_t last_pixel = *(row++) | 0xFF000000;
|
||||
|
||||
/* For each pixel in current row */
|
||||
for (x = rect->left + 1; x < rect->right; x++) {
|
||||
|
||||
/* Get next pixel */
|
||||
uint32_t current_pixel = *(row++) | 0xFF000000;
|
||||
|
||||
/* Update same/different counts according to pixel value */
|
||||
if (current_pixel == last_pixel)
|
||||
num_same++;
|
||||
else
|
||||
num_different++;
|
||||
|
||||
last_pixel = current_pixel;
|
||||
|
||||
}
|
||||
|
||||
/* Advance to next row */
|
||||
buffer += stride;
|
||||
|
||||
}
|
||||
|
||||
/* Return rough approximation of optimality for PNG compression. As PNG
|
||||
* leverages lossless DEFLATE compression (which works by reducing the
|
||||
* number of bytes required to represent repeated data), an approximation
|
||||
* of the amount of repeated image data within the image is a reasonable
|
||||
* approximation for how well an image will compress. */
|
||||
return 0x100 * num_same / num_different - 0x400;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given rectangle would be optimally encoded as JPEG
|
||||
* rather than PNG.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to be queried.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to check.
|
||||
*
|
||||
* @param framerate
|
||||
* The rate that the region covered by the given rectangle has historically
|
||||
* been being updated within the given layer, in frames per second.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the rectangle would be optimally encoded as JPEG, zero
|
||||
* otherwise.
|
||||
*/
|
||||
static int LFR_guac_display_layer_should_use_jpeg(guac_display_layer* layer,
|
||||
const guac_rect* rect, int framerate) {
|
||||
|
||||
/* Do not use JPEG if lossless quality is required */
|
||||
if (layer->last_frame.lossless)
|
||||
return 0;
|
||||
|
||||
int rect_width = rect->right - rect->left;
|
||||
int rect_height = rect->bottom - rect->top;
|
||||
int rect_size = rect_width * rect_height;
|
||||
|
||||
/* JPEG is preferred if:
|
||||
* - frame rate is high enough
|
||||
* - image size is large enough
|
||||
* - PNG is not more optimal based on image contents */
|
||||
return framerate >= GUAC_DISPLAY_JPEG_FRAMERATE
|
||||
&& rect_size > GUAC_DISPLAY_JPEG_MIN_BITMAP_SIZE
|
||||
&& LFR_guac_display_layer_png_optimality(layer, rect) < 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given rectangle would be optimally encoded as WebP
|
||||
* rather than PNG.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to be queried.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to check.
|
||||
*
|
||||
* @param framerate
|
||||
* The rate that the region covered by the given rectangle has historically
|
||||
* been being updated within the given layer, in frames per second.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the rectangle would be optimally encoded as WebP, zero
|
||||
* otherwise.
|
||||
*/
|
||||
static int LFR_guac_display_layer_should_use_webp(guac_display_layer* layer,
|
||||
const guac_rect* rect, int framerate) {
|
||||
|
||||
/* Do not use WebP if not supported */
|
||||
if (!guac_client_supports_webp(layer->display->client))
|
||||
return 0;
|
||||
|
||||
/* WebP is preferred if:
|
||||
* - frame rate is high enough
|
||||
* - PNG is not more optimal based on image contents */
|
||||
return framerate >= GUAC_DISPLAY_JPEG_FRAMERATE
|
||||
&& LFR_guac_display_layer_png_optimality(layer, rect) < 0;
|
||||
|
||||
}
|
||||
|
||||
void* guac_display_worker_thread(void* data) {
|
||||
|
||||
int framerate;
|
||||
int has_outstanding_frames = 0;
|
||||
|
||||
guac_display* display = (guac_display*) data;
|
||||
guac_client* client = display->client;
|
||||
|
||||
guac_display_plan_operation op;
|
||||
while (guac_fifo_dequeue_and_lock(&display->ops, &op)) {
|
||||
|
||||
/* NOTE: Any thread that locks the operation queue can know that there
|
||||
* are no pending operations in progress if the queue is empty and
|
||||
* there are no active workers */
|
||||
display->active_workers++;
|
||||
guac_fifo_unlock(&display->ops);
|
||||
|
||||
guac_rwlock_acquire_read_lock(&display->last_frame.lock);
|
||||
guac_display_layer* display_layer = op.layer;
|
||||
switch (op.type) {
|
||||
|
||||
case GUAC_DISPLAY_PLAN_OPERATION_IMG:
|
||||
|
||||
framerate = INT_MAX;
|
||||
if (op.current_frame > op.last_frame)
|
||||
framerate = 1000 / (op.current_frame - op.last_frame);
|
||||
|
||||
guac_rect* dirty = &op.dest;
|
||||
|
||||
/* TODO: Determine whether to use PNG/WebP/JPEG purely
|
||||
* based on whether lossless encoding is required, the
|
||||
* expected time until another frame is received (time
|
||||
* since last frame), and estimated encoding times. The
|
||||
* time allowed per update should be divided up
|
||||
* proportionately based on the dirty_size of the update. */
|
||||
|
||||
/* TODO: Stream PNG/WebP/JPEG using progressive encoding such
|
||||
* that a frame that is currently being encoded can be
|
||||
* preempted by the next frame, with the connected client then
|
||||
* simply receiving a lower-quality intermediate frame. If
|
||||
* necessary, progressive encoding can be achieved by manually
|
||||
* dividing images into multiple reduced-resolution stages,
|
||||
* such that each image streamed is actually only one quarter
|
||||
* the size of the original image. Compositing via Guacamole
|
||||
* protocol instructions can reassemble those stages. */
|
||||
|
||||
/* Prefer WebP when reasonable */
|
||||
if (LFR_guac_display_layer_should_use_webp(display_layer, dirty, framerate))
|
||||
LFR_guac_display_layer_flush_to_webp(display_layer, dirty);
|
||||
|
||||
/* If not WebP, JPEG is the next best (lossy) choice */
|
||||
else if (display_layer->opaque && LFR_guac_display_layer_should_use_jpeg(display_layer, dirty, framerate))
|
||||
LFR_guac_display_layer_flush_to_jpeg(display_layer, dirty);
|
||||
|
||||
/* Use PNG if no lossy formats are appropriate */
|
||||
else
|
||||
LFR_guac_display_layer_flush_to_png(display_layer, dirty);
|
||||
|
||||
break;
|
||||
|
||||
case GUAC_DISPLAY_PLAN_OPERATION_COPY:
|
||||
guac_protocol_send_copy(client->socket, display_layer->last_frame_buffer,
|
||||
op.src.rect.left, op.src.rect.top, guac_rect_width(&op.src.rect), guac_rect_height(&op.src.rect),
|
||||
GUAC_COMP_OVER, display_layer->layer, op.dest.left, op.dest.top);
|
||||
break;
|
||||
|
||||
case GUAC_DISPLAY_PLAN_OPERATION_RECT:
|
||||
|
||||
pthread_mutex_lock(&display->op_path_lock);
|
||||
guac_protocol_send_rect(client->socket, display_layer->layer,
|
||||
op.dest.left, op.dest.top, guac_rect_width(&op.dest), guac_rect_height(&op.dest));
|
||||
|
||||
int alpha = (op.src.color & 0xFF000000) >> 24;
|
||||
int red = (op.src.color & 0x00FF0000) >> 16;
|
||||
int green = (op.src.color & 0x0000FF00) >> 8;
|
||||
int blue = (op.src.color & 0x000000FF);
|
||||
|
||||
/* Clear before drawing if layer is not opaque (transparency
|
||||
* will not be copied correctly otherwise) */
|
||||
if (!display_layer->opaque)
|
||||
guac_protocol_send_cfill(client->socket, GUAC_COMP_ROUT, display_layer->layer,
|
||||
0x00, 0x00, 0x00, 0xFF);
|
||||
|
||||
guac_protocol_send_cfill(client->socket, GUAC_COMP_OVER, display_layer->layer,
|
||||
red, green, blue, alpha);
|
||||
|
||||
pthread_mutex_unlock(&display->op_path_lock);
|
||||
break;
|
||||
|
||||
case GUAC_DISPLAY_PLAN_OPERATION_NOP:
|
||||
break;
|
||||
|
||||
case GUAC_DISPLAY_PLAN_END_FRAME:
|
||||
|
||||
guac_fifo_lock(&display->ops);
|
||||
int other_workers_busy = (display->active_workers > 1);
|
||||
guac_fifo_unlock(&display->ops);
|
||||
|
||||
/* If other workers are still busy, push the frame boundary
|
||||
* back on the queue so that it's picked up by one of those
|
||||
* workers */
|
||||
if (other_workers_busy) {
|
||||
guac_fifo_enqueue(&display->ops, &op);
|
||||
}
|
||||
|
||||
/* Otherwise, we've reached the end of the frame, and this is
|
||||
* the worker that will be sending that boundary to connected
|
||||
* users */
|
||||
else {
|
||||
|
||||
/* Use the amount of time that the client has been waiting
|
||||
* for a frame vs. the amount of time that it took the
|
||||
* client to process the most recently acknowledged frame
|
||||
* to calculate the amount of additional delay required to
|
||||
* allow the client to catch up. This value is used later,
|
||||
* after everything else related to the frame has been
|
||||
* finalized. */
|
||||
int time_since_last_frame = guac_timestamp_current() - client->last_sent_timestamp;
|
||||
int processing_lag = guac_client_get_processing_lag(client);
|
||||
int required_wait = processing_lag - time_since_last_frame;
|
||||
|
||||
/* Allow connected clients to move forward with rendering */
|
||||
guac_client_end_multiple_frames(client, display->last_frame.frames);
|
||||
|
||||
/* Commit any changed contents to client-side backing
|
||||
* buffer, while also determining whether any changes have
|
||||
* been made to the mouse cursor graphic. */
|
||||
int cursor_modified = 0;
|
||||
guac_display_layer* cursor = display->cursor_buffer;
|
||||
guac_display_layer* current = display->last_frame.layers;
|
||||
while (current != NULL) {
|
||||
|
||||
/* Save a copy of the changed region if the layer has
|
||||
* been modified since the last frame */
|
||||
guac_rect* dirty = ¤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;
|
||||
|
||||
}
|
||||
331
src/libguac/display.c
Normal file
331
src/libguac/display.c
Normal file
@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "display-plan.h"
|
||||
#include "display-priv.h"
|
||||
#include "guacamole/client.h"
|
||||
#include "guacamole/display.h"
|
||||
#include "guacamole/fifo.h"
|
||||
#include "guacamole/layer.h"
|
||||
#include "guacamole/mem.h"
|
||||
#include "guacamole/protocol.h"
|
||||
#include "guacamole/rect.h"
|
||||
#include "guacamole/rwlock.h"
|
||||
#include "guacamole/socket.h"
|
||||
#include "guacamole/timestamp.h"
|
||||
#include "guacamole/user.h"
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#include <winbase.h>
|
||||
#endif
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Returns the number of processors available to this process. If possible,
|
||||
* limits on otherwise available processors like CPU affinity will be taken
|
||||
* into account. If the number of available processors cannot be determined,
|
||||
* zero is returned.
|
||||
*
|
||||
* @return
|
||||
* The number of available processors, or zero if this value cannot be
|
||||
* determined for any reason.
|
||||
*/
|
||||
static unsigned long guac_display_nproc() {
|
||||
|
||||
#if defined(HAVE_SCHED_GETAFFINITY)
|
||||
|
||||
/* Linux, etc. implementation leveraging sched_getaffinity() (this is
|
||||
* specific to glibc and MUSL libc and is non-portable) */
|
||||
|
||||
cpu_set_t cpu_set;
|
||||
CPU_ZERO(&cpu_set);
|
||||
|
||||
if (sched_getaffinity(0, sizeof(cpu_set), &cpu_set) == 0) {
|
||||
long cpu_count = CPU_COUNT(&cpu_set);
|
||||
if (cpu_count > 0)
|
||||
return cpu_count;
|
||||
}
|
||||
|
||||
#elif defined(_SC_NPROCESSORS_ONLN)
|
||||
|
||||
/* Linux, etc. implementation leveraging sysconf() and _SC_NPROCESSORS_ONLN
|
||||
* (which is also non-portable) */
|
||||
|
||||
long cpu_count = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
if (cpu_count > 0)
|
||||
return cpu_count;
|
||||
|
||||
#elif defined(__MINGW32__)
|
||||
|
||||
/* Windows-specific implementation (clearly also non-portable) */
|
||||
|
||||
unsigned long cpu_count = 0;
|
||||
DWORD_PTR process_mask, system_mask;
|
||||
for (GetProcessAffinityMask(GetCurrentProcess(), &process_mask, &system_mask);
|
||||
process_mask != 0; process_mask >>= 1) {
|
||||
|
||||
if (process_mask & 1)
|
||||
cpu_count++;
|
||||
|
||||
}
|
||||
|
||||
if (cpu_count > 0)
|
||||
return cpu_count;
|
||||
|
||||
#else
|
||||
|
||||
/* Fallback implementation that does not query the number of CPUs available
|
||||
* at all, returning an error code (as portable as it gets) */
|
||||
|
||||
long cpu_count = 0;
|
||||
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
guac_display* guac_display_alloc(guac_client* client) {
|
||||
|
||||
/* Allocate and init core properties (really just the client pointer) */
|
||||
guac_display* display = guac_mem_zalloc(sizeof(guac_display));
|
||||
display->client = client;
|
||||
|
||||
/* Init last frame and pending frame tracking */
|
||||
guac_rwlock_init(&display->last_frame.lock);
|
||||
guac_rwlock_init(&display->pending_frame.lock);
|
||||
display->last_frame.timestamp = display->pending_frame.timestamp = guac_timestamp_current();
|
||||
|
||||
/* It's safe to discard const of the default layer here, as
|
||||
* guac_display_free_layer() function is specifically written to consider
|
||||
* the default layer as const */
|
||||
display->default_layer = guac_display_add_layer(display, (guac_layer*) GUAC_DEFAULT_LAYER, 1);
|
||||
display->cursor_buffer = guac_display_alloc_buffer(display, 0);
|
||||
|
||||
/* Init operation FIFO used by worker threads */
|
||||
guac_fifo_init(&display->ops, display->ops_items,
|
||||
GUAC_DISPLAY_WORKER_FIFO_SIZE, sizeof(guac_display_plan_operation));
|
||||
|
||||
/* Init lock specific to the GUAC_DISPLAY_PLAN_OPERATION_RECT operation */
|
||||
pthread_mutex_init(&display->op_path_lock, NULL);
|
||||
|
||||
int cpu_count = guac_display_nproc();
|
||||
if (cpu_count <= 0) {
|
||||
guac_client_log(client, GUAC_LOG_WARNING, "Number of available "
|
||||
"processors could not be determined. Assuming single-processor.");
|
||||
cpu_count = 1;
|
||||
}
|
||||
else {
|
||||
guac_client_log(client, GUAC_LOG_INFO, "Local system reports %i "
|
||||
"processor(s) are available.", cpu_count);
|
||||
}
|
||||
|
||||
display->worker_thread_count = cpu_count;
|
||||
display->worker_threads = guac_mem_alloc(display->worker_thread_count, sizeof(pthread_t));
|
||||
guac_client_log(client, GUAC_LOG_INFO, "Graphical updates will be encoded "
|
||||
"using %i worker thread(s).", display->worker_thread_count);
|
||||
|
||||
/* Now that the core of the display has been fully initialized, it's safe
|
||||
* to start the worker threads */
|
||||
for (int i = 0; i < display->worker_thread_count; i++)
|
||||
pthread_create(&(display->worker_threads[i]), NULL, guac_display_worker_thread, display);
|
||||
|
||||
return display;
|
||||
|
||||
}
|
||||
|
||||
void guac_display_free(guac_display* display) {
|
||||
|
||||
/* Stop further use of the operation FIFO */
|
||||
guac_fifo_invalidate(&display->ops);
|
||||
|
||||
/* Wait for all worker threads to terminate (they should nearly immediately
|
||||
* terminate following invalidation of the FIFO) */
|
||||
for (int i = 0; i < display->worker_thread_count; i++)
|
||||
pthread_join(display->worker_threads[i], NULL);
|
||||
|
||||
/* Free all layers within the pending_frame list (NOTE: This will also free
|
||||
* those layers from the last_frame list) */
|
||||
while (display->pending_frame.layers != NULL)
|
||||
guac_display_free_layer(display->pending_frame.layers);
|
||||
|
||||
/* Free any remaining layers that were present only on the last_frame list
|
||||
* and not on the pending_frame list */
|
||||
while (display->last_frame.layers != NULL)
|
||||
guac_display_free_layer(display->last_frame.layers);
|
||||
|
||||
pthread_mutex_destroy(&display->op_path_lock);
|
||||
guac_mem_free(display->worker_threads);
|
||||
guac_mem_free(display);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_dup(guac_display* display, guac_socket* socket) {
|
||||
|
||||
guac_client* client = display->client;
|
||||
guac_rwlock_acquire_read_lock(&display->last_frame.lock);
|
||||
|
||||
guac_display_layer* current = display->last_frame.layers;
|
||||
while (current != NULL) {
|
||||
|
||||
const guac_layer* layer = current->layer;
|
||||
|
||||
guac_rect layer_bounds;
|
||||
guac_display_layer_get_bounds(current, &layer_bounds);
|
||||
|
||||
int width = guac_rect_width(&layer_bounds);
|
||||
int height = guac_rect_height(&layer_bounds);
|
||||
|
||||
/* Get Cairo layer for specified rect */
|
||||
unsigned char* buffer = GUAC_DISPLAY_LAYER_STATE_MUTABLE_BUFFER(current->last_frame, layer_bounds);
|
||||
|
||||
cairo_surface_t* rect;
|
||||
|
||||
/* Use RGB24 if the image is fully opaque */
|
||||
if (current->opaque)
|
||||
rect = cairo_image_surface_create_for_data(buffer,
|
||||
CAIRO_FORMAT_RGB24, width, height,
|
||||
current->last_frame.buffer_stride);
|
||||
|
||||
/* Otherwise ARGB32 is needed */
|
||||
else
|
||||
rect = cairo_image_surface_create_for_data(buffer,
|
||||
CAIRO_FORMAT_ARGB32, width, height,
|
||||
current->last_frame.buffer_stride);
|
||||
|
||||
/* Send PNG for rect */
|
||||
guac_protocol_send_size(socket, layer, width, height);
|
||||
guac_client_stream_png(client, socket, GUAC_COMP_OVER, layer, 0, 0, rect);
|
||||
|
||||
/* Resync copy of previous frame */
|
||||
guac_protocol_send_copy(socket,
|
||||
layer, 0, 0, width, height,
|
||||
GUAC_COMP_SRC, current->last_frame_buffer, 0, 0);
|
||||
|
||||
/* Resync any properties that are specific to non-buffer layers */
|
||||
if (current->layer->index > 0) {
|
||||
|
||||
/* Resync layer opacity */
|
||||
guac_protocol_send_shade(socket, current->layer,
|
||||
current->last_frame.opacity);
|
||||
|
||||
/* Resync layer position/hierarchy */
|
||||
guac_protocol_send_move(socket, current->layer,
|
||||
current->last_frame.parent,
|
||||
current->last_frame.x,
|
||||
current->last_frame.y,
|
||||
current->last_frame.z);
|
||||
|
||||
}
|
||||
|
||||
/* Resync multitouch support */
|
||||
if (current->layer->index >= 0) {
|
||||
guac_protocol_send_set_int(socket, current->layer,
|
||||
GUAC_PROTOCOL_LAYER_PARAMETER_MULTI_TOUCH,
|
||||
current->last_frame.touches);
|
||||
}
|
||||
|
||||
current = current->last_frame.next;
|
||||
|
||||
}
|
||||
|
||||
/* Synchronize mouse cursor */
|
||||
guac_display_layer* cursor = display->cursor_buffer;
|
||||
guac_protocol_send_cursor(socket,
|
||||
display->last_frame.cursor_hotspot_x,
|
||||
display->last_frame.cursor_hotspot_y,
|
||||
cursor->layer, 0, 0,
|
||||
cursor->last_frame.width,
|
||||
cursor->last_frame.height);
|
||||
|
||||
/* Synchronize mouse location */
|
||||
guac_protocol_send_mouse(socket, display->last_frame.cursor_x, display->last_frame.cursor_y,
|
||||
display->last_frame.cursor_mask, client->last_sent_timestamp);
|
||||
|
||||
guac_protocol_send_sync(socket, client->last_sent_timestamp, display->last_frame.frames);
|
||||
guac_rwlock_release_lock(&display->last_frame.lock);
|
||||
|
||||
guac_socket_flush(socket);
|
||||
|
||||
}
|
||||
|
||||
void guac_display_notify_user_left(guac_display* display, guac_user* user) {
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
|
||||
/* Update to reflect leaving user, if necessary */
|
||||
if (display->pending_frame.cursor_user == user)
|
||||
display->pending_frame.cursor_user = NULL;
|
||||
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
}
|
||||
|
||||
void guac_display_notify_user_moved_mouse(guac_display* display, guac_user* user, int x, int y, int mask) {
|
||||
|
||||
guac_rwlock_acquire_write_lock(&display->pending_frame.lock);
|
||||
display->pending_frame.cursor_user = user;
|
||||
display->pending_frame.cursor_x = x;
|
||||
display->pending_frame.cursor_y = y;
|
||||
display->pending_frame.cursor_mask = mask;
|
||||
display->pending_dirty = 1;
|
||||
guac_rwlock_release_lock(&display->pending_frame.lock);
|
||||
|
||||
guac_display_end_multiple_frames(display, 0);
|
||||
|
||||
}
|
||||
|
||||
guac_display_layer* guac_display_default_layer(guac_display* display) {
|
||||
return display->default_layer;
|
||||
}
|
||||
|
||||
guac_display_layer* guac_display_alloc_layer(guac_display* display, int opaque) {
|
||||
return guac_display_add_layer(display, guac_client_alloc_layer(display->client), opaque);
|
||||
}
|
||||
|
||||
guac_display_layer* guac_display_alloc_buffer(guac_display* display, int opaque) {
|
||||
return guac_display_add_layer(display, guac_client_alloc_buffer(display->client), opaque);
|
||||
}
|
||||
|
||||
void guac_display_free_layer(guac_display_layer* display_layer) {
|
||||
|
||||
guac_display* display = display_layer->display;
|
||||
const guac_layer* layer = display_layer->layer;
|
||||
|
||||
guac_display_remove_layer(display_layer);
|
||||
|
||||
if (layer->index != 0) {
|
||||
|
||||
guac_client* client = display->client;
|
||||
guac_protocol_send_dispose(client->socket, layer);
|
||||
|
||||
/* As long as this isn't the display layer, it's safe to cast away the
|
||||
* constness and free the underlying layer/buffer. Only the default
|
||||
* layer (layer #0) is truly const. */
|
||||
if (layer->index > 0)
|
||||
guac_client_free_layer(client, (guac_layer*) layer);
|
||||
else
|
||||
guac_client_free_buffer(client, (guac_layer*) layer);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
210
src/libguac/fifo.c
Normal file
210
src/libguac/fifo.c
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "guacamole/fifo.h"
|
||||
#include "guacamole/flag.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void guac_fifo_init(guac_fifo* fifo, void* items,
|
||||
size_t max_items, size_t item_size) {
|
||||
|
||||
/* Init values describing the memory structure of the items array */
|
||||
fifo->items_offset = (char*) items - (char*) fifo;
|
||||
fifo->max_items = max_items;
|
||||
fifo->item_size = item_size;
|
||||
|
||||
/* The fifo is currently empty */
|
||||
guac_flag_init(&fifo->state);
|
||||
guac_flag_set(&fifo->state, GUAC_FIFO_STATE_READY);
|
||||
fifo->head = 0;
|
||||
fifo->item_count = 0;
|
||||
|
||||
}
|
||||
|
||||
void guac_fifo_destroy(guac_fifo* fifo) {
|
||||
guac_flag_destroy(&fifo->state);
|
||||
}
|
||||
|
||||
void guac_fifo_invalidate(guac_fifo* fifo) {
|
||||
guac_flag_set(&fifo->state, GUAC_FIFO_STATE_INVALID);
|
||||
}
|
||||
|
||||
void guac_fifo_lock(guac_fifo* fifo) {
|
||||
guac_flag_lock(&fifo->state);
|
||||
}
|
||||
|
||||
void guac_fifo_unlock(guac_fifo* fifo) {
|
||||
guac_flag_unlock(&fifo->state);
|
||||
}
|
||||
|
||||
int guac_fifo_is_valid(guac_fifo* fifo) {
|
||||
/* We don't need to acquire the lock here as (1) we are only reading the
|
||||
* flag and (2) the flag in question is a one-way, single-use signal (it's
|
||||
* only set, never cleared) */
|
||||
return !(fifo->state.value & GUAC_FIFO_STATE_INVALID);
|
||||
}
|
||||
|
||||
int guac_fifo_enqueue(guac_fifo* fifo,
|
||||
const void* item) {
|
||||
|
||||
if (!guac_fifo_enqueue_and_lock(fifo, item))
|
||||
return 0;
|
||||
|
||||
guac_flag_unlock(&fifo->state);
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
int guac_fifo_enqueue_and_lock(guac_fifo* fifo,
|
||||
const void* item) {
|
||||
|
||||
/* Block until fifo is ready for further items OR until the fifo is
|
||||
* invalidated */
|
||||
guac_flag_wait_and_lock(&fifo->state,
|
||||
GUAC_FIFO_STATE_INVALID | GUAC_FIFO_STATE_READY);
|
||||
|
||||
/* Bail out if the fifo has become invalid */
|
||||
if (fifo->state.value & GUAC_FIFO_STATE_INVALID) {
|
||||
guac_flag_unlock(&fifo->state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Abort program execution entirely if the fifo reports readiness but
|
||||
* somehow actually does not have available space (this should never happen
|
||||
* and indicates a bug) */
|
||||
if (fifo->item_count >= fifo->max_items)
|
||||
abort();
|
||||
|
||||
/* Update count of items within the fifo, clearing the readiness flag if
|
||||
* there is no longer any space for further items */
|
||||
fifo->item_count++;
|
||||
if (fifo->item_count == fifo->max_items)
|
||||
guac_flag_clear(&fifo->state, GUAC_FIFO_STATE_READY);
|
||||
|
||||
/* NOTE: At this point, there are `item_count - 1` items present in the
|
||||
* fifo, and `item_count - 1` is the index of the space in the items array
|
||||
* that should receive the item being added (relative to head) */
|
||||
|
||||
/* Copy data of item buffer into last item in fifo */
|
||||
size_t tail = (fifo->head + fifo->item_count - 1) % fifo->max_items;
|
||||
void* tail_item = ((char*) fifo) + fifo->items_offset + fifo->item_size * tail;
|
||||
memcpy(tail_item, item, fifo->item_size);
|
||||
|
||||
/* Advise any waiting threads that the fifo is now non-empty */
|
||||
guac_flag_set(&fifo->state, GUAC_FIFO_STATE_NONEMPTY);
|
||||
|
||||
/* Item enqueued successfully */
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Dequeues a single item from the given guac_fifo, storing a copy
|
||||
* of that item in the provided buffer. The event fifo MUST be non-empty. The
|
||||
* state flag of the fifo MUST already be locked.
|
||||
*
|
||||
* @param fifo
|
||||
* The guac_fifo to dequeue an item from.
|
||||
*
|
||||
* @param item
|
||||
* The buffer that should receive a copy of the dequeued item.
|
||||
*/
|
||||
static void dequeue(guac_fifo* fifo, void* item) {
|
||||
|
||||
/* Copy data of first item in fifo to provided output buffer */
|
||||
void* head_item = ((char*) fifo) + fifo->items_offset + fifo->item_size * fifo->head;
|
||||
memcpy(item, head_item, fifo->item_size);
|
||||
|
||||
/* Advance to next item in fifo, if any */
|
||||
fifo->item_count--;
|
||||
fifo->head = (fifo->head + 1) % fifo->max_items;
|
||||
|
||||
/* Keep state flag up-to-date with respect to non-emptiness ... */
|
||||
if (fifo->item_count == 0)
|
||||
guac_flag_clear(&fifo->state, GUAC_FIFO_STATE_NONEMPTY);
|
||||
|
||||
/* ... and readiness for further items */
|
||||
guac_flag_set(&fifo->state, GUAC_FIFO_STATE_READY);
|
||||
|
||||
/* Item has been dequeued successfully */
|
||||
|
||||
}
|
||||
|
||||
int guac_fifo_dequeue(guac_fifo* fifo, void* item) {
|
||||
|
||||
if (!guac_fifo_dequeue_and_lock(fifo, item))
|
||||
return 0;
|
||||
|
||||
guac_flag_unlock(&fifo->state);
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
int guac_fifo_timed_dequeue(guac_fifo* fifo,
|
||||
void* item, int msec_timeout) {
|
||||
|
||||
if (!guac_fifo_timed_dequeue_and_lock(fifo, item, msec_timeout))
|
||||
return 0;
|
||||
|
||||
guac_flag_unlock(&fifo->state);
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
int guac_fifo_dequeue_and_lock(guac_fifo* fifo, void* item) {
|
||||
|
||||
/* Block indefinitely while waiting for an item to be added, but bail out
|
||||
* if the fifo becomes invalid */
|
||||
guac_flag_wait_and_lock(&fifo->state,
|
||||
GUAC_FIFO_STATE_NONEMPTY | GUAC_FIFO_STATE_INVALID);
|
||||
|
||||
if (fifo->state.value & GUAC_FIFO_STATE_INVALID) {
|
||||
guac_flag_unlock(&fifo->state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dequeue(fifo, item);
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
int guac_fifo_timed_dequeue_and_lock(guac_fifo* fifo,
|
||||
void* item, int msec_timeout) {
|
||||
|
||||
/* Wait up to timeout for an item to be present in the fifo, failing if no
|
||||
* items enter the fifo before the timeout lapses */
|
||||
if (!guac_flag_timedwait_and_lock(&fifo->state,
|
||||
GUAC_FIFO_STATE_NONEMPTY | GUAC_FIFO_STATE_INVALID,
|
||||
msec_timeout)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fifo->state.value & GUAC_FIFO_STATE_INVALID) {
|
||||
guac_flag_unlock(&fifo->state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dequeue(fifo, item);
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
167
src/libguac/flag.c
Normal file
167
src/libguac/flag.c
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "guacamole/flag.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* The number of nanoseconds in a whole second.
|
||||
*/
|
||||
#define NANOS_PER_SECOND 1000000000L
|
||||
|
||||
void guac_flag_init(guac_flag* event_flag) {
|
||||
|
||||
/* The condition used by guac_flag to signal changes in its
|
||||
* value must be safe to share between processes, and must use the
|
||||
* system-wide monotonic clock (not the realtime clock, which is subject to
|
||||
* time changes) */
|
||||
pthread_condattr_t cond_attr;
|
||||
pthread_condattr_init(&cond_attr);
|
||||
pthread_condattr_setclock(&cond_attr, CLOCK_MONOTONIC);
|
||||
pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
|
||||
pthread_cond_init(&event_flag->value_changed, &cond_attr);
|
||||
|
||||
/* In addition to being safe to share between processes, the mutex used by
|
||||
* guac_flag to guard concurrent access to its value (AND to
|
||||
* signal changes in its value) must be recursive (you can lock the mutex
|
||||
* again even if the current thread has already locked it) */
|
||||
pthread_mutexattr_t mutex_attr;
|
||||
pthread_mutexattr_init(&mutex_attr);
|
||||
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
|
||||
pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&event_flag->value_mutex, &mutex_attr);
|
||||
|
||||
/* The initial value of all flags is unset (0) */
|
||||
event_flag->value = 0;
|
||||
|
||||
}
|
||||
|
||||
void guac_flag_destroy(guac_flag* event_flag) {
|
||||
pthread_cond_destroy(&event_flag->value_changed);
|
||||
pthread_mutex_destroy(&event_flag->value_mutex);
|
||||
}
|
||||
|
||||
void guac_flag_set_and_lock(guac_flag* event_flag,
|
||||
unsigned int flags) {
|
||||
|
||||
guac_flag_lock(event_flag);
|
||||
|
||||
/* Set specific bits of flag, leaving other bits unaffected */
|
||||
unsigned int old_value = event_flag->value;
|
||||
event_flag->value |= flags;
|
||||
|
||||
/* Signal other threads only if flag has changed as a result of this call */
|
||||
if (event_flag->value != old_value)
|
||||
pthread_cond_broadcast(&event_flag->value_changed);
|
||||
|
||||
}
|
||||
|
||||
void guac_flag_set(guac_flag* event_flag,
|
||||
unsigned int flags) {
|
||||
guac_flag_set_and_lock(event_flag, flags);
|
||||
guac_flag_unlock(event_flag);
|
||||
}
|
||||
|
||||
void guac_flag_clear_and_lock(guac_flag* event_flag,
|
||||
unsigned int flags) {
|
||||
|
||||
guac_flag_lock(event_flag);
|
||||
|
||||
/* Clear specific bits of flag, leaving other bits unaffected */
|
||||
event_flag->value &= ~flags;
|
||||
|
||||
/* NOTE: Other threads are NOT signalled here. Threads wait only for flags
|
||||
* to be set, not for flags to be cleared. */
|
||||
|
||||
}
|
||||
|
||||
void guac_flag_clear(guac_flag* event_flag,
|
||||
unsigned int flags) {
|
||||
guac_flag_clear_and_lock(event_flag, flags);
|
||||
guac_flag_unlock(event_flag);
|
||||
}
|
||||
|
||||
void guac_flag_lock(guac_flag* event_flag) {
|
||||
pthread_mutex_lock(&event_flag->value_mutex);
|
||||
}
|
||||
|
||||
void guac_flag_unlock(guac_flag* event_flag) {
|
||||
pthread_mutex_unlock(&event_flag->value_mutex);
|
||||
}
|
||||
|
||||
void guac_flag_wait_and_lock(guac_flag* event_flag,
|
||||
unsigned int flags) {
|
||||
|
||||
guac_flag_lock(event_flag);
|
||||
|
||||
/* Continue waiting until at least one of the desired flags has been set */
|
||||
while (!(event_flag->value & flags)) {
|
||||
|
||||
/* Wait for any change to any flags, bailing out if something is wrong
|
||||
* that would prevent waiting from ever succeeding (such a failure
|
||||
* would turn this into a busy loop) */
|
||||
if (pthread_cond_wait(&event_flag->value_changed,
|
||||
&event_flag->value_mutex)) {
|
||||
abort(); /* This should not happen except due to a bug */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* If we reach this point, at least one of the desired flags has been set,
|
||||
* and it is intentional that we continue to hold the lock (acquired on
|
||||
* behalf of the caller) */
|
||||
|
||||
}
|
||||
|
||||
int guac_flag_timedwait_and_lock(guac_flag* event_flag,
|
||||
unsigned int flags, unsigned int msec_timeout) {
|
||||
|
||||
guac_flag_lock(event_flag);
|
||||
|
||||
struct timespec ts_timeout;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts_timeout);
|
||||
|
||||
uint64_t nsec_timeout = msec_timeout * 1000000 + ts_timeout.tv_nsec;
|
||||
ts_timeout.tv_sec += nsec_timeout / NANOS_PER_SECOND;
|
||||
ts_timeout.tv_nsec = nsec_timeout % NANOS_PER_SECOND;
|
||||
|
||||
/* Continue waiting until at least one of the desired flags has been set */
|
||||
while (!(event_flag->value & flags)) {
|
||||
|
||||
/* Wait for any change to any flags, failing if a timeout occurs */
|
||||
if (pthread_cond_timedwait(&event_flag->value_changed,
|
||||
&event_flag->value_mutex, &ts_timeout)) {
|
||||
guac_flag_unlock(event_flag);
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* If we reach this point, at least one of the desired flags has been set,
|
||||
* and it is intentional that we continue to hold the lock (acquired on
|
||||
* behalf of the caller) */
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
47
src/libguac/guacamole/assert.h
Normal file
47
src/libguac/guacamole/assert.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_ASSERT_H
|
||||
#define GUAC_ASSERT_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Performs a runtime assertion that verifies the given condition evaluates to
|
||||
* true (non-zero). If the condition evaluates to false (zero), execution is
|
||||
* aborted with abort().
|
||||
*
|
||||
* This macro should be used only in cases where the performance impact of
|
||||
* verifying the assertion is negligible and it is benificial to always verify
|
||||
* the assertion. Unlike the standard assert(), this macro will never be
|
||||
* omitted by the compiler.
|
||||
*
|
||||
* @param expression
|
||||
* The condition to test.
|
||||
*/
|
||||
#define GUAC_ASSERT(expression) do { \
|
||||
if (!(expression)) { \
|
||||
fprintf(stderr, "GUAC_ASSERT in %s() failed at %s:%i.\n", \
|
||||
__func__, __FILE__, __LINE__); \
|
||||
abort(); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#endif
|
||||
47
src/libguac/guacamole/display-constants.h
Normal file
47
src/libguac/guacamole/display-constants.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_DISPLAY_CONSTANTS_H
|
||||
#define GUAC_DISPLAY_CONSTANTS_H
|
||||
|
||||
/**
|
||||
* Provides constants related to the abstract display implementation
|
||||
* (guac_display).
|
||||
*
|
||||
* @file display-constants.h
|
||||
*/
|
||||
|
||||
/**
|
||||
* The maximum width of any guac_display, in pixels.
|
||||
*/
|
||||
#define GUAC_DISPLAY_MAX_WIDTH 8192
|
||||
|
||||
/**
|
||||
* The maximum height of any guac_display, in pixels.
|
||||
*/
|
||||
#define GUAC_DISPLAY_MAX_HEIGHT 8192
|
||||
|
||||
/**
|
||||
* The number of bytes in each pixel of raw image data within a
|
||||
* guac_display_layer, as made accessible through a call to
|
||||
* guac_display_layer_open_raw().
|
||||
*/
|
||||
#define GUAC_DISPLAY_LAYER_RAW_BPP 4
|
||||
|
||||
#endif
|
||||
91
src/libguac/guacamole/display-types.h
Normal file
91
src/libguac/guacamole/display-types.h
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_DISPLAY_TYPES_H
|
||||
#define GUAC_DISPLAY_TYPES_H
|
||||
|
||||
/**
|
||||
* Provides type definitions related to the abstract display implementation
|
||||
* (guac_display).
|
||||
*
|
||||
* @file display-types.h
|
||||
*/
|
||||
|
||||
/**
|
||||
* Opaque representation of the remote (client-side) display of a Guacamole
|
||||
* connection (guac_client).
|
||||
*/
|
||||
typedef struct guac_display guac_display;
|
||||
|
||||
/**
|
||||
* Opaque representation of a layer within a guac_display. This may be a
|
||||
* visible layer or an off-screen buffer, and is effectively the guac_display
|
||||
* equivalent of a guac_layer.
|
||||
*/
|
||||
typedef struct guac_display_layer guac_display_layer;
|
||||
|
||||
/**
|
||||
* The current Cairo drawing context of a guac_display_layer, including a Cairo
|
||||
* image surface wrapping the underlying drawing buffer of the
|
||||
* guac_display_layer. After making graphical changes, the dirty rectangle of
|
||||
* this context must be updated such that it includes the regions modified by
|
||||
* those changes.
|
||||
*/
|
||||
typedef struct guac_display_layer_cairo_context guac_display_layer_cairo_context;
|
||||
|
||||
/**
|
||||
* The current raw drawing context of a guac_display_layer, including the
|
||||
* underlying drawing buffer of the guac_display_layer and memory layout
|
||||
* information. After making graphical changes, the dirty rectangle of this
|
||||
* context must be updated such that it includes the regions modified by those
|
||||
* changes.
|
||||
*/
|
||||
typedef struct guac_display_layer_raw_context guac_display_layer_raw_context;
|
||||
|
||||
/**
|
||||
* Pre-defined mouse cursor graphics.
|
||||
*/
|
||||
typedef enum guac_display_cursor_type {
|
||||
|
||||
/**
|
||||
* An empty (invisible/hidden) mouse cursor.
|
||||
*/
|
||||
GUAC_DISPLAY_CURSOR_NONE,
|
||||
|
||||
/**
|
||||
* A small dot. This is typically used in situations where cursor
|
||||
* information for the remote desktop is not available, thus all cursor
|
||||
* rendering must happen remotely, but it's still important that the user
|
||||
* be able to see the current location of their local mouse pointer.
|
||||
*/
|
||||
GUAC_DISPLAY_CURSOR_DOT,
|
||||
|
||||
/**
|
||||
* A vertical, I-shaped bar indicating text input or selection.
|
||||
*/
|
||||
GUAC_DISPLAY_CURSOR_IBAR,
|
||||
|
||||
/**
|
||||
* A standard, general-purpose pointer.
|
||||
*/
|
||||
GUAC_DISPLAY_CURSOR_POINTER
|
||||
|
||||
} guac_display_cursor_type;
|
||||
|
||||
#endif
|
||||
616
src/libguac/guacamole/display.h
Normal file
616
src/libguac/guacamole/display.h
Normal file
@ -0,0 +1,616 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_DISPLAY_H
|
||||
#define GUAC_DISPLAY_H
|
||||
|
||||
/**
|
||||
* Provides an abstract display implementation (guac_display), which handles
|
||||
* optimization automatically. Current optimizations include:
|
||||
*
|
||||
* - Scroll/copy detection
|
||||
* - Solid color detection
|
||||
* - Dirty rectangle reduction
|
||||
* - Dynamic selection of PNG/JPEG/WebP compression depending on update content
|
||||
* and frequency
|
||||
* - Combining/rewriting of updates based on estimated cost
|
||||
*
|
||||
* @file display.h
|
||||
*/
|
||||
|
||||
#include "client.h"
|
||||
#include "display-constants.h"
|
||||
#include "display-types.h"
|
||||
#include "rect.h"
|
||||
#include "socket.h"
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Returns the memory address of the given rectangle within the image buffer of
|
||||
* the given guac_display_layer_raw_context, where the upper-left corner of the
|
||||
* given buffer is (0, 0). If the memory address cannot be calculated because
|
||||
* doing so would overflow the maximum value of a size_t, execution of the
|
||||
* current process is automatically aborted.
|
||||
*
|
||||
* IMPORTANT: No checks are performed on whether the rectangle extends beyond
|
||||
* the bounds of the buffer, including considering whether the left/top
|
||||
* position of the rectangle is negative. If the rectangle has not already been
|
||||
* contrained to be within the bounds of the buffer, such checks must be
|
||||
* performed before dereferencing the value returned by this macro.
|
||||
*
|
||||
* @param context
|
||||
* The guac_display_layer_raw_context associated with the image buffer
|
||||
* within which the address of the given rectangle should be determined.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to determine the offset of.
|
||||
*/
|
||||
#define GUAC_DISPLAY_LAYER_RAW_BUFFER(context, rect) \
|
||||
GUAC_RECT_MUTABLE_BUFFER(rect, context->buffer, context->stride, GUAC_DISPLAY_LAYER_RAW_BPP)
|
||||
|
||||
struct guac_display_layer_cairo_context {
|
||||
|
||||
/**
|
||||
* A Cairo context created for the Cairo surface. This Cairo context is
|
||||
* persistent and will maintain its state between different calls to
|
||||
* guac_display_layer_open_cairo() for the same layer.
|
||||
*/
|
||||
cairo_t* cairo;
|
||||
|
||||
/**
|
||||
* A Cairo image surface wrapping the image buffer of this
|
||||
* guac_display_layer.
|
||||
*/
|
||||
cairo_surface_t* surface;
|
||||
|
||||
/**
|
||||
* A rectangle covering the current bounds of the graphical surface.
|
||||
*/
|
||||
guac_rect bounds;
|
||||
|
||||
/**
|
||||
* A rectangle covering the region of the guac_display_layer that has
|
||||
* changed since the last frame. This rectangle must be manually updated to
|
||||
* cover any additional changed regions before closing the
|
||||
* guac_display_layer_cairo_context.
|
||||
*/
|
||||
guac_rect dirty;
|
||||
|
||||
};
|
||||
|
||||
struct guac_display_layer_raw_context {
|
||||
|
||||
/**
|
||||
* The raw, underlying image buffer of the guac_display_layer. If the layer
|
||||
* was created as opaque, this image is 32-bit RGB with 8 bits per color
|
||||
* component, where the lowest-order byte is the blue component and the
|
||||
* highest-order byte is ignored. If the layer was not created as opaque,
|
||||
* this image is 32-bit ARGB with 8 bits per color component, where the
|
||||
* lowest-order byte is the blue component and the highest-order byte is
|
||||
* alpha.
|
||||
*/
|
||||
unsigned char* buffer;
|
||||
|
||||
/**
|
||||
* The number of bytes in each row of image data. This value is not
|
||||
* necessarily the same as the width of the image multiplied by the size of
|
||||
* each pixel. Additional space may be allocated to allow for memory
|
||||
* alignment or to make future resize operations more efficient.
|
||||
*/
|
||||
size_t stride;
|
||||
|
||||
/**
|
||||
* A rectangle covering the current bounds of the graphical surface. The
|
||||
* buffer must not be addressed outside these bounds.
|
||||
*/
|
||||
guac_rect bounds;
|
||||
|
||||
/**
|
||||
* A rectangle covering the region of the guac_display_layer that has
|
||||
* changed since the last frame. This rectangle must be manually updated to
|
||||
* cover any additional changed regions before closing the
|
||||
* guac_display_layer_raw_context.
|
||||
*/
|
||||
guac_rect dirty;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocates a new guac_display representing the remote display shared by all
|
||||
* connected users of the given guac_client. The dimensions of the display
|
||||
* should be set with guac_display_defaulta_layer() and
|
||||
* guac_display_layer_resize() once the desired display size is known. The
|
||||
* guac_display must eventually be freed through a call to guac_display_free().
|
||||
*
|
||||
* @param client
|
||||
* The guac_client whose remote display should be represented by the new
|
||||
* guac_display.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated guac_display representing the remote display of the
|
||||
* given guac_client.
|
||||
*/
|
||||
guac_display* guac_display_alloc(guac_client* client);
|
||||
|
||||
/**
|
||||
* Frees all resources associated with the given guac_display.
|
||||
*
|
||||
* @param display
|
||||
* The guac_display to free.
|
||||
*/
|
||||
void guac_display_free(guac_display* display);
|
||||
|
||||
/**
|
||||
* Replicates the current remote display state across the given socket. When
|
||||
* new users join a particular guac_client, this function should be used to
|
||||
* synchronize those users with the current display state.
|
||||
*
|
||||
* @param display
|
||||
* The display that should be synchronized to all users at the other end of
|
||||
* the given guac_socket.
|
||||
*
|
||||
* @param socket
|
||||
* The socket to send the current remote display state over.
|
||||
*/
|
||||
void guac_display_dup(guac_display* display, guac_socket* socket);
|
||||
|
||||
/**
|
||||
* Notifies the given guac_display that a specific user has left the connection
|
||||
* and need no longer be considered for future updates/events. This SHOULD
|
||||
* always be called when a user leaves the connection to ensure other future,
|
||||
* user-related events are interpreted correctly.
|
||||
*
|
||||
* @param display
|
||||
* The guac_display to notify.
|
||||
*
|
||||
* @param user
|
||||
* The user that left the connection.
|
||||
*/
|
||||
void guac_display_notify_user_left(guac_display* display, guac_user* user);
|
||||
|
||||
/**
|
||||
* Notifies the given guac_display that a specific user has changed the state
|
||||
* of the mouse, such as through moving the pointer or pressing/releasing a
|
||||
* mouse button.
|
||||
*
|
||||
* @param display
|
||||
* The guac_display to notify.
|
||||
*
|
||||
* @param user
|
||||
* The user that moved the mouse or pressed/released a mouse button.
|
||||
*
|
||||
* @param x
|
||||
* The X position of the mouse, in pixels.
|
||||
*
|
||||
* @param y
|
||||
* The Y position of the mouse, in pixels.
|
||||
*
|
||||
* @param mask
|
||||
* An integer value representing the current state of each button, where
|
||||
* the Nth bit within the integer is set to 1 if and only if the Nth mouse
|
||||
* button is currently pressed. The lowest-order bit is the left mouse
|
||||
* button, followed by the middle button, right button, and finally the up
|
||||
* and down buttons of the scroll wheel.
|
||||
*
|
||||
* @see GUAC_CLIENT_MOUSE_LEFT
|
||||
* @see GUAC_CLIENT_MOUSE_MIDDLE
|
||||
* @see GUAC_CLIENT_MOUSE_RIGHT
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_UP
|
||||
* @see GUAC_CLIENT_MOUSE_SCROLL_DOWN
|
||||
*/
|
||||
void guac_display_notify_user_moved_mouse(guac_display* display, guac_user* user, int x, int y, int mask);
|
||||
|
||||
/**
|
||||
* Ends the current frame, where the number of input frames that were
|
||||
* considered in creating this frame is either unknown or inapplicable,
|
||||
* allowing the guac_display to complete sending the frame to connected
|
||||
* clients.
|
||||
*
|
||||
* @param display
|
||||
* The guac_display that should send the current frame.
|
||||
*/
|
||||
void guac_display_end_frame(guac_display* display);
|
||||
|
||||
/**
|
||||
* Ends the current frame, where that frame may combine or otherwise represent the
|
||||
* changes of an arbitrary number of input frames, allowing the guac_display to
|
||||
* complete sending the frame to connected clients.
|
||||
*
|
||||
* @param display
|
||||
* The guac_display that should send the current frame.
|
||||
*
|
||||
* @param
|
||||
* The number of distinct frames that were considered or combined when
|
||||
* generating the current frame, or zero if the boundaries of relevant
|
||||
* frames are unknown.
|
||||
*/
|
||||
void guac_display_end_multiple_frames(guac_display* display, int frames);
|
||||
|
||||
/**
|
||||
* Returns the default layer for the given display. The default layer is the
|
||||
* only layer that always exists and serves as the root-level layer for all
|
||||
* other layers.
|
||||
*
|
||||
* @see GUAC_DEFAULT_LAYER
|
||||
*
|
||||
* @param display
|
||||
* The guac_display to return the default layer from.
|
||||
*
|
||||
* @return
|
||||
* A guac_display_layer representing the default layer for the given
|
||||
* guac_display.
|
||||
*/
|
||||
guac_display_layer* guac_display_default_layer(guac_display* display);
|
||||
|
||||
/**
|
||||
* Allocates a new layer for the given display. The new layer will initially be
|
||||
* a direct child of the display's default layer. When the layer is no longer
|
||||
* needed, it may be freed through calling guac_display_free_layer(). If not
|
||||
* freed manually through a call to guac_display_free_layer(), it will be freed
|
||||
* when the display is freed with guac_display_free().
|
||||
*
|
||||
* @param display
|
||||
* The guac_display to allocate a new layer for.
|
||||
*
|
||||
* @param opaque
|
||||
* Non-zero if the new layer will only ever contain opaque image contents
|
||||
* (the alpha channel should be ignored), zero otherwise.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated guac_display_layer that is initially a direct child of
|
||||
* the default layer.
|
||||
*/
|
||||
guac_display_layer* guac_display_alloc_layer(guac_display* display, int opaque);
|
||||
|
||||
/**
|
||||
* Allocates a new buffer (offscreen layer) for the given display. When the
|
||||
* buffer is no longer needed, it may be freed through calling
|
||||
* guac_display_free_layer(). If not freed manually through a call to
|
||||
* guac_display_free_layer(), it will be freed when the display is freed with
|
||||
* guac_display_free().
|
||||
*
|
||||
* @param display
|
||||
* The guac_display to allocate a new buffer for.
|
||||
*
|
||||
* @param opaque
|
||||
* Non-zero if the new buffer will only ever contain opaque image contents
|
||||
* (the alpha channel should be ignored), zero otherwise.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated guac_display_layer representing the new buffer.
|
||||
*/
|
||||
guac_display_layer* guac_display_alloc_buffer(guac_display* display, int opaque);
|
||||
|
||||
/**
|
||||
* Frees the given layer, releasing any underlying memory. If the layer has
|
||||
* already been used for rendering, it will be freed on the remote side, as
|
||||
* well, when the current pending frame is complete.
|
||||
*
|
||||
* @param display_layer
|
||||
* The layer to free.
|
||||
*/
|
||||
void guac_display_free_layer(guac_display_layer* display_layer);
|
||||
|
||||
/**
|
||||
* Returns a layer representing the current mouse cursor icon. Changes to the
|
||||
* contents of this layer will affect the remote mouse cursor after the current
|
||||
* pending frame is complete.
|
||||
*
|
||||
* @param display
|
||||
* The guac_display to return the cursor layer for.
|
||||
*
|
||||
* @return
|
||||
* A guac_display_layer representing the mouse cursor of the given
|
||||
* guac_display.
|
||||
*/
|
||||
guac_display_layer* guac_display_cursor(guac_display* display);
|
||||
|
||||
/**
|
||||
* Sets the remote mouse cursor to the given built-in cursor icon. Changes to
|
||||
* the remote mouse cursor will take effect after the current pending frame is
|
||||
* complete.
|
||||
*
|
||||
* @param display
|
||||
* The guac_display to set the cursor of.
|
||||
*
|
||||
* @param cursor_type
|
||||
* The built-in cursor icon to set the remote cursor to.
|
||||
*/
|
||||
void guac_display_set_cursor(guac_display* display,
|
||||
guac_display_cursor_type cursor_type);
|
||||
|
||||
/**
|
||||
* Sets the hotspot location of the remote mouse cursor. The hotspot is the
|
||||
* point within the mouse cursor where the click occurs. Changes to the hotspot
|
||||
* of the remote mouse cursor will take effect after the current pending frame
|
||||
* is complete.
|
||||
*
|
||||
* @param display
|
||||
* The guac_display to set the cursor hotspot of.
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the cursor hotspot, in pixels.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the cursor hotspot, in pixels.
|
||||
*/
|
||||
void guac_display_set_cursor_hotspot(guac_display* display, int x, int y);
|
||||
|
||||
/**
|
||||
* Stores the current bounding rectangle of the given layer in the given
|
||||
* guac_rect. The boundary stored will be the boundary of the current pending
|
||||
* frame.
|
||||
*
|
||||
* @oaram layer
|
||||
* The layer to determine the dimensions of.
|
||||
*
|
||||
* @param bounds
|
||||
* The guac_rect that should receive the bounding rectangle of the given
|
||||
* layer.
|
||||
*/
|
||||
void guac_display_layer_get_bounds(guac_display_layer* layer, guac_rect* bounds);
|
||||
|
||||
/**
|
||||
* Moves the given layer to the given coordinates. The changes to the given
|
||||
* layer will be made as part of the current pending frame, and will not take
|
||||
* effect on remote displays until the pending frame is complete.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to set the position of.
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the upper-left corner of the layer, in pixels.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the upper-left corner of the layer, in pixels.
|
||||
*/
|
||||
void guac_display_layer_move(guac_display_layer* layer, int x, int y);
|
||||
|
||||
/**
|
||||
* Sets the stacking position of the given layer relative to all other sibling
|
||||
* layers (direct children of the same parent). The change in relative layer
|
||||
* stacking position will be made as part of the current pending frame, and
|
||||
* will not take effect on remote displays until the pending frame is complete.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to set the stacking position of.
|
||||
*
|
||||
* #param z
|
||||
* The relative order of this layer.
|
||||
*/
|
||||
void guac_display_layer_stack(guac_display_layer* layer, int z);
|
||||
|
||||
/**
|
||||
* Reparents the given layer such that it is a direct child of the given parent
|
||||
* layer. The change in layer hierarchy will be made as part of the current
|
||||
* pending frame, and will not take effect on remote displays until the pending
|
||||
* frame is complete.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to change the parent of.
|
||||
*
|
||||
* @param parent
|
||||
* The layer that should be the new parent.
|
||||
*/
|
||||
void guac_display_layer_set_parent(guac_display_layer* layer, const guac_display_layer* parent);
|
||||
|
||||
/**
|
||||
* Sets the opacity of the given layer. The change in layer opacity will be
|
||||
* made as part of the current pending frame, and will not take effect on
|
||||
* remote displays until the pending frame is complete.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to change the opacity of.
|
||||
*
|
||||
* @param opacity
|
||||
* The opacity to assign to the given layer, as a value between 0 and 255
|
||||
* inclusive, where 0 is completely transparent and 255 is completely
|
||||
* opaque.
|
||||
*/
|
||||
void guac_display_layer_set_opacity(guac_display_layer* layer, int opacity);
|
||||
|
||||
/**
|
||||
* Sets whether graphical changes to the given layer are allowed to be
|
||||
* represented, updated, or sent using methods that can cause some loss of
|
||||
* information, such as JPEG or WebP compression. By default, layers are
|
||||
* allowed to use lossy methods. Changes to lossy vs. lossless behavior will
|
||||
* affect the current pending frame, as well as any frames that follow.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to change the lossy behavior of.
|
||||
*
|
||||
* @param lossless
|
||||
* Non-zero if the layer should be allowed to use lossy methods (the
|
||||
* default behavior), zero if the layer should use strictly lossless
|
||||
* methods.
|
||||
*/
|
||||
void guac_display_layer_set_lossless(guac_display_layer* layer, int lossless);
|
||||
|
||||
/**
|
||||
* Sets the level of multitouch support available for the given layer. The
|
||||
* change in layer multitouch support will be made as part of the current
|
||||
* pending frame, and will not take effect on remote displays until the pending
|
||||
* frame is complete. Setting multitouch support only has any effect on the
|
||||
* default layer.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to set the multitouch support level of.
|
||||
*
|
||||
* @param touches
|
||||
* The maximum number of simultaneous touches tracked by the layer, where 0
|
||||
* represents no touch support.
|
||||
*/
|
||||
void guac_display_layer_set_multitouch(guac_display_layer* layer, int touches);
|
||||
|
||||
/**
|
||||
* Resizes the given layer to the given dimensions. The change in layer size
|
||||
* will be made as part of the current pending frame, and will not take effect
|
||||
* on remote displays until the pending frame is complete.
|
||||
*
|
||||
* IMPORTANT: While it is safe to call this function while holding an open
|
||||
* context (raw or Cairo), this should only be done if the underlying buffer is
|
||||
* maintained externally or if the context is finished being used. Resizing a
|
||||
* layer can result in the underlying buffer being replaced.
|
||||
*
|
||||
* @param layer
|
||||
* The layer to set the size of.
|
||||
*
|
||||
* @param width
|
||||
* The new width to assign to the layer, in pixels. Any values provided
|
||||
* that are greater than GUAC_DISPLAY_MAX_WIDTH will instead be interpreted
|
||||
* as equal to GUAC_DISPLAY_MAX_WIDTH.
|
||||
*
|
||||
* @param height
|
||||
* The new height to assign to the layer, in pixels. Any values provided
|
||||
* that are greater than GUAC_DISPLAY_MAX_HEIGHT will instead be
|
||||
* interpreted as equal to GUAC_DISPLAY_MAX_HEIGHT.
|
||||
*/
|
||||
void guac_display_layer_resize(guac_display_layer* layer, int width, int height);
|
||||
|
||||
/**
|
||||
* Begins a drawing operation for the given layer, returning a context that can
|
||||
* be used to draw directly to the raw image buffer containing the layer's
|
||||
* current pending frame.
|
||||
*
|
||||
* Starting a draw operation acquires exclusive access to the display for the
|
||||
* current thread. When complete, the original calling thread must relinquish
|
||||
* exclusive access and free the graphical context by calling
|
||||
* guac_display_layer_close_raw(). It is the responsibility of the caller to
|
||||
* ensure the dirty rect within the returned context is updated to contain the
|
||||
* region modified, such as by calling guac_rect_expand().
|
||||
*
|
||||
* @param layer
|
||||
* The layer to draw to.
|
||||
*
|
||||
* @return
|
||||
* A mutable graphical context containing the current raw pending frame
|
||||
* state of the given layer.
|
||||
*/
|
||||
guac_display_layer_raw_context* guac_display_layer_open_raw(guac_display_layer* layer);
|
||||
|
||||
/**
|
||||
* Ends a drawing operation that was started with a call to
|
||||
* guac_display_layer_open_raw() and relinquishes exclusive access to the
|
||||
* display. All graphical changes made to the layer through the raw context
|
||||
* will be committed to the layer and will be included in the current pending
|
||||
* frame.
|
||||
*
|
||||
* This function MUST NOT be called by any thread other than the thread that called
|
||||
* guac_display_layer_open_raw() to obtain the given context.
|
||||
*
|
||||
* @param layer
|
||||
* The layer that finished being drawn to.
|
||||
*
|
||||
* @param context
|
||||
* The raw context of the drawing operation that has completed, as returned
|
||||
* by a previous call to guac_display_layer_open_raw().
|
||||
*/
|
||||
void guac_display_layer_close_raw(guac_display_layer* layer, guac_display_layer_raw_context* context);
|
||||
|
||||
/**
|
||||
* Fills a rectangle of image data within the given raw context with a single
|
||||
* color. All pixels within the rectangle are replaced with the given color. If
|
||||
* applicable, this includes the alpha channel. Compositing is not performed by
|
||||
* this function.
|
||||
*
|
||||
* @param context
|
||||
* The raw context of the layer that is being drawn to.
|
||||
*
|
||||
* @param dst
|
||||
* The rectangular area that should be filled with the given color.
|
||||
*
|
||||
* @param color
|
||||
* The color that should replace all current pixel values within the given
|
||||
* rectangular region.
|
||||
*/
|
||||
void guac_display_layer_raw_context_set(guac_display_layer_raw_context* context,
|
||||
const guac_rect* dst, uint32_t color);
|
||||
|
||||
/**
|
||||
* Copies a rectangle of image data from the given buffer to the given raw
|
||||
* context, replacing all pixel values within the given rectangle. Compositing
|
||||
* is not performed by this function.
|
||||
*
|
||||
* The size of the image data copied and the destination location of that data
|
||||
* within the layer are dictated by the given rectangle. If any offset needs to
|
||||
* be applied to the source image buffer, it is expected that this offset will
|
||||
* already have been applied via the address of the buffer provided to this
|
||||
* function, such as through an earlier call to GUAC_RECT_CONST_BUFFER().
|
||||
*
|
||||
* @param context
|
||||
* The raw context of the layer that is being drawn to.
|
||||
*
|
||||
* @param dst
|
||||
* The rectangular area that should be filled with the image data from the
|
||||
* given buffer.
|
||||
*
|
||||
* @param buffer
|
||||
* The containing the image data that should replace all current pixel
|
||||
* values within the given rectangular region.
|
||||
*
|
||||
* @param stride
|
||||
* The number of bytes in each row of image data within the given buffer.
|
||||
*/
|
||||
void guac_display_layer_raw_context_put(guac_display_layer_raw_context* context,
|
||||
const guac_rect* dst, const void* restrict buffer, size_t stride);
|
||||
|
||||
/**
|
||||
* Begins a drawing operation for the given layer, returning a context that can
|
||||
* be used to draw to a Cairo surface containing the layer's current pending
|
||||
* frame. The underlying Cairo state within the returned context will be
|
||||
* preserved between calls to guac_display_layer_open_cairo().
|
||||
*
|
||||
* Starting a draw operation acquires exclusive access to the display for the
|
||||
* current thread. When complete, the original calling thread must relinquish
|
||||
* exclusive access and free the graphical context by calling
|
||||
* guac_display_layer_close_cairo(). It is the responsibility of the caller to
|
||||
* ensure the dirty rect within the returned context is updated to contain the
|
||||
* region modified, such as by calling guac_rect_expand().
|
||||
*
|
||||
* @param layer
|
||||
* The layer to draw to.
|
||||
*
|
||||
* @return
|
||||
* A mutable graphical context containing the current pending frame state
|
||||
* of the given layer in the form of a Cairo surface.
|
||||
*/
|
||||
guac_display_layer_cairo_context* guac_display_layer_open_cairo(guac_display_layer* layer);
|
||||
|
||||
/**
|
||||
* Ends a drawing operation that was started with a call to
|
||||
* guac_display_layer_open_cairo() and relinquishes exclusive access to the
|
||||
* display. All graphical changes made to the layer through the Cairo context
|
||||
* will be committed to the layer and will be included in the current pending
|
||||
* frame.
|
||||
*
|
||||
* This function MUST NOT be called by any thread other than the thread that called
|
||||
* guac_display_layer_open_cairo() to obtain the given context.
|
||||
*
|
||||
* @param layer
|
||||
* The layer that finished being drawn to.
|
||||
*
|
||||
* @param context
|
||||
* The Cairo context of the drawing operation that has completed, as
|
||||
* returned by a previous call to guac_display_layer_open_cairo().
|
||||
*/
|
||||
void guac_display_layer_close_cairo(guac_display_layer* layer, guac_display_layer_cairo_context* context);
|
||||
|
||||
#endif
|
||||
57
src/libguac/guacamole/fifo-constants.h
Normal file
57
src/libguac/guacamole/fifo-constants.h
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_FIFO_CONSTANTS_H
|
||||
#define GUAC_FIFO_CONSTANTS_H
|
||||
|
||||
/**
|
||||
* @addtogroup fifo
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides constants for the abstract FIFO implementation (guac_fifo).
|
||||
*
|
||||
* @file fifo-constants.h
|
||||
*/
|
||||
|
||||
/**
|
||||
* The bitwise flag used by the "state" member of guac_fifo to represent that
|
||||
* the fifo has space for at least one item.
|
||||
*/
|
||||
#define GUAC_FIFO_STATE_READY 1
|
||||
|
||||
/**
|
||||
* The bitwise flag used by the "state" member of guac_fifo to represent that
|
||||
* the fifo contains at least one item.
|
||||
*/
|
||||
#define GUAC_FIFO_STATE_NONEMPTY 2
|
||||
|
||||
/**
|
||||
* The bitwise flag used by the "state" member of guac_fifo to represent that
|
||||
* the fifo is no longer valid and may not be used for any further operations.
|
||||
*/
|
||||
#define GUAC_FIFO_STATE_INVALID 4
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif
|
||||
|
||||
53
src/libguac/guacamole/fifo-types.h
Normal file
53
src/libguac/guacamole/fifo-types.h
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_FIFO_TYPES_H
|
||||
#define GUAC_FIFO_TYPES_H
|
||||
|
||||
/**
|
||||
* @addtogroup fifo
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides type definitions for the abstract FIFO implementation (guac_fifo).
|
||||
*
|
||||
* @file fifo-types.h
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generic base structure for a FIFO of arbitrary events. The size of the FIFO
|
||||
* and each event are up to the implementation. Each implementation must
|
||||
* provide this base structure with a pointer to the underlying array of items,
|
||||
* the maximum number of items supported, and the size in bytes of each item
|
||||
* through a call to guac_fifo_init().
|
||||
*
|
||||
* This generic base may be safely included in shared memory, but
|
||||
* implementations building off this base must ensure the base is initialized
|
||||
* with a call to guac_fifo_init() and that any additional
|
||||
* implementation-specific aspects are also safe for shared memory usage.
|
||||
*/
|
||||
typedef struct guac_fifo guac_fifo;
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif
|
||||
|
||||
314
src/libguac/guacamole/fifo.h
Normal file
314
src/libguac/guacamole/fifo.h
Normal file
@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_FIFO_H
|
||||
#define GUAC_FIFO_H
|
||||
|
||||
#include "fifo-constants.h"
|
||||
#include "fifo-types.h"
|
||||
#include "flag.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/**
|
||||
* Base FIFO implementation that allows arbitrary element sizes and arbitrary
|
||||
* element storage.
|
||||
*
|
||||
* @defgroup fifo guac_fifo
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides an abstract FIFO implementation (guac_fifo), which can support
|
||||
* arbitrary element sizes and storage.
|
||||
*
|
||||
* @file fifo.h
|
||||
*/
|
||||
|
||||
struct guac_fifo {
|
||||
|
||||
/**
|
||||
* The current state of this FIFO. This state primarily represents whether
|
||||
* the FIFO contains at least one item (is non-empty), but it is also used
|
||||
* to represent whether the FIFO is invalid (no longer permitted to contain
|
||||
* any items).
|
||||
*/
|
||||
guac_flag state;
|
||||
|
||||
/**
|
||||
* The maximum number of items that may be stored in this FIFO.
|
||||
*/
|
||||
size_t max_items;
|
||||
|
||||
/**
|
||||
* The size of each individual item, in bytes. All FIFO items must have a
|
||||
* constant size, though that size is implementation-dependent.
|
||||
*/
|
||||
size_t item_size;
|
||||
|
||||
/**
|
||||
* The index of the first item within this FIFO. As items are
|
||||
* added/removed, this value will advance as necessary to avoid needing to
|
||||
* spend CPU time moving existing items around in memory.
|
||||
*/
|
||||
size_t head;
|
||||
|
||||
/**
|
||||
* The current number of items stored within this FIFO.
|
||||
*/
|
||||
size_t item_count;
|
||||
|
||||
/**
|
||||
* The offset of the first byte of the implementation-specific array of
|
||||
* items within this FIFO, relative to the first byte of guac_fifo
|
||||
* structure.
|
||||
*/
|
||||
ssize_t items_offset;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the given guac_fifo such that it may be safely included in
|
||||
* shared memory and accessed by multiple processes. This function MUST be
|
||||
* invoked once (and ONLY once) for each guac_fifo being used, and MUST be
|
||||
* invoked before any such FIFO is used.
|
||||
*
|
||||
* The FIFO is empty upon initialization.
|
||||
*
|
||||
* @param fifo
|
||||
* The FIFO to initialize.
|
||||
*
|
||||
* @param items
|
||||
* The storage that the base implementation should use for queued items.
|
||||
* This storage MUST be large enough to contain the maximum number of items
|
||||
* as a contiguous array.
|
||||
*
|
||||
* @param max_items
|
||||
* The maximum number of items supported by the provided storage.
|
||||
*
|
||||
* @param item_size
|
||||
* The number of bytes required for each individual item in storage.
|
||||
*/
|
||||
void guac_fifo_init(guac_fifo* fifo, void* items,
|
||||
size_t max_items, size_t item_size);
|
||||
|
||||
/**
|
||||
* Releases all underlying resources used by the given guac_fifo, such as
|
||||
* pthread mutexes and conditions. The given guac_fifo MAY NOT be used after
|
||||
* this function has been called. This function MAY NOT be called while
|
||||
* exclusive access to the underlying state flag is held by any thread.
|
||||
*
|
||||
* This function does NOT free() the given guac_fifo pointer. If the memory
|
||||
* associated with the given guac_fifo has been manually allocated, it must be
|
||||
* manually freed as necessary.
|
||||
*
|
||||
* @param fifo
|
||||
* The FIFO to destroy.
|
||||
*/
|
||||
void guac_fifo_destroy(guac_fifo* fifo);
|
||||
|
||||
/**
|
||||
* Marks the given FIFO as invalid, preventing any further additions or
|
||||
* removals from the FIFO. Attempts to add/remove items from the FIFO from this
|
||||
* point forward will fail immediately, as will any outstanding attempts to
|
||||
* remove items that are currently blocked.
|
||||
*
|
||||
* This function is primarily necessary to allow for threadsafe cleanup of
|
||||
* queues. Lacking this function, there is no guarantee that an outstanding
|
||||
* call to guac_fifo_dequeue() won't still be indefinitely blocking.
|
||||
* Internally, such a condition would mean that the mutex of the state flag is
|
||||
* still held, which would mean that the FIFO can never be safely destroyed.
|
||||
*
|
||||
* @param fifo
|
||||
* The FIFO to invalidate.
|
||||
*/
|
||||
void guac_fifo_invalidate(guac_fifo* fifo);
|
||||
|
||||
/**
|
||||
* Returns whether the given FIFO is still valid. A FIFO is valid if it has not
|
||||
* yet been invalidated through a call to guac_fifo_invalidate().
|
||||
*
|
||||
* @param fifo
|
||||
* The FIFO to test.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the given FIFO is still valid, zero otherwise.
|
||||
*/
|
||||
int guac_fifo_is_valid(guac_fifo* fifo);
|
||||
|
||||
/**
|
||||
* Acquires exclusive access to this guac_fifo. When exclusive access is no
|
||||
* longer required, it must be manually relinquished through a call to
|
||||
* guac_fifo_unlock(). This function may be safely called while the current
|
||||
* thread already has exclusive access, however every such call must eventually
|
||||
* have a matching call to guac_fifo_unlock().
|
||||
*
|
||||
* NOTE: It is intended that locking/unlocking a guac_fifo may be used in lieu
|
||||
* of a mutex to guard concurrent access to any number of shared resources
|
||||
* related to the FIFO.
|
||||
*
|
||||
* @param fifo
|
||||
* The guac_fifo to lock.
|
||||
*/
|
||||
void guac_fifo_lock(guac_fifo* fifo);
|
||||
|
||||
/**
|
||||
* Relinquishes exclusive access to this guac_fifo. This function may only be
|
||||
* called by a thread that currently has exclusive access to the guac_fifo.
|
||||
*
|
||||
* NOTE: It is intended that locking/unlocking a guac_fifo may be used in lieu
|
||||
* of a mutex to guard concurrent access to any number of shared resources
|
||||
* related to the FIFO.
|
||||
*
|
||||
* @param fifo
|
||||
* The guac_fifo to unlock.
|
||||
*/
|
||||
void guac_fifo_unlock(guac_fifo* fifo);
|
||||
|
||||
/**
|
||||
* Adds a copy of the given item to the end of the given FIFO, and signals any
|
||||
* waiting threads that the FIFO is now non-empty. If there is insufficient
|
||||
* space in the FIFO, this function will block until at space is available. If
|
||||
* the FIFO is invalid or becomes invalid, this function returns immediately.
|
||||
*
|
||||
* @param fifo
|
||||
* The FIFO to add an item to.
|
||||
*
|
||||
* @param item
|
||||
* The item to add.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the item was successfully added, zero if items cannot be
|
||||
* added to the FIFO because the FIFO has been invalidated.
|
||||
*/
|
||||
int guac_fifo_enqueue(guac_fifo* fifo, const void* item);
|
||||
|
||||
/**
|
||||
* Atomically adds a copy of the given item to the end of the given FIFO,
|
||||
* signals any waiting threads that the FIFO is now non-empty, and leaves the
|
||||
* given FIFO locked. If there is insufficient space in the FIFO, this function
|
||||
* will block until at space is available. If the FIFO is invalid or becomes
|
||||
* invalid, this function returns immediately and the FIFO is not locked.
|
||||
*
|
||||
* @param fifo
|
||||
* The FIFO to add an item to.
|
||||
*
|
||||
* @param item
|
||||
* The item to add.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the item was successfully added, zero if items cannot be
|
||||
* added to the FIFO because the FIFO has been invalidated.
|
||||
*/
|
||||
int guac_fifo_enqueue_and_lock(guac_fifo* fifo, const void* item);
|
||||
|
||||
/**
|
||||
* Removes the oldest (first) item from the FIFO, storing a copy of that item
|
||||
* within the provided buffer. If the FIFO is currently empty, this function
|
||||
* will block until at least one item has been added to the FIFO or until the
|
||||
* FIFO becomes invalid.
|
||||
*
|
||||
* @param fifo
|
||||
* The FIFO to remove an item from.
|
||||
*
|
||||
* @param item
|
||||
* The buffer that should receive a copy of the removed item.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if an item was successfully removed, zero if items cannot be
|
||||
* removed from the FIFO because the FIFO has been invalidated.
|
||||
*/
|
||||
int guac_fifo_dequeue(guac_fifo* fifo, void* item);
|
||||
|
||||
/**
|
||||
* Atomically removes the oldest (first) item from the FIFO, storing a copy of
|
||||
* that item within the provided buffer. If this function successfully removes
|
||||
* an item, the FIFO is left locked after this function returns. If the FIFO is
|
||||
* currently empty, this function will block until at least one item has been
|
||||
* added to the FIFO or until the FIFO becomes invalid.
|
||||
*
|
||||
* @param fifo
|
||||
* The FIFO to remove an item from.
|
||||
*
|
||||
* @param item
|
||||
* The buffer that should receive a copy of the removed item.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if an item was successfully removed, zero if items cannot be
|
||||
* removed from the FIFO because the FIFO has been invalidated.
|
||||
*/
|
||||
int guac_fifo_dequeue_and_lock(guac_fifo* fifo, void* item);
|
||||
|
||||
/**
|
||||
* Removes the oldest (first) item from the FIFO, storing a copy of that item
|
||||
* within the provided buffer. If the FIFO is currently empty, this function
|
||||
* will block until at least one item has been added to the FIFO, until the
|
||||
* given timeout has elapsed, or until the FIFO becomes invalid.
|
||||
*
|
||||
* @param fifo
|
||||
* The FIFO to remove an item from.
|
||||
*
|
||||
* @param item
|
||||
* The buffer that should receive a copy of the removed item.
|
||||
*
|
||||
* @param msec_timeout
|
||||
* The maximum number of milliseconds to wait for at least one item to be
|
||||
* present within the FIFO (or for the FIFO to become invalid).
|
||||
*
|
||||
* @return
|
||||
* Non-zero if an item was successfully removed, zero if the timeout has
|
||||
* elapsed or if items cannot be removed from the FIFO because the FIFO has
|
||||
* been invalidated.
|
||||
*/
|
||||
int guac_fifo_timed_dequeue(guac_fifo* fifo,
|
||||
void* item, int msec_timeout);
|
||||
|
||||
/**
|
||||
* Atomically removes the oldest (first) item from the FIFO, storing a copy of
|
||||
* that item within the provided buffer. If this function successfully removes
|
||||
* an item, the FIFO is left locked after this function returns. If the FIFO is
|
||||
* currently empty, this function will block until at least one item has been
|
||||
* added to the FIFO, until the given timeout has elapsed, or until the FIFO
|
||||
* becomes invalid.
|
||||
*
|
||||
* @param fifo
|
||||
* The FIFO to remove an item from.
|
||||
*
|
||||
* @param item
|
||||
* The buffer that should receive a copy of the removed item.
|
||||
*
|
||||
* @param msec_timeout
|
||||
* The maximum number of milliseconds to wait for at least one item to be
|
||||
* present within the FIFO (or for the FIFO to become invalid).
|
||||
*
|
||||
* @return
|
||||
* Non-zero if an item was successfully removed, zero if the timeout has
|
||||
* elapsed or if items cannot be removed from the FIFO because the FIFO has
|
||||
* been invalidated.
|
||||
*/
|
||||
int guac_fifo_timed_dequeue_and_lock(guac_fifo* fifo,
|
||||
void* item, int msec_timeout);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#endif
|
||||
|
||||
36
src/libguac/guacamole/flag-types.h
Normal file
36
src/libguac/guacamole/flag-types.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_FLAG_TYPES_H
|
||||
#define GUAC_FLAG_TYPES_H
|
||||
|
||||
/**
|
||||
* Generic integer flag intended for signalling of arbitrary events between
|
||||
* processes. This flag may be safely included in shared memory, but must be
|
||||
* initialized with guac_flag_init().
|
||||
*
|
||||
* In addition to basic signalling and tracking of flag values, it is intended
|
||||
* that the locking/unlocking facilities of guac_flag may be used in
|
||||
* lieu of a mutex to guard concurrent access to any number of shared resources
|
||||
* related to the flag.
|
||||
*/
|
||||
typedef struct guac_flag guac_flag;
|
||||
|
||||
#endif
|
||||
|
||||
244
src/libguac/guacamole/flag.h
Normal file
244
src/libguac/guacamole/flag.h
Normal file
@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_FLAG_H
|
||||
#define GUAC_FLAG_H
|
||||
|
||||
#include "flag-types.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct guac_flag {
|
||||
|
||||
/**
|
||||
* The mutex used to ensure concurrent changes to the value of this flag
|
||||
* are threadsafe, as well as to satisfy the requirements of the pthread
|
||||
* conditional used to signal changes to the value of this flag.
|
||||
*/
|
||||
pthread_mutex_t value_mutex;
|
||||
|
||||
/**
|
||||
* Condition variable that signals when the value of this flag has changed.
|
||||
*/
|
||||
pthread_cond_t value_changed;
|
||||
|
||||
/**
|
||||
* The current value of this flag. This value may be the bitwise OR'd value
|
||||
* of any number of arbitrary flags, so long as those flags fit within an
|
||||
* int. It is entirely up to the user of this guac_flag to
|
||||
* define the meaning of any value(s) assigned.
|
||||
*/
|
||||
unsigned int value;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the given guac_flag such that it may be safely
|
||||
* included in shared memory and accessed by multiple processes. This function
|
||||
* MUST be invoked once (and ONLY once) for each guac_flag being
|
||||
* used, and MUST be invoked before any such flag is used.
|
||||
*
|
||||
* The value of the flag upon initialization is 0 (no flags set).
|
||||
*
|
||||
* @param event_flag
|
||||
* The flag to initialize.
|
||||
*/
|
||||
void guac_flag_init(guac_flag* event_flag);
|
||||
|
||||
/**
|
||||
* Releases all underlying resources used by the given guac_flag,
|
||||
* such as pthread mutexes and conditions. The given guac_flag MAY
|
||||
* NOT be used after this function has been called. This function MAY NOT be
|
||||
* called while exclusive access to the guac_flag is held by any
|
||||
* thread.
|
||||
*
|
||||
* This function does NOT free() the given guac_flag pointer. If the
|
||||
* memory associated with the given guac_flag has been manually
|
||||
* allocated, it must be manually freed as necessary.
|
||||
*
|
||||
* @param event_flag
|
||||
* The flag to destroy.
|
||||
*/
|
||||
void guac_flag_destroy(guac_flag* event_flag);
|
||||
|
||||
/**
|
||||
* Sets the given bitwise flag(s) within the value of the given
|
||||
* guac_flag, setting their corresponding bits to 1. The values of
|
||||
* other bitwise flags are not affected. If other threads are waiting for any
|
||||
* of these flags to be set, and at least one such flag has been set as a
|
||||
* result of this call, they will be signalled accordingly.
|
||||
*
|
||||
* This function is threadsafe and will acquire exclusive access to the given
|
||||
* guac_flag prior to changing the flag value. It is also safe to
|
||||
* call this function if exclusive access has already been acquired through
|
||||
* guac_flag_lock() or similar.
|
||||
*
|
||||
* @param event_flag
|
||||
* The guac_flag to modify.
|
||||
*
|
||||
* @param flags
|
||||
* The bitwise OR'd value of the flags to be set.
|
||||
*/
|
||||
void guac_flag_set(guac_flag* event_flag,
|
||||
unsigned int flags);
|
||||
|
||||
/**
|
||||
* Sets the given bitwise flag(s) within the value of the given guac_flag,
|
||||
* setting their corresponding bits to 1, while also acquiring exclusive access
|
||||
* to the guac_flag. The values of other bitwise flags are not affected. If
|
||||
* other threads are waiting for any of these flags to be set, and at least one
|
||||
* such flag has been set as a result of this call, they will be signalled
|
||||
* accordingly.
|
||||
*
|
||||
* This function is threadsafe and will acquire exclusive access to the given
|
||||
* guac_flag prior to changing the flag value. It is also safe to
|
||||
* call this function if exclusive access has already been acquired through
|
||||
* guac_flag_lock() or similar.
|
||||
*
|
||||
* @param event_flag
|
||||
* The guac_flag to modify.
|
||||
*
|
||||
* @param flags
|
||||
* The bitwise OR'd value of the flags to be set.
|
||||
*/
|
||||
void guac_flag_set_and_lock(guac_flag* event_flag,
|
||||
unsigned int flags);
|
||||
|
||||
/**
|
||||
* Clears the given bitwise flag(s) within the value of the given
|
||||
* guac_flag, setting their corresponding bits to 0. The values of
|
||||
* other bitwise flags are not affected. Unlike guac_flag_set(),
|
||||
* no threads will be notified that these flag values have changed.
|
||||
*
|
||||
* This function is threadsafe and will acquire exclusive access to the given
|
||||
* guac_flag prior to changing the flag value. It is also safe to
|
||||
* call this function if exclusive access has already been acquired through
|
||||
* guac_flag_lock() or similar.
|
||||
*
|
||||
* @param event_flag
|
||||
* The guac_flag to modify.
|
||||
*
|
||||
* @param flags
|
||||
* The bitwise OR'd value of the flags to be cleared. Each bit in this
|
||||
* value that is set to 1 will be set to 0 in the value of the
|
||||
* guac_flag.
|
||||
*/
|
||||
void guac_flag_clear(guac_flag* event_flag,
|
||||
unsigned int flags);
|
||||
|
||||
/**
|
||||
* Clears the given bitwise flag(s) within the value of the given guac_flag,
|
||||
* setting their corresponding bits to 0, while also acquiring exclusive access
|
||||
* to the guac_flag. The values of other bitwise flags are not affected. Unlike
|
||||
* guac_flag_set(), no threads will be notified that these flag values have
|
||||
* changed.
|
||||
*
|
||||
* This function is threadsafe and will acquire exclusive access to the given
|
||||
* guac_flag prior to changing the flag value. It is also safe to
|
||||
* call this function if exclusive access has already been acquired through
|
||||
* guac_flag_lock() or similar.
|
||||
*
|
||||
* @param event_flag
|
||||
* The guac_flag to modify.
|
||||
*
|
||||
* @param flags
|
||||
* The bitwise OR'd value of the flags to be cleared. Each bit in this
|
||||
* value that is set to 1 will be set to 0 in the value of the
|
||||
* guac_flag.
|
||||
*/
|
||||
void guac_flag_clear_and_lock(guac_flag* event_flag,
|
||||
unsigned int flags);
|
||||
|
||||
/**
|
||||
* Acquires exclusive access to this guac_flag. When exclusive
|
||||
* access is no longer required, it must be manually relinquished through a
|
||||
* call to guac_flag_unlock(). This function may be safely called
|
||||
* while the current thread already has exclusive access, however every such
|
||||
* call must eventually have a matching call to guac_flag_unlock().
|
||||
*
|
||||
* NOTE: It is intended that locking/unlocking a guac_flag may be
|
||||
* used in lieu of a mutex to guard concurrent access to any number of shared
|
||||
* resources related to the flag.
|
||||
*
|
||||
* @param event_flag
|
||||
* The guac_flag to lock.
|
||||
*/
|
||||
void guac_flag_lock(guac_flag* event_flag);
|
||||
|
||||
/**
|
||||
* Relinquishes exclusive access to this guac_flag. This function
|
||||
* may only be called by a thread that currently has exclusive access to the
|
||||
* guac_flag.
|
||||
*
|
||||
* NOTE: It is intended that locking/unlocking a guac_flag may be
|
||||
* used in lieu of a mutex to guard concurrent access to any number of shared
|
||||
* resources related to the flag.
|
||||
*
|
||||
* @param event_flag
|
||||
* The guac_flag to unlock.
|
||||
*/
|
||||
void guac_flag_unlock(guac_flag* event_flag);
|
||||
|
||||
/**
|
||||
* Waits indefinitely for any of the given flags to be set within the given
|
||||
* guac_flag. This function returns only after at least one of the
|
||||
* given flags has been set. After this function returns, the current thread
|
||||
* has exclusive access to the guac_flag and MUST relinquish that
|
||||
* access with a call to guac_flag_unlock() when finished.
|
||||
*
|
||||
* @param event_flag
|
||||
* The guac_flag to wait on.
|
||||
*
|
||||
* @param flags
|
||||
* The bitwise OR'd value of the specific flag(s) to wait for.
|
||||
*/
|
||||
void guac_flag_wait_and_lock(guac_flag* event_flag,
|
||||
unsigned int flags);
|
||||
|
||||
/**
|
||||
* Waits no longer than the given number of milliseconds for any of the given
|
||||
* flags to be set within the given guac_flag. This function returns
|
||||
* after at least one of the given flags has been set, or after the provided
|
||||
* time limit expires. After this function returns successfully, the current
|
||||
* thread has exclusive access to the guac_flag and MUST relinquish
|
||||
* that access with a call to guac_flag_unlock() when finished. If
|
||||
* the time limit lapses before any of the given flags has been set, this
|
||||
* function returns unsuccessfully without acquiring exclusive access.
|
||||
*
|
||||
* @param event_flag
|
||||
* The guac_flag to wait on.
|
||||
*
|
||||
* @param flags
|
||||
* The bitwise OR'd value of the specific flag(s) to wait for.
|
||||
*
|
||||
* @param msec_timeout
|
||||
* The maximum number of milliseconds to wait for at least one of the
|
||||
* desired flags to be set.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if at least one of the desired flags has been set and the
|
||||
* current thread now has exclusive access to the guac_flag, zero if none
|
||||
* of the desired flags were set within the time limit and the current
|
||||
* thread DOES NOT have exclusive access.
|
||||
*/
|
||||
int guac_flag_timedwait_and_lock(guac_flag* event_flag,
|
||||
unsigned int flags, unsigned int msec_timeout);
|
||||
|
||||
#endif
|
||||
|
||||
33
src/libguac/guacamole/rect-types.h
Normal file
33
src/libguac/guacamole/rect-types.h
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RECT_TYPES_H
|
||||
#define GUAC_RECT_TYPES_H
|
||||
|
||||
/**
|
||||
* A rectangle defined by its upper-left and lower-right corners. The
|
||||
* upper-left corner is inclusive (represents the start of the area contained
|
||||
* by the guac_rect), while the lower-right corner is exclusive (represents the
|
||||
* start of the area NOT contained by the guac_rect). All coordinates may be
|
||||
* negative.
|
||||
*/
|
||||
typedef struct guac_rect guac_rect;
|
||||
|
||||
#endif
|
||||
|
||||
271
src/libguac/guacamole/rect.h
Normal file
271
src/libguac/guacamole/rect.h
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RECT_H
|
||||
#define GUAC_RECT_H
|
||||
|
||||
#include "mem.h"
|
||||
#include "rect-types.h"
|
||||
|
||||
/**
|
||||
* Returns the memory address of the given rectangle within the given mutable
|
||||
* buffer, where the upper-left corner of the given buffer is (0, 0). If the
|
||||
* memory address cannot be calculated because doing so would overflow the
|
||||
* maximum value of a size_t, execution of the current process is automatically
|
||||
* aborted.
|
||||
*
|
||||
* IMPORTANT: No checks are performed on whether the rectangle extends beyond
|
||||
* the bounds of the buffer, including considering whether the left/top
|
||||
* position of the rectangle is negative. If the rectangle has not already been
|
||||
* contrained to be within the bounds of the buffer, such checks must be
|
||||
* performed before dereferencing the value returned by this macro.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to determine the offset of.
|
||||
*
|
||||
* @param buffer
|
||||
* The mutable buffer within which the address of the given rectangle
|
||||
* should be determined.
|
||||
*
|
||||
* @param stride
|
||||
* The number of bytes in each row of image data within the buffer.
|
||||
*
|
||||
* @param bpp
|
||||
* The number of bytes in each pixel of image data.
|
||||
*
|
||||
* @return
|
||||
* The memory address of the given rectangle within the given buffer.
|
||||
*/
|
||||
#define GUAC_RECT_MUTABLE_BUFFER(rect, buffer, stride, bpp) ((void*) ( \
|
||||
((unsigned char*) (buffer)) \
|
||||
+ guac_mem_ckd_mul_or_die((rect).top, stride) \
|
||||
+ guac_mem_ckd_mul_or_die((rect).left, bpp)))
|
||||
|
||||
/**
|
||||
* Returns the memory address of the given rectangle within the given immutable
|
||||
* (const) buffer, where the upper-left corner of the given buffer is (0, 0).
|
||||
* If the memory address cannot be calculated because doing so would overflow
|
||||
* the maximum value of a size_t, execution of the current process is
|
||||
* automatically aborted.
|
||||
*
|
||||
* IMPORTANT: No checks are performed on whether the rectangle extends beyond
|
||||
* the bounds of the buffer, including considering whether the left/top
|
||||
* position of the rectangle is negative. If the rectangle has not already been
|
||||
* contrained to be within the bounds of the buffer, such checks must be
|
||||
* performed before dereferencing the value returned by this macro.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to determine the offset of.
|
||||
*
|
||||
* @param buffer
|
||||
* The const buffer within which the address of the given rectangle should
|
||||
* be determined.
|
||||
*
|
||||
* @param stride
|
||||
* The number of bytes in each row of image data within the buffer.
|
||||
*
|
||||
* @param bpp
|
||||
* The number of bytes in each pixel of image data.
|
||||
*
|
||||
* @return
|
||||
* The memory address of the given rectangle within the given buffer.
|
||||
*/
|
||||
#define GUAC_RECT_CONST_BUFFER(rect, buffer, stride, bpp) ((const void*) ( \
|
||||
((const unsigned char*) (buffer)) \
|
||||
+ guac_mem_ckd_mul_or_die((rect).top, stride) \
|
||||
+ guac_mem_ckd_mul_or_die((rect).left, bpp)))
|
||||
|
||||
struct guac_rect {
|
||||
|
||||
/**
|
||||
* The X coordinate of the upper-left corner of this rectangle (inclusive).
|
||||
* This value represents the least integer X coordinate that is part of
|
||||
* this rectangle, with greater integer X coordinates being part of this
|
||||
* rectangle up to but excluding the right boundary.
|
||||
*
|
||||
* This value MUST be less than or equal to the right boundary. If this
|
||||
* value is equal to the right boundary, the rectangle is empty (has no
|
||||
* width).
|
||||
*/
|
||||
int left;
|
||||
|
||||
/**
|
||||
* The Y coordinate of the upper-left corner of this rectangle (inclusive).
|
||||
* This value represents the least integer Y coordinate that is part of
|
||||
* this rectangle, with greater integer Y coordinates being part of this
|
||||
* rectangle up to but excluding the bottom boundary.
|
||||
*
|
||||
* This value MUST be less than or equal to the bottom boundary. If this
|
||||
* value is equal to the bottom boundary, the rectangle is empty (has no
|
||||
* height).
|
||||
*/
|
||||
int top;
|
||||
|
||||
/**
|
||||
* The X coordinate of the lower-right corner of this rectangle
|
||||
* (exclusive). This value represents the least integer X coordinate that
|
||||
* is NOT part of this rectangle, with lesser integer X coordinates being
|
||||
* part of this rectangle up to and including the left boundary.
|
||||
*
|
||||
* This value MUST be greater than or equal to the left boundary. If this
|
||||
* value is equal to the left boundary, the rectangle is empty (has no
|
||||
* width).
|
||||
*/
|
||||
int right;
|
||||
|
||||
/**
|
||||
* The Y coordinate of the lower-right corner of this rectangle
|
||||
* (exclusive). This value represents the least integer Y coordinate that
|
||||
* is NOT part of this rectangle, with lesser integer Y coordinates being
|
||||
* part of this rectangle up to and including the top boundary.
|
||||
*
|
||||
* This value MUST be greater than or equal to the top boundary. If this
|
||||
* value is equal to the top boundary, the rectangle is empty (has no
|
||||
* height).
|
||||
*/
|
||||
int bottom;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the given rectangle with the given coordinates and dimensions.
|
||||
* If a dimenion is negative, it is interpreted as if zero.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to initialize.
|
||||
*
|
||||
* @param x
|
||||
* The X coordinate of the upper-left corner of the rectangle.
|
||||
*
|
||||
* @param y
|
||||
* The Y coordinate of the upper-left corner of the rectangle.
|
||||
*
|
||||
* @param width
|
||||
* The width of the rectangle.
|
||||
*
|
||||
* @param height
|
||||
* The height of the rectangle.
|
||||
*/
|
||||
void guac_rect_init(guac_rect* rect, int x, int y, int width, int height);
|
||||
|
||||
/**
|
||||
* Extends the given rectangle such that each edge of the rectangle falls on
|
||||
* the edge of an NxN cell in a regular grid anchored at the upper-left corner,
|
||||
* where N is a power of two.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to adjust.
|
||||
*
|
||||
* @param bits
|
||||
* The size of the cells in the grid, as the exponent of the power of two
|
||||
* size of each grid cell edge. For example, to align the given rectangle
|
||||
* to the edges of a grid containing 8x8 cells, use a value of 3.
|
||||
*/
|
||||
void guac_rect_align(guac_rect* rect, unsigned int bits);
|
||||
|
||||
/**
|
||||
* Extends the given rectangle such that it contains at least the specified
|
||||
* minimum rectangle.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to extend.
|
||||
*
|
||||
* @param min
|
||||
* The minimum area which must be contained within the given rectangle.
|
||||
*/
|
||||
void guac_rect_extend(guac_rect* rect, const guac_rect* min);
|
||||
|
||||
/**
|
||||
* Collapses the given rectangle such that it exists only within the bounds of
|
||||
* the given maximum rectangle.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to collapse.
|
||||
*
|
||||
* @param max
|
||||
* The maximum area in which the given rectangle can exist.
|
||||
*/
|
||||
void guac_rect_constrain(guac_rect* rect, const guac_rect* max);
|
||||
|
||||
/**
|
||||
* Reduces the size of the given rectangle such that it does not exceed the
|
||||
* given width and height. The aspect ratio of the given rectangle is
|
||||
* preserved. If the original rectangle is already smaller than the given width
|
||||
* and height, this function has no effect.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to shrink while preserving aspect ratio.
|
||||
*
|
||||
* @param max_width
|
||||
* The maximum width that the given rectangle may have.
|
||||
*
|
||||
* @param max_height
|
||||
* The maximum height that the given rectangle may have.
|
||||
*/
|
||||
void guac_rect_shrink(guac_rect* rect, int max_width, int max_height);
|
||||
|
||||
/**
|
||||
* Returns whether the two given rectangles intersect.
|
||||
*
|
||||
* @param a
|
||||
* One of the rectangles to check.
|
||||
*
|
||||
* @param b
|
||||
* The other rectangle to check.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the rectangles intersect, zero otherwise.
|
||||
*/
|
||||
int guac_rect_intersects(const guac_rect* a, const guac_rect* b);
|
||||
|
||||
/**
|
||||
* Returns whether the given rectangle is empty. A rectangle is empty if it has
|
||||
* no area (has an effective width or height of zero).
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to test.
|
||||
*
|
||||
* @return
|
||||
* Non-zero if the rectangle is empty, zero otherwise.
|
||||
*/
|
||||
int guac_rect_is_empty(const guac_rect* rect);
|
||||
|
||||
/**
|
||||
* Returns the width of the given rectangle.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to determine the width of.
|
||||
*
|
||||
* @return
|
||||
* The width of the given rectangle.
|
||||
*/
|
||||
int guac_rect_width(const guac_rect* rect);
|
||||
|
||||
/**
|
||||
* Returns the height of the given rectangle.
|
||||
*
|
||||
* @param rect
|
||||
* The rectangle to determine the height of.
|
||||
*
|
||||
* @return
|
||||
* The height of the given rectangle.
|
||||
*/
|
||||
int guac_rect_height(const guac_rect* rect);
|
||||
|
||||
#endif
|
||||
178
src/libguac/rect.c
Normal file
178
src/libguac/rect.c
Normal file
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "guacamole/rect.h"
|
||||
|
||||
/**
|
||||
* Given a bitmask that is one less than a power of two (ie: 0xF, 0x1F, etc.),
|
||||
* rounds the given value in the negative direction to the nearest multiple of
|
||||
* that power of two. Positive values are rounded down towards zero while
|
||||
* negative values are rounded up toward negative values of greater magnitude.
|
||||
*
|
||||
* @param value
|
||||
* The value to round.
|
||||
*
|
||||
* @param mask
|
||||
* A bitmask whose integer value is one less than a power of two.
|
||||
*
|
||||
* @return
|
||||
* The given value, rounded to the nearest multiple of the power of two
|
||||
* represented by the given mask, where that rounding is performed in the
|
||||
* negative direction.
|
||||
*/
|
||||
#define GUAC_RECT_ROUND_NEG(value, mask) (value & ~mask)
|
||||
|
||||
/**
|
||||
* Given a bitmask that is one less than a power of two (ie: 0xF, 0x1F, etc.),
|
||||
* rounds the given value in the positive direction to the nearest multiple of
|
||||
* that power of two. Negative values are rounded down towards zero while
|
||||
* positive values are rounded up toward positive values of greater magnitude.
|
||||
*
|
||||
* @param value
|
||||
* The value to round.
|
||||
*
|
||||
* @param mask
|
||||
* A bitmask whose integer value is one less than a power of two.
|
||||
*
|
||||
* @return
|
||||
* The given value, rounded to the nearest multiple of the power of two
|
||||
* represented by the given mask, where that rounding is performed in the
|
||||
* positive direction.
|
||||
*/
|
||||
#define GUAC_RECT_ROUND_POS(value, mask) ((value + mask) & ~mask)
|
||||
|
||||
void guac_rect_init(guac_rect* rect, int x, int y, int width, int height) {
|
||||
*rect = (guac_rect) {
|
||||
.left = x,
|
||||
.top = y,
|
||||
.right = width > 0 ? x + width : x,
|
||||
.bottom = height > 0 ? y + height : y
|
||||
};
|
||||
}
|
||||
|
||||
void guac_rect_extend(guac_rect* rect, const guac_rect* min) {
|
||||
|
||||
/* The union of an empty rect and the provided rect should be that provided
|
||||
* rect. Considering the garbage coordinates that may be present in an
|
||||
* empty rect can otherwise produce incorrect results. */
|
||||
if (guac_rect_is_empty(rect)) {
|
||||
*rect = *min;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Extend edges of rectangle such that it contains the provided minimum
|
||||
* rectangle */
|
||||
if (min->left < rect->left) rect->left = min->left;
|
||||
if (min->top < rect->top) rect->top = min->top;
|
||||
if (min->right > rect->right) rect->right = min->right;
|
||||
if (min->bottom > rect->bottom) rect->bottom = min->bottom;
|
||||
|
||||
}
|
||||
|
||||
void guac_rect_constrain(guac_rect* rect, const guac_rect* max) {
|
||||
|
||||
/* Shrink edges of rectangle such that it is contained by the provided
|
||||
* maximum rectangle */
|
||||
if (max->left > rect->left) rect->left = max->left;
|
||||
if (max->top > rect->top) rect->top = max->top;
|
||||
if (max->right < rect->right) rect->right = max->right;
|
||||
if (max->bottom < rect->bottom) rect->bottom = max->bottom;
|
||||
|
||||
}
|
||||
|
||||
void guac_rect_shrink(guac_rect* rect, int max_width, int max_height) {
|
||||
|
||||
int original_width = guac_rect_width(rect);
|
||||
int original_height = guac_rect_height(rect);
|
||||
|
||||
/* Shrink only; do not _expand_ to reach the max width/height */
|
||||
if (original_width < max_width) max_width = original_width;
|
||||
if (original_height < max_height) max_height = original_height;
|
||||
|
||||
/* BOTH the width and height must be adjusted by the same factor in
|
||||
* order to preserve aspect ratio. Choosing the smallest adjustment
|
||||
* factor guarantees that the rectangle will be within bounds while
|
||||
* preserving aspect ratio to the greatest degree possible (there
|
||||
* is unavoidable integer rounding error). */
|
||||
|
||||
int scale_numerator, scale_denominator;
|
||||
|
||||
/* NOTE: The following test is mathematically equivalent to:
|
||||
*
|
||||
* if (max_width / original_width < max_height / original_height) {
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* but does not require floating point arithmetic. */
|
||||
if (max_width * original_height < max_height * original_width) {
|
||||
scale_numerator = max_width;
|
||||
scale_denominator = original_width;
|
||||
}
|
||||
else {
|
||||
scale_numerator = max_height;
|
||||
scale_denominator = original_height;
|
||||
}
|
||||
|
||||
rect->right = rect->left + original_width * scale_numerator / scale_denominator;
|
||||
rect->bottom = rect->top + original_height * scale_numerator / scale_denominator;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void guac_rect_align(guac_rect* rect, unsigned int bits) {
|
||||
|
||||
if (bits == 0)
|
||||
return;
|
||||
|
||||
int factor = 1 << bits;
|
||||
int mask = factor - 1;
|
||||
|
||||
/* Expand and shift rectangle as necessary for its edges to be aligned
|
||||
* along multiples of the given power of two */
|
||||
rect->left = GUAC_RECT_ROUND_NEG(rect->left, mask);
|
||||
rect->top = GUAC_RECT_ROUND_NEG(rect->top, mask);
|
||||
rect->right = GUAC_RECT_ROUND_POS(rect->right, mask);
|
||||
rect->bottom = GUAC_RECT_ROUND_POS(rect->bottom, mask);
|
||||
|
||||
}
|
||||
|
||||
int guac_rect_intersects(const guac_rect* a, const guac_rect* b) {
|
||||
|
||||
/* Two rectangles intersect if neither rectangle is wholly outside the
|
||||
* other */
|
||||
return !(
|
||||
b->right <= a->left || a->right <= b->left
|
||||
|| b->bottom <= a->top || a->bottom <= b->top
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
int guac_rect_is_empty(const guac_rect* rect) {
|
||||
return rect->right <= rect->left || rect->bottom <= rect->top;
|
||||
}
|
||||
|
||||
int guac_rect_width(const guac_rect* rect) {
|
||||
int width = rect->right - rect->left;
|
||||
return width > 0 ? width : 0;
|
||||
}
|
||||
|
||||
int guac_rect_height(const guac_rect* rect) {
|
||||
int height = rect->bottom - rect->top;
|
||||
return height > 0 ? height : 0;
|
||||
}
|
||||
@ -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 \
|
||||
|
||||
298
src/libguac/tests/fifo/fifo.c
Normal file
298
src/libguac/tests/fifo/fifo.c
Normal file
@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <guacamole/fifo.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* The maximum number of milliseconds to wait for a test event to be added to a
|
||||
* fifo.
|
||||
*/
|
||||
#define TEST_TIMEOUT 250
|
||||
|
||||
/**
|
||||
* The maximum number of items permitted in test_fifo.
|
||||
*/
|
||||
#define TEST_FIFO_MAX_ITEMS 4
|
||||
|
||||
/**
|
||||
* The rough amount of time to wait between fifo reads within the test thread,
|
||||
* in milliseconds. A random delay between 0ms and this value will be added
|
||||
* before each read. This is done to verify that the fifo behaves correctly
|
||||
* for cases where the sending thread is producing data much faster than it's
|
||||
* being read, slower than it's read, etc.
|
||||
*/
|
||||
#define TEST_READ_INTERVAL 10
|
||||
|
||||
/**
|
||||
* Zero-terminated set of arbitrarily-chosen values that will be provided as
|
||||
* the test_value of a sequence of test_events.
|
||||
*/
|
||||
unsigned int TEST_VALUES[] = {
|
||||
32, 32, 226, 136, 167, 44, 44, 44,
|
||||
226, 136, 167, 32, 32, 32, 32, 32,
|
||||
65, 112, 97, 119, 99, 104, 101, 10,
|
||||
32, 40, 226, 128, 162, 32, 226, 169,
|
||||
138, 32, 226, 128, 162, 41, 32, 32,
|
||||
71, 117, 97, 99, 97, 109, 101, 111,
|
||||
119, 108, 101, 33, 10, /* END */ 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Test event for an event fifo. This particular event contains a single
|
||||
* integer for verifying that events are received in the order expected, and a
|
||||
* chunk of arbitrary padding to ensure the base fifo is capable of supporting
|
||||
* events of arbitrary size.
|
||||
*/
|
||||
typedef union test_event {
|
||||
|
||||
/**
|
||||
* Arbitrary integer test value. This value is primarily intended to allow
|
||||
* unit tests to verify the order of received events matches the order they
|
||||
* were sent.
|
||||
*/
|
||||
unsigned int test_value;
|
||||
|
||||
/**
|
||||
* Arbitrary padding. This member is entirely ignored and is used only to
|
||||
* increase the storage size of this event. A wonky prime value is used
|
||||
* here to help ensure the tests inherently verify that the base fifo
|
||||
* implementation does not somehow depend on power-of-two alignment.
|
||||
*/
|
||||
char padding[73];
|
||||
|
||||
} test_event;
|
||||
|
||||
/**
|
||||
* Test event fifo that extends the guac_fifo base. This event
|
||||
* fifo differs from the base only in that it specifically stores test_events
|
||||
* alongside an array of expected event values.
|
||||
*/
|
||||
typedef struct test_fifo {
|
||||
|
||||
/**
|
||||
* The base fifo implementation.
|
||||
*/
|
||||
guac_fifo base;
|
||||
|
||||
/**
|
||||
* Storage for all event items in this fifo.
|
||||
*/
|
||||
test_event items[TEST_FIFO_MAX_ITEMS];
|
||||
|
||||
/**
|
||||
* A zero-terminated array of all integer values expected to be received as
|
||||
* test events, in the order that they are expected to be received.
|
||||
*/
|
||||
unsigned int* expected_values;
|
||||
|
||||
} test_fifo;
|
||||
|
||||
/**
|
||||
* Initializes the given test_fifo, assigning the given set of expected
|
||||
* values for later reference by unit tests. The pointer to the expected values
|
||||
* MUST remain valid until the text_fifo is destroyed.
|
||||
*
|
||||
* @param fifo
|
||||
* The test_fifo to initialize.
|
||||
*
|
||||
* @param expected_values
|
||||
* The zero-terminated set of expected values to be associated with the
|
||||
* given test_fifo.
|
||||
*/
|
||||
void test_fifo_init(test_fifo* fifo, unsigned int* expected_values) {
|
||||
|
||||
guac_fifo_init((guac_fifo*) fifo, &fifo->items,
|
||||
TEST_FIFO_MAX_ITEMS, sizeof(test_event));
|
||||
|
||||
fifo->expected_values = expected_values;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the given test_fifo, releasing any associated resources. It
|
||||
* is safe to clean up the set of expected values originally provided to
|
||||
* test_fifo_init() after this function has been invoked.
|
||||
*
|
||||
* @param fifo
|
||||
* The test_fifo to destroy.
|
||||
*/
|
||||
void test_fifo_destroy(test_fifo* fifo) {
|
||||
guac_fifo_destroy((guac_fifo*) fifo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread that continuously reads events from the given test_fifo,
|
||||
* verifying that each expected value is read in the correct order.
|
||||
*
|
||||
* @param data
|
||||
* The test_fifo to read from.
|
||||
*
|
||||
* @return
|
||||
* Always NULL.
|
||||
*/
|
||||
static void* queue_read_thread(void* data) {
|
||||
|
||||
test_fifo* fifo = (test_fifo*) data;
|
||||
test_event event;
|
||||
|
||||
/* Continuously read values until zero (end of expected values) is reached */
|
||||
for (unsigned int* current_expected_value = fifo->expected_values;
|
||||
/* Exit condition checked in body of loop*/; current_expected_value++) {
|
||||
|
||||
/* Induce random delays in reading to simulate real-world conditions
|
||||
* that may cause the fifo to fill */
|
||||
guac_timestamp_msleep(rand() % TEST_READ_INTERVAL);
|
||||
|
||||
int retval = guac_fifo_timed_dequeue(
|
||||
(guac_fifo*) fifo, &event, TEST_TIMEOUT);
|
||||
|
||||
/* A value of zero marks the end of the set of expected values, so the
|
||||
* fifo SHOULD fail to read at this point */
|
||||
if (*current_expected_value == 0) {
|
||||
printf(" | END\n");
|
||||
CU_ASSERT_FALSE(retval);
|
||||
break;
|
||||
}
|
||||
|
||||
/* For all other cases, the fifo should succeed in reading the next
|
||||
* event, and the value of that event should match the current value
|
||||
* from the set of expected values */
|
||||
else {
|
||||
printf(" | %i\n", event.test_value);
|
||||
CU_ASSERT_TRUE(retval);
|
||||
CU_ASSERT_EQUAL(event.test_value, *current_expected_value);
|
||||
}
|
||||
|
||||
/* Do not continue waiting for more events if the fifo is timing out
|
||||
* incorrectly */
|
||||
if (!retval)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic base test that sends all values in TEST_VALUES at the given
|
||||
* interval. Values are read by a separate thread that instead reads at
|
||||
* TEST_READ_INTERVAL, allowing the send/receive rates to differ. Timing
|
||||
* between each send/receive attempt is varied randomly but is always bounded
|
||||
* by the relevant interval.
|
||||
*
|
||||
* @param send_interval
|
||||
* The rough number of milliseconds to wait between sending each event. The
|
||||
* true number of milliseconds that elapse between each subsequent send
|
||||
* attempt is varied randomly, with this provided value functioning as an
|
||||
* upper bound.
|
||||
*/
|
||||
static void verify_send_receive(int send_interval) {
|
||||
|
||||
test_fifo fifo;
|
||||
|
||||
/* Create a test fifo that verifies each value within TEST_VALUES is
|
||||
* received in order */
|
||||
test_fifo_init(&fifo, TEST_VALUES);
|
||||
|
||||
/* Both this function and the thread it spawns will log sent/received event
|
||||
* values to STDOUT for sake of debugging and verification */
|
||||
printf("Sent | Received\n"
|
||||
"---- | --------\n");
|
||||
|
||||
/* Spawn thread that can independently wait for events to be flagged */
|
||||
pthread_t test_thread;
|
||||
CU_ASSERT_FALSE_FATAL(pthread_create(&test_thread, NULL, queue_read_thread, &fifo));
|
||||
|
||||
/* Send all test values in order */
|
||||
for (unsigned int* current = TEST_VALUES; *current != 0; current++) {
|
||||
|
||||
/* Pull next test value from TEST_VALUES array */
|
||||
test_event event = {
|
||||
.test_value = *current
|
||||
};
|
||||
|
||||
/* Induce random delays in reading to simulate real-world conditions
|
||||
* that may cause the fifo to fill */
|
||||
if (send_interval)
|
||||
guac_timestamp_msleep(rand() % send_interval);
|
||||
|
||||
printf("%4i |\n", event.test_value);
|
||||
guac_fifo_enqueue((guac_fifo*) &fifo, &event);
|
||||
|
||||
}
|
||||
|
||||
/* All test values have now been sent */
|
||||
printf(" END |\n");
|
||||
|
||||
/* Wait for thread to finish waiting for events */
|
||||
CU_ASSERT_FALSE(pthread_join(test_thread, NULL));
|
||||
|
||||
test_fifo_destroy(&fifo);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the base fifo implementation functions correctly when events
|
||||
* are sent slower than they are read.
|
||||
*/
|
||||
void test_fifo__slow_add() {
|
||||
|
||||
/* Add context for subsequent logging of sent/received values to STDOUT */
|
||||
printf("-------- %s() --------\n", __func__);
|
||||
|
||||
/* Send at half the speed of the reading thread */
|
||||
verify_send_receive(TEST_READ_INTERVAL * 2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the base fifo implementation functions correctly when events
|
||||
* are sent faster than they are read.
|
||||
*/
|
||||
void test_fifo__fast_add() {
|
||||
|
||||
/* Add context for subsequent logging of sent/received values to STDOUT */
|
||||
printf("-------- %s() --------\n", __func__);
|
||||
|
||||
/* Send as quickly as possible (much faster than reading thread) */
|
||||
verify_send_receive(0);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the base fifo implementation functions correctly when events
|
||||
* are sent at roughly the same speed as the reading thread.
|
||||
*/
|
||||
void test_fifo__interleaved() {
|
||||
|
||||
/* Add context for subsequent logging of sent/received values to STDOUT */
|
||||
printf("-------- %s() --------\n", __func__);
|
||||
|
||||
/* Send at roughly same speed as reading thread */
|
||||
verify_send_receive(TEST_READ_INTERVAL);
|
||||
|
||||
}
|
||||
|
||||
168
src/libguac/tests/flag/flag.c
Normal file
168
src/libguac/tests/flag/flag.c
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <guacamole/flag.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* The maximum number of milliseconds to wait for a test event to be flagged.
|
||||
*/
|
||||
#define TEST_TIMEOUT 250
|
||||
|
||||
/**
|
||||
* Arbitrary test event #1.
|
||||
*/
|
||||
#define TEST_EVENT_A 1
|
||||
|
||||
/**
|
||||
* Arbitrary test event #2.
|
||||
*/
|
||||
#define TEST_EVENT_B 2
|
||||
|
||||
/**
|
||||
* Arbitrary test event #3.
|
||||
*/
|
||||
#define TEST_EVENT_C 16
|
||||
|
||||
/**
|
||||
* Arbitrary test event #4.
|
||||
*/
|
||||
#define TEST_EVENT_D 64
|
||||
|
||||
/**
|
||||
* Thread that waits up to TEST_TIMEOUT milliseconds for TEST_EVENT_B or
|
||||
* TEST_EVENT_C to be flagged on a given guac_flag, returning the
|
||||
* result of that wait.
|
||||
*
|
||||
* @param data
|
||||
* The guac_flag to wait on.
|
||||
*
|
||||
* @return
|
||||
* An intptr_t (NOT a pointer) containing the value returned by
|
||||
* guac_flag_timedwait_and_lock().
|
||||
*/
|
||||
static void* flag_wait_thread(void* data) {
|
||||
|
||||
guac_flag* flag = (guac_flag*) data;
|
||||
|
||||
int retval = guac_flag_timedwait_and_lock(flag, TEST_EVENT_B | TEST_EVENT_C, TEST_TIMEOUT);
|
||||
guac_flag_unlock(flag);
|
||||
|
||||
return (void*) ((intptr_t) retval);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits up to TEST_TIMEOUT milliseconds for TEST_EVENT_B or TEST_EVENT_C to be
|
||||
* flagged on the given guac_flag, returning the result of that
|
||||
* wait. If provided, optional sets of flags will be additionally set or
|
||||
* cleared after the wait for the flag has started.
|
||||
*
|
||||
* @param flag
|
||||
* The guac_flag to wait on.
|
||||
*
|
||||
* @param set_flags
|
||||
* The flags that should be set, if any.
|
||||
*
|
||||
* @param clear_flags
|
||||
* The flags that should be cleared, if any.
|
||||
*
|
||||
* @return
|
||||
* The value returned by guac_flag_timedwait_and_lock() after
|
||||
* waiting for TEST_EVENT_B or TEST_EVENT_C to be flagged.
|
||||
*/
|
||||
static int wait_for_flag(guac_flag* flag, int set_flags, int clear_flags) {
|
||||
|
||||
/* Spawn thread that can independently wait for events to be flagged */
|
||||
pthread_t test_thread;
|
||||
CU_ASSERT_FALSE_FATAL(pthread_create(&test_thread, NULL, flag_wait_thread, flag));
|
||||
|
||||
/* Set/clear any requested event flags */
|
||||
if (set_flags) guac_flag_set(flag, set_flags);
|
||||
if (clear_flags) guac_flag_clear(flag, clear_flags);
|
||||
|
||||
/* Wait for thread to finish waiting for events */
|
||||
void* retval;
|
||||
CU_ASSERT_FALSE(pthread_join(test_thread, &retval));
|
||||
|
||||
return (int) ((intptr_t) retval);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a thread waiting on a particular event will NOT be notified if
|
||||
* absolutely zero events ever occur.
|
||||
*/
|
||||
void test_flag__ignore_total_silence() {
|
||||
|
||||
guac_flag test_flag;
|
||||
guac_flag_init(&test_flag);
|
||||
|
||||
/* Verify no interesting events occur if we set zero flags */
|
||||
CU_ASSERT_FALSE(wait_for_flag(&test_flag, 0, 0));
|
||||
|
||||
guac_flag_destroy(&test_flag);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a thread waiting on a particular event will NOT be notified if
|
||||
* that event never occurs, even if other events are occurring.
|
||||
*/
|
||||
void test_flag__ignore_uninteresting_events() {
|
||||
|
||||
guac_flag test_flag;
|
||||
guac_flag_init(&test_flag);
|
||||
|
||||
/* Verify no interesting events occurred if we only fire uninteresting
|
||||
* events */
|
||||
CU_ASSERT_FALSE(wait_for_flag(&test_flag, TEST_EVENT_A, 0));
|
||||
CU_ASSERT_FALSE(wait_for_flag(&test_flag, TEST_EVENT_D, TEST_EVENT_C));
|
||||
CU_ASSERT_FALSE(wait_for_flag(&test_flag, TEST_EVENT_A | TEST_EVENT_D, 0));
|
||||
|
||||
guac_flag_destroy(&test_flag);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a thread waiting on a particular event will be notified when
|
||||
* that event occurs.
|
||||
*/
|
||||
void test_flag__wake_for_interesting_events() {
|
||||
|
||||
guac_flag test_flag;
|
||||
guac_flag_init(&test_flag);
|
||||
|
||||
/* Verify interesting events are reported if fired ... */
|
||||
CU_ASSERT_TRUE(wait_for_flag(&test_flag, TEST_EVENT_B | TEST_EVENT_C, 0));
|
||||
|
||||
/* ... and continue to be reported if they remain set ... */
|
||||
guac_flag_clear(&test_flag, TEST_EVENT_B);
|
||||
CU_ASSERT_TRUE(wait_for_flag(&test_flag, 0, 0));
|
||||
|
||||
/* ... but not if all interesting events have since been cleared */
|
||||
guac_flag_clear(&test_flag, TEST_EVENT_C);
|
||||
CU_ASSERT_FALSE(wait_for_flag(&test_flag, 0, 0));
|
||||
|
||||
guac_flag_destroy(&test_flag);
|
||||
|
||||
}
|
||||
|
||||
70
src/libguac/tests/rect/align.c
Normal file
70
src/libguac/tests/rect/align.c
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <guacamole/rect.h>
|
||||
|
||||
/**
|
||||
* Test which verifies guac_rect_align() properly shifts and resizes rectangles
|
||||
* to fit an NxN grid.
|
||||
*/
|
||||
void test_rect__align() {
|
||||
|
||||
/* A cell size of 4 is 2^4 (16) */
|
||||
const int cell_size = 4;
|
||||
|
||||
guac_rect rect;
|
||||
|
||||
/* Simple case where only the rectangle dimensions need adjustment */
|
||||
guac_rect_init(&rect, 0, 0, 25, 25);
|
||||
guac_rect_align(&rect, cell_size);
|
||||
CU_ASSERT_EQUAL(0, rect.left);
|
||||
CU_ASSERT_EQUAL(0, rect.top);
|
||||
CU_ASSERT_EQUAL(32, rect.right);
|
||||
CU_ASSERT_EQUAL(32, rect.bottom);
|
||||
|
||||
/* More complex case where the rectangle location AND dimensions both need
|
||||
* adjustment */
|
||||
guac_rect_init(&rect, 75, 75, 25, 25);
|
||||
guac_rect_align(&rect, cell_size);
|
||||
CU_ASSERT_EQUAL(64, rect.left);
|
||||
CU_ASSERT_EQUAL(64, rect.top);
|
||||
CU_ASSERT_EQUAL(112, rect.right);
|
||||
CU_ASSERT_EQUAL(112, rect.bottom);
|
||||
|
||||
/* Complex case where the rectangle location AND dimensions both need
|
||||
* adjustment, and the rectangle location is negative */
|
||||
guac_rect_init(&rect, -5, -5, 25, 25);
|
||||
guac_rect_align(&rect, cell_size);
|
||||
CU_ASSERT_EQUAL(-16, rect.left);
|
||||
CU_ASSERT_EQUAL(-16, rect.top);
|
||||
CU_ASSERT_EQUAL(32, rect.right);
|
||||
CU_ASSERT_EQUAL(32, rect.bottom);
|
||||
|
||||
/* Complex case where the rectangle location AND dimensions both need
|
||||
* adjustment, and all rectangle coordinates are negative */
|
||||
guac_rect_init(&rect, -30, -30, 25, 25);
|
||||
guac_rect_align(&rect, cell_size);
|
||||
CU_ASSERT_EQUAL(-32, rect.left);
|
||||
CU_ASSERT_EQUAL(-32, rect.top);
|
||||
CU_ASSERT_EQUAL(0, rect.right);
|
||||
CU_ASSERT_EQUAL(0, rect.bottom);
|
||||
|
||||
}
|
||||
|
||||
42
src/libguac/tests/rect/constrain.c
Normal file
42
src/libguac/tests/rect/constrain.c
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <guacamole/rect.h>
|
||||
|
||||
/**
|
||||
* Test which verifies that guac_rect_constrain() restricts a given rectangle
|
||||
* to arbitrary bounds.
|
||||
*/
|
||||
void test_rect__constrain() {
|
||||
|
||||
guac_rect max;
|
||||
guac_rect rect;
|
||||
|
||||
guac_rect_init(&rect, -10, -10, 110, 110);
|
||||
guac_rect_init(&max, 0, 0, 100, 100);
|
||||
guac_rect_constrain(&rect, &max);
|
||||
|
||||
CU_ASSERT_EQUAL(0, rect.left);
|
||||
CU_ASSERT_EQUAL(0, rect.top);
|
||||
CU_ASSERT_EQUAL(100, rect.right);
|
||||
CU_ASSERT_EQUAL(100, rect.bottom);
|
||||
|
||||
}
|
||||
|
||||
41
src/libguac/tests/rect/extend.c
Normal file
41
src/libguac/tests/rect/extend.c
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <guacamole/rect.h>
|
||||
|
||||
/**
|
||||
* Test which verifies that guac_rect_extend() expands the given rectangle as
|
||||
* necessary to contain at least the given bounds.
|
||||
*/
|
||||
void test_rect__extend() {
|
||||
|
||||
guac_rect max;
|
||||
guac_rect rect;
|
||||
|
||||
guac_rect_init(&rect, 10, 10, 90, 90);
|
||||
guac_rect_init(&max, 0, 0, 100, 100);
|
||||
guac_rect_extend(&rect, &max);
|
||||
CU_ASSERT_EQUAL(0, rect.left);
|
||||
CU_ASSERT_EQUAL(0, rect.top);
|
||||
CU_ASSERT_EQUAL(100, rect.right);
|
||||
CU_ASSERT_EQUAL(100, rect.bottom);
|
||||
|
||||
}
|
||||
|
||||
38
src/libguac/tests/rect/init.c
Normal file
38
src/libguac/tests/rect/init.c
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <guacamole/rect.h>
|
||||
|
||||
/**
|
||||
* Test which verifies rectangle initialization via guac_rect_init().
|
||||
*/
|
||||
void test_rect__init() {
|
||||
|
||||
guac_rect max;
|
||||
|
||||
guac_rect_init(&max, 0, 0, 100, 100);
|
||||
|
||||
CU_ASSERT_EQUAL(0, max.left);
|
||||
CU_ASSERT_EQUAL(0, max.top);
|
||||
CU_ASSERT_EQUAL(100, max.right);
|
||||
CU_ASSERT_EQUAL(100, max.bottom);
|
||||
|
||||
}
|
||||
|
||||
85
src/libguac/tests/rect/intersects.c
Normal file
85
src/libguac/tests/rect/intersects.c
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
#include <guacamole/rect.h>
|
||||
|
||||
/**
|
||||
* Test which verifies intersection testing via guac_rect_intersects().
|
||||
*/
|
||||
void test_rect__intersects() {
|
||||
|
||||
int res;
|
||||
|
||||
guac_rect min;
|
||||
guac_rect rect;
|
||||
|
||||
/* NOTE: This rectangle will extend from (10, 10) inclusive to (20, 20) exclusive */
|
||||
guac_rect_init(&min, 10, 10, 10, 10);
|
||||
|
||||
/* Rectangle that does not intersect by a fair margin */
|
||||
guac_rect_init(&rect, 25, 25, 5, 5);
|
||||
res = guac_rect_intersects(&rect, &min);
|
||||
CU_ASSERT_FALSE(res);
|
||||
|
||||
/* Rectangle that barely does not intersect (one pixel away from intersecting) */
|
||||
guac_rect_init(&rect, 20, 20, 5, 5);
|
||||
res = guac_rect_intersects(&rect, &min);
|
||||
CU_ASSERT_FALSE(res);
|
||||
|
||||
/* Rectangle that intersects by being entirely inside the other */
|
||||
guac_rect_init(&rect, 11, 11, 5, 5);
|
||||
res = guac_rect_intersects(&rect, &min);
|
||||
CU_ASSERT_TRUE(res);
|
||||
|
||||
/* Rectangle that intersects with the upper-left corner */
|
||||
guac_rect_init(&rect, 8, 8, 5, 5);
|
||||
res = guac_rect_intersects(&rect, &min);
|
||||
CU_ASSERT_TRUE(res);
|
||||
|
||||
/* Rectangle that intersects with the lower-right corner */
|
||||
guac_rect_init(&rect, 18, 18, 5, 5);
|
||||
res = guac_rect_intersects(&rect, &min);
|
||||
CU_ASSERT_TRUE(res);
|
||||
|
||||
/* Rectangle that intersects with the uppper-left corner and shares both
|
||||
* the upper and left edges */
|
||||
guac_rect_init(&rect, 10, 10, 5, 5);
|
||||
res = guac_rect_intersects(&rect, &min);
|
||||
CU_ASSERT_TRUE(res);
|
||||
|
||||
/* Rectangle that barely fails to intersect the upper-left corner (one
|
||||
* pixel away) */
|
||||
guac_rect_init(&rect, 5, 10, 5, 5);
|
||||
res = guac_rect_intersects(&rect, &min);
|
||||
CU_ASSERT_FALSE(res);
|
||||
|
||||
/* Rectangle that barely fails to intersect the upper-right corner (one
|
||||
* pixel away) */
|
||||
guac_rect_init(&rect, 20, 10, 5, 5);
|
||||
res = guac_rect_intersects(&rect, &min);
|
||||
CU_ASSERT_FALSE(res);
|
||||
|
||||
/* Rectangle that intersects by entirely containing the other */
|
||||
guac_rect_init(&rect, 5, 5, 20, 20);
|
||||
res = guac_rect_intersects(&rect, &min);
|
||||
CU_ASSERT_TRUE(res);
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user