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; };