ddnet/src/game/client/components/debughud.cpp
Robert Müller 4be76f0d08 Add time scale to debug graphs for constant scrolling speed
Store X value (time) for all graph entries in addition to the Y value (FPS, prediction margin etc.). The `CGraph::Add` function adds values to the graph at the current time. The `CGraph::InsertAt` function allows specifying arbitrary X values, as long as the values are inserted in increasing order.

The entries are kept in a ringbuffer and old entries are recycled when it's full. The size of the ringbuffer is configurable for each graph, as the FPS graph needs significantly more buffer because values are added more often.

The scrolling speed of the graphs is fixed by specifying the maximum size of the window of values which should be displayed. For this purpose, a parameter is added to the `CGraph::Scale` function to specify the size of the window which should be rendered in the `CGraph::Render` function. For the FPS graph only the last second is rendered, so small spikes are still noticeable. For prediction and gametime margin graphs the last five seconds are rendered, which should result in a similar scrolling speed as before this change. The debug tuning graph is a special case, where the X values set manually and fixed to 0-127, same as before, instead of being based on the current time.

The graph rendering is made much more efficient by precalculating when the vertex colors need to be updated, to avoid all unnecessary calls to `SetColorVertex`. Additionally, line items are bundled together in an array to avoid calling `LinesDraw` for every individual line item.
2024-01-17 20:43:19 +01:00

268 lines
10 KiB
C++

/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <engine/graphics.h>
#include <engine/shared/config.h>
#include <engine/textrender.h>
#include <game/generated/protocol.h>
#include <game/client/gameclient.h>
#include <game/client/prediction/entities/character.h>
#include <game/localization.h>
#include "debughud.h"
static constexpr int64_t GRAPH_MAX_VALUES = 128;
CDebugHud::CDebugHud() :
m_RampGraph(GRAPH_MAX_VALUES),
m_ZoomedInGraph(GRAPH_MAX_VALUES)
{
}
void CDebugHud::RenderNetCorrections()
{
if(!g_Config.m_Debug || g_Config.m_DbgGraphs || !m_pClient->m_Snap.m_pLocalCharacter || !m_pClient->m_Snap.m_pLocalPrevCharacter)
return;
const float Height = 300.0f;
const float Width = Height * Graphics()->ScreenAspect();
Graphics()->MapScreen(0.0f, 0.0f, Width, Height);
const float Velspeed = length(vec2(m_pClient->m_Snap.m_pLocalCharacter->m_VelX / 256.0f, m_pClient->m_Snap.m_pLocalCharacter->m_VelY / 256.0f)) * Client()->GameTickSpeed();
const float VelspeedX = m_pClient->m_Snap.m_pLocalCharacter->m_VelX / 256.0f * Client()->GameTickSpeed();
const float VelspeedY = m_pClient->m_Snap.m_pLocalCharacter->m_VelY / 256.0f * Client()->GameTickSpeed();
const float Ramp = VelocityRamp(Velspeed, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampCurvature);
const CCharacter *pCharacter = m_pClient->m_GameWorld.GetCharacterByID(m_pClient->m_Snap.m_LocalClientID);
const float FontSize = 5.0f;
const float LineHeight = FontSize + 1.0f;
float y = 50.0f;
char aBuf[128];
const auto &&RenderRow = [&](const char *pLabel, const char *pValue) {
TextRender()->Text(Width - 100.0f, y, FontSize, pLabel);
TextRender()->Text(Width - 10.0f - TextRender()->TextWidth(FontSize, pValue), y, FontSize, pValue);
y += LineHeight;
};
TextRender()->TextColor(TextRender()->DefaultTextColor());
str_format(aBuf, sizeof(aBuf), "%.0f Bps", Velspeed / 32);
RenderRow("Velspeed:", aBuf);
str_format(aBuf, sizeof(aBuf), "%.0f Bps", VelspeedX / 32 * Ramp);
RenderRow("Velspeed.x * Ramp:", aBuf);
str_format(aBuf, sizeof(aBuf), "%.0f Bps", VelspeedY / 32);
RenderRow("Velspeed.y:", aBuf);
str_format(aBuf, sizeof(aBuf), "%.2f", Ramp);
RenderRow("Ramp:", aBuf);
str_from_int(pCharacter == nullptr ? -1 : pCharacter->m_TeleCheckpoint, aBuf);
RenderRow("Checkpoint:", aBuf);
str_from_int(pCharacter == nullptr ? -1 : pCharacter->m_TuneZone, aBuf);
RenderRow("Tune zone:", aBuf);
str_format(aBuf, sizeof(aBuf), "%.2f", m_pClient->m_Snap.m_pLocalCharacter->m_X / 32.0f);
RenderRow("Pos.x:", aBuf);
str_format(aBuf, sizeof(aBuf), "%.2f", m_pClient->m_Snap.m_pLocalCharacter->m_Y / 32.0f);
RenderRow("Pos.y:", aBuf);
str_from_int(m_pClient->m_Snap.m_pLocalCharacter->m_Angle, aBuf);
RenderRow("Angle:", aBuf);
str_from_int(m_pClient->NetobjNumCorrections(), aBuf);
RenderRow("Netobj corrections", aBuf);
RenderRow(" on:", m_pClient->NetobjCorrectedOn());
}
void CDebugHud::RenderTuning()
{
enum
{
DBG_TUNING_OFF = 0,
DBG_TUNING_SHOW_CHANGED,
DBG_TUNING_SHOW_ALL,
};
if(g_Config.m_DbgTuning == DBG_TUNING_OFF)
return;
const CCharacter *pCharacter = m_pClient->m_GameWorld.GetCharacterByID(m_pClient->m_Snap.m_LocalClientID);
const CTuningParams StandardTuning;
const CTuningParams *pGlobalTuning = m_pClient->GetTuning(0);
const CTuningParams *pZoneTuning = !m_pClient->m_GameWorld.m_WorldConfig.m_UseTuneZones || pCharacter == nullptr ? nullptr : m_pClient->GetTuning(pCharacter->m_TuneZone);
const CTuningParams *pActiveTuning = pZoneTuning == nullptr ? pGlobalTuning : pZoneTuning;
const float Height = 300.0f;
const float Width = Height * Graphics()->ScreenAspect();
Graphics()->MapScreen(0.0f, 0.0f, Width, Height);
const float FontSize = 5.0f;
const float StartY = 50.0f;
float y = StartY;
float StartX = 30.0f;
const auto &&RenderRow = [&](const char *pCol1, const char *pCol2, const char *pCol3) {
float x = StartX;
TextRender()->Text(x - TextRender()->TextWidth(FontSize, pCol1), y, FontSize, pCol1);
x += 30.0f;
TextRender()->Text(x - TextRender()->TextWidth(FontSize, pCol2), y, FontSize, pCol2);
x += 10.0f;
TextRender()->Text(x, y, FontSize, pCol3);
y += FontSize + 1.0f;
if(y >= Height - 80.0f)
{
y = StartY;
StartX += 130.0f;
}
};
for(int i = 0; i < CTuningParams::Num(); i++)
{
float CurrentGlobal, CurrentZone, Standard;
pGlobalTuning->Get(i, &CurrentGlobal);
if(pZoneTuning == nullptr)
CurrentZone = 0.0f;
else
pZoneTuning->Get(i, &CurrentZone);
StandardTuning.Get(i, &Standard);
if(g_Config.m_DbgTuning == DBG_TUNING_SHOW_CHANGED && Standard == CurrentGlobal && (pZoneTuning == nullptr || Standard == CurrentZone))
continue; // skip unchanged params
if(y == StartY)
{
TextRender()->TextColor(TextRender()->DefaultTextColor());
RenderRow("Standard", "Current", "Tuning");
}
ColorRGBA TextColor;
if(g_Config.m_DbgTuning == DBG_TUNING_SHOW_ALL && Standard == CurrentGlobal && (pZoneTuning == nullptr || Standard == CurrentZone))
TextColor = ColorRGBA(0.75f, 0.75f, 0.75f, 1.0f); // grey: value unchanged globally and in current zone
else if(Standard == CurrentGlobal && pZoneTuning != nullptr && Standard != CurrentZone)
TextColor = ColorRGBA(0.6f, 0.6f, 1.0f, 1.0f); // blue: value changed only in current zone
else if(Standard != CurrentGlobal && pZoneTuning != nullptr && Standard == CurrentZone)
TextColor = ColorRGBA(0.4f, 1.0f, 0.4f, 1.0f); // green: value changed globally but reset to default by tune zone
else
TextColor = ColorRGBA(1.0f, 0.5f, 0.5f, 1.0f); // red: value changed globally
TextRender()->TextColor(TextColor);
char aBufStandard[32];
str_format(aBufStandard, sizeof(aBufStandard), "%.2f", Standard);
char aBufCurrent[32];
str_format(aBufCurrent, sizeof(aBufCurrent), "%.2f", pZoneTuning == nullptr ? CurrentGlobal : CurrentZone);
RenderRow(aBufStandard, aBufCurrent, CTuningParams::Name(i));
}
TextRender()->TextColor(TextRender()->DefaultTextColor());
if(g_Config.m_DbgTuning == DBG_TUNING_SHOW_CHANGED)
return;
// Render Velspeed.X * Ramp Graphs
Graphics()->MapScreen(0.0f, 0.0f, Graphics()->ScreenWidth(), Graphics()->ScreenHeight());
const float GraphSpacing = Graphics()->ScreenWidth() / 100.0f;
const float GraphW = Graphics()->ScreenWidth() / 4.0f;
const float GraphH = Graphics()->ScreenHeight() / 6.0f;
const float GraphX = GraphW;
const float GraphY = Graphics()->ScreenHeight() - GraphH - GraphSpacing;
const int StepSizeRampGraph = 270;
const int StepSizeZoomedInGraph = 14;
if(m_OldVelrampStart != pActiveTuning->m_VelrampStart || m_OldVelrampRange != pActiveTuning->m_VelrampRange || m_OldVelrampCurvature != pActiveTuning->m_VelrampCurvature)
{
m_OldVelrampStart = pActiveTuning->m_VelrampStart;
m_OldVelrampRange = pActiveTuning->m_VelrampRange;
m_OldVelrampCurvature = pActiveTuning->m_VelrampCurvature;
m_RampGraph.Init(0.0f, 0.0f);
m_SpeedTurningPoint = 0;
float PreviousRampedSpeed = 1.0f;
for(int64_t i = 0; i < GRAPH_MAX_VALUES; i++)
{
// This is a calculation of the speed values per second on the X axis, from 270 to 34560 in steps of 270
const float Speed = (i + 1) * StepSizeRampGraph;
const float Ramp = VelocityRamp(Speed, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampCurvature);
const float RampedSpeed = Speed * Ramp;
if(RampedSpeed >= PreviousRampedSpeed)
{
m_RampGraph.InsertAt(i, RampedSpeed / 32, ColorRGBA(0.0f, 1.0f, 0.0f, 0.75f));
m_SpeedTurningPoint = Speed;
}
else
{
m_RampGraph.InsertAt(i, RampedSpeed / 32, ColorRGBA(1.0f, 0.0f, 0.0f, 0.75f));
}
PreviousRampedSpeed = RampedSpeed;
}
m_RampGraph.Scale(GRAPH_MAX_VALUES - 1);
m_ZoomedInGraph.Init(0.0f, 0.0f);
PreviousRampedSpeed = 1.0f;
MiddleOfZoomedInGraph = m_SpeedTurningPoint;
for(int64_t i = 0; i < GRAPH_MAX_VALUES; i++)
{
// This is a calculation of the speed values per second on the X axis, from (MiddleOfZoomedInGraph - 64 * StepSize) to (MiddleOfZoomedInGraph + 64 * StepSize)
const float Speed = MiddleOfZoomedInGraph - 64 * StepSizeZoomedInGraph + i * StepSizeZoomedInGraph;
const float Ramp = VelocityRamp(Speed, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampCurvature);
const float RampedSpeed = Speed * Ramp;
if(RampedSpeed >= PreviousRampedSpeed)
{
m_ZoomedInGraph.InsertAt(i, RampedSpeed / 32, ColorRGBA(0.0f, 1.0f, 0.0f, 0.75f));
m_SpeedTurningPoint = Speed;
}
else
{
m_ZoomedInGraph.InsertAt(i, RampedSpeed / 32, ColorRGBA(1.0f, 0.0f, 0.0f, 0.75f));
}
if(i == 0)
{
m_ZoomedInGraph.SetMin(RampedSpeed / 32);
}
PreviousRampedSpeed = RampedSpeed;
}
m_ZoomedInGraph.Scale(GRAPH_MAX_VALUES - 1);
}
const float GraphFontSize = 12.0f;
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "Velspeed.X * Ramp in Bps (Velspeed %d to %d)", StepSizeRampGraph / 32, 128 * StepSizeRampGraph / 32);
m_RampGraph.Render(Graphics(), TextRender(), GraphX, GraphY, GraphW, GraphH, aBuf);
str_format(aBuf, sizeof(aBuf), "Max Velspeed before it ramps off: %.2f Bps", m_SpeedTurningPoint / 32);
TextRender()->Text(GraphX, GraphY - GraphFontSize, GraphFontSize, aBuf);
str_format(aBuf, sizeof(aBuf), "Zoomed in on turning point (Velspeed %d to %d)", ((int)MiddleOfZoomedInGraph - 64 * StepSizeZoomedInGraph) / 32, ((int)MiddleOfZoomedInGraph + 64 * StepSizeZoomedInGraph) / 32);
m_ZoomedInGraph.Render(Graphics(), TextRender(), GraphX + GraphW + GraphSpacing, GraphY, GraphW, GraphH, aBuf);
}
void CDebugHud::RenderHint()
{
if(!g_Config.m_Debug)
return;
const float Height = 300.0f;
const float Width = Height * Graphics()->ScreenAspect();
Graphics()->MapScreen(0.0f, 0.0f, Width, Height);
const float FontSize = 5.0f;
const float Spacing = 5.0f;
TextRender()->TextColor(TextRender()->DefaultTextColor());
TextRender()->Text(Spacing, Height - FontSize - Spacing, FontSize, Localize("Debug mode enabled. Press Ctrl+Shift+D to disable debug mode."));
}
void CDebugHud::OnRender()
{
RenderTuning();
RenderNetCorrections();
RenderHint();
}