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