1 /* $Id: toglGLX.c,v 1.12 2009/10/22 20:40:52 gregcouch Exp $ */
2 
3 /* vi:set sw=4 expandtab: */
4 
5 /*
6  * Togl - a Tk OpenGL widget
7  *
8  * Copyright (C) 1996-2002  Brian Paul and Ben Bederson
9  * Copyright (C) 2005-2009  Greg Couch
10  * See the LICENSE file for copyright details.
11  */
12 
13 /* TODO: fullscreen support */
14 
15 #undef DEBUG_GLX
16 
17 static PFNGLXCHOOSEFBCONFIGPROC chooseFBConfig = NULL;
18 static PFNGLXGETFBCONFIGATTRIBPROC getFBConfigAttrib = NULL;
19 static PFNGLXGETVISUALFROMFBCONFIGPROC getVisualFromFBConfig = NULL;
20 static PFNGLXCREATEPBUFFERPROC createPbuffer = NULL;
21 static PFNGLXCREATEGLXPBUFFERSGIXPROC createPbufferSGIX = NULL;
22 static PFNGLXDESTROYPBUFFERPROC destroyPbuffer = NULL;
23 static PFNGLXQUERYDRAWABLEPROC queryPbuffer = NULL;
24 static Bool hasMultisampling = False;
25 static Bool hasPbuffer = False;
26 
27 struct FBInfo
28 {
29     int     acceleration;
30     int     samples;
31     int     depth;
32     int     colors;
33     GLXFBConfig fbcfg;
34     XVisualInfo *visInfo;
35 };
36 typedef struct FBInfo FBInfo;
37 
38 static int
FBInfoCmp(const void * a,const void * b)39 FBInfoCmp(const void *a, const void *b)
40 {
41     /*
42      * 1. full acceleration is better
43      * 2. greater color bits is better
44      * 3. greater depth bits is better
45      * 4. more multisampling is better
46      */
47     const FBInfo *x = (const FBInfo *) a;
48     const FBInfo *y = (const FBInfo *) b;
49 
50     if (x->acceleration != y->acceleration)
51         return y->acceleration - x->acceleration;
52     if (x->colors != y->colors)
53         return y->colors - x->colors;
54     if (x->depth != y->depth)
55         return y->depth - x->depth;
56     if (x->samples != y->samples)
57         return y->samples - x->samples;
58     return 0;
59 }
60 
61 #ifdef DEBUG_GLX
62 static int
fatal_error(Display * dpy,XErrorEvent * event)63 fatal_error(Display *dpy, XErrorEvent * event)
64 {
65     char    buf[256];
66 
67     XGetErrorText(dpy, event->error_code, buf, sizeof buf);
68     fprintf(stderr, "%s\n", buf);
69     abort();
70 }
71 #endif
72 
73 static XVisualInfo *
togl_pixelFormat(Togl * togl,int scrnum)74 togl_pixelFormat(Togl *togl, int scrnum)
75 {
76     int     attribs[256];
77     int     na = 0;
78     int     i;
79     XVisualInfo *visinfo;
80     FBInfo *info;
81     static int loadedOpenGL = False;
82 
83     if (!loadedOpenGL) {
84         int     dummy;
85         int     major, minor;
86         const char *extensions;
87 
88         /* Make sure OpenGL's GLX extension supported */
89         if (!glXQueryExtension(togl->display, &dummy, &dummy)) {
90             Tcl_SetResult(togl->Interp,
91                     TCL_STUPID "X server is missing OpenGL GLX extension",
92                     TCL_STATIC);
93             return NULL;
94         }
95 
96         loadedOpenGL = True;
97 #ifdef DEBUG_GLX
98         (void) XSetErrorHandler(fatal_error);
99 #endif
100 
101         glXQueryVersion(togl->display, &major, &minor);
102         extensions = glXQueryExtensionsString(togl->display, scrnum);
103 
104         if (major > 1 || (major == 1 && minor >= 3)) {
105             chooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC)
106                     Togl_GetProcAddr("glXChooseFBConfig");
107             getFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC)
108                     Togl_GetProcAddr("glXGetFBConfigAttrib");
109             getVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC)
110                     Togl_GetProcAddr("glXGetVisualFromFBConfig");
111             createPbuffer = (PFNGLXCREATEPBUFFERPROC)
112                     Togl_GetProcAddr("glXCreatePbuffer");
113             destroyPbuffer = (PFNGLXDESTROYPBUFFERPROC)
114                     Togl_GetProcAddr("glXDestroyPbuffer");
115             queryPbuffer = (PFNGLXQUERYDRAWABLEPROC)
116                     Togl_GetProcAddr("glXQueryDrawable");
117             if (createPbuffer && destroyPbuffer && queryPbuffer) {
118                 hasPbuffer = True;
119             } else {
120                 createPbuffer = NULL;
121                 destroyPbuffer = NULL;
122                 queryPbuffer = NULL;
123             }
124         }
125         if (major == 1 && minor == 2) {
126             chooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC)
127                     Togl_GetProcAddr("glXChooseFBConfigSGIX");
128             getFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC)
129                     Togl_GetProcAddr("glXGetFBConfigAttribSGIX");
130             getVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC)
131                     Togl_GetProcAddr("glXGetVisualFromFBConfigSGIX");
132             if (strstr(extensions, "GLX_SGIX_pbuffer") != NULL) {
133                 createPbufferSGIX = (PFNGLXCREATEGLXPBUFFERSGIXPROC)
134                         Togl_GetProcAddr("glXCreateGLXPbufferSGIX");
135                 destroyPbuffer = (PFNGLXDESTROYPBUFFERPROC)
136                         Togl_GetProcAddr("glXDestroyGLXPbufferSGIX");
137                 queryPbuffer = (PFNGLXQUERYDRAWABLEPROC)
138                         Togl_GetProcAddr("glXQueryGLXPbufferSGIX");
139                 if (createPbufferSGIX && destroyPbuffer && queryPbuffer) {
140                     hasPbuffer = True;
141                 } else {
142                     createPbufferSGIX = NULL;
143                     destroyPbuffer = NULL;
144                     queryPbuffer = NULL;
145                 }
146             }
147         }
148         if (chooseFBConfig) {
149             /* verify that chooseFBConfig works (workaround Mesa 6.5 bug) */
150             int     n = 0;
151             GLXFBConfig *cfgs;
152 
153             attribs[n++] = GLX_RENDER_TYPE;
154             attribs[n++] = GLX_RGBA_BIT;
155             attribs[n++] = None;
156 
157             cfgs = chooseFBConfig(togl->display, scrnum, attribs, &n);
158             if (cfgs == NULL || n == 0) {
159                 chooseFBConfig = NULL;
160             }
161             XFree(cfgs);
162         }
163         if (chooseFBConfig == NULL
164                 || getFBConfigAttrib == NULL || getVisualFromFBConfig == NULL) {
165             chooseFBConfig = NULL;
166             getFBConfigAttrib = NULL;
167             getVisualFromFBConfig = NULL;
168         }
169         if (hasPbuffer && !chooseFBConfig) {
170             hasPbuffer = False;
171         }
172 
173         if ((major > 1 || (major == 1 && minor >= 4))
174                 || strstr(extensions, "GLX_ARB_multisample") != NULL
175                 || strstr(extensions, "GLX_SGIS_multisample") != NULL) {
176             /* Client GLX supports multisampling, but does the server? Well, we
177              * can always ask. */
178             hasMultisampling = True;
179         }
180     }
181 
182     if (togl->MultisampleFlag && !hasMultisampling) {
183         Tcl_SetResult(togl->Interp,
184                 TCL_STUPID "multisampling not supported", TCL_STATIC);
185         return NULL;
186     }
187 
188     if (togl->PbufferFlag && !hasPbuffer) {
189         Tcl_SetResult(togl->Interp,
190                 TCL_STUPID "pbuffers are not supported", TCL_STATIC);
191         return NULL;
192     }
193 
194     /*
195      * Only use the newer glXGetFBConfig if there's an explicit need for it
196      * because it is buggy on many systems:
197      *  (1) NVidia 96.43.07 on Linux, single-buffered windows don't work
198      *  (2) Mesa 6.5.X and earlier fail
199      */
200     if (chooseFBConfig) {
201         /* have new glXGetFBConfig! */
202         int     count;
203         GLXFBConfig *cfgs;
204 
205         attribs[na++] = GLX_RENDER_TYPE;
206         if (togl->RgbaFlag) {
207             /* RGB[A] mode */
208             attribs[na++] = GLX_RGBA_BIT;
209             attribs[na++] = GLX_RED_SIZE;
210             attribs[na++] = togl->RgbaRed;
211             attribs[na++] = GLX_GREEN_SIZE;
212             attribs[na++] = togl->RgbaGreen;
213             attribs[na++] = GLX_BLUE_SIZE;
214             attribs[na++] = togl->RgbaBlue;
215             if (togl->AlphaFlag) {
216                 attribs[na++] = GLX_ALPHA_SIZE;
217                 attribs[na++] = togl->AlphaSize;
218             }
219         } else {
220             /* Color index mode */
221             attribs[na++] = GLX_COLOR_INDEX_BIT;
222             attribs[na++] = GLX_BUFFER_SIZE;
223             attribs[na++] = 1;
224         }
225         if (togl->DepthFlag) {
226             attribs[na++] = GLX_DEPTH_SIZE;
227             attribs[na++] = togl->DepthSize;
228         }
229         if (togl->DoubleFlag) {
230             attribs[na++] = GLX_DOUBLEBUFFER;
231             attribs[na++] = True;
232         }
233         if (togl->StencilFlag) {
234             attribs[na++] = GLX_STENCIL_SIZE;
235             attribs[na++] = togl->StencilSize;
236         }
237         if (togl->AccumFlag) {
238             attribs[na++] = GLX_ACCUM_RED_SIZE;
239             attribs[na++] = togl->AccumRed;
240             attribs[na++] = GLX_ACCUM_GREEN_SIZE;
241             attribs[na++] = togl->AccumGreen;
242             attribs[na++] = GLX_ACCUM_BLUE_SIZE;
243             attribs[na++] = togl->AccumBlue;
244             if (togl->AlphaFlag) {
245                 attribs[na++] = GLX_ACCUM_ALPHA_SIZE;
246                 attribs[na++] = togl->AccumAlpha;
247             }
248         }
249         if (togl->Stereo == TOGL_STEREO_NATIVE) {
250             attribs[na++] = GLX_STEREO;
251             attribs[na++] = True;
252         }
253         if (togl->MultisampleFlag) {
254             attribs[na++] = GLX_SAMPLE_BUFFERS_ARB;
255             attribs[na++] = 1;
256             attribs[na++] = GLX_SAMPLES_ARB;
257             attribs[na++] = 2;
258         }
259         if (togl->PbufferFlag) {
260             attribs[na++] = GLX_DRAWABLE_TYPE;
261             attribs[na++] = GLX_WINDOW_BIT | GLX_PBUFFER_BIT;
262         }
263         if (togl->AuxNumber != 0) {
264             attribs[na++] = GLX_AUX_BUFFERS;
265             attribs[na++] = togl->AuxNumber;
266         }
267         attribs[na++] = None;
268 
269         cfgs = chooseFBConfig(togl->display, scrnum, attribs, &count);
270         if (cfgs == NULL || count == 0) {
271             Tcl_SetResult(togl->Interp,
272                     TCL_STUPID "couldn't choose pixel format", TCL_STATIC);
273             return NULL;
274         }
275         /*
276          * Pick best format
277          */
278         info = (FBInfo *) malloc(count * sizeof (FBInfo));
279         for (i = 0; i != count; ++i) {
280             info[i].visInfo = getVisualFromFBConfig(togl->display, cfgs[i]);
281             info[i].fbcfg = cfgs[i];
282             getFBConfigAttrib(togl->display, cfgs[i], GLX_CONFIG_CAVEAT,
283                     &info[i].acceleration);
284             getFBConfigAttrib(togl->display, cfgs[i], GLX_BUFFER_SIZE,
285                     &info[i].colors);
286             getFBConfigAttrib(togl->display, cfgs[i], GLX_DEPTH_SIZE,
287                     &info[i].depth);
288             getFBConfigAttrib(togl->display, cfgs[i], GLX_SAMPLES,
289                     &info[i].samples);
290             /* revise attributes so larger is better */
291             info[i].acceleration = -(info[i].acceleration - GLX_NONE);
292             if (!togl->DepthFlag)
293                 info[i].depth = -info[i].depth;
294             if (!togl->MultisampleFlag)
295                 info[i].samples = -info[i].samples;
296         }
297         qsort(info, count, sizeof info[0], FBInfoCmp);
298 
299         togl->fbcfg = info[0].fbcfg;
300         visinfo = info[0].visInfo;
301         for (i = 1; i != count; ++i)
302             XFree(info[i].visInfo);
303         free(info);
304         XFree(cfgs);
305         return visinfo;
306     }
307 
308     /* use original glXChooseVisual */
309 
310     attribs[na++] = GLX_USE_GL;
311     if (togl->RgbaFlag) {
312         /* RGB[A] mode */
313         attribs[na++] = GLX_RGBA;
314         attribs[na++] = GLX_RED_SIZE;
315         attribs[na++] = togl->RgbaRed;
316         attribs[na++] = GLX_GREEN_SIZE;
317         attribs[na++] = togl->RgbaGreen;
318         attribs[na++] = GLX_BLUE_SIZE;
319         attribs[na++] = togl->RgbaBlue;
320         if (togl->AlphaFlag) {
321             attribs[na++] = GLX_ALPHA_SIZE;
322             attribs[na++] = togl->AlphaSize;
323         }
324     } else {
325         /* Color index mode */
326         attribs[na++] = GLX_BUFFER_SIZE;
327         attribs[na++] = 1;
328     }
329     if (togl->DepthFlag) {
330         attribs[na++] = GLX_DEPTH_SIZE;
331         attribs[na++] = togl->DepthSize;
332     }
333     if (togl->DoubleFlag) {
334         attribs[na++] = GLX_DOUBLEBUFFER;
335     }
336     if (togl->StencilFlag) {
337         attribs[na++] = GLX_STENCIL_SIZE;
338         attribs[na++] = togl->StencilSize;
339     }
340     if (togl->AccumFlag) {
341         attribs[na++] = GLX_ACCUM_RED_SIZE;
342         attribs[na++] = togl->AccumRed;
343         attribs[na++] = GLX_ACCUM_GREEN_SIZE;
344         attribs[na++] = togl->AccumGreen;
345         attribs[na++] = GLX_ACCUM_BLUE_SIZE;
346         attribs[na++] = togl->AccumBlue;
347         if (togl->AlphaFlag) {
348             attribs[na++] = GLX_ACCUM_ALPHA_SIZE;
349             attribs[na++] = togl->AccumAlpha;
350         }
351     }
352     if (togl->Stereo == TOGL_STEREO_NATIVE) {
353         attribs[na++] = GLX_STEREO;
354     }
355     if (togl->AuxNumber != 0) {
356         attribs[na++] = GLX_AUX_BUFFERS;
357         attribs[na++] = togl->AuxNumber;
358     }
359     attribs[na++] = None;
360 
361     visinfo = glXChooseVisual(togl->display, scrnum, attribs);
362     if (visinfo == NULL) {
363         Tcl_SetResult(togl->Interp,
364                 TCL_STUPID "couldn't choose pixel format", TCL_STATIC);
365         return NULL;
366     }
367     return visinfo;
368 }
369 
370 static int
togl_describePixelFormat(Togl * togl)371 togl_describePixelFormat(Togl *togl)
372 {
373     int     tmp = 0;
374 
375     /* fill in flags normally passed in that affect behavior */
376     (void) glXGetConfig(togl->display, togl->VisInfo, GLX_RGBA,
377             &togl->RgbaFlag);
378     (void) glXGetConfig(togl->display, togl->VisInfo, GLX_DOUBLEBUFFER,
379             &togl->DoubleFlag);
380     (void) glXGetConfig(togl->display, togl->VisInfo, GLX_DEPTH_SIZE, &tmp);
381     togl->DepthFlag = (tmp != 0);
382     (void) glXGetConfig(togl->display, togl->VisInfo, GLX_ACCUM_RED_SIZE, &tmp);
383     togl->AccumFlag = (tmp != 0);
384     (void) glXGetConfig(togl->display, togl->VisInfo, GLX_ALPHA_SIZE, &tmp);
385     togl->AlphaFlag = (tmp != 0);
386     (void) glXGetConfig(togl->display, togl->VisInfo, GLX_STENCIL_SIZE, &tmp);
387     togl->StencilFlag = (tmp != 0);
388     (void) glXGetConfig(togl->display, togl->VisInfo, GLX_STEREO, &tmp);
389     togl->Stereo = tmp ? TOGL_STEREO_NATIVE : TOGL_STEREO_NONE;
390     if (hasMultisampling) {
391         (void) glXGetConfig(togl->display, togl->VisInfo, GLX_SAMPLES, &tmp);
392         togl->MultisampleFlag = (tmp != 0);
393     }
394     return True;
395 }
396 
397 static Tcl_ThreadDataKey togl_XError;
398 struct ErrorData
399 {
400     int     error_code;
401     XErrorHandler prevHandler;
402 };
403 typedef struct ErrorData ErrorData;
404 
405 static int
togl_HandleXError(Display * dpy,XErrorEvent * event)406 togl_HandleXError(Display *dpy, XErrorEvent * event)
407 {
408     ErrorData *data = Tcl_GetThreadData(&togl_XError, (int) sizeof (ErrorData));
409 
410     data->error_code = event->error_code;
411     return 0;
412 }
413 
414 static void
togl_SetupXErrorHandler()415 togl_SetupXErrorHandler()
416 {
417     ErrorData *data = Tcl_GetThreadData(&togl_XError, (int) sizeof (ErrorData));
418 
419     data->error_code = Success; /* 0 */
420     data->prevHandler = XSetErrorHandler(togl_HandleXError);
421 }
422 
423 static int
togl_CheckForXError(const Togl * togl)424 togl_CheckForXError(const Togl *togl)
425 {
426     ErrorData *data = Tcl_GetThreadData(&togl_XError, (int) sizeof (ErrorData));
427 
428     XSync(togl->display, False);
429     (void) XSetErrorHandler(data->prevHandler);
430     return data->error_code;
431 }
432 
433 static GLXPbuffer
togl_createPbuffer(Togl * togl)434 togl_createPbuffer(Togl *togl)
435 {
436     int     attribs[32];
437     int     na = 0;
438     GLXPbuffer pbuf;
439 
440     togl_SetupXErrorHandler();
441     if (togl->LargestPbufferFlag) {
442         attribs[na++] = GLX_LARGEST_PBUFFER;
443         attribs[na++] = True;
444     }
445     attribs[na++] = GLX_PRESERVED_CONTENTS;
446     attribs[na++] = True;
447     if (createPbuffer) {
448         attribs[na++] = GLX_PBUFFER_WIDTH;
449         attribs[na++] = togl->Width;
450         attribs[na++] = GLX_PBUFFER_HEIGHT;
451         attribs[na++] = togl->Width;
452         attribs[na++] = None;
453         pbuf = createPbuffer(togl->display, togl->fbcfg, attribs);
454     } else {
455         attribs[na++] = None;
456         pbuf = createPbufferSGIX(togl->display, togl->fbcfg, togl->Width,
457                 togl->Height, attribs);
458     }
459     if (togl_CheckForXError(togl) || pbuf == None) {
460         Tcl_SetResult(togl->Interp,
461                 TCL_STUPID "unable to allocate pbuffer", TCL_STATIC);
462         return None;
463     }
464     if (pbuf && togl->LargestPbufferFlag) {
465         int     tmp;
466 
467         queryPbuffer(togl->display, pbuf, GLX_WIDTH, &tmp);
468         if (tmp != 0)
469             togl->Width = tmp;
470         queryPbuffer(togl->display, pbuf, GLX_HEIGHT, &tmp);
471         if (tmp != 0)
472             togl->Height = tmp;
473     }
474     return pbuf;
475 }
476 
477 static void
togl_destroyPbuffer(Togl * togl)478 togl_destroyPbuffer(Togl *togl)
479 {
480     destroyPbuffer(togl->display, togl->pbuf);
481 }
482