diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp
index 475e0a4a6..311e478d5 100644
--- a/src/engine/client/client.cpp
+++ b/src/engine/client/client.cpp
@@ -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");
}
}
diff --git a/src/engine/client/graph.cpp b/src/engine/client/graph.cpp
index 95f62ed8e..59ab25e68 100644
--- a/src/engine/client/graph.cpp
+++ b/src/engine/client/graph.cpp
@@ -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();
diff --git a/src/engine/client/graph.h b/src/engine/client/graph.h
index 296c6ff9a..090d506fa 100644
--- a/src/engine/client/graph.h
+++ b/src/engine/client/graph.h
@@ -6,6 +6,8 @@
#include
+#include
+
#include
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 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
diff --git a/src/game/client/components/debughud.cpp b/src/game/client/components/debughud.cpp
index 212744b24..07dbe661f 100644
--- a/src/game/client/components/debughud.cpp
+++ b/src/game/client/components/debughud.cpp
@@ -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;
diff --git a/src/game/client/components/debughud.h b/src/game/client/components/debughud.h
index 67b9019d3..3dc326d73 100644
--- a/src/game/client/components/debughud.h
+++ b/src/game/client/components/debughud.h
@@ -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;
};