/*
 * 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 <drm_fourcc.h>

#include "grd-context.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->has_syncobjs)
    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))
    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;
}
