]> git.jsancho.org Git - lugaru.git/blob - Source/OpenGL_Windows.cpp
45e7b8b31db2beae284d2304f4ed6671cc89f13a
[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 #include <jpeglib.h>
28 #include <png.h>
29 #include <zlib.h>
30
31 using namespace Game;
32
33 bool load_image(const char * fname, ImageRec & tex);
34 static bool load_png(const char * fname, ImageRec & tex);
35 static bool load_jpg(const char * fname, ImageRec & tex);
36 bool save_image(const char * fname);
37 static bool save_png(const char * fname);
38
39
40 #include "openal_wrapper.h"
41
42 extern float multiplier;
43 extern float sps;
44 extern float realmultiplier;
45 extern int slomo;
46 extern bool cellophane;
47 extern float texdetail;
48
49 extern bool osx;
50 extern bool freeze;
51 extern bool stillloading;
52 extern int mainmenu;
53 /*extern*/
54 bool gameFocused;
55
56 extern float slomospeed;
57 extern float slomofreq;
58 extern bool visibleloading;
59
60
61
62 #include <math.h>
63 #include <stdio.h>
64 #include <string.h>
65 #include <fstream>
66 #include <iostream>
67 #include <set>
68 #include "gamegl.h"
69 #include "MacCompatibility.h"
70 #include "Settings.h"
71
72 #ifdef WIN32
73 #include <shellapi.h>
74 #include "win-res/resource.h"
75 #endif
76
77 extern SDL_Window *sdlwindow;
78
79 using namespace std;
80
81 set<pair<int,int>> resolutions;
82
83 Boolean SetUp ();
84 void DoUpdate ();
85
86 void CleanUp (void);
87
88 // statics/globals (internal only) ------------------------------------------
89
90 #ifdef _MSC_VER
91 #pragma warning(push)
92 #pragma warning(disable: 4273)
93 #endif
94
95 #ifndef __MINGW32__ // FIXME: Temporary workaround for GL-8
96 #define GL_FUNC(ret,fn,params,call,rt) \
97     extern "C" { \
98         static ret (GLAPIENTRY *p##fn) params = NULL; \
99         ret GLAPIENTRY fn params { rt p##fn call; } \
100     }
101 #include "glstubs.h"
102 #undef GL_FUNC
103 #endif // __MINGW32__
104
105 #ifdef _MSC_VER
106 #pragma warning(pop)
107 #endif
108
109 static bool lookup_glsym(const char *funcname, void **func)
110 {
111     *func = SDL_GL_GetProcAddress(funcname);
112     if (*func == NULL) {
113         fprintf(stderr, "Failed to find OpenGL symbol \"%s\"\n", funcname);
114         return false;
115     }
116     return true;
117 }
118
119 static bool lookup_all_glsyms(void)
120 {
121     bool retval = true;
122 #ifndef __MINGW32__ // FIXME: Temporary workaround for GL-8
123 #define GL_FUNC(ret,fn,params,call,rt) \
124         if (!lookup_glsym(#fn, (void **) &p##fn)) retval = false;
125 #include "glstubs.h"
126 #undef GL_FUNC
127 #endif // __MINGW32__
128     return retval;
129 }
130
131 #ifndef __MINGW32__ // FIXME: Temporary workaround for GL-8
132 static void GLAPIENTRY glDeleteTextures_doNothing(GLsizei n, const GLuint *textures)
133 {
134     // no-op.
135 }
136 #endif // __MINGW32__
137
138 #ifdef MessageBox
139 #undef MessageBox
140 #endif
141 #define MessageBox(hwnd,text,title,flags) STUBBED("msgbox")
142
143 // Menu defs
144
145 int kContextWidth;
146 int kContextHeight;
147
148 Boolean gDone = false;
149
150 static int _argc = 0;
151 static char **_argv = NULL;
152
153 bool cmdline(const char *cmd)
154 {
155     for (int i = 1; i < _argc; i++) {
156         char *arg = _argv[i];
157         while (*arg == '-')
158             arg++;
159         if (strcasecmp(arg, cmd) == 0)
160             return true;
161     }
162
163     return false;
164 }
165
166 //-----------------------------------------------------------------------------------------------------------------------
167
168 // OpenGL Drawing
169
170 void initGL()
171 {
172     glClear( GL_COLOR_BUFFER_BIT );
173     swap_gl_buffers();
174
175     // clear all states
176     glDisable( GL_ALPHA_TEST);
177     glDisable( GL_BLEND);
178     glDisable( GL_DEPTH_TEST);
179     glDisable( GL_FOG);
180     glDisable( GL_LIGHTING);
181     glDisable( GL_LOGIC_OP);
182     glDisable( GL_TEXTURE_1D);
183     glDisable( GL_TEXTURE_2D);
184     glPixelTransferi( GL_MAP_COLOR, GL_FALSE);
185     glPixelTransferi( GL_RED_SCALE, 1);
186     glPixelTransferi( GL_RED_BIAS, 0);
187     glPixelTransferi( GL_GREEN_SCALE, 1);
188     glPixelTransferi( GL_GREEN_BIAS, 0);
189     glPixelTransferi( GL_BLUE_SCALE, 1);
190     glPixelTransferi( GL_BLUE_BIAS, 0);
191     glPixelTransferi( GL_ALPHA_SCALE, 1);
192     glPixelTransferi( GL_ALPHA_BIAS, 0);
193
194     // set initial rendering states
195     glShadeModel( GL_SMOOTH);
196     glClearDepth( 1.0f);
197     glDepthFunc( GL_LEQUAL);
198     glDepthMask( GL_TRUE);
199     glEnable( GL_DEPTH_TEST);
200     glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
201     glCullFace( GL_FRONT);
202     glEnable( GL_CULL_FACE);
203     glEnable( GL_LIGHTING);
204     glEnable( GL_DITHER);
205     glEnable( GL_COLOR_MATERIAL);
206     glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
207     glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
208     glAlphaFunc( GL_GREATER, 0.5f);
209
210     if ( CanInitStereo(stereomode) ) {
211         InitStereo(stereomode);
212     } else {
213         fprintf(stderr, "Failed to initialize stereo, disabling.\n");
214         stereomode = stereoNone;
215     }
216 }
217
218 void toggleFullscreen()
219 {
220     fullscreen = !fullscreen;
221     Uint32 flags = SDL_GetWindowFlags(sdlwindow);
222     if (flags & SDL_WINDOW_FULLSCREEN) {
223         flags &= ~SDL_WINDOW_FULLSCREEN;
224     } else {
225         flags |= SDL_WINDOW_FULLSCREEN;
226     }
227     SDL_SetWindowFullscreen(sdlwindow, flags);
228 }
229
230 static SDL_bool sdlEventProc(const SDL_Event &e)
231 {
232     switch (e.type) {
233         case SDL_QUIT:
234             return SDL_FALSE;
235
236         case SDL_WINDOWEVENT:
237             if (e.window.event == SDL_WINDOWEVENT_CLOSE) {
238                 return SDL_FALSE;
239             }
240         break;
241
242         case SDL_MOUSEMOTION:
243             deltah += e.motion.xrel;
244             deltav += e.motion.yrel;
245         break;
246
247         case SDL_KEYDOWN:
248             if ((e.key.keysym.scancode == SDL_SCANCODE_G) &&
249                 (e.key.keysym.mod & KMOD_CTRL)) {
250                 SDL_bool mode = SDL_TRUE;
251                 if ((SDL_GetWindowFlags(sdlwindow) & SDL_WINDOW_FULLSCREEN) == 0)
252                     mode = (SDL_GetWindowGrab(sdlwindow) ? SDL_FALSE : SDL_TRUE);
253                 SDL_SetWindowGrab(sdlwindow, mode);
254                 SDL_SetRelativeMouseMode(mode);
255             } else if ( (e.key.keysym.scancode == SDL_SCANCODE_RETURN) && (e.key.keysym.mod & KMOD_ALT) ) {
256                 toggleFullscreen();
257             }
258         break;
259     }
260     return SDL_TRUE;
261 }
262
263
264
265 // --------------------------------------------------------------------------
266
267 static Point gMidPoint;
268
269 Boolean SetUp ()
270 {
271     LOGFUNC;
272
273     osx = 0;
274     cellophane = 0;
275     texdetail = 4;
276     slomospeed = 0.25;
277     slomofreq = 8012;
278
279     DefaultSettings();
280
281     if (!SDL_WasInit(SDL_INIT_VIDEO))
282         if (SDL_Init(SDL_INIT_VIDEO) == -1) {
283             fprintf(stderr, "SDL_Init() failed: %s\n", SDL_GetError());
284             return false;
285         }
286     if (!LoadSettings()) {
287         fprintf(stderr, "Failed to load config, creating default\n");
288         SaveSettings();
289     }
290     if (kBitsPerPixel != 32 && kBitsPerPixel != 16) {
291         kBitsPerPixel = 16;
292     }
293
294     if (SDL_GL_LoadLibrary(NULL) == -1) {
295         fprintf(stderr, "SDL_GL_LoadLibrary() failed: %s\n", SDL_GetError());
296         SDL_Quit();
297         return false;
298     }
299
300     for (int displayIdx = 0; displayIdx < SDL_GetNumVideoDisplays(); ++displayIdx) {
301         for (int i = 0; i < SDL_GetNumDisplayModes(displayIdx); ++i) {
302             SDL_DisplayMode mode;
303             if (SDL_GetDisplayMode(displayIdx, i, &mode) == -1)
304                 continue;
305             if ((mode.w < 640) || (mode.h < 480))
306                 continue;  // sane lower limit.
307             pair<int,int> resolution(mode.w, mode.h);
308             resolutions.insert(resolution);
309         }
310     }
311
312     if (resolutions.empty()) {
313         const std::string error = "No suitable video resolutions found.";
314         cerr << error << endl;
315         SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Lugaru init failed!", error.c_str(), NULL);
316         SDL_Quit();
317         return false;
318     }
319
320     if (cmdline("showresolutions")) {
321         printf("Available resolutions:\n");
322         for (auto resolution = resolutions.begin(); resolution != resolutions.end(); resolution++) {
323             printf("  %d x %d\n", (int) resolution->first, (int) resolution->second);
324         }
325     }
326
327     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
328     SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
329
330     Uint32 sdlflags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
331     if ((fullscreen || cmdline("fullscreen")) && !cmdline("windowed")) {
332         fullscreen = 1;
333         sdlflags |= SDL_WINDOW_FULLSCREEN;
334     }
335     if (!cmdline("nomousegrab"))
336         sdlflags |= SDL_WINDOW_INPUT_GRABBED;
337
338     sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
339                                  kContextWidth, kContextHeight, sdlflags);
340
341     if (!sdlwindow) {
342         fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
343         fprintf(stderr, "forcing 640x480...\n");
344         kContextWidth = 640;
345         kContextHeight = 480;
346         sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
347                                      kContextWidth, kContextHeight, sdlflags);
348         if (!sdlwindow) {
349             fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
350             fprintf(stderr, "forcing 640x480 windowed mode...\n");
351             sdlflags &= ~SDL_WINDOW_FULLSCREEN;
352             sdlwindow = SDL_CreateWindow("Lugaru", SDL_WINDOWPOS_CENTERED_DISPLAY(0), SDL_WINDOWPOS_CENTERED_DISPLAY(0),
353                                          kContextWidth, kContextHeight, sdlflags);
354
355             if (!sdlwindow) {
356                 fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError());
357                 return false;
358             }
359         }
360     }
361
362     SDL_GLContext glctx = SDL_GL_CreateContext(sdlwindow);
363     if (!glctx) {
364         fprintf(stderr, "SDL_GL_CreateContext() failed: %s\n", SDL_GetError());
365         SDL_Quit();
366         return false;
367     }
368
369     SDL_GL_MakeCurrent(sdlwindow, glctx);
370
371     if (!lookup_all_glsyms()) {
372         fprintf(stderr, "Missing required OpenGL functions.\n");
373         SDL_Quit();
374         return false;
375     }
376
377     int dblbuf = 0;
378     if ((SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &dblbuf) == -1) || (!dblbuf))
379     {
380         fprintf(stderr, "Failed to get a double-buffered context.\n");
381         SDL_Quit();
382         return false;
383     }
384
385     if (SDL_GL_SetSwapInterval(-1) == -1)  // try swap_tear first.
386         SDL_GL_SetSwapInterval(1);
387
388     SDL_ShowCursor(0);
389     if (!cmdline("nomousegrab"))
390         SDL_SetRelativeMouseMode(SDL_TRUE);
391
392     initGL();
393
394     GLint width = kContextWidth;
395     GLint height = kContextHeight;
396     gMidPoint.h = width / 2;
397     gMidPoint.v = height / 2;
398     screenwidth = width;
399     screenheight = height;
400
401     newdetail = detail;
402     newscreenwidth = screenwidth;
403     newscreenheight = screenheight;
404
405     /* If saved resolution is not in the list, add it to the list (so that it’s selectable in the options) */
406     pair<int,int> startresolution(width,height);
407     if (resolutions.find(startresolution) == resolutions.end()) {
408         resolutions.insert(startresolution);
409     }
410
411     InitGame();
412
413     return true;
414 }
415
416
417 static void DoMouse()
418 {
419
420     if (mainmenu || ( (abs(deltah) < 10 * realmultiplier * 1000) && (abs(deltav) < 10 * realmultiplier * 1000) )) {
421         deltah *= usermousesensitivity;
422         deltav *= usermousesensitivity;
423         mousecoordh += deltah;
424         mousecoordv += deltav;
425         if (mousecoordh < 0)
426             mousecoordh = 0;
427         else if (mousecoordh >= kContextWidth)
428             mousecoordh = kContextWidth - 1;
429         if (mousecoordv < 0)
430             mousecoordv = 0;
431         else if (mousecoordv >= kContextHeight)
432             mousecoordv = kContextHeight - 1;
433     }
434
435 }
436
437 void DoFrameRate (int update)
438 {
439     static long frames = 0;
440
441     static AbsoluteTime time = {0, 0};
442     static AbsoluteTime frametime = {0, 0};
443     AbsoluteTime currTime = UpTime ();
444     double deltaTime = (float) AbsoluteDeltaToDuration (currTime, frametime);
445
446     if (0 > deltaTime) // if negative microseconds
447         deltaTime /= -1000000.0;
448     else // else milliseconds
449         deltaTime /= 1000.0;
450
451     multiplier = deltaTime;
452     if (multiplier < .001)
453         multiplier = .001;
454     if (multiplier > 10)
455         multiplier = 10;
456     if (update)
457         frametime = currTime; // reset for next time interval
458
459     deltaTime = (float) AbsoluteDeltaToDuration (currTime, time);
460
461     if (0 > deltaTime) // if negative microseconds
462         deltaTime /= -1000000.0;
463     else // else milliseconds
464         deltaTime /= 1000.0;
465     frames++;
466     if (0.001 <= deltaTime) { // has update interval passed
467         if (update) {
468             time = currTime; // reset for next time interval
469             frames = 0;
470         }
471     }
472 }
473
474
475 void DoUpdate ()
476 {
477     static float sps = 200;
478     static int count;
479     static float oldmult;
480
481     DoFrameRate(1);
482     if (multiplier > .6)
483         multiplier = .6;
484
485     fps = 1 / multiplier;
486
487     count = multiplier * sps;
488     if (count < 2)
489         count = 2;
490
491     realmultiplier = multiplier;
492     multiplier *= gamespeed;
493     if (difficulty == 1)
494         multiplier *= .9;
495     if (difficulty == 0)
496         multiplier *= .8;
497
498     if (loading == 4)
499         multiplier *= .00001;
500     if (slomo && !mainmenu)
501         multiplier *= slomospeed;
502     oldmult = multiplier;
503     multiplier /= (float)count;
504
505     DoMouse();
506
507     TickOnce();
508
509     for (int i = 0; i < count; i++) {
510         Tick();
511     }
512     multiplier = oldmult;
513
514     TickOnceAfter();
515     /* - Debug code to test how many channels were active on average per frame
516         static long frames = 0;
517
518         static AbsoluteTime start = {0,0};
519         AbsoluteTime currTime = UpTime ();
520         static int num_channels = 0;
521
522         num_channels += OPENAL_GetChannelsPlaying();
523         double deltaTime = (float) AbsoluteDeltaToDuration (currTime, start);
524
525         if (0 > deltaTime)  // if negative microseconds
526             deltaTime /= -1000000.0;
527         else                // else milliseconds
528             deltaTime /= 1000.0;
529
530         ++frames;
531
532         if (deltaTime >= 1)
533         {
534             start = currTime;
535             float avg_channels = (float)num_channels / (float)frames;
536
537             ofstream opstream("log.txt",ios::app);
538             opstream << "Average frame count: ";
539             opstream << frames;
540             opstream << " frames - ";
541             opstream << avg_channels;
542             opstream << " per frame.\n";
543             opstream.close();
544
545             frames = 0;
546             num_channels = 0;
547         }
548     */
549     if ( stereomode == stereoNone ) {
550         DrawGLScene(stereoCenter);
551     } else {
552         DrawGLScene(stereoLeft);
553         DrawGLScene(stereoRight);
554     }
555 }
556
557 // --------------------------------------------------------------------------
558
559
560 void CleanUp (void)
561 {
562     LOGFUNC;
563
564     SDL_Quit();
565 #ifndef __MINGW32__ // FIXME: Temporary workaround for GL-8
566 #define GL_FUNC(ret,fn,params,call,rt) p##fn = NULL;
567 #include "glstubs.h"
568 #undef GL_FUNC
569     // cheat here...static destructors are calling glDeleteTexture() after
570     //  the context is destroyed and libGL unloaded by SDL_Quit().
571     pglDeleteTextures = glDeleteTextures_doNothing;
572 #endif // __MINGW32__
573
574 }
575
576 // --------------------------------------------------------------------------
577
578 static bool IsFocused()
579 {
580     return ((SDL_GetWindowFlags(sdlwindow) & SDL_WINDOW_INPUT_FOCUS) != 0);
581 }
582
583
584
585 #ifndef WIN32
586 // (code lifted from physfs: http://icculus.org/physfs/ ... zlib license.)
587 static char *findBinaryInPath(const char *bin, char *envr)
588 {
589     size_t alloc_size = 0;
590     char *exe = NULL;
591     char *start = envr;
592     char *ptr;
593
594     do {
595         size_t size;
596         ptr = strchr(start, ':');  /* find next $PATH separator. */
597         if (ptr)
598             *ptr = '\0';
599
600         size = strlen(start) + strlen(bin) + 2;
601         if (size > alloc_size) {
602             char *x = (char *) realloc(exe, size);
603             if (x == NULL) {
604                 if (exe != NULL)
605                     free(exe);
606                 return(NULL);
607             } /* if */
608
609             alloc_size = size;
610             exe = x;
611         } /* if */
612
613         /* build full binary path... */
614         strcpy(exe, start);
615         if ((exe[0] == '\0') || (exe[strlen(exe) - 1] != '/'))
616             strcat(exe, "/");
617         strcat(exe, bin);
618
619         if (access(exe, X_OK) == 0) { /* Exists as executable? We're done. */
620             strcpy(exe, start);  /* i'm lazy. piss off. */
621             return(exe);
622         } /* if */
623
624         start = ptr + 1;  /* start points to beginning of next element. */
625     } while (ptr != NULL);
626
627     if (exe != NULL)
628         free(exe);
629
630     return(NULL);  /* doesn't exist in path. */
631 } /* findBinaryInPath */
632
633
634 char *calcBaseDir(const char *argv0)
635 {
636     /* If there isn't a path on argv0, then look through the $PATH for it. */
637     char *retval;
638     char *envr;
639
640     if (strchr(argv0, '/')) {
641         retval = strdup(argv0);
642         if (retval)
643             *((char *) strrchr(retval, '/')) = '\0';
644         return(retval);
645     }
646
647     envr = getenv("PATH");
648     if (!envr)
649         return NULL;
650     envr = strdup(envr);
651     if (!envr)
652         return NULL;
653     retval = findBinaryInPath(argv0, envr);
654     free(envr);
655     return(retval);
656 }
657
658 static inline void chdirToAppPath(const char *argv0)
659 {
660     char *dir = calcBaseDir(argv0);
661     if (dir) {
662 #if (defined(__APPLE__) && defined(__MACH__))
663         // Chop off /Contents/MacOS if it's at the end of the string, so we
664         //  land in the base of the app bundle.
665         const size_t len = strlen(dir);
666         const char *bundledirs = "/Contents/MacOS";
667         const size_t bundledirslen = strlen(bundledirs);
668         if (len > bundledirslen) {
669             char *ptr = (dir + len) - bundledirslen;
670             if (strcasecmp(ptr, bundledirs) == 0)
671                 *ptr = '\0';
672         }
673 #endif
674         chdir(dir);
675         free(dir);
676     }
677 }
678 #endif
679
680
681 int main(int argc, char **argv)
682 {
683     _argc = argc;
684     _argv = argv;
685
686     // !!! FIXME: we could use a Win32 API for this.  --ryan.
687 #ifndef WIN32
688     chdirToAppPath(argv[0]);
689 #endif
690
691     LOGFUNC;
692
693     try {
694         {
695             newGame();
696
697             //ofstream os("error.txt");
698             //os.close();
699             //ofstream os("log.txt");
700             //os.close();
701
702             if (!SetUp ())
703                 return 42;
704
705             while (!gDone && !tryquit) {
706                 if (IsFocused()) {
707                     gameFocused = true;
708
709                     // check windows messages
710
711                     deltah = 0;
712                     deltav = 0;
713                     SDL_Event e;
714                     if (!waiting) {
715                         // message pump
716                         while ( SDL_PollEvent( &e ) ) {
717                             if (!sdlEventProc(e)) {
718                                 gDone = true;
719                                 break;
720                             }
721                         }
722                     }
723
724                     // game
725                     DoUpdate();
726                 } else {
727                     if (gameFocused) {
728                         // allow game chance to pause
729                         gameFocused = false;
730                         DoUpdate();
731                     }
732
733                     // game is not in focus, give CPU time to other apps by waiting for messages instead of 'peeking'
734                     SDL_WaitEvent(0);
735                 }
736             }
737
738             deleteGame();
739         }
740
741         CleanUp ();
742
743         return 0;
744     } catch (const std::exception& error) {
745         CleanUp();
746
747         std::string e = "Caught exception: ";
748         e += error.what();
749
750         LOG(e);
751
752         MessageBox(g_windowHandle, error.what(), "ERROR", MB_OK | MB_ICONEXCLAMATION);
753     }
754
755     CleanUp();
756
757     return -1;
758 }
759
760
761
762 // --------------------------------------------------------------------------
763
764 bool load_image(const char *file_name, ImageRec &tex)
765 {
766     if (visibleloading)
767         Game::LoadingScreen();
768
769     if ( tex.data == NULL )
770         return false;
771
772     const char *ptr = strrchr((char *)file_name, '.');
773     if (ptr) {
774         if (strcasecmp(ptr + 1, "png") == 0)
775             return load_png(file_name, tex);
776         else if (strcasecmp(ptr + 1, "jpg") == 0)
777             return load_jpg(file_name, tex);
778     }
779
780     STUBBED("Unsupported image type");
781     return false;
782 }
783
784 struct my_error_mgr {
785     struct jpeg_error_mgr pub; /* "public" fields */
786     jmp_buf setjmp_buffer; /* for return to caller */
787 };
788 typedef struct my_error_mgr * my_error_ptr;
789
790
791 static void my_error_exit(j_common_ptr cinfo)
792 {
793     struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err;
794     longjmp(err->setjmp_buffer, 1);
795 }
796
797 /* stolen from public domain example.c code in libjpg distribution. */
798 static bool load_jpg(const char *file_name, ImageRec &tex)
799 {
800     struct jpeg_decompress_struct cinfo;
801     struct my_error_mgr jerr;
802     JSAMPROW buffer[1]; /* Output row buffer */
803     int row_stride; /* physical row width in output buffer */
804     FILE *infile = fopen(file_name, "rb");
805
806     if (infile == NULL)
807         return false;
808
809     cinfo.err = jpeg_std_error(&jerr.pub);
810     jerr.pub.error_exit = my_error_exit;
811     if (setjmp(jerr.setjmp_buffer)) {
812         jpeg_destroy_decompress(&cinfo);
813         fclose(infile);
814         return false;
815     }
816
817     jpeg_create_decompress(&cinfo);
818     jpeg_stdio_src(&cinfo, infile);
819     (void) jpeg_read_header(&cinfo, TRUE);
820
821     cinfo.out_color_space = JCS_RGB;
822     cinfo.quantize_colors = 0;
823     (void) jpeg_calc_output_dimensions(&cinfo);
824     (void) jpeg_start_decompress(&cinfo);
825
826     row_stride = cinfo.output_width * cinfo.output_components;
827     tex.sizeX = cinfo.output_width;
828     tex.sizeY = cinfo.output_height;
829     tex.bpp = 24;
830
831     while (cinfo.output_scanline < cinfo.output_height) {
832         buffer[0] = (JSAMPROW)(char *)tex.data +
833                     ((cinfo.output_height - 1) - cinfo.output_scanline) * row_stride;
834         (void) jpeg_read_scanlines(&cinfo, buffer, 1);
835     }
836
837     (void) jpeg_finish_decompress(&cinfo);
838     jpeg_destroy_decompress(&cinfo);
839     fclose(infile);
840
841     return true;
842 }
843
844
845 /* stolen from public domain example.c code in libpng distribution. */
846 static bool load_png(const char *file_name, ImageRec &tex)
847 {
848     bool hasalpha = false;
849     png_structp png_ptr = NULL;
850     png_infop info_ptr = NULL;
851     png_uint_32 width, height;
852     int bit_depth, color_type, interlace_type;
853     bool retval = false;
854     png_byte **row_pointers = NULL;
855     FILE *fp = fopen(file_name, "rb");
856
857     if (fp == NULL) {
858         cerr << file_name << " not found" << endl;
859         return(NULL);
860     }
861
862     png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
863     if (png_ptr == NULL)
864         goto png_done;
865
866     info_ptr = png_create_info_struct(png_ptr);
867     if (info_ptr == NULL)
868         goto png_done;
869
870     if (setjmp(png_jmpbuf(png_ptr)))
871         goto png_done;
872
873     png_init_io(png_ptr, fp);
874     png_read_png(png_ptr, info_ptr,
875                  PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING,
876                  NULL);
877     png_get_IHDR(png_ptr, info_ptr, &width, &height,
878                  &bit_depth, &color_type, &interlace_type, NULL, NULL);
879
880     if (bit_depth != 8)  // transform SHOULD handle this...
881         goto png_done;
882
883     if (color_type & PNG_COLOR_MASK_PALETTE)  // !!! FIXME?
884         goto png_done;
885
886     if ((color_type & PNG_COLOR_MASK_COLOR) == 0)  // !!! FIXME?
887         goto png_done;
888
889     hasalpha = ((color_type & PNG_COLOR_MASK_ALPHA) != 0);
890     row_pointers = png_get_rows(png_ptr, info_ptr);
891     if (!row_pointers)
892         goto png_done;
893
894     if (!hasalpha) {
895         png_byte *dst = tex.data;
896         for (int i = height - 1; i >= 0; i--) {
897             png_byte *src = row_pointers[i];
898             for (unsigned j = 0; j < width; j++) {
899                 dst[0] = src[0];
900                 dst[1] = src[1];
901                 dst[2] = src[2];
902                 dst[3] = 0xFF;
903                 src += 3;
904                 dst += 4;
905             }
906         }
907     }
908
909     else {
910         png_byte *dst = tex.data;
911         int pitch = width * 4;
912         for (int i = height - 1; i >= 0; i--, dst += pitch)
913             memcpy(dst, row_pointers[i], pitch);
914     }
915
916     tex.sizeX = width;
917     tex.sizeY = height;
918     tex.bpp = 32;
919     retval = true;
920
921 png_done:
922     if (!retval) {
923         cerr << "There was a problem loading " << file_name << endl;
924     }
925     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
926     if (fp)
927         fclose(fp);
928     return (retval);
929 }
930
931
932 bool save_image(const char *file_name)
933 {
934     const char *ptr = strrchr((char *)file_name, '.');
935     if (ptr) {
936         if (strcasecmp(ptr + 1, "png") == 0)
937             return save_png(file_name);
938     }
939
940     STUBBED("Unsupported image type");
941     return false;
942 }
943
944
945 static bool save_png(const char *file_name)
946 {
947     FILE *fp = NULL;
948     png_structp png_ptr = NULL;
949     png_infop info_ptr = NULL;
950     bool retval = false;
951
952     fp = fopen(file_name, "wb");
953     if (fp == NULL)
954         return false;
955
956     png_bytep *row_pointers = new png_bytep[kContextHeight];
957     png_bytep screenshot = new png_byte[kContextWidth * kContextHeight * 3];
958     if ((!screenshot) || (!row_pointers))
959         goto save_png_done;
960
961     glGetError();
962     glReadPixels(0, 0, kContextWidth, kContextHeight,
963                  GL_RGB, GL_UNSIGNED_BYTE, screenshot);
964     if (glGetError() != GL_NO_ERROR)
965         goto save_png_done;
966
967     for (int i = 0; i < kContextHeight; i++)
968         row_pointers[i] = screenshot + ((kContextWidth * ((kContextHeight - 1) - i)) * 3);
969
970     png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
971     if (png_ptr == NULL)
972         goto save_png_done;
973
974     info_ptr = png_create_info_struct(png_ptr);
975     if (info_ptr == NULL)
976         goto save_png_done;
977
978     if (setjmp(png_jmpbuf(png_ptr)))
979         goto save_png_done;
980
981     png_init_io(png_ptr, fp);
982
983     if (setjmp(png_jmpbuf(png_ptr)))
984         goto save_png_done;
985
986     png_set_IHDR(png_ptr, info_ptr, kContextWidth, kContextHeight,
987                  8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
988                  PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
989
990     png_write_info(png_ptr, info_ptr);
991
992     if (setjmp(png_jmpbuf(png_ptr)))
993         goto save_png_done;
994
995     png_write_image(png_ptr, row_pointers);
996
997     if (setjmp(png_jmpbuf(png_ptr)))
998         goto save_png_done;
999
1000     png_write_end(png_ptr, NULL);
1001     retval = true;
1002
1003 save_png_done:
1004     png_destroy_write_struct(&png_ptr, &info_ptr);
1005     delete[] screenshot;
1006     delete[] row_pointers;
1007     if (fp)
1008         fclose(fp);
1009     if (!retval)
1010         unlink(ConvertFileName(file_name));
1011     return retval;
1012 }
1013
1014
1015