Merge branch 'master' into master

This commit is contained in:
MelonSpeedruns 2020-02-06 13:53:24 -05:00 committed by GitHub
commit b7c29fa377
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 2304 additions and 2119 deletions

View file

@ -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>

View file

@ -101,6 +101,7 @@ namespace Ryujinx.Audio
}
_tracks.Clear();
_context.Dispose();
}
/// <summary>

View file

@ -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>

View file

@ -19,6 +19,7 @@ namespace Ryujinx.Common.Logging
ServiceAm,
ServiceApm,
ServiceAudio,
ServiceBcat,
ServiceBsd,
ServiceBtm,
ServiceCaps,

View file

@ -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>

View 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();
}
}
}

View file

@ -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
{

View file

@ -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;

View file

@ -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();

View file

@ -1,6 +1,6 @@
using System;
namespace Ryujinx.Profiler
namespace Ryujinx.Debugger.Profiler
{
public struct ProfileConfig : IEquatable<ProfileConfig>
{

View file

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Profiler.UI
namespace Ryujinx.Debugger.Profiler
{
public static class ProfileSorters
{

View file

@ -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>

View file

@ -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;
}
}

View file

@ -1,4 +1,4 @@
namespace Ryujinx.Profiler
namespace Ryujinx.Debugger.Profiler
{
public enum TimingFlagType
{

View file

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Profiler
namespace Ryujinx.Debugger.Profiler
{
public struct Timestamp
{

View 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>

View 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);
}
}
}

View 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>

View 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);
}
}
}
}
}

View 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>

View 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);
}
}
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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;
}
}
}

View 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;
}
}
}

View file

@ -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>

View file

@ -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);
}

View file

@ -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;

View file

@ -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>

View file

@ -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)
};

View file

@ -53,6 +53,7 @@ namespace Ryujinx.Graphics.Gpu.State
YControl = 0x4eb,
FirstVertex = 0x50d,
FirstInstance = 0x50e,
PointSize = 0x546,
ResetCounter = 0x54c,
RtDepthStencilEnable = 0x54e,
ConditionState = 0x554,

View file

@ -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)

View file

@ -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;

View file

@ -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();
}

View file

@ -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);

View file

@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.Shader
ComputeLocalSizeX,
ComputeLocalSizeY,
ComputeLocalSizeZ,
ComputeLocalMemorySize,
ComputeSharedMemorySize,
IsTextureBuffer,
IsTextureRectangle,

View file

@ -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:

View file

@ -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;

View file

@ -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);

View file

@ -1131,5 +1131,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
throw new UndefinedInstructionException(e.Address, e.OpCode);
}
protected override void Destroy()
{
CpuMemory.Dispose();
}
}
}

View file

@ -1141,6 +1141,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
Owner.Translator.Execute(Context, entrypoint);
Context.Dispose();
ThreadExit();
}

View file

@ -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);
}

View file

@ -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)
{

View file

@ -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;

View 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);
}
}
}

View file

@ -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;

View file

@ -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();
}

View file

@ -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)

View file

@ -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;
}
}
}

View file

@ -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());
}
}
}
}

View file

@ -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
}
}

View file

@ -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)
{

View file

@ -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)]

View file

@ -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;

View file

@ -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();

View file

@ -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)
{

View file

@ -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++)
{

View file

@ -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;

View file

@ -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();

View file

@ -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)
{

View file

@ -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);

View file

@ -1,4 +1,4 @@
using Ryujinx.Profiler;
using Ryujinx.Debugger.Profiler;
using System.Diagnostics;
using System.Timers;

View file

@ -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" />

View file

@ -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>

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -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();

View file

@ -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" />

View file

@ -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; }
}
}
}

View file

@ -0,0 +1,10 @@
using System;
namespace Ryujinx.Ui
{
public class ApplicationCountUpdatedEventArgs : EventArgs
{
public int NumAppsFound { get; set; }
public int NumAppsLoaded { get; set; }
}
}

View file

@ -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";
}
}
}
}

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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)

View file

@ -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>

View file

@ -438,9 +438,7 @@ namespace Ryujinx.Ui
MainWindow.SaveConfig();
MainWindow.ApplyTheme();
#pragma warning disable CS4014
MainWindow.UpdateGameTable();
#pragma warning restore CS4014
Dispose();
}