//========= Copyright Valve Corporation, All rights reserved. ============// // //=======================================================================================// #include "cbase.h" #if defined( REPLAY_ENABLED ) #include "replayrenderoverlay.h" #include "vgui_controls/TextImage.h" #include "replay/genericclassbased_replay.h" #include "iclientmode.h" #include "VGuiMatSurface/IMatSystemSurface.h" #include "ienginevgui.h" #include "vgui/IVGui.h" #include "econ/confirm_dialog.h" #include "replay/ireplaymanager.h" #include "replay/irecordingsessionmanager.h" #include "replay/ireplaymoviemanager.h" #include "replay/replayrenderer.h" #include "econ/econ_controls.h" // memdbgon must be the last include file in a .cpp file!!! #include //----------------------------------------------------------------------------- extern IReplayMovieManager *g_pReplayMovieManager; //----------------------------------------------------------------------------- using namespace vgui; //----------------------------------------------------------------------------- #define TMP_ENCODED_AUDIO ".tmp.aac" #ifdef USE_WEBM_FOR_REPLAY #define TMP_ENCODED_VIDEO ".tmp.webm" #else #define TMP_ENCODED_VIDEO ".tmp.mov" #endif //----------------------------------------------------------------------------- ConVar replay_enablerenderpreview( "replay_enablerenderpreview", "1", FCVAR_CLIENTDLL | FCVAR_DONTRECORD | FCVAR_ARCHIVE, "Enable preview during replay render." ); //----------------------------------------------------------------------------- void OnRenderCancelDialogButtonPressed( bool bConfirm, void *pContext ) { if ( bConfirm ) { g_pReplayMovieManager->CancelRender(); } } //----------------------------------------------------------------------------- CReplayRenderOverlay::CReplayRenderOverlay( Panel *pParent ) : BaseClass( pParent, "ReplayRenderOverlay" ), m_pBottom( NULL ), m_pCancelButton( NULL ), m_pTitleLabel( NULL ), m_pProgressLabel( NULL ), m_pFilenameLabel( NULL ), m_pRenderProgress( NULL ), m_pRenderer( NULL ), m_pPreviewCheckButton( NULL ), m_unNumFrames( 0 ), m_flStartTime( 0.0f ), m_flPreviousTimeLeft( 0.0f ) { if ( pParent == NULL ) { vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); SetScheme(scheme); SetProportional( true ); } ivgui()->AddTickSignal( GetVPanel(), 10 ); m_pRenderer = new CReplayRenderer( this ); } CReplayRenderOverlay::~CReplayRenderOverlay() { ivgui()->RemoveTickSignal( GetVPanel() ); delete m_pRenderer; } void CReplayRenderOverlay::Show() { // Setup panel SetVisible( true ); SetMouseInputEnabled( true ); SetKeyBoardInputEnabled( true ); MakePopup( true ); MoveToFront(); TFModalStack()->PushModal( this ); // Make sure game UI is hidden engine->ClientCmd_Unrestricted( "gameui_hide" ); InvalidateLayout( false, true ); } void CReplayRenderOverlay::Hide() { SetVisible( false ); TFModalStack()->PopModal( this ); MarkForDeletion(); } void CReplayRenderOverlay::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); // Load controls LoadControlSettings( "Resource/UI/replayrenderoverlay.res", "GAME" ); // Layout bottom m_pBottom = dynamic_cast< EditablePanel * >( FindChildByName( "BottomPanel" ) ); if ( !m_pBottom ) return; // Find some controls m_pTitleLabel = dynamic_cast< CExLabel * >( FindChildByName( "TitleLabel" ) ); m_pProgressLabel = dynamic_cast< CExLabel * >( FindChildByName( "ProgressLabel" ) ); m_pRenderProgress = dynamic_cast< ProgressBar * >( FindChildByName( "RenderProgress" ) ); m_pCancelButton = dynamic_cast< CExButton * >( FindChildByName( "CancelButton" ) ); m_pFilenameLabel = dynamic_cast< CExLabel * >( FindChildByName( "FilenameLabel" ) ); m_pPreviewCheckButton = dynamic_cast< CheckButton * >( FindChildByName( "PreviewCheckButton" ) ); m_pPreviewCheckButton->SetProportional( false ); m_pPreviewCheckButton->SetSelected( replay_enablerenderpreview.GetBool() ); m_pPreviewCheckButton->AddActionSignalTarget( this ); const char *pMovieFilename = m_pRenderer->GetMovieFilename(); if ( m_pFilenameLabel && pMovieFilename ) { const char *pFilename = V_UnqualifiedFileName( pMovieFilename ); m_pFilenameLabel->SetText( pFilename ); } } void CReplayRenderOverlay::PerformLayout() { BaseClass::PerformLayout(); if ( !m_pBottom ) return; int sw, sh; vgui::surface()->GetScreenSize( sw, sh ); SetBounds( 0, 0, sw, sh ); int nBottomPanelHeight = sh * .13f; int nBottomPanelStartY = sh - nBottomPanelHeight; m_pBottom->SetBounds( 0, nBottomPanelStartY, sw, nBottomPanelHeight ); int nBottomW = sw; int nBottomH = nBottomPanelHeight; // Setup progress bar if ( !m_pRenderProgress ) return; int nProgHeight = YRES(20); int nMargin = nBottomW/5; int nProgX = nMargin; int nProgY = nBottomPanelStartY + ( nBottomH - nProgHeight ) / 2; int nProgW = nBottomW - 2*nMargin; // Only show progress bar if replay is valid and length of render is non-zero, and the record start tick exists CReplay *pReplay = g_pReplayManager->GetPlayingReplay(); if ( pReplay ) { const float flTotalTime = pReplay->m_flLength; const int nServerRecordStartTick = g_pClientReplayContext->GetRecordingSessionManager()->GetServerStartTickForSession( pReplay->m_hSession ); // NOTE: Returns -1 on fail if ( flTotalTime > 0.0f && nServerRecordStartTick >= 0 ) { m_pRenderProgress->SetVisible( true ); m_pRenderProgress->SetBounds( nProgX, nProgY, nProgW, nProgHeight ); m_pRenderProgress->SetSegmentInfo( XRES(1), XRES(8) ); } } // Layout title label const int nTitleLabelY = nBottomPanelStartY + ( m_pBottom->GetTall() - m_pTitleLabel->GetTall() ) / 2; if ( m_pTitleLabel ) { m_pTitleLabel->SizeToContents(); m_pTitleLabel->SetPos( ( nProgX - m_pTitleLabel->GetWide() ) / 2, nTitleLabelY ); } // Layout preview check button if ( m_pPreviewCheckButton ) { m_pPreviewCheckButton->SizeToContents(); m_pPreviewCheckButton->SetPos( ( nProgX - m_pPreviewCheckButton->GetWide() ) / 2, nTitleLabelY + m_pTitleLabel->GetTall() + YRES(3) ); } // Layout filename label if ( m_pFilenameLabel ) { int nProgBottomY = nProgY + nProgHeight; m_pFilenameLabel->SizeToContents(); m_pFilenameLabel->SetPos( nProgX, nProgBottomY + ( sh - nProgBottomY - m_pFilenameLabel->GetTall() ) / 2 ); } // Layout progress label if ( m_pProgressLabel ) { int nProgBottomY = nProgY + nProgHeight; m_pProgressLabel->SizeToContents(); m_pProgressLabel->SetPos( nProgX, nProgBottomY + ( sh - nProgBottomY - m_pProgressLabel->GetTall() ) / 2 ); m_pProgressLabel->SetWide( nProgW ); } // Layout cancel button if ( !m_pCancelButton ) return; // Put cancel button half way in between progress bar and screen right int nProgRightX = nProgX + nProgW; m_pCancelButton->SetPos( nProgRightX + ( m_pBottom->GetWide() - nProgRightX - m_pCancelButton->GetWide() ) / 2, nBottomPanelStartY + ( m_pBottom->GetTall() - m_pCancelButton->GetTall() ) / 2 ); SetXToRed( m_pCancelButton ); m_pCancelButton->RequestFocus(); } void CReplayRenderOverlay::OnTick() { #if _DEBUG if ( m_bReloadScheme ) { InvalidateLayout( true, true ); m_bReloadScheme = false; } #endif // Update progress if ( m_pRenderProgress ) { CReplay *pReplay = g_pReplayManager->GetPlayingReplay(); if ( pReplay && m_pRenderProgress->IsVisible() ) { float flCurTime, flTotalTime; g_pClientReplayContext->GetPlaybackTimes( flCurTime, flTotalTime, pReplay, m_pRenderer->GetPerformance() ); const float flProgress = ( flTotalTime == 0.0f ) ? 1.0f : ( flCurTime / flTotalTime ); Assert( flTotalTime > 0.0f ); // NOTE: Progress bar will always be invisible if total time is 0, but check anyway to be safe. m_pRenderProgress->SetProgress( MAX( m_pRenderProgress->GetProgress(), flProgress ) ); // The MAX() here keeps the progress bar from thrashing if ( m_pProgressLabel ) { // @note Tom Bui: this is a horribly ugly hack, but the first couple of frames take a really freaking long time, so that // really blows out the estimate float flTimePassed = 0.0f; ++m_unNumFrames; const uint32 kNumFramesToWait = 10; if ( m_unNumFrames < kNumFramesToWait ) { m_flStartTime = gpGlobals->realtime; } else if ( m_unNumFrames > kNumFramesToWait ) { flTimePassed = gpGlobals->realtime - m_flStartTime; float flEstimatedTimeLeft = flProgress > 0.0f ? ( flTimePassed / flProgress ) - flTimePassed : 0.0f; // exponential moving average FIR filter // S(t) = smoothing_factor * Y(t) + (1 - smoothing_factor)* Y(t-1) // previous value is essentially 90% of the current value const float kSmoothingFactor = 0.1f; if ( m_flPreviousTimeLeft == 0.0f ) { m_flPreviousTimeLeft = flEstimatedTimeLeft; } else { m_flPreviousTimeLeft = kSmoothingFactor * flEstimatedTimeLeft + ( 1 - kSmoothingFactor ) * m_flPreviousTimeLeft; } } wchar_t wszTimeLeft[256]; wchar_t wszTime[256]; { const char *pRenderTime = CReplayTime::FormatTimeString( RoundFloatToInt( m_flPreviousTimeLeft ) ); g_pVGuiLocalize->ConvertANSIToUnicode( pRenderTime, wszTimeLeft, sizeof( wszTimeLeft ) ); } { const char *pRenderTime = CReplayTime::FormatTimeString( RoundFloatToInt( flTimePassed ) ); g_pVGuiLocalize->ConvertANSIToUnicode( pRenderTime, wszTime, sizeof( wszTime ) ); } wchar_t wszText[256]; g_pVGuiLocalize->ConstructString( wszText,sizeof( wszText ), g_pVGuiLocalize->Find( "#Replay_RenderOverlay_TimeLeft" ), 2, wszTime, wszTimeLeft ); m_pProgressLabel->SetText( wszText ); } } } } void CReplayRenderOverlay::OnMousePressed( MouseCode nCode ) { #if _DEBUG m_bReloadScheme = true; #endif BaseClass::OnMousePressed( nCode ); } void CReplayRenderOverlay::OnKeyCodeTyped( vgui::KeyCode nCode ) { if ( nCode == KEY_ESCAPE ) { if ( TFModalStack()->Top() == GetVPanel() ) { OnCommand( "confirmcancel" ); return; } } BaseClass::OnKeyCodeTyped( nCode ); } void CReplayRenderOverlay::OnCommand( const char *pCommand ) { if ( !V_stricmp( pCommand, "confirmcancel" ) ) { ShowConfirmDialog( "#Replay_CancelRenderTitle", "#Replay_ConfirmCancelRender", "#Replay_YesCancel", "#Replay_No", OnRenderCancelDialogButtonPressed, this, NULL, "replay\\replaydialog_warn.wav" ); return; } BaseClass::OnCommand( pCommand ); } void CReplayRenderOverlay::OnCheckButtonChecked( Panel *pPanel ) { replay_enablerenderpreview.SetValue( (int)m_pPreviewCheckButton->IsSelected() ); } //----------------------------------------------------------------------------- static CReplayRenderOverlay *s_pRenderOverlay = NULL; void ReplayUI_OpenReplayRenderOverlay() { if ( !g_pReplayMovieManager->IsRendering() ) return; // Delete any existing panel if ( s_pRenderOverlay ) { s_pRenderOverlay->MarkForDeletion(); } // Create the panel - get the render resolution from the settings s_pRenderOverlay = SETUP_PANEL( new CReplayRenderOverlay( NULL ) ); // Parenting to NULL allows us to turn off world rendering in engine/view.cpp (V_RenderView()) // Set the panel as the movie renderer, so it can receive begin/end render calls from the engine g_pClientReplayContext->SetMovieRenderer( s_pRenderOverlay->m_pRenderer ); } void ReplayUI_HideRenderOverlay() { if ( s_pRenderOverlay ) { s_pRenderOverlay->MarkForDeletion(); s_pRenderOverlay = NULL; } g_pClientReplayContext->SetMovieRenderer( NULL ); } //----------------------------------------------------------------------------- #endif