From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/client/vgui_netgraphpanel.cpp | 1513 +++++++++++++++++++++++++++++ 1 file changed, 1513 insertions(+) create mode 100644 mp/src/game/client/vgui_netgraphpanel.cpp (limited to 'mp/src/game/client/vgui_netgraphpanel.cpp') diff --git a/mp/src/game/client/vgui_netgraphpanel.cpp b/mp/src/game/client/vgui_netgraphpanel.cpp new file mode 100644 index 00000000..3fbd1113 --- /dev/null +++ b/mp/src/game/client/vgui_netgraphpanel.cpp @@ -0,0 +1,1513 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "hud.h" +#include "inetgraphpanel.h" +#include "kbutton.h" +#include +#include "input.h" +#include +#include "VGuiMatSurface/IMatSystemSurface.h" +#include +#include +#include +#include +#include +#include "tier0/vprof.h" +#include "cdll_bounded_cvars.h" + +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imesh.h" +#include "materialsystem/imaterial.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +static ConVar net_scale ( "net_scale", "5", FCVAR_ARCHIVE ); +static ConVar net_graphpos ( "net_graphpos", "1", FCVAR_ARCHIVE ); +static ConVar net_graphsolid ( "net_graphsolid", "1", FCVAR_ARCHIVE ); +static ConVar net_graphtext ( "net_graphtext", "1", FCVAR_ARCHIVE, "Draw text fields" ); +static ConVar net_graphmsecs ( "net_graphmsecs", "400", FCVAR_ARCHIVE, "The latency graph represents this many milliseconds." ); +static ConVar net_graphshowlatency( "net_graphshowlatency", "1", FCVAR_ARCHIVE, "Draw the ping/packet loss graph." ); +static ConVar net_graphshowinterp ( "net_graphshowinterp", "1", FCVAR_ARCHIVE, "Draw the interpolation graph." ); + +void NetgraphFontChangeCallback( IConVar *var, const char *pOldValue, float flOldValue ); + +static ConVar net_graph ( "net_graph","0", FCVAR_ARCHIVE, "Draw the network usage graph, = 2 draws data on payload, = 3 draws payload legend.", NetgraphFontChangeCallback ); +static ConVar net_graphheight ( "net_graphheight", "64", FCVAR_ARCHIVE, "Height of netgraph panel", NetgraphFontChangeCallback ); +static ConVar net_graphproportionalfont( "net_graphproportionalfont", "1", FCVAR_ARCHIVE, "Determines whether netgraph font is proportional or not", NetgraphFontChangeCallback ); + + +#define TIMINGS 1024 // Number of values to track (must be power of 2) b/c of masking +#define FRAMERATE_AVG_FRAC 0.9 +#define PACKETLOSS_AVG_FRAC 0.5 +#define PACKETCHOKE_AVG_FRAC 0.5 + +#define NUM_LATENCY_SAMPLES 8 + +#define GRAPH_RED (0.9 * 255) +#define GRAPH_GREEN (0.9 * 255) +#define GRAPH_BLUE (0.7 * 255) + +#define LERP_HEIGHT 24 + +#define COLOR_DROPPED 0 +#define COLOR_INVALID 1 +#define COLOR_SKIPPED 2 +#define COLOR_CHOKED 3 +#define COLOR_NORMAL 4 + +//----------------------------------------------------------------------------- +// Purpose: Displays the NetGraph +//----------------------------------------------------------------------------- +class CNetGraphPanel : public Panel +{ + typedef Panel BaseClass; +private: + typedef struct + { + int latency; + int choked; + } packet_latency_t; + + typedef struct + { + unsigned short msgbytes[INetChannelInfo::TOTAL+1]; + int sampleY; + int sampleHeight; + + } netbandwidthgraph_t; + + typedef struct + { + float cmd_lerp; + int size; + bool sent; + } cmdinfo_t; + + typedef struct + { + byte color[3]; + byte alpha; + } netcolor_t; + + byte colors[ LERP_HEIGHT ][3]; + + byte sendcolor[ 3 ]; + byte holdcolor[ 3 ]; + byte extrap_base_color[ 3 ]; + + packet_latency_t m_PacketLatency[ TIMINGS ]; + cmdinfo_t m_Cmdinfo[ TIMINGS ]; + netbandwidthgraph_t m_Graph[ TIMINGS ]; + + float m_Framerate; + float m_AvgLatency; + float m_AvgPacketLoss; + float m_AvgPacketChoke; + int m_IncomingSequence; + int m_OutgoingSequence; + int m_UpdateWindowSize; + float m_IncomingData; + float m_OutgoingData; + float m_AvgPacketIn; + float m_AvgPacketOut; + + int m_StreamRecv[MAX_FLOWS]; + int m_StreamTotal[MAX_FLOWS]; + + netcolor_t netcolors[5]; + + HFont m_hFontProportional; + HFont m_hFont; + + HFont m_hFontSmall; + const ConVar *cl_updaterate; + const ConVar *cl_cmdrate; + +public: + CNetGraphPanel( VPANEL parent ); + virtual ~CNetGraphPanel( void ); + + virtual void ApplySchemeSettings(IScheme *pScheme); + virtual void Paint(); + virtual void OnTick( void ); + + virtual bool ShouldDraw( void ); + + void InitColors( void ); + int GraphValue( void ); + + struct CLineSegment + { + int x1, y1, x2, y2; + byte color[4]; + byte color2[4]; + }; + + CUtlVector< CLineSegment > m_Rects; + + inline void DrawLine( vrect_t *rect, unsigned char *color, unsigned char alpha ); + inline void DrawLine2( vrect_t *rect, unsigned char *color, unsigned char *color2, unsigned char alpha, unsigned char alpha2 ); + + void ResetLineSegments(); + void DrawLineSegments(); + + int DrawDataSegment( vrect_t *rcFill, int bytes, byte r, byte g, byte b, byte alpha = 255); + void DrawUpdateRate( int xright, int y ); + void DrawCmdRate( int xright, int y ); + void DrawHatches( int x, int y, int maxmsgbytes ); + void DrawStreamProgress( int x, int y, int width ); + void DrawTimes( vrect_t vrect, cmdinfo_t *cmdinfo, int x, int w, int graphtype ); + void DrawTextFields( int graphvalue, int x, int y, int w, netbandwidthgraph_t *graph, cmdinfo_t *cmdinfo ); + void GraphGetXY( vrect_t *rect, int width, int *x, int *y ); + void GetCommandInfo( INetChannelInfo *netchannel, cmdinfo_t *cmdinfo ); + void GetFrameData( INetChannelInfo *netchannel, int *biggest_message, float *avg_message, float *f95thpercentile ); + void ColorForHeight( packet_latency_t *packet, byte *color, int *ping, byte *alpha ); + void GetColorValues( int color, byte *cv, byte *alpha ); + + void OnFontChanged(); + +private: + + void PaintLineArt( int x, int y, int w, int graphtype, int maxmsgbytes ); + void DrawLargePacketSizes( int x, int w, int graphtype, float warning_threshold ); + + HFont GetNetgraphFont() + { + return net_graphproportionalfont.GetBool() ? m_hFontProportional : m_hFont; + } + + void ComputeNetgraphHeight(); + void UpdateEstimatedServerFramerate( INetChannelInfo *netchannel ); + + CMaterialReference m_WhiteMaterial; + + int m_EstimatedWidth; + + int m_nNetGraphHeight; + + float m_flServerFramerate; + float m_flServerFramerateStdDeviation; +}; + +CNetGraphPanel *g_pNetGraphPanel = NULL; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *parent - +//----------------------------------------------------------------------------- +CNetGraphPanel::CNetGraphPanel( VPANEL parent ) +: BaseClass( NULL, "CNetGraphPanel" ) +{ + int w, h; + surface()->GetScreenSize( w, h ); + + SetParent( parent ); + SetSize( w, h ); + SetPos( 0, 0 ); + SetVisible( false ); + SetCursor( null ); + + m_hFont = 0; + m_hFontProportional = 0; + m_hFontSmall = 0; + m_EstimatedWidth = 1; + m_nNetGraphHeight = 100; + + SetFgColor( Color( 0, 0, 0, 255 ) ); + SetPaintBackgroundEnabled( false ); + + InitColors(); + + cl_updaterate = cvar->FindVar( "cl_updaterate" ); + cl_cmdrate = cvar->FindVar( "cl_cmdrate" ); + assert( cl_updaterate && cl_cmdrate ); + + memset( sendcolor, 0, 3 ); + memset( holdcolor, 0, 3 ); + sendcolor[ 0 ] = sendcolor[ 1 ] = 255; + + memset( extrap_base_color, 255, 3 ); + + memset( m_PacketLatency, 0, TIMINGS * sizeof( packet_latency_t ) ); + memset( m_Cmdinfo, 0, TIMINGS * sizeof( cmdinfo_t ) ); + memset( m_Graph, 0, TIMINGS * sizeof( netbandwidthgraph_t ) ); + + m_Framerate = 0.0f; + m_AvgLatency = 0.0f; + m_AvgPacketLoss = 0.0f; + m_AvgPacketChoke = 0.0f; + m_IncomingSequence = 0; + m_OutgoingSequence = 0; + m_UpdateWindowSize = 0; + m_IncomingData = 0; + m_OutgoingData = 0; + m_AvgPacketIn = 0.0f; + m_AvgPacketOut = 0.0f; + m_flServerFramerate = 0; + m_flServerFramerateStdDeviation = 0; + + netcolors[COLOR_DROPPED].color[0] = 255; + netcolors[COLOR_DROPPED].color[1] = 0; + netcolors[COLOR_DROPPED].color[2] = 0; + netcolors[COLOR_DROPPED].alpha = 255; + netcolors[COLOR_INVALID].color[0] = 0; + netcolors[COLOR_INVALID].color[1] = 0; + netcolors[COLOR_INVALID].color[2] = 255; + netcolors[COLOR_INVALID].alpha = 255; + netcolors[COLOR_SKIPPED].color[0] = 240; + netcolors[COLOR_SKIPPED].color[1] = 127; + netcolors[COLOR_SKIPPED].color[2] = 63; + netcolors[COLOR_SKIPPED].alpha = 255; + netcolors[COLOR_CHOKED].color[0] = 225; + netcolors[COLOR_CHOKED].color[1] = 225; + netcolors[COLOR_CHOKED].color[2] = 0; + netcolors[COLOR_CHOKED].alpha = 255; + netcolors[COLOR_NORMAL].color[0] = 63; + netcolors[COLOR_NORMAL].color[1] = 255; + netcolors[COLOR_NORMAL].color[2] = 63; + netcolors[COLOR_NORMAL].alpha = 232; + + ivgui()->AddTickSignal( GetVPanel(), 500 ); + + m_WhiteMaterial.Init( "vgui/white", TEXTURE_GROUP_OTHER ); + g_pNetGraphPanel = this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNetGraphPanel::~CNetGraphPanel( void ) +{ + g_pNetGraphPanel = NULL; +} + +void NetgraphFontChangeCallback( IConVar *var, const char *pOldValue, float flOldValue ) +{ + if ( g_pNetGraphPanel ) + { + g_pNetGraphPanel->OnFontChanged(); + } +} + +void CNetGraphPanel::OnFontChanged() +{ + // Estimate the width of our panel. + char str[512]; + wchar_t ustr[512]; + Q_snprintf( str, sizeof( str ), "fps: 435 ping: 533 ms lerp 112.3 ms 0/0" ); + g_pVGuiLocalize->ConvertANSIToUnicode( str, ustr, sizeof( ustr ) ); + int textTall; + if ( m_hFontProportional == vgui::INVALID_FONT ) + { + m_EstimatedWidth = textTall = 0; + } + else + { + g_pMatSystemSurface->GetTextSize( m_hFontProportional, ustr, m_EstimatedWidth, textTall ); + } + + int w, h; + surface()->GetScreenSize( w, h ); + SetSize( w, h ); + SetPos( 0, 0 ); + + ComputeNetgraphHeight(); +} + +void CNetGraphPanel::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + m_hFont = pScheme->GetFont( "DefaultFixedOutline", false ); + m_hFontProportional = pScheme->GetFont( "DefaultFixedOutline", true ); + m_hFontSmall = pScheme->GetFont( "DefaultVerySmall", false ); + + OnFontChanged(); +} + +void CNetGraphPanel::ComputeNetgraphHeight() +{ + m_nNetGraphHeight = net_graphheight.GetInt(); + + HFont fnt = GetNetgraphFont(); + int tall = surface()->GetFontTall( fnt ); + + int lines = 3; + if ( net_graph.GetInt() > 3 ) + { + lines = 5; + } + else if ( net_graph.GetInt() > 2 ) + { + lines = 4; + } + m_nNetGraphHeight = MAX( lines * tall, m_nNetGraphHeight ); +} + +//----------------------------------------------------------------------------- +// Purpose: Copies data from netcolor_t array into fields passed in +// Input : color - +// *cv - +// *alpha - +//----------------------------------------------------------------------------- +void CNetGraphPanel::GetColorValues( int color, byte *cv, byte *alpha ) +{ + int i; + netcolor_t *pc = &netcolors[ color ]; + for ( i = 0; i < 3; i++ ) + { + cv[ i ] = pc->color[ i ]; + } + *alpha = pc->alpha; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets appropriate color values +// Input : *packet - +// *color - +// *ping - +// *alpha - +//----------------------------------------------------------------------------- +void CNetGraphPanel::ColorForHeight( packet_latency_t *packet, byte *color, int *ping, byte *alpha ) +{ + int h = packet->latency; + *ping = 0; + switch ( h ) + { + case 9999: + GetColorValues( COLOR_DROPPED, color, alpha ); + break; + case 9998: + GetColorValues( COLOR_INVALID, color, alpha ); + break; + case 9997: + GetColorValues( COLOR_SKIPPED, color, alpha ); + break; + default: + *ping = 1; + if (packet->choked ) + { + GetColorValues( COLOR_CHOKED, color, alpha ); + } + else + { + GetColorValues( COLOR_NORMAL, color, alpha ); + } + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set up blend colors for comman/client-frame/interpolation graph +//----------------------------------------------------------------------------- +void CNetGraphPanel::InitColors( void ) +{ + int i, j; + byte mincolor[2][3]; + byte maxcolor[2][3]; + float dc[2][3]; + int hfrac; + float f; + + mincolor[0][0] = 63; + mincolor[0][1] = 0; + mincolor[0][2] = 100; + + maxcolor[0][0] = 0; + maxcolor[0][1] = 63; + maxcolor[0][2] = 255; + + mincolor[1][0] = 255; + mincolor[1][1] = 127; + mincolor[1][2] = 0; + + maxcolor[1][0] = 250; + maxcolor[1][1] = 0; + maxcolor[1][2] = 0; + + for ( i = 0; i < 3; i++ ) + { + dc[0][i] = (float)(maxcolor[0][i] - mincolor[0][i]); + dc[1][i] = (float)(maxcolor[1][i] - mincolor[1][i]); + } + + hfrac = LERP_HEIGHT / 3; + + for ( i = 0; i < LERP_HEIGHT; i++ ) + { + if ( i < hfrac ) + { + f = (float)i / (float)hfrac; + for ( j = 0; j < 3; j++ ) + { + colors[ i ][j] = mincolor[0][j] + f * dc[0][j]; + } + } + else + { + f = (float)(i-hfrac) / (float)(LERP_HEIGHT - hfrac ); + for ( j = 0; j < 3; j++ ) + { + colors[ i ][j] = mincolor[1][j] + f * dc[1][j]; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw client framerate / usercommand graph +// Input : vrect - +// *cmdinfo - +// x - +// w - +//----------------------------------------------------------------------------- + +void CNetGraphPanel::DrawTimes( vrect_t vrect, cmdinfo_t *cmdinfo, int x, int w, int graphtype ) +{ + if ( !net_graphshowinterp.GetBool() || graphtype <= 1 ) + return; + + int i; + int j; + int extrap_point; + int a, h; + vrect_t rcFill; + + ResetLineSegments(); + + extrap_point = LERP_HEIGHT / 3; + + for (a=0 ; a= extrap_point ) + { + int start = 0; + + h -= extrap_point; + rcFill.y -= extrap_point; + + if ( !net_graphsolid.GetInt() ) + { + rcFill.y -= (h - 1); + start = (h - 1); + } + + for ( j = start; j < h; j++ ) + { + DrawLine(&rcFill, colors[j + extrap_point], 255 ); + rcFill.y--; + } + } + else + { + int oldh; + oldh = h; + rcFill.y -= h; + h = extrap_point - h; + + if ( !net_graphsolid.GetInt() ) + { + h = 1; + } + + for ( j = 0; j < h; j++ ) + { + DrawLine(&rcFill, colors[j + oldh], 255 ); + rcFill.y--; + } + } + + rcFill.y = vrect.y + vrect.height - 4 - extrap_point; + + DrawLine( &rcFill, extrap_base_color, 255 ); + + rcFill.y = vrect.y + vrect.height - 3; + + if ( cmdinfo[ i ].sent ) + { + DrawLine( &rcFill, sendcolor, 255 ); + } + else + { + DrawLine( &rcFill, holdcolor, 200 ); + } + } + + DrawLineSegments(); +} + +//----------------------------------------------------------------------------- +// Purpose: Compute frame database for rendering m_NetChannel computes choked, and lost packets, too. +// Also computes latency data and sets max packet size +// Input : *packet_latency - +// *graph - +// *choke_count - +// *loss_count - +// *biggest_message - +// 1 - +//----------------------------------------------------------------------------- +void CNetGraphPanel::GetFrameData( INetChannelInfo *netchannel, int *biggest_message, float *avg_message, float *f95thpercentile ) +{ + float frame_received_time; + // float frame_latency; + + *biggest_message = 0; + *avg_message = 0.0f; + *f95thpercentile = 0.0f; + + int msg_count = 0; + + m_IncomingSequence = netchannel->GetSequenceNr( FLOW_INCOMING ); + m_OutgoingSequence = netchannel->GetSequenceNr( FLOW_OUTGOING ); + m_UpdateWindowSize = netchannel->GetBufferSize(); + m_AvgPacketLoss = netchannel->GetAvgLoss( FLOW_INCOMING ); + m_AvgPacketChoke = netchannel->GetAvgChoke( FLOW_INCOMING ); + m_AvgLatency = netchannel->GetAvgLatency( FLOW_OUTGOING ); + m_IncomingData = netchannel->GetAvgData( FLOW_INCOMING ) / 1024.0f; + m_OutgoingData = netchannel->GetAvgData( FLOW_OUTGOING ) / 1024.0f; + m_AvgPacketIn = netchannel->GetAvgPackets( FLOW_INCOMING ); + m_AvgPacketOut = netchannel->GetAvgPackets( FLOW_OUTGOING ); + + for ( int i=0; iGetStreamProgress( i, &m_StreamRecv[i], &m_StreamTotal[i] ); + + float flAdjust = 0.0f; + + if ( cl_updaterate->GetFloat() > 0.001f ) + { + flAdjust = -0.5f / cl_updaterate->GetFloat(); + + m_AvgLatency += flAdjust; + } + + // Can't be below zero + m_AvgLatency = MAX( 0.0, m_AvgLatency ); + + flAdjust *= 1000.0f; + + // Fill in frame data + for ( int seqnr =m_IncomingSequence - m_UpdateWindowSize + 1 + ; seqnr <= m_IncomingSequence + ; seqnr++) + { + + + frame_received_time = netchannel->GetPacketTime( FLOW_INCOMING, seqnr ); + + netbandwidthgraph_t *nbwg = &m_Graph[ seqnr & ( TIMINGS - 1 )]; + packet_latency_t *lat = &m_PacketLatency[ seqnr & ( TIMINGS - 1 ) ]; + + netchannel->GetPacketResponseLatency( FLOW_INCOMING, seqnr, &lat->latency, &lat->choked ); + + if ( lat->latency < 9995 ) + { + lat->latency += flAdjust; + lat->latency = MAX( lat->latency, 0 ); + } + + for ( int i=0; i<=INetChannelInfo::TOTAL; i++ ) + { + nbwg->msgbytes[i] = netchannel->GetPacketBytes( FLOW_INCOMING, seqnr, i ); + } + + // Assert ( nbwg->msgbytes[INetChannelInfo::TOTAL] > 0 ); + + if ( nbwg->msgbytes[INetChannelInfo::TOTAL] > *biggest_message ) + { + *biggest_message = nbwg->msgbytes[INetChannelInfo::TOTAL]; + } + + *avg_message += (float)( nbwg->msgbytes[INetChannelInfo::TOTAL] ); + msg_count++; + + + } + + if ( *biggest_message > 1000 ) + { + *biggest_message = 1000; + } + + if ( msg_count >= 1 ) + { + *avg_message /= msg_count; + + int deviationsquared = 0; + + // Compute std deviation + // Fill in frame data + for (int seqnr=m_IncomingSequence - m_UpdateWindowSize + 1 + ; seqnr <= m_IncomingSequence + ; seqnr++) + { + int bytes = m_Graph[ seqnr & ( TIMINGS - 1 )].msgbytes[INetChannelInfo::TOTAL] - ( *avg_message ); + + deviationsquared += ( bytes * bytes ); + } + + float var = ( float )( deviationsquared ) / (float)( msg_count - 1 ); + float stddev = sqrt( var ); + + *f95thpercentile = *avg_message + 2.0f * stddev; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Fills in command interpolation/holdback & message size data +// Input : *cmdinfo - +//----------------------------------------------------------------------------- +void CNetGraphPanel::GetCommandInfo( INetChannelInfo *netchannel, cmdinfo_t *cmdinfo ) +{ + for ( int seqnr = m_OutgoingSequence - m_UpdateWindowSize + 1 + ; seqnr <= m_OutgoingSequence + ; seqnr++) + { + // Also set up the lerp point. + cmdinfo_t *ci = &cmdinfo[ seqnr & ( TIMINGS - 1 ) ]; + + ci->cmd_lerp = netchannel->GetCommandInterpolationAmount( FLOW_OUTGOING, seqnr ); + ci->sent = netchannel->IsValidPacket( FLOW_OUTGOING, seqnr ); + ci->size = netchannel->GetPacketBytes( FLOW_OUTGOING, seqnr, INetChannelInfo::TOTAL); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draws overlay text fields showing framerate, latency, bandwidth breakdowns, +// and, optionally, packet loss and choked packet percentages +// Input : graphvalue - +// x - +// y - +// *graph - +// *cmdinfo - +// count - +// avg - +// *framerate - +// 0.0 - +// avg - +//----------------------------------------------------------------------------- +void CNetGraphPanel::DrawTextFields( int graphvalue, int x, int y, int w, netbandwidthgraph_t *graph, cmdinfo_t *cmdinfo ) +{ + if ( !net_graphtext.GetBool() ) + return; + + static int lastout; + + char sz[ 256 ]; + int out; + + HFont font = GetNetgraphFont(); + + // Move rolling average + m_Framerate = FRAMERATE_AVG_FRAC * m_Framerate + ( 1.0 - FRAMERATE_AVG_FRAC ) * gpGlobals->absoluteframetime; + + // Print it out + y -= m_nNetGraphHeight; + + int saveY = y; + + if ( m_Framerate <= 0.0f ) + m_Framerate = 1.0f; + + if ( engine->IsPlayingDemo() ) + m_AvgLatency = 0.0f; + + int textTall = surface()->GetFontTall( font ); + + Q_snprintf( sz, sizeof( sz ), "fps:%4i ping: %i ms", (int)(1.0f / m_Framerate), (int)(m_AvgLatency*1000.0f) ); + + g_pMatSystemSurface->DrawColoredText( font, x, y, GRAPH_RED, GRAPH_GREEN, GRAPH_BLUE, 255, sz ); + + // Draw update rate + DrawUpdateRate( x + w, y ); + + y += textTall; + + out = cmdinfo[ ( ( m_OutgoingSequence - 1 ) & ( TIMINGS - 1 ) ) ].size; + if ( !out ) + { + out = lastout; + } + else + { + lastout = out; + } + + int totalsize = graph[ ( m_IncomingSequence & ( TIMINGS - 1 ) ) ].msgbytes[INetChannelInfo::TOTAL]; + + Q_snprintf( sz, sizeof( sz ), "in :%4i %2.2f k/s ", totalsize, m_IncomingData ); + + int textWidth = g_pMatSystemSurface->DrawTextLen( font, "%s", sz ); + + g_pMatSystemSurface->DrawColoredText( font, x, y, GRAPH_RED, GRAPH_GREEN, GRAPH_BLUE, 255, sz ); + + Q_snprintf( sz, sizeof( sz ), "lerp: %5.1f ms", GetClientInterpAmount() * 1000.0f ); + + int interpcolor[ 3 ] = { GRAPH_RED, GRAPH_GREEN, GRAPH_BLUE }; + float flInterp = GetClientInterpAmount(); + if ( flInterp > 0.001f ) + { + // Server framerate is lower than interp can possibly deal with + if ( m_flServerFramerate < ( 1.0f / flInterp ) ) + { + interpcolor[ 0 ] = 255; + interpcolor[ 1 ] = 255; + interpcolor[ 2 ] = 31; + } + // flInterp is below recommended setting!!! + else if ( flInterp < ( 2.0f / cl_updaterate->GetFloat() ) ) + { + interpcolor[ 0 ] = 255; + interpcolor[ 1 ] = 125; + interpcolor[ 2 ] = 31; + } + } + + g_pMatSystemSurface->DrawColoredText( font, x + textWidth, y, interpcolor[ 0 ], interpcolor[ 1 ], interpcolor[ 2 ], 255, sz ); + + Q_snprintf( sz, sizeof( sz ), "%3.1f/s", m_AvgPacketIn ); + textWidth = g_pMatSystemSurface->DrawTextLen( font, "%s", sz ); + + g_pMatSystemSurface->DrawColoredText( font, x + w - textWidth - 1, y, GRAPH_RED, GRAPH_GREEN, GRAPH_BLUE, 255, sz ); + + y += textTall; + + Q_snprintf( sz, sizeof( sz ), "out:%4i %2.2f k/s", out, m_OutgoingData ); + + g_pMatSystemSurface->DrawColoredText( font, x, y, GRAPH_RED, GRAPH_GREEN, GRAPH_BLUE, 255, sz ); + + Q_snprintf( sz, sizeof( sz ), "%3.1f/s", m_AvgPacketOut ); + textWidth = g_pMatSystemSurface->DrawTextLen( font, "%s", sz ); + + g_pMatSystemSurface->DrawColoredText( font, x + w - textWidth - 1, y, GRAPH_RED, GRAPH_GREEN, GRAPH_BLUE, 255, sz ); + + y += textTall; + + DrawCmdRate( x + w, y ); + + if ( graphvalue > 2 ) + { + Q_snprintf( sz, sizeof( sz ), "loss:%3i choke: %2i ", (int)(m_AvgPacketLoss*100.0f), (int)(m_AvgPacketChoke*100.0f) ); + + textWidth = g_pMatSystemSurface->DrawTextLen( font, "%s", sz ); + + g_pMatSystemSurface->DrawColoredText( font, x, y, GRAPH_RED, GRAPH_GREEN, GRAPH_BLUE, 255, sz ); + + y += textTall; + + if ( graphvalue > 3 ) + { + Q_snprintf( sz, sizeof( sz ), "sv : %5.1f var: %4.2f msec", m_flServerFramerate, m_flServerFramerateStdDeviation * 1000.0f ); + + int servercolor[ 3 ] = { GRAPH_RED, GRAPH_GREEN, GRAPH_BLUE }; + + if ( m_flServerFramerate < 10.0f ) + { + servercolor[ 0 ] = 255; + servercolor[ 1 ] = 31; + servercolor[ 2 ] = 31; + } + else if ( m_flServerFramerate < 20.0f ) + { + servercolor[ 0 ] = 255; + servercolor[ 1 ] = 255; + servercolor[ 2 ] = 0; + } + + g_pMatSystemSurface->DrawColoredText( font, x, y, servercolor[ 0 ], servercolor[ 1 ], servercolor[ 2 ], 255, sz ); + + y += textTall; + } + } + + // Draw legend + if ( graphvalue >= 3 ) + { + int textTall = g_pMatSystemSurface->GetFontTall( m_hFontSmall ); + + y = saveY - textTall - 5; + int cw, ch; + g_pMatSystemSurface->GetTextSize( m_hFontSmall, L"otherplayersWWW", cw, ch ); + if ( x - cw < 0 ) + { + x += w + 5; + } + else + { + x -= cw; + } + + g_pMatSystemSurface->DrawColoredText( m_hFontSmall, x, y, 0, 0, 255, 255, "localplayer" ); + y -= textTall; + g_pMatSystemSurface->DrawColoredText( m_hFontSmall, x, y, 0, 255, 0, 255, "otherplayers" ); + y -= textTall; + g_pMatSystemSurface->DrawColoredText( m_hFontSmall, x, y, 255, 0, 0, 255, "entities" ); + y -= textTall; + g_pMatSystemSurface->DrawColoredText( m_hFontSmall, x, y, 255, 255, 0, 255, "sounds" ); + y -= textTall; + g_pMatSystemSurface->DrawColoredText( m_hFontSmall, x, y, 0, 255, 255, 255, "events" ); + y -= textTall; + g_pMatSystemSurface->DrawColoredText( m_hFontSmall, x, y, 128, 128, 0, 255, "usermessages" ); + y -= textTall; + g_pMatSystemSurface->DrawColoredText( m_hFontSmall, x, y, 0, 128, 128, 255, "entmessages" ); + y -= textTall; + g_pMatSystemSurface->DrawColoredText( m_hFontSmall, x, y, 128, 0, 0, 255, "stringcmds" ); + y -= textTall; + g_pMatSystemSurface->DrawColoredText( m_hFontSmall, x, y, 0, 128, 0, 255, "stringtables" ); + y -= textTall; + g_pMatSystemSurface->DrawColoredText( m_hFontSmall, x, y, 0, 0, 128, 255, "voice" ); + y -= textTall; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Determine type of graph to show, or if +graph key is held down, use detailed graph +// Output : int +//----------------------------------------------------------------------------- +int CNetGraphPanel::GraphValue( void ) +{ + int graphtype; + + graphtype = net_graph.GetInt(); + + if ( !graphtype && !( in_graph.state & 1 ) ) + return 0; + + // With +graph key, use max area + if ( !graphtype ) + { + graphtype = 2; + } + + return graphtype; +} + +//----------------------------------------------------------------------------- +// Purpose: Figure out x and y position for graph based on net_graphpos +// value. +// Input : *rect - +// width - +// *x - +// *y - +//----------------------------------------------------------------------------- +void CNetGraphPanel::GraphGetXY( vrect_t *rect, int width, int *x, int *y ) +{ + *x = rect->x + 5; + + switch ( net_graphpos.GetInt() ) + { + case 0: + break; + case 1: + *x = rect->x + rect->width - 5 - width; + break; + case 2: + *x = rect->x + ( rect->width - 10 - width ) / 2; + break; + default: + *x = rect->x + clamp( (int) XRES( net_graphpos.GetInt() ), 5, rect->width - width - 5 ); + } + + *y = rect->y+rect->height - LERP_HEIGHT - 5; +} + +//----------------------------------------------------------------------------- +// Purpose: drawing stream progess (file download etc) as green bars ( under in/out) +// Input : x - +// y - +// maxmsgbytes - +//----------------------------------------------------------------------------- + +void CNetGraphPanel::DrawStreamProgress( int x, int y, int width ) +{ + vrect_t rcLine; + + rcLine.height = 1; + rcLine.x = x; + + byte color[3]; color[0] = 0; color[1] = 200; color[2] = 0; + + if ( m_StreamTotal[FLOW_INCOMING] > 0 ) + { + rcLine.y = y - m_nNetGraphHeight + 15 + 14; + rcLine.width = (m_StreamRecv[FLOW_INCOMING]*width)/m_StreamTotal[FLOW_INCOMING]; + DrawLine( &rcLine, color, 255 ); + } + + if ( m_StreamTotal[FLOW_OUTGOING] > 0 ) + { + rcLine.y = y - m_nNetGraphHeight + 2*15 + 14; + rcLine.width = (m_StreamRecv[FLOW_OUTGOING]*width)/m_StreamTotal[FLOW_OUTGOING]; + DrawLine( &rcLine, color, 255 ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: If showing bandwidth data, draw hatches big enough for largest message +// Input : x - +// y - +// maxmsgbytes - +//----------------------------------------------------------------------------- +void CNetGraphPanel::DrawHatches( int x, int y, int maxmsgbytes ) +{ + int starty; + int ystep; + vrect_t rcHatch; + + byte colorminor[3]; + byte color[3]; + + ystep = (int)( 10.0 / net_scale.GetFloat() ); + ystep = MAX( ystep, 1 ); + + rcHatch.y = y; + rcHatch.height = 1; + rcHatch.x = x; + rcHatch.width = 4; + + color[0] = 0; + color[1] = 200; + color[2] = 0; + + colorminor[0] = 63; + colorminor[1] = 63; + colorminor[2] = 0; + + for ( starty = rcHatch.y; rcHatch.y > 0 && ((starty - rcHatch.y)*net_scale.GetFloat() < ( maxmsgbytes + 50 ) ); rcHatch.y -= ystep ) + { + if ( !((int)((starty - rcHatch.y)*net_scale.GetFloat() ) % 50 ) ) + { + DrawLine( &rcHatch, color, 255 ); + } + else if ( ystep > 5 ) + { + DrawLine( &rcHatch, colorminor, 200 ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: State how many updates a second are being requested +// Input : x - +// y - +//----------------------------------------------------------------------------- +void CNetGraphPanel::DrawUpdateRate( int xright, int y ) +{ + char sz[ 32 ]; + Q_snprintf( sz, sizeof( sz ), "%i/s", cl_updaterate->GetInt() ); + wchar_t unicode[ 32 ]; + g_pVGuiLocalize->ConvertANSIToUnicode( sz, unicode, sizeof( unicode ) ); + + // Last one + int textWide, textTall; + + g_pMatSystemSurface->GetTextSize( GetNetgraphFont(), unicode, textWide, textTall ); + + g_pMatSystemSurface->DrawColoredText( GetNetgraphFont(), xright - textWide - 1, y, GRAPH_RED, GRAPH_GREEN, GRAPH_BLUE, 255, "%s", sz ); +} + +//----------------------------------------------------------------------------- +// Purpose: State how many updates a second are being requested +// Input : x - +// y - +//----------------------------------------------------------------------------- +void CNetGraphPanel::DrawCmdRate( int xright, int y ) +{ + char sz[ 32 ]; + Q_snprintf( sz, sizeof( sz ), "%i/s", cl_cmdrate->GetInt() ); + wchar_t unicode[ 32 ]; + g_pVGuiLocalize->ConvertANSIToUnicode( sz, unicode, sizeof( unicode ) ); + + // Last one + int textWide, textTall; + + g_pMatSystemSurface->GetTextSize( GetNetgraphFont(), unicode, textWide, textTall ); + + g_pMatSystemSurface->DrawColoredText( GetNetgraphFont(), xright - textWide - 1, y, GRAPH_RED, GRAPH_GREEN, GRAPH_BLUE, 255, "%s", sz ); +} + +//----------------------------------------------------------------------------- +// Purpose: Draws bandwidth breakdown data +// Input : *rcFill - +// bytes - +// r - +// g - +// b - +// alpha - +// Output : int +//----------------------------------------------------------------------------- +int CNetGraphPanel::DrawDataSegment( vrect_t *rcFill, int bytes, byte r, byte g, byte b, byte alpha ) +{ + int h; + byte color[3]; + + h = bytes / net_scale.GetFloat(); + + color[0] = r; + color[1] = g; + color[2] = b; + + rcFill->height = h; + rcFill->y -= h; + + if ( rcFill->y < 2 ) + return 0; + + DrawLine( rcFill, color, alpha ); + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNetGraphPanel::OnTick( void ) +{ + bool bVisible = ShouldDraw(); + if ( IsVisible() != bVisible ) + { + SetVisible( bVisible ); + } +} + +bool CNetGraphPanel::ShouldDraw( void ) +{ + if ( GraphValue() != 0 ) + return true; + + return false; +} + +void CNetGraphPanel::DrawLargePacketSizes( int x, int w, int graphtype, float warning_threshold ) +{ + vrect_t rcFill = {0,0,0,0}; + int a, i; + + for (a=0 ; a MAX( 300, warning_threshold ) ) + { + char sz[ 32 ]; + Q_snprintf( sz, sizeof( sz ), "%i", nTotalBytes ); + + int len = g_pMatSystemSurface->DrawTextLen( m_hFont, sz ); + + int textx, texty; + + textx = rcFill.x - len / 2; + texty = MAX( 0, rcFill.y - 11 ); + + g_pMatSystemSurface->DrawColoredText( m_hFont, textx, texty, 255, 255, 255, 255, sz ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: A basic version (doesn't taken into account the "holding after +// screenshot" bit like TF does, but is good enough for hud_freezecamhide. +//----------------------------------------------------------------------------- +static bool IsTakingAFreezecamScreenshot() +{ + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + bool bInFreezeCam = ( pPlayer && pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ); + + return ( bInFreezeCam && engine->IsTakingScreenshot() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNetGraphPanel::Paint() +{ + VPROF( "CNetGraphPanel::Paint" ); + + // Don't display net_graph if taking freezecam screenshot and hud_freezecamhide is enabled + extern ConVar hud_freezecamhide; + if ( hud_freezecamhide.GetBool() && IsTakingAFreezecamScreenshot() ) + return; + + int graphtype; + + int x, y; + int w; + vrect_t vrect; + + int maxmsgbytes = 0; + + float avg_message = 0.0f; + float warning_threshold = 0.0f; + + if ( ( graphtype = GraphValue() ) == 0 ) + return; + + // Since we divide by scale, make sure it's sensible + if ( net_scale.GetFloat() <= 0 ) + { + net_scale.SetValue( 0.1f ); + } + + int sw, sh; + surface()->GetScreenSize( sw, sh ); + + // Get screen rectangle + vrect.x = 0; + vrect.y = 0; + vrect.width = sw; + vrect.height = sh; + + + w = MIN( (int)TIMINGS, m_EstimatedWidth ); + if ( vrect.width < w + 10 ) + { + w = vrect.width - 10; + } + + // get current client netchannel INetChannelInfo interface + INetChannelInfo *nci = engine->GetNetChannelInfo(); + + if ( nci ) + { + // update incoming data + GetFrameData( nci, &maxmsgbytes, &avg_message, &warning_threshold ); + + // update outgoing data + GetCommandInfo( nci, m_Cmdinfo ); + + UpdateEstimatedServerFramerate( nci ); + } + + GraphGetXY( &vrect, w, &x, &y ); + + if ( graphtype > 1 ) + { + PaintLineArt( x, y, w, graphtype, maxmsgbytes ); + + DrawLargePacketSizes( x, w, graphtype, warning_threshold ); + } + + // Draw client frame timing info + DrawTimes( vrect, m_Cmdinfo, x, w, graphtype ); + + DrawTextFields( graphtype, x, y, w, m_Graph, m_Cmdinfo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNetGraphPanel::PaintLineArt( int x, int y, int w, int graphtype, int maxmsgbytes ) +{ + VPROF( "CNetGraphPanel::PaintLineArt" ); + + ResetLineSegments(); + + int lastvalidh = 0; + + byte color[3]; + int ping; + byte alpha; + vrect_t rcFill = {0,0,0,0}; + + int pingheight = m_nNetGraphHeight - LERP_HEIGHT - 2; + + if (net_graphmsecs.GetInt() < 50 ) + { + net_graphmsecs.SetValue( 50 ); + } + + bool bShowLatency = net_graphshowlatency.GetBool() && graphtype >= 2; + + for (int a=0 ; a pingheight ) + { + h = pingheight; + } + + rcFill.x = x + w -a -1; + rcFill.y = y - h; + rcFill.width = 1; + rcFill.height = h; + if ( ping ) + { + rcFill.height = pl->choked ? 2 : 1; + } + + if ( !ping ) + { + DrawLine2(&rcFill, color, color, alpha, 31 ); + } + else + { + DrawLine(&rcFill, color, alpha ); + } + + rcFill.y = y; + rcFill.height = 1; + + color[0] = 0; + color[1] = 255; + color[2] = 0; + + DrawLine( &rcFill, color, 160 ); + + if ( graphtype < 2 ) + continue; + + // Draw a separator. + rcFill.y = y - m_nNetGraphHeight - 1; + rcFill.height = 1; + + color[0] = 255; + color[1] = 255; + color[2] = 255; + + DrawLine(&rcFill, color, 255 ); + + // Move up for begining of data + rcFill.y -= 1; + + // Packet didn't have any real data... + if ( m_PacketLatency[i].latency > 9995 ) + continue; + + + if ( !DrawDataSegment( &rcFill, m_Graph[ i ].msgbytes[INetChannelInfo::LOCALPLAYER], 0, 0, 255 ) ) + continue; + + if ( !DrawDataSegment( &rcFill, m_Graph[ i ].msgbytes[INetChannelInfo::OTHERPLAYERS], 0, 255, 0 ) ) + continue; + + if ( !DrawDataSegment( &rcFill, m_Graph[ i ].msgbytes[INetChannelInfo::ENTITIES], 255, 0, 0 ) ) + continue; + + if ( !DrawDataSegment( &rcFill, m_Graph[ i ].msgbytes[INetChannelInfo::SOUNDS], 255, 255, 0) ) + continue; + + if ( !DrawDataSegment( &rcFill, m_Graph[ i ].msgbytes[INetChannelInfo::EVENTS], 0, 255, 255 ) ) + continue; + + if ( !DrawDataSegment( &rcFill, m_Graph[ i ].msgbytes[INetChannelInfo::USERMESSAGES], 128, 128, 0 ) ) + continue; + + if ( !DrawDataSegment( &rcFill, m_Graph[ i ].msgbytes[INetChannelInfo::ENTMESSAGES], 0, 128, 128 ) ) + continue; + + if ( !DrawDataSegment( &rcFill, m_Graph[ i ].msgbytes[INetChannelInfo::STRINGCMD], 128, 0, 0) ) + continue; + + if ( !DrawDataSegment( &rcFill, m_Graph[ i ].msgbytes[INetChannelInfo::STRINGTABLE], 0, 128, 0) ) + continue; + + if ( !DrawDataSegment( &rcFill, m_Graph[ i ].msgbytes[INetChannelInfo::VOICE], 0, 0, 128 ) ) + continue; + + // Final data chunk is total size, don't use solid line routine for this + h = m_Graph[i].msgbytes[INetChannelInfo::TOTAL] / net_scale.GetFloat(); + + color[ 0 ] = color[ 1 ] = color[ 2 ] = 240; + + rcFill.height = 1; + rcFill.y = y - m_nNetGraphHeight - 1 - h; + + if ( rcFill.y < 2 ) + continue; + + DrawLine(&rcFill, color, 128 ); + + // Cache off height + m_Graph[i].sampleY = rcFill.y; + m_Graph[i].sampleHeight = rcFill.height; + } + + if ( graphtype >= 2 ) + { + // Draw hatches for first one: + // on the far right side + DrawHatches( x, y - m_nNetGraphHeight - 1, maxmsgbytes ); + + DrawStreamProgress( x, y, w ); + } + + DrawLineSegments(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNetGraphPanel::ResetLineSegments() +{ + m_Rects.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNetGraphPanel::DrawLineSegments() +{ + int c = m_Rects.Count(); + if ( c <= 0 ) + return; + + CMatRenderContextPtr pRenderContext( materials ); + IMesh* m_pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, m_WhiteMaterial ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( m_pMesh, MATERIAL_LINES, c ); + + int i; + for ( i = 0 ; i < c; i++ ) + { + CLineSegment *seg = &m_Rects[ i ]; + + meshBuilder.Color4ubv( seg->color ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Position3f( seg->x1, seg->y1, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ubv( seg->color2 ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.Position3f( seg->x2, seg->y2, 0 ); + meshBuilder.AdvanceVertex(); + } + + meshBuilder.End(); + + m_pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Purpose: Draws a colored, filled rectangle +// Input : *rect - +// *color - +// alpha - +//----------------------------------------------------------------------------- +void CNetGraphPanel::DrawLine( vrect_t *rect, unsigned char *color, unsigned char alpha ) +{ + DrawLine2( rect, color, color, alpha, alpha ); +} + +//----------------------------------------------------------------------------- +// Purpose: Draws a colored, filled rectangle +// Input : *rect - +// *color - +// alpha - +//----------------------------------------------------------------------------- +void CNetGraphPanel::DrawLine2( vrect_t *rect, unsigned char *color, unsigned char *color2, unsigned char alpha, unsigned char alpha2 ) +{ + VPROF( "CNetGraphPanel::DrawLine2" ); + + int idx = m_Rects.AddToTail(); + CLineSegment *seg = &m_Rects[ idx ]; + + seg->color[0] = color[0]; + seg->color[1] = color[1]; + seg->color[2] = color[2]; + seg->color[3] = alpha; + seg->color2[0] = color2[0]; + seg->color2[1] = color2[1]; + seg->color2[2] = color2[2]; + seg->color2[3] = alpha2; + + if ( rect->width == 1 ) + { + seg->x1 = rect->x; + seg->y1 = rect->y; + seg->x2 = rect->x; + seg->y2 = rect->y + rect->height; + } + else if ( rect->height == 1 ) + { + seg->x1 = rect->x; + seg->y1 = rect->y; + seg->x2 = rect->x + rect->width; + seg->y2 = rect->y; + } + else + { + Assert( 0 ); + m_Rects.Remove( idx ); + } +} + +void CNetGraphPanel::UpdateEstimatedServerFramerate( INetChannelInfo *netchannel ) +{ + float flFrameTime; + netchannel->GetRemoteFramerate( &flFrameTime, &m_flServerFramerateStdDeviation ); + if ( flFrameTime > FLT_EPSILON ) + { + m_flServerFramerate = 1.0f / flFrameTime; + } +} + +class CNetGraphPanelInterface : public INetGraphPanel +{ +private: + CNetGraphPanel *netGraphPanel; +public: + CNetGraphPanelInterface( void ) + { + netGraphPanel = NULL; + } + void Create( VPANEL parent ) + { + netGraphPanel = new CNetGraphPanel( parent ); + } + void Destroy( void ) + { + if ( netGraphPanel ) + { + netGraphPanel->SetParent( (Panel *)NULL ); + delete netGraphPanel; + netGraphPanel = NULL; + } + } +}; + +static CNetGraphPanelInterface g_NetGraphPanel; +INetGraphPanel *netgraphpanel = ( INetGraphPanel * )&g_NetGraphPanel; -- cgit v1.2.3