1 /*
2 
3 *************************************************************************
4 
5 ArmageTron -- Just another Tron Lightcycle Game in 3D.
6 Copyright (C) 2000  Manuel Moos (manuel@moosnet.de)
7 
8 **************************************************************************
9 
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 ***************************************************************************
25 
26 */
27 
28 
29 #include "defs.h"
30 #ifndef DEDICATED
31 #include "rSDL.h"
32 #endif
33 
34 #include "rSysdep.h"
35 #include "tInitExit.h"
36 #include "tDirectories.h"
37 #include "tSysTime.h"
38 #include "rConsole.h"
39 #include "config.h"
40 #include <iostream>
41 #include "rScreen.h"
42 #include "rGL.h"
43 #include "tCommandLine.h"
44 #include "tConfiguration.h"
45 #include "tRecorder.h"
46 
47 #ifndef DEDICATED
48 #include "SDL_thread.h"
49 #include "SDL_mutex.h"
50 
51 #include <png.h>
52 #define SCREENSHOT_PNG_BITDEPTH 8
53 #define SCREENSHOT_BYTES_PER_PIXEL 3
54 #ifndef SDL_OPENGL
55 #ifndef DIRTY
56 #define DIRTY
57 #endif
58 #endif
59 
60 //#ifndef SDL_OPENGL
61 //#error "need SDL 1.1"
62 //#endif
63 
64 #ifndef DIRTY
65 
66 // nothing to be done.
67 
68 /*
69 //#elif defined(HAVE_FXMESA)
70  #include <GL/gl>
71  #include <GL/fxmesa>
72 
73  static fxMesaContext ctx=NULL;
74 */
75 
76 #elif defined(WIN32)
77 
78  #include <windows.h>
79  #include <windef.h>
80  #include "rGL.h"
81 static HDC hDC=NULL;
82 static HGLRC hRC=NULL;
83 
84 #elif defined(unix) || defined(__unix__)
85 
86 #include <GL/glx.h>
87 static GLXContext cx;
88 Display *dpy=NULL;
89 Window  win;
90 
91 #endif
92 
93 #ifdef DIRTY
94 #include <SDL_syswm.h>
95 
96 // graphics initialisation and cleanup:
InitGL()97 bool  rSysDep::InitGL(){
98     SDL_SysWMinfo system;
99     SDL_VERSION(&system.version);
100     if (!SDL_GetWMInfo(&system)){
101         std::cerr << "Video information not available!\n";
102         return(false);
103     }
104 
105     /*
106     con << "SDL version: " << (int)system.version.major
107          << "." <<  (int)system.version.minor << "." <<  (int)system.version.patch << '\n';
108     */
109 
110     /*
111     //#ifdef HAVE_FXMESA
112     if(!ctx){
113       int x=fxQueryHardware();
114       if(x){
115         std::cerr << "No 3Dfx hardware available.\n" << x << '\n';
116         return(false);
117       }
118 
119       GLint attribs[]={FXMESA_DOUBLEBUFFER,FXMESA_DEPTH_SIZE,16,FXMESA_NONE};
120       ctx=fxMesaCreateBestContext(0,sr_screenWidth,sr_screenHeight,attribs);
121 
122       if (!ctx){
123         std::cerr << "Could not create FX rendering context!\n";
124         return(false);
125       }
126 
127       fxMesaMakeCurrent(ctx);
128     }
129     */
130 #ifdef WIN32
131     // windows GL initialisation stolen from
132     // http://www.geocities.com/SiliconValley/Code/1219/opengl32.html
133 
134     if (!hRC){
135         HWND hWnd=system.window;
136 
137         PIXELFORMATDESCRIPTOR pfd;
138         int iFormat;
139 
140         // get the device context (DC)
141         hDC = GetDC( hWnd );
142         if (!hDC) return false;
143 
144         // set the pixel format for the DC
145         ZeroMemory( &pfd, sizeof( pfd ) );
146         pfd.nSize = sizeof( pfd );
147         pfd.nVersion = 1;
148         pfd.dwFlags = PFD_DRAW_TO_WINDOW |
149                       PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
150         pfd.iPixelType = PFD_TYPE_RGBA;
151         pfd.cColorBits = currentScreensetting.colorDepth ? 24 : 16;
152         pfd.cDepthBits = 16;
153         pfd.iLayerType = PFD_MAIN_PLANE;
154         iFormat = ChoosePixelFormat( hDC, &pfd );
155         SetPixelFormat( hDC, iFormat, &pfd );
156 
157         // create and enable the render context (RC)
158         hRC = wglCreateContext( hDC );
159         if (!hRC || !wglMakeCurrent( hDC, hRC ))
160             return false;
161     }
162 
163 #elif defined(unix) || defined(__unix__)
164     if (system.subsystem!=SDL_SYSWM_X11){
165         std::cerr << "System is not X11!\n";
166         std::cerr << (int)system.subsystem << "!=" << (int)SDL_SYSWM_X11 <<'\n';
167         return false;
168     }
169 
170     if(!dpy){
171 
172         dpy=system.info.x11.display;
173         win=system.info.x11.window;
174 
175         int errorbase,tEventbase;
176         if (glXQueryExtension(dpy,&errorbase,&tEventbase) == False){
177             std::cerr << "OpenGL through GLX not supported.\n";
178             return false;
179         }
180 
181         int configuration[]={GLX_DOUBLEBUFFER,GLX_RGBA,GLX_DEPTH_SIZE ,12, GLX_RED_SIZE,1,
182                              GLX_BLUE_SIZE,1,GLX_GREEN_SIZE,1,None};
183 
184         XVisualInfo *vi=glXChooseVisual(dpy,DefaultScreen(dpy),configuration);
185 
186         if(vi== NULL){
187             std::cerr << "Could not initialize Visual.\n";
188             return false;
189         }
190 
191         cx=glXCreateContext(dpy,vi,
192                             NULL,True);
193 
194         if(cx== NULL){
195             std::cerr << "Could not initialize GL context.\n";
196             return false;
197         }
198 
199         if (!glXMakeCurrent(dpy,win,cx)){
200             dpy=0;
201             return false;
202         }
203     }
204 
205 #endif
206 
207     return true;
208 }
209 
ExitGL()210 void  rSysDep::ExitGL(){
211     SDL_SysWMinfo system;
212     SDL_GetWMInfo(&system);
213 
214     /*
215     #ifdef HAVE_FXMESA
216 
217     if(ctx){
218       fxMesaDestroyContext(ctx);
219       ctx=NULL;
220       fxCloseHardware();
221     }
222     */
223 
224 #if defined(WIN32)
225     HWND hWnd=system.window;
226 
227     // windows GL cleanup stolen from
228     // http://www.geocities.com/SiliconValley/Code/1219/opengl32.html
229     if(hRC){
230 
231         wglMakeCurrent( NULL, NULL );
232         wglDeleteContext( hRC );
233         ReleaseDC( hWnd, hDC );
234 
235         hRC=NULL;
236         hDC=NULL;
237     }
238 #elif defined(unix) || defined(__unix__)
239     if(dpy){
240 
241         //    glXReleaseBuffersMESA( dpy, win );
242         glXMakeCurrent(dpy,None,NULL);
243         glXDestroyContext(dpy, cx );
244         dpy=NULL;
245     }
246 #endif
247 }
248 #endif // DIRTY
249 
250 bool sr_screenshotIsPlanned=false;
251 
252 static bool png_screenshot=true;
253 static tConfItem<bool> pns("PNG_SCREENSHOT",png_screenshot);
254 #ifndef DEDICATED
255 
SDL_SavePNG(SDL_Surface * image,tString filename)256 static void SDL_SavePNG(SDL_Surface *image, tString filename){
257     png_structp png_ptr;
258     png_infop info_ptr;
259     png_byte **row_ptrs;
260     int i;
261     static FILE *fp;
262 
263     if (!(fp = fopen(filename, "wb"))) {
264         fprintf(stderr, "can't open file for writing\n");
265         return;
266     }
267 
268     if (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) {
269         return;
270     }
271 
272     if (!(info_ptr = png_create_info_struct(png_ptr))) {
273         png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
274         return;
275     }
276 
277     png_init_io(png_ptr, fp);
278 
279     png_set_IHDR(png_ptr, info_ptr, sr_screenWidth, sr_screenHeight,
280                  SCREENSHOT_PNG_BITDEPTH, PNG_COLOR_TYPE_RGB,
281                  PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
282                  PNG_FILTER_TYPE_DEFAULT);
283     png_write_info(png_ptr, info_ptr);
284 
285     // get pointers
286     if(!(row_ptrs = (png_byte**) malloc(sr_screenHeight * sizeof(png_byte*)))) {
287         png_destroy_write_struct(&png_ptr, &info_ptr);
288         return;
289     }
290 
291     for(i = 0; i < sr_screenHeight; i++) {
292         row_ptrs[i] = (png_byte *)image->pixels + (sr_screenHeight - i - 1)
293                       * SCREENSHOT_BYTES_PER_PIXEL * sr_screenWidth;
294     }
295 
296     png_write_image(png_ptr, row_ptrs);
297     png_write_end(png_ptr, info_ptr);
298     png_destroy_write_struct(&png_ptr, &info_ptr);
299 
300     free(row_ptrs);
301     fclose(fp);
302 }
303 #endif
304 
make_screenshot()305 static void make_screenshot(){
306 #ifndef DEDICATED
307     // screenshot count
308     static int number=0;
309     number++;
310 
311     SDL_Surface *image;
312     SDL_Surface *temp;
313     int idx;
314     image = SDL_CreateRGBSurface(SDL_SWSURFACE, sr_screenWidth, sr_screenHeight,
315                                  24, 0x0000FF, 0x00FF00, 0xFF0000 ,0);
316     temp = SDL_CreateRGBSurface(SDL_SWSURFACE, sr_screenWidth, sr_screenHeight,
317                                 24, 0x0000FF, 0x00FF00, 0xFF0000, 0);
318 
319     // make upside down screenshot
320     glReadPixels(0,0,sr_screenWidth, sr_screenHeight, GL_RGB,
321                  GL_UNSIGNED_BYTE, image->pixels);
322 
323     // turn image around
324     for (idx = 0; idx < sr_screenHeight; idx++)
325     {
326         memcpy(reinterpret_cast<char *>(temp->pixels) + 3 * sr_screenWidth * idx,
327                reinterpret_cast<char *>(image->pixels)+ 3
328                * sr_screenWidth*(sr_screenHeight - idx-1),
329                3*sr_screenWidth);
330     }
331 
332     // save screenshot in unused slot
333     bool done = false;
334     while ( !done )
335     {
336         // generate filename
337         tString fileName("screenshot_");
338         fileName << number;
339         if(png_screenshot)
340             fileName << ".png";
341         else
342             fileName << ".bmp";
343 
344         // test if file exists
345         std::ifstream s;
346         if ( tDirectories::Screenshot().Open( s, fileName ) )
347         {
348             // yes! try next number
349             number++;
350             continue;
351         }
352 
353         // save image
354         if(png_screenshot)
355             SDL_SavePNG(image, tDirectories::Screenshot().GetWritePath( fileName ));
356         else
357             SDL_SaveBMP(temp, tDirectories::Screenshot().GetWritePath( fileName ) );
358         done = true;
359     }
360 
361     // cleanup
362     SDL_FreeSurface(image);
363     SDL_FreeSurface(temp);
364 #endif
365 }
366 
367 class PerformanceCounter
368 {
369 public:
PerformanceCounter()370     PerformanceCounter(): count_(0){ tRealSysTimeFloat(); }
Count()371     unsigned int Count(){ return count_++; }
~PerformanceCounter()372     ~PerformanceCounter()
373     {
374         double time = tRealSysTimeFloat();
375         std::stringstream s;
376         s << count_ << " frames in " << time << " seconds: " << count_ / time << " fps.\n";
377 #ifdef WIN32
378         MessageBox (NULL, s.str().c_str() , "Performance", MB_OK);
379 #else
380         std::cout << s.str();
381 #endif
382     }
383 private:
384     unsigned int count_;
385 };
386 
387 static double s_nextFastForwardFrameRecorded=0; // the next frame to render in recorded time
388 static double s_nextFastForwardFrameReal=0;     // the next frame to render in real time
389 #endif // DEDICATED
390 
391 // settings for fast forward mode
392 static REAL sr_FF_Maxstep=1; // maximum step between rendered frames
393 static tSettingItem<REAL> c_ff( "FAST_FORWARD_MAXSTEP",
394                                 sr_FF_Maxstep );
395 
396 static REAL sr_FF_MaxstepReal=.05; // maximum step in real time between rendered frames
397 static tSettingItem<REAL> c_ffre( "FAST_FORWARD_MAXSTEP_REAL",
398                                   sr_FF_MaxstepReal );
399 
400 static REAL sr_FF_MaxstepRel=1; // maximum step between rendered frames relative to end of FF mode
401 static tSettingItem<REAL> c_ffr( "FAST_FORWARD_MAXSTEP_REL",
402                                  sr_FF_MaxstepRel );
403 
404 
405 static double s_fastForwardTo=0;
406 static bool   s_fastForward =false;
407 static bool   s_benchmark   =false;
408 
409 class rFastForwardCommandLineAnalyzer: public tCommandLineAnalyzer
410 {
411 private:
DoAnalyze(tCommandLineParser & parser)412     virtual bool DoAnalyze( tCommandLineParser & parser )
413     {
414         // get option
415         tString forward;
416         if ( parser.GetOption( forward, "--fastforward" ) )
417         {
418             // set fast forward mode
419             s_fastForward = true;
420 
421             // read time
422             std::stringstream str(static_cast< char const * >( forward ) );
423             str >> s_fastForwardTo;
424 
425             return true;
426         }
427 
428         if ( parser.GetSwitch( "--benchmark" ) )
429         {
430             // set benchmark mode
431             s_benchmark = true;
432             return true;
433         }
434 
435         return false;
436     }
437 
DoHelp(std::ostream & s)438     virtual void DoHelp( std::ostream & s )
439     {                                      //
440         s << "--fastforward <time>         : lets time run very fast until the given time is reached\n";
441         s << "--benchmark                  : renders frames as they were recorded\n";
442     }
443 };
444 
445 static rFastForwardCommandLineAnalyzer analyzer;
446 
447 // #define MILLION 1000000
448 
449 /*
450 static double lastFrame = -1;
451 static void sr_DelayFrame( int targetFPS )
452 {
453     // calculate microseconds per frame
454     int uSecsPerFrame = MILLION/(targetFPS + 10);
455 
456     // calculate microseconds spent rendering
457     double thisFrame = tRealSysTimeFloat();
458 
459     int uSecsPassed = static_cast<int>( MILLION * ( thisFrame - lastFrame ) );
460 
461 //    con << uSecsPassed << "\n";
462 
463     // wait
464     int uSecsToWait = uSecsPerFrame - uSecsPassed;
465     if ( uSecsToWait > 0 )
466         tDelay( uSecsToWait );
467 
468     // call glFinish to wait for GPU
469     glFinish();
470 }
471 */
472 
473 rSysDep::rSwapMode rSysDep::swapMode_ = rSysDep::rSwap_glFlush;
474 //rSysDep::rSwapMode rSysDep::swapMode_ = rSysDep::rSwap_60Hz;
475 
476 // buffer swap:
477 #ifndef DEDICATED
478 // for setting breakpoints in optimized mode, too
breakpoint()479 static void breakpoint(){}
480 
481 static bool sr_netSyncThreadGoOn = true;
482 static rSysDep::rNetIdler * sr_netIdler = NULL;
sr_NetSyncThread(void * lockVoid)483 int sr_NetSyncThread(void *lockVoid)
484 {
485     SDL_mutex *lock = (SDL_mutex *)lockVoid;
486 
487     SDL_mutexP(lock);
488 
489     while ( sr_netSyncThreadGoOn )
490     {
491         SDL_mutexV(lock);
492         // wait for network data
493         bool toDo = sr_netIdler->Wait();
494         SDL_mutexP(lock);
495 
496         if ( toDo )
497         {
498             // disable rendering (during auto-scrolling of console, for example)
499             bool glout = sr_glOut;
500             sr_glOut = false;
501 
502             // new network data arrived, handle it
503             sr_netIdler->Do();
504 
505             // enable rendering again
506             sr_glOut = glout;
507         }
508     }
509 
510     SDL_mutexV(lock);
511 
512     return 0;
513 }
514 
515 static SDL_Thread * sr_netSyncThread = NULL;
516 static SDL_mutex * sr_netLock = NULL;
StartNetSyncThread(rNetIdler * idler)517 void rSysDep::StartNetSyncThread( rNetIdler * idler )
518 {
519     sr_netIdler = idler;
520 
521     return;
522 
523     // can't use thrading trouble while recording
524     if ( tRecorder::IsRunning() )
525         return;
526 
527     if ( sr_netSyncThread )
528         return;
529 
530     // create lock
531     if ( !sr_netLock )
532         sr_netLock = SDL_CreateMutex();
533 
534     // start thread
535     sr_netSyncThread = SDL_CreateThread( sr_NetSyncThread, sr_netLock );
536     if ( !sr_netSyncThread )
537         return;
538 
539     // lock mutex, the thread should only do work while the main thread is waiting for the refresh
540     SDL_mutexP( sr_netLock );
541 }
542 
StopNetSyncThread()543 void rSysDep::StopNetSyncThread()
544 {
545     // stop and delete thread
546     if ( sr_netSyncThread )
547     {
548         SDL_mutexV(  sr_netLock );
549         sr_netSyncThreadGoOn = false;
550         SDL_WaitThread( sr_netSyncThread, NULL );
551         sr_netSyncThread = NULL;
552         sr_netIdler = NULL;
553     }
554 
555     // delete lock
556     if ( sr_netLock )
557     {
558         SDL_DestroyMutex( sr_netLock );
559         sr_netLock = NULL;
560     }
561 }
562 
SwapGL()563 void rSysDep::SwapGL(){
564     if ( s_benchmark )
565     {
566         static PerformanceCounter counter;
567         counter.Count();
568     }
569 
570     double time = tSysTimeFloat();
571     double realTime = tRealSysTimeFloat();
572 
573     bool next_glOut = sr_glOut;
574 
575     // adapt playback speed to recorded speed
576     if ( !s_benchmark && !s_fastForward && tRecorder::IsPlayingBack() )
577     {
578         static double timeOffset=0;
579         static double lastRendered=0;
580 
581         // calculate how much we're behind the rendering schedule
582         double behind = - time + realTime + timeOffset;
583         // std::cout << behind << " " << sr_glOut << "\n";
584 
585         // large delays can only be caused by breakpoints or map downloads; ignore them
586         if ( behind > .5 || realTime > lastRendered + .2 )
587         {
588             timeOffset -= behind;
589             next_glOut = true;
590         }
591         else
592         {
593             // we're a bit behind, skip the next frame
594             if ( behind > .1 )
595             {
596                 next_glOut = false;
597             }
598             else if ( sr_glOut )
599             {
600                 lastRendered=realTime;
601                 // we're ahead, pause a bit
602                 if  ( behind < -.5 )
603                     timeOffset -= behind;
604                 else if ( behind < -.1 )
605                 {
606                     int delay = int( -( behind + .1 ) * 1000000 );
607                     // std::cout << behind << ":" << delay << "\n";
608                     tDelayForce( delay );
609                 }
610             }
611             else
612             {
613                 // we're not behind any more. Reactivate rendering.
614                 next_glOut = true;
615             }
616         }
617 
618         if ( next_glOut )
619             lastRendered=realTime;
620     }
621 
622     if (!sr_glOut)
623     {
624         // display next frame in fast foward mode
625         if ( ( s_fastForward && ( time > s_nextFastForwardFrameRecorded || realTime > s_nextFastForwardFrameReal ) ) || next_glOut )
626         {
627             sr_glOut = true;
628             rSysDep::ClearGL();
629         }
630 
631         // in playback or recording mode, always execute frame tasks, they may be improtant for consistency
632         if ( tRecorder::IsRunning() )
633             rPerFrameTask::DoPerFrameTasks();
634 
635         return;
636     }
637 
638 
639     rPerFrameTask::DoPerFrameTasks();
640 
641     // unlock the mutex while waiting for the swap operation to finish
642     SDL_mutexV(  sr_netLock );
643     sr_LockSDL();
644 
645     switch( swapMode_ )
646     {
647     case rSwap_Fastest:
648         break;
649     case rSwap_glFlush:
650         glFlush();
651         break;
652     case rSwap_glFinish:
653         glFinish();
654         break;
655     }
656 
657 #if defined(SDL_OPENGL)
658     if (lastSuccess.useSDL)
659         SDL_GL_SwapBuffers();
660     //#elif defined(HAVE_FXMESA)
661     //fxMesaSwapBuffers();
662 #endif
663 
664 #ifdef DIRTY
665     if (!lastSuccess.useSDL){
666 #if defined(WIN32)
667         SwapBuffers( hDC );
668 #elif defined(unix) || defined(__unix__)
669         glXSwapBuffers(dpy,win);
670 #endif
671     }
672 #endif
673 
674     if (sr_screenshotIsPlanned){
675         make_screenshot();
676         sr_screenshotIsPlanned=false;
677     }
678 
679     sr_UnlockSDL();
680     // lock mutex again
681     SDL_mutexP(  sr_netLock );
682 
683 
684     // disable output in fast forward mode
685     if ( s_fastForward && tRecorder::IsPlayingBack() )
686     {
687         if ( time < s_fastForwardTo )
688         {
689             // next displayed frame should be ten percent closer to the target, but at most 10 seconds
690             s_nextFastForwardFrameRecorded = ( s_fastForwardTo - time ) * sr_FF_MaxstepRel;
691             if ( s_nextFastForwardFrameRecorded > sr_FF_Maxstep )
692                 s_nextFastForwardFrameRecorded = sr_FF_Maxstep ;
693             s_nextFastForwardFrameRecorded += time;
694             s_nextFastForwardFrameReal = realTime + sr_FF_MaxstepReal ;
695 
696             next_glOut = false;
697         }
698         else
699         {
700             std::cout << "End of fast forward mode.\n";
701             st_Breakpoint();
702             s_fastForward = false;
703         }
704     }
705 
706     //#ifdef DEBUG
707     if ( !s_fastForward )
708     {
709         breakpoint();
710     }
711     //#endif
712 
713     // store frame time for next frame
714     // lastFrame = tRealSysTimeFloat();
715 
716     sr_glOut = next_glOut;
717 }
718 #endif // dedicated
719 
720 #ifndef DEDICATED
721 static SDL_mutex *mut;
722 
stuff_init()723 static void stuff_init(){
724     mut=SDL_CreateMutex();
725 }
726 
727 static tInitExit stuff_ie(&stuff_init);
728 #endif
729 
sr_LockSDL()730 void sr_LockSDL(){
731     //std::cerr << "locking...";
732 #ifndef DEDICATED
733 #ifndef WIN32
734     //SDL_mutexP(mut);
735 #endif
736 #endif
737     //std::cerr << " locked!\n";
738 }
739 
sr_UnlockSDL()740 void sr_UnlockSDL(){
741     //std::cerr << "unlocking...";
742 #ifndef DEDICATED
743 #ifndef WIN32
744     //SDL_mutexV(mut);
745 #endif
746 #endif
747     //std::cerr << " unlocked!\n";
748 }
749 
750 #ifndef DEDICATED
ClearGL()751 void  rSysDep::ClearGL(){
752     if (sr_glOut){
753 
754         /*
755         if (sr_screenshotIsPlanned){
756           make_screenshot();
757           sr_screenshotIsPlanned=false;
758         }
759         */
760 
761         glClearColor(0.0,0.0,0.0,1.0);
762         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
763     }
764 }
765 #endif
766 
767 
768