Realtime graphing initial commit
This commit is contained in:
parent
1b076a6836
commit
8bad1e21e5
6 changed files with 270 additions and 203 deletions
|
@ -9,6 +9,8 @@ namespace Ryujinx.Profiler
|
|||
{
|
||||
public class InternalProfile
|
||||
{
|
||||
public long CurrentTime => SW.ElapsedTicks;
|
||||
|
||||
private Stopwatch SW;
|
||||
internal ConcurrentDictionary<ProfileConfig, TimingInfo> Timers;
|
||||
|
||||
|
|
|
@ -6,10 +6,12 @@ namespace Ryujinx.Profiler
|
|||
{
|
||||
public static class Profile
|
||||
{
|
||||
// Static
|
||||
private static InternalProfile _profileInstance;
|
||||
private static ProfilerSettings _settings;
|
||||
|
||||
public static float UpdateRate => _settings.UpdateRate;
|
||||
public static long HistoryLength => _settings.History;
|
||||
|
||||
public static bool ProfilingEnabled()
|
||||
{
|
||||
if (!_settings.Enabled)
|
||||
|
@ -89,9 +91,13 @@ namespace Ryujinx.Profiler
|
|||
return _profileInstance.GetProfilingData();
|
||||
}
|
||||
|
||||
public static float GetUpdateRate()
|
||||
|
||||
|
||||
public static long GetCurrentTime()
|
||||
{
|
||||
return _settings.UpdateRate;
|
||||
if (!ProfilingEnabled())
|
||||
return 0;
|
||||
return _profileInstance.CurrentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,50 +22,70 @@ namespace Ryujinx.Profiler
|
|||
// Work out average
|
||||
public long AverageTime => (Count == 0) ? -1 : TotalTime / Count;
|
||||
|
||||
// Intentionally not locked as it's only a get count
|
||||
public bool IsActive => _timestamps.Count > 0;
|
||||
|
||||
public long BeginTime
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_timestampLock)
|
||||
{
|
||||
if (_depth > 0)
|
||||
{
|
||||
return _currentTimestamp.BeginTime;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Timestamp collection
|
||||
public List<Timestamp> Timestamps;
|
||||
private readonly object timestampLock = new object();
|
||||
private Timestamp currentTimestamp;
|
||||
private List<Timestamp> _timestamps;
|
||||
private readonly object _timestampLock = new object();
|
||||
private readonly object _timestampListLock = new object();
|
||||
private Timestamp _currentTimestamp;
|
||||
|
||||
// Depth of current timer,
|
||||
// each begin call increments and each end call decrements
|
||||
private int depth;
|
||||
private int _depth;
|
||||
|
||||
|
||||
public TimingInfo()
|
||||
{
|
||||
Timestamps = new List<Timestamp>();
|
||||
depth = 0;
|
||||
_timestamps = new List<Timestamp>();
|
||||
_depth = 0;
|
||||
}
|
||||
|
||||
public void Begin(long beginTime)
|
||||
{
|
||||
lock (timestampLock)
|
||||
lock (_timestampLock)
|
||||
{
|
||||
// Finish current timestamp if already running
|
||||
if (depth > 0)
|
||||
if (_depth > 0)
|
||||
{
|
||||
EndUnsafe(beginTime);
|
||||
}
|
||||
|
||||
BeginUnsafe(beginTime);
|
||||
depth++;
|
||||
_depth++;
|
||||
}
|
||||
}
|
||||
|
||||
private void BeginUnsafe(long beginTime)
|
||||
{
|
||||
currentTimestamp.BeginTime = beginTime;
|
||||
currentTimestamp.EndTime = -1;
|
||||
_currentTimestamp.BeginTime = beginTime;
|
||||
_currentTimestamp.EndTime = -1;
|
||||
}
|
||||
|
||||
public void End(long endTime)
|
||||
{
|
||||
lock (timestampLock)
|
||||
lock (_timestampLock)
|
||||
{
|
||||
depth--;
|
||||
_depth--;
|
||||
|
||||
if (depth < 0)
|
||||
if (_depth < 0)
|
||||
{
|
||||
throw new Exception("Timing info end called without corresponding begin");
|
||||
}
|
||||
|
@ -73,7 +93,7 @@ namespace Ryujinx.Profiler
|
|||
EndUnsafe(endTime);
|
||||
|
||||
// Still have others using this timing info so recreate start for them
|
||||
if (depth > 0)
|
||||
if (_depth > 0)
|
||||
{
|
||||
BeginUnsafe(endTime);
|
||||
}
|
||||
|
@ -82,10 +102,13 @@ namespace Ryujinx.Profiler
|
|||
|
||||
private void EndUnsafe(long endTime)
|
||||
{
|
||||
currentTimestamp.EndTime = endTime;
|
||||
Timestamps.Add(currentTimestamp);
|
||||
_currentTimestamp.EndTime = endTime;
|
||||
lock (_timestampListLock)
|
||||
{
|
||||
_timestamps.Add(_currentTimestamp);
|
||||
}
|
||||
|
||||
var delta = currentTimestamp.EndTime - currentTimestamp.BeginTime;
|
||||
var delta = _currentTimestamp.EndTime - _currentTimestamp.BeginTime;
|
||||
TotalTime += delta;
|
||||
Instant += delta;
|
||||
|
||||
|
@ -96,13 +119,13 @@ namespace Ryujinx.Profiler
|
|||
// Remove any timestamps before given timestamp to free memory
|
||||
public void Cleanup(long before)
|
||||
{
|
||||
lock (timestampLock)
|
||||
lock (_timestampListLock)
|
||||
{
|
||||
int toRemove = 0;
|
||||
|
||||
for (int i = 0; i < Timestamps.Count; i++)
|
||||
for (int i = 0; i < _timestamps.Count; i++)
|
||||
{
|
||||
if (Timestamps[i].EndTime < before)
|
||||
if (_timestamps[i].EndTime < before)
|
||||
{
|
||||
toRemove++;
|
||||
}
|
||||
|
@ -114,7 +137,19 @@ namespace Ryujinx.Profiler
|
|||
}
|
||||
|
||||
if (toRemove > 0)
|
||||
Timestamps.RemoveRange(0, toRemove);
|
||||
{
|
||||
_timestamps.RemoveRange(0, toRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Timestamp[] GetAllTimestamps()
|
||||
{
|
||||
lock (_timestampListLock)
|
||||
{
|
||||
Timestamp[] returnTimestamps = new Timestamp[_timestamps.Count];
|
||||
_timestamps.CopyTo(returnTimestamps);
|
||||
return returnTimestamps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace Ryujinx.Profiler.UI
|
|||
private const int TitleHeight = 24;
|
||||
private const int TitleFontHeight = 16;
|
||||
private const int LinePadding = 2;
|
||||
private const int ColumnSpacing = 30;
|
||||
private const int ColumnSpacing = 15;
|
||||
private const int FilterHeight = 24;
|
||||
|
||||
// Sorting
|
||||
|
@ -69,6 +69,7 @@ namespace Ryujinx.Profiler.UI
|
|||
|
||||
// Profile data storage
|
||||
private List<KeyValuePair<ProfileConfig, TimingInfo>> _sortedProfileData;
|
||||
private long _captureTime;
|
||||
|
||||
// Input
|
||||
private bool _backspaceDown = false;
|
||||
|
@ -78,7 +79,8 @@ namespace Ryujinx.Profiler.UI
|
|||
// Event management
|
||||
private double _updateTimer;
|
||||
private double _processEventTimer;
|
||||
private bool _profileUpdated = false;
|
||||
private bool _profileUpdated = false;
|
||||
private readonly object _profileDataLock = new object();
|
||||
|
||||
|
||||
public ProfileWindow()
|
||||
|
@ -207,50 +209,47 @@ namespace Ryujinx.Profiler.UI
|
|||
|
||||
// Get timing data if enough time has passed
|
||||
_updateTimer += e.Time;
|
||||
if (!_paused && (_updateTimer > Profile.GetUpdateRate()))
|
||||
if (!_paused && (_updateTimer > Profile.UpdateRate))
|
||||
{
|
||||
_updateTimer = 0;
|
||||
_unsortedProfileData = Profile.GetProfilingData().ToList();
|
||||
_captureTime = Profile.GetCurrentTime();
|
||||
_profileUpdated = true;
|
||||
}
|
||||
|
||||
// Filtering
|
||||
if (_profileUpdated)
|
||||
{
|
||||
if (_showInactive)
|
||||
lock (_profileDataLock)
|
||||
{
|
||||
_sortedProfileData = _unsortedProfileData;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sortedProfileData = _unsortedProfileData.FindAll(kvp => kvp.Value.Instant > 0.001f);
|
||||
}
|
||||
_sortedProfileData = _showInactive ? _unsortedProfileData : _unsortedProfileData.FindAll(kvp => kvp.Value.IsActive);
|
||||
|
||||
if (_sortAction != null)
|
||||
{
|
||||
_sortedProfileData.Sort(_sortAction);
|
||||
}
|
||||
|
||||
if (_regexEnabled)
|
||||
{
|
||||
try
|
||||
if (_sortAction != null)
|
||||
{
|
||||
Regex filterRegex = new Regex(_filterText, RegexOptions.IgnoreCase);
|
||||
if (_filterText != "")
|
||||
_sortedProfileData.Sort(_sortAction);
|
||||
}
|
||||
|
||||
if (_regexEnabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
_sortedProfileData = _sortedProfileData.Where((pair => filterRegex.IsMatch(pair.Key.Search))).ToList();
|
||||
Regex filterRegex = new Regex(_filterText, RegexOptions.IgnoreCase);
|
||||
if (_filterText != "")
|
||||
{
|
||||
_sortedProfileData = _sortedProfileData.Where((pair => filterRegex.IsMatch(pair.Key.Search))).ToList();
|
||||
}
|
||||
}
|
||||
catch (ArgumentException argException)
|
||||
{
|
||||
// Skip filtering for invalid regex
|
||||
}
|
||||
}
|
||||
catch (ArgumentException argException)
|
||||
else
|
||||
{
|
||||
// Skip filtering for invalid regex
|
||||
// Regular filtering
|
||||
_sortedProfileData = _sortedProfileData.Where((pair => pair.Key.Search.ToLower().Contains(_filterText.ToLower()))).ToList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular filtering
|
||||
_sortedProfileData = _sortedProfileData.Where((pair => pair.Key.Search.ToLower().Contains(_filterText.ToLower()))).ToList();
|
||||
}
|
||||
|
||||
_profileUpdated = false;
|
||||
_redrawPending = true;
|
||||
|
@ -316,6 +315,8 @@ namespace Ryujinx.Profiler.UI
|
|||
float maxWidth = 0;
|
||||
float yOffset = _scrollPos - TitleHeight;
|
||||
float xOffset = 10;
|
||||
float timingDataLeft;
|
||||
float timingWidth;
|
||||
|
||||
// Background lines to make reading easier
|
||||
#region Background Lines
|
||||
|
@ -343,115 +344,130 @@ namespace Ryujinx.Profiler.UI
|
|||
GL.End();
|
||||
_maxScroll = (LineHeight + LinePadding) * (_sortedProfileData.Count - 1);
|
||||
#endregion
|
||||
|
||||
// Display category
|
||||
#region Category
|
||||
verticalIndex = 0;
|
||||
foreach (var entry in _sortedProfileData)
|
||||
|
||||
lock (_profileDataLock)
|
||||
{
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
width = _fontService.DrawText(entry.Key.Category, xOffset, y, LineHeight);
|
||||
if (width > maxWidth)
|
||||
// Display category
|
||||
#region Category
|
||||
verticalIndex = 0;
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
maxWidth = width;
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
width = _fontService.DrawText(entry.Key.Category, xOffset, y, LineHeight);
|
||||
|
||||
if (width > maxWidth)
|
||||
{
|
||||
maxWidth = width;
|
||||
}
|
||||
}
|
||||
}
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
width = _fontService.DrawText("Category", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
if (width > maxWidth)
|
||||
maxWidth = width;
|
||||
|
||||
xOffset += maxWidth + ColumnSpacing;
|
||||
#endregion
|
||||
|
||||
// Display session group
|
||||
#region Session Group
|
||||
maxWidth = 0;
|
||||
verticalIndex = 0;
|
||||
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
width = _fontService.DrawText(entry.Key.SessionGroup, xOffset, y, LineHeight);
|
||||
width = _fontService.DrawText("Category", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
if (width > maxWidth)
|
||||
{
|
||||
maxWidth = width;
|
||||
|
||||
xOffset += maxWidth + ColumnSpacing;
|
||||
#endregion
|
||||
|
||||
// Display session group
|
||||
#region Session Group
|
||||
maxWidth = 0;
|
||||
verticalIndex = 0;
|
||||
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
width = _fontService.DrawText(entry.Key.SessionGroup, xOffset, y, LineHeight);
|
||||
|
||||
if (width > maxWidth)
|
||||
{
|
||||
maxWidth = width;
|
||||
}
|
||||
}
|
||||
}
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
width = _fontService.DrawText("Group", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
if (width > maxWidth)
|
||||
maxWidth = width;
|
||||
|
||||
xOffset += maxWidth + ColumnSpacing;
|
||||
#endregion
|
||||
|
||||
// Display session item
|
||||
|
||||
#region Session Item
|
||||
maxWidth = 0;
|
||||
verticalIndex = 0;
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
width = _fontService.DrawText(entry.Key.SessionItem, xOffset, y, LineHeight);
|
||||
width = _fontService.DrawText("Group", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
if (width > maxWidth)
|
||||
{
|
||||
maxWidth = width;
|
||||
|
||||
xOffset += maxWidth + ColumnSpacing;
|
||||
#endregion
|
||||
|
||||
// Display session item
|
||||
#region Session Item
|
||||
maxWidth = 0;
|
||||
verticalIndex = 0;
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
width = _fontService.DrawText(entry.Key.SessionItem, xOffset, y, LineHeight);
|
||||
|
||||
if (width > maxWidth)
|
||||
{
|
||||
maxWidth = width;
|
||||
}
|
||||
}
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
width = _fontService.DrawText("Item", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
if (width > maxWidth)
|
||||
maxWidth = width;
|
||||
|
||||
xOffset += maxWidth + ColumnSpacing;
|
||||
_buttons[(int)ButtonIndex.TagTitle].UpdateSize(0, Height - TitleFontHeight, 0, (int)xOffset, TitleFontHeight);
|
||||
#endregion
|
||||
|
||||
// Timing data
|
||||
timingWidth = Width - xOffset - 370;
|
||||
timingDataLeft = xOffset;
|
||||
|
||||
GL.Scissor((int)xOffset, FilterHeight, (int)timingWidth, Height - TitleHeight - FilterHeight);
|
||||
|
||||
if (_displayGraph)
|
||||
{
|
||||
DrawGraph(xOffset, yOffset, timingWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBars(xOffset, yOffset, timingWidth);
|
||||
}
|
||||
|
||||
GL.Scissor(0, FilterHeight, Width, Height - TitleHeight - FilterHeight);
|
||||
|
||||
if (!_displayGraph)
|
||||
{
|
||||
_fontService.DrawText("Blue: Instant, Green: Avg, Red: Total", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
}
|
||||
|
||||
xOffset = Width - 360;
|
||||
|
||||
// Display timestamps
|
||||
#region Timestamps
|
||||
verticalIndex = 0;
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
_fontService.DrawText($"{Profile.ConvertTicksToMS(entry.Value.Instant):F3} ({entry.Value.InstantCount})", xOffset, y, LineHeight);
|
||||
_fontService.DrawText($"{Profile.ConvertTicksToMS(entry.Value.AverageTime):F3}", 150 + xOffset, y, LineHeight);
|
||||
_fontService.DrawText($"{Profile.ConvertTicksToMS(entry.Value.TotalTime):F3}", 260 + xOffset, y, LineHeight);
|
||||
}
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
float yHeight = Height - TitleFontHeight;
|
||||
|
||||
_fontService.DrawText("Instant (ms, count)", xOffset, yHeight, TitleFontHeight);
|
||||
_buttons[(int)ButtonIndex.InstantTitle].UpdateSize((int)xOffset, (int)yHeight, 0, 130, TitleFontHeight);
|
||||
|
||||
_fontService.DrawText("Average (ms)", 150 + xOffset, yHeight, TitleFontHeight);
|
||||
_buttons[(int)ButtonIndex.AverageTitle].UpdateSize((int)(150 + xOffset), (int)yHeight, 0, 130, TitleFontHeight);
|
||||
|
||||
_fontService.DrawText("Total (ms)", 260 + xOffset, yHeight, TitleFontHeight);
|
||||
_buttons[(int)ButtonIndex.TotalTitle].UpdateSize((int)(260 + xOffset), (int)yHeight, 0, Width, TitleFontHeight);
|
||||
#endregion
|
||||
}
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
width = _fontService.DrawText("Item", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
if (width > maxWidth)
|
||||
maxWidth = width;
|
||||
|
||||
xOffset += maxWidth + ColumnSpacing;
|
||||
_buttons[(int)ButtonIndex.TagTitle].UpdateSize(0, Height - TitleFontHeight, 0, (int)xOffset, TitleFontHeight);
|
||||
#endregion
|
||||
|
||||
// Time bars
|
||||
if (_displayGraph)
|
||||
{
|
||||
DrawGraph(xOffset, yOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBars(xOffset, yOffset);
|
||||
}
|
||||
|
||||
_fontService.DrawText("Blue: Instant, Green: Avg, Red: Total", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
xOffset = Width - 360;
|
||||
|
||||
// Display timestamps
|
||||
|
||||
#region Timestamps
|
||||
verticalIndex = 0;
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
_fontService.DrawText($"{Profile.ConvertTicksToMS(entry.Value.Instant):F3} ({entry.Value.InstantCount})", xOffset, y, LineHeight);
|
||||
_fontService.DrawText($"{Profile.ConvertTicksToMS(entry.Value.AverageTime):F3}", ColumnSpacing + 120 + xOffset, y, LineHeight);
|
||||
_fontService.DrawText($"{Profile.ConvertTicksToMS(entry.Value.TotalTime):F3}", ColumnSpacing + ColumnSpacing + 200 + xOffset, y, LineHeight);
|
||||
}
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
float yHeight = Height - TitleFontHeight;
|
||||
|
||||
_fontService.DrawText("Instant (ms, count)", xOffset, yHeight, TitleFontHeight);
|
||||
_buttons[(int)ButtonIndex.InstantTitle].UpdateSize((int)xOffset, (int)yHeight, 0, (int)(ColumnSpacing + 100), TitleFontHeight);
|
||||
|
||||
_fontService.DrawText("Average (ms)", ColumnSpacing + 120 + xOffset, yHeight, TitleFontHeight);
|
||||
_buttons[(int)ButtonIndex.AverageTitle].UpdateSize((int)(ColumnSpacing + 120 + xOffset), (int)yHeight, 0, (int)(ColumnSpacing + 100), TitleFontHeight);
|
||||
|
||||
_fontService.DrawText("Total (ms)", ColumnSpacing + ColumnSpacing + 200 + xOffset, yHeight, TitleFontHeight);
|
||||
_buttons[(int)ButtonIndex.TotalTitle].UpdateSize((int)(ColumnSpacing + ColumnSpacing + 200 + xOffset), (int)yHeight, 0, Width, TitleFontHeight);
|
||||
#endregion
|
||||
|
||||
#region Bottom bar
|
||||
// Show/Hide Inactive
|
||||
|
@ -495,6 +511,16 @@ namespace Ryujinx.Profiler.UI
|
|||
|
||||
GL.Vertex2(width + 30, 0);
|
||||
GL.Vertex2(width + 30, FilterHeight);
|
||||
|
||||
// Column dividers
|
||||
float timingDataTop = Height - TitleHeight;
|
||||
|
||||
GL.Vertex2(timingDataLeft, FilterHeight);
|
||||
GL.Vertex2(timingDataLeft, timingDataTop);
|
||||
|
||||
|
||||
GL.Vertex2(timingWidth + timingDataLeft, FilterHeight);
|
||||
GL.Vertex2(timingWidth + timingDataLeft, timingDataTop);
|
||||
GL.End();
|
||||
#endregion
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Ryujinx.Profiler.UI
|
|||
{
|
||||
public partial class ProfileWindow
|
||||
{
|
||||
private void DrawBars(float xOffset, float yOffset)
|
||||
private void DrawBars(float xOffset, float yOffset, float width)
|
||||
{
|
||||
if (_sortedProfileData.Count != 0)
|
||||
{
|
||||
|
@ -16,7 +16,6 @@ namespace Ryujinx.Profiler.UI
|
|||
|
||||
int verticalIndex = 0;
|
||||
float barHeight = (LineHeight - LinePadding) / 3.0f;
|
||||
float width = Width - xOffset - 370;
|
||||
|
||||
// Get max values
|
||||
var maxInstant = maxAverage = maxTotal = 0;
|
||||
|
|
|
@ -8,73 +8,72 @@ namespace Ryujinx.Profiler.UI
|
|||
{
|
||||
public partial class ProfileWindow
|
||||
{
|
||||
private void DrawGraph(float xOffset, float yOffset)
|
||||
private void DrawGraph(float xOffset, float yOffset, float width)
|
||||
{
|
||||
if (_sortedProfileData.Count != 0)
|
||||
{
|
||||
long maxAverage;
|
||||
long maxTotal;
|
||||
int left, right;
|
||||
float top, bottom;
|
||||
|
||||
int verticalIndex = 0;
|
||||
float barHeight = (LineHeight - LinePadding) / 3.0f;
|
||||
float width = Width - xOffset - 370;
|
||||
|
||||
// Get max values
|
||||
var maxInstant = maxAverage = maxTotal = 0;
|
||||
foreach (KeyValuePair<ProfileConfig, TimingInfo> kvp in _sortedProfileData)
|
||||
{
|
||||
maxInstant = Math.Max(maxInstant, kvp.Value.Instant);
|
||||
maxAverage = Math.Max(maxAverage, kvp.Value.AverageTime);
|
||||
maxTotal = Math.Max(maxTotal, kvp.Value.TotalTime);
|
||||
}
|
||||
int verticalIndex = 0;
|
||||
float barHeight = (LineHeight - LinePadding);
|
||||
long timeWidth = Profile.HistoryLength;
|
||||
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
GL.Begin(PrimitiveType.Triangles);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
// Instant
|
||||
GL.Color3(Color.Purple);
|
||||
float bottom = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
float top = bottom + barHeight;
|
||||
float right = (float)entry.Value.Instant / maxInstant * width + xOffset;
|
||||
GL.Color3(Color.Green);
|
||||
foreach (Timestamp timestamp in entry.Value.GetAllTimestamps())
|
||||
{
|
||||
left = (int)(xOffset + width - (((float)_captureTime - timestamp.BeginTime) / timeWidth) * width);
|
||||
right = (int)(xOffset + width - (((float)_captureTime - timestamp.EndTime) / timeWidth) * width);
|
||||
bottom = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
|
||||
top = bottom + barHeight;
|
||||
|
||||
// Skip rendering out of bounds bars
|
||||
if (top < 0 || bottom > Height)
|
||||
continue;
|
||||
// Make sure width is at least 1px
|
||||
right = Math.Max(left + 1, right);
|
||||
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
GL.Vertex2(xOffset, top);
|
||||
GL.Vertex2(right, top);
|
||||
// Skip rendering out of bounds bars
|
||||
if (top < 0 || bottom > Height)
|
||||
continue;
|
||||
|
||||
GL.Vertex2(right, top);
|
||||
GL.Vertex2(right, bottom);
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
GL.Vertex2(left, bottom);
|
||||
GL.Vertex2(left, top);
|
||||
GL.Vertex2(right, top);
|
||||
|
||||
// Average
|
||||
GL.Color3(Color.Purple);
|
||||
top += barHeight;
|
||||
bottom += barHeight;
|
||||
right = (float)entry.Value.AverageTime / maxAverage * width + xOffset;
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
GL.Vertex2(xOffset, top);
|
||||
GL.Vertex2(right, top);
|
||||
GL.Vertex2(right, top);
|
||||
GL.Vertex2(right, bottom);
|
||||
GL.Vertex2(left, bottom);
|
||||
}
|
||||
|
||||
GL.Vertex2(right, top);
|
||||
GL.Vertex2(right, bottom);
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
GL.Color3(Color.Red);
|
||||
// Currently capturing timestamp
|
||||
long entryBegin = entry.Value.BeginTime;
|
||||
if (entryBegin != -1)
|
||||
{
|
||||
left = (int)(xOffset + width - (((float)_captureTime - entryBegin) / timeWidth) * width);
|
||||
bottom = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
|
||||
top = bottom + barHeight;
|
||||
right = (int)(xOffset + width);
|
||||
|
||||
// Total
|
||||
GL.Color3(Color.Purple);
|
||||
top += barHeight;
|
||||
bottom += barHeight;
|
||||
right = (float)entry.Value.TotalTime / maxTotal * width + xOffset;
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
GL.Vertex2(xOffset, top);
|
||||
GL.Vertex2(right, top);
|
||||
// Make sure width is at least 1px
|
||||
left = Math.Min(left - 1, right);
|
||||
|
||||
GL.Vertex2(right, top);
|
||||
GL.Vertex2(right, bottom);
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
// Skip rendering out of bounds bars
|
||||
if (top < 0 || bottom > Height)
|
||||
continue;
|
||||
|
||||
GL.Vertex2(left, bottom);
|
||||
GL.Vertex2(left, top);
|
||||
GL.Vertex2(right, top);
|
||||
|
||||
GL.Vertex2(right, top);
|
||||
GL.Vertex2(right, bottom);
|
||||
GL.Vertex2(left, bottom);
|
||||
}
|
||||
|
||||
verticalIndex++;
|
||||
}
|
||||
|
||||
GL.End();
|
||||
|
|
Loading…
Add table
Reference in a new issue