Skip to content

Commit

Permalink
GUACAMOLE-377: Add convenient, default render thread implementation f…
Browse files Browse the repository at this point in the history
…or guac_display.
  • Loading branch information
mike-jumper committed Sep 28, 2024
1 parent be04aeb commit baec319
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/libguac/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ libguac_la_SOURCES = \
display-plan-combine.c \
display-plan-rect.c \
display-plan-search.c \
display-render-thread.c \
display-worker.c \
encode-jpeg.c \
encode-png.c \
Expand Down
50 changes: 50 additions & 0 deletions src/libguac/display-priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,56 @@
*/
#define GUAC_DISPLAY_RENDER_STATE_FRAME_NOT_IN_PROGRESS 2

/**
* Bitwise flag that is set on the state of a guac_display_render_thread when
* the thread should be stopped.
*/
#define GUAC_DISPLAY_RENDER_THREAD_STATE_STOPPING 1

/**
* Bitwise flag that is set on the state of a guac_display_render_thread when
* visible, graphical changes have been made.
*/
#define GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED 2

/**
* Bitwise flag that is set on the state of a guac_display_render_thread when
* a frame boundary has been reached.
*/
#define GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY 4

struct guac_display_render_thread {

/**
* The display this render thread should render to.
*/
guac_display* display;

/**
* The actual underlying POSIX thread.
*/
pthread_t thread;

/**
* Flag representing render state. This flag is used to store whether the
* render thread is stopping and whether the current frame has been
* modified or is ready.
*
* @see GUAC_DISPLAY_RENDER_THREAD_STATE_STOPPING
* @see GUAC_DISPLAY_RENDER_THREAD_FRAME_MODIFIED
* @see GUAC_DISPLAY_RENDER_THREAD_FRAME_READY
*/
guac_flag state;

/**
* The number of frames that have been explicitly marked as ready since the
* last frame sent. This will be zero if explicit frame boundaries are not
* currently being used.
*/
unsigned int frames;

};

/**
* 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
Expand Down
173 changes: 173 additions & 0 deletions src/libguac/display-render-thread.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* 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-priv.h"
#include "guacamole/client.h"
#include "guacamole/display.h"
#include "guacamole/flag.h"
#include "guacamole/mem.h"
#include "guacamole/timestamp.h"

/**
* The maximum duration of a frame in milliseconds. This ensures we at least
* meet a reasonable minimum framerate in the case that the RDP server provides
* no frame boundaries and streams data continuously enough that frame
* boundaries are not discernable through timing.
*
* The current value of 100 is equivalent to 10 frames per second.
*/
#define GUAC_DISPLAY_RENDER_THREAD_MAX_FRAME_DURATION 100

/**
* The minimum duration of a frame in milliseconds. This ensures we don't start
* flushing a ton of tiny frames if an RDP server provides no frame boundaries
* and streams data inconsistently enough that timing would suggest frame
* boundaries in the middle of a frame.
*
* The current value of 10 is equivalent to 100 frames per second.
*/
#define GUAC_DISPLAY_RENDER_THREAD_MIN_FRAME_DURATION 10

/**
* The start routine for the display render thread, consisting of a single
* render loop. The render loop will proceed until signalled to stop,
* determining frame boundaries via a combination of heuristics and explicit
* marking (if available).
*
* @param data
* The guac_display_render_thread structure containing the render thread
* state.
*
* @return
* Always NULL.
*/
static void* guac_display_render_loop(void* data) {

guac_display_render_thread* render_thread = (guac_display_render_thread*) data;
guac_display* display = render_thread->display;

for (;;) {

/* Wait indefinitely for any change to the frame state */
guac_flag_wait_and_lock(&render_thread->state,
GUAC_DISPLAY_RENDER_THREAD_STATE_STOPPING
| GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY
| GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED);

/* Bail out immediately upon upcoming disconnect */
if (render_thread->state.value & GUAC_DISPLAY_RENDER_THREAD_STATE_STOPPING) {
guac_flag_unlock(&render_thread->state);
return NULL;
}

int rendered_frames = 0;

/* Lacking explicit frame boundaries, handle the change in frame state,
* continuing to accumulate frame modifications while still within
* heuristically determined frame boundaries */
int allowed_wait = 0;
guac_timestamp frame_start = guac_timestamp_current();
do {

/* Continue processing messages for up to a reasonable
* minimum framerate without an explicit frame boundary
* indicating that the frame is not yet complete */
int frame_duration = guac_timestamp_current() - frame_start;
if (frame_duration > GUAC_DISPLAY_RENDER_THREAD_MAX_FRAME_DURATION) {
guac_flag_unlock(&render_thread->state);
break;
}

/* Use explicit frame boundaries whenever available */
if (render_thread->state.value & GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY) {

rendered_frames = render_thread->frames;
render_thread->frames = 0;

guac_flag_clear(&render_thread->state,
GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY
| GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED);
guac_flag_unlock(&render_thread->state);
break;

}

/* Do not exceed a reasonable maximum framerate without an
* explicit frame boundary terminating the frame early */
allowed_wait = GUAC_DISPLAY_RENDER_THREAD_MIN_FRAME_DURATION - frame_duration;
if (allowed_wait < 0)
allowed_wait = 0;

/* Wait for further modifications or other changes to frame state */

guac_flag_clear(&render_thread->state, GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED);
guac_flag_unlock(&render_thread->state);

} while (guac_flag_timedwait_and_lock(&render_thread->state,
GUAC_DISPLAY_RENDER_THREAD_STATE_STOPPING
| GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY
| GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED, allowed_wait));

guac_display_end_multiple_frames(display, rendered_frames);

}

return NULL;

}

guac_display_render_thread* guac_display_render_thread_create(guac_display* display) {

guac_display_render_thread* render_thread = guac_mem_alloc(sizeof(guac_display_render_thread));

guac_flag_init(&render_thread->state);
render_thread->display = display;
render_thread->frames = 0;

/* Start render thread (this will immediately begin blocking until frame
* modification or readiness is signalled) */
pthread_create(&render_thread->thread, NULL, guac_display_render_loop, render_thread);

return render_thread;

}

void guac_display_render_thread_notify_modified(guac_display_render_thread* render_thread) {
guac_flag_set(&render_thread->state, GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_MODIFIED);
}

void guac_display_render_thread_notify_frame(guac_display_render_thread* render_thread) {
guac_flag_set_and_lock(&render_thread->state, GUAC_DISPLAY_RENDER_THREAD_STATE_FRAME_READY);
render_thread->frames++;
guac_flag_unlock(&render_thread->state);
}

void guac_display_render_thread_destroy(guac_display_render_thread* render_thread) {

/* Clean up render thread after signalling it to stop */
guac_flag_set(&render_thread->state, GUAC_DISPLAY_RENDER_THREAD_STATE_STOPPING);
pthread_join(render_thread->thread, NULL);

/* Free remaining resources */
guac_flag_destroy(&render_thread->state);
guac_mem_free(render_thread);

}

6 changes: 6 additions & 0 deletions src/libguac/guacamole/display-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@
*/
typedef struct guac_display guac_display;

/**
* Opaque representation of a thread that continuously renders updated
* graphical data to the remote display.
*/
typedef struct guac_display_render_thread guac_display_render_thread;

/**
* 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
Expand Down
65 changes: 65 additions & 0 deletions src/libguac/guacamole/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,71 @@ guac_display_layer_cairo_context* guac_display_layer_open_cairo(guac_display_lay
*/
void guac_display_layer_close_cairo(guac_display_layer* layer, guac_display_layer_cairo_context* context);

/**
* Creates and starts a rendering thread for the given guac_display. The
* returned thread must eventually be freed with a call to
* guac_display_render_thread_destroy(). The rendering thread simplifies
* efficient handling of guac_display, but is not a requirement. If your use
* case is not well-served by the provided render thread, you can use your own
* render loop, thread, etc.
*
* The render thread will finalize and send frames after being notified that
* graphical changes have occurred, heuristically determining frame boundaries
* based on the lull in modifications that occurs between frames. In the event
* that modifications are made continuously without pause, the render thread
* will finalize and send frames at a reasonable minimum rate.
*
* If explicit frame boundaries are available, the render thread can be
* notified of these boundaries. Explicit boundaries will be preferred by the
* render thread over heuristically determined boundaries.
*
* @see guac_display_render_thread_notify_modified()
* @see guac_display_render_thread_notify_frame()
*
* @param display
* The display to start a rendering thread for.
*
* @return
* An opaque reference to the created, running rendering thread. This
* thread must be eventually freed through a call to
* guac_display_render_thread_destroy().
*/
guac_display_render_thread* guac_display_render_thread_create(guac_display* display);

/**
* Notifies the given render thread that the graphical state of the display has
* been modified in some visible way. The changes will then be included in a
* future frame by the render thread once a frame boundary has been reached.
* If frame boundaries are currently being determined heuristically by the
* render thread, it is the timing of calls to this function that determine the
* boundaries of frames.
*
* @param render_thread
* The render thread to notify of display modifications.
*/
void guac_display_render_thread_notify_modified(guac_display_render_thread* render_thread);

/**
* Notifies the given render thread that a frame boundary has been reached.
* Further heuristic detection of frame boundaries by the render thread will
* stop, and all further frames must be marked through calls to this function.
*
* @param render_thread
* The render thread to notify of an explicit frame boundary.
*/
void guac_display_render_thread_notify_frame(guac_display_render_thread* render_thread);

/**
* Safely stops and frees all resources associated with the given render
* thread. The provided pointer to the render thread is no longer valid after a
* call to this function. The guac_display associated with the render thread is
* unaffected.
*
* @param render_thread
* The render thread to stop and free.
*/
void guac_display_render_thread_destroy(guac_display_render_thread* render_thread);

/**
* @}
*/
Expand Down

0 comments on commit baec319

Please sign in to comment.