1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*
34  *  Environment variable controls available:
35  *
36  *   - COIN_CGLGLUE_NO_PBUFFERS: set to 1 to force software rendering of
37  *     offscreen contexts.
38  */
39 
40 #include "glue/gl_cgl.h"
41 #include "coindefs.h"
42 
43 #ifdef HAVE_CONFIG_H
44 #include "config.h"
45 #endif /* HAVE_CONFIG_H */
46 
47 #include <cstdlib>
48 #include <cstring>
49 #include <cassert>
50 
51 #include <Inventor/C/tidbits.h>
52 #include <Inventor/C/glue/gl.h>
53 #include <Inventor/C/errors/debugerror.h>
54 #include <Inventor/C/glue/dl.h>
55 
56 #include "glue/glp.h"
57 #include "glue/dlp.h"
58 
59 /* ********************************************************************** */
60 
61 #ifndef HAVE_CGL
62 
cglglue_context_is_using_pbuffer(void * COIN_UNUSED_ARG (ctx))63 SbBool cglglue_context_is_using_pbuffer(void * COIN_UNUSED_ARG(ctx))
64 {
65   assert(FALSE); return FALSE;
66 }
67 
cglglue_getprocaddress(const char * COIN_UNUSED_ARG (fname))68 void * cglglue_getprocaddress(const char * COIN_UNUSED_ARG(fname))
69 {
70   assert(FALSE); return NULL;
71 }
72 
cglglue_context_create_offscreen(unsigned int COIN_UNUSED_ARG (width),unsigned int COIN_UNUSED_ARG (height))73 void * cglglue_context_create_offscreen(unsigned int COIN_UNUSED_ARG(width),
74                                         unsigned int COIN_UNUSED_ARG(height)) {
75   assert(FALSE); return NULL;
76 }
77 
cglglue_context_make_current(void * COIN_UNUSED_ARG (ctx))78 SbBool cglglue_context_make_current(void * COIN_UNUSED_ARG(ctx))
79 {
80   assert(FALSE); return FALSE;
81 }
82 
cglglue_context_reinstate_previous(void * COIN_UNUSED_ARG (ctx))83 void cglglue_context_reinstate_previous(void * COIN_UNUSED_ARG(ctx))
84 {
85   assert(FALSE);
86 }
87 
cglglue_context_destruct(void * COIN_UNUSED_ARG (ctx))88 void cglglue_context_destruct(void * COIN_UNUSED_ARG(ctx))
89 {
90   assert(FALSE);
91 }
92 
93 #else /* HAVE_CGL */
94 
95 /* ********************************************************************** */
96 
97 #include <OpenGL/OpenGL.h>
98 
99 #ifndef HAVE_CGL_PBUFFER
100 
101 /*
102  * pBuffer functions are picked up at runtime, so the only thing
103  * we need from the CGL headers is the CGLPBuffer type, which is
104  * void* anyways...
105  */
106 typedef void * CGLPbuffer;
107 
108 #endif /* !HAVE_CGL_PBUFFER */
109 
110 typedef CGLError (* COIN_CGLCREATEPBUFFER) (GLsizei width,
111                                              GLsizei height,
112                                              GLenum target,
113                                              GLenum internalFormat,
114                                              GLint max_level,
115                                              CGLPBufferObj *pbuffer);
116 typedef CGLError (* COIN_CGLDESTROYPBUFFER) (CGLPBufferObj pbuffer);
117 typedef CGLError (* COIN_CGLSETPBUFFER) (CGLContextObj ctx,
118                                           CGLPBufferObj pbuffer,
119                                           GLenum face,
120                                           GLint level,
121                                           GLint screen);
122 typedef CGLError (* COIN_CGLTEXIMAGEPBUFFER) (CGLContextObj ctx,
123                                                CGLPBufferObj pbuffer,
124                                                GLenum source);
125 
126 static COIN_CGLCREATEPBUFFER cglglue_CGLCreatePBuffer = NULL;
127 static COIN_CGLDESTROYPBUFFER cglglue_CGLDestroyPBuffer = NULL;
128 static COIN_CGLSETPBUFFER cglglue_CGLSetPBuffer = NULL;
129 static COIN_CGLTEXIMAGEPBUFFER cglglue_CGLTexImagePBuffer = NULL;
130 
131 struct cglglue_contextdata;
132 static SbBool (* cglglue_context_create)(struct cglglue_contextdata * ctx) = NULL;
133 
134 static void
cglglue_resolve_symbols()135 cglglue_resolve_symbols()
136 {
137   /* Resolve symbols only once... */
138   if (cglglue_CGLCreatePBuffer && cglglue_CGLDestroyPBuffer &&
139       cglglue_CGLSetPBuffer && cglglue_CGLTexImagePBuffer) return;
140 
141   cglglue_CGLCreatePBuffer = (COIN_CGLCREATEPBUFFER)cglglue_getprocaddress("CGLCreatePBuffer");
142   cglglue_CGLDestroyPBuffer = (COIN_CGLDESTROYPBUFFER)cglglue_getprocaddress("CGLDestroyPBuffer");
143   cglglue_CGLSetPBuffer = (COIN_CGLSETPBUFFER)cglglue_getprocaddress("CGLSetPBuffer");
144   cglglue_CGLTexImagePBuffer = (COIN_CGLTEXIMAGEPBUFFER)cglglue_getprocaddress("CGLTexImagePBuffer");
145 }
146 
147 
148 static SbBool
cglglue_get_pbuffer_enable(void)149 cglglue_get_pbuffer_enable(void)
150 {
151   /* Make it possible to turn off pBuffer support completely.
152      Mostly relevant for debugging purposes. */
153   const char * env = coin_getenv("COIN_CGLGLUE_NO_PBUFFERS");
154   if (env && atoi(env) > 0) {
155     return FALSE;
156   } else {
157     cglglue_resolve_symbols();
158     return (cglglue_CGLCreatePBuffer && cglglue_CGLDestroyPBuffer &&
159             cglglue_CGLSetPBuffer && cglglue_CGLTexImagePBuffer);
160   }
161 }
162 
163 
164 struct cglglue_contextdata {
165   CGLContextObj storedcontext;
166   size_t rowbytes;
167   void * membuffer;
168   CGLContextObj cglcontext;
169   CGLPixelFormatObj pixformat;
170   CGLPBufferObj cglpbuffer;
171   unsigned int width;
172   unsigned int height;
173   SbBool pbufferisbound;
174 };
175 
176 
177 static struct cglglue_contextdata *
cglglue_contextdata_init(unsigned int width,unsigned int height)178 cglglue_contextdata_init(unsigned int width, unsigned int height)
179 {
180   struct cglglue_contextdata * ctx;
181   ctx = (struct cglglue_contextdata *)malloc(sizeof(struct cglglue_contextdata));
182 
183   ctx->storedcontext = NULL;
184   ctx->rowbytes = 0;
185   ctx->membuffer = NULL;
186   ctx->cglcontext = NULL;
187   ctx->pixformat = NULL;
188   ctx->cglpbuffer = NULL;
189   ctx->width = width;
190   ctx->height = height;
191   ctx->pbufferisbound = FALSE;
192   return ctx;
193 }
194 
195 static void
cglglue_contextdata_cleanup(struct cglglue_contextdata * ctx)196 cglglue_contextdata_cleanup(struct cglglue_contextdata * ctx)
197 {
198   if (ctx->cglcontext) CGLDestroyContext(ctx->cglcontext);
199   if (ctx->pixformat) CGLDestroyPixelFormat(ctx->pixformat);
200   if (ctx->cglpbuffer) cglglue_CGLDestroyPBuffer(ctx->cglpbuffer);
201   if (ctx->membuffer) free(ctx->membuffer);
202   free(ctx);
203 }
204 
205 static SbBool
cglglue_context_create_software(struct cglglue_contextdata * ctx)206 cglglue_context_create_software(struct cglglue_contextdata * ctx)
207 {
208   // Sets up a single-buffered pixel format
209   int attrib[] = {
210     kCGLPFAOffScreen,
211     kCGLPFANoRecovery,
212     kCGLPFAColorSize, 32,
213     kCGLPFAAlphaSize, 8,
214     kCGLPFADepthSize, 24,
215     kCGLPFAStencilSize, 1,
216     NULL
217   };
218 
219   /* FIXME: the following is a hack to get around a problem which
220      really demands more effort to be solved properly.
221 
222      The problem is that there is no way in the API of the
223      SoOffscreenRenderer class to specify what particular attributes
224      to request. This most often manifests itself as a problem for app
225      programmers in that they have made some kind of extension node
226      which uses the OpenGL stencil buffer. If no stencil buffer
227      happens to be part of the GL context format for the offscreen
228      renderer, these will not work properly. At the same time, we
229      don't want to default to requesting a stencil buffer, as that
230      takes a non-trivial amount of extra memory resources on the gfx
231      card.
232 
233      So until we have implemented the proper solution for making it
234      possible to pass in a detailed specification of which attributes
235      to request from offscreen GL contexts, we provide this temporary
236      work-around: the app programmer can set an envvar with a value
237      specifying the number of stencil buffer bits he/she wants.
238 
239      20060223 mortene.
240   */
241   const int v = coin_glglue_stencil_bits_hack();
242   if (v != -1) {
243     size_t i;
244     for (i = 0; i < (sizeof(attrib) / sizeof(attrib[0]) / 2); i++) {
245       if (attrib[i] == kCGLPFAStencilSize) { attrib[i+1] = v; }
246     }
247   }
248 
249   if (coin_glglue_debug()) {
250     cc_debugerror_postinfo("cglglue_context_create_software",
251                            "Creating software buffer.");
252   }
253 
254   GLint numPixelFormats;
255   CGLError err = CGLChoosePixelFormat((CGLPixelFormatAttribute *)attrib,
256                              &ctx->pixformat, &numPixelFormats);
257   if (err != kCGLNoError || !ctx->pixformat) {
258     cc_debugerror_postwarning("cglglue_context_create_software",
259                               "Couldn't get RGBA CGL pixelformat. %s",
260                               CGLErrorString(err));
261     return FALSE;
262   }
263 
264   err = CGLCreateContext(ctx->pixformat, NULL, &ctx->cglcontext);
265   if (err != kCGLNoError || !ctx->cglcontext) {
266     cc_debugerror_postwarning("cglglue_context_create_software",
267                               "Couldn't create CGL context. %s",
268                               CGLErrorString(err));
269     cglglue_contextdata_cleanup(ctx);
270     return FALSE;
271   }
272 
273   if (coin_glglue_debug()) {
274     cc_debugerror_postinfo("cglglue_context_create_software",
275                            "created new software offscreen context == %p",
276                            ctx->cglcontext);
277   }
278 
279   ctx->rowbytes = ctx->width * 4; // We use GL_RGBA as internal format
280   ctx->membuffer = malloc(ctx->height*ctx->rowbytes);
281   err = CGLSetOffScreen(ctx->cglcontext, ctx->width, ctx->height,
282                         ctx->rowbytes, ctx->membuffer);
283   if (err != kCGLNoError) {
284     cc_debugerror_post("cglglue_context_make_current",
285                        "Error setting offscreen context. %s",
286                        CGLError(err));
287     return FALSE;
288   }
289 
290 
291   return TRUE;
292 }
293 
294 
295 static SbBool
cglglue_context_create_pbuffer(struct cglglue_contextdata * ctx)296 cglglue_context_create_pbuffer(struct cglglue_contextdata * ctx)
297 {
298   /* FIXME: before we get here, we should have checked the requested
299      dimensions in the context struct versus GLX_MAX_PBUFFER_WIDTH,
300      GLX_MAX_PBUFFER_HEIGHT and GLX_MAX_PBUFFER_PIXELS
301      somewhere. Copied from gl_agl.cpp by kintel 20090316, originally
302      copied from gl_glx.c by kyrah 20031114, originally mentioned by mortene 20030811.
303 
304      Update kyrah 20040714: The way to query maximum pBuffer size
305      on CGL is (according to Geoff Stahl of Apple) GL_MAX_VIEWPORT_DIMS,
306      but this won't get us very far, since the numbers reported
307      reflect the theoretical maximum size. We'll in any case have
308      to try and allocate a pBuffer to see if we can actually get
309      so much VRAM...
310   */
311 
312   GLenum error;
313   GLint attribs[] = {
314     kCGLPFAColorSize, 32,
315     kCGLPFAAlphaSize, 8,
316     kCGLPFADepthSize, 24,
317     kCGLPFAStencilSize, 1,
318     kCGLPFAClosestPolicy,
319     kCGLPFAAccelerated,
320     kCGLPFANoRecovery,
321     NULL
322   };
323 
324   /* FIXME: this is a hack. See comment elsewhere in the file where
325      this function is used, for an elaborate explanation. 20060307
326      mortene. */
327   const int v = coin_glglue_stencil_bits_hack();
328   if (v != -1) {
329     size_t i;
330     for (i = 0; i < (sizeof(attribs) / sizeof(attribs[0]) / 2); i++) {
331       if (attribs[i] == kCGLPFAStencilSize) { attribs[i+1] = v; }
332     }
333   }
334 
335   /* Note that unlike in WGL, where the ability to bind a pBuffer
336      as a texture has to be specified when creating the pixel
337      format, CGL works in a different way: We can assume that
338      when pBuffers are supported, it is also possible to bind
339      the pBuffer as a texture (i.e. call cglTexImagePBuffer).
340    */
341 
342   if (coin_glglue_debug()) {
343     cc_debugerror_postinfo("cglglue_context_create_pbuffer",
344                            "Creating pBuffer.");
345   }
346 
347   GLint numPixelFormats;
348   CGLError err = CGLChoosePixelFormat((CGLPixelFormatAttribute *)attribs,
349                                       &ctx->pixformat, &numPixelFormats);
350   if (err != kCGLNoError) {
351     cc_debugerror_post("cglglue_context_create_pbuffer",
352                        "Couldn't create CGL Pixelformat: %s",
353                        CGLErrorString(err));
354     return FALSE;
355   }
356 
357   if (!ctx->pixformat) return FALSE;
358 
359   err = CGLCreateContext (ctx->pixformat, NULL, &ctx->cglcontext);
360   if (err != kCGLNoError || !ctx->cglcontext) {
361     cc_debugerror_post("cglglue_context_create_pbuffer",
362                        "Couldn't create CGL context: %s",
363                        CGLErrorString(err));
364     cglglue_contextdata_cleanup(ctx);
365     return FALSE;
366   }
367 
368 
369   /* The dimension of CGL pBuffers creating with the GL_TEXTURE_2D
370      flag must be both squared and powers of two; else we have to
371      use GL_TEXTURE_RECTANGLE_EXT. (It would of course be ok to use
372      the rectangle extension in all cases, but drivers may optimize
373      for the square, pow2 case.)
374      Note that it is not necessary to check for the availability of
375      GL_TEXTURE_RECTANGLE_EXT - we can always assume this extension
376      to be present when pBuffers are supported.
377   */
378   GLenum target = GL_TEXTURE_2D;
379   if (!coin_is_power_of_two(ctx->width)  ||
380       !coin_is_power_of_two(ctx->height) ||
381       ctx->width != ctx->height) {
382     target = GL_TEXTURE_RECTANGLE_EXT;
383   }
384 
385   err = cglglue_CGLCreatePBuffer(ctx->width, ctx->height, target,
386                                  GL_RGBA, 0, &ctx->cglpbuffer);
387   if (err != kCGLNoError) {
388     cc_debugerror_post("cglglue_context_create_pbuffer",
389                        "Couldn't create CGL pBuffer: %s",
390                        CGLErrorString(err));
391     return FALSE;
392   }
393   return TRUE;
394 }
395 
396 
397 void *
cglglue_context_create_offscreen(unsigned int width,unsigned int height)398 cglglue_context_create_offscreen(unsigned int width, unsigned int height)
399 {
400   struct cglglue_contextdata * ctx;
401   SbBool ok, pbuffer = FALSE, ispbuffer = FALSE;
402 
403   ctx = cglglue_contextdata_init(width, height);
404   if (!ctx) return NULL;
405 
406   /* Use cached function pointer for pBuffer vs SW context creation... */
407   if (cglglue_context_create != NULL) {
408 
409     ispbuffer = (cglglue_context_create == cglglue_context_create_pbuffer);
410 
411     /* Try to open a pBuffer context. If that fails, fall back to software. */
412     if (cglglue_context_create(ctx)) { return ctx; }
413     cglglue_contextdata_cleanup(ctx);
414 
415     if (ispbuffer) {
416       if (coin_glglue_debug()) {
417         cc_debugerror_postinfo("cglglue_context_create_offscreen",
418                               "pBuffer failed. Trying software ");
419       }
420       ctx = cglglue_contextdata_init(width, height);
421       assert(ctx);
422       if (cglglue_context_create_software(ctx)) { return ctx; }
423       cglglue_contextdata_cleanup(ctx);
424     }
425     return NULL;
426   }
427 
428   /* ... but the first time around, we have to figure out. */
429   pbuffer = cglglue_get_pbuffer_enable();
430 
431   if (coin_glglue_debug()) {
432     cc_debugerror_postinfo("cglglue_context_create_offscreen",
433                            "PBuffer offscreen rendering is %ssupported "
434                            "by the OpenGL driver", pbuffer ? "" : "NOT ");
435   }
436 
437   if (pbuffer && cglglue_context_create_pbuffer(ctx)) {
438     cglglue_context_create = cglglue_context_create_pbuffer;
439     return ctx;
440   } else if (cglglue_context_create_software(ctx)) {
441     cglglue_context_create = cglglue_context_create_software;
442     return ctx;
443   } else {
444     cglglue_contextdata_cleanup(ctx);
445     return NULL;
446   }
447 }
448 
449 
450 SbBool
cglglue_context_make_current(void * ctx)451 cglglue_context_make_current(void * ctx)
452 {
453   struct cglglue_contextdata * context = (struct cglglue_contextdata *)ctx;
454 
455   if (!context->cglpbuffer) {
456     if (context->cglcontext) {
457       context->storedcontext = CGLGetCurrentContext();
458     }
459 
460     if (coin_glglue_debug()) {
461       cc_debugerror_postinfo("cglglue_make_context_current",
462                              "store current status first => context==%p",
463                              context->storedcontext);
464     }
465 
466     CGLSetCurrentContext(context->cglcontext);
467     return TRUE;
468 
469   } else { /* pBuffer support available */
470 
471     context->storedcontext = CGLGetCurrentContext();
472     CGLError err = CGLSetCurrentContext(context->cglcontext);
473     if (err != kCGLNoError) {
474         cc_debugerror_post("cglglue_context_make_current",
475                            "Error setting offscreen context: %s",
476                            CGLErrorString(err));
477     }
478 
479     GLint vs;
480     err = CGLGetVirtualScreen(context->cglcontext, &vs);
481     if (err != kCGLNoError) {
482       cc_debugerror_post("cglglue_context_make_current",
483                          "Error getting virtual screen: %s",
484                          CGLErrorString(err));
485       return FALSE;
486     }
487     err = cglglue_CGLSetPBuffer(context->cglcontext, context->cglpbuffer, 0, 0, vs);
488     if (err != kCGLNoError) {
489       cc_debugerror_post("cglglue_context_make_current",
490                          "cglSetPBuffer failed: %s",
491                          CGLErrorString(err));
492       return FALSE;
493     }
494 
495     if (coin_glglue_debug()) {
496       cc_debugerror_postinfo("cglglue_context_make_current",
497                              "PBuffer Context (0x%X) Renderer: %s\n",
498                              context->cglcontext, glGetString(GL_RENDERER));
499     }
500     return TRUE;
501   }
502 }
503 
504 void
cglglue_context_reinstate_previous(void * ctx)505 cglglue_context_reinstate_previous(void * ctx)
506 {
507   struct cglglue_contextdata * context = (struct cglglue_contextdata *)ctx;
508 
509   if (!context->cglpbuffer) {
510 
511     if (coin_glglue_debug()) {
512       cc_debugerror_postinfo("cglglue_context_reinstate_previous",
513                              "releasing context");
514     }
515 
516     if (context->storedcontext) {
517 
518       if (coin_glglue_debug()) {
519         cc_debugerror_postinfo("cglglue_context_reinstate_previous",
520                                "restoring context %p to be current",
521                                context->storedcontext);
522       }
523 
524       CGLSetCurrentContext(context->storedcontext);
525     }
526 
527   } else { /* pBuffer support available */
528 
529     if (context->storedcontext) CGLSetCurrentContext(context->storedcontext);
530     else CGLSetCurrentContext(NULL);
531 
532   }
533 }
534 
535 void
cglglue_context_destruct(void * ctx)536 cglglue_context_destruct(void * ctx)
537 {
538   /* FIXME: needs to call into the (as of yet unimplemented)
539      C wrapper around the SoContextHandler. 20030310 mortene. */
540 
541   struct cglglue_contextdata * context = (struct cglglue_contextdata *)ctx;
542 
543   if (coin_glglue_debug()) {
544     cc_debugerror_postinfo("cglglue_context_destruct",
545                            "Destroying context %p", context->cglcontext);
546   }
547   cglglue_contextdata_cleanup(context);
548 }
549 
550 void
cglglue_context_bind_pbuffer(void * ctx)551 cglglue_context_bind_pbuffer(void * ctx)
552 {
553   struct cglglue_contextdata * context = (struct cglglue_contextdata *)ctx;
554 
555   CGLError err = cglglue_CGLTexImagePBuffer(context->storedcontext, context->cglpbuffer,
556                                             GL_FRONT);
557 
558   if (err != kCGLNoError) {
559     cc_debugerror_post("cglglue_context_bind_pbuffer()"
560                        "after binding pbuffer: %s",
561                        CGLErrorString(err));
562   }
563   else {
564     context->pbufferisbound = TRUE;
565   }
566 }
567 
568 void
cglglue_context_release_pbuffer(void * ctx)569 cglglue_context_release_pbuffer(void * ctx)
570 {
571   struct cglglue_contextdata * context = (struct cglglue_contextdata *)ctx;
572   CGLError err = cglglue_CGLDestroyPBuffer(context->cglpbuffer);
573   if (err != kCGLNoError) {
574     cc_debugerror_post("cglglue_context_release_pbuffer()"
575                        "releasing pbuffer: %s",
576                        CGLErrorString(err));
577   }
578 }
579 
580 SbBool
cglglue_context_pbuffer_is_bound(void * ctx)581 cglglue_context_pbuffer_is_bound(void * ctx)
582 {
583   struct cglglue_contextdata * context = (struct cglglue_contextdata *)ctx;
584   return (context->pbufferisbound);
585 }
586 
587 SbBool
cglglue_context_can_render_to_texture(void * ctx)588 cglglue_context_can_render_to_texture(void * ctx)
589 {
590   struct cglglue_contextdata * context = (struct cglglue_contextdata *)ctx;
591   return context->cglpbuffer != NULL;
592 }
593 
594 void
cglglue_cleanup(void)595 cglglue_cleanup(void)
596 {
597   cglglue_CGLCreatePBuffer = NULL;
598   cglglue_CGLDestroyPBuffer = NULL;
599   cglglue_CGLSetPBuffer = NULL;
600   cglglue_CGLTexImagePBuffer = NULL;
601 
602   cglglue_context_create = NULL;
603 }
604 
605 // used to look up CGL specific functions
606 void *
cglglue_getprocaddress(const char * fname)607 cglglue_getprocaddress(const char * fname)
608 {
609   void * ret = NULL;
610   cc_libhandle h = cc_dl_handle_with_gl_symbols();
611   if (h) {
612     ret = cc_dl_sym(h, fname);
613     cc_dl_close(h);
614   }
615   return ret;
616 }
617 
618 #endif /* HAVE_CGL */
619