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