/*
 * Copyright (C) 2007,2008,2009,2010,2011,2013 Intel Corporation.
 * Copyright (C) 2020 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.
 */

#include "cogl-config.h"

#include "winsys/cogl-onscreen-glx.h"

#include <GL/glx.h>
#include <sys/time.h>

#include "cogl-context-private.h"
#include "cogl-frame-info-private.h"
#include "cogl-renderer-private.h"
#include "cogl-x11-onscreen.h"
#include "cogl-xlib-renderer-private.h"
#include "winsys/cogl-glx-display-private.h"
#include "winsys/cogl-glx-renderer-private.h"
#include "winsys/cogl-winsys-glx-private.h"

struct _CoglOnscreenGlx
{
  CoglOnscreen parent;

  Window xwin;
  int x, y;
  CoglOutput *output;

  GLXDrawable glxwin;
  uint32_t last_swap_vsync_counter;
  uint32_t pending_sync_notify;
  uint32_t pending_complete_notify;
};

static void
x11_onscreen_init_iface (CoglX11OnscreenInterface *iface);

G_DEFINE_TYPE_WITH_CODE (CoglOnscreenGlx, cogl_onscreen_glx,
                         COGL_TYPE_ONSCREEN,
                         G_IMPLEMENT_INTERFACE (COGL_TYPE_X11_ONSCREEN,
                                                x11_onscreen_init_iface))

#define COGL_ONSCREEN_X11_EVENT_MASK (StructureNotifyMask | ExposureMask)

static gboolean
cogl_onscreen_glx_allocate (CoglFramebuffer  *framebuffer,
                            GError          **error)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (framebuffer);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  CoglDisplay *display = context->display;
  CoglGLXDisplay *glx_display = display->winsys;
  CoglXlibRenderer *xlib_renderer =
    _cogl_xlib_renderer_get_data (display->renderer);
  CoglGLXRenderer *glx_renderer = display->renderer->winsys;
  Window xwin;
  const CoglFramebufferConfig *config;
  GLXFBConfig fbconfig;
  GError *fbconfig_error = NULL;

  g_return_val_if_fail (glx_display->glx_context, FALSE);

  config = cogl_framebuffer_get_config (framebuffer);
  if (!cogl_display_glx_find_fbconfig (display, config,
                                       &fbconfig,
                                       &fbconfig_error))
    {
      g_set_error (error, COGL_WINSYS_ERROR,
                   COGL_WINSYS_ERROR_CREATE_CONTEXT,
                   "Unable to find suitable fbconfig for the GLX context: %s",
                   fbconfig_error->message);
      g_error_free (fbconfig_error);
      return FALSE;
    }

  /* Update the real number of samples_per_pixel now that we have
   * found an fbconfig... */
  if (config->samples_per_pixel)
    {
      int samples;
      int status = glx_renderer->glXGetFBConfigAttrib (xlib_renderer->xdpy,
                                                       fbconfig,
                                                       GLX_SAMPLES,
                                                       &samples);
      g_return_val_if_fail (status == Success, TRUE);
      cogl_framebuffer_update_samples_per_pixel (framebuffer, samples);
    }

  /* FIXME: We need to explicitly Select for ConfigureNotify events.
   * We need to document that for windows we create then toolkits
   * must be careful not to clear event mask bits that we select.
   */
    {
      int width;
      int height;
      CoglXlibTrapState state;
      XVisualInfo *xvisinfo;
      XSetWindowAttributes xattr;
      unsigned long mask;
      int xerror;

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

      _cogl_xlib_renderer_trap_errors (display->renderer, &state);

      xvisinfo = glx_renderer->glXGetVisualFromFBConfig (xlib_renderer->xdpy,
                                                         fbconfig);
      if (xvisinfo == NULL)
        {
          g_set_error_literal (error, COGL_WINSYS_ERROR,
                               COGL_WINSYS_ERROR_CREATE_ONSCREEN,
                               "Unable to retrieve the X11 visual of context's "
                               "fbconfig");
          return FALSE;
        }

      /* window attributes */
      xattr.background_pixel = WhitePixel (xlib_renderer->xdpy,
                                           DefaultScreen (xlib_renderer->xdpy));
      xattr.border_pixel = 0;
      /* XXX: is this an X resource that we are leaking‽... */
      xattr.colormap = XCreateColormap (xlib_renderer->xdpy,
                                        DefaultRootWindow (xlib_renderer->xdpy),
                                        xvisinfo->visual,
                                        AllocNone);
      xattr.event_mask = COGL_ONSCREEN_X11_EVENT_MASK;

      mask = CWBorderPixel | CWColormap | CWEventMask;

      xwin = XCreateWindow (xlib_renderer->xdpy,
                            DefaultRootWindow (xlib_renderer->xdpy),
                            0, 0,
                            width, height,
                            0,
                            xvisinfo->depth,
                            InputOutput,
                            xvisinfo->visual,
                            mask, &xattr);

      XFree (xvisinfo);

      XSync (xlib_renderer->xdpy, False);
      xerror = _cogl_xlib_renderer_untrap_errors (display->renderer, &state);
      if (xerror)
        {
          char message[1000];
          XGetErrorText (xlib_renderer->xdpy, xerror,
                         message, sizeof (message));
          g_set_error (error, COGL_WINSYS_ERROR,
                       COGL_WINSYS_ERROR_CREATE_ONSCREEN,
                       "X error while creating Window for CoglOnscreen: %s",
                       message);
          return FALSE;
        }
    }

  onscreen_glx->xwin = xwin;

  /* Try and create a GLXWindow to use with extensions dependent on
   * GLX versions >= 1.3 that don't accept regular X Windows as GLX
   * drawables. */
  if (glx_renderer->glx_major == 1 && glx_renderer->glx_minor >= 3)
    {
      onscreen_glx->glxwin =
        glx_renderer->glXCreateWindow (xlib_renderer->xdpy,
                                       fbconfig,
                                       onscreen_glx->xwin,
                                       NULL);
    }

#ifdef GLX_INTEL_swap_event
  if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT))
    {
      GLXDrawable drawable =
        onscreen_glx->glxwin ? onscreen_glx->glxwin : onscreen_glx->xwin;

      /* similarly to above, we unconditionally select this event
       * because we rely on it to advance the master clock, and
       * drive redraw/relayout, animations and event handling.
       */
      glx_renderer->glXSelectEvent (xlib_renderer->xdpy,
                                    drawable,
                                    GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK);
    }
#endif /* GLX_INTEL_swap_event */

  return TRUE;
}

static void
cogl_onscreen_glx_dispose (GObject *object)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (object);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (object);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  CoglGLXDisplay *glx_display = context->display->winsys;
  CoglXlibRenderer *xlib_renderer =
    _cogl_xlib_renderer_get_data (context->display->renderer);
  CoglGLXRenderer *glx_renderer = context->display->renderer->winsys;
  CoglXlibTrapState old_state;
  GLXDrawable drawable;

  G_OBJECT_CLASS (cogl_onscreen_glx_parent_class)->dispose (object);

  cogl_clear_object (&onscreen_glx->output);

  if (onscreen_glx->glxwin != None ||
      onscreen_glx->xwin != None)
    {
      _cogl_xlib_renderer_trap_errors (context->display->renderer, &old_state);

      drawable =
        onscreen_glx->glxwin == None ? onscreen_glx->xwin : onscreen_glx->glxwin;

      /* Cogl always needs a valid context bound to something so if we are
       * destroying the onscreen that is currently bound we'll switch back
       * to the dummy drawable. Although the documentation for
       * glXDestroyWindow states that a currently bound window won't
       * actually be destroyed until it is unbound, it looks like this
       * doesn't work if the X window itself is destroyed */
      if (drawable == cogl_context_glx_get_current_drawable (context))
        {
          GLXDrawable dummy_drawable = (glx_display->dummy_glxwin == None ?
                                        glx_display->dummy_xwin :
                                        glx_display->dummy_glxwin);

          glx_renderer->glXMakeContextCurrent (xlib_renderer->xdpy,
                                               dummy_drawable,
                                               dummy_drawable,
                                               glx_display->glx_context);
          cogl_context_glx_set_current_drawable (context, dummy_drawable);
        }

      if (onscreen_glx->glxwin != None)
        {
          glx_renderer->glXDestroyWindow (xlib_renderer->xdpy,
                                          onscreen_glx->glxwin);
          onscreen_glx->glxwin = None;
        }

      if (onscreen_glx->xwin != None)
        {
          XDestroyWindow (xlib_renderer->xdpy, onscreen_glx->xwin);
          onscreen_glx->xwin = None;
        }
      else
        {
          onscreen_glx->xwin = None;
        }

      XSync (xlib_renderer->xdpy, False);

      _cogl_xlib_renderer_untrap_errors (context->display->renderer,
                                         &old_state);
    }
}

static void
cogl_onscreen_glx_bind (CoglOnscreen *onscreen)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  CoglGLXDisplay *glx_display = context->display->winsys;
  CoglXlibRenderer *xlib_renderer =
    _cogl_xlib_renderer_get_data (context->display->renderer);
  CoglGLXRenderer *glx_renderer = context->display->renderer->winsys;
  CoglXlibTrapState old_state;
  GLXDrawable drawable;

  drawable =
    onscreen_glx->glxwin ? onscreen_glx->glxwin : onscreen_glx->xwin;

  if (cogl_context_glx_get_current_drawable (context) == drawable)
    return;

  _cogl_xlib_renderer_trap_errors (context->display->renderer, &old_state);

  COGL_NOTE (WINSYS,
             "MakeContextCurrent dpy: %p, window: 0x%x, context: %p",
             xlib_renderer->xdpy,
             (unsigned int) drawable,
             glx_display->glx_context);

  glx_renderer->glXMakeContextCurrent (xlib_renderer->xdpy,
                                       drawable,
                                       drawable,
                                       glx_display->glx_context);

  /* In case we are using GLX_SGI_swap_control for vblank syncing
   * we need call glXSwapIntervalSGI here to make sure that it
   * affects the current drawable.
   *
   * Note: we explicitly set to 0 when we aren't using the swap
   * interval to synchronize since some drivers have a default
   * swap interval of 1. Sadly some drivers even ignore requests
   * to disable the swap interval.
   *
   * NB: glXSwapIntervalSGI applies to the context not the
   * drawable which is why we can't just do this once when the
   * framebuffer is allocated.
   *
   * FIXME: We should check for GLX_EXT_swap_control which allows
   * per framebuffer swap intervals. GLX_MESA_swap_control also
   * allows per-framebuffer swap intervals but the semantics tend
   * to be more muddled since Mesa drivers tend to expose both the
   * MESA and SGI extensions which should technically be mutually
   * exclusive.
   */
  if (glx_renderer->glXSwapInterval)
    glx_renderer->glXSwapInterval (1);

  XSync (xlib_renderer->xdpy, False);

  /* FIXME: We should be reporting a GError here */
  if (_cogl_xlib_renderer_untrap_errors (context->display->renderer,
                                         &old_state))
    {
      g_warning ("X Error received while making drawable 0x%08lX current",
                 drawable);
      return;
    }

  cogl_context_glx_set_current_drawable (context, drawable);
}

static void
_cogl_winsys_wait_for_gpu (CoglOnscreen *onscreen)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *ctx = cogl_framebuffer_get_context (framebuffer);

  ctx->glFinish ();
}

static void
ensure_ust_type (CoglRenderer *renderer,
                 GLXDrawable   drawable)
{
  CoglGLXRenderer *glx_renderer =  renderer->winsys;
  CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (renderer);
  int64_t ust;
  int64_t msc;
  int64_t sbc;
  struct timeval tv;
  int64_t current_system_time;
  int64_t current_monotonic_time;

  if (glx_renderer->ust_type != COGL_GLX_UST_IS_UNKNOWN)
    return;

  glx_renderer->ust_type = COGL_GLX_UST_IS_OTHER;

  if (glx_renderer->glXGetSyncValues == NULL)
    goto out;

  if (!glx_renderer->glXGetSyncValues (xlib_renderer->xdpy, drawable,
                                       &ust, &msc, &sbc))
    goto out;

  /* This is the time source that existing (buggy) linux drm drivers
   * use */
  gettimeofday (&tv, NULL);
  current_system_time = (tv.tv_sec * G_GINT64_CONSTANT (1000000)) + tv.tv_usec;

  if (current_system_time > ust - 1000000 &&
      current_system_time < ust + 1000000)
    {
      glx_renderer->ust_type = COGL_GLX_UST_IS_GETTIMEOFDAY;
      goto out;
    }

  /* This is the time source that the newer (fixed) linux drm
   * drivers use (Linux >= 3.8) */
  current_monotonic_time = g_get_monotonic_time ();

  if (current_monotonic_time > ust - 1000000 &&
      current_monotonic_time < ust + 1000000)
    {
      glx_renderer->ust_type = COGL_GLX_UST_IS_MONOTONIC_TIME;
      goto out;
    }

 out:
  COGL_NOTE (WINSYS, "Classified OML system time as: %s",
             glx_renderer->ust_type == COGL_GLX_UST_IS_GETTIMEOFDAY ? "gettimeofday" :
             (glx_renderer->ust_type == COGL_GLX_UST_IS_MONOTONIC_TIME ? "monotonic" :
              "other"));
  return;
}

static int64_t
ust_to_microseconds (CoglRenderer *renderer,
                     GLXDrawable   drawable,
                     int64_t       ust)
{
  CoglGLXRenderer *glx_renderer = renderer->winsys;

  ensure_ust_type (renderer, drawable);

  switch (glx_renderer->ust_type)
    {
    case COGL_GLX_UST_IS_UNKNOWN:
      g_assert_not_reached ();
      break;
    case COGL_GLX_UST_IS_GETTIMEOFDAY:
    case COGL_GLX_UST_IS_MONOTONIC_TIME:
      return ust;
    case COGL_GLX_UST_IS_OTHER:
      /* In this case the scale of UST is undefined so we can't easily
       * scale to microseconds.
       *
       * For example the driver may be reporting the rdtsc CPU counter
       * as UST values and so the scale would need to be determined
       * empirically.
       *
       * Potentially we could block for a known duration within
       * ensure_ust_type() to measure the timescale of UST but for now
       * we just ignore unknown time sources */
      return 0;
    }

  return 0;
}

static gboolean
is_ust_monotonic (CoglRenderer *renderer,
                  GLXDrawable   drawable)
{
  CoglGLXRenderer *glx_renderer = renderer->winsys;

  ensure_ust_type (renderer, drawable);

  return (glx_renderer->ust_type == COGL_GLX_UST_IS_MONOTONIC_TIME);
}

static void
_cogl_winsys_wait_for_vblank (CoglOnscreen *onscreen)
{
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *ctx = cogl_framebuffer_get_context (framebuffer);
  CoglGLXRenderer *glx_renderer;
  CoglXlibRenderer *xlib_renderer;
  CoglGLXDisplay *glx_display;

  glx_renderer = ctx->display->renderer->winsys;
  xlib_renderer = _cogl_xlib_renderer_get_data (ctx->display->renderer);
  glx_display = ctx->display->winsys;

  if (glx_display->can_vblank_wait)
    {
      CoglFrameInfo *info = cogl_onscreen_peek_tail_frame_info (onscreen);
      info->flags |= COGL_FRAME_INFO_FLAG_VSYNC;

      if (glx_renderer->glXWaitForMsc)
        {
          CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
          Drawable drawable = onscreen_glx->glxwin;
          int64_t ust;
          int64_t msc;
          int64_t sbc;

          glx_renderer->glXWaitForMsc (xlib_renderer->xdpy, drawable,
                                       0, 1, 0,
                                       &ust, &msc, &sbc);

          if (is_ust_monotonic (ctx->display->renderer, drawable))
            {
              info->presentation_time_us =
                ust_to_microseconds (ctx->display->renderer,
                                     drawable,
                                     ust);
              info->flags |= COGL_FRAME_INFO_FLAG_HW_CLOCK;
            }
          else
            {
              info->presentation_time_us = g_get_monotonic_time ();
            }

          /* Intentionally truncating to lower 32 bits, same as DRM. */
          info->sequence = msc;
        }
      else
        {
          uint32_t current_count;

          glx_renderer->glXGetVideoSync (&current_count);
          glx_renderer->glXWaitVideoSync (2,
                                          (current_count + 1) % 2,
                                          &current_count);

          info->presentation_time_us = g_get_monotonic_time ();
        }
    }
}

static uint32_t
_cogl_winsys_get_vsync_counter (CoglContext *ctx)
{
  uint32_t video_sync_count;
  CoglGLXRenderer *glx_renderer;

  glx_renderer = ctx->display->renderer->winsys;

  glx_renderer->glXGetVideoSync (&video_sync_count);

  return video_sync_count;
}

#ifndef GLX_BACK_BUFFER_AGE_EXT
#define GLX_BACK_BUFFER_AGE_EXT 0x20F4
#endif

static int
cogl_onscreen_glx_get_buffer_age (CoglOnscreen *onscreen)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  CoglDisplay *display = context->display;
  CoglXlibRenderer *xlib_renderer = _cogl_xlib_renderer_get_data (context->display->renderer);
  CoglGLXRenderer *glx_renderer = context->display->renderer->winsys;
  CoglXlibTrapState old_state;
  GLXDrawable drawable;
  unsigned int age = 0;

  if (!_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE))
    return 0;

  drawable = onscreen_glx->glxwin ? onscreen_glx->glxwin : onscreen_glx->xwin;
  _cogl_xlib_renderer_trap_errors (display->renderer, &old_state);
  glx_renderer->glXQueryDrawable (xlib_renderer->xdpy, drawable, GLX_BACK_BUFFER_AGE_EXT, &age);
  _cogl_xlib_renderer_untrap_errors (display->renderer, &old_state);

  return age;
}

static void
set_frame_info_output (CoglOnscreen *onscreen,
                       CoglOutput   *output)
{
  CoglFrameInfo *info = cogl_onscreen_peek_tail_frame_info (onscreen);

  if (output)
    {
      float refresh_rate = cogl_output_get_refresh_rate (output);
      if (refresh_rate != 0.0)
        info->refresh_rate = refresh_rate;
    }
}

static void
cogl_onscreen_glx_flush_notification (CoglOnscreen *onscreen)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);

  while (onscreen_glx->pending_sync_notify > 0 ||
         onscreen_glx->pending_complete_notify > 0)
    {
      if (onscreen_glx->pending_sync_notify > 0)
        {
          CoglFrameInfo *info;

          info = cogl_onscreen_peek_head_frame_info (onscreen);
          _cogl_onscreen_notify_frame_sync (onscreen, info);
          onscreen_glx->pending_sync_notify--;
        }

      if (onscreen_glx->pending_complete_notify > 0)
        {
          CoglFrameInfo *info;

          info = cogl_onscreen_pop_head_frame_info (onscreen);
          _cogl_onscreen_notify_complete (onscreen, info);
          cogl_object_unref (info);
          onscreen_glx->pending_complete_notify--;
        }
    }
}

static void
flush_pending_notifications_cb (void *data,
                                void *user_data)
{
  CoglFramebuffer *framebuffer = data;

  if (COGL_IS_ONSCREEN (framebuffer))
    {
      CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);

      cogl_onscreen_glx_flush_notification (onscreen);
    }
}

static void
flush_pending_notifications_idle (void *user_data)
{
  CoglContext *context = user_data;
  CoglRenderer *renderer = context->display->renderer;
  CoglGLXRenderer *glx_renderer = renderer->winsys;

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

  g_list_foreach (context->framebuffers,
                  flush_pending_notifications_cb,
                  NULL);
}

static void
set_sync_pending (CoglOnscreen *onscreen)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  CoglRenderer *renderer = context->display->renderer;
  CoglGLXRenderer *glx_renderer = renderer->winsys;

  /* We only want to dispatch sync events when the application calls
   * cogl_context_dispatch so instead of immediately notifying we
   * queue an idle callback */
  if (!glx_renderer->flush_notifications_idle)
    {
      glx_renderer->flush_notifications_idle =
        _cogl_poll_renderer_add_idle (renderer,
                                      flush_pending_notifications_idle,
                                      context,
                                      NULL);
    }

  onscreen_glx->pending_sync_notify++;
}

static void
set_complete_pending (CoglOnscreen *onscreen)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  CoglRenderer *renderer = context->display->renderer;
  CoglGLXRenderer *glx_renderer = renderer->winsys;

  /* We only want to notify swap completion when the application calls
   * cogl_context_dispatch so instead of immediately notifying we
   * queue an idle callback */
  if (!glx_renderer->flush_notifications_idle)
    {
      glx_renderer->flush_notifications_idle =
        _cogl_poll_renderer_add_idle (renderer,
                                      flush_pending_notifications_idle,
                                      context,
                                      NULL);
    }

  onscreen_glx->pending_complete_notify++;
}

static void
cogl_onscreen_glx_swap_region (CoglOnscreen  *onscreen,
                               const int     *user_rectangles,
                               int            n_rectangles,
                               CoglFrameInfo *info,
                               gpointer       user_data)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  CoglXlibRenderer *xlib_renderer =
    _cogl_xlib_renderer_get_data (context->display->renderer);
  CoglGLXRenderer *glx_renderer = context->display->renderer->winsys;
  CoglGLXDisplay *glx_display = context->display->winsys;
  uint32_t end_frame_vsync_counter = 0;
  gboolean have_counter;
  gboolean can_wait;
  int x_min = 0, x_max = 0, y_min = 0, y_max = 0;

  /*
   * We assume that glXCopySubBuffer is synchronized which means it won't prevent multiple
   * blits per retrace if they can all be performed in the blanking period. If that's the
   * case then we still want to use the vblank sync menchanism but
   * we only need it to throttle redraws.
   */
  gboolean blit_sub_buffer_is_synchronized =
     _cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION_SYNCHRONIZED);

  int framebuffer_width =  cogl_framebuffer_get_width (framebuffer);
  int framebuffer_height =  cogl_framebuffer_get_height (framebuffer);
  int *rectangles = g_alloca (sizeof (int) * n_rectangles * 4);
  int i;

  /* glXCopySubBuffer expects rectangles relative to the bottom left corner but
   * we are given rectangles relative to the top left so we need to flip
   * them... */
  memcpy (rectangles, user_rectangles, sizeof (int) * n_rectangles * 4);
  for (i = 0; i < n_rectangles; i++)
    {
      int *rect = &rectangles[4 * i];

      if (i == 0)
        {
          x_min = rect[0];
          x_max = rect[0] + rect[2];
          y_min = rect[1];
          y_max = rect[1] + rect[3];
        }
      else
        {
          x_min = MIN (x_min, rect[0]);
          x_max = MAX (x_max, rect[0] + rect[2]);
          y_min = MIN (y_min, rect[1]);
          y_max = MAX (y_max, rect[1] + rect[3]);
        }

      rect[1] = framebuffer_height - rect[1] - rect[3];

    }

  cogl_context_flush_framebuffer_state (context,
                                        framebuffer,
                                        framebuffer,
                                        COGL_FRAMEBUFFER_STATE_BIND);

  have_counter = glx_display->have_vblank_counter;
  can_wait = glx_display->can_vblank_wait;

  /* We need to ensure that all the rendering is done, otherwise
   * redraw operations that are slower than the framerate can
   * queue up in the pipeline during a heavy animation, causing a
   * larger and larger backlog of rendering visible as lag to the
   * user.
   *
   * For an exaggerated example consider rendering at 60fps (so 16ms
   * per frame) and you have a really slow frame that takes 160ms to
   * render, even though painting the scene and issuing the commands
   * to the GPU takes no time at all. If all we did was use the
   * video_sync extension to throttle the painting done by the CPU
   * then every 16ms we would have another frame queued up even though
   * the GPU has only rendered one tenth of the current frame. By the
   * time the GPU would get to the 2nd frame there would be 9 frames
   * waiting to be rendered.
   *
   * The problem is that we don't currently have a good way to throttle
   * the GPU, only the CPU so we have to resort to synchronizing the
   * GPU with the CPU to throttle it.
   *
   * Note: since calling glFinish() and synchronizing the CPU with
   * the GPU is far from ideal, we hope that this is only a short
   * term solution.
   * - One idea is to using sync objects to track render
   *   completion so we can throttle the backlog (ideally with an
   *   additional extension that lets us get notifications in our
   *   mainloop instead of having to busy wait for the
   *   completion.)
   * - Another option is to support clipped redraws by reusing the
   *   contents of old back buffers such that we can flip instead
   *   of using a blit and then we can use GLX_INTEL_swap_events
   *   to throttle. For this though we would still probably want an
   *   additional extension so we can report the limited region of
   *   the window damage to X/compositors.
   */
  _cogl_winsys_wait_for_gpu (onscreen);

  if (blit_sub_buffer_is_synchronized && have_counter && can_wait)
    {
      end_frame_vsync_counter = _cogl_winsys_get_vsync_counter (context);

      /* If we have the GLX_SGI_video_sync extension then we can
       * be a bit smarter about how we throttle blits by avoiding
       * any waits if we can see that the video sync count has
       * already progressed. */
      if (onscreen_glx->last_swap_vsync_counter == end_frame_vsync_counter)
        _cogl_winsys_wait_for_vblank (onscreen);
    }
  else if (can_wait)
    _cogl_winsys_wait_for_vblank (onscreen);

  if (glx_renderer->glXCopySubBuffer)
    {
      Display *xdpy = xlib_renderer->xdpy;
      GLXDrawable drawable;

      drawable =
        onscreen_glx->glxwin ? onscreen_glx->glxwin : onscreen_glx->xwin;
      int i;
      for (i = 0; i < n_rectangles; i++)
        {
          int *rect = &rectangles[4 * i];
          glx_renderer->glXCopySubBuffer (xdpy, drawable,
                                          rect[0], rect[1], rect[2], rect[3]);
        }
    }
  else if (context->glBlitFramebuffer)
    {
      int i;
      /* XXX: checkout how this state interacts with the code to use
       * glBlitFramebuffer in Neil's texture atlasing branch */

      /* glBlitFramebuffer is affected by the scissor so we need to
       * ensure we have flushed an empty clip stack to get rid of it.
       * We also mark that the clip state is dirty so that it will be
       * flushed to the correct state the next time something is
       * drawn */
      _cogl_clip_stack_flush (NULL, framebuffer);
      context->current_draw_buffer_changes |= COGL_FRAMEBUFFER_STATE_CLIP;

      context->glDrawBuffer (GL_FRONT);
      for (i = 0; i < n_rectangles; i++)
        {
          int *rect = &rectangles[4 * i];
          int x2 = rect[0] + rect[2];
          int y2 = rect[1] + rect[3];
          context->glBlitFramebuffer (rect[0], rect[1], x2, y2,
                                      rect[0], rect[1], x2, y2,
                                      GL_COLOR_BUFFER_BIT, GL_NEAREST);
        }
      context->glDrawBuffer (context->current_gl_draw_buffer);
    }

  /* NB: unlike glXSwapBuffers, glXCopySubBuffer and
   * glBlitFramebuffer don't issue an implicit glFlush() so we
   * have to flush ourselves if we want the request to complete in
   * a finite amount of time since otherwise the driver can batch
   * the command indefinitely. */
  context->glFlush ();

  /* NB: It's important we save the counter we read before acting on
   * the swap request since if we are mixing and matching different
   * swap methods between frames we don't want to read the timer e.g.
   * after calling glFinish() some times and not for others.
   *
   * In other words; this way we consistently save the time at the end
   * of the applications frame such that the counter isn't muddled by
   * the varying costs of different swap methods.
   */
  if (have_counter)
    onscreen_glx->last_swap_vsync_counter = end_frame_vsync_counter;

  {
    CoglOutput *output;

    x_min = CLAMP (x_min, 0, framebuffer_width);
    x_max = CLAMP (x_max, 0, framebuffer_width);
    y_min = CLAMP (y_min, 0, framebuffer_width);
    y_max = CLAMP (y_max, 0, framebuffer_height);

    output =
      _cogl_xlib_renderer_output_for_rectangle (context->display->renderer,
                                                onscreen_glx->x + x_min,
                                                onscreen_glx->y + y_min,
                                                x_max - x_min,
                                                y_max - y_min);

    set_frame_info_output (onscreen, output);
  }

  /* XXX: we don't get SwapComplete events based on how we implement
   * the _swap_region() API but if cogl-onscreen.c knows we are
   * handling _SYNC and _COMPLETE events in the winsys then we need to
   * send fake events in this case.
   */
  if (_cogl_winsys_has_feature (COGL_WINSYS_FEATURE_SYNC_AND_COMPLETE_EVENT))
    {
      set_sync_pending (onscreen);
      set_complete_pending (onscreen);
    }
}

static void
cogl_onscreen_glx_swap_buffers_with_damage (CoglOnscreen  *onscreen,
                                            const int     *rectangles,
                                            int            n_rectangles,
                                            CoglFrameInfo *info,
                                            gpointer       user_data)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  CoglXlibRenderer *xlib_renderer =
    _cogl_xlib_renderer_get_data (context->display->renderer);
  CoglGLXRenderer *glx_renderer = context->display->renderer->winsys;
  CoglGLXDisplay *glx_display = context->display->winsys;
  gboolean have_counter;
  GLXDrawable drawable;

  /* XXX: theoretically this shouldn't be necessary but at least with
   * the Intel drivers we have see that if we don't call
   * glXMakeContextCurrent for the drawable we are swapping then
   * we get a BadDrawable error from the X server. */
  cogl_context_flush_framebuffer_state (context,
                                        framebuffer,
                                        framebuffer,
                                        COGL_FRAMEBUFFER_STATE_BIND);

  drawable = onscreen_glx->glxwin ? onscreen_glx->glxwin : onscreen_glx->xwin;

  have_counter = glx_display->have_vblank_counter;

  if (!glx_renderer->glXSwapInterval)
    {
      gboolean can_wait = have_counter || glx_display->can_vblank_wait;

      uint32_t end_frame_vsync_counter = 0;

      /* If the swap_region API is also being used then we need to track
       * the vsync counter for each swap request so we can manually
       * throttle swap_region requests. */
      if (have_counter)
        end_frame_vsync_counter = _cogl_winsys_get_vsync_counter (context);

      /* If we are going to wait for VBLANK manually, we not only
       * need to flush out pending drawing to the GPU before we
       * sleep, we need to wait for it to finish. Otherwise, we
       * may end up with the situation:
       *
       *        - We finish drawing      - GPU drawing continues
       *        - We go to sleep         - GPU drawing continues
       * VBLANK - We call glXSwapBuffers - GPU drawing continues
       *                                 - GPU drawing continues
       *                                 - Swap buffers happens
       *
       * Producing a tear. Calling glFinish() first will cause us
       * to properly wait for the next VBLANK before we swap. This
       * obviously does not happen when we use _GLX_SWAP and let
       * the driver do the right thing
       */
      _cogl_winsys_wait_for_gpu (onscreen);

      if (have_counter && can_wait)
        {
          if (onscreen_glx->last_swap_vsync_counter ==
              end_frame_vsync_counter)
            _cogl_winsys_wait_for_vblank (onscreen);
        }
      else if (can_wait)
        _cogl_winsys_wait_for_vblank (onscreen);
    }

  glx_renderer->glXSwapBuffers (xlib_renderer->xdpy, drawable);

  if (have_counter)
    onscreen_glx->last_swap_vsync_counter =
      _cogl_winsys_get_vsync_counter (context);

  set_frame_info_output (onscreen, onscreen_glx->output);
}

static Window
cogl_onscreen_glx_get_x11_window (CoglX11Onscreen *x11_onscreen)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (x11_onscreen);

  return onscreen_glx->xwin;
}

void
cogl_onscreen_glx_notify_swap_buffers (CoglOnscreen          *onscreen,
                                       GLXBufferSwapComplete *swap_event)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  gboolean ust_is_monotonic;
  CoglFrameInfo *info;

  /* We only want to notify that the swap is complete when the
     application calls cogl_context_dispatch so instead of immediately
     notifying we'll set a flag to remember to notify later */
  set_sync_pending (onscreen);

  info = cogl_onscreen_peek_head_frame_info (onscreen);
  info->flags |= COGL_FRAME_INFO_FLAG_VSYNC;

  ust_is_monotonic = is_ust_monotonic (context->display->renderer,
                                       onscreen_glx->glxwin);

  if (swap_event->ust != 0 && ust_is_monotonic)
    {
      info->presentation_time_us =
        ust_to_microseconds (context->display->renderer,
                             onscreen_glx->glxwin,
                             swap_event->ust);
      info->flags |= COGL_FRAME_INFO_FLAG_HW_CLOCK;
    }

  /* Intentionally truncating to lower 32 bits, same as DRM. */
  info->sequence = swap_event->msc;

  set_complete_pending (onscreen);
}

void
cogl_onscreen_glx_update_output (CoglOnscreen *onscreen)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  CoglDisplay *display = context->display;
  CoglOutput *output;
  int width, height;

  width = cogl_framebuffer_get_width (framebuffer);
  height = cogl_framebuffer_get_height (framebuffer);
  output = _cogl_xlib_renderer_output_for_rectangle (display->renderer,
                                                     onscreen_glx->x,
                                                     onscreen_glx->y,
                                                     width, height);
  if (onscreen_glx->output != output)
    {
      if (onscreen_glx->output)
        cogl_object_unref (onscreen_glx->output);

      onscreen_glx->output = output;

      if (output)
        cogl_object_ref (onscreen_glx->output);
    }
}

void
cogl_onscreen_glx_resize (CoglOnscreen    *onscreen,
                          XConfigureEvent *configure_event)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);
  CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen);
  CoglContext *context = cogl_framebuffer_get_context (framebuffer);
  CoglRenderer *renderer = context->display->renderer;
  CoglGLXRenderer *glx_renderer = renderer->winsys;
  int x, y;


  _cogl_framebuffer_winsys_update_size (framebuffer,
                                        configure_event->width,
                                        configure_event->height);

  /* We only want to notify that a resize happened when the
   * application calls cogl_context_dispatch so instead of immediately
   * notifying we queue an idle callback */
  if (!glx_renderer->flush_notifications_idle)
    {
      glx_renderer->flush_notifications_idle =
        _cogl_poll_renderer_add_idle (renderer,
                                      flush_pending_notifications_idle,
                                      context,
                                      NULL);
    }

  if (configure_event->send_event)
    {
      x = configure_event->x;
      y = configure_event->y;
    }
  else
    {
      Window child;
      XTranslateCoordinates (configure_event->display,
                             configure_event->window,
                             DefaultRootWindow (configure_event->display),
                             0, 0, &x, &y, &child);
    }

  onscreen_glx->x = x;
  onscreen_glx->y = y;

  cogl_onscreen_glx_update_output (onscreen);
}

gboolean
cogl_onscreen_glx_is_for_window (CoglOnscreen *onscreen,
                                 Window        window)
{
  CoglOnscreenGlx *onscreen_glx = COGL_ONSCREEN_GLX (onscreen);

  return onscreen_glx->xwin == window;
}

CoglOnscreenGlx *
cogl_onscreen_glx_new (CoglContext *context,
                       int          width,
                       int          height)
{
  CoglFramebufferDriverConfig driver_config;

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

static void
cogl_onscreen_glx_init (CoglOnscreenGlx *onscreen_glx)
{
}

static void
x11_onscreen_init_iface (CoglX11OnscreenInterface *iface)
{
  iface->get_x11_window = cogl_onscreen_glx_get_x11_window;
}

static void
cogl_onscreen_glx_class_init (CoglOnscreenGlxClass *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 = cogl_onscreen_glx_dispose;

  framebuffer_class->allocate = cogl_onscreen_glx_allocate;

  onscreen_class->bind = cogl_onscreen_glx_bind;
  onscreen_class->swap_buffers_with_damage =
    cogl_onscreen_glx_swap_buffers_with_damage;
  onscreen_class->swap_region = cogl_onscreen_glx_swap_region;
  onscreen_class->get_buffer_age = cogl_onscreen_glx_get_buffer_age;
}
