mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-10-01 12:30:00 +02:00
Render Profiler in GUI (#854)
* move profiler output to gui * addressed commits, rebased * removed whitespaces
This commit is contained in:
parent
db9f8f999f
commit
f2b9a9c2b0
41 changed files with 1358 additions and 1639 deletions
|
@ -13,6 +13,15 @@
|
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
|||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
|||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
32
Ryujinx.Debugger/Debugger.cs
Normal file
32
Ryujinx.Debugger/Debugger.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using Ryujinx.Debugger.UI;
|
||||
|
||||
namespace Ryujinx.Debugger
|
||||
{
|
||||
public class Debugger : IDisposable
|
||||
{
|
||||
public DebuggerWidget Widget { get; set; }
|
||||
|
||||
public Debugger()
|
||||
{
|
||||
Widget = new DebuggerWidget();
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
Widget.Enable();
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
Widget.Disable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Disable();
|
||||
|
||||
Widget.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Profiler
|
||||
namespace Ryujinx.Debugger.Profiler
|
||||
{
|
||||
public static class DumpProfile
|
||||
{
|
|
@ -1,12 +1,12 @@
|
|||
using System;
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ryujinx.Common;
|
||||
|
||||
namespace Ryujinx.Profiler
|
||||
namespace Ryujinx.Debugger.Profiler
|
||||
{
|
||||
public class InternalProfile
|
||||
{
|
|
@ -4,19 +4,17 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Profiler
|
||||
namespace Ryujinx.Debugger.Profiler
|
||||
{
|
||||
public static class Profile
|
||||
{
|
||||
public static float UpdateRate => _settings.UpdateRate;
|
||||
public static long HistoryLength => _settings.History;
|
||||
|
||||
public static ProfilerKeyboardHandler Controls => _settings.Controls;
|
||||
|
||||
private static InternalProfile _profileInstance;
|
||||
private static ProfilerSettings _settings;
|
||||
|
||||
[Conditional("USE_PROFILING")]
|
||||
[Conditional("USE_DEBUGGING")]
|
||||
public static void Initialize()
|
||||
{
|
||||
var config = ProfilerConfiguration.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ProfilerConfig.jsonc"));
|
||||
|
@ -29,14 +27,13 @@ namespace Ryujinx.Profiler
|
|||
UpdateRate = (config.UpdateRate <= 0) ? -1 : 1.0f / config.UpdateRate,
|
||||
History = (long)(config.History * PerformanceCounter.TicksPerSecond),
|
||||
MaxLevel = config.MaxLevel,
|
||||
Controls = config.Controls,
|
||||
MaxFlags = config.MaxFlags,
|
||||
};
|
||||
}
|
||||
|
||||
public static bool ProfilingEnabled()
|
||||
{
|
||||
#if USE_PROFILING
|
||||
#if USE_DEBUGGING
|
||||
if (!_settings.Enabled)
|
||||
return false;
|
||||
|
||||
|
@ -49,7 +46,7 @@ namespace Ryujinx.Profiler
|
|||
#endif
|
||||
}
|
||||
|
||||
[Conditional("USE_PROFILING")]
|
||||
[Conditional("USE_DEBUGGING")]
|
||||
public static void FinishProfiling()
|
||||
{
|
||||
if (!ProfilingEnabled())
|
||||
|
@ -61,7 +58,7 @@ namespace Ryujinx.Profiler
|
|||
_profileInstance.Dispose();
|
||||
}
|
||||
|
||||
[Conditional("USE_PROFILING")]
|
||||
[Conditional("USE_DEBUGGING")]
|
||||
public static void FlagTime(TimingFlagType flagType)
|
||||
{
|
||||
if (!ProfilingEnabled())
|
||||
|
@ -69,7 +66,7 @@ namespace Ryujinx.Profiler
|
|||
_profileInstance.FlagTime(flagType);
|
||||
}
|
||||
|
||||
[Conditional("USE_PROFILING")]
|
||||
[Conditional("USE_DEBUGGING")]
|
||||
public static void RegisterFlagReceiver(Action<TimingFlag> receiver)
|
||||
{
|
||||
if (!ProfilingEnabled())
|
||||
|
@ -77,7 +74,7 @@ namespace Ryujinx.Profiler
|
|||
_profileInstance.RegisterFlagReceiver(receiver);
|
||||
}
|
||||
|
||||
[Conditional("USE_PROFILING")]
|
||||
[Conditional("USE_DEBUGGING")]
|
||||
public static void Begin(ProfileConfig config)
|
||||
{
|
||||
if (!ProfilingEnabled())
|
||||
|
@ -87,7 +84,7 @@ namespace Ryujinx.Profiler
|
|||
_profileInstance.BeginProfile(config);
|
||||
}
|
||||
|
||||
[Conditional("USE_PROFILING")]
|
||||
[Conditional("USE_DEBUGGING")]
|
||||
public static void End(ProfileConfig config)
|
||||
{
|
||||
if (!ProfilingEnabled())
|
||||
|
@ -99,7 +96,7 @@ namespace Ryujinx.Profiler
|
|||
|
||||
public static string GetSession()
|
||||
{
|
||||
#if USE_PROFILING
|
||||
#if USE_DEBUGGING
|
||||
if (!ProfilingEnabled())
|
||||
return null;
|
||||
return _profileInstance.GetSession();
|
||||
|
@ -110,7 +107,7 @@ namespace Ryujinx.Profiler
|
|||
|
||||
public static List<KeyValuePair<ProfileConfig, TimingInfo>> GetProfilingData()
|
||||
{
|
||||
#if USE_PROFILING
|
||||
#if USE_DEBUGGING
|
||||
if (!ProfilingEnabled())
|
||||
return new List<KeyValuePair<ProfileConfig, TimingInfo>>();
|
||||
return _profileInstance.GetProfilingData();
|
||||
|
@ -121,7 +118,7 @@ namespace Ryujinx.Profiler
|
|||
|
||||
public static TimingFlag[] GetTimingFlags()
|
||||
{
|
||||
#if USE_PROFILING
|
||||
#if USE_DEBUGGING
|
||||
if (!ProfilingEnabled())
|
||||
return new TimingFlag[0];
|
||||
return _profileInstance.GetTimingFlags();
|
||||
|
@ -132,7 +129,7 @@ namespace Ryujinx.Profiler
|
|||
|
||||
public static (long[], long[]) GetTimingAveragesAndLast()
|
||||
{
|
||||
#if USE_PROFILING
|
||||
#if USE_DEBUGGING
|
||||
if (!ProfilingEnabled())
|
||||
return (new long[0], new long[0]);
|
||||
return _profileInstance.GetTimingAveragesAndLast();
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Profiler
|
||||
namespace Ryujinx.Debugger.Profiler
|
||||
{
|
||||
public struct ProfileConfig : IEquatable<ProfileConfig>
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Profiler.UI
|
||||
namespace Ryujinx.Debugger.Profiler
|
||||
{
|
||||
public static class ProfileSorters
|
||||
{
|
|
@ -1,10 +1,10 @@
|
|||
using OpenTK.Input;
|
||||
using Gdk;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Utf8Json;
|
||||
using Utf8Json.Resolvers;
|
||||
|
||||
namespace Ryujinx.Profiler
|
||||
namespace Ryujinx.Debugger.Profiler
|
||||
{
|
||||
public class ProfilerConfiguration
|
||||
{
|
||||
|
@ -15,8 +15,6 @@ namespace Ryujinx.Profiler
|
|||
public int MaxFlags { get; private set; }
|
||||
public float History { get; private set; }
|
||||
|
||||
public ProfilerKeyboardHandler Controls { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads a configuration file from disk
|
||||
/// </summary>
|
|
@ -1,4 +1,4 @@
|
|||
namespace Ryujinx.Profiler
|
||||
namespace Ryujinx.Debugger.Profiler
|
||||
{
|
||||
public class ProfilerSettings
|
||||
{
|
||||
|
@ -13,8 +13,5 @@
|
|||
// 19531225 = 5 seconds in ticks on most pc's.
|
||||
// It should get set on boot to the time specified in config
|
||||
public long History { get; set; } = 19531225;
|
||||
|
||||
// Controls
|
||||
public ProfilerKeyboardHandler Controls;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace Ryujinx.Profiler
|
||||
namespace Ryujinx.Debugger.Profiler
|
||||
{
|
||||
public enum TimingFlagType
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Profiler
|
||||
namespace Ryujinx.Debugger.Profiler
|
||||
{
|
||||
public struct Timestamp
|
||||
{
|
42
Ryujinx.Debugger/Ryujinx.Debugger.csproj
Normal file
42
Ryujinx.Debugger/Ryujinx.Debugger.csproj
Normal file
|
@ -0,0 +1,42 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<Configurations>Debug;Release;Profile Release;Profile Debug</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="UI\DebuggerWidget.glade" />
|
||||
<None Remove="UI\ProfilerWidget.glade" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="UI\DebuggerWidget.glade" />
|
||||
<EmbeddedResource Include="UI\ProfilerWidget.glade" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="GtkSharp" Version="3.22.25.56" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1.1" />
|
||||
<PackageReference Include="SkiaSharp.Views.Gtk3" Version="1.68.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="ProfilerConfig.jsonc">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
42
Ryujinx.Debugger/UI/DebuggerWidget.cs
Normal file
42
Ryujinx.Debugger/UI/DebuggerWidget.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using Gtk;
|
||||
using System;
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.Debugger.UI
|
||||
{
|
||||
public class DebuggerWidget : Box
|
||||
{
|
||||
public event EventHandler DebuggerEnabled;
|
||||
public event EventHandler DebuggerDisabled;
|
||||
|
||||
[GUI] Notebook _widgetNotebook;
|
||||
|
||||
public DebuggerWidget() : this(new Builder("Ryujinx.Debugger.UI.DebuggerWidget.glade")) { }
|
||||
|
||||
public DebuggerWidget(Builder builder) : base(builder.GetObject("_debuggerBox").Handle)
|
||||
{
|
||||
builder.Autoconnect(this);
|
||||
|
||||
LoadProfiler();
|
||||
}
|
||||
|
||||
public void LoadProfiler()
|
||||
{
|
||||
ProfilerWidget widget = new ProfilerWidget();
|
||||
|
||||
widget.RegisterParentDebugger(this);
|
||||
|
||||
_widgetNotebook.AppendPage(widget, new Label("Profiler"));
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
DebuggerEnabled.Invoke(this, null);
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
DebuggerDisabled.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
44
Ryujinx.Debugger/UI/DebuggerWidget.glade
Normal file
44
Ryujinx.Debugger/UI/DebuggerWidget.glade
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.21.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkBox" id="_debuggerBox">
|
||||
<property name="name">DebuggerBox</property>
|
||||
<property name="width_request">1024</property>
|
||||
<property name="height_request">720</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkNotebook" id="_widgetNotebook">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
801
Ryujinx.Debugger/UI/ProfilerWidget.cs
Normal file
801
Ryujinx.Debugger/UI/ProfilerWidget.cs
Normal file
|
@ -0,0 +1,801 @@
|
|||
using Gtk;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Debugger.Profiler;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.Views.Desktop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
using GUI = Gtk.Builder.ObjectAttribute;
|
||||
|
||||
namespace Ryujinx.Debugger.UI
|
||||
{
|
||||
public class ProfilerWidget : Box
|
||||
{
|
||||
private Thread _profilerThread;
|
||||
private double _prevTime;
|
||||
private bool _profilerRunning;
|
||||
|
||||
private TimingFlag[] _timingFlags;
|
||||
|
||||
private bool _initComplete = false;
|
||||
private bool _redrawPending = true;
|
||||
private bool _doStep = false;
|
||||
|
||||
// Layout
|
||||
private const int LineHeight = 16;
|
||||
private const int MinimumColumnWidth = 200;
|
||||
private const int TitleHeight = 24;
|
||||
private const int TitleFontHeight = 16;
|
||||
private const int LinePadding = 2;
|
||||
private const int ColumnSpacing = 15;
|
||||
private const int FilterHeight = 24;
|
||||
private const int BottomBarHeight = FilterHeight + LineHeight;
|
||||
|
||||
// Sorting
|
||||
private List<KeyValuePair<ProfileConfig, TimingInfo>> _unsortedProfileData;
|
||||
private IComparer<KeyValuePair<ProfileConfig, TimingInfo>> _sortAction = new ProfileSorters.TagAscending();
|
||||
|
||||
// Flag data
|
||||
private long[] _timingFlagsAverages;
|
||||
private long[] _timingFlagsLast;
|
||||
|
||||
// Filtering
|
||||
private string _filterText = "";
|
||||
private bool _regexEnabled = false;
|
||||
|
||||
// Scrolling
|
||||
private float _scrollPos = 0;
|
||||
|
||||
// Profile data storage
|
||||
private List<KeyValuePair<ProfileConfig, TimingInfo>> _sortedProfileData;
|
||||
private long _captureTime;
|
||||
|
||||
// Graph
|
||||
private SKColor[] _timingFlagColors = new[]
|
||||
{
|
||||
new SKColor(150, 25, 25, 50), // FrameSwap = 0
|
||||
new SKColor(25, 25, 150, 50), // SystemFrame = 1
|
||||
};
|
||||
|
||||
private const float GraphMoveSpeed = 40000;
|
||||
private const float GraphZoomSpeed = 50;
|
||||
|
||||
private float _graphZoom = 1;
|
||||
private float _graphPosition = 0;
|
||||
private int _rendererHeight => _renderer.AllocatedHeight;
|
||||
private int _rendererWidth => _renderer.AllocatedWidth;
|
||||
|
||||
// Event management
|
||||
private long _lastOutputUpdate;
|
||||
private long _lastOutputDraw;
|
||||
private long _lastOutputUpdateDuration;
|
||||
private long _lastOutputDrawDuration;
|
||||
private double _lastFrameTimeMs;
|
||||
private double _updateTimer;
|
||||
private bool _profileUpdated = false;
|
||||
private readonly object _profileDataLock = new object();
|
||||
|
||||
private SkRenderer _renderer;
|
||||
|
||||
[GUI] ScrolledWindow _scrollview;
|
||||
[GUI] CheckButton _enableCheckbutton;
|
||||
[GUI] Scrollbar _outputScrollbar;
|
||||
[GUI] Entry _filterBox;
|
||||
[GUI] ComboBox _modeBox;
|
||||
[GUI] CheckButton _showFlags;
|
||||
[GUI] CheckButton _showInactive;
|
||||
[GUI] Button _stepButton;
|
||||
[GUI] CheckButton _pauseCheckbutton;
|
||||
|
||||
public ProfilerWidget() : this(new Builder("Ryujinx.Debugger.UI.ProfilerWidget.glade")) { }
|
||||
|
||||
public ProfilerWidget(Builder builder) : base(builder.GetObject("_profilerBox").Handle)
|
||||
{
|
||||
builder.Autoconnect(this);
|
||||
|
||||
this.KeyPressEvent += ProfilerWidget_KeyPressEvent;
|
||||
|
||||
this.Expand = true;
|
||||
|
||||
_renderer = new SkRenderer();
|
||||
_renderer.Expand = true;
|
||||
|
||||
_outputScrollbar.ValueChanged += _outputScrollbar_ValueChanged;
|
||||
|
||||
_renderer.DrawGraphs += _renderer_DrawGraphs;
|
||||
|
||||
_filterBox.Changed += _filterBox_Changed;
|
||||
|
||||
_stepButton.Clicked += _stepButton_Clicked;
|
||||
|
||||
_scrollview.Add(_renderer);
|
||||
|
||||
if (Profile.UpdateRate <= 0)
|
||||
{
|
||||
// Perform step regardless of flag type
|
||||
Profile.RegisterFlagReceiver((t) =>
|
||||
{
|
||||
if (_pauseCheckbutton.Active)
|
||||
{
|
||||
_doStep = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void _stepButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (_pauseCheckbutton.Active)
|
||||
{
|
||||
_doStep = true;
|
||||
}
|
||||
|
||||
_profileUpdated = true;
|
||||
}
|
||||
|
||||
private void _filterBox_Changed(object sender, EventArgs e)
|
||||
{
|
||||
_filterText = _filterBox.Text;
|
||||
_profileUpdated = true;
|
||||
}
|
||||
|
||||
private void _outputScrollbar_ValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
_scrollPos = -(float)Math.Max(0, _outputScrollbar.Value);
|
||||
_profileUpdated = true;
|
||||
}
|
||||
|
||||
private void _renderer_DrawGraphs(object sender, EventArgs e)
|
||||
{
|
||||
if (e is SKPaintSurfaceEventArgs se)
|
||||
{
|
||||
Draw(se.Surface.Canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterParentDebugger(DebuggerWidget debugger)
|
||||
{
|
||||
debugger.DebuggerEnabled += Debugger_DebuggerAttached;
|
||||
debugger.DebuggerDisabled += Debugger_DebuggerDettached;
|
||||
}
|
||||
|
||||
private void Debugger_DebuggerDettached(object sender, EventArgs e)
|
||||
{
|
||||
_profilerRunning = false;
|
||||
|
||||
if (_profilerThread != null)
|
||||
{
|
||||
_profilerThread.Join();
|
||||
}
|
||||
}
|
||||
|
||||
private void Debugger_DebuggerAttached(object sender, EventArgs e)
|
||||
{
|
||||
_profilerRunning = false;
|
||||
|
||||
if (_profilerThread != null)
|
||||
{
|
||||
_profilerThread.Join();
|
||||
}
|
||||
|
||||
_profilerRunning = true;
|
||||
|
||||
_profilerThread = new Thread(UpdateLoop)
|
||||
{
|
||||
Name = "Profiler.UpdateThread"
|
||||
};
|
||||
_profilerThread.Start();
|
||||
}
|
||||
|
||||
private void ProfilerWidget_KeyPressEvent(object o, Gtk.KeyPressEventArgs args)
|
||||
{
|
||||
switch (args.Event.Key)
|
||||
{
|
||||
case Gdk.Key.Left:
|
||||
_graphPosition += (long)(GraphMoveSpeed * _lastFrameTimeMs);
|
||||
break;
|
||||
|
||||
case Gdk.Key.Right:
|
||||
_graphPosition = Math.Max(_graphPosition - (long)(GraphMoveSpeed * _lastFrameTimeMs), 0);
|
||||
break;
|
||||
|
||||
case Gdk.Key.Up:
|
||||
_graphZoom = MathF.Min(_graphZoom + (float)(GraphZoomSpeed * _lastFrameTimeMs), 100.0f);
|
||||
break;
|
||||
|
||||
case Gdk.Key.Down:
|
||||
_graphZoom = MathF.Max(_graphZoom - (float)(GraphZoomSpeed * _lastFrameTimeMs), 1f);
|
||||
break;
|
||||
}
|
||||
_profileUpdated = true;
|
||||
}
|
||||
|
||||
public void UpdateLoop()
|
||||
{
|
||||
_lastOutputUpdate = PerformanceCounter.ElapsedTicks;
|
||||
_lastOutputDraw = PerformanceCounter.ElapsedTicks;
|
||||
|
||||
while (_profilerRunning)
|
||||
{
|
||||
_lastOutputUpdate = PerformanceCounter.ElapsedTicks;
|
||||
int timeToSleepMs = (_pauseCheckbutton.Active || !_enableCheckbutton.Active) ? 33 : 1;
|
||||
|
||||
if (Profile.ProfilingEnabled() && _enableCheckbutton.Active)
|
||||
{
|
||||
double time = (double)PerformanceCounter.ElapsedTicks / PerformanceCounter.TicksPerSecond;
|
||||
|
||||
Update(time - _prevTime);
|
||||
|
||||
_lastOutputUpdateDuration = PerformanceCounter.ElapsedTicks - _lastOutputUpdate;
|
||||
_prevTime = time;
|
||||
|
||||
Gdk.Threads.AddIdle(1000, ()=>
|
||||
{
|
||||
_renderer.QueueDraw();
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
Thread.Sleep(timeToSleepMs);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(double frameTime)
|
||||
{
|
||||
_lastFrameTimeMs = frameTime;
|
||||
|
||||
// Get timing data if enough time has passed
|
||||
_updateTimer += frameTime;
|
||||
|
||||
if (_doStep || ((Profile.UpdateRate > 0) && (!_pauseCheckbutton.Active && (_updateTimer > Profile.UpdateRate))))
|
||||
{
|
||||
_updateTimer = 0;
|
||||
_captureTime = PerformanceCounter.ElapsedTicks;
|
||||
_timingFlags = Profile.GetTimingFlags();
|
||||
_doStep = false;
|
||||
_profileUpdated = true;
|
||||
|
||||
_unsortedProfileData = Profile.GetProfilingData();
|
||||
|
||||
(_timingFlagsAverages, _timingFlagsLast) = Profile.GetTimingAveragesAndLast();
|
||||
}
|
||||
|
||||
// Filtering
|
||||
if (_profileUpdated)
|
||||
{
|
||||
lock (_profileDataLock)
|
||||
{
|
||||
_sortedProfileData = _showInactive.Active ? _unsortedProfileData : _unsortedProfileData.FindAll(kvp => kvp.Value.IsActive);
|
||||
|
||||
if (_sortAction != null)
|
||||
{
|
||||
_sortedProfileData.Sort(_sortAction);
|
||||
}
|
||||
|
||||
if (_regexEnabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
Regex filterRegex = new Regex(_filterText, RegexOptions.IgnoreCase);
|
||||
if (_filterText != "")
|
||||
{
|
||||
_sortedProfileData = _sortedProfileData.Where((pair => filterRegex.IsMatch(pair.Key.Search))).ToList();
|
||||
}
|
||||
}
|
||||
catch (ArgumentException argException)
|
||||
{
|
||||
// Skip filtering for invalid regex
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular filtering
|
||||
_sortedProfileData = _sortedProfileData.Where((pair => pair.Key.Search.ToLower().Contains(_filterText.ToLower()))).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
_profileUpdated = false;
|
||||
_redrawPending = true;
|
||||
_initComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetTimeString(long timestamp)
|
||||
{
|
||||
float time = (float)timestamp / PerformanceCounter.TicksPerMillisecond;
|
||||
|
||||
return (time < 1) ? $"{time * 1000:F3}us" : $"{time:F3}ms";
|
||||
}
|
||||
|
||||
private void FilterBackspace()
|
||||
{
|
||||
if (_filterText.Length <= 1)
|
||||
{
|
||||
_filterText = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
_filterText = _filterText.Remove(_filterText.Length - 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private float GetLineY(float offset, float lineHeight, float padding, bool centre, int line)
|
||||
{
|
||||
return offset + lineHeight + padding + ((lineHeight + padding) * line) - ((centre) ? padding : 0);
|
||||
}
|
||||
|
||||
public void Draw(SKCanvas canvas)
|
||||
{
|
||||
_lastOutputDraw = PerformanceCounter.ElapsedTicks;
|
||||
if (!Visible ||
|
||||
!_initComplete ||
|
||||
!_enableCheckbutton.Active ||
|
||||
!_redrawPending)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float viewTop = TitleHeight + 5;
|
||||
float viewBottom = _rendererHeight - FilterHeight - LineHeight;
|
||||
|
||||
float columnWidth;
|
||||
float maxColumnWidth = MinimumColumnWidth;
|
||||
float yOffset = _scrollPos + viewTop;
|
||||
float xOffset = 10;
|
||||
float timingWidth;
|
||||
|
||||
float contentHeight = GetLineY(0, LineHeight, LinePadding, false, _sortedProfileData.Count - 1);
|
||||
|
||||
_outputScrollbar.Adjustment.Upper = contentHeight;
|
||||
_outputScrollbar.Adjustment.Lower = 0;
|
||||
_outputScrollbar.Adjustment.PageSize = viewBottom - viewTop;
|
||||
|
||||
|
||||
SKPaint textFont = new SKPaint()
|
||||
{
|
||||
Color = SKColors.White,
|
||||
TextSize = LineHeight
|
||||
};
|
||||
|
||||
SKPaint titleFont = new SKPaint()
|
||||
{
|
||||
Color = SKColors.White,
|
||||
TextSize = TitleFontHeight
|
||||
};
|
||||
|
||||
SKPaint evenItemBackground = new SKPaint()
|
||||
{
|
||||
Color = SKColors.Gray
|
||||
};
|
||||
|
||||
canvas.Save();
|
||||
canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
|
||||
|
||||
for (int i = 1; i < _sortedProfileData.Count; i += 2)
|
||||
{
|
||||
float top = GetLineY(yOffset, LineHeight, LinePadding, false, i - 1);
|
||||
float bottom = GetLineY(yOffset, LineHeight, LinePadding, false, i);
|
||||
|
||||
canvas.DrawRect(new SKRect(0, top, _rendererWidth, bottom), evenItemBackground);
|
||||
}
|
||||
|
||||
lock (_profileDataLock)
|
||||
{
|
||||
// Display category
|
||||
|
||||
for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
|
||||
{
|
||||
KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
|
||||
|
||||
if (entry.Key.Category == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
|
||||
|
||||
canvas.DrawText(entry.Key.Category, new SKPoint(xOffset, y), textFont);
|
||||
|
||||
columnWidth = textFont.MeasureText(entry.Key.Category);
|
||||
|
||||
if (columnWidth > maxColumnWidth)
|
||||
{
|
||||
maxColumnWidth = columnWidth;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Restore();
|
||||
canvas.DrawText("Category", new SKPoint(xOffset, TitleFontHeight + 2), titleFont);
|
||||
|
||||
columnWidth = titleFont.MeasureText("Category");
|
||||
|
||||
if (columnWidth > maxColumnWidth)
|
||||
{
|
||||
maxColumnWidth = columnWidth;
|
||||
}
|
||||
|
||||
xOffset += maxColumnWidth + ColumnSpacing;
|
||||
|
||||
canvas.DrawLine(new SKPoint(xOffset - ColumnSpacing / 2, 0), new SKPoint(xOffset - ColumnSpacing / 2, viewBottom), textFont);
|
||||
|
||||
// Display session group
|
||||
maxColumnWidth = MinimumColumnWidth;
|
||||
|
||||
canvas.Save();
|
||||
canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
|
||||
|
||||
for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
|
||||
{
|
||||
KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
|
||||
|
||||
if (entry.Key.SessionGroup == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
|
||||
|
||||
canvas.DrawText(entry.Key.SessionGroup, new SKPoint(xOffset, y), textFont);
|
||||
|
||||
columnWidth = textFont.MeasureText(entry.Key.SessionGroup);
|
||||
|
||||
if (columnWidth > maxColumnWidth)
|
||||
{
|
||||
maxColumnWidth = columnWidth;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Restore();
|
||||
canvas.DrawText("Group", new SKPoint(xOffset, TitleFontHeight + 2), titleFont);
|
||||
|
||||
columnWidth = titleFont.MeasureText("Group");
|
||||
|
||||
if (columnWidth > maxColumnWidth)
|
||||
{
|
||||
maxColumnWidth = columnWidth;
|
||||
}
|
||||
|
||||
xOffset += maxColumnWidth + ColumnSpacing;
|
||||
|
||||
canvas.DrawLine(new SKPoint(xOffset - ColumnSpacing / 2, 0), new SKPoint(xOffset - ColumnSpacing / 2, viewBottom), textFont);
|
||||
|
||||
// Display session item
|
||||
maxColumnWidth = MinimumColumnWidth;
|
||||
|
||||
canvas.Save();
|
||||
canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
|
||||
|
||||
for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
|
||||
{
|
||||
KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
|
||||
|
||||
if (entry.Key.SessionItem == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
|
||||
|
||||
canvas.DrawText(entry.Key.SessionGroup, new SKPoint(xOffset, y), textFont);
|
||||
|
||||
columnWidth = textFont.MeasureText(entry.Key.SessionItem);
|
||||
|
||||
if (columnWidth > maxColumnWidth)
|
||||
{
|
||||
maxColumnWidth = columnWidth;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Restore();
|
||||
canvas.DrawText("Item", new SKPoint(xOffset, TitleFontHeight + 2), titleFont);
|
||||
|
||||
columnWidth = titleFont.MeasureText("Item");
|
||||
|
||||
if (columnWidth > maxColumnWidth)
|
||||
{
|
||||
maxColumnWidth = columnWidth;
|
||||
}
|
||||
|
||||
xOffset += maxColumnWidth + ColumnSpacing;
|
||||
|
||||
timingWidth = _rendererWidth - xOffset - 370;
|
||||
|
||||
canvas.Save();
|
||||
canvas.ClipRect(new SKRect(0, viewTop, _rendererWidth, viewBottom), SKClipOperation.Intersect);
|
||||
canvas.DrawLine(new SKPoint(xOffset, 0), new SKPoint(xOffset, _rendererHeight), textFont);
|
||||
|
||||
int mode = _modeBox.Active;
|
||||
|
||||
canvas.Save();
|
||||
canvas.ClipRect(new SKRect(xOffset, yOffset,xOffset + timingWidth,yOffset + contentHeight),
|
||||
SKClipOperation.Intersect);
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case 0:
|
||||
DrawGraph(xOffset, yOffset, timingWidth, canvas);
|
||||
break;
|
||||
case 1:
|
||||
DrawBars(xOffset, yOffset, timingWidth, canvas);
|
||||
|
||||
canvas.DrawText("Blue: Instant, Green: Avg, Red: Total",
|
||||
new SKPoint(xOffset, _rendererHeight - TitleFontHeight), titleFont);
|
||||
break;
|
||||
}
|
||||
|
||||
canvas.Restore();
|
||||
canvas.DrawLine(new SKPoint(xOffset + timingWidth, 0), new SKPoint(xOffset + timingWidth, _rendererHeight), textFont);
|
||||
|
||||
xOffset = _rendererWidth - 360;
|
||||
|
||||
// Display timestamps
|
||||
long totalInstant = 0;
|
||||
long totalAverage = 0;
|
||||
long totalTime = 0;
|
||||
long totalCount = 0;
|
||||
|
||||
for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
|
||||
{
|
||||
KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
|
||||
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
|
||||
|
||||
canvas.DrawText($"{GetTimeString(entry.Value.Instant)} ({entry.Value.InstantCount})", new SKPoint(xOffset, y), textFont);
|
||||
canvas.DrawText(GetTimeString(entry.Value.AverageTime), new SKPoint(150 + xOffset, y), textFont);
|
||||
canvas.DrawText(GetTimeString(entry.Value.TotalTime), new SKPoint(260 + xOffset, y), textFont);
|
||||
|
||||
totalInstant += entry.Value.Instant;
|
||||
totalAverage += entry.Value.AverageTime;
|
||||
totalTime += entry.Value.TotalTime;
|
||||
totalCount += entry.Value.InstantCount;
|
||||
}
|
||||
|
||||
canvas.Restore();
|
||||
canvas.DrawLine(new SKPoint(0, viewTop), new SKPoint(_rendererWidth, viewTop), titleFont);
|
||||
|
||||
float yHeight = 0 + TitleFontHeight;
|
||||
|
||||
canvas.DrawText("Instant (Count)", new SKPoint(xOffset, yHeight), titleFont);
|
||||
canvas.DrawText("Average", new SKPoint(150 + xOffset, yHeight), titleFont);
|
||||
canvas.DrawText("Total (ms)", new SKPoint(260 + xOffset, yHeight), titleFont);
|
||||
|
||||
// Totals
|
||||
yHeight = _rendererHeight - FilterHeight + 3;
|
||||
|
||||
int textHeight = LineHeight - 2;
|
||||
|
||||
SKPaint detailFont = new SKPaint()
|
||||
{
|
||||
Color = new SKColor(100, 100, 255, 255),
|
||||
TextSize = textHeight
|
||||
};
|
||||
|
||||
canvas.DrawLine(new SkiaSharp.SKPoint(0, viewBottom), new SkiaSharp.SKPoint(_rendererWidth,viewBottom), textFont);
|
||||
|
||||
string hostTimeString = $"Host {GetTimeString(_timingFlagsLast[(int)TimingFlagType.SystemFrame])} " +
|
||||
$"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.SystemFrame])})";
|
||||
|
||||
canvas.DrawText(hostTimeString, new SKPoint(5, yHeight), detailFont);
|
||||
|
||||
float tempWidth = detailFont.MeasureText(hostTimeString);
|
||||
|
||||
detailFont.Color = SKColors.Red;
|
||||
|
||||
string gameTimeString = $"Game {GetTimeString(_timingFlagsLast[(int)TimingFlagType.FrameSwap])} " +
|
||||
$"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.FrameSwap])})";
|
||||
|
||||
canvas.DrawText(gameTimeString, new SKPoint(15 + tempWidth, yHeight), detailFont);
|
||||
|
||||
tempWidth += detailFont.MeasureText(gameTimeString);
|
||||
|
||||
detailFont.Color = SKColors.White;
|
||||
|
||||
canvas.DrawText($"Profiler: Update {GetTimeString(_lastOutputUpdateDuration)} Draw {GetTimeString(_lastOutputDrawDuration)}",
|
||||
new SKPoint(20 + tempWidth, yHeight), detailFont);
|
||||
|
||||
detailFont.Color = SKColors.White;
|
||||
|
||||
canvas.DrawText($"{GetTimeString(totalInstant)} ({totalCount})", new SKPoint(xOffset, yHeight), detailFont);
|
||||
canvas.DrawText(GetTimeString(totalAverage), new SKPoint(150 + xOffset, yHeight), detailFont);
|
||||
canvas.DrawText(GetTimeString(totalTime), new SKPoint(260 + xOffset, yHeight), detailFont);
|
||||
|
||||
_lastOutputDrawDuration = PerformanceCounter.ElapsedTicks - _lastOutputDraw;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGraph(float xOffset, float yOffset, float width, SKCanvas canvas)
|
||||
{
|
||||
if (_sortedProfileData.Count != 0)
|
||||
{
|
||||
int left, right;
|
||||
float top, bottom;
|
||||
|
||||
float graphRight = xOffset + width;
|
||||
float barHeight = (LineHeight - LinePadding);
|
||||
long history = Profile.HistoryLength;
|
||||
double timeWidthTicks = history / (double)_graphZoom;
|
||||
long graphPositionTicks = (long)(_graphPosition * PerformanceCounter.TicksPerMillisecond);
|
||||
long ticksPerPixel = (long)(timeWidthTicks / width);
|
||||
|
||||
// Reset start point if out of bounds
|
||||
if (timeWidthTicks + graphPositionTicks > history)
|
||||
{
|
||||
graphPositionTicks = history - (long)timeWidthTicks;
|
||||
_graphPosition = (float)graphPositionTicks / PerformanceCounter.TicksPerMillisecond;
|
||||
}
|
||||
|
||||
graphPositionTicks = _captureTime - graphPositionTicks;
|
||||
|
||||
// Draw timing flags
|
||||
if (_showFlags.Active)
|
||||
{
|
||||
TimingFlagType prevType = TimingFlagType.Count;
|
||||
|
||||
SKPaint timingPaint = new SKPaint
|
||||
{
|
||||
Color = _timingFlagColors.First()
|
||||
};
|
||||
|
||||
foreach (TimingFlag timingFlag in _timingFlags)
|
||||
{
|
||||
if (prevType != timingFlag.FlagType)
|
||||
{
|
||||
prevType = timingFlag.FlagType;
|
||||
timingPaint.Color = _timingFlagColors[(int)prevType];
|
||||
}
|
||||
|
||||
int x = (int)(graphRight - ((graphPositionTicks - timingFlag.Timestamp) / timeWidthTicks) * width);
|
||||
|
||||
if (x > xOffset)
|
||||
{
|
||||
canvas.DrawLine(new SKPoint(x, yOffset), new SKPoint(x, _rendererHeight), timingPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SKPaint barPaint = new SKPaint()
|
||||
{
|
||||
Color = SKColors.Green,
|
||||
};
|
||||
|
||||
// Draw bars
|
||||
for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
|
||||
{
|
||||
KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
|
||||
long furthest = 0;
|
||||
|
||||
bottom = GetLineY(yOffset, LineHeight, LinePadding, false, verticalIndex);
|
||||
top = bottom + barHeight;
|
||||
|
||||
// Skip rendering out of bounds bars
|
||||
if (top < 0 || bottom > _rendererHeight)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
barPaint.Color = SKColors.Green;
|
||||
|
||||
foreach (Timestamp timestamp in entry.Value.GetAllTimestamps())
|
||||
{
|
||||
// Skip drawing multiple timestamps on same pixel
|
||||
if (timestamp.EndTime < furthest)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
furthest = timestamp.EndTime + ticksPerPixel;
|
||||
|
||||
left = (int)(graphRight - ((graphPositionTicks - timestamp.BeginTime) / timeWidthTicks) * width);
|
||||
right = (int)(graphRight - ((graphPositionTicks - timestamp.EndTime) / timeWidthTicks) * width);
|
||||
|
||||
left = (int)Math.Max(xOffset +1, left);
|
||||
|
||||
// Make sure width is at least 1px
|
||||
right = Math.Max(left + 1, right);
|
||||
|
||||
canvas.DrawRect(new SKRect(left, top, right, bottom), barPaint);
|
||||
}
|
||||
|
||||
// Currently capturing timestamp
|
||||
barPaint.Color = SKColors.Red;
|
||||
|
||||
long entryBegin = entry.Value.BeginTime;
|
||||
|
||||
if (entryBegin != -1)
|
||||
{
|
||||
left = (int)(graphRight - ((graphPositionTicks - entryBegin) / timeWidthTicks) * width);
|
||||
|
||||
// Make sure width is at least 1px
|
||||
left = Math.Min(left - 1, (int)graphRight);
|
||||
|
||||
left = (int)Math.Max(xOffset + 1, left);
|
||||
|
||||
canvas.DrawRect(new SKRect(left, top, graphRight, bottom), barPaint);
|
||||
}
|
||||
}
|
||||
|
||||
string label = $"-{MathF.Round(_graphPosition, 2)} ms";
|
||||
|
||||
SKPaint labelPaint = new SKPaint()
|
||||
{
|
||||
Color = SKColors.White,
|
||||
TextSize = LineHeight
|
||||
};
|
||||
|
||||
float labelWidth = labelPaint.MeasureText(label);
|
||||
|
||||
canvas.DrawText(label,new SKPoint(graphRight - labelWidth - LinePadding, FilterHeight + LinePadding) , labelPaint);
|
||||
|
||||
canvas.DrawText($"-{MathF.Round((float)((timeWidthTicks / PerformanceCounter.TicksPerMillisecond) + _graphPosition), 2)} ms",
|
||||
new SKPoint(xOffset + LinePadding, FilterHeight + LinePadding), labelPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBars(float xOffset, float yOffset, float width, SKCanvas canvas)
|
||||
{
|
||||
if (_sortedProfileData.Count != 0)
|
||||
{
|
||||
long maxAverage = 0;
|
||||
long maxTotal = 0;
|
||||
long maxInstant = 0;
|
||||
|
||||
float barHeight = (LineHeight - LinePadding) / 3.0f;
|
||||
|
||||
// Get max values
|
||||
foreach (KeyValuePair<ProfileConfig, TimingInfo> kvp in _sortedProfileData)
|
||||
{
|
||||
maxInstant = Math.Max(maxInstant, kvp.Value.Instant);
|
||||
maxAverage = Math.Max(maxAverage, kvp.Value.AverageTime);
|
||||
maxTotal = Math.Max(maxTotal, kvp.Value.TotalTime);
|
||||
}
|
||||
|
||||
SKPaint barPaint = new SKPaint()
|
||||
{
|
||||
Color = SKColors.Blue
|
||||
};
|
||||
|
||||
for (int verticalIndex = 0; verticalIndex < _sortedProfileData.Count; verticalIndex++)
|
||||
{
|
||||
KeyValuePair<ProfileConfig, TimingInfo> entry = _sortedProfileData[verticalIndex];
|
||||
// Instant
|
||||
barPaint.Color = SKColors.Blue;
|
||||
|
||||
float bottom = GetLineY(yOffset, LineHeight, LinePadding, false, verticalIndex);
|
||||
float top = bottom + barHeight;
|
||||
float right = (float)entry.Value.Instant / maxInstant * width + xOffset;
|
||||
|
||||
// Skip rendering out of bounds bars
|
||||
if (top < 0 || bottom > _rendererHeight)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
canvas.DrawRect(new SKRect(xOffset, top, right, bottom), barPaint);
|
||||
|
||||
// Average
|
||||
barPaint.Color = SKColors.Green;
|
||||
|
||||
top += barHeight;
|
||||
bottom += barHeight;
|
||||
right = (float)entry.Value.AverageTime / maxAverage * width + xOffset;
|
||||
|
||||
canvas.DrawRect(new SKRect(xOffset, top, right, bottom), barPaint);
|
||||
|
||||
// Total
|
||||
barPaint.Color = SKColors.Red;
|
||||
|
||||
top += barHeight;
|
||||
bottom += barHeight;
|
||||
right = (float)entry.Value.TotalTime / maxTotal * width + xOffset;
|
||||
|
||||
canvas.DrawRect(new SKRect(xOffset, top, right, bottom), barPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
232
Ryujinx.Debugger/UI/ProfilerWidget.glade
Normal file
232
Ryujinx.Debugger/UI/ProfilerWidget.glade
Normal file
|
@ -0,0 +1,232 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.21.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkListStore" id="viewMode">
|
||||
<columns>
|
||||
<!-- column-name mode -->
|
||||
<column type="gint"/>
|
||||
<!-- column-name label -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<data>
|
||||
<row>
|
||||
<col id="0">0</col>
|
||||
<col id="1" translatable="yes">Graph</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0">1</col>
|
||||
<col id="1" translatable="yes">Bars</col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkBox" id="_profilerBox">
|
||||
<property name="name">ProfilerBox</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">5</property>
|
||||
<property name="margin_right">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_enableCheckbutton">
|
||||
<property name="label" translatable="yes">Enable Profiler</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="_scrollview">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="vscrollbar_policy">never</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrollbar" id="_outputScrollbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_showInactive">
|
||||
<property name="label" translatable="yes">Show Inactive</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_showFlags">
|
||||
<property name="label" translatable="yes">Show Flags</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_pauseCheckbutton">
|
||||
<property name="label" translatable="yes">Paused</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">View Mode: </property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="_modeBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">viewMode</property>
|
||||
<property name="active">0</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="modeTextRenderer"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Filter: </property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="_filterBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="_stepButton">
|
||||
<property name="label" translatable="yes">Step</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
23
Ryujinx.Debugger/UI/SkRenderer.cs
Normal file
23
Ryujinx.Debugger/UI/SkRenderer.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using SkiaSharp;
|
||||
using SkiaSharp.Views.Gtk;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Debugger.UI
|
||||
{
|
||||
public class SkRenderer : SKDrawingArea
|
||||
{
|
||||
public event EventHandler DrawGraphs;
|
||||
|
||||
public SkRenderer()
|
||||
{
|
||||
this.PaintSurface += SkRenderer_PaintSurface;
|
||||
}
|
||||
|
||||
private void SkRenderer_PaintSurface(object sender, SkiaSharp.Views.Desktop.SKPaintSurfaceEventArgs e)
|
||||
{
|
||||
e.Surface.Canvas.Clear(SKColors.Black);
|
||||
|
||||
DrawGraphs.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ using Ryujinx.HLE.HOS.Kernel.Ipc;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Ryujinx.Profiler;
|
||||
using Ryujinx.Debugger.Profiler;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.Profiler;
|
||||
using Ryujinx.Debugger.Profiler;
|
||||
using System.Diagnostics;
|
||||
using System.Timers;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
|||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Profiler\Ryujinx.Profiler.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Debugger\Ryujinx.Debugger.csproj" />
|
||||
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
using OpenTK.Input;
|
||||
|
||||
namespace Ryujinx.Profiler
|
||||
{
|
||||
public struct ProfilerButtons
|
||||
{
|
||||
public Key ToggleProfiler;
|
||||
}
|
||||
|
||||
public class ProfilerKeyboardHandler
|
||||
{
|
||||
public ProfilerButtons Buttons;
|
||||
|
||||
private KeyboardState _prevKeyboard;
|
||||
|
||||
public ProfilerKeyboardHandler(ProfilerButtons buttons)
|
||||
{
|
||||
Buttons = buttons;
|
||||
}
|
||||
|
||||
public bool TogglePressed(KeyboardState keyboard) => !keyboard[Buttons.ToggleProfiler] && _prevKeyboard[Buttons.ToggleProfiler];
|
||||
|
||||
public void SetPrevKeyboardState(KeyboardState keyboard)
|
||||
{
|
||||
_prevKeyboard = keyboard;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Configurations>Debug;Release;Profile Debug;Profile Release</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
|
||||
<PackageReference Include="SharpFontCore" Version="0.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Profile Debug' Or '$(Configuration)' == 'Profile Release'">
|
||||
<None Update="ProfilerConfig.jsonc">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,110 +0,0 @@
|
|||
using System;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Profiler.UI.SharpFontHelpers;
|
||||
|
||||
namespace Ryujinx.Profiler.UI
|
||||
{
|
||||
public class ProfileButton
|
||||
{
|
||||
// Store font service
|
||||
private FontService _fontService;
|
||||
|
||||
// Layout information
|
||||
private int _left, _right;
|
||||
private int _bottom, _top;
|
||||
private int _height;
|
||||
private int _padding;
|
||||
|
||||
// Label information
|
||||
private int _labelX, _labelY;
|
||||
private string _label;
|
||||
|
||||
// Misc
|
||||
private Action _clicked;
|
||||
private bool _visible;
|
||||
|
||||
public ProfileButton(FontService fontService, Action clicked)
|
||||
: this(fontService, clicked, 0, 0, 0, 0, 0)
|
||||
{
|
||||
_visible = false;
|
||||
}
|
||||
|
||||
public ProfileButton(FontService fontService, Action clicked, int x, int y, int padding, int height, int width)
|
||||
: this(fontService, "", clicked, x, y, padding, height, width)
|
||||
{
|
||||
_visible = false;
|
||||
}
|
||||
|
||||
public ProfileButton(FontService fontService, string label, Action clicked, int x, int y, int padding, int height, int width = -1)
|
||||
{
|
||||
_fontService = fontService;
|
||||
_clicked = clicked;
|
||||
|
||||
UpdateSize(label, x, y, padding, height, width);
|
||||
}
|
||||
|
||||
public int UpdateSize(string label, int x, int y, int padding, int height, int width = -1)
|
||||
{
|
||||
_visible = true;
|
||||
_label = label;
|
||||
|
||||
if (width == -1)
|
||||
{
|
||||
// Dummy draw to measure size
|
||||
width = (int)_fontService.DrawText(label, 0, 0, height, false);
|
||||
}
|
||||
|
||||
UpdateSize(x, y, padding, width, height);
|
||||
|
||||
return _right - _left;
|
||||
}
|
||||
|
||||
public void UpdateSize(int x, int y, int padding, int width, int height)
|
||||
{
|
||||
_height = height;
|
||||
_left = x;
|
||||
_bottom = y;
|
||||
_labelX = x + padding / 2;
|
||||
_labelY = y + padding / 2;
|
||||
_top = y + height + padding;
|
||||
_right = x + width + padding;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
if (!_visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw backing rectangle
|
||||
GL.Begin(PrimitiveType.Triangles);
|
||||
GL.Color3(Color.Black);
|
||||
GL.Vertex2(_left, _bottom);
|
||||
GL.Vertex2(_left, _top);
|
||||
GL.Vertex2(_right, _top);
|
||||
|
||||
GL.Vertex2(_right, _top);
|
||||
GL.Vertex2(_right, _bottom);
|
||||
GL.Vertex2(_left, _bottom);
|
||||
GL.End();
|
||||
|
||||
// Use font service to draw label
|
||||
_fontService.DrawText(_label, _labelX, _labelY, _height);
|
||||
}
|
||||
|
||||
public bool ProcessClick(int x, int y)
|
||||
{
|
||||
// If button contains x, y
|
||||
if (x > _left && x < _right &&
|
||||
y > _bottom && y < _top)
|
||||
{
|
||||
_clicked();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,773 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Input;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Profiler.UI.SharpFontHelpers;
|
||||
|
||||
namespace Ryujinx.Profiler.UI
|
||||
{
|
||||
public partial class ProfileWindow : GameWindow
|
||||
{
|
||||
// List all buttons for index in button array
|
||||
private enum ButtonIndex
|
||||
{
|
||||
TagTitle = 0,
|
||||
InstantTitle = 1,
|
||||
AverageTitle = 2,
|
||||
TotalTitle = 3,
|
||||
FilterBar = 4,
|
||||
ShowHideInactive = 5,
|
||||
Pause = 6,
|
||||
ChangeDisplay = 7,
|
||||
|
||||
// Don't automatically draw after here
|
||||
ToggleFlags = 8,
|
||||
Step = 9,
|
||||
|
||||
// Update this when new buttons are added.
|
||||
// These are indexes to the enum list
|
||||
Autodraw = 8,
|
||||
Count = 10,
|
||||
}
|
||||
|
||||
// Font service
|
||||
private FontService _fontService;
|
||||
|
||||
// UI variables
|
||||
private ProfileButton[] _buttons;
|
||||
|
||||
private bool _initComplete = false;
|
||||
private bool _visible = true;
|
||||
private bool _visibleChanged = true;
|
||||
private bool _viewportUpdated = true;
|
||||
private bool _redrawPending = true;
|
||||
private bool _displayGraph = true;
|
||||
private bool _displayFlags = true;
|
||||
private bool _showInactive = true;
|
||||
private bool _paused = false;
|
||||
private bool _doStep = false;
|
||||
|
||||
// Layout
|
||||
private const int LineHeight = 16;
|
||||
private const int TitleHeight = 24;
|
||||
private const int TitleFontHeight = 16;
|
||||
private const int LinePadding = 2;
|
||||
private const int ColumnSpacing = 15;
|
||||
private const int FilterHeight = 24;
|
||||
private const int BottomBarHeight = FilterHeight + LineHeight;
|
||||
|
||||
// Sorting
|
||||
private List<KeyValuePair<ProfileConfig, TimingInfo>> _unsortedProfileData;
|
||||
private IComparer<KeyValuePair<ProfileConfig, TimingInfo>> _sortAction = new ProfileSorters.TagAscending();
|
||||
|
||||
// Flag data
|
||||
private long[] _timingFlagsAverages;
|
||||
private long[] _timingFlagsLast;
|
||||
|
||||
// Filtering
|
||||
private string _filterText = "";
|
||||
private bool _regexEnabled = false;
|
||||
|
||||
// Scrolling
|
||||
private float _scrollPos = 0;
|
||||
private float _minScroll = 0;
|
||||
private float _maxScroll = 0;
|
||||
|
||||
// Profile data storage
|
||||
private List<KeyValuePair<ProfileConfig, TimingInfo>> _sortedProfileData;
|
||||
private long _captureTime;
|
||||
|
||||
// Input
|
||||
private bool _backspaceDown = false;
|
||||
private bool _prevBackspaceDown = false;
|
||||
private double _backspaceDownTime = 0;
|
||||
|
||||
// F35 used as no key
|
||||
private Key _graphControlKey = Key.F35;
|
||||
|
||||
// Event management
|
||||
private double _updateTimer;
|
||||
private double _processEventTimer;
|
||||
private bool _profileUpdated = false;
|
||||
private readonly object _profileDataLock = new object();
|
||||
|
||||
public ProfileWindow()
|
||||
// Graphics mode enables 2xAA
|
||||
: base(1280, 720, new GraphicsMode(new ColorFormat(8, 8, 8, 8), 1, 1, 2))
|
||||
{
|
||||
Title = "Profiler";
|
||||
Location = new Point(DisplayDevice.Default.Width - 1280,
|
||||
(DisplayDevice.Default.Height - 720) - 50);
|
||||
|
||||
if (Profile.UpdateRate <= 0)
|
||||
{
|
||||
// Perform step regardless of flag type
|
||||
Profile.RegisterFlagReceiver((t) =>
|
||||
{
|
||||
if (!_paused)
|
||||
{
|
||||
_doStep = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Large number to force an update on first update
|
||||
_updateTimer = 0xFFFF;
|
||||
|
||||
Init();
|
||||
|
||||
// Release context for render thread
|
||||
Context.MakeCurrent(null);
|
||||
}
|
||||
|
||||
public void ToggleVisible()
|
||||
{
|
||||
_visible = !_visible;
|
||||
_visibleChanged = true;
|
||||
}
|
||||
|
||||
private void SetSort(IComparer<KeyValuePair<ProfileConfig, TimingInfo>> filter)
|
||||
{
|
||||
_sortAction = filter;
|
||||
_profileUpdated = true;
|
||||
}
|
||||
|
||||
#region OnLoad
|
||||
/// <summary>
|
||||
/// Setup OpenGL and load resources
|
||||
/// </summary>
|
||||
public void Init()
|
||||
{
|
||||
GL.ClearColor(Color.Black);
|
||||
_fontService = new FontService();
|
||||
_fontService.InitializeTextures();
|
||||
_fontService.UpdateScreenHeight(Height);
|
||||
|
||||
_buttons = new ProfileButton[(int)ButtonIndex.Count];
|
||||
_buttons[(int)ButtonIndex.TagTitle] = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.TagAscending()));
|
||||
_buttons[(int)ButtonIndex.InstantTitle] = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.InstantAscending()));
|
||||
_buttons[(int)ButtonIndex.AverageTitle] = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.AverageAscending()));
|
||||
_buttons[(int)ButtonIndex.TotalTitle] = new ProfileButton(_fontService, () => SetSort(new ProfileSorters.TotalAscending()));
|
||||
_buttons[(int)ButtonIndex.Step] = new ProfileButton(_fontService, () => _doStep = true);
|
||||
_buttons[(int)ButtonIndex.FilterBar] = new ProfileButton(_fontService, () =>
|
||||
{
|
||||
_profileUpdated = true;
|
||||
_regexEnabled = !_regexEnabled;
|
||||
});
|
||||
|
||||
_buttons[(int)ButtonIndex.ShowHideInactive] = new ProfileButton(_fontService, () =>
|
||||
{
|
||||
_profileUpdated = true;
|
||||
_showInactive = !_showInactive;
|
||||
});
|
||||
|
||||
_buttons[(int)ButtonIndex.Pause] = new ProfileButton(_fontService, () =>
|
||||
{
|
||||
_profileUpdated = true;
|
||||
_paused = !_paused;
|
||||
});
|
||||
|
||||
_buttons[(int)ButtonIndex.ToggleFlags] = new ProfileButton(_fontService, () =>
|
||||
{
|
||||
_displayFlags = !_displayFlags;
|
||||
_redrawPending = true;
|
||||
});
|
||||
|
||||
_buttons[(int)ButtonIndex.ChangeDisplay] = new ProfileButton(_fontService, () =>
|
||||
{
|
||||
_displayGraph = !_displayGraph;
|
||||
_redrawPending = true;
|
||||
});
|
||||
|
||||
Visible = _visible;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OnResize
|
||||
/// <summary>
|
||||
/// Respond to resize events
|
||||
/// </summary>
|
||||
/// <param name="e">Contains information on the new GameWindow size.</param>
|
||||
/// <remarks>There is no need to call the base implementation.</remarks>
|
||||
protected override void OnResize(EventArgs e)
|
||||
{
|
||||
_viewportUpdated = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OnClose
|
||||
/// <summary>
|
||||
/// Intercept close event and hide instead
|
||||
/// </summary>
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
// Hide window
|
||||
_visible = false;
|
||||
_visibleChanged = true;
|
||||
|
||||
// Cancel close
|
||||
e.Cancel = true;
|
||||
|
||||
base.OnClosing(e);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OnUpdateFrame
|
||||
/// <summary>
|
||||
/// Profile Update Loop
|
||||
/// </summary>
|
||||
/// <param name="e">Contains timing information.</param>
|
||||
/// <remarks>There is no need to call the base implementation.</remarks>
|
||||
public void Update(FrameEventArgs e)
|
||||
{
|
||||
if (_visibleChanged)
|
||||
{
|
||||
Visible = _visible;
|
||||
_visibleChanged = false;
|
||||
}
|
||||
|
||||
// Backspace handling
|
||||
if (_backspaceDown)
|
||||
{
|
||||
if (!_prevBackspaceDown)
|
||||
{
|
||||
_backspaceDownTime = 0;
|
||||
FilterBackspace();
|
||||
}
|
||||
else
|
||||
{
|
||||
_backspaceDownTime += e.Time;
|
||||
if (_backspaceDownTime > 0.3)
|
||||
{
|
||||
_backspaceDownTime -= 0.05;
|
||||
FilterBackspace();
|
||||
}
|
||||
}
|
||||
}
|
||||
_prevBackspaceDown = _backspaceDown;
|
||||
|
||||
// Get timing data if enough time has passed
|
||||
_updateTimer += e.Time;
|
||||
if (_doStep || ((Profile.UpdateRate > 0) && (!_paused && (_updateTimer > Profile.UpdateRate))))
|
||||
{
|
||||
_updateTimer = 0;
|
||||
_captureTime = PerformanceCounter.ElapsedTicks;
|
||||
_timingFlags = Profile.GetTimingFlags();
|
||||
_doStep = false;
|
||||
_profileUpdated = true;
|
||||
|
||||
_unsortedProfileData = Profile.GetProfilingData();
|
||||
(_timingFlagsAverages, _timingFlagsLast) = Profile.GetTimingAveragesAndLast();
|
||||
|
||||
}
|
||||
|
||||
// Filtering
|
||||
if (_profileUpdated)
|
||||
{
|
||||
lock (_profileDataLock)
|
||||
{
|
||||
_sortedProfileData = _showInactive ? _unsortedProfileData : _unsortedProfileData.FindAll(kvp => kvp.Value.IsActive);
|
||||
|
||||
if (_sortAction != null)
|
||||
{
|
||||
_sortedProfileData.Sort(_sortAction);
|
||||
}
|
||||
|
||||
if (_regexEnabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
Regex filterRegex = new Regex(_filterText, RegexOptions.IgnoreCase);
|
||||
if (_filterText != "")
|
||||
{
|
||||
_sortedProfileData = _sortedProfileData.Where((pair => filterRegex.IsMatch(pair.Key.Search))).ToList();
|
||||
}
|
||||
}
|
||||
catch (ArgumentException argException)
|
||||
{
|
||||
// Skip filtering for invalid regex
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular filtering
|
||||
_sortedProfileData = _sortedProfileData.Where((pair => pair.Key.Search.ToLower().Contains(_filterText.ToLower()))).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
_profileUpdated = false;
|
||||
_redrawPending = true;
|
||||
_initComplete = true;
|
||||
}
|
||||
|
||||
// Check for events 20 times a second
|
||||
_processEventTimer += e.Time;
|
||||
if (_processEventTimer > 0.05)
|
||||
{
|
||||
ProcessEvents();
|
||||
|
||||
if (_graphControlKey != Key.F35)
|
||||
{
|
||||
switch (_graphControlKey)
|
||||
{
|
||||
case Key.Left:
|
||||
_graphPosition += (long) (GraphMoveSpeed * e.Time);
|
||||
break;
|
||||
|
||||
case Key.Right:
|
||||
_graphPosition = Math.Max(_graphPosition - (long) (GraphMoveSpeed * e.Time), 0);
|
||||
break;
|
||||
|
||||
case Key.Up:
|
||||
_graphZoom = MathF.Min(_graphZoom + (float) (GraphZoomSpeed * e.Time), 100.0f);
|
||||
break;
|
||||
|
||||
case Key.Down:
|
||||
_graphZoom = MathF.Max(_graphZoom - (float) (GraphZoomSpeed * e.Time), 1f);
|
||||
break;
|
||||
}
|
||||
|
||||
_redrawPending = true;
|
||||
}
|
||||
|
||||
_processEventTimer = 0;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OnRenderFrame
|
||||
/// <summary>
|
||||
/// Profile Render Loop
|
||||
/// </summary>
|
||||
/// <remarks>There is no need to call the base implementation.</remarks>
|
||||
public void Draw()
|
||||
{
|
||||
if (!_visible || !_initComplete)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update viewport
|
||||
if (_viewportUpdated)
|
||||
{
|
||||
GL.Viewport(0, 0, Width, Height);
|
||||
|
||||
GL.MatrixMode(MatrixMode.Projection);
|
||||
GL.LoadIdentity();
|
||||
GL.Ortho(0, Width, 0, Height, 0.0, 4.0);
|
||||
|
||||
_fontService.UpdateScreenHeight(Height);
|
||||
|
||||
_viewportUpdated = false;
|
||||
_redrawPending = true;
|
||||
}
|
||||
|
||||
if (!_redrawPending)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Frame setup
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
GL.ClearColor(Color.Black);
|
||||
|
||||
_fontService.fontColor = Color.White;
|
||||
int verticalIndex = 0;
|
||||
|
||||
float width;
|
||||
float maxWidth = 0;
|
||||
float yOffset = _scrollPos - TitleHeight;
|
||||
float xOffset = 10;
|
||||
float timingDataLeft;
|
||||
float timingWidth;
|
||||
|
||||
// Background lines to make reading easier
|
||||
#region Background Lines
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
GL.Scissor(0, BottomBarHeight, Width, Height - TitleHeight - BottomBarHeight);
|
||||
GL.Begin(PrimitiveType.Triangles);
|
||||
GL.Color3(0.2f, 0.2f, 0.2f);
|
||||
for (int i = 0; i < _sortedProfileData.Count; i += 2)
|
||||
{
|
||||
float top = GetLineY(yOffset, LineHeight, LinePadding, false, i - 1);
|
||||
float bottom = GetLineY(yOffset, LineHeight, LinePadding, false, i);
|
||||
|
||||
// Skip rendering out of bounds bars
|
||||
if (top < 0 || bottom > Height)
|
||||
continue;
|
||||
|
||||
GL.Vertex2(0, bottom);
|
||||
GL.Vertex2(0, top);
|
||||
GL.Vertex2(Width, top);
|
||||
|
||||
GL.Vertex2(Width, top);
|
||||
GL.Vertex2(Width, bottom);
|
||||
GL.Vertex2(0, bottom);
|
||||
}
|
||||
GL.End();
|
||||
_maxScroll = (LineHeight + LinePadding) * (_sortedProfileData.Count - 1);
|
||||
#endregion
|
||||
|
||||
lock (_profileDataLock)
|
||||
{
|
||||
// Display category
|
||||
#region Category
|
||||
verticalIndex = 0;
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
if (entry.Key.Category == null)
|
||||
{
|
||||
verticalIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
width = _fontService.DrawText(entry.Key.Category, xOffset, y, LineHeight);
|
||||
|
||||
if (width > maxWidth)
|
||||
{
|
||||
maxWidth = width;
|
||||
}
|
||||
}
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
width = _fontService.DrawText("Category", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
if (width > maxWidth)
|
||||
maxWidth = width;
|
||||
|
||||
xOffset += maxWidth + ColumnSpacing;
|
||||
#endregion
|
||||
|
||||
// Display session group
|
||||
#region Session Group
|
||||
maxWidth = 0;
|
||||
verticalIndex = 0;
|
||||
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
if (entry.Key.SessionGroup == null)
|
||||
{
|
||||
verticalIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
width = _fontService.DrawText(entry.Key.SessionGroup, xOffset, y, LineHeight);
|
||||
|
||||
if (width > maxWidth)
|
||||
{
|
||||
maxWidth = width;
|
||||
}
|
||||
}
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
width = _fontService.DrawText("Group", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
if (width > maxWidth)
|
||||
maxWidth = width;
|
||||
|
||||
xOffset += maxWidth + ColumnSpacing;
|
||||
#endregion
|
||||
|
||||
// Display session item
|
||||
#region Session Item
|
||||
maxWidth = 0;
|
||||
verticalIndex = 0;
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
if (entry.Key.SessionItem == null)
|
||||
{
|
||||
verticalIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
width = _fontService.DrawText(entry.Key.SessionItem, xOffset, y, LineHeight);
|
||||
|
||||
if (width > maxWidth)
|
||||
{
|
||||
maxWidth = width;
|
||||
}
|
||||
}
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
width = _fontService.DrawText("Item", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
if (width > maxWidth)
|
||||
maxWidth = width;
|
||||
|
||||
xOffset += maxWidth + ColumnSpacing;
|
||||
_buttons[(int)ButtonIndex.TagTitle].UpdateSize(0, Height - TitleFontHeight, 0, (int)xOffset, TitleFontHeight);
|
||||
#endregion
|
||||
|
||||
// Timing data
|
||||
timingWidth = Width - xOffset - 370;
|
||||
timingDataLeft = xOffset;
|
||||
|
||||
GL.Scissor((int)xOffset, BottomBarHeight, (int)timingWidth, Height - TitleHeight - BottomBarHeight);
|
||||
|
||||
if (_displayGraph)
|
||||
{
|
||||
DrawGraph(xOffset, yOffset, timingWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawBars(xOffset, yOffset, timingWidth);
|
||||
}
|
||||
|
||||
GL.Scissor(0, BottomBarHeight, Width, Height - TitleHeight - BottomBarHeight);
|
||||
|
||||
if (!_displayGraph)
|
||||
{
|
||||
_fontService.DrawText("Blue: Instant, Green: Avg, Red: Total", xOffset, Height - TitleFontHeight, TitleFontHeight);
|
||||
}
|
||||
|
||||
xOffset = Width - 360;
|
||||
|
||||
// Display timestamps
|
||||
#region Timestamps
|
||||
verticalIndex = 0;
|
||||
long totalInstant = 0;
|
||||
long totalAverage = 0;
|
||||
long totalTime = 0;
|
||||
long totalCount = 0;
|
||||
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
float y = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
|
||||
_fontService.DrawText($"{GetTimeString(entry.Value.Instant)} ({entry.Value.InstantCount})", xOffset, y, LineHeight);
|
||||
|
||||
_fontService.DrawText(GetTimeString(entry.Value.AverageTime), 150 + xOffset, y, LineHeight);
|
||||
|
||||
_fontService.DrawText(GetTimeString(entry.Value.TotalTime), 260 + xOffset, y, LineHeight);
|
||||
|
||||
totalInstant += entry.Value.Instant;
|
||||
totalAverage += entry.Value.AverageTime;
|
||||
totalTime += entry.Value.TotalTime;
|
||||
totalCount += entry.Value.InstantCount;
|
||||
}
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
float yHeight = Height - TitleFontHeight;
|
||||
|
||||
_fontService.DrawText("Instant (Count)", xOffset, yHeight, TitleFontHeight);
|
||||
_buttons[(int)ButtonIndex.InstantTitle].UpdateSize((int)xOffset, (int)yHeight, 0, 130, TitleFontHeight);
|
||||
|
||||
_fontService.DrawText("Average", 150 + xOffset, yHeight, TitleFontHeight);
|
||||
_buttons[(int)ButtonIndex.AverageTitle].UpdateSize((int)(150 + xOffset), (int)yHeight, 0, 130, TitleFontHeight);
|
||||
|
||||
_fontService.DrawText("Total (ms)", 260 + xOffset, yHeight, TitleFontHeight);
|
||||
_buttons[(int)ButtonIndex.TotalTitle].UpdateSize((int)(260 + xOffset), (int)yHeight, 0, Width, TitleFontHeight);
|
||||
|
||||
// Totals
|
||||
yHeight = FilterHeight + 3;
|
||||
int textHeight = LineHeight - 2;
|
||||
|
||||
_fontService.fontColor = new Color(100, 100, 255, 255);
|
||||
float tempWidth = _fontService.DrawText($"Host {GetTimeString(_timingFlagsLast[(int)TimingFlagType.SystemFrame])} " +
|
||||
$"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.SystemFrame])})", 5, yHeight, textHeight);
|
||||
|
||||
_fontService.fontColor = Color.Red;
|
||||
_fontService.DrawText($"Game {GetTimeString(_timingFlagsLast[(int)TimingFlagType.FrameSwap])} " +
|
||||
$"({GetTimeString(_timingFlagsAverages[(int)TimingFlagType.FrameSwap])})", 15 + tempWidth, yHeight, textHeight);
|
||||
_fontService.fontColor = Color.White;
|
||||
|
||||
|
||||
_fontService.DrawText($"{GetTimeString(totalInstant)} ({totalCount})", xOffset, yHeight, textHeight);
|
||||
_fontService.DrawText(GetTimeString(totalAverage), 150 + xOffset, yHeight, textHeight);
|
||||
_fontService.DrawText(GetTimeString(totalTime), 260 + xOffset, yHeight, textHeight);
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Bottom bar
|
||||
// Show/Hide Inactive
|
||||
float widthShowHideButton = _buttons[(int)ButtonIndex.ShowHideInactive].UpdateSize($"{(_showInactive ? "Hide" : "Show")} Inactive", 5, 5, 4, 16);
|
||||
|
||||
// Play/Pause
|
||||
float widthPlayPauseButton = _buttons[(int)ButtonIndex.Pause].UpdateSize(_paused ? "Play" : "Pause", 15 + (int)widthShowHideButton, 5, 4, 16) + widthShowHideButton;
|
||||
|
||||
// Step
|
||||
float widthStepButton = widthPlayPauseButton;
|
||||
|
||||
if (_paused)
|
||||
{
|
||||
widthStepButton += _buttons[(int)ButtonIndex.Step].UpdateSize("Step", (int)(25 + widthPlayPauseButton), 5, 4, 16) + 10;
|
||||
_buttons[(int)ButtonIndex.Step].Draw();
|
||||
}
|
||||
|
||||
// Change display
|
||||
float widthChangeDisplay = _buttons[(int)ButtonIndex.ChangeDisplay].UpdateSize($"View: {(_displayGraph ? "Graph" : "Bars")}", 25 + (int)widthStepButton, 5, 4, 16) + widthStepButton;
|
||||
|
||||
width = widthChangeDisplay;
|
||||
|
||||
if (_displayGraph)
|
||||
{
|
||||
width += _buttons[(int) ButtonIndex.ToggleFlags].UpdateSize($"{(_displayFlags ? "Hide" : "Show")} Flags", 35 + (int)widthChangeDisplay, 5, 4, 16) + 10;
|
||||
_buttons[(int)ButtonIndex.ToggleFlags].Draw();
|
||||
}
|
||||
|
||||
// Filter bar
|
||||
_fontService.DrawText($"{(_regexEnabled ? "Regex " : "Filter")}: {_filterText}", 35 + width, 7, 16);
|
||||
_buttons[(int)ButtonIndex.FilterBar].UpdateSize((int)(45 + width), 0, 0, Width, FilterHeight);
|
||||
#endregion
|
||||
|
||||
// Draw buttons
|
||||
for (int i = 0; i < (int)ButtonIndex.Autodraw; i++)
|
||||
{
|
||||
_buttons[i].Draw();
|
||||
}
|
||||
|
||||
// Dividing lines
|
||||
#region Dividing lines
|
||||
GL.Color3(Color.White);
|
||||
GL.Begin(PrimitiveType.Lines);
|
||||
// Top divider
|
||||
GL.Vertex2(0, Height -TitleHeight);
|
||||
GL.Vertex2(Width, Height - TitleHeight);
|
||||
|
||||
// Bottom divider
|
||||
GL.Vertex2(0, FilterHeight);
|
||||
GL.Vertex2(Width, FilterHeight);
|
||||
|
||||
GL.Vertex2(0, BottomBarHeight);
|
||||
GL.Vertex2(Width, BottomBarHeight);
|
||||
|
||||
// Bottom vertical dividers
|
||||
GL.Vertex2(widthShowHideButton + 10, 0);
|
||||
GL.Vertex2(widthShowHideButton + 10, FilterHeight);
|
||||
|
||||
GL.Vertex2(widthPlayPauseButton + 20, 0);
|
||||
GL.Vertex2(widthPlayPauseButton + 20, FilterHeight);
|
||||
|
||||
if (_paused)
|
||||
{
|
||||
GL.Vertex2(widthStepButton + 20, 0);
|
||||
GL.Vertex2(widthStepButton + 20, FilterHeight);
|
||||
}
|
||||
|
||||
if (_displayGraph)
|
||||
{
|
||||
GL.Vertex2(widthChangeDisplay + 30, 0);
|
||||
GL.Vertex2(widthChangeDisplay + 30, FilterHeight);
|
||||
}
|
||||
|
||||
GL.Vertex2(width + 30, 0);
|
||||
GL.Vertex2(width + 30, FilterHeight);
|
||||
|
||||
// Column dividers
|
||||
float timingDataTop = Height - TitleHeight;
|
||||
|
||||
GL.Vertex2(timingDataLeft, FilterHeight);
|
||||
GL.Vertex2(timingDataLeft, timingDataTop);
|
||||
|
||||
GL.Vertex2(timingWidth + timingDataLeft, FilterHeight);
|
||||
GL.Vertex2(timingWidth + timingDataLeft, timingDataTop);
|
||||
GL.End();
|
||||
#endregion
|
||||
|
||||
_redrawPending = false;
|
||||
SwapBuffers();
|
||||
}
|
||||
#endregion
|
||||
|
||||
private string GetTimeString(long timestamp)
|
||||
{
|
||||
float time = (float)timestamp / PerformanceCounter.TicksPerMillisecond;
|
||||
return (time < 1) ? $"{time * 1000:F3}us" : $"{time:F3}ms";
|
||||
}
|
||||
|
||||
private void FilterBackspace()
|
||||
{
|
||||
if (_filterText.Length <= 1)
|
||||
{
|
||||
_filterText = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
_filterText = _filterText.Remove(_filterText.Length - 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private float GetLineY(float offset, float lineHeight, float padding, bool centre, int line)
|
||||
{
|
||||
return Height + offset - lineHeight - padding - ((lineHeight + padding) * line) + ((centre) ? padding : 0);
|
||||
}
|
||||
|
||||
protected override void OnKeyPress(KeyPressEventArgs e)
|
||||
{
|
||||
_filterText += e.KeyChar;
|
||||
_profileUpdated = true;
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyboardKeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.BackSpace:
|
||||
_profileUpdated = _backspaceDown = true;
|
||||
return;
|
||||
|
||||
case Key.Left:
|
||||
case Key.Right:
|
||||
case Key.Up:
|
||||
case Key.Down:
|
||||
_graphControlKey = e.Key;
|
||||
return;
|
||||
}
|
||||
base.OnKeyUp(e);
|
||||
}
|
||||
|
||||
protected override void OnKeyUp(KeyboardKeyEventArgs e)
|
||||
{
|
||||
// Can't go into switch as value isn't constant
|
||||
if (e.Key == Profile.Controls.Buttons.ToggleProfiler)
|
||||
{
|
||||
ToggleVisible();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.BackSpace:
|
||||
_backspaceDown = false;
|
||||
return;
|
||||
|
||||
case Key.Left:
|
||||
case Key.Right:
|
||||
case Key.Up:
|
||||
case Key.Down:
|
||||
_graphControlKey = Key.F35;
|
||||
return;
|
||||
}
|
||||
base.OnKeyUp(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseButtonEventArgs e)
|
||||
{
|
||||
foreach (ProfileButton button in _buttons)
|
||||
{
|
||||
if (button.ProcessClick(e.X, Height - e.Y))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
_scrollPos += e.Delta * -30;
|
||||
if (_scrollPos < _minScroll)
|
||||
_scrollPos = _minScroll;
|
||||
if (_scrollPos > _maxScroll)
|
||||
_scrollPos = _maxScroll;
|
||||
|
||||
_redrawPending = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace Ryujinx.Profiler.UI
|
||||
{
|
||||
public partial class ProfileWindow
|
||||
{
|
||||
private void DrawBars(float xOffset, float yOffset, float width)
|
||||
{
|
||||
if (_sortedProfileData.Count != 0)
|
||||
{
|
||||
long maxAverage;
|
||||
long maxTotal;
|
||||
|
||||
int verticalIndex = 0;
|
||||
float barHeight = (LineHeight - LinePadding) / 3.0f;
|
||||
|
||||
// Get max values
|
||||
long maxInstant = maxAverage = maxTotal = 0;
|
||||
foreach (KeyValuePair<ProfileConfig, TimingInfo> kvp in _sortedProfileData)
|
||||
{
|
||||
maxInstant = Math.Max(maxInstant, kvp.Value.Instant);
|
||||
maxAverage = Math.Max(maxAverage, kvp.Value.AverageTime);
|
||||
maxTotal = Math.Max(maxTotal, kvp.Value.TotalTime);
|
||||
}
|
||||
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
GL.Begin(PrimitiveType.Triangles);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
// Instant
|
||||
GL.Color3(Color.Blue);
|
||||
float bottom = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex++);
|
||||
float top = bottom + barHeight;
|
||||
float right = (float)entry.Value.Instant / maxInstant * width + xOffset;
|
||||
|
||||
// Skip rendering out of bounds bars
|
||||
if (top < 0 || bottom > Height)
|
||||
continue;
|
||||
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
GL.Vertex2(xOffset, top);
|
||||
GL.Vertex2(right, top);
|
||||
|
||||
GL.Vertex2(right, top);
|
||||
GL.Vertex2(right, bottom);
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
|
||||
// Average
|
||||
GL.Color3(Color.Green);
|
||||
top += barHeight;
|
||||
bottom += barHeight;
|
||||
right = (float)entry.Value.AverageTime / maxAverage * width + xOffset;
|
||||
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
GL.Vertex2(xOffset, top);
|
||||
GL.Vertex2(right, top);
|
||||
|
||||
GL.Vertex2(right, top);
|
||||
GL.Vertex2(right, bottom);
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
|
||||
// Total
|
||||
GL.Color3(Color.Red);
|
||||
top += barHeight;
|
||||
bottom += barHeight;
|
||||
right = (float)entry.Value.TotalTime / maxTotal * width + xOffset;
|
||||
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
GL.Vertex2(xOffset, top);
|
||||
GL.Vertex2(right, top);
|
||||
|
||||
GL.Vertex2(right, top);
|
||||
GL.Vertex2(right, bottom);
|
||||
GL.Vertex2(xOffset, bottom);
|
||||
}
|
||||
|
||||
GL.End();
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
using System;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common;
|
||||
|
||||
namespace Ryujinx.Profiler.UI
|
||||
{
|
||||
public partial class ProfileWindow
|
||||
{
|
||||
// Color index equal to timing flag type as int
|
||||
private Color[] _timingFlagColors = new[]
|
||||
{
|
||||
new Color(150, 25, 25, 50), // FrameSwap = 0
|
||||
new Color(25, 25, 150, 50), // SystemFrame = 1
|
||||
};
|
||||
|
||||
private TimingFlag[] _timingFlags;
|
||||
|
||||
private const float GraphMoveSpeed = 40000;
|
||||
private const float GraphZoomSpeed = 50;
|
||||
|
||||
private float _graphZoom = 1;
|
||||
private float _graphPosition = 0;
|
||||
|
||||
private void DrawGraph(float xOffset, float yOffset, float width)
|
||||
{
|
||||
if (_sortedProfileData.Count != 0)
|
||||
{
|
||||
int left, right;
|
||||
float top, bottom;
|
||||
|
||||
int verticalIndex = 0;
|
||||
float graphRight = xOffset + width;
|
||||
float barHeight = (LineHeight - LinePadding);
|
||||
long history = Profile.HistoryLength;
|
||||
double timeWidthTicks = history / (double)_graphZoom;
|
||||
long graphPositionTicks = (long)(_graphPosition * PerformanceCounter.TicksPerMillisecond);
|
||||
long ticksPerPixel = (long)(timeWidthTicks / width);
|
||||
|
||||
// Reset start point if out of bounds
|
||||
if (timeWidthTicks + graphPositionTicks > history)
|
||||
{
|
||||
graphPositionTicks = history - (long)timeWidthTicks;
|
||||
_graphPosition = (float)graphPositionTicks / PerformanceCounter.TicksPerMillisecond;
|
||||
}
|
||||
|
||||
graphPositionTicks = _captureTime - graphPositionTicks;
|
||||
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
|
||||
// Draw timing flags
|
||||
if (_displayFlags)
|
||||
{
|
||||
TimingFlagType prevType = TimingFlagType.Count;
|
||||
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
|
||||
GL.Begin(PrimitiveType.Lines);
|
||||
foreach (TimingFlag timingFlag in _timingFlags)
|
||||
{
|
||||
if (prevType != timingFlag.FlagType)
|
||||
{
|
||||
prevType = timingFlag.FlagType;
|
||||
GL.Color4(_timingFlagColors[(int)prevType]);
|
||||
}
|
||||
|
||||
int x = (int)(graphRight - ((graphPositionTicks - timingFlag.Timestamp) / timeWidthTicks) * width);
|
||||
GL.Vertex2(x, 0);
|
||||
GL.Vertex2(x, Height);
|
||||
}
|
||||
GL.End();
|
||||
GL.Disable(EnableCap.Blend);
|
||||
}
|
||||
|
||||
// Draw bars
|
||||
GL.Begin(PrimitiveType.Triangles);
|
||||
foreach (var entry in _sortedProfileData)
|
||||
{
|
||||
long furthest = 0;
|
||||
|
||||
bottom = GetLineY(yOffset, LineHeight, LinePadding, true, verticalIndex);
|
||||
top = bottom + barHeight;
|
||||
|
||||
// Skip rendering out of bounds bars
|
||||
if (top < 0 || bottom > Height)
|
||||
{
|
||||
verticalIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
GL.Color3(Color.Green);
|
||||
foreach (Timestamp timestamp in entry.Value.GetAllTimestamps())
|
||||
{
|
||||
// Skip drawing multiple timestamps on same pixel
|
||||
if (timestamp.EndTime < furthest)
|
||||
continue;
|
||||
furthest = timestamp.EndTime + ticksPerPixel;
|
||||
|
||||
left = (int)(graphRight - ((graphPositionTicks - timestamp.BeginTime) / timeWidthTicks) * width);
|
||||
right = (int)(graphRight - ((graphPositionTicks - timestamp.EndTime) / timeWidthTicks) * width);
|
||||
|
||||
// Make sure width is at least 1px
|
||||
right = Math.Max(left + 1, right);
|
||||
|
||||
GL.Vertex2(left, bottom);
|
||||
GL.Vertex2(left, top);
|
||||
GL.Vertex2(right, top);
|
||||
|
||||
GL.Vertex2(right, top);
|
||||
GL.Vertex2(right, bottom);
|
||||
GL.Vertex2(left, bottom);
|
||||
}
|
||||
|
||||
// Currently capturing timestamp
|
||||
GL.Color3(Color.Red);
|
||||
long entryBegin = entry.Value.BeginTime;
|
||||
if (entryBegin != -1)
|
||||
{
|
||||
left = (int)(graphRight - ((graphPositionTicks - entryBegin) / timeWidthTicks) * width);
|
||||
|
||||
// Make sure width is at least 1px
|
||||
left = Math.Min(left - 1, (int)graphRight);
|
||||
|
||||
GL.Vertex2(left, bottom);
|
||||
GL.Vertex2(left, top);
|
||||
GL.Vertex2(graphRight, top);
|
||||
|
||||
GL.Vertex2(graphRight, top);
|
||||
GL.Vertex2(graphRight, bottom);
|
||||
GL.Vertex2(left, bottom);
|
||||
}
|
||||
|
||||
verticalIndex++;
|
||||
}
|
||||
|
||||
GL.End();
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
string label = $"-{MathF.Round(_graphPosition, 2)} ms";
|
||||
|
||||
// Dummy draw for measure
|
||||
float labelWidth = _fontService.DrawText(label, 0, 0, LineHeight, false);
|
||||
_fontService.DrawText(label, graphRight - labelWidth - LinePadding, FilterHeight + LinePadding, LineHeight);
|
||||
|
||||
_fontService.DrawText($"-{MathF.Round((float)((timeWidthTicks / PerformanceCounter.TicksPerMillisecond) + _graphPosition), 2)} ms", xOffset + LinePadding, FilterHeight + LinePadding, LineHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
using System.Threading;
|
||||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
using Ryujinx.Common;
|
||||
|
||||
namespace Ryujinx.Profiler.UI
|
||||
{
|
||||
public class ProfileWindowManager
|
||||
{
|
||||
private ProfileWindow _window;
|
||||
private Thread _profileThread;
|
||||
private Thread _renderThread;
|
||||
private bool _profilerRunning;
|
||||
|
||||
// Timing
|
||||
private double _prevTime;
|
||||
|
||||
public ProfileWindowManager()
|
||||
{
|
||||
if (Profile.ProfilingEnabled())
|
||||
{
|
||||
_profilerRunning = true;
|
||||
_prevTime = 0;
|
||||
_profileThread = new Thread(ProfileLoop)
|
||||
{
|
||||
Name = "Profiler.ProfileThread"
|
||||
};
|
||||
_profileThread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleVisible()
|
||||
{
|
||||
if (Profile.ProfilingEnabled())
|
||||
{
|
||||
_window.ToggleVisible();
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (_window != null)
|
||||
{
|
||||
_profilerRunning = false;
|
||||
_window.Close();
|
||||
_window.Dispose();
|
||||
}
|
||||
|
||||
_window = null;
|
||||
}
|
||||
|
||||
public void UpdateKeyInput(KeyboardState keyboard)
|
||||
{
|
||||
if (Profile.Controls.TogglePressed(keyboard))
|
||||
{
|
||||
ToggleVisible();
|
||||
}
|
||||
Profile.Controls.SetPrevKeyboardState(keyboard);
|
||||
}
|
||||
|
||||
private void ProfileLoop()
|
||||
{
|
||||
using (_window = new ProfileWindow())
|
||||
{
|
||||
// Create thread for render loop
|
||||
_renderThread = new Thread(RenderLoop)
|
||||
{
|
||||
Name = "Profiler.RenderThread"
|
||||
};
|
||||
_renderThread.Start();
|
||||
|
||||
while (_profilerRunning)
|
||||
{
|
||||
double time = (double)PerformanceCounter.ElapsedTicks / PerformanceCounter.TicksPerSecond;
|
||||
_window.Update(new FrameEventArgs(time - _prevTime));
|
||||
_prevTime = time;
|
||||
|
||||
// Sleep to be less taxing, update usually does very little
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderLoop()
|
||||
{
|
||||
_window.Context.MakeCurrent(_window.WindowInfo);
|
||||
|
||||
while (_profilerRunning)
|
||||
{
|
||||
_window.Draw();
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using SharpFont;
|
||||
|
||||
namespace Ryujinx.Profiler.UI.SharpFontHelpers
|
||||
{
|
||||
public class FontService
|
||||
{
|
||||
private struct CharacterInfo
|
||||
{
|
||||
public float Left;
|
||||
public float Right;
|
||||
public float Top;
|
||||
public float Bottom;
|
||||
|
||||
public int Width;
|
||||
public float Height;
|
||||
|
||||
public float AspectRatio;
|
||||
|
||||
public float BearingX;
|
||||
public float BearingY;
|
||||
public float Advance;
|
||||
}
|
||||
|
||||
private const int SheetWidth = 1024;
|
||||
private const int SheetHeight = 512;
|
||||
private int ScreenWidth, ScreenHeight;
|
||||
private int CharacterTextureSheet;
|
||||
private CharacterInfo[] characters;
|
||||
|
||||
public Color fontColor { get; set; } = Color.Black;
|
||||
|
||||
private string GetFontPath()
|
||||
{
|
||||
string fontFolder = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
|
||||
|
||||
// Only uses Arial, add more fonts here if wanted
|
||||
string path = Path.Combine(fontFolder, "arial.ttf");
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
throw new Exception($"Profiler exception. Required font Courier New or Arial not installed to {fontFolder}");
|
||||
}
|
||||
|
||||
public void InitializeTextures()
|
||||
{
|
||||
// Create and init some vars
|
||||
uint[] rawCharacterSheet = new uint[SheetWidth * SheetHeight];
|
||||
int x;
|
||||
int y;
|
||||
int lineOffset;
|
||||
int maxHeight;
|
||||
|
||||
x = y = lineOffset = maxHeight = 0;
|
||||
characters = new CharacterInfo[94];
|
||||
|
||||
// Get font
|
||||
var font = new FontFace(File.OpenRead(GetFontPath()));
|
||||
|
||||
// Update raw data for each character
|
||||
for (int i = 0; i < 94; i++)
|
||||
{
|
||||
var surface = RenderSurface((char)(i + 33), font, out float xBearing, out float yBearing, out float advance);
|
||||
|
||||
characters[i] = UpdateTexture(surface, ref rawCharacterSheet, ref x, ref y, ref lineOffset);
|
||||
characters[i].BearingX = xBearing;
|
||||
characters[i].BearingY = yBearing;
|
||||
characters[i].Advance = advance;
|
||||
|
||||
if (maxHeight < characters[i].Height)
|
||||
maxHeight = (int)characters[i].Height;
|
||||
}
|
||||
|
||||
// Fix height for characters shorter than line height
|
||||
for (int i = 0; i < 94; i++)
|
||||
{
|
||||
characters[i].BearingX /= characters[i].Width;
|
||||
characters[i].BearingY /= maxHeight;
|
||||
characters[i].Advance /= characters[i].Width;
|
||||
characters[i].Height /= maxHeight;
|
||||
characters[i].AspectRatio = (float)characters[i].Width / maxHeight;
|
||||
}
|
||||
|
||||
// Convert raw data into texture
|
||||
CharacterTextureSheet = GL.GenTexture();
|
||||
GL.BindTexture(TextureTarget.Texture2D, CharacterTextureSheet);
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Clamp);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Clamp);
|
||||
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, SheetWidth, SheetHeight, 0, PixelFormat.Rgba, PixelType.UnsignedInt8888, rawCharacterSheet);
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
}
|
||||
|
||||
public void UpdateScreenHeight(int height)
|
||||
{
|
||||
ScreenHeight = height;
|
||||
}
|
||||
|
||||
public float DrawText(string text, float x, float y, float height, bool draw = true)
|
||||
{
|
||||
float originalX = x;
|
||||
|
||||
// Skip out of bounds draw
|
||||
if (y < height * -2 || y > ScreenHeight + height * 2)
|
||||
{
|
||||
draw = false;
|
||||
}
|
||||
|
||||
if (draw)
|
||||
{
|
||||
// Use font map texture
|
||||
GL.BindTexture(TextureTarget.Texture2D, CharacterTextureSheet);
|
||||
|
||||
// Enable blending and textures
|
||||
GL.Enable(EnableCap.Texture2D);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
|
||||
// Draw all characters
|
||||
GL.Begin(PrimitiveType.Triangles);
|
||||
GL.Color4(fontColor);
|
||||
}
|
||||
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == ' ')
|
||||
{
|
||||
x += height / 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
CharacterInfo charInfo = characters[text[i] - 33];
|
||||
float width = (charInfo.AspectRatio * height);
|
||||
x += (charInfo.BearingX * charInfo.AspectRatio) * width;
|
||||
float right = x + width;
|
||||
if (draw)
|
||||
{
|
||||
DrawChar(charInfo, x, right, y + height * (charInfo.Height - charInfo.BearingY), y - height * charInfo.BearingY);
|
||||
}
|
||||
x = right + charInfo.Advance * charInfo.AspectRatio + 1;
|
||||
}
|
||||
|
||||
if (draw)
|
||||
{
|
||||
GL.End();
|
||||
|
||||
// Cleanup for caller
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
GL.Disable(EnableCap.Texture2D);
|
||||
GL.Disable(EnableCap.Blend);
|
||||
}
|
||||
|
||||
// Return width of rendered text
|
||||
return x - originalX;
|
||||
}
|
||||
|
||||
private void DrawChar(CharacterInfo charInfo, float left, float right, float top, float bottom)
|
||||
{
|
||||
GL.TexCoord2(charInfo.Left, charInfo.Bottom); GL.Vertex2(left, bottom);
|
||||
GL.TexCoord2(charInfo.Left, charInfo.Top); GL.Vertex2(left, top);
|
||||
GL.TexCoord2(charInfo.Right, charInfo.Top); GL.Vertex2(right, top);
|
||||
|
||||
GL.TexCoord2(charInfo.Right, charInfo.Top); GL.Vertex2(right, top);
|
||||
GL.TexCoord2(charInfo.Right, charInfo.Bottom); GL.Vertex2(right, bottom);
|
||||
GL.TexCoord2(charInfo.Left, charInfo.Bottom); GL.Vertex2(left, bottom);
|
||||
}
|
||||
|
||||
public unsafe Surface RenderSurface(char c, FontFace font, out float xBearing, out float yBearing, out float advance)
|
||||
{
|
||||
var glyph = font.GetGlyph(c, 64);
|
||||
xBearing = glyph.HorizontalMetrics.Bearing.X;
|
||||
yBearing = glyph.RenderHeight - glyph.HorizontalMetrics.Bearing.Y;
|
||||
advance = glyph.HorizontalMetrics.Advance;
|
||||
|
||||
var surface = new Surface
|
||||
{
|
||||
Bits = Marshal.AllocHGlobal(glyph.RenderWidth * glyph.RenderHeight),
|
||||
Width = glyph.RenderWidth,
|
||||
Height = glyph.RenderHeight,
|
||||
Pitch = glyph.RenderWidth
|
||||
};
|
||||
|
||||
var stuff = (byte*)surface.Bits;
|
||||
for (int i = 0; i < surface.Width * surface.Height; i++)
|
||||
*stuff++ = 0;
|
||||
|
||||
glyph.RenderTo(surface);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
private CharacterInfo UpdateTexture(Surface surface, ref uint[] rawCharMap, ref int posX, ref int posY, ref int lineOffset)
|
||||
{
|
||||
int width = surface.Width;
|
||||
int height = surface.Height;
|
||||
int len = width * height;
|
||||
byte[] data = new byte[len];
|
||||
|
||||
// Get character bitmap
|
||||
Marshal.Copy(surface.Bits, data, 0, len);
|
||||
|
||||
// Find a slot
|
||||
if (posX + width > SheetWidth)
|
||||
{
|
||||
posX = 0;
|
||||
posY += lineOffset;
|
||||
lineOffset = 0;
|
||||
}
|
||||
|
||||
// Update lineOffset
|
||||
if (lineOffset < height)
|
||||
{
|
||||
lineOffset = height + 1;
|
||||
}
|
||||
|
||||
// Copy char to sheet
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
int destOffset = (y + posY) * SheetWidth + posX;
|
||||
int sourceOffset = y * width;
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
rawCharMap[destOffset + x] = (uint)((0xFFFFFF << 8) | data[sourceOffset + x]);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate character info
|
||||
CharacterInfo charInfo = new CharacterInfo()
|
||||
{
|
||||
Left = (float)posX / SheetWidth,
|
||||
Right = (float)(posX + width) / SheetWidth,
|
||||
Top = (float)(posY - 1) / SheetHeight,
|
||||
Bottom = (float)(posY + height) / SheetHeight,
|
||||
Width = width,
|
||||
Height = height,
|
||||
};
|
||||
|
||||
// Update x
|
||||
posX += width + 1;
|
||||
|
||||
// Give the memory back
|
||||
Marshal.FreeHGlobal(surface.Bits);
|
||||
return charInfo;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,12 +12,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
22
Ryujinx.sln
22
Ryujinx.sln
|
@ -10,9 +10,6 @@ EndProject
|
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Unicorn", "Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj", "{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE", "Ryujinx.HLE\Ryujinx.HLE.csproj", "{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34} = {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "Ryujinx.Audio\Ryujinx.Audio.csproj", "{5C1D818E-682A-46A5-9D54-30006E26C270}"
|
||||
EndProject
|
||||
|
@ -22,8 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luea", "Ryujinx.LLE\Luea.cs
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "Ryujinx.Common\Ryujinx.Common.csproj", "{5FD4E4F6-8928-4B3C-BE07-28A675C17226}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Profiler", "Ryujinx.Profiler\Ryujinx.Profiler.csproj", "{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Gpu", "Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj", "{ADA7EA87-0D63-4D97-9433-922A2124401F}"
|
||||
|
@ -37,6 +32,7 @@ EndProject
|
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Nvdec", "Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Debugger", "Ryujinx.Debugger\Ryujinx.Debugger.csproj", "{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -110,14 +106,6 @@ Global
|
|||
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU
|
||||
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU
|
||||
{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU
|
||||
{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU
|
||||
{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU
|
||||
{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
@ -174,6 +162,14 @@ Global
|
|||
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU
|
||||
{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU
|
||||
{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU
|
||||
{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU
|
||||
{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using Gtk;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Configuration;
|
||||
using Ryujinx.Profiler;
|
||||
using Ryujinx.Debugger.Profiler;
|
||||
using Ryujinx.Ui;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Release|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile Debug|AnyCPU'">
|
||||
<DefineConstants>TRACE;USE_PROFILING</DefineConstants>
|
||||
<DefineConstants>TRACE;USE_DEBUGGING</DefineConstants>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -72,7 +72,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.147" />
|
||||
<PackageReference Include="GtkSharp" Version="3.22.25.24" />
|
||||
<PackageReference Include="GtkSharp" Version="3.22.25.56" />
|
||||
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||
<PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
|
||||
</ItemGroup>
|
||||
|
@ -80,8 +80,8 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Debugger\Ryujinx.Debugger.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Profiler\Ryujinx.Profiler.csproj" />
|
||||
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
|
||||
|
|
|
@ -5,8 +5,6 @@ using Ryujinx.Configuration;
|
|||
using Ryujinx.Graphics.OpenGL;
|
||||
using Ryujinx.HLE;
|
||||
using Ryujinx.HLE.Input;
|
||||
using Ryujinx.Profiler.UI;
|
||||
using Ryujinx.Ui;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
|
@ -41,10 +39,6 @@ namespace Ryujinx.Ui
|
|||
|
||||
private string _newTitle;
|
||||
|
||||
#if USE_PROFILING
|
||||
private ProfileWindowManager _profileWindow;
|
||||
#endif
|
||||
|
||||
public GlScreen(Switch device)
|
||||
: base(1280, 720,
|
||||
new GraphicsMode(), "Ryujinx", 0,
|
||||
|
@ -65,11 +59,6 @@ namespace Ryujinx.Ui
|
|||
Location = new Point(
|
||||
(DisplayDevice.Default.Width / 2) - (Width / 2),
|
||||
(DisplayDevice.Default.Height / 2) - (Height / 2));
|
||||
|
||||
#if USE_PROFILING
|
||||
// Start profile window, it will handle itself from there
|
||||
_profileWindow = new ProfileWindowManager();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void RenderLoop()
|
||||
|
@ -171,11 +160,6 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
KeyboardState keyboard = _keyboard.Value;
|
||||
|
||||
#if USE_PROFILING
|
||||
// Profiler input, lets the profiler get access to the main windows keyboard state
|
||||
_profileWindow.UpdateKeyInput(keyboard);
|
||||
#endif
|
||||
|
||||
// Normal Input
|
||||
currentHotkeyButtons = KeyboardControls.GetHotkeyButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
|
||||
currentButton = KeyboardControls.GetButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
|
||||
|
@ -330,10 +314,6 @@ namespace Ryujinx.Ui
|
|||
|
||||
protected override void OnUnload(EventArgs e)
|
||||
{
|
||||
#if USE_PROFILING
|
||||
_profileWindow.Close();
|
||||
#endif
|
||||
|
||||
_renderThread.Join();
|
||||
|
||||
base.OnUnload(e);
|
||||
|
|
|
@ -3,11 +3,12 @@ using JsonPrettyPrinterPlus;
|
|||
using Ryujinx.Audio;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Configuration;
|
||||
using Ryujinx.Debugger.Profiler;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.OpenGL;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.FileSystem.Content;
|
||||
using Ryujinx.Profiler;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
@ -36,9 +37,12 @@ namespace Ryujinx.Ui
|
|||
private static bool _updatingGameTable;
|
||||
private static bool _gameLoaded;
|
||||
private static bool _ending;
|
||||
private static bool _debuggerOpened;
|
||||
|
||||
private static TreeView _treeView;
|
||||
|
||||
private static Debugger.Debugger _debugger;
|
||||
|
||||
#pragma warning disable CS0649
|
||||
#pragma warning disable IDE0044
|
||||
[GUI] Window _mainWin;
|
||||
|
@ -61,6 +65,8 @@ namespace Ryujinx.Ui
|
|||
[GUI] Label _progressLabel;
|
||||
[GUI] Label _firmwareVersionLabel;
|
||||
[GUI] LevelBar _progressBar;
|
||||
[GUI] MenuItem _openDebugger;
|
||||
[GUI] MenuItem _toolsMenu;
|
||||
#pragma warning restore CS0649
|
||||
#pragma warning restore IDE0044
|
||||
|
||||
|
@ -118,6 +124,13 @@ namespace Ryujinx.Ui
|
|||
if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _fileSizeToggle.Active = true;
|
||||
if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _pathToggle.Active = true;
|
||||
|
||||
#if USE_DEBUGGING
|
||||
_debugger = new Debugger.Debugger();
|
||||
_openDebugger.Activated += _openDebugger_Opened;
|
||||
#else
|
||||
_openDebugger.Visible = false;
|
||||
#endif
|
||||
|
||||
_gameTable.Model = _tableStore = new ListStore(
|
||||
typeof(bool),
|
||||
typeof(Gdk.Pixbuf),
|
||||
|
@ -141,6 +154,36 @@ namespace Ryujinx.Ui
|
|||
Task.Run(RefreshFirmwareLabel);
|
||||
}
|
||||
|
||||
#if USE_DEBUGGING
|
||||
private void _openDebugger_Opened(object sender, EventArgs e)
|
||||
{
|
||||
if (_debuggerOpened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Window debugWindow = new Window("Debugger");
|
||||
|
||||
debugWindow.SetSizeRequest(1280, 640);
|
||||
debugWindow.Child = _debugger.Widget;
|
||||
debugWindow.DeleteEvent += DebugWindow_DeleteEvent;
|
||||
debugWindow.ShowAll();
|
||||
|
||||
_debugger.Enable();
|
||||
|
||||
_debuggerOpened = true;
|
||||
}
|
||||
|
||||
private void DebugWindow_DeleteEvent(object o, DeleteEventArgs args)
|
||||
{
|
||||
_debuggerOpened = false;
|
||||
|
||||
_debugger.Disable();
|
||||
|
||||
(_debugger.Widget.Parent as Window)?.Remove(_debugger.Widget);
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static void ApplyTheme()
|
||||
{
|
||||
if (!ConfigurationState.Instance.Ui.EnableCustomTheme)
|
||||
|
@ -307,7 +350,15 @@ namespace Ryujinx.Ui
|
|||
#if MACOS_BUILD
|
||||
CreateGameWindow(device);
|
||||
#else
|
||||
new Thread(() => CreateGameWindow(device)).Start();
|
||||
var windowThread = new Thread(() =>
|
||||
{
|
||||
CreateGameWindow(device);
|
||||
})
|
||||
{
|
||||
Name = "GUI.WindowThread"
|
||||
};
|
||||
|
||||
windowThread.Start();
|
||||
#endif
|
||||
|
||||
_gameLoaded = true;
|
||||
|
@ -366,6 +417,11 @@ namespace Ryujinx.Ui
|
|||
|
||||
private void End(HLE.Switch device)
|
||||
{
|
||||
|
||||
#if USE_DEBUGGING
|
||||
_debugger.Dispose();
|
||||
#endif
|
||||
|
||||
if (_ending)
|
||||
{
|
||||
return;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.21.0 -->
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkApplicationWindow" id="_mainWin">
|
||||
|
@ -8,6 +8,9 @@
|
|||
<property name="window_position">center</property>
|
||||
<property name="default_width">1280</property>
|
||||
<property name="default_height">750</property>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="_box">
|
||||
<property name="visible">True</property>
|
||||
|
@ -255,7 +258,7 @@
|
|||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="ToolsMenu">
|
||||
<object class="GtkMenuItem" id="_toolsMenu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Tools</property>
|
||||
|
@ -296,6 +299,14 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="_openDebugger">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Open Debugger</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -499,8 +510,5 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
|
Loading…
Reference in a new issue