Files
grd/grd-rdp-renderer.c
2026-02-13 13:06:50 +09:00

1202 lines
38 KiB
C

/*
* Copyright (C) 2023 Pascal Nowack
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "config.h"
#include "grd-rdp-renderer.h"
#include "grd-context.h"
#include "grd-encode-session.h"
#include "grd-hwaccel-nvidia.h"
#include "grd-hwaccel-vaapi.h"
#include "grd-hwaccel-vulkan.h"
#include "grd-rdp-dvc-graphics-pipeline.h"
#include "grd-rdp-frame.h"
#include "grd-rdp-private.h"
#include "grd-rdp-render-context.h"
#include "grd-rdp-server.h"
#include "grd-rdp-surface.h"
#include "grd-rdp-surface-renderer.h"
#include "grd-rdp-sw-encoder-ca.h"
#include "grd-rdp-view-creator.h"
#include "grd-session-rdp.h"
enum
{
INHIBITION_DONE,
GFX_INITABLE,
N_SIGNALS
};
static guint signals[N_SIGNALS];
struct _GrdRdpRenderer
{
GObject parent;
gboolean in_shutdown;
GrdSessionRdp *session_rdp;
GrdVkPhysicalDevice *vk_physical_device;
GrdVkDevice *vk_device;
GrdHwAccelVaapi *hwaccel_vaapi;
GrdRdpSwEncoderCa *encoder_ca;
GThread *graphics_thread;
GMainContext *graphics_context;
gboolean graphics_subsystem_failed;
gboolean stop_rendering;
GCond stop_rendering_cond;
gboolean rendering_inhibited;
gboolean output_suppressed;
gboolean pending_gfx_init;
gboolean pending_gfx_graphics_reset;
GMutex surface_renderers_mutex;
GHashTable *surface_renderer_table;
GMutex inhibition_mutex;
GHashTable *render_context_table;
GHashTable *acquired_render_contexts;
GHashTable *render_resource_mappings;
GSource *surface_disposal_source;
GAsyncQueue *disposal_queue;
GSource *surface_render_source;
GHashTable *queued_frames;
GMutex view_creations_mutex;
GHashTable *finished_view_creations;
GMutex frame_encodings_mutex;
GHashTable *finished_frame_encodings;
};
G_DEFINE_TYPE (GrdRdpRenderer, grd_rdp_renderer, G_TYPE_OBJECT)
GMainContext *
grd_rdp_renderer_get_graphics_context (GrdRdpRenderer *renderer)
{
return renderer->graphics_context;
}
GrdSessionRdp *
grd_rdp_renderer_get_session (GrdRdpRenderer *renderer)
{
return renderer->session_rdp;
}
GrdVkDevice *
grd_rdp_renderer_get_vk_device (GrdRdpRenderer *renderer)
{
return renderer->vk_device;
}
GrdHwAccelVaapi *
grd_rdp_renderer_get_hwaccel_vaapi (GrdRdpRenderer *renderer)
{
return renderer->hwaccel_vaapi;
}
GrdRdpSwEncoderCa *
grd_rdp_renderer_get_encoder_ca (GrdRdpRenderer *renderer)
{
return renderer->encoder_ca;
}
static void
trigger_render_sources (GrdRdpRenderer *renderer)
{
GrdRdpSurfaceRenderer *surface_renderer = NULL;
g_autoptr (GMutexLocker) locker = NULL;
GHashTableIter iter;
locker = g_mutex_locker_new (&renderer->surface_renderers_mutex);
g_hash_table_iter_init (&iter, renderer->surface_renderer_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &surface_renderer))
grd_rdp_surface_renderer_trigger_render_source (surface_renderer);
}
void
grd_rdp_renderer_update_output_suppression_state (GrdRdpRenderer *renderer,
gboolean suppress_output)
{
renderer->output_suppressed = suppress_output;
if (!renderer->output_suppressed)
trigger_render_sources (renderer);
}
static void
stop_rendering (GrdRdpRenderer *renderer)
{
g_mutex_lock (&renderer->inhibition_mutex);
renderer->stop_rendering = TRUE;
while (g_hash_table_size (renderer->acquired_render_contexts) > 0)
g_cond_wait (&renderer->stop_rendering_cond, &renderer->inhibition_mutex);
g_mutex_unlock (&renderer->inhibition_mutex);
}
void
grd_rdp_renderer_invoke_shutdown (GrdRdpRenderer *renderer)
{
g_assert (renderer->graphics_context);
stop_rendering (renderer);
renderer->in_shutdown = TRUE;
g_main_context_wakeup (renderer->graphics_context);
g_clear_pointer (&renderer->graphics_thread, g_thread_join);
}
static gpointer
graphics_thread_func (gpointer data)
{
GrdRdpRenderer *renderer = data;
GrdRdpServer *rdp_server = grd_session_rdp_get_server (renderer->session_rdp);
GrdHwAccelNvidia *hwaccel_nvidia =
grd_rdp_server_get_hwaccel_nvidia (rdp_server);
if (hwaccel_nvidia)
grd_hwaccel_nvidia_push_cuda_context (hwaccel_nvidia);
while (!renderer->in_shutdown)
g_main_context_iteration (renderer->graphics_context, TRUE);
if (hwaccel_nvidia)
grd_hwaccel_nvidia_pop_cuda_context (hwaccel_nvidia);
return NULL;
}
static gboolean
maybe_initialize_hardware_acceleration (GrdRdpRenderer *renderer,
GrdHwAccelVulkan *hwaccel_vulkan)
{
GrdVkPhysicalDevice *vk_physical_device;
g_autoptr (GError) error = NULL;
vk_physical_device =
grd_hwaccel_vulkan_acquire_physical_device (hwaccel_vulkan, &error);
if (!vk_physical_device)
{
g_message ("[HWAccel.Vulkan] Could not acquire Vulkan physical "
"device: %s", error->message);
return TRUE;
}
renderer->vk_physical_device = vk_physical_device;
renderer->vk_device = grd_hwaccel_vulkan_acquire_device (hwaccel_vulkan,
vk_physical_device,
&error);
if (!renderer->vk_device)
{
g_warning ("[RDP] Failed to acquire Vulkan device: %s",
error->message);
return FALSE;
}
renderer->hwaccel_vaapi = grd_hwaccel_vaapi_new (renderer->vk_device,
&error);
if (!renderer->hwaccel_vaapi)
{
g_message ("[RDP] Did not initialize VAAPI: %s", error->message);
g_clear_object (&renderer->vk_device);
}
return TRUE;
}
gboolean
grd_rdp_renderer_start (GrdRdpRenderer *renderer)
{
GrdRdpServer *rdp_server = grd_session_rdp_get_server (renderer->session_rdp);
GrdHwAccelVulkan *hwaccel_vulkan =
grd_rdp_server_get_hwaccel_vulkan (rdp_server);
g_autoptr (GError) error = NULL;
if (hwaccel_vulkan &&
!maybe_initialize_hardware_acceleration (renderer, hwaccel_vulkan))
return FALSE;
renderer->encoder_ca = grd_rdp_sw_encoder_ca_new (&error);
if (!renderer->encoder_ca)
{
g_warning ("[RDP] Failed to create fallback software renderer: %s",
error->message);
return FALSE;
}
renderer->graphics_thread = g_thread_new ("RDP graphics thread",
graphics_thread_func,
renderer);
return TRUE;
}
static GrdRdpDvcGraphicsPipeline *
graphics_pipeline_from_renderer (GrdRdpRenderer *renderer)
{
return grd_session_rdp_get_graphics_pipeline (renderer->session_rdp);
}
void
grd_rdp_renderer_notify_new_desktop_layout (GrdRdpRenderer *renderer,
uint32_t desktop_width,
uint32_t desktop_height)
{
rdpContext *rdp_context =
grd_session_rdp_get_rdp_context (renderer->session_rdp);
rdpSettings *rdp_settings = rdp_context->settings;
g_assert (graphics_pipeline_from_renderer (renderer));
renderer->pending_gfx_graphics_reset = TRUE;
freerdp_settings_set_uint32 (rdp_settings, FreeRDP_DesktopWidth,
desktop_width);
freerdp_settings_set_uint32 (rdp_settings, FreeRDP_DesktopHeight,
desktop_height);
}
void
grd_rdp_renderer_notify_graphics_pipeline_ready (GrdRdpRenderer *renderer)
{
g_debug ("[RDP] Renderer: Received Graphics Pipeline ready notification");
renderer->pending_gfx_graphics_reset = TRUE;
renderer->pending_gfx_init = FALSE;
trigger_render_sources (renderer);
}
void
grd_rdp_renderer_notify_graphics_pipeline_reset (GrdRdpRenderer *renderer)
{
gboolean gfx_initable = FALSE;
g_debug ("[RDP] Renderer: Received Graphics Pipeline reset notification");
g_mutex_lock (&renderer->inhibition_mutex);
renderer->pending_gfx_init = TRUE;
if (g_hash_table_size (renderer->acquired_render_contexts) == 0)
gfx_initable = TRUE;
g_mutex_unlock (&renderer->inhibition_mutex);
if (gfx_initable)
g_signal_emit (renderer, signals[GFX_INITABLE], 0);
}
void
grd_rdp_renderer_inhibit_rendering (GrdRdpRenderer *renderer)
{
gboolean inhibition_done = FALSE;
g_mutex_lock (&renderer->inhibition_mutex);
renderer->rendering_inhibited = TRUE;
if (g_hash_table_size (renderer->acquired_render_contexts) == 0)
inhibition_done = TRUE;
g_mutex_unlock (&renderer->inhibition_mutex);
if (inhibition_done)
g_signal_emit (renderer, signals[INHIBITION_DONE], 0);
}
void
grd_rdp_renderer_uninhibit_rendering (GrdRdpRenderer *renderer)
{
renderer->rendering_inhibited = FALSE;
trigger_render_sources (renderer);
}
GrdRdpSurface *
grd_rdp_renderer_try_acquire_surface (GrdRdpRenderer *renderer,
uint32_t refresh_rate)
{
GrdRdpSurface *rdp_surface;
GrdRdpSurfaceRenderer *surface_renderer;
rdp_surface = grd_rdp_surface_new (renderer);
if (!rdp_surface)
return NULL;
surface_renderer = grd_rdp_surface_renderer_new (rdp_surface, renderer,
refresh_rate);
grd_rdp_surface_attach_surface_renderer (rdp_surface, surface_renderer);
g_mutex_lock (&renderer->surface_renderers_mutex);
g_hash_table_insert (renderer->surface_renderer_table,
rdp_surface, surface_renderer);
g_mutex_unlock (&renderer->surface_renderers_mutex);
return rdp_surface;
}
void
grd_rdp_renderer_release_surface (GrdRdpRenderer *renderer,
GrdRdpSurface *rdp_surface)
{
g_assert (rdp_surface);
g_async_queue_push (renderer->disposal_queue, rdp_surface);
g_source_set_ready_time (renderer->surface_disposal_source, 0);
}
static void
invalidate_surfaces (GrdRdpRenderer *renderer,
gboolean locked)
{
GrdRdpSurfaceRenderer *surface_renderer = NULL;
g_autoptr (GMutexLocker) locker = NULL;
GHashTableIter iter;
locker = g_mutex_locker_new (&renderer->surface_renderers_mutex);
g_hash_table_iter_init (&iter, renderer->surface_renderer_table);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &surface_renderer))
{
if (!locked)
grd_rdp_surface_renderer_invalidate_surface_unlocked (surface_renderer);
else
grd_rdp_surface_renderer_invalidate_surface (surface_renderer);
}
}
static void
clear_render_contexts (GrdRdpRenderer *renderer,
gboolean locked)
{
g_assert (g_hash_table_size (renderer->acquired_render_contexts) == 0);
g_assert (g_hash_table_size (renderer->queued_frames) == 0);
invalidate_surfaces (renderer, locked);
g_hash_table_remove_all (renderer->render_resource_mappings);
g_hash_table_remove_all (renderer->render_context_table);
}
static void
maybe_reset_graphics (GrdRdpRenderer *renderer)
{
GrdRdpDvcGraphicsPipeline *graphics_pipeline =
graphics_pipeline_from_renderer (renderer);
rdpContext *rdp_context =
grd_session_rdp_get_rdp_context (renderer->session_rdp);
rdpSettings *rdp_settings = rdp_context->settings;
uint32_t desktop_width =
freerdp_settings_get_uint32 (rdp_settings, FreeRDP_DesktopWidth);
uint32_t desktop_height =
freerdp_settings_get_uint32 (rdp_settings, FreeRDP_DesktopHeight);
g_autofree MONITOR_DEF *monitor_defs = NULL;
uint32_t n_monitors;
uint32_t i;
if (!renderer->pending_gfx_graphics_reset)
return;
clear_render_contexts (renderer, FALSE);
n_monitors = freerdp_settings_get_uint32 (rdp_settings, FreeRDP_MonitorCount);
g_assert (n_monitors > 0);
monitor_defs = g_new0 (MONITOR_DEF, n_monitors);
for (i = 0; i < n_monitors; ++i)
{
const rdpMonitor *monitor =
freerdp_settings_get_pointer_array (rdp_settings,
FreeRDP_MonitorDefArray, i);
MONITOR_DEF *monitor_def = &monitor_defs[i];
monitor_def->left = monitor->x;
monitor_def->top = monitor->y;
monitor_def->right = monitor_def->left + monitor->width - 1;
monitor_def->bottom = monitor_def->top + monitor->height - 1;
if (monitor->is_primary)
monitor_def->flags = MONITOR_PRIMARY;
}
grd_rdp_dvc_graphics_pipeline_reset_graphics (graphics_pipeline,
desktop_width, desktop_height,
monitor_defs, n_monitors);
renderer->pending_gfx_graphics_reset = FALSE;
}
static void
destroy_render_context_locked (GrdRdpRenderer *renderer,
GrdRdpSurface *rdp_surface)
{
GrdRdpRenderContext *render_context = NULL;
if (!g_hash_table_lookup_extended (renderer->render_context_table,
rdp_surface,
NULL, (gpointer *) &render_context))
return;
g_assert (render_context);
g_assert (!g_hash_table_contains (renderer->acquired_render_contexts,
render_context));
g_assert (!g_hash_table_contains (renderer->queued_frames,
render_context));
g_hash_table_remove (renderer->render_resource_mappings, render_context);
g_hash_table_remove (renderer->render_context_table, rdp_surface);
}
static void
destroy_render_context (GrdRdpRenderer *renderer,
GrdRdpSurface *rdp_surface)
{
g_autoptr (GMutexLocker) locker = NULL;
locker = g_mutex_locker_new (&renderer->inhibition_mutex);
destroy_render_context_locked (renderer, rdp_surface);
}
static GrdRdpRenderContext *
render_context_ref (GrdRdpRenderer *renderer,
GrdRdpRenderContext *render_context)
{
uint32_t *ref_count = NULL;
if (g_hash_table_lookup_extended (renderer->acquired_render_contexts,
render_context,
NULL, (gpointer *) &ref_count))
{
g_assert (*ref_count > 0);
++(*ref_count);
return render_context;
}
ref_count = g_new0 (uint32_t, 1);
*ref_count = 1;
g_hash_table_insert (renderer->acquired_render_contexts,
render_context, ref_count);
return render_context;
}
static void
render_context_unref (GrdRdpRenderer *renderer,
GrdRdpRenderContext *render_context)
{
uint32_t *ref_count = NULL;
if (!g_hash_table_lookup_extended (renderer->acquired_render_contexts,
render_context,
NULL, (gpointer *) &ref_count))
g_assert_not_reached ();
g_assert (*ref_count > 0);
--(*ref_count);
if (*ref_count == 0)
g_hash_table_remove (renderer->acquired_render_contexts, render_context);
}
static void
handle_graphics_subsystem_failure (GrdRdpRenderer *renderer)
{
renderer->graphics_subsystem_failed = TRUE;
renderer->stop_rendering = TRUE;
grd_session_rdp_notify_error (renderer->session_rdp,
GRD_SESSION_RDP_ERROR_GRAPHICS_SUBSYSTEM_FAILED);
}
GrdRdpRenderContext *
grd_rdp_renderer_try_acquire_render_context (GrdRdpRenderer *renderer,
GrdRdpSurface *rdp_surface,
GrdRdpAcquireContextFlags flags)
{
GrdRdpRenderContext *render_context = NULL;
g_autoptr (GMutexLocker) locker = NULL;
g_assert (!((flags & GRD_RDP_ACQUIRE_CONTEXT_FLAG_FORCE_RESET) &&
(flags & GRD_RDP_ACQUIRE_CONTEXT_FLAG_RETAIN_OR_NULL)));
locker = g_mutex_locker_new (&renderer->inhibition_mutex);
if (renderer->stop_rendering ||
renderer->rendering_inhibited ||
renderer->pending_gfx_init ||
renderer->output_suppressed)
return NULL;
maybe_reset_graphics (renderer);
if (flags & GRD_RDP_ACQUIRE_CONTEXT_FLAG_FORCE_RESET)
destroy_render_context_locked (renderer, rdp_surface);
if (g_hash_table_lookup_extended (renderer->render_context_table, rdp_surface,
NULL, (gpointer *) &render_context))
return render_context_ref (renderer, render_context);
if (flags & GRD_RDP_ACQUIRE_CONTEXT_FLAG_RETAIN_OR_NULL)
return NULL;
render_context = grd_rdp_render_context_new (renderer, rdp_surface);
if (!render_context)
{
handle_graphics_subsystem_failure (renderer);
return NULL;
}
g_hash_table_insert (renderer->render_context_table,
rdp_surface, render_context);
g_hash_table_insert (renderer->render_resource_mappings,
render_context, g_hash_table_new (NULL, NULL));
return render_context_ref (renderer, render_context);
}
void
grd_rdp_renderer_release_render_context (GrdRdpRenderer *renderer,
GrdRdpRenderContext *render_context)
{
gboolean inhibition_done = FALSE;
gboolean gfx_initable = FALSE;
g_mutex_lock (&renderer->inhibition_mutex);
render_context_unref (renderer, render_context);
if (renderer->stop_rendering &&
g_hash_table_size (renderer->acquired_render_contexts) == 0)
g_cond_signal (&renderer->stop_rendering_cond);
if (renderer->rendering_inhibited &&
g_hash_table_size (renderer->acquired_render_contexts) == 0)
inhibition_done = TRUE;
if (renderer->pending_gfx_init &&
g_hash_table_size (renderer->acquired_render_contexts) == 0)
gfx_initable = TRUE;
g_mutex_unlock (&renderer->inhibition_mutex);
if (inhibition_done)
g_signal_emit (renderer, signals[INHIBITION_DONE], 0);
if (gfx_initable)
g_signal_emit (renderer, signals[GFX_INITABLE], 0);
}
void
grd_rdp_renderer_clear_render_contexts (GrdRdpRenderer *renderer)
{
clear_render_contexts (renderer, TRUE);
}
static void
queue_prepared_frame (GrdRdpRenderer *renderer,
GrdRdpRenderContext *render_context,
GrdRdpFrame *rdp_frame)
{
GHashTable *acquired_resources = NULL;
GrdRdpViewCreator *view_creator;
if (!g_hash_table_lookup_extended (renderer->render_resource_mappings,
render_context,
NULL, (gpointer *) &acquired_resources))
g_assert_not_reached ();
view_creator = grd_rdp_render_context_get_view_creator (render_context);
g_assert (!g_hash_table_contains (acquired_resources, view_creator));
grd_rdp_frame_notify_picked_up (rdp_frame);
g_hash_table_insert (acquired_resources, view_creator, rdp_frame);
g_mutex_lock (&renderer->view_creations_mutex);
g_hash_table_add (renderer->finished_view_creations, rdp_frame);
g_mutex_unlock (&renderer->view_creations_mutex);
}
void
grd_rdp_renderer_submit_frame (GrdRdpRenderer *renderer,
GrdRdpRenderContext *render_context,
GrdRdpFrame *rdp_frame)
{
grd_rdp_frame_set_renderer (rdp_frame, renderer);
if (grd_rdp_frame_has_valid_view (rdp_frame))
queue_prepared_frame (renderer, render_context, rdp_frame);
else
g_hash_table_insert (renderer->queued_frames, render_context, rdp_frame);
g_source_set_ready_time (renderer->surface_render_source, 0);
}
gboolean
grd_rdp_renderer_render_frame (GrdRdpRenderer *renderer,
GrdRdpSurface *rdp_surface,
GrdRdpRenderContext *render_context,
GrdRdpLegacyBuffer *buffer)
{
GrdRdpDvcGraphicsPipeline *graphics_pipeline =
graphics_pipeline_from_renderer (renderer);
return grd_rdp_dvc_graphics_pipeline_refresh_gfx (graphics_pipeline,
rdp_surface, render_context,
buffer);
}
GrdRdpRenderer *
grd_rdp_renderer_new (GrdSessionRdp *session_rdp)
{
GrdRdpRenderer *renderer;
renderer = g_object_new (GRD_TYPE_RDP_RENDERER, NULL);
renderer->session_rdp = session_rdp;
return renderer;
}
static gboolean
dispose_surfaces (gpointer user_data)
{
GrdRdpRenderer *renderer = user_data;
GrdRdpSurface *rdp_surface;
while ((rdp_surface = g_async_queue_try_pop (renderer->disposal_queue)))
{
destroy_render_context (renderer, rdp_surface);
g_mutex_lock (&renderer->surface_renderers_mutex);
g_hash_table_remove (renderer->surface_renderer_table, rdp_surface);
g_mutex_unlock (&renderer->surface_renderers_mutex);
grd_rdp_surface_free (rdp_surface);
}
return G_SOURCE_CONTINUE;
}
static void
grd_rdp_renderer_dispose (GObject *object)
{
GrdRdpRenderer *renderer = GRD_RDP_RENDERER (object);
grd_rdp_renderer_invoke_shutdown (renderer);
if (renderer->acquired_render_contexts)
g_assert (g_hash_table_size (renderer->acquired_render_contexts) == 0);
if (renderer->render_resource_mappings)
g_assert (g_hash_table_size (renderer->render_resource_mappings) == 0);
if (renderer->queued_frames)
g_assert (g_hash_table_size (renderer->queued_frames) == 0);
if (renderer->finished_view_creations)
g_assert (g_hash_table_size (renderer->finished_view_creations) == 0);
if (renderer->finished_frame_encodings)
g_assert (g_hash_table_size (renderer->finished_frame_encodings) == 0);
if (renderer->surface_render_source)
{
g_source_destroy (renderer->surface_render_source);
g_clear_pointer (&renderer->surface_render_source, g_source_unref);
}
if (renderer->surface_disposal_source)
{
g_source_destroy (renderer->surface_disposal_source);
g_clear_pointer (&renderer->surface_disposal_source, g_source_unref);
}
g_clear_pointer (&renderer->graphics_context, g_main_context_unref);
dispose_surfaces (renderer);
g_clear_pointer (&renderer->finished_frame_encodings, g_hash_table_unref);
g_clear_pointer (&renderer->finished_view_creations, g_hash_table_unref);
g_clear_pointer (&renderer->queued_frames, g_hash_table_unref);
g_clear_pointer (&renderer->render_resource_mappings, g_hash_table_unref);
g_clear_pointer (&renderer->acquired_render_contexts, g_hash_table_unref);
g_clear_pointer (&renderer->render_context_table, g_hash_table_unref);
g_clear_pointer (&renderer->disposal_queue, g_async_queue_unref);
g_assert (g_hash_table_size (renderer->surface_renderer_table) == 0);
g_clear_object (&renderer->encoder_ca);
g_clear_object (&renderer->hwaccel_vaapi);
g_clear_object (&renderer->vk_device);
g_clear_object (&renderer->vk_physical_device);
G_OBJECT_CLASS (grd_rdp_renderer_parent_class)->dispose (object);
}
static void
grd_rdp_renderer_finalize (GObject *object)
{
GrdRdpRenderer *renderer = GRD_RDP_RENDERER (object);
g_mutex_clear (&renderer->frame_encodings_mutex);
g_mutex_clear (&renderer->view_creations_mutex);
g_cond_clear (&renderer->stop_rendering_cond);
g_mutex_clear (&renderer->inhibition_mutex);
g_mutex_clear (&renderer->surface_renderers_mutex);
g_clear_pointer (&renderer->surface_renderer_table, g_hash_table_unref);
G_OBJECT_CLASS (grd_rdp_renderer_parent_class)->finalize (object);
}
static void
release_acquired_resource (GrdRdpRenderer *renderer,
GrdRdpRenderContext *render_context,
gpointer resource)
{
GHashTable *acquired_resources = NULL;
if (!g_hash_table_lookup_extended (renderer->render_resource_mappings,
render_context,
NULL, (gpointer *) &acquired_resources))
g_assert_not_reached ();
if (!g_hash_table_remove (acquired_resources, resource))
g_assert_not_reached ();
}
static GList *
fetch_rendered_frames (GrdRdpRenderer *renderer)
{
g_autoptr (GMutexLocker) locker = NULL;
GrdRdpFrame *rdp_frame = NULL;
GHashTableIter iter;
GList *rendered_frames;
locker = g_mutex_locker_new (&renderer->frame_encodings_mutex);
g_hash_table_iter_init (&iter, renderer->finished_frame_encodings);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &rdp_frame))
{
GrdRdpRenderContext *render_context =
grd_rdp_frame_get_render_context (rdp_frame);
GrdEncodeSession *encode_session =
grd_rdp_render_context_get_encode_session (render_context);
release_acquired_resource (renderer, render_context, encode_session);
if (!grd_rdp_frame_get_bitstreams (rdp_frame))
g_hash_table_iter_remove (&iter);
}
rendered_frames =
g_hash_table_get_values (renderer->finished_frame_encodings);
g_hash_table_steal_all (renderer->finished_frame_encodings);
return rendered_frames;
}
static void
on_bitstream_locked (GrdEncodeSession *encode_session,
GrdBitstream *bitstream,
gpointer user_data,
GError *error)
{
GrdRdpFrame *rdp_frame = user_data;
GrdRdpRenderer *renderer = grd_rdp_frame_get_renderer (rdp_frame);
if (bitstream)
{
GList *bitstreams = grd_rdp_frame_get_bitstreams (rdp_frame);
bitstreams = g_list_append (bitstreams, bitstream);
grd_rdp_frame_set_bitstreams (rdp_frame, bitstreams);
}
else
{
g_warning ("[RDP] Failed to lock bitstream: %s", error->message);
}
g_mutex_lock (&renderer->frame_encodings_mutex);
if (!grd_encode_session_has_pending_frames (encode_session))
g_hash_table_add (renderer->finished_frame_encodings, rdp_frame);
if (!bitstream)
renderer->graphics_subsystem_failed = TRUE;
g_mutex_unlock (&renderer->frame_encodings_mutex);
g_source_set_ready_time (renderer->surface_render_source, 0);
}
static gboolean
encode_image_views (GrdRdpRenderer *renderer,
GrdRdpFrame *rdp_frame)
{
g_autoptr (GMutexLocker) locker = NULL;
gboolean submitted_frame = FALSE;
g_autoptr (GError) error = NULL;
GrdImageView *image_view;
locker = g_mutex_locker_new (&renderer->frame_encodings_mutex);
while ((image_view = grd_rdp_frame_pop_image_view (rdp_frame)))
{
GrdRdpRenderContext *render_context =
grd_rdp_frame_get_render_context (rdp_frame);
GrdEncodeSession *encode_session =
grd_rdp_render_context_get_encode_session (render_context);
GrdEncodeContext *encode_context =
grd_rdp_frame_get_encode_context (rdp_frame);
if (!grd_encode_session_encode_frame (encode_session, encode_context,
image_view, &error))
{
g_warning ("[RDP] Failed to encode frame: %s", error->message);
if (!submitted_frame)
{
release_acquired_resource (renderer, render_context,
encode_session);
grd_rdp_frame_free (rdp_frame);
}
return FALSE;
}
submitted_frame = TRUE;
grd_encode_session_lock_bitstream (encode_session, image_view,
on_bitstream_locked, rdp_frame);
}
return TRUE;
}
static gboolean
maybe_start_encodings (GrdRdpRenderer *renderer)
{
g_autoptr (GMutexLocker) locker = NULL;
GrdRdpFrame *rdp_frame = NULL;
GHashTableIter iter;
if (renderer->stop_rendering)
return TRUE;
locker = g_mutex_locker_new (&renderer->view_creations_mutex);
if (renderer->graphics_subsystem_failed)
return FALSE;
g_hash_table_iter_init (&iter, renderer->finished_view_creations);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &rdp_frame))
{
GHashTable *acquired_resources = NULL;
GrdRdpRenderContext *render_context;
GrdRdpViewCreator *view_creator;
GrdEncodeSession *encode_session;
g_assert (grd_rdp_frame_has_valid_view (rdp_frame));
render_context = grd_rdp_frame_get_render_context (rdp_frame);
view_creator = grd_rdp_render_context_get_view_creator (render_context);
if (!grd_rdp_frame_is_surface_damaged (rdp_frame))
{
release_acquired_resource (renderer, render_context, view_creator);
g_hash_table_iter_remove (&iter);
continue;
}
if (!g_hash_table_lookup_extended (renderer->render_resource_mappings,
render_context,
NULL, (gpointer *) &acquired_resources))
g_assert_not_reached ();
encode_session =
grd_rdp_render_context_get_encode_session (render_context);
if (g_hash_table_contains (acquired_resources, encode_session))
continue;
release_acquired_resource (renderer, render_context, view_creator);
g_hash_table_insert (acquired_resources, encode_session, rdp_frame);
g_hash_table_iter_steal (&iter);
if (!encode_image_views (renderer, rdp_frame))
return FALSE;
}
return TRUE;
}
static void
on_view_created (GrdRdpFrame *rdp_frame,
GError *error)
{
GrdRdpRenderer *renderer = grd_rdp_frame_get_renderer (rdp_frame);
if (!grd_rdp_frame_has_valid_view (rdp_frame))
g_warning ("[RDP] Failed to create image view: %s", error->message);
g_mutex_lock (&renderer->view_creations_mutex);
g_hash_table_add (renderer->finished_view_creations, rdp_frame);
if (!grd_rdp_frame_has_valid_view (rdp_frame))
renderer->graphics_subsystem_failed = TRUE;
g_mutex_unlock (&renderer->view_creations_mutex);
g_source_set_ready_time (renderer->surface_render_source, 0);
}
static gboolean
maybe_start_view_creations (GrdRdpRenderer *renderer)
{
GrdRdpFrame *rdp_frame = NULL;
GHashTableIter iter;
if (renderer->stop_rendering)
return TRUE;
g_hash_table_iter_init (&iter, renderer->queued_frames);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &rdp_frame))
{
GHashTable *acquired_resources = NULL;
GrdRdpRenderContext *render_context;
GrdRdpViewCreator *view_creator;
g_autoptr (GError) error = NULL;
render_context = grd_rdp_frame_get_render_context (rdp_frame);
if (!g_hash_table_lookup_extended (renderer->render_resource_mappings,
render_context,
NULL, (gpointer *) &acquired_resources))
g_assert_not_reached ();
view_creator = grd_rdp_render_context_get_view_creator (render_context);
if (g_hash_table_contains (acquired_resources, view_creator))
continue;
grd_rdp_frame_notify_picked_up (rdp_frame);
/* There is no resource to release here (no predecessor) */
g_hash_table_insert (acquired_resources, view_creator, rdp_frame);
g_hash_table_iter_steal (&iter);
if (!grd_rdp_view_creator_create_view (view_creator, rdp_frame,
on_view_created, &error))
{
g_warning ("[RDP] Failed to create view: %s", error->message);
grd_rdp_frame_free (rdp_frame);
return FALSE;
}
}
return TRUE;
}
static void
submit_rendered_frames (GrdRdpRenderer *renderer,
GList *frames)
{
GrdRdpDvcGraphicsPipeline *graphics_pipeline =
graphics_pipeline_from_renderer (renderer);
GList *l;
for (l = frames; l; l = l->next)
{
GrdRdpFrame *rdp_frame = l->data;
grd_rdp_dvc_graphics_pipeline_submit_frame (graphics_pipeline,
rdp_frame);
grd_rdp_frame_notify_frame_submission (rdp_frame);
}
}
static void
release_bitstreams (gpointer data,
gpointer user_data)
{
GrdRdpRenderer *renderer = user_data;
GrdRdpFrame *rdp_frame = data;
GList *bitstreams = grd_rdp_frame_get_bitstreams (rdp_frame);
GList *l;
for (l = bitstreams; l; l = l->next)
{
GrdRdpRenderContext *render_context =
grd_rdp_frame_get_render_context (rdp_frame);
GrdEncodeSession *encode_session =
grd_rdp_render_context_get_encode_session (render_context);
GrdBitstream *bitstream = l->data;
g_autoptr (GError) error = NULL;
if (!grd_encode_session_unlock_bitstream (encode_session, bitstream,
&error))
{
g_warning ("[RDP] Renderer: Failed to unlock bitstream %s",
error->message);
handle_graphics_subsystem_failure (renderer);
}
}
g_list_free (bitstreams);
grd_rdp_frame_set_bitstreams (rdp_frame, NULL);
}
static void
clear_pending_frames (GrdRdpRenderer *renderer)
{
GrdRdpFrame *rdp_frame = NULL;
GHashTableIter iter;
g_hash_table_remove_all (renderer->queued_frames);
g_mutex_lock (&renderer->view_creations_mutex);
g_hash_table_iter_init (&iter, renderer->finished_view_creations);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &rdp_frame))
{
GrdRdpRenderContext *render_context =
grd_rdp_frame_get_render_context (rdp_frame);
GrdRdpViewCreator *view_creator =
grd_rdp_render_context_get_view_creator (render_context);
release_acquired_resource (renderer, render_context, view_creator);
g_hash_table_iter_remove (&iter);
}
g_mutex_unlock (&renderer->view_creations_mutex);
g_mutex_lock (&renderer->frame_encodings_mutex);
g_hash_table_iter_init (&iter, renderer->finished_frame_encodings);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &rdp_frame))
{
GrdRdpRenderContext *render_context =
grd_rdp_frame_get_render_context (rdp_frame);
GrdEncodeSession *encode_session =
grd_rdp_render_context_get_encode_session (render_context);
release_acquired_resource (renderer, render_context, encode_session);
release_bitstreams (rdp_frame, renderer);
g_hash_table_iter_remove (&iter);
}
g_mutex_unlock (&renderer->frame_encodings_mutex);
}
static gboolean
render_surfaces (gpointer user_data)
{
GrdRdpRenderer *renderer = user_data;
GList *finished_frames;
finished_frames = fetch_rendered_frames (renderer);
if (!maybe_start_encodings (renderer) ||
!maybe_start_view_creations (renderer))
handle_graphics_subsystem_failure (renderer);
if (!renderer->stop_rendering)
submit_rendered_frames (renderer, finished_frames);
else
clear_pending_frames (renderer);
g_list_foreach (finished_frames, release_bitstreams, renderer);
g_clear_list (&finished_frames, (GDestroyNotify) grd_rdp_frame_free);
return G_SOURCE_CONTINUE;
}
static gboolean
source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
g_source_set_ready_time (source, -1);
return callback (user_data);
}
static GSourceFuncs source_funcs =
{
.dispatch = source_dispatch,
};
static void
grd_rdp_renderer_init (GrdRdpRenderer *renderer)
{
GSource *surface_disposal_source;
GSource *surface_render_source;
renderer->surface_renderer_table = g_hash_table_new (NULL, NULL);
renderer->render_context_table = g_hash_table_new_full (NULL, NULL,
NULL, g_object_unref);
renderer->acquired_render_contexts = g_hash_table_new_full (NULL, NULL,
NULL, g_free);
renderer->render_resource_mappings =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) g_hash_table_unref);
renderer->disposal_queue = g_async_queue_new ();
renderer->queued_frames =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) grd_rdp_frame_free);
renderer->finished_view_creations =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) grd_rdp_frame_free);
renderer->finished_frame_encodings =
g_hash_table_new_full (NULL, NULL,
NULL, (GDestroyNotify) grd_rdp_frame_free);
g_mutex_init (&renderer->surface_renderers_mutex);
g_mutex_init (&renderer->inhibition_mutex);
g_cond_init (&renderer->stop_rendering_cond);
g_mutex_init (&renderer->view_creations_mutex);
g_mutex_init (&renderer->frame_encodings_mutex);
renderer->graphics_context = g_main_context_new ();
surface_disposal_source = g_source_new (&source_funcs, sizeof (GSource));
g_source_set_callback (surface_disposal_source, dispose_surfaces,
renderer, NULL);
g_source_set_ready_time (surface_disposal_source, -1);
g_source_attach (surface_disposal_source, renderer->graphics_context);
renderer->surface_disposal_source = surface_disposal_source;
surface_render_source = g_source_new (&source_funcs, sizeof (GSource));
g_source_set_callback (surface_render_source, render_surfaces,
renderer, NULL);
g_source_set_ready_time (surface_render_source, -1);
g_source_attach (surface_render_source, renderer->graphics_context);
renderer->surface_render_source = surface_render_source;
}
static void
grd_rdp_renderer_class_init (GrdRdpRendererClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_rdp_renderer_dispose;
object_class->finalize = grd_rdp_renderer_finalize;
signals[INHIBITION_DONE] = g_signal_new ("inhibition-done",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[GFX_INITABLE] = g_signal_new ("gfx-initable",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}