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