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