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