ProfilerWidget.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. using Gtk;
  2. using Ryujinx.Common;
  3. using Ryujinx.Debugger.Profiler;
  4. using SkiaSharp;
  5. using SkiaSharp.Views.Desktop;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Text.RegularExpressions;
  10. using System.Threading;
  11. using GUI = Gtk.Builder.ObjectAttribute;
  12. namespace Ryujinx.Debugger.UI
  13. {
  14. public class ProfilerWidget : Box
  15. {
  16. private Thread _profilerThread;
  17. private double _prevTime;
  18. private bool _profilerRunning;
  19. private TimingFlag[] _timingFlags;
  20. private bool _initComplete = false;
  21. private bool _redrawPending = true;
  22. private bool _doStep = false;
  23. // Layout
  24. private const int LineHeight = 16;
  25. private const int MinimumColumnWidth = 200;
  26. private const int TitleHeight = 24;
  27. private const int TitleFontHeight = 16;
  28. private const int LinePadding = 2;
  29. private const int ColumnSpacing = 15;
  30. private const int FilterHeight = 24;
  31. private const int BottomBarHeight = FilterHeight + LineHeight;
  32. // Sorting
  33. private List<KeyValuePair<ProfileConfig, TimingInfo>> _unsortedProfileData;
  34. private IComparer<KeyValuePair<ProfileConfig, TimingInfo>> _sortAction = new ProfileSorters.TagAscending();
  35. // Flag data
  36. private long[] _timingFlagsAverages;
  37. private long[] _timingFlagsLast;
  38. // Filtering
  39. private string _filterText = "";
  40. private bool _regexEnabled = false;
  41. // Scrolling
  42. private float _scrollPos = 0;
  43. // Profile data storage
  44. private List<KeyValuePair<ProfileConfig, TimingInfo>> _sortedProfileData;
  45. private long _captureTime;
  46. // Graph
  47. private SKColor[] _timingFlagColors = new[]
  48. {
  49. new SKColor(150, 25, 25, 50), // FrameSwap = 0
  50. new SKColor(25, 25, 150, 50), // SystemFrame = 1
  51. };
  52. private const float GraphMoveSpeed = 40000;
  53. private const float GraphZoomSpeed = 50;
  54. private float _graphZoom = 1;
  55. private float _graphPosition = 0;
  56. private int _rendererHeight => _renderer.AllocatedHeight;
  57. private int _rendererWidth => _renderer.AllocatedWidth;
  58. // Event management
  59. private long _lastOutputUpdate;
  60. private long _lastOutputDraw;
  61. private long _lastOutputUpdateDuration;
  62. private long _lastOutputDrawDuration;
  63. private double _lastFrameTimeMs;
  64. private double _updateTimer;
  65. private bool _profileUpdated = false;
  66. private readonly object _profileDataLock = new object();
  67. private SkRenderer _renderer;
  68. [GUI] ScrolledWindow _scrollview;
  69. [GUI] CheckButton _enableCheckbutton;
  70. [GUI] Scrollbar _outputScrollbar;
  71. [GUI] Entry _filterBox;
  72. [GUI] ComboBox _modeBox;
  73. [GUI] CheckButton _showFlags;
  74. [GUI] CheckButton _showInactive;
  75. [GUI] Button _stepButton;
  76. [GUI] CheckButton _pauseCheckbutton;
  77. public ProfilerWidget() : this(new Builder("Ryujinx.Debugger.UI.ProfilerWidget.glade")) { }
  78. public ProfilerWidget(Builder builder) : base(builder.GetObject("_profilerBox").Handle)
  79. {
  80. builder.Autoconnect(this);
  81. this.KeyPressEvent += ProfilerWidget_KeyPressEvent;
  82. this.Expand = true;
  83. _renderer = new SkRenderer();
  84. _renderer.Expand = true;
  85. _outputScrollbar.ValueChanged += _outputScrollbar_ValueChanged;
  86. _renderer.DrawGraphs += _renderer_DrawGraphs;
  87. _filterBox.Changed += _filterBox_Changed;
  88. _stepButton.Clicked += _stepButton_Clicked;
  89. _scrollview.Add(_renderer);
  90. if (Profile.UpdateRate <= 0)
  91. {
  92. // Perform step regardless of flag type
  93. Profile.RegisterFlagReceiver((t) =>
  94. {
  95. if (_pauseCheckbutton.Active)
  96. {
  97. _doStep = true;
  98. }
  99. });
  100. }
  101. }
  102. private void _stepButton_Clicked(object sender, EventArgs e)
  103. {
  104. if (_pauseCheckbutton.Active)
  105. {
  106. _doStep = true;
  107. }
  108. _profileUpdated = true;
  109. }
  110. private void _filterBox_Changed(object sender, EventArgs e)
  111. {
  112. _filterText = _filterBox.Text;
  113. _profileUpdated = true;
  114. }
  115. private void _outputScrollbar_ValueChanged(object sender, EventArgs e)
  116. {
  117. _scrollPos = -(float)Math.Max(0, _outputScrollbar.Value);
  118. _profileUpdated = true;
  119. }
  120. private void _renderer_DrawGraphs(object sender, EventArgs e)
  121. {
  122. if (e is SKPaintSurfaceEventArgs se)
  123. {
  124. Draw(se.Surface.Canvas);
  125. }
  126. }
  127. public void RegisterParentDebugger(DebuggerWidget debugger)
  128. {
  129. debugger.DebuggerEnabled += Debugger_DebuggerAttached;
  130. debugger.DebuggerDisabled += Debugger_DebuggerDettached;
  131. }
  132. private void Debugger_DebuggerDettached(object sender, EventArgs e)
  133. {
  134. _profilerRunning = false;
  135. if (_profilerThread != null)
  136. {
  137. _profilerThread.Join();
  138. }
  139. }
  140. private void Debugger_DebuggerAttached(object sender, EventArgs e)
  141. {
  142. _profilerRunning = false;
  143. if (_profilerThread != null)
  144. {
  145. _profilerThread.Join();
  146. }
  147. _profilerRunning = true;
  148. _profilerThread = new Thread(UpdateLoop)
  149. {
  150. Name = "Profiler.UpdateThread"
  151. };
  152. _profilerThread.Start();
  153. }
  154. private void ProfilerWidget_KeyPressEvent(object o, Gtk.KeyPressEventArgs args)
  155. {
  156. switch (args.Event.Key)
  157. {
  158. case Gdk.Key.Left:
  159. _graphPosition += (long)(GraphMoveSpeed * _lastFrameTimeMs);
  160. break;
  161. case Gdk.Key.Right:
  162. _graphPosition = Math.Max(_graphPosition - (long)(GraphMoveSpeed * _lastFrameTimeMs), 0);
  163. break;
  164. case Gdk.Key.Up:
  165. _graphZoom = MathF.Min(_graphZoom + (float)(GraphZoomSpeed * _lastFrameTimeMs), 100.0f);
  166. break;
  167. case Gdk.Key.Down:
  168. _graphZoom = MathF.Max(_graphZoom - (float)(GraphZoomSpeed * _lastFrameTimeMs), 1f);
  169. break;
  170. }
  171. _profileUpdated = true;
  172. }
  173. public void UpdateLoop()
  174. {
  175. _lastOutputUpdate = PerformanceCounter.ElapsedTicks;
  176. _lastOutputDraw = PerformanceCounter.ElapsedTicks;
  177. while (_profilerRunning)
  178. {
  179. _lastOutputUpdate = PerformanceCounter.ElapsedTicks;
  180. int timeToSleepMs = (_pauseCheckbutton.Active || !_enableCheckbutton.Active) ? 33 : 1;
  181. if (Profile.ProfilingEnabled() && _enableCheckbutton.Active)
  182. {
  183. double time = (double)PerformanceCounter.ElapsedTicks / PerformanceCounter.TicksPerSecond;
  184. Update(time - _prevTime);
  185. _lastOutputUpdateDuration = PerformanceCounter.ElapsedTicks - _lastOutputUpdate;
  186. _prevTime = time;
  187. Gdk.Threads.AddIdle(1000, ()=>
  188. {
  189. _renderer.QueueDraw();
  190. return true;
  191. });
  192. }
  193. Thread.Sleep(timeToSleepMs);
  194. }
  195. }
  196. public void Update(double frameTime)
  197. {
  198. _lastFrameTimeMs = frameTime;
  199. // Get timing data if enough time has passed
  200. _updateTimer += frameTime;
  201. if (_doStep || ((Profile.UpdateRate > 0) && (!_pauseCheckbutton.Active && (_updateTimer > Profile.UpdateRate))))
  202. {
  203. _updateTimer = 0;
  204. _captureTime = PerformanceCounter.ElapsedTicks;
  205. _timingFlags = Profile.GetTimingFlags();
  206. _doStep = false;
  207. _profileUpdated = true;
  208. _unsortedProfileData = Profile.GetProfilingData();
  209. (_timingFlagsAverages, _timingFlagsLast) = Profile.GetTimingAveragesAndLast();
  210. }
  211. // Filtering
  212. if (_profileUpdated)
  213. {
  214. lock (_profileDataLock)
  215. {
  216. _sortedProfileData = _showInactive.Active ? _unsortedProfileData : _unsortedProfileData.FindAll(kvp => kvp.Value.IsActive);
  217. if (_sortAction != null)
  218. {
  219. _sortedProfileData.Sort(_sortAction);
  220. }
  221. if (_regexEnabled)
  222. {
  223. try
  224. {
  225. Regex filterRegex = new Regex(_filterText, RegexOptions.IgnoreCase);
  226. if (_filterText != "")
  227. {
  228. _sortedProfileData = _sortedProfileData.Where((pair => filterRegex.IsMatch(pair.Key.Search))).ToList();
  229. }
  230. }
  231. catch (ArgumentException argException)
  232. {
  233. // Skip filtering for invalid regex
  234. }
  235. }
  236. else
  237. {
  238. // Regular filtering
  239. _sortedProfileData = _sortedProfileData.Where((pair => pair.Key.Search.ToLower().Contains(_filterText.ToLower()))).ToList();
  240. }
  241. }
  242. _profileUpdated = false;
  243. _redrawPending = true;
  244. _initComplete = true;
  245. }
  246. }
  247. private string GetTimeString(long timestamp)
  248. {
  249. float time = (float)timestamp / PerformanceCounter.TicksPerMillisecond;
  250. return (time < 1) ? $"{time * 1000:F3}us" : $"{time:F3}ms";
  251. }
  252. private void FilterBackspace()
  253. {
  254. if (_filterText.Length <= 1)
  255. {
  256. _filterText = "";
  257. }
  258. else
  259. {
  260. _filterText = _filterText.Remove(_filterText.Length - 1, 1);
  261. }
  262. }
  263. private float GetLineY(float offset, float lineHeight, float padding, bool centre, int line)
  264. {
  265. return offset + lineHeight + padding + ((lineHeight + padding) * line) - ((centre) ? padding : 0);
  266. }
  267. public void Draw(SKCanvas canvas)
  268. {
  269. _lastOutputDraw = PerformanceCounter.ElapsedTicks;
  270. if (!Visible ||
  271. !_initComplete ||
  272. !_enableCheckbutton.Active ||
  273. !_redrawPending)
  274. {
  275. return;
  276. }
  277. float viewTop = TitleHeight + 5;
  278. float viewBottom = _rendererHeight - FilterHeight - LineHeight;
  279. float columnWidth;
  280. float maxColumnWidth = MinimumColumnWidth;
  281. float yOffset = _scrollPos + viewTop;
  282. float xOffset = 10;
  283. float timingWidth;
  284. float contentHeight = GetLineY(0, LineHeight, LinePadding, false, _sortedProfileData.Count - 1);
  285. _outputScrollbar.Adjustment.Upper = contentHeight;
  286. _outputScrollbar.Adjustment.Lower = 0;
  287. _outputScrollbar.Adjustment.PageSize = viewBottom - viewTop;
  288. SKPaint textFont = new SKPaint()
  289. {
  290. Color = SKColors.White,
  291. TextSize = LineHeight
  292. };
  293. SKPaint titleFont = new SKPaint()
  294. {
  295. Color = SKColors.White,
  296. TextSize = TitleFontHeight
  297. };
  298. SKPaint evenItemBackground = new SKPaint()
  299. {
  300. Color = SKColors.Gray
  301. };
  302. canvas.Save();
  303. canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
  304. for (int i = 1; i < _sortedProfileData.Count; i += 2)
  305. {
  306. float top = GetLineY(yOffset, LineHeight, LinePadding, false, i - 1);
  307. float bottom = GetLineY(yOffset, LineHeight, LinePadding, false, i);
  308. canvas.DrawRect(new SKRect(0, top, _rendererWidth, bottom), evenItemBackground);
  309. }
  310. lock (_profileDataLock)
  311. {
  312. // Display category
  313. for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
  314. {
  315. KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
  316. if (entry.Key.Category == null)
  317. {
  318. continue;
  319. }
  320. float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
  321. canvas.DrawText(entry.Key.Category, new SKPoint(xOffset, y), textFont);
  322. columnWidth = textFont.MeasureText(entry.Key.Category);
  323. if (columnWidth > maxColumnWidth)
  324. {
  325. maxColumnWidth = columnWidth;
  326. }
  327. }
  328. canvas.Restore();
  329. canvas.DrawText("Category", new SKPoint(xOffset, TitleFontHeight + 2), titleFont);
  330. columnWidth = titleFont.MeasureText("Category");
  331. if (columnWidth > maxColumnWidth)
  332. {
  333. maxColumnWidth = columnWidth;
  334. }
  335. xOffset += maxColumnWidth + ColumnSpacing;
  336. canvas.DrawLine(new SKPoint(xOffset - ColumnSpacing / 2, 0), new SKPoint(xOffset - ColumnSpacing / 2, viewBottom), textFont);
  337. // Display session group
  338. maxColumnWidth = MinimumColumnWidth;
  339. canvas.Save();
  340. canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
  341. for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
  342. {
  343. KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
  344. if (entry.Key.SessionGroup == null)
  345. {
  346. continue;
  347. }
  348. float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
  349. canvas.DrawText(entry.Key.SessionGroup, new SKPoint(xOffset, y), textFont);
  350. columnWidth = textFont.MeasureText(entry.Key.SessionGroup);
  351. if (columnWidth > maxColumnWidth)
  352. {
  353. maxColumnWidth = columnWidth;
  354. }
  355. }
  356. canvas.Restore();
  357. canvas.DrawText("Group", new SKPoint(xOffset, TitleFontHeight + 2), titleFont);
  358. columnWidth = titleFont.MeasureText("Group");
  359. if (columnWidth > maxColumnWidth)
  360. {
  361. maxColumnWidth = columnWidth;
  362. }
  363. xOffset += maxColumnWidth + ColumnSpacing;
  364. canvas.DrawLine(new SKPoint(xOffset - ColumnSpacing / 2, 0), new SKPoint(xOffset - ColumnSpacing / 2, viewBottom), textFont);
  365. // Display session item
  366. maxColumnWidth = MinimumColumnWidth;
  367. canvas.Save();
  368. canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
  369. for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
  370. {
  371. KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
  372. if (entry.Key.SessionItem == null)
  373. {
  374. continue;
  375. }
  376. float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
  377. canvas.DrawText(entry.Key.SessionItem, new SKPoint(xOffset, y), textFont);
  378. columnWidth = textFont.MeasureText(entry.Key.SessionItem);
  379. if (columnWidth > maxColumnWidth)
  380. {
  381. maxColumnWidth = columnWidth;
  382. }
  383. }
  384. canvas.Restore();
  385. canvas.DrawText("Item", new SKPoint(xOffset, TitleFontHeight + 2), titleFont);
  386. columnWidth = titleFont.MeasureText("Item");
  387. if (columnWidth > maxColumnWidth)
  388. {
  389. maxColumnWidth = columnWidth;
  390. }
  391. xOffset += maxColumnWidth + ColumnSpacing;
  392. timingWidth = _rendererWidth - xOffset - 370;
  393. canvas.Save();
  394. canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
  395. canvas.DrawLine(new SKPoint(xOffset, 0), new SKPoint(xOffset, _rendererHeight), textFont);
  396. int mode = _modeBox.Active;
  397. canvas.Save();
  398. canvas.ClipRect(new SKRect(xOffset, yOffset,xOffset + timingWidth,yOffset + contentHeight),
  399. SKClipOperation.Intersect);
  400. switch (mode)
  401. {
  402. case 0:
  403. DrawGraph(xOffset, yOffset, timingWidth, canvas);
  404. break;
  405. case 1:
  406. DrawBars(xOffset, yOffset, timingWidth, canvas);
  407. canvas.DrawText("Blue: Instant, Green: Avg, Red: Total",
  408. new SKPoint(xOffset, _rendererHeight - TitleFontHeight), titleFont);
  409. break;
  410. }
  411. canvas.Restore();
  412. canvas.DrawLine(new SKPoint(xOffset + timingWidth, 0), new SKPoint(xOffset + timingWidth, _rendererHeight), textFont);
  413. xOffset = _rendererWidth - 360;
  414. // Display timestamps
  415. long totalInstant = 0;
  416. long totalAverage = 0;
  417. long totalTime = 0;
  418. long totalCount = 0;
  419. for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
  420. {
  421. KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
  422. float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
  423. canvas.DrawText($"{GetTimeString(entry.Value.Instant)} ({entry.Value.InstantCount})", new SKPoint(xOffset, y), textFont);
  424. canvas.DrawText(GetTimeString(entry.Value.AverageTime), new SKPoint(150 + xOffset, y), textFont);
  425. canvas.DrawText(GetTimeString(entry.Value.TotalTime), new SKPoint(260 + xOffset, y), textFont);
  426. totalInstant += entry.Value.Instant;
  427. totalAverage += entry.Value.AverageTime;
  428. totalTime += entry.Value.TotalTime;
  429. totalCount += entry.Value.InstantCount;
  430. }
  431. canvas.Restore();
  432. canvas.DrawLine(new SKPoint(0, viewTop), new SKPoint(_rendererWidth, viewTop), titleFont);
  433. float yHeight = 0 + TitleFontHeight;
  434. canvas.DrawText("Instant (Count)", new SKPoint(xOffset, yHeight), titleFont);
  435. canvas.DrawText("Average", new SKPoint(150 + xOffset, yHeight), titleFont);
  436. canvas.DrawText("Total (ms)", new SKPoint(260 + xOffset, yHeight), titleFont);
  437. // Totals
  438. yHeight = _rendererHeight - FilterHeight + 3;
  439. int textHeight = LineHeight - 2;
  440. SKPaint detailFont = new SKPaint()
  441. {
  442. Color = new SKColor(100, 100, 255, 255),
  443. TextSize = textHeight
  444. };
  445. canvas.DrawLine(new SkiaSharp.SKPoint(0, viewBottom), new SkiaSharp.SKPoint(_rendererWidth,viewBottom), textFont);
  446. string hostTimeString = $"Host {GetTimeString(_timingFlagsLast[(int)TimingFlagType.SystemFrame])} " +
  447. $"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.SystemFrame])})";
  448. canvas.DrawText(hostTimeString, new SKPoint(5, yHeight), detailFont);
  449. float tempWidth = detailFont.MeasureText(hostTimeString);
  450. detailFont.Color = SKColors.Red;
  451. string gameTimeString = $"Game {GetTimeString(_timingFlagsLast[(int)TimingFlagType.FrameSwap])} " +
  452. $"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.FrameSwap])})";
  453. canvas.DrawText(gameTimeString, new SKPoint(15 + tempWidth, yHeight), detailFont);
  454. tempWidth += detailFont.MeasureText(gameTimeString);
  455. detailFont.Color = SKColors.White;
  456. canvas.DrawText($"Profiler: Update {GetTimeString(_lastOutputUpdateDuration)} Draw {GetTimeString(_lastOutputDrawDuration)}",
  457. new SKPoint(20 + tempWidth, yHeight), detailFont);
  458. detailFont.Color = SKColors.White;
  459. canvas.DrawText($"{GetTimeString(totalInstant)} ({totalCount})", new SKPoint(xOffset, yHeight), detailFont);
  460. canvas.DrawText(GetTimeString(totalAverage), new SKPoint(150 + xOffset, yHeight), detailFont);
  461. canvas.DrawText(GetTimeString(totalTime), new SKPoint(260 + xOffset, yHeight), detailFont);
  462. _lastOutputDrawDuration = PerformanceCounter.ElapsedTicks - _lastOutputDraw;
  463. }
  464. }
  465. private void DrawGraph(float xOffset, float yOffset, float width, SKCanvas canvas)
  466. {
  467. if (_sortedProfileData.Count != 0)
  468. {
  469. int left, right;
  470. float top, bottom;
  471. float graphRight = xOffset + width;
  472. float barHeight = (LineHeight - LinePadding);
  473. long history = Profile.HistoryLength;
  474. double timeWidthTicks = history / (double)_graphZoom;
  475. long graphPositionTicks = (long)(_graphPosition * PerformanceCounter.TicksPerMillisecond);
  476. long ticksPerPixel = (long)(timeWidthTicks / width);
  477. // Reset start point if out of bounds
  478. if (timeWidthTicks + graphPositionTicks > history)
  479. {
  480. graphPositionTicks = history - (long)timeWidthTicks;
  481. _graphPosition = (float)graphPositionTicks / PerformanceCounter.TicksPerMillisecond;
  482. }
  483. graphPositionTicks = _captureTime - graphPositionTicks;
  484. // Draw timing flags
  485. if (_showFlags.Active)
  486. {
  487. TimingFlagType prevType = TimingFlagType.Count;
  488. SKPaint timingPaint = new SKPaint
  489. {
  490. Color = _timingFlagColors.First()
  491. };
  492. foreach (TimingFlag timingFlag in _timingFlags)
  493. {
  494. if (prevType != timingFlag.FlagType)
  495. {
  496. prevType = timingFlag.FlagType;
  497. timingPaint.Color = _timingFlagColors[(int)prevType];
  498. }
  499. int x = (int)(graphRight - ((graphPositionTicks - timingFlag.Timestamp) / timeWidthTicks) * width);
  500. if (x > xOffset)
  501. {
  502. canvas.DrawLine(new SKPoint(x, yOffset), new SKPoint(x, _rendererHeight), timingPaint);
  503. }
  504. }
  505. }
  506. SKPaint barPaint = new SKPaint()
  507. {
  508. Color = SKColors.Green,
  509. };
  510. // Draw bars
  511. for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
  512. {
  513. KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
  514. long furthest = 0;
  515. bottom = GetLineY(yOffset, LineHeight, LinePadding, false, verticalIndex);
  516. top = bottom + barHeight;
  517. // Skip rendering out of bounds bars
  518. if (top < 0 || bottom > _rendererHeight)
  519. {
  520. continue;
  521. }
  522. barPaint.Color = SKColors.Green;
  523. foreach (Timestamp timestamp in entry.Value.GetAllTimestamps())
  524. {
  525. // Skip drawing multiple timestamps on same pixel
  526. if (timestamp.EndTime < furthest)
  527. {
  528. continue;
  529. }
  530. furthest = timestamp.EndTime + ticksPerPixel;
  531. left = (int)(graphRight - ((graphPositionTicks - timestamp.BeginTime) / timeWidthTicks) * width);
  532. right = (int)(graphRight - ((graphPositionTicks - timestamp.EndTime) / timeWidthTicks) * width);
  533. left = (int)Math.Max(xOffset +1, left);
  534. // Make sure width is at least 1px
  535. right = Math.Max(left + 1, right);
  536. canvas.DrawRect(new SKRect(left, top, right, bottom), barPaint);
  537. }
  538. // Currently capturing timestamp
  539. barPaint.Color = SKColors.Red;
  540. long entryBegin = entry.Value.BeginTime;
  541. if (entryBegin != -1)
  542. {
  543. left = (int)(graphRight - ((graphPositionTicks - entryBegin) / timeWidthTicks) * width);
  544. // Make sure width is at least 1px
  545. left = Math.Min(left - 1, (int)graphRight);
  546. left = (int)Math.Max(xOffset + 1, left);
  547. canvas.DrawRect(new SKRect(left, top, graphRight, bottom), barPaint);
  548. }
  549. }
  550. string label = $"-{MathF.Round(_graphPosition, 2)} ms";
  551. SKPaint labelPaint = new SKPaint()
  552. {
  553. Color = SKColors.White,
  554. TextSize = LineHeight
  555. };
  556. float labelWidth = labelPaint.MeasureText(label);
  557. canvas.DrawText(label,new SKPoint(graphRight - labelWidth - LinePadding, FilterHeight + LinePadding) , labelPaint);
  558. canvas.DrawText($"-{MathF.Round((float)((timeWidthTicks / PerformanceCounter.TicksPerMillisecond) + _graphPosition), 2)} ms",
  559. new SKPoint(xOffset + LinePadding, FilterHeight + LinePadding), labelPaint);
  560. }
  561. }
  562. private void DrawBars(float xOffset, float yOffset, float width, SKCanvas canvas)
  563. {
  564. if (_sortedProfileData.Count != 0)
  565. {
  566. long maxAverage = 0;
  567. long maxTotal = 0;
  568. long maxInstant = 0;
  569. float barHeight = (LineHeight - LinePadding) / 3.0f;
  570. // Get max values
  571. foreach (KeyValuePair<ProfileConfig, TimingInfo> kvp in _sortedProfileData)
  572. {
  573. maxInstant = Math.Max(maxInstant, kvp.Value.Instant);
  574. maxAverage = Math.Max(maxAverage, kvp.Value.AverageTime);
  575. maxTotal = Math.Max(maxTotal, kvp.Value.TotalTime);
  576. }
  577. SKPaint barPaint = new SKPaint()
  578. {
  579. Color = SKColors.Blue
  580. };
  581. for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
  582. {
  583. KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
  584. // Instant
  585. barPaint.Color = SKColors.Blue;
  586. float bottom = GetLineY(yOffset, LineHeight, LinePadding, false, verticalIndex);
  587. float top = bottom + barHeight;
  588. float right = (float)entry.Value.Instant / maxInstant * width + xOffset;
  589. // Skip rendering out of bounds bars
  590. if (top < 0 || bottom > _rendererHeight)
  591. {
  592. continue;
  593. }
  594. canvas.DrawRect(new SKRect(xOffset, top, right, bottom), barPaint);
  595. // Average
  596. barPaint.Color = SKColors.Green;
  597. top += barHeight;
  598. bottom += barHeight;
  599. right = (float)entry.Value.AverageTime / maxAverage * width + xOffset;
  600. canvas.DrawRect(new SKRect(xOffset, top, right, bottom), barPaint);
  601. // Total
  602. barPaint.Color = SKColors.Red;
  603. top += barHeight;
  604. bottom += barHeight;
  605. right = (float)entry.Value.TotalTime / maxTotal * width + xOffset;
  606. canvas.DrawRect(new SKRect(xOffset, top, right, bottom), barPaint);
  607. }
  608. }
  609. }
  610. }
  611. }