]> git.jsancho.org Git - lugaru.git/blob - Source/OpenGL_Windows.cpp
d79162bf38930d2f10bdb9237cb708f7e56f6641
[lugaru.git] / Source / OpenGL_Windows.cpp
1 /*
2 Copyright (C) 2003, 2010 - Wolfire Games
3
4 This file is part of Lugaru.
5
6 Lugaru is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Lugaru is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Lugaru.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <math.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <fstream>
24 #include <iostream>
25 #include <zlib.h>
26 #include <set>
27 #include "gamegl.h"
28 #include "MacCompatibility.h"
29 #include "Settings.h"
30
31 #include "Game.h"
32
33 using namespace Game;
34
35 #include "openal_wrapper.h"
36
37 #ifdef WIN32
38 #include <windows.h>
39 #include <shellapi.h>
40 #include "win-res/resource.h"
41 #endif
42
43 extern float multiplier;
44 extern float sps;
45 extern float realmultiplier;
46 extern int slomo;
47 extern bool cellophane;
48 extern float texdetail;
49
50 extern bool osx;
51 extern bool freeze;
52 extern bool stillloading;
53 extern int mainmenu;
54
55 extern float slomospeed;
56 extern float slomofreq;
57 extern bool visibleloading;
58
59 extern SDL_Window *sdlwindow;
60
61 using namespace std;
62
63 set<pair<int,int>> resolutions;
64
65 // statics/globals (internal only) ------------------------------------------
66
67 #ifdef _MSC_VER
68 #pragma warning(push)
69 #pragma warning(disable: 4273)
70 #endif
71
72 #ifndef __MINGW32__ // FIXME: Temporary workaround for GL-8
73 #define GL_FUNC(ret,fn,params,call,rt) \
74     extern "C" { \
75         static ret (GLAPIENTRY *p##fn) params = NULL; \
76         ret GLAPIENTRY fn params { rt p##fn call; } \
77     }
78 #include "glstubs.h"
79 #undef GL_FUNC
80 #endif // __MINGW32__
81
82 #ifdef _MSC_VER
83 #pragma warning(pop)
84 #endif
85
86 static bool lookup_glsym(const char *funcname, void **func)
87 {
88     *func = SDL_GL_GetProcAddress(funcname);
89     if (*func == NULL) {
90         fprintf(stderr, "Failed to find OpenGL symbol \"%s\"\n", funcname);
91         return false;
92     }
93     return true;
94 }
95
96 static bool lookup_all_glsyms(void)
97 {
98     bool retval = true;
99 #ifndef __MINGW32__ // FIXME: Temporary workaround for GL-8
100 #define GL_FUNC(ret,fn,params,call,rt) \
101         if (!lookup_glsym(#fn, (void **) &p##fn)) retval = false;
102 #include "glstubs.h"
103 #undef GL_FUNC
104 #endif // __MINGW32__
105     return retval;
106 }
107
108 #ifndef __MINGW32__ // FIXME: Temporary workaround for GL-8
109 static void GLAPIENTRY glDeleteTextures_doNothing(GLsizei n, const GLuint *textures)
110 {
111     // no-op.
112 }
113 #endif // __MINGW32__
114
115 #ifdef MessageBox
116 #undef MessageBox
117 #endif
118 #define MessageBox(hwnd,text,title,flags) STUBBED("msgbox")
119
120 // Menu defs
121
122 int kContextWidth;
123 int kContextHeight;
124
125 static int _argc = 0;
126 static char **_argv = NULL;
127
128 bool cmdline(const char *cmd)
129 {
130     for (int i = 1; i < _argc; i++) {
131         char *arg = _argv[i];
132         while (*arg == '-')
133             arg++;
134         if (strcasecmp(arg, cmd) == 0)
135             return true;
136     }
137
138     return false;
139 }
140
141 //-----------------------------------------------------------------------------------------------------------------------
142
143 // OpenGL Drawing
144
145 void initGL()
146 {
147     glClear( GL_COLOR_BUFFER_BIT );
148     swap_gl_buffers();
149
150     // clear all states
151     glDisable( GL_ALPHA_TEST);
152     glDisable( GL_BLEND);
153     glDisable( GL_DEPTH_TEST);
154     glDisable( GL_FOG);
155     glDisable( GL_LIGHTING);
156     glDisable( GL_LOGIC_OP);
157     glDisable( GL_TEXTURE_1D);
158     glDisable( GL_TEXTURE_2D);
159     glPixelTransferi( GL_MAP_COLOR, GL_FALSE);
160     glPixelTransferi( GL_RED_SCALE, 1);
161     glPixelTransferi( GL_RED_BIAS, 0);
162     glPixelTransferi( GL_GREEN_SCALE, 1);
163     glPixelTransferi( GL_GREEN_BIAS, 0);
164     glPixelTransferi( GL_BLUE_SCALE, 1);
165     glPixelTransferi( GL_BLUE_BIAS, 0);
166     glPixelTransferi( GL_ALPHA_SCALE, 1);
167     glPixelTransferi( GL_ALPHA_BIAS, 0);
168
169     // set initial rendering states
170     glShadeModel( GL_SMOOTH);
171     glClearDepth( 1.0f);
172     glDepthFunc( GL_LEQUAL);
173     glDepthMask( GL_TRUE);
174     glEnable( GL_DEPTH_TEST);
175     glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
176     glCullFace( GL_FRONT);
177     glEnable( GL_CULL_FACE);
178     glEnable( GL_LIGHTING);
179     glEnable( GL_DITHER);
180     glEnable( GL_COLOR_MATERIAL);
181     glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
182     glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
183     glAlphaFunc( GL_GREATER, 0.5f);
184
185     if ( CanInitStereo(stereomode) ) {
186         InitStereo(stereomode);
187     } else {
188         fprintf(stderr, "Failed to initialize stereo, disabling.\n");
189         stereomode = stereoNone;
190     }
191 }
192
193 void toggleFullscreen()
194 {
195     fullscreen = !fullscreen;
196     Uint32 flags = SDL_GetWindowFlags(sdlwindow);
197     if (flags & SDL_WINDOW_FULLSCREEN) {
198         flags &= ~SDL_WINDOW_FULLSCREEN;
199     } else {
200         flags |= SDL_WINDOW_FULLSCREEN;
201     }
202     SDL_SetWindowFullscreen(sdlwindow, flags);
203 }
204
205 static SDL_bool sdlEventProc(const SDL_Event &e)
206 {
207     switch (e.type) {
208         case SDL_QUIT:
209             return SDL_FALSE;
210
211         case SDL_WINDOWEVENT:
212             if (e.window.event == SDL_WINDOWEVENT_CLOSE) {
213                 return SDL_FALSE;
214             }
215         break;
216
217         case SDL_MOUSEMOTION:
218             deltah += e.motion.xrel;
219             deltav += e.motion.yrel;
220         break;
221
222         case SDL_KEYDOWN:
223             if ((e.key.keysym.scancode == SDL_SCANCODE_G) &&
224                 (e.key.keysym.mod & KMOD_CTRL)) {
225                 SDL_bool mode = SDL_TRUE;
226                 if ((SDL_GetWindowFlags(sdlwindow) & SDL_WINDOW_FULLSCREEN) == 0)
227                     mode = (SDL_GetWindowGrab(sdlwindow) ? SDL_FALSE : SDL_TRUE);
228                 SDL_SetWindowGrab(sdlwindow, mode);
229                 SDL_SetRelativeMouseMode(mode);
230             } else if ( (e.key.keysym.scancode == SDL_SCANCODE_RETURN) && (e.key.keysym.mod & KMOD_ALT) ) {
231                 toggleFullscreen();
232             }
233         break;
234     }
235     return SDL_TRUE;
236 }
237
238
239
240 // --------------------------------------------------------------------------
241
242 static Point gMidPoint;
243
244 bool SetUp ()
245 {
246     LOGFUNC;
247
248     osx = 0;
249     cellophane = 0;
250     texdetail = 4;
251     slomospeed = 0.25;
252     slomofreq = 8012;
253
254     DefaultSettings();
255
256     if (!SDL_WasInit(SDL_INIT_VIDEO))
257         if (SDL_Init(SDL_INIT_VIDEO) == -1) {
258             fprintf(stderr, "SDL_Init() failed: %s\n", SDL_GetError());
259             return false;
260         }
261     if (!LoadSettings()) {
262         fprintf(stderr, "Failed to load config, creating default\n");
263         SaveSettings();
264     }
265     if (kBitsPerPixel != 32 && kBitsPerPixel != 16) {
266         kBitsPerPixel = 16;
267     }
268
269     if (SDL_GL_LoadLibrary(NULL) == -1) {
270         fprintf(stderr, "SDL_GL_LoadLibrary() failed: %s\n", SDL_GetError());
271         SDL_Quit();
272         return false;
273     }
274
275     for (int displayIdx = 0; displayIdx < SDL_GetNumVideoDisplays(); ++displayIdx) {
276         for (int i = 0; i < SDL_GetNumDisplayModes(displayIdx); ++i) {
277             SDL_DisplayMode mode;
278             if (SDL_GetDisplayMode(displayIdx, i, &mode) == -1)
279                 continue;
280             if ((mode.w < 640) || (mode.h < 480))
281                 continue;  // sane lower limit.
282             pair<int,int> resolution(mode.w, mode.h);
283             resolutions.insert(resolution);
284         }
285     }
286
287     if (resolutions.empty()) {
288         const std::string error = "No suitable video resolutions found.";
289         cerr << error << endl;
290         SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Lugaru init failed!", error.c_str(), NULL);
291         SDL_Quit();
292         return false;
293     }
294
295     if (cmdline("showresolutions")) {
296         printf("Available resolutions:\n");
297         for (auto resolution = resolutions.begin(); resolution != resolutions.end(); resolution++) {
298             printf("  %d x %d\n", (int) resolution->first, (int) resolution->second);
299         }
300     }
301
302     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
303     SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
304
305     Uint32 sdlflags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
306     if ((fullscreen || cmdline("fullscreen")) && !cmdline("windowed")) {
307         fullscreen = 1;
308         sdlflags |= SDL_WINDOW_FULLSCREEN;
309     }
310     if (!cmdline("nomousegrab"))
311         sdlflags |= SDL_WINDOW_INPUT_GRABBED;
312
313     sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
314                                  kContextWidth, kContextHeight, sdlflags);
315
316     if (!sdlwindow) {
317         fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
318         fprintf(stderr, "forcing 640x480...\n");
319         kContextWidth = 640;
320         kContextHeight = 480;
321         sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
322                                      kContextWidth, kContextHeight, sdlflags);
323         if (!sdlwindow) {
324             fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
325             fprintf(stderr, "forcing 640x480 windowed mode...\n");
326             sdlflags &= ~SDL_WINDOW_FULLSCREEN;
327             sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
328                                          kContextWidth, kContextHeight, sdlflags);
329
330             if (!sdlwindow) {
331                 fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
332                 return false;
333             }
334         }
335     }
336
337     SDL_GLContext glctx = SDL_GL_CreateContext(sdlwindow);
338     if (!glctx) {
339         fprintf(stderr, "SDL_GL_CreateContext() failed: %s\n", SDL_GetError());
340         SDL_Quit();
341         return false;
342     }
343
344     SDL_GL_MakeCurrent(sdlwindow, glctx);
345
346     if (!lookup_all_glsyms()) {
347         fprintf(stderr, "Missing required OpenGL functions.\n");
348         SDL_Quit();
349         return false;
350     }
351
352     int dblbuf = 0;
353     if ((SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &dblbuf) == -1) || (!dblbuf))
354     {
355         fprintf(stderr, "Failed to get a double-buffered context.\n");
356         SDL_Quit();
357         return false;
358     }
359
360     if (SDL_GL_SetSwapInterval(-1) == -1)  // try swap_tear first.
361         SDL_GL_SetSwapInterval(1);
362
363     SDL_ShowCursor(0);
364     if (!cmdline("nomousegrab"))
365         SDL_SetRelativeMouseMode(SDL_TRUE);
366
367     initGL();
368
369     GLint width = kContextWidth;
370     GLint height = kContextHeight;
371     gMidPoint.h = width / 2;
372     gMidPoint.v = height / 2;
373     screenwidth = width;
374     screenheight = height;
375
376     newdetail = detail;
377     newscreenwidth = screenwidth;
378     newscreenheight = screenheight;
379
380     /* If saved resolution is not in the list, add it to the list (so that it’s selectable in the options) */
381     pair<int,int> startresolution(width,height);
382     if (resolutions.find(startresolution) == resolutions.end()) {
383         resolutions.insert(startresolution);
384     }
385
386     InitGame();
387
388     return true;
389 }
390
391
392 static void DoMouse()
393 {
394
395     if (mainmenu || ( (abs(deltah) < 10 * realmultiplier * 1000) && (abs(deltav) < 10 * realmultiplier * 1000) )) {
396         deltah *= usermousesensitivity;
397         deltav *= usermousesensitivity;
398         mousecoordh += deltah;
399         mousecoordv += deltav;
400         if (mousecoordh < 0)
401             mousecoordh = 0;
402         else if (mousecoordh >= kContextWidth)
403             mousecoordh = kContextWidth - 1;
404         if (mousecoordv < 0)
405             mousecoordv = 0;
406         else if (mousecoordv >= kContextHeight)
407             mousecoordv = kContextHeight - 1;
408     }
409
410 }
411
412 void DoFrameRate (int update)
413 {
414     static long frames = 0;
415
416     static AbsoluteTime time = {0, 0};
417     static AbsoluteTime frametime = {0, 0};
418     AbsoluteTime currTime = UpTime ();
419     double deltaTime = (float) AbsoluteDeltaToDuration (currTime, frametime);
420
421     if (0 > deltaTime) // if negative microseconds
422         deltaTime /= -1000000.0;
423     else // else milliseconds
424         deltaTime /= 1000.0;
425
426     multiplier = deltaTime;
427     if (multiplier < .001)
428         multiplier = .001;
429     if (multiplier > 10)
430         multiplier = 10;
431     if (update)
432         frametime = currTime; // reset for next time interval
433
434     deltaTime = (float) AbsoluteDeltaToDuration (currTime, time);
435
436     if (0 > deltaTime) // if negative microseconds
437         deltaTime /= -1000000.0;
438     else // else milliseconds
439         deltaTime /= 1000.0;
440     frames++;
441     if (0.001 <= deltaTime) { // has update interval passed
442         if (update) {
443             time = currTime; // reset for next time interval
444             frames = 0;
445         }
446     }
447 }
448
449
450 void DoUpdate ()
451 {
452     static float sps = 200;
453     static int count;
454     static float oldmult;
455
456     DoFrameRate(1);
457     if (multiplier > .6)
458         multiplier = .6;
459
460     fps = 1 / multiplier;
461
462     count = multiplier * sps;
463     if (count < 2)
464         count = 2;
465
466     realmultiplier = multiplier;
467     multiplier *= gamespeed;
468     if (difficulty == 1)
469         multiplier *= .9;
470     if (difficulty == 0)
471         multiplier *= .8;
472
473     if (loading == 4)
474         multiplier *= .00001;
475     if (slomo && !mainmenu)
476         multiplier *= slomospeed;
477     oldmult = multiplier;
478     multiplier /= (float)count;
479
480     DoMouse();
481
482     TickOnce();
483
484     for (int i = 0; i < count; i++) {
485         Tick();
486     }
487     multiplier = oldmult;
488
489     TickOnceAfter();
490     /* - Debug code to test how many channels were active on average per frame
491         static long frames = 0;
492
493         static AbsoluteTime start = {0,0};
494         AbsoluteTime currTime = UpTime ();
495         static int num_channels = 0;
496
497         num_channels += OPENAL_GetChannelsPlaying();
498         double deltaTime = (float) AbsoluteDeltaToDuration (currTime, start);
499
500         if (0 > deltaTime)  // if negative microseconds
501             deltaTime /= -1000000.0;
502         else                // else milliseconds
503             deltaTime /= 1000.0;
504
505         ++frames;
506
507         if (deltaTime >= 1)
508         {
509             start = currTime;
510             float avg_channels = (float)num_channels / (float)frames;
511
512             ofstream opstream("log.txt",ios::app);
513             opstream << "Average frame count: ";
514             opstream << frames;
515             opstream << " frames - ";
516             opstream << avg_channels;
517             opstream << " per frame.\n";
518             opstream.close();
519
520             frames = 0;
521             num_channels = 0;
522         }
523     */
524     if ( stereomode == stereoNone ) {
525         DrawGLScene(stereoCenter);
526     } else {
527         DrawGLScene(stereoLeft);
528         DrawGLScene(stereoRight);
529     }
530 }
531
532 // --------------------------------------------------------------------------
533
534
535 void CleanUp (void)
536 {
537     LOGFUNC;
538
539     SDL_Quit();
540 #ifndef __MINGW32__ // FIXME: Temporary workaround for GL-8
541 #define GL_FUNC(ret,fn,params,call,rt) p##fn = NULL;
542 #include "glstubs.h"
543 #undef GL_FUNC
544     // cheat here...static destructors are calling glDeleteTexture() after
545     //  the context is destroyed and libGL unloaded by SDL_Quit().
546     pglDeleteTextures = glDeleteTextures_doNothing;
547 #endif // __MINGW32__
548
549 }
550
551 // --------------------------------------------------------------------------
552
553 static bool IsFocused()
554 {
555     return ((SDL_GetWindowFlags(sdlwindow) & SDL_WINDOW_INPUT_FOCUS) != 0);
556 }
557
558
559
560 #ifndef WIN32
561 // (code lifted from physfs: http://icculus.org/physfs/ ... zlib license.)
562 static char *findBinaryInPath(const char *bin, char *envr)
563 {
564     size_t alloc_size = 0;
565     char *exe = NULL;
566     char *start = envr;
567     char *ptr;
568
569     do {
570         size_t size;
571         ptr = strchr(start, ':');  /* find next $PATH separator. */
572         if (ptr)
573             *ptr = '\0';
574
575         size = strlen(start) + strlen(bin) + 2;
576         if (size > alloc_size) {
577             char *x = (char *) realloc(exe, size);
578             if (x == NULL) {
579                 if (exe != NULL)
580                     free(exe);
581                 return(NULL);
582             } /* if */
583
584             alloc_size = size;
585             exe = x;
586         } /* if */
587
588         /* build full binary path... */
589         strcpy(exe, start);
590         if ((exe[0] == '\0') || (exe[strlen(exe) - 1] != '/'))
591             strcat(exe, "/");
592         strcat(exe, bin);
593
594         if (access(exe, X_OK) == 0) { /* Exists as executable? We're done. */
595             strcpy(exe, start);  /* i'm lazy. piss off. */
596             return(exe);
597         } /* if */
598
599         start = ptr + 1;  /* start points to beginning of next element. */
600     } while (ptr != NULL);
601
602     if (exe != NULL)
603         free(exe);
604
605     return(NULL);  /* doesn't exist in path. */
606 } /* findBinaryInPath */
607
608
609 char *calcBaseDir(const char *argv0)
610 {
611     /* If there isn't a path on argv0, then look through the $PATH for it. */
612     char *retval;
613     char *envr;
614
615     if (strchr(argv0, '/')) {
616         retval = strdup(argv0);
617         if (retval)
618             *((char *) strrchr(retval, '/')) = '\0';
619         return(retval);
620     }
621
622     envr = getenv("PATH");
623     if (!envr)
624         return NULL;
625     envr = strdup(envr);
626     if (!envr)
627         return NULL;
628     retval = findBinaryInPath(argv0, envr);
629     free(envr);
630     return(retval);
631 }
632
633 static inline void chdirToAppPath(const char *argv0)
634 {
635     char *dir = calcBaseDir(argv0);
636     if (dir) {
637 #if (defined(__APPLE__) && defined(__MACH__))
638         // Chop off /Contents/MacOS if it's at the end of the string, so we
639         //  land in the base of the app bundle.
640         const size_t len = strlen(dir);
641         const char *bundledirs = "/Contents/MacOS";
642         const size_t bundledirslen = strlen(bundledirs);
643         if (len > bundledirslen) {
644             char *ptr = (dir + len) - bundledirslen;
645             if (strcasecmp(ptr, bundledirs) == 0)
646                 *ptr = '\0';
647         }
648 #endif
649         chdir(dir);
650         free(dir);
651     }
652 }
653 #endif
654
655
656 int main(int argc, char **argv)
657 {
658     _argc = argc;
659     _argv = argv;
660
661     // !!! FIXME: we could use a Win32 API for this.  --ryan.
662 #ifndef WIN32
663     chdirToAppPath(argv[0]);
664 #endif
665
666     LOGFUNC;
667
668     try {
669         {
670             newGame();
671
672             if (!SetUp ())
673                 return 42;
674
675             bool gameDone = false;
676             bool gameFocused = true;
677
678             while (!gameDone && !tryquit) {
679                 if (IsFocused()) {
680                     gameFocused = true;
681
682                     // check windows messages
683
684                     deltah = 0;
685                     deltav = 0;
686                     SDL_Event e;
687                     if (!waiting) {
688                         // message pump
689                         while ( SDL_PollEvent( &e ) ) {
690                             if (!sdlEventProc(e)) {
691                                 gameDone = true;
692                                 break;
693                             }
694                         }
695                     }
696
697                     // game
698                     DoUpdate();
699                 } else {
700                     if (gameFocused) {
701                         // allow game chance to pause
702                         gameFocused = false;
703                         DoUpdate();
704                     }
705
706                     // game is not in focus, give CPU time to other apps by waiting for messages instead of 'peeking'
707                     SDL_WaitEvent(0);
708                 }
709             }
710
711             deleteGame();
712         }
713
714         CleanUp ();
715
716         return 0;
717     } catch (const std::exception& error) {
718         CleanUp();
719
720         std::string e = "Caught exception: ";
721         e += error.what();
722
723         LOG(e);
724
725         MessageBox(g_windowHandle, error.what(), "ERROR", MB_OK | MB_ICONEXCLAMATION);
726
727         return -1;
728     }
729 }