FrameTimeGraph.cs 93 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305
  1. using UnityEngine;
  2. using System;
  3. using System.Collections.Generic;
  4. using UnityEngine.Profiling;
  5. namespace UnityEditor.Performance.ProfileAnalyzer
  6. {
  7. [Serializable]
  8. internal class FrameTimeGraphGlobalSettings
  9. {
  10. public bool showThreads = false;
  11. public bool showSelectedMarker = true;
  12. public bool showFrameLines = true;
  13. public bool showFrameLineText = true;
  14. public bool showOrderedByFrameDuration = false;
  15. }
  16. internal class FrameTimeGraph
  17. {
  18. static FrameTimeGraphGlobalSettings m_GlobalSettings = new FrameTimeGraphGlobalSettings();
  19. static public void SetGlobalSettings(FrameTimeGraphGlobalSettings globalSettings)
  20. {
  21. m_GlobalSettings = globalSettings;
  22. }
  23. public struct Data
  24. {
  25. public readonly float ms;
  26. public readonly int frameOffset;
  27. public Data(float _ms, int _index)
  28. {
  29. ms = _ms;
  30. frameOffset = _index;
  31. }
  32. };
  33. public delegate void SetRange(List<int> selected, int clickCount, FrameTimeGraph.State inputStatus);
  34. public delegate void SetActive(bool active);
  35. public enum State
  36. {
  37. None,
  38. Dragging,
  39. DragComplete
  40. };
  41. enum DragDirection
  42. {
  43. Start,
  44. Forward,
  45. Backward,
  46. None
  47. };
  48. enum AxisMode
  49. {
  50. One60HzFrame,
  51. Two60HzFrames,
  52. Four60HzFrames,
  53. Max,
  54. Custom
  55. };
  56. Draw2D m_2D;
  57. int m_DragBeginFirstOffset;
  58. int m_DragBeginLastOffset;
  59. bool m_Dragging;
  60. int m_DragFirstOffset;
  61. int m_DragLastOffset;
  62. bool m_Moving;
  63. int m_MoveHandleOffset;
  64. bool m_SingleControlAction;
  65. int m_ClickCount;
  66. double m_LastClickTime;
  67. bool m_MouseReleased;
  68. bool m_Zoomed;
  69. int m_ZoomStartOffset;
  70. int m_ZoomEndOffset;
  71. Color m_ColorBarBackground;
  72. Color m_ColorBarBackgroundSelected;
  73. Color m_ColorBar;
  74. Color m_ColorBarOutOfRange;
  75. Color m_ColorBarSelected;
  76. Color m_ColorBarThreads;
  77. Color m_ColorBarThreadsOutOfRange;
  78. Color m_ColorBarThreadsSelected;
  79. Color m_ColorBarMarker;
  80. Color m_ColorBarMarkerOutOfRange;
  81. Color m_ColorBarMarkerSelected;
  82. Color m_ColorGridLine;
  83. FrameTimeGraph m_PairedWithFrameTimeGraph;
  84. internal static class Styles
  85. {
  86. public static readonly GUIContent menuItemClearSelection = new GUIContent("Clear Selection");
  87. public static readonly GUIContent menuItemSelectAll = new GUIContent("Select All");
  88. public static readonly GUIContent menuItemInvertSelection = new GUIContent("Invert Selection");
  89. public static readonly GUIContent menuItemZoomSelection = new GUIContent("Zoom Selection");
  90. public static readonly GUIContent menuItemZoomAll = new GUIContent("Zoom All");
  91. public static readonly GUIContent menuItemSelectMin = new GUIContent("Select Shortest Frame");
  92. public static readonly GUIContent menuItemSelectMax = new GUIContent("Select Longest Frame");
  93. public static readonly GUIContent menuItemSelectMedian = new GUIContent("Select Median Frame");
  94. public static readonly GUIContent menuItemSelectPrevious = new GUIContent("Move selection left _LEFT");
  95. public static readonly GUIContent menuItemSelectNext = new GUIContent("Move selection right _RIGHT");
  96. public static readonly GUIContent menuItemSelectGrow = new GUIContent("Grow selection _=");
  97. public static readonly GUIContent menuItemSelectShrink = new GUIContent("Shrink selection _-");
  98. public static readonly GUIContent menuItemSelectGrowLeft = new GUIContent("Grow selection left _<");
  99. public static readonly GUIContent menuItemSelectGrowRight = new GUIContent("Grow selection right _>");
  100. public static readonly GUIContent menuItemSelectShrinkLeft = new GUIContent("Shrink selection left _&<");
  101. public static readonly GUIContent menuItemSelectShrinkRight = new GUIContent("Shrink selection right _&>");
  102. public static readonly GUIContent menuItemSelectGrowFast = new GUIContent("Grow selection (fast) _#=");
  103. public static readonly GUIContent menuItemSelectShrinkFast = new GUIContent("Shrink selection (fast) _#-");
  104. public static readonly GUIContent menuItemSelectGrowLeftFast = new GUIContent("Grow selection left (fast) _#<");
  105. public static readonly GUIContent menuItemSelectGrowRightFast = new GUIContent("Grow selection right (fast) _#>");
  106. public static readonly GUIContent menuItemSelectShrinkLeftFast = new GUIContent("Shrink selection left (fast) _#&<");
  107. public static readonly GUIContent menuItemSelectShrinkRightFast = new GUIContent("Shrink selection right (fast) _#&>");
  108. public static readonly GUIContent menuItemShowSelectedMarker = new GUIContent("Show Selected Marker");
  109. public static readonly GUIContent menuItemShowThreads = new GUIContent("Show Filtered Threads");
  110. // public static readonly GUIContent menuItemDetailedMode = new GUIContent("Detailed mode");
  111. public static readonly GUIContent menuItemShowFrameLines = new GUIContent("Show Frame Lines");
  112. public static readonly GUIContent menuItemShowFrameLineText = new GUIContent("Show Frame Line Text");
  113. public static readonly GUIContent menuItemShowOrderedByFrameDuration = new GUIContent("Order by Frame Duration");
  114. }
  115. const int kXAxisWidth = 80;
  116. const int kYAxisDetailThreshold = 40;
  117. const int kOverrunHeight = 3;
  118. static AxisMode s_YAxisMode;
  119. static float m_YAxisMs;
  120. bool m_IsOrderedByFrameDuration;
  121. List<Data> m_Values = new List<Data> {};
  122. List<int> m_LastSelectedFrameOffsets = new List<int> {};
  123. int[] m_FrameOffsetToDataOffsetMapping = new int[] {};
  124. SetRange m_SetRange;
  125. SetActive m_SetActive;
  126. List<int> m_CurrentSelection = new List<int>();
  127. int m_CurrentSelectionFirstDataOffset;
  128. int m_CurrentSelectionLastDataOffset;
  129. int m_GraphId;
  130. int m_ControlID;
  131. static int s_LastSelectedGraphId = -1;
  132. static int s_CurrentSelectedGraphId = -1;
  133. bool m_Enabled;
  134. struct BarData
  135. {
  136. public float x;
  137. public float y;
  138. public float w;
  139. public float h;
  140. public int startDataOffset;
  141. public int endDataOffset;
  142. public float yMin;
  143. public float yMax;
  144. public BarData(float _x, float _y, float _w, float _h, int _startDataOffset, int _endDataOffset, float _yMin, float _yMax)
  145. {
  146. x = _x;
  147. y = _y;
  148. w = _w;
  149. h = _h;
  150. startDataOffset = _startDataOffset;
  151. endDataOffset = _endDataOffset;
  152. yMin = _yMin;
  153. yMax = _yMax;
  154. }
  155. }
  156. List<BarData> m_Bars = new List<BarData>();
  157. DisplayUnits m_Units;
  158. Rect m_LastRect;
  159. int m_MaxFrames;
  160. string DisplayUnits()
  161. {
  162. return m_Units.Postfix();
  163. }
  164. string ToDisplayUnits(float ms, bool showUnits = false, int limitToNDigits = 5)
  165. {
  166. return m_Units.ToString(ms, showUnits, limitToNDigits);
  167. }
  168. public void SetUnits(Units units)
  169. {
  170. m_Units = new DisplayUnits(units);
  171. }
  172. public void Reset()
  173. {
  174. m_Zoomed = false;
  175. m_Dragging = false;
  176. ClearDragSelection();
  177. m_Moving = false;
  178. m_ClickCount = 0;
  179. m_MouseReleased = false;
  180. }
  181. void Init()
  182. {
  183. Reset();
  184. m_PairedWithFrameTimeGraph = null;
  185. m_YAxisMs = 100f;
  186. s_YAxisMode = AxisMode.Max;
  187. m_IsOrderedByFrameDuration = false;
  188. m_Enabled = true;
  189. m_LastRect = new Rect(0, 0, 0, 0);
  190. m_MaxFrames = -1;
  191. }
  192. public void MakeGraphActive(bool activate)
  193. {
  194. if (activate)
  195. {
  196. if (s_CurrentSelectedGraphId != m_GraphId)
  197. {
  198. s_LastSelectedGraphId = m_GraphId;
  199. s_CurrentSelectedGraphId = m_GraphId;
  200. // Make sure we are not still selecting another graph
  201. GUIUtility.hotControl = 0;
  202. m_SetActive(true);
  203. }
  204. if (GUI.GetNameOfFocusedControl() != "FrameTimeGraph")
  205. {
  206. // Take focus away from any other control
  207. // Doesn't really matter what the name is here
  208. GUI.FocusControl("FrameTimeGraph");
  209. }
  210. }
  211. else
  212. {
  213. if (s_CurrentSelectedGraphId == m_GraphId)
  214. {
  215. s_CurrentSelectedGraphId = -1;
  216. // Remember this was the active control
  217. // Before this point one of the inner labels would have been active
  218. // GUIUtility.hotControl = m_ControlID;
  219. m_SetActive(false);
  220. }
  221. }
  222. }
  223. public bool IsGraphActive()
  224. {
  225. if (s_CurrentSelectedGraphId == m_GraphId)
  226. return true;
  227. if (s_LastSelectedGraphId == m_GraphId && GUIUtility.hotControl == m_ControlID)
  228. return true;
  229. return false;
  230. }
  231. public FrameTimeGraph(int graphID, Draw2D draw2D, Units units, Color background, Color backgroundSelected, Color barColor, Color barSelected, Color barMarker, Color barMarkerSelected, Color barThreads, Color barThreadsSelected, Color colorGridlines)
  232. {
  233. m_GraphId = graphID;
  234. m_ControlID = 0;
  235. m_2D = draw2D;
  236. SetUnits(units);
  237. Init();
  238. float ratio = 0.75f;
  239. m_ColorBarBackground = background;
  240. m_ColorBarBackgroundSelected = backgroundSelected;
  241. m_ColorBar = barColor;
  242. m_ColorBarOutOfRange = new Color(barColor.r * ratio, barColor.g * ratio, barColor.b * ratio);
  243. m_ColorBarSelected = barSelected;
  244. m_ColorBarMarker = barMarker;
  245. m_ColorBarMarkerOutOfRange = new Color(barMarker.r * ratio, barMarker.g * ratio, barMarker.b * ratio);
  246. m_ColorBarMarkerSelected = barMarkerSelected;
  247. m_ColorBarThreads = barThreads;
  248. m_ColorBarThreadsOutOfRange = new Color(barThreads.r * ratio, barThreads.g * ratio, barThreads.b * ratio);
  249. m_ColorBarThreadsSelected = barThreadsSelected;
  250. m_ColorGridLine = colorGridlines;
  251. }
  252. int ClampToRange(int value, int min, int max)
  253. {
  254. if (value < min)
  255. value = min;
  256. if (value > max)
  257. value = max;
  258. return value;
  259. }
  260. int GetDataOffsetForXUnclamped(int xPosition, int width, int totalDataSize)
  261. {
  262. int visibleDataSize;
  263. if (m_Zoomed)
  264. visibleDataSize = (m_ZoomEndOffset - m_ZoomStartOffset) + 1;
  265. else
  266. visibleDataSize = totalDataSize;
  267. int dataOffset = (int)(xPosition * visibleDataSize / width);
  268. if (m_Zoomed)
  269. dataOffset += m_ZoomStartOffset;
  270. return dataOffset;
  271. }
  272. int GetDataOffsetForX(int xPosition, int width, int totalDataSize)
  273. {
  274. //xPosition = ClampToRange(xPosition, 0, width-1);
  275. int dataOffset = GetDataOffsetForXUnclamped(xPosition, width, totalDataSize);
  276. return ClampToRange(dataOffset, 0, totalDataSize - 1);
  277. }
  278. int GetXForDataOffset(int dataOffset, int width, int totalDataSize)
  279. {
  280. //frameOffset = ClampToRange(frameOffset, 0, frames-1);
  281. int visibleDataSize;
  282. if (m_Zoomed)
  283. {
  284. dataOffset = ClampToRange(dataOffset, m_ZoomStartOffset, m_ZoomEndOffset + 1);
  285. dataOffset -= m_ZoomStartOffset;
  286. visibleDataSize = (m_ZoomEndOffset - m_ZoomStartOffset) + 1;
  287. }
  288. else
  289. visibleDataSize = totalDataSize;
  290. int x = (int)(dataOffset * width / visibleDataSize);
  291. x = ClampToRange(x, 0, width - 1);
  292. return x;
  293. }
  294. void SetDragMovement(int startOffset, int endOffset, int currentSelectionFirstDataOffset, int currentSelectionLastDataOffset)
  295. {
  296. // Maintain length but clamp to range
  297. int frames = m_Values.Count;
  298. int currentSelectionRange = currentSelectionLastDataOffset - currentSelectionFirstDataOffset;
  299. endOffset = startOffset + currentSelectionRange;
  300. startOffset = ClampToRange(startOffset, 0, frames - (currentSelectionRange + 1));
  301. endOffset = ClampToRange(endOffset, 0, frames - 1);
  302. SetDragSelection(startOffset, endOffset);
  303. if (m_PairedWithFrameTimeGraph != null && !m_SingleControlAction)
  304. {
  305. m_PairedWithFrameTimeGraph.SetDragSelection(m_DragFirstOffset, m_DragLastOffset);
  306. }
  307. }
  308. void SetDragSelection(int startOffset, int endOffset, DragDirection dragDirection)
  309. {
  310. // No need to clamp these as input is clamped.
  311. switch (dragDirection)
  312. {
  313. case DragDirection.Forward:
  314. SetDragSelection(m_DragBeginFirstOffset, endOffset);
  315. break;
  316. case DragDirection.Backward:
  317. SetDragSelection(startOffset, m_DragBeginLastOffset);
  318. break;
  319. case DragDirection.Start:
  320. SetDragSelection(startOffset, endOffset);
  321. // Record first selected bar range
  322. m_DragBeginFirstOffset = m_DragFirstOffset;
  323. m_DragBeginLastOffset = m_DragLastOffset;
  324. break;
  325. }
  326. if (m_PairedWithFrameTimeGraph != null && !m_SingleControlAction)
  327. {
  328. m_PairedWithFrameTimeGraph.SetDragSelection(m_DragFirstOffset, m_DragLastOffset);
  329. }
  330. }
  331. public void SetDragSelection(int startOffset, int endOffset)
  332. {
  333. m_DragFirstOffset = startOffset;
  334. m_DragLastOffset = endOffset;
  335. }
  336. public void ClearDragSelection()
  337. {
  338. m_DragFirstOffset = -1;
  339. m_DragLastOffset = -1;
  340. }
  341. public bool HasDragRegion()
  342. {
  343. return (m_DragFirstOffset != -1);
  344. }
  345. public void GetSelectedRange(List<int> frameOffsets, out int firstDataOffset, out int lastDataOffset, out int firstFrameOffset, out int lastFrameOffset)
  346. {
  347. int frames = m_Values != null ? m_Values.Count : 0;
  348. firstDataOffset = 0;
  349. lastDataOffset = frames - 1;
  350. firstFrameOffset = 0;
  351. lastFrameOffset = frames - 1;
  352. if (m_FrameOffsetToDataOffsetMapping.Length > 0)
  353. {
  354. // By default data is ordered by index so first/last will be the selected visible range
  355. if (frameOffsets.Count >= 1)
  356. {
  357. firstFrameOffset = frameOffsets[0];
  358. lastFrameOffset = firstFrameOffset;
  359. firstDataOffset = GetDataOffset(firstFrameOffset);
  360. lastDataOffset = firstDataOffset;
  361. }
  362. if (frameOffsets.Count >= 2)
  363. {
  364. lastFrameOffset = frameOffsets[frameOffsets.Count - 1];
  365. lastDataOffset = GetDataOffset(lastFrameOffset);
  366. }
  367. if (m_GlobalSettings.showOrderedByFrameDuration)
  368. {
  369. // Need to find the selected items with lowest and highest ms values
  370. if (frameOffsets.Count > 0)
  371. {
  372. int dataOffset = GetDataOffset(firstFrameOffset);
  373. firstDataOffset = dataOffset;
  374. lastDataOffset = dataOffset;
  375. float firstDataMS = m_Values[dataOffset].ms;
  376. float lastDataMS = m_Values[dataOffset].ms;
  377. foreach (int frameOffset in frameOffsets)
  378. {
  379. dataOffset = GetDataOffset(frameOffset);
  380. float ms = m_Values[dataOffset].ms;
  381. if (ms <= firstDataMS && dataOffset < firstDataOffset)
  382. {
  383. firstDataMS = ms;
  384. firstDataOffset = dataOffset;
  385. }
  386. if (ms >= lastDataMS && dataOffset > lastDataOffset)
  387. {
  388. lastDataMS = ms;
  389. lastDataOffset = dataOffset;
  390. }
  391. }
  392. }
  393. }
  394. }
  395. }
  396. public bool IsMultiSelectControlHeld()
  397. {
  398. #if UNITY_EDITOR_OSX
  399. return Event.current.command;
  400. #else
  401. return Event.current.control;
  402. #endif
  403. }
  404. public State ProcessInput()
  405. {
  406. if (!IsEnabled())
  407. return State.None;
  408. if (m_Values == null)
  409. return State.None;
  410. if (m_LastRect.width == 0 || m_MaxFrames < 0)
  411. return State.None;
  412. Rect rect = m_LastRect;
  413. int maxFrames = m_MaxFrames;
  414. int dataLength = m_Values.Count;
  415. if (dataLength <= 0)
  416. return State.None;
  417. if (m_IsOrderedByFrameDuration != m_GlobalSettings.showOrderedByFrameDuration)
  418. {
  419. // Reorder if necessary
  420. SetData(m_Values);
  421. }
  422. int currentSelectionFirstDataOffset;
  423. int currentSelectionLastDataOffset;
  424. int currentSelectionFirstFrameOffset;
  425. int currentSelectionLastFrameOffset;
  426. GetSelectedRange(m_LastSelectedFrameOffsets, out currentSelectionFirstDataOffset, out currentSelectionLastDataOffset, out currentSelectionFirstFrameOffset, out currentSelectionLastFrameOffset);
  427. m_CurrentSelection.Clear();
  428. m_CurrentSelection.AddRange(m_LastSelectedFrameOffsets);
  429. m_CurrentSelectionFirstDataOffset = currentSelectionFirstDataOffset;
  430. m_CurrentSelectionLastDataOffset = currentSelectionLastDataOffset;
  431. if (Event.current.isKey && Event.current.type == EventType.KeyDown && !m_Dragging && !m_MouseReleased)
  432. {
  433. if (IsGraphActive())
  434. {
  435. int step = Event.current.shift ? 10 : 1;
  436. var eventUsed = false;
  437. switch (Event.current.keyCode)
  438. {
  439. case KeyCode.LeftArrow:
  440. SelectPrevious(step);
  441. eventUsed = true;
  442. break;
  443. case KeyCode.RightArrow:
  444. SelectNext(step);
  445. eventUsed = true;
  446. break;
  447. case KeyCode.Less:
  448. case KeyCode.Comma:
  449. if (Event.current.alt)
  450. SelectShrinkLeft(step);
  451. else
  452. SelectGrowLeft(step);
  453. eventUsed = true;
  454. break;
  455. case KeyCode.Greater:
  456. case KeyCode.Period:
  457. if (Event.current.alt)
  458. SelectShrinkRight(step);
  459. else
  460. SelectGrowRight(step);
  461. eventUsed = true;
  462. break;
  463. case KeyCode.Plus:
  464. case KeyCode.Equals:
  465. case KeyCode.KeypadPlus:
  466. if (Event.current.alt)
  467. SelectShrink(step);
  468. else
  469. SelectGrow(step);
  470. eventUsed = true;
  471. break;
  472. case KeyCode.Underscore:
  473. case KeyCode.Minus:
  474. case KeyCode.KeypadMinus:
  475. if (Event.current.alt)
  476. SelectGrow(step);
  477. else
  478. SelectShrink(step);
  479. eventUsed = true;
  480. break;
  481. }
  482. if (eventUsed)
  483. Event.current.Use();
  484. }
  485. }
  486. float doubleClickTimeout = 0.25f;
  487. if (m_MouseReleased)
  488. {
  489. if ((EditorApplication.timeSinceStartup - m_LastClickTime) > doubleClickTimeout)
  490. {
  491. // By this point we will know if its a single or double click
  492. bool append = IsMultiSelectControlHeld();
  493. CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.DragComplete, append);
  494. ClearDragSelection();
  495. if (m_PairedWithFrameTimeGraph != null && !m_SingleControlAction)
  496. m_PairedWithFrameTimeGraph.ClearDragSelection();
  497. m_MouseReleased = false;
  498. }
  499. }
  500. int width = (int)rect.width;
  501. int height = (int)rect.height;
  502. float xStart = rect.xMin;
  503. if (height > kYAxisDetailThreshold)
  504. {
  505. float h = GUI.skin.label.lineHeight;
  506. xStart += kXAxisWidth;
  507. width -= kXAxisWidth;
  508. }
  509. if (maxFrames > 0)
  510. {
  511. if (!m_Zoomed)
  512. width = width * dataLength / maxFrames;
  513. }
  514. // Process input
  515. Event e = Event.current;
  516. if (e.isMouse)
  517. {
  518. if (m_Dragging)
  519. {
  520. if (e.type == EventType.MouseUp)
  521. {
  522. m_Dragging = false;
  523. m_Moving = false;
  524. // Delay the action as we are checking for double click
  525. m_MouseReleased = true;
  526. return State.Dragging;
  527. }
  528. }
  529. int x = (int)(e.mousePosition.x - xStart);
  530. int dataOffset = GetDataOffsetForXUnclamped(x, width, dataLength);
  531. if (m_Moving)
  532. dataOffset -= m_MoveHandleOffset;
  533. dataOffset = ClampToRange(dataOffset, 0, dataLength - 1);
  534. int frameOffsetBeforeNext = Math.Max(dataOffset, GetDataOffsetForX(x + 1, width, dataLength) - 1);
  535. if (m_Dragging)
  536. {
  537. if (e.button == 0)
  538. {
  539. // Still dragging (doesn't have to be within the y bounds)
  540. if (m_Moving)
  541. {
  542. // Forward drag from start point
  543. SetDragMovement(dataOffset, frameOffsetBeforeNext, currentSelectionFirstDataOffset, currentSelectionLastDataOffset);
  544. }
  545. else
  546. {
  547. DragDirection dragDirection = (dataOffset < m_DragBeginFirstOffset) ? DragDirection.Backward : DragDirection.Forward;
  548. SetDragSelection(dataOffset, frameOffsetBeforeNext, dragDirection);
  549. }
  550. CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.Dragging);
  551. return State.Dragging;
  552. }
  553. }
  554. else
  555. {
  556. if (e.mousePosition.x >= rect.x && e.mousePosition.x <= rect.xMax &&
  557. e.mousePosition.y >= rect.y && e.mousePosition.y <= rect.yMax)
  558. {
  559. if (e.mousePosition.x >= xStart && e.mousePosition.x <= (xStart + width) &&
  560. e.mousePosition.y >= rect.y && e.mousePosition.y < rect.yMax)
  561. {
  562. MakeGraphActive(true);
  563. if (e.type == EventType.MouseDown && e.button == 0)
  564. {
  565. // Drag start (must be within the bounds of the control)
  566. // Might be single or double click
  567. m_LastClickTime = EditorApplication.timeSinceStartup;
  568. m_ClickCount = e.clickCount;
  569. m_Dragging = true;
  570. m_Moving = false;
  571. if (currentSelectionFirstDataOffset != 0 || currentSelectionLastDataOffset != dataLength - 1)
  572. {
  573. // Selection is valid
  574. if (e.shift && dataOffset >= currentSelectionFirstDataOffset && frameOffsetBeforeNext <= currentSelectionLastDataOffset)
  575. {
  576. // Moving if shift held and we are inside the current selection range
  577. m_Moving = true;
  578. }
  579. }
  580. if (m_PairedWithFrameTimeGraph != null)
  581. m_SingleControlAction = e.alt; // Record if we are acting only on this control rather than the paired one too
  582. else
  583. m_SingleControlAction = true;
  584. if (m_Moving)
  585. {
  586. m_MoveHandleOffset = dataOffset - currentSelectionFirstDataOffset;
  587. SetDragMovement(currentSelectionFirstDataOffset, currentSelectionLastDataOffset, currentSelectionFirstDataOffset, currentSelectionLastDataOffset);
  588. }
  589. else
  590. {
  591. //SetDragSelection(dataOffset, frameOffsetBeforeNext, DragDirection.Start);
  592. // Select just 1 frame
  593. SetDragSelection(dataOffset, dataOffset, DragDirection.Start);
  594. }
  595. CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.Dragging);
  596. return State.Dragging;
  597. }
  598. }
  599. }
  600. else
  601. {
  602. // Left this graph area
  603. MakeGraphActive(false);
  604. }
  605. }
  606. }
  607. if (m_MouseReleased)
  608. {
  609. // Not finished drag fully yet
  610. CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.Dragging);
  611. return State.Dragging;
  612. }
  613. return State.None;
  614. }
  615. public float GetDataRange()
  616. {
  617. if (m_Values == null)
  618. return 0f;
  619. int frames = m_Values.Count;
  620. float min = 0f;
  621. float max = 0f;
  622. for (int frameOffset = 0; frameOffset < frames; frameOffset++)
  623. {
  624. float ms = m_Values[frameOffset].ms;
  625. if (ms > max)
  626. max = ms;
  627. }
  628. float hRange = max - min;
  629. return hRange;
  630. }
  631. public void PairWith(FrameTimeGraph otherFrameTimeGraph)
  632. {
  633. if (m_PairedWithFrameTimeGraph != null)
  634. {
  635. // Clear existing pairing
  636. m_PairedWithFrameTimeGraph.m_PairedWithFrameTimeGraph = null;
  637. }
  638. m_PairedWithFrameTimeGraph = otherFrameTimeGraph;
  639. if (otherFrameTimeGraph != null)
  640. otherFrameTimeGraph.m_PairedWithFrameTimeGraph = this;
  641. }
  642. public FrameTimeGraph GetPairedWith()
  643. {
  644. return m_PairedWithFrameTimeGraph;
  645. }
  646. public float GetYAxisRange(float yMax)
  647. {
  648. switch (s_YAxisMode)
  649. {
  650. case AxisMode.One60HzFrame:
  651. return 1000f / 60f;
  652. case AxisMode.Two60HzFrames:
  653. return 2000f / 60f;
  654. case AxisMode.Four60HzFrames:
  655. return 4000f / 60f;
  656. case AxisMode.Max:
  657. return yMax;
  658. case AxisMode.Custom:
  659. return m_YAxisMs;
  660. }
  661. return yMax;
  662. }
  663. public void SetData(List<Data> values)
  664. {
  665. if (values == null)
  666. return;
  667. m_Values = values;
  668. if (m_GlobalSettings.showOrderedByFrameDuration)
  669. m_Values.Sort((a, b) => { return a.ms.CompareTo(b.ms); });
  670. else
  671. m_Values.Sort((a, b) => { return a.frameOffset.CompareTo(b.frameOffset); });
  672. m_FrameOffsetToDataOffsetMapping = new int[m_Values.Count];
  673. for (int dataOffset = 0; dataOffset < m_Values.Count; dataOffset++)
  674. m_FrameOffsetToDataOffsetMapping[m_Values[dataOffset].frameOffset] = dataOffset;
  675. m_CurrentSelection.Clear();
  676. for (int frameIndex = 0; frameIndex < m_Values.Count; frameIndex++)
  677. {
  678. m_CurrentSelection.Add(frameIndex);
  679. }
  680. m_CurrentSelectionFirstDataOffset = 0;
  681. m_CurrentSelectionLastDataOffset = m_Values.Count - 1;
  682. m_IsOrderedByFrameDuration = m_GlobalSettings.showOrderedByFrameDuration;
  683. }
  684. int GetDataOffset(int frameOffset)
  685. {
  686. if (frameOffset < 0 || frameOffset >= m_FrameOffsetToDataOffsetMapping.Length)
  687. {
  688. Debug.Log(string.Format("{0} out of range of frame offset to data offset mapping {1}", frameOffset, m_FrameOffsetToDataOffsetMapping.Length));
  689. return 0;
  690. }
  691. return m_FrameOffsetToDataOffsetMapping[frameOffset];
  692. }
  693. public bool HasData()
  694. {
  695. if (m_Values == null)
  696. return false;
  697. if (m_Values.Count == 0)
  698. return false;
  699. return true;
  700. }
  701. public void SetActiveCallback(SetActive setActive)
  702. {
  703. m_SetActive = setActive;
  704. }
  705. public void SetRangeCallback(SetRange setRange)
  706. {
  707. m_SetRange = setRange;
  708. }
  709. void CallSetRange(int startDataOffset, int endDataOffset, int clickCount, bool singleControlAction, FrameTimeGraph.State inputStatus, bool append = false, bool effectPaired = true)
  710. {
  711. if (m_SetRange == null)
  712. return;
  713. if (startDataOffset < 0 && endDataOffset < 0)
  714. {
  715. // Clear
  716. m_SetRange(new List<int>(), clickCount, inputStatus);
  717. return;
  718. }
  719. startDataOffset = Math.Max(0, startDataOffset);
  720. endDataOffset = Math.Min(endDataOffset, m_Values.Count - 1);
  721. List<int> selected = new List<int>();
  722. if (append && m_LastSelectedFrameOffsets.Count != m_Values.Count)
  723. {
  724. foreach (int frameOffset in m_LastSelectedFrameOffsets)
  725. {
  726. int dataOffset = m_FrameOffsetToDataOffsetMapping[frameOffset];
  727. if (dataOffset >= 0 && dataOffset < m_Values.Count)
  728. {
  729. selected.Add(frameOffset);
  730. }
  731. }
  732. }
  733. for (int dataOffset = startDataOffset; dataOffset <= endDataOffset; dataOffset++)
  734. {
  735. if (dataOffset >= 0 && dataOffset < m_Values.Count)
  736. {
  737. int frameOffset = m_Values[dataOffset].frameOffset;
  738. if (append == false || !selected.Contains(frameOffset))
  739. {
  740. selected.Add(frameOffset);
  741. }
  742. }
  743. }
  744. // Sort selection in frame index order so start is lowest and end is highest
  745. selected.Sort();
  746. if (selected.Count == 0)
  747. return;
  748. m_SetRange(selected, clickCount, inputStatus);
  749. if (m_PairedWithFrameTimeGraph != null && m_PairedWithFrameTimeGraph.m_Values.Count > 1 && effectPaired && !singleControlAction)
  750. {
  751. // Update selection on the other frame time graph
  752. int mainMaxFrame = m_Values.Count - 1;
  753. int otherMaxFrame = m_PairedWithFrameTimeGraph.m_Values.Count - 1;
  754. int startOffset = startDataOffset;
  755. int endOffset = endDataOffset;
  756. if (startOffset > otherMaxFrame)
  757. {
  758. if (append)
  759. {
  760. // Nothing more to do
  761. return;
  762. }
  763. // Select all, if the main selection is outsize the range of the other
  764. startOffset = 0;
  765. endOffset = otherMaxFrame;
  766. }
  767. else
  768. {
  769. if (startOffset == 0 && endOffset == mainMaxFrame)
  770. {
  771. // If clearing main selection then clear the other section fully too
  772. endOffset = otherMaxFrame;
  773. }
  774. startOffset = ClampToRange(startOffset, 0, otherMaxFrame);
  775. endOffset = ClampToRange(endOffset, 0, otherMaxFrame);
  776. }
  777. m_PairedWithFrameTimeGraph.CallSetRange(startOffset, endOffset, clickCount, singleControlAction, inputStatus, append, false);
  778. }
  779. }
  780. bool HasNoSelection()
  781. {
  782. if (m_Values == null)
  783. return false;
  784. int frames = m_Values.Count;
  785. return ((m_CurrentSelectionFirstDataOffset < 0 || m_CurrentSelectionLastDataOffset >= frames) &&
  786. (m_CurrentSelectionLastDataOffset < 0 || m_CurrentSelectionLastDataOffset >= frames));
  787. }
  788. bool HasSelectedAll()
  789. {
  790. if (m_Values == null)
  791. return false;
  792. int frames = m_Values.Count;
  793. return (m_CurrentSelectionFirstDataOffset == 0 && m_CurrentSelectionLastDataOffset == (frames - 1));
  794. }
  795. bool HasSubsetSelected()
  796. {
  797. if (m_Values == null)
  798. return false;
  799. return !(HasSelectedAll() || HasNoSelection());
  800. }
  801. void RegenerateBars(float x, float y, float width, float height, float yRange)
  802. {
  803. int frames = m_Values.Count;
  804. if (frames <= 0)
  805. return;
  806. m_Bars.Clear();
  807. int nextDataOffset = GetDataOffsetForX(0, (int)width, frames);
  808. for (int barX = 0; barX < width; barX++)
  809. {
  810. int startDataOffset = nextDataOffset;
  811. nextDataOffset = GetDataOffsetForX(barX + 1, (int)width, frames);
  812. int endDataOffset = Math.Max(startDataOffset, nextDataOffset - 1);
  813. float min = m_Values[startDataOffset].ms;
  814. float max = min;
  815. for (int dataOffset = startDataOffset + 1; dataOffset <= endDataOffset; dataOffset++)
  816. {
  817. float ms = m_Values[dataOffset].ms;
  818. if (ms < min)
  819. min = ms;
  820. if (ms > max)
  821. max = ms;
  822. }
  823. float maxClamped = Math.Min(max, yRange);
  824. float h = height * maxClamped / yRange;
  825. m_Bars.Add(new BarData(x + barX, y, 1, h, startDataOffset, endDataOffset, min, max));
  826. }
  827. }
  828. public void SetEnabled(bool enabled)
  829. {
  830. m_Enabled = enabled;
  831. }
  832. public bool IsEnabled()
  833. {
  834. return m_Enabled;
  835. }
  836. float GetTotalSelectionTime(List<int> selectedFrameOffsets)
  837. {
  838. float totalMs = 0;
  839. for (int i = 0; i < selectedFrameOffsets.Count; i++)
  840. {
  841. int frameOffset = selectedFrameOffsets[i];
  842. int dataOffset = m_FrameOffsetToDataOffsetMapping[frameOffset];
  843. totalMs += m_Values[dataOffset].ms;
  844. }
  845. return totalMs;
  846. }
  847. float GetTotalSelectionTime(int firstOffset, int lastOffset)
  848. {
  849. float totalMs = 0;
  850. for (int frameOffset = firstOffset; frameOffset <= lastOffset; frameOffset++)
  851. {
  852. if (frameOffset < m_FrameOffsetToDataOffsetMapping.Length)
  853. {
  854. int dataOffset = m_FrameOffsetToDataOffsetMapping[frameOffset];
  855. totalMs += m_Values[dataOffset].ms;
  856. }
  857. }
  858. return totalMs;
  859. }
  860. void ShowFrameLines(float x, float y, float yRange, float width, float height)
  861. {
  862. float msSegment = 1000f / 60f;
  863. int lines = (int)(yRange / msSegment);
  864. int step = 1;
  865. for (int line = 1; line <= lines; line += step, step *= 2)
  866. {
  867. float ms = line * msSegment;
  868. float h = height * ms / yRange;
  869. m_2D.DrawLine(x, y + h, x + width - 1, y + h, m_ColorGridLine);
  870. }
  871. }
  872. bool InSelectedRegion(int startDataOffset, int endDataOffset, int selectedFirstOffset, int selectedLastOffset, Dictionary<int, int> frameOffsetToSelectionIndex, bool subsetSelected)
  873. {
  874. bool inSelectionRegion = false;
  875. bool showCurrentSelection = false;
  876. if (HasDragRegion())
  877. {
  878. if (endDataOffset >= selectedFirstOffset && startDataOffset <= selectedLastOffset)
  879. {
  880. inSelectionRegion = true;
  881. }
  882. if (IsMultiSelectControlHeld() && m_LastSelectedFrameOffsets.Count != m_Values.Count)
  883. {
  884. // Show current selection too
  885. showCurrentSelection = true;
  886. }
  887. }
  888. else
  889. {
  890. showCurrentSelection = true;
  891. }
  892. if (showCurrentSelection)
  893. {
  894. //if (subsetSelected)
  895. {
  896. for (int dataOffset = startDataOffset; dataOffset <= endDataOffset; dataOffset++)
  897. {
  898. int frameOffset = m_Values[dataOffset].frameOffset;
  899. if (frameOffsetToSelectionIndex.ContainsKey(frameOffset))
  900. {
  901. inSelectionRegion = true;
  902. break;
  903. }
  904. }
  905. }
  906. }
  907. return inSelectionRegion;
  908. }
  909. public void Draw(Rect rect, ProfileAnalysis analysis, List<int> selectedFrameOffsets, float yMax, int offsetToDisplayMapping, int offsetToIndexMapping, string selectedMarkerName, int maxFrames = 0, ProfileAnalysis fullAnalysis = null)
  910. {
  911. Profiler.BeginSample("FrameTimeGraph.Draw");
  912. // Must be outside repaint to make sure next controls work correctly (specifically marker name filter)
  913. int controlID = GUIUtility.GetControlID(FocusType.Keyboard);
  914. if (Event.current.type == EventType.Repaint)
  915. {
  916. m_LastRect = rect;
  917. // Control id would change during non repaint phase if tooltips displayed if we update this outside the repaint
  918. m_ControlID = controlID;
  919. }
  920. m_MaxFrames = maxFrames;
  921. if (m_Values == null)
  922. return;
  923. m_LastSelectedFrameOffsets = selectedFrameOffsets;
  924. int totalDataSize = m_Values.Count;
  925. if (totalDataSize <= 0)
  926. return;
  927. if (m_IsOrderedByFrameDuration != m_GlobalSettings.showOrderedByFrameDuration)
  928. {
  929. // Reorder if necessary
  930. SetData(m_Values);
  931. }
  932. // Get start and end selection span
  933. int currentSelectionFirstDataOffset;
  934. int currentSelectionLastDataOffset;
  935. int currentSelectionFirstFrameOffset;
  936. int currentSelectionLastFrameOffset;
  937. GetSelectedRange(selectedFrameOffsets, out currentSelectionFirstDataOffset, out currentSelectionLastDataOffset, out currentSelectionFirstFrameOffset, out currentSelectionLastFrameOffset);
  938. // Create mapping from offset to selection for faster selection detection
  939. Dictionary<int, int> frameOffsetToSelectionIndex = new Dictionary<int, int>();
  940. for (int i = 0; i < selectedFrameOffsets.Count; i++)
  941. {
  942. int frameOffset = selectedFrameOffsets[i];
  943. frameOffsetToSelectionIndex[frameOffset] = i;
  944. }
  945. Event current = Event.current;
  946. int selectedFirstOffset;
  947. int selectedLastOffset;
  948. int selectedCount;
  949. bool subsetSelected = false;
  950. if (HasDragRegion())
  951. {
  952. selectedFirstOffset = m_DragFirstOffset;
  953. selectedLastOffset = m_DragLastOffset;
  954. if (selectedFirstOffset > m_Values.Count - 1)
  955. {
  956. // Selection off the end
  957. selectedFirstOffset = m_Values.Count;
  958. selectedLastOffset = m_Values.Count;
  959. selectedCount = 0;
  960. }
  961. else
  962. {
  963. selectedFirstOffset = ClampToRange(selectedFirstOffset, 0, m_Values.Count - 1);
  964. selectedLastOffset = ClampToRange(selectedLastOffset, 0, m_Values.Count - 1);
  965. selectedCount = 1 + (selectedLastOffset - selectedFirstOffset);
  966. subsetSelected = true;
  967. }
  968. }
  969. else
  970. {
  971. selectedFirstOffset = currentSelectionFirstDataOffset;
  972. selectedLastOffset = currentSelectionLastDataOffset;
  973. selectedCount = selectedFrameOffsets.Count;
  974. subsetSelected = (selectedCount > 0 && selectedCount != totalDataSize);
  975. }
  976. // Draw frames and selection
  977. float width = rect.width;
  978. float height = rect.height;
  979. bool showAxis = false;
  980. float xStart = 0f;
  981. float yStart = 0f;
  982. if (height > kYAxisDetailThreshold)
  983. {
  984. showAxis = true;
  985. float h = GUI.skin.label.lineHeight;
  986. xStart += kXAxisWidth;
  987. width -= kXAxisWidth;
  988. yStart += h;
  989. height -= h;
  990. }
  991. if (maxFrames > 0)
  992. {
  993. if (!m_Zoomed)
  994. width = width * totalDataSize / maxFrames;
  995. }
  996. // Start / End
  997. int startOffset = m_Zoomed ? m_ZoomStartOffset : 0;
  998. int endOffset = m_Zoomed ? m_ZoomEndOffset : totalDataSize - 1;
  999. // Get try index values
  1000. int startIndex = offsetToDisplayMapping + startOffset;
  1001. int endIndex = offsetToDisplayMapping + endOffset;
  1002. int selectedFirstIndex = offsetToDisplayMapping + selectedFirstOffset;
  1003. int selectedLastIndex = offsetToDisplayMapping + selectedLastOffset;
  1004. string detailsString = "";
  1005. if (!showAxis)
  1006. {
  1007. string frameRangeString;
  1008. if (startIndex == endIndex)
  1009. frameRangeString = string.Format("Total Range {0}", startIndex);
  1010. else
  1011. frameRangeString = string.Format("Total Range {0} - {1} [{2}]", startIndex, endIndex, 1 + (endIndex - startIndex));
  1012. // Selection range
  1013. string selectedTooltip = "";
  1014. if (subsetSelected)
  1015. {
  1016. if (selectedFirstIndex == selectedLastIndex)
  1017. selectedTooltip = string.Format("\nSelected {0}\n", selectedFirstIndex);
  1018. else
  1019. selectedTooltip = string.Format("\nSelected {0} - {1} [{2}]", selectedFirstIndex, selectedLastIndex, selectedCount);
  1020. }
  1021. detailsString = string.Format("\n\n{0}{1}", frameRangeString, selectedTooltip);
  1022. }
  1023. float yRange = GetYAxisRange(yMax);
  1024. bool lastEnabled = GUI.enabled;
  1025. bool enabled = IsEnabled();
  1026. GUI.enabled = enabled;
  1027. if (m_2D.DrawStart(rect, Draw2D.Origin.BottomLeft))
  1028. {
  1029. float totalMs;
  1030. if (HasDragRegion())
  1031. {
  1032. totalMs = GetTotalSelectionTime(selectedFirstOffset, selectedLastOffset);
  1033. }
  1034. else
  1035. {
  1036. totalMs = GetTotalSelectionTime(selectedFrameOffsets);
  1037. }
  1038. string timeForSelectedFrames = ToDisplayUnits(totalMs, true, 0);
  1039. string timeForSelectedFramesClamped = ToDisplayUnits(totalMs, true, 1);
  1040. string selectionAreaString = string.Format("\n\nTotal time for {0} selected frames\n{1} ({2})", selectedCount, timeForSelectedFrames, timeForSelectedFramesClamped);
  1041. Color selectedControl = GUI.skin.settings.selectionColor;
  1042. if (IsGraphActive())
  1043. {
  1044. m_2D.DrawBox(xStart, yStart, width, height, selectedControl);
  1045. }
  1046. //xStart -= 1f;
  1047. yStart += 1f;
  1048. width -= 1f;
  1049. height -= 1f;
  1050. m_2D.DrawFilledBox(xStart, yStart, width, height, m_ColorBarBackground);
  1051. RegenerateBars(xStart, yStart, width, height, yRange);
  1052. foreach (BarData bar in m_Bars)
  1053. {
  1054. bool inSelectionRegion = InSelectedRegion(bar.startDataOffset, bar.endDataOffset, selectedFirstOffset, selectedLastOffset, frameOffsetToSelectionIndex, subsetSelected);
  1055. if (inSelectionRegion)
  1056. {
  1057. m_2D.DrawFilledBox(bar.x, bar.y, bar.w, height, m_ColorBarBackgroundSelected);
  1058. }
  1059. }
  1060. if (m_GlobalSettings.showFrameLines)
  1061. {
  1062. ShowFrameLines(xStart, yStart, yRange, width, height);
  1063. }
  1064. ProfileAnalysis analysisData = analysis;
  1065. bool full = false;
  1066. if (fullAnalysis != null)
  1067. {
  1068. analysisData = fullAnalysis;
  1069. full = true;
  1070. }
  1071. MarkerData selectedMarker = (m_GlobalSettings.showSelectedMarker && analysisData != null) ? analysisData.GetMarkerByName(selectedMarkerName) : null;
  1072. foreach (BarData bar in m_Bars)
  1073. {
  1074. bool inSelectionRegion = InSelectedRegion(bar.startDataOffset, bar.endDataOffset, selectedFirstOffset, selectedLastOffset, frameOffsetToSelectionIndex, subsetSelected);
  1075. if (inSelectionRegion)
  1076. {
  1077. m_2D.DrawFilledBox(bar.x, bar.y, bar.w, bar.h, m_ColorBarSelected);
  1078. }
  1079. else
  1080. {
  1081. m_2D.DrawFilledBox(bar.x, bar.y, bar.w, bar.h, m_ColorBar);
  1082. }
  1083. // Show where its been clamped
  1084. if (bar.yMax > yRange)
  1085. {
  1086. m_2D.DrawFilledBox(bar.x, bar.y + height, 1, kOverrunHeight, m_ColorBarOutOfRange);
  1087. }
  1088. if (analysisData != null && (full || !m_Dragging))
  1089. {
  1090. // Analysis is just on the subset
  1091. if (m_GlobalSettings.showThreads)
  1092. {
  1093. Profiler.BeginSample("FrameTimeGraph.ShowThreads");
  1094. ShowThreads(height, yRange, bar, full,
  1095. analysisData.GetThreads(), subsetSelected, selectedFirstOffset, selectedLastOffset,
  1096. offsetToIndexMapping, frameOffsetToSelectionIndex);
  1097. Profiler.EndSample();
  1098. }
  1099. if (m_GlobalSettings.showSelectedMarker)
  1100. {
  1101. // Analysis is just on the subset (unless we have full analysis data)
  1102. ShowSelectedMarker(height, yRange, bar, full, selectedMarker, subsetSelected, selectedFirstOffset, selectedLastOffset,
  1103. offsetToIndexMapping, frameOffsetToSelectionIndex);
  1104. }
  1105. }
  1106. }
  1107. m_2D.DrawEnd();
  1108. if (m_GlobalSettings.showFrameLines && m_GlobalSettings.showFrameLineText)
  1109. {
  1110. ShowFrameLinesText(rect, xStart, yStart, yRange, width, height, subsetSelected, selectedFirstOffset, selectedLastOffset);
  1111. }
  1112. foreach (BarData bar in m_Bars)
  1113. {
  1114. bool inSelectionRegion = InSelectedRegion(bar.startDataOffset, bar.endDataOffset, selectedFirstOffset, selectedLastOffset, frameOffsetToSelectionIndex, subsetSelected);
  1115. // Draw tooltip for bar (or 1 pixel segment of bar)
  1116. {
  1117. int barStartIndex = offsetToDisplayMapping + m_Values[bar.startDataOffset].frameOffset;
  1118. int barEndIndex = offsetToDisplayMapping + m_Values[bar.endDataOffset].frameOffset;
  1119. string tooltip;
  1120. if (barStartIndex == barEndIndex)
  1121. tooltip = string.Format("Frame {0}\n{1}{2}", barStartIndex, ToDisplayUnits(bar.yMax, true), detailsString);
  1122. else
  1123. tooltip = string.Format("Frame {0}-{1}\n{2} max\n{3} min{4}", barStartIndex, barEndIndex, ToDisplayUnits(bar.yMax, true), ToDisplayUnits(bar.yMin, true), detailsString);
  1124. if (inSelectionRegion)
  1125. tooltip += selectionAreaString;
  1126. GUI.Label(new Rect(rect.x + bar.x, rect.y + 5, bar.w, height), new GUIContent("", tooltip));
  1127. }
  1128. }
  1129. }
  1130. GUI.enabled = lastEnabled;
  1131. if (showAxis)
  1132. {
  1133. int zoomedSelectedFirstOffset = selectedFirstOffset;
  1134. int zoomedSelectedLastOffset = selectedLastOffset;
  1135. int zoomedSelectedCount = selectedCount;
  1136. if (m_Zoomed)
  1137. {
  1138. if (selectedFirstOffset > endOffset || selectedLastOffset < startOffset)
  1139. {
  1140. zoomedSelectedCount = 0;
  1141. }
  1142. else
  1143. {
  1144. // Clamp selection range to zoom range
  1145. zoomedSelectedFirstOffset = ClampToRange(selectedFirstOffset, startOffset, endOffset);
  1146. zoomedSelectedLastOffset = ClampToRange(selectedLastOffset, startOffset, endOffset);
  1147. if (HasDragRegion())
  1148. {
  1149. zoomedSelectedCount = 1 + (zoomedSelectedLastOffset - zoomedSelectedFirstOffset);
  1150. }
  1151. else
  1152. {
  1153. zoomedSelectedCount = 0;
  1154. foreach (var offset in selectedFrameOffsets)
  1155. {
  1156. if (offset >= startOffset && offset <= endOffset)
  1157. {
  1158. zoomedSelectedCount++;
  1159. }
  1160. }
  1161. }
  1162. }
  1163. }
  1164. ShowAxis(rect, xStart, width, startOffset, endOffset, zoomedSelectedFirstOffset, zoomedSelectedLastOffset, zoomedSelectedCount, selectedCount, yMax, totalDataSize, offsetToDisplayMapping);
  1165. }
  1166. GUI.enabled = enabled;
  1167. if (rect.Contains(current.mousePosition) && current.type == EventType.ContextClick)
  1168. {
  1169. var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
  1170. ShowContextMenu(subsetSelected, selectedCount);
  1171. current.Use();
  1172. ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.FrameTimeContextMenu, analytic.GetDurationInSeconds(), true);
  1173. }
  1174. GUI.enabled = lastEnabled;
  1175. Profiler.EndSample();
  1176. }
  1177. void ShowThreads(float height, float yRange, BarData bar, bool full,
  1178. List<ThreadData> threads, bool subsetSelected, int selectedFirstOffset, int selectedLastOffset,
  1179. int offsetToIndexMapping, Dictionary<int, int> frameOffsetToSelectionIndex)
  1180. {
  1181. float max = float.MinValue;
  1182. bool selected = false;
  1183. for (int dataOffset = bar.startDataOffset; dataOffset <= bar.endDataOffset; dataOffset++)
  1184. {
  1185. int frameOffset = m_Values[dataOffset].frameOffset;
  1186. if (!full && !frameOffsetToSelectionIndex.ContainsKey(frameOffset))
  1187. continue;
  1188. float threadMs = 0f;
  1189. foreach (var thread in threads)
  1190. {
  1191. int frameIndex = offsetToIndexMapping + frameOffset;
  1192. var frame = thread.GetFrame(frameIndex);
  1193. if (frame == null)
  1194. continue;
  1195. float ms = frame.Value.ms;
  1196. if (ms > threadMs)
  1197. threadMs = ms;
  1198. }
  1199. if (threadMs > max)
  1200. max = threadMs;
  1201. if (m_Dragging)
  1202. {
  1203. if (frameOffset >= selectedFirstOffset && frameOffset <= selectedLastOffset)
  1204. selected = true;
  1205. }
  1206. else if (subsetSelected)
  1207. {
  1208. if (frameOffsetToSelectionIndex.ContainsKey(frameOffset))
  1209. selected = true;
  1210. }
  1211. }
  1212. if (full || selected)
  1213. {
  1214. // Clamp to frame time (these values can be time summed over multiple threads)
  1215. if (max > bar.yMax)
  1216. max = bar.yMax;
  1217. float maxClamped = Math.Min(max, yRange);
  1218. float h = height * maxClamped / yRange;
  1219. m_2D.DrawFilledBox(bar.x, bar.y, bar.w, h, selected ? m_ColorBarThreadsSelected : m_ColorBarThreads);
  1220. // Show where its been clamped
  1221. if (max > yRange)
  1222. {
  1223. m_2D.DrawFilledBox(bar.x, bar.y + height, bar.w, kOverrunHeight, m_ColorBarThreadsOutOfRange);
  1224. }
  1225. }
  1226. }
  1227. void ShowSelectedMarker(float height, float yRange, BarData bar, bool full,
  1228. MarkerData selectedMarker, bool subsetSelected, int selectedFirstOffset, int selectedLastOffset,
  1229. int offsetToIndexMapping, Dictionary<int, int> frameOffsetToSelectionIndex)
  1230. {
  1231. float max = 0f;
  1232. bool selected = false;
  1233. if (selectedMarker != null)
  1234. {
  1235. for (int dataOffset = bar.startDataOffset; dataOffset <= bar.endDataOffset; dataOffset++)
  1236. {
  1237. int frameOffset = m_Values[dataOffset].frameOffset;
  1238. if (!full && !frameOffsetToSelectionIndex.ContainsKey(frameOffset))
  1239. continue;
  1240. float ms = selectedMarker.GetFrameMs(offsetToIndexMapping + frameOffset);
  1241. if (ms > max)
  1242. max = ms;
  1243. if (m_Dragging)
  1244. {
  1245. if (frameOffset >= selectedFirstOffset && frameOffset <= selectedLastOffset)
  1246. selected = true;
  1247. }
  1248. else if (subsetSelected)
  1249. {
  1250. if (frameOffsetToSelectionIndex.ContainsKey(frameOffset))
  1251. selected = true;
  1252. }
  1253. }
  1254. }
  1255. if (full || selected)
  1256. {
  1257. // Clamp to frame time (these values can be tiem summed over multiple threads)
  1258. if (max > bar.yMax)
  1259. max = bar.yMax;
  1260. float maxClamped = Math.Min(max, yRange);
  1261. float h = height * maxClamped / yRange;
  1262. m_2D.DrawFilledBox(bar.x, bar.y, bar.w, h, selected ? m_ColorBarMarkerSelected : m_ColorBarMarker);
  1263. if (max > 0f)
  1264. {
  1265. // we start the bar lower so that very small markers still show up.
  1266. m_2D.DrawFilledBox(bar.x, bar.y - kOverrunHeight, bar.w, kOverrunHeight, m_ColorBarMarkerOutOfRange);
  1267. }
  1268. // Show where its been clamped
  1269. if (max > yRange)
  1270. {
  1271. m_2D.DrawFilledBox(bar.x, bar.y + height, bar.w, kOverrunHeight, m_ColorBarMarkerOutOfRange);
  1272. }
  1273. }
  1274. }
  1275. void ShowFrameLinesText(Rect rect, float xStart, float yStart, float yRange, float width, float height, bool subsetSelected, int selectedFirstOffset, int selectedLastOffset)
  1276. {
  1277. int totalDataSize = m_Values.Count;
  1278. float y = yStart;
  1279. float msSegment = 1000f / 60f;
  1280. int lines = (int)(yRange / msSegment);
  1281. int step = 1;
  1282. for (int line = 1; line <= lines; line += step, step *= 2)
  1283. {
  1284. float ms = line * msSegment;
  1285. float h = height * ms / yRange;
  1286. int edgePad = 3;
  1287. if (h >= (height / 4) && h < (height - GUI.skin.label.lineHeight))
  1288. {
  1289. GUIContent content = new GUIContent(ToDisplayUnits((float)Math.Floor(ms), true, 0));
  1290. Vector2 size = EditorStyles.miniTextField.CalcSize(content);
  1291. bool left = true;
  1292. if (subsetSelected)
  1293. {
  1294. float x = GetXForDataOffset(selectedFirstOffset, (int)width, totalDataSize);
  1295. float x2 = GetXForDataOffset(selectedLastOffset + 1, (int)width, totalDataSize);
  1296. // text would overlap selection so move it if that prevents overlap
  1297. if (left)
  1298. {
  1299. if (x < (size.x + edgePad) && x2 < (width - (size.x + edgePad)))
  1300. left = false;
  1301. }
  1302. else
  1303. {
  1304. if (x > (size.x + edgePad) && x2 > (width - (size.x + edgePad)))
  1305. left = true;
  1306. }
  1307. }
  1308. Rect r;
  1309. if (left)
  1310. r = new Rect(rect.x + (xStart + edgePad), (rect.y - y) + (height - h), size.x, EditorStyles.miniTextField.lineHeight);
  1311. else
  1312. r = new Rect(rect.x + (xStart + width) - (size.x + edgePad), (rect.y - y) + (height - h), size.x, EditorStyles.miniTextField.lineHeight);
  1313. GUI.Label(r, content, EditorStyles.miniTextField);
  1314. }
  1315. }
  1316. }
  1317. void ShowSelectionMenuItem(bool subsetSelected, GenericMenu menu, GUIContent style, bool state, GenericMenu.MenuFunction func)
  1318. {
  1319. if (subsetSelected)
  1320. menu.AddItem(style, state, func);
  1321. else
  1322. menu.AddDisabledItem(style);
  1323. }
  1324. void ShowContextMenu(bool subsetSelected, int selectionCount)
  1325. {
  1326. GenericMenu menu = new GenericMenu();
  1327. bool showselectionOptions = subsetSelected || ((m_PairedWithFrameTimeGraph != null) && m_PairedWithFrameTimeGraph.HasSubsetSelected());
  1328. ShowSelectionMenuItem(showselectionOptions || selectionCount == 0, menu, Styles.menuItemSelectAll, false, () => SelectAll());
  1329. ShowSelectionMenuItem(showselectionOptions || selectionCount == m_Values.Count, menu, Styles.menuItemClearSelection, false, () => ClearSelection());
  1330. menu.AddItem(Styles.menuItemInvertSelection, false, () => InvertSelection());
  1331. menu.AddItem(Styles.menuItemSelectMin, false, () => SelectMin());
  1332. menu.AddItem(Styles.menuItemSelectMax, false, () => SelectMax());
  1333. menu.AddItem(Styles.menuItemSelectMedian, false, () => SelectMedian());
  1334. menu.AddSeparator("");
  1335. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectPrevious, false, () => SelectPrevious(1));
  1336. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectNext, false, () => SelectNext(1));
  1337. menu.AddSeparator("");
  1338. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrow, false, () => SelectGrow(1));
  1339. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrink, false, () => SelectShrink(1));
  1340. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowLeft, false, () => SelectGrowLeft(1));
  1341. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowRight, false, () => SelectGrowRight(1));
  1342. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkLeft, false, () => SelectShrinkLeft(1));
  1343. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkRight, false, () => SelectShrinkRight(1));
  1344. menu.AddSeparator("");
  1345. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowFast, false, () => SelectGrow(10));
  1346. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkFast, false, () => SelectShrink(10));
  1347. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowLeftFast, false, () => SelectGrowLeft(10));
  1348. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowRightFast, false, () => SelectGrowRight(10));
  1349. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkLeftFast, false, () => SelectShrinkLeft(10));
  1350. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkRightFast, false, () => SelectShrinkRight(10));
  1351. menu.AddSeparator("");
  1352. ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemZoomSelection, false, () => ZoomSelection());
  1353. ShowSelectionMenuItem(m_Zoomed, menu, Styles.menuItemZoomAll, false, () => ZoomAll());
  1354. menu.AddSeparator("");
  1355. menu.AddItem(Styles.menuItemShowSelectedMarker, m_GlobalSettings.showSelectedMarker, () => ToggleShowSelectedMarker());
  1356. menu.AddItem(Styles.menuItemShowThreads, m_GlobalSettings.showThreads, () => ToggleShowThreads());
  1357. menu.AddItem(Styles.menuItemShowFrameLines, m_GlobalSettings.showFrameLines, () => ToggleShowFrameLines());
  1358. //menu.AddItem(Styles.menuItemShowFrameLineText, m_GlobalSettings.showFrameLineText, () => ToggleShowFrameLinesText());
  1359. menu.AddSeparator("");
  1360. menu.AddItem(Styles.menuItemShowOrderedByFrameDuration, m_GlobalSettings.showOrderedByFrameDuration, () => ToggleShowOrderedByFrameDuration());
  1361. menu.ShowAsContext();
  1362. }
  1363. string GetYMaxText(float value)
  1364. {
  1365. return ToDisplayUnits(value, true, 0);
  1366. }
  1367. void DrawYAxisRangeSelector(Rect rect, float yMax)
  1368. {
  1369. string yMaxText = GetYMaxText(yMax);
  1370. List<GUIContent> yAxisOptions = new List<GUIContent>();
  1371. var graphScaleUnits = ToDisplayUnits(1000f / 60f, true, 0);
  1372. yAxisOptions.Add(new GUIContent(graphScaleUnits, string.Format("Graph Scale : {0} is equivalent to 60Hz or 60FPS.", graphScaleUnits)));
  1373. graphScaleUnits = ToDisplayUnits(1000f / 30f, true, 0);
  1374. yAxisOptions.Add(new GUIContent(graphScaleUnits, string.Format("Graph Scale : {0} is equivalent to 30Hz or 30FPS.", graphScaleUnits)));
  1375. graphScaleUnits = ToDisplayUnits(1000f / 15f, true, 0);
  1376. yAxisOptions.Add(new GUIContent(graphScaleUnits, string.Format("Graph Scale : {0} is equivalent to 15Hz or 15FPS.", graphScaleUnits)));
  1377. yAxisOptions.Add(new GUIContent(yMaxText, "Graph Scale : Max frame time from data"));
  1378. float width = 0;
  1379. foreach (var content in yAxisOptions)
  1380. {
  1381. Vector2 size = EditorStyles.popup.CalcSize(content);
  1382. if (size.x > width)
  1383. width = size.x;
  1384. }
  1385. // Use smaller width if text is shorter
  1386. int margin = 2;
  1387. width = Math.Min(width + margin, rect.width);
  1388. // Shift right to right align
  1389. rect.x += (rect.width - width);
  1390. rect.x -= margin;
  1391. rect.width = width;
  1392. s_YAxisMode = (AxisMode)EditorGUI.Popup(rect, (int)s_YAxisMode, yAxisOptions.ToArray());
  1393. }
  1394. void ShowAxis(Rect rect, float xStart, float width, int startOffset, int endOffset, int selectedFirstOffset, int selectedLastOffset, int selectedCount, int totalSelectedCount, float yMax, int totalDataSize, int offsetToDisplayMapping)
  1395. {
  1396. GUIStyle leftAlignStyle = new GUIStyle(GUI.skin.label);
  1397. leftAlignStyle.padding = new RectOffset(leftAlignStyle.padding.left, leftAlignStyle.padding.right, 0, 0);
  1398. leftAlignStyle.alignment = TextAnchor.MiddleLeft;
  1399. GUIStyle rightAlignStyle = new GUIStyle(GUI.skin.label);
  1400. rightAlignStyle.padding = new RectOffset(rightAlignStyle.padding.left, rightAlignStyle.padding.right, 0, 0);
  1401. rightAlignStyle.alignment = TextAnchor.MiddleRight;
  1402. // y axis
  1403. float h = GUI.skin.label.lineHeight;
  1404. float y = rect.y + ((rect.height - 1) - h);
  1405. DrawYAxisRangeSelector(new Rect(rect.x, rect.y, kXAxisWidth, h), yMax);
  1406. string yMinText = ToDisplayUnits(0, true);
  1407. GUI.Label(new Rect(rect.x, y - h, kXAxisWidth, h), yMinText, rightAlignStyle);
  1408. // x axis
  1409. rect.x += xStart;
  1410. leftAlignStyle.padding = new RectOffset(0, 0, leftAlignStyle.padding.top, leftAlignStyle.padding.bottom);
  1411. rightAlignStyle.padding = new RectOffset(0, 0, rightAlignStyle.padding.top, rightAlignStyle.padding.bottom);
  1412. int startIndex = offsetToDisplayMapping + startOffset;
  1413. string startIndexText = string.Format("{0}", startIndex);
  1414. GUIContent startIndexContent = new GUIContent(startIndexText);
  1415. Vector2 startIndexSize = GUI.skin.label.CalcSize(startIndexContent);
  1416. bool drawStart = !m_GlobalSettings.showOrderedByFrameDuration;
  1417. int endIndex = offsetToDisplayMapping + endOffset;
  1418. string endIndexText = string.Format("{0}", endIndex);
  1419. GUIContent endIndexContent = new GUIContent(endIndexText);
  1420. Vector2 endIndexSize = GUI.skin.label.CalcSize(endIndexContent);
  1421. bool drawEnd = !m_GlobalSettings.showOrderedByFrameDuration;
  1422. // Show selection frame values (if space for them)
  1423. if (totalSelectedCount > 0)
  1424. {
  1425. if (selectedCount == 0)
  1426. {
  1427. // If we have no selection then adjust 'selection start/end to span whole view so the count display is centred)
  1428. selectedFirstOffset = startOffset;
  1429. selectedLastOffset = endOffset;
  1430. }
  1431. int selectedFirstX = GetXForDataOffset(selectedFirstOffset, (int)width, totalDataSize);
  1432. int selectedLastX = GetXForDataOffset(selectedLastOffset + 1, (int)width, totalDataSize); // last + 1 so right hand side of the bbar
  1433. int selectedRangeWidth = 1 + (selectedLastX - selectedFirstX);
  1434. int selectedFirstIndex = offsetToDisplayMapping + selectedFirstOffset;
  1435. int selectedLastIndex = offsetToDisplayMapping + selectedLastOffset;
  1436. string selectionCountText;
  1437. if (totalSelectedCount != selectedCount)
  1438. selectionCountText = string.Format("[{0} of {1}]", selectedCount, totalSelectedCount);
  1439. else
  1440. selectionCountText = string.Format("[{0}]", selectedCount);
  1441. string selectionRangeText;
  1442. if (selectedCount > 1)
  1443. {
  1444. if (m_GlobalSettings.showOrderedByFrameDuration)
  1445. selectionRangeText = selectionCountText;
  1446. else
  1447. selectionRangeText = string.Format("{0} {1} {2}", selectedFirstIndex, selectionCountText, selectedLastIndex);
  1448. }
  1449. else
  1450. selectionRangeText = string.Format("{0} {1}", selectedFirstIndex, selectionCountText);
  1451. string tooltip = string.Format("{0} frames in selection", selectedCount);
  1452. if (totalSelectedCount != selectedCount)
  1453. {
  1454. tooltip = string.Format("{0} frames in zoomed selection\n{1} frames in overall selection", selectedCount, totalSelectedCount);
  1455. }
  1456. GUIContent selectionRangeTextContent = new GUIContent(selectionRangeText, tooltip);
  1457. Vector2 selectionRangeTextSize = GUI.skin.label.CalcSize(selectionRangeTextContent);
  1458. if ((selectedRangeWidth > selectionRangeTextSize.x && selectedCount > 1) || selectedCount == 0)
  1459. {
  1460. // Selection width is larger than the text so we can split the text
  1461. string selectedFirstIndexText = string.Format("{0}", selectedFirstIndex);
  1462. GUIContent selectedFirstIndexContent = new GUIContent(selectedFirstIndexText);
  1463. Vector2 selectedFirstIndexSize = GUI.skin.label.CalcSize(selectedFirstIndexContent);
  1464. if (m_GlobalSettings.showOrderedByFrameDuration)
  1465. selectedFirstIndexSize.x = 0;
  1466. string selectedLastIndexText = string.Format("{0}", selectedLastIndex);
  1467. GUIContent selectedLastIndexContent = new GUIContent(selectedLastIndexText);
  1468. Vector2 selectedLastIndexSize = GUI.skin.label.CalcSize(selectedLastIndexContent);
  1469. if (m_GlobalSettings.showOrderedByFrameDuration)
  1470. selectedLastIndexSize.x = 0;
  1471. GUIContent selectedCountContent = new GUIContent(selectionCountText, tooltip);
  1472. Vector2 selectedCountSize = GUI.skin.label.CalcSize(selectedCountContent);
  1473. Rect rFirst = new Rect(rect.x + selectedFirstX, y, selectedFirstIndexSize.x, selectedFirstIndexSize.y);
  1474. GUI.Label(rFirst, selectedFirstIndexContent);
  1475. Rect rLast = new Rect(rect.x + selectedLastX - selectedLastIndexSize.x, y, selectedLastIndexSize.x, selectedLastIndexSize.y);
  1476. GUI.Label(rLast, selectedLastIndexContent);
  1477. float mid = selectedFirstX + ((selectedLastX - selectedFirstX) / 2);
  1478. Rect rCount = new Rect(rect.x + mid - (selectedCountSize.x / 2), y, selectedCountSize.x, selectedCountSize.y);
  1479. GUI.Label(rCount, selectedCountContent);
  1480. if (selectedFirstX < startIndexSize.x)
  1481. {
  1482. // would overlap with start text
  1483. drawStart = false;
  1484. }
  1485. if (selectedLastX > ((width - 1) - endIndexSize.x))
  1486. {
  1487. // would overlap with end text
  1488. drawEnd = false;
  1489. }
  1490. }
  1491. else
  1492. {
  1493. int mid = (selectedFirstX + (selectedRangeWidth / 2));
  1494. int selectionTextX = mid - (int)(selectionRangeTextSize.x / 2);
  1495. selectionTextX = ClampToRange(selectionTextX, 0, (int)((width - 1) - selectionRangeTextSize.x));
  1496. Rect rangeRect = new Rect(rect.x + selectionTextX, y, selectionRangeTextSize.x, selectionRangeTextSize.y);
  1497. GUI.Label(rangeRect, selectionRangeTextContent);
  1498. if (selectionTextX < startIndexSize.x)
  1499. {
  1500. // would overlap with start text
  1501. drawStart = false;
  1502. }
  1503. if ((selectionTextX + selectionRangeTextSize.x) > ((width - 1) - endIndexSize.x))
  1504. {
  1505. // would overlap with end text
  1506. drawEnd = false;
  1507. }
  1508. }
  1509. }
  1510. // Show start and end values
  1511. if (drawStart)
  1512. {
  1513. Rect leftRect = new Rect(rect.x, y, startIndexSize.x, startIndexSize.y);
  1514. GUI.Label(leftRect, startIndexContent, leftAlignStyle);
  1515. }
  1516. if (drawEnd)
  1517. {
  1518. Rect rightRect = new Rect(rect.x + ((width - 1) - endIndexSize.x), y, endIndexSize.x, endIndexSize.y);
  1519. GUI.Label(rightRect, endIndexContent, rightAlignStyle);
  1520. }
  1521. }
  1522. void ClearSelection(bool effectPaired = true)
  1523. {
  1524. int dataLength = m_Values.Count;
  1525. bool singleControlAction = true; // As we need the frame range to be unique to each data set
  1526. CallSetRange(-1, -1, 0, singleControlAction, FrameTimeGraph.State.DragComplete);
  1527. // Disable zoom
  1528. m_Zoomed = false;
  1529. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1530. m_PairedWithFrameTimeGraph.ClearSelection(false);
  1531. }
  1532. void SelectAll(bool effectPaired = true)
  1533. {
  1534. int dataLength = m_Values.Count;
  1535. bool singleControlAction = true; // As we need the frame range to be unique to each data set
  1536. CallSetRange(0, dataLength - 1, 0, singleControlAction, FrameTimeGraph.State.DragComplete);
  1537. // Disable zoom
  1538. m_Zoomed = false;
  1539. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1540. m_PairedWithFrameTimeGraph.SelectAll(false);
  1541. }
  1542. void InvertSelection(bool effectPaired = true)
  1543. {
  1544. int dataLength = m_Values.Count;
  1545. Dictionary<int, int> frameOffsetToSelectionIndex = new Dictionary<int, int>();
  1546. for (int i = 0; i < m_CurrentSelection.Count; i++)
  1547. {
  1548. int frameOffset = m_CurrentSelection[i];
  1549. frameOffsetToSelectionIndex[frameOffset] = i;
  1550. }
  1551. m_CurrentSelection.Clear();
  1552. for (int frameIndex = 0; frameIndex < dataLength; frameIndex++)
  1553. {
  1554. if (!frameOffsetToSelectionIndex.ContainsKey(frameIndex))
  1555. m_CurrentSelection.Add(frameIndex);
  1556. }
  1557. m_SetRange(m_CurrentSelection, 1, FrameTimeGraph.State.DragComplete);
  1558. // Disable zoom
  1559. m_Zoomed = false;
  1560. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1561. m_PairedWithFrameTimeGraph.InvertSelection(false);
  1562. }
  1563. void ZoomSelection(bool effectPaired = true)
  1564. {
  1565. m_Zoomed = true;
  1566. m_ZoomStartOffset = m_CurrentSelectionFirstDataOffset;
  1567. m_ZoomEndOffset = m_CurrentSelectionLastDataOffset;
  1568. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1569. m_PairedWithFrameTimeGraph.ZoomSelection(false);
  1570. }
  1571. void ZoomAll(bool effectPaired = true)
  1572. {
  1573. m_Zoomed = false;
  1574. int frames = m_Values.Count;
  1575. m_ZoomStartOffset = 0;
  1576. m_ZoomEndOffset = frames - 1;
  1577. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1578. m_PairedWithFrameTimeGraph.ZoomAll(false);
  1579. }
  1580. void SelectMin(bool effectPaired = true)
  1581. {
  1582. int dataLength = m_Values.Count;
  1583. if (dataLength <= 0)
  1584. return;
  1585. int minDataOffset = 0;
  1586. float msMin = m_Values[0].ms;
  1587. for (int dataOffset = 0; dataOffset < dataLength; dataOffset++)
  1588. {
  1589. float ms = m_Values[dataOffset].ms;
  1590. if (ms < msMin)
  1591. {
  1592. msMin = ms;
  1593. minDataOffset = dataOffset;
  1594. }
  1595. }
  1596. bool singleControlAction = true;
  1597. CallSetRange(minDataOffset, minDataOffset, 1, singleControlAction, State.DragComplete); // act like single click, so we select frame
  1598. //CallSetRange(minDataOffset, minDataOffset, 2, singleControlAction, State.DragComplete); // act like double click, so we jump to the frame
  1599. m_Zoomed = false;
  1600. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1601. m_PairedWithFrameTimeGraph.SelectMin(false);
  1602. }
  1603. void SelectMax(bool effectPaired = true)
  1604. {
  1605. int dataLength = m_Values.Count;
  1606. if (dataLength <= 0)
  1607. return;
  1608. int maxDataOffset = 0;
  1609. float msMax = m_Values[0].ms;
  1610. for (int dataOffset = 0; dataOffset < dataLength; dataOffset++)
  1611. {
  1612. float ms = m_Values[dataOffset].ms;
  1613. if (ms > msMax)
  1614. {
  1615. msMax = ms;
  1616. maxDataOffset = dataOffset;
  1617. }
  1618. }
  1619. bool singleControlAction = true;
  1620. CallSetRange(maxDataOffset, maxDataOffset, 1, singleControlAction, State.DragComplete); // act like single click, so we select frame
  1621. //CallSetRange(maxDataOffset, maxDataOffset, 2, singleControlAction, State.DragComplete); // act like double click, so we jump to the frame
  1622. m_Zoomed = false;
  1623. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1624. m_PairedWithFrameTimeGraph.SelectMax(false);
  1625. }
  1626. float GetPercentageOffset(List<Data> data, float percent, out int outputFrameOffset)
  1627. {
  1628. int index = (int)((data.Count - 1) * percent / 100);
  1629. outputFrameOffset = data[index].frameOffset;
  1630. // True median is half of the sum of the middle 2 frames for an even count. However this would be a value never recorded so we avoid that.
  1631. return data[index].ms;
  1632. }
  1633. void SelectMedian(bool effectPaired = true)
  1634. {
  1635. int dataLength = m_Values.Count;
  1636. if (dataLength <= 0)
  1637. return;
  1638. List<Data> sortedValues = new List<Data>();
  1639. foreach (var value in m_Values)
  1640. {
  1641. Data data = new Data(value.ms, value.frameOffset);
  1642. sortedValues.Add(data);
  1643. }
  1644. // If ms value is the same then order by frame offset
  1645. sortedValues.Sort((a, b) => { int compare = a.ms.CompareTo(b.ms); return compare == 0 ? a.frameOffset.CompareTo(b.frameOffset) : compare; });
  1646. int medianFrameOffset = 0;
  1647. GetPercentageOffset(sortedValues, 50, out medianFrameOffset);
  1648. int medianDataOffset = m_FrameOffsetToDataOffsetMapping[medianFrameOffset];
  1649. bool singleControlAction = true;
  1650. CallSetRange(medianDataOffset, medianDataOffset, 1, singleControlAction, State.DragComplete); // act like single click, so we select frame
  1651. //CallSetRange(medianDataOffset, medianDataOffset, 2, singleControlAction, State.DragComplete); // act like double click, so we jump to the frame
  1652. m_Zoomed = false;
  1653. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1654. m_PairedWithFrameTimeGraph.SelectMedian(false);
  1655. }
  1656. public void SelectPrevious(int step, bool effectPaired = true)
  1657. {
  1658. int clicks = 1;
  1659. bool singleClickAction = true;
  1660. MoveSelectedRange(-step, clicks, singleClickAction, State.DragComplete);
  1661. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1662. m_PairedWithFrameTimeGraph.SelectPrevious(step, false);
  1663. }
  1664. public void SelectNext(int step, bool effectPaired = true)
  1665. {
  1666. int clicks = 1;
  1667. bool singleClickAction = true;
  1668. MoveSelectedRange(step, clicks, singleClickAction, State.DragComplete);
  1669. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1670. m_PairedWithFrameTimeGraph.SelectNext(step, false);
  1671. }
  1672. public void SelectGrow(int step, bool effectPaired = true)
  1673. {
  1674. int clicks = 1;
  1675. bool singleClickAction = true;
  1676. ResizeSelectedRange(-step, step, clicks, singleClickAction, State.DragComplete);
  1677. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1678. m_PairedWithFrameTimeGraph.SelectGrow(step, false);
  1679. }
  1680. public void SelectGrowLeft(int step, bool effectPaired = true)
  1681. {
  1682. int clicks = 1;
  1683. bool singleClickAction = true;
  1684. ResizeSelectedRange(-step, 0, clicks, singleClickAction, State.DragComplete);
  1685. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1686. m_PairedWithFrameTimeGraph.SelectGrowLeft(step, false);
  1687. }
  1688. public void SelectGrowRight(int step, bool effectPaired = true)
  1689. {
  1690. int clicks = 1;
  1691. bool singleClickAction = true;
  1692. ResizeSelectedRange(0, step, clicks, singleClickAction, State.DragComplete);
  1693. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1694. m_PairedWithFrameTimeGraph.SelectGrowRight(step, false);
  1695. }
  1696. public void SelectShrink(int step, bool effectPaired = true)
  1697. {
  1698. int clicks = 1;
  1699. bool singleClickAction = true;
  1700. ResizeSelectedRange(step, -step, clicks, singleClickAction, State.DragComplete);
  1701. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1702. m_PairedWithFrameTimeGraph.SelectShrink(step, false);
  1703. }
  1704. public void SelectShrinkLeft(int step, bool effectPaired = true)
  1705. {
  1706. int clicks = 1;
  1707. bool singleClickAction = true;
  1708. ResizeSelectedRange(step, 0, clicks, singleClickAction, State.DragComplete);
  1709. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1710. m_PairedWithFrameTimeGraph.SelectShrinkLeft(step, false);
  1711. }
  1712. public void SelectShrinkRight(int step, bool effectPaired = true)
  1713. {
  1714. int clicks = 1;
  1715. bool singleClickAction = true;
  1716. ResizeSelectedRange(0, -step, clicks, singleClickAction, State.DragComplete);
  1717. if (m_PairedWithFrameTimeGraph != null && effectPaired)
  1718. m_PairedWithFrameTimeGraph.SelectShrinkRight(step, false);
  1719. }
  1720. public void ToggleShowThreads()
  1721. {
  1722. m_GlobalSettings.showThreads ^= true;
  1723. }
  1724. public void ToggleShowSelectedMarker()
  1725. {
  1726. m_GlobalSettings.showSelectedMarker ^= true;
  1727. }
  1728. public void ToggleShowFrameLines()
  1729. {
  1730. m_GlobalSettings.showFrameLines ^= true;
  1731. }
  1732. public void ToggleShowFrameLinesText()
  1733. {
  1734. m_GlobalSettings.showFrameLineText ^= true;
  1735. }
  1736. public void ToggleShowOrderedByFrameDuration()
  1737. {
  1738. m_GlobalSettings.showOrderedByFrameDuration ^= true;
  1739. SetData(m_Values); // Update order
  1740. if (m_PairedWithFrameTimeGraph != null)
  1741. {
  1742. m_PairedWithFrameTimeGraph.SetData(m_PairedWithFrameTimeGraph.m_Values); // Update order
  1743. }
  1744. }
  1745. internal struct SelectedRangeState
  1746. {
  1747. public int currentSelectionFirstDataOffset;
  1748. public int currentSelectionLastDataOffset;
  1749. public List<int> lastSelectedFrameOffsets;
  1750. }
  1751. void MoveSelectedRange(int offset, int clickCount, bool singleControlAction, State inputStatus)
  1752. {
  1753. MoveSelectedRange(offset, clickCount, singleControlAction, inputStatus, new SelectedRangeState()
  1754. {
  1755. currentSelectionFirstDataOffset = m_CurrentSelectionFirstDataOffset,
  1756. currentSelectionLastDataOffset = m_CurrentSelectionLastDataOffset,
  1757. lastSelectedFrameOffsets = m_LastSelectedFrameOffsets,
  1758. });
  1759. }
  1760. internal void MoveSelectedRange(int offset, int clickCount, bool singleControlAction, State inputStatus, SelectedRangeState selectedRange)
  1761. {
  1762. var currentSelectionFirstDataOffset = selectedRange.currentSelectionFirstDataOffset;
  1763. var currentSelectionLastDataOffset = selectedRange.currentSelectionLastDataOffset;
  1764. var lastSelectedFrameOffsets = selectedRange.lastSelectedFrameOffsets;
  1765. if (offset < 0)
  1766. {
  1767. // Clamp offset to graph lower bound.
  1768. if (currentSelectionFirstDataOffset + offset < 0)
  1769. {
  1770. offset = -currentSelectionFirstDataOffset;
  1771. }
  1772. }
  1773. else
  1774. {
  1775. // Clamp offset to graph upper bound.
  1776. if (currentSelectionLastDataOffset + offset >= m_Values.Count)
  1777. {
  1778. offset = (m_Values.Count - 1) - currentSelectionLastDataOffset;
  1779. }
  1780. }
  1781. // Offset selection.
  1782. List<int> selected = new List<int>();
  1783. foreach (int selectedFrameOffset in lastSelectedFrameOffsets)
  1784. {
  1785. var selectedDataOffset = m_FrameOffsetToDataOffsetMapping[selectedFrameOffset];
  1786. var newDataOffset = selectedDataOffset + offset;
  1787. if (newDataOffset >= 0 && newDataOffset < m_Values.Count)
  1788. {
  1789. var newFrameOffset = m_Values[newDataOffset].frameOffset;
  1790. if (!selected.Contains(newFrameOffset))
  1791. {
  1792. selected.Add(newFrameOffset);
  1793. }
  1794. }
  1795. }
  1796. // Sort selection in frame index order.
  1797. selected.Sort();
  1798. m_SetRange(selected, clickCount, inputStatus);
  1799. }
  1800. void ResizeSelectedRange(int leftOffset, int rightOffset, int clickCount, bool singleControlAction, State inputState)
  1801. {
  1802. ResizeSelectedRange(leftOffset, rightOffset, clickCount, singleControlAction, inputState, new SelectedRangeState()
  1803. {
  1804. currentSelectionFirstDataOffset = m_CurrentSelectionFirstDataOffset,
  1805. currentSelectionLastDataOffset = m_CurrentSelectionLastDataOffset,
  1806. lastSelectedFrameOffsets = m_LastSelectedFrameOffsets,
  1807. });
  1808. }
  1809. internal void ResizeSelectedRange(int leftOffset, int rightOffset, int clickCount, bool singleControlAction, State inputState, SelectedRangeState selectedRange)
  1810. {
  1811. const int k_InvalidDataOffset = -1;
  1812. var currentSelectionFirstDataOffset = selectedRange.currentSelectionFirstDataOffset;
  1813. var currentSelectionLastDataOffset = selectedRange.currentSelectionLastDataOffset;
  1814. var lastSelectedFrameOffsets = selectedRange.lastSelectedFrameOffsets;
  1815. // Clamp left offset to lower graph bound.
  1816. bool isGrowingLeft = leftOffset < 0;
  1817. if (isGrowingLeft && currentSelectionFirstDataOffset + leftOffset < 0)
  1818. {
  1819. leftOffset = -currentSelectionFirstDataOffset;
  1820. }
  1821. // Clamp right offset to upper graph bound.
  1822. bool isGrowingRight = rightOffset > 0;
  1823. if (isGrowingRight && currentSelectionLastDataOffset + rightOffset > (m_Values.Count - 1))
  1824. {
  1825. rightOffset = (m_Values.Count - 1) - currentSelectionLastDataOffset;
  1826. }
  1827. int selectionStartDataOffset = k_InvalidDataOffset;
  1828. List<int> selected = new List<int>(lastSelectedFrameOffsets);
  1829. for (int dataOffset = 0; dataOffset < m_Values.Count; ++dataOffset)
  1830. {
  1831. var frameOffset = m_Values[dataOffset].frameOffset;
  1832. if (selectionStartDataOffset == k_InvalidDataOffset)
  1833. {
  1834. // Find selection start.
  1835. if (lastSelectedFrameOffsets.Contains(frameOffset))
  1836. {
  1837. selectionStartDataOffset = dataOffset;
  1838. }
  1839. }
  1840. else
  1841. {
  1842. // Find selection end.
  1843. bool isSelected = lastSelectedFrameOffsets.Contains(frameOffset);
  1844. if (!isSelected || dataOffset == (m_Values.Count - 1))
  1845. {
  1846. int selectionEndDataOffset;
  1847. // If we reached the last index and it is selected, this index is the selection end. Otherwise, the previous index is the last selected.
  1848. bool isLastIndex = dataOffset == (m_Values.Count - 1);
  1849. if (isLastIndex && isSelected)
  1850. {
  1851. selectionEndDataOffset = dataOffset;
  1852. }
  1853. else
  1854. {
  1855. selectionEndDataOffset = dataOffset - 1;
  1856. }
  1857. int newSelectionStartDataOffset = Mathf.Clamp(selectionStartDataOffset + leftOffset, 0, m_Values.Count - 1);
  1858. int newSelectionEndDataOffset = Mathf.Clamp(selectionEndDataOffset + rightOffset, 0, m_Values.Count - 1);
  1859. // Enforce a minimum selection width.
  1860. if (newSelectionEndDataOffset < newSelectionStartDataOffset)
  1861. {
  1862. var maximumOffset = (selectionEndDataOffset - selectionStartDataOffset) * 0.5f;
  1863. var adjustedLeftOffset = Mathf.CeilToInt(maximumOffset);
  1864. newSelectionStartDataOffset = Mathf.Clamp(selectionStartDataOffset + adjustedLeftOffset, 0, m_Values.Count - 1);
  1865. var adjustedRightOffset = -Mathf.FloorToInt(maximumOffset);
  1866. newSelectionEndDataOffset = Mathf.Clamp(selectionEndDataOffset + adjustedRightOffset, 0, m_Values.Count - 1);
  1867. }
  1868. if (selectionStartDataOffset != newSelectionStartDataOffset)
  1869. {
  1870. // Resize from left edge.
  1871. int startDataOffset = Mathf.Min(selectionStartDataOffset, newSelectionStartDataOffset);
  1872. int endDataOffset = Mathf.Max(selectionStartDataOffset, newSelectionStartDataOffset);
  1873. MoveSelectionEdge(startDataOffset, endDataOffset, isGrowingLeft, ref selected);
  1874. }
  1875. if (selectionEndDataOffset != newSelectionEndDataOffset)
  1876. {
  1877. // Resize from right edge (iterate backwards).
  1878. int startDataOffset = Mathf.Max(selectionEndDataOffset, newSelectionEndDataOffset);
  1879. int endDataOffset = Mathf.Min(selectionEndDataOffset, newSelectionEndDataOffset);
  1880. MoveSelectionEdge(startDataOffset, endDataOffset, isGrowingRight, ref selected);
  1881. }
  1882. // Reset to find next selection.
  1883. selectionStartDataOffset = k_InvalidDataOffset;
  1884. }
  1885. }
  1886. }
  1887. // Sort selection in frame index order.
  1888. selected.Sort();
  1889. m_SetRange(selected, clickCount, inputState);
  1890. }
  1891. void MoveSelectionEdge(int startDataOffset, int endDataOffset, bool isGrowingFromEdge, ref List<int> selection)
  1892. {
  1893. var direction = (startDataOffset >= endDataOffset) ? -1 : 1;
  1894. for (int dataOffset = startDataOffset; dataOffset != endDataOffset; dataOffset += direction)
  1895. {
  1896. var frameOffset = m_Values[dataOffset].frameOffset;
  1897. var indexInSelection = selection.IndexOf(frameOffset);
  1898. if (isGrowingFromEdge)
  1899. {
  1900. if (indexInSelection == -1)
  1901. {
  1902. selection.Add(frameOffset);
  1903. }
  1904. }
  1905. else
  1906. {
  1907. if (indexInSelection != -1)
  1908. {
  1909. selection.RemoveAt(indexInSelection);
  1910. }
  1911. }
  1912. }
  1913. }
  1914. }
  1915. }