Merge branch 'master' into master
This commit is contained in:
commit
b7c29fa377
87 changed files with 2304 additions and 2119 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>
|
||||
|
|
|
@ -101,6 +101,7 @@ namespace Ryujinx.Audio
|
|||
}
|
||||
|
||||
_tracks.Clear();
|
||||
_context.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace Ryujinx.Common.Logging
|
|||
ServiceAm,
|
||||
ServiceApm,
|
||||
ServiceAudio,
|
||||
ServiceBcat,
|
||||
ServiceBsd,
|
||||
ServiceBtm,
|
||||
ServiceCaps,
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
@ -26,17 +26,17 @@ namespace Ryujinx.Profiler
|
|||
|
||||
// Cleanup thread
|
||||
private readonly Thread _cleanupThread;
|
||||
private bool _cleanupRunning;
|
||||
private readonly long _history;
|
||||
private long _preserve;
|
||||
private bool _cleanupRunning;
|
||||
private readonly long _history;
|
||||
private long _preserve;
|
||||
|
||||
// Timing flags
|
||||
private TimingFlag[] _timingFlags;
|
||||
private long[] _timingFlagAverages;
|
||||
private long[] _timingFlagLast;
|
||||
private long[] _timingFlagLastDelta;
|
||||
private int _timingFlagCount;
|
||||
private int _timingFlagIndex;
|
||||
private long[] _timingFlagAverages;
|
||||
private long[] _timingFlagLast;
|
||||
private long[] _timingFlagLastDelta;
|
||||
private int _timingFlagCount;
|
||||
private int _timingFlagIndex;
|
||||
|
||||
private int _maxFlags;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,6 +42,8 @@ namespace Ryujinx.Graphics.GAL
|
|||
|
||||
void SetImage(int index, ShaderStage stage, ITexture texture);
|
||||
|
||||
void SetPointSize(float size);
|
||||
|
||||
void SetPrimitiveRestart(bool enable, int index);
|
||||
|
||||
void SetPrimitiveTopology(PrimitiveTopology topology);
|
||||
|
|
|
@ -17,29 +17,31 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
/// <param name="argument">Method call argument</param>
|
||||
public void Dispatch(GpuState state, int argument)
|
||||
{
|
||||
uint dispatchParamsAddress = (uint)state.Get<int>(MethodOffset.DispatchParamsAddress);
|
||||
uint qmdAddress = (uint)state.Get<int>(MethodOffset.DispatchParamsAddress);
|
||||
|
||||
var dispatchParams = _context.MemoryAccessor.Read<ComputeParams>((ulong)dispatchParamsAddress << 8);
|
||||
var qmd = _context.MemoryAccessor.Read<ComputeQmd>((ulong)qmdAddress << 8);
|
||||
|
||||
GpuVa shaderBaseAddress = state.Get<GpuVa>(MethodOffset.ShaderBaseAddress);
|
||||
|
||||
ulong shaderGpuVa = shaderBaseAddress.Pack() + (uint)dispatchParams.ShaderOffset;
|
||||
ulong shaderGpuVa = shaderBaseAddress.Pack() + (uint)qmd.ProgramOffset;
|
||||
|
||||
// Note: A size of 0 is also invalid, the size must be at least 1.
|
||||
int sharedMemorySize = Math.Clamp(dispatchParams.SharedMemorySize & 0xffff, 1, _context.Capabilities.MaximumComputeSharedMemorySize);
|
||||
int localMemorySize = qmd.ShaderLocalMemoryLowSize + qmd.ShaderLocalMemoryHighSize;
|
||||
|
||||
int sharedMemorySize = Math.Min(qmd.SharedMemorySize, _context.Capabilities.MaximumComputeSharedMemorySize);
|
||||
|
||||
ComputeShader cs = ShaderCache.GetComputeShader(
|
||||
shaderGpuVa,
|
||||
sharedMemorySize,
|
||||
dispatchParams.UnpackBlockSizeX(),
|
||||
dispatchParams.UnpackBlockSizeY(),
|
||||
dispatchParams.UnpackBlockSizeZ());
|
||||
qmd.CtaThreadDimension0,
|
||||
qmd.CtaThreadDimension1,
|
||||
qmd.CtaThreadDimension2,
|
||||
localMemorySize,
|
||||
sharedMemorySize);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||
|
||||
var samplerPool = state.Get<PoolState>(MethodOffset.SamplerPoolState);
|
||||
|
||||
TextureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, dispatchParams.SamplerIndex);
|
||||
TextureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, qmd.SamplerIndex);
|
||||
|
||||
var texturePool = state.Get<PoolState>(MethodOffset.TexturePoolState);
|
||||
|
||||
|
@ -50,17 +52,19 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
ShaderProgramInfo info = cs.Shader.Program.Info;
|
||||
|
||||
uint sbEnableMask = 0;
|
||||
uint ubEnableMask = dispatchParams.UnpackUniformBuffersEnableMask();
|
||||
uint ubEnableMask = 0;
|
||||
|
||||
for (int index = 0; index < dispatchParams.UniformBuffers.Length; index++)
|
||||
for (int index = 0; index < Constants.TotalCpUniformBuffers; index++)
|
||||
{
|
||||
if ((ubEnableMask & (1 << index)) == 0)
|
||||
if (!qmd.ConstantBufferValid(index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong gpuVa = dispatchParams.UniformBuffers[index].PackAddress();
|
||||
ulong size = dispatchParams.UniformBuffers[index].UnpackSize();
|
||||
ubEnableMask |= 1u << index;
|
||||
|
||||
ulong gpuVa = (uint)qmd.ConstantBufferAddrLower(index) | (ulong)qmd.ConstantBufferAddrUpper(index) << 32;
|
||||
ulong size = (ulong)qmd.ConstantBufferSize(index);
|
||||
|
||||
BufferManager.SetComputeUniformBuffer(index, gpuVa, size);
|
||||
}
|
||||
|
@ -131,9 +135,9 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
TextureManager.CommitComputeBindings();
|
||||
|
||||
_context.Renderer.Pipeline.DispatchCompute(
|
||||
dispatchParams.UnpackGridSizeX(),
|
||||
dispatchParams.UnpackGridSizeY(),
|
||||
dispatchParams.UnpackGridSizeZ());
|
||||
qmd.CtaRasterWidth,
|
||||
qmd.CtaRasterHeight,
|
||||
qmd.CtaRasterDepth);
|
||||
|
||||
UpdateShaderState(state);
|
||||
}
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
using Ryujinx.Graphics.Gpu.State;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine
|
||||
{
|
||||
/// <summary>
|
||||
/// Compute uniform buffer parameters.
|
||||
/// </summary>
|
||||
struct UniformBufferParams
|
||||
{
|
||||
public int AddressLow;
|
||||
public int AddressHighAndSize;
|
||||
|
||||
/// <summary>
|
||||
/// Packs the split address to a 64-bits integer.
|
||||
/// </summary>
|
||||
/// <returns>Uniform buffer GPU virtual address</returns>
|
||||
public ulong PackAddress()
|
||||
{
|
||||
return (uint)AddressLow | ((ulong)(AddressHighAndSize & 0xff) << 32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the uniform buffer size in bytes.
|
||||
/// </summary>
|
||||
/// <returns>Uniform buffer size in bytes</returns>
|
||||
public ulong UnpackSize()
|
||||
{
|
||||
return (ulong)((AddressHighAndSize >> 15) & 0x1ffff);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute dispatch parameters.
|
||||
/// </summary>
|
||||
struct ComputeParams
|
||||
{
|
||||
public int Unknown0;
|
||||
public int Unknown1;
|
||||
public int Unknown2;
|
||||
public int Unknown3;
|
||||
public int Unknown4;
|
||||
public int Unknown5;
|
||||
public int Unknown6;
|
||||
public int Unknown7;
|
||||
public int ShaderOffset;
|
||||
public int Unknown9;
|
||||
public int Unknown10;
|
||||
public SamplerIndex SamplerIndex;
|
||||
public int GridSizeX;
|
||||
public int GridSizeYZ;
|
||||
public int Unknown14;
|
||||
public int Unknown15;
|
||||
public int Unknown16;
|
||||
public int SharedMemorySize;
|
||||
public int BlockSizeX;
|
||||
public int BlockSizeYZ;
|
||||
public int UniformBuffersConfig;
|
||||
public int Unknown21;
|
||||
public int Unknown22;
|
||||
public int Unknown23;
|
||||
public int Unknown24;
|
||||
public int Unknown25;
|
||||
public int Unknown26;
|
||||
public int Unknown27;
|
||||
public int Unknown28;
|
||||
|
||||
private UniformBufferParams _uniformBuffer0;
|
||||
private UniformBufferParams _uniformBuffer1;
|
||||
private UniformBufferParams _uniformBuffer2;
|
||||
private UniformBufferParams _uniformBuffer3;
|
||||
private UniformBufferParams _uniformBuffer4;
|
||||
private UniformBufferParams _uniformBuffer5;
|
||||
private UniformBufferParams _uniformBuffer6;
|
||||
private UniformBufferParams _uniformBuffer7;
|
||||
|
||||
/// <summary>
|
||||
/// Uniform buffer parameters.
|
||||
/// </summary>
|
||||
public Span<UniformBufferParams> UniformBuffers
|
||||
{
|
||||
get
|
||||
{
|
||||
return MemoryMarshal.CreateSpan(ref _uniformBuffer0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
public int Unknown45;
|
||||
public int Unknown46;
|
||||
public int Unknown47;
|
||||
public int Unknown48;
|
||||
public int Unknown49;
|
||||
public int Unknown50;
|
||||
public int Unknown51;
|
||||
public int Unknown52;
|
||||
public int Unknown53;
|
||||
public int Unknown54;
|
||||
public int Unknown55;
|
||||
public int Unknown56;
|
||||
public int Unknown57;
|
||||
public int Unknown58;
|
||||
public int Unknown59;
|
||||
public int Unknown60;
|
||||
public int Unknown61;
|
||||
public int Unknown62;
|
||||
public int Unknown63;
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the work group X size.
|
||||
/// </summary>
|
||||
/// <returns>Work group X size</returns>
|
||||
public int UnpackGridSizeX()
|
||||
{
|
||||
return GridSizeX & 0x7fffffff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the work group Y size.
|
||||
/// </summary>
|
||||
/// <returns>Work group Y size</returns>
|
||||
public int UnpackGridSizeY()
|
||||
{
|
||||
return GridSizeYZ & 0xffff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the work group Z size.
|
||||
/// </summary>
|
||||
/// <returns>Work group Z size</returns>
|
||||
public int UnpackGridSizeZ()
|
||||
{
|
||||
return (GridSizeYZ >> 16) & 0xffff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the local group X size.
|
||||
/// </summary>
|
||||
/// <returns>Local group X size</returns>
|
||||
public int UnpackBlockSizeX()
|
||||
{
|
||||
return (BlockSizeX >> 16) & 0xffff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the local group Y size.
|
||||
/// </summary>
|
||||
/// <returns>Local group Y size</returns>
|
||||
public int UnpackBlockSizeY()
|
||||
{
|
||||
return BlockSizeYZ & 0xffff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the local group Z size.
|
||||
/// </summary>
|
||||
/// <returns>Local group Z size</returns>
|
||||
public int UnpackBlockSizeZ()
|
||||
{
|
||||
return (BlockSizeYZ >> 16) & 0xffff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the uniform buffers enable mask.
|
||||
/// Each bit set on the mask indicates that the respective buffer index is enabled.
|
||||
/// </summary>
|
||||
/// <returns>Uniform buffers enable mask</returns>
|
||||
public uint UnpackUniformBuffersEnableMask()
|
||||
{
|
||||
return (uint)UniformBuffersConfig & 0xff;
|
||||
}
|
||||
}
|
||||
}
|
275
Ryujinx.Graphics.Gpu/Engine/ComputeQmd.cs
Normal file
275
Ryujinx.Graphics.Gpu/Engine/ComputeQmd.cs
Normal file
|
@ -0,0 +1,275 @@
|
|||
using Ryujinx.Graphics.Gpu.State;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the dependent Queue Meta Data.
|
||||
/// </summary>
|
||||
enum DependentQmdType
|
||||
{
|
||||
Queue,
|
||||
Grid
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of the release memory barrier.
|
||||
/// </summary>
|
||||
enum ReleaseMembarType
|
||||
{
|
||||
FeNone,
|
||||
FeSysmembar
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of the CWD memory barrier.
|
||||
/// </summary>
|
||||
enum CwdMembarType
|
||||
{
|
||||
L1None,
|
||||
L1Sysmembar,
|
||||
L1Membar
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NaN behavior of 32-bits float operations on the shader.
|
||||
/// </summary>
|
||||
enum Fp32NanBehavior
|
||||
{
|
||||
Legacy,
|
||||
Fp64Compatible
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NaN behavior of 32-bits float to integer conversion on the shader.
|
||||
/// </summary>
|
||||
enum Fp32F2iNanBehavior
|
||||
{
|
||||
PassZero,
|
||||
PassIndefinite
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limit of calls.
|
||||
/// </summary>
|
||||
enum ApiVisibleCallLimit
|
||||
{
|
||||
_32,
|
||||
NoCheck
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared memory bank mapping mode.
|
||||
/// </summary>
|
||||
enum SharedMemoryBankMapping
|
||||
{
|
||||
FourBytesPerBank,
|
||||
EightBytesPerBank
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Denormal behavior of 32-bits float narrowing instructions.
|
||||
/// </summary>
|
||||
enum Fp32NarrowInstruction
|
||||
{
|
||||
KeepDenorms,
|
||||
FlushDenorms
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration of the L1 cache.
|
||||
/// </summary>
|
||||
enum L1Configuration
|
||||
{
|
||||
DirectlyAddressableMemorySize16kb,
|
||||
DirectlyAddressableMemorySize32kb,
|
||||
DirectlyAddressableMemorySize48kb
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduction operation.
|
||||
/// </summary>
|
||||
enum ReductionOp
|
||||
{
|
||||
RedAdd,
|
||||
RedMin,
|
||||
RedMax,
|
||||
RedInc,
|
||||
RedDec,
|
||||
RedAnd,
|
||||
RedOr,
|
||||
RedXor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduction format.
|
||||
/// </summary>
|
||||
enum ReductionFormat
|
||||
{
|
||||
Unsigned32,
|
||||
Signed32
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Size of a structure in words.
|
||||
/// </summary>
|
||||
enum StructureSize
|
||||
{
|
||||
FourWords,
|
||||
OneWord
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute Queue Meta Data.
|
||||
/// </summary>
|
||||
unsafe struct ComputeQmd
|
||||
{
|
||||
private fixed int _words[64];
|
||||
|
||||
public int OuterPut => BitRange(30, 0);
|
||||
public bool OuterOverflow => Bit(31);
|
||||
public int OuterGet => BitRange(62, 32);
|
||||
public bool OuterStickyOverflow => Bit(63);
|
||||
public int InnerGet => BitRange(94, 64);
|
||||
public bool InnerOverflow => Bit(95);
|
||||
public int InnerPut => BitRange(126, 96);
|
||||
public bool InnerStickyOverflow => Bit(127);
|
||||
public int QmdReservedAA => BitRange(159, 128);
|
||||
public int DependentQmdPointer => BitRange(191, 160);
|
||||
public int QmdGroupId => BitRange(197, 192);
|
||||
public bool SmGlobalCachingEnable => Bit(198);
|
||||
public bool RunCtaInOneSmPartition => Bit(199);
|
||||
public bool IsQueue => Bit(200);
|
||||
public bool AddToHeadOfQmdGroupLinkedList => Bit(201);
|
||||
public bool SemaphoreReleaseEnable0 => Bit(202);
|
||||
public bool SemaphoreReleaseEnable1 => Bit(203);
|
||||
public bool RequireSchedulingPcas => Bit(204);
|
||||
public bool DependentQmdScheduleEnable => Bit(205);
|
||||
public DependentQmdType DependentQmdType => (DependentQmdType)BitRange(206, 206);
|
||||
public bool DependentQmdFieldCopy => Bit(207);
|
||||
public int QmdReservedB => BitRange(223, 208);
|
||||
public int CircularQueueSize => BitRange(248, 224);
|
||||
public bool QmdReservedC => Bit(249);
|
||||
public bool InvalidateTextureHeaderCache => Bit(250);
|
||||
public bool InvalidateTextureSamplerCache => Bit(251);
|
||||
public bool InvalidateTextureDataCache => Bit(252);
|
||||
public bool InvalidateShaderDataCache => Bit(253);
|
||||
public bool InvalidateInstructionCache => Bit(254);
|
||||
public bool InvalidateShaderConstantCache => Bit(255);
|
||||
public int ProgramOffset => BitRange(287, 256);
|
||||
public int CircularQueueAddrLower => BitRange(319, 288);
|
||||
public int CircularQueueAddrUpper => BitRange(327, 320);
|
||||
public int QmdReservedD => BitRange(335, 328);
|
||||
public int CircularQueueEntrySize => BitRange(351, 336);
|
||||
public int CwdReferenceCountId => BitRange(357, 352);
|
||||
public int CwdReferenceCountDeltaMinusOne => BitRange(365, 358);
|
||||
public ReleaseMembarType ReleaseMembarType => (ReleaseMembarType)BitRange(366, 366);
|
||||
public bool CwdReferenceCountIncrEnable => Bit(367);
|
||||
public CwdMembarType CwdMembarType => (CwdMembarType)BitRange(369, 368);
|
||||
public bool SequentiallyRunCtas => Bit(370);
|
||||
public bool CwdReferenceCountDecrEnable => Bit(371);
|
||||
public bool Throttled => Bit(372);
|
||||
public Fp32NanBehavior Fp32NanBehavior => (Fp32NanBehavior)BitRange(376, 376);
|
||||
public Fp32F2iNanBehavior Fp32F2iNanBehavior => (Fp32F2iNanBehavior)BitRange(377, 377);
|
||||
public ApiVisibleCallLimit ApiVisibleCallLimit => (ApiVisibleCallLimit)BitRange(378, 378);
|
||||
public SharedMemoryBankMapping SharedMemoryBankMapping => (SharedMemoryBankMapping)BitRange(379, 379);
|
||||
public SamplerIndex SamplerIndex => (SamplerIndex)BitRange(382, 382);
|
||||
public Fp32NarrowInstruction Fp32NarrowInstruction => (Fp32NarrowInstruction)BitRange(383, 383);
|
||||
public int CtaRasterWidth => BitRange(415, 384);
|
||||
public int CtaRasterHeight => BitRange(431, 416);
|
||||
public int CtaRasterDepth => BitRange(447, 432);
|
||||
public int CtaRasterWidthResume => BitRange(479, 448);
|
||||
public int CtaRasterHeightResume => BitRange(495, 480);
|
||||
public int CtaRasterDepthResume => BitRange(511, 496);
|
||||
public int QueueEntriesPerCtaMinusOne => BitRange(518, 512);
|
||||
public int CoalesceWaitingPeriod => BitRange(529, 522);
|
||||
public int SharedMemorySize => BitRange(561, 544);
|
||||
public int QmdReservedG => BitRange(575, 562);
|
||||
public int QmdVersion => BitRange(579, 576);
|
||||
public int QmdMajorVersion => BitRange(583, 580);
|
||||
public int QmdReservedH => BitRange(591, 584);
|
||||
public int CtaThreadDimension0 => BitRange(607, 592);
|
||||
public int CtaThreadDimension1 => BitRange(623, 608);
|
||||
public int CtaThreadDimension2 => BitRange(639, 624);
|
||||
public bool ConstantBufferValid(int i) => Bit(640 + i * 1);
|
||||
public int QmdReservedI => BitRange(668, 648);
|
||||
public L1Configuration L1Configuration => (L1Configuration)BitRange(671, 669);
|
||||
public int SmDisableMaskLower => BitRange(703, 672);
|
||||
public int SmDisableMaskUpper => BitRange(735, 704);
|
||||
public int Release0AddressLower => BitRange(767, 736);
|
||||
public int Release0AddressUpper => BitRange(775, 768);
|
||||
public int QmdReservedJ => BitRange(783, 776);
|
||||
public ReductionOp Release0ReductionOp => (ReductionOp)BitRange(790, 788);
|
||||
public bool QmdReservedK => Bit(791);
|
||||
public ReductionFormat Release0ReductionFormat => (ReductionFormat)BitRange(793, 792);
|
||||
public bool Release0ReductionEnable => Bit(794);
|
||||
public StructureSize Release0StructureSize => (StructureSize)BitRange(799, 799);
|
||||
public int Release0Payload => BitRange(831, 800);
|
||||
public int Release1AddressLower => BitRange(863, 832);
|
||||
public int Release1AddressUpper => BitRange(871, 864);
|
||||
public int QmdReservedL => BitRange(879, 872);
|
||||
public ReductionOp Release1ReductionOp => (ReductionOp)BitRange(886, 884);
|
||||
public bool QmdReservedM => Bit(887);
|
||||
public ReductionFormat Release1ReductionFormat => (ReductionFormat)BitRange(889, 888);
|
||||
public bool Release1ReductionEnable => Bit(890);
|
||||
public StructureSize Release1StructureSize => (StructureSize)BitRange(895, 895);
|
||||
public int Release1Payload => BitRange(927, 896);
|
||||
public int ConstantBufferAddrLower(int i) => BitRange(959 + i * 64, 928 + i * 64);
|
||||
public int ConstantBufferAddrUpper(int i) => BitRange(967 + i * 64, 960 + i * 64);
|
||||
public int ConstantBufferReservedAddr(int i) => BitRange(973 + i * 64, 968 + i * 64);
|
||||
public bool ConstantBufferInvalidate(int i) => Bit(974 + i * 64);
|
||||
public int ConstantBufferSize(int i) => BitRange(991 + i * 64, 975 + i * 64);
|
||||
public int ShaderLocalMemoryLowSize => BitRange(1463, 1440);
|
||||
public int QmdReservedN => BitRange(1466, 1464);
|
||||
public int BarrierCount => BitRange(1471, 1467);
|
||||
public int ShaderLocalMemoryHighSize => BitRange(1495, 1472);
|
||||
public int RegisterCount => BitRange(1503, 1496);
|
||||
public int ShaderLocalMemoryCrsSize => BitRange(1527, 1504);
|
||||
public int SassVersion => BitRange(1535, 1528);
|
||||
public int HwOnlyInnerGet => BitRange(1566, 1536);
|
||||
public bool HwOnlyRequireSchedulingPcas => Bit(1567);
|
||||
public int HwOnlyInnerPut => BitRange(1598, 1568);
|
||||
public bool HwOnlyScgType => Bit(1599);
|
||||
public int HwOnlySpanListHeadIndex => BitRange(1629, 1600);
|
||||
public bool QmdReservedQ => Bit(1630);
|
||||
public bool HwOnlySpanListHeadIndexValid => Bit(1631);
|
||||
public int HwOnlySkedNextQmdPointer => BitRange(1663, 1632);
|
||||
public int QmdSpareE => BitRange(1695, 1664);
|
||||
public int QmdSpareF => BitRange(1727, 1696);
|
||||
public int QmdSpareG => BitRange(1759, 1728);
|
||||
public int QmdSpareH => BitRange(1791, 1760);
|
||||
public int QmdSpareI => BitRange(1823, 1792);
|
||||
public int QmdSpareJ => BitRange(1855, 1824);
|
||||
public int QmdSpareK => BitRange(1887, 1856);
|
||||
public int QmdSpareL => BitRange(1919, 1888);
|
||||
public int QmdSpareM => BitRange(1951, 1920);
|
||||
public int QmdSpareN => BitRange(1983, 1952);
|
||||
public int DebugIdUpper => BitRange(2015, 1984);
|
||||
public int DebugIdLower => BitRange(2047, 2016);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool Bit(int bit)
|
||||
{
|
||||
if ((uint)bit >= 64 * 32)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bit));
|
||||
}
|
||||
|
||||
return (_words[bit >> 5] & (1 << (bit & 31))) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int BitRange(int upper, int lower)
|
||||
{
|
||||
if ((uint)lower >= 64 * 32)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(lower));
|
||||
}
|
||||
|
||||
int mask = (int)(uint.MaxValue >> (32 - (upper - lower + 1)));
|
||||
|
||||
return (_words[lower >> 5] >> (lower & 31)) & mask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -161,6 +161,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
UpdateVertexAttribState(state);
|
||||
}
|
||||
|
||||
if (state.QueryModified(MethodOffset.PointSize))
|
||||
{
|
||||
UpdatePointSizeState(state);
|
||||
}
|
||||
|
||||
if (state.QueryModified(MethodOffset.PrimitiveRestartState))
|
||||
{
|
||||
UpdatePrimitiveRestartState(state);
|
||||
|
@ -507,6 +512,17 @@ namespace Ryujinx.Graphics.Gpu.Engine
|
|||
_context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates host point size based on guest GPU state.
|
||||
/// </summary>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
private void UpdatePointSizeState(GpuState state)
|
||||
{
|
||||
float size = state.Get<float>(MethodOffset.PointSize);
|
||||
|
||||
_context.Renderer.Pipeline.SetPointSize(size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates host primitive restart based on guest GPU state.
|
||||
/// </summary>
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
{
|
||||
ulong processVa = _context.MemoryManager.Translate(gpuVa);
|
||||
|
||||
ulong size = Math.Min(_context.MemoryManager.GetSubSize(gpuVa), maxSize);
|
||||
ulong size = _context.MemoryManager.GetSubSize(gpuVa, maxSize);
|
||||
|
||||
return _context.PhysicalMemory.GetSpan(processVa, size);
|
||||
}
|
||||
|
|
|
@ -237,14 +237,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||
/// Gets the number of mapped or reserved pages on a given region.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">Start GPU virtual address of the region</param>
|
||||
/// <param name="maxSize">Maximum size of the data</param>
|
||||
/// <returns>Mapped size in bytes of the specified region</returns>
|
||||
internal ulong GetSubSize(ulong gpuVa)
|
||||
internal ulong GetSubSize(ulong gpuVa, ulong maxSize)
|
||||
{
|
||||
ulong size = 0;
|
||||
|
||||
while (GetPte(gpuVa + size) != PteUnmapped)
|
||||
{
|
||||
size += PageSize;
|
||||
if (size >= maxSize)
|
||||
{
|
||||
return maxSize;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
|
|
|
@ -13,4 +13,12 @@
|
|||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -51,12 +51,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
/// This automatically translates, compiles and adds the code to the cache if not present.
|
||||
/// </remarks>
|
||||
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
|
||||
/// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
|
||||
/// <param name="localSizeX">Local group size X of the computer shader</param>
|
||||
/// <param name="localSizeY">Local group size Y of the computer shader</param>
|
||||
/// <param name="localSizeZ">Local group size Z of the computer shader</param>
|
||||
/// <param name="localMemorySize">Local memory size of the compute shader</param>
|
||||
/// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
|
||||
/// <returns>Compiled compute shader code</returns>
|
||||
public ComputeShader GetComputeShader(ulong gpuVa, int sharedMemorySize, int localSizeX, int localSizeY, int localSizeZ)
|
||||
public ComputeShader GetComputeShader(
|
||||
ulong gpuVa,
|
||||
int localSizeX,
|
||||
int localSizeY,
|
||||
int localSizeZ,
|
||||
int localMemorySize,
|
||||
int sharedMemorySize)
|
||||
{
|
||||
bool isCached = _cpPrograms.TryGetValue(gpuVa, out List<ComputeShader> list);
|
||||
|
||||
|
@ -71,7 +78,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
}
|
||||
}
|
||||
|
||||
CachedShader shader = TranslateComputeShader(gpuVa, sharedMemorySize, localSizeX, localSizeY, localSizeZ);
|
||||
CachedShader shader = TranslateComputeShader(
|
||||
gpuVa,
|
||||
localSizeX,
|
||||
localSizeY,
|
||||
localSizeZ,
|
||||
localMemorySize,
|
||||
sharedMemorySize);
|
||||
|
||||
shader.HostShader = _context.Renderer.CompileShader(shader.Program);
|
||||
|
||||
|
@ -222,27 +235,28 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < shader.Code.Length; index++)
|
||||
{
|
||||
if (_context.MemoryAccessor.ReadInt32(gpuVa + (ulong)index * 4) != shader.Code[index])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ReadOnlySpan<byte> memoryCode = _context.MemoryAccessor.GetSpan(gpuVa, (ulong)shader.Code.Length * 4);
|
||||
|
||||
return false;
|
||||
return !MemoryMarshal.Cast<byte, int>(memoryCode).SequenceEqual(shader.Code);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates the binary Maxwell shader code to something that the host API accepts.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">GPU virtual address of the binary shader code</param>
|
||||
/// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
|
||||
/// <param name="localSizeX">Local group size X of the computer shader</param>
|
||||
/// <param name="localSizeY">Local group size Y of the computer shader</param>
|
||||
/// <param name="localSizeZ">Local group size Z of the computer shader</param>
|
||||
/// <param name="localMemorySize">Local memory size of the compute shader</param>
|
||||
/// <param name="sharedMemorySize">Shared memory size of the compute shader</param>
|
||||
/// <returns>Compiled compute shader code</returns>
|
||||
private CachedShader TranslateComputeShader(ulong gpuVa, int sharedMemorySize, int localSizeX, int localSizeY, int localSizeZ)
|
||||
private CachedShader TranslateComputeShader(
|
||||
ulong gpuVa,
|
||||
int localSizeX,
|
||||
int localSizeY,
|
||||
int localSizeZ,
|
||||
int localMemorySize,
|
||||
int sharedMemorySize)
|
||||
{
|
||||
if (gpuVa == 0)
|
||||
{
|
||||
|
@ -256,6 +270,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
QueryInfoName.ComputeLocalSizeX => localSizeX,
|
||||
QueryInfoName.ComputeLocalSizeY => localSizeY,
|
||||
QueryInfoName.ComputeLocalSizeZ => localSizeZ,
|
||||
QueryInfoName.ComputeLocalMemorySize => localMemorySize,
|
||||
QueryInfoName.ComputeSharedMemorySize => sharedMemorySize,
|
||||
_ => QueryInfoCommon(info)
|
||||
};
|
||||
|
|
|
@ -53,6 +53,7 @@ namespace Ryujinx.Graphics.Gpu.State
|
|||
YControl = 0x4eb,
|
||||
FirstVertex = 0x50d,
|
||||
FirstInstance = 0x50e,
|
||||
PointSize = 0x546,
|
||||
ResetCounter = 0x54c,
|
||||
RtDepthStencilEnable = 0x54e,
|
||||
ConditionState = 0x554,
|
||||
|
|
|
@ -601,6 +601,11 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
_vertexArray.SetIndexBuffer((Buffer)buffer.Buffer);
|
||||
}
|
||||
|
||||
public void SetPointSize(float size)
|
||||
{
|
||||
GL.PointSize(size);
|
||||
}
|
||||
|
||||
public void SetPrimitiveRestart(bool enable, int index)
|
||||
{
|
||||
if (!enable)
|
||||
|
|
|
@ -77,14 +77,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||
|
||||
Bind();
|
||||
|
||||
int extraBlockindex = GL.GetUniformBlockIndex(Handle, "Extra");
|
||||
|
||||
if (extraBlockindex >= 0)
|
||||
{
|
||||
GL.UniformBlockBinding(Handle, extraBlockindex, 0);
|
||||
}
|
||||
|
||||
int ubBindingPoint = 1;
|
||||
int ubBindingPoint = 0;
|
||||
int sbBindingPoint = 0;
|
||||
int textureUnit = 0;
|
||||
int imageUnit = 0;
|
||||
|
|
|
@ -47,25 +47,35 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||
context.AppendLine();
|
||||
}
|
||||
|
||||
context.AppendLine("layout (std140) uniform Extra");
|
||||
|
||||
context.EnterScope();
|
||||
|
||||
context.AppendLine("vec2 flip;");
|
||||
context.AppendLine("int instance;");
|
||||
|
||||
context.LeaveScope(";");
|
||||
|
||||
context.AppendLine();
|
||||
|
||||
context.AppendLine($"uint {DefaultNames.LocalMemoryName}[0x100];");
|
||||
context.AppendLine();
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Compute)
|
||||
{
|
||||
string size = NumberFormatter.FormatInt(BitUtils.DivRoundUp(context.Config.QueryInfo(QueryInfoName.ComputeSharedMemorySize), 4));
|
||||
int localMemorySize = BitUtils.DivRoundUp(context.Config.QueryInfo(QueryInfoName.ComputeLocalMemorySize), 4);
|
||||
|
||||
context.AppendLine($"shared uint {DefaultNames.SharedMemoryName}[{size}];");
|
||||
if (localMemorySize != 0)
|
||||
{
|
||||
string localMemorySizeStr = NumberFormatter.FormatInt(localMemorySize);
|
||||
|
||||
context.AppendLine($"uint {DefaultNames.LocalMemoryName}[{localMemorySizeStr}];");
|
||||
context.AppendLine();
|
||||
}
|
||||
|
||||
int sharedMemorySize = BitUtils.DivRoundUp(context.Config.QueryInfo(QueryInfoName.ComputeSharedMemorySize), 4);
|
||||
|
||||
if (sharedMemorySize != 0)
|
||||
{
|
||||
string sharedMemorySizeStr = NumberFormatter.FormatInt(sharedMemorySize);
|
||||
|
||||
context.AppendLine($"shared uint {DefaultNames.SharedMemoryName}[{sharedMemorySizeStr}];");
|
||||
context.AppendLine();
|
||||
}
|
||||
}
|
||||
else if (context.Config.LocalMemorySize != 0)
|
||||
{
|
||||
int localMemorySize = BitUtils.DivRoundUp(context.Config.LocalMemorySize, 4);
|
||||
|
||||
string localMemorySizeStr = NumberFormatter.FormatInt(localMemorySize);
|
||||
|
||||
context.AppendLine($"uint {DefaultNames.LocalMemoryName}[{localMemorySizeStr}];");
|
||||
context.AppendLine();
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,8 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||
|
||||
switch (op.Mode)
|
||||
{
|
||||
case InterpolationMode.Pass: iq = InterpolationQualifier.NoPerspective; break;
|
||||
case InterpolationMode.Constant: iq = InterpolationQualifier.Flat; break;
|
||||
case InterpolationMode.Pass: iq = InterpolationQualifier.NoPerspective; break;
|
||||
}
|
||||
|
||||
Operand srcA = Attribute(op.AttributeOffset, iq);
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.Shader
|
|||
ComputeLocalSizeX,
|
||||
ComputeLocalSizeY,
|
||||
ComputeLocalSizeZ,
|
||||
ComputeLocalMemorySize,
|
||||
ComputeSharedMemorySize,
|
||||
IsTextureBuffer,
|
||||
IsTextureRectangle,
|
||||
|
|
|
@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
|
||||
public int MaxOutputVertices { get; }
|
||||
|
||||
public int LocalMemorySize { get; }
|
||||
|
||||
public OutputMapTarget[] OmapTargets { get; }
|
||||
public bool OmapSampleMask { get; }
|
||||
public bool OmapDepth { get; }
|
||||
|
@ -23,6 +25,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
Stage = ShaderStage.Compute;
|
||||
OutputTopology = OutputTopology.PointList;
|
||||
MaxOutputVertices = 0;
|
||||
LocalMemorySize = 0;
|
||||
OmapTargets = null;
|
||||
OmapSampleMask = false;
|
||||
OmapDepth = false;
|
||||
|
@ -35,6 +38,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
Stage = header.Stage;
|
||||
OutputTopology = header.OutputTopology;
|
||||
MaxOutputVertices = header.MaxOutputVertexCount;
|
||||
LocalMemorySize = header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize;
|
||||
OmapTargets = header.OmapTargets;
|
||||
OmapSampleMask = header.OmapSampleMask;
|
||||
OmapDepth = header.OmapDepth;
|
||||
|
@ -80,6 +84,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||
case QueryInfoName.ComputeLocalSizeY:
|
||||
case QueryInfoName.ComputeLocalSizeZ:
|
||||
return 1;
|
||||
case QueryInfoName.ComputeLocalMemorySize:
|
||||
return 0x1000;
|
||||
case QueryInfoName.ComputeSharedMemorySize:
|
||||
return 0xc000;
|
||||
case QueryInfoName.IsTextureBuffer:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
|
||||
namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
|
@ -8,14 +8,14 @@ namespace Ryujinx.HLE.FileSystem
|
|||
public long SaveId { get; private set; }
|
||||
public SaveDataType SaveDataType { get; private set; }
|
||||
public SaveSpaceId SaveSpaceId { get; private set; }
|
||||
public UInt128 UserId { get; private set; }
|
||||
public UserId UserId { get; private set; }
|
||||
|
||||
public SaveInfo(
|
||||
ulong titleId,
|
||||
long saveId,
|
||||
SaveDataType saveDataType,
|
||||
SaveSpaceId saveSpaceId,
|
||||
UInt128 userId = new UInt128())
|
||||
UserId userId = new UserId())
|
||||
{
|
||||
TitleId = titleId;
|
||||
SaveId = saveId;
|
||||
|
|
|
@ -107,6 +107,7 @@ namespace Ryujinx.HLE.HOS
|
|||
public Keyset KeySet => Device.FileSystem.KeySet;
|
||||
|
||||
private bool _hasStarted;
|
||||
private bool _isDisposed;
|
||||
|
||||
public BlitStruct<ApplicationControlProperty> ControlData { get; set; }
|
||||
|
||||
|
@ -617,19 +618,19 @@ namespace Ryujinx.HLE.HOS
|
|||
metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
||||
}
|
||||
|
||||
metaData.Aci0.TitleId = nacp.PresenceGroupId;
|
||||
|
||||
if (metaData.Aci0.TitleId == 0)
|
||||
if (nacp.PresenceGroupId != 0)
|
||||
{
|
||||
metaData.Aci0.TitleId = nacp.PresenceGroupId;
|
||||
}
|
||||
else if (nacp.SaveDataOwnerId.Value != 0)
|
||||
{
|
||||
metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value;
|
||||
}
|
||||
|
||||
if (metaData.Aci0.TitleId == 0)
|
||||
else if (nacp.AddOnContentBaseId != 0)
|
||||
{
|
||||
metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000;
|
||||
}
|
||||
|
||||
if (metaData.Aci0.TitleId.ToString("x16") == "fffffffffffff000")
|
||||
else
|
||||
{
|
||||
metaData.Aci0.TitleId = 0000000000000000;
|
||||
}
|
||||
|
@ -669,8 +670,7 @@ namespace Ryujinx.HLE.HOS
|
|||
{
|
||||
Logger.PrintInfo(LogClass.Application, "Ensuring required savedata exists.");
|
||||
|
||||
UInt128 lastOpenedUser = State.Account.LastOpenedUser.UserId;
|
||||
Uid user = new Uid((ulong)lastOpenedUser.Low, (ulong)lastOpenedUser.High);
|
||||
Uid user = State.Account.LastOpenedUser.UserId.ToLibHacUid();
|
||||
|
||||
ref ApplicationControlProperty control = ref ControlData.Value;
|
||||
|
||||
|
@ -741,8 +741,10 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
if (!_isDisposed && disposing)
|
||||
{
|
||||
_isDisposed = true;
|
||||
|
||||
KProcess terminationProcess = new KProcess(this);
|
||||
|
||||
KThread terminationThread = new KThread(this);
|
||||
|
|
|
@ -1131,5 +1131,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||
{
|
||||
throw new UndefinedInstructionException(e.Address, e.OpCode);
|
||||
}
|
||||
|
||||
protected override void Destroy()
|
||||
{
|
||||
CpuMemory.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1141,6 +1141,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
{
|
||||
Owner.Translator.Execute(Context, entrypoint);
|
||||
|
||||
Context.Dispose();
|
||||
|
||||
ThreadExit();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Ryujinx.HLE.Utilities;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -16,14 +15,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
_profiles = new ConcurrentDictionary<string, UserProfile>();
|
||||
}
|
||||
|
||||
public void AddUser(UInt128 userId, string name)
|
||||
public void AddUser(UserId userId, string name)
|
||||
{
|
||||
UserProfile profile = new UserProfile(userId, name);
|
||||
|
||||
_profiles.AddOrUpdate(userId.ToString(), profile, (key, old) => profile);
|
||||
}
|
||||
|
||||
public void OpenUser(UInt128 userId)
|
||||
public void OpenUser(UserId userId)
|
||||
{
|
||||
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
||||
{
|
||||
|
@ -31,7 +30,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
}
|
||||
}
|
||||
|
||||
public void CloseUser(UInt128 userId)
|
||||
public void CloseUser(UserId userId)
|
||||
{
|
||||
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
||||
{
|
||||
|
@ -44,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
return _profiles.Count;
|
||||
}
|
||||
|
||||
internal bool TryGetUser(UInt128 userId, out UserProfile profile)
|
||||
internal bool TryGetUser(UserId userId, out UserProfile profile)
|
||||
{
|
||||
return _profiles.TryGetValue(userId.ToString(), out profile);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Arp;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
// GetUserExistence(nn::account::Uid) -> bool
|
||||
public ResultCode GetUserExistence(ServiceCtx context)
|
||||
{
|
||||
UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10));
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
|
@ -75,8 +75,8 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
break;
|
||||
}
|
||||
|
||||
context.Memory.WriteInt64(outputPosition + (long)offset, userProfile.UserId.Low);
|
||||
context.Memory.WriteInt64(outputPosition + (long)offset + 8, userProfile.UserId.High);
|
||||
context.Memory.WriteInt64(outputPosition + (long)offset, userProfile.UserId.High);
|
||||
context.Memory.WriteInt64(outputPosition + (long)offset + 8, userProfile.UserId.Low);
|
||||
|
||||
offset += 0x10;
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
// GetProfile(nn::account::Uid) -> object<nn::account::profile::IProfile>
|
||||
public ResultCode GetProfile(ServiceCtx context)
|
||||
{
|
||||
UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10));
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (!context.Device.System.State.Account.TryGetUser(userId, out UserProfile userProfile))
|
||||
{
|
||||
|
@ -131,7 +131,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
if (context.Device.System.State.Account.GetUserCount() != 1)
|
||||
{
|
||||
// Invalid UserId.
|
||||
new UInt128(0, 0).Write(context.ResponseData);
|
||||
new UserId(0, 0).Write(context.ResponseData);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
// GetBaasAccountManagerForApplication(nn::account::Uid) -> object<nn::account::baas::IManagerForApplication>
|
||||
public ResultCode GetBaasAccountManagerForApplication(ServiceCtx context)
|
||||
{
|
||||
UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10));
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
|
@ -220,7 +220,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10));
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
|
@ -258,7 +258,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10));
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Arp;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
{
|
||||
class IManagerForApplication : IpcService
|
||||
{
|
||||
private UInt128 _userId;
|
||||
private UserId _userId;
|
||||
private ApplicationLaunchProperty _applicationLaunchProperty;
|
||||
|
||||
public IManagerForApplication(UInt128 userId, ApplicationLaunchProperty applicationLaunchProperty)
|
||||
public IManagerForApplication(UserId userId, ApplicationLaunchProperty applicationLaunchProperty)
|
||||
{
|
||||
_userId = userId;
|
||||
_applicationLaunchProperty = applicationLaunchProperty;
|
||||
|
|
81
Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs
Normal file
81
Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using LibHac.Account;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct UserId : IEquatable<UserId>
|
||||
{
|
||||
public readonly long High;
|
||||
public readonly long Low;
|
||||
|
||||
public bool IsNull => (Low | High) == 0;
|
||||
|
||||
public UserId(long low, long high)
|
||||
{
|
||||
Low = low;
|
||||
High = high;
|
||||
}
|
||||
|
||||
public UserId(byte[] bytes)
|
||||
{
|
||||
High = BitConverter.ToInt64(bytes, 0);
|
||||
Low = BitConverter.ToInt64(bytes, 8);
|
||||
}
|
||||
|
||||
public UserId(string hex)
|
||||
{
|
||||
if (hex == null || hex.Length != 32 || !hex.All("0123456789abcdefABCDEF".Contains))
|
||||
{
|
||||
throw new ArgumentException("Invalid Hex value!", nameof(hex));
|
||||
}
|
||||
|
||||
Low = Convert.ToInt64(hex.Substring(16), 16);
|
||||
High = Convert.ToInt64(hex.Substring(0, 16), 16);
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter binaryWriter)
|
||||
{
|
||||
binaryWriter.Write(High);
|
||||
binaryWriter.Write(Low);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return High.ToString("x16") + Low.ToString("x16");
|
||||
}
|
||||
|
||||
public static bool operator ==(UserId x, UserId y)
|
||||
{
|
||||
return x.Equals(y);
|
||||
}
|
||||
|
||||
public static bool operator !=(UserId x, UserId y)
|
||||
{
|
||||
return !x.Equals(y);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is UserId userId && Equals(userId);
|
||||
}
|
||||
|
||||
public bool Equals(UserId cmpObj)
|
||||
{
|
||||
return Low == cmpObj.Low && High == cmpObj.High;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Low, High);
|
||||
}
|
||||
|
||||
public Uid ToLibHacUid()
|
||||
{
|
||||
return new Uid((ulong)High, (ulong)Low);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
{
|
||||
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public UInt128 UserId { get; private set; }
|
||||
public UserId UserId { get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
|
@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||
public AccountState AccountState { get; set; }
|
||||
public AccountState OnlinePlayState { get; set; }
|
||||
|
||||
public UserProfile(UInt128 userId, string name)
|
||||
public UserProfile(UserId userId, string name)
|
||||
{
|
||||
UserId = userId;
|
||||
Name = name;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
|
||||
{
|
||||
|
@ -6,7 +7,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
|
|||
{
|
||||
private const uint LaunchParamsMagic = 0xc79497ca;
|
||||
|
||||
public static byte[] MakeLaunchParams()
|
||||
public static byte[] MakeLaunchParams(UserProfile userProfile)
|
||||
{
|
||||
// Size needs to be at least 0x88 bytes otherwise application errors.
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
|
@ -17,8 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
|
|||
|
||||
writer.Write(LaunchParamsMagic);
|
||||
writer.Write(1); // IsAccountSelected? Only lower 8 bits actually used.
|
||||
writer.Write(1L); // User Id Low (note: User Id needs to be != 0)
|
||||
writer.Write(0L); // User Id High
|
||||
userProfile.UserId.Write(writer);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using LibHac;
|
||||
using LibHac.Account;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.Common;
|
||||
|
@ -13,6 +14,7 @@ using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
|
|||
using System;
|
||||
|
||||
using static LibHac.Fs.ApplicationSaveDataManagement;
|
||||
using AccountUid = Ryujinx.HLE.HOS.Services.Account.Acc.UserId;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy
|
||||
{
|
||||
|
@ -30,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
public ResultCode PopLaunchParameter(ServiceCtx context)
|
||||
{
|
||||
// Only the first 0x18 bytes of the Data seems to be actually used.
|
||||
MakeObject(context, new AppletAE.IStorage(StorageHelper.MakeLaunchParams()));
|
||||
MakeObject(context, new AppletAE.IStorage(StorageHelper.MakeLaunchParams(context.Device.System.State.Account.LastOpenedUser)));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -39,7 +41,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
// EnsureSaveData(nn::account::Uid) -> u64
|
||||
public ResultCode EnsureSaveData(ServiceCtx context)
|
||||
{
|
||||
Uid userId = context.RequestData.ReadStruct<Uid>();
|
||||
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
|
||||
TitleId titleId = new TitleId(context.Process.TitleId);
|
||||
|
||||
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.System.ControlData;
|
||||
|
@ -108,6 +110,23 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
// GetSaveDataSize(u8, nn::account::Uid) -> (u64, u64)
|
||||
[Command(26)] // 3.0.0+
|
||||
public ResultCode GetSaveDataSize(ServiceCtx context)
|
||||
{
|
||||
SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadByte();
|
||||
context.RequestData.BaseStream.Seek(7, System.IO.SeekOrigin.Current);
|
||||
|
||||
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
|
||||
|
||||
// TODO: We return a size of 2GB as we use a directory based save system. This should be enough for most of the games.
|
||||
context.ResponseData.Write(2000000000u);
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceAm, new { saveDataType, userId });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(40)]
|
||||
// NotifyRunning() -> b8
|
||||
public ResultCode NotifyRunning(ServiceCtx context)
|
||||
|
|
|
@ -5,5 +5,14 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
|
|||
class IBcatService : IpcService
|
||||
{
|
||||
public IBcatService(ApplicationLaunchProperty applicationLaunchProperty) { }
|
||||
|
||||
[Command(10100)]
|
||||
// RequestSyncDeliveryCache() -> object<nn::bcat::detail::ipc::IDeliveryCacheProgressService>
|
||||
public ResultCode RequestSyncDeliveryCache(ServiceCtx context)
|
||||
{
|
||||
MakeObject(context, new IDeliveryCacheProgressService(context));
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator.Types;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
|
||||
{
|
||||
class IDeliveryCacheProgressService : IpcService
|
||||
{
|
||||
private KEvent _event;
|
||||
|
||||
public IDeliveryCacheProgressService(ServiceCtx context)
|
||||
{
|
||||
_event = new KEvent(context.Device.System);
|
||||
}
|
||||
|
||||
[Command(0)]
|
||||
// GetEvent() -> handle<copy>
|
||||
public ResultCode GetEvent(ServiceCtx context)
|
||||
{
|
||||
if (context.Process.HandleTable.GenerateHandle(_event.ReadableEvent, out int handle) != KernelResult.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Out of handles!");
|
||||
}
|
||||
|
||||
context.Response.HandleDesc = IpcHandleDesc.MakeMove(handle);
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceBcat);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(1)]
|
||||
// GetImpl() -> buffer<nn::bcat::detail::DeliveryCacheProgressImpl, 0x1a>
|
||||
public ResultCode GetImpl(ServiceCtx context)
|
||||
{
|
||||
DeliveryCacheProgressImpl deliveryCacheProgress = new DeliveryCacheProgressImpl
|
||||
{
|
||||
State = DeliveryCacheProgressImpl.Status.Done,
|
||||
Result = 0
|
||||
};
|
||||
|
||||
WriteDeliveryCacheProgressImpl(context, context.Request.RecvListBuff[0], deliveryCacheProgress);
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceBcat);
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private void WriteDeliveryCacheProgressImpl(ServiceCtx context, IpcRecvListBuffDesc ipcDesc, DeliveryCacheProgressImpl deliveryCacheProgress)
|
||||
{
|
||||
using (MemoryStream memory = new MemoryStream((int)ipcDesc.Size))
|
||||
using (BinaryWriter bufferWriter = new BinaryWriter(memory))
|
||||
{
|
||||
bufferWriter.WriteStruct(deliveryCacheProgress);
|
||||
context.Memory.WriteBytes(ipcDesc.Position, memory.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x200)]
|
||||
public struct DeliveryCacheProgressImpl
|
||||
{
|
||||
public enum Status
|
||||
{
|
||||
// TODO: determine other values
|
||||
Done = 9
|
||||
}
|
||||
|
||||
public Status State;
|
||||
public uint Result;
|
||||
// TODO: reverse the rest of the structure
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
|
||||
|
@ -28,10 +29,10 @@ namespace Ryujinx.HLE.HOS.Services.Friend
|
|||
}
|
||||
|
||||
[Command(1)] // 2.0.0+
|
||||
// CreateNotificationService(nn::account::Uid) -> object<nn::friends::detail::ipc::INotificationService>
|
||||
// CreateNotificationService(nn::account::Uid userId) -> object<nn::friends::detail::ipc::INotificationService>
|
||||
public ResultCode CreateNotificationService(ServiceCtx context)
|
||||
{
|
||||
UInt128 userId = context.RequestData.ReadStruct<UInt128>();
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (userId.IsNull)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
||||
|
@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
|||
[StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x200, CharSet = CharSet.Ansi)]
|
||||
struct Friend
|
||||
{
|
||||
public UInt128 UserId;
|
||||
public UserId UserId;
|
||||
public long NetworkUserId;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x21)]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
||||
|
@ -6,7 +6,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
|||
[StructLayout(LayoutKind.Sequential, Pack = 0x8, CharSet = CharSet.Ansi)]
|
||||
struct UserPresence
|
||||
{
|
||||
public UInt128 UserId;
|
||||
public UserId UserId;
|
||||
public long LastTimeOnlineTimestamp;
|
||||
public PresenceStatus Status;
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ using Ryujinx.Common;
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@ -18,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
}
|
||||
|
||||
[Command(10100)]
|
||||
// nn::friends::GetFriendListIds(int offset, nn::account::Uid userUUID, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
|
||||
// nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
|
||||
// -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa>
|
||||
public ResultCode GetFriendListIds(ServiceCtx context)
|
||||
{
|
||||
|
@ -27,13 +26,13 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
// Padding
|
||||
context.RequestData.ReadInt32();
|
||||
|
||||
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
|
||||
|
||||
// Pid placeholder
|
||||
context.RequestData.ReadInt64();
|
||||
|
||||
if (uuid.IsNull)
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
@ -43,7 +42,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
|
||||
Logger.PrintStub(LogClass.ServiceFriend, new
|
||||
{
|
||||
UserId = uuid.ToString(),
|
||||
UserId = userId.ToString(),
|
||||
offset,
|
||||
filter.PresenceStatus,
|
||||
filter.IsFavoriteOnly,
|
||||
|
@ -57,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
}
|
||||
|
||||
[Command(10101)]
|
||||
// nn::friends::GetFriendList(int offset, nn::account::Uid userUUID, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
|
||||
// nn::friends::GetFriendList(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
|
||||
// -> int outCount, array<nn::friends::detail::FriendImpl, 0x6>
|
||||
public ResultCode GetFriendList(ServiceCtx context)
|
||||
{
|
||||
|
@ -66,13 +65,13 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
// Padding
|
||||
context.RequestData.ReadInt32();
|
||||
|
||||
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
|
||||
|
||||
// Pid placeholder
|
||||
context.RequestData.ReadInt64();
|
||||
|
||||
if (uuid.IsNull)
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
@ -81,7 +80,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
context.ResponseData.Write(0);
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceFriend, new {
|
||||
UserId = uuid.ToString(),
|
||||
UserId = userId.ToString(),
|
||||
offset,
|
||||
filter.PresenceStatus,
|
||||
filter.IsFavoriteOnly,
|
||||
|
@ -95,43 +94,43 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
}
|
||||
|
||||
[Command(10600)]
|
||||
// nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid)
|
||||
// nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid userId)
|
||||
public ResultCode DeclareOpenOnlinePlaySession(ServiceCtx context)
|
||||
{
|
||||
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (uuid.IsNull)
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile))
|
||||
if (context.Device.System.State.Account.TryGetUser(userId, out UserProfile profile))
|
||||
{
|
||||
profile.OnlinePlayState = AccountState.Open;
|
||||
}
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), profile.OnlinePlayState });
|
||||
Logger.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString(), profile.OnlinePlayState });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[Command(10601)]
|
||||
// nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid)
|
||||
// nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid userId)
|
||||
public ResultCode DeclareCloseOnlinePlaySession(ServiceCtx context)
|
||||
{
|
||||
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
|
||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
if (uuid.IsNull)
|
||||
if (userId.IsNull)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
|
||||
if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile))
|
||||
if (context.Device.System.State.Account.TryGetUser(userId, out UserProfile profile))
|
||||
{
|
||||
profile.OnlinePlayState = AccountState.Closed;
|
||||
}
|
||||
|
||||
Logger.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), profile.OnlinePlayState });
|
||||
Logger.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString(), profile.OnlinePlayState });
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
@ -140,7 +139,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
// nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer<nn::friends::detail::UserPresenceImpl, 0x19>)
|
||||
public ResultCode UpdateUserPresence(ServiceCtx context)
|
||||
{
|
||||
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
|
||||
UserId uuid = context.RequestData.ReadStruct<UserId>();
|
||||
|
||||
// Pid placeholder
|
||||
context.RequestData.ReadInt64();
|
||||
|
|
|
@ -2,8 +2,8 @@ using Ryujinx.Common;
|
|||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
@ -11,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
{
|
||||
class INotificationService : IpcService, IDisposable
|
||||
{
|
||||
private readonly UInt128 _userId;
|
||||
private readonly UserId _userId;
|
||||
private readonly FriendServicePermissionLevel _permissionLevel;
|
||||
|
||||
private readonly object _lock = new object();
|
||||
|
@ -24,7 +24,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
private bool _hasNewFriendRequest;
|
||||
private bool _hasFriendListUpdate;
|
||||
|
||||
public INotificationService(ServiceCtx context, UInt128 userId, FriendServicePermissionLevel permissionLevel)
|
||||
public INotificationService(ServiceCtx context, UserId userId, FriendServicePermissionLevel permissionLevel)
|
||||
{
|
||||
_userId = userId;
|
||||
_permissionLevel = permissionLevel;
|
||||
|
@ -98,7 +98,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
return ResultCode.NotificationQueueEmpty;
|
||||
}
|
||||
|
||||
public void SignalFriendListUpdate(UInt128 targetId)
|
||||
public void SignalFriendListUpdate(UserId targetId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
|
@ -140,7 +140,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|||
}
|
||||
}
|
||||
|
||||
public void SignalNewFriendRequest(UInt128 targetId)
|
||||
public void SignalNewFriendRequest(UserId targetId)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
|
||||
{
|
||||
|
@ -57,7 +58,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
|
|||
}
|
||||
|
||||
// TODO: Use this when we will have enough things to go online.
|
||||
public void SignalFriendListUpdate(UInt128 targetId)
|
||||
public void SignalFriendListUpdate(UserId targetId)
|
||||
{
|
||||
for (int i = 0; i < _registry.Length; i++)
|
||||
{
|
||||
|
@ -69,7 +70,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
|
|||
}
|
||||
|
||||
// TODO: Use this when we will have enough things to go online.
|
||||
public void SignalNewFriendRequest(UInt128 targetId)
|
||||
public void SignalNewFriendRequest(UserId targetId)
|
||||
{
|
||||
for (int i = 0; i < _registry.Length; i++)
|
||||
{
|
||||
|
|
|
@ -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,5 +1,7 @@
|
|||
using MsgPack.Serialization;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
@ -48,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Services.Prepo
|
|||
|
||||
private ResultCode ProcessReport(ServiceCtx context, bool withUserID)
|
||||
{
|
||||
UInt128 userId = withUserID ? new UInt128(context.RequestData.ReadBytes(0x10)) : new UInt128();
|
||||
UserId userId = withUserID ? context.RequestData.ReadStruct<UserId>() : new UserId();
|
||||
string gameRoom = StringUtils.ReadUtf8String(context);
|
||||
|
||||
if (withUserID)
|
||||
|
@ -79,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Services.Prepo
|
|||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
private string ReadReportBuffer(byte[] buffer, string room, UInt128 userId)
|
||||
private string ReadReportBuffer(byte[] buffer, string room, UserId userId)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService.Types;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -10,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
|||
{
|
||||
static class QueryPlayStatisticsManager
|
||||
{
|
||||
private static Dictionary<UInt128, ApplicationPlayStatistics> applicationPlayStatistics = new Dictionary<UInt128, ApplicationPlayStatistics>();
|
||||
private static Dictionary<UserId, ApplicationPlayStatistics> applicationPlayStatistics = new Dictionary<UserId, ApplicationPlayStatistics>();
|
||||
|
||||
internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false)
|
||||
{
|
||||
|
@ -20,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
|||
long outputPosition = context.Request.ReceiveBuff[0].Position;
|
||||
long outputSize = context.Request.ReceiveBuff[0].Size;
|
||||
|
||||
UInt128 userId = byUserId ? new UInt128(context.RequestData.ReadBytes(0x10)) : new UInt128();
|
||||
UserId userId = byUserId ? context.RequestData.ReadStruct<UserId>() : new UserId();
|
||||
|
||||
if (byUserId)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.SystemState
|
||||
|
@ -54,7 +53,7 @@ namespace Ryujinx.HLE.HOS.SystemState
|
|||
|
||||
Account = new AccountUtils();
|
||||
|
||||
UInt128 defaultUid = new UInt128("00000000000000000000000000000001");
|
||||
UserId defaultUid = new UserId("00000000000000010000000000000000");
|
||||
|
||||
Account.AddUser(defaultUid, "Player");
|
||||
Account.OpenUser(defaultUid);
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
17
Ryujinx.sln
17
Ryujinx.sln
|
@ -13,9 +13,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
|
||||
|
@ -25,8 +22,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}"
|
||||
|
@ -43,6 +38,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec", "R
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Updater", "Ryujinx.Updater\Ryujinx.Updater.csproj", "{529E361C-FEF5-4C07-A2F4-3351C45CADDD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Nvdec", "Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Debugger", "Ryujinx.Debugger\Ryujinx.Debugger.csproj", "{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -115,14 +114,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
|
||||
|
|
|
@ -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;
|
||||
|
@ -51,7 +51,7 @@ namespace Ryujinx
|
|||
string userProfilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".switch", "prod.keys");
|
||||
if (!File.Exists(appDataPath) && !File.Exists(userProfilePath) && !Migration.IsMigrationNeeded())
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("Key file was not found. Please refer to `KEYS.md` for more info");
|
||||
GtkDialog.CreateWarningDialog("Key file was not found", "Please refer to `KEYS.md` for more info");
|
||||
}
|
||||
|
||||
MainWindow mainWindow = new MainWindow();
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
@ -82,8 +82,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" />
|
||||
|
|
|
@ -4,8 +4,6 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
public class ApplicationAddedEventArgs : EventArgs
|
||||
{
|
||||
public ApplicationData AppData { get; set; }
|
||||
public int NumAppsFound { get; set; }
|
||||
public int NumAppsLoaded { get; set; }
|
||||
public ApplicationData AppData { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
10
Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs
Normal file
10
Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Ui
|
||||
{
|
||||
public class ApplicationCountUpdatedEventArgs : EventArgs
|
||||
{
|
||||
public int NumAppsFound { get; set; }
|
||||
public int NumAppsLoaded { get; set; }
|
||||
}
|
||||
}
|
|
@ -26,7 +26,8 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
public class ApplicationLibrary
|
||||
{
|
||||
public static event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
||||
public static event EventHandler<ApplicationAddedEventArgs> ApplicationAdded;
|
||||
public static event EventHandler<ApplicationCountUpdatedEventArgs> ApplicationCountUpdated;
|
||||
|
||||
private static readonly byte[] _nspIcon = GetResourceBytes("Ryujinx.Ui.assets.NSPIcon.png");
|
||||
private static readonly byte[] _xciIcon = GetResourceBytes("Ryujinx.Ui.assets.XCIIcon.png");
|
||||
|
@ -36,12 +37,14 @@ namespace Ryujinx.Ui
|
|||
|
||||
private static VirtualFileSystem _virtualFileSystem;
|
||||
private static Language _desiredTitleLanguage;
|
||||
private static bool _loadingError;
|
||||
|
||||
public static void LoadApplications(List<string> appDirs, VirtualFileSystem virtualFileSystem, Language desiredTitleLanguage)
|
||||
{
|
||||
int numApplicationsFound = 0;
|
||||
int numApplicationsLoaded = 0;
|
||||
|
||||
_loadingError = false;
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
_desiredTitleLanguage = desiredTitleLanguage;
|
||||
|
||||
|
@ -49,7 +52,7 @@ namespace Ryujinx.Ui
|
|||
List<string> applications = new List<string>();
|
||||
foreach (string appDir in appDirs)
|
||||
{
|
||||
if (Directory.Exists(appDir) == false)
|
||||
if (!Directory.Exists(appDir))
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"The \"game_dirs\" section in \"Config.json\" contains an invalid directory: \"{appDir}\"");
|
||||
|
||||
|
@ -58,67 +61,16 @@ namespace Ryujinx.Ui
|
|||
|
||||
foreach (string app in Directory.GetFiles(appDir, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if ((Path.GetExtension(app) == ".xci") ||
|
||||
(Path.GetExtension(app) == ".nro") ||
|
||||
(Path.GetExtension(app) == ".nso") ||
|
||||
(Path.GetFileName(app) == "hbl.nsp"))
|
||||
if ((Path.GetExtension(app).ToLower() == ".nsp") ||
|
||||
(Path.GetExtension(app).ToLower() == ".pfs0")||
|
||||
(Path.GetExtension(app).ToLower() == ".xci") ||
|
||||
(Path.GetExtension(app).ToLower() == ".nca") ||
|
||||
(Path.GetExtension(app).ToLower() == ".nro") ||
|
||||
(Path.GetExtension(app).ToLower() == ".nso"))
|
||||
{
|
||||
applications.Add(app);
|
||||
numApplicationsFound++;
|
||||
}
|
||||
else if ((Path.GetExtension(app) == ".nsp") || (Path.GetExtension(app) == ".pfs0"))
|
||||
{
|
||||
try
|
||||
{
|
||||
bool hasMainNca = false;
|
||||
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage());
|
||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program && !nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
hasMainNca = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMainNca)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed.");
|
||||
}
|
||||
|
||||
applications.Add(app);
|
||||
numApplicationsFound++;
|
||||
}
|
||||
else if (Path.GetExtension(app) == ".nca")
|
||||
{
|
||||
try
|
||||
{
|
||||
Nca nca = new Nca(_virtualFileSystem.KeySet, new FileStream(app, FileMode.Open, FileAccess.Read).AsStorage());
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.Header.ContentType != NcaContentType.Program || nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"{app}: The header key is incorrect or missing and therefore the NCA header content type check has failed.");
|
||||
}
|
||||
|
||||
applications.Add(app);
|
||||
numApplicationsFound++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,15 +87,16 @@ namespace Ryujinx.Ui
|
|||
|
||||
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
if ((Path.GetExtension(applicationPath) == ".nsp") ||
|
||||
(Path.GetExtension(applicationPath) == ".pfs0") ||
|
||||
(Path.GetExtension(applicationPath) == ".xci"))
|
||||
if ((Path.GetExtension(applicationPath).ToLower() == ".nsp") ||
|
||||
(Path.GetExtension(applicationPath).ToLower() == ".pfs0") ||
|
||||
(Path.GetExtension(applicationPath).ToLower() == ".xci"))
|
||||
{
|
||||
try
|
||||
{
|
||||
PartitionFileSystem pfs;
|
||||
|
||||
if (Path.GetExtension(applicationPath) == ".xci")
|
||||
bool isExeFs = false;
|
||||
|
||||
if (Path.GetExtension(applicationPath).ToLower() == ".xci")
|
||||
{
|
||||
Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage());
|
||||
|
||||
|
@ -152,13 +105,41 @@ namespace Ryujinx.Ui
|
|||
else
|
||||
{
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
|
||||
// If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
|
||||
bool hasMainNca = false;
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
|
||||
{
|
||||
if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
|
||||
{
|
||||
pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath, OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.Header.ContentType == NcaContentType.Program && !nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
hasMainNca = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
|
||||
{
|
||||
isExeFs = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMainNca && !isExeFs)
|
||||
{
|
||||
numApplicationsFound--;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the ControlFS in variable called controlFs
|
||||
IFileSystem controlFs = GetControlFs(pfs);
|
||||
|
||||
// If this is null then this is probably not a normal NSP, it's probably an ExeFS as an NSP
|
||||
if (controlFs == null)
|
||||
if (isExeFs)
|
||||
{
|
||||
applicationIcon = _nspIcon;
|
||||
|
||||
|
@ -174,6 +155,9 @@ namespace Ryujinx.Ui
|
|||
}
|
||||
else
|
||||
{
|
||||
// Store the ControlFS in variable called controlFs
|
||||
IFileSystem controlFs = GetControlFs(pfs);
|
||||
|
||||
// Creates NACP class from the NACP file
|
||||
controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp", OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
|
@ -182,31 +166,7 @@ namespace Ryujinx.Ui
|
|||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = controlData.DisplayVersion;
|
||||
|
||||
titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
}
|
||||
|
||||
titleId = controlData.PresenceGroupId.ToString("x16");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = controlData.SaveDataOwnerId.ToString("x16");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||
}
|
||||
|
||||
developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(developer))
|
||||
{
|
||||
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
||||
}
|
||||
GetNameIdDeveloper(controlData, out titleName, out titleId, out developer);
|
||||
|
||||
// Read the icon from the ControlFS and store it as a byte array
|
||||
try
|
||||
|
@ -244,25 +204,35 @@ namespace Ryujinx.Ui
|
|||
|
||||
if (applicationIcon == null)
|
||||
{
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
||||
applicationIcon = Path.GetExtension(applicationPath).ToLower() == ".xci" ? _xciIcon : _nspIcon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MissingKeyException exception)
|
||||
{
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
||||
applicationIcon = Path.GetExtension(applicationPath).ToLower() == ".xci" ? _xciIcon : _nspIcon;
|
||||
|
||||
Logger.PrintWarning(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".xci" ? _xciIcon : _nspIcon;
|
||||
applicationIcon = Path.GetExtension(applicationPath).ToLower() == ".xci" ? _xciIcon : _nspIcon;
|
||||
|
||||
Logger.PrintWarning(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.PrintError(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
||||
Logger.PrintDebug(LogClass.Application, exception.ToString());
|
||||
|
||||
numApplicationsFound--;
|
||||
_loadingError = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (Path.GetExtension(applicationPath) == ".nro")
|
||||
else if (Path.GetExtension(applicationPath).ToLower() == ".nro")
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(file);
|
||||
|
||||
|
@ -273,67 +243,87 @@ namespace Ryujinx.Ui
|
|||
return reader.ReadBytes(size);
|
||||
}
|
||||
|
||||
file.Seek(24, SeekOrigin.Begin);
|
||||
int assetOffset = reader.ReadInt32();
|
||||
|
||||
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
|
||||
try
|
||||
{
|
||||
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
|
||||
file.Seek(24, SeekOrigin.Begin);
|
||||
|
||||
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
|
||||
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
||||
int assetOffset = reader.ReadInt32();
|
||||
|
||||
ulong nacpOffset = reader.ReadUInt64();
|
||||
ulong nacpSize = reader.ReadUInt64();
|
||||
|
||||
// Reads and stores game icon as byte array
|
||||
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
||||
|
||||
// Creates memory stream out of byte array which is the NACP
|
||||
using (MemoryStream stream = new MemoryStream(Read(assetOffset + (int)nacpOffset, (int)nacpSize)))
|
||||
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
|
||||
{
|
||||
// Creates NACP class from the memory stream
|
||||
Nacp controlData = new Nacp(stream);
|
||||
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
|
||||
|
||||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = controlData.DisplayVersion;
|
||||
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
|
||||
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
||||
|
||||
titleName = controlData.Descriptions[(int)_desiredTitleLanguage].Title;
|
||||
ulong nacpOffset = reader.ReadUInt64();
|
||||
ulong nacpSize = reader.ReadUInt64();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
// Reads and stores game icon as byte array
|
||||
applicationIcon = Read(assetOffset + iconOffset, (int) iconSize);
|
||||
|
||||
// Creates memory stream out of byte array which is the NACP
|
||||
using (MemoryStream stream = new MemoryStream(Read(assetOffset + (int) nacpOffset, (int) nacpSize)))
|
||||
{
|
||||
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
}
|
||||
// Creates NACP class from the memory stream
|
||||
Nacp controlData = new Nacp(stream);
|
||||
|
||||
titleId = controlData.PresenceGroupId.ToString("x16");
|
||||
// Get the title name, title ID, developer name and version number from the NACP
|
||||
version = controlData.DisplayVersion;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = controlData.SaveDataOwnerId.ToString("x16");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleId))
|
||||
{
|
||||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||
}
|
||||
|
||||
developer = controlData.Descriptions[(int)_desiredTitleLanguage].Developer;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(developer))
|
||||
{
|
||||
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
||||
GetNameIdDeveloper(controlData, out titleName, out titleId, out developer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
applicationIcon = _nroIcon;
|
||||
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch
|
||||
{
|
||||
applicationIcon = _nroIcon;
|
||||
Logger.PrintError(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
||||
|
||||
numApplicationsFound--;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// If its an NCA or NSO we just set defaults
|
||||
else if ((Path.GetExtension(applicationPath) == ".nca") || (Path.GetExtension(applicationPath) == ".nso"))
|
||||
else if (Path.GetExtension(applicationPath).ToLower() == ".nca")
|
||||
{
|
||||
applicationIcon = Path.GetExtension(applicationPath) == ".nca" ? _ncaIcon : _nsoIcon;
|
||||
try
|
||||
{
|
||||
Nca nca = new Nca(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage());
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.Header.ContentType != NcaContentType.Program || nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
numApplicationsFound--;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Logger.PrintWarning(LogClass.Application, $"The NCA header content type check has failed. This is usually because the header key is incorrect or missing. Errored File: {applicationPath}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.PrintError(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
||||
|
||||
numApplicationsFound--;
|
||||
_loadingError = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
applicationIcon = _ncaIcon;
|
||||
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||
}
|
||||
// If its an NSO we just set defaults
|
||||
else if (Path.GetExtension(applicationPath).ToLower() == ".nso")
|
||||
{
|
||||
applicationIcon = _nsoIcon;
|
||||
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||
}
|
||||
}
|
||||
|
@ -373,12 +363,30 @@ namespace Ryujinx.Ui
|
|||
numApplicationsLoaded++;
|
||||
|
||||
OnApplicationAdded(new ApplicationAddedEventArgs()
|
||||
{
|
||||
AppData = data,
|
||||
{
|
||||
AppData = data
|
||||
});
|
||||
|
||||
OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs()
|
||||
{
|
||||
NumAppsFound = numApplicationsFound,
|
||||
NumAppsLoaded = numApplicationsLoaded
|
||||
});
|
||||
}
|
||||
|
||||
OnApplicationCountUpdated(new ApplicationCountUpdatedEventArgs()
|
||||
{
|
||||
NumAppsFound = numApplicationsFound,
|
||||
NumAppsLoaded = numApplicationsLoaded
|
||||
});
|
||||
|
||||
if (_loadingError)
|
||||
{
|
||||
Gtk.Application.Invoke(delegate
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("One or more files encountered were not of a valid type, check logs for more info.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected static void OnApplicationAdded(ApplicationAddedEventArgs e)
|
||||
|
@ -386,6 +394,11 @@ namespace Ryujinx.Ui
|
|||
ApplicationAdded?.Invoke(null, e);
|
||||
}
|
||||
|
||||
protected static void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
|
||||
{
|
||||
ApplicationCountUpdated?.Invoke(null, e);
|
||||
}
|
||||
|
||||
private static byte[] GetResourceBytes(string resourceName)
|
||||
{
|
||||
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
|
||||
|
@ -433,7 +446,7 @@ namespace Ryujinx.Ui
|
|||
internal static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action<ApplicationMetadata> modifyFunction = null)
|
||||
{
|
||||
string metadataFolder = Path.Combine(_virtualFileSystem.GetBasePath(), "games", titleId, "gui");
|
||||
string metadataFile = Path.Combine(metadataFolder, "metadata.json");
|
||||
string metadataFile = Path.Combine(metadataFolder, "metadata.json");
|
||||
|
||||
IJsonFormatterResolver resolver = CompositeResolver.Create(new[] { StandardResolver.AllowPrivateSnakeCase });
|
||||
|
||||
|
@ -497,5 +510,50 @@ namespace Ryujinx.Ui
|
|||
|
||||
return readableString;
|
||||
}
|
||||
|
||||
private static void GetNameIdDeveloper(Nacp controlData, out string titleName, out string titleId, out string developer)
|
||||
{
|
||||
Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
|
||||
|
||||
NacpDescription nacpDescription = controlData.Descriptions.ToList().Find(x => x.Language == desiredTitleLanguage);
|
||||
|
||||
if (nacpDescription != null)
|
||||
{
|
||||
titleName = nacpDescription.Title;
|
||||
developer = nacpDescription.Developer;
|
||||
}
|
||||
else
|
||||
{
|
||||
titleName = null;
|
||||
developer = null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(titleName))
|
||||
{
|
||||
titleName = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(developer))
|
||||
{
|
||||
developer = controlData.Descriptions.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Developer)).Developer;
|
||||
}
|
||||
|
||||
if (controlData.PresenceGroupId != 0)
|
||||
{
|
||||
titleId = controlData.PresenceGroupId.ToString("x16");
|
||||
}
|
||||
else if (controlData.SaveDataOwnerId != 0)
|
||||
{
|
||||
titleId = controlData.SaveDataOwnerId.ToString("x16");
|
||||
}
|
||||
else if (controlData.AddOnContentBaseId != 0)
|
||||
{
|
||||
titleId = (controlData.AddOnContentBaseId - 0x1000).ToString("x16");
|
||||
}
|
||||
else
|
||||
{
|
||||
titleId = "0000000000000000";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -7,14 +7,14 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
internal class GtkDialog
|
||||
{
|
||||
internal static void CreateErrorDialog(string errorMessage)
|
||||
internal static void CreateDialog(string title, string text, string secondaryText)
|
||||
{
|
||||
MessageDialog errorDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, null)
|
||||
{
|
||||
Title = "Ryujinx - Error",
|
||||
Title = title,
|
||||
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
|
||||
Text = "Ryujinx has encountered an error",
|
||||
SecondaryText = errorMessage,
|
||||
Text = text,
|
||||
SecondaryText = secondaryText,
|
||||
WindowPosition = WindowPosition.Center
|
||||
};
|
||||
errorDialog.SetSizeRequest(100, 20);
|
||||
|
@ -50,5 +50,15 @@ namespace Ryujinx.Ui
|
|||
messageDialog.Run();
|
||||
messageDialog.Dispose();
|
||||
}
|
||||
|
||||
internal static void CreateWarningDialog(string text, string secondaryText)
|
||||
{
|
||||
CreateDialog("Ryujinx - Warning", text, secondaryText);
|
||||
}
|
||||
|
||||
internal static void CreateErrorDialog(string errorMessage)
|
||||
{
|
||||
CreateDialog("Ryujinx - Error", "Ryujinx has encountered an error", errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@ using Gtk;
|
|||
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;
|
||||
|
@ -27,14 +28,19 @@ namespace Ryujinx.Ui
|
|||
|
||||
private static GlScreen _screen;
|
||||
|
||||
private static AutoResetEvent _screenExitStatus = new AutoResetEvent(false);
|
||||
|
||||
private static ListStore _tableStore;
|
||||
|
||||
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;
|
||||
|
@ -57,6 +63,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
|
||||
|
||||
|
@ -68,7 +76,8 @@ namespace Ryujinx.Ui
|
|||
|
||||
DeleteEvent += Window_Close;
|
||||
|
||||
ApplicationLibrary.ApplicationAdded += Application_Added;
|
||||
ApplicationLibrary.ApplicationAdded += Application_Added;
|
||||
ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
||||
|
||||
_gameTable.ButtonReleaseEvent += Row_Clicked;
|
||||
|
||||
|
@ -113,6 +122,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),
|
||||
|
@ -131,13 +147,41 @@ namespace Ryujinx.Ui
|
|||
_tableStore.SetSortColumnId(0, SortType.Descending);
|
||||
|
||||
UpdateColumns();
|
||||
#pragma warning disable CS4014
|
||||
UpdateGameTable();
|
||||
#pragma warning restore CS4014
|
||||
|
||||
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)
|
||||
|
@ -205,7 +249,7 @@ namespace Ryujinx.Ui
|
|||
return instance;
|
||||
}
|
||||
|
||||
internal static async Task UpdateGameTable()
|
||||
internal static void UpdateGameTable()
|
||||
{
|
||||
if (_updatingGameTable)
|
||||
{
|
||||
|
@ -216,17 +260,23 @@ namespace Ryujinx.Ui
|
|||
|
||||
_tableStore.Clear();
|
||||
|
||||
await Task.Run(() => ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs,
|
||||
_virtualFileSystem, ConfigurationState.Instance.System.Language));
|
||||
Thread applicationLibraryThread = new Thread(() =>
|
||||
{
|
||||
ApplicationLibrary.LoadApplications(ConfigurationState.Instance.Ui.GameDirs,
|
||||
_virtualFileSystem, ConfigurationState.Instance.System.Language);
|
||||
|
||||
_updatingGameTable = false;
|
||||
_updatingGameTable = false;
|
||||
});
|
||||
applicationLibraryThread.Name = "GUI.ApplicationLibraryThread";
|
||||
applicationLibraryThread.IsBackground = true;
|
||||
applicationLibraryThread.Start();
|
||||
}
|
||||
|
||||
internal void LoadApplication(string path)
|
||||
{
|
||||
if (_gameLoaded)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("A game has already been loaded. Please close the emulator and try again");
|
||||
GtkDialog.CreateDialog("Ryujinx", "A game has already been loaded", "Please close it first and try again.");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -295,10 +345,20 @@ namespace Ryujinx.Ui
|
|||
|
||||
_emulationContext = device;
|
||||
|
||||
_screenExitStatus.Reset();
|
||||
|
||||
#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;
|
||||
|
@ -333,6 +393,8 @@ namespace Ryujinx.Ui
|
|||
|
||||
DiscordIntegrationModule.SwitchToMainMenu();
|
||||
|
||||
_screenExitStatus.Set();
|
||||
|
||||
Application.Invoke(delegate
|
||||
{
|
||||
_stopEmulation.Sensitive = false;
|
||||
|
@ -357,6 +419,11 @@ namespace Ryujinx.Ui
|
|||
|
||||
private void End(HLE.Switch device)
|
||||
{
|
||||
|
||||
#if USE_DEBUGGING
|
||||
_debugger.Dispose();
|
||||
#endif
|
||||
|
||||
if (_ending)
|
||||
{
|
||||
return;
|
||||
|
@ -367,12 +434,17 @@ namespace Ryujinx.Ui
|
|||
if (device != null)
|
||||
{
|
||||
UpdateGameMetadata(device.System.TitleIdText);
|
||||
|
||||
if (_screen != null)
|
||||
{
|
||||
_screen.Exit();
|
||||
_screenExitStatus.WaitOne();
|
||||
}
|
||||
}
|
||||
|
||||
Dispose();
|
||||
|
||||
Profile.FinishProfiling();
|
||||
device?.Dispose();
|
||||
DiscordIntegrationModule.Exit();
|
||||
Logger.Shutdown();
|
||||
Application.Quit();
|
||||
|
@ -419,9 +491,22 @@ namespace Ryujinx.Ui
|
|||
args.AppData.FileExtension,
|
||||
args.AppData.FileSize,
|
||||
args.AppData.Path);
|
||||
});
|
||||
}
|
||||
|
||||
private void ApplicationCount_Updated(object sender, ApplicationCountUpdatedEventArgs args)
|
||||
{
|
||||
Application.Invoke(delegate
|
||||
{
|
||||
_progressLabel.Text = $"{args.NumAppsLoaded}/{args.NumAppsFound} Games Loaded";
|
||||
_progressBar.Value = (float)args.NumAppsLoaded / args.NumAppsFound;
|
||||
float barValue = 0;
|
||||
|
||||
if (args.NumAppsFound != 0)
|
||||
{
|
||||
barValue = (float)args.NumAppsLoaded / args.NumAppsFound;
|
||||
}
|
||||
|
||||
_progressBar.Value = barValue;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -506,13 +591,11 @@ namespace Ryujinx.Ui
|
|||
|
||||
private void Exit_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
_screen?.Exit();
|
||||
End(_emulationContext);
|
||||
}
|
||||
|
||||
private void Window_Close(object sender, DeleteEventArgs args)
|
||||
{
|
||||
_screen?.Exit();
|
||||
End(_emulationContext);
|
||||
}
|
||||
|
||||
|
@ -825,9 +908,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
private void RefreshList_Pressed(object sender, ButtonReleaseEventArgs args)
|
||||
{
|
||||
#pragma warning disable CS4014
|
||||
UpdateGameTable();
|
||||
#pragma warning restore CS4014
|
||||
}
|
||||
|
||||
private static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -438,9 +438,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
MainWindow.SaveConfig();
|
||||
MainWindow.ApplyTheme();
|
||||
#pragma warning disable CS4014
|
||||
MainWindow.UpdateGameTable();
|
||||
#pragma warning restore CS4014
|
||||
Dispose();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue