/*
 * Copyright (C) 2011 Intel Corporation.
 * Copyright (C) 2016-2020 Red Hat
 * Copyright (c) 2018,2019 DisplayLink (UK) Ltd.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "config.h"

#include "backends/native/meta-onscreen-native.h"

#include <glib/gstdio.h>
#include <drm_fourcc.h>

#include "backends/native/meta-crtc-kms.h"
#include "backends/native/meta-device-pool.h"
#include "backends/native/meta-drm-buffer-dumb.h"
#include "backends/native/meta-drm-buffer-gbm.h"
#include "backends/native/meta-drm-buffer-import.h"
#include "backends/native/meta-drm-buffer.h"
#include "backends/native/meta-frame-native.h"
#include "backends/native/meta-kms-connector.h"
#include "backends/native/meta-kms-device.h"
#include "backends/native/meta-kms-plane.h"
#include "backends/native/meta-kms-utils.h"
#include "backends/native/meta-kms.h"
#include "backends/native/meta-output-kms.h"
#include "backends/native/meta-render-device-gbm.h"
#include "backends/native/meta-render-device.h"
#include "backends/native/meta-renderer-egl.h"
#include "backends/native/meta-renderer-native-gles3.h"
#include "backends/native/meta-renderer-native-private.h"
#include "backends/native/meta-egl-gbm.h"
#include "cogl/cogl.h"
#include "cogl/cogl-context-private.h"
#include "cogl/cogl-frame-info-private.h"
#include "common/meta-cogl-drm-formats.h"
#include "common/meta-drm-format-helpers.h"
#include "mtk/mtk.h"

/*
 * The maximum amount of damage rectangles to maintain. At a certain point there
 * are so many damage rectangles that individually processing them isn't worth
 * the time they save based on a full redraw, which is determined by this
 * number.
 */
#define MAX_DAMAGE_RECTANGLES 16

/*
 * The maximum supported buffer age for secondary GPU surfaces.
 */
#define MAX_SECONDARY_GPU_BUFFER_AGE 2

typedef enum _MetaSharedFramebufferImportStatus
{
  /* Not tried importing yet. */
  META_SHARED_FRAMEBUFFER_IMPORT_STATUS_NONE,
  /* Tried before and failed. */
  META_SHARED_FRAMEBUFFER_IMPORT_STATUS_FAILED,
  /* Tried before and succeeded. */
  META_SHARED_FRAMEBUFFER_IMPORT_STATUS_OK
} MetaSharedFramebufferImportStatus;

typedef struct _MetaOnscreenNativeSecondaryGpuState
{
  MetaGpuKms *gpu_kms;
  MetaRendererNativeGpuData *renderer_gpu_data;

  struct {
    MetaDrmBufferGbm *buffer_gbm[2];
    int buffer_index;
  } gbm;

  struct {
    MetaDrmBufferDumb *current_dumb_fb;
    MetaDrmBufferDumb *dumb_fbs[3];
  } cpu;

  MtkRegion *damage_regions[MAX_SECONDARY_GPU_BUFFER_AGE];
  int damage_region_index;

  gboolean noted_primary_gpu_copy_ok;
  gboolean noted_primary_gpu_copy_failed;
  MetaSharedFramebufferImportStatus import_status;
  MetaDrmBuffer *source_framebuffer;
} MetaOnscreenNativeSecondaryGpuState;

typedef struct _RenderSource
{
  GSource source;
  MetaOnscreenNative *onscreen_native;
  GHashTable *frames;
} RenderSource;

typedef struct _KmsProperty
{
  gboolean invalidated;
  int64_t target_frame_counter;
  gulong signal_handler_id;
} KmsProperty;

struct _MetaOnscreenNative
{
  CoglOnscreenEgl parent;

  MetaRendererNative *renderer_native;
  MetaGpuKms *render_gpu;
  MetaOutput *output;
  MetaCrtc *crtc;

  MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;

  /* Keep the last presented MetaDrmBuffer alive while it's being scannout out,
   * for two purposes:
   * - Prevent a direct scanout buffer from being released to the client for
   *   re-use prematurely
   * - Avoid drmModeRmFB implicitly disabling the KMS plane it's assigned to
   */
  MetaDrmBuffer *presented_buffer;
  ClutterFrame *posted_frame;
  ClutterFrame *next_frame;
  GSource *next_frame_ready_source;

  struct {
    struct gbm_surface *surface;

    struct {
      struct gbm_bo *gbm;
      MetaDrmBufferGbm *buffer_gbm;
      CoglTexture *cogl_tex;
      CoglOffscreen *cogl_fbo;
      CoglFrameInfo  *frame_info;
      uint32_t age;
    } bos[3], *scanout, *front, *back;
  } gbm;

  gboolean needs_flush;

  gboolean vrr_allowed;
  gboolean vrr_enabled;

  MetaRendererView *view;

  GSource *render_source;

  union {
    struct {
      KmsProperty gamma_lut;
      KmsProperty ctm;
      KmsProperty privacy_screen;
    } property;
    KmsProperty properties[3];
  };
};

G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native,
               COGL_TYPE_ONSCREEN_EGL)

static void
maybe_post_next_frame (CoglOnscreen *onscreen);

static void
post_nonprimary_plane_update (MetaOnscreenNative *onscreen_native,
                              ClutterFrame       *frame,
                              MetaKmsUpdate      *kms_update);

static gboolean
init_secondary_gpu_state (MetaRendererNative  *renderer_native,
                          CoglOnscreen        *onscreen,
                          GError             **error);

static void
render_source_remove_frame (GSource      *source,
                            ClutterFrame *frame)
{
  RenderSource *render_source;

  if (!source || !frame)
    return;

  render_source = (RenderSource *) source;
  if (g_hash_table_contains (render_source->frames, frame))
    {
      MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);

      meta_frame_native_remove_source (frame_native, source);
      g_hash_table_remove (render_source->frames, frame);
    }
}

static void
render_source_add_frame (GSource      *source,
                         ClutterFrame *frame)
{
  RenderSource *render_source;
  MetaFrameNative *frame_native;

  g_return_if_fail (source != NULL);
  g_return_if_fail (frame != NULL);

  render_source = (RenderSource *) source;
  g_return_if_fail (!g_hash_table_contains (render_source->frames, frame));

  frame_native = meta_frame_native_from_frame (frame);
  meta_frame_native_add_source (frame_native, source);
  g_hash_table_insert (render_source->frames, clutter_frame_ref (frame), NULL);
}

static gboolean
render_source_ready (GSource *source)
{
  RenderSource *render_source = (RenderSource *) source;
  MetaOnscreenNative *onscreen_native = render_source->onscreen_native;
  ClutterFrame *frame = onscreen_native->next_frame;
  MetaFrameNative *frame_native;

  if (frame == NULL)
    return FALSE;

  if (!g_hash_table_contains (render_source->frames, frame))
    return FALSE;

  frame_native = meta_frame_native_from_frame (frame);
  return meta_frame_native_is_ready (frame_native);
}

static void
maybe_post_next_frame_if_renderer_finished (CoglOnscreen *onscreen)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  RenderSource *render_source = (RenderSource *) onscreen_native->render_source;
  ClutterFrame *frame = onscreen_native->next_frame;

  if (frame == NULL)
    return;

  if (render_source &&
      g_hash_table_contains (render_source->frames, frame) &&
      !meta_frame_native_is_ready (meta_frame_native_from_frame (frame)))
    {
      return;
    }

  maybe_post_next_frame (onscreen);
}

static gboolean
render_source_prepare (GSource *source,
                       gint    *timeout_ms)
{
  *timeout_ms = -1;

  return render_source_ready (source);
}

static gboolean
render_source_check (GSource *source)
{
  return render_source_ready (source);
}

static gboolean
render_source_dispatch (GSource     *source,
                        GSourceFunc  callback,
                        gpointer     user_data)
{
  if (callback)
    callback (user_data);

  return G_SOURCE_CONTINUE;
}

static void
render_source_finalize (GSource *source)
{
  RenderSource *render_source = (RenderSource *) source;

  g_clear_pointer (&render_source->frames, g_hash_table_destroy);
}

static void
maybe_init_render_source (MetaOnscreenNative *onscreen_native)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen_native);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  const char *force_render_source;
  gboolean use_render_source;

  force_render_source = g_getenv ("MUTTER_DEBUG_FORCE_RENDER_SOURCE");
  if (force_render_source != NULL)
    {
      use_render_source = g_strcmp0 (force_render_source, "0") != 0;
    }
  else
    {
      MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state =
        onscreen_native->secondary_gpu_state;
      gboolean output_gpu_is_nvidia;

      if (secondary_gpu_state)
        {
          MetaRendererNativeGpuData *renderer_gpu_data =
            secondary_gpu_state->renderer_gpu_data;

          output_gpu_is_nvidia = renderer_gpu_data->secondary.is_nvidia;
        }
      else
        {
          CoglRenderer *renderer =
            cogl_context_get_renderer (cogl_context);
          const char *egl_vendor =
            cogl_renderer_egl_query_string (COGL_RENDERER_EGL (renderer),
                                            EGL_VENDOR);
          output_gpu_is_nvidia =
            g_strcmp0 (egl_vendor, "NVIDIA") == 0;
        }

      use_render_source = output_gpu_is_nvidia && !secondary_gpu_state;
    }

  if (use_render_source)
    {
      static GSourceFuncs render_source_funcs = {
        .prepare = render_source_prepare,
        .check = render_source_check,
        .dispatch = render_source_dispatch,
        .finalize = render_source_finalize,
      };
      g_autoptr (GMainContext) main_context = NULL;
      GSource *source;
      RenderSource *render_source;

      if (!cogl_context_has_winsys_feature (cogl_context,
                                            COGL_WINSYS_FEATURE_SYNC_FD))
        {
          g_warning ("Render source feature was requested but is disabled "
                     "due to lack of driver support.");
          return;
        }

      main_context = g_main_context_ref_thread_default ();

      source = g_source_new (&render_source_funcs, sizeof (RenderSource));
      onscreen_native->render_source = source;
      render_source = (RenderSource *) source;
      render_source->frames = g_hash_table_new_full (g_direct_hash,
                                                     g_direct_equal,
                                                     (GDestroyNotify) clutter_frame_unref,
                                                     NULL);
      render_source->onscreen_native = onscreen_native;
      g_source_set_name (source, "MetaOnscreenNative.render_source");
      g_source_set_can_recurse (source, FALSE);
      g_source_set_callback (source,
                             (GSourceFunc) maybe_post_next_frame_if_renderer_finished,
                             onscreen_native,
                             NULL);
      g_source_attach (source, main_context);
    }
}

static void
meta_onscreen_native_clear_posted_fb (CoglOnscreen *onscreen)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);

  if (!onscreen_native->posted_frame)
    return;

  g_clear_pointer (&onscreen_native->posted_frame, clutter_frame_unref);
  maybe_post_next_frame_if_renderer_finished (onscreen);
}

static void
maybe_update_frame_info (MetaCrtc         *crtc,
                         CoglFrameInfo    *frame_info,
                         int64_t           time_us,
                         CoglFrameInfoFlag flags,
                         unsigned int      sequence)
{
  const MetaCrtcConfig *crtc_config;
  const MetaCrtcModeInfo *crtc_mode_info;
  float refresh_rate;

  g_return_if_fail (crtc);

  crtc_config = meta_crtc_get_config (crtc);
  if (!crtc_config)
    return;

  crtc_mode_info = meta_crtc_mode_get_info (crtc_config->mode);
  refresh_rate = crtc_mode_info->refresh_rate;
  if (refresh_rate >= frame_info->refresh_rate)
    {
      frame_info->presentation_time_us = time_us;
      frame_info->refresh_rate = refresh_rate;
      frame_info->flags |= flags;
      frame_info->sequence = sequence;
    }
}

static void
notify_frame_info_complete (CoglFrameInfo *info)
{
  CoglOnscreen *onscreen = cogl_frame_info_get_onscreen (info);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  int i;

  if (!onscreen_native->gbm.surface)
    {
      for (i = 0; i < G_N_ELEMENTS (onscreen_native->gbm.bos); i++)
        {
          if (onscreen_native->gbm.bos[i].frame_info != info)
            continue;

          onscreen_native->gbm.bos[i].frame_info = NULL;

          if (onscreen_native->gbm.front == &onscreen_native->gbm.bos[i])
            onscreen_native->gbm.front = NULL;

          break;
        }
    }

  _cogl_onscreen_notify_frame_sync (onscreen, info);
  _cogl_onscreen_notify_complete (onscreen, info);
}

static void
notify_crtc_presented (MetaKmsCrtc      *kms_crtc,
                       ClutterFrame     *frame,
                       int64_t           time_us,
                       CoglFrameInfoFlag flags,
                       unsigned int      sequence)
{
  CoglFrameInfo *frame_info = clutter_frame_get_cogl_frame_info (frame);
  CoglOnscreen *onscreen = cogl_frame_info_get_onscreen (frame_info);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
  MetaDrmBuffer *buffer = meta_frame_native_get_buffer (frame_native);
  MetaCrtc *crtc;
  int64_t frame_counter;

  frame_counter = cogl_frame_info_get_frame_counter (frame_info);

  for (int i = 0; i < G_N_ELEMENTS (onscreen_native->properties); i++)
    {
      KmsProperty *prop = &onscreen_native->properties[i];

      if (frame_counter >= prop->target_frame_counter)
        prop->target_frame_counter = 0;
    }

  crtc = META_CRTC (meta_crtc_kms_from_kms_crtc (kms_crtc));
  maybe_update_frame_info (crtc, frame_info, time_us, flags, sequence);

  if (!onscreen_native->gbm.surface)
    {
      int i;

      for (i = 0; i < G_N_ELEMENTS (onscreen_native->gbm.bos); i++)
        {
          if (onscreen_native->gbm.bos[i].frame_info != frame_info)
            continue;

          onscreen_native->gbm.scanout = &onscreen_native->gbm.bos[i];
          onscreen_native->gbm.scanout->frame_info = NULL;
          break;
        }
    }

  notify_frame_info_complete (frame_info);

  if (buffer)
    g_set_object (&onscreen_native->presented_buffer, buffer);

  meta_onscreen_native_clear_posted_fb (onscreen);
}

static void
page_flip_feedback_flipped (MetaKmsCrtc  *kms_crtc,
                            unsigned int  sequence,
                            unsigned int  tv_sec,
                            unsigned int  tv_usec,
                            gpointer      user_data)
{
  ClutterFrame *frame = user_data;
  struct timeval page_flip_time;
  MetaKmsDevice *kms_device;
  int64_t presentation_time_us;
  CoglFrameInfoFlag flags = COGL_FRAME_INFO_FLAG_VSYNC;

  page_flip_time = (struct timeval) {
    .tv_sec = tv_sec,
    .tv_usec = tv_usec,
  };

  kms_device = meta_kms_crtc_get_device (kms_crtc);
  if (meta_kms_device_uses_monotonic_clock (kms_device))
    {
      presentation_time_us = meta_timeval_to_microseconds (&page_flip_time);
      flags |= COGL_FRAME_INFO_FLAG_HW_CLOCK;
    }
  else
    {
      /*
       * Other parts of the code assume MONOTONIC timestamps. So, if the device
       * timestamp isn't MONOTONIC, don't use it.
       */
      presentation_time_us = g_get_monotonic_time ();
    }

  notify_crtc_presented (kms_crtc,
                         frame,
                         presentation_time_us,
                         flags,
                         sequence);
}

static void
page_flip_feedback_ready (MetaKmsCrtc *kms_crtc,
                          gpointer     user_data)
{
  ClutterFrame *frame = user_data;
  CoglFrameInfo *frame_info = clutter_frame_get_cogl_frame_info (frame);
  CoglOnscreen *onscreen = cogl_frame_info_get_onscreen (frame_info);

  frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;

  notify_frame_info_complete (frame_info);

  meta_onscreen_native_clear_posted_fb (onscreen);
}

static void
page_flip_feedback_mode_set_fallback (MetaKmsCrtc *kms_crtc,
                                      gpointer     user_data)
{
  ClutterFrame *frame = user_data;
  int64_t now_us;

  /*
   * We ended up not page flipping, thus we don't have a presentation time to
   * use. Lets use the next best thing: the current time.
   */

  now_us = g_get_monotonic_time ();

  notify_crtc_presented (kms_crtc,
                         frame,
                         now_us,
                         COGL_FRAME_INFO_FLAG_NONE,
                         0);
}

static void
page_flip_feedback_discarded (MetaKmsCrtc  *kms_crtc,
                              gpointer      user_data,
                              const GError *error)
{
  ClutterFrame *frame = user_data;
  CoglFrameInfo *frame_info = clutter_frame_get_cogl_frame_info (frame);
  CoglOnscreen *onscreen = cogl_frame_info_get_onscreen (frame_info);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  int64_t frame_counter;

  /*
   * Page flipping failed, but we want to fail gracefully, so to avoid freezing
   * the frame clock, emit a symbolic flip.
   */

  if (error &&
      !g_error_matches (error,
                        G_IO_ERROR,
                        G_IO_ERROR_PERMISSION_DENIED))

    g_warning ("Page flip discarded: %s", error->message);

  frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;

  frame_counter = cogl_frame_info_get_frame_counter (frame_info);

  for (int i = 0; i < G_N_ELEMENTS (onscreen_native->properties); i++)
    {
      KmsProperty *prop = &onscreen_native->properties[i];

      if (prop->target_frame_counter != 0 &&
          frame_counter >= prop->target_frame_counter)
        {
          prop->invalidated = TRUE;
          prop->target_frame_counter = 0;
        }
    }

  notify_frame_info_complete (frame_info);

  meta_onscreen_native_clear_posted_fb (onscreen);
}

static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = {
  .flipped = page_flip_feedback_flipped,
  .ready = page_flip_feedback_ready,
  .mode_set_fallback = page_flip_feedback_mode_set_fallback,
  .discarded = page_flip_feedback_discarded,
};

void
meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen)
{
  /* If the monitor woke up in the 100ms between this callback being queued
   * and dispatched, and the shell is fully idle (has nothing more to swap)
   * then we just woke to an indefinitely black screen. The only saving grace
   * here is that shells usually have multiple frames they want to display
   * soon after wakeup. But let's not assume that's always the case. Fix it
   * by displaying the last swap.
   */
  maybe_post_next_frame (onscreen);
}

static void
apply_transform (MetaCrtcKms            *crtc_kms,
                 MetaKmsPlaneAssignment *kms_plane_assignment,
                 MetaKmsPlane           *kms_plane)
{
  MetaCrtc *crtc = META_CRTC (crtc_kms);
  const MetaCrtcConfig *crtc_config;
  MtkMonitorTransform hw_transform;

  crtc_config = meta_crtc_get_config (crtc);

  hw_transform = crtc_config->transform;
  if (!meta_kms_plane_is_transform_handled (kms_plane, hw_transform))
    hw_transform = MTK_MONITOR_TRANSFORM_NORMAL;
  if (!meta_kms_plane_is_transform_handled (kms_plane, hw_transform))
    return;

  meta_kms_plane_update_set_rotation (kms_plane,
                                      kms_plane_assignment,
                                      hw_transform);
}

static void
apply_color_encoding (MetaKmsPlaneAssignment *kms_plane_assignment,
                      MetaKmsPlane           *kms_plane)
{
  if (!meta_kms_plane_is_color_encoding_handled (kms_plane,
                                                 META_KMS_PLANE_YCBCR_COLOR_ENCODING_BT709))
    return;

  meta_kms_plane_update_set_color_encoding (kms_plane,
                                            kms_plane_assignment,
                                            META_KMS_PLANE_YCBCR_COLOR_ENCODING_BT709);
}

static void
apply_color_range (MetaKmsPlaneAssignment *kms_plane_assignment,
                   MetaKmsPlane           *kms_plane)
{
  if (!meta_kms_plane_is_color_range_handled (kms_plane,
                                              META_KMS_PLANE_YCBCR_COLOR_RANGE_LIMITED))
    return;

  meta_kms_plane_update_set_color_range (kms_plane,
                                         kms_plane_assignment,
                                         META_KMS_PLANE_YCBCR_COLOR_RANGE_LIMITED);
}

static MetaKmsPlaneAssignment *
assign_primary_plane (MetaCrtcKms            *crtc_kms,
                      MetaDrmBuffer          *buffer,
                      MetaKmsUpdate          *kms_update,
                      MetaKmsAssignPlaneFlag  flags,
                      const graphene_rect_t  *src_rect,
                      const MtkRectangle     *dst_rect)
{
  MetaCrtc *crtc = META_CRTC (crtc_kms);
  MetaFixed16Rectangle src_rect_fixed16;
  MetaKmsCrtc *kms_crtc;
  MetaKmsPlane *primary_kms_plane;
  MetaKmsPlaneAssignment *plane_assignment;

  src_rect_fixed16 = (MetaFixed16Rectangle) {
    .x = meta_fixed_16_from_double (src_rect->origin.x),
    .y = meta_fixed_16_from_double (src_rect->origin.y),
    .width = meta_fixed_16_from_double (src_rect->size.width),
    .height = meta_fixed_16_from_double (src_rect->size.height),
  };

  meta_topic (META_DEBUG_KMS,
              "Assigning buffer to primary plane update on CRTC "
              "(%" G_GUINT64_FORMAT ") with src rect %f,%f %fx%f "
              "and dst rect %d,%d %dx%d",
              meta_crtc_get_id (crtc), src_rect->origin.x, src_rect->origin.y,
              src_rect->size.width, src_rect->size.height,
              dst_rect->x, dst_rect->y, dst_rect->width, dst_rect->height);

  kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
  primary_kms_plane = meta_crtc_kms_get_assigned_primary_plane (crtc_kms);
  plane_assignment = meta_kms_update_assign_plane (kms_update,
                                                   kms_crtc,
                                                   primary_kms_plane,
                                                   buffer,
                                                   src_rect_fixed16,
                                                   *dst_rect,
                                                   flags);
  apply_transform (crtc_kms, plane_assignment, primary_kms_plane);
  apply_color_encoding (plane_assignment, primary_kms_plane);
  apply_color_range (plane_assignment, primary_kms_plane);

  return plane_assignment;
}

static gboolean
meta_onscreen_native_flip_crtc (CoglOnscreen           *onscreen,
                                ClutterFrame           *frame,
                                MetaRendererView       *view,
                                MetaCrtc               *crtc,
                                MetaKmsUpdate          *kms_update,
                                MetaKmsAssignPlaneFlag  flags,
                                const MtkRegion        *region)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaRendererNative *renderer_native = onscreen_native->renderer_native;
  MetaFrameNative *frame_native;
  MetaGpuKms *render_gpu = onscreen_native->render_gpu;
  MetaCrtcKms *crtc_kms = META_CRTC_KMS (crtc);
  MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
  MetaRendererNativeGpuData *renderer_gpu_data;
#ifndef G_DISABLE_ASSERT
  MetaGpuKms *gpu_kms;
#endif
  MetaDrmBuffer *buffer;
  CoglScanout *scanout;
  MetaKmsPlaneAssignment *plane_assignment;
  graphene_rect_t src_rect;
  MtkRectangle dst_rect;

  COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeFlipCrtcs,
                           "Meta::OnscreenNative::flip_crtc()");

#ifndef G_DISABLE_ASSERT
  gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));

  g_assert (meta_gpu_kms_is_crtc_active (gpu_kms, crtc));
#endif

  renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native,
                                                         render_gpu);
  switch (renderer_gpu_data->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      frame_native = meta_frame_native_from_frame (frame);
      buffer = meta_frame_native_get_buffer (frame_native);
      if (!buffer)
        return FALSE;

      scanout = meta_frame_native_get_scanout (frame_native);

      if (scanout)
        {
          cogl_scanout_get_src_rect (scanout, &src_rect);
          cogl_scanout_get_dst_rect (scanout, &dst_rect);
        }
      else
        {
          src_rect = (graphene_rect_t) {
            .origin.x = 0,
            .origin.y = 0,
            .size.width = meta_drm_buffer_get_width (buffer),
            .size.height = meta_drm_buffer_get_height (buffer)
          };
          dst_rect = (MtkRectangle) {
            .x = 0,
            .y = 0,
            .width = meta_drm_buffer_get_width (buffer),
            .height = meta_drm_buffer_get_height (buffer)
          };
        }

      plane_assignment = assign_primary_plane (crtc_kms,
                                               buffer,
                                               kms_update,
                                               flags,
                                               &src_rect,
                                               &dst_rect);

      if (region && !mtk_region_is_empty (region))
        meta_kms_plane_assignment_set_fb_damage (plane_assignment, region);
      break;
    case META_RENDERER_NATIVE_MODE_SURFACELESS:
      g_assert_not_reached ();
      break;
    }

  meta_kms_update_add_page_flip_listener (kms_update,
                                          kms_crtc,
                                          &page_flip_listener_vtable,
                                          NULL,
                                          clutter_frame_ref (frame),
                                          (GDestroyNotify) clutter_frame_unref);
  return TRUE;
}

static void
set_underscan (MetaOutputKms *output_kms,
               MetaKmsUpdate *kms_update)
{
  MetaOutput *output = META_OUTPUT (output_kms);
  const MetaOutputInfo *output_info = meta_output_get_info (output);
  MetaKmsConnector *kms_connector =
    meta_output_kms_get_kms_connector (output_kms);

  if (!output_info->supports_underscanning)
    return;

  if (meta_output_is_underscanning (output))
    {
      MetaCrtc *crtc;
      const MetaCrtcConfig *crtc_config;
      const MetaCrtcModeInfo *crtc_mode_info;
      uint64_t hborder, vborder;

      crtc = meta_output_get_assigned_crtc (output);
      crtc_config = meta_crtc_get_config (crtc);
      crtc_mode_info = meta_crtc_mode_get_info (crtc_config->mode);

      hborder = MIN (128, (uint64_t) round (crtc_mode_info->width * 0.05));
      vborder = MIN (128, (uint64_t) round (crtc_mode_info->height * 0.05));

      g_debug ("Setting underscan of connector %s to %" G_GUINT64_FORMAT " x %" G_GUINT64_FORMAT,
               meta_kms_connector_get_name (kms_connector),
               hborder, vborder);

      meta_kms_update_set_underscanning (kms_update,
                                         kms_connector,
                                         hborder, vborder);
    }
  else
    {
      g_debug ("Unsetting underscan of connector %s",
               meta_kms_connector_get_name (kms_connector));

      meta_kms_update_unset_underscanning (kms_update,
                                           kms_connector);
    }
}

static void
set_max_bpc (MetaOutputKms *output_kms,
             MetaKmsUpdate *kms_update)
{
  MetaOutput *output = META_OUTPUT (output_kms);
  const MetaOutputInfo *output_info = meta_output_get_info (output);
  MetaKmsConnector *kms_connector =
    meta_output_kms_get_kms_connector (output_kms);
  unsigned int max_bpc;

  if (!meta_output_get_max_bpc (output, &max_bpc))
    return;

  if (output_info->max_bpc_min == 0 && output_info->max_bpc_max == 0)
    return;

  if (max_bpc < output_info->max_bpc_min || max_bpc > output_info->max_bpc_max)
    {
      g_warning ("Ignoring out of range value %u for max bpc (%u-%u)",
                 max_bpc,
                 (unsigned) output_info->max_bpc_min,
                 (unsigned) output_info->max_bpc_max);
      return;
    }

  meta_kms_update_set_max_bpc (kms_update, kms_connector, max_bpc);
}

static void
set_rgb_range (MetaOutputKms *output_kms,
               MetaKmsUpdate *kms_update)
{
  MetaOutput *output = META_OUTPUT (output_kms);
  const MetaOutputInfo *output_info = meta_output_get_info (output);
  MetaKmsConnector *kms_connector =
    meta_output_kms_get_kms_connector (output_kms);
  MetaOutputRGBRange rgb_range = meta_output_peek_rgb_range (output);

  if (rgb_range == META_OUTPUT_RGB_RANGE_AUTO &&
      !(output_info->supported_rgb_ranges & (1 << rgb_range)))
    return;

  if (!(output_info->supported_rgb_ranges & (1 << rgb_range)))
    {
      g_warning ("Ignoring unsupported RGB Range");
      return;
    }

  meta_kms_update_set_broadcast_rgb (kms_update, kms_connector, rgb_range);
}

static void
set_color_mode (MetaOutputKms *output_kms,
                MetaKmsUpdate *kms_update)
{
  MetaOutput *output = META_OUTPUT (output_kms);
  MetaKmsConnector *kms_connector =
    meta_output_kms_get_kms_connector (output_kms);
  MetaOutputHdrMetadata hdr_metadata;
  MetaOutputColorspace color_space;


  meta_output_get_color_metadata (output, &hdr_metadata, &color_space);

  if (meta_kms_connector_supports_colorspace (kms_connector))
    meta_kms_update_set_color_space (kms_update, kms_connector, color_space);

  if (meta_kms_connector_supports_hdr_metadata (kms_connector))
    meta_kms_update_set_hdr_metadata (kms_update, kms_connector, &hdr_metadata);
}

static void
meta_onscreen_native_set_crtc_mode (CoglOnscreen              *onscreen,
                                    MetaKmsUpdate             *kms_update,
                                    MetaRendererNativeGpuData *renderer_gpu_data)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);

  COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeSetCrtcModes,
                           "Meta::OnscreenNative::set_crtc_mode()");

  switch (renderer_gpu_data->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      break;
    case META_RENDERER_NATIVE_MODE_SURFACELESS:
      g_assert_not_reached ();
      break;
    }

  meta_crtc_kms_set_mode (crtc_kms, kms_update);
  set_underscan (META_OUTPUT_KMS (onscreen_native->output), kms_update);
  set_max_bpc (META_OUTPUT_KMS (onscreen_native->output), kms_update);
  set_rgb_range (META_OUTPUT_KMS (onscreen_native->output), kms_update);
  set_color_mode (META_OUTPUT_KMS (onscreen_native->output), kms_update);
}

static void
secondary_gpu_release_dumb (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state)
{
  unsigned i;

  for (i = 0; i < G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs); i++)
    g_clear_object (&secondary_gpu_state->cpu.dumb_fbs[i]);
}

static void
secondary_gpu_state_free (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state)
{
  unsigned int i;

  g_clear_object (&secondary_gpu_state->gbm.buffer_gbm[0]);
  g_clear_object (&secondary_gpu_state->gbm.buffer_gbm[1]);
  g_clear_object (&secondary_gpu_state->source_framebuffer);

  for (i = 0; i < MAX_SECONDARY_GPU_BUFFER_AGE; i++)
    {
      g_clear_pointer (&secondary_gpu_state->damage_regions[i],
                       mtk_region_unref);
    }

  secondary_gpu_release_dumb (secondary_gpu_state);

  g_free (secondary_gpu_state);
}

static MetaDrmBuffer *
import_shared_framebuffer (CoglOnscreen                        *onscreen,
                           MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
                           MetaDrmBuffer                       *primary_gpu_fb)
{
  MetaRenderDevice *render_device;
  g_autoptr (GError) error = NULL;
  MetaDrmBuffer *imported_buffer;

  render_device = secondary_gpu_state->renderer_gpu_data->render_device;
  imported_buffer =
    meta_render_device_import_dma_buf (render_device,
                                       primary_gpu_fb,
                                       &error);
  if (!imported_buffer)
    {
      g_warning ("Zero-copy disabled for %s, import failed: %s",
                 meta_render_device_get_name (render_device),
                 error->message);
      secondary_gpu_state->import_status =
        META_SHARED_FRAMEBUFFER_IMPORT_STATUS_FAILED;
      return NULL;
    }

  if (secondary_gpu_state->import_status ==
      META_SHARED_FRAMEBUFFER_IMPORT_STATUS_NONE)
    {
      meta_topic (META_DEBUG_KMS,
                  "Using zero-copy for %s succeeded once.",
                  meta_render_device_get_name (render_device));
    }

  secondary_gpu_state->import_status =
    META_SHARED_FRAMEBUFFER_IMPORT_STATUS_OK;
  return imported_buffer;
}

static MtkRegion *
build_bo_full_damage_rectangle (struct gbm_bo *bo)
{
  return mtk_region_create_rectangle (&MTK_RECTANGLE_INIT (0, 0,
                                                           gbm_bo_get_width (bo),
                                                           gbm_bo_get_height (bo)));
}

static void
push_secondary_gpu_damage_rectangles (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
                                      struct gbm_bo                       *bo,
                                      const MtkRegion                     *region)
{
  MtkRegion *region_to_store = NULL;
  int write_index = secondary_gpu_state->damage_region_index;

  if (mtk_region_num_rectangles (region) > 0)
    region_to_store = mtk_region_copy (region);
  else
    region_to_store = build_bo_full_damage_rectangle (bo);

  if (secondary_gpu_state->damage_regions[write_index] != NULL)
    mtk_region_unref (secondary_gpu_state->damage_regions[write_index]);

  secondary_gpu_state->damage_regions[write_index] = region_to_store;
  secondary_gpu_state->damage_region_index =
    (secondary_gpu_state->damage_region_index + 1) % MAX_SECONDARY_GPU_BUFFER_AGE;
}

static MtkRegion *
build_secondary_gpu_damage_region (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
                                   struct gbm_bo                       *bo,
                                   int                                  age)
{
  int i, damage_region_index;
  g_autoptr (MtkRegion) region = NULL;

  if (age == 0)
    return build_bo_full_damage_rectangle (bo);

  region = mtk_region_create ();

  for (i = 1; i <= age; ++i)
    {
      MtkRegion *damage_region;

      damage_region_index = ((secondary_gpu_state->damage_region_index - i) %
                             MAX_SECONDARY_GPU_BUFFER_AGE);

      if (damage_region_index < 0)
        damage_region_index += MAX_SECONDARY_GPU_BUFFER_AGE;

      damage_region =
        secondary_gpu_state->damage_regions[damage_region_index];

      if (damage_region != NULL)
        mtk_region_union (region, damage_region);
    }

  if (mtk_region_num_rectangles (region) > MAX_DAMAGE_RECTANGLES)
    return build_bo_full_damage_rectangle (bo);

  return g_steal_pointer (&region);
}

static MetaDrmBufferGbm *
get_secondary_gpu_buffer_and_age (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
                                  int                                 *out_buffer_age)
{
  int buffer_index = secondary_gpu_state->gbm.buffer_index;

  if (buffer_index < 0)
    {
      buffer_index += 2;
      secondary_gpu_state->gbm.buffer_index++;
      *out_buffer_age = 0;
    }
  else
    {
      secondary_gpu_state->gbm.buffer_index = 1 - buffer_index;
      *out_buffer_age = 2;
    }

  return g_object_ref (secondary_gpu_state->gbm.buffer_gbm[buffer_index]);
}

static MetaDrmBuffer *
copy_shared_framebuffer_gpu (CoglOnscreen                         *onscreen,
                             MetaFrameNative                      *frame_native,
                             MetaOnscreenNativeSecondaryGpuState  *secondary_gpu_state,
                             MetaRendererNativeGpuData            *renderer_gpu_data,
                             MetaDrmBuffer                        *primary_gpu_fb,
                             const MtkRegion                      *region,
                             GError                              **error)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglDriver *driver = cogl_context_get_driver (cogl_context);
  CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
  MetaRenderDevice *render_device;
  CoglRendererEGL *renderer_egl;
  MetaDrmBufferGbm *dst_buffer_gbm = NULL, *src_buffer_gbm;
  struct gbm_bo *dst_bo, *src_bo;
  EGLSync egl_sync = EGL_NO_SYNC;
  g_autofd int sync_fd = -1;
  EGLImageKHR dst_egl_image, src_egl_image;
  int buffer_age;
  g_autoptr (MtkRegion) blit_region = NULL;
  g_autoptr (MtkRegion) region_to_push = NULL;

  COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferSecondaryGpu,
                           "copy_shared_framebuffer_gpu()");

  if (renderer_gpu_data->secondary.is_nvidia)
    sync_fd = meta_frame_native_steal_sync_fd (frame_native);

  render_device = renderer_gpu_data->render_device;
  renderer_egl =
    COGL_RENDERER_EGL (meta_render_device_get_renderer_egl (render_device));

  if (!cogl_renderer_egl_make_current (renderer_egl,
                                       EGL_NO_SURFACE,
                                       EGL_NO_SURFACE,
                                       renderer_gpu_data->secondary.egl_context,
                                       error))
    {
      g_prefix_error (error, "Failed to make current: ");
      goto done;
    }

  if (sync_fd >= 0)
    {
      EGLAttrib attribs[3];

      attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID;
      attribs[1] = g_steal_fd (&sync_fd);
      attribs[2] = EGL_NONE;

      if (!cogl_renderer_egl_create_egl_sync (renderer_egl,
                                              EGL_SYNC_NATIVE_FENCE_ANDROID,
                                              attribs,
                                              &egl_sync,
                                              error))
        {
          g_prefix_error (error, "Failed to create EGLSync on secondary GPU: ");
          goto done;
        }

      if (!cogl_renderer_egl_wait_egl_sync (renderer_egl,
                                            egl_sync,
                                            0,
                                            error))
        {
          g_prefix_error (error, "Failed to wait for EGLSync on secondary GPU: ");
          goto done;
        }
    }

  src_buffer_gbm = META_DRM_BUFFER_GBM (primary_gpu_fb);
  src_bo = meta_drm_buffer_gbm_get_bo (src_buffer_gbm);
  src_egl_image = meta_egl_ensure_gbm_bo_egl_image (renderer_egl,
                                                    src_bo,
                                                    META_EGL_GPU_SECONDARY,
                                                    error);

  if (!src_egl_image)
    {
      g_prefix_error (error, "Failed to create EGL image from buffer object for secondary GPU: ");
      goto done;
    }

  dst_buffer_gbm = get_secondary_gpu_buffer_and_age (secondary_gpu_state,
                                                     &buffer_age);
  dst_bo = meta_drm_buffer_gbm_get_bo (dst_buffer_gbm);
  dst_egl_image = meta_egl_ensure_gbm_bo_egl_image (renderer_egl,
                                                    dst_bo,
                                                    META_EGL_GPU_SECONDARY,
                                                    error);
  if (!dst_egl_image)
    {
      g_prefix_error (error, "Failed to create EGL image from buffer object for secondary GPU: ");
      goto done;
    }

  push_secondary_gpu_damage_rectangles (secondary_gpu_state, src_bo, region);
  blit_region = build_secondary_gpu_damage_region (secondary_gpu_state,
                                                   src_bo, buffer_age);

  if (!meta_renderer_native_gles3_blit_shared_bo (driver,
                                                  renderer_egl,
                                                  renderer_gpu_data->secondary.egl_context,
                                                  dst_egl_image,
                                                  src_egl_image,
                                                  src_bo,
                                                  blit_region,
                                                  error))
    {
      g_prefix_error (error, "Failed to blit shared framebuffer: ");
      goto done;
    }

  sync_fd = cogl_renderer_egl_create_sync_fd (renderer_egl, error);
  if (sync_fd < 0)
    {
      g_prefix_error (error, "Failed to create sync fd: ");
      goto done;
    }

  meta_frame_native_set_sync_fd (frame_native, g_steal_fd (&sync_fd));

done:
  if (egl_sync != EGL_NO_SYNC)
    {
      g_autoptr (GError) local_error = NULL;

      if (!cogl_renderer_egl_destroy_egl_sync (renderer_egl,
                                               egl_sync,
                                               &local_error))
        g_warning ("Failed to destroy secondary GPU EGLSync: %s", local_error->message);
    }

  cogl_display_egl_ensure_current (COGL_DISPLAY_EGL (cogl_display));

  return dst_buffer_gbm ? META_DRM_BUFFER (dst_buffer_gbm) : NULL;
}

static MetaDrmBufferDumb *
secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state)
{
  MetaDrmBufferDumb *current_dumb_fb;
  const int n_dumb_fbs = G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs);
  int i;

  current_dumb_fb = secondary_gpu_state->cpu.current_dumb_fb;
  for (i = 0; i < n_dumb_fbs; i++)
    {
      if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[i])
        return secondary_gpu_state->cpu.dumb_fbs[(i + 1) % n_dumb_fbs];
    }

  return secondary_gpu_state->cpu.dumb_fbs[0];
}

static MetaDrmBuffer *
copy_shared_framebuffer_primary_gpu (CoglOnscreen                        *onscreen,
                                     MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
                                     const MtkRegion                     *region)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaRendererNative *renderer_native = onscreen_native->renderer_native;
  MetaGpuKms *primary_gpu;
  MetaRendererNativeGpuData *primary_gpu_data;
  MetaDrmBufferDumb *buffer_dumb;
  MetaDrmBuffer *buffer;
  int width, height;
  CoglFramebuffer *dmabuf_fb;
  CoglContext *cogl_context;
  CoglDisplay *cogl_display;
  CoglRenderer *cogl_renderer;
  const MetaFormatInfo *format_info;
  int dmabuf_fd;
  g_autoptr (GError) error = NULL;
  uint32_t stride;
  uint32_t offset;
  uint32_t drm_format;
  uint64_t modifier;
  int n_rectangles;

  COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferPrimaryGpu,
                           "copy_shared_framebuffer_primary_gpu()");

  if (!secondary_gpu_state)
    return NULL;

  primary_gpu = meta_renderer_native_get_primary_gpu (renderer_native);
  primary_gpu_data =
    meta_renderer_native_get_gpu_data (renderer_native, primary_gpu);
  if (!primary_gpu_data->secondary.has_EGL_EXT_image_dma_buf_import_modifiers)
    return NULL;

  buffer_dumb = secondary_gpu_get_next_dumb_buffer (secondary_gpu_state);
  buffer = META_DRM_BUFFER (buffer_dumb);

  width = meta_drm_buffer_get_width (buffer);
  height = meta_drm_buffer_get_height (buffer);
  stride = meta_drm_buffer_get_stride (buffer);
  offset = 0;
  modifier = DRM_FORMAT_MOD_LINEAR;
  drm_format = meta_drm_buffer_get_format (buffer);

  g_assert (cogl_framebuffer_get_width (framebuffer) == width);
  g_assert (cogl_framebuffer_get_height (framebuffer) == height);

  format_info = meta_format_info_from_drm_format (drm_format);
  g_assert (format_info);

  dmabuf_fd = meta_drm_buffer_dumb_ensure_dmabuf_fd (buffer_dumb, &error);
  if (dmabuf_fd < 0)
    {
      meta_topic (META_DEBUG_KMS,
                  "Failed to create DMA buffer: %s", error->message);
      return NULL;
    }

  cogl_context = meta_renderer_native_get_cogl_context (renderer_native);
  cogl_display = cogl_context_get_display (cogl_context);
  cogl_renderer = cogl_display_get_renderer (cogl_display);

  modifier = DRM_FORMAT_MOD_LINEAR;
  dmabuf_fb = cogl_renderer_create_dma_buf_framebuffer (cogl_renderer,
                                                        cogl_context,
                                                        width,
                                                        height,
                                                        drm_format,
                                                        format_info->cogl_format,
                                                        1,
                                                        &dmabuf_fd,
                                                        &stride,
                                                        &offset,
                                                        &modifier,
                                                        &error);

  if (error)
    {
      meta_topic (META_DEBUG_KMS,
                  "Failed to create DMA buffer for blitting: %s",
                  error->message);
      return NULL;
    }

  n_rectangles = mtk_region_num_rectangles (region);
  if (((n_rectangles == 0 || n_rectangles > MAX_DAMAGE_RECTANGLES) &&
       !cogl_framebuffer_blit (framebuffer,
                               COGL_FRAMEBUFFER (dmabuf_fb),
                               0, 0, 0, 0,
                               width, height,
                               &error)) ||
      !cogl_framebuffer_blit_region (framebuffer, COGL_FRAMEBUFFER (dmabuf_fb),
                                     region,
                                     0, 0,
                                     &error))
    {
      g_object_unref (dmabuf_fb);
      return NULL;
    }

  secondary_gpu_state->cpu.current_dumb_fb = buffer_dumb;

  return g_object_ref (buffer);
}

static MetaDrmBuffer *
copy_shared_framebuffer_cpu (CoglOnscreen                        *onscreen,
                             MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
                             MetaRendererNativeGpuData           *renderer_gpu_data)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  MetaDrmBufferDumb *buffer_dumb;
  MetaDrmBuffer *buffer;
  int width, height, stride;
  uint32_t drm_format;
  void *buffer_data;
  CoglBitmap *dumb_bitmap;
  CoglPixelFormat cogl_format;
  const MetaFormatInfo *format_info;

  COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferCpu,
                           "copy_shared_framebuffer_cpu()");

  buffer_dumb = secondary_gpu_get_next_dumb_buffer (secondary_gpu_state);
  buffer = META_DRM_BUFFER (buffer_dumb);

  width = meta_drm_buffer_get_width (buffer);
  height = meta_drm_buffer_get_height (buffer);
  stride = meta_drm_buffer_get_stride (buffer);
  drm_format = meta_drm_buffer_get_format (buffer);
  buffer_data = meta_drm_buffer_dumb_get_data (buffer_dumb);

  g_assert (cogl_framebuffer_get_width (framebuffer) == width);
  g_assert (cogl_framebuffer_get_height (framebuffer) == height);

  format_info = meta_format_info_from_drm_format (drm_format);
  g_assert (format_info);
  cogl_format = format_info->cogl_format;

  dumb_bitmap = cogl_bitmap_new_for_data (cogl_context,
                                          width,
                                          height,
                                          cogl_format,
                                          stride,
                                          buffer_data);

  if (!cogl_framebuffer_read_pixels_into_bitmap (framebuffer,
                                                 0 /* x */,
                                                 0 /* y */,
                                                 COGL_READ_PIXELS_COLOR_BUFFER,
                                                 dumb_bitmap))
    g_warning ("Failed to CPU-copy to a secondary GPU output");

  g_object_unref (dumb_bitmap);

  secondary_gpu_state->cpu.current_dumb_fb = buffer_dumb;

  return g_object_ref (buffer);
}

static MetaDrmBuffer *
update_secondary_gpu_state_pre_swap_buffers (CoglOnscreen    *onscreen,
                                             const MtkRegion *region)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
  MetaDrmBuffer *copy = NULL;

  COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeGpuStatePreSwapBuffers,
                           "update_secondary_gpu_state_pre_swap_buffers()");

  secondary_gpu_state = onscreen_native->secondary_gpu_state;
  if (secondary_gpu_state)
    {
      MetaRendererNativeGpuData *renderer_gpu_data;
      MetaRenderDevice *render_device;

      renderer_gpu_data = secondary_gpu_state->renderer_gpu_data;
      render_device = renderer_gpu_data->render_device;
      switch (renderer_gpu_data->secondary.copy_mode)
        {
        case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU:
          /* Done after eglSwapBuffers. */
          break;
        case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO:
          /* Done after eglSwapBuffers. */
          if (secondary_gpu_state->import_status ==
              META_SHARED_FRAMEBUFFER_IMPORT_STATUS_OK)
            break;
          /* prepare fallback */
          G_GNUC_FALLTHROUGH;
        case META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY:
          if (!renderer_gpu_data->secondary.copy_mode_primary_force_cpu)
            {
              copy = copy_shared_framebuffer_primary_gpu (onscreen,
                                                          secondary_gpu_state,
                                                          region);
            }

          if (!copy)
            {
              if (!secondary_gpu_state->noted_primary_gpu_copy_failed &&
                  !renderer_gpu_data->secondary.copy_mode_primary_force_cpu)
                {
                  meta_topic (META_DEBUG_KMS,
                              "Using primary GPU to copy for %s failed once.",
                              meta_render_device_get_name (render_device));
                  secondary_gpu_state->noted_primary_gpu_copy_failed = TRUE;
                }

              copy = copy_shared_framebuffer_cpu (onscreen,
                                                  secondary_gpu_state,
                                                  renderer_gpu_data);
            }
          else if (!secondary_gpu_state->noted_primary_gpu_copy_ok)
            {
              meta_topic (META_DEBUG_KMS,
                          "Using primary GPU to copy for %s succeeded once.",
                          meta_render_device_get_name (render_device));
              secondary_gpu_state->noted_primary_gpu_copy_ok = TRUE;
            }
          break;
        }
    }

  return copy;
}

static MetaDrmBuffer *
acquire_front_buffer (CoglOnscreen     *onscreen,
                      MetaFrameNative  *frame_native,
                      MetaDrmBuffer    *primary_gpu_fb,
                      MetaDrmBuffer    *secondary_gpu_fb,
                      const MtkRegion  *region,
                      GError          **error)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaRendererNative *renderer_native = onscreen_native->renderer_native;
  MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
  MetaRendererNativeGpuData *renderer_gpu_data;
  MetaDrmBuffer *imported_fb;

  COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeGpuStatePostSwapBuffers,
                           "acquire_front_buffer()");

  secondary_gpu_state = onscreen_native->secondary_gpu_state;
  if (!secondary_gpu_state)
    return g_object_ref (primary_gpu_fb);

  renderer_gpu_data =
    meta_renderer_native_get_gpu_data (renderer_native,
                                       secondary_gpu_state->gpu_kms);
  switch (renderer_gpu_data->secondary.copy_mode)
    {
    case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO:
      imported_fb = import_shared_framebuffer (onscreen,
                                               secondary_gpu_state,
                                               primary_gpu_fb);
      if (imported_fb)
        return imported_fb;
      /* The fallback was prepared in pre_swap_buffers and is currently
       * in secondary_gpu_fb.
       */
      renderer_gpu_data->secondary.copy_mode =
        META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY;
      G_GNUC_FALLTHROUGH;
    case META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY:
      if (secondary_gpu_fb == NULL)
        {
          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                       "Missing secondary GPU framebuffer");
          return NULL;
        }
      return g_object_ref (secondary_gpu_fb);
    case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU:
      return copy_shared_framebuffer_gpu (onscreen,
                                          frame_native,
                                          secondary_gpu_state,
                                          renderer_gpu_data,
                                          primary_gpu_fb,
                                          region,
                                          error);
    }

  g_assert_not_reached ();
  return NULL;
}

static void
ensure_crtc_modes (CoglOnscreen  *onscreen,
                   MetaKmsUpdate *kms_update)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
  MetaRendererNativeGpuData *renderer_gpu_data =
    meta_renderer_egl_get_renderer_gpu_data (META_RENDERER_EGL (cogl_renderer));
  MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native;

  if (meta_renderer_native_pop_pending_mode_set (renderer_native,
                                                 onscreen_native->view))
    meta_onscreen_native_set_crtc_mode (onscreen, kms_update, renderer_gpu_data);
}

static void
swap_buffer_result_feedback (const MetaKmsFeedback *kms_feedback,
                             gpointer               user_data)
{
  ClutterFrame *frame = user_data;
  CoglFrameInfo *frame_info = clutter_frame_get_cogl_frame_info (frame);
  CoglOnscreen *onscreen = cogl_frame_info_get_onscreen (frame_info);
  const GError *error = NULL;

  /*
   * Page flipping failed, but we want to fail gracefully, so to avoid freezing
   * the frame clock, emit a symbolic flip.
   */

  error = meta_kms_feedback_get_error (kms_feedback);
  if (!error)
    {
      if (frame_info->kms_ready_time_us == 0)
        {
          frame_info->kms_ready_time_us =
            meta_kms_feedback_get_ready_time_us (kms_feedback);
        }

      return;
    }

  if (!g_error_matches (error,
                        META_KMS_ERROR,
                        META_KMS_ERROR_DISCARDED) &&
      !g_error_matches (error,
                        G_IO_ERROR,
                        G_IO_ERROR_PERMISSION_DENIED))
    g_warning ("Page flip failed: %s", error->message);

  frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
  notify_frame_info_complete (frame_info);

  meta_onscreen_native_clear_posted_fb (onscreen);
}

static void
assign_next_frame (MetaOnscreenNative *onscreen_native,
                   ClutterFrame       *frame)
{
  CoglOnscreen *onscreen = COGL_ONSCREEN (onscreen_native);

  if (onscreen_native->next_frame != NULL)
    {
      if (!onscreen_native->render_source)
        g_warning_once ("Unexpected non-NULL onscreen_native->next_frame");

      /* This is needed to make sure we lock the correct front buffer for the
       * new frame, as eglSwapBuffers() swaps what is the active front buffer.
       *
       * On Nvidia this will result in potentially synchronously waiting for
       * pending GPU work.
       */
      cogl_framebuffer_flush (COGL_FRAMEBUFFER (onscreen));
      maybe_post_next_frame (onscreen);

      if (onscreen_native->next_frame != NULL)
        g_warning_once ("maybe_post_next_frame didn't clear next frame");
    }

  onscreen_native->next_frame = clutter_frame_ref (frame);
}

static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = {
  .feedback = swap_buffer_result_feedback,
};

static void
scanout_result_feedback (const MetaKmsFeedback *kms_feedback,
                         gpointer               user_data);

static const MetaKmsResultListenerVtable scanout_result_listener_vtable = {
  .feedback = scanout_result_feedback,
};

static void
meta_onscreen_native_queue_damage_region (CoglOnscreen    *onscreen,
                                          const MtkRegion *region)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  CoglOnscreenClass *parent_class =
    COGL_ONSCREEN_CLASS (meta_onscreen_native_parent_class);

  if (!onscreen_native->gbm.surface)
    return;

  parent_class->queue_damage_region (onscreen, region);
}

static gboolean
swap_buffers_with_damage (CoglOnscreen    *onscreen,
                          const MtkRegion *region,
                          CoglFrameInfo   *frame_info,
                          gpointer         user_data)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);

  if (onscreen_native->gbm.surface)
    {
      CoglOnscreenClass *parent_class =
        COGL_ONSCREEN_CLASS (meta_onscreen_native_parent_class);

      return parent_class->swap_buffers_with_damage (onscreen,
                                                     region,
                                                     frame_info,
                                                     user_data);
    }
  else
    {
      CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen_native);
      int i;

      /* Update latest sync object */
      cogl_framebuffer_flush (framebuffer);

      for (i = 0; i < G_N_ELEMENTS (onscreen_native->gbm.bos); i++)
        {
          if (onscreen_native->gbm.bos[i].age)
            onscreen_native->gbm.bos[i].age++;
        }

      onscreen_native->gbm.front = g_steal_pointer (&onscreen_native->gbm.back);
      onscreen_native->gbm.front->frame_info = frame_info;
      onscreen_native->gbm.front->age = 1;
      return TRUE;
    }
}

static MetaDrmBufferGbm *
lock_front_buffer (MetaOnscreenNative  *onscreen_native,
                   GError             **error)
{
  if (onscreen_native->gbm.surface)
    {
      CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen_native);
      CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
      CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
      CoglRenderer *cogl_renderer = cogl_display_get_renderer (cogl_display);
      MetaRendererNativeGpuData *renderer_gpu_data =
        meta_renderer_egl_get_renderer_gpu_data (META_RENDERER_EGL (cogl_renderer));
      MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native;
      MetaDrmBufferFlags buffer_flags = META_DRM_BUFFER_FLAG_NONE;
      MetaDeviceFile *render_device_file;

      if (!meta_renderer_native_use_modifiers (renderer_native))
        buffer_flags |= META_DRM_BUFFER_FLAG_DISABLE_MODIFIERS;

      render_device_file =
        meta_render_device_get_device_file (renderer_gpu_data->render_device);
      return meta_drm_buffer_gbm_new_lock_front (render_device_file,
                                                 onscreen_native->gbm.surface,
                                                 buffer_flags,
                                                 error);
    }
  else
    {
      MetaDrmBufferGbm *buffer_gbm;

      buffer_gbm = g_steal_pointer (&onscreen_native->gbm.front)->buffer_gbm;
      return g_object_ref (buffer_gbm);
    }
}

static gboolean
meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen    *onscreen,
                                               const MtkRegion *region,
                                               CoglFrameInfo   *frame_info,
                                               gpointer         user_data)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
  ClutterFrame *frame = user_data;
  MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
  g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL;
  g_autofd int sync_fd = -1;

  COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers,
                           "Meta::OnscreenNative::swap_buffers_with_damage()");

  assign_next_frame (onscreen_native, frame);

  secondary_gpu_fb =
    update_secondary_gpu_state_pre_swap_buffers (onscreen, region);

  secondary_gpu_state = onscreen_native->secondary_gpu_state;
  if (secondary_gpu_state)
    {
      g_clear_object (&secondary_gpu_state->source_framebuffer);
      secondary_gpu_state->source_framebuffer =
        g_steal_pointer (&secondary_gpu_fb);
    }

  if (!swap_buffers_with_damage (onscreen, region, frame_info, user_data))
    {
      g_warning ("%s failed. Frame aborted.", __func__);
      g_clear_pointer (&onscreen_native->next_frame, clutter_frame_unref);
      clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE);
      return FALSE;
    }

  sync_fd = cogl_renderer_get_latest_sync_fd (cogl_renderer);

  clutter_frame_set_result (frame,
                            CLUTTER_FRAME_RESULT_PENDING_PRESENTED);

  meta_frame_native_set_damage (frame_native, region);

  if (sync_fd >= 0)
    {
      meta_frame_native_set_sync_fd (frame_native, g_steal_fd (&sync_fd));
      if (onscreen_native->render_source)
        render_source_add_frame (onscreen_native->render_source, frame);
      else
        maybe_post_next_frame (onscreen);
    }
  else
    {
      maybe_post_next_frame (onscreen);
    }

  return TRUE;
}

typedef struct _NextFrameReadySource {
  GSource parent;
  CoglOnscreen *onscreen;
} NextFrameReadySource;

static void
mark_next_frame_kms_ready (CoglOnscreen *onscreen)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  CoglFrameInfo *frame_info;

  frame_info = clutter_frame_get_cogl_frame_info (onscreen_native->next_frame);

  if (frame_info->kms_ready_time_us == 0)
    frame_info->kms_ready_time_us = g_get_monotonic_time ();
}

static gboolean
next_frame_source_dispatch (GSource     *source,
                            GSourceFunc  callback,
                            gpointer     user_data)
{
  NextFrameReadySource *next_frame_ready_source;
  MetaOnscreenNative *onscreen_native;

  next_frame_ready_source = (NextFrameReadySource *) source;
  mark_next_frame_kms_ready (next_frame_ready_source->onscreen);
  onscreen_native = META_ONSCREEN_NATIVE (next_frame_ready_source->onscreen);
  g_clear_pointer (&onscreen_native->next_frame_ready_source, g_source_destroy);
  return G_SOURCE_REMOVE;
}

static GSourceFuncs next_frame_source_funcs = {
  .dispatch = next_frame_source_dispatch,
};

static void
maybe_mark_next_frame_kms_ready (CoglOnscreen *onscreen)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaFrameNative *frame_native;
  GSource *source;
  NextFrameReadySource *next_frame_ready_source;
  int sync_fd;

  frame_native = meta_frame_native_from_frame (onscreen_native->next_frame);
  sync_fd = meta_frame_native_peek_sync_fd (frame_native);
  if (sync_fd < 0 ||
      mtk_is_fd_readable (sync_fd))
    {
      mark_next_frame_kms_ready (onscreen);
      return;
    }

  source = g_source_new (&next_frame_source_funcs,
                         sizeof (NextFrameReadySource));
  g_source_add_unix_fd (source, sync_fd, G_IO_IN);

  g_source_set_static_name (source,
                            "[mutter] Clutter onscreen native next frame");
  g_source_attach (source, NULL);
  g_source_unref (source);
  onscreen_native->next_frame_ready_source = source;

  next_frame_ready_source = (NextFrameReadySource *) source;
  next_frame_ready_source->onscreen = onscreen;
}

static void
maybe_post_next_frame (CoglOnscreen *onscreen)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
  MetaRendererNativeGpuData *renderer_gpu_data =
    meta_renderer_egl_get_renderer_gpu_data (META_RENDERER_EGL (cogl_renderer));
  MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native;
  MetaRenderer *renderer = META_RENDERER (renderer_native);
  MetaBackend *backend = meta_renderer_get_backend (renderer);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaOutputKms *output_kms = META_OUTPUT_KMS (onscreen_native->output);
  MetaKmsConnector *kms_connector =
    meta_output_kms_get_kms_connector (output_kms);
  MetaPowerSave power_save_mode;
  MetaKmsCrtc *kms_crtc;
  MetaKmsDevice *kms_device;
  MetaKmsUpdate *kms_update;
  g_autoptr (MetaKmsFeedback) kms_feedback = NULL;
  g_autoptr (ClutterFrame) frame = NULL;
  MetaFrameNative *frame_native;
  MtkRegion *region;
  int sync_fd;
  const MetaKmsResultListenerVtable *listener;
  MetaKmsAssignPlaneFlag flip_flags;
  gboolean is_direct_scanout;
  gboolean is_swap_buffers;
  CoglFrameInfo *frame_info;
  MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend);
  MetaKms *kms = meta_backend_native_get_kms (backend_native);

  COGL_TRACE_SCOPED_ANCHOR (MetaRendererNativePostKmsUpdate);

  if (onscreen_native->next_frame == NULL)
    return;

  /* Prevent a busy wait. Even if we're not ready to post next_frame yet,
   * the render source has done its job by getting us here at least once.
   * If we need to come back here later to retry then it will be via the
   * presentation-related callbacks. So spinning the render source until
   * then would be a waste. Especially if the monitor is in power saving.
   */
  render_source_remove_frame (onscreen_native->render_source,
                              onscreen_native->next_frame);

  if (onscreen_native->view == NULL)
    return;

  if (onscreen_native->posted_frame)
    {
      maybe_mark_next_frame_kms_ready (onscreen);
      return;
    }

  g_clear_pointer (&onscreen_native->next_frame_ready_source, g_source_destroy);

  frame = g_steal_pointer (&onscreen_native->next_frame);
  frame_info = clutter_frame_get_cogl_frame_info (frame);
  frame_native = meta_frame_native_from_frame (frame);
  region = meta_frame_native_get_damage (frame_native);

  is_direct_scanout = meta_frame_native_get_scanout (frame_native) != NULL;
  is_swap_buffers = region != NULL;

  if (is_direct_scanout)
    {
      listener = &scanout_result_listener_vtable;
      flip_flags = META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC;
    }
  else if (is_swap_buffers)
    {
      g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL;
      g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL;
      g_autoptr (MetaDrmBuffer) buffer = NULL;
      g_autoptr (GError) error = NULL;
      MetaDrmBufferGbm *buffer_gbm;
      MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
      MetaDeviceFile *render_device_file =
        meta_render_device_get_device_file (renderer_gpu_data->render_device);

      listener = &swap_buffer_result_listener_vtable;
      flip_flags = META_KMS_ASSIGN_PLANE_FLAG_NONE;

      secondary_gpu_state = onscreen_native->secondary_gpu_state;
      if (secondary_gpu_state)
        {
          secondary_gpu_fb =
            g_steal_pointer (&secondary_gpu_state->source_framebuffer);
        }

      switch (renderer_gpu_data->mode)
        {
        case META_RENDERER_NATIVE_MODE_GBM:
          /* We might end up here with the wrong EGLSurface being current when
           * being notified about being presented, results in glitches in some
           * drivers. Strictly there shouldn't be a need for this, but make
           * sure it's current anyway, as a workaround.
           */
          cogl_onscreen_bind (onscreen);

          buffer_gbm = lock_front_buffer (onscreen_native, &error);
          if (!buffer_gbm)
            {
              g_warning ("Failed to lock front buffer on %s: %s",
                         meta_device_file_get_path (render_device_file),
                         error->message);
              goto post_failed;
            }

          primary_gpu_fb = META_DRM_BUFFER (g_steal_pointer (&buffer_gbm));
          buffer = acquire_front_buffer (onscreen,
                                         frame_native,
                                         primary_gpu_fb,
                                         secondary_gpu_fb,
                                         region,
                                         &error);
          if (buffer == NULL)
            {
              g_warning ("Failed to acquire front buffer: %s", error->message);
              goto post_failed;
            }

          meta_frame_native_set_buffer (frame_native, buffer);

          if (!meta_drm_buffer_ensure_fb_id (buffer, &error))
            {
              MetaDeviceFile *device_file =
                meta_drm_buffer_get_device_file (buffer);

              g_warning ("Failed to ensure KMS FB ID on %s: %s",
                         meta_device_file_get_path (device_file),
                         error->message);
              goto post_failed;
            }
          break;
        case META_RENDERER_NATIVE_MODE_SURFACELESS:
          break;
        }
    }
  else /* property-only update, no framebuffer changes */
    {
      listener = &swap_buffer_result_listener_vtable;
      flip_flags = META_KMS_ASSIGN_PLANE_FLAG_NONE;
    }

  power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager);
  if (power_save_mode != META_POWER_SAVE_ON)
    {
      if (!onscreen_native->gbm.surface)
        {
          onscreen_native->gbm.scanout = NULL;
          onscreen_native->gbm.bos[0].age = 0;
          onscreen_native->gbm.bos[1].age = 0;
          onscreen_native->gbm.bos[2].age = 0;
        }

      meta_renderer_native_queue_power_save_page_flip (renderer_native,
                                                       onscreen);
    }

  if (power_save_mode != META_POWER_SAVE_ON ||
      meta_kms_is_shutting_down (kms))
    goto post_failed;

  kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc));
  kms_device = meta_kms_crtc_get_device (kms_crtc);
  kms_update = meta_frame_native_ensure_kms_update (frame_native,
                                                    kms_device);

  meta_kms_update_add_result_listener (kms_update,
                                       listener,
                                       NULL,
                                       clutter_frame_ref (frame),
                                       (GDestroyNotify) clutter_frame_unref);

  ensure_crtc_modes (onscreen, kms_update);
  if (!meta_onscreen_native_flip_crtc (onscreen,
                                       frame,
                                       onscreen_native->view,
                                       onscreen_native->crtc,
                                       kms_update,
                                       flip_flags,
                                       region))
    {
      kms_update = meta_frame_native_steal_kms_update (frame_native);
      post_nonprimary_plane_update (onscreen_native, frame, kms_update);
      onscreen_native->posted_frame = clutter_frame_ref (frame);
      return;
    }

  onscreen_native->posted_frame = clutter_frame_ref (frame);

  COGL_TRACE_BEGIN_ANCHORED (MetaRendererNativePostKmsUpdate,
                             "Meta::OnscreenNative::maybe_post_next_frame#post_pending_update()");

  switch (renderer_gpu_data->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      if (meta_renderer_native_has_pending_mode_sets (renderer_native))
        {
          meta_topic (META_DEBUG_KMS,
                      "Postponing primary plane composite update for CRTC %u (%s) to %s",
                      meta_kms_crtc_get_id (kms_crtc),
                      meta_kms_device_get_path (kms_device),
                      meta_kms_connector_get_name (kms_connector));

          kms_update = meta_frame_native_steal_kms_update (frame_native);
          meta_renderer_native_queue_mode_set_update (renderer_native,
                                                      kms_update);
          return;
        }
      else if (meta_renderer_native_has_pending_mode_set (renderer_native))
        {
          meta_topic (META_DEBUG_KMS, "Posting global mode set updates on %s",
                      meta_kms_device_get_path (kms_device));

          kms_update = meta_frame_native_steal_kms_update (frame_native);
          meta_renderer_native_queue_mode_set_update (renderer_native,
                                                      kms_update);

          meta_frame_native_steal_kms_update (frame_native);
          meta_renderer_native_post_mode_set_updates (renderer_native);
          return;
        }
      break;
    case META_RENDERER_NATIVE_MODE_SURFACELESS:
      g_assert_not_reached ();
      break;
    }

  meta_topic (META_DEBUG_KMS,
              "Posting primary plane %s update for CRTC %u (%s) to %s",
              is_direct_scanout ? "direct scanout" : "composite",
              meta_kms_crtc_get_id (kms_crtc),
              meta_kms_device_get_path (kms_device),
              meta_kms_connector_get_name (kms_connector));

  kms_update = meta_frame_native_steal_kms_update (frame_native);

  sync_fd = meta_frame_native_steal_sync_fd (frame_native);
  if (sync_fd >= 0)
    meta_kms_update_set_sync_fd (kms_update, g_steal_fd (&sync_fd));

  if (frame->is_target_presentation_time)
    {
      meta_kms_update_set_target_presentation_time (kms_update,
                                                    frame->expected_presentation_time_us);
    }

  meta_kms_device_post_update (kms_device, kms_update,
                               META_KMS_UPDATE_FLAG_NONE);

  return;

post_failed:
  frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
  notify_frame_info_complete (frame_info);
}

gboolean
meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen,
                                                   CoglScanout  *scanout)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaCrtc *crtc = onscreen_native->crtc;
  MetaCrtcKms *crtc_kms = META_CRTC_KMS (crtc);
  MetaGpuKms *gpu_kms;
  MetaKmsDevice *kms_device;
  MetaKmsCrtc *kms_crtc;
  MetaKmsUpdate *test_update;
  MetaDrmBuffer *buffer;
  g_autoptr (MetaKmsFeedback) kms_feedback = NULL;
  MetaKmsFeedbackResult result;
  graphene_rect_t src_rect;
  MtkRectangle dst_rect;

  gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
  kms_device = meta_gpu_kms_get_kms_device (gpu_kms);
  kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);

  test_update = meta_kms_update_new (kms_device);

  cogl_scanout_get_src_rect (scanout, &src_rect);
  cogl_scanout_get_dst_rect (scanout, &dst_rect);

  buffer = META_DRM_BUFFER (cogl_scanout_get_buffer (scanout));
  assign_primary_plane (crtc_kms,
                        buffer,
                        test_update,
                        META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC,
                        &src_rect,
                        &dst_rect);

  meta_topic (META_DEBUG_KMS,
              "Posting direct scanout test update for CRTC %u (%s) synchronously",
              meta_kms_crtc_get_id (kms_crtc),
              meta_kms_device_get_path (kms_device));

  kms_feedback =
    meta_kms_device_process_update_sync (kms_device, test_update,
                                         META_KMS_UPDATE_FLAG_TEST_ONLY);

  result = meta_kms_feedback_get_result (kms_feedback);
  return result == META_KMS_FEEDBACK_PASSED;
}

gboolean
meta_onscreen_native_supports_direct_scanout (CoglOnscreen *onscreen)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);

  return !onscreen_native->secondary_gpu_state;
}

static void
scanout_result_feedback (const MetaKmsFeedback *kms_feedback,
                         gpointer               user_data)
{
  ClutterFrame *frame = user_data;
  CoglFrameInfo *frame_info = clutter_frame_get_cogl_frame_info (frame);
  CoglOnscreen *onscreen = cogl_frame_info_get_onscreen (frame_info);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  const GError *error = NULL;

  error = meta_kms_feedback_get_error (kms_feedback);
  if (!error)
    {
      if (frame_info->kms_ready_time_us == 0)
        {
          frame_info->kms_ready_time_us =
            meta_kms_feedback_get_ready_time_us (kms_feedback);
        }

      return;
    }

  if (!g_error_matches (error,
                        G_IO_ERROR,
                        G_IO_ERROR_PERMISSION_DENIED))
    {
      ClutterStageView *view = CLUTTER_STAGE_VIEW (onscreen_native->view);
      MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
      CoglScanout *scanout = meta_frame_native_get_scanout (frame_native);

      g_warning ("Direct scanout page flip failed: %s", error->message);

      cogl_scanout_notify_failed (scanout, onscreen);
      if (onscreen_native->next_frame == NULL && view != NULL)
        {
          clutter_stage_view_add_redraw_clip (view, NULL);
          clutter_stage_view_schedule_update_now (view);
        }
    }

  frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;

  notify_frame_info_complete (frame_info);
  meta_onscreen_native_clear_posted_fb (onscreen);
}

static gboolean
meta_onscreen_native_direct_scanout (CoglOnscreen   *onscreen,
                                     CoglScanout    *scanout,
                                     CoglFrameInfo  *frame_info,
                                     gpointer        user_data,
                                     GError        **error)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaGpuKms *render_gpu = onscreen_native->render_gpu;
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
  MetaRendererNativeGpuData *renderer_gpu_data =
    meta_renderer_egl_get_renderer_gpu_data (META_RENDERER_EGL (cogl_renderer));
  MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native;
  ClutterFrame *frame = user_data;
  MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);

  renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native,
                                                         render_gpu);

  g_warn_if_fail (renderer_gpu_data->mode == META_RENDERER_NATIVE_MODE_GBM);

  assign_next_frame (onscreen_native, frame);

  meta_frame_native_set_scanout (frame_native, scanout);
  meta_frame_native_set_buffer (frame_native,
                                META_DRM_BUFFER (cogl_scanout_get_buffer (scanout)));

  clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);

  maybe_post_next_frame (onscreen);
  return TRUE;
}

static void
ensure_back_bo (MetaOnscreenNative *onscreen_native)
{
  uint32_t best_age = ~0U;
  int i;

  if (onscreen_native->gbm.back)
    return;

  for (i = 0; i < G_N_ELEMENTS (onscreen_native->gbm.bos); i++)
    {
      uint32_t age = onscreen_native->gbm.bos[i].age;

      /* Can't use front / scanout BO */
      if (onscreen_native->gbm.bos[i].frame_info ||
          onscreen_native->gbm.scanout == &onscreen_native->gbm.bos[i])
        continue;

      if (age < best_age)
        {
          if (age)
            best_age = age;

          if (age || !onscreen_native->gbm.back)
            onscreen_native->gbm.back = &onscreen_native->gbm.bos[i];
        }
    }
}

static void
meta_onscreen_native_flush_state (CoglContext          *ctx,
                                  CoglFramebuffer      *draw_buffer,
                                  CoglFramebuffer      *read_buffer,
                                  CoglFramebufferState  state)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (draw_buffer);
  CoglFramebufferClass *framebuffer_class =
    COGL_FRAMEBUFFER_CLASS (meta_onscreen_native_parent_class);

  COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeFlushState,
                           "Meta::OnscreenNative::flush_state()");

  if (onscreen_native->gbm.surface)
    {
      framebuffer_class->flush_state (ctx, draw_buffer, read_buffer, state);
      return;
    }

  /* First bind the back buffer FBO */
  if (state & COGL_FRAMEBUFFER_STATE_BIND)
    {
      CoglFramebuffer *bind_fbo;

      ensure_back_bo (onscreen_native);
      bind_fbo = COGL_FRAMEBUFFER (onscreen_native->gbm.back->cogl_fbo);
      framebuffer_class->flush_state (ctx,
                                      bind_fbo, bind_fbo,
                                      COGL_FRAMEBUFFER_STATE_BIND);
    }

  /* Then flush the remaining state, which synchronizes the FBO state to the
   * MetaOnscreenNative ancestor CoglFramebuffer's state.
   */
  state &= ~COGL_FRAMEBUFFER_STATE_BIND;
  if (state)
    framebuffer_class->flush_state (ctx, draw_buffer, read_buffer, state);
}

static int
meta_onscreen_native_get_buffer_age (CoglOnscreen *onscreen)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeGetBufferAge,
                           "Meta::OnscreenNative::get_buffer_age()");

  if (onscreen_native->gbm.surface)
    {
      CoglOnscreenClass *parent_class =
        COGL_ONSCREEN_CLASS (meta_onscreen_native_parent_class);

      return parent_class->get_buffer_age (onscreen);
    }

  ensure_back_bo (onscreen_native);
  return onscreen_native->gbm.back->age;
}

static gboolean
meta_onscreen_native_get_window_handles (CoglOnscreen *onscreen,
                                         gpointer     *device_out,
                                         gpointer     *window_out)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
  CoglDisplayEGL *cogl_display_egl = COGL_DISPLAY_EGL (cogl_display);

  *device_out = cogl_display_egl_get_egl_context (cogl_display_egl);
  *window_out = onscreen_native;
  return TRUE;
}

static void
add_cogl_frame_info (MetaCrtc     *crtc,
                     ClutterFrame *frame)
{
  MetaGpu *gpu = meta_crtc_get_gpu (crtc);
  MetaBackend *backend = meta_gpu_get_backend (gpu);
  ClutterStage *stage = CLUTTER_STAGE (meta_backend_get_stage (backend));
  ClutterStageWindow *stage_window = _clutter_stage_get_window (stage);
  MetaRenderer *renderer = meta_backend_get_renderer (backend);
  MetaRendererView *view = meta_renderer_get_view_for_crtc (renderer, crtc);

  meta_stage_impl_add_cogl_frame_info (META_STAGE_IMPL (stage_window),
                                       CLUTTER_STAGE_VIEW (view),
                                       frame);
}

void
meta_onscreen_native_allow_vrr (MetaOnscreenNative *onscreen_native,
                                gboolean            allowed)
{
  onscreen_native->vrr_allowed = allowed;
}

static void
maybe_update_vrr (MetaOnscreenNative *onscreen_native,
                  ClutterFrame       *frame)
{
  MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
  MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
  MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
  const MetaKmsCrtcState *crtc_state =
    meta_kms_crtc_get_current_state (kms_crtc);
  MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
  ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (onscreen_native->view);
  ClutterFrameClock *frame_clock =
    clutter_stage_view_get_frame_clock (stage_view);
  ClutterFrameClockMode frame_clock_mode;
  MetaKmsUpdate *kms_update;
  gboolean vrr_enabled = FALSE;

  if (meta_output_is_vrr_enabled (onscreen_native->output))
    vrr_enabled = onscreen_native->vrr_allowed;

  if (vrr_enabled != onscreen_native->vrr_enabled)
    {
      frame_clock_mode = vrr_enabled ? CLUTTER_FRAME_CLOCK_MODE_VARIABLE :
                         CLUTTER_FRAME_CLOCK_MODE_FIXED;
      clutter_frame_clock_set_mode (frame_clock, frame_clock_mode);
      onscreen_native->vrr_enabled = vrr_enabled;
    }

  if (crtc_state->vrr.supported &&
      vrr_enabled != crtc_state->vrr.enabled)
    {
      kms_update = meta_frame_native_ensure_kms_update (frame_native, kms_device);
      meta_kms_update_set_vrr (kms_update, kms_crtc, vrr_enabled);
    }
}

void
meta_onscreen_native_before_redraw (CoglOnscreen *onscreen,
                                    ClutterFrame *frame)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);

  if (meta_get_debug_paint_flags () & META_DEBUG_PAINT_SYNC_CURSOR_PRIMARY)
    {
      MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
      MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);

      meta_kms_device_await_flush (meta_kms_crtc_get_device (kms_crtc), kms_crtc);
    }

  maybe_update_vrr (onscreen_native, frame);
}

void
meta_onscreen_native_prepare_frame (CoglOnscreen *onscreen,
                                    ClutterFrame *frame)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
  MetaOutputKms *output_kms = META_OUTPUT_KMS (onscreen_native->output);
  MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
  MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);;
  MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
  int64_t target_frame_counter = cogl_onscreen_get_frame_counter (onscreen);

  if (onscreen_native->property.gamma_lut.invalidated)
    {
      const MetaGammaLut *gamma;
      MetaKmsUpdate *kms_update;

      kms_update = meta_frame_native_ensure_kms_update (frame_native,
                                                        kms_device);

      gamma = meta_crtc_kms_peek_gamma_lut (crtc_kms);
      meta_kms_update_set_crtc_gamma (kms_update, kms_crtc, gamma);
      onscreen_native->property.gamma_lut.invalidated = FALSE;
      onscreen_native->property.gamma_lut.target_frame_counter =
        target_frame_counter;
    }

  if (onscreen_native->property.ctm.invalidated)
    {
      const MetaCtm *ctm;
      MetaKmsUpdate *kms_update;

      kms_update = meta_frame_native_ensure_kms_update (frame_native,
                                                        kms_device);

      ctm = meta_crtc_kms_peek_ctm (crtc_kms);
      meta_kms_update_set_crtc_ctm (kms_update, kms_crtc, ctm);
      onscreen_native->property.ctm.invalidated = FALSE;
      onscreen_native->property.ctm.target_frame_counter =
        target_frame_counter;
    }

  if (onscreen_native->property.privacy_screen.invalidated)
    {
      MetaKmsConnector *kms_connector =
        meta_output_kms_get_kms_connector (output_kms);
      MetaKmsUpdate *kms_update;
      gboolean enabled;

      kms_update = meta_frame_native_ensure_kms_update (frame_native,
                                                        kms_device);

      enabled = meta_output_is_privacy_screen_enabled (onscreen_native->output);
      meta_kms_update_set_privacy_screen (kms_update, kms_connector, enabled);
      onscreen_native->property.privacy_screen.invalidated = FALSE;
      onscreen_native->property.privacy_screen.target_frame_counter =
        target_frame_counter;
    }
}

static void
finish_frame_result_feedback (const MetaKmsFeedback *kms_feedback,
                              gpointer               user_data)
{
  ClutterFrame *frame = user_data;
  CoglFrameInfo *frame_info = clutter_frame_get_cogl_frame_info (frame);
  CoglOnscreen *onscreen = cogl_frame_info_get_onscreen (frame_info);
  const GError *error = NULL;

  error = meta_kms_feedback_get_error (kms_feedback);
  if (!error)
    {
      if (frame_info->kms_ready_time_us == 0)
        {
          frame_info->kms_ready_time_us =
            meta_kms_feedback_get_ready_time_us (kms_feedback);
        }

      return;
    }

  if (!g_error_matches (error,
                        G_IO_ERROR,
                        G_IO_ERROR_PERMISSION_DENIED) &&
      !g_error_matches (error,
                        META_KMS_ERROR,
                        META_KMS_ERROR_EMPTY_UPDATE))
    g_warning ("Cursor update failed: %s", error->message);

  frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;

  notify_frame_info_complete (frame_info);
  meta_onscreen_native_clear_posted_fb (onscreen);
}

static const MetaKmsResultListenerVtable finish_frame_result_listener_vtable = {
  .feedback = finish_frame_result_feedback,
};

void
meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
                                   ClutterFrame *frame)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaCrtc *crtc = onscreen_native->crtc;
  MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc));
  MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
  MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
  MetaKmsUpdate *kms_update;

  onscreen_native->needs_flush |= meta_kms_device_handle_flush (kms_device,
                                                                kms_crtc);

  if (!meta_frame_native_has_kms_update (frame_native))
    {
      if (!onscreen_native->needs_flush ||
          onscreen_native->posted_frame != NULL)
        {
          clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE);
          return;
        }
    }

  if (onscreen_native->posted_frame != NULL &&
      onscreen_native->next_frame == NULL)
    {
      g_return_if_fail (meta_frame_native_has_kms_update (frame_native));
      assign_next_frame (onscreen_native, frame);
      clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
      return;
    }

  kms_update = meta_frame_native_steal_kms_update (frame_native);

  if (onscreen_native->posted_frame != NULL &&
      onscreen_native->next_frame != NULL)
    {
      MetaFrameNative *next_frame_native;
      MetaKmsUpdate *next_kms_update;

      g_return_if_fail (kms_update);

      next_frame_native =
        meta_frame_native_from_frame (onscreen_native->next_frame);
      next_kms_update =
        meta_frame_native_ensure_kms_update (next_frame_native, kms_device);
      meta_kms_update_merge_from (next_kms_update, kms_update);
      meta_kms_update_free (kms_update);
      clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE);
      return;
    }

  if (!kms_update)
    {
      kms_update = meta_kms_update_new (kms_device);
      g_warn_if_fail (onscreen_native->needs_flush);
    }

  if (onscreen_native->needs_flush)
    {
      meta_kms_update_set_flushing (kms_update, kms_crtc);
      onscreen_native->needs_flush = FALSE;
    }

  post_nonprimary_plane_update (onscreen_native, frame, kms_update);
  onscreen_native->posted_frame = clutter_frame_ref (frame);

  clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
}

static void
post_nonprimary_plane_update (MetaOnscreenNative *onscreen_native,
                              ClutterFrame       *frame,
                              MetaKmsUpdate      *kms_update)
{
  MetaCrtc *crtc = onscreen_native->crtc;
  MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc));
  MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
  g_autoptr (MetaKmsFeedback) kms_feedback = NULL;

  add_cogl_frame_info (crtc, frame);

  meta_kms_update_add_result_listener (kms_update,
                                       &finish_frame_result_listener_vtable,
                                       NULL,
                                       clutter_frame_ref (frame),
                                       (GDestroyNotify) clutter_frame_unref);

  meta_kms_update_add_page_flip_listener (kms_update,
                                          kms_crtc,
                                          &page_flip_listener_vtable,
                                          NULL,
                                          clutter_frame_ref (frame),
                                          (GDestroyNotify) clutter_frame_unref);

  meta_topic (META_DEBUG_KMS,
              "Posting non-primary plane update for CRTC %u (%s)",
              meta_kms_crtc_get_id (kms_crtc),
              meta_kms_device_get_path (kms_device));

  meta_kms_update_set_flushing (kms_update, kms_crtc);
  meta_kms_device_post_update (kms_device, kms_update,
                               META_KMS_UPDATE_FLAG_NONE);
}

static void
discard_pending_swap (ClutterFrame **frame)
{
  if (frame && *frame)
    {
      MetaFrameNative *frame_native;
      MetaKmsUpdate *kms_update;

      frame_native = meta_frame_native_from_frame (*frame);
      kms_update = meta_frame_native_steal_kms_update (frame_native);
      g_clear_pointer (&kms_update, meta_kms_update_free);
      g_clear_pointer (frame, clutter_frame_unref);
    }
}

void
meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;

  secondary_gpu_state = onscreen_native->secondary_gpu_state;
  if (secondary_gpu_state)
    g_clear_object (&secondary_gpu_state->source_framebuffer);

  render_source_remove_frame (onscreen_native->render_source,
                              onscreen_native->next_frame);
  g_clear_pointer (&onscreen_native->next_frame_ready_source, g_source_destroy);
  discard_pending_swap (&onscreen_native->next_frame);
}

static gboolean
should_surface_be_sharable (CoglOnscreen *onscreen)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);

  if (META_GPU_KMS (meta_crtc_get_gpu (onscreen_native->crtc)) ==
      onscreen_native->render_gpu)
    return FALSE;
  else
    return TRUE;
}

static uint32_t
get_gbm_format_from_egl (CoglRendererEGL *renderer_egl,
                         EGLConfig        egl_config)
{
  uint32_t gbm_format;
  EGLint native_visual_id;

  if (cogl_renderer_egl_get_config_attrib (renderer_egl,
                                           egl_config,
                                           EGL_NATIVE_VISUAL_ID,
                                           &native_visual_id,
                                           NULL))
    gbm_format = (uint32_t) native_visual_id;
  else
    g_assert_not_reached ();

  return gbm_format;
}

static GArray *
get_supported_kms_modifiers (MetaCrtcKms *crtc_kms,
                             uint32_t     format)
{
  MetaKmsPlane *plane = meta_crtc_kms_get_assigned_primary_plane (crtc_kms);
  GArray *crtc_mods;

  g_return_val_if_fail (plane, NULL);

  crtc_mods = meta_kms_plane_get_modifiers_for_format (plane, format);
  if (!crtc_mods)
    return NULL;

  return g_array_copy (crtc_mods);
}

static GArray *
get_supported_egl_modifiers (CoglOnscreen *onscreen,
                             MetaCrtcKms  *crtc_kms,
                             uint32_t      format)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaRendererNative *renderer_native = onscreen_native->renderer_native;
  MetaGpu *gpu;
  MetaRendererNativeGpuData *renderer_gpu_data;
  MetaRenderDevice *render_device;
  GArray *modifiers;
  g_autoptr (GError) error = NULL;

  gpu = meta_crtc_get_gpu (META_CRTC (crtc_kms));
  renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native,
                                                         META_GPU_KMS (gpu));
  render_device = renderer_gpu_data->render_device;

  modifiers = meta_render_device_query_drm_modifiers (render_device, format,
                                                      COGL_DRM_MODIFIER_FILTER_NONE,
                                                      &error);
  if (!modifiers)
    {
      g_warning ("Failed to query DMABUF modifiers: %s", error->message);
      return NULL;
    }

  return modifiers;
}

static GArray *
get_supported_modifiers (CoglOnscreen *onscreen,
                         uint32_t      format)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
  MetaGpu *gpu;
  g_autoptr (GArray) modifiers = NULL;

  gpu = meta_crtc_get_gpu (META_CRTC (crtc_kms));
  if (gpu == META_GPU (onscreen_native->render_gpu))
    modifiers = get_supported_kms_modifiers (crtc_kms, format);
  else
    modifiers = get_supported_egl_modifiers (onscreen, crtc_kms, format);

  return g_steal_pointer (&modifiers);
}

static GArray *
get_supported_kms_formats (CoglOnscreen *onscreen)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
  MetaKmsPlane *plane = meta_crtc_kms_get_assigned_primary_plane (crtc_kms);

  return meta_kms_plane_copy_drm_format_list (plane);
}

static const uint32_t alphaless_10bpc_formats[] = {
  GBM_FORMAT_XRGB2101010,
  GBM_FORMAT_XBGR2101010,
  GBM_FORMAT_RGBX1010102,
  GBM_FORMAT_BGRX1010102,
};

static const uint32_t default_formats[] = {
  GBM_FORMAT_ARGB2101010,
  GBM_FORMAT_ABGR2101010,
  GBM_FORMAT_RGBA1010102,
  GBM_FORMAT_BGRA1010102,
  GBM_FORMAT_XBGR8888,
  GBM_FORMAT_ABGR8888,
  GBM_FORMAT_RGBX8888,
  GBM_FORMAT_RGBA8888,
  GBM_FORMAT_BGRX8888,
  GBM_FORMAT_BGRA8888,
  GBM_FORMAT_XRGB8888,
  GBM_FORMAT_ARGB8888,
};

static gboolean
choose_onscreen_egl_config (CoglOnscreen  *onscreen,
                            EGLConfig     *out_config,
                            GError       **error)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
  CoglRenderer *cogl_renderer = cogl_display_get_renderer (cogl_display);
  CoglRendererEGL *renderer_egl = COGL_RENDERER_EGL (cogl_renderer);
  MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
  MetaKmsPlane *kms_plane = meta_crtc_kms_get_assigned_primary_plane (crtc_kms);
  EGLint attrs[COGL_MAX_EGL_CONFIG_ATTRIBS];

  g_return_val_if_fail (META_IS_KMS_PLANE (kms_plane), FALSE);

  cogl_display_egl_determine_attributes (COGL_DISPLAY_EGL (cogl_display),
                                         attrs);

  /* Secondary GPU contexts use GLES3, which doesn't guarantee that 10 bpc
   * formats without alpha are renderable
   */
  if (!should_surface_be_sharable (onscreen) &&
      meta_renderer_native_choose_gbm_format (kms_plane,
                                              renderer_egl,
                                              attrs,
                                              alphaless_10bpc_formats,
                                              G_N_ELEMENTS (alphaless_10bpc_formats),
                                              "surface",
                                              out_config,
                                              error))
    return TRUE;

  if (meta_renderer_native_choose_gbm_format (kms_plane,
                                              renderer_egl,
                                              attrs,
                                              default_formats,
                                              G_N_ELEMENTS (default_formats),
                                              "surface",
                                              out_config,
                                              error))
    return TRUE;

  return FALSE;
}

static gboolean
choose_gbm_format (MetaKmsPlane   *kms_plane,
                   const uint32_t *in_gbm_formats,
                   size_t          n_formats,
                   uint32_t       *out_gbm_format)
{
  for (int i = 0; i < n_formats; i++)
    {
      if (kms_plane &&
          !meta_kms_plane_is_format_supported (kms_plane, in_gbm_formats[i]))
        {
          if (meta_is_topic_enabled (META_DEBUG_RENDER))
            {
              MetaDrmFormatBuf format_string;

              meta_drm_format_to_string (&format_string, in_gbm_formats[i]);
              meta_topic (META_DEBUG_RENDER,
                          "KMS CRTC doesn't support format %s",
                          format_string.s);
            }

          continue;
        }

      *out_gbm_format = in_gbm_formats[i];
      return TRUE;
    }

  return FALSE;
}

static GArray *
get_modifiers (CoglOnscreen *onscreen,
               uint32_t      format,
               gboolean      should_be_sharable)
{
  const uint64_t mod_linear = DRM_FORMAT_MOD_LINEAR;
  GArray *modifiers;

  if (!should_be_sharable)
    return get_supported_modifiers (onscreen, format);

  modifiers = g_array_sized_new (FALSE, FALSE, sizeof (uint64_t), 1);
  g_array_append_val (modifiers, mod_linear);
  return modifiers;
}

static void
dispose_bos_gbm (MetaOnscreenNative *onscreen_native)
{
  int i;

  for (i = 0; i < G_N_ELEMENTS (onscreen_native->gbm.bos); i++)
    {
      g_clear_object (&onscreen_native->gbm.bos[i].cogl_fbo);
      g_clear_object (&onscreen_native->gbm.bos[i].cogl_tex);
      g_clear_object (&onscreen_native->gbm.bos[i].buffer_gbm);
      g_clear_pointer (&onscreen_native->gbm.bos[i].gbm, gbm_bo_destroy);
    }
}

static gboolean
create_bos_gbm (CoglOnscreen  *onscreen,
                int            width,
                int            height,
                GError       **error)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaRendererNative *renderer_native = onscreen_native->renderer_native;
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglRenderbuffers *renderbuffers = NULL;
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
  CoglRenderer *cogl_renderer = cogl_display_get_renderer (cogl_display);
  MetaRendererNativeGpuData *renderer_gpu_data =
    meta_renderer_egl_get_renderer_gpu_data (META_RENDERER_EGL (cogl_renderer));
  MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
  MetaKmsPlane *kms_plane = NULL;
  uint32_t gbm_flags = GBM_BO_USE_RENDERING;
  size_t num_bos = G_N_ELEMENTS (onscreen_native->gbm.bos);
  MetaRenderDevice *render_device;
  MetaRenderDeviceGbm *render_device_gbm;
  struct gbm_device *gbm_device;
  MetaDeviceFile *device_file;
  gboolean should_be_sharable;
  const MetaFormatInfo *format_info;
  GArray *modifiers = NULL;
  uint32_t gbm_format = 0;
  int i;

  should_be_sharable = should_surface_be_sharable (onscreen);
  if (!should_be_sharable)
    {
      kms_plane = meta_crtc_kms_get_assigned_primary_plane (crtc_kms);
      gbm_flags |= GBM_BO_USE_SCANOUT;
    }

  if (!should_be_sharable)
    {
      choose_gbm_format (kms_plane,
                         alphaless_10bpc_formats,
                         G_N_ELEMENTS (alphaless_10bpc_formats),
                         &gbm_format);
    }

  if (gbm_format == 0 &&
      !choose_gbm_format (kms_plane,
                          default_formats,
                          G_N_ELEMENTS (default_formats),
                          &gbm_format))
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                   "No GBM format found");
      return FALSE;
    }

  format_info = meta_format_info_from_drm_format (gbm_format);
  renderer_gpu_data =
    meta_renderer_native_get_gpu_data (renderer_native,
                                       onscreen_native->render_gpu);
  render_device = renderer_gpu_data->render_device;
  device_file = meta_render_device_get_device_file (render_device);
  render_device_gbm = META_RENDER_DEVICE_GBM (render_device);
  gbm_device = meta_render_device_gbm_get_gbm_device (render_device_gbm);

  if (meta_renderer_native_use_modifiers (renderer_native))
    modifiers = get_modifiers (onscreen, gbm_format, should_be_sharable);

  if (modifiers)
    {
      for (i = 0; i < num_bos; i++)
        {
          onscreen_native->gbm.bos[i].gbm =
            gbm_bo_create_with_modifiers2 (gbm_device,
                                           width, height, gbm_format,
                                           (uint64_t *) modifiers->data,
                                           modifiers->len,
                                           gbm_flags);
          if (!onscreen_native->gbm.bos[i].gbm)
            break;
        }

      g_array_free (modifiers, TRUE);
    }

  if (!onscreen_native->gbm.bos[num_bos - 1].gbm)
    {
      modifiers = NULL;

      if (should_be_sharable)
        gbm_flags |= GBM_BO_USE_LINEAR;

      for (i = 0; i < num_bos; i++)
        {
          g_clear_pointer (&onscreen_native->gbm.bos[i].gbm, gbm_bo_destroy);
          onscreen_native->gbm.bos[i].gbm = gbm_bo_create (gbm_device,
                                                           width, height,
                                                           gbm_format,
                                                           gbm_flags);
          if (!onscreen_native->gbm.bos[i].gbm)
            break;
        }
    }

  if (onscreen_native->gbm.bos[num_bos - 1].gbm)
    {
      MetaDrmBufferFlags flags = META_DRM_BUFFER_FLAG_NONE;
      EGLImageKHR egl_image;

      if (!modifiers)
        flags = META_DRM_BUFFER_FLAG_DISABLE_MODIFIERS;

      for (i = 0; i < num_bos; i++)
        {
          onscreen_native->gbm.bos[i].buffer_gbm =
            meta_drm_buffer_gbm_new_take (device_file,
                                          onscreen_native->gbm.bos[i].gbm,
                                          flags,
                                          error);
          if (!onscreen_native->gbm.bos[i].buffer_gbm)
            break;

          if (!onscreen_native->secondary_gpu_state)
            {
              MetaDrmBuffer *buffer =
                META_DRM_BUFFER (onscreen_native->gbm.bos[i].buffer_gbm);

              if (!meta_drm_buffer_ensure_fb_id (buffer, error))
                break;
            }

          egl_image =
            meta_egl_ensure_gbm_bo_egl_image (COGL_RENDERER_EGL (cogl_renderer),
                                              g_steal_pointer (&onscreen_native->gbm.bos[i].gbm),
                                              META_EGL_GPU_PRIMARY,
                                              error);
          if (egl_image == EGL_NO_IMAGE_KHR)
            break;

          onscreen_native->gbm.bos[i].cogl_tex =
            cogl_texture_2d_new_from_egl_image (cogl_context,
                                                width, height,
                                                format_info->cogl_format,
                                                egl_image,
                                                COGL_EGL_IMAGE_FLAG_NO_GET_DATA,
                                                error);
          if (!onscreen_native->gbm.bos[i].cogl_tex)
            break;

          onscreen_native->gbm.bos[i].cogl_fbo =
            cogl_offscreen_new_with_texture (onscreen_native->gbm.bos[i].cogl_tex);
          framebuffer = COGL_FRAMEBUFFER (onscreen_native->gbm.bos[i].cogl_fbo);
          if (!cogl_framebuffer_allocate (framebuffer, error))
            {
              g_clear_object (&onscreen_native->gbm.bos[i].cogl_fbo);
              break;
            }

          if (!renderbuffers)
            renderbuffers = cogl_framebuffer_peek_renderbuffers (framebuffer);
          else
            cogl_framebuffer_bind_renderbuffers (framebuffer, renderbuffers);
        }

      if (onscreen_native->gbm.bos[num_bos - 1].cogl_fbo)
        return TRUE;
    }
  else
    {
      g_set_error (error, COGL_WINSYS_ERROR,
                   COGL_WINSYS_ERROR_CREATE_ONSCREEN,
                   "Failed to allocate GBM BOs");
    }

  dispose_bos_gbm (onscreen_native);
  return FALSE;
}

static gboolean
create_surfaces_gbm (CoglOnscreen        *onscreen,
                     int                  width,
                     int                  height,
                     struct gbm_surface **gbm_surface,
                     EGLSurface          *egl_surface,
                     GError             **error)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaRendererNative *renderer_native = onscreen_native->renderer_native;
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
  CoglDisplayEGL *cogl_display_egl = COGL_DISPLAY_EGL (cogl_display);
  CoglRenderer *cogl_renderer = cogl_display_get_renderer (cogl_display);
  CoglRendererEGL *cogl_renderer_egl = COGL_RENDERER_EGL (cogl_renderer);
  MetaRendererNativeGpuData *renderer_gpu_data =
    meta_renderer_egl_get_renderer_gpu_data (META_RENDERER_EGL (cogl_renderer));
  MetaRenderDeviceGbm *render_device_gbm;
  struct gbm_device *gbm_device;
  struct gbm_surface *new_gbm_surface = NULL;
  EGLNativeWindowType egl_native_window;
  gboolean should_be_sharable;
  EGLSurface new_egl_surface;
  EGLConfig egl_config;
  uint32_t format;

  renderer_gpu_data =
    meta_renderer_native_get_gpu_data (renderer_native,
                                       onscreen_native->render_gpu);
  render_device_gbm = META_RENDER_DEVICE_GBM (renderer_gpu_data->render_device);
  gbm_device = meta_render_device_gbm_get_gbm_device (render_device_gbm);

  should_be_sharable = should_surface_be_sharable (onscreen);

  if (!cogl_renderer_egl_has_feature (cogl_renderer_egl,
                                      COGL_EGL_WINSYS_FEATURE_NO_CONFIG_CONTEXT) ||
      !choose_onscreen_egl_config (onscreen, &egl_config, error))
    egl_config = cogl_display_egl_get_egl_config (cogl_display_egl);

  format = get_gbm_format_from_egl (cogl_renderer_egl,
                                    egl_config);

  if (meta_renderer_native_use_modifiers (renderer_native))
    {
      GArray *modifiers;

      modifiers = get_modifiers (onscreen, format, should_be_sharable);
      if (modifiers)
        {
          new_gbm_surface =
            gbm_surface_create_with_modifiers (gbm_device,
                                               width, height, format,
                                               (uint64_t *) modifiers->data,
                                               modifiers->len);
          g_array_free (modifiers, TRUE);
        }
    }

  if (!new_gbm_surface)
    {
      uint32_t flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING;

      if (should_be_sharable)
        flags |= GBM_BO_USE_LINEAR;

      new_gbm_surface = gbm_surface_create (gbm_device,
                                            width, height,
                                            format,
                                            flags);
    }

  if (!new_gbm_surface)
    {
      g_set_error (error, COGL_WINSYS_ERROR,
                   COGL_WINSYS_ERROR_CREATE_ONSCREEN,
                   "Failed to allocate surface: %s", g_strerror (errno));
      return FALSE;
    }

  egl_native_window = (EGLNativeWindowType) new_gbm_surface;
  new_egl_surface =
    cogl_renderer_egl_create_window_surface (cogl_renderer_egl,
                                             egl_config,
                                             egl_native_window,
                                             NULL,
                                             error);
  if (new_egl_surface == EGL_NO_SURFACE)
    {
      gbm_surface_destroy (new_gbm_surface);
      return FALSE;
    }

  *gbm_surface = new_gbm_surface;
  *egl_surface = new_egl_surface;

  return TRUE;
}

void
meta_onscreen_native_set_view (CoglOnscreen     *onscreen,
                               MetaRendererView *view)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  onscreen_native->view = view;
}

static gboolean
meta_onscreen_native_is_nvidia (MetaOnscreenNative *onscreen_native)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen_native);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
  CoglRendererEGL *renderer_egl =
    COGL_RENDERER_EGL (cogl_display_get_renderer (cogl_display));
  const char *egl_vendor;

  egl_vendor = cogl_renderer_egl_query_string (renderer_egl, EGL_VENDOR);
  return g_strcmp0 (egl_vendor, "NVIDIA") == 0;
}

static gboolean
should_try_fbos (CoglOnscreen *onscreen)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen_native);
  CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer);
  CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
  CoglRendererEGL *renderer_egl =
    COGL_RENDERER_EGL (cogl_display_get_renderer (cogl_display));
  gboolean is_nvidia, has_partial_updates;
  const char *use_fbos;

  use_fbos = getenv ("MUTTER_DEBUG_USE_FBOS");
  if (use_fbos)
    {
      gboolean try;

      try = g_strcmp0 (use_fbos, "1") == 0;
      meta_topic (META_DEBUG_KMS,
                  "%srying FBO path per MUTTER_DEBUG_USE_FBOS=%s",
                  try ? "T" : "Not t",
                  use_fbos);
      return try;
    }

  is_nvidia = meta_onscreen_native_is_nvidia (onscreen_native);

  /* The Mesa Gallium HUD can only work with a surface */
  if (!is_nvidia &&
      getenv ("GALLIUM_HUD"))
    {
      meta_topic (META_DEBUG_KMS,
                  "Not trying FBO path because GALLIUM_HUD is set");
      return FALSE;
    }

  if (is_nvidia &&
      !should_surface_be_sharable (onscreen))
    {
      meta_topic (META_DEBUG_KMS,
                  "Trying FBO path for nvidia primary scanout");
      return TRUE;
    }

  has_partial_updates = cogl_renderer_egl_has_extensions (renderer_egl,
                                                          NULL,
                                                          "EGL_KHR_partial_update",
                                                          NULL);

  meta_topic (META_DEBUG_KMS,
              "%srying FBO path because EGL_KHR_partial_update %ssupported",
              has_partial_updates ? "Not t" : "T",
              has_partial_updates ? "" : "not ");
  return !has_partial_updates;
}

static gboolean
meta_onscreen_native_allocate (CoglFramebuffer  *framebuffer,
                               GError          **error)
{
  CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  CoglOnscreenEgl *onscreen_egl = COGL_ONSCREEN_EGL (onscreen);
  MetaRendererNativeGpuData *renderer_gpu_data;
  struct gbm_surface *gbm_surface;
  EGLSurface egl_surface;
  int width;
  int height;
  CoglFramebufferClass *parent_class;

  if (META_GPU_KMS (meta_crtc_get_gpu (onscreen_native->crtc)) !=
      onscreen_native->render_gpu)
    {
      if (!init_secondary_gpu_state (onscreen_native->renderer_native,
                                     onscreen, error))
        return FALSE;
    }

  width = cogl_framebuffer_get_width (framebuffer);
  height = cogl_framebuffer_get_height (framebuffer);

  renderer_gpu_data =
    meta_renderer_native_get_gpu_data (onscreen_native->renderer_native,
                                       onscreen_native->render_gpu);
  switch (renderer_gpu_data->mode)
    {
      gboolean create_surfaces;

    case META_RENDERER_NATIVE_MODE_GBM:
      create_surfaces = !should_try_fbos (onscreen);
      if (!create_surfaces)
        {
          g_autoptr (GError) local_error = NULL;

          if (!create_bos_gbm (onscreen, width, height, &local_error))
            {
              meta_topic (META_DEBUG_KMS,
                          "Failed to create GBM BOs, falling back to surface: %s",
                          local_error->message);
              create_surfaces = TRUE;
            }
        }

      if (create_surfaces)
        {
          if (!create_surfaces_gbm (onscreen,
                                    width, height,
                                    &gbm_surface,
                                    &egl_surface,
                                    error))
            return FALSE;

          onscreen_native->gbm.surface = gbm_surface;
          cogl_onscreen_egl_set_egl_surface (onscreen_egl, egl_surface);
          maybe_init_render_source (onscreen_native);
        }
      break;
    case META_RENDERER_NATIVE_MODE_SURFACELESS:
      g_assert_not_reached ();
      break;
    }

  parent_class = COGL_FRAMEBUFFER_CLASS (meta_onscreen_native_parent_class);
  return parent_class->allocate (framebuffer, error);
}

static gboolean
all_primary_planes_support_format (MetaCrtcKms *crtc_kms,
                                   uint32_t     drm_format)
{
  MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
  MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
  gboolean supported = FALSE;
  GList *l;

  for (l = meta_kms_device_get_planes (kms_device); l; l = l->next)
    {
      MetaKmsPlane *kms_plane = l->data;

      if (meta_kms_plane_get_plane_type (kms_plane) !=
          META_KMS_PLANE_TYPE_PRIMARY)
        continue;

      if (!meta_kms_plane_is_usable_with (kms_plane, kms_crtc))
        continue;

      supported = TRUE;

      if (!meta_kms_plane_is_format_supported (kms_plane, drm_format))
        return FALSE;
    }

  return supported;
}

static gboolean
all_crtcs_support_format (MetaGpuKms *gpu_kms,
                          uint32_t    drm_format)
{
  GList *l;

  for (l = meta_gpu_get_crtcs (META_GPU (gpu_kms)); l; l = l->next)
    {
      MetaCrtcKms *crtc_kms = META_CRTC_KMS (l->data);

      if (!all_primary_planes_support_format (crtc_kms, drm_format))
        break;
    }

  return l == NULL;
}

static MetaDrmBufferGbm *
create_secondary_gpu_buffer (CoglOnscreen        *onscreen,
                             struct gbm_device   *gbm_device,
                             MetaDeviceFile      *device_file,
                             int                  width,
                             int                  height,
                             uint32_t             format,
                             GError             **error)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
  MetaGpuKms *gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (onscreen_native->crtc));
  MetaKmsDevice *kms_device = meta_gpu_kms_get_kms_device (gpu_kms);
  const char *kms_modifiers_debug_env;
  MetaKmsDeviceFlag kms_device_flags;
  MetaDrmBufferFlags drm_buffer_flags;
  struct gbm_bo *gbm_bo;
  g_autoptr (MetaDrmBufferGbm) buffer_gbm = NULL;
  g_autoptr (GArray) modifiers = NULL;

  kms_modifiers_debug_env = g_getenv ("MUTTER_DEBUG_USE_KMS_MODIFIERS");
  kms_device_flags = meta_kms_device_get_flags (kms_device);
  if (kms_device_flags & META_KMS_DEVICE_FLAG_HAS_ADDFB2 &&
      (kms_modifiers_debug_env ?
       g_strcmp0 (kms_modifiers_debug_env, "1") == 0 :
       !(kms_device_flags & META_KMS_DEVICE_FLAG_DISABLE_MODIFIERS)))
    modifiers = get_supported_kms_modifiers (crtc_kms, format);

  if (modifiers)
    {
      gbm_bo = gbm_bo_create_with_modifiers2 (gbm_device,
                                              width,
                                              height,
                                              format,
                                              (uint64_t *) modifiers->data,
                                              modifiers->len,
                                              GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
      drm_buffer_flags = META_DRM_BUFFER_FLAG_NONE;
    }
  else
    {
      gbm_bo = gbm_bo_create (gbm_device,
                              width,
                              height,
                              format,
                              GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
      drm_buffer_flags = META_DRM_BUFFER_FLAG_DISABLE_MODIFIERS;
    }

  if (!gbm_bo)
    {
      g_set_error (error,  G_IO_ERROR, G_IO_ERROR_FAILED,
                   "gbm_bo_create failed for secondary GPU: %s",
                   strerror (errno));
      return NULL;
    }

  buffer_gbm = meta_drm_buffer_gbm_new_take (device_file,
                                             gbm_bo,
                                             drm_buffer_flags,
                                             error);
  if (!buffer_gbm)
    {
      gbm_bo_destroy (gbm_bo);
      g_prefix_error (error,
                      "meta_drm_buffer_gbm_new_take failed for secondary GPU: ");
      return NULL;
    }

  if (!meta_drm_buffer_ensure_fb_id (META_DRM_BUFFER (buffer_gbm), error))
    {
      g_prefix_error (error,
                      "meta_drm_buffer_ensure_fb_id failed for secondary GPU: ");
      return NULL;
    }

  return g_steal_pointer (&buffer_gbm);
}

static gboolean
create_secondary_gpu_buffers (CoglOnscreen                         *onscreen,
                              MetaRendererNativeGpuData            *renderer_gpu_data,
                              MetaOnscreenNativeSecondaryGpuState  *secondary_gpu_state,
                              int                                   width,
                              int                                   height,
                              GError                              **error)
{
  MetaRenderDevice *render_device = renderer_gpu_data->render_device;
  MetaRenderDeviceGbm *render_device_gbm =
    META_RENDER_DEVICE_GBM (render_device);
  struct gbm_device *gbm_device;
  MetaDeviceFile *device_file;
  static const uint32_t gles3_formats[] = {
    GBM_FORMAT_ARGB2101010,
    GBM_FORMAT_ABGR2101010,
    GBM_FORMAT_RGBA1010102,
    GBM_FORMAT_BGRA1010102,
    GBM_FORMAT_XRGB8888,
    GBM_FORMAT_ARGB8888,
  };
  uint32_t format;
  int i;

  gbm_device = meta_render_device_gbm_get_gbm_device (render_device_gbm);
  device_file = meta_render_device_get_device_file (render_device);

  for (i = 0; i < G_N_ELEMENTS (gles3_formats); i++)
    {
      format = gles3_formats[i];
      g_clear_error (error);

      if (!all_crtcs_support_format (secondary_gpu_state->gpu_kms, format))
        {
          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                       "KMS CRTC doesn't support GBM format");
          continue;
        }

      secondary_gpu_state->gbm.buffer_gbm[0] =
        create_secondary_gpu_buffer (onscreen, gbm_device, device_file,
                                     width, height, format,
                                     error);
      if (!secondary_gpu_state->gbm.buffer_gbm[0])
        continue;

      secondary_gpu_state->gbm.buffer_gbm[1] =
        create_secondary_gpu_buffer (onscreen, gbm_device, device_file,
                                     width, height, format,
                                     error);
      if (secondary_gpu_state->gbm.buffer_gbm[1])
        break;

      g_clear_object (&secondary_gpu_state->gbm.buffer_gbm[0]);
    }

  return secondary_gpu_state->gbm.buffer_gbm[0] != NULL;
}

static gboolean
meta_onscreen_native_is_y_flipped (CoglFramebuffer *framebuffer)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (framebuffer);

  return onscreen_native->gbm.surface == NULL;
}

static gboolean
init_secondary_gpu_state_gpu_copy_mode (CoglOnscreen               *onscreen,
                                        MetaRendererNativeGpuData  *renderer_gpu_data,
                                        GError                    **error)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
  MetaGpuKms *gpu_kms;

  secondary_gpu_state = g_new0 (MetaOnscreenNativeSecondaryGpuState, 1);

  gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (onscreen_native->crtc));
  secondary_gpu_state->gpu_kms = gpu_kms;
  secondary_gpu_state->renderer_gpu_data = renderer_gpu_data;
  secondary_gpu_state->gbm.buffer_index = -2;

  if (!create_secondary_gpu_buffers (onscreen,
                                     renderer_gpu_data,
                                     secondary_gpu_state,
                                     cogl_framebuffer_get_width (framebuffer),
                                     cogl_framebuffer_get_height (framebuffer),
                                     error))
    {
      g_free (secondary_gpu_state);
      return FALSE;
    }

  onscreen_native->secondary_gpu_state = secondary_gpu_state;

  return TRUE;
}

static uint32_t
pick_secondary_gpu_framebuffer_format_for_cpu (CoglOnscreen *onscreen)
{
  /*
   * cogl_framebuffer_read_pixels_into_bitmap () supported formats in
   * preference order. Ideally these should depend on the render buffer
   * format copy_shared_framebuffer_cpu () will be reading from but
   * alpha channel ignored.
   */
  static const uint32_t preferred_formats[] =
    {
      /*
       * DRM_FORMAT_XBGR8888 a.k.a GL_RGBA, GL_UNSIGNED_BYTE on
       * little-endian is possibly the most optimized glReadPixels
       * output format. glReadPixels cannot avoid manufacturing an alpha
       * channel if the render buffer does not have one and converting
       * to ABGR8888 may be more optimized than ARGB8888.
       */
      DRM_FORMAT_XBGR8888,
      /* The rest are other fairly commonly used formats in OpenGL. */
      DRM_FORMAT_XRGB8888,
    };
  g_autoptr (GArray) formats = NULL;
  size_t k;
  unsigned int i;
  uint32_t drm_format;

  formats = get_supported_kms_formats (onscreen);

  /* Check if any of our preferred formats are supported. */
  for (k = 0; k < G_N_ELEMENTS (preferred_formats); k++)
    {
      g_assert (meta_format_info_from_drm_format (preferred_formats[k]));

      for (i = 0; i < formats->len; i++)
        {
          drm_format = g_array_index (formats, uint32_t, i);

          if (drm_format == preferred_formats[k])
            return drm_format;
        }
    }

  /*
   * Otherwise just pick an arbitrary format we recognize. The formats
   * list is not in any specific order and we don't know any better
   * either.
   */
  for (i = 0; i < formats->len; i++)
    {
      drm_format = g_array_index (formats, uint32_t, i);

      if (meta_format_info_from_drm_format (drm_format))
        return drm_format;
    }

  return DRM_FORMAT_INVALID;
}

static gboolean
init_secondary_gpu_state_cpu_copy_mode (MetaRendererNative         *renderer_native,
                                        CoglOnscreen               *onscreen,
                                        MetaRendererNativeGpuData  *renderer_gpu_data,
                                        GError                    **error)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
  MetaRenderDevice *render_device;
  MetaGpuKms *gpu_kms;
  int width, height;
  unsigned int i;
  uint32_t drm_format;
  MetaDrmFormatBuf tmp;

  drm_format = pick_secondary_gpu_framebuffer_format_for_cpu (onscreen);
  if (drm_format == DRM_FORMAT_INVALID)
    {
      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
                   "Could not find a suitable pixel format in CPU copy mode");
      return FALSE;
    }

  width = cogl_framebuffer_get_width (framebuffer);
  height = cogl_framebuffer_get_height (framebuffer);

  gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (onscreen_native->crtc));
  render_device = renderer_gpu_data->render_device;
  meta_topic (META_DEBUG_KMS,
              "Secondary GPU %s using DRM format '%s' (0x%x) for a %dx%d output.",
              meta_render_device_get_name (render_device),
              meta_drm_format_to_string (&tmp, drm_format),
              drm_format,
              width, height);

  secondary_gpu_state = g_new0 (MetaOnscreenNativeSecondaryGpuState, 1);
  secondary_gpu_state->renderer_gpu_data = renderer_gpu_data;
  secondary_gpu_state->gpu_kms = gpu_kms;

  for (i = 0; i < G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs); i++)
    {
      MetaDrmBuffer *dumb_buffer;

      dumb_buffer = meta_render_device_allocate_dumb_buf (render_device,
                                                          width, height,
                                                          drm_format,
                                                          error);
      if (!dumb_buffer)
        {
          secondary_gpu_state_free (secondary_gpu_state);
          return FALSE;
        }

      secondary_gpu_state->cpu.dumb_fbs[i] = META_DRM_BUFFER_DUMB (dumb_buffer);
    }

  /*
   * This function initializes everything needed for
   * META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO as well.
   */
  secondary_gpu_state->import_status =
    META_SHARED_FRAMEBUFFER_IMPORT_STATUS_NONE;

  onscreen_native->secondary_gpu_state = secondary_gpu_state;

  return TRUE;
}

static gboolean
init_secondary_gpu_state (MetaRendererNative  *renderer_native,
                          CoglOnscreen        *onscreen,
                          GError             **error)
{
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaGpu *gpu = meta_crtc_get_gpu (onscreen_native->crtc);
  MetaRendererNativeGpuData *renderer_gpu_data;
  g_autoptr (GError) local_error = NULL;

  renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native,
                                                         META_GPU_KMS (gpu));

  switch (renderer_gpu_data->secondary.copy_mode)
    {
    case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU:
      if (init_secondary_gpu_state_gpu_copy_mode (onscreen,
                                                  renderer_gpu_data,
                                                  &local_error))
        return TRUE;

      g_warning ("Secondary GPU initialization failed (%s). "
                 "Falling back to GPU-less mode instead, so the "
                 "secondary monitor may be slow to update.",
                 local_error->message);

      renderer_gpu_data->secondary.copy_mode =
        META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO;

      G_GNUC_FALLTHROUGH;
    case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO:
      /*
       * Initialize also the primary copy mode, so that if zero-copy
       * path fails, which is quite likely, we can simply continue
       * with the primary copy path on the very first frame.
       */
      G_GNUC_FALLTHROUGH;
    case META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY:
      if (!init_secondary_gpu_state_cpu_copy_mode (renderer_native,
                                                   onscreen,
                                                   renderer_gpu_data,
                                                   error))
        return FALSE;
      break;
    }

  return TRUE;
}

void
meta_onscreen_native_invalidate (MetaOnscreenNative *onscreen_native)
{
  const MetaOutputInfo *output_info =
    meta_output_get_info (onscreen_native->output);

  if (meta_crtc_get_gamma_lut_size (onscreen_native->crtc) > 0)
    onscreen_native->property.gamma_lut.invalidated = TRUE;
  if (meta_crtc_is_ctm_supported (onscreen_native->crtc))
    onscreen_native->property.ctm.invalidated = TRUE;
  if (output_info->supports_privacy_screen)
    onscreen_native->property.privacy_screen.invalidated = TRUE;
}

static void
on_gamma_lut_changed (MetaCrtc           *crtc,
                      MetaOnscreenNative *onscreen_native)
{
  ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (onscreen_native->view);

  onscreen_native->property.gamma_lut.invalidated = TRUE;
  clutter_stage_view_schedule_update (stage_view);
}

static void
on_ctm_changed (MetaCrtc           *crtc,
                MetaOnscreenNative *onscreen_native)
{
  ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (onscreen_native->view);

  onscreen_native->property.ctm.invalidated = TRUE;
  clutter_stage_view_schedule_update (stage_view);
}

static void
on_privacy_screen_enabled_changed (MetaOutput         *output,
                                   GParamSpec         *pspec,
                                   MetaOnscreenNative *onscreen_native)
{
  ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (onscreen_native->view);

  onscreen_native->property.privacy_screen.invalidated = TRUE;
  clutter_stage_view_schedule_update (stage_view);
}

MetaOnscreenNative *
meta_onscreen_native_new (MetaRendererNative *renderer_native,
                          MetaGpuKms         *render_gpu,
                          MetaOutput         *output,
                          MetaCrtc           *crtc,
                          CoglContext        *cogl_context,
                          int                 width,
                          int                 height)
{
  MetaOnscreenNative *onscreen_native;
  CoglFramebufferDriverConfig driver_config;
  const MetaOutputInfo *output_info = meta_output_get_info (output);

  driver_config = (CoglFramebufferDriverConfig) {
    .type = COGL_FRAMEBUFFER_DRIVER_TYPE_BACK,
  };
  onscreen_native = g_object_new (META_TYPE_ONSCREEN_NATIVE,
                                  "context", cogl_context,
                                  "driver-config", &driver_config,
                                  "width", width,
                                  "height", height,
                                  NULL);

  onscreen_native->renderer_native = renderer_native;
  onscreen_native->render_gpu = render_gpu;

  g_set_object (&onscreen_native->output, output);
  g_set_object (&onscreen_native->crtc, crtc);

  if (meta_crtc_get_gamma_lut_size (crtc) > 0)
    {
      onscreen_native->property.gamma_lut.invalidated = TRUE;
      onscreen_native->property.gamma_lut.signal_handler_id =
        g_signal_connect (crtc, "gamma-lut-changed",
                          G_CALLBACK (on_gamma_lut_changed),
                          onscreen_native);
    }

  if (meta_crtc_is_ctm_supported (crtc))
    {
      onscreen_native->property.ctm.invalidated = TRUE;
      onscreen_native->property.ctm.signal_handler_id =
        g_signal_connect (crtc, "ctm-changed",
                          G_CALLBACK (on_ctm_changed),
                          onscreen_native);
    }

  if (output_info->supports_privacy_screen)
    {
      onscreen_native->property.privacy_screen.invalidated = TRUE;
      onscreen_native->property.privacy_screen.signal_handler_id =
        g_signal_connect (output, "notify::is-privacy-screen-enabled",
                          G_CALLBACK (on_privacy_screen_enabled_changed),
                          onscreen_native);
    }

  return onscreen_native;
}

static void
clear_invalidation_handlers (MetaOnscreenNative *onscreen_native)
{
  g_clear_signal_handler (&onscreen_native->property.gamma_lut.signal_handler_id,
                          onscreen_native->crtc);
  g_clear_signal_handler (&onscreen_native->property.ctm.signal_handler_id,
                          onscreen_native->crtc);
  g_clear_signal_handler (&onscreen_native->property.privacy_screen.signal_handler_id,
                          onscreen_native->output);
}

static void
meta_onscreen_native_dispose (GObject *object)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (object);
  CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
  MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
  MetaRendererNative *renderer_native = onscreen_native->renderer_native;
  MetaRendererNativeGpuData *renderer_gpu_data;

  g_clear_pointer (&onscreen_native->next_frame_ready_source, g_source_destroy);
  g_clear_pointer (&onscreen_native->render_source, g_source_destroy);
  meta_onscreen_native_detach (onscreen_native);

  meta_onscreen_native_discard_pending_swaps (onscreen);
  g_clear_pointer (&onscreen_native->posted_frame, clutter_frame_unref);
  g_clear_object (&onscreen_native->presented_buffer);

  renderer_gpu_data =
    meta_renderer_native_get_gpu_data (renderer_native,
                                       onscreen_native->render_gpu);
  switch (renderer_gpu_data->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      dispose_bos_gbm (onscreen_native);
      break;
    case META_RENDERER_NATIVE_MODE_SURFACELESS:
      g_assert_not_reached ();
      break;
    }

  G_OBJECT_CLASS (meta_onscreen_native_parent_class)->dispose (object);

  g_clear_pointer (&onscreen_native->gbm.surface, gbm_surface_destroy);
  g_clear_pointer (&onscreen_native->secondary_gpu_state,
                   secondary_gpu_state_free);

  g_clear_object (&onscreen_native->output);
  g_clear_object (&onscreen_native->crtc);
}

static void
meta_onscreen_native_init (MetaOnscreenNative *onscreen_native)
{
}

static void
meta_onscreen_native_class_init (MetaOnscreenNativeClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  CoglFramebufferClass *framebuffer_class = COGL_FRAMEBUFFER_CLASS (klass);
  CoglOnscreenClass *onscreen_class = COGL_ONSCREEN_CLASS (klass);

  object_class->dispose = meta_onscreen_native_dispose;

  framebuffer_class->allocate = meta_onscreen_native_allocate;
  framebuffer_class->flush_state = meta_onscreen_native_flush_state;
  framebuffer_class->is_y_flipped = meta_onscreen_native_is_y_flipped;

  onscreen_class->queue_damage_region =
    meta_onscreen_native_queue_damage_region;
  onscreen_class->swap_buffers_with_damage =
    meta_onscreen_native_swap_buffers_with_damage;
  onscreen_class->direct_scanout = meta_onscreen_native_direct_scanout;
  onscreen_class->get_window_handles = meta_onscreen_native_get_window_handles;
  onscreen_class->get_buffer_age = meta_onscreen_native_get_buffer_age;
}

MetaCrtc *
meta_onscreen_native_get_crtc (MetaOnscreenNative *onscreen_native)
{
  return onscreen_native->crtc;
}

void
meta_onscreen_native_detach (MetaOnscreenNative *onscreen_native)
{
  clear_invalidation_handlers (onscreen_native);
  onscreen_native->view = NULL;
}
