TimeRuler.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // TimeRuler.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Diagnostics;
  13. using System.Threading;
  14. using System.Text;
  15. using Microsoft.Xna.Framework;
  16. using Microsoft.Xna.Framework.Graphics;
  17. #endregion
  18. namespace PerformanceMeasuring.GameDebugTools
  19. {
  20. /// <summary>
  21. /// Realtime CPU measuring tool
  22. /// </summary>
  23. /// <remarks>
  24. /// You can visually find bottle neck, and know how much you can put more CPU jobs
  25. /// by using this tool.
  26. /// Because of this is real time profile, you can find glitches in the game too.
  27. ///
  28. /// TimeRuler provide the following features:
  29. /// * Up to 8 bars (Configurable)
  30. /// * Change colors for each markers
  31. /// * Marker logging.
  32. /// * It won't even generate BeginMark/EndMark method calls when you got rid of the
  33. /// TRACE constant.
  34. /// * It supports up to 32 (Configurable) nested BeginMark method calls.
  35. /// * Multithreaded safe
  36. /// * Automatically changes display frames based on frame duration.
  37. ///
  38. /// How to use:
  39. /// Added TimerRuler instance to Game.Components and call timerRuler.StartFrame in
  40. /// top of the Game.Update method.
  41. ///
  42. /// Then, surround the code that you want measure by BeginMark and EndMark.
  43. ///
  44. /// timeRuler.BeginMark( "Update", Color.Blue );
  45. /// // process that you want to measure.
  46. /// timerRuler.EndMark( "Update" );
  47. ///
  48. /// Also, you can specify bar index of marker (default value is 0)
  49. ///
  50. /// timeRuler.BeginMark( 1, "Update", Color.Blue );
  51. ///
  52. /// All profiling methods has CondionalAttribute with "TRACE".
  53. /// If you not specified "TRACE" constant, it doesn't even generate
  54. /// method calls for BeginMark/EndMark.
  55. /// So, don't forget remove "TRACE" constant when you release your game.
  56. ///
  57. /// </remarks>
  58. public class TimeRuler : DrawableGameComponent
  59. {
  60. #region Constants
  61. /// <summary>
  62. /// Max bar count.
  63. /// </summary>
  64. const int MaxBars = 8;
  65. /// <summary>
  66. /// Maximum sample number for each bar.
  67. /// </summary>
  68. const int MaxSamples = 256;
  69. /// <summary>
  70. /// Maximum nest calls for each bar.
  71. /// </summary>
  72. const int MaxNestCall = 32;
  73. /// <summary>
  74. /// Maximum display frames.
  75. /// </summary>
  76. const int MaxSampleFrames = 4;
  77. /// <summary>
  78. /// Duration (in frame count) for take snap shot of log.
  79. /// </summary>
  80. const int LogSnapDuration = 120;
  81. /// <summary>
  82. /// Height(in pixels) of bar.
  83. /// </summary>
  84. const int BarHeight = 8;
  85. /// <summary>
  86. /// Padding(in pixels) of bar.
  87. /// </summary>
  88. const int BarPadding = 2;
  89. /// <summary>
  90. /// Delay frame count for auto display frame adjustment.
  91. /// </summary>
  92. const int AutoAdjustDelay = 30;
  93. #endregion
  94. #region Properties
  95. /// <summary>
  96. /// Gets/Set log display or no.
  97. /// </summary>
  98. public bool ShowLog { get; set; }
  99. /// <summary>
  100. /// Gets/Sets target sample frames.
  101. /// </summary>
  102. public int TargetSampleFrames { get; set; }
  103. /// <summary>
  104. /// Gets/Sets TimeRuler rendering position.
  105. /// </summary>
  106. public Vector2 Position { get { return position; } set { position = value; } }
  107. /// <summary>
  108. /// Gets/Sets timer ruler width.
  109. /// </summary>
  110. public int Width { get; set; }
  111. #endregion
  112. #region Fields
  113. #if TRACE
  114. /// <summary>
  115. /// Marker structure.
  116. /// </summary>
  117. private struct Marker
  118. {
  119. public int MarkerId;
  120. public float BeginTime;
  121. public float EndTime;
  122. public Color Color;
  123. }
  124. /// <summary>
  125. /// Collection of markers.
  126. /// </summary>
  127. private class MarkerCollection
  128. {
  129. // Marker collection.
  130. public Marker[] Markers = new Marker[MaxSamples];
  131. public int MarkCount;
  132. // Marker nest information.
  133. public int[] MarkerNests = new int[MaxNestCall];
  134. public int NestCount;
  135. }
  136. /// <summary>
  137. /// Frame logging information.
  138. /// </summary>
  139. private class FrameLog
  140. {
  141. public MarkerCollection[] Bars;
  142. public FrameLog()
  143. {
  144. // Initialize markers.
  145. Bars = new MarkerCollection[MaxBars];
  146. for (int i = 0; i < MaxBars; ++i)
  147. Bars[i] = new MarkerCollection();
  148. }
  149. }
  150. /// <summary>
  151. /// Marker information
  152. /// </summary>
  153. private class MarkerInfo
  154. {
  155. // Name of marker.
  156. public string Name;
  157. // Marker log.
  158. public MarkerLog[] Logs = new MarkerLog[MaxBars];
  159. public MarkerInfo(string name)
  160. {
  161. Name = name;
  162. }
  163. }
  164. /// <summary>
  165. /// Marker log information.
  166. /// </summary>
  167. private struct MarkerLog
  168. {
  169. public float SnapMin;
  170. public float SnapMax;
  171. public float SnapAvg;
  172. public float Min;
  173. public float Max;
  174. public float Avg;
  175. public int Samples;
  176. public Color Color;
  177. public bool Initialized;
  178. }
  179. // Reference of debug manager.
  180. DebugManager debugManager;
  181. // Logs for each frames.
  182. FrameLog[] logs;
  183. // Previous frame log.
  184. FrameLog prevLog;
  185. // Current log.
  186. FrameLog curLog;
  187. // Current frame count.
  188. int frameCount;
  189. // Stopwatch for measure the time.
  190. Stopwatch stopwatch = new Stopwatch();
  191. // Marker information array.
  192. List<MarkerInfo> markers = new List<MarkerInfo>();
  193. // Dictionary that maps from marker name to marker id.
  194. Dictionary<string, int> markerNameToIdMap = new Dictionary<string, int>();
  195. // Display frame adjust counter.
  196. int frameAdjust;
  197. // Current display frame count.
  198. int sampleFrames;
  199. // Marker log string.
  200. StringBuilder logString = new StringBuilder(512);
  201. // You want to call StartFrame at beginning of Game.Update method.
  202. // But Game.Update gets calls multiple time when game runs slow in fixed time step mode.
  203. // In this case, we should ignore StartFrame call.
  204. // To do this, we just keep tracking of number of StartFrame calls until Draw gets called.
  205. int updateCount;
  206. #endif
  207. // TimerRuler draw position.
  208. Vector2 position;
  209. #endregion
  210. #region Initialization
  211. public TimeRuler(Game game)
  212. : base(game)
  213. {
  214. // Add this as a service.
  215. Game.Services.AddService(typeof(TimeRuler), this);
  216. }
  217. public override void Initialize()
  218. {
  219. #if TRACE
  220. debugManager =
  221. Game.Services.GetService(typeof(DebugManager)) as DebugManager;
  222. if (debugManager == null)
  223. throw new InvalidOperationException("DebugManager is not registered.");
  224. // Add "tr" command if DebugCommandHost is registered.
  225. IDebugCommandHost host =
  226. Game.Services.GetService(typeof(IDebugCommandHost))
  227. as IDebugCommandHost;
  228. if (host != null)
  229. {
  230. host.RegisterCommand("tr", "TimeRuler", this.CommandExecute);
  231. this.Visible = true;
  232. }
  233. // Initialize Parameters.
  234. logs = new FrameLog[2];
  235. for (int i = 0; i < logs.Length; ++i)
  236. logs[i] = new FrameLog();
  237. sampleFrames = TargetSampleFrames = 1;
  238. // Time-Ruler's update method doesn't need to get called.
  239. this.Enabled = false;
  240. #endif
  241. base.Initialize();
  242. }
  243. protected override void LoadContent()
  244. {
  245. Width = (int)(GraphicsDevice.Viewport.Width * 0.8f);
  246. Layout layout = new Layout(GraphicsDevice.Viewport);
  247. position = layout.Place(new Vector2(Width, BarHeight),
  248. 0, 0.01f, Alignment.BottomCenter);
  249. base.LoadContent();
  250. }
  251. #if TRACE
  252. /// <summary>
  253. /// 'tr' command execution.
  254. /// </summary>
  255. void CommandExecute(IDebugCommandHost host, string command,
  256. IList<string> arguments)
  257. {
  258. bool previousVisible = Visible;
  259. if (arguments.Count == 0)
  260. Visible = !Visible;
  261. char[] subArgSeparator = new[] { ':' };
  262. foreach (string orgArg in arguments)
  263. {
  264. string arg = orgArg.ToLower();
  265. string[] subargs = arg.Split(subArgSeparator);
  266. switch (subargs[0])
  267. {
  268. case "on":
  269. Visible = true;
  270. break;
  271. case "off":
  272. Visible = false;
  273. break;
  274. case "reset":
  275. ResetLog();
  276. break;
  277. case "log":
  278. if (subargs.Length > 1)
  279. {
  280. if (String.Compare(subargs[1], "on") == 0)
  281. ShowLog = true;
  282. if (String.Compare(subargs[1], "off") == 0)
  283. ShowLog = false;
  284. }
  285. else
  286. {
  287. ShowLog = !ShowLog;
  288. }
  289. break;
  290. case "frame":
  291. int a = Int32.Parse(subargs[1]);
  292. a = Math.Max(a, 1);
  293. a = Math.Min(a, MaxSampleFrames);
  294. TargetSampleFrames = a;
  295. break;
  296. case "/?":
  297. case "--help":
  298. host.Echo("tr [log|on|off|reset|frame]");
  299. host.Echo("Options:");
  300. host.Echo(" on Display TimeRuler.");
  301. host.Echo(" off Hide TimeRuler.");
  302. host.Echo(" log Show/Hide marker log.");
  303. host.Echo(" reset Reset marker log.");
  304. host.Echo(" frame:sampleFrames");
  305. host.Echo(" Change target sample frame count");
  306. break;
  307. default:
  308. break;
  309. }
  310. }
  311. // Reset update count when Visible state changed.
  312. if (Visible != previousVisible)
  313. {
  314. Interlocked.Exchange(ref updateCount, 0);
  315. }
  316. }
  317. #endif
  318. #endregion
  319. #region Measuring methods
  320. /// <summary>
  321. /// Start new frame.
  322. /// </summary>
  323. [Conditional("TRACE")]
  324. public void StartFrame()
  325. {
  326. #if TRACE
  327. lock (this)
  328. {
  329. // We skip reset frame when this method gets called multiple times.
  330. int count = Interlocked.Increment(ref updateCount);
  331. if (Visible && (1 < count && count < MaxSampleFrames))
  332. return;
  333. // Update current frame log.
  334. prevLog = logs[frameCount++ & 0x1];
  335. curLog = logs[frameCount & 0x1];
  336. float endFrameTime = (float)stopwatch.Elapsed.TotalMilliseconds;
  337. // Update marker and create a log.
  338. for (int barIdx = 0; barIdx < prevLog.Bars.Length; ++barIdx)
  339. {
  340. MarkerCollection prevBar = prevLog.Bars[barIdx];
  341. MarkerCollection nextBar = curLog.Bars[barIdx];
  342. // Re-open marker that didn't get called EndMark in previous frame.
  343. for (int nest = 0; nest < prevBar.NestCount; ++nest)
  344. {
  345. int markerIdx = prevBar.MarkerNests[nest];
  346. prevBar.Markers[markerIdx].EndTime = endFrameTime;
  347. nextBar.MarkerNests[nest] = nest;
  348. nextBar.Markers[nest].MarkerId =
  349. prevBar.Markers[markerIdx].MarkerId;
  350. nextBar.Markers[nest].BeginTime = 0;
  351. nextBar.Markers[nest].EndTime = -1;
  352. nextBar.Markers[nest].Color = prevBar.Markers[markerIdx].Color;
  353. }
  354. // Update marker log.
  355. for (int markerIdx = 0; markerIdx < prevBar.MarkCount; ++markerIdx)
  356. {
  357. float duration = prevBar.Markers[markerIdx].EndTime -
  358. prevBar.Markers[markerIdx].BeginTime;
  359. int markerId = prevBar.Markers[markerIdx].MarkerId;
  360. MarkerInfo m = markers[markerId];
  361. m.Logs[barIdx].Color = prevBar.Markers[markerIdx].Color;
  362. if (!m.Logs[barIdx].Initialized)
  363. {
  364. // First frame process.
  365. m.Logs[barIdx].Min = duration;
  366. m.Logs[barIdx].Max = duration;
  367. m.Logs[barIdx].Avg = duration;
  368. m.Logs[barIdx].Initialized = true;
  369. }
  370. else
  371. {
  372. // Process after first frame.
  373. m.Logs[barIdx].Min = Math.Min(m.Logs[barIdx].Min, duration);
  374. m.Logs[barIdx].Max = Math.Min(m.Logs[barIdx].Max, duration);
  375. m.Logs[barIdx].Avg += duration;
  376. m.Logs[barIdx].Avg *= 0.5f;
  377. if (m.Logs[barIdx].Samples++ >= LogSnapDuration)
  378. {
  379. m.Logs[barIdx].SnapMin = m.Logs[barIdx].Min;
  380. m.Logs[barIdx].SnapMax = m.Logs[barIdx].Max;
  381. m.Logs[barIdx].SnapAvg = m.Logs[barIdx].Avg;
  382. m.Logs[barIdx].Samples = 0;
  383. }
  384. }
  385. }
  386. nextBar.MarkCount = prevBar.NestCount;
  387. nextBar.NestCount = prevBar.NestCount;
  388. }
  389. // Start measuring.
  390. stopwatch.Reset();
  391. stopwatch.Start();
  392. }
  393. #endif
  394. }
  395. /// <summary>
  396. /// Start measure time.
  397. /// </summary>
  398. /// <param name="markerName">name of marker.</param>
  399. /// <param name="color">color/param>
  400. [Conditional("TRACE")]
  401. public void BeginMark(string markerName, Color color)
  402. {
  403. #if TRACE
  404. BeginMark(0, markerName, color);
  405. #endif
  406. }
  407. /// <summary>
  408. /// Start measure time.
  409. /// </summary>
  410. /// <param name="barIndex">index of bar</param>
  411. /// <param name="markerName">name of marker.</param>
  412. /// <param name="color">color/param>
  413. [Conditional("TRACE")]
  414. public void BeginMark(int barIndex, string markerName, Color color)
  415. {
  416. #if TRACE
  417. lock (this)
  418. {
  419. if (barIndex < 0 || barIndex >= MaxBars)
  420. throw new ArgumentOutOfRangeException("barIndex");
  421. MarkerCollection bar = curLog.Bars[barIndex];
  422. if (bar.MarkCount >= MaxSamples)
  423. {
  424. throw new OverflowException(
  425. "Exceeded sample count.\n" +
  426. "Either set larger number to TimeRuler.MaxSmpale or" +
  427. "lower sample count.");
  428. }
  429. if (bar.NestCount >= MaxNestCall)
  430. {
  431. throw new OverflowException(
  432. "Exceeded nest count.\n" +
  433. "Either set larget number to TimeRuler.MaxNestCall or" +
  434. "lower nest calls.");
  435. }
  436. // Gets registered marker.
  437. int markerId;
  438. if (!markerNameToIdMap.TryGetValue(markerName, out markerId))
  439. {
  440. // Register this if this marker is not registered.
  441. markerId = markers.Count;
  442. markerNameToIdMap.Add(markerName, markerId);
  443. markers.Add(new MarkerInfo(markerName));
  444. }
  445. // Start measuring.
  446. bar.MarkerNests[bar.NestCount++] = bar.MarkCount;
  447. // Fill marker parameters.
  448. bar.Markers[bar.MarkCount].MarkerId = markerId;
  449. bar.Markers[bar.MarkCount].Color = color;
  450. bar.Markers[bar.MarkCount].BeginTime =
  451. (float)stopwatch.Elapsed.TotalMilliseconds;
  452. bar.Markers[bar.MarkCount].EndTime = -1;
  453. bar.MarkCount++;
  454. }
  455. #endif
  456. }
  457. /// <summary>
  458. /// End measuring.
  459. /// </summary>
  460. /// <param name="markerName">Name of marker.</param>
  461. [Conditional("TRACE")]
  462. public void EndMark(string markerName)
  463. {
  464. #if TRACE
  465. EndMark(0, markerName);
  466. #endif
  467. }
  468. /// <summary>
  469. /// End measuring.
  470. /// </summary>
  471. /// <param name="barIndex">Index of bar.</param>
  472. /// <param name="markerName">Name of marker.</param>
  473. [Conditional("TRACE")]
  474. public void EndMark(int barIndex, string markerName)
  475. {
  476. #if TRACE
  477. lock (this)
  478. {
  479. if (barIndex < 0 || barIndex >= MaxBars)
  480. throw new ArgumentOutOfRangeException("barIndex");
  481. MarkerCollection bar = curLog.Bars[barIndex];
  482. if (bar.NestCount <= 0)
  483. {
  484. throw new InvalidOperationException(
  485. "Call BeingMark method before call EndMark method.");
  486. }
  487. int markerId;
  488. if (!markerNameToIdMap.TryGetValue(markerName, out markerId))
  489. {
  490. throw new InvalidOperationException(
  491. String.Format("Maker '{0}' is not registered." +
  492. "Make sure you specifed same name as you used for BeginMark" +
  493. " method.",
  494. markerName));
  495. }
  496. int markerIdx = bar.MarkerNests[--bar.NestCount];
  497. if (bar.Markers[markerIdx].MarkerId != markerId)
  498. {
  499. throw new InvalidOperationException(
  500. "Incorrect call order of BeginMark/EndMark method." +
  501. "You call it like BeginMark(A), BeginMark(B), EndMark(B), EndMark(A)" +
  502. " But you can't call it like " +
  503. "BeginMark(A), BeginMark(B), EndMark(A), EndMark(B).");
  504. }
  505. bar.Markers[markerIdx].EndTime =
  506. (float)stopwatch.Elapsed.TotalMilliseconds;
  507. }
  508. #endif
  509. }
  510. /// <summary>
  511. /// Get average time of given bar index and marker name.
  512. /// </summary>
  513. /// <param name="barIndex">Index of bar</param>
  514. /// <param name="markerName">name of marker</param>
  515. /// <returns>average spending time in ms.</returns>
  516. public float GetAverageTime(int barIndex, string markerName)
  517. {
  518. #if TRACE
  519. if (barIndex < 0 || barIndex >= MaxBars)
  520. throw new ArgumentOutOfRangeException("barIndex");
  521. float result = 0;
  522. int markerId;
  523. if (markerNameToIdMap.TryGetValue(markerName, out markerId))
  524. result = markers[markerId].Logs[barIndex].Avg;
  525. return result;
  526. #else
  527. return 0f;
  528. #endif
  529. }
  530. /// <summary>
  531. /// Reset marker log.
  532. /// </summary>
  533. [Conditional("TRACE")]
  534. public void ResetLog()
  535. {
  536. #if TRACE
  537. lock (this)
  538. {
  539. foreach (MarkerInfo markerInfo in markers)
  540. {
  541. for (int i = 0; i < markerInfo.Logs.Length; ++i)
  542. {
  543. markerInfo.Logs[i].Initialized = false;
  544. markerInfo.Logs[i].SnapMin = 0;
  545. markerInfo.Logs[i].SnapMax = 0;
  546. markerInfo.Logs[i].SnapAvg = 0;
  547. markerInfo.Logs[i].Min = 0;
  548. markerInfo.Logs[i].Max = 0;
  549. markerInfo.Logs[i].Avg = 0;
  550. markerInfo.Logs[i].Samples = 0;
  551. }
  552. }
  553. }
  554. #endif
  555. }
  556. #endregion
  557. #region Draw
  558. public override void Draw(GameTime gameTime)
  559. {
  560. Draw(position, Width);
  561. base.Draw(gameTime);
  562. }
  563. [Conditional("TRACE")]
  564. public void Draw(Vector2 position, int width)
  565. {
  566. #if TRACE
  567. // Reset update count.
  568. Interlocked.Exchange(ref updateCount, 0);
  569. // Gets SpriteBatch, SpriteFont, and WhiteTexture from DebugManager.
  570. SpriteBatch spriteBatch = debugManager.SpriteBatch;
  571. SpriteFont font = debugManager.DebugFont;
  572. Texture2D texture = debugManager.WhiteTexture;
  573. // Adjust size and position based of number of bars we should draw.
  574. int height = 0;
  575. float maxTime = 0;
  576. foreach (MarkerCollection bar in prevLog.Bars)
  577. {
  578. if (bar.MarkCount > 0)
  579. {
  580. height += BarHeight + BarPadding * 2;
  581. maxTime = Math.Max(maxTime,
  582. bar.Markers[bar.MarkCount - 1].EndTime);
  583. }
  584. }
  585. // Auto display frame adjustment.
  586. // For example, if the entire process of frame doesn't finish in less than 16.6ms
  587. // thin it will adjust display frame duration as 33.3ms.
  588. const float frameSpan = 1.0f / 60.0f * 1000f;
  589. float sampleSpan = (float)sampleFrames * frameSpan;
  590. if (maxTime > sampleSpan)
  591. frameAdjust = Math.Max(0, frameAdjust) + 1;
  592. else
  593. frameAdjust = Math.Min(0, frameAdjust) - 1;
  594. if (Math.Abs(frameAdjust) > AutoAdjustDelay)
  595. {
  596. sampleFrames = Math.Min(MaxSampleFrames, sampleFrames);
  597. sampleFrames =
  598. Math.Max(TargetSampleFrames, (int)(maxTime / frameSpan) + 1);
  599. frameAdjust = 0;
  600. }
  601. // Compute factor that converts from ms to pixel.
  602. float msToPs = (float)width / sampleSpan;
  603. // Draw start position.
  604. int startY = (int)position.Y - (height - BarHeight);
  605. // Current y position.
  606. int y = startY;
  607. spriteBatch.Begin();
  608. // Draw transparency background.
  609. Rectangle rc = new Rectangle((int)position.X, y, width, height);
  610. spriteBatch.Draw(texture, rc, new Color(0, 0, 0, 128));
  611. // Draw markers for each bars.
  612. rc.Height = BarHeight;
  613. foreach (MarkerCollection bar in prevLog.Bars)
  614. {
  615. rc.Y = y + BarPadding;
  616. if (bar.MarkCount > 0)
  617. {
  618. for (int j = 0; j < bar.MarkCount; ++j)
  619. {
  620. float bt = bar.Markers[j].BeginTime;
  621. float et = bar.Markers[j].EndTime;
  622. int sx = (int)(position.X + bt * msToPs);
  623. int ex = (int)(position.X + et * msToPs);
  624. rc.X = sx;
  625. rc.Width = Math.Max(ex - sx, 1);
  626. spriteBatch.Draw(texture, rc, bar.Markers[j].Color);
  627. }
  628. }
  629. y += BarHeight + BarPadding;
  630. }
  631. // Draw grid lines.
  632. // Each grid represents ms.
  633. rc = new Rectangle((int)position.X, (int)startY, 1, height);
  634. for (float t = 1.0f; t < sampleSpan; t += 1.0f)
  635. {
  636. rc.X = (int)(position.X + t * msToPs);
  637. spriteBatch.Draw(texture, rc, Color.Gray);
  638. }
  639. // Draw frame grid.
  640. for (int i = 0; i <= sampleFrames; ++i)
  641. {
  642. rc.X = (int)(position.X + frameSpan * (float)i * msToPs);
  643. spriteBatch.Draw(texture, rc, Color.White);
  644. }
  645. // Draw log.
  646. if (ShowLog)
  647. {
  648. // Generate log string.
  649. y = startY - font.LineSpacing;
  650. logString.Length = 0;
  651. foreach (MarkerInfo markerInfo in markers)
  652. {
  653. for (int i = 0; i < MaxBars; ++i)
  654. {
  655. if (markerInfo.Logs[i].Initialized)
  656. {
  657. if (logString.Length > 0)
  658. logString.Append("\n");
  659. logString.Append(" Bar ");
  660. logString.AppendNumber(i);
  661. logString.Append(" ");
  662. logString.Append(markerInfo.Name);
  663. logString.Append(" Avg.:");
  664. logString.AppendNumber(markerInfo.Logs[i].SnapAvg);
  665. logString.Append("ms ");
  666. y -= font.LineSpacing;
  667. }
  668. }
  669. }
  670. // Compute background size and draw it.
  671. Vector2 size = font.MeasureString(logString);
  672. rc = new Rectangle((int)position.X, (int)y, (int)size.X + 12, (int)size.Y);
  673. spriteBatch.Draw(texture, rc, new Color(0, 0, 0, 128));
  674. // Draw log string.
  675. spriteBatch.DrawString(font, logString,
  676. new Vector2(position.X + 12, y), Color.White);
  677. // Draw log color boxes.
  678. y += (int)((float)font.LineSpacing * 0.3f);
  679. rc = new Rectangle((int)position.X + 4, y, 10, 10);
  680. Rectangle rc2 = new Rectangle((int)position.X + 5, y + 1, 8, 8);
  681. foreach (MarkerInfo markerInfo in markers)
  682. {
  683. for (int i = 0; i < MaxBars; ++i)
  684. {
  685. if (markerInfo.Logs[i].Initialized)
  686. {
  687. rc.Y = y;
  688. rc2.Y = y + 1;
  689. spriteBatch.Draw(texture, rc, Color.White);
  690. spriteBatch.Draw(texture, rc2, markerInfo.Logs[i].Color);
  691. y += font.LineSpacing;
  692. }
  693. }
  694. }
  695. }
  696. spriteBatch.End();
  697. #endif
  698. }
  699. #endregion
  700. }
  701. }