/* GLXPrivate.c Author: Patrick Baggett Created: 1/11/2013 Purpose: OpenGL on X Windows (GLX) utility code License: This program is free software. It comes without any warranty, to the extent permitted by applicable law. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See http://sam.zoy.org/wtfpl/COPYING for more details. */ #include #include "XlibPrivate.h" #include #include #include /* GL constants */ #define GL_VERSION 0x1F02 /* GLX constants */ #define GLX_DOUBLEBUFFER 5 #define GLX_STEREO 6 #define GLX_AUX_BUFFERS 7 #define GLX_RED_SIZE 8 #define GLX_GREEN_SIZE 9 #define GLX_BLUE_SIZE 10 #define GLX_ALPHA_SIZE 11 #define GLX_DEPTH_SIZE 12 #define GLX_STENCIL_SIZE 13 #define GLX_DRAWABLE_TYPE 0x8010 #define GLX_RGBA_TYPE 0x8014 #define GLX_WINDOW_BIT 0x00000001 #define GLX_PIXMAP_BIT 0x00000002 #define GLX_PBUFFER_BIT 0x00000004 /* GLX_ARB_multisample */ #define GLX_SAMPLE_BUFFERS_ARB 100000 #define GLX_SAMPLES_ARB 100001 #define GLX_PBUFFER_HEIGHT 0x8040 #define GLX_PBUFFER_WIDTH 0x8041 /* GLX_ARB_create_context[_profile] */ #define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 #define GLX_CONTEXT_FLAGS_ARB 0x2094 #define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 #define GLX_CONTEXT_DEBUG_BIT_ARB 0x0001 #define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 #define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 #define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 GLXFunctions glX = { 0 }; static void setupAttribs(int* glxAttr, const SST_OpenGLContextAttributes* attribs, Bool supportsMultisample); static int* addAttr(int* ptr, int attr, int value) { ptr[0] = attr; ptr[1] = value; return ptr + 2; } /******************************************************************************/ int loadGLX() { const int flags = RTLD_LAZY | RTLD_GLOBAL; void* libGL; /* Set all values to null/0/false */ memset(&glX, 0, sizeof(GLXFunctions)); /* Load OpenGL library with proper DT_SONAME */ libGL = dlopen("libGL.so.1", flags); if(libGL == NULL) { /* Failed...try generic name for it */ libGL = dlopen("libGL.so", flags); if(libGL == NULL) return 0; } /* Resolve symbols */ glX.QueryExtension = dlsym(libGL, "glXQueryExtension"); glX.QueryVersion = dlsym(libGL, "glXQueryVersion"); glX.QueryExtensionsString = dlsym(libGL, "glXQueryExtensionsString"); glX.ChooseFBConfig = dlsym(libGL, "glXChooseFBConfig"); glX.CreateNewContext = dlsym(libGL, "glXCreateNewContext"); glX.DestroyContext = dlsym(libGL, "glXDestroyContext"); glX.MakeContextCurrent = dlsym(libGL, "glXMakeContextCurrent"); glX.GetCurrentContext = dlsym(libGL, "glXGetCurrentContext"); glX.SwapBuffers = dlsym(libGL, "glXSwapBuffers"); glX.CreatePbuffer = dlsym(libGL, "glXCreatePbuffer"); glX.DestroyPbuffer = dlsym(libGL, "glXDestroyPbuffer"); /* On Linux, glXGetProcAddressARB() is statically exported from libGL.so as part of LSB. However, on non-LSB compliant or other UNIX systems, the -ARB version may not exist. Try the unsuffixed form if the -ARB variant does not exist. */ glX.GetProcAddressARB = dlsym(libGL, "glXGetProcAddressARB"); if(glX.GetProcAddressARB == NULL) glX.GetProcAddressARB = dlsym(libGL, "glXGetProcAddress"); /* We dont resolve glXCreateContextAttribsARB yet; first check GLX extension strings. */ glX.CreateContextAttribsARB = NULL; /* Save library handle */ glX.libGL = libGL; return 1; } /******************************************************************************/ void unloadGLX() { dlclose(glX.libGL); } /******************************************************************************/ int isGLXLoaded() { return (glX.libGL != NULL); } /******************************************************************************/ int isGLXSupported(Display* display) { int major = 0, minor = 0; int combined; const char* ext; /* Query if the X server supports GLX. If it doesn't, then nothing to do */ if(glX.QueryExtension(display, NULL, NULL) == False) { fprintf(stderr, "%s:GLX extension not supported on this connection.\n", "libsst-wm"); return 0; } /* Query the version; we need 1.3 or later */ if(glX.QueryVersion(display, &major, &minor) == False) { fprintf(stderr, "%s:Unable to query GLX version.\n", "libsst-wm"); return 0; } /* Fail on less than 1.3 */ combined = (major << 8) | minor; if(combined < 0x13) { fprintf(stderr, "%s:GLX version 1.3 required, found %d.%d\n", "libsst-wm", major, minor); return 0; } ext = glX.QueryExtensionsString(display, DefaultScreen(display)); /* "GLX_ARB_create_context" and "GLX_ARB_create_context_profile" might both match the first strstr(), but "GLX_ARB_create_context_profile" requires the former, so it's all good. TODO: write less stupid extension checker - one day may not be true */ if(combined >= 0x14) /* GLX 1.4 required for GLX_ARB_create_context */ { glX.supportsCreateContextARB = (Bool)(strstr(ext, "GLX_ARB_create_context") != NULL); glX.supportsProfiles = (Bool)(strstr(ext, "GLX_ARB_create_context_profile") != NULL); /* Supposedly GLX_ARB_create_context is supported. Try to resolve the symbol. If we can't, then backout. */ if(glX.supportsCreateContextARB) { glX.CreateContextAttribsARB = glX.GetProcAddressARB("glXCreateContextAttribsARB"); if(glX.CreateContextAttribsARB == NULL) { glX.supportsCreateContextARB = False; } } } else /* Don't have GLX 1.4, so can't possibly have these */ { glX.supportsCreateContextARB = False; glX.supportsProfiles = False; } glX.supportsMultisample = (Bool)(strstr(ext, "GLX_ARB_multisample") != NULL); return 1; } /******************************************************************************/ SST_OpenGLContext GLXCreateOpenGLContext(SST_DisplayTarget_Xlib* displayTarget, SST_WMOpenGLType apiType, const SST_OpenGLContextAttributes* attribs, SST_OpenGLContextAttributes* selectedAttribsReturn) { int attr[MAX_GL_ATTRS]; Display* display = displayTarget->display; GLXFBConfig* configs; GLXFBConfig fbconfig; GLXContext ctx; int nrConfigs = 0; SST_OpenGLContext_Xlib* GLctx; uint8_t major, minor; GLXPbuffer pb; const int pbattr[5] = { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, None }; /* GLX cannot be used to do anything other than standard OpenGL */ if(apiType != SSTGL_OPENGL) return NULL; /* GLX doesn't support multisampling, but it was explicitly requested */ if(!glX.supportsMultisample && attribs->multisampleFactor > 1) return NULL; /* Copy SST attributes to GLX attributes */ setupAttribs(attr, attribs, glX.supportsMultisample); /* Get a list of configs -- we're going to only use the first one though */ configs = glX.ChooseFBConfig(display, DefaultScreen(display), attr, &nrConfigs); if(configs == NULL) return NULL; /* Copy first FBConfig entry, free array */ fbconfig = *configs; X.Free(configs); /* Use the new create context functions? */ if(glX.supportsCreateContextARB) { int attr[32]; int* ptr = &attr[0]; int ctxFlags = 0; /* Set the major/minor version */ ptr = addAttr(ptr, GLX_CONTEXT_MAJOR_VERSION_ARB, attribs->contextVersionMajor); ptr = addAttr(ptr, GLX_CONTEXT_MINOR_VERSION_ARB, attribs->contextVersionMinor); /* If GLX_ARB_create_context_profile is supported, add a profile mask */ if(glX.supportsProfiles) ptr = addAttr(ptr, GLX_CONTEXT_PROFILE_MASK_ARB, (attribs->legacyEnabled ? GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : GLX_CONTEXT_CORE_PROFILE_BIT_ARB)); /* Enable/disable forward compatibility */ if(!attribs->legacyEnabled) ctxFlags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; /* Debug mode? */ if(attribs->debugEnabled) ctxFlags |= GLX_CONTEXT_DEBUG_BIT_ARB; ptr = addAttr(ptr, GLX_CONTEXT_FLAGS_ARB, ctxFlags); *ptr = None; ctx = glX.CreateContextAttribsARB(display, fbconfig, NULL, True, attr); if(ctx == NULL) return NULL; } else /* Use older GLX 1.3 glXCreateNewContext() function */ { /* Attempt to create an OpenGL context */ ctx = glX.CreateNewContext(display, fbconfig, GLX_RGBA_TYPE, NULL, True); if(ctx == NULL) return NULL; } /* In order to check the version number, we're going to need to create a temporary context. I'm using a Pbuffer to do so. */ pb = glX.CreatePbuffer(display, fbconfig, pbattr); if(pb == None) { glX.DestroyContext(display, ctx); return NULL; } /* Activate it */ if(glX.MakeContextCurrent(display, pb, pb, ctx)) { const char* (*pglGetString)(unsigned int); const char* version; /* Check libGL for this */ pglGetString = dlsym(glX.libGL, "glGetString"); version = pglGetString(GL_VERSION); major = (uint8_t)(version[0] - '0'); /* "X.Y" -> [0]:major, [2]:minor */ minor = (uint8_t)(version[2] - '0'); /* Save context version information */ if(selectedAttribsReturn != NULL) { selectedAttribsReturn->contextVersionMajor = (uint8_t)major; selectedAttribsReturn->contextVersionMinor = (uint8_t)minor; } /* OK, we have the information we need. Unbind the context and destroy the Pbuffer */ glX.MakeContextCurrent(display, None, None, NULL); glX.DestroyPbuffer(display, pb); /* If the major version is too low or the major version is OK, but the minor version is lacking, then fail */ if(major < attribs->contextVersionMajor || (major == attribs->contextVersionMajor && minor < attribs->contextVersionMinor)) { glX.DestroyContext(display, ctx); return NULL; } } else /* Failed to make current */ { glX.DestroyContext(display, ctx); glX.DestroyPbuffer(display, pb); return NULL; } GLctx = (SST_OpenGLContext_Xlib*)malloc(sizeof(SST_OpenGLContext_Xlib)); if(GLctx == NULL) { glX.DestroyContext(display, ctx); return NULL; } GLctx->glxcontext = ctx; GLctx->displayTarget = displayTarget; GLctx->ctxVersion[0] = major; GLctx->ctxVersion[1] = minor; return GLctx; } /******************************************************************************/ static void setupAttribs(int* glxAttr, const SST_OpenGLContextAttributes* attribs, Bool supportsMultisample) { int* ptr = glxAttr; int r, b, g; switch(attribs->colorBits) { /* Since these values are "at least", 5-5-5 and 5-6-5 configurations can use the same number */ case 15: case 16: r = g = b = 5; break; case 24: r = g = b = 8; } ptr = addAttr(ptr, GLX_DOUBLEBUFFER, True); ptr = addAttr(ptr, GLX_STEREO, (attribs-> stereoEnabled != 0)); ptr = addAttr(ptr, GLX_RED_SIZE, r); ptr = addAttr(ptr, GLX_GREEN_SIZE, r); ptr = addAttr(ptr, GLX_BLUE_SIZE, b); ptr = addAttr(ptr, GLX_ALPHA_SIZE, ((int)attribs->alphaBits) & 0xFF); ptr = addAttr(ptr, GLX_DEPTH_SIZE, ((int)attribs->depthBits) & 0xFF); ptr = addAttr(ptr, GLX_STENCIL_SIZE, ((int)attribs->stencilBits) & 0xFF); /* Add multisample attributes as appropriate */ if(attribs->multisampleFactor > 1 && supportsMultisample) { ptr = addAttr(ptr, GLX_SAMPLE_BUFFERS_ARB, 1); ptr = addAttr(ptr, GLX_SAMPLES_ARB, ((int)attribs->multisampleFactor) & 0xFF); } /* End attribute list */ *ptr = None; }