using Beefy.widgets; using Beefy.gfx; using Beefy.theme.dark; using System.Collections; using System; using Beefy.geom; using System.Diagnostics; using Beefy.utils; using Beefy; using Beefy.theme; using Beefy.events; namespace BeefPerf { class PerfView : Widget { public enum ScreenItem { case None; case Entry(BPSelection); case TrackHeader(TrackNode); case TracksTerminator; case TrackTreeArrow(TrackNode); case TrackMenu(TrackNode); case TrackLine(TrackNodeEntry, int32 line); case Event(int32 trackIdx, int64 tick); } class ZoomAnimState { public int64 mStartTickOffset; public int64 mEndTickOffset; public double mStartTickScale; public double mEndTickScale; public float mStartYOffset; public float mEndYOffset; public float mPct; } public class HiliteZone { public int32 mZoneNameId; public String mString ~ delete _; public this(int32 zoneNameId, String str) { mZoneNameId = zoneNameId; mString = new String(str); } public bool Matches(int32 zoneNameId, String str) { if ((mZoneNameId >= 0) && (zoneNameId >= 0)) return zoneNameId == mZoneNameId; return mString == str; } } class ZoomAction : UndoAction { public PerfView mPerfView; public int64 mPrevTickOffset; public double mPrevTickScale; public int64 mTickOffset; public double mTickScale; public override bool Undo() { mPerfView.PhysZoomTo(mPrevTickOffset, mPrevTickScale); return true; } public override bool Redo() { mPerfView.PhysZoomTo(mTickOffset, mTickScale); return true; } } public abstract class TrackNode { public const int32 cHeaderHeight = 22; public String mName ~ delete _; public bool mHasRef = true; public TrackNodeGroup mParent; public float mRot; public float mShowChildPct = 1.0f; public bool mIsOpen = true; public this() { Open(true, true); } public void Open(bool open, bool immediate = false) { mIsOpen = open; if (immediate) mRot = mIsOpen ? (Math.PI_f / 2) : 0; } public void Update() { float rotSpeed = (0.07f + (10.0f / (GetChildHeight() + 20.0f))); if ((mIsOpen) && (mRot < Math.PI_f / 2)) { mRot = Math.Min(Math.PI_f / 2, mRot + rotSpeed); } else if ((!mIsOpen) && (mRot > 0)) { mRot = (float)Math.Max(0, mRot - rotSpeed); } if (mIsOpen) mShowChildPct = Math.Min(Math.Sin(mRot) + 0.001f, 1.0f); else mShowChildPct = Math.Max(1.0f - Math.Sin(Math.PI_f / 2 - mRot) - 0.001f, 0.0f); } public abstract float GetChildHeight(); public float GetHeight() { return cHeaderHeight + GetChildHeight() * mShowChildPct; } public float GetFinalHeight() { if (mIsOpen) return cHeaderHeight + GetChildHeight(); else return cHeaderHeight; } public virtual bool WantsClip() { return (mShowChildPct != 1.0f); } } public class TrackNodeEntry : TrackNode { public const int32 cLineHeight = 22; public const int32 cPeekHeight = 3; // Just to show the nub of any clipped-off entries public int32 mTrackIdx; public int32 mChildHeight = 8 * cLineHeight + cPeekHeight; public override float GetChildHeight() { return mChildHeight; } public override bool WantsClip() { if (base.WantsClip()) return true; return (mChildHeight % cLineHeight) != 0; } } public class TrackNodeGroup : TrackNode { public List mChildren = new List() ~ DeleteContainerAndItems!(_); public override float GetChildHeight() { float childHeight = 0; for (var entry in mChildren) { childHeight += entry.GetHeight(); } return childHeight; } } public Board mBoard; public BpSession mSession; public int64 mTickOffset; public int64 mWantTickOffset; public double mTickOffsetFrac; public double mTickScale = 0.0000015; public float mOfsY; public float? mWantOfsY; public float mDownX; public float mDownY; public float mDragSize; public int32 mDownTick = -1; public bool mGotFirstTick; public bool mDidDrag; public bool mFollowEnd; public int32 mMouseStillTicks; public int64 mSelectionFromTick; public int64 mSelectionToTick; public HiliteZone mMouseoverHiliteZone ~ delete _; public HiliteZone mProfileHiliteZone ~ delete _; public int32 mThreadDataVersion; public TrackNodeGroup mNodeRoot = new TrackNodeGroup() ~ delete _; public Scrollbar mHorzScrollbar; public Scrollbar mVertScrollbar; public ScreenItem mSelection; ZoomAnimState mZoomAnimState ~ delete _; public TrackNodeEntry mDragNodeEntry; public float mDragTrackOfsY; UndoManager mUndoManager = new UndoManager() ~ delete _; public this(Board board, BpSession session) { mBoard = board; mSession = session; mTickOffset = mSession.mFirstTick; mClipGfx = true; mHorzScrollbar = ThemeFactory.mDefault.CreateScrollbar(Scrollbar.Orientation.Horz); mHorzScrollbar.Init(); mHorzScrollbar.mOnScrollEvent.Add(new => HorzEventHandler); mHorzScrollbar.mDoAutoClamp = false; AddWidgetAtIndex(0, mHorzScrollbar); mVertScrollbar = ThemeFactory.mDefault.CreateScrollbar(Scrollbar.Orientation.Vert); mVertScrollbar.Init(); mVertScrollbar.mOnScrollEvent.Add(new => VertEventHandler); AddWidgetAtIndex(0, mVertScrollbar); } void HorzEventHandler(ScrollEvent theEvent) { mFollowEnd = false; mTickOffsetFrac = 0; mTickOffset = (int64)theEvent.mNewPos + mSession.mFirstTick; } void VertEventHandler(ScrollEvent theEvent) { mOfsY = (float)-theEvent.mNewPos; } void ClearSelection() { mSelectionFromTick = 0; mSelectionToTick = 0; gApp.mMainFrame.mStatusBar.mSelTime = 0; gApp.SetCursor(.Pointer); } int32 GetXFromTick(int64 tick) { double xVal = (double)((tick - mTickOffset - mTickOffsetFrac) * mTickScale); return (int32)Math.Clamp(xVal, -100000, 100000); } int64 GetTickFromX(float x) { double ticksPerPixel = 1.0 / mTickScale; return (int64)(x * ticksPerPixel) + mTickOffset; } int32 GetHeaderHeight() { return 24; } Result GetYFromTrackHelper(TrackNodeGroup group, int trackIdx, int trackLine, float startCurY) { float curY = startCurY; for (var trackNode in group.mChildren) { var subGroup = trackNode as TrackNodeGroup; if (subGroup != null) { var result = GetYFromTrackHelper(subGroup, trackIdx, trackLine, curY + TrackNodeGroup.cHeaderHeight); if (result case .Ok) return result; } else { var trackNodeEntry = (TrackNodeEntry)trackNode; if (trackNodeEntry.mTrackIdx == trackIdx) { return curY + TrackNodeEntry.cHeaderHeight + (float)(trackLine * TrackNodeEntry.cLineHeight); } } curY += trackNode.GetHeight(); } return .Err; } Result GetYFromTrack(int trackIdx, int trackLine) { return GetYFromTrackHelper(mNodeRoot, trackIdx, trackLine, 0); } void EnsureOpen(int trackIdx) { TrackNodeEntry foundEntry = null; WithNodes(mNodeRoot, scope [&] (node) => { if (var entry = node as TrackNodeEntry) { if (entry.mTrackIdx == trackIdx) foundEntry = entry; } }); if (foundEntry != null) { if (!foundEntry.mIsOpen) foundEntry.Open(true); var checkGroup = foundEntry.mParent; while (checkGroup != null) { if (!checkGroup.mIsOpen) checkGroup.Open(true); checkGroup = checkGroup.mParent; } } } int64 GetTickFromX(float x, int64 useTickOffset, double useTickScale) { double ticksPerPixel = 1.0 / useTickScale; return (int64)(x * ticksPerPixel) + useTickOffset; } bool IsInSelection(float x) { if (mSelectionToTick == 0) return false; let mouseTick = GetTickFromX(x); int64 minSelTick = Math.Min(mSelectionFromTick, mSelectionToTick); int64 maxSelTick = Math.Max(mSelectionFromTick, mSelectionToTick); return ((mouseTick >= minSelTick) && (mouseTick <= maxSelTick)); } int32 GetTrackXOfs() { int32 xOfs = 8; // return xOfs; } enum SelectItem { case Track(Rect rect, int32 zoneNameId, String unformattedName, String formattedName, int64 startTick, int64 endTick, int32 trackIdx, int32 stackDepth); case Event(String name, String details, int64 tick, int32 trackIdx); case TrackHeader(TrackNode); } delegate void SelectCallback(SelectItem selectItem); class DrawContext { public PerfView mPerfView; public float mCursorX; public float mCursorY; public ScreenItem mSelection; public SelectCallback mSelectCallback; public int64 mMinTick; public int64 mMaxTick; public int64 mTargetMinTicks; public int64 mDataRead; public int32 mSmallCount; public bool mDrawEntries = true; public int32 mDbgStreamDataCount; void DrawRootCmdTarget(Graphics g, ref int numFrames) { int64 minTick = mPerfView.mTickOffset; int64 maxTick = (int64)(minTick + (mPerfView.mWidth / mPerfView.mTickScale) + 1); // Adjust for xOfs minTick -= (int64)(8 / mPerfView.mTickScale); int64 prevFrameTick = 0; float xOfs = 0; float frameBarAlpha = Math.Clamp(0.6f - ((float)numFrames / (float)mPerfView.mWidth * 7.0f), 0.0f, 0.5f); var rootCmdTarget = mPerfView.mSession.mRootCmdTarget; for (int streamDataListIdx < rootCmdTarget.mStreamDataList.Count) { var streamData = rootCmdTarget.mStreamDataList[streamDataListIdx]; if ((streamData.mSplitTick > 0) && (streamData.mSplitTick < minTick)) continue; // All data is to the left if (streamData.mStartTick > maxTick) continue; // All data is to the right BPStateContext stateCtx = scope BPStateContext(mPerfView.mSession, streamData); CmdLoop: while (true) { switch (stateCtx.GetNextEvent()) { case let .PrevFrameTick(tick): prevFrameTick = tick; case let .FrameTick(tick): float x = mPerfView.GetXFromTick(tick) + xOfs; if ((x >= 0) && (x < mPerfView.mWidth)) { numFrames++; if (g != null) { float drawBarAlpha = frameBarAlpha; drawBarAlpha *= Math.Clamp(x / 64.0f - 1.0f, 0.0f, 1.0f); if (drawBarAlpha > 0) { using (g.PushColor(Color.Get(0xFFFFFF, drawBarAlpha))) g.FillRect(x, 0, 1, mPerfView.mHeight); } } } if (x > mPerfView.mWidth) break CmdLoop; prevFrameTick = tick; case .EndOfStream: break CmdLoop; default: } } } } bool DrawEvent(Graphics g, int trackIdx, float xOfs, float yOfs, int64 tick, ScreenItem showSelection) { int32 x = mPerfView.GetXFromTick(tick); float drawX = x - 10 + xOfs; float drawY = yOfs; if (g != null) { if ((showSelection case .Event(let eventThreadIdx, let eventTick)) && (trackIdx == eventThreadIdx) && (eventTick == tick)) { float scale = 1.25f + (float)Math.Cos(mPerfView.mUpdateCnt * 0.15)*0.08f; using (g.PushScale(scale, scale, drawX + 10, drawY - 1 + 10)) g.Draw(DarkTheme.sDarkTheme.GetImage(.DropShadow), drawX, drawY - 1); } g.Draw(DarkTheme.sDarkTheme.GetImage(.EventInfo), drawX, drawY); } if ((mCursorX != -1) && (mCursorX >= drawX + 3) && (mCursorX < drawX + 17) && ((mCursorY == -1) || ((mCursorY >= drawY + 3) && (mCursorY < drawY + 17)))) { return true; } return false; } public void DrawEntry(Graphics g, TrackNodeEntry trackNodeEntry, float trackXOfs, float trackYOfs) { float childHeight = trackNodeEntry.mChildHeight; int32 trackIdx = trackNodeEntry.mTrackIdx; bool isFirstDrawn = true; int64 minDrawDurationAdd = (int64)(1.0 / mPerfView.mTickScale); List entryStack = scope List(32); BpTrack thread = mPerfView.mSession.mThreads[trackIdx]; int32 xOfs = (int32)0; float trackHeight = 20 + childHeight; float yOfs = trackYOfs; if (!mDrawEntries) return; if (yOfs + trackHeight < 0) return; // Off the top if (yOfs > mPerfView.mHeight) return; // Off the bottom int32 threadStartX = mPerfView.GetXFromTick(thread.mCreatedTick); int32 threadEndX; if (thread.mRemoveTick != 0) threadEndX = mPerfView.GetXFromTick(thread.mRemoveTick); else threadEndX = mPerfView.GetXFromTick(mPerfView.mSession.mCurTick); if (g != null) { using (g.PushColor(0x10FFFFFF)) g.FillRect(threadStartX, yOfs, threadEndX - threadStartX + 1, trackHeight + 1); } int64 minTick = mMinTick; int64 maxTick = mMaxTick; int32 maxMouseoverDepth = (int32)(childHeight / TrackNodeEntry.cLineHeight); int32 maxTrackDepth = (int32)(childHeight / TrackNodeEntry.cLineHeight + 0.9f); var client = mPerfView.mSession; // When zoomed out we want to avoid drawing lots of small zones in the same place int64 minDrawTickForEvent = 0; var minDrawEndTickForDepth = scope int64[maxTrackDepth]; var lastDrawEndXForDepth = scope int64[maxTrackDepth]; var showSelection = mPerfView.mSelection; uint32 color = 0xA000FF00; int32 insideSelectAtDepth = -1; Header: if (g != null) { /*using (g.PushColor(0xFF000000)) { g.FillRect(0 + xOfs, 0 + yOfs, 200, 20); } */ /*String str = trackNodeEntry.mName; if (str == null) { str = scope:Header String(); str.FormatInto("Thread {0}", thread.mNativeThreadId); } DrawHeader(g, str, trackXOfs + xOfs, yOfs, false);*/ //g.DrawString(str, 4 + xOfs, 0 + yOfs, .Left, 200 - 8, .Ellipsis); } String tempDynStr = scope String(); bool autoLOD = true; bool dbgColorDataList = false; BpStreamLOD streamLOD = null; if (mPerfView.mHasFocus) { if (mPerfView.mWidgetWindow.IsKeyDown((KeyCode)'0')) autoLOD = false; else if (mPerfView.mWidgetWindow.IsKeyDown((KeyCode)'1')) { streamLOD = thread.mStreamLODs[0]; autoLOD = false; } else if (mPerfView.mWidgetWindow.IsKeyDown((KeyCode)'2')) { streamLOD = thread.mStreamLODs[1]; autoLOD = false; } else { //streamLOD = thread.mStreamLODs[1]; //autoLOD = false; } if (mPerfView.mWidgetWindow.IsKeyDown((KeyCode)'D')) dbgColorDataList = true; } for (int streamDataListIdx < thread.mStreamDataList.Count) { bool isLOD = false; var streamData = thread.mStreamDataList[streamDataListIdx]; var fullStreamData = streamData; if ((streamData.mSplitTick > 0) && (streamData.mSplitTick < mMinTick)) continue; // All data is to the left if (streamData.mStartTick > mMaxTick) continue; // All data is to the right if (dbgColorDataList) { mDbgStreamDataCount++; color = (uint32)((mDbgStreamDataCount * 0x12345678) ^ (mDbgStreamDataCount * 0x17654321)) & 0x00FFFFFF | 0xE0000000; } if (autoLOD) { double bestErrorPct = Double.MaxValue; BpStreamData bestLODStreamData = null; bool foundMoreDetailed = false; for (int32 lodIdx < (int32)thread.mStreamLODs.Count) { var checkStreamLOD = thread.mStreamLODs[lodIdx]; if (streamDataListIdx >= checkStreamLOD.mStreamDataList.Count) continue; var checkStreamData = checkStreamLOD.mStreamDataList[streamDataListIdx]; double errorPct = Double.MaxValue; if (mTargetMinTicks > checkStreamData.mMinTicks) { errorPct = (double)mTargetMinTicks / checkStreamData.mMinTicks; foundMoreDetailed = true; } else { // Having too little information is way worse than too much errorPct = ((double)checkStreamData.mMinTicks / mTargetMinTicks) * 8; } if (errorPct < bestErrorPct) { bestErrorPct = errorPct; bestLODStreamData = checkStreamData; isLOD = true; } } if ((!foundMoreDetailed) && (bestErrorPct > 32.0)) { // Use the full version bestLODStreamData = null; } if (bestLODStreamData != null) streamData = bestLODStreamData; } else if ((streamLOD != null) && (streamDataListIdx < streamLOD.mStreamDataList.Count)) { var lodStreamData = streamLOD.mStreamDataList[streamDataListIdx]; if (lodStreamData != null) { streamData = lodStreamData; isLOD = true; } } entryStack.Clear(); BPStateContext stateCtx = scope BPStateContext(mPerfView.mSession, streamData); String tempStr = scope String(128); CmdLoop: while (true) { switch (stateCtx.GetNextEvent()) { case let .Enter(startTick, strIdx): BPEntry entry; entry.mStartTick = startTick; entry.mZoneNameId = strIdx; entry.mParamsReadPos = stateCtx.ReadPos; if ((showSelection case .Entry(let bpSelection)) && (entry.mStartTick == bpSelection.mTickStart) && (entryStack.Count == bpSelection.mDepth)) { insideSelectAtDepth = (int32)entryStack.Count; } if ((startTick > maxTick) && (entryStack.Count == 0) && (!isLOD)) { // Off right side // We can only fully terminate if we don't have any other stack entries that may need terminating // LOD entries may have SmallEntries pending so we can't terminate early break CmdLoop; } entryStack.Add(entry); case let .Leave(endTick): let entry = entryStack.PopBack(); int32 stackDepth = (int32)entryStack.Count; if (stackDepth == insideSelectAtDepth) insideSelectAtDepth = -1; if (stackDepth >= maxTrackDepth) continue; // Too deep to draw if (endTick < minTick) continue; // Off left side if (entry.mStartTick > maxTick) continue; // Off right side if ((!isFirstDrawn) && (entry.mStartTick < streamData.mStartTick)) continue; // Would have already been drawn by a previous streamData if (endTick < minDrawEndTickForDepth[stackDepth]) continue; // Too close to prev depth minDrawEndTickForDepth[stackDepth] = endTick + minDrawDurationAdd; int32 endX = mPerfView.GetXFromTick(endTick) + xOfs + 1; int32 startX = mPerfView.GetXFromTick(entry.mStartTick) + xOfs; if (startX <= lastDrawEndXForDepth[stackDepth]) { startX = (int32)lastDrawEndXForDepth[stackDepth] + 1; // Leave empty space if (startX >= endX) continue; } else if (startX == endX) endX++; // Make at least 1 pixel wide lastDrawEndXForDepth[stackDepth] = endX; float w = endX - startX; Rect rect = Rect((int32)startX, 22 + stackDepth*22 + yOfs, w, 20); String str; int32 paramsSize; int32 paramReadPos = entry.mParamsReadPos; if (entry.mZoneNameId < 0) { int32 nameLen = -entry.mZoneNameId; str = tempDynStr; str.Reference((char8*)stateCtx.mReadStart + paramReadPos - nameLen, nameLen, 0); paramsSize = -1; } else { let zoneName = client.mZoneNames[entry.mZoneNameId]; str = zoneName.mName; paramsSize = zoneName.mParamsSize; } String unformattedStr = str; Rect strRect = rect; if (strRect.mX < 0) { strRect.mWidth += strRect.mX; strRect.mX = 0; } bool wantsDrawStr = strRect.mWidth > 16; bool isMouseover = false; if ((stackDepth < maxMouseoverDepth) && (mCursorX != -1) && (mCursorX >= rect.mX) && (mCursorX < rect.mX + rect.mWidth) && ((mCursorY == -1) || ((mCursorY >= rect.mY) && (mCursorY < rect.mY + rect.mHeight)))) { wantsDrawStr = true; isMouseover = true; } if (paramsSize != 0) { int32 prevReadPos = stateCtx.ReadPos; stateCtx.ReadPos = paramReadPos; defer { stateCtx.ReadPos = prevReadPos; } if (paramsSize == -1) { paramsSize = (int32)stateCtx.ReadSLEB128(); } if (paramsSize > 0) { String name = str; tempStr.Clear(); str = tempStr; for (int idx = 0; idx < name.Length; idx++) { let c = name[idx]; if (c == '%') { let cNext = name[idx + 1]; idx++; if (cNext == '%') { str.Append('%'); } else if (cNext == 'd') { int32 val = 0; stateCtx.Read(&val, 4); val.ToString(str); } else if (cNext == 'f') { float val = 0; stateCtx.Read(&val, 4); val.ToString(str); } else if (cNext == 's') { while (true) { char8 paramC = (char8)stateCtx.Read(); if (paramC == 0) break; str.Append(paramC); } } } else str.Append(c); } } } if (isMouseover) { BPSelection newSel; newSel.mThreadIdx = trackIdx; newSel.mTickStart = entry.mStartTick; newSel.mTickEnd = endTick; newSel.mDepth = stackDepth; mSelection = .Entry(newSel); if (mSelectCallback != null) { mSelectCallback(.Track(rect, entry.mZoneNameId, unformattedStr, str, entry.mStartTick, endTick, trackIdx, stackDepth)); } } if (g != null) { //using (g.PushColor(0xFF00A000)) using (g.PushColor(color)) g.FillRect(rect.mX, rect.mY, rect.mWidth, rect.mHeight); //Rect strRect = default(Rect); //strRect.SetIntersectionOf(rect, Rect(0, 0, mWidth, mHeight)); if (wantsDrawStr) g.DrawString(str, strRect.mX + 3, strRect.mY, .Left, strRect.mWidth - 4, .Truncate); if ((showSelection case .Entry(let bpSelection)) && (entry.mStartTick == bpSelection.mTickStart) && (stackDepth == bpSelection.mDepth)) { for (int32 i = 0; i < 3; i++) { float alpha = (0.8f - i*0.25f) + (float)Math.Cos(mPerfView.mUpdateCnt * 0.15)*(0.0f + i*0.1f); using (g.PushColor(Color.Get(alpha))) { Rect outlineRect = rect; outlineRect.Inflate(-i, -i); g.OutlineRect(outlineRect.mX, outlineRect.mY, outlineRect.mWidth, outlineRect.mHeight, 1); } } } bool doHilite = false; if ((mPerfView.mMouseoverHiliteZone != null) && (mPerfView.mMouseoverHiliteZone.Matches(entry.mZoneNameId, unformattedStr))) doHilite = true; if ((insideSelectAtDepth != -1) && (mPerfView.mProfileHiliteZone != null) && (mPerfView.mProfileHiliteZone.Matches(entry.mZoneNameId, unformattedStr))) doHilite = true; if (doHilite) { float alpha = Math.Min(0.2f + rect.mWidth * 0.1f, 0.8f); using (g.PushColor(Color.Get(alpha))) { Rect outlineRect = rect; BPUtils.DrawOutlineHilite(g, outlineRect.mX, outlineRect.mY, outlineRect.mWidth, outlineRect.mHeight); } } } case let .LODSmallEntry(startTick, endTick, stackDepth): if (g != null) { if (stackDepth >= maxTrackDepth) continue; // Too deep to draw if (endTick < minTick) continue; // Off left side if (startTick > maxTick) continue; // Off right side int32 endX = mPerfView.GetXFromTick(endTick) + xOfs + 1; int32 startX = mPerfView.GetXFromTick(startTick) + xOfs; if (startX <= lastDrawEndXForDepth[stackDepth]) { startX = (int32)lastDrawEndXForDepth[stackDepth] + 1; // Leave empty space if (startX >= endX) continue; } else if (startX == endX) endX++; // Make at least 1 pixel wide // Remove empty bar space at end to avoid double-spacing issue if ((startX - endX) % 2 == 0) endX--; lastDrawEndXForDepth[stackDepth] = endX; float w = endX - startX; Rect rect = Rect((int32)startX, 22 + stackDepth*22 + yOfs, w, 20); bool debugColor = false; if (!debugColor) { using (g.PushColor(color)) { for (int32 x = (int32)rect.mX; x < (int32)(rect.mX + rect.mWidth); x += 2) { //if (x % 2 == 0) g.FillRect(x, rect.mY, 1, rect.mHeight); } } } else { uint32 dbgColor = (uint32)((mSmallCount * 0x12345678) ^ (mSmallCount * 0x17654321)) & 0x00FFFFFF | 0xE0000000; using (g.PushColor(dbgColor)) g.OutlineRect(rect.mX, rect.mY, rect.mWidth, rect.mHeight); } mSmallCount++; } case let .Event(tick, name, details): if (tick < minDrawTickForEvent) continue; // Too close to prev depth minDrawTickForEvent = tick + minDrawDurationAdd * 3; if (DrawEvent(g, trackIdx, xOfs, yOfs, tick, showSelection)) { if (mSelectCallback != null) { String nameStr = scope String(); nameStr.Reference(name); String detailsStr = scope String(); detailsStr.Reference(details); mSelection = .Event(trackIdx, tick); mSelectCallback(.Event(nameStr, detailsStr, tick, trackIdx)); } } case let .LODEvent(tick, paramsOfs): if (tick < minDrawTickForEvent) continue; // Too close to prev depth minDrawTickForEvent = tick + minDrawDurationAdd * 3; if (DrawEvent(g, trackIdx, xOfs, yOfs, tick, showSelection)) { if (mSelectCallback != null) { uint8* readPtr = &fullStreamData.mBuffer[0] + paramsOfs; char8* name = (char8*)readPtr; int32 nameLen = String.StrLen(name); readPtr += nameLen + 1; char8* details = (char8*)readPtr; String nameStr = scope String(); nameStr.Reference(name); String detailsStr = scope String(); detailsStr.Reference(details); mSelectCallback(.Event(nameStr, detailsStr, tick, trackIdx)); } } case .EndOfStream: break CmdLoop; default: } } mDataRead += stateCtx.ReadPos; isFirstDrawn = false; } /*for (var entry in ref thread.mEntries) { float startX = GetXFromTick(entry.mTickStart); float endX = GetXFromTick(entry.mTickEnd); Rect rect = Rect(startX, 22, endX - startX, 22); g.FillRect(rect.mX, rect.mY, rect.mWidth, rect.mHeight); }*/ } void DrawHeader(Graphics g, TrackNode trackNode, String name, float xOfs, float yOfs) { var font = DarkTheme.sDarkTheme.mSmallFont; bool isGroup = (trackNode == null) || (trackNode is TrackNodeGroup); float rectWidth = 80; if (name != null) rectWidth = Math.Min(font.GetWidth(name) + 56, 300); /*using (g.PushColor(isGroup ? 0x80A0A0A0 : 0x80FFFFFF)) g.FillRect(rectWidth + xOfs, 10 + yOfs, mPerfView.mWidth, 2);*/ float drawY = yOfs; if (mCursorX != -1) { if (name == null) { var rect = Rect(xOfs, drawY, rectWidth, 20); if (rect.Contains(mCursorX, mCursorY)) { mSelection = .TracksTerminator; } } else { var rect = Rect(xOfs, drawY, rectWidth, 20); if (rect.Contains(mCursorX, mCursorY)) { mSelection = .TrackHeader(trackNode); if (mSelectCallback != null) mSelectCallback(.TrackHeader(trackNode)); } rect = Rect(xOfs, drawY, 20, 20); if (rect.Contains(mCursorX, mCursorY)) { mSelection = .TrackTreeArrow(trackNode); } rect = Rect(rectWidth - 24 + xOfs, drawY, 24, 20); if (rect.Contains(mCursorX, mCursorY)) { mSelection = .TrackMenu(trackNode); } } } if (g == null) return; using (g.PushColor(isGroup ? 0xFFA0A0A0 : 0xFFFFFFFF)) g.DrawBox(DarkTheme.sDarkTheme.GetImage(.WhiteBox), 0 + xOfs, 0 + drawY, rectWidth, 20); if (name != null) { g.DrawString(name, 20 + xOfs, 0 + drawY, .Left, rectWidth - 20 - 24, .Ellipsis); g.Draw(DarkTheme.sDarkTheme.GetImage(.DropMenuButton), rectWidth - 24 + xOfs, 0 + drawY); } if (trackNode != null) { Matrix matrix = Matrix.IdentityMatrix; matrix.Translate(-10, -10); matrix.Rotate(trackNode.mRot); matrix.Translate(xOfs + 10, drawY + 10); using (g.PushMatrix(matrix)) g.Draw(DarkTheme.sDarkTheme.mTreeArrow); } } public void DrawTrackNodes(Graphics g, TrackNodeGroup group, float inXOfs, float inYOfs) { float xOfs = inXOfs; float yOfs = inYOfs; if (group.mName != null) { xOfs += 20; yOfs += 22; } for (var node in group.mChildren) { float nodeHeight = node.GetHeight(); var trackNodeEntry = node as TrackNodeEntry; if (node.mShowChildPct > 0) { if ((node.WantsClip()) && (g != null)) g.PushClip(0, yOfs, mPerfView.mWidth, nodeHeight - 1); if (trackNodeEntry != null) DrawEntry(g, trackNodeEntry, xOfs, yOfs); else DrawTrackNodes(g, (TrackNodeGroup)node, xOfs, yOfs); if ((node.WantsClip()) && (g != null)) g.PopClip(); } yOfs += nodeHeight; } if (group.mName != null) { if (g != null) { /*DrawHeader(g, group.mName, selfXOfs, selfYOfs, true); float height = group.GetHeight(); using (g.PushColor(0xFF303030)) g.FillRect(1 + selfXOfs, 20 + selfYOfs, 3, height - 32);*/ } } } public void DrawTrackNodesTop(Graphics g, TrackNodeGroup group, float inXOfs, float inYOfs) { float xOfs = inXOfs; float yOfs = inYOfs; float selfXOfs = xOfs; float selfYOfs = yOfs; if (group.mName != null) { xOfs += 20; yOfs += 22; } if (group.mShowChildPct > 0) { for (var node in group.mChildren) { float nodeHeight = node.GetHeight(); if ((node.WantsClip()) && (g != null)) g.PushClip(0, yOfs, mPerfView.mWidth, nodeHeight); var trackNodeEntry = node as TrackNodeEntry; if (trackNodeEntry != null) { Header: { var thread = mPerfView.mSession.mThreads[trackNodeEntry.mTrackIdx]; String str = trackNodeEntry.mName; if (str == null) { str = scope:Header String(); str.AppendF("Thread {0}", thread.mNativeThreadId); } DrawHeader(g, trackNodeEntry, str, xOfs, yOfs); if ((mCursorX != -1) && (!mDrawEntries)) { int32 clientY = (int32)(mCursorY - yOfs - TrackNodeEntry.cHeaderHeight); if ((clientY >= 0) && (clientY < nodeHeight)) { int32 stackDepth = clientY / TrackNodeEntry.cLineHeight; mSelection = .TrackLine(trackNodeEntry, stackDepth); } } if (g != null) { if (nodeHeight > 24) { using (g.PushColor(0x60A0A0A0)) { g.FillRect(1 + xOfs, 20 + yOfs, 3, nodeHeight - 24); //g.FillRect(1 + xOfs, nodeHeight - 4 - 3 + yOfs, 8, 3); } } } //g.DrawString(str, 4 + xOfs, 0 + yOfs, .Left, 200 - 8, .Ellipsis); } } else DrawTrackNodesTop(g, (TrackNodeGroup)node, xOfs, yOfs); if ((node.WantsClip()) && (g != null)) g.PopClip(); yOfs += nodeHeight; //yOfs += trackNodeGroup.GetHeight(); } } //yOfs += group.GetHeight(); if (group.mName != null) { DrawHeader(g, group, group.mName, selfXOfs, selfYOfs); if (g != null) { float height = group.GetHeight(); using (g.PushColor(0x60606060)) g.FillRect(1 + selfXOfs, 20 + selfYOfs, 3, height - 24); } } } public void DrawEntries(Graphics g) { g?.SetFont(DarkTheme.sDarkTheme.mSmallFont); mMinTick = mPerfView.mTickOffset; mMaxTick = (int64)(mMinTick + (mPerfView.mWidth / mPerfView.mTickScale) + 1); if (mCursorX != -1) { mMinTick = mPerfView.GetTickFromX(mCursorX); mMaxTick = mMinTick; } // Adjust for xOfs mMinTick -= (int64)(8 / mPerfView.mTickScale); //maxTick -= (int64)(20 / mTickScale); // For testing clipping //minTick += (int64)(10 / mTickScale); //maxTick -= (int64)(20 / mTickScale); //int64 minDuration = (int64)(0.3 / mTickScale); mTargetMinTicks = (int64)(1.0 / mPerfView.mTickScale); if (g != null) { //int32 xOfs = 8; int numFrames = 0; DrawRootCmdTarget(null, ref numFrames); DrawRootCmdTarget(g, ref numFrames); if (mPerfView.mSelectionFromTick != 0) { int32 fromX = mPerfView.GetXFromTick(mPerfView.mSelectionFromTick); int32 toX = mPerfView.GetXFromTick(mPerfView.mSelectionToTick); using (g.PushColor(0x80404080)) g.FillRect(fromX, 0, toX - fromX, mPerfView.mHeight); } } // Draw track nodes { if (g != null) g.PushClip(0, 0, mPerfView.mWidth - 18, mPerfView.mHeight - 18); float yOfsStart; if (mPerfView.mOfsY > 0) yOfsStart = (int32)(0.5f * (float)Math.Pow(mPerfView.mOfsY, 0.8)); else yOfsStart = (int32)mPerfView.mOfsY; yOfsStart += 16; float yOfs = yOfsStart; DrawTrackNodes(g, mPerfView.mNodeRoot, 4, yOfs); yOfs = yOfsStart; if (g != null) { /*uint32 colorLeft = 0xFF202020; uint32 colorRight = 0x00202020; g.FillRectGradient(0, 0, 60, mPerfView.mHeight, colorLeft, colorLeft, colorLeft, colorLeft); g.FillRectGradient(60, 0, 16, mPerfView.mHeight, colorLeft, colorRight, colorLeft, colorRight);*/ } DrawTrackNodesTop(g, mPerfView.mNodeRoot, 4, yOfs); DrawHeader(g, null, null, 4, mPerfView.mNodeRoot.GetHeight() - 22 + yOfs); /*if (g != null) { using (g.PushColor(0x60A0A0A0)) g.FillRect(4, mPerfView.mNodeRoot.GetHeight() - 22 + yOfsStart, 64, 3); }*/ if (g != null) g.PopClip(); } /*if (mUpdateCnt % 120 == 0) Debug.WriteLine("DataRead: {0}", dataRead);*/ if ((g != null) && (mPerfView.mSession.mFirstTick != 0)) { using (g.PushColor(0xFF202020)) g.FillRect(0, 0, mPerfView.mWidth, 15); double usPerTick = mPerfView.mSession.GetTicksToUSScale(); double pixelsPerUS = 1 / usPerTick * mPerfView.mTickScale; int64 usPerSeg = 1; int segSubTickMod = 1; // Calculate usPerSeg do { double pixelsPerUSLeft = pixelsPerUS; while (true) { if (pixelsPerUSLeft > 100.00) { segSubTickMod = 1; break; } else if (pixelsPerUSLeft > 50.00) { segSubTickMod = 2; break; } else if (pixelsPerUSLeft > 20.00) { segSubTickMod = 5; break; } else if (pixelsPerUSLeft > 10.0) { segSubTickMod = 10; break; } pixelsPerUSLeft *= 10; usPerSeg *= 10; } } //int64 dispScale = 1000; //ms double ticksPerSeg = usPerSeg / usPerTick; int64 relTick = mPerfView.mTickOffset - mPerfView.mSession.mFirstTick; double leftSegF = (relTick / ticksPerSeg) + (mPerfView.mTickOffsetFrac / ticksPerSeg); int64 leftSeg = (int64)leftSegF; leftSeg -= segSubTickMod; double leftSegFrac = leftSegF - leftSeg; double pixelsPerSeg = (usPerSeg / usPerTick * mPerfView.mTickScale); int64 maxDispSegs = (int64)(mPerfView.mWidth / pixelsPerSeg) + 2 + segSubTickMod; for (int64 segOfs = 0; segOfs < maxDispSegs; segOfs++) { float xOfs = (int32)(0 + (segOfs - leftSegFrac) * pixelsPerSeg); int64 segIdx = leftSeg + segOfs; if ((segSubTickMod > 0) && (segIdx % segSubTickMod != 0)) { g.FillRect(xOfs, 13, 1, 2); continue; } g.FillRect(xOfs, 0, 1, 15); String str = scope String(64); int64 timeUS = segIdx * usPerSeg; //0:00:03.123456 {with zeroes trimmed} str.AppendF("{0}:{1:02}:", (int32)((timeUS / 60 / 60 / 1000000)), (int32)((timeUS / 60 / 1000000) % 60)); using (g.PushColor(0x80FFFFFF)) g.DrawString(str, xOfs + 4, -2); xOfs += g.mFont.GetWidth(str); str.Clear(); str.AppendF("{0:02}.{1:06}", (int32)((timeUS / 1000000) % 60), (int32)((timeUS % 1000000))); int maxTrim = 0; if (pixelsPerUS < 0.012) maxTrim = 5; else if (pixelsPerUS < 0.12) maxTrim = 3; for (int i = 0; i < maxTrim; i++) { if (str.EndsWith("0")) str.Remove(str.Length - 1); } g.DrawString(str, xOfs + 4, -2); } } } } ScreenItem DrawEntries(Graphics g, float cursorX, float cursorY, SelectCallback selectCallback = null) { DrawContext dc = scope DrawContext(); dc.mPerfView = this; dc.mCursorX = cursorX; dc.mCursorY = cursorY; dc.mSelectCallback = selectCallback; dc.DrawEntries(g); return dc.mSelection; } public override void Draw(Graphics g) { base.Draw(g); DrawEntries(g, -1, -1); } ScreenItem GetSelection(float x, float y) { return DrawEntries(null, x, y); } ScreenItem GetScreenItemAt(float x, float y, bool allowEntries) { DrawContext dc = scope DrawContext(); dc.mPerfView = this; dc.mCursorX = x; dc.mCursorY = y; dc.mDrawEntries = allowEntries; dc.DrawEntries(null); return dc.mSelection; } void WithNodes(TrackNode node, delegate void(TrackNode) act) { act(node); var group = node as TrackNodeGroup; if (group == null) return; for (var child in group.mChildren) WithNodes(child, act); } delegate void TrackNodePositionDelegate(TrackNode node, float xOfs, float yOfs, float childHeight); void WithNodesPositions(TrackNodeGroup group, TrackNodePositionDelegate dlg, ref float xOfs, ref float yOfs) { /*if (group.mName != null) { float xOfs = 8; using (g.PushColor(0xFF000000)) { g.FillRect(0 + xOfs, 0 + yOfs, 200, 20); } g.DrawString(group.mName, 4 + xOfs, 0 + yOfs, .Left, 200 - 8, .Ellipsis); yOfs += 22; } for (var node in group.mChildren) { var trackNodeEntry = node as TrackNodeEntry; if (trackNodeEntry != null) { float trackHeight = 10 * 20; DrawEntry(g, trackNodeEntry.mTrackIdx, yOfs, trackHeight); yOfs += trackHeight; continue; } var trackNodeGroup = (TrackNodeGroup)node; DrawTrackNodes(g, trackNodeGroup, ref yOfs); }*/ } void WithNodesPositions(TrackNode node, TrackNodePositionDelegate dlg) { //float xOfs = 8; //float yOfs = 16; //WithNodesPositions() } void ParseName(TrackNodeGroup trackNodeGroup, String name, int trackIdx) { trackNodeGroup.mHasRef = true; int slashIdx = -1; if (name != null) slashIdx = name.IndexOf('/'); if (slashIdx == -1) { for (int idx < trackNodeGroup.mChildren.Count) { var trackNodeEntry = trackNodeGroup.mChildren[idx] as TrackNodeEntry; if (trackNodeEntry != null) { if (trackNodeEntry.mTrackIdx == trackIdx) { trackNodeEntry.mHasRef = true; return; } } } var trackNodeEntry = new TrackNodeEntry(); if (name != null) trackNodeEntry.mName = new String(name); trackNodeEntry.mTrackIdx = (int32)trackIdx; trackNodeEntry.mParent = trackNodeGroup; trackNodeGroup.mChildren.Add(trackNodeEntry); } else { String groupName = scope String(name, 0, slashIdx); String subName = scope String(name, slashIdx + 1); for (int idx < trackNodeGroup.mChildren.Count) { var subTrackNodeGroup = trackNodeGroup.mChildren[idx] as TrackNodeGroup; if (subTrackNodeGroup != null) { if (String.Equals(subTrackNodeGroup.mName, groupName, .OrdinalIgnoreCase)) { ParseName(subTrackNodeGroup, subName, trackIdx); return; } } } var subTrackNodeGroup = new TrackNodeGroup(); subTrackNodeGroup.mName = new String(groupName); subTrackNodeGroup.mParent = trackNodeGroup; trackNodeGroup.mChildren.Add(subTrackNodeGroup); ParseName(subTrackNodeGroup, subName, trackIdx); } } void UpdateThreads() { mThreadDataVersion = mSession.mThreadDataVersion; WithNodes(mNodeRoot, scope (node) => { node.mHasRef = false; }); for (var thread in mSession.mThreads) { ParseName(mNodeRoot, thread.mName, @thread.Index); } WithNodes(mNodeRoot, scope (node) => { if (!node.mHasRef) { node.mParent.mChildren.Remove(node); delete node; return; } var nodeGroup = node as TrackNodeGroup; if (nodeGroup != null) { nodeGroup.mChildren.Sort(scope [&] (lhs, rhs) => { String lhsName = lhs.mName; String rhsName = rhs.mName; // Put named tracks first if (lhsName == null) { if (rhsName != null) return 1; var lhsEntry = (TrackNodeEntry)lhs; var rhsEntry = (TrackNodeEntry)rhs; return mSession.mThreads[lhsEntry.mTrackIdx].mNativeThreadId - mSession.mThreads[rhsEntry.mTrackIdx].mNativeThreadId; } if (rhsName == null) return -1; return String.Compare(lhs.mName, rhs.mName, true); }); } }); } public override void Update() { base.Update(); if (mThreadDataVersion != mSession.mThreadDataVersion) { UpdateThreads(); mSession.mThreadDataVersion = mThreadDataVersion; } WithNodes(mNodeRoot, scope (node) => { node.Update(); }); if (!mMouseOver) mMouseStillTicks = 0; if ((!mGotFirstTick) && (mSession.mFirstTick != 0)) { mTickOffset = mSession.mFirstTick; mGotFirstTick = true; } if (mMouseFlags == 0) { if (mOfsY > 0) mOfsY = Math.Max(mOfsY * 0.75f - 10, 0); } if ((mFollowEnd) && (mGotFirstTick)) { // Kinda ugly way to do this... //mTickOffset = Int64.MaxValue; //ClampView(); int64 maxOffset = GetMaxOffset(); //mWantTickOffset += ; int64 ticksPerUpdate = (int64)((gApp.mTimePerFrame * 1000000) / mSession.GetTicksToUSScale()); mWantTickOffset += ticksPerUpdate; int64 endDiffTicks = maxOffset - mWantTickOffset; if (endDiffTicks < 0) mWantTickOffset = maxOffset; else if (endDiffTicks > ticksPerUpdate) mWantTickOffset = maxOffset - ticksPerUpdate; /*if (mUpdateCnt % 60 == 0) { int64 endDiffTicks = mWantTickOffset - GetMaxOffset(); int32 endDeltaMS = (int32)(endDiffTicks * mClient.GetTicksToUSScale() / 1000); Debug.WriteLine("End Diff: {0}", endDeltaMS); }*/ } if (mWantTickOffset != 0) { //int64 linearAdd = (int64)(10000 / mClient.GetTicksToUSScale()); int64 linearAdd = (int64)(3 / mTickScale); int64 delta = mWantTickOffset - mTickOffset; int64 deltaAdd = delta / 5; deltaAdd += Math.Sign(delta) * linearAdd; if (Math.Abs(deltaAdd) >= Math.Abs(delta)) { mTickOffset = mWantTickOffset; if (!mFollowEnd) mWantTickOffset = 0; } else { mTickOffset += deltaAdd; } ClampView(); } if (mWantOfsY.HasValue) { float delta = mWantOfsY.Value - mOfsY; float deltaAdd = delta / 5; deltaAdd += Math.Sign(delta) * 1; if (Math.Abs(deltaAdd) >= Math.Abs(delta)) { mOfsY = mWantOfsY.Value; mWantOfsY = null; } else { mOfsY += deltaAdd; } ClampView(); } if (mZoomAnimState != null) { mZoomAnimState.mPct += 0.05f; if (mZoomAnimState.mPct >= 1.0) { mTickOffset = mZoomAnimState.mEndTickOffset; mTickScale = mZoomAnimState.mEndTickScale; delete mZoomAnimState; mZoomAnimState = null; } else { float pct = Utils.EaseInAndOut(mZoomAnimState.mPct); int64 destCenterTick = mZoomAnimState.mEndTickOffset + (int64)((mWidth / 2) / mZoomAnimState.mEndTickScale); double destCTStartX = (destCenterTick - mZoomAnimState.mStartTickOffset) * mZoomAnimState.mStartTickScale; double destCTEndX = (destCenterTick - mZoomAnimState.mEndTickOffset) * mZoomAnimState.mEndTickScale; double destCTX = Math.Lerp(destCTStartX, destCTEndX, pct); double scale = Math.Lerp(mZoomAnimState.mStartTickScale, mZoomAnimState.mEndTickScale, pct); mTickScale = scale; mTickOffset = destCenterTick - (int64)(destCTX / scale); } ClampView(); } if (gApp.mIsUpdateBatchStart) { (float mouseX, float mouseY) = GetMouseCoords(); bool showedTooltip = false; bool isMoving = (mWantTickOffset != 0) && (mWantTickOffset != mTickOffset); DeleteAndNullify!(mMouseoverHiliteZone); //mMouseStillTicks++; if ((mMouseOver) && (!isMoving) && (mMouseFlags == 0)) { if (mSelectionFromTick != 0) { if (IsInSelection(mouseX)) { int64 minSelTick = Math.Min(mSelectionFromTick, mSelectionToTick); int64 maxSelTick = Math.Max(mSelectionFromTick, mSelectionToTick); var str = scope String(); BpClient.TimeToStr(mSession.TicksToUS(minSelTick - mSession.mFirstTick), str); str.Append("\n"); BpClient.TimeToStr(mSession.TicksToUS(maxSelTick - mSession.mFirstTick), str); str.Append("\n"); BpClient.ElapsedTimeToStr(mSession.TicksToUS(maxSelTick - minSelTick), str); //str.Append("\nClick to Zoom"); DarkTooltipManager.ShowTooltip(str, this, mouseX + 16, mouseY + 12); showedTooltip = true; } } if (!showedTooltip) { String tooltipStr = scope String(); DrawEntries(null, mouseX, mouseY, scope [&] (selectItem) => { tooltipStr.Clear(); if (selectItem case let .Track(rect, zoneNameId, unformattedName, formattedName, tickStart, tickEnd, trackIdx, stackDepth)) { mMouseoverHiliteZone = new HiliteZone(zoneNameId, unformattedName); tooltipStr.Append(formattedName); tooltipStr.Append("\n"); //str.FormatInto("Ticks: {0}", tickEnd - tickStart); mSession.ElapsedTicksToStr(tickEnd - tickStart, tooltipStr); } else if (selectItem case let .Event(name, details, tick, trackIdx)) { tooltipStr.Append(name); if (!details.IsEmpty) { tooltipStr.Append("\n"); tooltipStr.Append(details); } } /*if (DarkTooltipManager.sTooltip != null) { showedTooltip = true; DarkTooltipManager.sTooltip.Reinit(str, this, mouseX + 16, mouseY + 12, 0, 0, false, false); return; }*/ /*if (str.IsEmpty()) return;*/ //Debug.WriteLine("ShowTooltip {0} {1}", mouseX + 16, mouseY + 12); }); if (!tooltipStr.IsEmpty) { DarkTooltipManager.ShowTooltip(tooltipStr, this, mouseX + 16, mouseY + 12); showedTooltip = true; } } } if (!showedTooltip) DarkTooltipManager.CloseTooltip(); } UpdateScrollbars(); } void ProfileSelection(int32 trackIdx) { if (mSelectionFromTick == 0) return; BPSelection selection; selection.mTickStart = Math.Min(mSelectionFromTick, mSelectionToTick); selection.mTickEnd = Math.Max(mSelectionFromTick, mSelectionToTick); selection.mDepth = -1; selection.mThreadIdx = trackIdx; gApp.mProfilePanel.Show(this, selection); } public override void MouseDown(float x, float y, int32 btn, int32 btnCount) { base.MouseDown(x, y, btn, btnCount); var screenItem = GetScreenItemAt(x, y, false); switch (screenItem) { case .TrackTreeArrow(let trackNode): trackNode.Open(!trackNode.mIsOpen, false); return; case .TrackMenu(let trackNode): var trackEntry = trackNode as TrackNodeEntry; Menu menu = new Menu(); #unwarn var menuItem = menu.AddItem("Set Track Color ..."); //menuItem.mMenuItemSelectedHandler.Add(new (evt) => IDEApp.sApp.ShowDisassemblyAtCursor()); if ((trackEntry != null) && (mSelectionFromTick != 0)) { menuItem = menu.AddItem("Profile Selection"); menuItem.mOnMenuItemSelected.Add(new (evt) => ProfileSelection(trackEntry.mTrackIdx)); } MenuWidget menuWidget = DarkTheme.sDarkTheme.CreateMenuWidget(menu); menuWidget.Init(this, x, y); return; default: } mDownX = x; mDownY = y; mDidDrag = false; mDownTick = mUpdateCnt; mDragSize = 0; mFollowEnd = false; mWantTickOffset = 0; if (mWidgetWindow.IsKeyDown(.Shift)) { mSelectionFromTick = GetTickFromX(x); mSelectionToTick = mSelectionFromTick; } else { if (IsInSelection(x)) { int64 minSelTick = Math.Min(mSelectionFromTick, mSelectionToTick); int64 maxSelTick = Math.Max(mSelectionFromTick, mSelectionToTick); ZoomTo(minSelTick, maxSelTick); } ClearSelection(); } if ((btn == 0) && (btnCount == 2)) { switch (mSelection) { case .Entry(let bpSelection): ZoomTo(bpSelection.mTickStart, bpSelection.mTickEnd); default: } } SetFocus(); } public override void MouseUp(float x, float y, int32 btn) { base.MouseUp(x, y, btn); if (mDragNodeEntry != null) { mDragNodeEntry.mChildHeight = (int32)(Math.Round(mDragNodeEntry.mChildHeight / (double)TrackNodeEntry.cLineHeight) * TrackNodeEntry.cLineHeight) + TrackNodeEntry.cPeekHeight; } if (mSelectionToTick != 0) { // } else { if ((!mDidDrag) || ((mDragSize <= 3) && (mUpdateCnt - mDownTick < 20))) { mSelection = GetSelection(x, y); if (mSelection case let .Entry(selection)) { gApp.ZoneSelected(this, selection); //mSelection = selection; } else { //gApp.ZoneSelected(null, BPSelection()); } } } mDownTick = -1; } public override void MouseMove(float x, float y) { base.MouseMove(x, y); mMouseStillTicks = 0; //gApp.CloseTooltip(); Cursor wantCursor = .Pointer; double ticksPerPixel = 1.0 / mTickScale; gApp.mMainFrame.mStatusBar.mShowTime = mSession.TicksToUS((int64)(GetTickFromX(x) - mSession.mFirstTick)); gApp.mMainFrame.mStatusBar.mSelTick = GetTickFromX(x); if (mMouseFlags.HasFlag(.Left)) { if (mDownTick == -1) return; if (mDragNodeEntry != null) { wantCursor = .SizeNS; mDragNodeEntry.mChildHeight = (int32)Math.Max(20, y + mDragTrackOfsY); } else if (mSelectionFromTick != 0) { mSelectionToTick = GetTickFromX(x); gApp.mMainFrame.mStatusBar.mSelTime = mSession.TicksToUS(Math.Abs((int64)(mSelectionToTick - mSelectionFromTick))); } else { float dx = (x - mDownX); float dy = (y - mDownY); mDragSize += Math.Abs(dx) + Math.Abs(dy); double tickOfsAdd = (-dx * ticksPerPixel) + mTickOffsetFrac; mTickOffset += (int64)tickOfsAdd; mTickOffsetFrac = tickOfsAdd - ((int64)tickOfsAdd); mOfsY += dy; ClampView(); mDidDrag = true; mDownX = x; mDownY = y; } } else { mDragNodeEntry = null; TrackNode findBefore = null; bool forceLastNode = false; var screenItem = GetScreenItemAt(x, y, false); if (screenItem case .TracksTerminator) { findBefore = mNodeRoot; forceLastNode = true; } if (screenItem case .TrackHeader(let trackNodeEntry)) { findBefore = trackNodeEntry; } //if (screenItem case .TrackHeader(let trackNodeEntry)) if (findBefore != null) { TrackNodeEntry prevNodeEntry = null; //var findBefore = trackNodeEntry; FindPrevNodeEntry: while ((findBefore != null) || (forceLastNode)) { TrackNodeGroup checkParent; int checkIdx; if (forceLastNode) { checkParent = mNodeRoot; checkIdx = checkParent.mChildren.Count - 1; forceLastNode = false; } else { checkParent = findBefore.mParent; if (checkParent == null) break; checkIdx = checkParent.mChildren.IndexOf(findBefore) - 1; } while (true) { while (checkIdx >= 0) { var checkNode = checkParent.mChildren[checkIdx]; if (checkNode.mIsOpen) break; checkIdx--; } if (checkIdx < 0) { findBefore = checkParent; break; } else { var checkNode = checkParent.mChildren[checkIdx]; while (true) { var checkNodeEntry = checkNode as TrackNodeEntry; if (checkNodeEntry != null) { prevNodeEntry = checkNodeEntry; break FindPrevNodeEntry; } var checkNodeGroup = (TrackNodeGroup)checkNode; int subCheckIdx = checkNodeGroup.mChildren.Count - 1; while (subCheckIdx >= 0) { checkNode = checkNodeGroup.mChildren[subCheckIdx]; if (checkNode.mIsOpen) break; subCheckIdx--; } if (subCheckIdx == -1) { checkIdx--; break; //findBefore = checkParent; //break; } } } } } if (prevNodeEntry != null) { wantCursor = .SizeNS; mDragNodeEntry = prevNodeEntry; mDragTrackOfsY = mDragNodeEntry.mChildHeight - y; } } else if (mSelectionFromTick != 0) { if (IsInSelection(x)) { wantCursor = .Hand; } else { wantCursor = .Pointer; } } } gApp.SetCursor(wantCursor); } public override void MouseWheel(float x, float y, float deltaX, float deltaY) { base.MouseWheel(x, y, deltaX, deltaY); double ticksPerPixel = 1.0 / mTickScale; double mouseTick = (x * ticksPerPixel) + mTickOffset; float scaleFactor = (mWidgetWindow.IsKeyDown(.Control)) ? 1.02f : 1.2f; if (deltaY > 0) mTickScale *= scaleFactor; else if (deltaY < 0) mTickScale /= scaleFactor; ClampView(); // Clamp scale first ticksPerPixel = 1.0 / mTickScale; mTickOffset = (int64)(mouseTick - (x * ticksPerPixel)); ClampView(); // Then clamp offset } public override void MouseEnter() { base.MouseEnter(); } public override void MouseLeave() { base.MouseLeave(); gApp.mMainFrame.mStatusBar.mShowTime = 0; gApp.SetCursor(.Pointer); } double MaxVisibleTicks { get { return ((mWidth - 32) / mTickScale); } } // Truncates int64 GetMaxOffset() { // Show an extra 10us int64 maxTick = mSession.mCurTick + (int64)(10 / mSession.GetTicksToUSScale()); return (int64)(maxTick - MaxVisibleTicks); } void UpdateScrollbars() { if (!mHorzScrollbar.mThumb.mMouseDown) { int64 maxTick = mSession.mCurTick - mSession.mFirstTick; mHorzScrollbar.mPageSize = MaxVisibleTicks; mHorzScrollbar.mContentSize = maxTick; mHorzScrollbar.mContentPos = mTickOffset - mSession.mFirstTick; mHorzScrollbar.UpdateData(); } if (!mVertScrollbar.mThumb.mMouseDown) { mVertScrollbar.mPageSize = mHeight; mVertScrollbar.mContentSize = mNodeRoot.GetHeight() + mHeight - mHeight / 8; mVertScrollbar.mContentPos = -mOfsY; mVertScrollbar.UpdateData(); } } void ClampView() { if (!mGotFirstTick) return; if (mTickOffset < mSession.mFirstTick) { mTickOffset = mSession.mFirstTick; mTickOffsetFrac = 0; } else if (mTickOffset == mSession.mFirstTick) { if (mTickOffsetFrac < 0) mTickOffsetFrac = 0; } double maxVisibleTicks = MaxVisibleTicks; // Show an extra 10us int64 maxTick = mSession.mCurTick + (int64)(10 / mSession.GetTicksToUSScale()); int64 showTicks = maxTick - mTickOffset; if (showTicks < maxVisibleTicks) { mTickOffset = maxTick - (int64)maxVisibleTicks; mTickOffsetFrac = maxVisibleTicks - (int64)maxVisibleTicks; } if (mOfsY > 0) mOfsY = 0; // Allow scrolling up enough that we can expose the lower 25% as empty space #unwarn //(int32 track0YOfs, int32 trackHeight) = GetTrackYPos(0); //double minOfsY = -((mClient.mThreads.Count * trackHeight) - /*mHeight * 3 / 4*/ mHeight / 8); double minOfsY = -(mNodeRoot.GetHeight() - /*mHeight * 3 / 4*/ mHeight / 8); if (minOfsY >= 0) mOfsY = 0; else if (mOfsY < minOfsY) { mOfsY = (int32)minOfsY; } if (mSession.mCurTick != mSession.mFirstTick) { double minScale = (mWidth - 32) / (double)(mSession.mCurTick - mSession.mFirstTick); // Limit to full view double maxScale = 1000 * mSession.GetTicksToUSScale(); mTickScale = Math.Clamp(mTickScale, minScale, maxScale); } } void FindNextZone(int32 findDir) { mFollowEnd = false; (float mouseX, float mouseY) = GetMouseCoords(); /*(int32 yOfs, int32 trackHeight) = GetTrackYPos(0); int32 trackIdx = (int32)(mouseY - yOfs) / trackHeight; if (trackIdx >= mClient.mThreads.Count) return;*/ int32 stackDepthSelected; int32 trackIdx; var screenItem = GetScreenItemAt(mouseX, mouseY, false); if (screenItem case let .TrackLine(inTrackNodeEntry, inStackDepth)) { trackIdx = inTrackNodeEntry.mTrackIdx; stackDepthSelected = inStackDepth; } else { return; } //(yOfs, trackHeight) = GetTrackYPos(trackIdx); int64 useTickOffset = (mWantTickOffset != 0) ? mWantTickOffset : mTickOffset; int64 mouseTick = GetTickFromX(mouseX, useTickOffset, mTickScale); /*int32 deepest = 0; DrawEntries(null, mouseX, -1, scope [&] (rect, name, tickStart, tickEnd, foundTrackIdx, stackDepth) => { if (trackIdx == foundTrackIdx) deepest = Math.Max(deepest, stackDepth); });*/ //int32 trackSelected = (int32)((mouseY - yOfs) - 22) / 20; var thread = mSession.mThreads[trackIdx]; int64 minDist = (int64)(8 / mTickScale); int64 foundStartTick = 0; FindBlock: do { if (findDir > 0) { int64 minWantTick = mouseTick + minDist; for (int streamDataListIdx < thread.mStreamDataList.Count) { var streamData = thread.mStreamDataList[streamDataListIdx]; if ((streamData.mSplitTick > 0) && (mouseTick > streamData.mSplitTick)) continue; // All entries are before mouseTick int stackDepth = 0; BPStateContext stateCtx = scope BPStateContext(mSession, streamData); CmdLoop: while (true) { switch (stateCtx.GetNextEvent()) { case let .Enter(startTick, strIdx): if ((startTick > minWantTick) && (stackDepth <= stackDepthSelected)) { bool isOld = ((startTick <= streamData.mStartTick) && (stackDepth < stateCtx.mSplitCarryoverCount)); if (!isOld) { foundStartTick = startTick; break FindBlock; } } stackDepth++; case let .Leave(endTick): stackDepth--; case .EndOfStream: break CmdLoop; default: } } } } else { int64 maxWantTick = mouseTick - minDist; int streamDataListIdx = 0; for (int checkStreamDataListIdx < thread.mStreamDataList.Count) { var streamData = thread.mStreamDataList[checkStreamDataListIdx]; if ((streamData.mSplitTick > 0) && (mouseTick > streamData.mSplitTick)) continue; // All entries are before mouseTick else { streamDataListIdx = checkStreamDataListIdx; break; } } while (streamDataListIdx >= 0) { var streamData = thread.mStreamDataList[streamDataListIdx]; int stackDepth = 0; BPStateContext stateCtx = scope BPStateContext(mSession, streamData); CmdLoop: while (true) { switch (stateCtx.GetNextEvent()) { case let .Enter(startTick, strIdx): if ((startTick < maxWantTick) && (stackDepth <= stackDepthSelected)) { // Don't break, find the last one foundStartTick = startTick; } stackDepth++; case let .Leave(endTick): stackDepth--; case .EndOfStream: break CmdLoop; default: } } if (foundStartTick != 0) break; streamDataListIdx--; } } } if (foundStartTick == 0) return; mWantTickOffset = foundStartTick - (int64)(mouseX / mTickScale); } public override void KeyDown(KeyCode keyCode, bool isRepeat) { base.KeyDown(keyCode, isRepeat); bool ctrlDown = mWidgetWindow.IsKeyDown(.Control); bool altDown = mWidgetWindow.IsKeyDown(.Alt); bool shiftDown = mWidgetWindow.IsKeyDown(.Shift); if ((!ctrlDown) && (!altDown) && (!shiftDown)) { switch (keyCode) { case .Home: mWantTickOffset = mSession.mFirstTick; mFollowEnd = false; //mWantPos = case .End: mWantTickOffset = GetMaxOffset(); mFollowEnd = true; case .Left: FindNextZone(-1); case .Right: FindNextZone(1); case .Escape: ClearSelection(); case (KeyCode)' ': mFollowEnd = false; default: } } else if (ctrlDown) { switch (keyCode) { case (KeyCode)'Z': mUndoManager.Undo(); case (KeyCode)'Y': mUndoManager.Redo(); case (KeyCode)'X': mGotFirstTick = false; mSession.ClearData(); gApp.mFindPanel.Clear(); gApp.mProfilePanel.Clear(); default: } } } public override void Resize(float x, float y, float width, float height) { base.Resize(x, y, width, height); ClampView(); float baseSize = 18; float yPos = Math.Max(0, mHeight - baseSize); mHorzScrollbar.Resize(0, yPos, mWidth - baseSize + 2, baseSize); mVertScrollbar.Resize(mWidth - baseSize, 20, baseSize, mHeight - baseSize + 2 - 20); } (float, float) GetMouseCoords() { float mouseX; float mouseY; RootToSelfTranslate(mWidgetWindow.mClientMouseX, mWidgetWindow.mClientMouseY, out mouseX, out mouseY); return (mouseX, mouseY); } public void EnsureVisible(int32 trackIdx, int32 trackLine) { EnsureOpen(trackIdx); float wantY = GetYFromTrack(trackIdx, trackLine); float wantYBot = wantY + TrackNode.cHeaderHeight; float dispHeight = mHeight - GetHeaderHeight() - 18; if (wantY <= -mOfsY) mWantOfsY = -wantY + 20; else if ((wantYBot > dispHeight - mOfsY)) mWantOfsY = dispHeight - wantYBot; } public void ZoomTo(int64 startTick, int64 endTick) { float usableWidth = mWidth - 20; // Adjust for scrollbar if (endTick == 0) { int64 adjustedStartTick = startTick - (int64)(180 / mTickScale); ZoomAction zoomAction = new ZoomAction(); zoomAction.mPerfView = this; zoomAction.mPrevTickScale = mTickScale; zoomAction.mPrevTickOffset = mTickOffset; zoomAction.mTickOffset = adjustedStartTick; zoomAction.mTickScale = mTickScale; mUndoManager.Add(zoomAction); PhysZoomTo(adjustedStartTick, mTickScale); return; } // The larger this number, the more we show on either side of the zoomed area double scale = (double)usableWidth / (endTick - startTick); int64 leftAdjust = (int64)(64 / scale); int64 rightAdjust = (int64)(32 / scale); int64 adjustedStartTick = startTick - leftAdjust; int64 adjustedEndTick = endTick + rightAdjust; scale = (double)usableWidth / (adjustedEndTick - adjustedStartTick); ZoomAction zoomAction = new ZoomAction(); zoomAction.mPerfView = this; zoomAction.mPrevTickScale = mTickScale; zoomAction.mPrevTickOffset = mTickOffset; zoomAction.mTickOffset = adjustedStartTick; zoomAction.mTickScale = scale; mUndoManager.Add(zoomAction); PhysZoomTo(adjustedStartTick, scale); } public void PhysZoomTo(int64 startTick, double scale) { delete mZoomAnimState; mTickOffsetFrac = 0; var zoomAnimState = new ZoomAnimState(); zoomAnimState.mStartTickOffset = mTickOffset; zoomAnimState.mEndTickOffset = startTick; zoomAnimState.mStartTickScale = mTickScale; zoomAnimState.mEndTickScale = scale; mZoomAnimState = zoomAnimState; } public void SaveEntrySummary(StringView findStr, StringView filePath) { bool found = false; TrackLoop: for (var track in mSession.mThreads) { int64 findTick = -1; int findDepth = 0; for (var streamData in track.mStreamDataList) { BPStateContext stateCtx = scope BPStateContext(mSession, streamData); CmdLoop: while (true) { switch (stateCtx.GetNextEvent()) { case let .Enter(startTick, strIdx): if (findDepth > 0) { findDepth++; } if (findTick != -1) break; if (strIdx < 0) { } else { let zoneName = mSession.mZoneNames[strIdx]; if (zoneName.mName == findStr) { findTick = startTick; findDepth = 1; } } case let .Leave(endTick): if (findDepth > 0) { findDepth--; if (findDepth == 0) { found = true; BPSelection sel; sel.mThreadIdx = (int32)@track.Index; sel.mTickStart = findTick; sel.mTickEnd = endTick; sel.mDepth = 0; gApp.mProfilePanel.[Friend]mFormatCheckbox.Checked = false; gApp.mProfilePanel.[Friend]mListView.mSortType.mColumn = 3; gApp.mProfilePanel.Show(this, sel); while (true) { gApp.mProfilePanel.Update(); if ((gApp.mProfilePanel.[Friend]mProfileCtx == null) || (gApp.mProfilePanel.[Friend]mProfileCtx.mDone)) break; } var str = scope String(); gApp.mProfilePanel.[Friend]mListView.GetSummaryString(str); if (Utils.WriteTextFile(filePath, str) case .Err) gApp.Fail(scope String()..AppendF("Failed to create file '{0}'", filePath)); break TrackLoop; } } case let .Event(tick, name, details): case .EndOfStream: break CmdLoop; default: } } } } if (!found) { gApp.Fail("Summary string not found"); } } } }