Common_demos.cpp (13375B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. 23 24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. 25 26 =========================================================================== 27 */ 28 29 #include "../idlib/precompiled.h" 30 #pragma hdrstop 31 32 #include "Common_local.h" 33 34 /* 35 ================ 36 FindUnusedFileName 37 ================ 38 */ 39 static idStr FindUnusedFileName( const char *format ) { 40 idStr filename; 41 42 for ( int i = 0 ; i < 999 ; i++ ) { 43 filename.Format( format, i ); 44 int len = fileSystem->ReadFile( filename, NULL, NULL ); 45 if ( len <= 0 ) { 46 return filename; // file doesn't exist 47 } 48 } 49 50 return filename; 51 } 52 53 /* 54 ================ 55 idCommonLocal::StartRecordingRenderDemo 56 ================ 57 */ 58 void idCommonLocal::StartRecordingRenderDemo( const char *demoName ) { 59 if ( writeDemo ) { 60 // allow it to act like a toggle 61 StopRecordingRenderDemo(); 62 return; 63 } 64 65 if ( !demoName[0] ) { 66 common->Printf( "idCommonLocal::StartRecordingRenderDemo: no name specified\n" ); 67 return; 68 } 69 70 console->Close(); 71 72 writeDemo = new (TAG_SYSTEM) idDemoFile; 73 if ( !writeDemo->OpenForWriting( demoName ) ) { 74 common->Printf( "error opening %s\n", demoName ); 75 delete writeDemo; 76 writeDemo = NULL; 77 return; 78 } 79 80 common->Printf( "recording to %s\n", writeDemo->GetName() ); 81 82 writeDemo->WriteInt( DS_VERSION ); 83 writeDemo->WriteInt( RENDERDEMO_VERSION ); 84 85 // if we are in a map already, dump the current state 86 soundWorld->StartWritingDemo( writeDemo ); 87 renderWorld->StartWritingDemo( writeDemo ); 88 } 89 90 /* 91 ================ 92 idCommonLocal::StopRecordingRenderDemo 93 ================ 94 */ 95 void idCommonLocal::StopRecordingRenderDemo() { 96 if ( !writeDemo ) { 97 common->Printf( "idCommonLocal::StopRecordingRenderDemo: not recording\n" ); 98 return; 99 } 100 soundWorld->StopWritingDemo(); 101 renderWorld->StopWritingDemo(); 102 103 writeDemo->Close(); 104 common->Printf( "stopped recording %s.\n", writeDemo->GetName() ); 105 delete writeDemo; 106 writeDemo = NULL; 107 } 108 109 /* 110 ================ 111 idCommonLocal::StopPlayingRenderDemo 112 113 Reports timeDemo numbers and finishes any avi recording 114 ================ 115 */ 116 void idCommonLocal::StopPlayingRenderDemo() { 117 if ( !readDemo ) { 118 timeDemo = TD_NO; 119 return; 120 } 121 122 // Record the stop time before doing anything that could be time consuming 123 int timeDemoStopTime = Sys_Milliseconds(); 124 125 EndAVICapture(); 126 127 readDemo->Close(); 128 129 soundWorld->StopAllSounds(); 130 soundSystem->SetPlayingSoundWorld( menuSoundWorld ); 131 132 common->Printf( "stopped playing %s.\n", readDemo->GetName() ); 133 delete readDemo; 134 readDemo = NULL; 135 136 if ( timeDemo ) { 137 // report the stats 138 float demoSeconds = ( timeDemoStopTime - timeDemoStartTime ) * 0.001f; 139 float demoFPS = numDemoFrames / demoSeconds; 140 idStr message = va( "%i frames rendered in %3.1f seconds = %3.1f fps\n", numDemoFrames, demoSeconds, demoFPS ); 141 142 common->Printf( message ); 143 if ( timeDemo == TD_YES_THEN_QUIT ) { 144 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" ); 145 } 146 timeDemo = TD_NO; 147 } 148 } 149 150 /* 151 ================ 152 idCommonLocal::DemoShot 153 154 A demoShot is a single frame demo 155 ================ 156 */ 157 void idCommonLocal::DemoShot( const char *demoName ) { 158 StartRecordingRenderDemo( demoName ); 159 160 // force draw one frame 161 const bool captureToImage = false; 162 UpdateScreen( captureToImage ); 163 164 StopRecordingRenderDemo(); 165 } 166 167 /* 168 ================ 169 idCommonLocal::StartPlayingRenderDemo 170 ================ 171 */ 172 void idCommonLocal::StartPlayingRenderDemo( idStr demoName ) { 173 if ( !demoName[0] ) { 174 common->Printf( "idCommonLocal::StartPlayingRenderDemo: no name specified\n" ); 175 return; 176 } 177 178 // make sure localSound / GUI intro music shuts up 179 soundWorld->StopAllSounds(); 180 soundWorld->PlayShaderDirectly( "", 0 ); 181 menuSoundWorld->StopAllSounds(); 182 menuSoundWorld->PlayShaderDirectly( "", 0 ); 183 184 // exit any current game 185 Stop(); 186 187 // automatically put the console away 188 console->Close(); 189 190 readDemo = new (TAG_SYSTEM) idDemoFile; 191 demoName.DefaultFileExtension( ".demo" ); 192 if ( !readDemo->OpenForReading( demoName ) ) { 193 common->Printf( "couldn't open %s\n", demoName.c_str() ); 194 delete readDemo; 195 readDemo = NULL; 196 Stop(); 197 StartMenu(); 198 return; 199 } 200 201 const bool captureToImage = false; 202 UpdateScreen( captureToImage ); 203 204 AdvanceRenderDemo( true ); 205 206 numDemoFrames = 1; 207 208 timeDemoStartTime = Sys_Milliseconds(); 209 } 210 211 /* 212 ================ 213 idCommonLocal::TimeRenderDemo 214 ================ 215 */ 216 void idCommonLocal::TimeRenderDemo( const char *demoName, bool twice, bool quit ) { 217 idStr demo = demoName; 218 219 StartPlayingRenderDemo( demo ); 220 221 if ( twice && readDemo ) { 222 while ( readDemo ) { 223 const bool captureToImage = false; 224 UpdateScreen( captureToImage ); 225 AdvanceRenderDemo( true ); 226 } 227 228 StartPlayingRenderDemo( demo ); 229 } 230 231 232 if ( !readDemo ) { 233 return; 234 } 235 236 if ( quit ) { 237 // this allows hardware vendors to automate some testing 238 timeDemo = TD_YES_THEN_QUIT; 239 } else { 240 timeDemo = TD_YES; 241 } 242 } 243 244 245 /* 246 ================ 247 idCommonLocal::BeginAVICapture 248 ================ 249 */ 250 void idCommonLocal::BeginAVICapture( const char *demoName ) { 251 idStr name = demoName; 252 name.ExtractFileBase( aviDemoShortName ); 253 aviCaptureMode = true; 254 aviDemoFrameCount = 0; 255 soundWorld->AVIOpen( va( "demos/%s/", aviDemoShortName.c_str() ), aviDemoShortName.c_str() ); 256 } 257 258 /* 259 ================ 260 idCommonLocal::EndAVICapture 261 ================ 262 */ 263 void idCommonLocal::EndAVICapture() { 264 if ( !aviCaptureMode ) { 265 return; 266 } 267 268 soundWorld->AVIClose(); 269 270 // write a .roqParam file so the demo can be converted to a roq file 271 idFile *f = fileSystem->OpenFileWrite( va( "demos/%s/%s.roqParam", 272 aviDemoShortName.c_str(), aviDemoShortName.c_str() ) ); 273 f->Printf( "INPUT_DIR demos/%s\n", aviDemoShortName.c_str() ); 274 f->Printf( "FILENAME demos/%s/%s.RoQ\n", aviDemoShortName.c_str(), aviDemoShortName.c_str() ); 275 f->Printf( "\nINPUT\n" ); 276 f->Printf( "%s_*.tga [00000-%05i]\n", aviDemoShortName.c_str(), (int)( aviDemoFrameCount-1 ) ); 277 f->Printf( "END_INPUT\n" ); 278 delete f; 279 280 common->Printf( "captured %i frames for %s.\n", ( int )aviDemoFrameCount, aviDemoShortName.c_str() ); 281 282 aviCaptureMode = false; 283 } 284 285 286 /* 287 ================ 288 idCommonLocal::AVIRenderDemo 289 ================ 290 */ 291 void idCommonLocal::AVIRenderDemo( const char *_demoName ) { 292 idStr demoName = _demoName; // copy off from va() buffer 293 294 StartPlayingRenderDemo( demoName ); 295 if ( !readDemo ) { 296 return; 297 } 298 299 BeginAVICapture( demoName.c_str() ) ; 300 301 // I don't understand why I need to do this twice, something 302 // strange with the nvidia swapbuffers? 303 const bool captureToImage = false; 304 UpdateScreen( captureToImage ); 305 } 306 307 /* 308 ================ 309 idCommonLocal::AVIGame 310 311 Start AVI recording the current game session 312 ================ 313 */ 314 void idCommonLocal::AVIGame( const char *demoName ) { 315 if ( aviCaptureMode ) { 316 EndAVICapture(); 317 return; 318 } 319 320 if ( !mapSpawned ) { 321 common->Printf( "No map spawned.\n" ); 322 } 323 324 if ( !demoName || !demoName[0] ) { 325 idStr filename = FindUnusedFileName( "demos/game%03i.game" ); 326 demoName = filename.c_str(); 327 328 // write a one byte stub .game file just so the FindUnusedFileName works, 329 fileSystem->WriteFile( demoName, demoName, 1 ); 330 } 331 332 BeginAVICapture( demoName ) ; 333 } 334 335 /* 336 ================ 337 idCommonLocal::CompressDemoFile 338 ================ 339 */ 340 void idCommonLocal::CompressDemoFile( const char *scheme, const char *demoName ) { 341 idStr fullDemoName = "demos/"; 342 fullDemoName += demoName; 343 fullDemoName.DefaultFileExtension( ".demo" ); 344 idStr compressedName = fullDemoName; 345 compressedName.StripFileExtension(); 346 compressedName.Append( "_compressed.demo" ); 347 348 int savedCompression = cvarSystem->GetCVarInteger("com_compressDemos"); 349 bool savedPreload = cvarSystem->GetCVarBool("com_preloadDemos"); 350 cvarSystem->SetCVarBool( "com_preloadDemos", false ); 351 cvarSystem->SetCVarInteger("com_compressDemos", atoi(scheme) ); 352 353 idDemoFile demoread, demowrite; 354 if ( !demoread.OpenForReading( fullDemoName ) ) { 355 common->Printf( "Could not open %s for reading\n", fullDemoName.c_str() ); 356 return; 357 } 358 if ( !demowrite.OpenForWriting( compressedName ) ) { 359 common->Printf( "Could not open %s for writing\n", compressedName.c_str() ); 360 demoread.Close(); 361 cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload ); 362 cvarSystem->SetCVarInteger("com_compressDemos", savedCompression); 363 return; 364 } 365 common->SetRefreshOnPrint( true ); 366 common->Printf( "Compressing %s to %s...\n", fullDemoName.c_str(), compressedName.c_str() ); 367 368 static const int bufferSize = 65535; 369 char buffer[bufferSize]; 370 int bytesRead; 371 while ( 0 != (bytesRead = demoread.Read( buffer, bufferSize ) ) ) { 372 demowrite.Write( buffer, bytesRead ); 373 common->Printf( "." ); 374 } 375 376 demoread.Close(); 377 demowrite.Close(); 378 379 cvarSystem->SetCVarBool( "com_preloadDemos", savedPreload ); 380 cvarSystem->SetCVarInteger("com_compressDemos", savedCompression); 381 382 common->Printf( "Done\n" ); 383 common->SetRefreshOnPrint( false ); 384 385 } 386 387 /* 388 =============== 389 idCommonLocal::AdvanceRenderDemo 390 =============== 391 */ 392 void idCommonLocal::AdvanceRenderDemo( bool singleFrameOnly ) { 393 int ds = DS_FINISHED; 394 readDemo->ReadInt( ds ); 395 396 switch ( ds ) { 397 case DS_FINISHED: 398 if ( numDemoFrames != 1 ) { 399 // if the demo has a single frame (a demoShot), continuously replay 400 // the renderView that has already been read 401 Stop(); 402 StartMenu(); 403 } 404 return; 405 case DS_RENDER: 406 if ( renderWorld->ProcessDemoCommand( readDemo, ¤tDemoRenderView, &demoTimeOffset ) ) { 407 // a view is ready to render 408 numDemoFrames++; 409 } 410 break; 411 case DS_SOUND: 412 soundWorld->ProcessDemoCommand( readDemo ); 413 break; 414 default: 415 common->Error( "Bad render demo token" ); 416 } 417 } 418 419 /* 420 ================ 421 Common_DemoShot_f 422 ================ 423 */ 424 CONSOLE_COMMAND( demoShot, "writes a screenshot as a demo", NULL ) { 425 if ( args.Argc() != 2 ) { 426 idStr filename = FindUnusedFileName( "demos/shot%03i.demo" ); 427 commonLocal.DemoShot( filename ); 428 } else { 429 commonLocal.DemoShot( va( "demos/shot_%s.demo", args.Argv(1) ) ); 430 } 431 } 432 433 /* 434 ================ 435 Common_RecordDemo_f 436 ================ 437 */ 438 CONSOLE_COMMAND( recordDemo, "records a demo", NULL ) { 439 if ( args.Argc() != 2 ) { 440 idStr filename = FindUnusedFileName( "demos/demo%03i.demo" ); 441 commonLocal.StartRecordingRenderDemo( filename ); 442 } else { 443 commonLocal.StartRecordingRenderDemo( va( "demos/%s.demo", args.Argv(1) ) ); 444 } 445 } 446 447 /* 448 ================ 449 Common_CompressDemo_f 450 ================ 451 */ 452 CONSOLE_COMMAND( compressDemo, "compresses a demo file", idCmdSystem::ArgCompletion_DemoName ) { 453 if ( args.Argc() == 2 ) { 454 commonLocal.CompressDemoFile( "2", args.Argv(1) ); 455 } else if ( args.Argc() == 3 ) { 456 commonLocal.CompressDemoFile( args.Argv(2), args.Argv(1) ); 457 } else { 458 common->Printf("use: CompressDemo <file> [scheme]\nscheme is the same as com_compressDemo, defaults to 2" ); 459 } 460 } 461 462 /* 463 ================ 464 Common_StopRecordingDemo_f 465 ================ 466 */ 467 CONSOLE_COMMAND( stopRecording, "stops demo recording", NULL ) { 468 commonLocal.StopRecordingRenderDemo(); 469 } 470 471 /* 472 ================ 473 Common_PlayDemo_f 474 ================ 475 */ 476 CONSOLE_COMMAND( playDemo, "plays back a demo", idCmdSystem::ArgCompletion_DemoName ) { 477 if ( args.Argc() >= 2 ) { 478 commonLocal.StartPlayingRenderDemo( va( "demos/%s", args.Argv(1) ) ); 479 } 480 } 481 482 /* 483 ================ 484 Common_TimeDemo_f 485 ================ 486 */ 487 CONSOLE_COMMAND( timeDemo, "times a demo", idCmdSystem::ArgCompletion_DemoName ) { 488 if ( args.Argc() >= 2 ) { 489 commonLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ), ( args.Argc() > 2 ), false ); 490 } 491 } 492 493 /* 494 ================ 495 Common_TimeDemoQuit_f 496 ================ 497 */ 498 CONSOLE_COMMAND( timeDemoQuit, "times a demo and quits", idCmdSystem::ArgCompletion_DemoName ) { 499 commonLocal.TimeRenderDemo( va( "demos/%s", args.Argv(1) ), true ); 500 } 501 502 /* 503 ================ 504 Common_AVIDemo_f 505 ================ 506 */ 507 CONSOLE_COMMAND( aviDemo, "writes AVIs for a demo", idCmdSystem::ArgCompletion_DemoName ) { 508 commonLocal.AVIRenderDemo( va( "demos/%s", args.Argv(1) ) ); 509 } 510 511 /* 512 ================ 513 Common_AVIGame_f 514 ================ 515 */ 516 CONSOLE_COMMAND( aviGame, "writes AVIs for the current game", NULL ) { 517 commonLocal.AVIGame( args.Argv(1) ); 518 }