/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2014 Crytek * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Diagnostics; using System.Linq; using System.Text; using System.Windows.Forms; using WeifenLuo.WinFormsUI.Docking; using renderdocui.Code; using renderdoc; using System.IO; namespace renderdocui.Windows { public partial class EventBrowser : DockContent, ILogViewerForm { class DeferredEvent { public UInt32 frameID = 0; public UInt32 eventID = 0; public ResourceId defCtx = ResourceId.Null; public UInt32 firstDefEv = 0; public UInt32 lastDefEv = 0; } private List m_FrameNodes = new List(); private Core m_Core; public EventBrowser(Core core) { InitializeComponent(); Icon = global::renderdocui.Properties.Resources.icon; HideJumpAndFind(); m_Core = core; DockHandler.GetPersistStringCallback = PersistString; var col = eventView.Columns["Drawcall"]; eventView.Columns.SetVisibleIndex(col, -1); col = eventView.Columns["Duration"]; eventView.Columns.SetVisibleIndex(col, -1); UpdateDurationColumn(); eventView.CellPainter.CellDataConverter = DataToString; findEventButton.Enabled = false; jumpEventButton.Enabled = false; timeDraws.Enabled = false; } public class PersistData { public static int currentPersistVersion = 1; public int persistVersion = currentPersistVersion; public struct ColumnArrangement { public string fieldname; public int visibleindex; public int width; }; public List visibleColumns = new List(); public static PersistData GetDefaults(TreelistView.TreeListView view) { PersistData data = new PersistData(); foreach (var c in view.Columns) { ColumnArrangement a = new ColumnArrangement(); a.fieldname = c.Fieldname; a.visibleindex = c.VisibleIndex; a.width = c.Width; data.visibleColumns.Add(a); } return data; } } public void InitFromPersistString(string str) { PersistData data = null; try { if (str.Length > GetType().ToString().Length) { var reader = new StringReader(str.Substring(GetType().ToString().Length)); System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(PersistData)); data = (PersistData)xs.Deserialize(reader); reader.Close(); } } catch (System.Xml.XmlException) { } catch (InvalidOperationException) { // don't need to handle it. Leave data null and pick up defaults below } if (data == null || data.persistVersion != PersistData.currentPersistVersion) { data = PersistData.GetDefaults(eventView); } ApplyPersistData(data); } private void ApplyPersistData(PersistData data) { // loop twice because first time will ensure the right columns are visible but // e.g. if the first column we grabbed should be in visibleindex 2, it would // get shown and be forced to 0 (arbitrary example). Second pass ensures the // order is correct for (int i = 0; i < 2; i++) { foreach (var c in data.visibleColumns) { var col = eventView.Columns[c.fieldname]; if (col == null) continue; eventView.Columns.SetVisibleIndex(col, c.visibleindex); if (i == 1) col.Width = c.width; } } } private string PersistString() { var writer = new StringWriter(); writer.Write(GetType().ToString()); PersistData data = PersistData.GetDefaults(eventView); System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(PersistData)); xs.Serialize(writer, data); return writer.ToString(); } private PersistantConfig.TimeUnit m_TimeUnit = PersistantConfig.TimeUnit.Microseconds; private void UpdateDurationColumn() { m_TimeUnit = m_Core.Config.EventBrowser_TimeUnit; string durationString = PersistantConfig.UnitPrefix(m_TimeUnit); eventView.Columns["Duration"].Caption = String.Format("Duration ({0})", durationString); } private string DataToString(TreelistView.TreeListColumn column, object data) { if (column.Fieldname == "Duration") { double f = (double)data; if (f < 0.0) return ""; if (m_Core.Config.EventBrowser_TimeUnit != m_TimeUnit) UpdateDurationColumn(); if (m_Core.Config.EventBrowser_TimeUnit == PersistantConfig.TimeUnit.Milliseconds) f *= 1000.0; else if (m_Core.Config.EventBrowser_TimeUnit == PersistantConfig.TimeUnit.Microseconds) f *= 1000000.0; else if (m_Core.Config.EventBrowser_TimeUnit == PersistantConfig.TimeUnit.Nanoseconds) f *= 1000000000.0; return Formatter.Format(f); } return data.ToString(); } private TreelistView.Node MakeMarker(string text) { return new TreelistView.Node(new object[] { "", "", text, -1.0 }); } private TreelistView.Node MakeNode(UInt32 EID, UInt32 draw, string text, double duration) { return new TreelistView.Node(new object[] { EID, draw, text, duration }); } private TreelistView.Node AddDrawcall(TreelistView.Node existing, FetchDrawcall drawcall, TreelistView.Node root) { if (m_Core.Config.EventBrowser_HideEmpty) { if ((drawcall.children == null || drawcall.children.Length == 0) && (drawcall.flags & DrawcallFlags.PushMarker) != 0) return null; } UInt32 eventNum = drawcall.eventID; double duration = drawcall.duration; TreelistView.Node drawNode = MakeNode(eventNum, drawcall.drawcallID, drawcall.name, duration); if (existing != null) { existing.SetData(drawNode.GetData()); drawNode = existing; } else { root.Nodes.Add(drawNode); } DeferredEvent def = new DeferredEvent(); def.frameID = m_Core.CurFrame; def.eventID = eventNum; if (drawcall.context != m_Core.FrameInfo[m_Core.CurFrame].immContextId) { def.defCtx = drawcall.context; def.lastDefEv = drawcall.eventID; FetchDrawcall parent = drawcall.parent; while(!parent.name.Contains("ExecuteCommand")) parent = parent.parent; def.eventID = parent.eventID-1; def.firstDefEv = parent.children[0].eventID; if(parent.children[0].events.Length > 0) def.firstDefEv = parent.children[0].events[0].eventID; } drawNode.Tag = def; if (drawcall.children != null && drawcall.children.Length > 0) { for (int i = 0; i < drawcall.children.Length; i++) { TreelistView.Node d = drawNode.Nodes.Count > i ? drawNode.Nodes[i] : null; AddDrawcall(d, drawcall.children[i], drawNode); if (i > 0 && (drawcall.children[i-1].flags & DrawcallFlags.SetMarker) > 0) { drawNode.Nodes[drawNode.Nodes.Count - 2].Tag = drawNode.Nodes.LastNode.Tag; } if ((double)drawNode.Nodes[i]["Duration"] > 0.0) drawNode["Duration"] = Math.Max(0.0, (double)drawNode["Duration"]) + (double)drawNode.Nodes[i]["Duration"]; } bool found = false; for (int i = drawcall.children.Length - 1; i >= 0; i--) { if ((drawcall.children[i].flags & DrawcallFlags.SetMarker) == 0) { drawNode.Tag = drawNode.Nodes[i].Tag; found = true; break; } } if(!found) drawNode.Tag = drawNode.Nodes.LastNode.Tag; } return drawNode; } private void AddFrameDrawcalls(TreelistView.Node frame, FetchDrawcall[] drawcalls) { eventView.BeginUpdate(); frame["Duration"] = -1.0; DeferredEvent startEv = new DeferredEvent(); startEv.frameID = m_Core.CurFrame; startEv.eventID = 0; if(frame.Nodes.Count == 0) frame.Nodes.Add(MakeNode(0, 0, "Frame Start", -1.0)).Tag = startEv; for (int i = 0; i < drawcalls.Length; i++) { TreelistView.Node d = frame.Nodes.Count > (i + 1) ? frame.Nodes[i + 1] : null; TreelistView.Node newD = AddDrawcall(d, drawcalls[i], frame); if (newD != null) { d = newD; if ((double)d["Duration"] > 0.0) frame["Duration"] = Math.Max(0.0, (double)frame["Duration"]) + (double)d["Duration"]; } } frame.Tag = frame.Nodes.LastNode.Tag; eventView.EndUpdate(); } public void OnLogfileClosed() { eventView.BeginUpdate(); eventView.Nodes.Clear(); m_FrameNodes.Clear(); eventView.EndUpdate(); findEventButton.Enabled = false; jumpEventButton.Enabled = false; timeDraws.Enabled = false; } public void OnLogfileLoaded() { FetchFrameInfo[] frameList = m_Core.FrameInfo; findEventButton.Enabled = true; jumpEventButton.Enabled = true; timeDraws.Enabled = true; eventView.BeginUpdate(); eventView.Nodes.Clear(); m_FrameNodes.Clear(); for (int curFrame = 0; curFrame < frameList.Length; curFrame++) { TreelistView.Node frame = eventView.Nodes.Add(MakeMarker("Frame #" + frameList[curFrame].frameNumber.ToString())); m_FrameNodes.Add(frame); } eventView.EndUpdate(); for (int curFrame = 0; curFrame < frameList.Length; curFrame++) AddFrameDrawcalls(m_FrameNodes[curFrame], m_Core.GetDrawcalls((UInt32)curFrame)); if (frameList.Length > 0) { // frame 1 -> event 1 TreelistView.Node node = eventView.Nodes[0].Nodes[0]; ExpandNode(node); DeferredEvent evt = eventView.Nodes[0].Nodes.LastNode.Tag as DeferredEvent; m_Core.SetEventID(null, 0, evt.eventID + 1); eventView.NodesSelection.Clear(); eventView.NodesSelection.Add(eventView.Nodes[0]); eventView.FocusedNode = eventView.Nodes[0]; } } public void ExpandNode(TreelistView.Node node) { var n = node; while (node != null) { node.Expand(); node = node.Parent; } eventView.EnsureVisible(n); } private bool SelectEvent(ref TreelistView.Node found, TreelistView.NodeCollection nodes, UInt32 frameID, UInt32 eventID) { foreach (var n in nodes) { DeferredEvent ndef = n.Tag is DeferredEvent ? n.Tag as DeferredEvent : null; DeferredEvent fdef = found != null && found.Tag is DeferredEvent ? found.Tag as DeferredEvent : null; if (ndef != null) { if (ndef.eventID >= eventID && (found == null || ndef.eventID <= fdef.eventID)) found = n; if (ndef.eventID == eventID && n.Nodes.Count == 0) return true; } if (n.Nodes.Count > 0) { bool exact = SelectEvent(ref found, n.Nodes, frameID, eventID); if (exact) return true; } } return false; } private bool SelectEvent(UInt32 frameID, UInt32 eventID) { if (eventView.Nodes.Count == 0) return false; TreelistView.Node found = null; SelectEvent(ref found, eventView.Nodes[0].Nodes, frameID, eventID); if (found != null) { eventView.FocusedNode = found; ExpandNode(found); return true; } return false; } private void ClearFindIcons(TreelistView.NodeCollection nodes) { foreach (var n in nodes) { n.Image = null; if (n.Nodes.Count > 0) { ClearFindIcons(n.Nodes); } } } private void ClearFindIcons() { if (eventView.Nodes.Count > 0) { ClearFindIcons(eventView.Nodes[0].Nodes); eventView.Invalidate(); } } private int SetFindIcons(TreelistView.NodeCollection nodes, string filter) { int results = 0; foreach (var n in nodes) { if (n.Tag is DeferredEvent) { if (n["Name"].ToString().ToLowerInvariant().Contains(filter)) { n.Image = global::renderdocui.Properties.Resources.find; results++; } } if (n.Nodes.Count > 0) { results += SetFindIcons(n.Nodes, filter); } } return results; } private int SetFindIcons(string filter) { if(filter == "") return 0; return SetFindIcons(eventView.Nodes[0].Nodes, filter.ToLowerInvariant()); } private TreelistView.Node FindNode(TreelistView.NodeCollection nodes, string filter, UInt32 after) { foreach (var n in nodes) { if (n.Tag is DeferredEvent) { if ((UInt32)n["EID"] > after && n["Name"].ToString().ToLowerInvariant().Contains(filter)) return n; } if (n.Nodes.Count > 0) { TreelistView.Node found = FindNode(n.Nodes, filter, after); if (found != null) return found; } } return null; } private int FindEvent(TreelistView.NodeCollection nodes, string filter, UInt32 after, bool forward) { if(nodes == null) return -1; for (int i = forward ? 0 : nodes.Count - 1; i >= 0 && i < nodes.Count; i += forward ? 1 : -1) { var n = nodes[i]; if (n.Tag is DeferredEvent) { DeferredEvent def = n.Tag as DeferredEvent; bool matchesAfter = (forward && def.eventID > after) || (!forward && def.eventID < after); if (matchesAfter && n["Name"].ToString().ToLowerInvariant().Contains(filter)) return (int)def.eventID; } if (n.Nodes.Count > 0) { int found = FindEvent(n.Nodes, filter, after, forward); if (found > 0) return found; } } return -1; } private int FindEvent(string filter, UInt32 after, bool forward) { if (eventView.Nodes.Count == 0) return 0; return FindEvent(eventView.Nodes[0].Nodes, filter.ToLowerInvariant(), after, forward); } public void OnEventSelected(UInt32 frameID, UInt32 eventID) { SelectEvent(frameID, eventID); Invalidate(); } private void eventView_AfterSelect(object sender, TreeViewEventArgs e) { if (eventView.SelectedNode.Tag != null) { DeferredEvent def = eventView.SelectedNode.Tag as DeferredEvent; if (def.defCtx != ResourceId.Null) m_Core.SetContextFilter(this, 0, def.eventID, def.defCtx, def.firstDefEv, def.lastDefEv); else m_Core.SetEventID(this, 0, def.eventID); } } private void EventBrowser_Shown(object sender, EventArgs e) { if (m_Core.LogLoaded) OnLogfileLoaded(); } private void ShowJump() { HideJumpAndFind(); jumpStrip.Visible = true; findStrip.Visible = false; jumpToEID.Text = ""; jumpToEID.Focus(); } private void ShowFind() { HideJumpAndFind(); jumpStrip.Visible = false; findStrip.Visible = true; findEvent.Text = ""; findEvent.Focus(); findEvent.BackColor = SystemColors.Window; } private void HideJumpAndFind() { jumpStrip.Visible = false; if (findEvent.Text == "") { findStrip.Visible = false; ClearFindIcons(); } } private void eventView_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.G && e.Control && m_Core.LogLoaded) { ShowJump(); } if (e.KeyCode == Keys.F && e.Control && m_Core.LogLoaded) { ShowFind(); } if (e.KeyCode == Keys.T && e.Control && m_Core.LogLoaded) { TimeDrawcalls(); } if (e.KeyCode == Keys.C && e.Control) { string text = ""; for(int i=0; i < eventView.FocusedNode.Count; i++) { text += DataToString(eventView.Columns[i], eventView.FocusedNode[i]) + " "; } text += Environment.NewLine; Clipboard.SetText(text); } } private void SelectColumns() { var columns = new Dictionary(); foreach (var c in eventView.Columns) columns.Add(c.Fieldname, c.VisibleIndex >= 0); var cs = new Dialogs.ColumnSelector(columns, "Name"); var result = cs.ShowDialog(); if (result == DialogResult.OK) { columns = cs.GetColumnValues(); foreach (var c in columns) { var col = eventView.Columns[c.Key]; if (col == null) continue; if (!c.Value && col.VisibleIndex >= 0) eventView.Columns.SetVisibleIndex(col, -1); else if (c.Value && col.VisibleIndex < 0) eventView.Columns.SetVisibleIndex(col, eventView.Columns.VisibleColumns.Length); } } } private void TimeDrawcalls() { m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { m_Core.TimeDrawcalls(r); BeginInvoke((MethodInvoker)delegate { var col = eventView.Columns["Duration"]; if (col.VisibleIndex == -1) { eventView.Columns.SetVisibleIndex(col, eventView.Columns.VisibleColumns.Length); } for (int curFrame = 0; curFrame < m_FrameNodes.Count; curFrame++) AddFrameDrawcalls(m_FrameNodes[curFrame], m_Core.GetDrawcalls((UInt32)curFrame)); }); }); } private void jumpFind_Leave(object sender, EventArgs e) { HideJumpAndFind(); } private void jumpToEID_TextChanged(object sender, EventArgs e) { /* UInt32 eid = 0; if (UInt32.TryParse(jumpToEID.Text, out eid)) { SelectEvent(0, eid); } */ } private void findEvent_TextChanged(object sender, EventArgs e) { if (findEvent.Text != "") { findHighlight.Enabled = false; findHighlight.Enabled = true; } else { findHighlight.Enabled = false; findEvent.BackColor = SystemColors.Window; ClearFindIcons(); } } private void findHighlight_Tick(object sender, EventArgs e) { if (findEvent.Text == "") { findEvent.BackColor = SystemColors.Window; ClearFindIcons(); } ClearFindIcons(); int results = SetFindIcons(findEvent.Text); if (results > 0) findEvent.BackColor = SystemColors.Window; else findEvent.BackColor = Color.Red; findHighlight.Enabled = false; } private void findEvent_KeyPress(object sender, KeyPressEventArgs e) { // escape key if (e.KeyChar == '\0') { findHighlight.Enabled = false; findEvent.Text = ""; HideJumpAndFind(); eventView.Focus(); e.Handled = true; } if (e.KeyChar == '\n' || e.KeyChar == '\r') { if (findHighlight.Enabled) { findHighlight.Enabled = false; findHighlight_Tick(sender, null); } if (findEvent.Text != "") { Find(true); } e.Handled = true; } } private void jumpToEID_KeyPress(object sender, KeyPressEventArgs e) { // escape key if (e.KeyChar == '\0') { HideJumpAndFind(); eventView.Focus(); e.Handled = true; } if (e.KeyChar == '\n' || e.KeyChar == '\r') { UInt32 eid = 0; if (UInt32.TryParse(jumpToEID.Text, out eid)) { SelectEvent(0, eid); } //HideJumpAndFind(); e.Handled = true; } } private void closeFind_Click(object sender, EventArgs e) { findEvent.Text = ""; HideJumpAndFind(); eventView.Focus(); } private void EventBrowser_Leave(object sender, EventArgs e) { HideJumpAndFind(); } private void findNext_Click(object sender, EventArgs e) { Find(true); } private void findPrev_Click(object sender, EventArgs e) { Find(false); } private void Find(bool forward) { if(findEvent.Text == "") return; UInt32 curEID = m_Core.CurEvent; if (eventView.SelectedNode != null && eventView.Tag is DeferredEvent) curEID = (eventView.Tag as DeferredEvent).eventID; int eid = FindEvent(findEvent.Text, curEID, forward); if (eid >= 0) { SelectEvent(0, (UInt32)eid); findEvent.BackColor = SystemColors.Window; } else // if(WrapSearch) { eid = FindEvent(findEvent.Text, forward ? 0 : UInt32.MaxValue, forward); if (eid >= 0) { SelectEvent(0, (UInt32)eid); findEvent.BackColor = SystemColors.Window; } else { findEvent.BackColor = Color.Red; } } } private void findEventButton_Click(object sender, EventArgs e) { bool show = !findStrip.Visible; HideJumpAndFind(); eventView.Focus(); if (show) ShowFind(); } private void jumpEventButton_Click(object sender, EventArgs e) { bool show = !jumpStrip.Visible; HideJumpAndFind(); eventView.Focus(); if (show) ShowJump(); } private void timeDraws_Click(object sender, EventArgs e) { TimeDrawcalls(); } private void selectColumnsButton_Click(object sender, EventArgs e) { SelectColumns(); } private void selectVisibleColumnsToolStripMenuItem_Click(object sender, EventArgs e) { SelectColumns(); } private void EventBrowser_FormClosed(object sender, FormClosedEventArgs e) { m_Core.RemoveLogViewer(this); } } }