Files
renderdoc/renderdocui/Windows/TimelineBar.cs
T
baldurk 5bcbb47203 List GenerateMips, Copy and Resolve calls in event browser with draws
* What gets listed as a 'drawcall' is a bit fuzzy - previously it was
  drawing calls, dispatches and clears, but you could make a good
  argument for these to be included as well. As a semi-experiment, these
  calls are now included and will be listed in the event browser.
* Other calls can change resources like direct buffer or texture uploads
  and Map() type calls, but these remain as API calls listed between
  draw calls. Again this is mostly an arbitrary distinction.
* D3D11 logs are backwards compatible, GL logs are not (although it'd
  be relatively simple, GL logs will likely break backwards compat soon
  anyway, so not worth supporting it now only to break it soon).
2015-03-11 13:13:41 +00:00

1111 lines
41 KiB
C#

/******************************************************************************
* 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.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;
using renderdocui.Code;
using renderdoc;
namespace renderdocui.Windows
{
// this entire class needs burning down and rewriting from scratch.
public partial class TimelineBar : DockContent, ILogViewerForm
{
private Core m_Core;
private float m_Zoom = 1.0f;
public TimelineBar(Core core)
{
InitializeComponent();
Icon = global::renderdocui.Properties.Resources.icon;
m_Core = core;
this.DoubleBuffered = true;
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
panel.Painting = true;
panel.MouseWheelHandler = new MouseEventHandler(panel_MouseWheel);
UpdateScrollbar(1.0f, 0.0f);
}
void panel_MouseWheel(object sender, MouseEventArgs e)
{
double z = Math.Log(m_Zoom);
z += e.Delta / 2500.0;
UpdateScrollbar(Math.Max(1.0f, (float)Math.Exp(z)), (float)e.X / (float)panel.ClientRectangle.Width);
}
public void OnLogfileClosed()
{
m_Root = null;
m_HighlightResource = ResourceId.Null;
m_HighlightUsage = null;
panel.Invalidate();
UpdateScrollbar(1.0f, 0.0f);
}
public void OnLogfileLoaded()
{
m_Root = GatherEvents(m_Core.CurDrawcalls);
panel.Invalidate();
UpdateScrollbar(1.0f, 0.0f);
}
public void OnEventSelected(UInt32 frameID, UInt32 eventID)
{
panel.Invalidate();
}
private ResourceId m_HighlightResource = ResourceId.Null;
private string m_HighlightName = "";
private EventUsage[] m_HighlightUsage = null;
public void HighlightResource(ResourceId id, string name, EventUsage[] usage)
{
m_HighlightResource = id;
m_HighlightName = name;
m_HighlightUsage = usage;
panel.Invalidate();
}
private FetchTexture m_HistoryTex = null;
private Point m_HistoryPoint = Point.Empty;
private PixelModification[] m_History = null;
public void HighlightHistory(FetchTexture tex, Point pt, PixelModification[] modif)
{
m_HistoryTex = tex;
m_HistoryPoint = pt;
m_History = modif;
panel.Invalidate();
}
private RectangleF GetSubrect(RectangleF rect, float startSeg, float segWidth)
{
var subRect = rect;
subRect.X = rect.X + rect.Width * startSeg;
subRect.Width = rect.Width * segWidth;
return subRect;
}
private float[] lastPipX = new float[] { -100.0f, -100.0f, -100.0f };
private float DrawPip(Graphics g, Color col, RectangleF rect, int type,
int idx, int numChildren, float startSeg, float segWidth, string text)
{
var subRect = GetSubrect(rect, startSeg, segWidth);
subRect.X += pipRadius;
subRect.Y += pipPaddingY;
subRect.Width -= pipRadius * 2;
subRect.Height = pipRadius * 2;
float delta = (float)(idx + 1) / (float)(numChildren + 1);
float x = subRect.X - pipRadius + delta * Math.Max(0, subRect.Width);
float y = subRect.Y;
float width = pipRadius * 2;
float height = pipRadius * 2;
if (type == 0)
{
using(var brush = new SolidBrush(col))
g.FillPie(brush, x, y, width, height, 0.0f, 360.0f);
}
if (type == 1 || type == 2 || type == 3 || type == 4)
{
height += 2;
width += 4;
x -= 2;
PointF[] uptri = { new PointF(x, y+height-2),
new PointF(x+width, y+height-2),
new PointF(x+width/2.0f, y+2) };
bool update = true;
if (type > 2)
{
if (type == 3)
{
uptri[1] = new PointF(x + width / 2.0f, y + height - 2);
update = false;
}
if (type == 4)
{
uptri[0] = new PointF(x + width / 2.0f, y + height - 2);
}
type = 1;
}
if (x - lastPipX[type] > pipRadius*2.0f)
{
if (type == 2)
{
using (var pen = new Pen(col, 2))
g.DrawPolygon(pen, uptri);
}
else
{
using (var brush = new SolidBrush(col))
g.FillPolygon(brush, uptri);
}
if(update)
lastPipX[type] = x;
}
}
return x + width / 2;
}
private const int pipPaddingY = 8;
private const int pipRadius = 5;
private const int barPadding = 4;
private const int barHeight = 24;
private const float barBorder = 1.0f;
private Font barFont = new Font("Consolas", 10.0f, FontStyle.Regular);
private Color lightBack = Color.FromArgb(235, 235, 235);
private Color darkBack = Color.FromArgb(200, 200, 200);
private float MinBarSize(Graphics g, string text)
{
var size = g.MeasureString(text, barFont);
return size.Width + barBorder*2 + barPadding*2;
}
class Range
{
public Range(uint f, uint l) { first = f; last = l; }
public uint first;
public uint last;
};
private List<Range> m_ranges = new List<Range>();
private uint maxeid = 100000000;
private void MarkWrite(uint eid)
{
if (m_ranges.Count == 0 || m_ranges[m_ranges.Count-1].last < maxeid)
m_ranges.Add(new Range(eid, maxeid));
}
private void MarkRead(uint eid)
{
if (m_ranges.Count == 0)
m_ranges.Add(new Range(0, eid));
else
m_ranges[m_ranges.Count-1].last = eid;
}
private RectangleF DrawBar(Graphics g, Color back, RectangleF rect, float startSeg, float segWidth, string text, bool visible)
{
var subRect = GetSubrect(rect, startSeg, segWidth);
subRect.Height = barHeight;
if (subRect.Contains(markerPos) && visible && showMarker && markerPos.Y < ClientRectangle.Height - pipRadius*6)
{
back = Color.LightYellow;
Cursor = Cursors.Hand;
}
if (visible)
{
using(var brush = new SolidBrush(back))
g.FillRectangle(brush, subRect.X, subRect.Y, subRect.Width, subRect.Height);
using(var pen = new Pen(Brushes.Black, barBorder))
g.DrawRectangle(pen, subRect.X, subRect.Y, subRect.Width, subRect.Height);
var p = new Pen(Color.FromArgb(230, 230, 230), 1);
g.DrawLine(p, new PointF(subRect.Left, subRect.Bottom), new PointF(subRect.Left, rect.Bottom - pipRadius * 6));
g.DrawLine(p, new PointF(subRect.Right, subRect.Bottom), new PointF(subRect.Right, rect.Bottom - pipRadius * 6));
p.Dispose();
}
var stringSize = g.MeasureString(text, barFont);
float left = subRect.X + barPadding;
if(left < barPadding)
left = Math.Min(barPadding, Math.Max(subRect.X + subRect.Width - barPadding * 2 - stringSize.Width, left));
var textRect = new RectangleF(left, subRect.Y + barPadding,
subRect.Width - barPadding*2, subRect.Height - barPadding);
textRect.Height = Math.Min(textRect.Height, rect.Height - pipRadius * 6);
if (visible)
{
g.Clip = new Region(textRect);
g.DrawString(text, barFont, Brushes.Black, textRect.X, textRect.Y);
g.ResetClip();
}
var ret = rect;
ret.X = subRect.X;
ret.Y = subRect.Y + subRect.Height;
ret.Width = subRect.Width;
ret.Height = rect.Height - subRect.Height;
return ret;
}
private class Section
{
public string Name = "";
public bool Expanded = false;
public List<Section> subsections = null;
public List<FetchDrawcall> draws = null;
public RectangleF lastRect = new RectangleF();
public List<float> lastPoss = new List<float>();
public List<bool> lastUsage = new List<bool>();
public List<bool> lastVisible = new List<bool>();
}
private Section m_Root = null;
private Section GatherEvents(FetchDrawcall[] draws)
{
var sections = new List<List<FetchDrawcall>>();
foreach (var d in draws)
{
if ((d.flags & (DrawcallFlags.SetMarker | DrawcallFlags.Present)) > 0)
continue;
if (m_Core.Config.EventBrowser_HideEmpty)
{
if ((d.children == null || d.children.Length == 0) && (d.flags & DrawcallFlags.PushMarker) != 0)
continue;
}
bool newSection = ((d.flags & DrawcallFlags.PushMarker) > 0 || sections.Count == 0);
if (!newSection)
{
var lastSection = sections.Last();
if (lastSection.Count == 1 && (lastSection[0].flags & DrawcallFlags.PushMarker) > 0)
newSection = true;
}
if (newSection)
sections.Add(new List<FetchDrawcall>());
sections.Last().Add(d);
}
Section ret = new Section();
ret.subsections = new List<Section>();
foreach (var s in sections)
{
Section sec = null;
if (s.Count == 1 && (s[0].flags & DrawcallFlags.PushMarker) > 0)
{
sec = GatherEvents(s[0].children);
sec.Name = s[0].name;
}
else
{
sec = new Section();
sec.draws = s;
for (int i = 0; i < sec.draws.Count; i++)
{
sec.lastPoss.Add(0.0f);
sec.lastUsage.Add(false);
sec.lastVisible.Add(false);
}
}
ret.subsections.Add(sec);
}
return ret;
}
private float MinSectionSize(Graphics g, Section s)
{
float myWidth = 20.0f;
if (s.Name.Length > 0)
myWidth = Math.Max(myWidth, MinBarSize(g, "+ " + s.Name));
if (s.subsections == null || s.subsections.Count == 0)
return myWidth;
float childWidth = 0.0f;
if(s.Expanded && s.subsections != null)
foreach (var sub in s.subsections)
childWidth += MinSectionSize(g, sub);
return Math.Max(myWidth, childWidth);
}
private void RenderSection(int depth, Graphics g, RectangleF rect, Section section, bool visible, float lastVisibleHeight)
{
float start = 0.0f;
float[] minwidths = new float[section.subsections.Count];
float[] widths = new float[section.subsections.Count];
float maxwidth = 0.0f;
float totalwidth = 0.0f;
for (int i = 0; i < section.subsections.Count; i++)
{
// initial widths are minwidth, used to 'proportionally' size sections.
// all that matters here is the relative proportions and that it's >= minwidth,
// it gets sized up or down to fit later.
minwidths[i] = MinSectionSize(g, section.subsections[i]);
widths[i] = minwidths[i];
maxwidth = Math.Max(maxwidth, minwidths[i]);
totalwidth += widths[i];
}
float scale = rect.Width / totalwidth;
// if we have space free, scale everything up the same
if (totalwidth < rect.Width)
{
for (int i = 0; i < section.subsections.Count; i++)
widths[i] *= scale;
}
else
{
// scale everything down to fit (this will reduce some below minwidth)
for (int i = 0; i < section.subsections.Count; i++)
widths[i] *= scale;
// search for sections that are > their min width, and skim off the top.
for (int i = 0; i < section.subsections.Count; i++)
{
// found a section that's too small
if (widths[i] < minwidths[i])
{
// we try and skim an equal amount off every section, so the scaling
// is nice and uniform rather than left-focussed
float missing = minwidths[i] - widths[i];
float share = missing / (float)(section.subsections.Count - 1);
bool slack = false;
// keep going trying to find some slack and skimming it off
int iters = 0;
do
{
slack = false;
iters++;
if (iters == 10) break;
for (int j = 0; j < section.subsections.Count; )
{
// ignore current section
if (i == j)
{
j++;
continue;
}
// if this section has free space
if (widths[j] > minwidths[j])
{
float avail = widths[j] - minwidths[j];
// skim off up as much as is available, up to the share per section
float delta = Math.Max(0.1f, Math.Min(avail, share));
widths[i] += delta;
widths[j] -= delta;
missing -= delta;
// if we didn't skim off our share, recalculate how much we'll need to
// skim off each, and start again
if (avail < share)
{
share = missing / (float)(section.subsections.Count - 1);
j = 0;
continue;
}
slack = true;
}
j++;
}
// keep going while there are sections with slack, and we need to make some up.
// ie. if we find all our missing space then we can finish, but also if we haven't
// found enough but there's nothing to give, we also give up.
} while (slack && missing > 0.0f);
}
}
}
for (int i = 0; i < section.subsections.Count; i++)
widths[i] /= rect.Width;
var col = depth % 2 == 0 ? lightBack : darkBack;
var clipRect = rect;
clipRect.Height -= pipRadius * 6;
for (int i = 0; i < section.subsections.Count; i++)
{
var s = section.subsections[i];
if (s.Name.Length > 0)
{
g.Clip = new Region(clipRect);
var childRect = DrawBar(g, col, rect, start, widths[i], (s.Expanded ? "- " : "+ ") + s.Name, visible);
g.ResetClip();
RenderSection(depth + 1, g, childRect, s, visible && s.Expanded, visible ? childRect.Top : lastVisibleHeight);
var backRect = GetSubrect(childRect, 0.0f, 1.0f);
backRect.Y += barBorder / 2;
backRect.Height = barHeight;
LinearGradientBrush brush = new LinearGradientBrush(new PointF(0, backRect.Y),
new PointF(0, backRect.Y + backRect.Height + 1),
Color.FromArgb(255, darkBack), Color.FromArgb(0, darkBack));
if (visible && s.Expanded && (s.subsections == null || s.subsections.Count == 0))
{
g.Clip = new Region(clipRect);
g.FillRectangle(brush, backRect);
g.ResetClip();
}
brush.Dispose();
s.lastRect = childRect;
s.lastRect.Y = rect.Y;
s.lastRect.Height = childRect.Y - rect.Y;
if (!visible) s.lastRect.Height = 0;
}
else
{
var backRect = GetSubrect(rect, start, widths[i]);
backRect.Y += barBorder/2;
backRect.Height = barHeight;
LinearGradientBrush brush = new LinearGradientBrush(new PointF(0, backRect.Y),
new PointF(0, backRect.Y + backRect.Height + 1),
Color.FromArgb(255, darkBack), Color.FromArgb(0, darkBack));
if (visible)
{
g.Clip = new Region(clipRect);
g.FillRectangle(brush, backRect);
g.ResetClip();
}
brush.Dispose();
int highlight = -1;
var highlightBarRect = rect;
highlightBarRect.Y = highlightBarRect.Bottom - pipRadius * 4;
highlightBarRect.Height = pipRadius * 2;
if(s.draws != null)
{
for (int d = 0; d < s.draws.Count; d++)
{
if (m_History != null)
{
foreach (var u in m_History)
{
if (u.eventID == s.draws[d].eventID)
{
var barcol = Color.Black;
int type = 2;
DrawPip(g, barcol, highlightBarRect, type, d, s.draws.Count, start, widths[i], "");
}
}
}
else if (m_HighlightUsage != null)
{
foreach (var u in m_HighlightUsage)
{
if (u.eventID == s.draws[d].eventID)
{
var barcol = Color.Black;
int type = 2;
DrawPip(g, barcol, highlightBarRect, type, d, s.draws.Count, start, widths[i], "");
}
}
}
s.lastUsage[d] = false;
s.lastVisible[d] = visible;
}
for (int d = 0; d < s.draws.Count; d++)
{
if (m_History != null)
{
foreach (var u in m_History)
{
if (u.eventID == s.draws[d].eventID)
{
if (u.EventPassed())
{
DrawPip(g, Color.Lime, highlightBarRect, 1, d, s.draws.Count, start, widths[i], "");
MarkWrite(s.draws[d].eventID);
}
else
{
DrawPip(g, Color.Crimson, highlightBarRect, 1, d, s.draws.Count, start, widths[i], "");
MarkRead(s.draws[d].eventID);
}
s.lastUsage[d] = true;
}
}
}
else if (m_HighlightUsage != null)
{
foreach (var u in m_HighlightUsage)
{
if (u.eventID == s.draws[d].eventID)
{
// read/write
if (u.usage == ResourceUsage.CS_UAV ||
u.usage == ResourceUsage.PS_UAV ||
u.usage == ResourceUsage.GenMips ||
u.usage == ResourceUsage.Copy ||
u.usage == ResourceUsage.Resolve)
{
DrawPip(g, Color.Orchid, highlightBarRect, 3, d, s.draws.Count, start, widths[i], "");
DrawPip(g, Color.Lime, highlightBarRect, 4, d, s.draws.Count, start, widths[i], "");
MarkWrite(s.draws[d].eventID);
}
// write
else if (u.usage == ResourceUsage.SO ||
u.usage == ResourceUsage.OM_DSV ||
u.usage == ResourceUsage.OM_RTV ||
u.usage == ResourceUsage.CopyDst ||
u.usage == ResourceUsage.ResolveDst)
{
DrawPip(g, Color.Orchid, highlightBarRect, 1, d, s.draws.Count, start, widths[i], "");
MarkWrite(s.draws[d].eventID);
}
// clear
else if (u.usage == ResourceUsage.Clear)
{
DrawPip(g, Color.Silver, highlightBarRect, 1, d, s.draws.Count, start, widths[i], "");
MarkWrite(s.draws[d].eventID);
}
// read
else
{
DrawPip(g, Color.Lime, highlightBarRect, 1, d, s.draws.Count, start, widths[i], "");
MarkRead(s.draws[d].eventID);
}
s.lastUsage[d] = true;
}
}
}
}
for (int d = 0; d < s.draws.Count; d++)
{
if (s.draws[d].eventID == m_Core.CurEvent)
highlight = d;
if (visible)
{
g.Clip = new Region(clipRect);
if (s.draws[d].eventID != m_Core.CurEvent)
s.lastPoss[d] = DrawPip(g, Color.Blue, rect, 0, d, s.draws.Count, start, widths[i], s.draws[d].name);
g.ResetClip();
}
else
{
s.lastPoss[d] = DrawPip(g, Color.Blue, rect, 999, d, s.draws.Count, start, widths[i], s.draws[d].name);
}
}
}
if (highlight >= 0)
{
var subRect = GetSubrect(rect, start, widths[i]);
subRect.X += pipRadius;
subRect.Y += pipPaddingY;
subRect.Width -= pipRadius * 2;
subRect.Height = pipRadius * 2;
float delta = (float)(highlight + 1) / (float)(s.draws.Count + 1);
m_CurrentMarker = new PointF(subRect.X + delta * Math.Max(0, subRect.Width), lastVisibleHeight);
}
if (highlight >= 0 && visible && s.draws != null)
{
g.Clip = new Region(clipRect);
s.lastPoss[highlight] = DrawPip(g, Color.LightGreen, rect, 0, highlight, s.draws.Count, start, widths[i], s.draws[highlight].name);
g.ResetClip();
}
s.lastRect = backRect;
if (!visible) s.lastRect.Height = 0;
}
start += widths[i];
}
}
private PointF m_CurrentMarker = new PointF(-1, -1);
private bool m_FailedPaint = false;
private void panel_Paint(object sender, PaintEventArgs e)
{
if(ClientRectangle.Width <= 0 || ClientRectangle.Height <= 0)
return;
Cursor = Cursors.Arrow;
Bitmap bmp = null;
try
{
bmp = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);
}
catch (System.ArgumentException)
{
// out of memory or huge bitmap. Clear to black rather than crashing
e.Graphics.Clear(Color.Black);
if(!m_FailedPaint)
{
renderdoc.StaticExports.LogText("Failed to paint TimelineBar - System.ArgumentException");
m_FailedPaint = true;
}
return;
}
var g = Graphics.FromImage(bmp);
g.SmoothingMode = SmoothingMode.AntiAlias;
var clientRect = panel.ClientRectangle;
var rect = clientRect;
rect.Inflate(-4, -4);
rect.Width = (int)(rect.Width * m_Zoom);
rect.X -= (int)(rect.Width * ScrollPos);
if (m_Core.LogLoaded)
Text = "Timeline - Frame #" + m_Core.FrameInfo[m_Core.CurFrame].frameNumber;
else
Text = "Timeline";
g.Clear(Color.White);
var barRect = new Rectangle(clientRect.Left + 1, clientRect.Bottom - pipRadius * 6, clientRect.Width - 2, pipRadius * 6 - 2);
using (var brush = new SolidBrush(Color.Azure))
g.FillRectangle(brush, barRect);
using (var pen = new Pen(Brushes.Black, 2))
{
g.DrawLine(pen,
new Point(clientRect.Left, clientRect.Bottom - pipRadius * 6),
new Point(clientRect.Right, clientRect.Bottom - pipRadius * 6));
g.DrawRectangle(pen, new Rectangle(clientRect.Left, clientRect.Top, clientRect.Width - 1, clientRect.Height - 1));
}
lastPipX[0] = lastPipX[1] = lastPipX[2] = -100.0f;
if (m_History != null)
{
g.DrawString("Pixel history for " + m_HistoryTex.name, barFont, Brushes.Black, barRect.X, barRect.Y + 2);
}
else if (m_HighlightResource != ResourceId.Null)
{
g.DrawString(m_HighlightName + " Reads", barFont, Brushes.Black, barRect.X, barRect.Y + 2);
barRect.X += (int)Math.Ceiling(g.MeasureString(m_HighlightName + " Reads", barFont).Width);
barRect.X += pipRadius;
DrawPip(g, Color.Black, new RectangleF(barRect.X, barRect.Y - pipRadius, pipRadius * 2, pipRadius * 2), 2, 0, 1, 0.0f, 1.0f, "");
DrawPip(g, Color.Lime, new RectangleF(barRect.X, barRect.Y - pipRadius, pipRadius * 2, pipRadius * 2), 1, 0, 1, 0.0f, 1.0f, "");
barRect.X += pipRadius * 2;
barRect.X += pipRadius;
g.DrawString(", Clears ", barFont, Brushes.Black, barRect.X, barRect.Y + 2);
barRect.X += (int)Math.Ceiling(g.MeasureString(", Clears ", barFont).Width);
barRect.X += pipRadius;
DrawPip(g, Color.Black, new RectangleF(barRect.X, barRect.Y - pipRadius, pipRadius * 2, pipRadius * 2), 2, 0, 1, 0.0f, 1.0f, "");
DrawPip(g, Color.Silver, new RectangleF(barRect.X, barRect.Y - pipRadius, pipRadius * 2, pipRadius * 2), 1, 0, 1, 0.0f, 1.0f, "");
barRect.X += pipRadius * 2;
barRect.X += pipRadius;
g.DrawString(" and Writes ", barFont, Brushes.Black, barRect.X, barRect.Y + 2);
barRect.X += (int)Math.Ceiling(g.MeasureString(" and Writes", barFont).Width);
barRect.X += pipRadius;
DrawPip(g, Color.Black, new RectangleF(barRect.X, barRect.Y - pipRadius, pipRadius * 2, pipRadius * 2), 2, 0, 1, 0.0f, 1.0f, "");
DrawPip(g, Color.Orchid, new RectangleF(barRect.X, barRect.Y - pipRadius, pipRadius * 2, pipRadius * 2), 1, 0, 1, 0.0f, 1.0f, "");
}
m_CurrentMarker = new PointF(-1, -1);
m_ranges.Clear();
if (m_Core.LogLoaded)
{
var frameRect = rect;
using (var pen = new Pen(Brushes.Black, 2))
g.DrawLine(pen, new Point(frameRect.Left, frameRect.Top), new Point(frameRect.Right, frameRect.Top));
var childRect = frameRect;
childRect.Y += 1;
lastPipX[0] = lastPipX[1] = lastPipX[2] = -100.0f;
if (m_Root != null)
RenderSection(1, g, childRect, m_Root, true, childRect.Top);
/*
if (m_ranges.Count > 0)
{
* var pen = new Pen(Brushes.Red, 8);
foreach (Range r in m_ranges)
{
float a = r.first == 0 ? 0.0f : GetEIDPoint(m_Root, r.first);
float b = r.last == maxeid ? clientRect.Width : GetEIDPoint(m_Root, r.last);
g.DrawLine(pen, new Point(clientRect.Left + (int)a, clientRect.Bottom - pipRadius * 3),
new Point(clientRect.Left + (int)b, clientRect.Bottom - pipRadius * 3));
}
* pen.Dispose();
}
*/
}
if (m_CurrentMarker.X >= 0)
{
using (var pen = new Pen(Color.FromArgb(200, 200, 200), 2.0f))
g.DrawLine(pen,
new PointF(m_CurrentMarker.X, m_CurrentMarker.Y),
new PointF(m_CurrentMarker.X, clientRect.Bottom - pipRadius * 6 - 2));
}
if (showMarker)
{
using (var pen = new Pen(Brushes.Red, 2.0f))
g.DrawLine(pen, new Point(markerPos.X, 0), new Point(markerPos.X, ClientRectangle.Height));
}
e.Graphics.DrawImage(bmp, 0, 0, ClientRectangle, GraphicsUnit.Pixel);
g.Dispose();
bmp.Dispose();
}
private float ScrollPos
{
get
{
if (m_Zoom <= 1.0f)
return 0.0f;
return (float)scroll.Value / (float)scroll.Maximum;
}
}
private void UpdateScrollbar(float newZoom, float zoomPos)
{
if (newZoom <= 1.0f)
scroll.Visible = false;
else
scroll.Visible = true;
// get previous zoom at zoomPos
float prev = ScrollPos + zoomPos / m_Zoom;
scroll.Maximum = 200;
scroll.LargeChange = 100;
scroll.SmallChange = 10;
scroll.Maximum = (int)(100.0f * newZoom) - 1;
// set value so it is the same, centred around zoomPos
scroll.Value = (int)Math.Round(Math.Max(0, Math.Min(1, prev - (zoomPos / newZoom))) * scroll.Maximum);
// Hackity-hack! Clamp from the right
if(scroll.Maximum > scroll.LargeChange)
scroll.Value = Math.Min(scroll.Value, scroll.Maximum - scroll.LargeChange + 1);
m_Zoom = newZoom;
panel.Invalidate();
}
private void scroll_Scroll(object sender, ScrollEventArgs e)
{
panel.Invalidate();
}
private bool showMarker = false;
private Point markerPos = Point.Empty;
private void panel_MouseLeave(object sender, EventArgs e)
{
showMarker = false;
markerPos = Point.Empty;
panel.Invalidate();
Cursor = Cursors.Arrow;
}
private void panel_MouseEnter(object sender, EventArgs e)
{
showMarker = true;
}
private void panel_MouseMove(object sender, MouseEventArgs e)
{
markerPos = e.Location;
panel.Invalidate();
}
private void TimelineBar_Resize(object sender, EventArgs e)
{
panel.Invalidate();
}
private float GetEIDPoint(Section s, uint eid)
{
if (s == null)
return -1.0f;
if (s.subsections != null)
{
foreach (var sub in s.subsections)
{
float p = GetEIDPoint(sub, eid);
if (p > 0)
return p;
}
}
for (int i = 0; i < s.lastPoss.Count; i++)
{
if (s.draws[i].eventID == eid)
return s.lastPoss[i];
}
return -1.0f;
}
private void FindDraw(Point p, Section s,
ref FetchDrawcall left, ref float dleft, ref bool uleft,
ref FetchDrawcall right, ref float dright, ref bool uright)
{
if (s == null)
return;
var rect = s.lastRect;
rect.Y = 0;
rect.Height = 10000;
if (s.subsections != null)
{
foreach (var sub in s.subsections)
{
FindDraw(p, sub, ref left, ref dleft, ref uleft, ref right, ref dright, ref uright);
if(left != null && right != null)
return;
}
}
if (rect.Contains(p))
{
if (s.draws == null || s.draws.Count == 0)
return;
for(int i=0; i < s.lastPoss.Count; i++)
{
if (s.lastVisible[i])
{
if (s.lastPoss[i] <= p.X)
{
if (
// not found left
left == null ||
// this left is closer and as usage-y, or we don't have a usage-y one yet
(s.lastPoss[i] > dleft && s.lastUsage[i] == uleft) ||
// this left is WAY closer
(s.lastPoss[i] > dleft + 20.0f) ||
// this left is more usage-y
(s.lastUsage[i] && !uleft)
)
{
dleft = s.lastPoss[i];
uleft = s.lastUsage[i];
left = s.draws[i];
}
}
if (s.lastPoss[i] > p.X)
{
if (
// not found right
right == null ||
// this right is closer and as usage-y, or we don't have a usage-y one yet
(s.lastPoss[i] < dright && s.lastUsage[i] == uright) ||
// this right is WAY closer
(s.lastPoss[i] < dright - 20.0f) ||
// this right is more usage-y
(s.lastUsage[i] && !uright)
)
{
dright = s.lastPoss[i];
uright = s.lastUsage[i];
right = s.draws[i];
}
}
}
}
if (left != null && right != null)
return;
}
}
private FetchDrawcall FindDraw(Point p)
{
FetchDrawcall left = null;
FetchDrawcall right = null;
float dleft = -1.0f;
float dright = -1.0f;
bool uleft = false;
bool uright = false;
FindDraw(p, m_Root, ref left, ref dleft, ref uleft, ref right, ref dright, ref uright);
if(left == null)
return right;
if(right == null)
return left;
if(Math.Abs(p.X - dleft) < Math.Abs(p.X - dright))
return left;
else
return right;
}
private bool ProcessClick(Point p, Section s)
{
if(s == null)
return false;
if (s.lastRect.Contains(p))
{
s.Expanded = !s.Expanded;
return true;
}
else
{
if (s.subsections != null)
{
foreach (var sub in s.subsections)
{
var ret = ProcessClick(p, sub);
if (ret)
return ret;
}
}
}
return false;
}
private void panel_Click(object sender, EventArgs e)
{
var p = panel.PointToClient(Cursor.Position);
var expanded = ProcessClick(p, m_Root);
if (!expanded)
{
var draw = FindDraw(p);
if (draw != null)
m_Core.SetEventID(null, m_Core.CurFrame, draw.eventID);
}
}
private void TimelineBar_FormClosed(object sender, FormClosedEventArgs e)
{
m_Core.RemoveLogViewer(this);
barFont.Dispose();
}
}
}