common_frame.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition Source Code 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 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. 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.
  17. 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.
  18. ===========================================================================
  19. */
  20. #include "../idlib/precompiled.h"
  21. #pragma hdrstop
  22. #include "Common_local.h"
  23. #include "../renderer/Image.h"
  24. #include "../renderer/ImageOpts.h"
  25. #include "../../doomclassic/doom/doomlib.h"
  26. #include "../../doomclassic/doom/globaldata.h"
  27. /*
  28. New for tech4x:
  29. Unlike previous SMP work, the actual GPU command drawing is done in the main thread, which avoids the
  30. OpenGL problems with needing windows to be created by the same thread that creates the context, as well
  31. as the issues with passing context ownership back and forth on the 360.
  32. The game tic and the generation of the draw command list is now run in a separate thread, and overlapped
  33. with the interpretation of the previous draw command list.
  34. While the game tic should be nicely contained, the draw command generation winds through the user interface
  35. code, and is potentially hazardous. For now, the overlap will be restricted to the renderer back end,
  36. which should also be nicely contained.
  37. */
  38. #define DEFAULT_FIXED_TIC "0"
  39. #define DEFAULT_NO_SLEEP "0"
  40. idCVar com_deltaTimeClamp( "com_deltaTimeClamp", "50", CVAR_INTEGER, "don't process more than this time in a single frame" );
  41. idCVar com_fixedTic( "com_fixedTic", DEFAULT_FIXED_TIC, CVAR_BOOL, "run a single game frame per render frame" );
  42. idCVar com_noSleep( "com_noSleep", DEFAULT_NO_SLEEP, CVAR_BOOL, "don't sleep if the game is running too fast" );
  43. idCVar com_smp( "com_smp", "1", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "run the game and draw code in a separate thread" );
  44. idCVar com_aviDemoSamples( "com_aviDemoSamples", "16", CVAR_SYSTEM, "" );
  45. idCVar com_aviDemoWidth( "com_aviDemoWidth", "256", CVAR_SYSTEM, "" );
  46. idCVar com_aviDemoHeight( "com_aviDemoHeight", "256", CVAR_SYSTEM, "" );
  47. idCVar com_skipGameDraw( "com_skipGameDraw", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
  48. idCVar com_sleepGame( "com_sleepGame", "0", CVAR_SYSTEM | CVAR_INTEGER, "intentionally add a sleep in the game time" );
  49. idCVar com_sleepDraw( "com_sleepDraw", "0", CVAR_SYSTEM | CVAR_INTEGER, "intentionally add a sleep in the draw time" );
  50. idCVar com_sleepRender( "com_sleepRender", "0", CVAR_SYSTEM | CVAR_INTEGER, "intentionally add a sleep in the render time" );
  51. idCVar net_drawDebugHud( "net_drawDebugHud", "0", CVAR_SYSTEM | CVAR_INTEGER, "0 = None, 1 = Hud 1, 2 = Hud 2, 3 = Snapshots" );
  52. idCVar timescale( "timescale", "1", CVAR_SYSTEM | CVAR_FLOAT, "Number of game frames to run per render frame", 0.001f, 100.0f );
  53. extern idCVar in_useJoystick;
  54. extern idCVar in_joystickRumble;
  55. /*
  56. ===============
  57. idGameThread::Run
  58. Run in a background thread for performance, but can also
  59. be called directly in the foreground thread for comparison.
  60. ===============
  61. */
  62. int idGameThread::Run() {
  63. commonLocal.frameTiming.startGameTime = Sys_Microseconds();
  64. // debugging tool to test frame dropping behavior
  65. if ( com_sleepGame.GetInteger() ) {
  66. Sys_Sleep( com_sleepGame.GetInteger() );
  67. }
  68. if ( numGameFrames == 0 ) {
  69. // Ensure there's no stale gameReturn data from a paused game
  70. ret = gameReturn_t();
  71. }
  72. if ( isClient ) {
  73. // run the game logic
  74. for ( int i = 0; i < numGameFrames; i++ ) {
  75. SCOPED_PROFILE_EVENT( "Client Prediction" );
  76. if ( userCmdMgr ) {
  77. game->ClientRunFrame( *userCmdMgr, ( i == numGameFrames - 1 ), ret );
  78. }
  79. if ( ret.syncNextGameFrame || ret.sessionCommand[0] != 0 ) {
  80. break;
  81. }
  82. }
  83. } else {
  84. // run the game logic
  85. for ( int i = 0; i < numGameFrames; i++ ) {
  86. SCOPED_PROFILE_EVENT( "GameTic" );
  87. if ( userCmdMgr ) {
  88. game->RunFrame( *userCmdMgr, ret );
  89. }
  90. if ( ret.syncNextGameFrame || ret.sessionCommand[0] != 0 ) {
  91. break;
  92. }
  93. }
  94. }
  95. // we should have consumed all of our usercmds
  96. if ( userCmdMgr ) {
  97. if ( userCmdMgr->HasUserCmdForPlayer( game->GetLocalClientNum() ) && common->GetCurrentGame() == DOOM3_BFG ) {
  98. idLib::Printf( "idGameThread::Run: didn't consume all usercmds\n" );
  99. }
  100. }
  101. commonLocal.frameTiming.finishGameTime = Sys_Microseconds();
  102. SetThreadGameTime( ( commonLocal.frameTiming.finishGameTime - commonLocal.frameTiming.startGameTime ) / 1000 );
  103. // build render commands and geometry
  104. {
  105. SCOPED_PROFILE_EVENT( "Draw" );
  106. commonLocal.Draw();
  107. }
  108. commonLocal.frameTiming.finishDrawTime = Sys_Microseconds();
  109. SetThreadRenderTime( ( commonLocal.frameTiming.finishDrawTime - commonLocal.frameTiming.finishGameTime ) / 1000 );
  110. SetThreadTotalTime( ( commonLocal.frameTiming.finishDrawTime - commonLocal.frameTiming.startGameTime ) / 1000 );
  111. return 0;
  112. }
  113. /*
  114. ===============
  115. idGameThread::RunGameAndDraw
  116. ===============
  117. */
  118. gameReturn_t idGameThread::RunGameAndDraw( int numGameFrames_, idUserCmdMgr & userCmdMgr_, bool isClient_, int startGameFrame ) {
  119. // this should always immediately return
  120. this->WaitForThread();
  121. // save the usercmds for the background thread to pick up
  122. userCmdMgr = &userCmdMgr_;
  123. isClient = isClient_;
  124. // grab the return value created by the last thread execution
  125. gameReturn_t latchedRet = ret;
  126. numGameFrames = numGameFrames_;
  127. // start the thread going
  128. if ( com_smp.GetBool() == false ) {
  129. // run it in the main thread so PIX profiling catches everything
  130. Run();
  131. } else {
  132. this->SignalWork();
  133. }
  134. // return the latched result while the thread runs in the background
  135. return latchedRet;
  136. }
  137. /*
  138. ===============
  139. idCommonLocal::DrawWipeModel
  140. Draw the fade material over everything that has been drawn
  141. ===============
  142. */
  143. void idCommonLocal::DrawWipeModel() {
  144. if ( wipeStartTime >= wipeStopTime ) {
  145. return;
  146. }
  147. int currentTime = Sys_Milliseconds();
  148. if ( !wipeHold && currentTime > wipeStopTime ) {
  149. return;
  150. }
  151. float fade = ( float )( currentTime - wipeStartTime ) / ( wipeStopTime - wipeStartTime );
  152. renderSystem->SetColor4( 1, 1, 1, fade );
  153. renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, wipeMaterial );
  154. }
  155. /*
  156. ===============
  157. idCommonLocal::Draw
  158. ===============
  159. */
  160. void idCommonLocal::Draw() {
  161. // debugging tool to test frame dropping behavior
  162. if ( com_sleepDraw.GetInteger() ) {
  163. Sys_Sleep( com_sleepDraw.GetInteger() );
  164. }
  165. if ( loadGUI != NULL ) {
  166. loadGUI->Render( renderSystem, Sys_Milliseconds() );
  167. } else if ( currentGame == DOOM_CLASSIC || currentGame == DOOM2_CLASSIC ) {
  168. const float sysWidth = renderSystem->GetWidth() * renderSystem->GetPixelAspect();
  169. const float sysHeight = renderSystem->GetHeight();
  170. const float sysAspect = sysWidth / sysHeight;
  171. const float doomAspect = 4.0f / 3.0f;
  172. const float adjustment = sysAspect / doomAspect;
  173. const float barHeight = ( adjustment >= 1.0f ) ? 0.0f : ( 1.0f - adjustment ) * (float)SCREEN_HEIGHT * 0.25f;
  174. const float barWidth = ( adjustment <= 1.0f ) ? 0.0f : ( adjustment - 1.0f ) * (float)SCREEN_WIDTH * 0.25f;
  175. if ( barHeight > 0.0f ) {
  176. renderSystem->SetColor( colorBlack );
  177. renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, barHeight, 0, 0, 1, 1, whiteMaterial );
  178. renderSystem->DrawStretchPic( 0, SCREEN_HEIGHT - barHeight, SCREEN_WIDTH, barHeight, 0, 0, 1, 1, whiteMaterial );
  179. }
  180. if ( barWidth > 0.0f ) {
  181. renderSystem->SetColor( colorBlack );
  182. renderSystem->DrawStretchPic( 0, 0, barWidth, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial );
  183. renderSystem->DrawStretchPic( SCREEN_WIDTH - barWidth, 0, barWidth, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial );
  184. }
  185. renderSystem->SetColor4( 1, 1, 1, 1 );
  186. renderSystem->DrawStretchPic( barWidth, barHeight, SCREEN_WIDTH - barWidth * 2.0f, SCREEN_HEIGHT - barHeight * 2.0f, 0, 0, 1, 1, doomClassicMaterial );
  187. } else if ( game && game->Shell_IsActive() ) {
  188. bool gameDraw = game->Draw( game->GetLocalClientNum() );
  189. if ( !gameDraw ) {
  190. renderSystem->SetColor( colorBlack );
  191. renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial );
  192. }
  193. game->Shell_Render();
  194. } else if ( readDemo ) {
  195. renderWorld->RenderScene( &currentDemoRenderView );
  196. renderSystem->DrawDemoPics();
  197. } else if ( mapSpawned ) {
  198. bool gameDraw = false;
  199. // normal drawing for both single and multi player
  200. if ( !com_skipGameDraw.GetBool() && Game()->GetLocalClientNum() >= 0 ) {
  201. // draw the game view
  202. int start = Sys_Milliseconds();
  203. if ( game ) {
  204. gameDraw = game->Draw( Game()->GetLocalClientNum() );
  205. }
  206. int end = Sys_Milliseconds();
  207. time_gameDraw += ( end - start ); // note time used for com_speeds
  208. }
  209. if ( !gameDraw ) {
  210. renderSystem->SetColor( colorBlack );
  211. renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, whiteMaterial );
  212. }
  213. // save off the 2D drawing from the game
  214. if ( writeDemo ) {
  215. renderSystem->WriteDemoPics();
  216. }
  217. } else {
  218. renderSystem->SetColor4( 0, 0, 0, 1 );
  219. renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial );
  220. }
  221. {
  222. SCOPED_PROFILE_EVENT( "Post-Draw" );
  223. // draw the wipe material on top of this if it hasn't completed yet
  224. DrawWipeModel();
  225. Dialog().Render( loadGUI != NULL );
  226. // draw the half console / notify console on top of everything
  227. console->Draw( false );
  228. }
  229. }
  230. /*
  231. ===============
  232. idCommonLocal::UpdateScreen
  233. This is an out-of-sequence screen update, not the normal game rendering
  234. ===============
  235. */
  236. void idCommonLocal::UpdateScreen( bool captureToImage ) {
  237. if ( insideUpdateScreen ) {
  238. return;
  239. }
  240. insideUpdateScreen = true;
  241. // make sure the game / draw thread has completed
  242. gameThread.WaitForThread();
  243. // release the mouse capture back to the desktop
  244. Sys_GrabMouseCursor( false );
  245. // build all the draw commands without running a new game tic
  246. Draw();
  247. if ( captureToImage ) {
  248. renderSystem->CaptureRenderToImage( "_currentRender", false );
  249. }
  250. // this should exit right after vsync, with the GPU idle and ready to draw
  251. const emptyCommand_t * cmd = renderSystem->SwapCommandBuffers( &time_frontend, &time_backend, &time_shadows, &time_gpu );
  252. // get the GPU busy with new commands
  253. renderSystem->RenderCommandBuffers( cmd );
  254. insideUpdateScreen = false;
  255. }
  256. /*
  257. ================
  258. idCommonLocal::ProcessGameReturn
  259. ================
  260. */
  261. void idCommonLocal::ProcessGameReturn( const gameReturn_t & ret ) {
  262. // set joystick rumble
  263. if ( in_useJoystick.GetBool() && in_joystickRumble.GetBool() && !game->Shell_IsActive() && session->GetSignInManager().GetMasterInputDevice() >= 0 ) {
  264. Sys_SetRumble( session->GetSignInManager().GetMasterInputDevice(), ret.vibrationLow, ret.vibrationHigh ); // Only set the rumble on the active controller
  265. } else {
  266. for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) {
  267. Sys_SetRumble( i, 0, 0 );
  268. }
  269. }
  270. syncNextGameFrame = ret.syncNextGameFrame;
  271. if ( ret.sessionCommand[0] ) {
  272. idCmdArgs args;
  273. args.TokenizeString( ret.sessionCommand, false );
  274. if ( !idStr::Icmp( args.Argv(0), "map" ) ) {
  275. MoveToNewMap( args.Argv( 1 ), false );
  276. } else if ( !idStr::Icmp( args.Argv(0), "devmap" ) ) {
  277. MoveToNewMap( args.Argv( 1 ), true );
  278. } else if ( !idStr::Icmp( args.Argv(0), "died" ) ) {
  279. if ( !IsMultiplayer() ) {
  280. game->Shell_Show( true );
  281. }
  282. } else if ( !idStr::Icmp( args.Argv(0), "disconnect" ) ) {
  283. cmdSystem->BufferCommandText( CMD_EXEC_INSERT, "stoprecording ; disconnect" );
  284. } else if ( !idStr::Icmp( args.Argv(0), "endOfDemo" ) ) {
  285. cmdSystem->BufferCommandText( CMD_EXEC_NOW, "endOfDemo" );
  286. }
  287. }
  288. }
  289. extern idCVar com_forceGenericSIMD;
  290. /*
  291. =================
  292. idCommonLocal::Frame
  293. =================
  294. */
  295. void idCommonLocal::Frame() {
  296. try {
  297. SCOPED_PROFILE_EVENT( "Common::Frame" );
  298. // This is the only place this is incremented
  299. idLib::frameNumber++;
  300. // allow changing SIMD usage on the fly
  301. if ( com_forceGenericSIMD.IsModified() ) {
  302. idSIMD::InitProcessor( "doom", com_forceGenericSIMD.GetBool() );
  303. com_forceGenericSIMD.ClearModified();
  304. }
  305. // Do the actual switch between Doom 3 and the classics here so
  306. // that things don't get confused in the middle of the frame.
  307. PerformGameSwitch();
  308. // pump all the events
  309. Sys_GenerateEvents();
  310. // write config file if anything changed
  311. WriteConfiguration();
  312. eventLoop->RunEventLoop();
  313. // Activate the shell if it's been requested
  314. if ( showShellRequested && game ) {
  315. game->Shell_Show( true );
  316. showShellRequested = false;
  317. }
  318. // if the console or another gui is down, we don't need to hold the mouse cursor
  319. bool chatting = false;
  320. if ( console->Active() || Dialog().IsDialogActive() || session->IsSystemUIShowing() || ( game && game->InhibitControls() && !IsPlayingDoomClassic() ) ) {
  321. Sys_GrabMouseCursor( false );
  322. usercmdGen->InhibitUsercmd( INHIBIT_SESSION, true );
  323. chatting = true;
  324. } else {
  325. Sys_GrabMouseCursor( true );
  326. usercmdGen->InhibitUsercmd( INHIBIT_SESSION, false );
  327. }
  328. const bool pauseGame = ( !mapSpawned || ( !IsMultiplayer() && ( Dialog().IsDialogPausing() || session->IsSystemUIShowing() || ( game && game->Shell_IsActive() ) ) ) ) && !IsPlayingDoomClassic();
  329. // save the screenshot and audio from the last draw if needed
  330. if ( aviCaptureMode ) {
  331. idStr name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviDemoFrameCount++ );
  332. renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL );
  333. // remove any printed lines at the top before taking the screenshot
  334. console->ClearNotifyLines();
  335. // this will call Draw, possibly multiple times if com_aviDemoSamples is > 1
  336. renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL );
  337. }
  338. //--------------------------------------------
  339. // wait for the GPU to finish drawing
  340. //
  341. // It is imporant to minimize the time spent between this
  342. // section and the call to renderSystem->RenderCommandBuffers(),
  343. // because the GPU is completely idle.
  344. //--------------------------------------------
  345. // this should exit right after vsync, with the GPU idle and ready to draw
  346. // This may block if the GPU isn't finished renderng the previous frame.
  347. frameTiming.startSyncTime = Sys_Microseconds();
  348. const emptyCommand_t * renderCommands = NULL;
  349. if ( com_smp.GetBool() ) {
  350. renderCommands = renderSystem->SwapCommandBuffers( &time_frontend, &time_backend, &time_shadows, &time_gpu );
  351. } else {
  352. // the GPU will stay idle through command generation for minimal
  353. // input latency
  354. renderSystem->SwapCommandBuffers_FinishRendering( &time_frontend, &time_backend, &time_shadows, &time_gpu );
  355. }
  356. frameTiming.finishSyncTime = Sys_Microseconds();
  357. //--------------------------------------------
  358. // Determine how many game tics we are going to run,
  359. // now that the previous frame is completely finished.
  360. //
  361. // It is important that any waiting on the GPU be done
  362. // before this, or there will be a bad stuttering when
  363. // dropping frames for performance management.
  364. //--------------------------------------------
  365. // input:
  366. // thisFrameTime
  367. // com_noSleep
  368. // com_engineHz
  369. // com_fixedTic
  370. // com_deltaTimeClamp
  371. // IsMultiplayer
  372. //
  373. // in/out state:
  374. // gameFrame
  375. // gameTimeResidual
  376. // lastFrameTime
  377. // syncNextFrame
  378. //
  379. // Output:
  380. // numGameFrames
  381. // How many game frames to run
  382. int numGameFrames = 0;
  383. for(;;) {
  384. const int thisFrameTime = Sys_Milliseconds();
  385. static int lastFrameTime = thisFrameTime; // initialized only the first time
  386. const int deltaMilliseconds = thisFrameTime - lastFrameTime;
  387. lastFrameTime = thisFrameTime;
  388. // if there was a large gap in time since the last frame, or the frame
  389. // rate is very very low, limit the number of frames we will run
  390. const int clampedDeltaMilliseconds = Min( deltaMilliseconds, com_deltaTimeClamp.GetInteger() );
  391. gameTimeResidual += clampedDeltaMilliseconds * timescale.GetFloat();
  392. // don't run any frames when paused
  393. if ( pauseGame ) {
  394. gameFrame++;
  395. gameTimeResidual = 0;
  396. break;
  397. }
  398. // debug cvar to force multiple game tics
  399. if ( com_fixedTic.GetInteger() > 0 ) {
  400. numGameFrames = com_fixedTic.GetInteger();
  401. gameFrame += numGameFrames;
  402. gameTimeResidual = 0;
  403. break;
  404. }
  405. if ( syncNextGameFrame ) {
  406. // don't sleep at all
  407. syncNextGameFrame = false;
  408. gameFrame++;
  409. numGameFrames++;
  410. gameTimeResidual = 0;
  411. break;
  412. }
  413. for ( ;; ) {
  414. // How much time to wait before running the next frame,
  415. // based on com_engineHz
  416. const int frameDelay = FRAME_TO_MSEC( gameFrame + 1 ) - FRAME_TO_MSEC( gameFrame );
  417. if ( gameTimeResidual < frameDelay ) {
  418. break;
  419. }
  420. gameTimeResidual -= frameDelay;
  421. gameFrame++;
  422. numGameFrames++;
  423. // if there is enough residual left, we may run additional frames
  424. }
  425. if ( numGameFrames > 0 ) {
  426. // ready to actually run them
  427. break;
  428. }
  429. // if we are vsyncing, we always want to run at least one game
  430. // frame and never sleep, which might happen due to scheduling issues
  431. // if we were just looking at real time.
  432. if ( com_noSleep.GetBool() ) {
  433. numGameFrames = 1;
  434. gameFrame += numGameFrames;
  435. gameTimeResidual = 0;
  436. break;
  437. }
  438. // not enough time has passed to run a frame, as might happen if
  439. // we don't have vsync on, or the monitor is running at 120hz while
  440. // com_engineHz is 60, so sleep a bit and check again
  441. Sys_Sleep( 0 );
  442. }
  443. //--------------------------------------------
  444. // It would be better to push as much of this as possible
  445. // either before or after the renderSystem->SwapCommandBuffers(),
  446. // because the GPU is completely idle.
  447. //--------------------------------------------
  448. // Update session and syncronize to the new session state after sleeping
  449. session->UpdateSignInManager();
  450. session->Pump();
  451. session->ProcessSnapAckQueue();
  452. if ( session->GetState() == idSession::LOADING ) {
  453. // If the session reports we should be loading a map, load it!
  454. ExecuteMapChange();
  455. mapSpawnData.savegameFile = NULL;
  456. mapSpawnData.persistentPlayerInfo.Clear();
  457. return;
  458. } else if ( session->GetState() != idSession::INGAME && mapSpawned ) {
  459. // If the game is running, but the session reports we are not in a game, disconnect
  460. // This happens when a server disconnects us or we sign out
  461. LeaveGame();
  462. return;
  463. }
  464. if ( mapSpawned && !pauseGame ) {
  465. if ( IsClient() ) {
  466. RunNetworkSnapshotFrame();
  467. }
  468. }
  469. ExecuteReliableMessages();
  470. // send frame and mouse events to active guis
  471. GuiFrameEvents();
  472. //--------------------------------------------
  473. // Prepare usercmds and kick off the game processing
  474. // in a background thread
  475. //--------------------------------------------
  476. // get the previous usercmd for bypassed head tracking transform
  477. const usercmd_t previousCmd = usercmdGen->GetCurrentUsercmd();
  478. // build a new usercmd
  479. int deviceNum = session->GetSignInManager().GetMasterInputDevice();
  480. usercmdGen->BuildCurrentUsercmd( deviceNum );
  481. if ( deviceNum == -1 ) {
  482. for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) {
  483. Sys_PollJoystickInputEvents( i );
  484. Sys_EndJoystickInputEvents();
  485. }
  486. }
  487. if ( pauseGame ) {
  488. usercmdGen->Clear();
  489. }
  490. usercmd_t newCmd = usercmdGen->GetCurrentUsercmd();
  491. // Store server game time - don't let time go past last SS time in case we are extrapolating
  492. if ( IsClient() ) {
  493. newCmd.serverGameMilliseconds = std::min( Game()->GetServerGameTimeMs(), Game()->GetSSEndTime() );
  494. } else {
  495. newCmd.serverGameMilliseconds = Game()->GetServerGameTimeMs();
  496. }
  497. userCmdMgr.MakeReadPtrCurrentForPlayer( Game()->GetLocalClientNum() );
  498. // Stuff a copy of this userCmd for each game frame we are going to run.
  499. // Ideally, the usercmds would be built in another thread so you could
  500. // still get 60hz control accuracy when the game is running slower.
  501. for ( int i = 0 ; i < numGameFrames ; i++ ) {
  502. newCmd.clientGameMilliseconds = FRAME_TO_MSEC( gameFrame-numGameFrames+i+1 );
  503. userCmdMgr.PutUserCmdForPlayer( game->GetLocalClientNum(), newCmd );
  504. }
  505. // If we're in Doom or Doom 2, run tics and upload the new texture.
  506. if ( ( GetCurrentGame() == DOOM_CLASSIC || GetCurrentGame() == DOOM2_CLASSIC ) && !( Dialog().IsDialogPausing() || session->IsSystemUIShowing() ) ) {
  507. RunDoomClassicFrame();
  508. }
  509. // start the game / draw command generation thread going in the background
  510. gameReturn_t ret = gameThread.RunGameAndDraw( numGameFrames, userCmdMgr, IsClient(), gameFrame - numGameFrames );
  511. if ( !com_smp.GetBool() ) {
  512. // in non-smp mode, run the commands we just generated, instead of
  513. // frame-delayed ones from a background thread
  514. renderCommands = renderSystem->SwapCommandBuffers_FinishCommandBuffers();
  515. }
  516. //----------------------------------------
  517. // Run the render back end, getting the GPU busy with new commands
  518. // ASAP to minimize the pipeline bubble.
  519. //----------------------------------------
  520. frameTiming.startRenderTime = Sys_Microseconds();
  521. renderSystem->RenderCommandBuffers( renderCommands );
  522. if ( com_sleepRender.GetInteger() > 0 ) {
  523. // debug tool to test frame adaption
  524. Sys_Sleep( com_sleepRender.GetInteger() );
  525. }
  526. frameTiming.finishRenderTime = Sys_Microseconds();
  527. // make sure the game / draw thread has completed
  528. // This may block if the game is taking longer than the render back end
  529. gameThread.WaitForThread();
  530. // Send local usermds to the server.
  531. // This happens after the game frame has run so that prediction data is up to date.
  532. SendUsercmds( Game()->GetLocalClientNum() );
  533. // Now that we have an updated game frame, we can send out new snapshots to our clients
  534. session->Pump(); // Pump to get updated usercmds to relay
  535. SendSnapshots();
  536. // Render the sound system using the latest commands from the game thread
  537. if ( pauseGame ) {
  538. soundWorld->Pause();
  539. soundSystem->SetPlayingSoundWorld( menuSoundWorld );
  540. } else {
  541. soundWorld->UnPause();
  542. soundSystem->SetPlayingSoundWorld( soundWorld );
  543. }
  544. soundSystem->Render();
  545. // process the game return for map changes, etc
  546. ProcessGameReturn( ret );
  547. idLobbyBase & lobby = session->GetActivePlatformLobbyBase();
  548. if ( lobby.HasActivePeers() ) {
  549. if ( net_drawDebugHud.GetInteger() == 1 ) {
  550. lobby.DrawDebugNetworkHUD();
  551. }
  552. if ( net_drawDebugHud.GetInteger() == 2 ) {
  553. lobby.DrawDebugNetworkHUD2();
  554. }
  555. lobby.DrawDebugNetworkHUD_ServerSnapshotMetrics( net_drawDebugHud.GetInteger() == 3 );
  556. }
  557. // report timing information
  558. if ( com_speeds.GetBool() ) {
  559. static int lastTime = Sys_Milliseconds();
  560. int nowTime = Sys_Milliseconds();
  561. int com_frameMsec = nowTime - lastTime;
  562. lastTime = nowTime;
  563. Printf( "frame:%d all:%3d gfr:%3d rf:%3lld bk:%3lld\n", idLib::frameNumber, com_frameMsec, time_gameFrame, time_frontend / 1000, time_backend / 1000 );
  564. time_gameFrame = 0;
  565. time_gameDraw = 0;
  566. }
  567. // the FPU stack better be empty at this point or some bad code or compiler bug left values on the stack
  568. if ( !Sys_FPU_StackIsEmpty() ) {
  569. Printf( Sys_FPU_GetState() );
  570. FatalError( "idCommon::Frame: the FPU stack is not empty at the end of the frame\n" );
  571. }
  572. mainFrameTiming = frameTiming;
  573. session->GetSaveGameManager().Pump();
  574. } catch( idException & ) {
  575. return; // an ERP_DROP was thrown
  576. }
  577. }
  578. /*
  579. =================
  580. idCommonLocal::RunDoomClassicFrame
  581. =================
  582. */
  583. void idCommonLocal::RunDoomClassicFrame() {
  584. static int doomTics = 0;
  585. if( DoomLib::expansionDirty ) {
  586. // re-Initialize the Doom Engine.
  587. DoomLib::Interface.Shutdown();
  588. DoomLib::Interface.Startup( 1, false );
  589. DoomLib::expansionDirty = false;
  590. }
  591. if ( DoomLib::Interface.Frame( doomTics, &userCmdMgr ) ) {
  592. Globals *data = (Globals*)DoomLib::GetGlobalData( 0 );
  593. idArray< unsigned int, 256 > palette;
  594. std::copy( data->XColorMap, data->XColorMap + palette.Num(), palette.Ptr() );
  595. // Do the palette lookup.
  596. for ( int row = 0; row < DOOMCLASSIC_RENDERHEIGHT; ++row ) {
  597. for ( int column = 0; column < DOOMCLASSIC_RENDERWIDTH; ++column ) {
  598. const int doomScreenPixelIndex = row * DOOMCLASSIC_RENDERWIDTH + column;
  599. const byte paletteIndex = data->screens[0][doomScreenPixelIndex];
  600. const unsigned int paletteColor = palette[paletteIndex];
  601. const byte red = (paletteColor & 0xFF000000) >> 24;
  602. const byte green = (paletteColor & 0x00FF0000) >> 16;
  603. const byte blue = (paletteColor & 0x0000FF00) >> 8;
  604. const int imageDataPixelIndex = row * DOOMCLASSIC_RENDERWIDTH * DOOMCLASSIC_BYTES_PER_PIXEL + column * DOOMCLASSIC_BYTES_PER_PIXEL;
  605. doomClassicImageData[imageDataPixelIndex] = red;
  606. doomClassicImageData[imageDataPixelIndex + 1] = green;
  607. doomClassicImageData[imageDataPixelIndex + 2] = blue;
  608. doomClassicImageData[imageDataPixelIndex + 3] = 255;
  609. }
  610. }
  611. }
  612. renderSystem->UploadImage( "_doomClassic", doomClassicImageData.Ptr(), DOOMCLASSIC_RENDERWIDTH, DOOMCLASSIC_RENDERHEIGHT );
  613. doomTics++;
  614. }