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