[gnome-remote-desktop] rdp: Add initial support for NVENC
- From: Jonas Ådahl <jadahl src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-remote-desktop] rdp: Add initial support for NVENC
- Date: Fri, 3 Sep 2021 09:34:43 +0000 (UTC)
commit eacedf334ead4660883eda828aa4c24c2def420a
Author: Pascal Nowack <Pascal Nowack gmx de>
Date: Mon Jul 5 17:15:59 2021 +0200
rdp: Add initial support for NVENC
With the NVENC and CUDA class implemented in the last commit, add now
the handling to use them to produce AVC420 frames.
H264 content can only be submitted with the graphics pipeline and will
only be produced, if the client supports H264.
Since NVENC does not support any damage rects (only emphasis regions),
always encode the frame progressively.
The first frame will obviously be an IDR frame, while the subsequent
frames will be progressive frames.
Using H264 drastically reduces the bandwidth usage.
gnome-remote-desktop uses, instead of a constant QP value, a constant
quality value (of 22) to encode the frames, since using the constant QP
can waste bandwidth, while the constant quality value produces the same
quality, like the constant QP, but the encoder may use less bytes to
produce the bitstream.
src/grd-rdp-graphics-pipeline.c | 217 +++++++++++++++++++++++++++++++++++++++-
src/grd-rdp-graphics-pipeline.h | 5 +
src/grd-rdp-server.c | 30 +++++-
src/grd-session-rdp.c | 17 +++-
src/grd-session-rdp.h | 6 +-
5 files changed, 270 insertions(+), 5 deletions(-)
---
diff --git a/src/grd-rdp-graphics-pipeline.c b/src/grd-rdp-graphics-pipeline.c
index 57d1a78..685d009 100644
--- a/src/grd-rdp-graphics-pipeline.c
+++ b/src/grd-rdp-graphics-pipeline.c
@@ -26,12 +26,28 @@
#include "grd-rdp-frame-info.h"
#include "grd-rdp-gfx-surface.h"
#include "grd-rdp-network-autodetection.h"
+#ifdef HAVE_NVENC
+#include "grd-rdp-nvenc.h"
+#endif /* HAVE_NVENC */
#include "grd-rdp-surface.h"
#include "grd-session-rdp.h"
#define ENC_TIMES_CHECK_INTERVAL_MS 1000
#define MAX_TRACKED_ENC_FRAMES 1000
+typedef enum _HwAccelAPI
+{
+ HW_ACCEL_API_NONE = 0,
+ HW_ACCEL_API_NVENC = 1 << 0,
+} HwAccelAPI;
+
+typedef struct _HWAccelContext
+{
+ HwAccelAPI api;
+ uint32_t encode_session_id;
+ gboolean has_first_frame;
+} HWAccelContext;
+
typedef struct _GfxSurfaceContext
{
GrdRdpGfxSurface *gfx_surface;
@@ -80,6 +96,11 @@ struct _GrdRdpGraphicsPipeline
GSource *rtt_pause_source;
GQueue *enc_times;
+ GHashTable *surface_hwaccel_table;
+#ifdef HAVE_NVENC
+ GrdRdpNvenc *rdp_nvenc;
+#endif /* HAVE_NVENC */
+
uint32_t next_frame_id;
uint16_t next_surface_id;
uint32_t next_serial;
@@ -87,6 +108,15 @@ struct _GrdRdpGraphicsPipeline
G_DEFINE_TYPE (GrdRdpGraphicsPipeline, grd_rdp_graphics_pipeline, G_TYPE_OBJECT);
+#ifdef HAVE_NVENC
+void
+grd_rdp_graphics_pipeline_set_nvenc (GrdRdpGraphicsPipeline *graphics_pipeline,
+ GrdRdpNvenc *rdp_nvenc)
+{
+ graphics_pipeline->rdp_nvenc = rdp_nvenc;
+}
+#endif /* HAVE_NVENC */
+
void
grd_rdp_graphics_pipeline_create_surface (GrdRdpGraphicsPipeline *graphics_pipeline,
GrdRdpGfxSurface *gfx_surface)
@@ -97,6 +127,10 @@ grd_rdp_graphics_pipeline_create_surface (GrdRdpGraphicsPipeline *graphics_pipel
uint16_t surface_id = grd_rdp_gfx_surface_get_surface_id (gfx_surface);
uint32_t surface_serial = grd_rdp_gfx_surface_get_serial (gfx_surface);
GfxSurfaceContext *surface_context;
+#ifdef HAVE_NVENC
+ HWAccelContext *hwaccel_context;
+ uint32_t encode_session_id;
+#endif /* HAVE_NVENC */
surface_context = g_malloc0 (sizeof (GfxSurfaceContext));
@@ -107,6 +141,28 @@ grd_rdp_graphics_pipeline_create_surface (GrdRdpGraphicsPipeline *graphics_pipel
surface_context->gfx_surface = gfx_surface;
g_hash_table_insert (graphics_pipeline->serial_surface_table,
GUINT_TO_POINTER (surface_serial), surface_context);
+
+#ifdef HAVE_NVENC
+ if ((rdpgfx_context->rdpcontext->settings->GfxAVC444v2 ||
+ rdpgfx_context->rdpcontext->settings->GfxAVC444 ||
+ rdpgfx_context->rdpcontext->settings->GfxH264) &&
+ graphics_pipeline->rdp_nvenc &&
+ grd_rdp_nvenc_create_encode_session (graphics_pipeline->rdp_nvenc,
+ &encode_session_id,
+ rdp_surface->width,
+ rdp_surface->height,
+ rdp_surface->refresh_rate))
+ {
+ g_debug ("[RDP.RDPGFX] Creating NVENC session for surface %u", surface_id);
+
+ hwaccel_context = g_malloc0 (sizeof (HWAccelContext));
+ hwaccel_context->api = HW_ACCEL_API_NVENC;
+ hwaccel_context->encode_session_id = encode_session_id;
+
+ g_hash_table_insert (graphics_pipeline->surface_hwaccel_table,
+ GUINT_TO_POINTER (surface_id), hwaccel_context);
+ }
+#endif /* HAVE_NVENC */
g_mutex_unlock (&graphics_pipeline->gfx_mutex);
create_surface.surfaceId = surface_id;
@@ -126,6 +182,9 @@ grd_rdp_graphics_pipeline_delete_surface (GrdRdpGraphicsPipeline *graphics_pipel
RDPGFX_DELETE_SURFACE_PDU delete_surface = {0};
gboolean needs_encoding_context_deletion = FALSE;
GfxSurfaceContext *surface_context;
+#ifdef HAVE_NVENC
+ HWAccelContext *hwaccel_context;
+#endif /* HAVE_NVENC */
uint16_t surface_id;
uint32_t codec_context_id;
uint32_t surface_serial;
@@ -150,6 +209,20 @@ grd_rdp_graphics_pipeline_delete_surface (GrdRdpGraphicsPipeline *graphics_pipel
GUINT_TO_POINTER (surface_serial));
}
+#ifdef HAVE_NVENC
+ if (g_hash_table_steal_extended (graphics_pipeline->surface_hwaccel_table,
+ GUINT_TO_POINTER (surface_id),
+ NULL, (gpointer *) &hwaccel_context))
+ {
+ g_debug ("[RDP.RDPGFX] Destroying NVENC session for surface %u", surface_id);
+
+ g_assert (hwaccel_context->api == HW_ACCEL_API_NVENC);
+ grd_rdp_nvenc_free_encode_session (graphics_pipeline->rdp_nvenc,
+ hwaccel_context->encode_session_id);
+ g_free (hwaccel_context);
+ }
+#endif /* HAVE_NVENC */
+
if (g_hash_table_steal_extended (graphics_pipeline->codec_context_table,
GUINT_TO_POINTER (codec_context_id),
NULL, NULL))
@@ -324,6 +397,122 @@ enqueue_tracked_frame_info (GrdRdpGraphicsPipeline *graphics_pipeline,
g_queue_push_tail (graphics_pipeline->encoded_frames, gfx_frame_info);
}
+#ifdef HAVE_NVENC
+static gboolean
+refresh_gfx_surface_avc420 (GrdRdpGraphicsPipeline *graphics_pipeline,
+ HWAccelContext *hwaccel_context,
+ GrdRdpSurface *rdp_surface,
+ cairo_region_t *region,
+ uint8_t *src_data,
+ int64_t *enc_time_us)
+{
+ RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context;
+ GrdRdpGfxSurface *gfx_surface = rdp_surface->gfx_surface;
+ RDPGFX_SURFACE_COMMAND cmd = {0};
+ RDPGFX_START_FRAME_PDU cmd_start = {0};
+ RDPGFX_END_FRAME_PDU cmd_end = {0};
+ RDPGFX_AVC420_BITMAP_STREAM avc420 = {0};
+ SYSTEMTIME system_time;
+ cairo_rectangle_int_t cairo_rect, region_extents;
+ int n_rects;
+ uint16_t surface_width = rdp_surface->width;
+ uint16_t surface_height = rdp_surface->height;
+ uint16_t aligned_width;
+ uint16_t aligned_height;
+ uint32_t surface_serial;
+ int64_t enc_ack_time_us;
+ int i;
+
+ if (!rdp_surface->valid)
+ rdp_surface->valid = TRUE;
+
+ aligned_width = surface_width + (surface_width % 16 ? 16 - surface_width % 16 : 0);
+ aligned_height = surface_height + (surface_height % 64 ? 64 - surface_height % 64 : 0);
+
+ if (!grd_rdp_nvenc_avc420_encode_bgrx_frame (graphics_pipeline->rdp_nvenc,
+ hwaccel_context->encode_session_id,
+ src_data,
+ surface_width, surface_height,
+ aligned_width, aligned_height,
+ &avc420.data, &avc420.length))
+ {
+ g_warning ("[RDP.RDPGFX] Failed to encode YUV420 frame");
+ return FALSE;
+ }
+
+ GetSystemTime (&system_time);
+ cmd_start.timestamp = system_time.wHour << 22 |
+ system_time.wMinute << 16 |
+ system_time.wSecond << 10 |
+ system_time.wMilliseconds;
+ cmd_start.frameId = get_next_free_frame_id (graphics_pipeline);
+ cmd_end.frameId = cmd_start.frameId;
+
+ cairo_region_get_extents (region, ®ion_extents);
+
+ cmd.surfaceId = grd_rdp_gfx_surface_get_surface_id (gfx_surface);
+ cmd.codecId = RDPGFX_CODECID_AVC420;
+ cmd.format = PIXEL_FORMAT_BGRX32;
+ cmd.left = 0;
+ cmd.top = 0;
+ cmd.right = region_extents.x + region_extents.width;
+ cmd.bottom = region_extents.y + region_extents.height;
+ cmd.length = 0;
+ cmd.data = NULL;
+ cmd.extra = &avc420;
+
+ avc420.meta.numRegionRects = n_rects = cairo_region_num_rectangles (region);
+ avc420.meta.regionRects = g_malloc0 (n_rects * sizeof (RECTANGLE_16));
+ avc420.meta.quantQualityVals = g_malloc0 (n_rects * sizeof (RDPGFX_H264_QUANT_QUALITY));
+ for (i = 0; i < n_rects; ++i)
+ {
+ cairo_region_get_rectangle (region, i, &cairo_rect);
+
+ avc420.meta.regionRects[i].left = cairo_rect.x;
+ avc420.meta.regionRects[i].top = cairo_rect.y;
+ avc420.meta.regionRects[i].right = cairo_rect.x + cairo_rect.width;
+ avc420.meta.regionRects[i].bottom = cairo_rect.y + cairo_rect.height;
+
+ avc420.meta.quantQualityVals[i].qp = 22;
+ avc420.meta.quantQualityVals[i].p = hwaccel_context->has_first_frame ? 1 : 0;
+ avc420.meta.quantQualityVals[i].qualityVal = 100;
+ }
+ hwaccel_context->has_first_frame = TRUE;
+
+ g_mutex_lock (&graphics_pipeline->gfx_mutex);
+ enc_ack_time_us = g_get_monotonic_time ();
+ grd_rdp_gfx_surface_unack_frame (gfx_surface, cmd_start.frameId,
+ enc_ack_time_us);
+
+ surface_serial = grd_rdp_gfx_surface_get_serial (gfx_surface);
+ g_hash_table_insert (graphics_pipeline->frame_serial_table,
+ GUINT_TO_POINTER (cmd_start.frameId),
+ GUINT_TO_POINTER (surface_serial));
+ surface_serial_ref (graphics_pipeline, surface_serial);
+ ++graphics_pipeline->total_frames_encoded;
+
+ if (graphics_pipeline->frame_acks_suspended)
+ {
+ grd_rdp_gfx_surface_ack_frame (gfx_surface, cmd_start.frameId,
+ enc_ack_time_us);
+ enqueue_tracked_frame_info (graphics_pipeline, surface_serial,
+ cmd_start.frameId, enc_ack_time_us);
+ }
+ g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+
+ rdpgfx_context->SurfaceFrameCommand (rdpgfx_context, &cmd,
+ &cmd_start, &cmd_end);
+
+ *enc_time_us = enc_ack_time_us;
+
+ g_free (avc420.data);
+ g_free (avc420.meta.quantQualityVals);
+ g_free (avc420.meta.regionRects);
+
+ return TRUE;
+}
+#endif /* HAVE_NVENC */
+
static gboolean
rfx_progressive_write_message (RFX_MESSAGE *rfx_message,
wStream *s,
@@ -702,6 +891,10 @@ grd_rdp_graphics_pipeline_refresh_gfx (GrdRdpGraphicsPipeline *graphics_pipeline
RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context;
rdpSettings *rdp_settings = rdpgfx_context->rdpcontext->settings;
GrdSessionRdp *session_rdp = graphics_pipeline->session_rdp;
+#ifdef HAVE_NVENC
+ HWAccelContext *hwaccel_context;
+ uint16_t surface_id;
+#endif /* HAVE_NVENC */
int64_t enc_time_us;
gboolean success;
@@ -723,8 +916,24 @@ grd_rdp_graphics_pipeline_refresh_gfx (GrdRdpGraphicsPipeline *graphics_pipeline
map_surface_to_output (graphics_pipeline, rdp_surface->gfx_surface);
}
- success = refresh_gfx_surface_rfx_progressive (graphics_pipeline, rdp_surface,
- region, src_data, &enc_time_us);
+#ifdef HAVE_NVENC
+ surface_id = grd_rdp_gfx_surface_get_surface_id (rdp_surface->gfx_surface);
+ if (rdp_settings->GfxH264 &&
+ g_hash_table_lookup_extended (graphics_pipeline->surface_hwaccel_table,
+ GUINT_TO_POINTER (surface_id),
+ NULL, (gpointer *) &hwaccel_context))
+ {
+ g_assert (hwaccel_context->api == HW_ACCEL_API_NVENC);
+ success = refresh_gfx_surface_avc420 (graphics_pipeline, hwaccel_context,
+ rdp_surface, region, src_data,
+ &enc_time_us);
+ }
+ else
+#endif /* HAVE_NVENC */
+ {
+ success = refresh_gfx_surface_rfx_progressive (graphics_pipeline, rdp_surface,
+ region, src_data, &enc_time_us);
+ }
if (success)
{
@@ -1103,6 +1312,9 @@ grd_rdp_graphics_pipeline_dispose (GObject *object)
g_clear_pointer (&graphics_pipeline->encoded_frames, g_queue_free);
}
+ g_assert (g_hash_table_size (graphics_pipeline->surface_hwaccel_table) == 0);
+ g_clear_pointer (&graphics_pipeline->surface_hwaccel_table, g_hash_table_destroy);
+
g_clear_pointer (&graphics_pipeline->cap_sets, g_free);
g_assert (g_hash_table_size (graphics_pipeline->serial_surface_table) == 0);
@@ -1281,6 +1493,7 @@ grd_rdp_graphics_pipeline_init (GrdRdpGraphicsPipeline *graphics_pipeline)
graphics_pipeline->frame_serial_table = g_hash_table_new (NULL, NULL);
graphics_pipeline->serial_surface_table = g_hash_table_new_full (NULL, NULL,
NULL, g_free);
+ graphics_pipeline->surface_hwaccel_table = g_hash_table_new (NULL, NULL);
graphics_pipeline->encoded_frames = g_queue_new ();
graphics_pipeline->enc_times = g_queue_new ();
diff --git a/src/grd-rdp-graphics-pipeline.h b/src/grd-rdp-graphics-pipeline.h
index 37cfd00..78fea3a 100644
--- a/src/grd-rdp-graphics-pipeline.h
+++ b/src/grd-rdp-graphics-pipeline.h
@@ -40,6 +40,11 @@ GrdRdpGraphicsPipeline *grd_rdp_graphics_pipeline_new (GrdSessionRdp
void grd_rdp_graphics_pipeline_maybe_init (GrdRdpGraphicsPipeline *graphics_pipeline);
+#ifdef HAVE_NVENC
+void grd_rdp_graphics_pipeline_set_nvenc (GrdRdpGraphicsPipeline *graphics_pipeline,
+ GrdRdpNvenc *rdp_nvenc);
+#endif /* HAVE_NVENC */
+
void grd_rdp_graphics_pipeline_create_surface (GrdRdpGraphicsPipeline *graphics_pipeline,
GrdRdpGfxSurface *gfx_surface);
diff --git a/src/grd-rdp-server.c b/src/grd-rdp-server.c
index 3eb13a1..8624eab 100644
--- a/src/grd-rdp-server.c
+++ b/src/grd-rdp-server.c
@@ -28,6 +28,7 @@
#include <winpr/ssl.h>
#include "grd-context.h"
+#include "grd-rdp-nvenc.h"
#include "grd-session-rdp.h"
enum
@@ -47,6 +48,9 @@ struct _GrdRdpServer
guint idle_task;
GrdContext *context;
+#ifdef HAVE_NVENC
+ GrdRdpNvenc *rdp_nvenc;
+#endif /* HAVE_NVENC */
};
G_DEFINE_TYPE (GrdRdpServer, grd_rdp_server, G_TYPE_SOCKET_SERVICE);
@@ -111,7 +115,11 @@ on_incoming (GSocketService *service,
g_debug ("New incoming RDP connection");
- if (!(session_rdp = grd_session_rdp_new (rdp_server, connection)))
+ if (!(session_rdp = grd_session_rdp_new (rdp_server, connection,
+#ifdef HAVE_NVENC
+ rdp_server->rdp_nvenc,
+#endif /* HAVE_NVENC */
+ 0)))
return TRUE;
rdp_server->sessions = g_list_append (rdp_server->sessions, session_rdp);
@@ -190,6 +198,10 @@ grd_rdp_server_dispose (GObject *object)
{
GrdRdpServer *rdp_server = GRD_RDP_SERVER (object);
+#ifdef HAVE_NVENC
+ g_clear_object (&rdp_server->rdp_nvenc);
+#endif /* HAVE_NVENC */
+
if (rdp_server->idle_task)
{
g_source_remove (rdp_server->idle_task);
@@ -226,6 +238,22 @@ grd_rdp_server_init (GrdRdpServer *rdp_server)
* Run the primitives benchmark here to save time, when initializing a session
*/
primitives_get ();
+
+#ifdef HAVE_NVENC
+ rdp_server->rdp_nvenc = grd_rdp_nvenc_new ();
+ if (rdp_server->rdp_nvenc)
+ {
+ g_debug ("[RDP] Initialization of NVENC was successful");
+ }
+ else
+ {
+ g_message ("[RDP] Initialization of NVENC failed. "
+ "No hardware acceleration available");
+ }
+#else
+ g_message ("[RDP] RDP backend is built WITHOUT support for NVENC and CUDA. "
+ "No hardware acceleration available");
+#endif /* HAVE_NVENC */
}
static void
diff --git a/src/grd-session-rdp.c b/src/grd-session-rdp.c
index 217c6dd..deeb777 100644
--- a/src/grd-session-rdp.c
+++ b/src/grd-session-rdp.c
@@ -141,6 +141,10 @@ struct _GrdSessionRdp
NSCThreadPoolContext nsc_thread_pool_context;
RawThreadPoolContext raw_thread_pool_context;
+#ifdef HAVE_NVENC
+ GrdRdpNvenc *rdp_nvenc;
+#endif /*HAVE_NVENC*/
+
GSource *pending_encode_source;
unsigned int close_session_idle_id;
@@ -1627,6 +1631,10 @@ rdp_peer_post_connect (freerdp_peer *peer)
rdp_peer_context->network_autodetection,
rdp_peer_context->encode_stream,
rdp_peer_context->rfx_context);
+#ifdef HAVE_NVENC
+ grd_rdp_graphics_pipeline_set_nvenc (rdp_peer_context->graphics_pipeline,
+ session_rdp->rdp_nvenc);
+#endif /* HAVE_NVENC */
}
grd_session_start (GRD_SESSION (session_rdp));
@@ -1893,7 +1901,11 @@ socket_thread_func (gpointer data)
GrdSessionRdp *
grd_session_rdp_new (GrdRdpServer *rdp_server,
- GSocketConnection *connection)
+ GSocketConnection *connection,
+#ifdef HAVE_NVENC
+ GrdRdpNvenc *rdp_nvenc,
+#endif /* HAVE_NVENC */
+ int reserved)
{
GrdSessionRdp *session_rdp;
GrdContext *context;
@@ -1923,6 +1935,9 @@ grd_session_rdp_new (GrdRdpServer *rdp_server,
NULL);
session_rdp->connection = g_object_ref (connection);
+#ifdef HAVE_NVENC
+ session_rdp->rdp_nvenc = rdp_nvenc;
+#endif /* HAVE_NVENC */
session_rdp->start_event = CreateEvent (NULL, TRUE, FALSE, NULL);
session_rdp->stop_event = CreateEvent (NULL, TRUE, FALSE, NULL);
diff --git a/src/grd-session-rdp.h b/src/grd-session-rdp.h
index 61dc806..6e3c592 100644
--- a/src/grd-session-rdp.h
+++ b/src/grd-session-rdp.h
@@ -33,7 +33,11 @@ G_DECLARE_FINAL_TYPE (GrdSessionRdp,
GrdSession);
GrdSessionRdp *grd_session_rdp_new (GrdRdpServer *rdp_server,
- GSocketConnection *connection);
+ GSocketConnection *connection,
+#ifdef HAVE_NVENC
+ GrdRdpNvenc *rdp_nvenc,
+#endif /* HAVE_NVENC */
+ int reserved);
void grd_session_rdp_notify_error (GrdSessionRdp *session_rdp,
uint32_t error_info);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]