// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using FlaxEditor.GUI; using FlaxEditor.GUI.Tabs; using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.Windows.Profiler { /// /// Editor tool window for profiling games. /// /// 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; private long _lastManagedMemory = 0; private long _lastManagedMemoryProfiler = 0; /// /// Gets or sets a value indicating whether live events recording is enabled. /// public bool LiveRecording { get => _liveRecordingButton.Checked; set { if (value != LiveRecording) { _liveRecordingButton.Checked = value; } } } /// /// Gets or sets the index of the selected frame to view (note: some view modes may not use it). -1 for the last frame. /// public int ViewFrameIndex { get => _frameIndex; set { value = Mathf.Clamp(value, -1, _framesCount - 1); if (_frameIndex != value) { _frameIndex = value; UpdateButtons(); UpdateView(); } } } /// /// 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). /// public bool ShowOnlyLastUpdateEvents { get => _showOnlyLastUpdateEvents; set { if (_showOnlyLastUpdateEvents != value) { _showOnlyLastUpdateEvents = value; UpdateButtons(); UpdateView(); } } } /// /// Initializes a new instance of the class. /// /// The editor. public ProfilerWindow(Editor editor) : base(editor, true, ScrollBars.None) { Title = "Profiler"; var toolstrip = new ToolStrip { Parent = this, }; _liveRecordingButton = toolstrip.AddButton(editor.Icons.Play64); _liveRecordingButton.LinkTooltip("Live profiling events recording"); _liveRecordingButton.AutoCheck = true; _liveRecordingButton.Clicked += OnLiveRecordingChanged; _clearButton = toolstrip.AddButton(editor.Icons.Rotate32, Clear); _clearButton.LinkTooltip("Clear data"); toolstrip.AddSeparator(); _prevFrameButton = toolstrip.AddButton(editor.Icons.Left64, () => ViewFrameIndex--); _prevFrameButton.LinkTooltip("Previous frame"); _nextFrameButton = toolstrip.AddButton(editor.Icons.Right64, () => ViewFrameIndex++); _nextFrameButton.LinkTooltip("Next frame"); _lastFrameButton = toolstrip.AddButton(editor.Icons.Skip64, () => ViewFrameIndex = -1); _lastFrameButton.LinkTooltip("Current frame"); toolstrip.AddSeparator(); _showOnlyLastUpdateEventsButton = toolstrip.AddButton(editor.Icons.CenterView64, () => 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 Float2(120, 32), Parent = this }; _tabs.SelectedTabChanged += OnSelectedTabChanged; FlaxEditor.Utilities.Utils.SetupCommonInputActions(this); InputActions.Bindings.RemoveAll(x => x.Callback == this.FocusOrShow); InputActions.Add(options => options.ProfilerWindow, Hide); } private void OnLiveRecordingChanged() { _liveRecordingButton.Icon = LiveRecording ? Editor.Icons.Stop64 : Editor.Icons.Play64; ProfilingTools.Enabled = LiveRecording; } /// /// Adds the mode. /// /// /// To remove the mode simply call on mode. /// /// The mode. 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; } /// /// Clears data. /// public void Clear() { _frameIndex = -1; _framesCount = 0; _lastManagedMemory = 0; for (int i = 0; i < _tabs.ChildrenCount; i++) { if (_tabs.Children[i] is ProfilerMode mode) { mode.Clear(); FlaxEngine.Profiler.BeginEvent("ProfilerWindow.UpdateView"); mode.UpdateView(ViewFrameIndex, _showOnlyLastUpdateEvents); FlaxEngine.Profiler.EndEvent(); } } UpdateButtons(); } private void OnSelectedTabChanged(Tabs tabs) { if (tabs.SelectedTab is ProfilerMode mode) { FlaxEngine.Profiler.BeginEvent("ProfilerWindow.UpdateView"); mode.UpdateView(ViewFrameIndex, _showOnlyLastUpdateEvents); FlaxEngine.Profiler.EndEvent(); } } private void UpdateButtons() { _clearButton.Enabled = _framesCount > 0; _prevFrameButton.Enabled = _frameIndex > 0; _nextFrameButton.Enabled = (_framesCount - _frameIndex - 1) > 0; _lastFrameButton.Enabled = _framesCount > 0; _showOnlyLastUpdateEventsButton.Checked = _showOnlyLastUpdateEvents; } private void UpdateView() { if (_tabs.SelectedTab is ProfilerMode mode) { FlaxEngine.Profiler.BeginEvent("ProfilerWindow.UpdateView"); mode.UpdateView(_frameIndex, _showOnlyLastUpdateEvents); FlaxEngine.Profiler.EndEvent(); } } /// public override void OnInit() { // Create default modes AddMode(new Overall()); AddMode(new CPU()); AddMode(new GPU()); AddMode(new MemoryGPU()); AddMode(new Memory()); AddMode(new Assets()); AddMode(new Network()); AddMode(new Physics()); // Init view _frameIndex = -1; for (int i = 0; i < _tabs.ChildrenCount; i++) { if (_tabs.Children[i] is ProfilerMode mode) mode.UpdateView(_frameIndex, _showOnlyLastUpdateEvents); } UpdateButtons(); ScriptsBuilder.ScriptsReloadEnd += Clear; // Prevent crashes if any of the profiler tabs has some scripting types cached (eg. asset type info) } /// public override void OnUpdate() { if (LiveRecording) { FlaxEngine.Profiler.BeginEvent("ProfilerWindow.OnUpdate"); // Get memory allocations during last frame long managedMemory = GC.GetAllocatedBytesForCurrentThread(); if (_lastManagedMemory == 0) _lastManagedMemory = managedMemory; var managedAllocs = managedMemory - _lastManagedMemory - _lastManagedMemoryProfiler; _lastManagedMemory = managedMemory; ProfilerMode.SharedUpdateData sharedData = new ProfilerMode.SharedUpdateData(); sharedData.Begin(); sharedData.ManagedMemoryAllocation = (int)managedAllocs; for (int i = 0; i < _tabs.ChildrenCount; i++) { if (_tabs.Children[i] is ProfilerMode mode) { FlaxEngine.Profiler.BeginEvent(mode.GetType().FullName); mode.Update(ref sharedData); FlaxEngine.Profiler.EndEvent(); } } { if (_tabs.SelectedTab is ProfilerMode mode) { FlaxEngine.Profiler.BeginEvent("ProfilerWindow.UpdateView"); mode.UpdateView(_frameIndex, _showOnlyLastUpdateEvents); FlaxEngine.Profiler.EndEvent(); } } sharedData.End(); _framesCount = Mathf.Min(_framesCount + 1, ProfilerMode.MaxSamples); UpdateButtons(); // Get memory allocations within profiler window update to exclude from stats managedMemory = GC.GetAllocatedBytesForCurrentThread(); _lastManagedMemoryProfiler = managedMemory - _lastManagedMemory; FlaxEngine.Profiler.EndEvent(); } } /// 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; } } }