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