Merge pull request #7824 from Robyt3/Client-FPS-Graph-Timebase

Add time scale to debug graphs for constant scrolling speed
This commit is contained in:
Dennis Felsing 2024-01-17 23:29:13 +00:00 committed by GitHub
commit 0bce368c77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 206 additions and 63 deletions

View file

@ -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");
}
}

View file

@ -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();

View file

@ -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

View file

@ -1,7 +1,5 @@
/* (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 <base/system.h>
#include "ringbuffer.h"
CRingBufferBase::CItem *CRingBufferBase::NextBlock(CItem *pItem)

View file

@ -3,6 +3,8 @@
#ifndef ENGINE_SHARED_RINGBUFFER_H
#define ENGINE_SHARED_RINGBUFFER_H
#include <base/system.h>
class CRingBufferBase
{
class CItem
@ -44,18 +46,13 @@ public:
// Will start to destroy items to try to fit the next one
FLAG_RECYCLE = 1
};
static constexpr int ITEM_SIZE = sizeof(CItem);
};
template<typename T, int TSIZE, int TFLAGS = 0>
class CStaticRingBuffer : public CRingBufferBase
template<typename T>
class CTypedRingBuffer : public CRingBufferBase
{
unsigned char m_aBuffer[TSIZE];
public:
CStaticRingBuffer() { Init(); }
void Init() { CRingBufferBase::Init(m_aBuffer, TSIZE, TFLAGS); }
T *Allocate(int Size) { return (T *)CRingBufferBase::Allocate(Size); }
int PopFirst() { return CRingBufferBase::PopFirst(); }
@ -65,4 +62,36 @@ public:
T *Last() { return (T *)CRingBufferBase::Last(); }
};
template<typename T, int TSIZE, int TFLAGS = 0>
class CStaticRingBuffer : public CTypedRingBuffer<T>
{
unsigned char m_aBuffer[TSIZE];
public:
CStaticRingBuffer() { Init(); }
void Init() { CRingBufferBase::Init(m_aBuffer, TSIZE, TFLAGS); }
};
template<typename T>
class CDynamicRingBuffer : public CTypedRingBuffer<T>
{
unsigned char *m_pBuffer = nullptr;
public:
CDynamicRingBuffer(int Size, int Flags = 0) { Init(Size, Flags); }
virtual ~CDynamicRingBuffer()
{
free(m_pBuffer);
}
void Init(int Size, int Flags)
{
free(m_pBuffer);
m_pBuffer = static_cast<unsigned char *>(malloc(Size));
CRingBufferBase::Init(m_pBuffer, Size, Flags);
}
};
#endif

View file

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

View file

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