/*
 *  gstvaapiwindow_glx.c - VA/GLX window abstraction
 *
 *  Copyright (C) 2010-2011 Splitted-Desktop Systems
 *    Author: Gwenole Beauchesne <gwenole.beauchesne@splitted-desktop.com>
 *  Copyright (C) 2012-2014 Intel Corporation
 *    Author: Gwenole Beauchesne <gwenole.beauchesne@intel.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301 USA
 */

/**
 * SECTION:gstvaapiwindow_glx
 * @short_description: VA/GLX window abstraction
 */

#include "sysdeps.h"
#include "gstvaapiwindow_glx.h"
#include "gstvaapiwindow_x11_priv.h"
#include "gstvaapidisplay_x11.h"
#include "gstvaapidisplay_x11_priv.h"
#include "gstvaapidisplay_glx_priv.h"
#include "gstvaapiutils_x11.h"
#include "gstvaapiutils_glx.h"

#define DEBUG 1
#include "gstvaapidebug.h"

#define GST_VAAPI_WINDOW_GLX_GET_PRIVATE(window) \
    (&GST_VAAPI_WINDOW_GLX(window)->priv)

#define GST_VAAPI_WINDOW_GLX_CLASS(klass) \
    ((GstVaapiWindowGLXClass *)(klass))

#define GST_VAAPI_WINDOW_GLX_GET_CLASS(obj) \
    GST_VAAPI_WINDOW_GLX_CLASS(GST_VAAPI_OBJECT_GET_CLASS(obj))

typedef struct _GstVaapiWindowGLXPrivate GstVaapiWindowGLXPrivate;
typedef struct _GstVaapiWindowGLXClass GstVaapiWindowGLXClass;

struct _GstVaapiWindowGLXPrivate
{
  Colormap cmap;
  GLContextState *gl_context;
};

/**
 * GstVaapiWindowGLX:
 *
 * An X11 #Window suitable for GLX rendering.
 */
struct _GstVaapiWindowGLX
{
  /*< private > */
  GstVaapiWindowX11 parent_instance;

  GstVaapiWindowGLXPrivate priv;
};

/**
 * GstVaapiWindowGLXClass:
 *
 * An X11 #Window suitable for GLX rendering.
 */
struct _GstVaapiWindowGLXClass
{
  /*< private > */
  GstVaapiWindowX11Class parent_class;

  GstVaapiObjectFinalizeFunc parent_finalize;
  GstVaapiWindowResizeFunc parent_resize;
};

/* Fill rectangle coords with capped bounds */
static inline void
fill_rect (GstVaapiRectangle * dst_rect,
    const GstVaapiRectangle * src_rect, guint width, guint height)
{
  if (src_rect) {
    dst_rect->x = src_rect->x > 0 ? src_rect->x : 0;
    dst_rect->y = src_rect->y > 0 ? src_rect->y : 0;
    if (src_rect->x + src_rect->width < width)
      dst_rect->width = src_rect->width;
    else
      dst_rect->width = width - dst_rect->x;
    if (src_rect->y + src_rect->height < height)
      dst_rect->height = src_rect->height;
    else
      dst_rect->height = height - dst_rect->y;
  } else {
    dst_rect->x = 0;
    dst_rect->y = 0;
    dst_rect->width = width;
    dst_rect->height = height;
  }
}

static void
_gst_vaapi_window_glx_destroy_context (GstVaapiWindow * window)
{
  GstVaapiWindowGLXPrivate *const priv =
      GST_VAAPI_WINDOW_GLX_GET_PRIVATE (window);

  GST_VAAPI_OBJECT_LOCK_DISPLAY (window);
  if (priv->gl_context) {
    gl_destroy_context (priv->gl_context);
    priv->gl_context = NULL;
  }
  GST_VAAPI_OBJECT_UNLOCK_DISPLAY (window);
}

static gboolean
_gst_vaapi_window_glx_create_context (GstVaapiWindow * window,
    GLXContext foreign_context)
{
  GstVaapiWindowGLXPrivate *const priv =
      GST_VAAPI_WINDOW_GLX_GET_PRIVATE (window);
  Display *const dpy = GST_VAAPI_OBJECT_NATIVE_DISPLAY (window);
  GLContextState parent_cs;

  parent_cs.display = dpy;
  parent_cs.window = None;
  parent_cs.context = foreign_context;

  GST_VAAPI_OBJECT_LOCK_DISPLAY (window);
  priv->gl_context = gl_create_context (dpy, DefaultScreen (dpy), &parent_cs);
  if (!priv->gl_context) {
    GST_DEBUG ("could not create GLX context");
    goto end;
  }

  if (!glXIsDirect (dpy, priv->gl_context->context)) {
    GST_DEBUG ("could not create a direct-rendering GLX context");
    goto out_destroy_context;
  }
  goto end;

out_destroy_context:
  gl_destroy_context (priv->gl_context);
  priv->gl_context = NULL;
end:
  GST_VAAPI_OBJECT_UNLOCK_DISPLAY (window);
  return priv->gl_context != NULL;
}

static gboolean
_gst_vaapi_window_glx_ensure_context (GstVaapiWindow * window,
    GLXContext foreign_context)
{
  GstVaapiWindowGLXPrivate *const priv =
      GST_VAAPI_WINDOW_GLX_GET_PRIVATE (window);

  if (priv->gl_context) {
    if (!foreign_context || foreign_context == priv->gl_context->context)
      return TRUE;
    _gst_vaapi_window_glx_destroy_context (window);
  }
  return _gst_vaapi_window_glx_create_context (window, foreign_context);
}

static gboolean
gst_vaapi_window_glx_ensure_context (GstVaapiWindow * window,
    GLXContext foreign_context)
{
  GstVaapiWindowGLXPrivate *const priv =
      GST_VAAPI_WINDOW_GLX_GET_PRIVATE (window);
  GLContextState old_cs;
  guint width, height;

  if (!_gst_vaapi_window_glx_ensure_context (window, foreign_context))
    return FALSE;

  priv->gl_context->window = GST_VAAPI_OBJECT_ID (window);
  if (!gl_set_current_context (priv->gl_context, &old_cs)) {
    GST_DEBUG ("could not make newly created GLX context current");
    return FALSE;
  }

  glDisable (GL_DEPTH_TEST);
  glDepthMask (GL_FALSE);
  glDisable (GL_CULL_FACE);
  glDrawBuffer (GL_BACK);
  glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glEnable (GL_BLEND);
  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  gst_vaapi_window_get_size (window, &width, &height);
  gl_resize (width, height);

  gl_set_bgcolor (0);
  glClear (GL_COLOR_BUFFER_BIT);
  gl_set_current_context (&old_cs, NULL);
  return TRUE;
}

static guintptr
gst_vaapi_window_glx_get_visual_id (GstVaapiWindow * window)
{
  GstVaapiWindowGLXPrivate *const priv =
      GST_VAAPI_WINDOW_GLX_GET_PRIVATE (window);

  if (!_gst_vaapi_window_glx_ensure_context (window, NULL))
    return 0;
  return priv->gl_context->visual->visualid;
}

static void
gst_vaapi_window_glx_destroy_colormap (GstVaapiWindow * window)
{
  GstVaapiWindowGLXPrivate *const priv =
      GST_VAAPI_WINDOW_GLX_GET_PRIVATE (window);
  Display *const dpy = GST_VAAPI_OBJECT_NATIVE_DISPLAY (window);

  if (priv->cmap) {
    if (!window->use_foreign_window) {
      GST_VAAPI_OBJECT_LOCK_DISPLAY (window);
      XFreeColormap (dpy, priv->cmap);
      GST_VAAPI_OBJECT_UNLOCK_DISPLAY (window);
    }
    priv->cmap = None;
  }
}

static Colormap
gst_vaapi_window_glx_create_colormap (GstVaapiWindow * window)
{
  GstVaapiWindowGLXPrivate *const priv =
      GST_VAAPI_WINDOW_GLX_GET_PRIVATE (window);
  Display *const dpy = GST_VAAPI_OBJECT_NATIVE_DISPLAY (window);
  XWindowAttributes wattr;
  gboolean success = FALSE;

  if (!priv->cmap) {
    if (!window->use_foreign_window) {
      if (!_gst_vaapi_window_glx_ensure_context (window, NULL))
        return None;
      GST_VAAPI_OBJECT_LOCK_DISPLAY (window);
      x11_trap_errors ();
      /* XXX: add a GstVaapiDisplayX11:x11-screen property? */
      priv->cmap = XCreateColormap (dpy, RootWindow (dpy, DefaultScreen (dpy)),
          priv->gl_context->visual->visual, AllocNone);
      success = x11_untrap_errors () == 0;
      GST_VAAPI_OBJECT_UNLOCK_DISPLAY (window);
    } else {
      GST_VAAPI_OBJECT_LOCK_DISPLAY (window);
      x11_trap_errors ();
      XGetWindowAttributes (dpy, GST_VAAPI_OBJECT_ID (window), &wattr);
      priv->cmap = wattr.colormap;
      success = x11_untrap_errors () == 0;
      GST_VAAPI_OBJECT_UNLOCK_DISPLAY (window);
    }
    if (!success)
      return None;
  }
  return priv->cmap;
}

static guintptr
gst_vaapi_window_glx_get_colormap (GstVaapiWindow * window)
{
  return GPOINTER_TO_SIZE (gst_vaapi_window_glx_create_colormap (window));
}

static gboolean
gst_vaapi_window_glx_resize (GstVaapiWindow * window, guint width, guint height)
{
  GstVaapiWindowGLXPrivate *const priv =
      GST_VAAPI_WINDOW_GLX_GET_PRIVATE (window);
  const GstVaapiWindowGLXClass *const klass =
      GST_VAAPI_WINDOW_GLX_GET_CLASS (window);
  Display *const dpy = GST_VAAPI_OBJECT_NATIVE_DISPLAY (window);
  GLContextState old_cs;

  if (!klass->parent_resize (window, width, height))
    return FALSE;

  GST_VAAPI_OBJECT_LOCK_DISPLAY (window);
  XSync (dpy, False);           /* make sure resize completed */
  if (gl_set_current_context (priv->gl_context, &old_cs)) {
    gl_resize (width, height);
    gl_set_current_context (&old_cs, NULL);
  }
  GST_VAAPI_OBJECT_UNLOCK_DISPLAY (window);
  return TRUE;
}

static void
gst_vaapi_window_glx_finalize (GstVaapiWindowGLX * window)
{
  GstVaapiWindow *const base_window = GST_VAAPI_WINDOW (window);

  _gst_vaapi_window_glx_destroy_context (base_window);
  gst_vaapi_window_glx_destroy_colormap (base_window);

  GST_VAAPI_WINDOW_GLX_GET_CLASS (window)->parent_finalize (GST_VAAPI_OBJECT
      (window));
}

static void
gst_vaapi_window_glx_class_init (GstVaapiWindowGLXClass * klass)
{
  GstVaapiWindowClass *const window_class = GST_VAAPI_WINDOW_CLASS (klass);
  GstVaapiWindowX11Class *const xwindow_class =
      GST_VAAPI_WINDOW_X11_CLASS (klass);

  gst_vaapi_window_x11_class_init (xwindow_class);
  klass->parent_resize = window_class->resize;
  klass->parent_finalize = GST_VAAPI_OBJECT_CLASS (klass)->finalize;
  window_class->resize = gst_vaapi_window_glx_resize;
  window_class->get_visual_id = gst_vaapi_window_glx_get_visual_id;
  window_class->get_colormap = gst_vaapi_window_glx_get_colormap;
}

GST_VAAPI_OBJECT_DEFINE_CLASS_WITH_CODE (GstVaapiWindowGLX,
    gst_vaapi_window_glx, gst_vaapi_window_glx_class_init (&g_class));

/**
 * gst_vaapi_window_glx_new:
 * @display: a #GstVaapiDisplay
 * @width: the requested window width, in pixels
 * @height: the requested windo height, in pixels
 *
 * Creates a window with the specified @width and @height. The window
 * will be attached to the @display and remains invisible to the user
 * until gst_vaapi_window_show() is called.
 *
 * Return value: the newly allocated #GstVaapiWindow object
 */
GstVaapiWindow *
gst_vaapi_window_glx_new (GstVaapiDisplay * display, guint width, guint height)
{
  GstVaapiWindow *window;

  g_return_val_if_fail (GST_VAAPI_IS_DISPLAY_GLX (display), NULL);

  window =
      gst_vaapi_window_new_internal (GST_VAAPI_WINDOW_CLASS
      (gst_vaapi_window_glx_class ()), display, GST_VAAPI_ID_INVALID, width,
      height);
  if (!window)
    return NULL;

  if (!gst_vaapi_window_glx_ensure_context (window, NULL))
    goto error;
  return window;

error:
  gst_vaapi_window_unref (window);
  return NULL;
}

/**
 * gst_vaapi_window_glx_new_with_xid:
 * @display: a #GstVaapiDisplay
 * @xid: an X11 #Window id
 *
 * Creates a #GstVaapiWindow using the X11 #Window @xid. The caller
 * still owns the window and must call XDestroyWindow() when all
 * #GstVaapiWindow references are released. Doing so too early can
 * yield undefined behaviour.
 *
 * Return value: the newly allocated #GstVaapiWindow object
 */
GstVaapiWindow *
gst_vaapi_window_glx_new_with_xid (GstVaapiDisplay * display, Window xid)
{
  GstVaapiWindow *window;

  GST_DEBUG ("new window from xid 0x%08x", (guint) xid);

  g_return_val_if_fail (GST_VAAPI_IS_DISPLAY_GLX (display), NULL);
  g_return_val_if_fail (xid != None, NULL);

  window =
      gst_vaapi_window_new_internal (GST_VAAPI_WINDOW_CLASS
      (gst_vaapi_window_glx_class ()), display, xid, 0, 0);
  if (!window)
    return NULL;

  if (!gst_vaapi_window_glx_ensure_context (window, NULL))
    goto error;
  return window;

error:
  gst_vaapi_window_unref (window);
  return NULL;
}

/**
 * gst_vaapi_window_glx_get_context:
 * @window: a #GstVaapiWindowGLX
 *
 * Returns the #GLXContext bound to the @window.
 *
 * Return value: the #GLXContext bound to the @window
 */
GLXContext
gst_vaapi_window_glx_get_context (GstVaapiWindowGLX * window)
{
  g_return_val_if_fail (window != NULL, NULL);

  return GST_VAAPI_WINDOW_GLX_GET_PRIVATE (window)->gl_context->context;
}

/**
 * gst_vaapi_window_glx_set_context:
 * @window: a #GstVaapiWindowGLX
 * @ctx: a GLX context
 *
 * Binds GLX context @ctx to @window. If @ctx is non %NULL, the caller
 * is responsible to making sure it has compatible visual with that of
 * the underlying X window. If @ctx is %NULL, a new context is created
 * and the @window owns it.
 *
 * Return value: %TRUE on success
 */
gboolean
gst_vaapi_window_glx_set_context (GstVaapiWindowGLX * window, GLXContext ctx)
{
  g_return_val_if_fail (window != NULL, FALSE);

  return gst_vaapi_window_glx_ensure_context (GST_VAAPI_WINDOW (window), ctx);
}

/**
 * gst_vaapi_window_glx_make_current:
 * @window: a #GstVaapiWindowGLX
 *
 * Makes the @window GLX context the current GLX rendering context of
 * the calling thread, replacing the previously current context if
 * there was one.
 *
 * Return value: %TRUE on success
 */
gboolean
gst_vaapi_window_glx_make_current (GstVaapiWindowGLX * window)
{
  gboolean success;

  g_return_val_if_fail (window != NULL, FALSE);

  GST_VAAPI_OBJECT_LOCK_DISPLAY (window);
  success = gl_set_current_context (window->priv.gl_context, NULL);
  GST_VAAPI_OBJECT_UNLOCK_DISPLAY (window);
  return success;
}

/**
 * gst_vaapi_window_glx_swap_buffers:
 * @window: a #GstVaapiWindowGLX
 *
 * Promotes the contents of the back buffer of @window to become the
 * contents of the front buffer of @window. This simply is wrapper
 * around glXSwapBuffers().
 */
void
gst_vaapi_window_glx_swap_buffers (GstVaapiWindowGLX * window)
{
  g_return_if_fail (window != NULL);

  GST_VAAPI_OBJECT_LOCK_DISPLAY (window);
  gl_swap_buffers (window->priv.gl_context);
  GST_VAAPI_OBJECT_UNLOCK_DISPLAY (window);
}

/**
 * gst_vaapi_window_glx_put_texture:
 * @window: a #GstVaapiWindowGLX
 * @texture: a #GstVaapiTexture
 * @src_rect: the sub-rectangle of the source texture to
 *   extract and process. If %NULL, the entire texture will be used.
 * @dst_rect: the sub-rectangle of the destination
 *   window into which the texture is rendered. If %NULL, the entire
 *   window will be used.
 *
 * Renders the @texture region specified by @src_rect into the @window
 * region specified by @dst_rect.
 *
 * NOTE: only GL_TEXTURE_2D textures are supported at this time.
 *
 * Return value: %TRUE on success
 */
gboolean
gst_vaapi_window_glx_put_texture (GstVaapiWindowGLX * window,
    GstVaapiTexture * texture,
    const GstVaapiRectangle * src_rect, const GstVaapiRectangle * dst_rect)
{
  GstVaapiRectangle tmp_src_rect, tmp_dst_rect;
  GLTextureState ts;
  GLenum tex_target;
  GLuint tex_id;
  guint tex_width, tex_height;
  guint win_width, win_height;

  g_return_val_if_fail (window != NULL, FALSE);
  g_return_val_if_fail (texture != NULL, FALSE);

  gst_vaapi_texture_get_size (texture, &tex_width, &tex_height);
  fill_rect (&tmp_src_rect, src_rect, tex_width, tex_height);
  src_rect = &tmp_src_rect;

  gst_vaapi_window_get_size (GST_VAAPI_WINDOW (window), &win_width,
      &win_height);
  fill_rect (&tmp_dst_rect, dst_rect, win_width, win_height);
  dst_rect = &tmp_dst_rect;

  /* XXX: only GL_TEXTURE_2D textures are supported at this time */
  tex_target = gst_vaapi_texture_get_target (texture);
  if (tex_target != GL_TEXTURE_2D)
    return FALSE;

  tex_id = gst_vaapi_texture_get_id (texture);
  if (!gl_bind_texture (&ts, tex_target, tex_id))
    return FALSE;
  glColor4f (1.0f, 1.0f, 1.0f, 1.0f);
  glPushMatrix ();
  glTranslatef ((GLfloat) dst_rect->x, (GLfloat) dst_rect->y, 0.0f);
  glBegin (GL_QUADS);
  {
    const float tx1 = (float) src_rect->x / tex_width;
    const float tx2 = (float) (src_rect->x + src_rect->width) / tex_width;
    const float ty1 = (float) src_rect->y / tex_height;
    const float ty2 = (float) (src_rect->y + src_rect->height) / tex_height;
    const guint w = dst_rect->width;
    const guint h = dst_rect->height;
    glTexCoord2f (tx1, ty1);
    glVertex2i (0, 0);
    glTexCoord2f (tx1, ty2);
    glVertex2i (0, h);
    glTexCoord2f (tx2, ty2);
    glVertex2i (w, h);
    glTexCoord2f (tx2, ty1);
    glVertex2i (w, 0);
  }
  glEnd ();
  glPopMatrix ();
  gl_unbind_texture (&ts);
  return TRUE;
}
