Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

380 lines
11 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "cbase.h"
  5. #if defined( REPLAY_ENABLED )
  6. #include "replayrenderoverlay.h"
  7. #include "vgui_controls/TextImage.h"
  8. #include "replay/genericclassbased_replay.h"
  9. #include "iclientmode.h"
  10. #include "VGuiMatSurface/IMatSystemSurface.h"
  11. #include "ienginevgui.h"
  12. #include "vgui/IVGui.h"
  13. #include "econ/confirm_dialog.h"
  14. #include "replay/ireplaymanager.h"
  15. #include "replay/irecordingsessionmanager.h"
  16. #include "replay/ireplaymoviemanager.h"
  17. #include "replay/replayrenderer.h"
  18. #include "econ/econ_controls.h"
  19. // memdbgon must be the last include file in a .cpp file!!!
  20. #include <tier0/memdbgon.h>
  21. //-----------------------------------------------------------------------------
  22. extern IReplayMovieManager *g_pReplayMovieManager;
  23. //-----------------------------------------------------------------------------
  24. using namespace vgui;
  25. //-----------------------------------------------------------------------------
  26. #define TMP_ENCODED_AUDIO ".tmp.aac"
  27. #ifdef USE_WEBM_FOR_REPLAY
  28. #define TMP_ENCODED_VIDEO ".tmp.webm"
  29. #else
  30. #define TMP_ENCODED_VIDEO ".tmp.mov"
  31. #endif
  32. //-----------------------------------------------------------------------------
  33. ConVar replay_enablerenderpreview( "replay_enablerenderpreview", "1", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE, "Enable preview during replay render." );
  34. //-----------------------------------------------------------------------------
  35. void OnRenderCancelDialogButtonPressed( bool bConfirm, void *pContext )
  36. {
  37. if ( bConfirm )
  38. {
  39. g_pReplayMovieManager->CancelRender();
  40. }
  41. }
  42. //-----------------------------------------------------------------------------
  43. CReplayRenderOverlay::CReplayRenderOverlay( Panel *pParent )
  44. : BaseClass( pParent, "ReplayRenderOverlay" ),
  45. m_pBottom( NULL ),
  46. m_pCancelButton( NULL ),
  47. m_pTitleLabel( NULL ),
  48. m_pProgressLabel( NULL ),
  49. m_pFilenameLabel( NULL ),
  50. m_pRenderProgress( NULL ),
  51. m_pRenderer( NULL ),
  52. m_pPreviewCheckButton( NULL ),
  53. m_unNumFrames( 0 ),
  54. m_flStartTime( 0.0f ),
  55. m_flPreviousTimeLeft( 0.0f )
  56. {
  57. if ( pParent == NULL )
  58. {
  59. vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme");
  60. SetScheme(scheme);
  61. SetProportional( true );
  62. }
  63. ivgui()->AddTickSignal( GetVPanel(), 10 );
  64. m_pRenderer = new CReplayRenderer( this );
  65. }
  66. CReplayRenderOverlay::~CReplayRenderOverlay()
  67. {
  68. ivgui()->RemoveTickSignal( GetVPanel() );
  69. delete m_pRenderer;
  70. }
  71. void CReplayRenderOverlay::Show()
  72. {
  73. // Setup panel
  74. SetVisible( true );
  75. SetMouseInputEnabled( true );
  76. SetKeyBoardInputEnabled( true );
  77. MakePopup( true );
  78. MoveToFront();
  79. TFModalStack()->PushModal( this );
  80. // Make sure game UI is hidden
  81. engine->ClientCmd_Unrestricted( "gameui_hide" );
  82. InvalidateLayout( false, true );
  83. }
  84. void CReplayRenderOverlay::Hide()
  85. {
  86. SetVisible( false );
  87. TFModalStack()->PopModal( this );
  88. MarkForDeletion();
  89. }
  90. void CReplayRenderOverlay::ApplySchemeSettings( IScheme *pScheme )
  91. {
  92. BaseClass::ApplySchemeSettings( pScheme );
  93. // Load controls
  94. LoadControlSettings( "Resource/UI/replayrenderoverlay.res", "GAME" );
  95. // Layout bottom
  96. m_pBottom = dynamic_cast< EditablePanel * >( FindChildByName( "BottomPanel" ) );
  97. if ( !m_pBottom )
  98. return;
  99. // Find some controls
  100. m_pTitleLabel = dynamic_cast< CExLabel * >( FindChildByName( "TitleLabel" ) );
  101. m_pProgressLabel = dynamic_cast< CExLabel * >( FindChildByName( "ProgressLabel" ) );
  102. m_pRenderProgress = dynamic_cast< ProgressBar * >( FindChildByName( "RenderProgress" ) );
  103. m_pCancelButton = dynamic_cast< CExButton * >( FindChildByName( "CancelButton" ) );
  104. m_pFilenameLabel = dynamic_cast< CExLabel * >( FindChildByName( "FilenameLabel" ) );
  105. m_pPreviewCheckButton = dynamic_cast< CheckButton * >( FindChildByName( "PreviewCheckButton" ) );
  106. m_pPreviewCheckButton->SetProportional( false );
  107. m_pPreviewCheckButton->SetSelected( replay_enablerenderpreview.GetBool() );
  108. m_pPreviewCheckButton->AddActionSignalTarget( this );
  109. const char *pMovieFilename = m_pRenderer->GetMovieFilename();
  110. if ( m_pFilenameLabel && pMovieFilename )
  111. {
  112. const char *pFilename = V_UnqualifiedFileName( pMovieFilename );
  113. m_pFilenameLabel->SetText( pFilename );
  114. }
  115. }
  116. void CReplayRenderOverlay::PerformLayout()
  117. {
  118. BaseClass::PerformLayout();
  119. if ( !m_pBottom )
  120. return;
  121. int sw, sh;
  122. vgui::surface()->GetScreenSize( sw, sh );
  123. SetBounds( 0, 0, sw, sh );
  124. int nBottomPanelHeight = sh * .13f;
  125. int nBottomPanelStartY = sh - nBottomPanelHeight;
  126. m_pBottom->SetBounds( 0, nBottomPanelStartY, sw, nBottomPanelHeight );
  127. int nBottomW = sw;
  128. int nBottomH = nBottomPanelHeight;
  129. // Setup progress bar
  130. if ( !m_pRenderProgress )
  131. return;
  132. int nProgHeight = YRES(20);
  133. int nMargin = nBottomW/5;
  134. int nProgX = nMargin;
  135. int nProgY = nBottomPanelStartY + ( nBottomH - nProgHeight ) / 2;
  136. int nProgW = nBottomW - 2*nMargin;
  137. // Only show progress bar if replay is valid and length of render is non-zero, and the record start tick exists
  138. CReplay *pReplay = g_pReplayManager->GetPlayingReplay();
  139. if ( pReplay )
  140. {
  141. const float flTotalTime = pReplay->m_flLength;
  142. const int nServerRecordStartTick = g_pClientReplayContext->GetRecordingSessionManager()->GetServerStartTickForSession( pReplay->m_hSession ); // NOTE: Returns -1 on fail
  143. if ( flTotalTime > 0.0f && nServerRecordStartTick >= 0 )
  144. {
  145. m_pRenderProgress->SetVisible( true );
  146. m_pRenderProgress->SetBounds( nProgX, nProgY, nProgW, nProgHeight );
  147. m_pRenderProgress->SetSegmentInfo( XRES(1), XRES(8) );
  148. }
  149. }
  150. // Layout title label
  151. const int nTitleLabelY = nBottomPanelStartY + ( m_pBottom->GetTall() - m_pTitleLabel->GetTall() ) / 2;
  152. if ( m_pTitleLabel )
  153. {
  154. m_pTitleLabel->SizeToContents();
  155. m_pTitleLabel->SetPos( ( nProgX - m_pTitleLabel->GetWide() ) / 2, nTitleLabelY );
  156. }
  157. // Layout preview check button
  158. if ( m_pPreviewCheckButton )
  159. {
  160. m_pPreviewCheckButton->SizeToContents();
  161. m_pPreviewCheckButton->SetPos( ( nProgX - m_pPreviewCheckButton->GetWide() ) / 2, nTitleLabelY + m_pTitleLabel->GetTall() + YRES(3) );
  162. }
  163. // Layout filename label
  164. if ( m_pFilenameLabel )
  165. {
  166. int nProgBottomY = nProgY + nProgHeight;
  167. m_pFilenameLabel->SizeToContents();
  168. m_pFilenameLabel->SetPos( nProgX, nProgBottomY + ( sh - nProgBottomY - m_pFilenameLabel->GetTall() ) / 2 );
  169. }
  170. // Layout progress label
  171. if ( m_pProgressLabel )
  172. {
  173. int nProgBottomY = nProgY + nProgHeight;
  174. m_pProgressLabel->SizeToContents();
  175. m_pProgressLabel->SetPos( nProgX, nProgBottomY + ( sh - nProgBottomY - m_pProgressLabel->GetTall() ) / 2 );
  176. m_pProgressLabel->SetWide( nProgW );
  177. }
  178. // Layout cancel button
  179. if ( !m_pCancelButton )
  180. return;
  181. // Put cancel button half way in between progress bar and screen right
  182. int nProgRightX = nProgX + nProgW;
  183. m_pCancelButton->SetPos(
  184. nProgRightX + ( m_pBottom->GetWide() - nProgRightX - m_pCancelButton->GetWide() ) / 2,
  185. nBottomPanelStartY + ( m_pBottom->GetTall() - m_pCancelButton->GetTall() ) / 2
  186. );
  187. SetXToRed( m_pCancelButton );
  188. m_pCancelButton->RequestFocus();
  189. }
  190. void CReplayRenderOverlay::OnTick()
  191. {
  192. #if _DEBUG
  193. if ( m_bReloadScheme )
  194. {
  195. InvalidateLayout( true, true );
  196. m_bReloadScheme = false;
  197. }
  198. #endif
  199. // Update progress
  200. if ( m_pRenderProgress )
  201. {
  202. CReplay *pReplay = g_pReplayManager->GetPlayingReplay();
  203. if ( pReplay && m_pRenderProgress->IsVisible() )
  204. {
  205. float flCurTime, flTotalTime;
  206. g_pClientReplayContext->GetPlaybackTimes( flCurTime, flTotalTime, pReplay, m_pRenderer->GetPerformance() );
  207. const float flProgress = ( flTotalTime == 0.0f ) ? 1.0f : ( flCurTime / flTotalTime );
  208. Assert( flTotalTime > 0.0f ); // NOTE: Progress bar will always be invisible if total time is 0, but check anyway to be safe.
  209. m_pRenderProgress->SetProgress( MAX( m_pRenderProgress->GetProgress(), flProgress ) ); // The MAX() here keeps the progress bar from thrashing
  210. if ( m_pProgressLabel )
  211. {
  212. // @note Tom Bui: this is a horribly ugly hack, but the first couple of frames take a really freaking long time, so that
  213. // really blows out the estimate
  214. float flTimePassed = 0.0f;
  215. ++m_unNumFrames;
  216. const uint32 kNumFramesToWait = 10;
  217. if ( m_unNumFrames < kNumFramesToWait )
  218. {
  219. m_flStartTime = gpGlobals->realtime;
  220. }
  221. else if ( m_unNumFrames > kNumFramesToWait )
  222. {
  223. flTimePassed = gpGlobals->realtime - m_flStartTime;
  224. float flEstimatedTimeLeft = flProgress > 0.0f ? ( flTimePassed / flProgress ) - flTimePassed : 0.0f;
  225. // exponential moving average FIR filter
  226. // S(t) = smoothing_factor * Y(t) + (1 - smoothing_factor)* Y(t-1)
  227. // previous value is essentially 90% of the current value
  228. const float kSmoothingFactor = 0.1f;
  229. if ( m_flPreviousTimeLeft == 0.0f )
  230. {
  231. m_flPreviousTimeLeft = flEstimatedTimeLeft;
  232. }
  233. else
  234. {
  235. m_flPreviousTimeLeft = kSmoothingFactor * flEstimatedTimeLeft + ( 1 - kSmoothingFactor ) * m_flPreviousTimeLeft;
  236. }
  237. }
  238. wchar_t wszTimeLeft[256];
  239. wchar_t wszTime[256];
  240. {
  241. const char *pRenderTime = CReplayTime::FormatTimeString( RoundFloatToInt( m_flPreviousTimeLeft ) );
  242. g_pVGuiLocalize->ConvertANSIToUnicode( pRenderTime, wszTimeLeft, sizeof( wszTimeLeft ) );
  243. }
  244. {
  245. const char *pRenderTime = CReplayTime::FormatTimeString( RoundFloatToInt( flTimePassed ) );
  246. g_pVGuiLocalize->ConvertANSIToUnicode( pRenderTime, wszTime, sizeof( wszTime ) );
  247. }
  248. wchar_t wszText[256];
  249. g_pVGuiLocalize->ConstructString_safe( wszText, g_pVGuiLocalize->Find( "#Replay_RenderOverlay_TimeLeft" ), 2, wszTime, wszTimeLeft );
  250. m_pProgressLabel->SetText( wszText );
  251. }
  252. }
  253. }
  254. }
  255. void CReplayRenderOverlay::OnMousePressed( MouseCode nCode )
  256. {
  257. #if _DEBUG
  258. m_bReloadScheme = true;
  259. #endif
  260. BaseClass::OnMousePressed( nCode );
  261. }
  262. void CReplayRenderOverlay::OnKeyCodeTyped( vgui::KeyCode nCode )
  263. {
  264. if ( nCode == KEY_ESCAPE )
  265. {
  266. if ( TFModalStack()->Top() == GetVPanel() )
  267. {
  268. OnCommand( "confirmcancel" );
  269. return;
  270. }
  271. }
  272. BaseClass::OnKeyCodeTyped( nCode );
  273. }
  274. void CReplayRenderOverlay::OnCommand( const char *pCommand )
  275. {
  276. if ( !V_stricmp( pCommand, "confirmcancel" ) )
  277. {
  278. ShowConfirmDialog( "#Replay_CancelRenderTitle", "#Replay_ConfirmCancelRender", "#Replay_YesCancel", "#Replay_No", OnRenderCancelDialogButtonPressed, this, NULL, "replay\\replaydialog_warn.wav" );
  279. return;
  280. }
  281. BaseClass::OnCommand( pCommand );
  282. }
  283. void CReplayRenderOverlay::OnCheckButtonChecked( Panel *pPanel )
  284. {
  285. replay_enablerenderpreview.SetValue( (int)m_pPreviewCheckButton->IsSelected() );
  286. }
  287. //-----------------------------------------------------------------------------
  288. static CReplayRenderOverlay *s_pRenderOverlay = NULL;
  289. void ReplayUI_OpenReplayRenderOverlay()
  290. {
  291. if ( !g_pReplayMovieManager->IsRendering() )
  292. return;
  293. // Delete any existing panel
  294. if ( s_pRenderOverlay )
  295. {
  296. s_pRenderOverlay->MarkForDeletion();
  297. }
  298. // Create the panel - get the render resolution from the settings
  299. s_pRenderOverlay = SETUP_PANEL( new CReplayRenderOverlay( NULL ) ); // Parenting to NULL allows us to turn off world rendering in engine/view.cpp (V_RenderView())
  300. // Set the panel as the movie renderer, so it can receive begin/end render calls from the engine
  301. g_pClientReplayContext->SetMovieRenderer( s_pRenderOverlay->m_pRenderer );
  302. }
  303. void ReplayUI_HideRenderOverlay()
  304. {
  305. if ( s_pRenderOverlay )
  306. {
  307. s_pRenderOverlay->MarkForDeletion();
  308. s_pRenderOverlay = NULL;
  309. }
  310. g_pClientReplayContext->SetMovieRenderer( NULL );
  311. }
  312. //-----------------------------------------------------------------------------
  313. #endif