/* $Id: toglGLX.c,v 1.12 2009/10/22 20:40:52 gregcouch Exp $ */ /* vi:set sw=4 expandtab: */ /* * Togl - a Tk OpenGL widget * * Copyright (C) 1996-2002 Brian Paul and Ben Bederson * Copyright (C) 2005-2009 Greg Couch * See the LICENSE file for copyright details. */ /* TODO: fullscreen support */ #undef DEBUG_GLX static PFNGLXCHOOSEFBCONFIGPROC chooseFBConfig = NULL; static PFNGLXGETFBCONFIGATTRIBPROC getFBConfigAttrib = NULL; static PFNGLXGETVISUALFROMFBCONFIGPROC getVisualFromFBConfig = NULL; static PFNGLXCREATEPBUFFERPROC createPbuffer = NULL; static PFNGLXCREATEGLXPBUFFERSGIXPROC createPbufferSGIX = NULL; static PFNGLXDESTROYPBUFFERPROC destroyPbuffer = NULL; static PFNGLXQUERYDRAWABLEPROC queryPbuffer = NULL; static Bool hasMultisampling = False; static Bool hasPbuffer = False; struct FBInfo { int acceleration; int samples; int depth; int colors; GLXFBConfig fbcfg; XVisualInfo *visInfo; }; typedef struct FBInfo FBInfo; static int FBInfoCmp(const void *a, const void *b) { /* * 1. full acceleration is better * 2. greater color bits is better * 3. greater depth bits is better * 4. more multisampling is better */ const FBInfo *x = (const FBInfo *) a; const FBInfo *y = (const FBInfo *) b; if (x->acceleration != y->acceleration) return y->acceleration - x->acceleration; if (x->colors != y->colors) return y->colors - x->colors; if (x->depth != y->depth) return y->depth - x->depth; if (x->samples != y->samples) return y->samples - x->samples; return 0; } #ifdef DEBUG_GLX static int fatal_error(Display *dpy, XErrorEvent * event) { char buf[256]; XGetErrorText(dpy, event->error_code, buf, sizeof buf); fprintf(stderr, "%s\n", buf); abort(); } #endif static XVisualInfo * togl_pixelFormat(Togl *togl, int scrnum) { int attribs[256]; int na = 0; int i; XVisualInfo *visinfo; FBInfo *info; static int loadedOpenGL = False; if (!loadedOpenGL) { int dummy; int major, minor; const char *extensions; /* Make sure OpenGL's GLX extension supported */ if (!glXQueryExtension(togl->display, &dummy, &dummy)) { Tcl_SetResult(togl->Interp, TCL_STUPID "X server is missing OpenGL GLX extension", TCL_STATIC); return NULL; } loadedOpenGL = True; #ifdef DEBUG_GLX (void) XSetErrorHandler(fatal_error); #endif glXQueryVersion(togl->display, &major, &minor); extensions = glXQueryExtensionsString(togl->display, scrnum); if (major > 1 || (major == 1 && minor >= 3)) { chooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC) Togl_GetProcAddr("glXChooseFBConfig"); getFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) Togl_GetProcAddr("glXGetFBConfigAttrib"); getVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) Togl_GetProcAddr("glXGetVisualFromFBConfig"); createPbuffer = (PFNGLXCREATEPBUFFERPROC) Togl_GetProcAddr("glXCreatePbuffer"); destroyPbuffer = (PFNGLXDESTROYPBUFFERPROC) Togl_GetProcAddr("glXDestroyPbuffer"); queryPbuffer = (PFNGLXQUERYDRAWABLEPROC) Togl_GetProcAddr("glXQueryDrawable"); if (createPbuffer && destroyPbuffer && queryPbuffer) { hasPbuffer = True; } else { createPbuffer = NULL; destroyPbuffer = NULL; queryPbuffer = NULL; } } if (major == 1 && minor == 2) { chooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC) Togl_GetProcAddr("glXChooseFBConfigSGIX"); getFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) Togl_GetProcAddr("glXGetFBConfigAttribSGIX"); getVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) Togl_GetProcAddr("glXGetVisualFromFBConfigSGIX"); if (strstr(extensions, "GLX_SGIX_pbuffer") != NULL) { createPbufferSGIX = (PFNGLXCREATEGLXPBUFFERSGIXPROC) Togl_GetProcAddr("glXCreateGLXPbufferSGIX"); destroyPbuffer = (PFNGLXDESTROYPBUFFERPROC) Togl_GetProcAddr("glXDestroyGLXPbufferSGIX"); queryPbuffer = (PFNGLXQUERYDRAWABLEPROC) Togl_GetProcAddr("glXQueryGLXPbufferSGIX"); if (createPbufferSGIX && destroyPbuffer && queryPbuffer) { hasPbuffer = True; } else { createPbufferSGIX = NULL; destroyPbuffer = NULL; queryPbuffer = NULL; } } } if (chooseFBConfig) { /* verify that chooseFBConfig works (workaround Mesa 6.5 bug) */ int n = 0; GLXFBConfig *cfgs; attribs[n++] = GLX_RENDER_TYPE; attribs[n++] = GLX_RGBA_BIT; attribs[n++] = None; cfgs = chooseFBConfig(togl->display, scrnum, attribs, &n); if (cfgs == NULL || n == 0) { chooseFBConfig = NULL; } XFree(cfgs); } if (chooseFBConfig == NULL || getFBConfigAttrib == NULL || getVisualFromFBConfig == NULL) { chooseFBConfig = NULL; getFBConfigAttrib = NULL; getVisualFromFBConfig = NULL; } if (hasPbuffer && !chooseFBConfig) { hasPbuffer = False; } if ((major > 1 || (major == 1 && minor >= 4)) || strstr(extensions, "GLX_ARB_multisample") != NULL || strstr(extensions, "GLX_SGIS_multisample") != NULL) { /* Client GLX supports multisampling, but does the server? Well, we * can always ask. */ hasMultisampling = True; } } if (togl->MultisampleFlag && !hasMultisampling) { Tcl_SetResult(togl->Interp, TCL_STUPID "multisampling not supported", TCL_STATIC); return NULL; } if (togl->PbufferFlag && !hasPbuffer) { Tcl_SetResult(togl->Interp, TCL_STUPID "pbuffers are not supported", TCL_STATIC); return NULL; } /* * Only use the newer glXGetFBConfig if there's an explicit need for it * because it is buggy on many systems: * (1) NVidia 96.43.07 on Linux, single-buffered windows don't work * (2) Mesa 6.5.X and earlier fail */ if (chooseFBConfig) { /* have new glXGetFBConfig! */ int count; GLXFBConfig *cfgs; attribs[na++] = GLX_RENDER_TYPE; if (togl->RgbaFlag) { /* RGB[A] mode */ attribs[na++] = GLX_RGBA_BIT; attribs[na++] = GLX_RED_SIZE; attribs[na++] = togl->RgbaRed; attribs[na++] = GLX_GREEN_SIZE; attribs[na++] = togl->RgbaGreen; attribs[na++] = GLX_BLUE_SIZE; attribs[na++] = togl->RgbaBlue; if (togl->AlphaFlag) { attribs[na++] = GLX_ALPHA_SIZE; attribs[na++] = togl->AlphaSize; } } else { /* Color index mode */ attribs[na++] = GLX_COLOR_INDEX_BIT; attribs[na++] = GLX_BUFFER_SIZE; attribs[na++] = 1; } if (togl->DepthFlag) { attribs[na++] = GLX_DEPTH_SIZE; attribs[na++] = togl->DepthSize; } if (togl->DoubleFlag) { attribs[na++] = GLX_DOUBLEBUFFER; attribs[na++] = True; } if (togl->StencilFlag) { attribs[na++] = GLX_STENCIL_SIZE; attribs[na++] = togl->StencilSize; } if (togl->AccumFlag) { attribs[na++] = GLX_ACCUM_RED_SIZE; attribs[na++] = togl->AccumRed; attribs[na++] = GLX_ACCUM_GREEN_SIZE; attribs[na++] = togl->AccumGreen; attribs[na++] = GLX_ACCUM_BLUE_SIZE; attribs[na++] = togl->AccumBlue; if (togl->AlphaFlag) { attribs[na++] = GLX_ACCUM_ALPHA_SIZE; attribs[na++] = togl->AccumAlpha; } } if (togl->Stereo == TOGL_STEREO_NATIVE) { attribs[na++] = GLX_STEREO; attribs[na++] = True; } if (togl->MultisampleFlag) { attribs[na++] = GLX_SAMPLE_BUFFERS_ARB; attribs[na++] = 1; attribs[na++] = GLX_SAMPLES_ARB; attribs[na++] = 2; } if (togl->PbufferFlag) { attribs[na++] = GLX_DRAWABLE_TYPE; attribs[na++] = GLX_WINDOW_BIT | GLX_PBUFFER_BIT; } if (togl->AuxNumber != 0) { attribs[na++] = GLX_AUX_BUFFERS; attribs[na++] = togl->AuxNumber; } attribs[na++] = None; cfgs = chooseFBConfig(togl->display, scrnum, attribs, &count); if (cfgs == NULL || count == 0) { Tcl_SetResult(togl->Interp, TCL_STUPID "couldn't choose pixel format", TCL_STATIC); return NULL; } /* * Pick best format */ info = (FBInfo *) malloc(count * sizeof (FBInfo)); for (i = 0; i != count; ++i) { info[i].visInfo = getVisualFromFBConfig(togl->display, cfgs[i]); info[i].fbcfg = cfgs[i]; getFBConfigAttrib(togl->display, cfgs[i], GLX_CONFIG_CAVEAT, &info[i].acceleration); getFBConfigAttrib(togl->display, cfgs[i], GLX_BUFFER_SIZE, &info[i].colors); getFBConfigAttrib(togl->display, cfgs[i], GLX_DEPTH_SIZE, &info[i].depth); getFBConfigAttrib(togl->display, cfgs[i], GLX_SAMPLES, &info[i].samples); /* revise attributes so larger is better */ info[i].acceleration = -(info[i].acceleration - GLX_NONE); if (!togl->DepthFlag) info[i].depth = -info[i].depth; if (!togl->MultisampleFlag) info[i].samples = -info[i].samples; } qsort(info, count, sizeof info[0], FBInfoCmp); togl->fbcfg = info[0].fbcfg; visinfo = info[0].visInfo; for (i = 1; i != count; ++i) XFree(info[i].visInfo); free(info); XFree(cfgs); return visinfo; } /* use original glXChooseVisual */ attribs[na++] = GLX_USE_GL; if (togl->RgbaFlag) { /* RGB[A] mode */ attribs[na++] = GLX_RGBA; attribs[na++] = GLX_RED_SIZE; attribs[na++] = togl->RgbaRed; attribs[na++] = GLX_GREEN_SIZE; attribs[na++] = togl->RgbaGreen; attribs[na++] = GLX_BLUE_SIZE; attribs[na++] = togl->RgbaBlue; if (togl->AlphaFlag) { attribs[na++] = GLX_ALPHA_SIZE; attribs[na++] = togl->AlphaSize; } } else { /* Color index mode */ attribs[na++] = GLX_BUFFER_SIZE; attribs[na++] = 1; } if (togl->DepthFlag) { attribs[na++] = GLX_DEPTH_SIZE; attribs[na++] = togl->DepthSize; } if (togl->DoubleFlag) { attribs[na++] = GLX_DOUBLEBUFFER; } if (togl->StencilFlag) { attribs[na++] = GLX_STENCIL_SIZE; attribs[na++] = togl->StencilSize; } if (togl->AccumFlag) { attribs[na++] = GLX_ACCUM_RED_SIZE; attribs[na++] = togl->AccumRed; attribs[na++] = GLX_ACCUM_GREEN_SIZE; attribs[na++] = togl->AccumGreen; attribs[na++] = GLX_ACCUM_BLUE_SIZE; attribs[na++] = togl->AccumBlue; if (togl->AlphaFlag) { attribs[na++] = GLX_ACCUM_ALPHA_SIZE; attribs[na++] = togl->AccumAlpha; } } if (togl->Stereo == TOGL_STEREO_NATIVE) { attribs[na++] = GLX_STEREO; } if (togl->AuxNumber != 0) { attribs[na++] = GLX_AUX_BUFFERS; attribs[na++] = togl->AuxNumber; } attribs[na++] = None; visinfo = glXChooseVisual(togl->display, scrnum, attribs); if (visinfo == NULL) { Tcl_SetResult(togl->Interp, TCL_STUPID "couldn't choose pixel format", TCL_STATIC); return NULL; } return visinfo; } static int togl_describePixelFormat(Togl *togl) { int tmp = 0; /* fill in flags normally passed in that affect behavior */ (void) glXGetConfig(togl->display, togl->VisInfo, GLX_RGBA, &togl->RgbaFlag); (void) glXGetConfig(togl->display, togl->VisInfo, GLX_DOUBLEBUFFER, &togl->DoubleFlag); (void) glXGetConfig(togl->display, togl->VisInfo, GLX_DEPTH_SIZE, &tmp); togl->DepthFlag = (tmp != 0); (void) glXGetConfig(togl->display, togl->VisInfo, GLX_ACCUM_RED_SIZE, &tmp); togl->AccumFlag = (tmp != 0); (void) glXGetConfig(togl->display, togl->VisInfo, GLX_ALPHA_SIZE, &tmp); togl->AlphaFlag = (tmp != 0); (void) glXGetConfig(togl->display, togl->VisInfo, GLX_STENCIL_SIZE, &tmp); togl->StencilFlag = (tmp != 0); (void) glXGetConfig(togl->display, togl->VisInfo, GLX_STEREO, &tmp); togl->Stereo = tmp ? TOGL_STEREO_NATIVE : TOGL_STEREO_NONE; if (hasMultisampling) { (void) glXGetConfig(togl->display, togl->VisInfo, GLX_SAMPLES, &tmp); togl->MultisampleFlag = (tmp != 0); } return True; } static Tcl_ThreadDataKey togl_XError; struct ErrorData { int error_code; XErrorHandler prevHandler; }; typedef struct ErrorData ErrorData; static int togl_HandleXError(Display *dpy, XErrorEvent * event) { ErrorData *data = Tcl_GetThreadData(&togl_XError, (int) sizeof (ErrorData)); data->error_code = event->error_code; return 0; } static void togl_SetupXErrorHandler() { ErrorData *data = Tcl_GetThreadData(&togl_XError, (int) sizeof (ErrorData)); data->error_code = Success; /* 0 */ data->prevHandler = XSetErrorHandler(togl_HandleXError); } static int togl_CheckForXError(const Togl *togl) { ErrorData *data = Tcl_GetThreadData(&togl_XError, (int) sizeof (ErrorData)); XSync(togl->display, False); (void) XSetErrorHandler(data->prevHandler); return data->error_code; } static GLXPbuffer togl_createPbuffer(Togl *togl) { int attribs[32]; int na = 0; GLXPbuffer pbuf; togl_SetupXErrorHandler(); if (togl->LargestPbufferFlag) { attribs[na++] = GLX_LARGEST_PBUFFER; attribs[na++] = True; } attribs[na++] = GLX_PRESERVED_CONTENTS; attribs[na++] = True; if (createPbuffer) { attribs[na++] = GLX_PBUFFER_WIDTH; attribs[na++] = togl->Width; attribs[na++] = GLX_PBUFFER_HEIGHT; attribs[na++] = togl->Width; attribs[na++] = None; pbuf = createPbuffer(togl->display, togl->fbcfg, attribs); } else { attribs[na++] = None; pbuf = createPbufferSGIX(togl->display, togl->fbcfg, togl->Width, togl->Height, attribs); } if (togl_CheckForXError(togl) || pbuf == None) { Tcl_SetResult(togl->Interp, TCL_STUPID "unable to allocate pbuffer", TCL_STATIC); return None; } if (pbuf && togl->LargestPbufferFlag) { int tmp; queryPbuffer(togl->display, pbuf, GLX_WIDTH, &tmp); if (tmp != 0) togl->Width = tmp; queryPbuffer(togl->display, pbuf, GLX_HEIGHT, &tmp); if (tmp != 0) togl->Height = tmp; } return pbuf; } static void togl_destroyPbuffer(Togl *togl) { destroyPbuffer(togl->display, togl->pbuf); }