/neo/framework/common_frame.cpp
C++ | 748 lines | 414 code | 117 blank | 217 comment | 135 complexity | 6fc25fc7b610e770192efb3d6ee64329 MD5 | raw file
- /*
- ===========================================================================
- Doom 3 BFG Edition GPL Source Code
- Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
- This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
- Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
- 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.
- 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.
- ===========================================================================
- */
- #include "../idlib/precompiled.h"
- #pragma hdrstop
- #include "Common_local.h"
- #include "../renderer/Image.h"
- #include "../renderer/ImageOpts.h"
- #include "../../doomclassic/doom/doomlib.h"
- #include "../../doomclassic/doom/globaldata.h"
- /*
- New for tech4x:
- Unlike previous SMP work, the actual GPU command drawing is done in the main thread, which avoids the
- OpenGL problems with needing windows to be created by the same thread that creates the context, as well
- as the issues with passing context ownership back and forth on the 360.
- The game tic and the generation of the draw command list is now run in a separate thread, and overlapped
- with the interpretation of the previous draw command list.
- While the game tic should be nicely contained, the draw command generation winds through the user interface
- code, and is potentially hazardous. For now, the overlap will be restricted to the renderer back end,
- which should also be nicely contained.
- */
- #define DEFAULT_FIXED_TIC "0"
- #define DEFAULT_NO_SLEEP "0"
- idCVar com_deltaTimeClamp( "com_deltaTimeClamp", "50", CVAR_INTEGER, "don't process more than this time in a single frame" );
- idCVar com_fixedTic( "com_fixedTic", DEFAULT_FIXED_TIC, CVAR_BOOL, "run a single game frame per render frame" );
- idCVar com_noSleep( "com_noSleep", DEFAULT_NO_SLEEP, CVAR_BOOL, "don't sleep if the game is running too fast" );
- idCVar com_smp( "com_smp", "1", CVAR_BOOL|CVAR_SYSTEM|CVAR_NOCHEAT, "run the game and draw code in a separate thread" );
- idCVar com_aviDemoSamples( "com_aviDemoSamples", "16", CVAR_SYSTEM, "" );
- idCVar com_aviDemoWidth( "com_aviDemoWidth", "256", CVAR_SYSTEM, "" );
- idCVar com_aviDemoHeight( "com_aviDemoHeight", "256", CVAR_SYSTEM, "" );
- idCVar com_skipGameDraw( "com_skipGameDraw", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
- idCVar com_sleepGame( "com_sleepGame", "0", CVAR_SYSTEM | CVAR_INTEGER, "intentionally add a sleep in the game time" );
- idCVar com_sleepDraw( "com_sleepDraw", "0", CVAR_SYSTEM | CVAR_INTEGER, "intentionally add a sleep in the draw time" );
- idCVar com_sleepRender( "com_sleepRender", "0", CVAR_SYSTEM | CVAR_INTEGER, "intentionally add a sleep in the render time" );
- idCVar net_drawDebugHud( "net_drawDebugHud", "0", CVAR_SYSTEM | CVAR_INTEGER, "0 = None, 1 = Hud 1, 2 = Hud 2, 3 = Snapshots" );
- idCVar timescale( "timescale", "1", CVAR_SYSTEM | CVAR_FLOAT, "Number of game frames to run per render frame", 0.001f, 100.0f );
- extern idCVar in_useJoystick;
- extern idCVar in_joystickRumble;
- /*
- ===============
- idGameThread::Run
- Run in a background thread for performance, but can also
- be called directly in the foreground thread for comparison.
- ===============
- */
- int idGameThread::Run() {
- commonLocal.frameTiming.startGameTime = Sys_Microseconds();
- // debugging tool to test frame dropping behavior
- if ( com_sleepGame.GetInteger() ) {
- Sys_Sleep( com_sleepGame.GetInteger() );
- }
- if ( numGameFrames == 0 ) {
- // Ensure there's no stale gameReturn data from a paused game
- ret = gameReturn_t();
- }
- if ( isClient ) {
- // run the game logic
- for ( int i = 0; i < numGameFrames; i++ ) {
- SCOPED_PROFILE_EVENT( "Client Prediction" );
- if ( userCmdMgr ) {
- game->ClientRunFrame( *userCmdMgr, ( i == numGameFrames - 1 ), ret );
- }
- if ( ret.syncNextGameFrame || ret.sessionCommand[0] != 0 ) {
- break;
- }
- }
- } else {
- // run the game logic
- for ( int i = 0; i < numGameFrames; i++ ) {
- SCOPED_PROFILE_EVENT( "GameTic" );
- if ( userCmdMgr ) {
- game->RunFrame( *userCmdMgr, ret );
- }
- if ( ret.syncNextGameFrame || ret.sessionCommand[0] != 0 ) {
- break;
- }
- }
- }
- // we should have consumed all of our usercmds
- if ( userCmdMgr ) {
- if ( userCmdMgr->HasUserCmdForPlayer( game->GetLocalClientNum() ) && common->GetCurrentGame() == DOOM3_BFG ) {
- idLib::Printf( "idGameThread::Run: didn't consume all usercmds\n" );
- }
- }
- commonLocal.frameTiming.finishGameTime = Sys_Microseconds();
- SetThreadGameTime( ( commonLocal.frameTiming.finishGameTime - commonLocal.frameTiming.startGameTime ) / 1000 );
- // build render commands and geometry
- {
- SCOPED_PROFILE_EVENT( "Draw" );
- commonLocal.Draw();
- }
- commonLocal.frameTiming.finishDrawTime = Sys_Microseconds();
- SetThreadRenderTime( ( commonLocal.frameTiming.finishDrawTime - commonLocal.frameTiming.finishGameTime ) / 1000 );
- SetThreadTotalTime( ( commonLocal.frameTiming.finishDrawTime - commonLocal.frameTiming.startGameTime ) / 1000 );
- return 0;
- }
- /*
- ===============
- idGameThread::RunGameAndDraw
- ===============
- */
- gameReturn_t idGameThread::RunGameAndDraw( int numGameFrames_, idUserCmdMgr & userCmdMgr_, bool isClient_, int startGameFrame ) {
- // this should always immediately return
- this->WaitForThread();
- // save the usercmds for the background thread to pick up
- userCmdMgr = &userCmdMgr_;
- isClient = isClient_;
- // grab the return value created by the last thread execution
- gameReturn_t latchedRet = ret;
- numGameFrames = numGameFrames_;
- // start the thread going
- if ( com_smp.GetBool() == false ) {
- // run it in the main thread so PIX profiling catches everything
- Run();
- } else {
- this->SignalWork();
- }
- // return the latched result while the thread runs in the background
- return latchedRet;
- }
- /*
- ===============
- idCommonLocal::DrawWipeModel
- Draw the fade material over everything that has been drawn
- ===============
- */
- void idCommonLocal::DrawWipeModel() {
- if ( wipeStartTime >= wipeStopTime ) {
- return;
- }
- int currentTime = Sys_Milliseconds();
- if ( !wipeHold && currentTime > wipeStopTime ) {
- return;
- }
- float fade = ( float )( currentTime - wipeStartTime ) / ( wipeStopTime - wipeStartTime );
- renderSystem->SetColor4( 1, 1, 1, fade );
- renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, wipeMaterial );
- }
- /*
- ===============
- idCommonLocal::Draw
- ===============
- */
- void idCommonLocal::Draw() {
- // debugging tool to test frame dropping behavior
- if ( com_sleepDraw.GetInteger() ) {
- Sys_Sleep( com_sleepDraw.GetInteger() );
- }
- if ( loadGUI != NULL ) {
- loadGUI->Render( renderSystem, Sys_Milliseconds() );
- } else if ( currentGame == DOOM_CLASSIC || currentGame == DOOM2_CLASSIC ) {
- const float sysWidth = renderSystem->GetWidth() * renderSystem->GetPixelAspect();
- const float sysHeight = renderSystem->GetHeight();
- const float sysAspect = sysWidth / sysHeight;
- const float doomAspect = 4.0f / 3.0f;
- const float adjustment = sysAspect / doomAspect;
- const float barHeight = ( adjustment >= 1.0f ) ? 0.0f : ( 1.0f - adjustment ) * (float)SCREEN_HEIGHT * 0.25f;
- const float barWidth = ( adjustment <= 1.0f ) ? 0.0f : ( adjustment - 1.0f ) * (float)SCREEN_WIDTH * 0.25f;
- if ( barHeight > 0.0f ) {
- renderSystem->SetColor( colorBlack );
- renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, barHeight, 0, 0, 1, 1, whiteMaterial );
- renderSystem->DrawStretchPic( 0, SCREEN_HEIGHT - barHeight, SCREEN_WIDTH, barHeight, 0, 0, 1, 1, whiteMaterial );
- }
- if ( barWidth > 0.0f ) {
- renderSystem->SetColor( colorBlack );
- renderSystem->DrawStretchPic( 0, 0, barWidth, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial );
- renderSystem->DrawStretchPic( SCREEN_WIDTH - barWidth, 0, barWidth, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial );
- }
- renderSystem->SetColor4( 1, 1, 1, 1 );
- renderSystem->DrawStretchPic( barWidth, barHeight, SCREEN_WIDTH - barWidth * 2.0f, SCREEN_HEIGHT - barHeight * 2.0f, 0, 0, 1, 1, doomClassicMaterial );
- } else if ( game && game->Shell_IsActive() ) {
- bool gameDraw = game->Draw( game->GetLocalClientNum() );
- if ( !gameDraw ) {
- renderSystem->SetColor( colorBlack );
- renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial );
- }
- game->Shell_Render();
- } else if ( readDemo ) {
- renderWorld->RenderScene( ¤tDemoRenderView );
- renderSystem->DrawDemoPics();
- } else if ( mapSpawned ) {
- bool gameDraw = false;
- // normal drawing for both single and multi player
- if ( !com_skipGameDraw.GetBool() && Game()->GetLocalClientNum() >= 0 ) {
- // draw the game view
- int start = Sys_Milliseconds();
- if ( game ) {
- gameDraw = game->Draw( Game()->GetLocalClientNum() );
- }
- int end = Sys_Milliseconds();
- time_gameDraw += ( end - start ); // note time used for com_speeds
- }
- if ( !gameDraw ) {
- renderSystem->SetColor( colorBlack );
- renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, whiteMaterial );
- }
- // save off the 2D drawing from the game
- if ( writeDemo ) {
- renderSystem->WriteDemoPics();
- }
- } else {
- renderSystem->SetColor4( 0, 0, 0, 1 );
- renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial );
- }
- {
- SCOPED_PROFILE_EVENT( "Post-Draw" );
- // draw the wipe material on top of this if it hasn't completed yet
- DrawWipeModel();
- Dialog().Render( loadGUI != NULL );
- // draw the half console / notify console on top of everything
- console->Draw( false );
- }
- }
- /*
- ===============
- idCommonLocal::UpdateScreen
- This is an out-of-sequence screen update, not the normal game rendering
- ===============
- */
- void idCommonLocal::UpdateScreen( bool captureToImage ) {
- if ( insideUpdateScreen ) {
- return;
- }
- insideUpdateScreen = true;
- // make sure the game / draw thread has completed
- gameThread.WaitForThread();
- // release the mouse capture back to the desktop
- Sys_GrabMouseCursor( false );
- // build all the draw commands without running a new game tic
- Draw();
- if ( captureToImage ) {
- renderSystem->CaptureRenderToImage( "_currentRender", false );
- }
- // this should exit right after vsync, with the GPU idle and ready to draw
- const emptyCommand_t * cmd = renderSystem->SwapCommandBuffers( &time_frontend, &time_backend, &time_shadows, &time_gpu );
- // get the GPU busy with new commands
- renderSystem->RenderCommandBuffers( cmd );
- insideUpdateScreen = false;
- }
- /*
- ================
- idCommonLocal::ProcessGameReturn
- ================
- */
- void idCommonLocal::ProcessGameReturn( const gameReturn_t & ret ) {
- // set joystick rumble
- if ( in_useJoystick.GetBool() && in_joystickRumble.GetBool() && !game->Shell_IsActive() && session->GetSignInManager().GetMasterInputDevice() >= 0 ) {
- Sys_SetRumble( session->GetSignInManager().GetMasterInputDevice(), ret.vibrationLow, ret.vibrationHigh ); // Only set the rumble on the active controller
- } else {
- for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) {
- Sys_SetRumble( i, 0, 0 );
- }
- }
- syncNextGameFrame = ret.syncNextGameFrame;
- if ( ret.sessionCommand[0] ) {
- idCmdArgs args;
- args.TokenizeString( ret.sessionCommand, false );
- if ( !idStr::Icmp( args.Argv(0), "map" ) ) {
- MoveToNewMap( args.Argv( 1 ), false );
- } else if ( !idStr::Icmp( args.Argv(0), "devmap" ) ) {
- MoveToNewMap( args.Argv( 1 ), true );
- } else if ( !idStr::Icmp( args.Argv(0), "died" ) ) {
- if ( !IsMultiplayer() ) {
- game->Shell_Show( true );
- }
- } else if ( !idStr::Icmp( args.Argv(0), "disconnect" ) ) {
- cmdSystem->BufferCommandText( CMD_EXEC_INSERT, "stoprecording ; disconnect" );
- } else if ( !idStr::Icmp( args.Argv(0), "endOfDemo" ) ) {
- cmdSystem->BufferCommandText( CMD_EXEC_NOW, "endOfDemo" );
- }
- }
- }
- extern idCVar com_forceGenericSIMD;
- /*
- =================
- idCommonLocal::Frame
- =================
- */
- void idCommonLocal::Frame() {
- try {
- SCOPED_PROFILE_EVENT( "Common::Frame" );
- // This is the only place this is incremented
- idLib::frameNumber++;
- // allow changing SIMD usage on the fly
- if ( com_forceGenericSIMD.IsModified() ) {
- idSIMD::InitProcessor( "doom", com_forceGenericSIMD.GetBool() );
- com_forceGenericSIMD.ClearModified();
- }
- // Do the actual switch between Doom 3 and the classics here so
- // that things don't get confused in the middle of the frame.
- PerformGameSwitch();
- // pump all the events
- Sys_GenerateEvents();
- // write config file if anything changed
- WriteConfiguration();
- eventLoop->RunEventLoop();
- // Activate the shell if it's been requested
- if ( showShellRequested && game ) {
- game->Shell_Show( true );
- showShellRequested = false;
- }
- // if the console or another gui is down, we don't need to hold the mouse cursor
- bool chatting = false;
- if ( console->Active() || Dialog().IsDialogActive() || session->IsSystemUIShowing() || ( game && game->InhibitControls() && !IsPlayingDoomClassic() ) ) {
- Sys_GrabMouseCursor( false );
- usercmdGen->InhibitUsercmd( INHIBIT_SESSION, true );
- chatting = true;
- } else {
- Sys_GrabMouseCursor( true );
- usercmdGen->InhibitUsercmd( INHIBIT_SESSION, false );
- }
- const bool pauseGame = ( !mapSpawned || ( !IsMultiplayer() && ( Dialog().IsDialogPausing() || session->IsSystemUIShowing() || ( game && game->Shell_IsActive() ) ) ) ) && !IsPlayingDoomClassic();
- // save the screenshot and audio from the last draw if needed
- if ( aviCaptureMode ) {
- idStr name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviDemoFrameCount++ );
- renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL );
- // remove any printed lines at the top before taking the screenshot
- console->ClearNotifyLines();
- // this will call Draw, possibly multiple times if com_aviDemoSamples is > 1
- renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL );
- }
- //--------------------------------------------
- // wait for the GPU to finish drawing
- //
- // It is imporant to minimize the time spent between this
- // section and the call to renderSystem->RenderCommandBuffers(),
- // because the GPU is completely idle.
- //--------------------------------------------
- // this should exit right after vsync, with the GPU idle and ready to draw
- // This may block if the GPU isn't finished renderng the previous frame.
- frameTiming.startSyncTime = Sys_Microseconds();
- const emptyCommand_t * renderCommands = NULL;
- if ( com_smp.GetBool() ) {
- renderCommands = renderSystem->SwapCommandBuffers( &time_frontend, &time_backend, &time_shadows, &time_gpu );
- } else {
- // the GPU will stay idle through command generation for minimal
- // input latency
- renderSystem->SwapCommandBuffers_FinishRendering( &time_frontend, &time_backend, &time_shadows, &time_gpu );
- }
- frameTiming.finishSyncTime = Sys_Microseconds();
- //--------------------------------------------
- // Determine how many game tics we are going to run,
- // now that the previous frame is completely finished.
- //
- // It is important that any waiting on the GPU be done
- // before this, or there will be a bad stuttering when
- // dropping frames for performance management.
- //--------------------------------------------
- // input:
- // thisFrameTime
- // com_noSleep
- // com_engineHz
- // com_fixedTic
- // com_deltaTimeClamp
- // IsMultiplayer
- //
- // in/out state:
- // gameFrame
- // gameTimeResidual
- // lastFrameTime
- // syncNextFrame
- //
- // Output:
- // numGameFrames
- // How many game frames to run
- int numGameFrames = 0;
- for(;;) {
- const int thisFrameTime = Sys_Milliseconds();
- static int lastFrameTime = thisFrameTime; // initialized only the first time
- const int deltaMilliseconds = thisFrameTime - lastFrameTime;
- lastFrameTime = thisFrameTime;
- // if there was a large gap in time since the last frame, or the frame
- // rate is very very low, limit the number of frames we will run
- const int clampedDeltaMilliseconds = Min( deltaMilliseconds, com_deltaTimeClamp.GetInteger() );
- gameTimeResidual += clampedDeltaMilliseconds * timescale.GetFloat();
- // don't run any frames when paused
- if ( pauseGame ) {
- gameFrame++;
- gameTimeResidual = 0;
- break;
- }
- // debug cvar to force multiple game tics
- if ( com_fixedTic.GetInteger() > 0 ) {
- numGameFrames = com_fixedTic.GetInteger();
- gameFrame += numGameFrames;
- gameTimeResidual = 0;
- break;
- }
- if ( syncNextGameFrame ) {
- // don't sleep at all
- syncNextGameFrame = false;
- gameFrame++;
- numGameFrames++;
- gameTimeResidual = 0;
- break;
- }
- for ( ;; ) {
- // How much time to wait before running the next frame,
- // based on com_engineHz
- const int frameDelay = FRAME_TO_MSEC( gameFrame + 1 ) - FRAME_TO_MSEC( gameFrame );
- if ( gameTimeResidual < frameDelay ) {
- break;
- }
- gameTimeResidual -= frameDelay;
- gameFrame++;
- numGameFrames++;
- // if there is enough residual left, we may run additional frames
- }
- if ( numGameFrames > 0 ) {
- // ready to actually run them
- break;
- }
- // if we are vsyncing, we always want to run at least one game
- // frame and never sleep, which might happen due to scheduling issues
- // if we were just looking at real time.
- if ( com_noSleep.GetBool() ) {
- numGameFrames = 1;
- gameFrame += numGameFrames;
- gameTimeResidual = 0;
- break;
- }
- // not enough time has passed to run a frame, as might happen if
- // we don't have vsync on, or the monitor is running at 120hz while
- // com_engineHz is 60, so sleep a bit and check again
- Sys_Sleep( 0 );
- }
- //--------------------------------------------
- // It would be better to push as much of this as possible
- // either before or after the renderSystem->SwapCommandBuffers(),
- // because the GPU is completely idle.
- //--------------------------------------------
- // Update session and syncronize to the new session state after sleeping
- session->UpdateSignInManager();
- session->Pump();
- session->ProcessSnapAckQueue();
- if ( session->GetState() == idSession::LOADING ) {
- // If the session reports we should be loading a map, load it!
- ExecuteMapChange();
- mapSpawnData.savegameFile = NULL;
- mapSpawnData.persistentPlayerInfo.Clear();
- return;
- } else if ( session->GetState() != idSession::INGAME && mapSpawned ) {
- // If the game is running, but the session reports we are not in a game, disconnect
- // This happens when a server disconnects us or we sign out
- LeaveGame();
- return;
- }
- if ( mapSpawned && !pauseGame ) {
- if ( IsClient() ) {
- RunNetworkSnapshotFrame();
- }
- }
- ExecuteReliableMessages();
- // send frame and mouse events to active guis
- GuiFrameEvents();
- //--------------------------------------------
- // Prepare usercmds and kick off the game processing
- // in a background thread
- //--------------------------------------------
- // get the previous usercmd for bypassed head tracking transform
- const usercmd_t previousCmd = usercmdGen->GetCurrentUsercmd();
- // build a new usercmd
- int deviceNum = session->GetSignInManager().GetMasterInputDevice();
- usercmdGen->BuildCurrentUsercmd( deviceNum );
- if ( deviceNum == -1 ) {
- for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) {
- Sys_PollJoystickInputEvents( i );
- Sys_EndJoystickInputEvents();
- }
- }
- if ( pauseGame ) {
- usercmdGen->Clear();
- }
- usercmd_t newCmd = usercmdGen->GetCurrentUsercmd();
- // Store server game time - don't let time go past last SS time in case we are extrapolating
- if ( IsClient() ) {
- newCmd.serverGameMilliseconds = std::min( Game()->GetServerGameTimeMs(), Game()->GetSSEndTime() );
- } else {
- newCmd.serverGameMilliseconds = Game()->GetServerGameTimeMs();
- }
- userCmdMgr.MakeReadPtrCurrentForPlayer( Game()->GetLocalClientNum() );
- // Stuff a copy of this userCmd for each game frame we are going to run.
- // Ideally, the usercmds would be built in another thread so you could
- // still get 60hz control accuracy when the game is running slower.
- for ( int i = 0 ; i < numGameFrames ; i++ ) {
- newCmd.clientGameMilliseconds = FRAME_TO_MSEC( gameFrame-numGameFrames+i+1 );
- userCmdMgr.PutUserCmdForPlayer( game->GetLocalClientNum(), newCmd );
- }
- // If we're in Doom or Doom 2, run tics and upload the new texture.
- if ( ( GetCurrentGame() == DOOM_CLASSIC || GetCurrentGame() == DOOM2_CLASSIC ) && !( Dialog().IsDialogPausing() || session->IsSystemUIShowing() ) ) {
- RunDoomClassicFrame();
- }
-
- // start the game / draw command generation thread going in the background
- gameReturn_t ret = gameThread.RunGameAndDraw( numGameFrames, userCmdMgr, IsClient(), gameFrame - numGameFrames );
- if ( !com_smp.GetBool() ) {
- // in non-smp mode, run the commands we just generated, instead of
- // frame-delayed ones from a background thread
- renderCommands = renderSystem->SwapCommandBuffers_FinishCommandBuffers();
- }
- //----------------------------------------
- // Run the render back end, getting the GPU busy with new commands
- // ASAP to minimize the pipeline bubble.
- //----------------------------------------
- frameTiming.startRenderTime = Sys_Microseconds();
- renderSystem->RenderCommandBuffers( renderCommands );
- if ( com_sleepRender.GetInteger() > 0 ) {
- // debug tool to test frame adaption
- Sys_Sleep( com_sleepRender.GetInteger() );
- }
- frameTiming.finishRenderTime = Sys_Microseconds();
- // make sure the game / draw thread has completed
- // This may block if the game is taking longer than the render back end
- gameThread.WaitForThread();
- // Send local usermds to the server.
- // This happens after the game frame has run so that prediction data is up to date.
- SendUsercmds( Game()->GetLocalClientNum() );
- // Now that we have an updated game frame, we can send out new snapshots to our clients
- session->Pump(); // Pump to get updated usercmds to relay
- SendSnapshots();
- // Render the sound system using the latest commands from the game thread
- if ( pauseGame ) {
- soundWorld->Pause();
- soundSystem->SetPlayingSoundWorld( menuSoundWorld );
- } else {
- soundWorld->UnPause();
- soundSystem->SetPlayingSoundWorld( soundWorld );
- }
- soundSystem->Render();
- // process the game return for map changes, etc
- ProcessGameReturn( ret );
- idLobbyBase & lobby = session->GetActivePlatformLobbyBase();
- if ( lobby.HasActivePeers() ) {
- if ( net_drawDebugHud.GetInteger() == 1 ) {
- lobby.DrawDebugNetworkHUD();
- }
- if ( net_drawDebugHud.GetInteger() == 2 ) {
- lobby.DrawDebugNetworkHUD2();
- }
- lobby.DrawDebugNetworkHUD_ServerSnapshotMetrics( net_drawDebugHud.GetInteger() == 3 );
- }
- // report timing information
- if ( com_speeds.GetBool() ) {
- static int lastTime = Sys_Milliseconds();
- int nowTime = Sys_Milliseconds();
- int com_frameMsec = nowTime - lastTime;
- lastTime = nowTime;
- Printf( "frame:%d all:%3d gfr:%3d rf:%3lld bk:%3lld\n", idLib::frameNumber, com_frameMsec, time_gameFrame, time_frontend / 1000, time_backend / 1000 );
- time_gameFrame = 0;
- time_gameDraw = 0;
- }
- // the FPU stack better be empty at this point or some bad code or compiler bug left values on the stack
- if ( !Sys_FPU_StackIsEmpty() ) {
- Printf( Sys_FPU_GetState() );
- FatalError( "idCommon::Frame: the FPU stack is not empty at the end of the frame\n" );
- }
- mainFrameTiming = frameTiming;
- session->GetSaveGameManager().Pump();
- } catch( idException & ) {
- return; // an ERP_DROP was thrown
- }
- }
- /*
- =================
- idCommonLocal::RunDoomClassicFrame
- =================
- */
- void idCommonLocal::RunDoomClassicFrame() {
- static int doomTics = 0;
- if( DoomLib::expansionDirty ) {
- // re-Initialize the Doom Engine.
- DoomLib::Interface.Shutdown();
- DoomLib::Interface.Startup( 1, false );
- DoomLib::expansionDirty = false;
- }
- if ( DoomLib::Interface.Frame( doomTics, &userCmdMgr ) ) {
- Globals *data = (Globals*)DoomLib::GetGlobalData( 0 );
- idArray< unsigned int, 256 > palette;
- std::copy( data->XColorMap, data->XColorMap + palette.Num(), palette.Ptr() );
- // Do the palette lookup.
- for ( int row = 0; row < DOOMCLASSIC_RENDERHEIGHT; ++row ) {
- for ( int column = 0; column < DOOMCLASSIC_RENDERWIDTH; ++column ) {
- const int doomScreenPixelIndex = row * DOOMCLASSIC_RENDERWIDTH + column;
- const byte paletteIndex = data->screens[0][doomScreenPixelIndex];
- const unsigned int paletteColor = palette[paletteIndex];
- const byte red = (paletteColor & 0xFF000000) >> 24;
- const byte green = (paletteColor & 0x00FF0000) >> 16;
- const byte blue = (paletteColor & 0x0000FF00) >> 8;
- const int imageDataPixelIndex = row * DOOMCLASSIC_RENDERWIDTH * DOOMCLASSIC_BYTES_PER_PIXEL + column * DOOMCLASSIC_BYTES_PER_PIXEL;
- doomClassicImageData[imageDataPixelIndex] = red;
- doomClassicImageData[imageDataPixelIndex + 1] = green;
- doomClassicImageData[imageDataPixelIndex + 2] = blue;
- doomClassicImageData[imageDataPixelIndex + 3] = 255;
- }
- }
- }
- renderSystem->UploadImage( "_doomClassic", doomClassicImageData.Ptr(), DOOMCLASSIC_RENDERWIDTH, DOOMCLASSIC_RENDERHEIGHT );
- doomTics++;
- }