ProfilerWidget.cs 29 KB

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