/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */

/*
 * Copyright (C) 2011 Intel Corporation.
 * Copyright (C) 2016 Red Hat
 *
 * 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.
 *
 * Authors:
 *   Rob Bradford <rob@linux.intel.com> (from cogl-winsys-egl-kms.c)
 *   Kristian Høgsberg (from eglkms.c)
 *   Benjamin Franzke (from eglkms.c)
 *   Robert Bragg <robert@linux.intel.com> (from cogl-winsys-egl-kms.c)
 *   Neil Roberts <neil@linux.intel.com> (from cogl-winsys-egl-kms.c)
 *   Jonas Ådahl <jadahl@redhat.com>
 *
 */

#include "config.h"

#include <errno.h>
#include <fcntl.h>
#include <gbm.h>
#include <gio/gio.h>
#include <glib-object.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>

#include "backends/meta-backend-private.h"
#include "backends/meta-egl.h"
#include "backends/meta-egl-ext.h"
#include "backends/meta-renderer-view.h"
#include "backends/native/meta-monitor-manager-kms.h"
#include "backends/native/meta-renderer-native.h"
#include "cogl/cogl.h"
#include "core/boxes-private.h"

#ifndef EGL_DRM_MASTER_FD_EXT
#define EGL_DRM_MASTER_FD_EXT 0x333C
#endif

enum
{
  PROP_0,

  PROP_KMS_FD,
  PROP_KMS_FILE_PATH,

  PROP_LAST
};

typedef struct _MetaOnscreenNative
{
  struct {
    struct gbm_surface *surface;
    uint32_t current_fb_id;
    uint32_t next_fb_id;
    struct gbm_bo *current_bo;
    struct gbm_bo *next_bo;
  } gbm;

#ifdef HAVE_EGL_DEVICE
  struct {
    EGLStreamKHR stream;

    struct {
      uint32_t fb_id;
      uint32_t handle;
      void *map;
      uint64_t map_size;
    } dumb_fb;
  } egl;
#endif

  gboolean pending_queue_swap_notify;
  gboolean pending_swap_notify;

  gboolean pending_set_crtc;

  int64_t pending_queue_swap_notify_frame_count;
  int64_t pending_swap_notify_frame_count;

  MetaRendererView *view;
  int pending_flips;
} MetaOnscreenNative;

struct _MetaRendererNative
{
  MetaRenderer parent;

  int kms_fd;
  char *kms_file_path;

  MetaRendererNativeMode mode;

  EGLDisplay egl_display;

  struct {
    struct gbm_device *device;
  } gbm;

#ifdef HAVE_EGL_DEVICE
  struct {
    EGLDeviceEXT device;

    gboolean no_egl_output_drm_flip_event;
  } egl;
#endif

  CoglClosure *swap_notify_idle;

  int64_t frame_counter;

  gboolean no_add_fb2;
};

static void
initable_iface_init (GInitableIface *initable_iface);

G_DEFINE_TYPE_WITH_CODE (MetaRendererNative,
                         meta_renderer_native,
                         META_TYPE_RENDERER,
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                                initable_iface_init))

static const CoglWinsysEGLVtable _cogl_winsys_egl_vtable;
static const CoglWinsysVtable *parent_vtable;

static void
meta_renderer_native_disconnect (CoglRenderer *cogl_renderer)
{
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;

  if (egl_renderer->edpy != EGL_NO_DISPLAY)
    eglTerminate (egl_renderer->edpy);

  g_slice_free (CoglRendererEGL, egl_renderer);
}

static void
flush_pending_swap_notify (CoglFramebuffer *framebuffer)
{
  if (framebuffer->type == COGL_FRAMEBUFFER_TYPE_ONSCREEN)
    {
      CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
      CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
      MetaOnscreenNative *onscreen_native = egl_onscreen->platform;

      if (onscreen_native->pending_swap_notify)
        {
          CoglFrameInfo *info;

          while ((info = g_queue_peek_head (&onscreen->pending_frame_infos)) &&
                 info->global_frame_counter <= onscreen_native->pending_swap_notify_frame_count)
            {
              _cogl_onscreen_notify_frame_sync (onscreen, info);
              _cogl_onscreen_notify_complete (onscreen, info);
              cogl_object_unref (info);
              g_queue_pop_head (&onscreen->pending_frame_infos);
            }

          onscreen_native->pending_swap_notify = FALSE;
          cogl_object_unref (onscreen);
        }
    }
}

static void
flush_pending_swap_notify_idle (void *user_data)
{
  CoglContext *cogl_context = user_data;
  CoglRendererEGL *egl_renderer = cogl_context->display->renderer->winsys;
  MetaRendererNative *renderer_native = egl_renderer->platform;
  GList *l;

  /* This needs to be disconnected before invoking the callbacks in
   * case the callbacks cause it to be queued again */
  _cogl_closure_disconnect (renderer_native->swap_notify_idle);
  renderer_native->swap_notify_idle = NULL;

  l = cogl_context->framebuffers;
  while (l)
    {
      GList *next = l->next;
      CoglFramebuffer *framebuffer = l->data;

      flush_pending_swap_notify (framebuffer);

      l = next;
    }
}

static void
free_current_bo (CoglOnscreen *onscreen)
{
  CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
  MetaOnscreenNative *onscreen_native = egl_onscreen->platform;
  CoglContext *cogl_context = COGL_FRAMEBUFFER (onscreen)->context;
  CoglRenderer *cogl_renderer = cogl_context->display->renderer;
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;
  MetaRendererNative *renderer_native = egl_renderer->platform;

  if (onscreen_native->gbm.current_fb_id)
    {
      drmModeRmFB (renderer_native->kms_fd,
                   onscreen_native->gbm.current_fb_id);
      onscreen_native->gbm.current_fb_id = 0;
    }
  if (onscreen_native->gbm.current_bo)
    {
      gbm_surface_release_buffer (onscreen_native->gbm.surface,
                                  onscreen_native->gbm.current_bo);
      onscreen_native->gbm.current_bo = NULL;
    }
}

static void
meta_onscreen_native_queue_swap_notify (CoglOnscreen *onscreen)
{
  CoglOnscreenEGL *egl_onscreen =  onscreen->winsys;
  MetaOnscreenNative *onscreen_native = egl_onscreen->platform;
  MetaBackend *backend = meta_get_backend ();
  ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
  CoglContext *cogl_context =
    clutter_backend_get_cogl_context (clutter_backend);
  CoglRenderer *cogl_renderer = cogl_context->display->renderer;
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;
  MetaRendererNative *renderer_native = egl_renderer->platform;

  onscreen_native->pending_swap_notify_frame_count =
    onscreen_native->pending_queue_swap_notify_frame_count;

  if (onscreen_native->pending_swap_notify)
    return;

  /* We only want to notify that the swap is complete when the
   * application calls cogl_context_dispatch so instead of
   * immediately notifying we queue an idle callback */
  if (!renderer_native->swap_notify_idle)
    {
      renderer_native->swap_notify_idle =
        _cogl_poll_renderer_add_idle (cogl_renderer,
                                      flush_pending_swap_notify_idle,
                                      cogl_context,
                                      NULL);
    }

  /*
   * The framebuffer will have its own referenc while the swap notify is
   * pending. Otherwise when destroying the view would drop the pending
   * notification with if the destruction happens before the idle callback
   * is invoked.
   */
  cogl_object_ref (onscreen);
  onscreen_native->pending_swap_notify = TRUE;
}

static gboolean
meta_renderer_native_connect (CoglRenderer *cogl_renderer,
                              GError      **error)
{
  MetaBackend *backend = meta_get_backend ();
  MetaRenderer *renderer = meta_backend_get_renderer (backend);
  MetaRendererNative *renderer_native = META_RENDERER_NATIVE (renderer);
  CoglRendererEGL *egl_renderer;

  cogl_renderer->winsys = g_slice_new0 (CoglRendererEGL);
  egl_renderer = cogl_renderer->winsys;

  egl_renderer->platform_vtable = &_cogl_winsys_egl_vtable;
  egl_renderer->platform = renderer_native;
  egl_renderer->edpy = renderer_native->egl_display;

  if (!_cogl_winsys_egl_renderer_connect_common (cogl_renderer, error))
    goto fail;

  return TRUE;

fail:
  meta_renderer_native_disconnect (cogl_renderer);

  return FALSE;
}

static int
meta_renderer_native_add_egl_config_attributes (CoglDisplay           *cogl_display,
                                                CoglFramebufferConfig *config,
                                                EGLint                *attributes)
{
  CoglRendererEGL *egl_renderer = cogl_display->renderer->winsys;
  MetaRendererNative *renderer_native = egl_renderer->platform;
  int i = 0;

  switch (renderer_native->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      attributes[i++] = EGL_SURFACE_TYPE;
      attributes[i++] = EGL_WINDOW_BIT;
      break;
#ifdef HAVE_EGL_DEVICE
    case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
      attributes[i++] = EGL_SURFACE_TYPE;
      attributes[i++] = EGL_STREAM_BIT_KHR;
      break;
#endif
    }

  return i;
}

static gboolean
meta_renderer_native_setup_egl_display (CoglDisplay *cogl_display,
                                        GError     **error)
{
  CoglDisplayEGL *egl_display = cogl_display->winsys;
  CoglRendererEGL *egl_renderer = cogl_display->renderer->winsys;
  MetaRendererNative *renderer_native = egl_renderer->platform;

  egl_display->platform = renderer_native;

  /* Force a full modeset / drmModeSetCrtc on
   * the first swap buffers call.
   */
  meta_renderer_native_queue_modes_reset (renderer_native);

  return TRUE;
}

static void
meta_renderer_native_destroy_egl_display (CoglDisplay *cogl_display)
{
}

static EGLSurface
create_dummy_pbuffer_surface (EGLDisplay egl_display,
                              GError   **error)
{
  MetaBackend *backend = meta_get_backend ();
  MetaEgl *egl = meta_backend_get_egl (backend);
  EGLConfig pbuffer_config;
  static const EGLint pbuffer_config_attribs[] = {
    EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
    EGL_RED_SIZE, 1,
    EGL_GREEN_SIZE, 1,
    EGL_BLUE_SIZE, 1,
    EGL_ALPHA_SIZE, 0,
    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
    EGL_NONE
  };
  static const EGLint pbuffer_attribs[] = {
    EGL_WIDTH, 16,
    EGL_HEIGHT, 16,
    EGL_NONE
  };

  if (!meta_egl_choose_config (egl, egl_display, pbuffer_config_attribs,
                               &pbuffer_config, error))
    return EGL_NO_SURFACE;

  return meta_egl_create_pbuffer_surface (egl, egl_display,
                                          pbuffer_config, pbuffer_attribs,
                                          error);
}

static gboolean
meta_renderer_native_egl_context_created (CoglDisplay *cogl_display,
                                          GError     **error)
{
  CoglDisplayEGL *egl_display = cogl_display->winsys;
  CoglRenderer *cogl_renderer = cogl_display->renderer;
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;

  if ((egl_renderer->private_features &
       COGL_EGL_WINSYS_FEATURE_SURFACELESS_CONTEXT) == 0)
    {
      egl_display->dummy_surface =
        create_dummy_pbuffer_surface (egl_renderer->edpy, error);
      if (egl_display->dummy_surface == EGL_NO_SURFACE)
        return FALSE;
    }

  if (!_cogl_winsys_egl_make_current (cogl_display,
                                      egl_display->dummy_surface,
                                      egl_display->dummy_surface,
                                      egl_display->egl_context))
    {
      _cogl_set_error (error, COGL_WINSYS_ERROR,
                   COGL_WINSYS_ERROR_CREATE_CONTEXT,
                   "Failed to make context current");
      return FALSE;
    }

  return TRUE;
}

static void
meta_renderer_native_egl_cleanup_context (CoglDisplay *cogl_display)
{
  CoglDisplayEGL *egl_display = cogl_display->winsys;
  CoglRenderer *cogl_renderer = cogl_display->renderer;
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;

  if (egl_display->dummy_surface != EGL_NO_SURFACE)
    {
      eglDestroySurface (egl_renderer->edpy, egl_display->dummy_surface);
      egl_display->dummy_surface = EGL_NO_SURFACE;
    }
}

static void
meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen)
{
  CoglOnscreenEGL *egl_onscreen =  onscreen->winsys;
  MetaOnscreenNative *onscreen_native = egl_onscreen->platform;

  free_current_bo (onscreen);

  onscreen_native->gbm.current_fb_id = onscreen_native->gbm.next_fb_id;
  onscreen_native->gbm.next_fb_id = 0;

  onscreen_native->gbm.current_bo = onscreen_native->gbm.next_bo;
  onscreen_native->gbm.next_bo = NULL;
}

static void
on_crtc_flipped (GClosure         *closure,
                 MetaRendererView *view)
{
  ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (view);
  CoglFramebuffer *framebuffer =
    clutter_stage_view_get_onscreen (stage_view);
  CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
  CoglOnscreenEGL *egl_onscreen =  onscreen->winsys;
  MetaOnscreenNative *onscreen_native = egl_onscreen->platform;
  MetaBackend *backend = meta_get_backend ();
  MetaRenderer *renderer = meta_backend_get_renderer (backend);
  MetaRendererNative *renderer_native = META_RENDERER_NATIVE (renderer);

  onscreen_native->pending_flips--;
  if (onscreen_native->pending_flips == 0)
    {
      onscreen_native->pending_queue_swap_notify = FALSE;

      meta_onscreen_native_queue_swap_notify (onscreen);

      switch (renderer_native->mode)
        {
        case META_RENDERER_NATIVE_MODE_GBM:
          meta_onscreen_native_swap_drm_fb (onscreen);
          break;
#ifdef HAVE_EGL_DEVICE
        case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
          break;
#endif
        }
    }
}

static void
flip_closure_destroyed (MetaRendererView *view)
{
  ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (view);
  CoglFramebuffer *framebuffer =
    clutter_stage_view_get_onscreen (stage_view);
  CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
  CoglOnscreenEGL *egl_onscreen =  onscreen->winsys;
  MetaOnscreenNative *onscreen_native = egl_onscreen->platform;
  MetaBackend *backend = meta_get_backend ();
  MetaRenderer *renderer = meta_backend_get_renderer (backend);
  MetaRendererNative *renderer_native = META_RENDERER_NATIVE (renderer);

  switch (renderer_native->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      if (onscreen_native->gbm.next_fb_id)
        {
          drmModeRmFB (renderer_native->kms_fd, onscreen_native->gbm.next_fb_id);
          gbm_surface_release_buffer (onscreen_native->gbm.surface,
                                      onscreen_native->gbm.next_bo);
          onscreen_native->gbm.next_bo = NULL;
          onscreen_native->gbm.next_fb_id = 0;
        }

      break;
#ifdef HAVE_EGL_DEVICE
    case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
      break;
#endif
    }

  if (onscreen_native->pending_queue_swap_notify)
    {
      meta_onscreen_native_queue_swap_notify (onscreen);
      onscreen_native->pending_queue_swap_notify = FALSE;
    }

  g_object_unref (view);
}

#ifdef HAVE_EGL_DEVICE
static void
flip_egl_stream (MetaRendererNative *renderer_native,
                 MetaOnscreenNative *onscreen_native,
                 GClosure           *flip_closure)
{
  MetaBackend *backend = meta_get_backend ();
  MetaEgl *egl = meta_backend_get_egl (backend);
  ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
  CoglContext *cogl_context =
    clutter_backend_get_cogl_context (clutter_backend);
  CoglDisplay *cogl_display = cogl_context->display;
  CoglRendererEGL *egl_renderer = cogl_display->renderer->winsys;
  EGLAttrib *acquire_attribs;
  GError *error = NULL;

  if (renderer_native->egl.no_egl_output_drm_flip_event)
    return;

  acquire_attribs = (EGLAttrib[]) {
    EGL_DRM_FLIP_EVENT_DATA_NV,
    (EGLAttrib) flip_closure,
    EGL_NONE
  };
  if (!meta_egl_stream_consumer_acquire_attrib (egl,
                                                egl_renderer->edpy,
                                                onscreen_native->egl.stream,
                                                acquire_attribs,
                                                &error))
    {
      g_warning ("Failed to flip EGL stream (%s), relying on clock from now on",
                 error->message);
      g_error_free (error);
      renderer_native->egl.no_egl_output_drm_flip_event = TRUE;
      return;
    }

  g_closure_ref (flip_closure);

  return;
}
#endif /* HAVE_EGL_DEVICE */

static void
meta_onscreen_native_flip_crtc (MetaOnscreenNative *onscreen_native,
                                GClosure           *flip_closure,
                                MetaCRTC           *crtc,
                                int                 x,
                                int                 y,
                                gboolean           *fb_in_use)
{
  MetaBackend *backend = meta_get_backend ();
  MetaRenderer *renderer = meta_backend_get_renderer (backend);
  MetaRendererNative *renderer_native = META_RENDERER_NATIVE (renderer);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaMonitorManagerKms *monitor_manager_kms =
    META_MONITOR_MANAGER_KMS (monitor_manager);

  if (!meta_monitor_manager_kms_is_crtc_active (monitor_manager_kms,
                                                crtc))
    {
      *fb_in_use = FALSE;
      return;
    }

  switch (renderer_native->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      if (meta_monitor_manager_kms_flip_crtc (monitor_manager_kms,
                                              crtc,
                                              x, y,
                                              onscreen_native->gbm.next_fb_id,
                                              flip_closure,
                                              fb_in_use))
        onscreen_native->pending_flips++;
      break;
#ifdef HAVE_EGL_DEVICE
    case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
      flip_egl_stream (renderer_native, onscreen_native, flip_closure);
      onscreen_native->pending_flips++;
      *fb_in_use = TRUE;
      break;
#endif
    }
}

static void
meta_onscreen_native_set_crtc_modes (MetaOnscreenNative *onscreen_native)
{
  MetaBackend *backend = meta_get_backend ();
  MetaRenderer *renderer = meta_backend_get_renderer (backend);
  MetaRendererNative *renderer_native = META_RENDERER_NATIVE (renderer);
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaMonitorManagerKms *monitor_manager_kms =
    META_MONITOR_MANAGER_KMS (monitor_manager);
  MetaRendererView *view = onscreen_native->view;
  uint32_t fb_id = 0;
  MetaMonitorInfo *monitor_info;

  switch (renderer_native->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      fb_id = onscreen_native->gbm.next_fb_id;
      break;
#ifdef HAVE_EGL_DEVICE
    case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
      fb_id = onscreen_native->egl.dumb_fb.fb_id;
      break;
#endif
    }

  g_assert (fb_id != 0);

  monitor_info = meta_renderer_view_get_monitor_info (view);
  if (monitor_info)
    {
      unsigned int i;

      for (i = 0; i < monitor_manager->n_crtcs; i++)
        {
          MetaCRTC *crtc = &monitor_manager->crtcs[i];
          int x = crtc->rect.x - monitor_info->rect.x;
          int y = crtc->rect.y - monitor_info->rect.y;

          if (crtc->logical_monitor != monitor_info)
            continue;

          meta_monitor_manager_kms_apply_crtc_mode (monitor_manager_kms,
                                                    crtc,
                                                    x, y,
                                                    fb_id);
        }
    }
  else
    {
      unsigned int i;

      for (i = 0; i < monitor_manager->n_crtcs; i++)
        {
          MetaCRTC *crtc = &monitor_manager->crtcs[i];

          meta_monitor_manager_kms_apply_crtc_mode (monitor_manager_kms,
                                                    crtc,
                                                    crtc->rect.x, crtc->rect.y,
                                                    fb_id);
        }
    }
}

static void
meta_onscreen_native_flip_crtcs (CoglOnscreen *onscreen)
{
  CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
  MetaOnscreenNative *onscreen_native = egl_onscreen->platform;
  MetaBackend *backend = meta_get_backend ();
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaRendererView *view = onscreen_native->view;
  GClosure *flip_closure;
  MetaMonitorInfo *monitor_info;
  gboolean fb_in_use = FALSE;

  /*
   * Create a closure that either will be invoked or destructed.
   * Invoking the closure represents a completed flip. If the closure
   * is destructed before being invoked, the framebuffer references will be
   * cleaned up accordingly.
   *
   * Each successful flip will each own one reference to the closure, thus keep
   * it alive until either invoked or destructed. If flipping failed, the
   * closure will be destructed before this function goes out of scope.
   */
  flip_closure = g_cclosure_new (G_CALLBACK (on_crtc_flipped),
                                 g_object_ref (view),
                                 (GClosureNotify) flip_closure_destroyed);
  g_closure_set_marshal (flip_closure, g_cclosure_marshal_generic);

  /* Either flip the CRTC's of the monitor info, if we are drawing just part
   * of the stage, or all of the CRTC's if we are drawing the whole stage.
   */
  monitor_info = meta_renderer_view_get_monitor_info (view);
  if (monitor_info)
    {
      unsigned int i;

      for (i = 0; i < monitor_manager->n_crtcs; i++)
        {
          MetaCRTC *crtc = &monitor_manager->crtcs[i];
          int x = crtc->rect.x - monitor_info->rect.x;
          int y = crtc->rect.y - monitor_info->rect.y;

          if (crtc->logical_monitor != monitor_info)
            continue;

          meta_onscreen_native_flip_crtc (onscreen_native, flip_closure,
                                          crtc, x, y,
                                          &fb_in_use);
        }
    }
  else
    {
      unsigned int i;

      for (i = 0; i < monitor_manager->n_crtcs; i++)
        {
          MetaCRTC *crtc = &monitor_manager->crtcs[i];

          meta_onscreen_native_flip_crtc (onscreen_native, flip_closure,
                                          crtc, crtc->rect.x, crtc->rect.y,
                                          &fb_in_use);
        }
    }

  /*
   * If the framebuffer is in use, but we don't have any pending flips it means
   * that flipping is not supported and we set the next framebuffer directly.
   * Since we won't receive a flip callback, lets just notify listeners
   * directly.
   */
  if (fb_in_use && onscreen_native->pending_flips == 0)
    {
      MetaRenderer *renderer = meta_backend_get_renderer (backend);
      MetaRendererNative *renderer_native = META_RENDERER_NATIVE (renderer);

      switch (renderer_native->mode)
        {
        case META_RENDERER_NATIVE_MODE_GBM:
          meta_onscreen_native_swap_drm_fb (onscreen);
          break;
#ifdef HAVE_EGL_DEVICE
        case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
          break;
#endif
        }
    }

  onscreen_native->pending_queue_swap_notify = TRUE;

  g_closure_unref (flip_closure);
}

static gboolean
gbm_get_next_fb_id (CoglOnscreen   *onscreen,
                    struct gbm_bo **out_next_bo,
                    uint32_t       *out_next_fb_id)
{
  CoglContext *cogl_context = COGL_FRAMEBUFFER (onscreen)->context;
  CoglRenderer *cogl_renderer = cogl_context->display->renderer;
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;
  MetaRendererNative *renderer_native = egl_renderer->platform;
  CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
  MetaOnscreenNative *onscreen_native = egl_onscreen->platform;
  uint32_t handle, stride;
  struct gbm_bo *next_bo;
  uint32_t next_fb_id;

  /* Now we need to set the CRTC to whatever is the front buffer */
  next_bo = gbm_surface_lock_front_buffer (onscreen_native->gbm.surface);

  stride = gbm_bo_get_stride (next_bo);
  handle = gbm_bo_get_handle (next_bo).u32;

  if (drmModeAddFB (renderer_native->kms_fd,
                    cogl_framebuffer_get_width (COGL_FRAMEBUFFER (onscreen)),
                    cogl_framebuffer_get_height (COGL_FRAMEBUFFER (onscreen)),
                    24, /* depth */
                    32, /* bpp */
                    stride,
                    handle,
                    &next_fb_id))
    {
      g_warning ("Failed to create new back buffer handle: %m");
      gbm_surface_release_buffer (onscreen_native->gbm.surface, next_bo);
      return FALSE;
    }

  *out_next_bo = next_bo;
  *out_next_fb_id = next_fb_id;
  return TRUE;
}

static void
meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
                                               const int    *rectangles,
                                               int           n_rectangles)
{
  CoglContext *cogl_context = COGL_FRAMEBUFFER (onscreen)->context;
  CoglRenderer *cogl_renderer = cogl_context->display->renderer;
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;
  MetaRendererNative *renderer_native = egl_renderer->platform;
  CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
  MetaOnscreenNative *onscreen_native = egl_onscreen->platform;
  MetaBackend *backend = meta_get_backend ();
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaMonitorManagerKms *monitor_manager_kms =
    META_MONITOR_MANAGER_KMS (monitor_manager);
  CoglFrameInfo *frame_info;

  frame_info = g_queue_peek_tail (&onscreen->pending_frame_infos);
  frame_info->global_frame_counter = renderer_native->frame_counter;

  /*
   * Wait for the flip callback before continuing, as we might have started the
   * animation earlier due to the animation being driven by some other monitor.
   */
  while (onscreen_native->pending_flips)
    meta_monitor_manager_kms_wait_for_flip (monitor_manager_kms);

  parent_vtable->onscreen_swap_buffers_with_damage (onscreen,
                                                    rectangles,
                                                    n_rectangles);

  switch (renderer_native->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      g_warn_if_fail (onscreen_native->gbm.next_bo == NULL &&
                      onscreen_native->gbm.next_fb_id == 0);

      if (!gbm_get_next_fb_id (onscreen,
                               &onscreen_native->gbm.next_bo,
                               &onscreen_native->gbm.next_fb_id))
        return;

      break;
#ifdef HAVE_EGL_DEVICE
    case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
      break;
#endif
    }

  /* If this is the first framebuffer to be presented then we now setup the
   * crtc modes, else we flip from the previous buffer */
  if (onscreen_native->pending_set_crtc)
    {
      meta_onscreen_native_set_crtc_modes (onscreen_native);
      onscreen_native->pending_set_crtc = FALSE;
    }

  onscreen_native->pending_queue_swap_notify_frame_count = renderer_native->frame_counter;
  meta_onscreen_native_flip_crtcs (onscreen);
}

static gboolean
meta_renderer_native_init_egl_context (CoglContext *cogl_context,
                                       GError     **error)
{
#ifdef HAVE_EGL_DEVICE
  CoglRenderer *cogl_renderer = cogl_context->display->renderer;
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;
  MetaRendererNative *renderer_native = egl_renderer->platform;
#endif

  COGL_FLAGS_SET (cogl_context->features,
                  COGL_FEATURE_ID_SWAP_BUFFERS_EVENT, TRUE);
  /* TODO: remove this deprecated feature */
  COGL_FLAGS_SET (cogl_context->winsys_features,
                  COGL_WINSYS_FEATURE_SWAP_BUFFERS_EVENT,
                  TRUE);
  COGL_FLAGS_SET (cogl_context->winsys_features,
                  COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT,
                  TRUE);
  COGL_FLAGS_SET (cogl_context->winsys_features,
                  COGL_WINSYS_FEATURE_MULTIPLE_ONSCREEN,
                  TRUE);

#ifdef HAVE_EGL_DEVICE
  if (renderer_native->mode == META_RENDERER_NATIVE_MODE_EGL_DEVICE)
    COGL_FLAGS_SET (cogl_context->features,
                    COGL_FEATURE_ID_TEXTURE_EGL_IMAGE_EXTERNAL, TRUE);
#endif

  return TRUE;
}

static gboolean
meta_renderer_native_create_surface_gbm (MetaRendererNative  *renderer_native,
                                         int                  width,
                                         int                  height,
                                         struct gbm_surface **gbm_surface,
                                         EGLSurface          *egl_surface,
                                         GError             **error)
{
  MetaBackend *backend = meta_get_backend ();
  ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
  CoglContext *cogl_context =
    clutter_backend_get_cogl_context (clutter_backend);
  CoglDisplay *cogl_display = cogl_context->display;
  CoglDisplayEGL *egl_display = cogl_display->winsys;
  CoglRendererEGL *egl_renderer = cogl_display->renderer->winsys;
  struct gbm_surface *new_gbm_surface;
  EGLNativeWindowType egl_native_window;
  EGLSurface new_egl_surface;

  new_gbm_surface = gbm_surface_create (renderer_native->gbm.device,
                                        width, height,
                                        GBM_FORMAT_XRGB8888,
                                        GBM_BO_USE_SCANOUT |
                                        GBM_BO_USE_RENDERING);

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

  egl_native_window = (EGLNativeWindowType) new_gbm_surface;
  new_egl_surface = eglCreateWindowSurface (egl_renderer->edpy,
                                            egl_display->egl_config,
                                            egl_native_window,
                                            NULL);
  if (new_egl_surface == EGL_NO_SURFACE)
    {
      gbm_surface_destroy (new_gbm_surface);
      g_set_error (error, COGL_WINSYS_ERROR,
                   COGL_WINSYS_ERROR_CREATE_ONSCREEN,
                   "Failed to allocate surface");
      return FALSE;
    }

  *gbm_surface = new_gbm_surface;
  *egl_surface = new_egl_surface;

  return TRUE;
}

#ifdef HAVE_EGL_DEVICE
static gboolean
meta_renderer_native_create_surface_egl_device (MetaRendererNative *renderer_native,
                                                MetaMonitorInfo    *monitor_info,
                                                int                 width,
                                                int                 height,
                                                EGLStreamKHR       *out_egl_stream,
                                                EGLSurface         *out_egl_surface,
                                                GError            **error)
{
  MetaBackend *backend = meta_get_backend ();
  MetaEgl *egl = meta_backend_get_egl (backend);
  ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
  CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend);
  CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
  CoglDisplayEGL *cogl_egl_display = cogl_display->winsys;
  CoglRenderer *cogl_renderer = cogl_display->renderer;
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;
  EGLDisplay egl_display = egl_renderer->edpy;
  EGLConfig egl_config;
  EGLStreamKHR egl_stream;
  EGLSurface egl_surface;
  EGLint num_layers;
  EGLOutputLayerEXT output_layer;
  EGLAttrib output_attribs[] = {
    /*
     * An "monitor_info" may have multiple outputs/crtcs in case its tiled,
     * but as far as I can tell, EGL only allows you to pass one crtc_id, so
     * lets pass the first one.
     */
    EGL_DRM_CRTC_EXT, monitor_info->outputs[0]->crtc->crtc_id,
    EGL_NONE,
  };
  EGLint stream_attribs[] = {
    EGL_STREAM_FIFO_LENGTH_KHR, 1,
#ifdef EGL_EXT_stream_acquire_mode
    EGL_CONSUMER_AUTO_ACQUIRE_EXT, EGL_FALSE,
#endif
    EGL_NONE
  };
  EGLint stream_producer_attribs[] = {
    EGL_WIDTH, width,
    EGL_HEIGHT, height,
    EGL_NONE
  };

  egl_stream = meta_egl_create_stream (egl, egl_display, stream_attribs, error);
  if (egl_stream == EGL_NO_STREAM_KHR)
    return FALSE;

  if (!meta_egl_get_output_layers (egl, egl_display,
                                   output_attribs,
                                   &output_layer, 1, &num_layers,
                                   error))
    {
      meta_egl_destroy_stream (egl, egl_display, egl_stream, NULL);
      return FALSE;
    }

  if (num_layers < 1)
    {
      meta_egl_destroy_stream (egl, egl_display, egl_stream, NULL);
      g_set_error (error, G_IO_ERROR,
                   G_IO_ERROR_FAILED,
                   "Unable to find output layers.");
      return FALSE;
    }

  if (!meta_egl_stream_consumer_output (egl, egl_display,
                                        egl_stream, output_layer,
                                        error))
    {
      meta_egl_destroy_stream (egl, egl_display, egl_stream, NULL);
      return FALSE;
    }

  egl_config = cogl_egl_display->egl_config;
  egl_surface = meta_egl_create_stream_producer_surface (egl,
                                                         egl_display,
                                                         egl_config,
                                                         egl_stream,
                                                         stream_producer_attribs,
                                                         error);
  if (egl_surface == EGL_NO_SURFACE)
    {
      meta_egl_destroy_stream (egl, egl_display, egl_stream, NULL);
      return FALSE;
    }

  *out_egl_stream = egl_stream;
  *out_egl_surface = egl_surface;

  return TRUE;
}

static gboolean
init_dumb_fb (MetaRendererNative *renderer_native,
              MetaOnscreenNative *onscreen_native,
              int                 width,
              int                 height,
              GError            **error)
{
  struct drm_mode_create_dumb create_arg;
  struct drm_mode_destroy_dumb destroy_arg;
  struct drm_mode_map_dumb map_arg;
  uint32_t fb_id = 0;
  void *map;

  create_arg = (struct drm_mode_create_dumb) {
    .bpp = 32, /* RGBX8888 */
    .width = width,
    .height = height
  };
  if (drmIoctl (renderer_native->kms_fd, DRM_IOCTL_MODE_CREATE_DUMB,
                &create_arg) != 0)
    {
      g_set_error (error, G_IO_ERROR,
                   G_IO_ERROR_FAILED,
                   "Failed to create dumb drm buffer: %s",
                   g_strerror (errno));
      goto err_ioctl;
    }

  if (!renderer_native->no_add_fb2)
    {
      uint32_t handles[4] = { create_arg.handle, };
      uint32_t pitches[4] = { create_arg.pitch, };
      uint32_t offsets[4] = { 0 };

      if (drmModeAddFB2 (renderer_native->kms_fd, width, height,
                         GBM_FORMAT_XRGB8888,
                         handles, pitches, offsets,
                         &fb_id, 0) != 0)
        {
          g_warning ("drmModeAddFB2 failed (%s), falling back to drmModeAddFB",
                     g_strerror (errno));
          renderer_native->no_add_fb2 = TRUE;
        }
    }

  if (renderer_native->no_add_fb2)
    {
      if (drmModeAddFB (renderer_native->kms_fd, width, height,
                        24 /* depth of RGBX8888 */,
                        32 /* bpp of RGBX8888 */,
                        create_arg.pitch,
                        create_arg.handle,
                        &fb_id) != 0)
        {
          g_set_error (error, G_IO_ERROR,
                       G_IO_ERROR_FAILED,
                       "drmModeAddFB failed: %s",
                       g_strerror (errno));
          goto err_add_fb;
        }
    }

  map_arg = (struct drm_mode_map_dumb) {
    .handle = create_arg.handle
  };
  if (drmIoctl (renderer_native->kms_fd, DRM_IOCTL_MODE_MAP_DUMB,
                &map_arg) != 0)
    {
      g_set_error (error, G_IO_ERROR,
                   G_IO_ERROR_FAILED,
                   "Failed to map dumb drm buffer: %s",
                   g_strerror (errno));
      goto err_map_dumb;
    }

  map = mmap (NULL, create_arg.size, PROT_WRITE, MAP_SHARED,
              renderer_native->kms_fd,
              map_arg.offset);
  if (map == MAP_FAILED)
    {
      g_set_error (error, G_IO_ERROR,
                   G_IO_ERROR_FAILED,
                   "Failed to mmap dumb drm buffer memory: %s",
                   g_strerror (errno));
      goto err_mmap;
    }

  onscreen_native->egl.dumb_fb.fb_id = fb_id;
  onscreen_native->egl.dumb_fb.handle = create_arg.handle;
  onscreen_native->egl.dumb_fb.map = map;
  onscreen_native->egl.dumb_fb.map_size = create_arg.size;

  return TRUE;

err_mmap:
err_map_dumb:
  drmModeRmFB (renderer_native->kms_fd, fb_id);

err_add_fb:
  destroy_arg = (struct drm_mode_destroy_dumb) {
    .handle = create_arg.handle
  };
  drmIoctl (renderer_native->kms_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg);

err_ioctl:
  return FALSE;
}

static void
release_dumb_fb (MetaRendererNative *renderer_native,
                 MetaOnscreenNative *onscreen_native)
{
  struct drm_mode_destroy_dumb destroy_arg;

  if (!onscreen_native->egl.dumb_fb.map)
    return;

  munmap (onscreen_native->egl.dumb_fb.map,
          onscreen_native->egl.dumb_fb.map_size);
  onscreen_native->egl.dumb_fb.map = NULL;

  drmModeRmFB (renderer_native->kms_fd,
               onscreen_native->egl.dumb_fb.fb_id);

  destroy_arg = (struct drm_mode_destroy_dumb) {
    .handle = onscreen_native->egl.dumb_fb.handle
  };
  drmIoctl (renderer_native->kms_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg);
}
#endif /* HAVE_EGL_DEVICE */

static gboolean
meta_renderer_native_init_onscreen (CoglOnscreen *onscreen,
                                    GError      **error)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = framebuffer->context;
  CoglDisplay *cogl_display = cogl_context->display;
  CoglDisplayEGL *egl_display = cogl_display->winsys;
  CoglOnscreenEGL *egl_onscreen;
  MetaOnscreenNative *onscreen_native;

  _COGL_RETURN_VAL_IF_FAIL (egl_display->egl_context, FALSE);

  onscreen->winsys = g_slice_new0 (CoglOnscreenEGL);
  egl_onscreen = onscreen->winsys;

  onscreen_native = g_slice_new0 (MetaOnscreenNative);
  egl_onscreen->platform = onscreen_native;

  /*
   * Don't actually initialize anything here, since we may not have the
   * information available yet, and there is no way to pass it at this stage.
   * To properly allocate a MetaOnscreenNative, the caller must call
   * meta_onscreen_native_allocate() after cogl_framebuffer_allocate().
   *
   * TODO: Turn CoglFramebuffer/CoglOnscreen into GObjects, so it's possible
   * to add backend specific properties.
   */

  return TRUE;
}

static gboolean
meta_onscreen_native_allocate (CoglOnscreen *onscreen,
                               GError      **error)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = framebuffer->context;
  CoglDisplay *cogl_display = cogl_context->display;
  CoglRenderer *cogl_renderer = cogl_display->renderer;
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;
  MetaRendererNative *renderer_native = egl_renderer->platform;
  CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
  MetaOnscreenNative *onscreen_native = egl_onscreen->platform;
  struct gbm_surface *gbm_surface;
  EGLSurface egl_surface;
  int width;
  int height;
#ifdef HAVE_EGL_DEVICE
  MetaRendererView *view;
  MetaMonitorInfo *monitor_info;
  EGLStreamKHR egl_stream;
#endif

  onscreen_native->pending_set_crtc = TRUE;

  /* If a kms_fd is set then the display width and height
   * won't be available until meta_renderer_native_set_layout
   * is called. In that case, defer creating the surface
   * until then.
   */
  width = cogl_framebuffer_get_width (framebuffer);
  height = cogl_framebuffer_get_height (framebuffer);
  if (width == 0 || height == 0)
    return TRUE;

  switch (renderer_native->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      if (!meta_renderer_native_create_surface_gbm (renderer_native,
                                                    width, height,
                                                    &gbm_surface,
                                                    &egl_surface,
                                                    error))
        return FALSE;

      onscreen_native->gbm.surface = gbm_surface;
      egl_onscreen->egl_surface = egl_surface;
      break;
#ifdef HAVE_EGL_DEVICE
    case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
      if (!init_dumb_fb (renderer_native, onscreen_native,
                         width, height, error))
        return FALSE;

      view = onscreen_native->view;
      monitor_info = meta_renderer_view_get_monitor_info (view);
      if (!meta_renderer_native_create_surface_egl_device (renderer_native,
                                                           monitor_info,
                                                           width, height,
                                                           &egl_stream,
                                                           &egl_surface,
                                                           error))
        return FALSE;

      onscreen_native->egl.stream = egl_stream;
      egl_onscreen->egl_surface = egl_surface;
      break;
#endif /* HAVE_EGL_DEVICE */
    }

  return TRUE;
}

static void
meta_renderer_native_release_onscreen (CoglOnscreen *onscreen)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *cogl_context = framebuffer->context;
  CoglRenderer *cogl_renderer = cogl_context->display->renderer;
  CoglRendererEGL *egl_renderer = cogl_renderer->winsys;
  CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
  MetaRendererNative *renderer_native = egl_renderer->platform;
  MetaOnscreenNative *onscreen_native;

  /* If we never successfully allocated then there's nothing to do */
  if (egl_onscreen == NULL)
    return;

  onscreen_native = egl_onscreen->platform;

  if (egl_onscreen->egl_surface != EGL_NO_SURFACE)
    {
      eglDestroySurface (egl_renderer->edpy, egl_onscreen->egl_surface);
      egl_onscreen->egl_surface = EGL_NO_SURFACE;
    }

  switch (renderer_native->mode)
    {
    case META_RENDERER_NATIVE_MODE_GBM:
      /* flip state takes a reference on the onscreen so there should
       * never be outstanding flips when we reach here. */
      g_return_if_fail (onscreen_native->gbm.next_fb_id == 0);

      free_current_bo (onscreen);

      if (onscreen_native->gbm.surface)
        {
          gbm_surface_destroy (onscreen_native->gbm.surface);
          onscreen_native->gbm.surface = NULL;
        }
      break;
#ifdef HAVE_EGL_DEVICE
    case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
      release_dumb_fb (renderer_native, onscreen_native);
      if (onscreen_native->egl.stream != EGL_NO_STREAM_KHR)
        {
          MetaBackend *backend = meta_get_backend ();
          MetaEgl *egl = meta_backend_get_egl (backend);

          meta_egl_destroy_stream (egl,
                                   egl_renderer->edpy,
                                   onscreen_native->egl.stream,
                                   NULL);
          onscreen_native->egl.stream = EGL_NO_STREAM_KHR;
        }
      break;
#endif /* HAVE_EGL_DEVICE */
    }

  g_slice_free (MetaOnscreenNative, onscreen_native);
  g_slice_free (CoglOnscreenEGL, onscreen->winsys);
  onscreen->winsys = NULL;
}

static const CoglWinsysEGLVtable
_cogl_winsys_egl_vtable = {
  .add_config_attributes = meta_renderer_native_add_egl_config_attributes,
  .display_setup = meta_renderer_native_setup_egl_display,
  .display_destroy = meta_renderer_native_destroy_egl_display,
  .context_created = meta_renderer_native_egl_context_created,
  .cleanup_context = meta_renderer_native_egl_cleanup_context,
  .context_init = meta_renderer_native_init_egl_context
};

MetaRendererNativeMode
meta_renderer_native_get_mode (MetaRendererNative *renderer_native)
{
  return renderer_native->mode;
}

struct gbm_device *
meta_renderer_native_get_gbm (MetaRendererNative *renderer_native)
{
  return renderer_native->gbm.device;
}

int
meta_renderer_native_get_kms_fd (MetaRendererNative *renderer_native)
{
  return renderer_native->kms_fd;
}

void
meta_renderer_native_queue_modes_reset (MetaRendererNative *renderer_native)
{
  MetaRenderer *renderer = META_RENDERER (renderer_native);
  GList *l;

  for (l = meta_renderer_get_views (renderer); l; l = l->next)
    {
      ClutterStageView *stage_view = l->data;
      CoglFramebuffer *framebuffer =
        clutter_stage_view_get_onscreen (stage_view);
      CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
      CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
      MetaOnscreenNative *onscreen_native = egl_onscreen->platform;

      onscreen_native->pending_set_crtc = TRUE;
    }
}

static MetaMonitorTransform
meta_renderer_native_get_monitor_info_transform (MetaRenderer    *renderer,
                                                 MetaMonitorInfo *monitor_info)
{
  MetaBackend *backend = meta_get_backend ();
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  MetaMonitorManagerKms *monitor_manager_kms =
    META_MONITOR_MANAGER_KMS (monitor_manager);

  g_assert (monitor_info->n_outputs > 0);

  return meta_monitor_manager_kms_get_view_transform (monitor_manager_kms,
                                                      monitor_info->outputs[0]->crtc);
}

static CoglOnscreen *
meta_renderer_native_create_onscreen (MetaRendererNative    *renderer,
                                      CoglContext           *context,
                                      MetaMonitorTransform   transform,
                                      gint                   view_width,
                                      gint                   view_height)
{
  CoglOnscreen *onscreen;
  gint width, height;
  GError *error = NULL;

  if (meta_monitor_transform_is_rotated (transform))
    {
      width = view_height;
      height = view_width;
    }
  else
    {
      width = view_width;
      height = view_height;
    }

  onscreen = cogl_onscreen_new (context, width, height);
  cogl_onscreen_set_swap_throttled (onscreen,
                                    _clutter_get_sync_to_vblank ());

  if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (onscreen), &error))
    {
      g_warning ("Could not create onscreen: %s", error->message);
      cogl_object_unref (onscreen);
      g_error_free (error);
      return NULL;
    }

  return onscreen;
}

static CoglOffscreen *
meta_renderer_native_create_offscreen (MetaRendererNative    *renderer,
                                       CoglContext           *context,
                                       MetaMonitorTransform   transform,
                                       gint                   view_width,
                                       gint                   view_height)
{
  CoglOffscreen *fb;
  CoglTexture2D *tex;
  GError *error = NULL;

  tex = cogl_texture_2d_new_with_size (context, view_width, view_height);
  cogl_primitive_texture_set_auto_mipmap (COGL_PRIMITIVE_TEXTURE (tex), FALSE);

  if (!cogl_texture_allocate (COGL_TEXTURE (tex), &error))
    {
      cogl_object_unref (tex);
      return FALSE;
    }

  fb = cogl_offscreen_new_with_texture (COGL_TEXTURE (tex));
  cogl_object_unref (tex);
  if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (fb), &error))
    {
      g_warning ("Could not create offscreen: %s", error->message);
      g_error_free (error);
      cogl_object_unref (fb);
      return FALSE;
    }

  return fb;
}

gboolean
meta_renderer_native_set_legacy_view_size (MetaRendererNative *renderer_native,
                                           MetaRendererView   *view,
                                           int                 width,
                                           int                 height,
                                           GError            **error)
{
  ClutterBackend *clutter_backend = clutter_get_default_backend ();
  CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend);
  CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
  CoglRendererEGL *egl_renderer = cogl_display->renderer->winsys;
  ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (view);
  cairo_rectangle_int_t view_layout;

  clutter_stage_view_get_layout (stage_view, &view_layout);

  if (width != view_layout.width || height != view_layout.height)
    {
      MetaBackend *backend = meta_get_backend ();
      MetaMonitorManager *monitor_manager =
        meta_backend_get_monitor_manager (backend);
      MetaMonitorManagerKms *monitor_manager_kms =
        META_MONITOR_MANAGER_KMS (monitor_manager);
      CoglFramebuffer *framebuffer =
        clutter_stage_view_get_onscreen (stage_view);
      CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
      CoglOnscreenEGL *egl_onscreen = onscreen->winsys;
      MetaOnscreenNative *onscreen_native = egl_onscreen->platform;
      CoglDisplayEGL *egl_display = cogl_display->winsys;
      struct gbm_surface *new_surface;
      EGLSurface new_egl_surface;
      cairo_rectangle_int_t view_layout;

      /*
       * Ensure we don't have any pending flips that will want
       * to swap the current buffer.
       */
      while (onscreen_native->gbm.next_fb_id != 0)
        meta_monitor_manager_kms_wait_for_flip (monitor_manager_kms);

      /* Need to drop the GBM surface and create a new one */

      if (!meta_renderer_native_create_surface_gbm (renderer_native,
                                                    width, height,
                                                    &new_surface,
                                                    &new_egl_surface,
                                                    error))
        return FALSE;

      if (egl_onscreen->egl_surface)
        {
          _cogl_winsys_egl_make_current (cogl_display,
                                         egl_display->dummy_surface,
                                         egl_display->dummy_surface,
                                         egl_display->egl_context);
          eglDestroySurface (egl_renderer->edpy,
                             egl_onscreen->egl_surface);
        }

      /*
       * Release the current buffer and destroy the associated surface. The
       * kernel will deal with keeping the actual buffer alive until its no
       * longer used.
       */
      free_current_bo (onscreen);
      g_clear_pointer (&onscreen_native->gbm.surface, gbm_surface_destroy);

      /*
       * Update the active gbm and egl surfaces and make sure they they are
       * used for drawing the coming frame.
       */
      onscreen_native->gbm.surface = new_surface;
      egl_onscreen->egl_surface = new_egl_surface;
      _cogl_winsys_egl_make_current (cogl_display,
                                     egl_onscreen->egl_surface,
                                     egl_onscreen->egl_surface,
                                     egl_display->egl_context);

      view_layout = (cairo_rectangle_int_t) {
        .width = width,
        .height = height
      };
      g_object_set (G_OBJECT (view),
                    "layout", &view_layout,
                    NULL);

      _cogl_framebuffer_winsys_update_size (framebuffer, width, height);
    }

  meta_renderer_native_queue_modes_reset (renderer_native);

  return TRUE;
}

static const CoglWinsysVtable *
get_native_cogl_winsys_vtable (void)
{
  static gboolean vtable_inited = FALSE;
  static CoglWinsysVtable vtable;

  if (!vtable_inited)
    {
      /* The this winsys is a subclass of the EGL winsys so we
         start by copying its vtable */

      parent_vtable = _cogl_winsys_egl_get_vtable ();
      vtable = *parent_vtable;

      vtable.id = COGL_WINSYS_ID_CUSTOM;
      vtable.name = "EGL_KMS";

      vtable.renderer_connect = meta_renderer_native_connect;
      vtable.renderer_disconnect = meta_renderer_native_disconnect;

      vtable.onscreen_init = meta_renderer_native_init_onscreen;
      vtable.onscreen_deinit = meta_renderer_native_release_onscreen;

      /* The KMS winsys doesn't support swap region */
      vtable.onscreen_swap_region = NULL;
      vtable.onscreen_swap_buffers_with_damage =
        meta_onscreen_native_swap_buffers_with_damage;

      vtable_inited = TRUE;
    }

  return &vtable;
}

static CoglRenderer *
meta_renderer_native_create_cogl_renderer (MetaRenderer *renderer)
{
  CoglRenderer *cogl_renderer;

  cogl_renderer = cogl_renderer_new ();
  cogl_renderer_set_custom_winsys (cogl_renderer,
                                   get_native_cogl_winsys_vtable);

  return cogl_renderer;
}

static void
meta_onscreen_native_set_view (CoglOnscreen     *onscreen,
                               MetaRendererView *view)
{
  CoglOnscreenEGL *egl_onscreen;
  MetaOnscreenNative *onscreen_native;

  egl_onscreen = onscreen->winsys;
  onscreen_native = egl_onscreen->platform;
  onscreen_native->view = view;
}

MetaRendererView *
meta_renderer_native_create_legacy_view (MetaRendererNative *renderer_native)
{
  MetaBackend *backend = meta_get_backend ();
  MetaMonitorManager *monitor_manager =
    meta_backend_get_monitor_manager (backend);
  CoglOnscreen *onscreen = NULL;
  ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
  CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend);
  cairo_rectangle_int_t view_layout = { 0 };
  MetaRendererView *view;
  GError *error = NULL;

  if (!monitor_manager)
    return NULL;

  meta_monitor_manager_get_screen_size (monitor_manager,
                                        &view_layout.width,
                                        &view_layout.height);

  onscreen = meta_renderer_native_create_onscreen (renderer_native,
                                                   cogl_context,
                                                   META_MONITOR_TRANSFORM_NORMAL,
                                                   view_layout.width,
                                                   view_layout.height);

  if (!onscreen)
    meta_fatal ("Failed to allocate onscreen framebuffer\n");

  view = g_object_new (META_TYPE_RENDERER_VIEW,
                       "layout", &view_layout,
                       "framebuffer", onscreen,
                       NULL);

  meta_onscreen_native_set_view (onscreen, view);

  if (!meta_onscreen_native_allocate (onscreen, &error))
    {
      g_warning ("Could not create onscreen: %s", error->message);
      cogl_object_unref (onscreen);
      g_object_unref (view);
      g_error_free (error);
      return NULL;
    }

  cogl_object_unref (onscreen);

  return view;
}

static MetaRendererView *
meta_renderer_native_create_view (MetaRenderer    *renderer,
                                  MetaMonitorInfo *monitor_info)
{
  MetaBackend *backend = meta_get_backend ();
  ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend);
  CoglContext *cogl_context = clutter_backend_get_cogl_context (clutter_backend);
  CoglDisplay *cogl_display = cogl_context_get_display (cogl_context);
  CoglDisplayEGL *egl_display = cogl_display->winsys;
  CoglOnscreenEGL *egl_onscreen;
  MetaMonitorTransform transform;
  CoglOnscreen *onscreen = NULL;
  CoglOffscreen *offscreen = NULL;
  MetaRendererView *view;
  GError *error = NULL;

  transform = meta_renderer_native_get_monitor_info_transform (renderer,
                                                               monitor_info);

  onscreen = meta_renderer_native_create_onscreen (META_RENDERER_NATIVE (renderer),
                                                   cogl_context,
                                                   transform,
                                                   monitor_info->rect.width,
                                                   monitor_info->rect.height);
  if (!onscreen)
    meta_fatal ("Failed to allocate onscreen framebuffer\n");

  if (transform != META_MONITOR_TRANSFORM_NORMAL)
    {
      offscreen = meta_renderer_native_create_offscreen (META_RENDERER_NATIVE (renderer),
                                                         cogl_context,
                                                         transform,
                                                         monitor_info->rect.width,
                                                         monitor_info->rect.height);
      if (!offscreen)
        meta_fatal ("Failed to allocate back buffer texture\n");
    }

  view = g_object_new (META_TYPE_RENDERER_VIEW,
                       "layout", &monitor_info->rect,
                       "framebuffer", onscreen,
                       "offscreen", offscreen,
                       "monitor-info", monitor_info,
                       "transform", transform,
                       NULL);
  g_clear_pointer (&offscreen, cogl_object_unref);

  meta_onscreen_native_set_view (onscreen, view);

  if (!meta_onscreen_native_allocate (onscreen, &error))
    {
      g_warning ("Could not create onscreen: %s", error->message);
      cogl_object_unref (onscreen);
      g_object_unref (view);
      g_error_free (error);
      return NULL;
    }

  cogl_object_unref (onscreen);

  /* Ensure we don't point to stale surfaces when creating the offscreen */
  egl_onscreen = onscreen->winsys;
  _cogl_winsys_egl_make_current (cogl_display,
                                 egl_onscreen->egl_surface,
                                 egl_onscreen->egl_surface,
                                 egl_display->egl_context);

  return view;
}

void
meta_renderer_native_finish_frame (MetaRendererNative *renderer_native)
{
  renderer_native->frame_counter++;
}

int64_t
meta_renderer_native_get_frame_counter (MetaRendererNative *renderer_native)
{
  return renderer_native->frame_counter;
}

static void
meta_renderer_native_get_property (GObject    *object,
                                   guint       prop_id,
                                   GValue     *value,
                                   GParamSpec *pspec)
{
  MetaRendererNative *renderer_native = META_RENDERER_NATIVE (object);

  switch (prop_id)
    {
    case PROP_KMS_FD:
      g_value_set_int (value, renderer_native->kms_fd);
      break;
    case PROP_KMS_FILE_PATH:
      g_value_set_string (value, renderer_native->kms_file_path);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
meta_renderer_native_set_property (GObject      *object,
                                   guint         prop_id,
                                   const GValue *value,
                                   GParamSpec   *pspec)
{
  MetaRendererNative *renderer_native = META_RENDERER_NATIVE (object);

  switch (prop_id)
    {
    case PROP_KMS_FD:
      renderer_native->kms_fd = g_value_get_int (value);
      break;
    case PROP_KMS_FILE_PATH:
      renderer_native->kms_file_path = g_strdup (g_value_get_string (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
meta_renderer_native_finalize (GObject *object)
{
  MetaRendererNative *renderer_native = META_RENDERER_NATIVE (object);

  g_clear_pointer (&renderer_native->gbm.device, gbm_device_destroy);

  g_free (renderer_native->kms_file_path);

  G_OBJECT_CLASS (meta_renderer_native_parent_class)->finalize (object);
}

static gboolean
init_gbm (MetaRendererNative *renderer_native,
          GError            **error)
{
  MetaBackend *backend = meta_get_backend ();
  MetaEgl *egl = meta_backend_get_egl (backend);
  struct gbm_device *gbm_device;
  EGLDisplay egl_display;

  if (!meta_egl_has_extensions (egl, EGL_NO_DISPLAY, NULL,
                                "EGL_MESA_platform_gbm",
                                NULL))
    {
      g_set_error (error, G_IO_ERROR,
                   G_IO_ERROR_FAILED,
                   "Missing extension for GBM renderer: EGL_KHR_platform_gbm");
      return FALSE;
    }

  gbm_device = gbm_create_device (renderer_native->kms_fd);
  if (!gbm_device)
    {
      g_set_error (error, G_IO_ERROR,
                   G_IO_ERROR_FAILED,
                   "Failed to create gbm device: %s", g_strerror (errno));
      return FALSE;
    }

  egl_display = meta_egl_get_platform_display (egl,
                                               EGL_PLATFORM_GBM_KHR,
                                               gbm_device, NULL, error);
  if (egl_display == EGL_NO_DISPLAY)
    {
      gbm_device_destroy (gbm_device);
      return FALSE;
    }

  renderer_native->egl_display = egl_display;
  renderer_native->gbm.device = gbm_device;
  renderer_native->mode = META_RENDERER_NATIVE_MODE_GBM;

  return TRUE;
}

#ifdef HAVE_EGL_DEVICE
static const char *
get_drm_device_file (MetaEgl     *egl,
                     EGLDeviceEXT device,
                     GError     **error)
{
  if (!meta_egl_egl_device_has_extensions (egl, device,
                                           NULL,
                                           "EGL_EXT_device_drm",
                                           NULL))
    {
      g_set_error (error, G_IO_ERROR,
                   G_IO_ERROR_FAILED,
                   "Missing required EGLDevice extension EGL_EXT_device_drm");
      return NULL;
    }

  return meta_egl_query_device_string (egl, device,
                                       EGL_DRM_DEVICE_FILE_EXT,
                                       error);
}

static EGLDeviceEXT
find_egl_device (MetaRendererNative *renderer_native,
                 GError            **error)
{
  MetaBackend *backend = meta_get_backend ();
  MetaEgl *egl = meta_backend_get_egl (backend);
  char **missing_extensions;
  EGLint num_devices;
  EGLDeviceEXT *devices;
  EGLDeviceEXT device;
  EGLint i;

  if (!meta_egl_has_extensions (egl,
                                EGL_NO_DISPLAY,
                                &missing_extensions,
                                "EGL_EXT_device_base",
                                NULL))
    {
      char *missing_extensions_str;

      missing_extensions_str = g_strjoinv (", ", missing_extensions);
      g_set_error (error, G_IO_ERROR,
                   G_IO_ERROR_FAILED,
                   "Missing EGL extensions required for EGLDevice renderer: %s",
                   missing_extensions_str);
      g_free (missing_extensions_str);
      g_free (missing_extensions);
      return EGL_NO_DEVICE_EXT;
    }

  if (!meta_egl_query_devices (egl, 0, NULL, &num_devices, error))
    return EGL_NO_DEVICE_EXT;

  devices = g_new0 (EGLDeviceEXT, num_devices);
  if (!meta_egl_query_devices (egl, num_devices, devices, &num_devices,
                               error))
    {
      g_free (devices);
      return EGL_NO_DEVICE_EXT;
    }

  device = EGL_NO_DEVICE_EXT;
  for (i = 0; i < num_devices; i++)
    {
      const char *egl_device_drm_path;

      g_clear_error (error);

      egl_device_drm_path = get_drm_device_file (egl, devices[i], error);
      if (!egl_device_drm_path)
        continue;

      if (g_str_equal (egl_device_drm_path, renderer_native->kms_file_path))
        {
          device = devices[i];
          break;
        }
    }
  g_free (devices);

  if (device == EGL_NO_DEVICE_EXT)
    {
      if (!*error)
        g_set_error (error, G_IO_ERROR,
                     G_IO_ERROR_FAILED,
                     "Failed to find matching EGLDeviceEXT");
      return EGL_NO_DEVICE_EXT;
    }

  return device;
}

static EGLDisplay
get_egl_device_display (MetaRendererNative *renderer_native,
                        EGLDeviceEXT        egl_device,
                        GError            **error)
{
  MetaBackend *backend = meta_get_backend ();
  MetaEgl *egl = meta_backend_get_egl (backend);
  EGLint platform_attribs[] = {
    EGL_DRM_MASTER_FD_EXT, renderer_native->kms_fd,
    EGL_NONE
  };

  return meta_egl_get_platform_display (egl, EGL_PLATFORM_DEVICE_EXT,
                                        (void *) egl_device,
                                        platform_attribs,
                                        error);
}

static gboolean
init_egl_device (MetaRendererNative *renderer_native,
                 GError            **error)
{
  MetaBackend *backend = meta_get_backend ();
  MetaEgl *egl = meta_backend_get_egl (backend);
  char **missing_extensions;
  EGLDeviceEXT egl_device;
  EGLDisplay egl_display;

  if (!meta_is_stage_views_enabled())
    {
      g_set_error (error, G_IO_ERROR,
                   G_IO_ERROR_FAILED,
                   "EGLDevice requires stage views enabled");
      return FALSE;
    }

  egl_device = find_egl_device (renderer_native, error);
  if (egl_device == EGL_NO_DEVICE_EXT)
    return FALSE;

  egl_display = get_egl_device_display (renderer_native, egl_device, error);
  if (egl_display == EGL_NO_DISPLAY)
    return FALSE;

  if (!meta_egl_initialize (egl, egl_display, error))
    return FALSE;

  if (!meta_egl_has_extensions (egl,
                                egl_display,
                                &missing_extensions,
                                "EGL_NV_output_drm_flip_event",
                                "EGL_EXT_output_base",
                                "EGL_EXT_output_drm",
                                "EGL_KHR_stream",
                                "EGL_KHR_stream_producer_eglsurface",
                                "EGL_EXT_stream_consumer_egloutput",
                                "EGL_EXT_stream_acquire_mode",
                                NULL))
    {
      char *missing_extensions_str;

      missing_extensions_str = g_strjoinv (", ", missing_extensions);
      g_set_error (error, G_IO_ERROR,
                   G_IO_ERROR_FAILED,
                   "Missing EGL extensions required for EGLDevice renderer: %s",
                   missing_extensions_str);
      g_free (missing_extensions_str);
      g_free (missing_extensions);
      return FALSE;
    }

  renderer_native->egl_display = egl_display;
  renderer_native->egl.device = egl_device;
  renderer_native->mode = META_RENDERER_NATIVE_MODE_EGL_DEVICE;

  return TRUE;
}
#endif /* HAVE_EGL_DEVICE */

static gboolean
meta_renderer_native_initable_init (GInitable     *initable,
                                    GCancellable  *cancellable,
                                    GError       **error)
{
  MetaRendererNative *renderer_native = META_RENDERER_NATIVE (initable);
  GError *gbm_error = NULL;
#ifdef HAVE_EGL_DEVICE
  GError *egl_device_error = NULL;
#endif

  if (init_gbm (renderer_native, &gbm_error))
    return TRUE;

#ifdef HAVE_EGL_DEVICE
  if (init_egl_device (renderer_native, &egl_device_error))
    {
      g_error_free (gbm_error);
      return TRUE;
    }
#endif

  g_set_error (error, G_IO_ERROR,
               G_IO_ERROR_FAILED,
               "Failed to initialize renderer: "
               "%s"
#ifdef HAVE_EGL_DEVICE
               ", %s"
#endif
               , gbm_error->message
#ifdef HAVE_EGL_DEVICE
               , egl_device_error->message
#endif
  );

  g_error_free (gbm_error);
#ifdef HAVE_EGL_DEVICE
  g_error_free (egl_device_error);
#endif

  return FALSE;
}

static void
initable_iface_init (GInitableIface *initable_iface)
{
  initable_iface->init = meta_renderer_native_initable_init;
}

static void
meta_renderer_native_init (MetaRendererNative *renderer_native)
{
}

static void
meta_renderer_native_class_init (MetaRendererNativeClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  MetaRendererClass *renderer_class = META_RENDERER_CLASS (klass);

  object_class->get_property = meta_renderer_native_get_property;
  object_class->set_property = meta_renderer_native_set_property;
  object_class->finalize = meta_renderer_native_finalize;

  renderer_class->create_cogl_renderer = meta_renderer_native_create_cogl_renderer;
  renderer_class->create_view = meta_renderer_native_create_view;

  g_object_class_install_property (object_class,
                                   PROP_KMS_FD,
                                   g_param_spec_int ("kms-fd",
                                                     "KMS fd",
                                                     "The KMS file descriptor",
                                                     0, G_MAXINT, 0,
                                                     G_PARAM_READWRITE |
                                                     G_PARAM_CONSTRUCT_ONLY));
  g_object_class_install_property (object_class,
                                   PROP_KMS_FILE_PATH,
                                   g_param_spec_string ("kms-file-path",
                                                        "KMS file path",
                                                        "The KMS file path",
                                                        NULL,
                                                        G_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));
}

MetaRendererNative *
meta_renderer_native_new (int         kms_fd,
                          const char *kms_file_path,
                          GError    **error)
{
  MetaRendererNative *renderer_native;

  renderer_native = g_object_new (META_TYPE_RENDERER_NATIVE,
                                  "kms-fd", kms_fd,
                                  "kms-file-path", kms_file_path,
                                  NULL);
  if (!g_initable_init (G_INITABLE (renderer_native), NULL, error))
    {
      g_object_unref (renderer_native);
      return NULL;
    }

  return renderer_native;
}
