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 class InternalProfile
|
||||||
{
|
{
|
||||||
|
public long CurrentTime => SW.ElapsedTicks;
|
||||||
|
|
||||||
private Stopwatch SW;
|
private Stopwatch SW;
|
||||||
internal ConcurrentDictionary<ProfileConfig, TimingInfo> Timers;
|
internal ConcurrentDictionary<ProfileConfig, TimingInfo> Timers;
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,12 @@ namespace Ryujinx.Profiler
|
||||||
{
|
{
|
||||||
public static class Profile
|
public static class Profile
|
||||||
{
|
{
|
||||||
// Static
|
|
||||||
private static InternalProfile _profileInstance;
|
private static InternalProfile _profileInstance;
|
||||||
private static ProfilerSettings _settings;
|
private static ProfilerSettings _settings;
|
||||||
|
|
||||||
|
public static float UpdateRate => _settings.UpdateRate;
|
||||||
|
public static long HistoryLength => _settings.History;
|
||||||
|
|
||||||
public static bool ProfilingEnabled()
|
public static bool ProfilingEnabled()
|
||||||
{
|
{
|
||||||
if (!_settings.Enabled)
|
if (!_settings.Enabled)
|
||||||
|
@ -89,9 +91,13 @@ namespace Ryujinx.Profiler
|
||||||
return _profileInstance.GetProfilingData();
|
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
|
// Work out average
|
||||||
public long AverageTime => (Count == 0) ? -1 : TotalTime / Count;
|
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
|
// Timestamp collection
|
||||||
public List<Timestamp> Timestamps;
|
private List<Timestamp> _timestamps;
|
||||||
private readonly object timestampLock = new object();
|
private readonly object _timestampLock = new object();
|
||||||
private Timestamp currentTimestamp;
|
private readonly object _timestampListLock = new object();
|
||||||
|
private Timestamp _currentTimestamp;
|
||||||
|
|
||||||
// Depth of current timer,
|
// Depth of current timer,
|
||||||
// each begin call increments and each end call decrements
|
// each begin call increments and each end call decrements
|
||||||
private int depth;
|
private int _depth;
|
||||||
|
|
||||||
|
|
||||||
public TimingInfo()
|
public TimingInfo()
|
||||||
{
|
{
|
||||||
Timestamps = new List<Timestamp>();
|
_timestamps = new List<Timestamp>();
|
||||||
depth = 0;
|
_depth = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Begin(long beginTime)
|
public void Begin(long beginTime)
|
||||||
{
|
{
|
||||||
lock (timestampLock)
|
lock (_timestampLock)
|
||||||
{
|
{
|
||||||
// Finish current timestamp if already running
|
// Finish current timestamp if already running
|
||||||
if (depth > 0)
|
if (_depth > 0)
|
||||||
{
|
{
|
||||||
EndUnsafe(beginTime);
|
EndUnsafe(beginTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
BeginUnsafe(beginTime);
|
BeginUnsafe(beginTime);
|
||||||
depth++;
|
_depth++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BeginUnsafe(long beginTime)
|
private void BeginUnsafe(long beginTime)
|
||||||
{
|
{
|
||||||
currentTimestamp.BeginTime = beginTime;
|
_currentTimestamp.BeginTime = beginTime;
|
||||||
currentTimestamp.EndTime = -1;
|
_currentTimestamp.EndTime = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void End(long endTime)
|
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");
|
throw new Exception("Timing info end called without corresponding begin");
|
||||||
}
|
}
|
||||||
|
@ -73,7 +93,7 @@ namespace Ryujinx.Profiler
|
||||||
EndUnsafe(endTime);
|
EndUnsafe(endTime);
|
||||||
|
|
||||||
// Still have others using this timing info so recreate start for them
|
// Still have others using this timing info so recreate start for them
|
||||||
if (depth > 0)
|
if (_depth > 0)
|
||||||
{
|
{
|
||||||
BeginUnsafe(endTime);
|
BeginUnsafe(endTime);
|
||||||
}
|
}
|
||||||
|
@ -82,10 +102,13 @@ namespace Ryujinx.Profiler
|
||||||
|
|
||||||
private void EndUnsafe(long endTime)
|
private void EndUnsafe(long endTime)
|
||||||
{
|
{
|
||||||
currentTimestamp.EndTime = endTime;
|
_currentTimestamp.EndTime = endTime;
|
||||||
Timestamps.Add(currentTimestamp);
|
lock (_timestampListLock)
|
||||||
|
{
|
||||||
|
_timestamps.Add(_currentTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
var delta = currentTimestamp.EndTime - currentTimestamp.BeginTime;
|
var delta = _currentTimestamp.EndTime - _currentTimestamp.BeginTime;
|
||||||
TotalTime += delta;
|
TotalTime += delta;
|
||||||
Instant += delta;
|
Instant += delta;
|
||||||
|
|
||||||
|
@ -96,13 +119,13 @@ namespace Ryujinx.Profiler
|
||||||
// Remove any timestamps before given timestamp to free memory
|
// Remove any timestamps before given timestamp to free memory
|
||||||
public void Cleanup(long before)
|
public void Cleanup(long before)
|
||||||
{
|
{
|
||||||
lock (timestampLock)
|
lock (_timestampListLock)
|
||||||
{
|
{
|
||||||
int toRemove = 0;
|
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++;
|
toRemove++;
|
||||||
}
|
}
|
||||||
|
@ -114,7 +137,19 @@ namespace Ryujinx.Profiler
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toRemove > 0)
|
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 TitleHeight = 24;
|
||||||
private const int TitleFontHeight = 16;
|
private const int TitleFontHeight = 16;
|
||||||
private const int LinePadding = 2;
|
private const int LinePadding = 2;
|
||||||
private const int ColumnSpacing = 30;
|
private const int ColumnSpacing = 15;
|
||||||
private const int FilterHeight = 24;
|
private const int FilterHeight = 24;
|
||||||
|
|
||||||
// Sorting
|
// Sorting
|
||||||
|
@ -69,6 +69,7 @@ namespace Ryujinx.Profiler.UI
|
||||||
|
|
||||||
// Profile data storage
|
// Profile data storage
|
||||||
private List<KeyValuePair<ProfileConfig, TimingInfo>> _sortedProfileData;
|
private List<KeyValuePair<ProfileConfig, TimingInfo>> _sortedProfileData;
|
||||||
|
private long _captureTime;
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
private bool _backspaceDown = false;
|
private bool _backspaceDown = false;
|
||||||
|
@ -78,7 +79,8 @@ namespace Ryujinx.Profiler.UI
|
||||||
// Event management
|
// Event management
|
||||||
private double _updateTimer;
|
private double _updateTimer;
|
||||||
private double _processEventTimer;
|
private double _processEventTimer;
|
||||||
private bool _profileUpdated = false;
|
private bool _profileUpdated = false;
|
||||||
|
private readonly object _profileDataLock = new object();
|
||||||
|
|
||||||
|
|
||||||
public ProfileWindow()
|
public ProfileWindow()
|
||||||
|
@ -207,50 +209,47 @@ namespace Ryujinx.Profiler.UI
|
||||||
|
|
||||||
// Get timing data if enough time has passed
|
// Get timing data if enough time has passed
|
||||||
_updateTimer += e.Time;
|
_updateTimer += e.Time;
|
||||||
if (!_paused && (_updateTimer > Profile.GetUpdateRate()))
|
if (!_paused && (_updateTimer > Profile.UpdateRate))
|
||||||
{
|
{
|
||||||
_updateTimer = 0;
|
_updateTimer = 0;
|
||||||
_unsortedProfileData = Profile.GetProfilingData().ToList();
|
_unsortedProfileData = Profile.GetProfilingData().ToList();
|
||||||
|
_captureTime = Profile.GetCurrentTime();
|
||||||
_profileUpdated = true;
|
_profileUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtering
|
// Filtering
|
||||||
if (_profileUpdated)
|
if (_profileUpdated)
|
||||||
{
|
{
|
||||||
if (_showInactive)
|
lock (_profileDataLock)
|
||||||
{
|
{
|
||||||
_sortedProfileData = _unsortedProfileData;
|
_sortedProfileData = _showInactive ? _unsortedProfileData : _unsortedProfileData.FindAll(kvp => kvp.Value.IsActive);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_sortedProfileData = _unsortedProfileData.FindAll(kvp => kvp.Value.Instant > 0.001f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_sortAction != null)
|
if (_sortAction != null)
|
||||||
{
|
|
||||||
_sortedProfileData.Sort(_sortAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_regexEnabled)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Regex filterRegex = new Regex(_filterText, RegexOptions.IgnoreCase);
|
_sortedProfileData.Sort(_sortAction);
|
||||||
if (_filterText != "")
|
}
|
||||||
|
|
||||||
|
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;
|
_profileUpdated = false;
|
||||||
_redrawPending = true;
|
_redrawPending = true;
|
||||||
|
@ -316,6 +315,8 @@ namespace Ryujinx.Profiler.UI
|
||||||
float maxWidth = 0;
|
float maxWidth = 0;
|
||||||
float yOffset = _scrollPos - TitleHeight;
|
float yOffset = _scrollPos - TitleHeight;
|
||||||
float xOffset = 10;
|
float xOffset = 10;
|
||||||
|
float timingDataLeft;
|
||||||
|
float timingWidth;
|
||||||
|
|
||||||
// Background lines to make reading easier
|
// Background lines to make reading easier
|
||||||
#region Background Lines
|
#region Background Lines
|
||||||
|
@ -343,115 +344,130 @@ namespace Ryujinx.Profiler.UI
|
||||||
GL.End();
|
GL.End();
|
||||||
_maxScroll = (LineHeight + LinePadding) * (_sortedProfileData.Count - 1);
|
_maxScroll = (LineHeight + LinePadding) * (_sortedProfileData.Count - 1);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
// Display category
|
lock (_profileDataLock)
|
||||||
#region Category
|
|
||||||
verticalIndex = 0;
|
|
||||||
foreach (var entry in _sortedProfileData)
|
|
||||||
{
|
{
|
||||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
// Display category
|
||||||
width = _fontService.DrawText(entry.Key.Category, xOffset, y, LineHeight);
|
#region Category
|
||||||
if (width > maxWidth)
|
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);
|
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)
|
if (width > maxWidth)
|
||||||
{
|
|
||||||
maxWidth = width;
|
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);
|
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)
|
if (width > maxWidth)
|
||||||
{
|
|
||||||
maxWidth = width;
|
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
|
#region Bottom bar
|
||||||
// Show/Hide Inactive
|
// Show/Hide Inactive
|
||||||
|
@ -495,6 +511,16 @@ namespace Ryujinx.Profiler.UI
|
||||||
|
|
||||||
GL.Vertex2(width + 30, 0);
|
GL.Vertex2(width + 30, 0);
|
||||||
GL.Vertex2(width + 30, FilterHeight);
|
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();
|
GL.End();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace Ryujinx.Profiler.UI
|
||||||
{
|
{
|
||||||
public partial class ProfileWindow
|
public partial class ProfileWindow
|
||||||
{
|
{
|
||||||
private void DrawBars(float xOffset, float yOffset)
|
private void DrawBars(float xOffset, float yOffset, float width)
|
||||||
{
|
{
|
||||||
if (_sortedProfileData.Count != 0)
|
if (_sortedProfileData.Count != 0)
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,6 @@ namespace Ryujinx.Profiler.UI
|
||||||
|
|
||||||
int verticalIndex = 0;
|
int verticalIndex = 0;
|
||||||
float barHeight = (LineHeight - LinePadding) / 3.0f;
|
float barHeight = (LineHeight - LinePadding) / 3.0f;
|
||||||
float width = Width - xOffset - 370;
|
|
||||||
|
|
||||||
// Get max values
|
// Get max values
|
||||||
var maxInstant = maxAverage = maxTotal = 0;
|
var maxInstant = maxAverage = maxTotal = 0;
|
||||||
|
|
|
@ -8,73 +8,72 @@ namespace Ryujinx.Profiler.UI
|
||||||
{
|
{
|
||||||
public partial class ProfileWindow
|
public partial class ProfileWindow
|
||||||
{
|
{
|
||||||
private void DrawGraph(float xOffset, float yOffset)
|
private void DrawGraph(float xOffset, float yOffset, float width)
|
||||||
{
|
{
|
||||||
if (_sortedProfileData.Count != 0)
|
if (_sortedProfileData.Count != 0)
|
||||||
{
|
{
|
||||||
long maxAverage;
|
int left, right;
|
||||||
long maxTotal;
|
float top, bottom;
|
||||||
|
|
||||||
int verticalIndex = 0;
|
int verticalIndex = 0;
|
||||||
float barHeight = (LineHeight - LinePadding) / 3.0f;
|
float barHeight = (LineHeight - LinePadding);
|
||||||
float width = Width - xOffset - 370;
|
long timeWidth = Profile.HistoryLength;
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
GL.Enable(EnableCap.ScissorTest);
|
GL.Enable(EnableCap.ScissorTest);
|
||||||
GL.Begin(PrimitiveType.Triangles);
|
GL.Begin(PrimitiveType.Triangles);
|
||||||
foreach (var entry in _sortedProfileData)
|
foreach (var entry in _sortedProfileData)
|
||||||
{
|
{
|
||||||
// Instant
|
GL.Color3(Color.Green);
|
||||||
GL.Color3(Color.Purple);
|
foreach (Timestamp timestamp in entry.Value.GetAllTimestamps())
|
||||||
float bottom = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
{
|
||||||
float top = bottom + barHeight;
|
left = (int)(xOffset + width - (((float)_captureTime - timestamp.BeginTime) / timeWidth) * width);
|
||||||
float right = (float)entry.Value.Instant / maxInstant * width + xOffset;
|
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
|
// Make sure width is at least 1px
|
||||||
if (top < 0 || bottom > Height)
|
right = Math.Max(left + 1, right);
|
||||||
continue;
|
|
||||||
|
|
||||||
GL.Vertex2(xOffset, bottom);
|
// Skip rendering out of bounds bars
|
||||||
GL.Vertex2(xOffset, top);
|
if (top < 0 || bottom > Height)
|
||||||
GL.Vertex2(right, top);
|
continue;
|
||||||
|
|
||||||
GL.Vertex2(right, top);
|
GL.Vertex2(left, bottom);
|
||||||
GL.Vertex2(right, bottom);
|
GL.Vertex2(left, top);
|
||||||
GL.Vertex2(xOffset, bottom);
|
GL.Vertex2(right, top);
|
||||||
|
|
||||||
// Average
|
GL.Vertex2(right, top);
|
||||||
GL.Color3(Color.Purple);
|
GL.Vertex2(right, bottom);
|
||||||
top += barHeight;
|
GL.Vertex2(left, bottom);
|
||||||
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.Color3(Color.Red);
|
||||||
GL.Vertex2(right, bottom);
|
// Currently capturing timestamp
|
||||||
GL.Vertex2(xOffset, bottom);
|
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
|
// Make sure width is at least 1px
|
||||||
GL.Color3(Color.Purple);
|
left = Math.Min(left - 1, right);
|
||||||
top += barHeight;
|
|
||||||
bottom += barHeight;
|
|
||||||
right = (float)entry.Value.TotalTime / maxTotal * width + xOffset;
|
|
||||||
GL.Vertex2(xOffset, bottom);
|
|
||||||
GL.Vertex2(xOffset, top);
|
|
||||||
GL.Vertex2(right, top);
|
|
||||||
|
|
||||||
GL.Vertex2(right, top);
|
// Skip rendering out of bounds bars
|
||||||
GL.Vertex2(right, bottom);
|
if (top < 0 || bottom > Height)
|
||||||
GL.Vertex2(xOffset, bottom);
|
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();
|
GL.End();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue