You're breathtaking!
This commit is contained in:
438
Source/Editor/Windows/Profiler/CPU.cs
Normal file
438
Source/Editor/Windows/Profiler/CPU.cs
Normal file
@@ -0,0 +1,438 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// The CPU performance profiling mode.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Windows.Profiler.ProfilerMode" />
|
||||
internal sealed unsafe class CPU : ProfilerMode
|
||||
{
|
||||
private readonly SingleChart _mainChart;
|
||||
private readonly Timeline _timeline;
|
||||
private readonly Table _table;
|
||||
private readonly SamplesBuffer<ProfilingTools.ThreadStats[]> _events = new SamplesBuffer<ProfilingTools.ThreadStats[]>();
|
||||
private bool _showOnlyLastUpdateEvents;
|
||||
|
||||
public CPU()
|
||||
: base("CPU")
|
||||
{
|
||||
// Layout
|
||||
var panel = new Panel(ScrollBars.Vertical)
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = Margin.Zero,
|
||||
Parent = this,
|
||||
};
|
||||
var layout = new VerticalPanel
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||
Offsets = Margin.Zero,
|
||||
IsScrollable = true,
|
||||
Parent = panel,
|
||||
};
|
||||
|
||||
// Chart
|
||||
_mainChart = new SingleChart
|
||||
{
|
||||
Title = "Update",
|
||||
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
|
||||
Parent = layout,
|
||||
};
|
||||
_mainChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
|
||||
// Timeline
|
||||
_timeline = new Timeline
|
||||
{
|
||||
Height = 340,
|
||||
Parent = layout,
|
||||
};
|
||||
|
||||
// Table
|
||||
var headerColor = Style.Current.LightBackground;
|
||||
_table = new Table
|
||||
{
|
||||
Columns = new[]
|
||||
{
|
||||
new ColumnDefinition
|
||||
{
|
||||
UseExpandCollapseMode = true,
|
||||
CellAlignment = TextAlignment.Near,
|
||||
Title = "Event",
|
||||
TitleBackgroundColor = headerColor,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Total",
|
||||
TitleBackgroundColor = headerColor,
|
||||
FormatValue = FormatCellPercentage,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Self",
|
||||
TitleBackgroundColor = headerColor,
|
||||
FormatValue = FormatCellPercentage,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Time ms",
|
||||
TitleBackgroundColor = headerColor,
|
||||
FormatValue = FormatCellMs,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Self ms",
|
||||
TitleBackgroundColor = headerColor,
|
||||
FormatValue = FormatCellMs,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Memory",
|
||||
TitleBackgroundColor = headerColor,
|
||||
FormatValue = FormatCellBytes,
|
||||
},
|
||||
},
|
||||
Parent = layout,
|
||||
};
|
||||
_table.Splits = new[]
|
||||
{
|
||||
0.5f,
|
||||
0.1f,
|
||||
0.1f,
|
||||
0.1f,
|
||||
0.1f,
|
||||
0.1f,
|
||||
};
|
||||
}
|
||||
|
||||
private string FormatCellPercentage(object x)
|
||||
{
|
||||
return ((float)x).ToString("0.0") + '%';
|
||||
}
|
||||
|
||||
private string FormatCellMs(object x)
|
||||
{
|
||||
return ((float)x).ToString("0.00");
|
||||
}
|
||||
|
||||
private string FormatCellBytes(object x)
|
||||
{
|
||||
return Utilities.Utils.FormatBytesCount((int)x);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Clear()
|
||||
{
|
||||
_mainChart.Clear();
|
||||
_events.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(ref SharedUpdateData sharedData)
|
||||
{
|
||||
_mainChart.AddSample(sharedData.Stats.UpdateTimeMs);
|
||||
|
||||
// Gather CPU events
|
||||
var events = sharedData.GetEventsCPU();
|
||||
_events.Add(events);
|
||||
|
||||
// Update timeline if using the last frame
|
||||
if (_mainChart.SelectedSampleIndex == -1)
|
||||
{
|
||||
var viewRange = GetEventsViewRange();
|
||||
UpdateTimeline(ref viewRange);
|
||||
UpdateTable(ref viewRange);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents)
|
||||
{
|
||||
_showOnlyLastUpdateEvents = showOnlyLastUpdateEvents;
|
||||
_mainChart.SelectedSampleIndex = selectedFrame;
|
||||
|
||||
var viewRange = GetEventsViewRange();
|
||||
UpdateTimeline(ref viewRange);
|
||||
UpdateTable(ref viewRange);
|
||||
}
|
||||
|
||||
private struct ViewRange
|
||||
{
|
||||
public double Start;
|
||||
public double End;
|
||||
|
||||
public static ViewRange Full = new ViewRange
|
||||
{
|
||||
Start = float.MinValue,
|
||||
End = float.MaxValue
|
||||
};
|
||||
|
||||
public ViewRange(ref ProfilerCPU.Event e)
|
||||
{
|
||||
Start = e.Start - MinEventTimeMs;
|
||||
End = e.End + MinEventTimeMs;
|
||||
}
|
||||
|
||||
public bool SkipEvent(ref ProfilerCPU.Event e)
|
||||
{
|
||||
return e.Start < Start || e.Start > End;
|
||||
}
|
||||
}
|
||||
|
||||
private ViewRange GetEventsViewRange()
|
||||
{
|
||||
if (_showOnlyLastUpdateEvents)
|
||||
{
|
||||
// Find root event named 'Update' and use it as a view range
|
||||
if (_events.Count != 0)
|
||||
{
|
||||
var data = _events.Get(_mainChart.SelectedSampleIndex);
|
||||
if (data != null)
|
||||
{
|
||||
for (int j = 0; j < data.Length; j++)
|
||||
{
|
||||
var events = data[j].Events;
|
||||
if (events == null)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < events.Length; i++)
|
||||
{
|
||||
var e = events[i];
|
||||
|
||||
if (e.Depth == 0 && new string(e.Name) == "Update")
|
||||
{
|
||||
return new ViewRange(ref e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ViewRange.Full;
|
||||
}
|
||||
|
||||
private void AddEvent(double startTime, int maxDepth, float xOffset, int depthOffset, int index, ProfilerCPU.Event[] events, ContainerControl parent)
|
||||
{
|
||||
ref ProfilerCPU.Event e = ref events[index];
|
||||
|
||||
double length = e.End - e.Start;
|
||||
double scale = 100.0;
|
||||
float x = (float)((e.Start - startTime) * scale);
|
||||
float width = (float)(length * scale);
|
||||
string name = new string(e.Name).Replace("::", ".");
|
||||
|
||||
var control = new Timeline.Event(x + xOffset, e.Depth + depthOffset, width)
|
||||
{
|
||||
Name = name,
|
||||
TooltipText = string.Format("{0}, {1} ms", name, ((int)(length * 1000.0) / 1000.0f)),
|
||||
Parent = parent,
|
||||
};
|
||||
|
||||
// Spawn sub events
|
||||
int childrenDepth = e.Depth + 1;
|
||||
if (childrenDepth <= maxDepth)
|
||||
{
|
||||
while (++index < events.Length)
|
||||
{
|
||||
int subDepth = events[index].Depth;
|
||||
|
||||
if (subDepth <= e.Depth)
|
||||
break;
|
||||
if (subDepth == childrenDepth)
|
||||
{
|
||||
AddEvent(startTime, maxDepth, xOffset, depthOffset, index, events, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTimeline(ref ViewRange viewRange)
|
||||
{
|
||||
var container = _timeline.EventsContainer;
|
||||
|
||||
// Clear previous events
|
||||
container.DisposeChildren();
|
||||
|
||||
container.LockChildrenRecursive();
|
||||
|
||||
_timeline.Height = UpdateTimelineInner(ref viewRange);
|
||||
|
||||
container.UnlockChildrenRecursive();
|
||||
container.PerformLayout();
|
||||
}
|
||||
|
||||
private float UpdateTimelineInner(ref ViewRange viewRange)
|
||||
{
|
||||
if (_events.Count == 0)
|
||||
return 0;
|
||||
var data = _events.Get(_mainChart.SelectedSampleIndex);
|
||||
if (data == null || data.Length == 0)
|
||||
return 0;
|
||||
|
||||
// Find the first event start time (for the timeline start time)
|
||||
double startTime = double.MaxValue;
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
if (data[i].Events != null && data[i].Events.Length != 0)
|
||||
startTime = Math.Min(startTime, data[i].Events[0].Start);
|
||||
}
|
||||
if (startTime >= double.MaxValue)
|
||||
return 0;
|
||||
|
||||
var container = _timeline.EventsContainer;
|
||||
|
||||
// Create timeline track per thread
|
||||
int depthOffset = 0;
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
var events = data[i].Events;
|
||||
if (events == null)
|
||||
continue;
|
||||
|
||||
// Check maximum depth
|
||||
int maxDepth = -1;
|
||||
for (int j = 0; j < events.Length; j++)
|
||||
{
|
||||
var e = events[j];
|
||||
|
||||
// Reject events outside the view range
|
||||
if (viewRange.SkipEvent(ref e))
|
||||
continue;
|
||||
|
||||
maxDepth = Mathf.Max(maxDepth, e.Depth);
|
||||
}
|
||||
|
||||
// Skip empty tracks
|
||||
if (maxDepth == -1)
|
||||
continue;
|
||||
|
||||
// Add thread label
|
||||
float xOffset = 90;
|
||||
var label = new Timeline.TrackLabel
|
||||
{
|
||||
Bounds = new Rectangle(0, depthOffset * Timeline.Event.DefaultHeight, xOffset, (maxDepth + 2) * Timeline.Event.DefaultHeight),
|
||||
Name = data[i].Name,
|
||||
BackgroundColor = Style.Current.Background * 1.1f,
|
||||
Parent = container,
|
||||
};
|
||||
|
||||
// Add events
|
||||
for (int j = 0; j < events.Length; j++)
|
||||
{
|
||||
var e = events[j];
|
||||
if (e.Depth == 0)
|
||||
{
|
||||
// Reject events outside the view range
|
||||
if (viewRange.SkipEvent(ref e))
|
||||
continue;
|
||||
|
||||
AddEvent(startTime, maxDepth, xOffset, depthOffset, j, events, container);
|
||||
}
|
||||
}
|
||||
|
||||
depthOffset += maxDepth + 2;
|
||||
}
|
||||
|
||||
return Timeline.Event.DefaultHeight * depthOffset;
|
||||
}
|
||||
|
||||
private void UpdateTable(ref ViewRange viewRange)
|
||||
{
|
||||
_table.DisposeChildren();
|
||||
|
||||
_table.LockChildrenRecursive();
|
||||
|
||||
UpdateTableInner(ref viewRange);
|
||||
|
||||
_table.UnlockChildrenRecursive();
|
||||
_table.PerformLayout();
|
||||
}
|
||||
|
||||
private void UpdateTableInner(ref ViewRange viewRange)
|
||||
{
|
||||
if (_events.Count == 0)
|
||||
return;
|
||||
var data = _events.Get(_mainChart.SelectedSampleIndex);
|
||||
if (data == null || data.Length == 0)
|
||||
return;
|
||||
|
||||
float totalTimeMs = _mainChart.SelectedSample;
|
||||
|
||||
// Add rows
|
||||
var rowColor2 = Style.Current.Background * 1.4f;
|
||||
for (int j = 0; j < data.Length; j++)
|
||||
{
|
||||
var events = data[j].Events;
|
||||
if (events == null)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < events.Length; i++)
|
||||
{
|
||||
var e = events[i];
|
||||
var time = Math.Max(e.End - e.Start, MinEventTimeMs);
|
||||
|
||||
// Reject events outside the view range
|
||||
if (viewRange.SkipEvent(ref e))
|
||||
continue;
|
||||
|
||||
// Count sub-events time
|
||||
double subEventsTimeTotal = 0;
|
||||
int subEventsMemoryTotal = e.ManagedMemoryAllocation + e.NativeMemoryAllocation;
|
||||
for (int k = i + 1; k < events.Length; k++)
|
||||
{
|
||||
var sub = events[k];
|
||||
if (sub.Depth == e.Depth + 1)
|
||||
{
|
||||
subEventsTimeTotal += Math.Max(sub.End - sub.Start, MinEventTimeMs);
|
||||
}
|
||||
else if (sub.Depth <= e.Depth)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
subEventsMemoryTotal += sub.ManagedMemoryAllocation + e.NativeMemoryAllocation;
|
||||
}
|
||||
|
||||
string name = new string(e.Name).Replace("::", ".");
|
||||
|
||||
var row = new Row
|
||||
{
|
||||
Values = new object[]
|
||||
{
|
||||
// Event
|
||||
name,
|
||||
|
||||
// Total (%)
|
||||
(int)(time / totalTimeMs * 1000.0f) / 10.0f,
|
||||
|
||||
// Self (%)
|
||||
(int)((time - subEventsTimeTotal) / time * 1000.0f) / 10.0f,
|
||||
|
||||
// Time ms
|
||||
(float)((time * 10000.0f) / 10000.0f),
|
||||
|
||||
// Self ms
|
||||
(float)(((time - subEventsTimeTotal) * 10000.0f) / 10000.0f),
|
||||
|
||||
// Memory Alloc
|
||||
subEventsMemoryTotal,
|
||||
},
|
||||
Depth = e.Depth,
|
||||
Width = _table.Width,
|
||||
Parent = _table,
|
||||
};
|
||||
|
||||
if (i % 2 == 0)
|
||||
row.BackgroundColor = rowColor2;
|
||||
row.Visible = e.Depth < 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
315
Source/Editor/Windows/Profiler/GPU.cs
Normal file
315
Source/Editor/Windows/Profiler/GPU.cs
Normal file
@@ -0,0 +1,315 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// The GPU performance profiling mode.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Windows.Profiler.ProfilerMode" />
|
||||
internal sealed unsafe class GPU : ProfilerMode
|
||||
{
|
||||
private readonly SingleChart _drawTimeCPU;
|
||||
private readonly SingleChart _drawTimeGPU;
|
||||
private readonly Timeline _timeline;
|
||||
private readonly Table _table;
|
||||
private readonly SamplesBuffer<ProfilerGPU.Event[]> _events = new SamplesBuffer<ProfilerGPU.Event[]>();
|
||||
|
||||
public GPU()
|
||||
: base("GPU")
|
||||
{
|
||||
// Layout
|
||||
var panel = new Panel(ScrollBars.Vertical)
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = Margin.Zero,
|
||||
Parent = this,
|
||||
};
|
||||
var layout = new VerticalPanel
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||
Offsets = Margin.Zero,
|
||||
IsScrollable = true,
|
||||
Parent = panel,
|
||||
};
|
||||
|
||||
// Chart
|
||||
_drawTimeCPU = new SingleChart
|
||||
{
|
||||
Title = "Draw (CPU)",
|
||||
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
|
||||
Parent = layout,
|
||||
};
|
||||
_drawTimeCPU.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
_drawTimeGPU = new SingleChart
|
||||
{
|
||||
Title = "Draw (GPU)",
|
||||
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
|
||||
Parent = layout,
|
||||
};
|
||||
_drawTimeGPU.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
|
||||
// Timeline
|
||||
_timeline = new Timeline
|
||||
{
|
||||
Height = 340,
|
||||
Parent = layout,
|
||||
};
|
||||
|
||||
// Table
|
||||
var headerColor = Style.Current.LightBackground;
|
||||
_table = new Table
|
||||
{
|
||||
Columns = new[]
|
||||
{
|
||||
new ColumnDefinition
|
||||
{
|
||||
UseExpandCollapseMode = true,
|
||||
CellAlignment = TextAlignment.Near,
|
||||
Title = "Event",
|
||||
TitleBackgroundColor = headerColor,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Total",
|
||||
TitleBackgroundColor = headerColor,
|
||||
FormatValue = (x) => ((float)x).ToString("0.0") + '%',
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "GPU ms",
|
||||
TitleBackgroundColor = headerColor,
|
||||
FormatValue = (x) => ((float)x).ToString("0.000"),
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Draw Calls",
|
||||
TitleBackgroundColor = headerColor,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Triangles",
|
||||
TitleBackgroundColor = headerColor,
|
||||
},
|
||||
new ColumnDefinition
|
||||
{
|
||||
Title = "Vertices",
|
||||
TitleBackgroundColor = headerColor,
|
||||
},
|
||||
},
|
||||
Parent = layout,
|
||||
};
|
||||
_table.Splits = new[]
|
||||
{
|
||||
0.5f,
|
||||
0.1f,
|
||||
0.1f,
|
||||
0.1f,
|
||||
0.1f,
|
||||
0.1f,
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Clear()
|
||||
{
|
||||
_drawTimeCPU.Clear();
|
||||
_drawTimeGPU.Clear();
|
||||
_events.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(ref SharedUpdateData sharedData)
|
||||
{
|
||||
// Gather GPU events
|
||||
var data = sharedData.GetEventsGPU();
|
||||
_events.Add(data);
|
||||
|
||||
// Peek draw time
|
||||
_drawTimeCPU.AddSample(sharedData.Stats.DrawCPUTimeMs);
|
||||
_drawTimeGPU.AddSample(sharedData.Stats.DrawGPUTimeMs);
|
||||
|
||||
// Update timeline if using the last frame
|
||||
if (_drawTimeCPU.SelectedSampleIndex == -1)
|
||||
{
|
||||
UpdateTimeline();
|
||||
UpdateTable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents)
|
||||
{
|
||||
_drawTimeCPU.SelectedSampleIndex = selectedFrame;
|
||||
_drawTimeGPU.SelectedSampleIndex = selectedFrame;
|
||||
UpdateTimeline();
|
||||
UpdateTable();
|
||||
}
|
||||
|
||||
private float AddEvent(float x, int maxDepth, int index, ProfilerGPU.Event[] events, ContainerControl parent)
|
||||
{
|
||||
ref ProfilerGPU.Event e = ref events[index];
|
||||
|
||||
double scale = 100.0;
|
||||
float width = (float)(e.Time * scale);
|
||||
string name = new string(e.Name);
|
||||
|
||||
var control = new Timeline.Event(x, e.Depth, width)
|
||||
{
|
||||
Name = name,
|
||||
TooltipText = string.Format("{0}, {1} ms", name, ((int)(e.Time * 10000.0) / 10000.0f)),
|
||||
Parent = parent,
|
||||
};
|
||||
|
||||
// Spawn sub events
|
||||
int childrenDepth = e.Depth + 1;
|
||||
if (childrenDepth <= maxDepth)
|
||||
{
|
||||
// Count sub events total duration
|
||||
double subEventsDuration = 0;
|
||||
int tmpIndex = index;
|
||||
while (++tmpIndex < events.Length)
|
||||
{
|
||||
int subDepth = events[tmpIndex].Depth;
|
||||
|
||||
if (subDepth <= e.Depth)
|
||||
break;
|
||||
if (subDepth == childrenDepth)
|
||||
subEventsDuration += events[tmpIndex].Time;
|
||||
}
|
||||
|
||||
// Skip if has no sub events
|
||||
if (subEventsDuration > 0)
|
||||
{
|
||||
// Apply some offset to sub-events (center them within this event)
|
||||
x += (float)((e.Time - subEventsDuration) * scale) * 0.5f;
|
||||
|
||||
while (++index < events.Length)
|
||||
{
|
||||
int subDepth = events[index].Depth;
|
||||
|
||||
if (subDepth <= e.Depth)
|
||||
break;
|
||||
if (subDepth == childrenDepth)
|
||||
{
|
||||
x += AddEvent(x, maxDepth, index, events, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
private void UpdateTimeline()
|
||||
{
|
||||
var container = _timeline.EventsContainer;
|
||||
|
||||
// Clear previous events
|
||||
container.DisposeChildren();
|
||||
|
||||
container.LockChildrenRecursive();
|
||||
|
||||
_timeline.Height = UpdateTimelineInner();
|
||||
|
||||
container.UnlockChildrenRecursive();
|
||||
container.PerformLayout();
|
||||
}
|
||||
|
||||
private float UpdateTimelineInner()
|
||||
{
|
||||
if (_events.Count == 0)
|
||||
return 0;
|
||||
var data = _events.Get(_drawTimeCPU.SelectedSampleIndex);
|
||||
if (data == null || data.Length == 0)
|
||||
return 0;
|
||||
|
||||
var container = _timeline.EventsContainer;
|
||||
var events = data;
|
||||
|
||||
// Check maximum depth
|
||||
int maxDepth = 0;
|
||||
for (int j = 0; j < events.Length; j++)
|
||||
{
|
||||
maxDepth = Mathf.Max(maxDepth, events[j].Depth);
|
||||
}
|
||||
|
||||
// Add events
|
||||
float x = 0;
|
||||
for (int j = 0; j < events.Length; j++)
|
||||
{
|
||||
if (events[j].Depth == 0)
|
||||
{
|
||||
x += AddEvent(x, maxDepth, j, events, container);
|
||||
}
|
||||
}
|
||||
|
||||
return Timeline.Event.DefaultHeight * (maxDepth + 2);
|
||||
}
|
||||
|
||||
private void UpdateTable()
|
||||
{
|
||||
_table.DisposeChildren();
|
||||
|
||||
_table.LockChildrenRecursive();
|
||||
|
||||
UpdateTableInner();
|
||||
|
||||
_table.UnlockChildrenRecursive();
|
||||
_table.PerformLayout();
|
||||
}
|
||||
|
||||
private void UpdateTableInner()
|
||||
{
|
||||
if (_events.Count == 0)
|
||||
return;
|
||||
var data = _events.Get(_drawTimeCPU.SelectedSampleIndex);
|
||||
if (data == null || data.Length == 0)
|
||||
return;
|
||||
|
||||
float totalTimeMs = _drawTimeCPU.SelectedSample;
|
||||
|
||||
// Add rows
|
||||
var rowColor2 = Style.Current.Background * 1.4f;
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
var e = data[i];
|
||||
string name = new string(e.Name);
|
||||
|
||||
var row = new Row
|
||||
{
|
||||
Values = new object[]
|
||||
{
|
||||
// Event
|
||||
name,
|
||||
|
||||
// Total (%)
|
||||
(int)(e.Time / totalTimeMs * 1000.0f) / 10.0f,
|
||||
|
||||
// GPU ms
|
||||
(e.Time * 10000.0f) / 10000.0f,
|
||||
|
||||
// Draw Calls
|
||||
e.Stats.DrawCalls,
|
||||
|
||||
// Triangles
|
||||
e.Stats.Triangles,
|
||||
|
||||
// Vertices
|
||||
e.Stats.Vertices,
|
||||
},
|
||||
Depth = e.Depth,
|
||||
Width = _table.Width,
|
||||
Parent = _table,
|
||||
};
|
||||
|
||||
if (i % 2 == 0)
|
||||
row.BackgroundColor = rowColor2;
|
||||
row.Visible = e.Depth < 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Source/Editor/Windows/Profiler/Memory.cs
Normal file
91
Source/Editor/Windows/Profiler/Memory.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// The memory profiling mode focused on system memory allocations breakdown.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Windows.Profiler.ProfilerMode" />
|
||||
internal sealed class Memory : ProfilerMode
|
||||
{
|
||||
private readonly SingleChart _nativeAllocationsChart;
|
||||
private readonly SingleChart _managedAllocationsChart;
|
||||
|
||||
public Memory()
|
||||
: base("Memory")
|
||||
{
|
||||
// Layout
|
||||
var panel = new Panel(ScrollBars.Vertical)
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = Margin.Zero,
|
||||
Parent = this,
|
||||
};
|
||||
var layout = new VerticalPanel
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||
Offsets = Margin.Zero,
|
||||
IsScrollable = true,
|
||||
Parent = panel,
|
||||
};
|
||||
|
||||
// Chart
|
||||
_nativeAllocationsChart = new SingleChart
|
||||
{
|
||||
Title = "Native Memory Allocation",
|
||||
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
|
||||
Parent = layout,
|
||||
};
|
||||
_nativeAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
_managedAllocationsChart = new SingleChart
|
||||
{
|
||||
Title = "Managed Memory Allocation",
|
||||
FormatSample = v => Utilities.Utils.FormatBytesCount((int)v),
|
||||
Parent = layout,
|
||||
};
|
||||
_managedAllocationsChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Clear()
|
||||
{
|
||||
_nativeAllocationsChart.Clear();
|
||||
_managedAllocationsChart.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(ref SharedUpdateData sharedData)
|
||||
{
|
||||
// Count memory allocated during last frame
|
||||
int nativeMemoryAllocation = 0;
|
||||
int managedMemoryAllocation = 0;
|
||||
var events = sharedData.GetEventsCPU();
|
||||
var length = events?.Length ?? 0;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
var ee = events[i].Events;
|
||||
if (ee == null)
|
||||
continue;
|
||||
for (int j = 0; j < ee.Length; j++)
|
||||
{
|
||||
ref var e = ref ee[j];
|
||||
nativeMemoryAllocation += e.NativeMemoryAllocation;
|
||||
managedMemoryAllocation += e.ManagedMemoryAllocation;
|
||||
}
|
||||
}
|
||||
|
||||
_nativeAllocationsChart.AddSample(nativeMemoryAllocation);
|
||||
_managedAllocationsChart.AddSample(managedMemoryAllocation);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents)
|
||||
{
|
||||
_nativeAllocationsChart.SelectedSampleIndex = selectedFrame;
|
||||
_managedAllocationsChart.SelectedSampleIndex = selectedFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
116
Source/Editor/Windows/Profiler/Overall.cs
Normal file
116
Source/Editor/Windows/Profiler/Overall.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// The general profiling mode with major game performance charts and stats.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Windows.Profiler.ProfilerMode" />
|
||||
internal sealed class Overall : ProfilerMode
|
||||
{
|
||||
private readonly SingleChart _fpsChart;
|
||||
private readonly SingleChart _updateTimeChart;
|
||||
private readonly SingleChart _drawTimeCPUChart;
|
||||
private readonly SingleChart _drawTimeGPUChart;
|
||||
private readonly SingleChart _cpuMemChart;
|
||||
private readonly SingleChart _gpuMemChart;
|
||||
|
||||
public Overall()
|
||||
: base("Overall")
|
||||
{
|
||||
// Layout
|
||||
var panel = new Panel(ScrollBars.Vertical)
|
||||
{
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = Margin.Zero,
|
||||
Parent = this,
|
||||
};
|
||||
var layout = new VerticalPanel
|
||||
{
|
||||
AnchorPreset = AnchorPresets.HorizontalStretchTop,
|
||||
Offsets = Margin.Zero,
|
||||
IsScrollable = true,
|
||||
Parent = panel,
|
||||
};
|
||||
|
||||
// Charts
|
||||
_fpsChart = new SingleChart
|
||||
{
|
||||
Title = "FPS",
|
||||
Parent = layout,
|
||||
};
|
||||
_fpsChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
_updateTimeChart = new SingleChart
|
||||
{
|
||||
Title = "Update Time",
|
||||
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
|
||||
Parent = layout,
|
||||
};
|
||||
_updateTimeChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
_drawTimeCPUChart = new SingleChart
|
||||
{
|
||||
Title = "Draw Time (CPU)",
|
||||
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
|
||||
Parent = layout,
|
||||
};
|
||||
_drawTimeCPUChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
_drawTimeGPUChart = new SingleChart
|
||||
{
|
||||
Title = "Draw Time (GPU)",
|
||||
FormatSample = v => (Mathf.RoundToInt(v * 10.0f) / 10.0f) + " ms",
|
||||
Parent = layout,
|
||||
};
|
||||
_drawTimeGPUChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
_cpuMemChart = new SingleChart
|
||||
{
|
||||
Title = "CPU Memory",
|
||||
FormatSample = v => ((int)v) + " MB",
|
||||
Parent = layout,
|
||||
};
|
||||
_cpuMemChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
_gpuMemChart = new SingleChart
|
||||
{
|
||||
Title = "GPU Memory",
|
||||
FormatSample = v => ((int)v) + " MB",
|
||||
Parent = layout,
|
||||
};
|
||||
_gpuMemChart.SelectedSampleChanged += OnSelectedSampleChanged;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Clear()
|
||||
{
|
||||
_fpsChart.Clear();
|
||||
_updateTimeChart.Clear();
|
||||
_drawTimeCPUChart.Clear();
|
||||
_drawTimeGPUChart.Clear();
|
||||
_cpuMemChart.Clear();
|
||||
_gpuMemChart.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(ref SharedUpdateData sharedData)
|
||||
{
|
||||
_fpsChart.AddSample(sharedData.Stats.FPS);
|
||||
_updateTimeChart.AddSample(sharedData.Stats.UpdateTimeMs);
|
||||
_drawTimeCPUChart.AddSample(sharedData.Stats.DrawCPUTimeMs);
|
||||
_drawTimeGPUChart.AddSample(sharedData.Stats.DrawGPUTimeMs);
|
||||
_cpuMemChart.AddSample(sharedData.Stats.ProcessMemory.UsedPhysicalMemory / 1024 / 1024);
|
||||
_gpuMemChart.AddSample(sharedData.Stats.MemoryGPU.Used / 1024 / 1024);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents)
|
||||
{
|
||||
_fpsChart.SelectedSampleIndex = selectedFrame;
|
||||
_updateTimeChart.SelectedSampleIndex = selectedFrame;
|
||||
_drawTimeCPUChart.SelectedSampleIndex = selectedFrame;
|
||||
_drawTimeGPUChart.SelectedSampleIndex = selectedFrame;
|
||||
_cpuMemChart.SelectedSampleIndex = selectedFrame;
|
||||
_gpuMemChart.SelectedSampleIndex = selectedFrame;
|
||||
}
|
||||
}
|
||||
}
|
||||
126
Source/Editor/Windows/Profiler/ProfilerMode.cs
Normal file
126
Source/Editor/Windows/Profiler/ProfilerMode.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.GUI.Tabs;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all profiler modes. Implementation collects profiling events and presents it using dedicated UI.
|
||||
/// </summary>
|
||||
public class ProfilerMode : Tab
|
||||
{
|
||||
/// <summary>
|
||||
/// The shared data container for the profiler modes. Used to reduce calls to profiler tool backend for the same data across different profiler fronted modes.
|
||||
/// </summary>
|
||||
public struct SharedUpdateData
|
||||
{
|
||||
private ProfilingTools.ThreadStats[] _cpuEvents;
|
||||
private ProfilerGPU.Event[] _gpuEvents;
|
||||
|
||||
/// <summary>
|
||||
/// The main stats. Gathered by auto by profiler before profiler mode update.
|
||||
/// </summary>
|
||||
public ProfilingTools.MainStats Stats;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collected CPU events by the profiler from local or remote session.
|
||||
/// </summary>
|
||||
/// <returns>Buffer with events per thread.</returns>
|
||||
public ProfilingTools.ThreadStats[] GetEventsCPU()
|
||||
{
|
||||
return _cpuEvents ?? (_cpuEvents = ProfilingTools.EventsCPU);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collected GPU events by the profiler from local or remote session.
|
||||
/// </summary>
|
||||
/// <returns>Buffer with rendering events.</returns>
|
||||
public ProfilerGPU.Event[] GetEventsGPU()
|
||||
{
|
||||
return _gpuEvents ?? (_gpuEvents = ProfilingTools.EventsGPU);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins the data usage. Prepares the container.
|
||||
/// </summary>
|
||||
public void Begin()
|
||||
{
|
||||
Stats = ProfilingTools.Stats;
|
||||
_cpuEvents = null;
|
||||
_gpuEvents = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the data usage. Cleanups the container.
|
||||
/// </summary>
|
||||
public void End()
|
||||
{
|
||||
_cpuEvents = null;
|
||||
_gpuEvents = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of samples to collect.
|
||||
/// </summary>
|
||||
public const int MaxSamples = 60 * 5;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum event time in ms.
|
||||
/// </summary>
|
||||
public const double MinEventTimeMs = 0.000000001;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when selected sample gets changed. Profiling window should propagate this change to all charts and view modes.
|
||||
/// </summary>
|
||||
public event Action<int> SelectedSampleChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProfilerMode(string text)
|
||||
: base(text)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this instance.
|
||||
/// </summary>
|
||||
public virtual void Init()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears this instance.
|
||||
/// </summary>
|
||||
public virtual void Clear()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates this instance. Called every frame if live recording is enabled.
|
||||
/// </summary>
|
||||
/// <param name="sharedData">The shared data.</param>
|
||||
public virtual void Update(ref SharedUpdateData sharedData)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the mode view. Called after init and on selected frame changed.
|
||||
/// </summary>
|
||||
/// <param name="selectedFrame">The selected frame index.</param>
|
||||
/// <param name="showOnlyLastUpdateEvents">True if show only events that happened during the last engine update (excluding events from fixed update or draw event), otherwise show all collected events.</param>
|
||||
public virtual void UpdateView(int selectedFrame, bool showOnlyLastUpdateEvents)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when selected sample gets changed.
|
||||
/// </summary>
|
||||
/// <param name="frameIndex">Index of the view frame.</param>
|
||||
protected virtual void OnSelectedSampleChanged(int frameIndex)
|
||||
{
|
||||
SelectedSampleChanged?.Invoke(frameIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
240
Source/Editor/Windows/Profiler/ProfilerWindow.cs
Normal file
240
Source/Editor/Windows/Profiler/ProfilerWindow.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEditor.GUI;
|
||||
using FlaxEditor.GUI.Tabs;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor tool window for profiling games.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEditor.Windows.EditorWindow" />
|
||||
public sealed class ProfilerWindow : EditorWindow
|
||||
{
|
||||
private readonly ToolStripButton _liveRecordingButton;
|
||||
private readonly ToolStripButton _clearButton;
|
||||
private readonly ToolStripButton _prevFrameButton;
|
||||
private readonly ToolStripButton _nextFrameButton;
|
||||
private readonly ToolStripButton _lastFrameButton;
|
||||
private readonly ToolStripButton _showOnlyLastUpdateEventsButton;
|
||||
private readonly Tabs _tabs;
|
||||
private int _frameIndex = -1;
|
||||
private int _framesCount;
|
||||
private bool _showOnlyLastUpdateEvents = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether live events recording is enabled.
|
||||
/// </summary>
|
||||
public bool LiveRecording
|
||||
{
|
||||
get => _liveRecordingButton.Checked;
|
||||
set
|
||||
{
|
||||
if (value != LiveRecording)
|
||||
{
|
||||
_liveRecordingButton.Checked = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the selected frame to view (note: some view modes may not use it).
|
||||
/// </summary>
|
||||
public int ViewFrameIndex
|
||||
{
|
||||
get => _frameIndex;
|
||||
set
|
||||
{
|
||||
value = Mathf.Clamp(value, -1, _framesCount - 1);
|
||||
if (_frameIndex != value)
|
||||
{
|
||||
_frameIndex = value;
|
||||
|
||||
UpdateButtons();
|
||||
if (_tabs.SelectedTab is ProfilerMode mode)
|
||||
mode.UpdateView(_frameIndex, _showOnlyLastUpdateEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether show only last update events and hide events from the other callbacks (e.g. draw or fixed update).
|
||||
/// </summary>
|
||||
public bool ShowOnlyLastUpdateEvents
|
||||
{
|
||||
get => _showOnlyLastUpdateEvents;
|
||||
set
|
||||
{
|
||||
if (_showOnlyLastUpdateEvents != value)
|
||||
{
|
||||
_showOnlyLastUpdateEvents = value;
|
||||
|
||||
UpdateButtons();
|
||||
if (_tabs.SelectedTab is ProfilerMode mode)
|
||||
mode.UpdateView(_frameIndex, _showOnlyLastUpdateEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProfilerWindow"/> class.
|
||||
/// </summary>
|
||||
/// <param name="editor">The editor.</param>
|
||||
public ProfilerWindow(Editor editor)
|
||||
: base(editor, true, ScrollBars.None)
|
||||
{
|
||||
Title = "Profiler";
|
||||
|
||||
var toolstrip = new ToolStrip
|
||||
{
|
||||
Offsets = new Margin(0, 0, 0, 32),
|
||||
Parent = this,
|
||||
};
|
||||
_liveRecordingButton = toolstrip.AddButton(editor.Icons.Play32);
|
||||
_liveRecordingButton.LinkTooltip("Live profiling events recording");
|
||||
_liveRecordingButton.AutoCheck = true;
|
||||
_clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear);
|
||||
_clearButton.LinkTooltip("Clear data");
|
||||
toolstrip.AddSeparator();
|
||||
_prevFrameButton = toolstrip.AddButton(editor.Icons.ArrowLeft32, () => ViewFrameIndex--);
|
||||
_prevFrameButton.LinkTooltip("Previous frame");
|
||||
_nextFrameButton = toolstrip.AddButton(editor.Icons.ArrowRight32, () => ViewFrameIndex++);
|
||||
_nextFrameButton.LinkTooltip("Next frame");
|
||||
_lastFrameButton = toolstrip.AddButton(editor.Icons.Step32, () => ViewFrameIndex = -1);
|
||||
_lastFrameButton.LinkTooltip("Current frame");
|
||||
toolstrip.AddSeparator();
|
||||
_showOnlyLastUpdateEventsButton = toolstrip.AddButton(editor.Icons.PageScale32, () => ShowOnlyLastUpdateEvents = !ShowOnlyLastUpdateEvents);
|
||||
_showOnlyLastUpdateEventsButton.LinkTooltip("Show only last update events and hide events from the other callbacks (e.g. draw or fixed update)");
|
||||
|
||||
_tabs = new Tabs
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
AnchorPreset = AnchorPresets.StretchAll,
|
||||
Offsets = new Margin(0, 0, toolstrip.Bottom, 0),
|
||||
TabsSize = new Vector2(120, 32),
|
||||
Parent = this
|
||||
};
|
||||
_tabs.SelectedTabChanged += OnSelectedTabChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the mode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To remove the mode simply call <see cref="Control.Dispose"/> on mode.
|
||||
/// </remarks>
|
||||
/// <param name="mode">The mode.</param>
|
||||
public void AddMode(ProfilerMode mode)
|
||||
{
|
||||
if (mode == null)
|
||||
throw new ArgumentNullException();
|
||||
mode.Init();
|
||||
_tabs.AddTab(mode);
|
||||
mode.SelectedSampleChanged += ModeOnSelectedSampleChanged;
|
||||
}
|
||||
|
||||
private void ModeOnSelectedSampleChanged(int frameIndex)
|
||||
{
|
||||
ViewFrameIndex = frameIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears data.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_frameIndex = -1;
|
||||
_framesCount = 0;
|
||||
for (int i = 0; i < _tabs.ChildrenCount; i++)
|
||||
{
|
||||
if (_tabs.Children[i] is ProfilerMode mode)
|
||||
{
|
||||
mode.Clear();
|
||||
mode.UpdateView(ViewFrameIndex, _showOnlyLastUpdateEvents);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateButtons();
|
||||
}
|
||||
|
||||
private void OnSelectedTabChanged(Tabs tabs)
|
||||
{
|
||||
if (tabs.SelectedTab is ProfilerMode mode)
|
||||
mode.UpdateView(ViewFrameIndex, _showOnlyLastUpdateEvents);
|
||||
}
|
||||
|
||||
private void UpdateButtons()
|
||||
{
|
||||
_clearButton.Enabled = _framesCount > 0;
|
||||
_prevFrameButton.Enabled = _frameIndex > 0;
|
||||
_nextFrameButton.Enabled = (_framesCount - _frameIndex - 1) > 0;
|
||||
_lastFrameButton.Enabled = _framesCount > 0;
|
||||
_showOnlyLastUpdateEventsButton.Checked = _showOnlyLastUpdateEvents;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnInit()
|
||||
{
|
||||
// Create default modes
|
||||
AddMode(new Overall());
|
||||
AddMode(new CPU());
|
||||
AddMode(new GPU());
|
||||
AddMode(new Memory());
|
||||
|
||||
// Init view
|
||||
_frameIndex = -1;
|
||||
for (int i = 0; i < _tabs.ChildrenCount; i++)
|
||||
{
|
||||
if (_tabs.Children[i] is ProfilerMode mode)
|
||||
mode.UpdateView(ViewFrameIndex, _showOnlyLastUpdateEvents);
|
||||
}
|
||||
|
||||
UpdateButtons();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnUpdate()
|
||||
{
|
||||
if (LiveRecording)
|
||||
{
|
||||
FlaxEngine.Profiler.BeginEvent("ProfilerWindow.OnUpdate");
|
||||
|
||||
ProfilerMode.SharedUpdateData sharedData = new ProfilerMode.SharedUpdateData();
|
||||
sharedData.Begin();
|
||||
for (int i = 0; i < _tabs.ChildrenCount; i++)
|
||||
{
|
||||
if (_tabs.Children[i] is ProfilerMode mode)
|
||||
mode.Update(ref sharedData);
|
||||
}
|
||||
sharedData.End();
|
||||
|
||||
_framesCount = Mathf.Min(_framesCount + 1, ProfilerMode.MaxSamples);
|
||||
UpdateButtons();
|
||||
|
||||
FlaxEngine.Profiler.EndEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnKeyDown(KeyboardKeys key)
|
||||
{
|
||||
if (base.OnKeyDown(key))
|
||||
return true;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case KeyboardKeys.ArrowLeft:
|
||||
ViewFrameIndex--;
|
||||
return true;
|
||||
case KeyboardKeys.ArrowRight:
|
||||
ViewFrameIndex++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
Source/Editor/Windows/Profiler/SamplesBuffer.cs
Normal file
103
Source/Editor/Windows/Profiler/SamplesBuffer.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Profiler samples storage buffer. Support recording new frame samples.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Single sample data type.</typeparam>
|
||||
public class SamplesBuffer<T>
|
||||
{
|
||||
private T[] _data;
|
||||
private int _count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of samples in the buffer.
|
||||
/// </summary>
|
||||
public int Count => _count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last sample value. Check buffer <see cref="Count"/> before calling this property.
|
||||
/// </summary>
|
||||
public T Last => _data[_count - 1];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sample value at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>The sample value.</returns>
|
||||
public T this[int index]
|
||||
{
|
||||
get => _data[index];
|
||||
set => _data[index] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SamplesBuffer{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The maximum buffer capacity.</param>
|
||||
public SamplesBuffer(int capacity = ProfilerMode.MaxSamples)
|
||||
{
|
||||
_data = new T[capacity];
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sample at the specified index or the last sample if index is equal to -1.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>The sample value</returns>
|
||||
public T Get(int index)
|
||||
{
|
||||
return index == -1 ? _data[_count - 1] : _data[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears this buffer.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified sample to the buffer.
|
||||
/// </summary>
|
||||
/// <param name="sample">The sample.</param>
|
||||
public void Add(T sample)
|
||||
{
|
||||
// Remove first sample if no space
|
||||
if (_count == _data.Length)
|
||||
{
|
||||
for (int i = 1; i < _count; i++)
|
||||
{
|
||||
_data[i - 1] = _data[i];
|
||||
}
|
||||
|
||||
_count--;
|
||||
}
|
||||
|
||||
_data[_count++] = sample;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified sample to the buffer.
|
||||
/// </summary>
|
||||
/// <param name="sample">The sample.</param>
|
||||
public void Add(ref T sample)
|
||||
{
|
||||
// Remove first sample if no space
|
||||
if (_count == _data.Length)
|
||||
{
|
||||
for (int i = 1; i < _count; i++)
|
||||
{
|
||||
_data[i - 1] = _data[i];
|
||||
}
|
||||
|
||||
_count--;
|
||||
}
|
||||
|
||||
_data[_count++] = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
195
Source/Editor/Windows/Profiler/SingleChart.cs
Normal file
195
Source/Editor/Windows/Profiler/SingleChart.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws simple chart.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Control" />
|
||||
internal class SingleChart : Control
|
||||
{
|
||||
private const float TitleHeight = 20;
|
||||
private const float PointsOffset = 4;
|
||||
private readonly SamplesBuffer<float> _samples;
|
||||
private string _sample;
|
||||
private int _selectedSampleIndex = -1;
|
||||
private bool _isSelecting;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the chart title.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the selected sample. Value -1 is used to indicate no selection (using the latest sample).
|
||||
/// </summary>
|
||||
public int SelectedSampleIndex
|
||||
{
|
||||
get => _selectedSampleIndex;
|
||||
set
|
||||
{
|
||||
value = Mathf.Clamp(value, -1, _samples.Count - 1);
|
||||
if (_selectedSampleIndex != value)
|
||||
{
|
||||
_selectedSampleIndex = value;
|
||||
_sample = _samples.Count == 0 ? string.Empty : FormatSample(_samples.Get(_selectedSampleIndex));
|
||||
|
||||
SelectedSampleChanged?.Invoke(_selectedSampleIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the selected sample value.
|
||||
/// </summary>
|
||||
public float SelectedSample => _samples.Get(_selectedSampleIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when selected sample gets changed.
|
||||
/// </summary>
|
||||
public event Action<int> SelectedSampleChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The handler function to format sample value for label text.
|
||||
/// </summary>
|
||||
public Func<float, string> FormatSample = (v) => v.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SingleChart"/> class.
|
||||
/// </summary>
|
||||
/// <param name="maxSamples">The maximum samples to collect.</param>
|
||||
public SingleChart(int maxSamples = ProfilerMode.MaxSamples)
|
||||
: base(0, 0, 100, 60 + TitleHeight)
|
||||
{
|
||||
_samples = new SamplesBuffer<float>(maxSamples);
|
||||
_sample = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all the samples.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_samples.Clear();
|
||||
_sample = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the sample value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
public void AddSample(float value)
|
||||
{
|
||||
_samples.Add(value);
|
||||
_sample = FormatSample(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
float chartHeight = Height - TitleHeight;
|
||||
|
||||
// Draw chart
|
||||
if (_samples.Count > 0)
|
||||
{
|
||||
var chartRect = new Rectangle(0, 0, Width, chartHeight);
|
||||
Render2D.PushClip(ref chartRect);
|
||||
|
||||
if (_selectedSampleIndex != -1)
|
||||
{
|
||||
float selectedX = Width - (_samples.Count - _selectedSampleIndex - 1) * PointsOffset;
|
||||
Render2D.DrawLine(new Vector2(selectedX, 0), new Vector2(selectedX, chartHeight), Color.White, 1.5f);
|
||||
}
|
||||
|
||||
int samplesInViewCount = Math.Min((int)(Width / PointsOffset), _samples.Count) - 1;
|
||||
float maxValue = _samples[_samples.Count - 1];
|
||||
for (int i = 2; i < samplesInViewCount; i++)
|
||||
{
|
||||
maxValue = Mathf.Max(maxValue, _samples[_samples.Count - i]);
|
||||
}
|
||||
|
||||
Color chartColor = style.BackgroundSelected;
|
||||
Vector2 chartRoot = chartRect.BottomRight;
|
||||
float samplesRange = maxValue * 1.1f;
|
||||
float samplesCoeff = -chartHeight / samplesRange;
|
||||
Vector2 posPrev = chartRoot + new Vector2(0, _samples.Last * samplesCoeff);
|
||||
float posX = 0;
|
||||
|
||||
for (int i = _samples.Count - 1; i >= 0; i--)
|
||||
{
|
||||
float sample = _samples[i];
|
||||
Vector2 pos = chartRoot + new Vector2(posX, sample * samplesCoeff);
|
||||
Render2D.DrawLine(posPrev, pos, chartColor);
|
||||
posPrev = pos;
|
||||
posX -= PointsOffset;
|
||||
}
|
||||
|
||||
Render2D.PopClip();
|
||||
}
|
||||
|
||||
// Draw title
|
||||
var headerRect = new Rectangle(0, chartHeight, Width, TitleHeight);
|
||||
var headerTextRect = new Rectangle(2, chartHeight, Width - 4, TitleHeight);
|
||||
Render2D.FillRectangle(headerRect, style.BackgroundNormal);
|
||||
Render2D.DrawText(style.FontMedium, Title, headerTextRect, Color.White * 0.8f, TextAlignment.Near, TextAlignment.Center);
|
||||
Render2D.DrawText(style.FontMedium, _sample, headerTextRect, Color.White, TextAlignment.Far, TextAlignment.Center);
|
||||
}
|
||||
|
||||
private void OnClick(ref Vector2 location)
|
||||
{
|
||||
float samplesWidth = _samples.Count * PointsOffset;
|
||||
SelectedSampleIndex = (int)((samplesWidth - Width + location.X) / PointsOffset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseDown(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && location.Y < (Height - TitleHeight))
|
||||
{
|
||||
_isSelecting = true;
|
||||
OnClick(ref location);
|
||||
StartMouseCapture();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnMouseMove(Vector2 location)
|
||||
{
|
||||
if (_isSelecting)
|
||||
{
|
||||
OnClick(ref location);
|
||||
}
|
||||
|
||||
base.OnMouseMove(location);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool OnMouseUp(Vector2 location, MouseButton button)
|
||||
{
|
||||
if (button == MouseButton.Left && _isSelecting)
|
||||
{
|
||||
_isSelecting = false;
|
||||
EndMouseCapture();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseUp(location, button);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnEndMouseCapture()
|
||||
{
|
||||
_isSelecting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
139
Source/Editor/Windows/Profiler/Timeline.cs
Normal file
139
Source/Editor/Windows/Profiler/Timeline.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
using FlaxEngine;
|
||||
using FlaxEngine.GUI;
|
||||
|
||||
namespace FlaxEditor.Windows.Profiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Events timeline control.
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.Panel" />
|
||||
public class Timeline : Panel
|
||||
{
|
||||
/// <summary>
|
||||
/// Single timeline event control.
|
||||
/// </summary>
|
||||
/// <seealso cref="ContainerControl" />
|
||||
public class Event : ContainerControl
|
||||
{
|
||||
private static readonly Color[] Colors =
|
||||
{
|
||||
new Color(0.8f, 0.894117653f, 0.709803939f, 1f),
|
||||
new Color(0.1254902f, 0.698039234f, 0.6666667f, 1f),
|
||||
new Color(0.4831376f, 0.6211768f, 0.0219608f, 1f),
|
||||
new Color(0.3827448f, 0.2886272f, 0.5239216f, 1f),
|
||||
new Color(0.8f, 0.4423528f, 0f, 1f),
|
||||
new Color(0.4486272f, 0.4078432f, 0.050196f, 1f),
|
||||
new Color(0.4831376f, 0.6211768f, 0.0219608f, 1f),
|
||||
new Color(0.4831376f, 0.6211768f, 0.0219608f, 1f),
|
||||
new Color(0.2070592f, 0.5333336f, 0.6556864f, 1f),
|
||||
new Color(0.8f, 0.4423528f, 0f, 1f),
|
||||
new Color(0.4486272f, 0.4078432f, 0.050196f, 1f),
|
||||
new Color(0.7749016f, 0.6368624f, 0.0250984f, 1f),
|
||||
new Color(0.5333336f, 0.16f, 0.0282352f, 1f),
|
||||
new Color(0.3827448f, 0.2886272f, 0.5239216f, 1f),
|
||||
new Color(0.478431374f, 0.482352942f, 0.117647059f, 1f),
|
||||
new Color(0.9411765f, 0.5019608f, 0.5019608f, 1f),
|
||||
new Color(0.6627451f, 0.6627451f, 0.6627451f, 1f),
|
||||
new Color(0.545098066f, 0f, 0.545098066f, 1f),
|
||||
};
|
||||
|
||||
private Color _color;
|
||||
private float _nameLength;
|
||||
|
||||
/// <summary>
|
||||
/// The default height of the event.
|
||||
/// </summary>
|
||||
public const float DefaultHeight = 25.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the event name.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Event"/> class.
|
||||
/// </summary>
|
||||
/// <param name="x">The x position.</param>
|
||||
/// <param name="depth">The timeline row index (event depth).</param>
|
||||
/// <param name="width">The width.</param>
|
||||
public Event(float x, int depth, float width)
|
||||
: base(x, depth * DefaultHeight, width, DefaultHeight - 1)
|
||||
{
|
||||
_nameLength = -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnParentChangedInternal()
|
||||
{
|
||||
base.OnParentChangedInternal();
|
||||
|
||||
int key = (HasParent ? Parent.GetChildIndex(this) : 1) * (string.IsNullOrEmpty(Name) ? 1 : Name[0]);
|
||||
_color = Colors[key % Colors.Length] * 0.8f;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
var bounds = new Rectangle(Vector2.Zero, Size);
|
||||
Color color = _color;
|
||||
if (IsMouseOver)
|
||||
color *= 1.1f;
|
||||
|
||||
Render2D.FillRectangle(bounds, color);
|
||||
Render2D.DrawRectangle(bounds, color * 0.5f);
|
||||
|
||||
if (_nameLength < 0 && style.FontMedium)
|
||||
_nameLength = style.FontMedium.MeasureText(Name).X;
|
||||
|
||||
if (_nameLength < bounds.Width + 4)
|
||||
{
|
||||
Render2D.PushClip(bounds);
|
||||
Render2D.DrawText(style.FontMedium, Name, bounds, Color.White, TextAlignment.Center, TextAlignment.Center);
|
||||
Render2D.PopClip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timeline track label
|
||||
/// </summary>
|
||||
/// <seealso cref="FlaxEngine.GUI.ContainerControl" />
|
||||
public class TrackLabel : ContainerControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw();
|
||||
|
||||
var style = Style.Current;
|
||||
var rect = new Rectangle(Vector2.Zero, Size);
|
||||
Render2D.PushClip(rect);
|
||||
Render2D.DrawText(style.FontMedium, Name, rect, Color.White, TextAlignment.Center, TextAlignment.Center, TextWrapping.WrapChars);
|
||||
Render2D.PopClip();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the events container control. Use it to remove/add events to the timeline.
|
||||
/// </summary>
|
||||
public ContainerControl EventsContainer => this;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Timeline"/> class.
|
||||
/// </summary>
|
||||
public Timeline()
|
||||
: base(ScrollBars.Both)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user