]> git.jsancho.org Git - lugaru.git/blob - Source/OpenGL_Windows.cpp
Fixed #11 so that we can close the window while editing text
[lugaru.git] / Source / OpenGL_Windows.cpp
1 /*
2 Copyright (C) 2003, 2010 - Wolfire Games
3 Copyright (C) 2010-2016 - Lugaru contributors (see AUTHORS file)
4
5 This file is part of Lugaru.
6
7 Lugaru is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 Lugaru is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Lugaru.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <math.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <fstream>
25 #include <iostream>
26 #include <zlib.h>
27 #include <set>
28 #include "gamegl.h"
29 #include "MacCompatibility.h"
30 #include "Settings.h"
31
32 #include "Game.h"
33
34 using namespace Game;
35
36 #include "openal_wrapper.h"
37
38 #ifdef WIN32
39 #include <windows.h>
40 #include <shellapi.h>
41 #include "win-res/resource.h"
42 #endif
43
44 extern float multiplier;
45 extern float sps;
46 extern float realmultiplier;
47 extern int slomo;
48 extern bool cellophane;
49 extern float texdetail;
50
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 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     cellophane = 0;
249     texdetail = 4;
250     slomospeed = 0.25;
251     slomofreq = 8012;
252
253     DefaultSettings();
254
255     if (!SDL_WasInit(SDL_INIT_VIDEO))
256         if (SDL_Init(SDL_INIT_VIDEO) == -1) {
257             fprintf(stderr, "SDL_Init() failed: %s\n", SDL_GetError());
258             return false;
259         }
260     if (!LoadSettings()) {
261         fprintf(stderr, "Failed to load config, creating default\n");
262         SaveSettings();
263     }
264     if (kBitsPerPixel != 32 && kBitsPerPixel != 16) {
265         kBitsPerPixel = 16;
266     }
267
268     if (SDL_GL_LoadLibrary(NULL) == -1) {
269         fprintf(stderr, "SDL_GL_LoadLibrary() failed: %s\n", SDL_GetError());
270         SDL_Quit();
271         return false;
272     }
273
274     for (int displayIdx = 0; displayIdx < SDL_GetNumVideoDisplays(); ++displayIdx) {
275         for (int i = 0; i < SDL_GetNumDisplayModes(displayIdx); ++i) {
276             SDL_DisplayMode mode;
277             if (SDL_GetDisplayMode(displayIdx, i, &mode) == -1)
278                 continue;
279             if ((mode.w < 640) || (mode.h < 480))
280                 continue;  // sane lower limit.
281             pair<int,int> resolution(mode.w, mode.h);
282             resolutions.insert(resolution);
283         }
284     }
285
286     if (resolutions.empty()) {
287         const std::string error = "No suitable video resolutions found.";
288         cerr << error << endl;
289         SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Lugaru init failed!", error.c_str(), NULL);
290         SDL_Quit();
291         return false;
292     }
293
294     if (cmdline("showresolutions")) {
295         printf("Available resolutions:\n");
296         for (auto resolution = resolutions.begin(); resolution != resolutions.end(); resolution++) {
297             printf("  %d x %d\n", (int) resolution->first, (int) resolution->second);
298         }
299     }
300
301     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
302     SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
303
304     Uint32 sdlflags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
305     if ((fullscreen || cmdline("fullscreen")) && !cmdline("windowed")) {
306         fullscreen = 1;
307         sdlflags |= SDL_WINDOW_FULLSCREEN;
308     }
309     if (!cmdline("nomousegrab"))
310         sdlflags |= SDL_WINDOW_INPUT_GRABBED;
311
312     sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
313                                  kContextWidth, kContextHeight, sdlflags);
314
315     if (!sdlwindow) {
316         fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
317         fprintf(stderr, "forcing 640x480...\n");
318         kContextWidth = 640;
319         kContextHeight = 480;
320         sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
321                                      kContextWidth, kContextHeight, sdlflags);
322         if (!sdlwindow) {
323             fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
324             fprintf(stderr, "forcing 640x480 windowed mode...\n");
325             sdlflags &= ~SDL_WINDOW_FULLSCREEN;
326             sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
327                                          kContextWidth, kContextHeight, sdlflags);
328
329             if (!sdlwindow) {
330                 fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
331                 return false;
332             }
333         }
334     }
335
336     SDL_GLContext glctx = SDL_GL_CreateContext(sdlwindow);
337     if (!glctx) {
338         fprintf(stderr, "SDL_GL_CreateContext() failed: %s\n", SDL_GetError());
339         SDL_Quit();
340         return false;
341     }
342
343     SDL_GL_MakeCurrent(sdlwindow, glctx);
344
345     if (!lookup_all_glsyms()) {
346         fprintf(stderr, "Missing required OpenGL functions.\n");
347         SDL_Quit();
348         return false;
349     }
350
351     int dblbuf = 0;
352     if ((SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &dblbuf) == -1) || (!dblbuf))
353     {
354         fprintf(stderr, "Failed to get a double-buffered context.\n");
355         SDL_Quit();
356         return false;
357     }
358
359     if (SDL_GL_SetSwapInterval(-1) == -1)  // try swap_tear first.
360         SDL_GL_SetSwapInterval(1);
361
362     SDL_ShowCursor(0);
363     if (!cmdline("nomousegrab"))
364         SDL_SetRelativeMouseMode(SDL_TRUE);
365
366     initGL();
367
368     GLint width = kContextWidth;
369     GLint height = kContextHeight;
370     gMidPoint.h = width / 2;
371     gMidPoint.v = height / 2;
372     screenwidth = width;
373     screenheight = height;
374
375     newdetail = detail;
376     newscreenwidth = screenwidth;
377     newscreenheight = screenheight;
378
379     /* If saved resolution is not in the list, add it to the list (so that it’s selectable in the options) */
380     pair<int,int> startresolution(width,height);
381     if (resolutions.find(startresolution) == resolutions.end()) {
382         resolutions.insert(startresolution);
383     }
384
385     InitGame();
386
387     return true;
388 }
389
390
391 static void DoMouse()
392 {
393
394     if (mainmenu || ( (abs(deltah) < 10 * realmultiplier * 1000) && (abs(deltav) < 10 * realmultiplier * 1000) )) {
395         deltah *= usermousesensitivity;
396         deltav *= usermousesensitivity;
397         mousecoordh += deltah;
398         mousecoordv += deltav;
399         if (mousecoordh < 0)
400             mousecoordh = 0;
401         else if (mousecoordh >= kContextWidth)
402             mousecoordh = kContextWidth - 1;
403         if (mousecoordv < 0)
404             mousecoordv = 0;
405         else if (mousecoordv >= kContextHeight)
406             mousecoordv = kContextHeight - 1;
407     }
408
409 }
410
411 void DoFrameRate (int update)
412 {
413     static long frames = 0;
414
415     static AbsoluteTime time = {0, 0};
416     static AbsoluteTime frametime = {0, 0};
417     AbsoluteTime currTime = UpTime ();
418     double deltaTime = (float) AbsoluteDeltaToDuration (currTime, frametime);
419
420     if (0 > deltaTime) // if negative microseconds
421         deltaTime /= -1000000.0;
422     else // else milliseconds
423         deltaTime /= 1000.0;
424
425     multiplier = deltaTime;
426     if (multiplier < .001)
427         multiplier = .001;
428     if (multiplier > 10)
429         multiplier = 10;
430     if (update)
431         frametime = currTime; // reset for next time interval
432
433     deltaTime = (float) AbsoluteDeltaToDuration (currTime, time);
434
435     if (0 > deltaTime) // if negative microseconds
436         deltaTime /= -1000000.0;
437     else // else milliseconds
438         deltaTime /= 1000.0;
439     frames++;
440     if (0.001 <= deltaTime) { // has update interval passed
441         if (update) {
442             time = currTime; // reset for next time interval
443             frames = 0;
444         }
445     }
446 }
447
448
449 void DoUpdate ()
450 {
451     static float sps = 200;
452     static int count;
453     static float oldmult;
454
455     DoFrameRate(1);
456     if (multiplier > .6)
457         multiplier = .6;
458
459     fps = 1 / multiplier;
460
461     count = multiplier * sps;
462     if (count < 2)
463         count = 2;
464
465     realmultiplier = multiplier;
466     multiplier *= gamespeed;
467     if (difficulty == 1)
468         multiplier *= .9;
469     if (difficulty == 0)
470         multiplier *= .8;
471
472     if (loading == 4)
473         multiplier *= .00001;
474     if (slomo && !mainmenu)
475         multiplier *= slomospeed;
476     oldmult = multiplier;
477     multiplier /= (float)count;
478
479     DoMouse();
480
481     TickOnce();
482
483     for (int i = 0; i < count; i++) {
484         Tick();
485     }
486     multiplier = oldmult;
487
488     TickOnceAfter();
489     /* - Debug code to test how many channels were active on average per frame
490         static long frames = 0;
491
492         static AbsoluteTime start = {0,0};
493         AbsoluteTime currTime = UpTime ();
494         static int num_channels = 0;
495
496         num_channels += OPENAL_GetChannelsPlaying();
497         double deltaTime = (float) AbsoluteDeltaToDuration (currTime, start);
498
499         if (0 > deltaTime)  // if negative microseconds
500             deltaTime /= -1000000.0;
501         else                // else milliseconds
502             deltaTime /= 1000.0;
503
504         ++frames;
505
506         if (deltaTime >= 1)
507         {
508             start = currTime;
509             float avg_channels = (float)num_channels / (float)frames;
510
511             ofstream opstream("log.txt",ios::app);
512             opstream << "Average frame count: ";
513             opstream << frames;
514             opstream << " frames - ";
515             opstream << avg_channels;
516             opstream << " per frame.\n";
517             opstream.close();
518
519             frames = 0;
520             num_channels = 0;
521         }
522     */
523     if ( stereomode == stereoNone ) {
524         DrawGLScene(stereoCenter);
525     } else {
526         DrawGLScene(stereoLeft);
527         DrawGLScene(stereoRight);
528     }
529 }
530
531 // --------------------------------------------------------------------------
532
533
534 void CleanUp (void)
535 {
536     LOGFUNC;
537
538     SDL_Quit();
539 #ifndef __MINGW32__ // FIXME: Temporary workaround for GL-8
540 #define GL_FUNC(ret,fn,params,call,rt) p##fn = NULL;
541 #include "glstubs.h"
542 #undef GL_FUNC
543     // cheat here...static destructors are calling glDeleteTexture() after
544     //  the context is destroyed and libGL unloaded by SDL_Quit().
545     pglDeleteTextures = glDeleteTextures_doNothing;
546 #endif // __MINGW32__
547
548 }
549
550 // --------------------------------------------------------------------------
551
552 static bool IsFocused()
553 {
554     return ((SDL_GetWindowFlags(sdlwindow) & SDL_WINDOW_INPUT_FOCUS) != 0);
555 }
556
557
558
559 #ifndef WIN32
560 // (code lifted from physfs: http://icculus.org/physfs/ ... zlib license.)
561 static char *findBinaryInPath(const char *bin, char *envr)
562 {
563     size_t alloc_size = 0;
564     char *exe = NULL;
565     char *start = envr;
566     char *ptr;
567
568     do {
569         size_t size;
570         ptr = strchr(start, ':');  /* find next $PATH separator. */
571         if (ptr)
572             *ptr = '\0';
573
574         size = strlen(start) + strlen(bin) + 2;
575         if (size > alloc_size) {
576             char *x = (char *) realloc(exe, size);
577             if (x == NULL) {
578                 if (exe != NULL)
579                     free(exe);
580                 return(NULL);
581             } /* if */
582
583             alloc_size = size;
584             exe = x;
585         } /* if */
586
587         /* build full binary path... */
588         strcpy(exe, start);
589         if ((exe[0] == '\0') || (exe[strlen(exe) - 1] != '/'))
590             strcat(exe, "/");
591         strcat(exe, bin);
592
593         if (access(exe, X_OK) == 0) { /* Exists as executable? We're done. */
594             strcpy(exe, start);  /* i'm lazy. piss off. */
595             return(exe);
596         } /* if */
597
598         start = ptr + 1;  /* start points to beginning of next element. */
599     } while (ptr != NULL);
600
601     if (exe != NULL)
602         free(exe);
603
604     return(NULL);  /* doesn't exist in path. */
605 } /* findBinaryInPath */
606
607
608 char *calcBaseDir(const char *argv0)
609 {
610     /* If there isn't a path on argv0, then look through the $PATH for it. */
611     char *retval;
612     char *envr;
613
614     if (strchr(argv0, '/')) {
615         retval = strdup(argv0);
616         if (retval)
617             *((char *) strrchr(retval, '/')) = '\0';
618         return(retval);
619     }
620
621     envr = getenv("PATH");
622     if (!envr)
623         return NULL;
624     envr = strdup(envr);
625     if (!envr)
626         return NULL;
627     retval = findBinaryInPath(argv0, envr);
628     free(envr);
629     return(retval);
630 }
631
632 static inline void chdirToAppPath(const char *argv0)
633 {
634     char *dir = calcBaseDir(argv0);
635     if (dir) {
636 #if (defined(__APPLE__) && defined(__MACH__))
637         // Chop off /Contents/MacOS if it's at the end of the string, so we
638         //  land in the base of the app bundle.
639         const size_t len = strlen(dir);
640         const char *bundledirs = "/Contents/MacOS";
641         const size_t bundledirslen = strlen(bundledirs);
642         if (len > bundledirslen) {
643             char *ptr = (dir + len) - bundledirslen;
644             if (strcasecmp(ptr, bundledirs) == 0)
645                 *ptr = '\0';
646         }
647 #endif
648         chdir(dir);
649         free(dir);
650     }
651 }
652 #endif
653
654
655 int main(int argc, char **argv)
656 {
657     _argc = argc;
658     _argv = argv;
659
660     // !!! FIXME: we could use a Win32 API for this.  --ryan.
661 #ifndef WIN32
662     chdirToAppPath(argv[0]);
663 #endif
664
665     LOGFUNC;
666
667     try {
668         {
669             newGame();
670
671             if (!SetUp ())
672                 return 42;
673
674             bool gameDone = false;
675             bool gameFocused = true;
676
677             while (!gameDone && !tryquit) {
678                 if (IsFocused()) {
679                     gameFocused = true;
680
681                     // check windows messages
682
683                     deltah = 0;
684                     deltav = 0;
685                     SDL_Event e;
686                     if (!waiting) {
687                         // message pump
688                         while ( SDL_PollEvent( &e ) ) {
689                             if (!sdlEventProc(e)) {
690                                 gameDone = true;
691                                 break;
692                             }
693                         }
694                     }
695
696                     // game
697                     DoUpdate();
698                 } else {
699                     if (gameFocused) {
700                         // allow game chance to pause
701                         gameFocused = false;
702                         DoUpdate();
703                     }
704
705                     // game is not in focus, give CPU time to other apps by waiting for messages instead of 'peeking'
706                     SDL_WaitEvent(0);
707                 }
708             }
709
710             deleteGame();
711         }
712
713         CleanUp ();
714
715         return 0;
716     } catch (const std::exception& error) {
717         CleanUp();
718
719         std::string e = "Caught exception: ";
720         e += error.what();
721
722         LOG(e);
723
724         MessageBox(g_windowHandle, error.what(), "ERROR", MB_OK | MB_ICONEXCLAMATION);
725
726         return -1;
727     }
728 }