0,0 → 1,1182 |
/************************************************************************** |
* |
* 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; |
} |