/**************************************************************************
 *
 * Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas.
 * Copyright 2009-2010 Chia-I Wu <olvaffe@gmail.com>
 * Copyright 2010-2011 LunarG, Inc.
 * All Rights Reserved.
 *
 * 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, sub license, 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 (including the
 * next paragraph) 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.
 *
 **************************************************************************/


/**
 * This is an EGL driver that wraps GLX. This gives the benefit of being
 * completely agnostic of the direct rendering implementation.
 *
 * Authors: Alan Hourihane <alanh@tungstengraphics.com>
 */

#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <dlfcn.h>
#include "GL/glx.h"

#include "eglconfig.h"
#include "eglcontext.h"
#include "egldefines.h"
#include "egldisplay.h"
#include "egldriver.h"
#include "eglcurrent.h"
#include "egllog.h"
#include "eglsurface.h"

#define CALLOC_STRUCT(T)   (struct T *) calloc(1, sizeof(struct T))

#ifndef GLX_VERSION_1_4
#error "GL/glx.h must be equal to or greater than GLX 1.4"
#endif

/* GLX 1.0 */
typedef GLXContext (*GLXCREATECONTEXTPROC)( Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct );
typedef void (*GLXDESTROYCONTEXTPROC)( Display *dpy, GLXContext ctx );
typedef Bool (*GLXMAKECURRENTPROC)( Display *dpy, GLXDrawable drawable, GLXContext ctx);
typedef void (*GLXSWAPBUFFERSPROC)( Display *dpy, GLXDrawable drawable );
typedef GLXPixmap (*GLXCREATEGLXPIXMAPPROC)( Display *dpy, XVisualInfo *visual, Pixmap pixmap );
typedef void (*GLXDESTROYGLXPIXMAPPROC)( Display *dpy, GLXPixmap pixmap );
typedef Bool (*GLXQUERYVERSIONPROC)( Display *dpy, int *maj, int *min );
typedef int (*GLXGETCONFIGPROC)( Display *dpy, XVisualInfo *visual, int attrib, int *value );
typedef void (*GLXWAITGLPROC)( void );
typedef void (*GLXWAITXPROC)( void );

/* GLX 1.1 */
typedef const char *(*GLXQUERYEXTENSIONSSTRINGPROC)( Display *dpy, int screen );
typedef const char *(*GLXQUERYSERVERSTRINGPROC)( Display *dpy, int screen, int name );
typedef const char *(*GLXGETCLIENTSTRINGPROC)( Display *dpy, int name );

/** subclass of _EGLDriver */
struct GLX_egl_driver
{
   _EGLDriver Base;   /**< base class */

   void *handle;

   /* GLX 1.0 */
   GLXCREATECONTEXTPROC glXCreateContext;
   GLXDESTROYCONTEXTPROC glXDestroyContext;
   GLXMAKECURRENTPROC glXMakeCurrent;
   GLXSWAPBUFFERSPROC glXSwapBuffers;
   GLXCREATEGLXPIXMAPPROC glXCreateGLXPixmap;
   GLXDESTROYGLXPIXMAPPROC glXDestroyGLXPixmap;
   GLXQUERYVERSIONPROC glXQueryVersion;
   GLXGETCONFIGPROC glXGetConfig;
   GLXWAITGLPROC glXWaitGL;
   GLXWAITXPROC glXWaitX;

   /* GLX 1.1 */
   GLXQUERYEXTENSIONSSTRINGPROC glXQueryExtensionsString;
   GLXQUERYSERVERSTRINGPROC glXQueryServerString;
   GLXGETCLIENTSTRINGPROC glXGetClientString;

   /* GLX 1.3 or (GLX_SGI_make_current_read and GLX_SGIX_fbconfig) */
   PFNGLXGETFBCONFIGSPROC glXGetFBConfigs;
   PFNGLXGETFBCONFIGATTRIBPROC glXGetFBConfigAttrib;
   PFNGLXGETVISUALFROMFBCONFIGPROC glXGetVisualFromFBConfig;
   PFNGLXCREATEWINDOWPROC glXCreateWindow;
   PFNGLXDESTROYWINDOWPROC glXDestroyWindow;
   PFNGLXCREATEPIXMAPPROC glXCreatePixmap;
   PFNGLXDESTROYPIXMAPPROC glXDestroyPixmap;
   PFNGLXCREATEPBUFFERPROC glXCreatePbuffer;
   PFNGLXDESTROYPBUFFERPROC glXDestroyPbuffer;
   PFNGLXCREATENEWCONTEXTPROC glXCreateNewContext;
   PFNGLXMAKECONTEXTCURRENTPROC glXMakeContextCurrent;

   /* GLX 1.4 or GLX_ARB_get_proc_address */
   PFNGLXGETPROCADDRESSPROC glXGetProcAddress;

   /* GLX_SGIX_pbuffer */
   PFNGLXCREATEGLXPBUFFERSGIXPROC glXCreateGLXPbufferSGIX;
   PFNGLXDESTROYGLXPBUFFERSGIXPROC glXDestroyGLXPbufferSGIX;
};


/** driver data of _EGLDisplay */
struct GLX_egl_display
{
   Display *dpy;
   XVisualInfo *visuals;
   GLXFBConfig *fbconfigs;

   int glx_maj, glx_min;

   const char *extensions;
   EGLBoolean have_1_3;
   EGLBoolean have_make_current_read;
   EGLBoolean have_fbconfig;
   EGLBoolean have_pbuffer;

   /* workaround quirks of different GLX implementations */
   EGLBoolean single_buffered_quirk;
   EGLBoolean glx_window_quirk;
};


/** subclass of _EGLContext */
struct GLX_egl_context
{
   _EGLContext Base;   /**< base class */

   GLXContext context;
};


/** subclass of _EGLSurface */
struct GLX_egl_surface
{
   _EGLSurface Base;   /**< base class */

   Drawable drawable;
   GLXDrawable glx_drawable;

   void (*destroy)(Display *, GLXDrawable);
};


/** subclass of _EGLConfig */
struct GLX_egl_config
{
   _EGLConfig Base;   /**< base class */
   EGLBoolean double_buffered;
   int index;
};

/* standard typecasts */
_EGL_DRIVER_STANDARD_TYPECASTS(GLX_egl)

static int
GLX_egl_config_index(_EGLConfig *conf)
{
   struct GLX_egl_config *GLX_conf = GLX_egl_config(conf);
   return GLX_conf->index;
}


static const struct {
   int attr;
   int egl_attr;
} fbconfig_attributes[] = {
   /* table 3.1 of GLX 1.4 */
   { GLX_FBCONFIG_ID,                  0 },
   { GLX_BUFFER_SIZE,                  EGL_BUFFER_SIZE },
   { GLX_LEVEL,                        EGL_LEVEL },
   { GLX_DOUBLEBUFFER,                 0 },
   { GLX_STEREO,                       0 },
   { GLX_AUX_BUFFERS,                  0 },
   { GLX_RED_SIZE,                     EGL_RED_SIZE },
   { GLX_GREEN_SIZE,                   EGL_GREEN_SIZE },
   { GLX_BLUE_SIZE,                    EGL_BLUE_SIZE },
   { GLX_ALPHA_SIZE,                   EGL_ALPHA_SIZE },
   { GLX_DEPTH_SIZE,                   EGL_DEPTH_SIZE },
   { GLX_STENCIL_SIZE,                 EGL_STENCIL_SIZE },
   { GLX_ACCUM_RED_SIZE,               0 },
   { GLX_ACCUM_GREEN_SIZE,             0 },
   { GLX_ACCUM_BLUE_SIZE,              0 },
   { GLX_ACCUM_ALPHA_SIZE,             0 },
   { GLX_SAMPLE_BUFFERS,               EGL_SAMPLE_BUFFERS },
   { GLX_SAMPLES,                      EGL_SAMPLES },
   { GLX_RENDER_TYPE,                  0 },
   { GLX_DRAWABLE_TYPE,                EGL_SURFACE_TYPE },
   { GLX_X_RENDERABLE,                 EGL_NATIVE_RENDERABLE },
   { GLX_X_VISUAL_TYPE,                EGL_NATIVE_VISUAL_TYPE },
   { GLX_CONFIG_CAVEAT,                EGL_CONFIG_CAVEAT },
   { GLX_TRANSPARENT_TYPE,             EGL_TRANSPARENT_TYPE },
   { GLX_TRANSPARENT_INDEX_VALUE,      0 },
   { GLX_TRANSPARENT_RED_VALUE,        EGL_TRANSPARENT_RED_VALUE },
   { GLX_TRANSPARENT_GREEN_VALUE,      EGL_TRANSPARENT_GREEN_VALUE },
   { GLX_TRANSPARENT_BLUE_VALUE,       EGL_TRANSPARENT_BLUE_VALUE },
   { GLX_MAX_PBUFFER_WIDTH,            EGL_MAX_PBUFFER_WIDTH },
   { GLX_MAX_PBUFFER_HEIGHT,           EGL_MAX_PBUFFER_HEIGHT },
   { GLX_MAX_PBUFFER_PIXELS,           EGL_MAX_PBUFFER_PIXELS },
   { GLX_VISUAL_ID,                    EGL_NATIVE_VISUAL_ID }
};


static EGLBoolean
convert_fbconfig(struct GLX_egl_driver *GLX_drv,
                 struct GLX_egl_display *GLX_dpy, GLXFBConfig fbconfig,
                 struct GLX_egl_config *GLX_conf)
{
   Display *dpy = GLX_dpy->dpy;
   int err, attr, val;
   unsigned i;

   /* must have rgba bit */
   err = GLX_drv->glXGetFBConfigAttrib(dpy, fbconfig, GLX_RENDER_TYPE, &val);
   if (err || !(val & GLX_RGBA_BIT))
      return EGL_FALSE;

   /* must know whether it is double-buffered */
   err = GLX_drv->glXGetFBConfigAttrib(dpy, fbconfig, GLX_DOUBLEBUFFER, &val);
   if (err)
      return EGL_FALSE;
   GLX_conf->double_buffered = val;

   GLX_conf->Base.RenderableType = EGL_OPENGL_BIT;
   GLX_conf->Base.Conformant = EGL_OPENGL_BIT;

   for (i = 0; i < ARRAY_SIZE(fbconfig_attributes); i++) {
      EGLint egl_attr, egl_val;

      attr = fbconfig_attributes[i].attr;
      egl_attr = fbconfig_attributes[i].egl_attr;
      if (!egl_attr)
         continue;

      err = GLX_drv->glXGetFBConfigAttrib(dpy, fbconfig, attr, &val);
      if (err) {
         if (err == GLX_BAD_ATTRIBUTE) {
            err = 0;
            continue;
         }
         break;
      }

      switch (egl_attr) {
      case EGL_SURFACE_TYPE:
         egl_val = 0;
         if (val & GLX_WINDOW_BIT)
            egl_val |= EGL_WINDOW_BIT;
         /* pixmap and pbuffer surfaces must be single-buffered in EGL */
         if (!GLX_conf->double_buffered) {
            if (val & GLX_PIXMAP_BIT)
               egl_val |= EGL_PIXMAP_BIT;
            if (val & GLX_PBUFFER_BIT)
               egl_val |= EGL_PBUFFER_BIT;
         }
         break;
      case EGL_NATIVE_VISUAL_TYPE:
         switch (val) {
         case GLX_TRUE_COLOR:
            egl_val = TrueColor;
            break;
         case GLX_DIRECT_COLOR:
            egl_val = DirectColor;
            break;
         case GLX_PSEUDO_COLOR:
            egl_val = PseudoColor;
            break;
         case GLX_STATIC_COLOR:
            egl_val = StaticColor;
            break;
         case GLX_GRAY_SCALE:
            egl_val = GrayScale;
            break;
         case GLX_STATIC_GRAY:
            egl_val = StaticGray;
            break;
         default:
            egl_val = EGL_NONE;
            break;
         }
         break;
      case EGL_CONFIG_CAVEAT:
         egl_val = EGL_NONE;
         if (val == GLX_SLOW_CONFIG) {
            egl_val = EGL_SLOW_CONFIG;
         }
         else if (val == GLX_NON_CONFORMANT_CONFIG) {
            GLX_conf->Base.Conformant &= ~EGL_OPENGL_BIT;
            egl_val = EGL_NONE;
         }
         break;
      case EGL_TRANSPARENT_TYPE:
         egl_val = (val == GLX_TRANSPARENT_RGB) ?
            EGL_TRANSPARENT_RGB : EGL_NONE;
         break;
      default:
         egl_val = val;
         break;
      }

      _eglSetConfigKey(&GLX_conf->Base, egl_attr, egl_val);
   }
   if (err)
      return EGL_FALSE;

   if (!GLX_conf->Base.SurfaceType)
      return EGL_FALSE;

   return EGL_TRUE;
}

static const struct {
   int attr;
   int egl_attr;
} visual_attributes[] = {
   /* table 3.7 of GLX 1.4 */
   { GLX_USE_GL,              0 },
   { GLX_BUFFER_SIZE,         EGL_BUFFER_SIZE },
   { GLX_LEVEL,               EGL_LEVEL },
   { GLX_RGBA,                0 },
   { GLX_DOUBLEBUFFER,        0 },
   { GLX_STEREO,              0 },
   { GLX_AUX_BUFFERS,         0 },
   { GLX_RED_SIZE,            EGL_RED_SIZE },
   { GLX_GREEN_SIZE,          EGL_GREEN_SIZE },
   { GLX_BLUE_SIZE,           EGL_BLUE_SIZE },
   { GLX_ALPHA_SIZE,          EGL_ALPHA_SIZE },
   { GLX_DEPTH_SIZE,          EGL_DEPTH_SIZE },
   { GLX_STENCIL_SIZE,        EGL_STENCIL_SIZE },
   { GLX_ACCUM_RED_SIZE,      0 },
   { GLX_ACCUM_GREEN_SIZE,    0 },
   { GLX_ACCUM_BLUE_SIZE,     0 },
   { GLX_ACCUM_ALPHA_SIZE,    0 },
   { GLX_SAMPLE_BUFFERS,      EGL_SAMPLE_BUFFERS },
   { GLX_SAMPLES,             EGL_SAMPLES },
   { GLX_FBCONFIG_ID,         0 },
   /* GLX_EXT_visual_rating */
   { GLX_VISUAL_CAVEAT_EXT,   EGL_CONFIG_CAVEAT }
};

static EGLBoolean
convert_visual(struct GLX_egl_driver *GLX_drv,
               struct GLX_egl_display *GLX_dpy, XVisualInfo *vinfo,
               struct GLX_egl_config *GLX_conf)
{
   Display *dpy = GLX_dpy->dpy;
   int err, attr, val;
   unsigned i;

   /* the visual must support OpenGL and RGBA buffer */
   err = GLX_drv->glXGetConfig(dpy, vinfo, GLX_USE_GL, &val);
   if (!err && val)
      err = GLX_drv->glXGetConfig(dpy, vinfo, GLX_RGBA, &val);
   if (err || !val)
      return EGL_FALSE;

   /* must know whether it is double-buffered */
   err = GLX_drv->glXGetConfig(dpy, vinfo, GLX_DOUBLEBUFFER, &val);
   if (err)
      return EGL_FALSE;
   GLX_conf->double_buffered = val;

   GLX_conf->Base.RenderableType = EGL_OPENGL_BIT;
   GLX_conf->Base.Conformant = EGL_OPENGL_BIT;
   GLX_conf->Base.SurfaceType = EGL_WINDOW_BIT;
   /* pixmap surfaces must be single-buffered in EGL */
   if (!GLX_conf->double_buffered)
      GLX_conf->Base.SurfaceType |= EGL_PIXMAP_BIT;

   GLX_conf->Base.NativeVisualID = vinfo->visualid;
   GLX_conf->Base.NativeVisualType = vinfo->class;
   GLX_conf->Base.NativeRenderable = EGL_TRUE;

   for (i = 0; i < ARRAY_SIZE(visual_attributes); i++) {
      EGLint egl_attr, egl_val;

      attr = visual_attributes[i].attr;
      egl_attr = visual_attributes[i].egl_attr;
      if (!egl_attr)
         continue;

      err = GLX_drv->glXGetConfig(dpy, vinfo, attr, &val);
      if (err) {
         if (err == GLX_BAD_ATTRIBUTE) {
            err = 0;
            continue;
         }
         break;
      }

      switch (egl_attr) {
      case EGL_CONFIG_CAVEAT:
         egl_val = EGL_NONE;
         if (val == GLX_SLOW_VISUAL_EXT) {
            egl_val = EGL_SLOW_CONFIG;
         }
         else if (val == GLX_NON_CONFORMANT_VISUAL_EXT) {
            GLX_conf->Base.Conformant &= ~EGL_OPENGL_BIT;
            egl_val = EGL_NONE;
         }
         break;
         break;
      default:
         egl_val = val;
         break;
      }
      _eglSetConfigKey(&GLX_conf->Base, egl_attr, egl_val);
   }

   return (err) ? EGL_FALSE : EGL_TRUE;
}


static void
fix_config(struct GLX_egl_display *GLX_dpy, struct GLX_egl_config *GLX_conf)
{
   _EGLConfig *conf = &GLX_conf->Base;

   if (!GLX_conf->double_buffered && GLX_dpy->single_buffered_quirk) {
      /* some GLX impls do not like single-buffered window surface */
      conf->SurfaceType &= ~EGL_WINDOW_BIT;
      /* pbuffer bit is usually not set */
      if (GLX_dpy->have_pbuffer)
         conf->SurfaceType |= EGL_PBUFFER_BIT;
   }

   /* no visual attribs unless window bit is set */
   if (!(conf->SurfaceType & EGL_WINDOW_BIT)) {
      conf->NativeVisualID = 0;
      conf->NativeVisualType = EGL_NONE;
   }

   if (conf->TransparentType != EGL_TRANSPARENT_RGB) {
      /* some impls set them to -1 (GLX_DONT_CARE) */
      conf->TransparentRedValue = 0;
      conf->TransparentGreenValue = 0;
      conf->TransparentBlueValue = 0;
   }

   /* make sure buffer size is set correctly */
   conf->BufferSize =
      conf->RedSize + conf->GreenSize + conf->BlueSize + conf->AlphaSize;
}


static EGLBoolean
create_configs(_EGLDriver *drv, _EGLDisplay *dpy, EGLint screen)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   struct GLX_egl_display *GLX_dpy = GLX_egl_display(dpy);
   EGLint num_configs = 0, i;
   EGLint id = 1;

   if (GLX_dpy->have_fbconfig) {
      GLX_dpy->fbconfigs =
         GLX_drv->glXGetFBConfigs(GLX_dpy->dpy, screen, &num_configs);
   }
   else {
      XVisualInfo vinfo_template;
      long mask;

      vinfo_template.screen = screen;
      mask = VisualScreenMask;
      GLX_dpy->visuals = XGetVisualInfo(GLX_dpy->dpy, mask, &vinfo_template,
                                        &num_configs);
   }

   if (!num_configs)
      return EGL_FALSE;

   for (i = 0; i < num_configs; i++) {
      struct GLX_egl_config *GLX_conf, template;
      EGLBoolean ok;

      memset(&template, 0, sizeof(template));
      _eglInitConfig(&template.Base, dpy, id);
      if (GLX_dpy->have_fbconfig) {
         ok = convert_fbconfig(GLX_drv, GLX_dpy,
               GLX_dpy->fbconfigs[i], &template);
      }
      else {
         ok = convert_visual(GLX_drv, GLX_dpy,
               &GLX_dpy->visuals[i], &template);
      }
      if (!ok)
        continue;

      fix_config(GLX_dpy, &template);
      if (!_eglValidateConfig(&template.Base, EGL_FALSE)) {
         _eglLog(_EGL_DEBUG, "GLX: failed to validate config %d", i);
         continue;
      }

      GLX_conf = CALLOC_STRUCT(GLX_egl_config);
      if (GLX_conf) {
         memcpy(GLX_conf, &template, sizeof(template));
         GLX_conf->index = i;

         _eglLinkConfig(&GLX_conf->Base);
         id++;
      }
   }

   return EGL_TRUE;
}


static void
check_extensions(struct GLX_egl_driver *GLX_drv,
                 struct GLX_egl_display *GLX_dpy, EGLint screen)
{
   GLX_dpy->extensions =
      GLX_drv->glXQueryExtensionsString(GLX_dpy->dpy, screen);
   if (GLX_dpy->extensions) {
      if (strstr(GLX_dpy->extensions, "GLX_SGI_make_current_read")) {
         /* GLX 1.3 entry points are used */
         GLX_dpy->have_make_current_read = EGL_TRUE;
      }

      if (strstr(GLX_dpy->extensions, "GLX_SGIX_fbconfig")) {
         /* GLX 1.3 entry points are used */
         GLX_dpy->have_fbconfig = EGL_TRUE;
      }

      if (strstr(GLX_dpy->extensions, "GLX_SGIX_pbuffer")) {
         if (GLX_drv->glXCreateGLXPbufferSGIX &&
             GLX_drv->glXDestroyGLXPbufferSGIX &&
             GLX_dpy->have_fbconfig)
            GLX_dpy->have_pbuffer = EGL_TRUE;
      }
   }

   if (GLX_dpy->glx_maj == 1 && GLX_dpy->glx_min >= 3) {
      GLX_dpy->have_1_3 = EGL_TRUE;
      GLX_dpy->have_make_current_read = EGL_TRUE;
      GLX_dpy->have_fbconfig = EGL_TRUE;
      GLX_dpy->have_pbuffer = EGL_TRUE;
   }
}


static void
check_quirks(struct GLX_egl_driver *GLX_drv,
             struct GLX_egl_display *GLX_dpy, EGLint screen)
{
   const char *vendor;

   GLX_dpy->single_buffered_quirk = EGL_TRUE;
   GLX_dpy->glx_window_quirk = EGL_TRUE;

   vendor = GLX_drv->glXGetClientString(GLX_dpy->dpy, GLX_VENDOR);
   if (vendor && strstr(vendor, "NVIDIA")) {
      vendor = GLX_drv->glXQueryServerString(GLX_dpy->dpy, screen, GLX_VENDOR);
      if (vendor && strstr(vendor, "NVIDIA")) {
         _eglLog(_EGL_DEBUG, "disable quirks");
         GLX_dpy->single_buffered_quirk = EGL_FALSE;
         GLX_dpy->glx_window_quirk = EGL_FALSE;
      }
   }
}


/**
 * Called via eglInitialize(), GLX_drv->API.Initialize().
 */
static EGLBoolean
GLX_eglInitialize(_EGLDriver *drv, _EGLDisplay *disp)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   struct GLX_egl_display *GLX_dpy;

   if (disp->Platform != _EGL_PLATFORM_X11)
      return EGL_FALSE;

   /* this is a fallback driver */
   if (!disp->Options.UseFallback)
      return EGL_FALSE;

   if (disp->Options.TestOnly)
      return EGL_TRUE;

   GLX_dpy = CALLOC_STRUCT(GLX_egl_display);
   if (!GLX_dpy)
      return _eglError(EGL_BAD_ALLOC, "eglInitialize");

   GLX_dpy->dpy = (Display *) disp->PlatformDisplay;
   if (!GLX_dpy->dpy) {
      GLX_dpy->dpy = XOpenDisplay(NULL);
      if (!GLX_dpy->dpy) {
         _eglLog(_EGL_WARNING, "GLX: XOpenDisplay failed");
         free(GLX_dpy);
         return EGL_FALSE;
      }
   }

   if (!GLX_drv->glXQueryVersion(GLX_dpy->dpy,
            &GLX_dpy->glx_maj, &GLX_dpy->glx_min)) {
      _eglLog(_EGL_WARNING, "GLX: glXQueryVersion failed");
      if (!disp->PlatformDisplay)
         XCloseDisplay(GLX_dpy->dpy);
      free(GLX_dpy);
      return EGL_FALSE;
   }

   disp->DriverData = (void *) GLX_dpy;
   disp->ClientAPIs = EGL_OPENGL_BIT;

   check_extensions(GLX_drv, GLX_dpy, DefaultScreen(GLX_dpy->dpy));
   check_quirks(GLX_drv, GLX_dpy, DefaultScreen(GLX_dpy->dpy));

   create_configs(drv, disp, DefaultScreen(GLX_dpy->dpy));
   if (!_eglGetArraySize(disp->Configs)) {
      _eglLog(_EGL_WARNING, "GLX: failed to create any config");
      if (!disp->PlatformDisplay)
         XCloseDisplay(GLX_dpy->dpy);
      free(GLX_dpy);
      return EGL_FALSE;
   }

   /* we're supporting EGL 1.4 */
   disp->VersionMajor = 1;
   disp->VersionMinor = 4;

   return EGL_TRUE;
}


/**
 * Called via eglTerminate(), drv->API.Terminate().
 */
static EGLBoolean
GLX_eglTerminate(_EGLDriver *drv, _EGLDisplay *disp)
{
   struct GLX_egl_display *GLX_dpy = GLX_egl_display(disp);

   _eglReleaseDisplayResources(drv, disp);
   _eglCleanupDisplay(disp);

   free(GLX_dpy->visuals);
   free(GLX_dpy->fbconfigs);

   if (!disp->PlatformDisplay)
      XCloseDisplay(GLX_dpy->dpy);
   free(GLX_dpy);

   disp->DriverData = NULL;

   return EGL_TRUE;
}


/**
 * Called via eglCreateContext(), drv->API.CreateContext().
 */
static _EGLContext *
GLX_eglCreateContext(_EGLDriver *drv, _EGLDisplay *disp, _EGLConfig *conf,
                      _EGLContext *share_list, const EGLint *attrib_list)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   struct GLX_egl_context *GLX_ctx = CALLOC_STRUCT(GLX_egl_context);
   struct GLX_egl_display *GLX_dpy = GLX_egl_display(disp);
   struct GLX_egl_context *GLX_ctx_shared = GLX_egl_context(share_list);

   if (!GLX_ctx) {
      _eglError(EGL_BAD_ALLOC, "eglCreateContext");
      return NULL;
   }

   if (!_eglInitContext(&GLX_ctx->Base, disp, conf, attrib_list)) {
      free(GLX_ctx);
      return NULL;
   }

   if (GLX_dpy->have_fbconfig) {
      GLX_ctx->context = GLX_drv->glXCreateNewContext(GLX_dpy->dpy,
            GLX_dpy->fbconfigs[GLX_egl_config_index(conf)],
            GLX_RGBA_TYPE,
            GLX_ctx_shared ? GLX_ctx_shared->context : NULL,
            GL_TRUE);
   }
   else {
      GLX_ctx->context = GLX_drv->glXCreateContext(GLX_dpy->dpy,
            &GLX_dpy->visuals[GLX_egl_config_index(conf)],
            GLX_ctx_shared ? GLX_ctx_shared->context : NULL,
            GL_TRUE);
   }
   if (!GLX_ctx->context) {
      free(GLX_ctx);
      return NULL;
   }

   return &GLX_ctx->Base;
}

/**
 * Called via eglDestroyContext(), drv->API.DestroyContext().
 */
static EGLBoolean
GLX_eglDestroyContext(_EGLDriver *drv, _EGLDisplay *disp, _EGLContext *ctx)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   struct GLX_egl_display *GLX_dpy = GLX_egl_display(disp);
   struct GLX_egl_context *GLX_ctx = GLX_egl_context(ctx);

   if (_eglPutContext(ctx)) {
      assert(GLX_ctx);
      GLX_drv->glXDestroyContext(GLX_dpy->dpy, GLX_ctx->context);

      free(GLX_ctx);
   }

   return EGL_TRUE;
}

/**
 * Destroy a surface.  The display is allowed to be uninitialized.
 */
static void
destroy_surface(_EGLDisplay *disp, _EGLSurface *surf)
{
   struct GLX_egl_display *GLX_dpy = GLX_egl_display(disp);
   struct GLX_egl_surface *GLX_surf = GLX_egl_surface(surf);

   if (GLX_surf->destroy)
      GLX_surf->destroy(GLX_dpy->dpy, GLX_surf->glx_drawable);

   free(GLX_surf);
}


/**
 * Called via eglMakeCurrent(), drv->API.MakeCurrent().
 */
static EGLBoolean
GLX_eglMakeCurrent(_EGLDriver *drv, _EGLDisplay *disp, _EGLSurface *dsurf,
                   _EGLSurface *rsurf, _EGLContext *ctx)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   struct GLX_egl_display *GLX_dpy = GLX_egl_display(disp);
   struct GLX_egl_surface *GLX_dsurf = GLX_egl_surface(dsurf);
   struct GLX_egl_surface *GLX_rsurf = GLX_egl_surface(rsurf);
   struct GLX_egl_context *GLX_ctx = GLX_egl_context(ctx);
   _EGLContext *old_ctx;
   _EGLSurface *old_dsurf, *old_rsurf;
   GLXDrawable ddraw, rdraw;
   GLXContext cctx;
   EGLBoolean ret = EGL_FALSE;

   /* make new bindings */
   if (!_eglBindContext(ctx, dsurf, rsurf, &old_ctx, &old_dsurf, &old_rsurf))
      return EGL_FALSE;

   ddraw = (GLX_dsurf) ? GLX_dsurf->glx_drawable : None;
   rdraw = (GLX_rsurf) ? GLX_rsurf->glx_drawable : None;
   cctx = (GLX_ctx) ? GLX_ctx->context : NULL;

   if (GLX_dpy->have_make_current_read)
      ret = GLX_drv->glXMakeContextCurrent(GLX_dpy->dpy, ddraw, rdraw, cctx);
   else if (ddraw == rdraw)
      ret = GLX_drv->glXMakeCurrent(GLX_dpy->dpy, ddraw, cctx);

   if (ret) {
      if (_eglPutSurface(old_dsurf))
         destroy_surface(disp, old_dsurf);
      if (_eglPutSurface(old_rsurf))
         destroy_surface(disp, old_rsurf);
      /* no destroy? */
      _eglPutContext(old_ctx);
   }
   else {
      /* undo the previous _eglBindContext */
      _eglBindContext(old_ctx, old_dsurf, old_rsurf, &ctx, &dsurf, &rsurf);
      assert(&GLX_ctx->Base == ctx &&
             &GLX_dsurf->Base == dsurf &&
             &GLX_rsurf->Base == rsurf);

      _eglPutSurface(dsurf);
      _eglPutSurface(rsurf);
      _eglPutContext(ctx);

      _eglPutSurface(old_dsurf);
      _eglPutSurface(old_rsurf);
      _eglPutContext(old_ctx);
   }

   return ret;
}

/** Get size of given window */
static Status
get_drawable_size(Display *dpy, Drawable d, unsigned *width, unsigned *height)
{
   Window root;
   Status stat;
   int xpos, ypos;
   unsigned int w, h, bw, depth;
   stat = XGetGeometry(dpy, d, &root, &xpos, &ypos, &w, &h, &bw, &depth);
   *width = w;
   *height = h;
   return stat;
}

/**
 * Called via eglCreateWindowSurface(), drv->API.CreateWindowSurface().
 */
static _EGLSurface *
GLX_eglCreateWindowSurface(_EGLDriver *drv, _EGLDisplay *disp,
                           _EGLConfig *conf, EGLNativeWindowType window,
                           const EGLint *attrib_list)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   struct GLX_egl_display *GLX_dpy = GLX_egl_display(disp);
   struct GLX_egl_surface *GLX_surf;
   unsigned width, height;

   GLX_surf = CALLOC_STRUCT(GLX_egl_surface);
   if (!GLX_surf) {
      _eglError(EGL_BAD_ALLOC, "eglCreateWindowSurface");
      return NULL;
   }

   if (!_eglInitSurface(&GLX_surf->Base, disp, EGL_WINDOW_BIT,
                        conf, attrib_list)) {
      free(GLX_surf);
      return NULL;
   }

   GLX_surf->drawable = window;

   if (GLX_dpy->have_1_3 && !GLX_dpy->glx_window_quirk) {
      GLX_surf->glx_drawable = GLX_drv->glXCreateWindow(GLX_dpy->dpy,
            GLX_dpy->fbconfigs[GLX_egl_config_index(conf)],
            GLX_surf->drawable, NULL);
   }
   else {
      GLX_surf->glx_drawable = GLX_surf->drawable;
   }

   if (!GLX_surf->glx_drawable) {
      free(GLX_surf);
      return NULL;
   }

   if (GLX_dpy->have_1_3 && !GLX_dpy->glx_window_quirk)
      GLX_surf->destroy = GLX_drv->glXDestroyWindow;

   get_drawable_size(GLX_dpy->dpy, window, &width, &height);
   GLX_surf->Base.Width = width;
   GLX_surf->Base.Height = height;

   return &GLX_surf->Base;
}

static _EGLSurface *
GLX_eglCreatePixmapSurface(_EGLDriver *drv, _EGLDisplay *disp,
                           _EGLConfig *conf, EGLNativePixmapType pixmap,
                           const EGLint *attrib_list)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   struct GLX_egl_display *GLX_dpy = GLX_egl_display(disp);
   struct GLX_egl_surface *GLX_surf;
   unsigned width, height;

   GLX_surf = CALLOC_STRUCT(GLX_egl_surface);
   if (!GLX_surf) {
      _eglError(EGL_BAD_ALLOC, "eglCreatePixmapSurface");
      return NULL;
   }

   if (!_eglInitSurface(&GLX_surf->Base, disp, EGL_PIXMAP_BIT,
                        conf, attrib_list)) {
      free(GLX_surf);
      return NULL;
   }

   GLX_surf->drawable = pixmap;

   if (GLX_dpy->have_1_3) {
      GLX_surf->glx_drawable = GLX_drv->glXCreatePixmap(GLX_dpy->dpy,
            GLX_dpy->fbconfigs[GLX_egl_config_index(conf)],
            GLX_surf->drawable, NULL);
   }
   else if (GLX_dpy->have_fbconfig) {
      GLXFBConfig fbconfig = GLX_dpy->fbconfigs[GLX_egl_config_index(conf)];
      XVisualInfo *vinfo;

      vinfo = GLX_drv->glXGetVisualFromFBConfig(GLX_dpy->dpy, fbconfig);
      if (vinfo) {
         GLX_surf->glx_drawable = GLX_drv->glXCreateGLXPixmap(GLX_dpy->dpy,
               vinfo, GLX_surf->drawable);
         free(vinfo);
      }
   }
   else {
      GLX_surf->glx_drawable = GLX_drv->glXCreateGLXPixmap(GLX_dpy->dpy,
            &GLX_dpy->visuals[GLX_egl_config_index(conf)],
            GLX_surf->drawable);
   }

   if (!GLX_surf->glx_drawable) {
      free(GLX_surf);
      return NULL;
   }

   GLX_surf->destroy = (GLX_dpy->have_1_3) ?
      GLX_drv->glXDestroyPixmap : GLX_drv->glXDestroyGLXPixmap;

   get_drawable_size(GLX_dpy->dpy, pixmap, &width, &height);
   GLX_surf->Base.Width = width;
   GLX_surf->Base.Height = height;

   return &GLX_surf->Base;
}

static _EGLSurface *
GLX_eglCreatePbufferSurface(_EGLDriver *drv, _EGLDisplay *disp,
                            _EGLConfig *conf, const EGLint *attrib_list)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   struct GLX_egl_display *GLX_dpy = GLX_egl_display(disp);
   struct GLX_egl_surface *GLX_surf;
   int attribs[5];
   int i;

   GLX_surf = CALLOC_STRUCT(GLX_egl_surface);
   if (!GLX_surf) {
      _eglError(EGL_BAD_ALLOC, "eglCreatePbufferSurface");
      return NULL;
   }

   if (!_eglInitSurface(&GLX_surf->Base, disp, EGL_PBUFFER_BIT,
                        conf, attrib_list)) {
      free(GLX_surf);
      return NULL;
   }

   i = 0;
   attribs[i] = None;

   GLX_surf->drawable = None;

   if (GLX_dpy->have_1_3) {
      /* put geometry in attribs */
      if (GLX_surf->Base.Width) {
         attribs[i++] = GLX_PBUFFER_WIDTH;
         attribs[i++] = GLX_surf->Base.Width;
      }
      if (GLX_surf->Base.Height) {
         attribs[i++] = GLX_PBUFFER_HEIGHT;
         attribs[i++] = GLX_surf->Base.Height;
      }
      attribs[i] = None;

      GLX_surf->glx_drawable = GLX_drv->glXCreatePbuffer(GLX_dpy->dpy,
            GLX_dpy->fbconfigs[GLX_egl_config_index(conf)], attribs);
   }
   else if (GLX_dpy->have_pbuffer) {
      GLX_surf->glx_drawable = GLX_drv->glXCreateGLXPbufferSGIX(GLX_dpy->dpy,
            GLX_dpy->fbconfigs[GLX_egl_config_index(conf)],
            GLX_surf->Base.Width,
            GLX_surf->Base.Height,
            attribs);
   }

   if (!GLX_surf->glx_drawable) {
      free(GLX_surf);
      return NULL;
   }

   GLX_surf->destroy = (GLX_dpy->have_1_3) ?
      GLX_drv->glXDestroyPbuffer : GLX_drv->glXDestroyGLXPbufferSGIX;

   return &GLX_surf->Base;
}


static EGLBoolean
GLX_eglDestroySurface(_EGLDriver *drv, _EGLDisplay *disp, _EGLSurface *surf)
{
   (void) drv;

   if (_eglPutSurface(surf))
      destroy_surface(disp, surf);

   return EGL_TRUE;
}


static EGLBoolean
GLX_eglSwapBuffers(_EGLDriver *drv, _EGLDisplay *disp, _EGLSurface *draw)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   struct GLX_egl_display *GLX_dpy = GLX_egl_display(disp);
   struct GLX_egl_surface *GLX_surf = GLX_egl_surface(draw);

   GLX_drv->glXSwapBuffers(GLX_dpy->dpy, GLX_surf->glx_drawable);

   return EGL_TRUE;
}

/*
 * Called from eglGetProcAddress() via drv->API.GetProcAddress().
 */
static _EGLProc
GLX_eglGetProcAddress(_EGLDriver *drv, const char *procname)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);

   return (_EGLProc) GLX_drv->glXGetProcAddress((const GLubyte *) procname);
}

static EGLBoolean
GLX_eglWaitClient(_EGLDriver *drv, _EGLDisplay *dpy, _EGLContext *ctx)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);

   (void) dpy;
   (void) ctx;

   GLX_drv->glXWaitGL();
   return EGL_TRUE;
}

static EGLBoolean
GLX_eglWaitNative(_EGLDriver *drv, _EGLDisplay *dpy, EGLint engine)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);

   (void) dpy;

   if (engine != EGL_CORE_NATIVE_ENGINE)
      return _eglError(EGL_BAD_PARAMETER, "eglWaitNative");
   GLX_drv->glXWaitX();
   return EGL_TRUE;
}

static void
GLX_Unload(_EGLDriver *drv)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);

   if (GLX_drv->handle)
      dlclose(GLX_drv->handle);
   free(GLX_drv);
}


static EGLBoolean
GLX_Load(_EGLDriver *drv)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   void *handle = NULL;

   GLX_drv->glXGetProcAddress = dlsym(RTLD_DEFAULT, "glXGetProcAddress");
   if (!GLX_drv->glXGetProcAddress)
      GLX_drv->glXGetProcAddress = dlsym(RTLD_DEFAULT, "glXGetProcAddressARB");
   if (!GLX_drv->glXGetProcAddress) {
      handle = dlopen("libGL.so", RTLD_LAZY | RTLD_LOCAL);
      if (!handle)
         goto fail;

      GLX_drv->glXGetProcAddress = dlsym(handle, "glXGetProcAddress");
      if (!GLX_drv->glXGetProcAddress)
         GLX_drv->glXGetProcAddress = dlsym(handle, "glXGetProcAddressARB");
      if (!GLX_drv->glXGetProcAddress)
         goto fail;
   }

#define GET_PROC(proc_type, proc_name, check)                        \
   do {                                                              \
      GLX_drv->proc_name = (proc_type)                               \
         GLX_drv->glXGetProcAddress((const GLubyte *) #proc_name);   \
      if (check && !GLX_drv->proc_name) goto fail;                   \
   } while (0)

   /* GLX 1.0 */
   GET_PROC(GLXCREATECONTEXTPROC, glXCreateContext, EGL_TRUE);
   GET_PROC(GLXDESTROYCONTEXTPROC, glXDestroyContext, EGL_TRUE);
   GET_PROC(GLXMAKECURRENTPROC, glXMakeCurrent, EGL_TRUE);
   GET_PROC(GLXSWAPBUFFERSPROC, glXSwapBuffers, EGL_TRUE);
   GET_PROC(GLXCREATEGLXPIXMAPPROC, glXCreateGLXPixmap, EGL_TRUE);
   GET_PROC(GLXDESTROYGLXPIXMAPPROC, glXDestroyGLXPixmap, EGL_TRUE);
   GET_PROC(GLXQUERYVERSIONPROC, glXQueryVersion, EGL_TRUE);
   GET_PROC(GLXGETCONFIGPROC, glXGetConfig, EGL_TRUE);
   GET_PROC(GLXWAITGLPROC, glXWaitGL, EGL_TRUE);
   GET_PROC(GLXWAITXPROC, glXWaitX, EGL_TRUE);

   /* GLX 1.1 */
   GET_PROC(GLXQUERYEXTENSIONSSTRINGPROC, glXQueryExtensionsString, EGL_TRUE);
   GET_PROC(GLXQUERYSERVERSTRINGPROC, glXQueryServerString, EGL_TRUE);
   GET_PROC(GLXGETCLIENTSTRINGPROC, glXGetClientString, EGL_TRUE);

   /* GLX 1.3 */
   GET_PROC(PFNGLXGETFBCONFIGSPROC, glXGetFBConfigs, EGL_FALSE);
   GET_PROC(PFNGLXGETFBCONFIGATTRIBPROC, glXGetFBConfigAttrib, EGL_FALSE);
   GET_PROC(PFNGLXGETVISUALFROMFBCONFIGPROC, glXGetVisualFromFBConfig, EGL_FALSE);
   GET_PROC(PFNGLXCREATEWINDOWPROC, glXCreateWindow, EGL_FALSE);
   GET_PROC(PFNGLXDESTROYWINDOWPROC, glXDestroyWindow, EGL_FALSE);
   GET_PROC(PFNGLXCREATEPIXMAPPROC, glXCreatePixmap, EGL_FALSE);
   GET_PROC(PFNGLXDESTROYPIXMAPPROC, glXDestroyPixmap, EGL_FALSE);
   GET_PROC(PFNGLXCREATEPBUFFERPROC, glXCreatePbuffer, EGL_FALSE);
   GET_PROC(PFNGLXDESTROYPBUFFERPROC, glXDestroyPbuffer, EGL_FALSE);
   GET_PROC(PFNGLXCREATENEWCONTEXTPROC, glXCreateNewContext, EGL_FALSE);
   GET_PROC(PFNGLXMAKECONTEXTCURRENTPROC, glXMakeContextCurrent, EGL_FALSE);

   /* GLX_SGIX_pbuffer */
   GET_PROC(PFNGLXCREATEGLXPBUFFERSGIXPROC,
         glXCreateGLXPbufferSGIX, EGL_FALSE);
   GET_PROC(PFNGLXDESTROYGLXPBUFFERSGIXPROC,
         glXDestroyGLXPbufferSGIX, EGL_FALSE);
#undef GET_PROC

   GLX_drv->handle = handle;

   return EGL_TRUE;

fail:
   if (handle)
      dlclose(handle);
   return EGL_FALSE;
}


/**
 * This is the main entrypoint into the driver, called by libEGL.
 * Create a new _EGLDriver object and init its dispatch table.
 */
_EGLDriver *
_eglBuiltInDriverGLX(const char *args)
{
   struct GLX_egl_driver *GLX_drv = CALLOC_STRUCT(GLX_egl_driver);

   (void) args;

   if (!GLX_drv)
      return NULL;

   if (!GLX_Load(&GLX_drv->Base)) {
      _eglLog(_EGL_WARNING, "GLX: failed to load GLX");
      free(GLX_drv);
      return NULL;
   }

   _eglInitDriverFallbacks(&GLX_drv->Base);
   GLX_drv->Base.API.Initialize = GLX_eglInitialize;
   GLX_drv->Base.API.Terminate = GLX_eglTerminate;
   GLX_drv->Base.API.CreateContext = GLX_eglCreateContext;
   GLX_drv->Base.API.DestroyContext = GLX_eglDestroyContext;
   GLX_drv->Base.API.MakeCurrent = GLX_eglMakeCurrent;
   GLX_drv->Base.API.CreateWindowSurface = GLX_eglCreateWindowSurface;
   GLX_drv->Base.API.CreatePixmapSurface = GLX_eglCreatePixmapSurface;
   GLX_drv->Base.API.CreatePbufferSurface = GLX_eglCreatePbufferSurface;
   GLX_drv->Base.API.DestroySurface = GLX_eglDestroySurface;
   GLX_drv->Base.API.SwapBuffers = GLX_eglSwapBuffers;
   GLX_drv->Base.API.GetProcAddress = GLX_eglGetProcAddress;
   GLX_drv->Base.API.WaitClient = GLX_eglWaitClient;
   GLX_drv->Base.API.WaitNative = GLX_eglWaitNative;

   GLX_drv->Base.Name = "GLX";
   GLX_drv->Base.Unload = GLX_Unload;

   return &GLX_drv->Base;
}
