/* * 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-surface-renderer.h" #include #include "grd-rdp-buffer.h" #include "grd-rdp-damage-detector.h" #include "grd-rdp-frame.h" #include "grd-rdp-legacy-buffer.h" #include "grd-rdp-pw-buffer.h" #include "grd-rdp-render-context.h" #include "grd-rdp-renderer.h" #include "grd-rdp-session-metrics.h" #include "grd-rdp-surface.h" #include "grd-session-rdp.h" #define FRAME_UPGRADE_DELAY_US (60 * 1000) #define UNLIMITED_FRAME_SLOTS (UINT32_MAX) /* * The value of the transition time is based upon testing. During times where * a lot of frames are submitted, the frame-controller could quickly alternate * between throttling and no-throttling. In these situations, the client is * ready for a frame containing only a main view, but not for a dual view. */ #define TRANSITION_TIME_US (200 * 1000) typedef enum { OBJECT_TYPE_BUFFER, OBJECT_TYPE_RENDER_CONTEXT, } ObjectType; typedef struct { ObjectType object_type; gpointer object; } UnrefContext; typedef struct { GrdRdpSurfaceRenderer *surface_renderer; GrdRdpBuffer *current_buffer; GrdRdpBuffer *last_buffer; } GrdRdpFrameContext; struct _GrdRdpSurfaceRenderer { GObject parent; GrdRdpSurface *rdp_surface; GrdRdpRenderer *renderer; uint32_t refresh_rate; GSource *object_unref_source; GAsyncQueue *unref_queue; GSource *render_source; gboolean pending_render_context_reset; GSource *frame_upgrade_source; gboolean pending_frame_upgrade; GSource *trigger_frame_upgrade_source; gboolean graphics_subsystem_failed; uint32_t total_frame_slots; GHashTable *assigned_frame_slots; int64_t unthrottled_since_us; GMutex render_mutex; GrdRdpBuffer *pending_buffer; GrdRdpBuffer *last_buffer; GHashTable *acquired_buffers; GHashTable *registered_buffers; GrdRdpBufferInfo *rdp_buffer_info; }; G_DEFINE_TYPE (GrdRdpSurfaceRenderer, grd_rdp_surface_renderer, G_TYPE_OBJECT) static UnrefContext * unref_context_new (ObjectType object_type, gpointer object) { UnrefContext *unref_context; unref_context = g_new0 (UnrefContext, 1); unref_context->object_type = object_type; unref_context->object = object; return unref_context; } uint32_t grd_rdp_surface_renderer_get_refresh_rate (GrdRdpSurfaceRenderer *surface_renderer) { return surface_renderer->refresh_rate; } GrdRdpBufferInfo * grd_rdp_surface_renderer_get_buffer_info (GrdRdpSurfaceRenderer *surface_renderer) { return surface_renderer->rdp_buffer_info; } uint32_t grd_rdp_surface_renderer_get_total_frame_slots (GrdRdpSurfaceRenderer *surface_renderer) { return surface_renderer->total_frame_slots; } static void trigger_frame_upgrade_schedule (GrdRdpSurfaceRenderer *surface_renderer) { g_source_set_ready_time (surface_renderer->trigger_frame_upgrade_source, 0); } void grd_rdp_surface_renderer_update_total_frame_slots (GrdRdpSurfaceRenderer *surface_renderer, uint32_t total_frame_slots) { uint32_t old_slot_count = surface_renderer->total_frame_slots; if (old_slot_count < UNLIMITED_FRAME_SLOTS && total_frame_slots == UNLIMITED_FRAME_SLOTS) surface_renderer->unthrottled_since_us = g_get_monotonic_time (); surface_renderer->total_frame_slots = total_frame_slots; if (old_slot_count < surface_renderer->total_frame_slots) grd_rdp_surface_renderer_trigger_render_source (surface_renderer); if (old_slot_count < UNLIMITED_FRAME_SLOTS && surface_renderer->total_frame_slots == UNLIMITED_FRAME_SLOTS) trigger_frame_upgrade_schedule (surface_renderer); } void grd_rdp_surface_renderer_notify_frame_upgrade_state (GrdRdpSurfaceRenderer *surface_renderer, gboolean can_upgrade_frame) { surface_renderer->pending_frame_upgrade = can_upgrade_frame; } static gboolean is_buffer_combination_valid (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpPwBuffer *rdp_pw_buffer, GError **error) { GrdRdpBufferInfo *rdp_buffer_info = surface_renderer->rdp_buffer_info; GrdRdpBufferType buffer_type; if (g_hash_table_size (surface_renderer->registered_buffers) == 0) return TRUE; g_assert (rdp_buffer_info); buffer_type = grd_rdp_pw_buffer_get_buffer_type (rdp_pw_buffer); if (buffer_type != rdp_buffer_info->buffer_type) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid buffer combination: Mixed buffer types"); return FALSE; } /* * No need to check whether an implicit or explicit DRM format modifier is * used per buffer, since per PipeWire stream there is only one DRM format * modifier and that one is used by all PW buffers of that PW stream. */ return TRUE; } static gboolean is_render_context_reset_required (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpPwBuffer *rdp_pw_buffer, uint64_t new_drm_format_modifier) { GrdRdpBufferInfo *rdp_buffer_info; GrdRdpBufferType buffer_type; uint64_t current_drm_format_modifier; if (g_hash_table_size (surface_renderer->registered_buffers) > 0) return FALSE; rdp_buffer_info = surface_renderer->rdp_buffer_info; if (!rdp_buffer_info) return FALSE; buffer_type = rdp_buffer_info->buffer_type; if (buffer_type != grd_rdp_pw_buffer_get_buffer_type (rdp_pw_buffer)) return TRUE; current_drm_format_modifier = rdp_buffer_info->drm_format_modifier; if (current_drm_format_modifier != DRM_FORMAT_MOD_INVALID && new_drm_format_modifier == DRM_FORMAT_MOD_INVALID) return TRUE; if (current_drm_format_modifier == DRM_FORMAT_MOD_INVALID && new_drm_format_modifier != DRM_FORMAT_MOD_INVALID) return TRUE; return FALSE; } static GrdRdpBufferInfo * rdp_buffer_info_new (GrdRdpPwBuffer *rdp_pw_buffer, uint32_t drm_format, uint64_t drm_format_modifier) { GrdRdpBufferType buffer_type = grd_rdp_pw_buffer_get_buffer_type (rdp_pw_buffer); GrdRdpBufferInfo *rdp_buffer_info; rdp_buffer_info = g_new0 (GrdRdpBufferInfo, 1); rdp_buffer_info->buffer_type = buffer_type; rdp_buffer_info->drm_format = drm_format; rdp_buffer_info->drm_format_modifier = drm_format_modifier; return rdp_buffer_info; } gboolean grd_rdp_surface_renderer_register_pw_buffer (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpPwBuffer *rdp_pw_buffer, uint32_t drm_format, uint64_t drm_format_modifier, GError **error) { GrdRdpSurface *rdp_surface = surface_renderer->rdp_surface; g_autofree GrdRdpBufferInfo *rdp_buffer_info = NULL; GrdVkDevice *vk_device = grd_rdp_renderer_get_vk_device (surface_renderer->renderer); GrdRdpBuffer *rdp_buffer; if (!is_buffer_combination_valid (surface_renderer, rdp_pw_buffer, error)) return FALSE; rdp_buffer_info = rdp_buffer_info_new (rdp_pw_buffer, drm_format, drm_format_modifier); rdp_buffer = grd_rdp_buffer_new (rdp_pw_buffer, rdp_buffer_info, rdp_surface, vk_device, error); if (!rdp_buffer) return FALSE; if (is_render_context_reset_required (surface_renderer, rdp_pw_buffer, drm_format_modifier)) surface_renderer->pending_render_context_reset = TRUE; if (surface_renderer->rdp_buffer_info && surface_renderer->rdp_buffer_info->buffer_type == GRD_RDP_BUFFER_TYPE_DMA_BUF && surface_renderer->rdp_buffer_info->drm_format_modifier != drm_format_modifier) g_assert (g_hash_table_size (surface_renderer->registered_buffers) == 0); if (g_hash_table_size (surface_renderer->registered_buffers) == 0) g_clear_pointer (&surface_renderer->rdp_buffer_info, g_free); if (surface_renderer->pending_render_context_reset) g_assert (!surface_renderer->rdp_buffer_info); if (!surface_renderer->rdp_buffer_info) surface_renderer->rdp_buffer_info = g_steal_pointer (&rdp_buffer_info); g_hash_table_insert (surface_renderer->registered_buffers, rdp_pw_buffer, rdp_buffer); return TRUE; } static void release_pw_buffer (GrdRdpBuffer *rdp_buffer) { GrdRdpPwBuffer *rdp_pw_buffer = grd_rdp_buffer_get_rdp_pw_buffer (rdp_buffer); grd_rdp_pw_buffer_queue_pw_buffer (rdp_pw_buffer); } void grd_rdp_surface_renderer_unregister_pw_buffer (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpPwBuffer *rdp_pw_buffer) { GrdRdpBuffer *rdp_buffer = NULL; GrdRdpBuffer *buffer_to_release = NULL; if (!g_hash_table_lookup_extended (surface_renderer->registered_buffers, rdp_pw_buffer, NULL, (gpointer *) &rdp_buffer)) g_assert_not_reached (); /* Ensure buffer cannot be acquired anymore */ g_mutex_lock (&surface_renderer->render_mutex); if (surface_renderer->pending_buffer || surface_renderer->last_buffer) g_assert (surface_renderer->pending_buffer != surface_renderer->last_buffer); if (surface_renderer->pending_buffer == rdp_buffer) buffer_to_release = g_steal_pointer (&surface_renderer->pending_buffer); if (surface_renderer->last_buffer == rdp_buffer) buffer_to_release = g_steal_pointer (&surface_renderer->last_buffer); grd_rdp_buffer_mark_for_removal (rdp_buffer); g_mutex_unlock (&surface_renderer->render_mutex); /* Ensure buffer lock is released and PipeWire buffer queued again */ grd_rdp_pw_buffer_ensure_unlocked (rdp_pw_buffer); g_clear_pointer (&buffer_to_release, release_pw_buffer); g_hash_table_remove (surface_renderer->registered_buffers, rdp_pw_buffer); } void grd_rdp_surface_renderer_submit_buffer (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpPwBuffer *rdp_pw_buffer) { GrdRdpBuffer *rdp_buffer = NULL; if (!g_hash_table_lookup_extended (surface_renderer->registered_buffers, rdp_pw_buffer, NULL, (gpointer *) &rdp_buffer)) g_assert_not_reached (); g_mutex_lock (&surface_renderer->render_mutex); g_clear_pointer (&surface_renderer->pending_buffer, release_pw_buffer); surface_renderer->pending_buffer = rdp_buffer; g_mutex_unlock (&surface_renderer->render_mutex); grd_rdp_surface_renderer_trigger_render_source (surface_renderer); } void grd_rdp_surface_renderer_submit_legacy_buffer (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpLegacyBuffer *buffer) { GrdRdpSurface *rdp_surface = surface_renderer->rdp_surface; g_mutex_lock (&surface_renderer->render_mutex); g_clear_pointer (&rdp_surface->pending_framebuffer, grd_rdp_legacy_buffer_release); rdp_surface->pending_framebuffer = buffer; g_mutex_unlock (&surface_renderer->render_mutex); grd_rdp_surface_renderer_trigger_render_source (surface_renderer); } void grd_rdp_surface_renderer_invalidate_surface (GrdRdpSurfaceRenderer *surface_renderer) { g_autoptr (GMutexLocker) locker = NULL; locker = g_mutex_locker_new (&surface_renderer->render_mutex); grd_rdp_surface_renderer_invalidate_surface_unlocked (surface_renderer); } void grd_rdp_surface_renderer_invalidate_surface_unlocked (GrdRdpSurfaceRenderer *surface_renderer) { g_assert (g_hash_table_size (surface_renderer->acquired_buffers) == 0); if (!surface_renderer->pending_buffer) { surface_renderer->pending_buffer = g_steal_pointer (&surface_renderer->last_buffer); } g_clear_pointer (&surface_renderer->last_buffer, release_pw_buffer); } void grd_rdp_surface_renderer_trigger_render_source (GrdRdpSurfaceRenderer *surface_renderer) { g_source_set_ready_time (surface_renderer->render_source, 0); trigger_frame_upgrade_schedule (surface_renderer); } void grd_rdp_surface_renderer_reset (GrdRdpSurfaceRenderer *surface_renderer) { GrdRdpSurface *rdp_surface = surface_renderer->rdp_surface; g_mutex_lock (&surface_renderer->render_mutex); g_clear_pointer (&rdp_surface->pending_framebuffer, grd_rdp_legacy_buffer_release); g_mutex_unlock (&surface_renderer->render_mutex); } static GrdRdpBuffer * rdp_buffer_ref (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpBuffer *rdp_buffer) { GrdRdpPwBuffer *rdp_pw_buffer = grd_rdp_buffer_get_rdp_pw_buffer (rdp_buffer); uint32_t *ref_count = NULL; if (g_hash_table_lookup_extended (surface_renderer->acquired_buffers, rdp_buffer, NULL, (gpointer *) &ref_count)) { g_assert (*ref_count > 0); ++(*ref_count); return rdp_buffer; } ref_count = g_new0 (uint32_t, 1); *ref_count = 1; grd_rdp_pw_buffer_acquire_lock (rdp_pw_buffer); g_hash_table_insert (surface_renderer->acquired_buffers, rdp_buffer, ref_count); return rdp_buffer; } static void rdp_buffer_unref (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpBuffer *rdp_buffer) { GrdRdpPwBuffer *rdp_pw_buffer = grd_rdp_buffer_get_rdp_pw_buffer (rdp_buffer); uint32_t *ref_count = NULL; if (!g_hash_table_lookup_extended (surface_renderer->acquired_buffers, rdp_buffer, NULL, (gpointer *) &ref_count)) g_assert_not_reached (); g_assert (*ref_count > 0); --(*ref_count); if (*ref_count == 0) { g_assert (surface_renderer->pending_buffer != rdp_buffer); /* * unregister_pw_buffer () already takes care of the PW buffer release * here. In such case, the second condition * (surface_renderer->last_buffer != rdp_buffer) is always true. */ if (!grd_rdp_buffer_is_marked_for_removal (rdp_buffer) && surface_renderer->last_buffer != rdp_buffer) release_pw_buffer (rdp_buffer); g_hash_table_remove (surface_renderer->acquired_buffers, rdp_buffer); grd_rdp_pw_buffer_maybe_release_lock (rdp_pw_buffer); } } static void unref_buffer (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpBuffer *rdp_buffer) { g_mutex_lock (&surface_renderer->render_mutex); g_assert (surface_renderer->pending_buffer != rdp_buffer); rdp_buffer_unref (surface_renderer, rdp_buffer); g_mutex_unlock (&surface_renderer->render_mutex); } static void unref_render_context (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpRenderContext *render_context) { grd_rdp_renderer_release_render_context (surface_renderer->renderer, render_context); } static gboolean unref_objects (gpointer user_data) { GrdRdpSurfaceRenderer *surface_renderer = user_data; UnrefContext *unref_context; while ((unref_context = g_async_queue_try_pop (surface_renderer->unref_queue))) { switch (unref_context->object_type) { case OBJECT_TYPE_BUFFER: unref_buffer (surface_renderer, unref_context->object); break; case OBJECT_TYPE_RENDER_CONTEXT: unref_render_context (surface_renderer, unref_context->object); break; } g_free (unref_context); } return G_SOURCE_CONTINUE; } static gboolean can_prepare_new_frame (GrdRdpSurfaceRenderer *surface_renderer) { uint32_t total_frame_slots = surface_renderer->total_frame_slots; uint32_t used_frame_slots; used_frame_slots = g_hash_table_size (surface_renderer->assigned_frame_slots); return total_frame_slots > used_frame_slots; } static void on_frame_picked_up (GrdRdpFrame *rdp_frame, gpointer user_data) { GrdRdpFrameContext *frame_context = user_data; GrdRdpSurfaceRenderer *surface_renderer = frame_context->surface_renderer; GrdRdpBuffer *current_buffer = frame_context->current_buffer; g_hash_table_add (surface_renderer->assigned_frame_slots, rdp_frame); if (!current_buffer) return; g_mutex_lock (&surface_renderer->render_mutex); if (!grd_rdp_buffer_is_marked_for_removal (current_buffer)) surface_renderer->last_buffer = current_buffer; else surface_renderer->last_buffer = NULL; g_mutex_unlock (&surface_renderer->render_mutex); } static void queue_buffer_unref (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpBuffer *rdp_buffer) { UnrefContext *unref_context; unref_context = unref_context_new (OBJECT_TYPE_BUFFER, rdp_buffer); g_async_queue_push (surface_renderer->unref_queue, unref_context); } static void on_view_finalization (GrdRdpFrame *rdp_frame, gpointer user_data) { GrdRdpFrameContext *frame_context = user_data; GrdRdpSurfaceRenderer *surface_renderer = frame_context->surface_renderer; GrdRdpBuffer *current_buffer = frame_context->current_buffer; GrdRdpBuffer *last_buffer = frame_context->last_buffer; queue_buffer_unref (surface_renderer, current_buffer); if (last_buffer) queue_buffer_unref (surface_renderer, last_buffer); g_source_set_ready_time (surface_renderer->object_unref_source, 0); } static void on_frame_submission (GrdRdpFrame *rdp_frame, gpointer user_data) { GrdRdpFrameContext *frame_context = user_data; GrdRdpSurfaceRenderer *surface_renderer = frame_context->surface_renderer; GrdRdpSurface *rdp_surface = surface_renderer->rdp_surface; GrdSessionRdp *session_rdp = grd_rdp_renderer_get_session (surface_renderer->renderer); GrdRdpSessionMetrics *session_metrics = grd_session_rdp_get_session_metrics (session_rdp); grd_rdp_session_metrics_notify_frame_transmission (session_metrics, rdp_surface); } static void reset_frame_upgrade_schedule (GrdRdpSurfaceRenderer *surface_renderer) { g_source_set_ready_time (surface_renderer->frame_upgrade_source, -1); trigger_frame_upgrade_schedule (surface_renderer); } static void queue_render_context_unref (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpFrame *rdp_frame) { GrdRdpRenderContext *render_context = grd_rdp_frame_get_render_context (rdp_frame); UnrefContext *unref_context; unref_context = unref_context_new (OBJECT_TYPE_RENDER_CONTEXT, render_context); g_async_queue_push (surface_renderer->unref_queue, unref_context); g_source_set_ready_time (surface_renderer->object_unref_source, 0); } static void on_frame_finalization (GrdRdpFrame *rdp_frame, gpointer user_data) { GrdRdpFrameContext *frame_context = user_data; GrdRdpSurfaceRenderer *surface_renderer = frame_context->surface_renderer; g_hash_table_remove (surface_renderer->assigned_frame_slots, rdp_frame); grd_rdp_surface_renderer_trigger_render_source (surface_renderer); reset_frame_upgrade_schedule (surface_renderer); queue_render_context_unref (surface_renderer, rdp_frame); } static gboolean should_avoid_auxiliary_frame (GrdRdpSurfaceRenderer *surface_renderer) { int64_t current_time_us; current_time_us = g_get_monotonic_time (); return g_hash_table_size (surface_renderer->assigned_frame_slots) > 0 || surface_renderer->total_frame_slots < UNLIMITED_FRAME_SLOTS || current_time_us - surface_renderer->unthrottled_since_us < TRANSITION_TIME_US; } static void maybe_downgrade_view_type (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpRenderContext *render_context, GrdRdpFrame *rdp_frame) { GrdRdpFrameViewType view_type; view_type = grd_rdp_frame_get_avc_view_type (rdp_frame); if (view_type != GRD_RDP_FRAME_VIEW_TYPE_DUAL) return; if (should_avoid_auxiliary_frame (surface_renderer) || grd_rdp_render_context_should_avoid_dual_frame (render_context)) grd_rdp_frame_set_avc_view_type (rdp_frame, GRD_RDP_FRAME_VIEW_TYPE_MAIN); } static void handle_pending_buffer (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpRenderContext *render_context) { GrdRdpRenderer *renderer = surface_renderer->renderer; GrdRdpFrameContext *frame_context; GrdRdpBuffer *current_buffer = NULL; GrdRdpBuffer *last_buffer = NULL; GrdRdpFrame *rdp_frame; frame_context = g_new0 (GrdRdpFrameContext, 1); frame_context->surface_renderer = surface_renderer; current_buffer = g_steal_pointer (&surface_renderer->pending_buffer); frame_context->current_buffer = rdp_buffer_ref (surface_renderer, current_buffer); if (surface_renderer->last_buffer) { last_buffer = surface_renderer->last_buffer; frame_context->last_buffer = rdp_buffer_ref (surface_renderer, last_buffer); } rdp_frame = grd_rdp_frame_new (render_context, current_buffer, last_buffer, on_frame_picked_up, on_view_finalization, on_frame_submission, on_frame_finalization, frame_context, g_free); maybe_downgrade_view_type (surface_renderer, render_context, rdp_frame); grd_rdp_renderer_submit_frame (renderer, render_context, rdp_frame); } static void handle_graphics_subsystem_failure (GrdRdpSurfaceRenderer *surface_renderer) { GrdSessionRdp *session_rdp = grd_rdp_renderer_get_session (surface_renderer->renderer); surface_renderer->graphics_subsystem_failed = TRUE; grd_session_rdp_notify_error (session_rdp, GRD_SESSION_RDP_ERROR_GRAPHICS_SUBSYSTEM_FAILED); } static void maybe_encode_pending_frame (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpRenderContext *render_context) { GrdRdpSurface *rdp_surface = surface_renderer->rdp_surface; GrdSessionRdp *session_rdp = grd_rdp_renderer_get_session (surface_renderer->renderer); GrdRdpSessionMetrics *session_metrics = grd_session_rdp_get_session_metrics (session_rdp); GrdRdpLegacyBuffer *buffer; buffer = g_steal_pointer (&rdp_surface->pending_framebuffer); if (!grd_rdp_damage_detector_submit_new_framebuffer (rdp_surface->detector, buffer)) { grd_rdp_legacy_buffer_release (buffer); handle_graphics_subsystem_failure (surface_renderer); return; } if (!grd_rdp_damage_detector_is_region_damaged (rdp_surface->detector)) return; if (!grd_rdp_renderer_render_frame (surface_renderer->renderer, rdp_surface, render_context, buffer)) { handle_graphics_subsystem_failure (surface_renderer); return; } grd_rdp_session_metrics_notify_frame_transmission (session_metrics, rdp_surface); } static gboolean maybe_render_frame (gpointer user_data) { GrdRdpSurfaceRenderer *surface_renderer = user_data; GrdRdpRenderer *renderer = surface_renderer->renderer; GrdRdpSurface *rdp_surface = surface_renderer->rdp_surface; GrdRdpAcquireContextFlags acquire_flags; GrdRdpRenderContext *render_context; g_autoptr (GMutexLocker) locker = NULL; if (surface_renderer->graphics_subsystem_failed) return G_SOURCE_CONTINUE; locker = g_mutex_locker_new (&surface_renderer->render_mutex); if (!surface_renderer->pending_buffer && !rdp_surface->pending_framebuffer) return G_SOURCE_CONTINUE; g_assert (!surface_renderer->pending_buffer || !rdp_surface->pending_framebuffer); g_source_set_ready_time (surface_renderer->frame_upgrade_source, -1); if (!can_prepare_new_frame (surface_renderer)) return G_SOURCE_CONTINUE; acquire_flags = GRD_RDP_ACQUIRE_CONTEXT_FLAG_NONE; if (surface_renderer->pending_render_context_reset) acquire_flags |= GRD_RDP_ACQUIRE_CONTEXT_FLAG_FORCE_RESET; render_context = grd_rdp_renderer_try_acquire_render_context (renderer, rdp_surface, acquire_flags); if (!render_context) return G_SOURCE_CONTINUE; surface_renderer->pending_render_context_reset = FALSE; if (surface_renderer->pending_buffer) { handle_pending_buffer (surface_renderer, render_context); } else if (rdp_surface->pending_framebuffer) /* Legacy render handling */ { maybe_encode_pending_frame (surface_renderer, render_context); grd_rdp_renderer_release_render_context (renderer, render_context); } else { g_assert_not_reached (); } return G_SOURCE_CONTINUE; } static gboolean should_retry_frame_upgrade (GrdRdpSurfaceRenderer *surface_renderer) { /* * should_avoid_auxiliary_frame() returned true. If the following conditions * are true too, then the transition period from throttled to unthrottled is * not over yet. */ return g_hash_table_size (surface_renderer->assigned_frame_slots) == 0 && surface_renderer->total_frame_slots == UNLIMITED_FRAME_SLOTS; } static void upgrade_frame (GrdRdpSurfaceRenderer *surface_renderer, GrdRdpRenderContext *render_context) { GrdRdpRenderer *renderer = surface_renderer->renderer; GrdRdpFrameContext *frame_context; GrdRdpFrame *rdp_frame; frame_context = g_new0 (GrdRdpFrameContext, 1); frame_context->surface_renderer = surface_renderer; rdp_frame = grd_rdp_frame_new (render_context, NULL, NULL, on_frame_picked_up, NULL, on_frame_submission, on_frame_finalization, frame_context, g_free); grd_rdp_renderer_submit_frame (renderer, render_context, rdp_frame); } static gboolean maybe_upgrade_frame (gpointer user_data) { GrdRdpSurfaceRenderer *surface_renderer = user_data; GrdRdpRenderer *renderer = surface_renderer->renderer; GrdRdpSurface *rdp_surface = surface_renderer->rdp_surface; GrdRdpAcquireContextFlags acquire_flags; GrdRdpRenderContext *render_context; g_autoptr (GMutexLocker) locker = NULL; locker = g_mutex_locker_new (&surface_renderer->render_mutex); if (surface_renderer->pending_buffer) return G_SOURCE_CONTINUE; g_clear_pointer (&locker, g_mutex_locker_free); if (!surface_renderer->pending_frame_upgrade) return G_SOURCE_CONTINUE; if (!can_prepare_new_frame (surface_renderer)) return G_SOURCE_CONTINUE; if (should_avoid_auxiliary_frame (surface_renderer)) { if (should_retry_frame_upgrade (surface_renderer)) trigger_frame_upgrade_schedule (surface_renderer); return G_SOURCE_CONTINUE; } acquire_flags = GRD_RDP_ACQUIRE_CONTEXT_FLAG_RETAIN_OR_NULL; render_context = grd_rdp_renderer_try_acquire_render_context (renderer, rdp_surface, acquire_flags); if (!render_context) return G_SOURCE_CONTINUE; upgrade_frame (surface_renderer, render_context); return G_SOURCE_CONTINUE; } static gboolean trigger_frame_upgrade (gpointer user_data) { GrdRdpSurfaceRenderer *surface_renderer = user_data; int64_t current_time_us; if (g_source_get_ready_time (surface_renderer->frame_upgrade_source) != -1) return G_SOURCE_CONTINUE; current_time_us = g_source_get_time (surface_renderer->frame_upgrade_source); g_source_set_ready_time (surface_renderer->frame_upgrade_source, current_time_us + FRAME_UPGRADE_DELAY_US); 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, }; GrdRdpSurfaceRenderer * grd_rdp_surface_renderer_new (GrdRdpSurface *rdp_surface, GrdRdpRenderer *renderer, uint32_t refresh_rate) { GMainContext *graphics_context = grd_rdp_renderer_get_graphics_context (renderer); GrdRdpSurfaceRenderer *surface_renderer; GSource *object_unref_source; GSource *render_source; GSource *frame_upgrade_source; GSource *trigger_frame_upgrade_source; surface_renderer = g_object_new (GRD_TYPE_RDP_SURFACE_RENDERER, NULL); surface_renderer->rdp_surface = rdp_surface; surface_renderer->renderer = renderer; surface_renderer->refresh_rate = refresh_rate; object_unref_source = g_source_new (&source_funcs, sizeof (GSource)); g_source_set_callback (object_unref_source, unref_objects, surface_renderer, NULL); g_source_set_ready_time (object_unref_source, -1); g_source_attach (object_unref_source, graphics_context); surface_renderer->object_unref_source = object_unref_source; render_source = g_source_new (&source_funcs, sizeof (GSource)); g_source_set_callback (render_source, maybe_render_frame, surface_renderer, NULL); g_source_set_ready_time (render_source, -1); g_source_attach (render_source, graphics_context); surface_renderer->render_source = render_source; frame_upgrade_source = g_source_new (&source_funcs, sizeof (GSource)); g_source_set_callback (frame_upgrade_source, maybe_upgrade_frame, surface_renderer, NULL); g_source_set_ready_time (frame_upgrade_source, -1); g_source_attach (frame_upgrade_source, graphics_context); surface_renderer->frame_upgrade_source = frame_upgrade_source; trigger_frame_upgrade_source = g_source_new (&source_funcs, sizeof (GSource)); g_source_set_callback (trigger_frame_upgrade_source, trigger_frame_upgrade, surface_renderer, NULL); g_source_set_ready_time (trigger_frame_upgrade_source, -1); g_source_attach (trigger_frame_upgrade_source, graphics_context); surface_renderer->trigger_frame_upgrade_source = trigger_frame_upgrade_source; return surface_renderer; } static void grd_rdp_surface_renderer_dispose (GObject *object) { GrdRdpSurfaceRenderer *surface_renderer = GRD_RDP_SURFACE_RENDERER (object); /* * No need to lock the render mutex here, the surface renderer outlives the * PipeWire stream. */ if (surface_renderer->acquired_buffers) g_assert (g_hash_table_size (surface_renderer->acquired_buffers) == 0); if (surface_renderer->assigned_frame_slots) g_assert (g_hash_table_size (surface_renderer->assigned_frame_slots) == 0); g_assert (g_async_queue_try_pop (surface_renderer->unref_queue) == NULL); if (surface_renderer->trigger_frame_upgrade_source) { g_source_destroy (surface_renderer->trigger_frame_upgrade_source); g_clear_pointer (&surface_renderer->trigger_frame_upgrade_source, g_source_unref); } if (surface_renderer->frame_upgrade_source) { g_source_destroy (surface_renderer->frame_upgrade_source); g_clear_pointer (&surface_renderer->frame_upgrade_source, g_source_unref); } if (surface_renderer->render_source) { g_source_destroy (surface_renderer->render_source); g_clear_pointer (&surface_renderer->render_source, g_source_unref); } if (surface_renderer->object_unref_source) { g_source_destroy (surface_renderer->object_unref_source); g_clear_pointer (&surface_renderer->object_unref_source, g_source_unref); } g_clear_pointer (&surface_renderer->assigned_frame_slots, g_hash_table_unref); g_clear_pointer (&surface_renderer->acquired_buffers, g_hash_table_unref); g_clear_pointer (&surface_renderer->registered_buffers, g_hash_table_unref); g_clear_pointer (&surface_renderer->rdp_buffer_info, g_free); G_OBJECT_CLASS (grd_rdp_surface_renderer_parent_class)->dispose (object); } static void grd_rdp_surface_renderer_finalize (GObject *object) { GrdRdpSurfaceRenderer *surface_renderer = GRD_RDP_SURFACE_RENDERER (object); g_clear_pointer (&surface_renderer->unref_queue, g_async_queue_unref); g_mutex_clear (&surface_renderer->render_mutex); G_OBJECT_CLASS (grd_rdp_surface_renderer_parent_class)->finalize (object); } static void grd_rdp_surface_renderer_init (GrdRdpSurfaceRenderer *surface_renderer) { surface_renderer->total_frame_slots = UNLIMITED_FRAME_SLOTS; surface_renderer->registered_buffers = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref); surface_renderer->acquired_buffers = g_hash_table_new_full (NULL, NULL, NULL, g_free); surface_renderer->assigned_frame_slots = g_hash_table_new (NULL, NULL); surface_renderer->unref_queue = g_async_queue_new (); g_mutex_init (&surface_renderer->render_mutex); } static void grd_rdp_surface_renderer_class_init (GrdRdpSurfaceRendererClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_surface_renderer_dispose; object_class->finalize = grd_rdp_surface_renderer_finalize; }