Declare OpenGL 2.0 dynamically-loaded functions for internal use
[freeglut] / src / fg_init.c
1 /*
2  * freeglut_init.c
3  *
4  * Various freeglut initialization functions.
5  *
6  * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
7  * Written by Pawel W. Olszta, <olszta@sourceforge.net>
8  * Creation date: Thu Dec 2 1999
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
23  * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26  */
27
28 #define FREEGLUT_BUILDING_LIB
29 #include <GL/freeglut.h>
30 #include "fg_internal.h"
31
32 /*
33  * TODO BEFORE THE STABLE RELEASE:
34  *
35  *  fgDeinitialize()        -- Win32's OK, X11 needs the OS-specific
36  *                             deinitialization done
37  *  glutInitDisplayString() -- display mode string parsing
38  *
39  * Wouldn't it be cool to use gettext() for error messages? I just love
40  * bash saying  "nie znaleziono pliku" instead of "file not found" :)
41  * Is gettext easily portable?
42  */
43
44 /* -- GLOBAL VARIABLES ----------------------------------------------------- */
45
46 /*
47  * A structure pointed by fgDisplay holds all information
48  * regarding the display, screen, root window etc.
49  */
50 SFG_Display fgDisplay;
51
52 /*
53  * The settings for the current freeglut session
54  */
55 SFG_State fgState = { { -1, -1, GL_FALSE },  /* Position */
56                       { 300, 300, GL_TRUE }, /* Size */
57                       GLUT_RGBA | GLUT_SINGLE | GLUT_DEPTH,  /* DisplayMode */
58                       GL_FALSE,              /* Initialised */
59                       GLUT_TRY_DIRECT_CONTEXT,  /* DirectContext */
60                       GL_FALSE,              /* ForceIconic */
61                       GL_FALSE,              /* UseCurrentContext */
62                       GL_FALSE,              /* GLDebugSwitch */
63                       GL_FALSE,              /* XSyncSwitch */
64                       GLUT_KEY_REPEAT_ON,    /* KeyRepeat */
65                       INVALID_MODIFIERS,     /* Modifiers */
66                       0,                     /* FPSInterval */
67                       0,                     /* SwapCount */
68                       0,                     /* SwapTime */
69                       0,                     /* Time */
70                       { NULL, NULL },         /* Timers */
71                       { NULL, NULL },         /* FreeTimers */
72                       NULL,                   /* IdleCallback */
73                       0,                      /* ActiveMenus */
74                       NULL,                   /* MenuStateCallback */
75                       NULL,                   /* MenuStatusCallback */
76                       { -1, -1, GL_TRUE },    /* GameModeSize */
77                       -1,                     /* GameModeDepth */
78                       -1,                     /* GameModeRefresh */
79                       GLUT_ACTION_EXIT,       /* ActionOnWindowClose */
80                       GLUT_EXEC_STATE_INIT,   /* ExecState */
81                       NULL,                   /* ProgramName */
82                       GL_FALSE,               /* JoysticksInitialised */
83                       0,                      /* NumActiveJoysticks */
84                       GL_FALSE,               /* InputDevsInitialised */
85                       0,                      /* MouseWheelTicks */
86                       1,                      /* AuxiliaryBufferNumber */
87                       4,                      /* SampleNumber */
88                       GL_FALSE,               /* SkipStaleMotion */
89                       1,                      /* OpenGL context MajorVersion */
90                       0,                      /* OpenGL context MinorVersion */
91                       0,                      /* OpenGL ContextFlags */
92                       0,                      /* OpenGL ContextProfile */
93                       0,                      /* HasOpenGL20 */
94                       NULL,                   /* ErrorFunc */
95                       NULL                    /* WarningFunc */
96 };
97
98
99 /* -- PRIVATE FUNCTIONS ---------------------------------------------------- */
100
101 extern void fgPlatformInitialize( const char* displayName );
102 extern void fgPlatformDeinitialiseInputDevices ( void );
103 extern void fgPlatformCloseDisplay ( void );
104 extern void fgPlatformDestroyContext ( SFG_PlatformDisplay pDisplay, SFG_WindowContextType MContext );
105
106
107 void fghParseCommandLineArguments ( int* pargc, char** argv, char **pDisplayName, char **pGeometry )
108 {
109 #ifndef _WIN32_WCE
110     int i, j, argc = *pargc;
111
112     {
113             /* check if GLUT_FPS env var is set */
114         const char *fps = getenv( "GLUT_FPS" );
115
116         if( fps )
117         {
118             int interval;
119             sscanf( fps, "%d", &interval );
120
121             if( interval <= 0 )
122                 fgState.FPSInterval = 5000;  /* 5000 millisecond default */
123             else
124                 fgState.FPSInterval = interval;
125         }
126     }
127
128     *pDisplayName = getenv( "DISPLAY" );
129
130     for( i = 1; i < argc; i++ )
131     {
132         if( strcmp( argv[ i ], "-display" ) == 0 )
133         {
134             if( ++i >= argc )
135                 fgError( "-display parameter must be followed by display name" );
136
137             *pDisplayName = argv[ i ];
138
139             argv[ i - 1 ] = NULL;
140             argv[ i     ] = NULL;
141             ( *pargc ) -= 2;
142         }
143         else if( strcmp( argv[ i ], "-geometry" ) == 0 )
144         {
145             if( ++i >= argc )
146                 fgError( "-geometry parameter must be followed by window "
147                          "geometry settings" );
148
149             *pGeometry = argv[ i ];
150
151             argv[ i - 1 ] = NULL;
152             argv[ i     ] = NULL;
153             ( *pargc ) -= 2;
154         }
155         else if( strcmp( argv[ i ], "-direct" ) == 0)
156         {
157             if( fgState.DirectContext == GLUT_FORCE_INDIRECT_CONTEXT )
158                 fgError( "parameters ambiguity, -direct and -indirect "
159                     "cannot be both specified" );
160
161             fgState.DirectContext = GLUT_FORCE_DIRECT_CONTEXT;
162             argv[ i ] = NULL;
163             ( *pargc )--;
164         }
165         else if( strcmp( argv[ i ], "-indirect" ) == 0 )
166         {
167             if( fgState.DirectContext == GLUT_FORCE_DIRECT_CONTEXT )
168                 fgError( "parameters ambiguity, -direct and -indirect "
169                     "cannot be both specified" );
170
171             fgState.DirectContext = GLUT_FORCE_INDIRECT_CONTEXT;
172             argv[ i ] = NULL;
173             (*pargc)--;
174         }
175         else if( strcmp( argv[ i ], "-iconic" ) == 0 )
176         {
177             fgState.ForceIconic = GL_TRUE;
178             argv[ i ] = NULL;
179             ( *pargc )--;
180         }
181         else if( strcmp( argv[ i ], "-gldebug" ) == 0 )
182         {
183             fgState.GLDebugSwitch = GL_TRUE;
184             argv[ i ] = NULL;
185             ( *pargc )--;
186         }
187         else if( strcmp( argv[ i ], "-sync" ) == 0 )
188         {
189             fgState.XSyncSwitch = GL_TRUE;
190             argv[ i ] = NULL;
191             ( *pargc )--;
192         }
193     }
194
195     /* Compact {argv}. */
196     for( i = j = 1; i < *pargc; i++, j++ )
197     {
198         /* Guaranteed to end because there are "*pargc" arguments left */
199         while ( argv[ j ] == NULL )
200             j++;
201         if ( i != j )
202             argv[ i ] = argv[ j ];
203     }
204
205 #endif /* _WIN32_WCE */
206
207 }
208
209
210 void fghCloseInputDevices ( void )
211 {
212     if ( fgState.JoysticksInitialised )
213         fgJoystickClose( );
214
215     if ( fgState.InputDevsInitialised )
216         fgInputDeviceClose( );
217 }
218
219
220 /*
221  * Perform the freeglut deinitialization...
222  */
223 void fgDeinitialize( void )
224 {
225     SFG_Timer *timer;
226
227     if( !fgState.Initialised )
228     {
229         return;
230     }
231
232         /* If we're in game mode, we want to leave game mode */
233     if( fgStructure.GameModeWindow ) {
234         glutLeaveGameMode();
235     }
236
237     /* If there was a menu created, destroy the rendering context */
238     if( fgStructure.MenuContext )
239     {
240                 fgPlatformDestroyContext (fgDisplay.pDisplay, fgStructure.MenuContext->MContext );
241         free( fgStructure.MenuContext );
242         fgStructure.MenuContext = NULL;
243     }
244
245     fgDestroyStructure( );
246
247     while( ( timer = fgState.Timers.First) )
248     {
249         fgListRemove( &fgState.Timers, &timer->Node );
250         free( timer );
251     }
252
253     while( ( timer = fgState.FreeTimers.First) )
254     {
255         fgListRemove( &fgState.FreeTimers, &timer->Node );
256         free( timer );
257     }
258
259         fgPlatformDeinitialiseInputDevices ();
260
261         fgState.MouseWheelTicks = 0;
262
263     fgState.MajorVersion = 1;
264     fgState.MinorVersion = 0;
265     fgState.ContextFlags = 0;
266     fgState.ContextProfile = 0;
267
268     fgState.Initialised = GL_FALSE;
269
270     fgState.Position.X = -1;
271     fgState.Position.Y = -1;
272     fgState.Position.Use = GL_FALSE;
273
274     fgState.Size.X = 300;
275     fgState.Size.Y = 300;
276     fgState.Size.Use = GL_TRUE;
277
278     fgState.DisplayMode = GLUT_RGBA | GLUT_SINGLE | GLUT_DEPTH;
279
280     fgState.DirectContext  = GLUT_TRY_DIRECT_CONTEXT;
281     fgState.ForceIconic         = GL_FALSE;
282     fgState.UseCurrentContext   = GL_FALSE;
283     fgState.GLDebugSwitch       = GL_FALSE;
284     fgState.XSyncSwitch         = GL_FALSE;
285     fgState.ActionOnWindowClose = GLUT_ACTION_EXIT;
286     fgState.ExecState           = GLUT_EXEC_STATE_INIT;
287
288     fgState.KeyRepeat       = GLUT_KEY_REPEAT_ON;
289     fgState.Modifiers       = INVALID_MODIFIERS;
290
291     fgState.GameModeSize.X  = -1;
292     fgState.GameModeSize.Y  = -1;
293     fgState.GameModeDepth   = -1;
294     fgState.GameModeRefresh = -1;
295
296     fgListInit( &fgState.Timers );
297     fgListInit( &fgState.FreeTimers );
298
299     fgState.IdleCallback = NULL;
300     fgState.MenuStateCallback = ( FGCBMenuState )NULL;
301     fgState.MenuStatusCallback = ( FGCBMenuStatus )NULL;
302
303     fgState.SwapCount   = 0;
304     fgState.SwapTime    = 0;
305     fgState.FPSInterval = 0;
306
307     if( fgState.ProgramName )
308     {
309         free( fgState.ProgramName );
310         fgState.ProgramName = NULL;
311     }
312
313         fgPlatformCloseDisplay ();
314
315     fgState.Initialised = GL_FALSE;
316 }
317
318
319 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
320 #if defined(NEED_XPARSEGEOMETRY_IMPL)
321 #   include "util/xparsegeometry_repl.h"
322 #endif
323
324 /*
325  * Perform initialization. This usually happens on the program startup
326  * and restarting after glutMainLoop termination...
327  */
328 void FGAPIENTRY glutInit( int* pargc, char** argv )
329 {
330     char* displayName = NULL;
331     char* geometry = NULL;
332     if( fgState.Initialised )
333         fgError( "illegal glutInit() reinitialization attempt" );
334
335     if (pargc && *pargc && argv && *argv && **argv)
336     {
337         fgState.ProgramName = strdup (*argv);
338
339         if( !fgState.ProgramName )
340             fgError ("Could not allocate space for the program's name.");
341     }
342
343     fgCreateStructure( );
344
345     /* Get start time */
346     fgState.Time = fgSystemTime();
347
348         fghParseCommandLineArguments ( pargc, argv, &displayName, &geometry );
349
350     /*
351      * Have the display created now. If there wasn't a "-display"
352      * in the program arguments, we will use the DISPLAY environment
353      * variable for opening the X display (see code above):
354      */
355     fgPlatformInitialize( displayName );
356
357     /*
358      * Geometry parsing deferred until here because we may need the screen
359      * size.
360      */
361
362     if (geometry )
363     {
364         unsigned int parsedWidth, parsedHeight;
365         int mask = XParseGeometry( geometry,
366                                    &fgState.Position.X, &fgState.Position.Y,
367                                    &parsedWidth, &parsedHeight );
368         /* TODO: Check for overflow? */
369         fgState.Size.X = parsedWidth;
370         fgState.Size.Y = parsedHeight;
371
372         if( (mask & (WidthValue|HeightValue)) == (WidthValue|HeightValue) )
373             fgState.Size.Use = GL_TRUE;
374
375         if( mask & XNegative )
376             fgState.Position.X += fgDisplay.ScreenWidth - fgState.Size.X;
377
378         if( mask & YNegative )
379             fgState.Position.Y += fgDisplay.ScreenHeight - fgState.Size.Y;
380
381         if( (mask & (XValue|YValue)) == (XValue|YValue) )
382             fgState.Position.Use = GL_TRUE;
383     }
384
385     fgInitGL2();
386 }
387
388 /*
389  * Undoes all the "glutInit" stuff
390  */
391 void FGAPIENTRY glutExit ( void )
392 {
393   fgDeinitialize ();
394 }
395
396 /*
397  * Sets the default initial window position for new windows
398  */
399 void FGAPIENTRY glutInitWindowPosition( int x, int y )
400 {
401     fgState.Position.X = x;
402     fgState.Position.Y = y;
403
404     if( ( x >= 0 ) && ( y >= 0 ) )
405         fgState.Position.Use = GL_TRUE;
406     else
407         fgState.Position.Use = GL_FALSE;
408 }
409
410 /*
411  * Sets the default initial window size for new windows
412  */
413 void FGAPIENTRY glutInitWindowSize( int width, int height )
414 {
415     fgState.Size.X = width;
416     fgState.Size.Y = height;
417
418     if( ( width > 0 ) && ( height > 0 ) )
419         fgState.Size.Use = GL_TRUE;
420     else
421         fgState.Size.Use = GL_FALSE;
422 }
423
424 /*
425  * Sets the default display mode for all new windows
426  */
427 void FGAPIENTRY glutInitDisplayMode( unsigned int displayMode )
428 {
429     /* We will make use of this value when creating a new OpenGL context... */
430     fgState.DisplayMode = displayMode;
431 }
432
433
434 /* -- INIT DISPLAY STRING PARSING ------------------------------------------ */
435
436 static char* Tokens[] =
437 {
438     "alpha", "acca", "acc", "blue", "buffer", "conformant", "depth", "double",
439     "green", "index", "num", "red", "rgba", "rgb", "luminance", "stencil",
440     "single", "stereo", "samples", "slow", "win32pdf", "win32pfd", "xvisual",
441     "xstaticgray", "xgrayscale", "xstaticcolor", "xpseudocolor",
442     "xtruecolor", "xdirectcolor",
443     "xstaticgrey", "xgreyscale", "xstaticcolour", "xpseudocolour",
444     "xtruecolour", "xdirectcolour", "borderless", "aux"
445 };
446 #define NUM_TOKENS             (sizeof(Tokens) / sizeof(*Tokens))
447
448 void FGAPIENTRY glutInitDisplayString( const char* displayMode )
449 {
450     int glut_state_flag = 0 ;
451     /*
452      * Unpack a lot of options from a character string.  The options are
453      * delimited by blanks or tabs.
454      */
455     char *token ;
456     size_t len = strlen ( displayMode );
457     char *buffer = (char *)malloc ( (len+1) * sizeof(char) );
458     memcpy ( buffer, displayMode, len );
459     buffer[len] = '\0';
460
461     token = strtok ( buffer, " \t" );
462
463     while ( token )
464     {
465         /* Process this token */
466         int i ;
467
468         /* Temporary fix:  Ignore any length specifications and at least
469          * process the basic token
470          * TODO:  Fix this permanently
471          */
472         size_t cleanlength = strcspn ( token, "=<>~!" );
473
474         for ( i = 0; i < NUM_TOKENS; i++ )
475         {
476             if ( strncmp ( token, Tokens[i], cleanlength ) == 0 ) break ;
477         }
478
479         switch ( i )
480         {
481         case 0 :  /* "alpha":  Alpha color buffer precision in bits */
482             glut_state_flag |= GLUT_ALPHA ;  /* Somebody fix this for me! */
483             break ;
484
485         case 1 :  /* "acca":  Red, green, blue, and alpha accumulation buffer
486                      precision in bits */
487             break ;
488
489         case 2 :  /* "acc":  Red, green, and blue accumulation buffer precision
490                      in bits with zero bits alpha */
491             glut_state_flag |= GLUT_ACCUM ;  /* Somebody fix this for me! */
492             break ;
493
494         case 3 :  /* "blue":  Blue color buffer precision in bits */
495             break ;
496
497         case 4 :  /* "buffer":  Number of bits in the color index color buffer
498                    */
499             break ;
500
501         case 5 :  /* "conformant":  Boolean indicating if the frame buffer
502                      configuration is conformant or not */
503             break ;
504
505         case 6 : /* "depth":  Number of bits of precision in the depth buffer */
506             glut_state_flag |= GLUT_DEPTH ;  /* Somebody fix this for me! */
507             break ;
508
509         case 7 :  /* "double":  Boolean indicating if the color buffer is
510                      double buffered */
511             glut_state_flag |= GLUT_DOUBLE ;
512             break ;
513
514         case 8 :  /* "green":  Green color buffer precision in bits */
515             break ;
516
517         case 9 :  /* "index":  Boolean if the color model is color index or not
518                    */
519             glut_state_flag |= GLUT_INDEX ;
520             break ;
521
522         case 10 :  /* "num":  A special capability  name indicating where the
523                       value represents the Nth frame buffer configuration
524                       matching the description string */
525             break ;
526
527         case 11 :  /* "red":  Red color buffer precision in bits */
528             break ;
529
530         case 12 :  /* "rgba":  Number of bits of red, green, blue, and alpha in
531                       the RGBA color buffer */
532             glut_state_flag |= GLUT_RGBA ;  /* Somebody fix this for me! */
533             break ;
534
535         case 13 :  /* "rgb":  Number of bits of red, green, and blue in the
536                       RGBA color buffer with zero bits alpha */
537             glut_state_flag |= GLUT_RGB ;  /* Somebody fix this for me! */
538             break ;
539
540         case 14 :  /* "luminance":  Number of bits of red in the RGBA and zero
541                       bits of green, blue (alpha not specified) of color buffer
542                       precision */
543             glut_state_flag |= GLUT_LUMINANCE ; /* Somebody fix this for me! */
544             break ;
545
546         case 15 :  /* "stencil":  Number of bits in the stencil buffer */
547             glut_state_flag |= GLUT_STENCIL;  /* Somebody fix this for me! */
548             break ;
549
550         case 16 :  /* "single":  Boolean indicate the color buffer is single
551                       buffered */
552             glut_state_flag |= GLUT_SINGLE ;
553             break ;
554
555         case 17 :  /* "stereo":  Boolean indicating the color buffer supports
556                       OpenGL-style stereo */
557             glut_state_flag |= GLUT_STEREO ;
558             break ;
559
560         case 18 :  /* "samples":  Indicates the number of multisamples to use
561                       based on GLX's SGIS_multisample extension (for
562                       antialiasing) */
563             glut_state_flag |= GLUT_MULTISAMPLE ; /*Somebody fix this for me!*/
564             break ;
565
566         case 19 :  /* "slow":  Boolean indicating if the frame buffer
567                       configuration is slow or not */
568             break ;
569
570         case 20 :  /* "win32pdf": (incorrect spelling but was there before */
571         case 21 :  /* "win32pfd":  matches the Win32 Pixel Format Descriptor by
572                       number */
573 #if TARGET_HOST_MS_WINDOWS
574 #endif
575             break ;
576
577         case 22 :  /* "xvisual":  matches the X visual ID by number */
578 #if TARGET_HOST_POSIX_X11
579 #endif
580             break ;
581
582         case 23 :  /* "xstaticgray": */
583         case 29 :  /* "xstaticgrey":  boolean indicating if the frame buffer
584                       configuration's X visual is of type StaticGray */
585 #if TARGET_HOST_POSIX_X11
586 #endif
587             break ;
588
589         case 24 :  /* "xgrayscale": */
590         case 30 :  /* "xgreyscale":  boolean indicating if the frame buffer
591                       configuration's X visual is of type GrayScale */
592 #if TARGET_HOST_POSIX_X11
593 #endif
594             break ;
595
596         case 25 :  /* "xstaticcolor": */
597         case 31 :  /* "xstaticcolour":  boolean indicating if the frame buffer
598                       configuration's X visual is of type StaticColor */
599 #if TARGET_HOST_POSIX_X11
600 #endif
601             break ;
602
603         case 26 :  /* "xpseudocolor": */
604         case 32 :  /* "xpseudocolour":  boolean indicating if the frame buffer
605                       configuration's X visual is of type PseudoColor */
606 #if TARGET_HOST_POSIX_X11
607 #endif
608             break ;
609
610         case 27 :  /* "xtruecolor": */
611         case 33 :  /* "xtruecolour":  boolean indicating if the frame buffer
612                       configuration's X visual is of type TrueColor */
613 #if TARGET_HOST_POSIX_X11
614 #endif
615             break ;
616
617         case 28 :  /* "xdirectcolor": */
618         case 34 :  /* "xdirectcolour":  boolean indicating if the frame buffer
619                       configuration's X visual is of type DirectColor */
620 #if TARGET_HOST_POSIX_X11
621 #endif
622             break ;
623
624         case 35 :  /* "borderless":  windows should not have borders */
625             glut_state_flag |= GLUT_BORDERLESS;
626             break ;
627
628         case 36 :  /* "aux":  some number of aux buffers */
629             glut_state_flag |= GLUT_AUX;
630             break ;
631
632         case 37 :  /* Unrecognized */
633             fgWarning ( "WARNING - Display string token not recognized:  %s",
634                         token );
635             break ;
636         }
637
638         token = strtok ( NULL, " \t" );
639     }
640
641     free ( buffer );
642
643     /* We will make use of this value when creating a new OpenGL context... */
644     fgState.DisplayMode = glut_state_flag;
645 }
646
647 /* -- SETTING OPENGL 3.0 CONTEXT CREATION PARAMETERS ---------------------- */
648
649 void FGAPIENTRY glutInitContextVersion( int majorVersion, int minorVersion )
650 {
651     /* We will make use of these value when creating a new OpenGL context... */
652     fgState.MajorVersion = majorVersion;
653     fgState.MinorVersion = minorVersion;
654 }
655
656
657 void FGAPIENTRY glutInitContextFlags( int flags )
658 {
659     /* We will make use of this value when creating a new OpenGL context... */
660     fgState.ContextFlags = flags;
661 }
662
663 void FGAPIENTRY glutInitContextProfile( int profile )
664 {
665     /* We will make use of this value when creating a new OpenGL context... */
666     fgState.ContextProfile = profile;
667 }
668
669 /* -------------- User Defined Error/Warning Handler Support -------------- */
670
671 /*
672  * Sets the user error handler (note the use of va_list for the args to the fmt)
673  */
674 void FGAPIENTRY glutInitErrorFunc( void (* vfgError) ( const char *fmt, va_list ap ) )
675 {
676     /* This allows user programs to handle freeglut errors */
677     fgState.ErrorFunc = vfgError;
678 }
679
680 /*
681  * Sets the user warning handler (note the use of va_list for the args to the fmt)
682  */
683 void FGAPIENTRY glutInitWarningFunc( void (* vfgWarning) ( const char *fmt, va_list ap ) )
684 {
685     /* This allows user programs to handle freeglut warnings */
686     fgState.WarningFunc = vfgWarning;
687 }
688
689 /*** END OF FILE ***/