mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-20 06:58:20 +00:00
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.
This commit is contained in:
parent
c90a52f51c
commit
4be76f0d08
|
@ -75,7 +75,10 @@ static const ColorRGBA gs_ClientNetworkPrintColor{0.7f, 1, 0.7f, 1.0f};
|
|||
static const ColorRGBA gs_ClientNetworkErrPrintColor{1.0f, 0.25f, 0.25f, 1.0f};
|
||||
|
||||
CClient::CClient() :
|
||||
m_DemoPlayer(&m_SnapshotDelta, true, [&]() { UpdateDemoIntraTimers(); })
|
||||
m_DemoPlayer(&m_SnapshotDelta, true, [&]() { UpdateDemoIntraTimers(); }),
|
||||
m_InputtimeMarginGraph(128),
|
||||
m_GametimeMarginGraph(128),
|
||||
m_FpsGraph(4096)
|
||||
{
|
||||
m_StateStartTime = time_get();
|
||||
for(auto &DemoRecorder : m_aDemoRecorder)
|
||||
|
@ -821,11 +824,11 @@ void CClient::DebugRender()
|
|||
float sp = Graphics()->ScreenWidth() / 100.0f;
|
||||
float x = Graphics()->ScreenWidth() - w - sp;
|
||||
|
||||
m_FpsGraph.Scale();
|
||||
m_FpsGraph.Scale(time_freq());
|
||||
m_FpsGraph.Render(Graphics(), TextRender(), x, sp * 5, w, h, "FPS");
|
||||
m_InputtimeMarginGraph.Scale();
|
||||
m_InputtimeMarginGraph.Scale(5 * time_freq());
|
||||
m_InputtimeMarginGraph.Render(Graphics(), TextRender(), x, sp * 6 + h, w, h, "Prediction Margin");
|
||||
m_GametimeMarginGraph.Scale();
|
||||
m_GametimeMarginGraph.Scale(5 * time_freq());
|
||||
m_GametimeMarginGraph.Render(Graphics(), TextRender(), x, sp * 7 + h * 2, w, h, "Gametime Margin");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,15 @@
|
|||
|
||||
#include "graph.h"
|
||||
|
||||
CGraph::CGraph(int MaxEntries) :
|
||||
m_Entries(MaxEntries * (sizeof(SEntry) + 2 * CRingBufferBase::ITEM_SIZE), CRingBufferBase::FLAG_RECYCLE)
|
||||
{
|
||||
}
|
||||
|
||||
void CGraph::Init(float Min, float Max)
|
||||
{
|
||||
SetMin(Min);
|
||||
SetMax(Max);
|
||||
m_Index = 0;
|
||||
for(auto &Entry : m_aEntries)
|
||||
Entry.m_Initialized = false;
|
||||
}
|
||||
|
||||
void CGraph::SetMin(float Min)
|
||||
|
@ -25,34 +27,103 @@ void CGraph::SetMax(float Max)
|
|||
m_MaxRange = m_Max = Max;
|
||||
}
|
||||
|
||||
void CGraph::Scale()
|
||||
void CGraph::Scale(int64_t WantedTotalTime)
|
||||
{
|
||||
// Scale X axis for wanted total time
|
||||
if(m_Entries.First() != nullptr)
|
||||
{
|
||||
const int64_t EndTime = m_Entries.Last()->m_Time;
|
||||
bool ScaleTotalTime = false;
|
||||
m_pFirstScaled = nullptr;
|
||||
|
||||
if(m_Entries.First()->m_Time >= EndTime - WantedTotalTime)
|
||||
{
|
||||
m_pFirstScaled = m_Entries.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pFirstScaled = m_Entries.Last();
|
||||
while(m_pFirstScaled)
|
||||
{
|
||||
SEntry *pPrev = m_Entries.Prev(m_pFirstScaled);
|
||||
if(pPrev == nullptr)
|
||||
break;
|
||||
if(pPrev->m_Time < EndTime - WantedTotalTime)
|
||||
{
|
||||
// Scale based on actual total time instead of based on wanted total time,
|
||||
// to avoid flickering last segment due to rounding errors.
|
||||
ScaleTotalTime = true;
|
||||
break;
|
||||
}
|
||||
m_pFirstScaled = pPrev;
|
||||
}
|
||||
}
|
||||
|
||||
m_RenderedTotalTime = ScaleTotalTime ? (EndTime - m_pFirstScaled->m_Time) : WantedTotalTime;
|
||||
|
||||
// Ensure that color is applied to first line segment
|
||||
if(m_pFirstScaled)
|
||||
{
|
||||
m_pFirstScaled->m_ApplyColor = true;
|
||||
SEntry *pNext = m_Entries.Next(m_pFirstScaled);
|
||||
if(pNext != nullptr)
|
||||
{
|
||||
pNext->m_ApplyColor = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pFirstScaled = nullptr;
|
||||
m_RenderedTotalTime = 0;
|
||||
}
|
||||
|
||||
// Scale Y axis
|
||||
m_Min = m_MinRange;
|
||||
m_Max = m_MaxRange;
|
||||
for(auto &Entry : m_aEntries)
|
||||
for(SEntry *pEntry = m_pFirstScaled; pEntry != nullptr; pEntry = m_Entries.Next(pEntry))
|
||||
{
|
||||
if(Entry.m_Value > m_Max)
|
||||
m_Max = Entry.m_Value;
|
||||
else if(Entry.m_Value < m_Min)
|
||||
m_Min = Entry.m_Value;
|
||||
if(pEntry->m_Value > m_Max)
|
||||
m_Max = pEntry->m_Value;
|
||||
else if(pEntry->m_Value < m_Min)
|
||||
m_Min = pEntry->m_Value;
|
||||
}
|
||||
}
|
||||
|
||||
void CGraph::Add(float Value, ColorRGBA Color)
|
||||
{
|
||||
InsertAt(m_Index, Value, Color);
|
||||
m_Index = (m_Index + 1) % MAX_VALUES;
|
||||
InsertAt(time_get(), Value, Color);
|
||||
}
|
||||
|
||||
void CGraph::InsertAt(size_t Index, float Value, ColorRGBA Color)
|
||||
void CGraph::InsertAt(int64_t Time, float Value, ColorRGBA Color)
|
||||
{
|
||||
dbg_assert(Index < MAX_VALUES, "Index out of bounds");
|
||||
m_aEntries[Index].m_Initialized = true;
|
||||
m_aEntries[Index].m_Value = Value;
|
||||
m_aEntries[Index].m_Color = Color;
|
||||
SEntry *pEntry = m_Entries.Allocate(sizeof(SEntry));
|
||||
pEntry->m_Time = Time;
|
||||
pEntry->m_Value = Value;
|
||||
pEntry->m_Color = Color;
|
||||
|
||||
// Determine whether the line (pPrev, pEntry) has different
|
||||
// vertex colors than the line (pPrevPrev, pPrev).
|
||||
SEntry *pPrev = m_Entries.Prev(pEntry);
|
||||
if(pPrev == nullptr)
|
||||
{
|
||||
pEntry->m_ApplyColor = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SEntry *pPrevPrev = m_Entries.Prev(pPrev);
|
||||
if(pPrevPrev == nullptr)
|
||||
{
|
||||
pEntry->m_ApplyColor = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pEntry->m_ApplyColor = Color != pPrev->m_Color || pPrev->m_Color != pPrevPrev->m_Color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CGraph::Render(IGraphics *pGraphics, ITextRender *pTextRender, float x, float y, float w, float h, const char *pDescription) const
|
||||
void CGraph::Render(IGraphics *pGraphics, ITextRender *pTextRender, float x, float y, float w, float h, const char *pDescription)
|
||||
{
|
||||
pGraphics->TextureClear();
|
||||
|
||||
|
@ -66,28 +137,61 @@ void CGraph::Render(IGraphics *pGraphics, ITextRender *pTextRender, float x, flo
|
|||
pGraphics->SetColor(0.95f, 0.95f, 0.95f, 1.0f);
|
||||
IGraphics::CLineItem LineItem(x, y + h / 2, x + w, y + h / 2);
|
||||
pGraphics->LinesDraw(&LineItem, 1);
|
||||
|
||||
pGraphics->SetColor(0.5f, 0.5f, 0.5f, 0.75f);
|
||||
IGraphics::CLineItem aLineItems[2] = {
|
||||
IGraphics::CLineItem(x, y + (h * 3) / 4, x + w, y + (h * 3) / 4),
|
||||
IGraphics::CLineItem(x, y + h / 4, x + w, y + h / 4)};
|
||||
pGraphics->LinesDraw(aLineItems, std::size(aLineItems));
|
||||
for(int i = 1; i < MAX_VALUES; i++)
|
||||
{
|
||||
const auto &Entry0 = m_aEntries[(m_Index + i - 1) % MAX_VALUES];
|
||||
const auto &Entry1 = m_aEntries[(m_Index + i) % MAX_VALUES];
|
||||
if(!Entry0.m_Initialized || !Entry1.m_Initialized)
|
||||
continue;
|
||||
float a0 = (i - 1) / (float)(MAX_VALUES - 1);
|
||||
float a1 = i / (float)(MAX_VALUES - 1);
|
||||
float v0 = (Entry0.m_Value - m_Min) / (m_Max - m_Min);
|
||||
float v1 = (Entry1.m_Value - m_Min) / (m_Max - m_Min);
|
||||
|
||||
IGraphics::CColorVertex aColorVertices[2] = {
|
||||
IGraphics::CColorVertex(0, Entry0.m_Color.r, Entry0.m_Color.g, Entry0.m_Color.b, Entry0.m_Color.a),
|
||||
IGraphics::CColorVertex(1, Entry1.m_Color.r, Entry1.m_Color.g, Entry1.m_Color.b, Entry1.m_Color.a)};
|
||||
pGraphics->SetColorVertex(aColorVertices, std::size(aColorVertices));
|
||||
IGraphics::CLineItem LineItem2(x + a0 * w, y + h - v0 * h, x + a1 * w, y + h - v1 * h);
|
||||
pGraphics->LinesDraw(&LineItem2, 1);
|
||||
if(m_pFirstScaled != nullptr)
|
||||
{
|
||||
IGraphics::CLineItem aValueLineItems[128];
|
||||
size_t NumValueLineItems = 0;
|
||||
|
||||
const int64_t StartTime = m_pFirstScaled->m_Time;
|
||||
|
||||
SEntry *pEntry0 = m_pFirstScaled;
|
||||
int a0 = round_to_int((pEntry0->m_Time - StartTime) * w / m_RenderedTotalTime);
|
||||
int v0 = round_to_int((pEntry0->m_Value - m_Min) * h / (m_Max - m_Min));
|
||||
while(pEntry0 != nullptr)
|
||||
{
|
||||
SEntry *pEntry1 = m_Entries.Next(pEntry0);
|
||||
if(pEntry1 == nullptr)
|
||||
break;
|
||||
|
||||
const int a1 = round_to_int((pEntry1->m_Time - StartTime) * w / m_RenderedTotalTime);
|
||||
const int v1 = round_to_int((pEntry1->m_Value - m_Min) * h / (m_Max - m_Min));
|
||||
|
||||
if(pEntry1->m_ApplyColor)
|
||||
{
|
||||
if(NumValueLineItems)
|
||||
{
|
||||
pGraphics->LinesDraw(aValueLineItems, NumValueLineItems);
|
||||
NumValueLineItems = 0;
|
||||
}
|
||||
|
||||
IGraphics::CColorVertex aColorVertices[2] = {
|
||||
IGraphics::CColorVertex(0, pEntry0->m_Color.r, pEntry0->m_Color.g, pEntry0->m_Color.b, pEntry0->m_Color.a),
|
||||
IGraphics::CColorVertex(1, pEntry1->m_Color.r, pEntry1->m_Color.g, pEntry1->m_Color.b, pEntry1->m_Color.a)};
|
||||
pGraphics->SetColorVertex(aColorVertices, std::size(aColorVertices));
|
||||
}
|
||||
if(NumValueLineItems == std::size(aValueLineItems))
|
||||
{
|
||||
pGraphics->LinesDraw(aValueLineItems, NumValueLineItems);
|
||||
NumValueLineItems = 0;
|
||||
}
|
||||
aValueLineItems[NumValueLineItems] = IGraphics::CLineItem(x + a0, y + h - v0, x + a1, y + h - v1);
|
||||
++NumValueLineItems;
|
||||
|
||||
pEntry0 = pEntry1;
|
||||
a0 = a1;
|
||||
v0 = v1;
|
||||
}
|
||||
if(NumValueLineItems)
|
||||
{
|
||||
pGraphics->LinesDraw(aValueLineItems, NumValueLineItems);
|
||||
}
|
||||
}
|
||||
pGraphics->LinesEnd();
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#include <base/color.h>
|
||||
|
||||
#include <engine/shared/ringbuffer.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
class IGraphics;
|
||||
|
@ -13,33 +15,31 @@ class ITextRender;
|
|||
|
||||
class CGraph
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
MAX_VALUES = 128,
|
||||
};
|
||||
|
||||
private:
|
||||
struct SEntry
|
||||
{
|
||||
bool m_Initialized;
|
||||
int64_t m_Time;
|
||||
float m_Value;
|
||||
ColorRGBA m_Color;
|
||||
bool m_ApplyColor;
|
||||
};
|
||||
SEntry *m_pFirstScaled = nullptr;
|
||||
int64_t m_RenderedTotalTime = 0;
|
||||
float m_Min, m_Max;
|
||||
float m_MinRange, m_MaxRange;
|
||||
SEntry m_aEntries[MAX_VALUES];
|
||||
size_t m_Index;
|
||||
CDynamicRingBuffer<SEntry> m_Entries;
|
||||
|
||||
public:
|
||||
CGraph(int MaxEntries);
|
||||
|
||||
void Init(float Min, float Max);
|
||||
void SetMin(float Min);
|
||||
void SetMax(float Max);
|
||||
|
||||
void Scale();
|
||||
void Scale(int64_t WantedTotalTime);
|
||||
void Add(float Value, ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 0.75f));
|
||||
void InsertAt(size_t Index, float Value, ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 0.75f));
|
||||
void Render(IGraphics *pGraphics, ITextRender *pTextRender, float x, float y, float w, float h, const char *pDescription) const;
|
||||
void InsertAt(int64_t Time, float Value, ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 0.75f));
|
||||
void Render(IGraphics *pGraphics, ITextRender *pTextRender, float x, float y, float w, float h, const char *pDescription);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -12,6 +12,14 @@
|
|||
|
||||
#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)
|
||||
|
@ -179,7 +187,7 @@ void CDebugHud::RenderTuning()
|
|||
m_RampGraph.Init(0.0f, 0.0f);
|
||||
m_SpeedTurningPoint = 0;
|
||||
float PreviousRampedSpeed = 1.0f;
|
||||
for(size_t i = 0; i < CGraph::MAX_VALUES; i++)
|
||||
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;
|
||||
|
@ -196,12 +204,12 @@ void CDebugHud::RenderTuning()
|
|||
}
|
||||
PreviousRampedSpeed = RampedSpeed;
|
||||
}
|
||||
m_RampGraph.Scale();
|
||||
m_RampGraph.Scale(GRAPH_MAX_VALUES - 1);
|
||||
|
||||
m_ZoomedInGraph.Init(0.0f, 0.0f);
|
||||
PreviousRampedSpeed = 1.0f;
|
||||
MiddleOfZoomedInGraph = m_SpeedTurningPoint;
|
||||
for(size_t i = 0; i < CGraph::MAX_VALUES; i++)
|
||||
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;
|
||||
|
@ -222,7 +230,7 @@ void CDebugHud::RenderTuning()
|
|||
}
|
||||
PreviousRampedSpeed = RampedSpeed;
|
||||
}
|
||||
m_ZoomedInGraph.Scale();
|
||||
m_ZoomedInGraph.Scale(GRAPH_MAX_VALUES - 1);
|
||||
}
|
||||
|
||||
const float GraphFontSize = 12.0f;
|
||||
|
|
|
@ -21,6 +21,7 @@ class CDebugHud : public CComponent
|
|||
float m_OldVelrampCurvature;
|
||||
|
||||
public:
|
||||
CDebugHud();
|
||||
virtual int Sizeof() const override { return sizeof(*this); }
|
||||
virtual void OnRender() override;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue