/* * 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-render-context.h" #include #include "grd-context.h" #include "grd-debug.h" #include "grd-encode-session.h" #include "grd-encode-session-ca-sw.h" #include "grd-hwaccel-vaapi.h" #include "grd-image-view.h" #include "grd-rdp-buffer-info.h" #include "grd-rdp-damage-detector.h" #include "grd-rdp-dvc-graphics-pipeline.h" #include "grd-rdp-frame.h" #include "grd-rdp-gfx-frame-controller.h" #include "grd-rdp-gfx-framerate-log.h" #include "grd-rdp-gfx-surface.h" #include "grd-rdp-render-state.h" #include "grd-rdp-renderer.h" #include "grd-rdp-server.h" #include "grd-rdp-surface.h" #include "grd-rdp-surface-renderer.h" #include "grd-rdp-view-creator-avc.h" #include "grd-rdp-view-creator-gen-gl.h" #include "grd-rdp-view-creator-gen-sw.h" #include "grd-utils.h" #include "grd-vk-device.h" #define STATE_TILE_WIDTH 64 #define STATE_TILE_HEIGHT 64 struct _GrdRdpRenderContext { GObject parent; GrdRdpRenderer *renderer; GrdRdpCodec codec; GrdRdpGfxSurface *gfx_surface; GrdRdpViewCreator *view_creator; GrdEncodeSession *encode_session; GHashTable *image_views; GHashTable *acquired_image_views; GrdImageView *last_acquired_image_view; gboolean delay_view_finalization; uint32_t *chroma_state_buffer; uint32_t state_buffer_length; }; G_DEFINE_TYPE (GrdRdpRenderContext, grd_rdp_render_context, G_TYPE_OBJECT) GrdRdpRenderer * grd_rdp_render_context_get_renderer (GrdRdpRenderContext *render_context) { return render_context->renderer; } GrdRdpCodec grd_rdp_render_context_get_codec (GrdRdpRenderContext *render_context) { return render_context->codec; } GrdRdpGfxSurface * grd_rdp_render_context_get_gfx_surface (GrdRdpRenderContext *render_context) { return render_context->gfx_surface; } GrdRdpViewCreator * grd_rdp_render_context_get_view_creator (GrdRdpRenderContext *render_context) { return render_context->view_creator; } GrdEncodeSession * grd_rdp_render_context_get_encode_session (GrdRdpRenderContext *render_context) { return render_context->encode_session; } gboolean grd_rdp_render_context_must_delay_view_finalization (GrdRdpRenderContext *render_context) { return render_context->delay_view_finalization; } gboolean grd_rdp_render_context_should_avoid_dual_frame (GrdRdpRenderContext *render_context) { GrdRdpGfxSurface *gfx_surface = render_context->gfx_surface; GrdRdpGfxFrameController *frame_controller; GrdRdpGfxFramerateLog *framerate_log; g_assert (gfx_surface); frame_controller = grd_rdp_gfx_surface_get_frame_controller (gfx_surface); framerate_log = grd_rdp_gfx_frame_controller_get_framerate_log (frame_controller); return grd_rdp_gfx_framerate_log_should_avoid_dual_frame (framerate_log); } static void notify_frame_upgrade_state (GrdRdpRenderContext *render_context, gboolean can_upgrade_frame) { GrdRdpGfxSurface *gfx_surface = render_context->gfx_surface; GrdRdpSurface *rdp_surface; GrdRdpSurfaceRenderer *surface_renderer; g_assert (gfx_surface); rdp_surface = grd_rdp_gfx_surface_get_rdp_surface (gfx_surface); surface_renderer = grd_rdp_surface_get_surface_renderer (rdp_surface); grd_rdp_surface_renderer_notify_frame_upgrade_state (surface_renderer, can_upgrade_frame); } static void update_frame_upgrade_state (GrdRdpRenderContext *render_context) { gboolean can_upgrade_frame; can_upgrade_frame = !!render_context->chroma_state_buffer && g_hash_table_size (render_context->acquired_image_views) == 0; notify_frame_upgrade_state (render_context, can_upgrade_frame); } GrdImageView * grd_rdp_render_context_acquire_image_view (GrdRdpRenderContext *render_context) { GrdImageView *image_view = NULL; GHashTableIter iter; g_hash_table_iter_init (&iter, render_context->image_views); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &image_view)) { if (g_hash_table_contains (render_context->acquired_image_views, image_view)) continue; g_hash_table_add (render_context->acquired_image_views, image_view); render_context->last_acquired_image_view = image_view; update_frame_upgrade_state (render_context); return image_view; } g_assert_not_reached (); return NULL; } void grd_rdp_render_context_release_image_view (GrdRdpRenderContext *render_context, GrdImageView *image_view) { if (!g_hash_table_remove (render_context->acquired_image_views, image_view)) g_assert_not_reached (); grd_image_view_notify_image_view_release (image_view); update_frame_upgrade_state (render_context); } static void maybe_downgrade_view_type (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 (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 gboolean is_auxiliary_view_needed (GrdRdpRenderState *render_state) { uint32_t *chroma_state_buffer = grd_rdp_render_state_get_chroma_state_buffer (render_state); uint32_t state_buffer_length = grd_rdp_render_state_get_state_buffer_length (render_state); uint32_t i; for (i = 0; i < state_buffer_length; ++i) { if (chroma_state_buffer[i] != 0) return TRUE; } return FALSE; } static void update_dual_frame (GrdRdpRenderContext *render_context, GrdRdpFrame *rdp_frame, GrdRdpRenderState *render_state) { uint32_t *damage_buffer = grd_rdp_render_state_get_damage_buffer (render_state); uint32_t state_buffer_length = grd_rdp_render_state_get_state_buffer_length (render_state); uint32_t i; if (!render_context->chroma_state_buffer) { if (!is_auxiliary_view_needed (render_state)) { grd_rdp_frame_set_avc_view_type (rdp_frame, GRD_RDP_FRAME_VIEW_TYPE_MAIN); } return; } g_assert (render_context->state_buffer_length == state_buffer_length); for (i = 0; i < render_context->state_buffer_length; ++i) { if (render_context->chroma_state_buffer[i] != 0) damage_buffer[i] = 1; } g_clear_pointer (&render_context->chroma_state_buffer, g_free); } static void update_main_frame (GrdRdpRenderContext *render_context, GrdRdpFrame *rdp_frame, GrdRdpRenderState *render_state) { uint32_t *damage_buffer = grd_rdp_render_state_get_damage_buffer (render_state); uint32_t *chroma_state_buffer = grd_rdp_render_state_get_chroma_state_buffer (render_state); uint32_t state_buffer_length = grd_rdp_render_state_get_state_buffer_length (render_state); gboolean pending_auxiliary_view = FALSE; uint32_t i; if (!render_context->chroma_state_buffer) { if (is_auxiliary_view_needed (render_state)) { render_context->chroma_state_buffer = g_memdup2 (chroma_state_buffer, state_buffer_length * sizeof (uint32_t)); render_context->state_buffer_length = state_buffer_length; } return; } g_assert (render_context->state_buffer_length == state_buffer_length); for (i = 0; i < render_context->state_buffer_length; ++i) { if (chroma_state_buffer[i] != 0) render_context->chroma_state_buffer[i] = 1; else if (damage_buffer[i] != 0) render_context->chroma_state_buffer[i] = 0; if (render_context->chroma_state_buffer[i] != 0) pending_auxiliary_view = TRUE; } if (!pending_auxiliary_view) g_clear_pointer (&render_context->chroma_state_buffer, g_free); } static void update_avc444_render_state (GrdRdpRenderContext *render_context, GrdRdpFrame *rdp_frame, GrdRdpRenderState *render_state) { maybe_downgrade_view_type (render_context, rdp_frame); switch (grd_rdp_frame_get_avc_view_type (rdp_frame)) { case GRD_RDP_FRAME_VIEW_TYPE_DUAL: update_dual_frame (render_context, rdp_frame, render_state); break; case GRD_RDP_FRAME_VIEW_TYPE_MAIN: update_main_frame (render_context, rdp_frame, render_state); break; case GRD_RDP_FRAME_VIEW_TYPE_AUX: g_assert_not_reached (); break; } update_frame_upgrade_state (render_context); } static cairo_region_t * create_damage_region (GrdRdpRenderContext *render_context, GrdRdpRenderState *render_state) { GrdRdpGfxSurface *gfx_surface = render_context->gfx_surface; uint32_t *damage_buffer = grd_rdp_render_state_get_damage_buffer (render_state); GrdRdpSurface *rdp_surface; uint32_t surface_width; uint32_t surface_height; uint32_t state_buffer_width; uint32_t state_buffer_height; uint32_t state_buffer_stride; cairo_region_t *damage_region; uint32_t x, y; g_assert (gfx_surface); rdp_surface = grd_rdp_gfx_surface_get_rdp_surface (gfx_surface); surface_width = grd_rdp_surface_get_width (rdp_surface); surface_height = grd_rdp_surface_get_height (rdp_surface); state_buffer_width = grd_get_aligned_size (surface_width, 64) / 64; state_buffer_height = grd_get_aligned_size (surface_height, 64) / 64; state_buffer_stride = state_buffer_width; damage_region = cairo_region_create (); g_assert (cairo_region_status (damage_region) == CAIRO_STATUS_SUCCESS); for (y = 0; y < state_buffer_height; ++y) { for (x = 0; x < state_buffer_width; ++x) { cairo_rectangle_int_t tile = {}; uint32_t target_pos; target_pos = y * state_buffer_stride + x; if (damage_buffer[target_pos] == 0) continue; tile.x = x * STATE_TILE_WIDTH; tile.y = y * STATE_TILE_HEIGHT; tile.width = surface_width - tile.x < STATE_TILE_WIDTH ? surface_width - tile.x : STATE_TILE_WIDTH; tile.height = surface_height - tile.y < STATE_TILE_HEIGHT ? surface_height - tile.y : STATE_TILE_HEIGHT; cairo_region_union_rectangle (damage_region, &tile); } } return damage_region; } void grd_rdp_render_context_update_frame_state (GrdRdpRenderContext *render_context, GrdRdpFrame *rdp_frame, GrdRdpRenderState *render_state) { cairo_region_t *damage_region; switch (render_context->codec) { case GRD_RDP_CODEC_CAPROGRESSIVE: case GRD_RDP_CODEC_AVC420: break; case GRD_RDP_CODEC_AVC444v2: update_avc444_render_state (render_context, rdp_frame, render_state); break; } damage_region = create_damage_region (render_context, render_state); grd_rdp_frame_set_damage_region (rdp_frame, damage_region); grd_rdp_render_state_free (render_state); } void grd_rdp_render_context_fetch_progressive_render_state (GrdRdpRenderContext *render_context, GrdImageView **image_view, cairo_region_t **damage_region) { g_autoptr (GrdRdpRenderState) render_state = NULL; g_assert (render_context->chroma_state_buffer); render_state = grd_rdp_render_state_new (render_context->chroma_state_buffer, NULL, render_context->state_buffer_length); *damage_region = create_damage_region (render_context, render_state); *image_view = render_context->last_acquired_image_view; g_assert (!g_hash_table_contains (render_context->acquired_image_views, *image_view)); g_hash_table_add (render_context->acquired_image_views, *image_view); g_clear_pointer (&render_context->chroma_state_buffer, g_free); update_frame_upgrade_state (render_context); } static gboolean is_gpu_driver_amd (GrdRdpRenderContext *render_context) { GrdVkDevice *vk_device = grd_rdp_renderer_get_vk_device (render_context->renderer); VkDriverId driver_id = grd_vk_device_get_driver_id (vk_device); return driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE || driver_id == VK_DRIVER_ID_MESA_RADV; } static void try_create_vaapi_session (GrdRdpRenderContext *render_context, GrdRdpSurface *rdp_surface, gboolean have_avc444) { GrdVkDevice *vk_device = grd_rdp_renderer_get_vk_device (render_context->renderer); GrdRdpSurfaceRenderer *surface_renderer = grd_rdp_surface_get_surface_renderer (rdp_surface); GrdHwAccelVaapi *hwaccel_vaapi = grd_rdp_renderer_get_hwaccel_vaapi (render_context->renderer); GrdRdpBufferInfo *buffer_info = grd_rdp_surface_renderer_get_buffer_info (surface_renderer); uint32_t refresh_rate = grd_rdp_surface_renderer_get_refresh_rate (surface_renderer); uint32_t surface_width = grd_rdp_surface_get_width (rdp_surface); uint32_t surface_height = grd_rdp_surface_get_height (rdp_surface); g_autoptr (GrdEncodeSession) encode_session = NULL; GrdRdpViewCreatorAVC *view_creator_avc; uint32_t render_surface_width = 0; uint32_t render_surface_height = 0; g_autoptr (GError) error = NULL; g_autoptr (GList) image_views = NULL; GList *l; g_assert (buffer_info->buffer_type == GRD_RDP_BUFFER_TYPE_DMA_BUF); if (!buffer_info->has_vk_image) return; if (buffer_info->drm_format_modifier == DRM_FORMAT_MOD_INVALID) return; encode_session = grd_hwaccel_vaapi_create_encode_session (hwaccel_vaapi, surface_width, surface_height, refresh_rate, &error); if (!encode_session) { g_debug ("[HWAccel.VAAPI] Could not create VAAPI encode session: %s", error->message); return; } grd_encode_session_get_surface_size (encode_session, &render_surface_width, &render_surface_height); g_assert (render_surface_width % 16 == 0); g_assert (render_surface_height % 16 == 0); g_assert (render_surface_width >= 16); g_assert (render_surface_height >= 16); view_creator_avc = grd_rdp_view_creator_avc_new (vk_device, render_surface_width, render_surface_height, surface_width, surface_height, &error); if (!view_creator_avc) { g_debug ("[HWAccel.Vulkan] Failed to create view creator for VAAPI " "encode session: %s", error->message); return; } image_views = grd_encode_session_get_image_views (encode_session); for (l = image_views; l; l = l->next) { GrdImageView *image_view = l->data; g_hash_table_add (render_context->image_views, image_view); } render_context->view_creator = GRD_RDP_VIEW_CREATOR (view_creator_avc); render_context->encode_session = g_steal_pointer (&encode_session); if (have_avc444) render_context->codec = GRD_RDP_CODEC_AVC444v2; else render_context->codec = GRD_RDP_CODEC_AVC420; g_debug ("[HWAccel.VAAPI] Created VAAPI encode session for surface with " "size %ux%u", surface_width, surface_height); } static gboolean create_egl_based_rfx_progressive_encode_session (GrdRdpRenderContext *render_context, GrdRdpSurface *rdp_surface, GError **error) { GrdRdpSwEncoderCa *encoder_ca = grd_rdp_renderer_get_encoder_ca (render_context->renderer); uint32_t surface_width = grd_rdp_surface_get_width (rdp_surface); uint32_t surface_height = grd_rdp_surface_get_height (rdp_surface); GrdEncodeSessionCaSw *encode_session_ca; GrdRdpViewCreatorGenGL *view_creator_gen_gl; GrdEncodeSession *encode_session; g_autoptr (GList) image_views = NULL; GList *l; encode_session_ca = grd_encode_session_ca_sw_new (encoder_ca, surface_width, surface_height, error); if (!encode_session_ca) return FALSE; view_creator_gen_gl = grd_rdp_view_creator_gen_gl_new (render_context, surface_width, surface_height); encode_session = GRD_ENCODE_SESSION (encode_session_ca); image_views = grd_encode_session_get_image_views (encode_session); for (l = image_views; l; l = l->next) { GrdImageView *image_view = l->data; g_hash_table_add (render_context->image_views, image_view); } render_context->view_creator = GRD_RDP_VIEW_CREATOR (view_creator_gen_gl); render_context->encode_session = encode_session; render_context->codec = GRD_RDP_CODEC_CAPROGRESSIVE; return TRUE; } static gboolean create_hw_accelerated_encode_session (GrdRdpRenderContext *render_context, GrdRdpSurface *rdp_surface, GError **error) { GrdSessionRdp *session_rdp = grd_rdp_renderer_get_session (render_context->renderer); GrdRdpDvcGraphicsPipeline *graphics_pipeline = grd_session_rdp_get_graphics_pipeline (session_rdp); GrdHwAccelVaapi *hwaccel_vaapi = grd_rdp_renderer_get_hwaccel_vaapi (render_context->renderer); gboolean have_avc444 = FALSE; gboolean have_avc420 = FALSE; grd_rdp_dvc_graphics_pipeline_get_capabilities (graphics_pipeline, &have_avc444, &have_avc420); if ((have_avc444 || have_avc420) && hwaccel_vaapi && !is_gpu_driver_amd (render_context) && grd_get_debug_flags () & GRD_DEBUG_VKVA) try_create_vaapi_session (render_context, rdp_surface, have_avc444); if (render_context->encode_session) return TRUE; return create_egl_based_rfx_progressive_encode_session (render_context, rdp_surface, error); } static gboolean create_sw_based_rfx_progressive_encode_session (GrdRdpRenderContext *render_context, GrdRdpSurface *rdp_surface, GError **error) { GrdRdpSwEncoderCa *encoder_ca = grd_rdp_renderer_get_encoder_ca (render_context->renderer); uint32_t surface_width = grd_rdp_surface_get_width (rdp_surface); uint32_t surface_height = grd_rdp_surface_get_height (rdp_surface); GrdEncodeSessionCaSw *encode_session_ca; GrdRdpViewCreatorGenSW *view_creator_gen_sw; GrdEncodeSession *encode_session; g_autoptr (GList) image_views = NULL; GList *l; encode_session_ca = grd_encode_session_ca_sw_new (encoder_ca, surface_width, surface_height, error); if (!encode_session_ca) return FALSE; view_creator_gen_sw = grd_rdp_view_creator_gen_sw_new (surface_width, surface_height); encode_session = GRD_ENCODE_SESSION (encode_session_ca); image_views = grd_encode_session_get_image_views (encode_session); for (l = image_views; l; l = l->next) { GrdImageView *image_view = l->data; g_hash_table_add (render_context->image_views, image_view); } render_context->view_creator = GRD_RDP_VIEW_CREATOR (view_creator_gen_sw); render_context->encode_session = encode_session; render_context->codec = GRD_RDP_CODEC_CAPROGRESSIVE; render_context->delay_view_finalization = TRUE; return TRUE; } static gboolean create_sw_based_encode_session (GrdRdpRenderContext *render_context, GrdRdpSurface *rdp_surface, GError **error) { return create_sw_based_rfx_progressive_encode_session (render_context, rdp_surface, error); } static gboolean create_encode_session (GrdRdpRenderContext *render_context, GrdRdpSurface *rdp_surface, GError **error) { GrdRdpSurfaceRenderer *surface_renderer = grd_rdp_surface_get_surface_renderer (rdp_surface); GrdRdpBufferInfo *buffer_info = grd_rdp_surface_renderer_get_buffer_info (surface_renderer); switch (buffer_info->buffer_type) { case GRD_RDP_BUFFER_TYPE_DMA_BUF: return create_hw_accelerated_encode_session (render_context, rdp_surface, error); case GRD_RDP_BUFFER_TYPE_MEM_FD: return create_sw_based_encode_session (render_context, rdp_surface, error); case GRD_RDP_BUFFER_TYPE_NONE: g_assert_not_reached (); return FALSE; } g_assert_not_reached (); return FALSE; } GrdRdpRenderContext * grd_rdp_render_context_new (GrdRdpRenderer *renderer, GrdRdpSurface *rdp_surface) { GrdSessionRdp *session_rdp = grd_rdp_renderer_get_session (renderer); GrdRdpDvcGraphicsPipeline *graphics_pipeline = grd_session_rdp_get_graphics_pipeline (session_rdp); g_autoptr (GrdRdpRenderContext) render_context = NULL; g_autoptr (GError) error = NULL; g_assert (graphics_pipeline); if (!grd_rdp_damage_detector_invalidate_surface (rdp_surface->detector)) return NULL; render_context = g_object_new (GRD_TYPE_RDP_RENDER_CONTEXT, NULL); render_context->renderer = renderer; render_context->gfx_surface = grd_rdp_dvc_graphics_pipeline_acquire_gfx_surface (graphics_pipeline, rdp_surface); if (!create_encode_session (render_context, rdp_surface, &error)) { g_warning ("[RDP] Failed to create encode session: %s", error->message); return NULL; } return g_steal_pointer (&render_context); } static void grd_rdp_render_context_dispose (GObject *object) { GrdRdpRenderContext *render_context = GRD_RDP_RENDER_CONTEXT (object); if (render_context->acquired_image_views) g_assert (g_hash_table_size (render_context->acquired_image_views) == 0); g_clear_pointer (&render_context->chroma_state_buffer, g_free); update_frame_upgrade_state (render_context); g_clear_object (&render_context->view_creator); g_clear_object (&render_context->encode_session); g_clear_object (&render_context->gfx_surface); G_OBJECT_CLASS (grd_rdp_render_context_parent_class)->dispose (object); } static void grd_rdp_render_context_finalize (GObject *object) { GrdRdpRenderContext *render_context = GRD_RDP_RENDER_CONTEXT (object); g_clear_pointer (&render_context->acquired_image_views, g_hash_table_unref); g_clear_pointer (&render_context->image_views, g_hash_table_unref); G_OBJECT_CLASS (grd_rdp_render_context_parent_class)->finalize (object); } static void grd_rdp_render_context_init (GrdRdpRenderContext *render_context) { render_context->image_views = g_hash_table_new (NULL, NULL); render_context->acquired_image_views = g_hash_table_new (NULL, NULL); } static void grd_rdp_render_context_class_init (GrdRdpRenderContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = grd_rdp_render_context_dispose; object_class->finalize = grd_rdp_render_context_finalize; }