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
380 lines
11 KiB
//========= 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 <tier0/memdbgon.h> |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
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_safe( 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
|
|
|