mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-18 16:10:26 +02:00
2507 lines
65 KiB
Beef
2507 lines
65 KiB
Beef
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<TrackNode> mChildren = new List<TrackNode>() ~ 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<float> 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<float> 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<BPEntry> entryStack = scope List<BPEntry>(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");
|
|
}
|
|
}
|
|
}
|
|
}
|