[gnome-remote-desktop] rdp: Add and implement classes for the graphics pipeline



commit 7a3031ba9617e36588300f1157db1d7433306e9f
Author: Pascal Nowack <Pascal Nowack gmx de>
Date:   Sat Mar 20 19:31:19 2021 +0100

    rdp: Add and implement classes for the graphics pipeline
    
    Starting with Windows 8, Microsoft revamped the graphics handling for
    RDP with the graphics pipeline.
    The graphics pipeline is a dynamic channel that redefines how frame
    updates are handled:
    
    1. Frame updates don't have to happen directly on the graphics output
       buffer any more.
       Instead, surfaces are used. These surfaces can be user-visible
       (onscreen) surfaces, or be used in the background for different
       purposes, like caching, using the surface to composite frame content
       to another surface using SurfaceToSurface, SurfaceToCache,
       CacheToSurface updates.
    2. Each frame update, whether it is a WireToSurface, SurfaceToSurface,
       etc., MUST be grouped into logical frames.
       These logical frames will then be used for the frame acknowledge,
       but can also be used for other purposes like preventing tearing.
       The legacy path also allows the usage of something like frame
       markers to mark logical frames.
       However, the usage of logical frames in the graphics pipeline is
       mandatory, as they are necessary for tracking frames, allowing the
       server to e.g. slow down the encoding rate, when the client is too
       slow with the decoding process.
    3. The graphics pipeline is a dynamic channel, which allows the
       graphics pipeline to also run via UDP in the future.
       Additionally, gnome-remote-desktop won't have to care about things
       like the MultifragMaxRequestSize any more, when pushing updates, as
       the dynamic channel handles splitting up the packages itself.
       Things like "pushing n tiles" now, and pushing the other m tiles in
       another PDU won't have to happen any more, like in the legacy path.
       This is especially useful, when handling codecs like H264, where
       gnome-remote-desktop won't know how the encoded data is structured.
    4. Progressive rendering: The RemoteFX Calista Progressive codec and
       H264 can be used for encoding content.
       Both codecs support the usage of progressive rendering.
       The server will then track the client state of the codec context
       and will possibly push progressive updates, which depend on the
       previous data that has been sent, allowing the server to reduce the
       bandwidth usage.
    5. For gnome-remote-desktop specially this also means that the encoding
       thread won't have to block any more, when pushing the frame update.
       Instead, like in the case of the cliprdr channel, the update is
       pushed to a queue, which will then be pushed in the socket thread,
       allowing the encoding thread to save time.
    
    For the graphics pipeline, gnome-remote-desktop needs to implement the
    following PDUs:
    
    CapsAdvertise:
    
    The RDP client sends this PDU once the graphics pipeline has been
    opened.
    It contains the capability sets, which the client supports.
    Each capability set may have contained some flags, like whether H264 is
    supported by the client or not.
    This PDU can also be sent during the connection to reset the graphics
    pipeline.
    This is only possible, when the first accepted capability set was at
    least version RDPGFX_CAPVERSION_103.
    
    CacheImportOffer:
    
    This PDU is usually sent by the client once the capabilities have been
    exchanged.
    The client tells the server, what currently is in its offline surface
    cache.
    Not every client, however, makes use of this PDU. xfreerdp, for
    example, won't ever send this PDU to the server, which means that
    xfreerdp won't have a frame cache, that is persistent between
    connections.
    
    FrameAcknowledge:
    
    This PDU is important. The client sends this PDU after a logical frame
    has been decoded and displayed by the client.
    It will contain the frame id, the amount of frames, that have been
    decoded by the client since the last protocol reset, and the queue
    depth.
    The queue depth can either provide no information, the amount of data,
    that is unprocessed by the client (in bytes) or indicate, that the
    client suspends the frame acknowledgement.
    In the latter case, the server side just assumes, that the client is
    fast enough to handle the frame updates.
    The client can, however, still opt back into frame acknowledgement by
    sending this PDU again with the queue depth not being set to the magic
    value, that indicates the frame acknowledgement suspension.
    
    QoeFrameAcknowledge:
    
    This is an optional PDU, which is usually sent after the
    FrameAcknowledge PDU containing information, like the time, that the
    client needed to decode and display the frame.
    This PDU is normally not sent. The usage is usually for debugging
    purposes only.
    
    This commit implements two classes: the graphics pipeline and the GFX
    surface.
    The GFX surface corresponds to an RDP surface. It is also autonomous by
    also being able to decide whether frame updates for a surface should be
    suspended or not.
    The usage here is to control the encoding rate, which usually happens,
    if the client is too slow with the decoding and displaying process to
    keep up with the server.
    This is done with the help of the GFX frame log.
    By default, the GFX surface always allows one in flight frame, before
    it will throttle the encoding rate.
    The encoding rate is controlled by suspending the encoding and it is
    resumed with an internal GSource, which runs in the same thread as the
    main encoding GSource.
    While this commit does not consider the round trip time yet, the GFX
    surface already has the handling for round trip times:
    
    The mechanism for this is similar to a Schmitt trigger:
    By default, the GFX surface enters the throttling mode, when two or
    more frame acks are missing.
    When this limit is reached, the encoding rate is determined by the ack
    rate of the client, meaning the encoding rate won't surpass the ack
    rate.
    This obviously only works, if the server is constantly encoding, which
    is e.g. the case, when watching a video.
    This throttling mechanism has a specific advantage: It is independent
    from the round trip time.
    Using the round trip time in the throttling mode can have the opposite
    behaviour of throttling by allowing more and more frames, since the
    round trip time would always increase, since the client gets flooded
    with too many frames, which has the effect, that the client might not
    be able to handle the RTTResponse directly.
    To leave the throttling mode, the amount of pending frame acks must
    fall below two again.
    
    This means: Whether the GFX surface throttles the encoding rate,
    depends on the amount of pending frame acks.
    The encoding rate is in the throttling mode determined by the encoding
    and ack rate.
    In later commits, this will also consider the round trip time by
    increasing the activate threshold.
    This will then allow gnome-remote-desktop handle fast clients with low
    latency, slow clients with low latency, fast clients with high latency,
    and slow clients with high latency.
    
    By letting the GFX surface handle the throttling, instead of the
    graphics pipeline, the client can optimize its frame handling by e.g.
    letting a specific GPU handle a specific surface, in case of hardware
    acceleration is being used.
    
    For the frame acknowledge in the graphics pipeline, the graphics
    pipeline needs to be able to track the frame ids too.
    The RDP client is allowed to suspend or resume the frame acknowledge.
    mstsc, for example, makes use of that.
    mstsc usually opts out of frame acknowledgement after the first frame
    was received.
    Only if mstsc realizes that it cannot keep up with the server, it will
    opt back into frame acknowledgement.
    In that case the graphics pipeline needs to restore the state of the
    client.
    To do that, track the frame ids of the encoded frames, regardless
    whether frame acknowledgement is suspended or not.
    When the frame acknowledgement is suspended, push the frame info about
    the currently encoded frame to a queue.
    This queue has a limit of 1000 tracked frames.
    When the client opts back into frame acknowledgement, use the total
    frames decoded value to determine how far the client lags behind.
    The, that way calculated amount of pending frame acks, will then be
    used to unack the last n frames, where n corresponds to the amount of
    the pending frame acks.
    The GFX surfaces will then reevaluate their throttling state.
    
    Another situation that needs to handled is, when a frame ack is
    received in a different order:
    1. frame_ack_n+1, which suspends the frame acknowledgement
    2. frame_ack_n+0, which continues the frame acknowledgement
    
    In this situation, the implementation of the graphics pipeline will now
    take a look at the pending frame acks using the total frames decoded
    value, which is included in the FrameAcknowledge PDU.
    If the value is <= 1000, then the graphics pipeline discards the PDU,
    since the frame had been already auto-acked with the frame_ack_n+1 due
    the SUSPEND_FRAME_ACKNOWLEDGEMENT indication.
    If the value is > 1000, which is highly unlikely, then the PDU won't be
    discarded.
    If the client really would lag that amount of frames behind, then there
    is certainly something wrong with the client.
    In that case, gnome-remote-desktop won't discard the PDU.
    
    Currently, only RFX Progressive with TILE_SIMPLE tiles (non-progressive
    encoding) is supported by the current implementation of the graphics
    pipeline.
    This codec MUST be supported by the client, when using the graphics
    pipeline.
    In the future, the RFX Progressive codec will be used as fallback, when
    other more efficient codecs are not available (like H264).

 src/grd-rdp-gfx-surface.c       |  277 +++++++++
 src/grd-rdp-gfx-surface.h       |   29 +
 src/grd-rdp-graphics-pipeline.c | 1175 +++++++++++++++++++++++++++++++++++++++
 src/grd-rdp-graphics-pipeline.h |   59 ++
 src/grd-types.h                 |    1 +
 src/meson.build                 |    2 +
 6 files changed, 1543 insertions(+)
---
diff --git a/src/grd-rdp-gfx-surface.c b/src/grd-rdp-gfx-surface.c
index 8b8d89b..f1b5532 100644
--- a/src/grd-rdp-gfx-surface.c
+++ b/src/grd-rdp-gfx-surface.c
@@ -21,19 +21,296 @@
 
 #include "grd-rdp-gfx-surface.h"
 
+#include "grd-rdp-gfx-frame-log.h"
+#include "grd-rdp-graphics-pipeline.h"
+#include "grd-rdp-surface.h"
+#include "grd-session-rdp.h"
+
+#define ACTIVATE_THROTTLING_TH_DEFAULT 2
+#define DEACTIVATE_THROTTLING_TH_DEFAULT 1
+
+typedef enum _ThrottlingState
+{
+  THROTTLING_STATE_INACTIVE,
+  THROTTLING_STATE_ACTIVE,
+} ThrottlingState;
+
 struct _GrdRdpGfxSurface
 {
   GObject parent;
+
+  GrdRdpGraphicsPipeline *graphics_pipeline;
+  GrdSessionRdp *session_rdp;
+  GrdRdpSurface *rdp_surface;
+  gboolean created;
+
+  uint16_t surface_id;
+  uint32_t codec_context_id;
+  uint32_t serial;
+
+  GSource *pending_encode_source;
+
+  GrdRdpGfxFrameLog *frame_log;
+
+  ThrottlingState throttling_state;
+  /* Throttling triggers on >= activate_throttling_th */
+  uint32_t activate_throttling_th;
+  /* Throttling triggers on <= deactivate_throttling_th */
+  uint32_t deactivate_throttling_th;
 };
 
 G_DEFINE_TYPE (GrdRdpGfxSurface, grd_rdp_gfx_surface, G_TYPE_OBJECT);
 
+uint16_t
+grd_rdp_gfx_surface_get_surface_id (GrdRdpGfxSurface *gfx_surface)
+{
+  return gfx_surface->surface_id;
+}
+
+uint32_t
+grd_rdp_gfx_surface_get_codec_context_id (GrdRdpGfxSurface *gfx_surface)
+{
+  return gfx_surface->codec_context_id;
+}
+
+uint32_t
+grd_rdp_gfx_surface_get_serial (GrdRdpGfxSurface *gfx_surface)
+{
+  return gfx_surface->serial;
+}
+
+GrdRdpSurface *
+grd_rdp_gfx_surface_get_rdp_surface (GrdRdpGfxSurface *gfx_surface)
+{
+  return gfx_surface->rdp_surface;
+}
+
+void
+grd_rdp_gfx_surface_unack_frame (GrdRdpGfxSurface *gfx_surface,
+                                 uint32_t          frame_id,
+                                 int64_t           enc_time_us)
+{
+  GrdRdpSurface *rdp_surface = gfx_surface->rdp_surface;
+  GrdRdpGfxFrameLog *frame_log = gfx_surface->frame_log;
+  uint32_t n_unacked_frames;
+  uint32_t enc_rate = 0;
+  uint32_t ack_rate = 0;
+
+  grd_rdp_gfx_frame_log_track_frame (frame_log, frame_id, enc_time_us);
+
+  n_unacked_frames = grd_rdp_gfx_frame_log_get_unacked_frames_count (frame_log);
+  grd_rdp_gfx_frame_log_update_rates (frame_log, &enc_rate, &ack_rate);
+
+  switch (gfx_surface->throttling_state)
+    {
+    case THROTTLING_STATE_INACTIVE:
+      if (n_unacked_frames >= gfx_surface->activate_throttling_th)
+        {
+          gfx_surface->throttling_state = THROTTLING_STATE_ACTIVE;
+          rdp_surface->encoding_suspended = TRUE;
+        }
+      break;
+    case THROTTLING_STATE_ACTIVE:
+      rdp_surface->encoding_suspended = enc_rate > ack_rate + 1;
+      break;
+    }
+}
+
+void
+grd_rdp_gfx_surface_ack_frame (GrdRdpGfxSurface *gfx_surface,
+                               uint32_t          frame_id,
+                               int64_t           ack_time_us)
+{
+  GrdRdpSurface *rdp_surface = gfx_surface->rdp_surface;
+  GrdRdpGfxFrameLog *frame_log = gfx_surface->frame_log;
+  gboolean encoding_was_suspended;
+  uint32_t n_unacked_frames;
+  uint32_t enc_rate = 0;
+  uint32_t ack_rate = 0;
+
+  grd_rdp_gfx_frame_log_ack_tracked_frame (frame_log, frame_id, ack_time_us);
+
+  encoding_was_suspended = rdp_surface->encoding_suspended;
+  n_unacked_frames = grd_rdp_gfx_frame_log_get_unacked_frames_count (frame_log);
+  grd_rdp_gfx_frame_log_update_rates (frame_log, &enc_rate, &ack_rate);
+
+  switch (gfx_surface->throttling_state)
+    {
+    case THROTTLING_STATE_INACTIVE:
+      break;
+    case THROTTLING_STATE_ACTIVE:
+      if (n_unacked_frames <= gfx_surface->deactivate_throttling_th)
+        {
+          gfx_surface->throttling_state = THROTTLING_STATE_INACTIVE;
+          rdp_surface->encoding_suspended = FALSE;
+        }
+      else
+        {
+          rdp_surface->encoding_suspended = enc_rate > ack_rate;
+        }
+      break;
+    }
+
+  if (encoding_was_suspended && !rdp_surface->encoding_suspended)
+    g_source_set_ready_time (gfx_surface->pending_encode_source, 0);
+}
+
+static void
+reevaluate_encoding_suspension_state (GrdRdpGfxSurface *gfx_surface)
+{
+  GrdRdpSurface *rdp_surface = gfx_surface->rdp_surface;
+  GrdRdpGfxFrameLog *frame_log = gfx_surface->frame_log;
+  uint32_t n_unacked_frames;
+  uint32_t enc_rate = 0;
+  uint32_t ack_rate = 0;
+
+  n_unacked_frames = grd_rdp_gfx_frame_log_get_unacked_frames_count (frame_log);
+  grd_rdp_gfx_frame_log_update_rates (frame_log, &enc_rate, &ack_rate);
+
+  switch (gfx_surface->throttling_state)
+    {
+    case THROTTLING_STATE_INACTIVE:
+      if (n_unacked_frames >= gfx_surface->activate_throttling_th)
+        {
+          gfx_surface->throttling_state = THROTTLING_STATE_ACTIVE;
+          rdp_surface->encoding_suspended = TRUE;
+        }
+      break;
+    case THROTTLING_STATE_ACTIVE:
+      g_assert (gfx_surface->activate_throttling_th >
+                gfx_surface->deactivate_throttling_th);
+      g_assert (n_unacked_frames > gfx_surface->deactivate_throttling_th);
+      g_assert (rdp_surface->encoding_suspended);
+      break;
+    }
+}
+
+void
+grd_rdp_gfx_surface_unack_last_acked_frame (GrdRdpGfxSurface *gfx_surface,
+                                            uint32_t          frame_id,
+                                            int64_t           enc_ack_time_us)
+{
+  grd_rdp_gfx_frame_log_unack_last_acked_frame (gfx_surface->frame_log, frame_id,
+                                                enc_ack_time_us);
+  reevaluate_encoding_suspension_state (gfx_surface);
+}
+
+void
+grd_rdp_gfx_surface_clear_all_unacked_frames (GrdRdpGfxSurface *gfx_surface)
+{
+  GrdRdpSurface *rdp_surface = gfx_surface->rdp_surface;
+  gboolean encoding_was_suspended;
+
+  grd_rdp_gfx_frame_log_clear (gfx_surface->frame_log);
+
+  encoding_was_suspended = rdp_surface->encoding_suspended;
+
+  gfx_surface->throttling_state = THROTTLING_STATE_INACTIVE;
+  rdp_surface->encoding_suspended = FALSE;
+
+  if (encoding_was_suspended)
+    g_source_set_ready_time (gfx_surface->pending_encode_source, 0);
+}
+
+static gboolean
+maybe_encode_pending_frame (gpointer user_data)
+{
+  GrdRdpGfxSurface *gfx_surface = user_data;
+
+  grd_session_rdp_maybe_encode_pending_frame (gfx_surface->session_rdp,
+                                              gfx_surface->rdp_surface);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+pending_encode_source_dispatch (GSource     *source,
+                                GSourceFunc  callback,
+                                gpointer     user_data)
+{
+  g_source_set_ready_time (source, -1);
+
+  return callback (user_data);
+}
+
+static GSourceFuncs pending_encode_source_funcs =
+{
+  .dispatch = pending_encode_source_dispatch,
+};
+
+GrdRdpGfxSurface *
+grd_rdp_gfx_surface_new (GrdRdpGraphicsPipeline *graphics_pipeline,
+                         GrdSessionRdp          *session_rdp,
+                         GrdRdpSurface          *rdp_surface,
+                         uint16_t                surface_id,
+                         uint32_t                serial)
+{
+  GrdRdpGfxSurface *gfx_surface;
+
+  gfx_surface = g_object_new (GRD_TYPE_RDP_GFX_SURFACE, NULL);
+  gfx_surface->graphics_pipeline = graphics_pipeline;
+  gfx_surface->session_rdp = session_rdp;
+  gfx_surface->rdp_surface = rdp_surface;
+  gfx_surface->surface_id = surface_id;
+  /*
+   * Use the same id for the codec context as for the surface
+   * (only relevant for RDPGFX_WIRE_TO_SURFACE_PDU_2 PDUs)
+   */
+  gfx_surface->codec_context_id = surface_id;
+
+  grd_rdp_graphics_pipeline_create_surface (graphics_pipeline, gfx_surface);
+  gfx_surface->created = TRUE;
+
+  gfx_surface->pending_encode_source = g_source_new (&pending_encode_source_funcs,
+                                                     sizeof (GSource));
+  g_source_set_callback (gfx_surface->pending_encode_source,
+                         maybe_encode_pending_frame, gfx_surface, NULL);
+  g_source_set_ready_time (gfx_surface->pending_encode_source, -1);
+  g_source_attach (gfx_surface->pending_encode_source, NULL);
+
+  return gfx_surface;
+}
+
+static void
+grd_rdp_gfx_surface_dispose (GObject *object)
+{
+  GrdRdpGfxSurface *gfx_surface = GRD_RDP_GFX_SURFACE (object);
+
+  if (gfx_surface->pending_encode_source)
+    {
+      g_source_destroy (gfx_surface->pending_encode_source);
+      g_clear_pointer (&gfx_surface->pending_encode_source, g_source_unref);
+    }
+
+  if (gfx_surface->created)
+    {
+      grd_rdp_graphics_pipeline_delete_surface (gfx_surface->graphics_pipeline,
+                                                gfx_surface);
+      gfx_surface->created = FALSE;
+    }
+
+  g_clear_object (&gfx_surface->frame_log);
+
+  G_OBJECT_CLASS (grd_rdp_gfx_surface_parent_class)->dispose (object);
+}
+
 static void
 grd_rdp_gfx_surface_init (GrdRdpGfxSurface *gfx_surface)
 {
+  gfx_surface->throttling_state = THROTTLING_STATE_INACTIVE;
+  gfx_surface->activate_throttling_th = ACTIVATE_THROTTLING_TH_DEFAULT;
+  gfx_surface->deactivate_throttling_th = DEACTIVATE_THROTTLING_TH_DEFAULT;
+
+  g_assert (gfx_surface->activate_throttling_th >
+            gfx_surface->deactivate_throttling_th);
+
+  gfx_surface->frame_log = grd_rdp_gfx_frame_log_new ();
 }
 
 static void
 grd_rdp_gfx_surface_class_init (GrdRdpGfxSurfaceClass *klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = grd_rdp_gfx_surface_dispose;
 }
diff --git a/src/grd-rdp-gfx-surface.h b/src/grd-rdp-gfx-surface.h
index ef54335..856e5e0 100644
--- a/src/grd-rdp-gfx-surface.h
+++ b/src/grd-rdp-gfx-surface.h
@@ -21,6 +21,7 @@
 #define GRD_RDP_GFX_SURFACE_H
 
 #include <glib-object.h>
+#include <stdint.h>
 
 #include "grd-types.h"
 
@@ -28,4 +29,32 @@
 G_DECLARE_FINAL_TYPE (GrdRdpGfxSurface, grd_rdp_gfx_surface,
                       GRD, RDP_GFX_SURFACE, GObject);
 
+GrdRdpGfxSurface *grd_rdp_gfx_surface_new (GrdRdpGraphicsPipeline *graphics_pipeline,
+                                           GrdSessionRdp          *session_rdp,
+                                           GrdRdpSurface          *rdp_surface,
+                                           uint16_t                surface_id,
+                                           uint32_t                serial);
+
+uint16_t grd_rdp_gfx_surface_get_surface_id (GrdRdpGfxSurface *gfx_surface);
+
+uint32_t grd_rdp_gfx_surface_get_codec_context_id (GrdRdpGfxSurface *gfx_surface);
+
+uint32_t grd_rdp_gfx_surface_get_serial (GrdRdpGfxSurface *gfx_surface);
+
+GrdRdpSurface *grd_rdp_gfx_surface_get_rdp_surface (GrdRdpGfxSurface *gfx_surface);
+
+void grd_rdp_gfx_surface_unack_frame (GrdRdpGfxSurface *gfx_surface,
+                                      uint32_t          frame_id,
+                                      int64_t           enc_time_us);
+
+void grd_rdp_gfx_surface_ack_frame (GrdRdpGfxSurface *gfx_surface,
+                                    uint32_t          frame_id,
+                                    int64_t           ack_time_us);
+
+void grd_rdp_gfx_surface_unack_last_acked_frame (GrdRdpGfxSurface *gfx_surface,
+                                                 uint32_t          frame_id,
+                                                 int64_t           enc_ack_time_us);
+
+void grd_rdp_gfx_surface_clear_all_unacked_frames (GrdRdpGfxSurface *gfx_surface);
+
 #endif /* GRD_RDP_GFX_SURFACE_H */
diff --git a/src/grd-rdp-graphics-pipeline.c b/src/grd-rdp-graphics-pipeline.c
new file mode 100644
index 0000000..36a3c6f
--- /dev/null
+++ b/src/grd-rdp-graphics-pipeline.c
@@ -0,0 +1,1175 @@
+/*
+ * Copyright (C) 2021 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-graphics-pipeline.h"
+
+#include <winpr/sysinfo.h>
+
+#include "grd-rdp-frame-info.h"
+#include "grd-rdp-gfx-surface.h"
+#include "grd-rdp-surface.h"
+#include "grd-session-rdp.h"
+
+#define MAX_TRACKED_ENC_FRAMES 1000
+
+typedef struct _GfxSurfaceContext
+{
+  GrdRdpGfxSurface *gfx_surface;
+  uint64_t ref_count;
+} GfxSurfaceContext;
+
+typedef struct _GfxFrameInfo
+{
+  GrdRdpFrameInfo frame_info;
+  uint32_t surface_serial;
+} GfxFrameInfo;
+
+struct _GrdRdpGraphicsPipeline
+{
+  GObject parent;
+
+  RdpgfxServerContext *rdpgfx_context;
+  HANDLE stop_event;
+  gboolean channel_opened;
+  gboolean initialized;
+  uint32_t initial_version;
+
+  GrdSessionRdp *session_rdp;
+  wStream *encode_stream;
+  RFX_CONTEXT *rfx_context;
+
+  GSource *protocol_reset_source;
+  GMutex caps_mutex;
+  RDPGFX_CAPSET *cap_sets;
+  uint16_t n_cap_sets;
+
+  GMutex gfx_mutex;
+  GHashTable *surface_table;
+  GHashTable *codec_context_table;
+
+  /* Unacknowledged Frames ADM element */
+  GHashTable *frame_serial_table;
+
+  GHashTable *serial_surface_table;
+  gboolean frame_acks_suspended;
+
+  GQueue *encoded_frames;
+  uint32_t total_frames_encoded;
+
+  uint32_t next_frame_id;
+  uint16_t next_surface_id;
+  uint32_t next_serial;
+};
+
+G_DEFINE_TYPE (GrdRdpGraphicsPipeline, grd_rdp_graphics_pipeline, G_TYPE_OBJECT);
+
+void
+grd_rdp_graphics_pipeline_create_surface (GrdRdpGraphicsPipeline *graphics_pipeline,
+                                          GrdRdpGfxSurface       *gfx_surface)
+{
+  RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context;
+  RDPGFX_CREATE_SURFACE_PDU create_surface = {0};
+  GrdRdpSurface *rdp_surface = grd_rdp_gfx_surface_get_rdp_surface (gfx_surface);
+  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;
+
+  surface_context = g_malloc0 (sizeof (GfxSurfaceContext));
+
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  g_hash_table_insert (graphics_pipeline->surface_table,
+                       GUINT_TO_POINTER (surface_id), gfx_surface);
+
+  surface_context->gfx_surface = gfx_surface;
+  g_hash_table_insert (graphics_pipeline->serial_surface_table,
+                       GUINT_TO_POINTER (surface_serial), surface_context);
+  g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+
+  create_surface.surfaceId = surface_id;
+  create_surface.width = rdp_surface->width;
+  create_surface.height = rdp_surface->height;
+  create_surface.pixelFormat = GFX_PIXEL_FORMAT_XRGB_8888;
+
+  rdpgfx_context->CreateSurface (rdpgfx_context, &create_surface);
+}
+
+void
+grd_rdp_graphics_pipeline_delete_surface (GrdRdpGraphicsPipeline *graphics_pipeline,
+                                          GrdRdpGfxSurface       *gfx_surface)
+{
+  RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context;
+  RDPGFX_DELETE_ENCODING_CONTEXT_PDU delete_encoding_context = {0};
+  RDPGFX_DELETE_SURFACE_PDU delete_surface = {0};
+  gboolean needs_encoding_context_deletion = FALSE;
+  GfxSurfaceContext *surface_context;
+  uint16_t surface_id;
+  uint32_t codec_context_id;
+  uint32_t surface_serial;
+
+  if (!graphics_pipeline->channel_opened)
+    return;
+
+  surface_id = grd_rdp_gfx_surface_get_surface_id (gfx_surface);
+  codec_context_id = grd_rdp_gfx_surface_get_codec_context_id (gfx_surface);
+  surface_serial = grd_rdp_gfx_surface_get_serial (gfx_surface);
+
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  if (!g_hash_table_lookup_extended (graphics_pipeline->serial_surface_table,
+                                     GUINT_TO_POINTER (surface_serial),
+                                     NULL, (gpointer *) &surface_context))
+    g_assert_not_reached ();
+
+  surface_context->gfx_surface = NULL;
+  if (surface_context->ref_count == 0)
+    {
+      g_hash_table_remove (graphics_pipeline->serial_surface_table,
+                           GUINT_TO_POINTER (surface_serial));
+    }
+
+  if (g_hash_table_steal_extended (graphics_pipeline->codec_context_table,
+                                   GUINT_TO_POINTER (codec_context_id),
+                                   NULL, NULL))
+    needs_encoding_context_deletion = TRUE;
+
+  g_hash_table_remove (graphics_pipeline->surface_table,
+                       GUINT_TO_POINTER (surface_id));
+  g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+
+  if (needs_encoding_context_deletion)
+    {
+      delete_encoding_context.surfaceId = surface_id;
+      delete_encoding_context.codecContextId = codec_context_id;
+
+      rdpgfx_context->DeleteEncodingContext (rdpgfx_context,
+                                             &delete_encoding_context);
+    }
+
+  delete_surface.surfaceId = surface_id;
+
+  rdpgfx_context->DeleteSurface (rdpgfx_context, &delete_surface);
+}
+
+void
+grd_rdp_graphics_pipeline_reset_graphics (GrdRdpGraphicsPipeline *graphics_pipeline,
+                                          uint32_t                width,
+                                          uint32_t                height,
+                                          MONITOR_DEF            *monitors,
+                                          uint32_t                n_monitors)
+{
+  RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context;
+  RDPGFX_RESET_GRAPHICS_PDU reset_graphics = {0};
+  GList *surfaces;
+  GList *l;
+
+  g_debug ("[RDP.RDPGFX] Resetting graphics");
+
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  surfaces = g_hash_table_get_values (graphics_pipeline->surface_table);
+
+  g_hash_table_steal_all (graphics_pipeline->surface_table);
+  g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+
+  for (l = surfaces; l; l = l->next)
+    {
+      GrdRdpGfxSurface *gfx_surface = l->data;
+      GrdRdpSurface *rdp_surface;
+
+      rdp_surface = grd_rdp_gfx_surface_get_rdp_surface (gfx_surface);
+      g_clear_object (&rdp_surface->gfx_surface);
+    }
+  g_list_free (surfaces);
+
+  /*
+   * width and height refer here to the size of the Graphics Output Buffer
+   * ADM (Abstract Data Model) element
+   */
+  reset_graphics.width = width;
+  reset_graphics.height = height;
+  reset_graphics.monitorCount = n_monitors;
+  reset_graphics.monitorDefArray = monitors;
+
+  rdpgfx_context->ResetGraphics (rdpgfx_context, &reset_graphics);
+}
+
+static uint32_t
+get_next_free_frame_id (GrdRdpGraphicsPipeline *graphics_pipeline)
+{
+  uint32_t frame_id = graphics_pipeline->next_frame_id;
+
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  while (g_hash_table_contains (graphics_pipeline->frame_serial_table,
+                                GUINT_TO_POINTER (frame_id)))
+    ++frame_id;
+  g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+
+  graphics_pipeline->next_frame_id = frame_id + 1;
+
+  return frame_id;
+}
+
+static void
+surface_serial_ref (GrdRdpGraphicsPipeline *graphics_pipeline,
+                    uint32_t                surface_serial)
+{
+  GfxSurfaceContext *surface_context;
+
+  if (!g_hash_table_lookup_extended (graphics_pipeline->serial_surface_table,
+                                     GUINT_TO_POINTER (surface_serial),
+                                     NULL, (gpointer *) &surface_context))
+    g_assert_not_reached ();
+
+  ++surface_context->ref_count;
+}
+
+static void
+surface_serial_unref (GrdRdpGraphicsPipeline *graphics_pipeline,
+                      uint32_t                surface_serial)
+{
+  GfxSurfaceContext *surface_context;
+
+  if (!g_hash_table_lookup_extended (graphics_pipeline->serial_surface_table,
+                                     GUINT_TO_POINTER (surface_serial),
+                                     NULL, (gpointer *) &surface_context))
+    g_assert_not_reached ();
+
+  g_assert (surface_context->ref_count > 0);
+  --surface_context->ref_count;
+
+  if (!surface_context->gfx_surface && surface_context->ref_count == 0)
+    {
+      g_hash_table_remove (graphics_pipeline->serial_surface_table,
+                           GUINT_TO_POINTER (surface_serial));
+    }
+}
+
+static void
+gfx_frame_info_free (GrdRdpGraphicsPipeline *graphics_pipeline,
+                     GfxFrameInfo           *gfx_frame_info)
+{
+  uint32_t surface_serial = gfx_frame_info->surface_serial;
+
+  g_hash_table_remove (graphics_pipeline->frame_serial_table,
+                       GUINT_TO_POINTER (gfx_frame_info->frame_info.frame_id));
+  surface_serial_unref (graphics_pipeline, surface_serial);
+
+  g_free (gfx_frame_info);
+}
+
+static void
+reduce_tracked_frame_infos (GrdRdpGraphicsPipeline *graphics_pipeline,
+                            uint32_t                max_tracked_frames)
+{
+  while (g_queue_peek_head (graphics_pipeline->encoded_frames) &&
+         g_queue_get_length (graphics_pipeline->encoded_frames) > max_tracked_frames)
+    {
+      gfx_frame_info_free (graphics_pipeline,
+                           g_queue_pop_head (graphics_pipeline->encoded_frames));
+    }
+}
+
+static void
+enqueue_tracked_frame_info (GrdRdpGraphicsPipeline *graphics_pipeline,
+                            uint32_t                surface_serial,
+                            uint32_t                frame_id,
+                            int64_t                 enc_time_us)
+{
+  GfxFrameInfo *gfx_frame_info;
+
+  g_assert (MAX_TRACKED_ENC_FRAMES > 1);
+  reduce_tracked_frame_infos (graphics_pipeline, MAX_TRACKED_ENC_FRAMES - 1);
+
+  gfx_frame_info = g_malloc0 (sizeof (GfxFrameInfo));
+  gfx_frame_info->frame_info.frame_id = frame_id;
+  gfx_frame_info->frame_info.enc_time_us = enc_time_us;
+  gfx_frame_info->surface_serial = surface_serial;
+
+  g_queue_push_tail (graphics_pipeline->encoded_frames, gfx_frame_info);
+}
+
+static gboolean
+rfx_progressive_write_message (RFX_MESSAGE *rfx_message,
+                               wStream     *s,
+                               gboolean     needs_progressive_header)
+{
+  uint32_t block_len;
+  uint32_t *qv;
+  RFX_TILE *rfx_tile;
+  uint32_t tiles_data_size;
+  uint16_t i;
+
+  if (needs_progressive_header)
+    {
+      /* RFX_PROGRESSIVE_SYNC */
+      block_len = 12;
+      if (!Stream_EnsureRemainingCapacity (s, block_len))
+        return FALSE;
+
+      Stream_Write_UINT16 (s, 0xCCC0);     /* blockType */
+      Stream_Write_UINT32 (s, block_len);  /* blockLen */
+      Stream_Write_UINT32 (s, 0xCACCACCA); /* magic */
+      Stream_Write_UINT16 (s, 0x0100);     /* version */
+
+      /* RFX_PROGRESSIVE_CONTEXT */
+      block_len = 10;
+      if (!Stream_EnsureRemainingCapacity (s, block_len))
+        return FALSE;
+
+      Stream_Write_UINT16 (s, 0xCCC3);    /* blockType */
+      Stream_Write_UINT32 (s, block_len); /* blockLen */
+      Stream_Write_UINT8 (s, 0);          /* ctxId */
+      Stream_Write_UINT16 (s, 0x0040);    /* tileSize */
+      Stream_Write_UINT8 (s, 0);          /* flags */
+    }
+
+  /* RFX_PROGRESSIVE_FRAME_BEGIN */
+  block_len = 12;
+  if (!Stream_EnsureRemainingCapacity (s, block_len))
+    return FALSE;
+
+  Stream_Write_UINT16 (s, 0xCCC1);                /* blockType */
+  Stream_Write_UINT32 (s, block_len);             /* blockLen */
+  Stream_Write_UINT32 (s, rfx_message->frameIdx); /* frameIndex */
+  Stream_Write_UINT16 (s, 1);                     /* regionCount */
+
+  /* RFX_PROGRESSIVE_REGION */
+  block_len = 18;
+  block_len += rfx_message->numRects * 8;
+  block_len += rfx_message->numQuant * 5;
+  tiles_data_size = rfx_message->numTiles * 22;
+
+  for (i = 0; i < rfx_message->numTiles; i++)
+    {
+      rfx_tile = rfx_message->tiles[i];
+      tiles_data_size += rfx_tile->YLen + rfx_tile->CbLen + rfx_tile->CrLen;
+    }
+
+  block_len += tiles_data_size;
+  if (!Stream_EnsureRemainingCapacity (s, block_len))
+    return FALSE;
+
+  Stream_Write_UINT16 (s, 0xCCC4);                /* blockType */
+  Stream_Write_UINT32 (s, block_len);             /* blockLen */
+  Stream_Write_UINT8 (s, 0x40);                   /* tileSize */
+  Stream_Write_UINT16 (s, rfx_message->numRects); /* numRects */
+  Stream_Write_UINT8 (s, rfx_message->numQuant);  /* numQuant */
+  Stream_Write_UINT8 (s, 0);                      /* numProgQuant */
+  Stream_Write_UINT8 (s, 0);                      /* flags */
+  Stream_Write_UINT16 (s, rfx_message->numTiles); /* numTiles */
+  Stream_Write_UINT32 (s, tiles_data_size);       /* tilesDataSize */
+
+  for (i = 0; i < rfx_message->numRects; i++)
+    {
+      /* TS_RFX_RECT */
+      Stream_Write_UINT16 (s, rfx_message->rects[i].x);      /* x */
+      Stream_Write_UINT16 (s, rfx_message->rects[i].y);      /* y */
+      Stream_Write_UINT16 (s, rfx_message->rects[i].width);  /* width */
+      Stream_Write_UINT16 (s, rfx_message->rects[i].height); /* height */
+    }
+
+  /*
+   * The RFX_COMPONENT_CODEC_QUANT structure differs from the
+   * TS_RFX_CODEC_QUANT ([MS-RDPRFX] section 2.2.2.1.5) structure with respect
+   * to the order of the bands.
+   *             0    1    2    3    4    5    6    7    8    9
+   * RDPRFX:   LL3, LH3, HL3, HH3, LH2, HL2, HH2, LH1, HL1, HH1
+   * RDPEGFX:  LL3, HL3, LH3, HH3, HL2, LH2, HH2, HL1, LH1, HH1
+   */
+  for (i = 0, qv = rfx_message->quantVals; i < rfx_message->numQuant; ++i, qv += 10)
+    {
+      /* RFX_COMPONENT_CODEC_QUANT */
+      Stream_Write_UINT8 (s, qv[0] + (qv[2] << 4)); /* LL3, HL3 */
+      Stream_Write_UINT8 (s, qv[1] + (qv[3] << 4)); /* LH3, HH3 */
+      Stream_Write_UINT8 (s, qv[5] + (qv[4] << 4)); /* HL2, LH2 */
+      Stream_Write_UINT8 (s, qv[6] + (qv[8] << 4)); /* HH2, HL1 */
+      Stream_Write_UINT8 (s, qv[7] + (qv[9] << 4)); /* LH1, HH1 */
+    }
+
+  for (i = 0; i < rfx_message->numTiles; ++i)
+    {
+      /* RFX_PROGRESSIVE_TILE_SIMPLE */
+      rfx_tile = rfx_message->tiles[i];
+      block_len = 22 + rfx_tile->YLen + rfx_tile->CbLen + rfx_tile->CrLen;
+      Stream_Write_UINT16 (s, 0xCCC5);                     /* blockType */
+      Stream_Write_UINT32 (s, block_len);                  /* blockLen */
+      Stream_Write_UINT8 (s, rfx_tile->quantIdxY);         /* quantIdxY */
+      Stream_Write_UINT8 (s, rfx_tile->quantIdxCb);        /* quantIdxCb */
+      Stream_Write_UINT8 (s, rfx_tile->quantIdxCr);        /* quantIdxCr */
+      Stream_Write_UINT16 (s, rfx_tile->xIdx);             /* xIdx */
+      Stream_Write_UINT16 (s, rfx_tile->yIdx);             /* yIdx */
+      Stream_Write_UINT8 (s, 0);                           /* flags */
+      Stream_Write_UINT16 (s, rfx_tile->YLen);             /* YLen */
+      Stream_Write_UINT16 (s, rfx_tile->CbLen);            /* CbLen */
+      Stream_Write_UINT16 (s, rfx_tile->CrLen);            /* CrLen */
+      Stream_Write_UINT16 (s, 0);                          /* tailLen */
+      Stream_Write (s, rfx_tile->YData, rfx_tile->YLen);   /* YData */
+      Stream_Write (s, rfx_tile->CbData, rfx_tile->CbLen); /* CbData */
+      Stream_Write (s, rfx_tile->CrData, rfx_tile->CrLen); /* CrData */
+    }
+
+  /* RFX_PROGRESSIVE_FRAME_END */
+  block_len = 6;
+  if (!Stream_EnsureRemainingCapacity (s, block_len))
+    return FALSE;
+
+  Stream_Write_UINT16 (s, 0xCCC2);    /* blockType */
+  Stream_Write_UINT32 (s, block_len); /* blockLen */
+
+  return TRUE;
+}
+
+static void
+refresh_gfx_surface_rfx_progressive (GrdRdpGraphicsPipeline *graphics_pipeline,
+                                     GrdRdpSurface          *rdp_surface,
+                                     cairo_region_t         *region,
+                                     uint8_t                *src_data)
+{
+  RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context;
+  GrdSessionRdp *session_rdp = graphics_pipeline->session_rdp;
+  GrdRdpGfxSurface *gfx_surface = rdp_surface->gfx_surface;
+  uint32_t src_stride = grd_session_rdp_get_stride_for_width (session_rdp,
+                                                              rdp_surface->width);
+  RDPGFX_SURFACE_COMMAND cmd = {0};
+  RDPGFX_START_FRAME_PDU cmd_start = {0};
+  RDPGFX_END_FRAME_PDU cmd_end = {0};
+  gboolean needs_progressive_header = FALSE;
+  cairo_rectangle_int_t cairo_rect;
+  RFX_RECT *rfx_rects, *rfx_rect;
+  int n_rects;
+  RFX_MESSAGE *rfx_message;
+  SYSTEMTIME system_time;
+  uint32_t codec_context_id;
+  uint32_t surface_serial;
+  int64_t enc_ack_time_us;
+  int i;
+
+  graphics_pipeline->rfx_context->mode = RLGR1;
+  if (!rdp_surface->valid)
+    {
+      rfx_context_reset (graphics_pipeline->rfx_context,
+                         rdp_surface->width, rdp_surface->height);
+      rdp_surface->valid = TRUE;
+    }
+
+  codec_context_id = grd_rdp_gfx_surface_get_codec_context_id (gfx_surface);
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  if (!g_hash_table_contains (graphics_pipeline->codec_context_table,
+                              GUINT_TO_POINTER (codec_context_id)))
+    needs_progressive_header = TRUE;
+  g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+
+  n_rects = cairo_region_num_rectangles (region);
+  rfx_rects = g_malloc0 (n_rects * sizeof (RFX_RECT));
+  for (i = 0; i < n_rects; ++i)
+    {
+      cairo_region_get_rectangle (region, i, &cairo_rect);
+
+      rfx_rect = &rfx_rects[i];
+      rfx_rect->x = cairo_rect.x;
+      rfx_rect->y = cairo_rect.y;
+      rfx_rect->width = cairo_rect.width;
+      rfx_rect->height = cairo_rect.height;
+    }
+
+  rfx_message = rfx_encode_message (graphics_pipeline->rfx_context,
+                                    rfx_rects,
+                                    n_rects,
+                                    src_data,
+                                    rdp_surface->width,
+                                    rdp_surface->height,
+                                    src_stride);
+  g_free (rfx_rects);
+
+  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;
+
+  cmd.surfaceId = grd_rdp_gfx_surface_get_surface_id (gfx_surface);
+  cmd.codecId = RDPGFX_CODECID_CAPROGRESSIVE;
+  cmd.contextId = codec_context_id;
+  cmd.format = PIXEL_FORMAT_BGRX32;
+
+  Stream_SetPosition (graphics_pipeline->encode_stream, 0);
+  if (!rfx_progressive_write_message (rfx_message,
+                                      graphics_pipeline->encode_stream,
+                                      needs_progressive_header))
+    {
+      g_warning ("[RDP.RDPGFX] rfx_progressive_write_message() failed");
+      rfx_message_free (graphics_pipeline->rfx_context, rfx_message);
+      return;
+    }
+  rfx_message_free (graphics_pipeline->rfx_context, rfx_message);
+
+  cmd.length = Stream_GetPosition (graphics_pipeline->encode_stream);
+  cmd.data = Stream_Buffer (graphics_pipeline->encode_stream);
+
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  if (needs_progressive_header)
+    {
+      g_hash_table_insert (graphics_pipeline->codec_context_table,
+                           GUINT_TO_POINTER (codec_context_id), gfx_surface);
+    }
+
+  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);
+}
+
+static uint16_t
+get_next_free_surface_id (GrdRdpGraphicsPipeline *graphics_pipeline)
+{
+  uint16_t surface_id = graphics_pipeline->next_surface_id;
+
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  while (g_hash_table_contains (graphics_pipeline->surface_table,
+                                GUINT_TO_POINTER (surface_id)))
+    ++surface_id;
+  g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+
+  graphics_pipeline->next_surface_id = surface_id + 1;
+
+  return surface_id;
+}
+
+static uint32_t
+get_next_free_serial (GrdRdpGraphicsPipeline *graphics_pipeline)
+{
+  uint32_t serial = graphics_pipeline->next_serial;
+
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  while (g_hash_table_contains (graphics_pipeline->serial_surface_table,
+                                GUINT_TO_POINTER (serial)))
+    ++serial;
+  g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+
+  graphics_pipeline->next_serial = serial + 1;
+
+  return serial;
+}
+
+static void
+map_surface_to_output (GrdRdpGraphicsPipeline *graphics_pipeline,
+                       GrdRdpGfxSurface       *gfx_surface)
+{
+  RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context;
+  RDPGFX_MAP_SURFACE_TO_OUTPUT_PDU map_surface_to_output = {0};
+  GrdRdpSurface *rdp_surface = grd_rdp_gfx_surface_get_rdp_surface (gfx_surface);
+  uint16_t surface_id = grd_rdp_gfx_surface_get_surface_id (gfx_surface);
+
+  map_surface_to_output.surfaceId = surface_id;
+  map_surface_to_output.outputOriginX = rdp_surface->output_origin_x;
+  map_surface_to_output.outputOriginY = rdp_surface->output_origin_y;
+
+  rdpgfx_context->MapSurfaceToOutput (rdpgfx_context, &map_surface_to_output);
+}
+
+void
+grd_rdp_graphics_pipeline_refresh_gfx (GrdRdpGraphicsPipeline *graphics_pipeline,
+                                       GrdRdpSurface          *rdp_surface,
+                                       cairo_region_t         *region,
+                                       uint8_t                *src_data)
+{
+  GrdSessionRdp *session_rdp = graphics_pipeline->session_rdp;
+
+  if (!rdp_surface->gfx_surface)
+    rdp_surface->valid = FALSE;
+  if (!rdp_surface->valid)
+    g_clear_object (&rdp_surface->gfx_surface);
+  if (!rdp_surface->gfx_surface)
+    {
+      rdp_surface->gfx_surface = grd_rdp_gfx_surface_new (
+        graphics_pipeline, session_rdp, rdp_surface,
+        get_next_free_surface_id (graphics_pipeline),
+        get_next_free_serial (graphics_pipeline));
+      map_surface_to_output (graphics_pipeline, rdp_surface->gfx_surface);
+    }
+
+  refresh_gfx_surface_rfx_progressive (graphics_pipeline, rdp_surface,
+                                       region, src_data);
+}
+
+static uint32_t cap_list[] =
+{
+  RDPGFX_CAPVERSION_106,
+  RDPGFX_CAPVERSION_105,
+  RDPGFX_CAPVERSION_104,
+  RDPGFX_CAPVERSION_103,
+  RDPGFX_CAPVERSION_102,
+  RDPGFX_CAPVERSION_101,
+  RDPGFX_CAPVERSION_10,
+  RDPGFX_CAPVERSION_81,
+  RDPGFX_CAPVERSION_8,
+};
+
+static gboolean
+cap_sets_contains_supported_version (RDPGFX_CAPSET *cap_sets,
+                                     uint16_t       n_cap_sets)
+{
+  size_t i;
+  uint16_t j;
+
+  for (i = 0; i < G_N_ELEMENTS (cap_list); ++i)
+    {
+      for (j = 0; j < n_cap_sets; ++j)
+        {
+          if (cap_sets[j].version == cap_list[i])
+            return TRUE;
+        }
+    }
+
+  g_warning ("[RDP.RDPGFX] Client did not advertise any supported "
+             "capability set");
+
+  return FALSE;
+}
+
+static uint32_t
+rdpgfx_caps_advertise (RdpgfxServerContext             *rdpgfx_context,
+                       const RDPGFX_CAPS_ADVERTISE_PDU *caps_advertise)
+{
+  GrdRdpGraphicsPipeline *graphics_pipeline = rdpgfx_context->custom;
+  GrdSessionRdp *session_rdp = graphics_pipeline->session_rdp;
+
+  g_debug ("[RDP.RDPGFX] Received a CapsAdvertise PDU");
+
+  if (graphics_pipeline->initialized &&
+      graphics_pipeline->initial_version < RDPGFX_CAPVERSION_103)
+    {
+      g_warning ("[RDP.RDPGFX] Protocol violation: Received an illegal "
+                 "CapsAdvertise PDU (RDPGFX: initialized, initial "
+                 "version < 103)");
+      grd_session_rdp_notify_error (graphics_pipeline->session_rdp,
+                                    ERRINFO_GRAPHICS_SUBSYSTEM_FAILED);
+
+      return CHANNEL_RC_ALREADY_INITIALIZED;
+    }
+
+  if (!cap_sets_contains_supported_version (caps_advertise->capsSets,
+                                            caps_advertise->capsSetCount))
+    {
+      g_warning ("[RDP.RDPGFX] CapsAdvertise PDU does NOT contain any supported "
+                 "capability sets");
+      grd_session_rdp_notify_error (graphics_pipeline->session_rdp,
+                                    ERRINFO_GRAPHICS_SUBSYSTEM_FAILED);
+
+      return CHANNEL_RC_UNSUPPORTED_VERSION;
+    }
+
+  g_mutex_lock (&graphics_pipeline->caps_mutex);
+  g_clear_pointer (&graphics_pipeline->cap_sets, g_free);
+
+  graphics_pipeline->n_cap_sets = caps_advertise->capsSetCount;
+  graphics_pipeline->cap_sets = g_memdup2 (caps_advertise->capsSets,
+                                           graphics_pipeline->n_cap_sets *
+                                           sizeof (RDPGFX_CAPSET));
+  g_mutex_unlock (&graphics_pipeline->caps_mutex);
+
+  grd_session_rdp_notify_graphics_pipeline_reset (session_rdp);
+  g_source_set_ready_time (graphics_pipeline->protocol_reset_source, 0);
+
+  return CHANNEL_RC_OK;
+}
+
+static uint32_t
+rdpgfx_cache_import_offer (RdpgfxServerContext                 *rdpgfx_context,
+                           const RDPGFX_CACHE_IMPORT_OFFER_PDU *cache_import_offer)
+{
+  RDPGFX_CACHE_IMPORT_REPLY_PDU cache_import_reply = {0};
+
+  return rdpgfx_context->CacheImportReply (rdpgfx_context, &cache_import_reply);
+}
+
+static void
+maybe_rewrite_frame_history (GrdRdpGraphicsPipeline *graphics_pipeline,
+                             uint32_t                pending_frame_acks)
+{
+  GfxFrameInfo *gfx_frame_info;
+
+  if (g_queue_get_length (graphics_pipeline->encoded_frames) == 0)
+    return;
+
+  reduce_tracked_frame_infos (graphics_pipeline, pending_frame_acks + 1);
+
+  while ((gfx_frame_info = g_queue_pop_tail (graphics_pipeline->encoded_frames)))
+    {
+      GrdRdpFrameInfo *frame_info = &gfx_frame_info->frame_info;
+      uint32_t surface_serial = gfx_frame_info->surface_serial;
+      GfxSurfaceContext *surface_context;
+
+      if (!g_hash_table_lookup_extended (graphics_pipeline->serial_surface_table,
+                                         GUINT_TO_POINTER (surface_serial),
+                                         NULL, (gpointer *) &surface_context))
+        g_assert_not_reached ();
+
+      if (surface_context->gfx_surface)
+        {
+          grd_rdp_gfx_surface_unack_last_acked_frame (surface_context->gfx_surface,
+                                                      frame_info->frame_id,
+                                                      frame_info->enc_time_us);
+        }
+
+      g_free (gfx_frame_info);
+    }
+}
+
+static void
+clear_all_unacked_frames_in_gfx_surface (gpointer key,
+                                         gpointer value,
+                                         gpointer user_data)
+{
+  GrdRdpGfxSurface *gfx_surface = value;
+
+  grd_rdp_gfx_surface_clear_all_unacked_frames (gfx_surface);
+}
+
+static gboolean
+frame_serial_free (gpointer key,
+                   gpointer value,
+                   gpointer user_data)
+{
+  GrdRdpGraphicsPipeline *graphics_pipeline = user_data;
+  uint32_t surface_serial = GPOINTER_TO_UINT (value);
+
+  surface_serial_unref (graphics_pipeline, surface_serial);
+
+  return TRUE;
+}
+
+static void
+suspend_frame_acknowledgement (GrdRdpGraphicsPipeline *graphics_pipeline)
+{
+  graphics_pipeline->frame_acks_suspended = TRUE;
+
+  g_hash_table_foreach (graphics_pipeline->surface_table,
+                        clear_all_unacked_frames_in_gfx_surface, NULL);
+
+  reduce_tracked_frame_infos (graphics_pipeline, 0);
+  g_hash_table_foreach_remove (graphics_pipeline->frame_serial_table,
+                               frame_serial_free, graphics_pipeline);
+}
+
+static void
+handle_frame_ack_event (GrdRdpGraphicsPipeline             *graphics_pipeline,
+                        const RDPGFX_FRAME_ACKNOWLEDGE_PDU *frame_acknowledge)
+{
+  uint32_t pending_frame_acks;
+  gpointer value = NULL;
+
+  pending_frame_acks = graphics_pipeline->total_frames_encoded -
+                       frame_acknowledge->totalFramesDecoded;
+  if (pending_frame_acks <= MAX_TRACKED_ENC_FRAMES &&
+      !g_hash_table_contains (graphics_pipeline->frame_serial_table,
+                              GUINT_TO_POINTER (frame_acknowledge->frameId)))
+    return;
+
+  maybe_rewrite_frame_history (graphics_pipeline, pending_frame_acks);
+  if (frame_acknowledge->queueDepth != SUSPEND_FRAME_ACKNOWLEDGEMENT)
+    graphics_pipeline->frame_acks_suspended = FALSE;
+
+  if (g_hash_table_steal_extended (graphics_pipeline->frame_serial_table,
+                                   GUINT_TO_POINTER (frame_acknowledge->frameId),
+                                   NULL, &value))
+    {
+      GfxSurfaceContext *surface_context;
+      uint32_t surface_serial;
+
+      surface_serial = GPOINTER_TO_UINT (value);
+      surface_serial_unref (graphics_pipeline, surface_serial);
+
+      if (!g_hash_table_lookup_extended (graphics_pipeline->serial_surface_table,
+                                         GUINT_TO_POINTER (surface_serial),
+                                         NULL, (gpointer *) &surface_context))
+        g_assert_not_reached ();
+
+      if (surface_context->gfx_surface)
+        {
+          grd_rdp_gfx_surface_ack_frame (surface_context->gfx_surface,
+                                         frame_acknowledge->frameId,
+                                         g_get_monotonic_time ());
+        }
+    }
+
+  if (frame_acknowledge->queueDepth == SUSPEND_FRAME_ACKNOWLEDGEMENT)
+    suspend_frame_acknowledgement (graphics_pipeline);
+}
+
+static uint32_t
+rdpgfx_frame_acknowledge (RdpgfxServerContext                *rdpgfx_context,
+                          const RDPGFX_FRAME_ACKNOWLEDGE_PDU *frame_acknowledge)
+{
+  GrdRdpGraphicsPipeline *graphics_pipeline = rdpgfx_context->custom;
+
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  handle_frame_ack_event (graphics_pipeline, frame_acknowledge);
+  g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+
+  return CHANNEL_RC_OK;
+}
+
+static uint32_t
+rdpgfx_qoe_frame_acknowledge (RdpgfxServerContext                    *rdpgfx_context,
+                              const RDPGFX_QOE_FRAME_ACKNOWLEDGE_PDU *qoe_frame_acknowledge)
+{
+  return CHANNEL_RC_OK;
+}
+
+void
+grd_rdp_graphics_pipeline_maybe_init (GrdRdpGraphicsPipeline *graphics_pipeline)
+{
+  RdpgfxServerContext *rdpgfx_context;
+
+  if (!graphics_pipeline)
+    return;
+
+  if (graphics_pipeline->channel_opened)
+    return;
+
+  if (WaitForSingleObject (graphics_pipeline->stop_event, 0) == WAIT_OBJECT_0)
+    return;
+
+  rdpgfx_context = graphics_pipeline->rdpgfx_context;
+  if (!rdpgfx_context->Open (rdpgfx_context))
+    {
+      g_warning ("[RDP.RDPGFX] Failed to open Graphics Pipeline. The client "
+                 "probably falsely advertised GFX support");
+      grd_session_rdp_notify_error (graphics_pipeline->session_rdp,
+                                    ERRINFO_GRAPHICS_SUBSYSTEM_FAILED);
+      return;
+    }
+
+  graphics_pipeline->channel_opened = TRUE;
+
+  return;
+}
+
+GrdRdpGraphicsPipeline *
+grd_rdp_graphics_pipeline_new (GrdSessionRdp *session_rdp,
+                               HANDLE         vcm,
+                               HANDLE         stop_event,
+                               rdpContext    *rdp_context,
+                               wStream       *encode_stream,
+                               RFX_CONTEXT   *rfx_context)
+{
+  GrdRdpGraphicsPipeline *graphics_pipeline;
+  RdpgfxServerContext *rdpgfx_context;
+
+  graphics_pipeline = g_object_new (GRD_TYPE_RDP_GRAPHICS_PIPELINE, NULL);
+  rdpgfx_context = rdpgfx_server_context_new (vcm);
+  if (!rdpgfx_context)
+    g_error ("[RDP.RDPGFX] Failed to create server context");
+
+  graphics_pipeline->rdpgfx_context = rdpgfx_context;
+  graphics_pipeline->stop_event = stop_event;
+  graphics_pipeline->session_rdp = session_rdp;
+  graphics_pipeline->encode_stream = encode_stream;
+  graphics_pipeline->rfx_context = rfx_context;
+
+  rdpgfx_context->CapsAdvertise = rdpgfx_caps_advertise;
+  rdpgfx_context->CacheImportOffer = rdpgfx_cache_import_offer;
+  rdpgfx_context->FrameAcknowledge = rdpgfx_frame_acknowledge;
+  rdpgfx_context->QoeFrameAcknowledge = rdpgfx_qoe_frame_acknowledge;
+  rdpgfx_context->rdpcontext = rdp_context;
+  rdpgfx_context->custom = graphics_pipeline;
+
+  return graphics_pipeline;
+}
+
+static void
+reset_graphics_pipeline (GrdRdpGraphicsPipeline *graphics_pipeline)
+{
+  GList *surfaces;
+  GList *l;
+
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  surfaces = g_hash_table_get_values (graphics_pipeline->surface_table);
+  g_hash_table_steal_all (graphics_pipeline->surface_table);
+
+  reduce_tracked_frame_infos (graphics_pipeline, 0);
+  g_hash_table_foreach_remove (graphics_pipeline->frame_serial_table,
+                               frame_serial_free, graphics_pipeline);
+  g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+
+  for (l = surfaces; l; l = l->next)
+    {
+      GrdRdpGfxSurface *gfx_surface = l->data;
+      GrdRdpSurface *rdp_surface;
+
+      rdp_surface = grd_rdp_gfx_surface_get_rdp_surface (gfx_surface);
+      g_clear_object (&rdp_surface->gfx_surface);
+    }
+  g_list_free (surfaces);
+
+  g_mutex_lock (&graphics_pipeline->gfx_mutex);
+  graphics_pipeline->frame_acks_suspended = FALSE;
+  graphics_pipeline->total_frames_encoded = 0;
+
+  g_assert (g_hash_table_size (graphics_pipeline->surface_table) == 0);
+  g_assert (g_hash_table_size (graphics_pipeline->codec_context_table) == 0);
+  g_assert (g_hash_table_size (graphics_pipeline->frame_serial_table) == 0);
+  g_assert (g_hash_table_size (graphics_pipeline->serial_surface_table) == 0);
+  g_assert (g_queue_get_length (graphics_pipeline->encoded_frames) == 0);
+  g_mutex_unlock (&graphics_pipeline->gfx_mutex);
+}
+
+static void
+grd_rdp_graphics_pipeline_dispose (GObject *object)
+{
+  GrdRdpGraphicsPipeline *graphics_pipeline = GRD_RDP_GRAPHICS_PIPELINE (object);
+
+  if (graphics_pipeline->channel_opened)
+    {
+      reset_graphics_pipeline (graphics_pipeline);
+      graphics_pipeline->rdpgfx_context->Close (graphics_pipeline->rdpgfx_context);
+      graphics_pipeline->channel_opened = FALSE;
+    }
+
+  if (graphics_pipeline->protocol_reset_source)
+    {
+      g_source_destroy (graphics_pipeline->protocol_reset_source);
+      g_clear_pointer (&graphics_pipeline->protocol_reset_source, g_source_unref);
+    }
+
+  if (graphics_pipeline->encoded_frames)
+    {
+      g_assert (g_queue_get_length (graphics_pipeline->encoded_frames) == 0);
+      g_clear_pointer (&graphics_pipeline->encoded_frames, g_queue_free);
+    }
+
+  g_clear_pointer (&graphics_pipeline->cap_sets, g_free);
+
+  g_assert (g_hash_table_size (graphics_pipeline->serial_surface_table) == 0);
+  g_clear_pointer (&graphics_pipeline->serial_surface_table, g_hash_table_destroy);
+  g_clear_pointer (&graphics_pipeline->frame_serial_table, g_hash_table_destroy);
+  g_clear_pointer (&graphics_pipeline->codec_context_table, g_hash_table_destroy);
+  g_clear_pointer (&graphics_pipeline->surface_table, g_hash_table_destroy);
+  g_clear_pointer (&graphics_pipeline->rdpgfx_context, rdpgfx_server_context_free);
+
+  G_OBJECT_CLASS (grd_rdp_graphics_pipeline_parent_class)->dispose (object);
+}
+
+static const char *
+rdpgfx_caps_version_to_string (uint32_t caps_version)
+{
+  switch (caps_version)
+    {
+    case RDPGFX_CAPVERSION_106:
+      return "RDPGFX_CAPVERSION_106";
+    case RDPGFX_CAPVERSION_105:
+      return "RDPGFX_CAPVERSION_105";
+    case RDPGFX_CAPVERSION_104:
+      return "RDPGFX_CAPVERSION_104";
+    case RDPGFX_CAPVERSION_103:
+      return "RDPGFX_CAPVERSION_103";
+    case RDPGFX_CAPVERSION_102:
+      return "RDPGFX_CAPVERSION_102";
+    case RDPGFX_CAPVERSION_101:
+      return "RDPGFX_CAPVERSION_101";
+    case RDPGFX_CAPVERSION_10:
+      return "RDPGFX_CAPVERSION_10";
+    case RDPGFX_CAPVERSION_81:
+      return "RDPGFX_CAPVERSION_81";
+    case RDPGFX_CAPVERSION_8:
+      return "RDPGFX_CAPVERSION_8";
+    default:
+      g_assert_not_reached ();
+    }
+
+  return NULL;
+}
+
+static gboolean
+test_caps_version (GrdRdpGraphicsPipeline *graphics_pipeline,
+                   RDPGFX_CAPSET          *cap_sets,
+                   uint16_t                n_cap_sets,
+                   uint32_t                caps_version)
+{
+  RdpgfxServerContext *rdpgfx_context = graphics_pipeline->rdpgfx_context;
+  rdpSettings *rdp_settings = rdpgfx_context->rdpcontext->settings;
+  RDPGFX_CAPS_CONFIRM_PDU caps_confirm = {0};
+  uint16_t i;
+
+  for (i = 0; i < n_cap_sets; ++i)
+    {
+      if (cap_sets[i].version == caps_version)
+        {
+          uint32_t flags = cap_sets[i].flags;
+
+          switch (caps_version)
+            {
+            case RDPGFX_CAPVERSION_106:
+            case RDPGFX_CAPVERSION_105:
+            case RDPGFX_CAPVERSION_104:
+            case RDPGFX_CAPVERSION_103:
+            case RDPGFX_CAPVERSION_102:
+            case RDPGFX_CAPVERSION_101:
+            case RDPGFX_CAPVERSION_10:
+              rdp_settings->GfxAVC444v2 = rdp_settings->GfxAVC444 =
+                rdp_settings->GfxH264 = !(flags & RDPGFX_CAPS_FLAG_AVC_DISABLED);
+              break;
+            case RDPGFX_CAPVERSION_81:
+              rdp_settings->GfxAVC444v2 = rdp_settings->GfxAVC444 = FALSE;
+              rdp_settings->GfxH264 = !!(flags & RDPGFX_CAPS_FLAG_AVC420_ENABLED);
+              break;
+            case RDPGFX_CAPVERSION_8:
+              rdp_settings->GfxAVC444v2 = rdp_settings->GfxAVC444 = FALSE;
+              rdp_settings->GfxH264 = FALSE;
+              break;
+            default:
+              g_assert_not_reached ();
+            }
+
+          g_message ("[RDP.RDPGFX] CapsAdvertise: Accepting capability set with version "
+                     "%s, Client cap flags: H264 (AVC444): %s, H264 (AVC420): %s",
+                     rdpgfx_caps_version_to_string (caps_version),
+                     rdp_settings->GfxAVC444v2 ? "true" : "false",
+                     rdp_settings->GfxH264 ? "true" : "false");
+          if (!graphics_pipeline->initialized)
+            graphics_pipeline->initial_version = caps_version;
+          graphics_pipeline->initialized = TRUE;
+
+          reset_graphics_pipeline (graphics_pipeline);
+
+          caps_confirm.capsSet = &cap_sets[i];
+
+          rdpgfx_context->CapsConfirm (rdpgfx_context, &caps_confirm);
+
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+static gboolean
+reset_protocol (gpointer user_data)
+{
+  GrdRdpGraphicsPipeline *graphics_pipeline = user_data;
+  GrdSessionRdp *session_rdp = graphics_pipeline->session_rdp;
+  RDPGFX_CAPSET *cap_sets;
+  uint16_t n_cap_sets;
+  size_t i;
+
+  g_mutex_lock (&graphics_pipeline->caps_mutex);
+  cap_sets = g_steal_pointer (&graphics_pipeline->cap_sets);
+  n_cap_sets = graphics_pipeline->n_cap_sets;
+  g_mutex_unlock (&graphics_pipeline->caps_mutex);
+
+  if (!cap_sets || !n_cap_sets)
+    {
+      g_assert (graphics_pipeline->initialized);
+
+      g_free (cap_sets);
+
+      return G_SOURCE_CONTINUE;
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (cap_list); ++i)
+    {
+      if (test_caps_version (graphics_pipeline,
+                             cap_sets, n_cap_sets,
+                             cap_list[i]))
+        {
+          grd_session_rdp_notify_graphics_pipeline_ready (session_rdp);
+          g_free (cap_sets);
+
+          return G_SOURCE_CONTINUE;
+        }
+    }
+  g_free (cap_sets);
+
+  /*
+   * CapsAdvertise already checked the capability sets to have at least one
+   * supported version.
+   * It is therefore impossible to hit this path.
+   */
+  g_assert_not_reached ();
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+protocol_reset_source_dispatch (GSource     *source,
+                                GSourceFunc  callback,
+                                gpointer     user_data)
+{
+  g_source_set_ready_time (source, -1);
+
+  return callback (user_data);
+}
+
+static GSourceFuncs protocol_reset_source_funcs =
+{
+  .dispatch = protocol_reset_source_dispatch,
+};
+
+static void
+grd_rdp_graphics_pipeline_init (GrdRdpGraphicsPipeline *graphics_pipeline)
+{
+  GSource *protocol_reset_source;
+
+  graphics_pipeline->surface_table = g_hash_table_new (NULL, NULL);
+  graphics_pipeline->codec_context_table = g_hash_table_new (NULL, NULL);
+
+  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->encoded_frames = g_queue_new ();
+
+  g_mutex_init (&graphics_pipeline->gfx_mutex);
+
+  protocol_reset_source = g_source_new (&protocol_reset_source_funcs,
+                                        sizeof (GSource));
+  g_source_set_callback (protocol_reset_source, reset_protocol,
+                         graphics_pipeline, NULL);
+  g_source_set_ready_time (protocol_reset_source, -1);
+  g_source_attach (protocol_reset_source, NULL);
+  graphics_pipeline->protocol_reset_source = protocol_reset_source;
+}
+
+static void
+grd_rdp_graphics_pipeline_class_init (GrdRdpGraphicsPipelineClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = grd_rdp_graphics_pipeline_dispose;
+}
diff --git a/src/grd-rdp-graphics-pipeline.h b/src/grd-rdp-graphics-pipeline.h
new file mode 100644
index 0000000..2e123a3
--- /dev/null
+++ b/src/grd-rdp-graphics-pipeline.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef GRD_RDP_GRAPHICS_PIPELINE_H
+#define GRD_RDP_GRAPHICS_PIPELINE_H
+
+#include <cairo/cairo.h>
+#include <freerdp/server/rdpgfx.h>
+#include <glib-object.h>
+
+#include "grd-types.h"
+
+#define GRD_TYPE_RDP_GRAPHICS_PIPELINE (grd_rdp_graphics_pipeline_get_type ())
+G_DECLARE_FINAL_TYPE (GrdRdpGraphicsPipeline, grd_rdp_graphics_pipeline,
+                      GRD, RDP_GRAPHICS_PIPELINE, GObject);
+
+GrdRdpGraphicsPipeline *grd_rdp_graphics_pipeline_new (GrdSessionRdp *session_rdp,
+                                                       HANDLE         vcm,
+                                                       HANDLE         stop_event,
+                                                       rdpContext    *rdp_context,
+                                                       wStream       *encode_stream,
+                                                       RFX_CONTEXT   *rfx_context);
+
+void grd_rdp_graphics_pipeline_maybe_init (GrdRdpGraphicsPipeline *graphics_pipeline);
+
+void grd_rdp_graphics_pipeline_create_surface (GrdRdpGraphicsPipeline *graphics_pipeline,
+                                               GrdRdpGfxSurface       *gfx_surface);
+
+void grd_rdp_graphics_pipeline_delete_surface (GrdRdpGraphicsPipeline *graphics_pipeline,
+                                               GrdRdpGfxSurface       *gfx_surface);
+
+void grd_rdp_graphics_pipeline_reset_graphics (GrdRdpGraphicsPipeline *graphics_pipeline,
+                                               uint32_t                width,
+                                               uint32_t                height,
+                                               MONITOR_DEF            *monitors,
+                                               uint32_t                n_monitors);
+
+void grd_rdp_graphics_pipeline_refresh_gfx (GrdRdpGraphicsPipeline *graphics_pipeline,
+                                            GrdRdpSurface          *rdp_surface,
+                                            cairo_region_t         *region,
+                                            uint8_t                *src_data);
+
+#endif /* GRD_RDP_GRAPHICS_PIPELINE_H */
diff --git a/src/grd-types.h b/src/grd-types.h
index 22dbd0e..b004d08 100644
--- a/src/grd-types.h
+++ b/src/grd-types.h
@@ -30,6 +30,7 @@ typedef struct _GrdClipboardVnc GrdClipboardVnc;
 typedef struct _GrdRdpEventQueue GrdRdpEventQueue;
 typedef struct _GrdRdpGfxFrameLog GrdRdpGfxFrameLog;
 typedef struct _GrdRdpGfxSurface GrdRdpGfxSurface;
+typedef struct _GrdRdpGraphicsPipeline GrdRdpGraphicsPipeline;
 typedef struct _GrdRdpSAMFile GrdRdpSAMFile;
 typedef struct _GrdRdpServer GrdRdpServer;
 typedef struct _GrdRdpSurface GrdRdpSurface;
diff --git a/src/meson.build b/src/meson.build
index 7d8d67f..75f1761 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -46,6 +46,8 @@ if have_rdp
     'grd-rdp-gfx-frame-log.h',
     'grd-rdp-gfx-surface.c',
     'grd-rdp-gfx-surface.h',
+    'grd-rdp-graphics-pipeline.c',
+    'grd-rdp-graphics-pipeline.h',
     'grd-rdp-pipewire-stream.c',
     'grd-rdp-pipewire-stream.h',
     'grd-rdp-sam.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]