From 7b0576db71e57e663e3c59ef80939fdb0b0c8be7 Mon Sep 17 00:00:00 2001 From: Xpl0itR Date: Fri, 31 Jan 2020 18:21:46 +0000 Subject: [PATCH 1/9] Fix application list (#891) * Fix application list * Convert file extensions to lowercase before comparing * AcK's requested changes * fixed bug found by gdkchan's requested changes * Account for mismatch between LibHac.TitleLanguage and ...System.Language --- Ryujinx.HLE/HOS/Horizon.cs | 14 +- Ryujinx/Ui/ApplicationAddedEventArgs.cs | 6 +- .../Ui/ApplicationCountUpdatedEventArgs.cs | 10 + Ryujinx/Ui/ApplicationLibrary.cs | 348 ++++++++++-------- Ryujinx/Ui/MainWindow.cs | 36 +- Ryujinx/Ui/SwitchSettings.cs | 2 - 6 files changed, 248 insertions(+), 168 deletions(-) create mode 100644 Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index e4e0894340..7254a5ed57 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -617,19 +617,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; } diff --git a/Ryujinx/Ui/ApplicationAddedEventArgs.cs b/Ryujinx/Ui/ApplicationAddedEventArgs.cs index 85a2f5a182..5e09389b5d 100644 --- a/Ryujinx/Ui/ApplicationAddedEventArgs.cs +++ b/Ryujinx/Ui/ApplicationAddedEventArgs.cs @@ -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; } } -} +} \ No newline at end of file diff --git a/Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs b/Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs new file mode 100644 index 0000000000..78e2e50cef --- /dev/null +++ b/Ryujinx/Ui/ApplicationCountUpdatedEventArgs.cs @@ -0,0 +1,10 @@ +using System; + +namespace Ryujinx.Ui +{ + public class ApplicationCountUpdatedEventArgs : EventArgs + { + public int NumAppsFound { get; set; } + public int NumAppsLoaded { get; set; } + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/ApplicationLibrary.cs b/Ryujinx/Ui/ApplicationLibrary.cs index 9d0f2617d2..3707cc634c 100644 --- a/Ryujinx/Ui/ApplicationLibrary.cs +++ b/Ryujinx/Ui/ApplicationLibrary.cs @@ -26,7 +26,8 @@ namespace Ryujinx.Ui { public class ApplicationLibrary { - public static event EventHandler ApplicationAdded; + public static event EventHandler ApplicationAdded; + public static event EventHandler 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 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 applications = new List(); 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 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"; + } + } } } diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 02fd801b68..61c59d356c 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -72,7 +72,8 @@ namespace Ryujinx.Ui DeleteEvent += Window_Close; - ApplicationLibrary.ApplicationAdded += Application_Added; + ApplicationLibrary.ApplicationAdded += Application_Added; + ApplicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated; _gameTable.ButtonReleaseEvent += Row_Clicked; @@ -135,9 +136,7 @@ namespace Ryujinx.Ui _tableStore.SetSortColumnId(0, SortType.Descending); UpdateColumns(); -#pragma warning disable CS4014 UpdateGameTable(); -#pragma warning restore CS4014 Task.Run(RefreshFirmwareLabel); } @@ -209,7 +208,7 @@ namespace Ryujinx.Ui return instance; } - internal static async Task UpdateGameTable() + internal static void UpdateGameTable() { if (_updatingGameTable) { @@ -220,10 +219,16 @@ 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) @@ -423,9 +428,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; }); } @@ -838,9 +856,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) diff --git a/Ryujinx/Ui/SwitchSettings.cs b/Ryujinx/Ui/SwitchSettings.cs index 8bd164d81f..72ac49604a 100644 --- a/Ryujinx/Ui/SwitchSettings.cs +++ b/Ryujinx/Ui/SwitchSettings.cs @@ -438,9 +438,7 @@ namespace Ryujinx.Ui MainWindow.SaveConfig(); MainWindow.ApplyTheme(); -#pragma warning disable CS4014 MainWindow.UpdateGameTable(); -#pragma warning restore CS4014 Dispose(); } From a1a5341bafa82d4346cb4ee61f43dcd94d53a6de Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 1 Feb 2020 20:18:58 -0300 Subject: [PATCH 2/9] Support flat interpolation qualifier on shaders (#915) --- Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs index 25bf259255..664c798b86 100644 --- a/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs @@ -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); From f373f870f769ef65c435aa307dc3fa1be6dc6fae Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sat, 1 Feb 2020 20:19:46 -0300 Subject: [PATCH 3/9] Support configurable point size (#916) --- Ryujinx.Graphics.GAL/IPipeline.cs | 2 ++ Ryujinx.Graphics.Gpu/Engine/Methods.cs | 16 ++++++++++++++++ Ryujinx.Graphics.Gpu/State/MethodOffset.cs | 1 + Ryujinx.Graphics.OpenGL/Pipeline.cs | 5 +++++ 4 files changed, 24 insertions(+) diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs index 41e35dd40a..1a5f1cf02f 100644 --- a/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/Ryujinx.Graphics.GAL/IPipeline.cs @@ -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); diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs index 31769f5a23..d9e7582b7a 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs @@ -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); } + /// + /// Updates host point size based on guest GPU state. + /// + /// Current GPU state + private void UpdatePointSizeState(GpuState state) + { + float size = state.Get(MethodOffset.PointSize); + + _context.Renderer.Pipeline.SetPointSize(size); + } + /// /// Updates host primitive restart based on guest GPU state. /// diff --git a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs index a89fc3797e..730ff40afa 100644 --- a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs +++ b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs @@ -53,6 +53,7 @@ namespace Ryujinx.Graphics.Gpu.State YControl = 0x4eb, FirstVertex = 0x50d, FirstInstance = 0x50e, + PointSize = 0x546, ResetCounter = 0x54c, RtDepthStencilEnable = 0x54e, ConditionState = 0x554, diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs index e308becfe6..c2a42370d0 100644 --- a/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -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) From ea14a955243705b5d5b22868c30c174e6524b4d3 Mon Sep 17 00:00:00 2001 From: Thog Date: Sun, 2 Feb 2020 04:24:17 +0100 Subject: [PATCH 4/9] Fix inconsistencies with UserId (#906) * Fix inconsistencies with UserId The account user id isn't an UUID. This PR adds a new UserId type with the correct value ordering to avoid mismatch with LibHac's Uid. This also fix an hardcoded value of the UserId. As the userid has been invalid for quite some time (and to avoid forcing users to their recreate saves), the userid has been changed to "00000000000000010000000000000000". Also implement a stub for IApplicationFunctions::GetSaveDataSize. (see the sources for the reason) Fix #626 * Address jd's & Ac_k's comments --- Ryujinx.HLE/FileSystem/SaveInfo.cs | 6 +- Ryujinx.HLE/HOS/Horizon.cs | 3 +- .../Acc/AccountService/AccountUtils.cs | 11 ++- .../Acc/IAccountServiceForApplication.cs | 18 ++--- .../Account/Acc/IManagerForApplication.cs | 5 +- .../HOS/Services/Account/Acc/Types/UserId.cs | 81 +++++++++++++++++++ .../Services/Account/Acc/Types/UserProfile.cs | 4 +- .../Am/AppletAE/Storage/StorageHelper.cs | 8 +- .../ApplicationProxy/IApplicationFunctions.cs | 23 +++++- .../HOS/Services/Friend/IServiceCreator.cs | 5 +- .../FriendService/Types/Friend.cs | 4 +- .../FriendService/Types/UserPresence.cs | 4 +- .../Friend/ServiceCreator/IFriendService.cs | 39 +++++---- .../ServiceCreator/INotificationService.cs | 10 +-- .../NotificationEventHandler.cs | 7 +- .../HOS/Services/Prepo/IPrepoService.cs | 6 +- .../QueryPlayStatisticsManager.cs | 7 +- Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs | 3 +- 18 files changed, 172 insertions(+), 72 deletions(-) create mode 100644 Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs diff --git a/Ryujinx.HLE/FileSystem/SaveInfo.cs b/Ryujinx.HLE/FileSystem/SaveInfo.cs index 0fc355758e..96f2f020e7 100644 --- a/Ryujinx.HLE/FileSystem/SaveInfo.cs +++ b/Ryujinx.HLE/FileSystem/SaveInfo.cs @@ -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; diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 7254a5ed57..a2bff7f622 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -669,8 +669,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; diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/AccountUtils.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/AccountUtils.cs index 7a70025ad0..8b7f1e54af 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/AccountUtils.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/AccountUtils.cs @@ -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(); } - 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); } diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs index ec26d11faf..a1c2c836e5 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs @@ -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(); 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 public ResultCode GetProfile(ServiceCtx context) { - UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10)); + UserId userId = context.RequestData.ReadStruct(); 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 public ResultCode GetBaasAccountManagerForApplication(ServiceCtx context) { - UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10)); + UserId userId = context.RequestData.ReadStruct(); 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(); 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(); if (userId.IsNull) { diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs index aa9e07bde6..d26a79da0c 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs @@ -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; diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs new file mode 100644 index 0000000000..06b6b55697 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs @@ -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 + { + 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); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs index 25004c2435..6758f7bb7d 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs @@ -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; diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs index 9c96221e2e..227cfdae12 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs @@ -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(); } diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs index 0840a91372..3ae24c555a 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -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 userId = context.RequestData.ReadStruct().ToLibHacUid(); TitleId titleId = new TitleId(context.Process.TitleId); BlitStruct 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().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) diff --git a/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs index cec3c422ad..4324f13e35 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs @@ -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 + // CreateNotificationService(nn::account::Uid userId) -> object public ResultCode CreateNotificationService(ServiceCtx context) { - UInt128 userId = context.RequestData.ReadStruct(); + UserId userId = context.RequestData.ReadStruct(); if (userId.IsNull) { diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs index 4947a5ce95..87f54bf3e2 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs @@ -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)] diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs index 5fe8bfd727..e7568a4aa5 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs @@ -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; diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs index 7492c5a72d..fa1b473861 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs @@ -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 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(); + UserId userId = context.RequestData.ReadStruct(); FriendFilter filter = context.RequestData.ReadStruct(); // 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 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(); + UserId userId = context.RequestData.ReadStruct(); FriendFilter filter = context.RequestData.ReadStruct(); // 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(); + UserId userId = context.RequestData.ReadStruct(); - 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(); + UserId userId = context.RequestData.ReadStruct(); - 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) public ResultCode UpdateUserPresence(ServiceCtx context) { - UInt128 uuid = context.RequestData.ReadStruct(); + UserId uuid = context.RequestData.ReadStruct(); // Pid placeholder context.RequestData.ReadInt64(); diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs index 1ff37442c1..87d998c393 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs @@ -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) { diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs index 19b15416a0..4a698f812d 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs @@ -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++) { diff --git a/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs b/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs index f5c8a873df..e882032bd1 100644 --- a/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs +++ b/Ryujinx.HLE/HOS/Services/Prepo/IPrepoService.cs @@ -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() : 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(); diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs index 925a2593b1..ce85ff2010 100644 --- a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs @@ -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 applicationPlayStatistics = new Dictionary(); + private static Dictionary applicationPlayStatistics = new Dictionary(); 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() : new UserId(); if (byUserId) { diff --git a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs index 2c3b188fe7..3784cb780c 100644 --- a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs +++ b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs @@ -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); From 796e5d14b4fadc15439d273f8ff8f9e9afc4033a Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 2 Feb 2020 00:25:52 -0300 Subject: [PATCH 5/9] Use correct shader local memory size instead of a hardcoded size (#914) * Use correct shader local size instead of a hardcoded size * Remove unused uniform block * Update XML doc * Local memory size has 23 bits on maxwell * Generate compute QMD struct from nv open doc header * Remove dummy arrays when shared or local memory is not used, other improvements --- Ryujinx.Graphics.Gpu/Engine/Compute.cs | 40 +-- Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs | 173 ----------- Ryujinx.Graphics.Gpu/Engine/ComputeQmd.cs | 275 ++++++++++++++++++ .../Ryujinx.Graphics.Gpu.csproj | 8 + Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 31 +- Ryujinx.Graphics.OpenGL/Program.cs | 9 +- .../CodeGen/Glsl/Declarations.cs | 42 ++- Ryujinx.Graphics.Shader/QueryInfoName.cs | 1 + .../Translation/ShaderConfig.cs | 6 + 9 files changed, 365 insertions(+), 220 deletions(-) delete mode 100644 Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs create mode 100644 Ryujinx.Graphics.Gpu/Engine/ComputeQmd.cs diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs index d24d2d8d72..9178cfb0d8 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Compute.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs @@ -17,29 +17,31 @@ namespace Ryujinx.Graphics.Gpu.Engine /// Method call argument public void Dispatch(GpuState state, int argument) { - uint dispatchParamsAddress = (uint)state.Get(MethodOffset.DispatchParamsAddress); + uint qmdAddress = (uint)state.Get(MethodOffset.DispatchParamsAddress); - var dispatchParams = _context.MemoryAccessor.Read((ulong)dispatchParamsAddress << 8); + var qmd = _context.MemoryAccessor.Read((ulong)qmdAddress << 8); GpuVa shaderBaseAddress = state.Get(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(MethodOffset.SamplerPoolState); - TextureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, dispatchParams.SamplerIndex); + TextureManager.SetComputeSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, qmd.SamplerIndex); var texturePool = state.Get(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); } diff --git a/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs b/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs deleted file mode 100644 index c19b43d81e..0000000000 --- a/Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs +++ /dev/null @@ -1,173 +0,0 @@ -using Ryujinx.Graphics.Gpu.State; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Engine -{ - /// - /// Compute uniform buffer parameters. - /// - struct UniformBufferParams - { - public int AddressLow; - public int AddressHighAndSize; - - /// - /// Packs the split address to a 64-bits integer. - /// - /// Uniform buffer GPU virtual address - public ulong PackAddress() - { - return (uint)AddressLow | ((ulong)(AddressHighAndSize & 0xff) << 32); - } - - /// - /// Unpacks the uniform buffer size in bytes. - /// - /// Uniform buffer size in bytes - public ulong UnpackSize() - { - return (ulong)((AddressHighAndSize >> 15) & 0x1ffff); - } - } - - /// - /// Compute dispatch parameters. - /// - 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; - - /// - /// Uniform buffer parameters. - /// - public Span 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; - - /// - /// Unpacks the work group X size. - /// - /// Work group X size - public int UnpackGridSizeX() - { - return GridSizeX & 0x7fffffff; - } - - /// - /// Unpacks the work group Y size. - /// - /// Work group Y size - public int UnpackGridSizeY() - { - return GridSizeYZ & 0xffff; - } - - /// - /// Unpacks the work group Z size. - /// - /// Work group Z size - public int UnpackGridSizeZ() - { - return (GridSizeYZ >> 16) & 0xffff; - } - - /// - /// Unpacks the local group X size. - /// - /// Local group X size - public int UnpackBlockSizeX() - { - return (BlockSizeX >> 16) & 0xffff; - } - - /// - /// Unpacks the local group Y size. - /// - /// Local group Y size - public int UnpackBlockSizeY() - { - return BlockSizeYZ & 0xffff; - } - - /// - /// Unpacks the local group Z size. - /// - /// Local group Z size - public int UnpackBlockSizeZ() - { - return (BlockSizeYZ >> 16) & 0xffff; - } - - /// - /// Unpacks the uniform buffers enable mask. - /// Each bit set on the mask indicates that the respective buffer index is enabled. - /// - /// Uniform buffers enable mask - public uint UnpackUniformBuffersEnableMask() - { - return (uint)UniformBuffersConfig & 0xff; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Engine/ComputeQmd.cs b/Ryujinx.Graphics.Gpu/Engine/ComputeQmd.cs new file mode 100644 index 0000000000..35418c2d80 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Engine/ComputeQmd.cs @@ -0,0 +1,275 @@ +using Ryujinx.Graphics.Gpu.State; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Engine +{ + /// + /// Type of the dependent Queue Meta Data. + /// + enum DependentQmdType + { + Queue, + Grid + } + + /// + /// Type of the release memory barrier. + /// + enum ReleaseMembarType + { + FeNone, + FeSysmembar + } + + /// + /// Type of the CWD memory barrier. + /// + enum CwdMembarType + { + L1None, + L1Sysmembar, + L1Membar + } + + /// + /// NaN behavior of 32-bits float operations on the shader. + /// + enum Fp32NanBehavior + { + Legacy, + Fp64Compatible + } + + /// + /// NaN behavior of 32-bits float to integer conversion on the shader. + /// + enum Fp32F2iNanBehavior + { + PassZero, + PassIndefinite + } + + /// + /// Limit of calls. + /// + enum ApiVisibleCallLimit + { + _32, + NoCheck + } + + /// + /// Shared memory bank mapping mode. + /// + enum SharedMemoryBankMapping + { + FourBytesPerBank, + EightBytesPerBank + } + + /// + /// Denormal behavior of 32-bits float narrowing instructions. + /// + enum Fp32NarrowInstruction + { + KeepDenorms, + FlushDenorms + } + + /// + /// Configuration of the L1 cache. + /// + enum L1Configuration + { + DirectlyAddressableMemorySize16kb, + DirectlyAddressableMemorySize32kb, + DirectlyAddressableMemorySize48kb + } + + /// + /// Reduction operation. + /// + enum ReductionOp + { + RedAdd, + RedMin, + RedMax, + RedInc, + RedDec, + RedAnd, + RedOr, + RedXor + } + + /// + /// Reduction format. + /// + enum ReductionFormat + { + Unsigned32, + Signed32 + } + + /// + /// Size of a structure in words. + /// + enum StructureSize + { + FourWords, + OneWord + } + + /// + /// Compute Queue Meta Data. + /// + 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; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj index b9751508ef..a55c4d1ceb 100644 --- a/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj +++ b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj @@ -13,4 +13,12 @@ win-x64;osx-x64;linux-x64 + + true + + + + true + + diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index dad1b0ac2e..8aa9b1c7b5 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -51,12 +51,19 @@ namespace Ryujinx.Graphics.Gpu.Shader /// This automatically translates, compiles and adds the code to the cache if not present. /// /// GPU virtual address of the binary shader code - /// Shared memory size of the compute shader /// Local group size X of the computer shader /// Local group size Y of the computer shader /// Local group size Z of the computer shader + /// Local memory size of the compute shader + /// Shared memory size of the compute shader /// Compiled compute shader code - 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 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); @@ -237,12 +250,19 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Translates the binary Maxwell shader code to something that the host API accepts. /// /// GPU virtual address of the binary shader code - /// Shared memory size of the compute shader /// Local group size X of the computer shader /// Local group size Y of the computer shader /// Local group size Z of the computer shader + /// Local memory size of the compute shader + /// Shared memory size of the compute shader /// Compiled compute shader code - 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 +276,7 @@ namespace Ryujinx.Graphics.Gpu.Shader QueryInfoName.ComputeLocalSizeX => localSizeX, QueryInfoName.ComputeLocalSizeY => localSizeY, QueryInfoName.ComputeLocalSizeZ => localSizeZ, + QueryInfoName.ComputeLocalMemorySize => localMemorySize, QueryInfoName.ComputeSharedMemorySize => sharedMemorySize, _ => QueryInfoCommon(info) }; diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs index a8ee7ae895..fe14e9a9db 100644 --- a/Ryujinx.Graphics.OpenGL/Program.cs +++ b/Ryujinx.Graphics.OpenGL/Program.cs @@ -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; diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index 200569c48e..2e7f9f1b07 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -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(); } diff --git a/Ryujinx.Graphics.Shader/QueryInfoName.cs b/Ryujinx.Graphics.Shader/QueryInfoName.cs index c4f2cb6cc2..887c0d7d14 100644 --- a/Ryujinx.Graphics.Shader/QueryInfoName.cs +++ b/Ryujinx.Graphics.Shader/QueryInfoName.cs @@ -5,6 +5,7 @@ namespace Ryujinx.Graphics.Shader ComputeLocalSizeX, ComputeLocalSizeY, ComputeLocalSizeZ, + ComputeLocalMemorySize, ComputeSharedMemorySize, IsTextureBuffer, IsTextureRectangle, diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs index 8a0f25fe45..e3708b41d6 100644 --- a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs +++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs @@ -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: From a0e6647860b689cd2c3d865e6b235b12ec96b89a Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 3 Feb 2020 19:11:22 +0000 Subject: [PATCH 6/9] Compare shader code using a span instead of individual reads. (#917) * Compare shader code using a span instead of individual reads. * Add comment for new parameter. * Remove unnecessary Math.Min --- Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs | 2 +- Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs | 7 ++++++- Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 10 ++-------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs index 17c0006288..fbe2cbc423 100644 --- a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs @@ -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); } diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs index ffca6f339b..e89255c884 100644 --- a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs +++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs @@ -237,14 +237,19 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Gets the number of mapped or reserved pages on a given region. /// /// Start GPU virtual address of the region + /// Maximum size of the data /// Mapped size in bytes of the specified region - 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; diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 8aa9b1c7b5..fc4bf778b9 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -235,15 +235,9 @@ 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 memoryCode = _context.MemoryAccessor.GetSpan(gpuVa, (ulong)shader.Code.Length * 4); - return false; + return !MemoryMarshal.Cast(memoryCode).SequenceEqual(shader.Code); } /// From db9f8f999f2c9a50e25685424271735ed3538539 Mon Sep 17 00:00:00 2001 From: Thog Date: Thu, 6 Feb 2020 05:09:59 +0100 Subject: [PATCH 7/9] Implement IDeliveryCacheProgressService in bcat (#908) * Implement IDeliveryCacheProgressService in bcat This stub IDeliveryCacheProgressService IPC interface as we don't plan to support cache delivery. * Address jd's comments * Address jd's comment correctly * Address gdk's comments --- Ryujinx.Common/Logging/LogClass.cs | 1 + .../Bcat/ServiceCreator/IBcatService.cs | 9 +++ .../IDeliveryCacheProgressService.cs | 64 +++++++++++++++++++ .../Types/DeliveryCacheProgressImpl.cs | 18 ++++++ 4 files changed, 92 insertions(+) create mode 100644 Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheProgressService.cs create mode 100644 Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/Types/DeliveryCacheProgressImpl.cs diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs index 43efd8d58b..2aa601464b 100644 --- a/Ryujinx.Common/Logging/LogClass.cs +++ b/Ryujinx.Common/Logging/LogClass.cs @@ -19,6 +19,7 @@ namespace Ryujinx.Common.Logging ServiceAm, ServiceApm, ServiceAudio, + ServiceBcat, ServiceBsd, ServiceBtm, ServiceCaps, diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs index 1b32756a25..ff743f1535 100644 --- a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs @@ -5,5 +5,14 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator class IBcatService : IpcService { public IBcatService(ApplicationLaunchProperty applicationLaunchProperty) { } + + [Command(10100)] + // RequestSyncDeliveryCache() -> object + public ResultCode RequestSyncDeliveryCache(ServiceCtx context) + { + MakeObject(context, new IDeliveryCacheProgressService(context)); + + return ResultCode.Success; + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheProgressService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheProgressService.cs new file mode 100644 index 0000000000..cb80904879 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheProgressService.cs @@ -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 + 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 + 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()); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/Types/DeliveryCacheProgressImpl.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/Types/DeliveryCacheProgressImpl.cs new file mode 100644 index 0000000000..fb9a67be6e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/Types/DeliveryCacheProgressImpl.cs @@ -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 + } +} From f2b9a9c2b0a3d7af3b56df9ae09db8a3b2d8506c Mon Sep 17 00:00:00 2001 From: emmauss Date: Thu, 6 Feb 2020 11:25:47 +0000 Subject: [PATCH 8/9] Render Profiler in GUI (#854) * move profiler output to gui * addressed commits, rebased * removed whitespaces --- ARMeilleure/ARMeilleure.csproj | 9 + Ryujinx.Audio/Ryujinx.Audio.csproj | 4 +- Ryujinx.Common/Ryujinx.Common.csproj | 4 +- Ryujinx.Debugger/Debugger.cs | 32 + .../Profiler}/DumpProfile.cs | 2 +- .../Profiler}/InternalProfile.cs | 22 +- .../Profiler}/Profile.cs | 27 +- .../Profiler}/ProfileConfig.cs | 2 +- .../Profiler}/ProfileSorters.cs | 2 +- .../Profiler}/ProfilerConfiguration.cs | 6 +- .../Profiler}/Settings.cs | 5 +- .../Profiler}/TimingFlag.cs | 2 +- .../Profiler}/TimingInfo.cs | 2 +- .../ProfilerConfig.jsonc | 0 Ryujinx.Debugger/Ryujinx.Debugger.csproj | 42 + Ryujinx.Debugger/UI/DebuggerWidget.cs | 42 + Ryujinx.Debugger/UI/DebuggerWidget.glade | 44 + Ryujinx.Debugger/UI/ProfilerWidget.cs | 801 ++++++++++++++++++ Ryujinx.Debugger/UI/ProfilerWidget.glade | 232 +++++ Ryujinx.Debugger/UI/SkRenderer.cs | 23 + Ryujinx.HLE/HOS/Services/IpcService.cs | 2 +- Ryujinx.HLE/PerformanceStatistics.cs | 2 +- Ryujinx.HLE/Ryujinx.HLE.csproj | 6 +- Ryujinx.LLE/Luea.csproj | 4 +- Ryujinx.Profiler/ProfilerKeyboardHandler.cs | 28 - Ryujinx.Profiler/Ryujinx.Profiler.csproj | 39 - Ryujinx.Profiler/UI/ProfileButton.cs | 110 --- Ryujinx.Profiler/UI/ProfileWindow.cs | 773 ----------------- Ryujinx.Profiler/UI/ProfileWindowBars.cs | 85 -- Ryujinx.Profiler/UI/ProfileWindowGraph.cs | 151 ---- Ryujinx.Profiler/UI/ProfileWindowManager.cs | 95 --- .../UI/SharpFontHelpers/FontService.cs | 257 ------ .../Ryujinx.ShaderTools.csproj | 4 +- .../Ryujinx.Tests.Unicorn.csproj | 4 +- Ryujinx.Tests/Ryujinx.Tests.csproj | 4 +- Ryujinx.sln | 22 +- Ryujinx/Program.cs | 2 +- Ryujinx/Ryujinx.csproj | 8 +- Ryujinx/Ui/GLScreen.cs | 20 - Ryujinx/Ui/MainWindow.cs | 60 +- Ryujinx/Ui/MainWindow.glade | 18 +- 41 files changed, 1358 insertions(+), 1639 deletions(-) create mode 100644 Ryujinx.Debugger/Debugger.cs rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/DumpProfile.cs (97%) rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/InternalProfile.cs (93%) rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/Profile.cs (88%) rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/ProfileConfig.cs (99%) rename {Ryujinx.Profiler/UI => Ryujinx.Debugger/Profiler}/ProfileSorters.cs (97%) rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/ProfilerConfiguration.cs (94%) rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/Settings.cs (86%) rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/TimingFlag.cs (87%) rename {Ryujinx.Profiler => Ryujinx.Debugger/Profiler}/TimingInfo.cs (99%) rename {Ryujinx.Profiler => Ryujinx.Debugger}/ProfilerConfig.jsonc (100%) create mode 100644 Ryujinx.Debugger/Ryujinx.Debugger.csproj create mode 100644 Ryujinx.Debugger/UI/DebuggerWidget.cs create mode 100644 Ryujinx.Debugger/UI/DebuggerWidget.glade create mode 100644 Ryujinx.Debugger/UI/ProfilerWidget.cs create mode 100644 Ryujinx.Debugger/UI/ProfilerWidget.glade create mode 100644 Ryujinx.Debugger/UI/SkRenderer.cs delete mode 100644 Ryujinx.Profiler/ProfilerKeyboardHandler.cs delete mode 100644 Ryujinx.Profiler/Ryujinx.Profiler.csproj delete mode 100644 Ryujinx.Profiler/UI/ProfileButton.cs delete mode 100644 Ryujinx.Profiler/UI/ProfileWindow.cs delete mode 100644 Ryujinx.Profiler/UI/ProfileWindowBars.cs delete mode 100644 Ryujinx.Profiler/UI/ProfileWindowGraph.cs delete mode 100644 Ryujinx.Profiler/UI/ProfileWindowManager.cs delete mode 100644 Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs diff --git a/ARMeilleure/ARMeilleure.csproj b/ARMeilleure/ARMeilleure.csproj index 4f55243fed..9567838ecc 100644 --- a/ARMeilleure/ARMeilleure.csproj +++ b/ARMeilleure/ARMeilleure.csproj @@ -13,6 +13,15 @@ true + + true + true + + + + true + + diff --git a/Ryujinx.Audio/Ryujinx.Audio.csproj b/Ryujinx.Audio/Ryujinx.Audio.csproj index 588b691814..b541043c75 100644 --- a/Ryujinx.Audio/Ryujinx.Audio.csproj +++ b/Ryujinx.Audio/Ryujinx.Audio.csproj @@ -12,7 +12,7 @@ true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING false @@ -22,7 +22,7 @@ true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING true diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj index 7f6fa32323..43a853a409 100644 --- a/Ryujinx.Common/Ryujinx.Common.csproj +++ b/Ryujinx.Common/Ryujinx.Common.csproj @@ -12,7 +12,7 @@ true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING false @@ -22,7 +22,7 @@ true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING true diff --git a/Ryujinx.Debugger/Debugger.cs b/Ryujinx.Debugger/Debugger.cs new file mode 100644 index 0000000000..6dd3354ce9 --- /dev/null +++ b/Ryujinx.Debugger/Debugger.cs @@ -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(); + } + } +} diff --git a/Ryujinx.Profiler/DumpProfile.cs b/Ryujinx.Debugger/Profiler/DumpProfile.cs similarity index 97% rename from Ryujinx.Profiler/DumpProfile.cs rename to Ryujinx.Debugger/Profiler/DumpProfile.cs index 62a027615d..e73314d4a7 100644 --- a/Ryujinx.Profiler/DumpProfile.cs +++ b/Ryujinx.Debugger/Profiler/DumpProfile.cs @@ -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 { diff --git a/Ryujinx.Profiler/InternalProfile.cs b/Ryujinx.Debugger/Profiler/InternalProfile.cs similarity index 93% rename from Ryujinx.Profiler/InternalProfile.cs rename to Ryujinx.Debugger/Profiler/InternalProfile.cs index 0346244423..0bda9e049b 100644 --- a/Ryujinx.Profiler/InternalProfile.cs +++ b/Ryujinx.Debugger/Profiler/InternalProfile.cs @@ -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; diff --git a/Ryujinx.Profiler/Profile.cs b/Ryujinx.Debugger/Profiler/Profile.cs similarity index 88% rename from Ryujinx.Profiler/Profile.cs rename to Ryujinx.Debugger/Profiler/Profile.cs index 4dba6ea542..862aa845d1 100644 --- a/Ryujinx.Profiler/Profile.cs +++ b/Ryujinx.Debugger/Profiler/Profile.cs @@ -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 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> GetProfilingData() { -#if USE_PROFILING +#if USE_DEBUGGING if (!ProfilingEnabled()) return new List>(); 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(); diff --git a/Ryujinx.Profiler/ProfileConfig.cs b/Ryujinx.Debugger/Profiler/ProfileConfig.cs similarity index 99% rename from Ryujinx.Profiler/ProfileConfig.cs rename to Ryujinx.Debugger/Profiler/ProfileConfig.cs index 4271bd2b86..0ec3e26db2 100644 --- a/Ryujinx.Profiler/ProfileConfig.cs +++ b/Ryujinx.Debugger/Profiler/ProfileConfig.cs @@ -1,6 +1,6 @@ using System; -namespace Ryujinx.Profiler +namespace Ryujinx.Debugger.Profiler { public struct ProfileConfig : IEquatable { diff --git a/Ryujinx.Profiler/UI/ProfileSorters.cs b/Ryujinx.Debugger/Profiler/ProfileSorters.cs similarity index 97% rename from Ryujinx.Profiler/UI/ProfileSorters.cs rename to Ryujinx.Debugger/Profiler/ProfileSorters.cs index 9f66de224c..2b730af58a 100644 --- a/Ryujinx.Profiler/UI/ProfileSorters.cs +++ b/Ryujinx.Debugger/Profiler/ProfileSorters.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Ryujinx.Profiler.UI +namespace Ryujinx.Debugger.Profiler { public static class ProfileSorters { diff --git a/Ryujinx.Profiler/ProfilerConfiguration.cs b/Ryujinx.Debugger/Profiler/ProfilerConfiguration.cs similarity index 94% rename from Ryujinx.Profiler/ProfilerConfiguration.cs rename to Ryujinx.Debugger/Profiler/ProfilerConfiguration.cs index 4fe616fa99..e0842f2e1f 100644 --- a/Ryujinx.Profiler/ProfilerConfiguration.cs +++ b/Ryujinx.Debugger/Profiler/ProfilerConfiguration.cs @@ -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; } - /// /// Loads a configuration file from disk /// diff --git a/Ryujinx.Profiler/Settings.cs b/Ryujinx.Debugger/Profiler/Settings.cs similarity index 86% rename from Ryujinx.Profiler/Settings.cs rename to Ryujinx.Debugger/Profiler/Settings.cs index f0c851b229..52aa0d8427 100644 --- a/Ryujinx.Profiler/Settings.cs +++ b/Ryujinx.Debugger/Profiler/Settings.cs @@ -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; } } diff --git a/Ryujinx.Profiler/TimingFlag.cs b/Ryujinx.Debugger/Profiler/TimingFlag.cs similarity index 87% rename from Ryujinx.Profiler/TimingFlag.cs rename to Ryujinx.Debugger/Profiler/TimingFlag.cs index 0cf55bdf32..8a34ac99f3 100644 --- a/Ryujinx.Profiler/TimingFlag.cs +++ b/Ryujinx.Debugger/Profiler/TimingFlag.cs @@ -1,4 +1,4 @@ -namespace Ryujinx.Profiler +namespace Ryujinx.Debugger.Profiler { public enum TimingFlagType { diff --git a/Ryujinx.Profiler/TimingInfo.cs b/Ryujinx.Debugger/Profiler/TimingInfo.cs similarity index 99% rename from Ryujinx.Profiler/TimingInfo.cs rename to Ryujinx.Debugger/Profiler/TimingInfo.cs index 6058ddbd81..90bd63d291 100644 --- a/Ryujinx.Profiler/TimingInfo.cs +++ b/Ryujinx.Debugger/Profiler/TimingInfo.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Ryujinx.Profiler +namespace Ryujinx.Debugger.Profiler { public struct Timestamp { diff --git a/Ryujinx.Profiler/ProfilerConfig.jsonc b/Ryujinx.Debugger/ProfilerConfig.jsonc similarity index 100% rename from Ryujinx.Profiler/ProfilerConfig.jsonc rename to Ryujinx.Debugger/ProfilerConfig.jsonc diff --git a/Ryujinx.Debugger/Ryujinx.Debugger.csproj b/Ryujinx.Debugger/Ryujinx.Debugger.csproj new file mode 100644 index 0000000000..a67662cc03 --- /dev/null +++ b/Ryujinx.Debugger/Ryujinx.Debugger.csproj @@ -0,0 +1,42 @@ + + + + netcoreapp3.0 + Debug;Release;Profile Release;Profile Debug + + + + TRACE;USE_DEBUGGING + + + + TRACE;USE_DEBUGGING + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/Ryujinx.Debugger/UI/DebuggerWidget.cs b/Ryujinx.Debugger/UI/DebuggerWidget.cs new file mode 100644 index 0000000000..b2d8458dc9 --- /dev/null +++ b/Ryujinx.Debugger/UI/DebuggerWidget.cs @@ -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); + } + } +} diff --git a/Ryujinx.Debugger/UI/DebuggerWidget.glade b/Ryujinx.Debugger/UI/DebuggerWidget.glade new file mode 100644 index 0000000000..7e6e691de1 --- /dev/null +++ b/Ryujinx.Debugger/UI/DebuggerWidget.glade @@ -0,0 +1,44 @@ + + + + + + DebuggerBox + 1024 + 720 + True + False + vertical + + + True + True + True + True + + + + + + + + + + + + + + + + + + + + + True + True + 0 + + + + diff --git a/Ryujinx.Debugger/UI/ProfilerWidget.cs b/Ryujinx.Debugger/UI/ProfilerWidget.cs new file mode 100644 index 0000000000..0dc4b84f42 --- /dev/null +++ b/Ryujinx.Debugger/UI/ProfilerWidget.cs @@ -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> _unsortedProfileData; + private IComparer> _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> _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 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 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 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 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 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 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 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); + } + } + } + } +} diff --git a/Ryujinx.Debugger/UI/ProfilerWidget.glade b/Ryujinx.Debugger/UI/ProfilerWidget.glade new file mode 100644 index 0000000000..00dd4f7044 --- /dev/null +++ b/Ryujinx.Debugger/UI/ProfilerWidget.glade @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + 0 + Graph + + + 1 + Bars + + + + + ProfilerBox + True + False + 5 + 5 + 5 + 5 + vertical + 10 + + + Enable Profiler + True + True + False + True + + + False + True + 0 + + + + + True + False + + + True + True + never + in + + + + + + True + True + 0 + + + + + True + False + vertical + + + False + True + 1 + + + + + False + True + 1 + + + + + True + False + 10 + + + Show Inactive + True + True + False + True + True + + + False + True + 0 + + + + + Show Flags + True + True + False + True + True + + + False + True + 1 + + + + + Paused + True + True + False + True + + + False + True + 2 + + + + + True + False + + + True + False + View Mode: + + + False + True + 0 + + + + + True + False + viewMode + 0 + + + + 1 + + + + + False + True + 1 + + + + + False + True + 3 + + + + + True + False + + + True + False + Filter: + + + False + True + 0 + + + + + True + True + + + False + True + 1 + + + + + False + True + 4 + + + + + Step + True + True + True + + + False + True + 5 + + + + + False + True + 2 + + + + diff --git a/Ryujinx.Debugger/UI/SkRenderer.cs b/Ryujinx.Debugger/UI/SkRenderer.cs new file mode 100644 index 0000000000..a95e4542cc --- /dev/null +++ b/Ryujinx.Debugger/UI/SkRenderer.cs @@ -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); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/IpcService.cs b/Ryujinx.HLE/HOS/Services/IpcService.cs index e953868358..67df453e5c 100644 --- a/Ryujinx.HLE/HOS/Services/IpcService.cs +++ b/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -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; diff --git a/Ryujinx.HLE/PerformanceStatistics.cs b/Ryujinx.HLE/PerformanceStatistics.cs index 896ab67b09..5abf2628ee 100644 --- a/Ryujinx.HLE/PerformanceStatistics.cs +++ b/Ryujinx.HLE/PerformanceStatistics.cs @@ -1,4 +1,4 @@ -using Ryujinx.Profiler; +using Ryujinx.Debugger.Profiler; using System.Diagnostics; using System.Timers; diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index ac4b822401..e9540ab876 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -12,7 +12,7 @@ true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING false @@ -22,7 +22,7 @@ true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING true @@ -44,7 +44,7 @@ - + diff --git a/Ryujinx.LLE/Luea.csproj b/Ryujinx.LLE/Luea.csproj index 7eb546d8fe..0184d1be75 100644 --- a/Ryujinx.LLE/Luea.csproj +++ b/Ryujinx.LLE/Luea.csproj @@ -8,12 +8,12 @@ - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING false diff --git a/Ryujinx.Profiler/ProfilerKeyboardHandler.cs b/Ryujinx.Profiler/ProfilerKeyboardHandler.cs deleted file mode 100644 index e6207e8924..0000000000 --- a/Ryujinx.Profiler/ProfilerKeyboardHandler.cs +++ /dev/null @@ -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; - } - } -} diff --git a/Ryujinx.Profiler/Ryujinx.Profiler.csproj b/Ryujinx.Profiler/Ryujinx.Profiler.csproj deleted file mode 100644 index 0e089ccf52..0000000000 --- a/Ryujinx.Profiler/Ryujinx.Profiler.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - netcoreapp3.0 - win-x64;osx-x64;linux-x64 - true - Debug;Release;Profile Debug;Profile Release - - - - TRACE - - - - TRACE;USE_PROFILING - false - - - - TRACE;USE_PROFILING - true - - - - - - - - - - - - - - PreserveNewest - - - - diff --git a/Ryujinx.Profiler/UI/ProfileButton.cs b/Ryujinx.Profiler/UI/ProfileButton.cs deleted file mode 100644 index 7e2ae72884..0000000000 --- a/Ryujinx.Profiler/UI/ProfileButton.cs +++ /dev/null @@ -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; - } - } -} diff --git a/Ryujinx.Profiler/UI/ProfileWindow.cs b/Ryujinx.Profiler/UI/ProfileWindow.cs deleted file mode 100644 index 1db70bc791..0000000000 --- a/Ryujinx.Profiler/UI/ProfileWindow.cs +++ /dev/null @@ -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> _unsortedProfileData; - private IComparer> _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> _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> filter) - { - _sortAction = filter; - _profileUpdated = true; - } - -#region OnLoad - /// - /// Setup OpenGL and load resources - /// - 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 - /// - /// Respond to resize events - /// - /// Contains information on the new GameWindow size. - /// There is no need to call the base implementation. - protected override void OnResize(EventArgs e) - { - _viewportUpdated = true; - } -#endregion - -#region OnClose - /// - /// Intercept close event and hide instead - /// - protected override void OnClosing(CancelEventArgs e) - { - // Hide window - _visible = false; - _visibleChanged = true; - - // Cancel close - e.Cancel = true; - - base.OnClosing(e); - } -#endregion - -#region OnUpdateFrame - /// - /// Profile Update Loop - /// - /// Contains timing information. - /// There is no need to call the base implementation. - 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 - /// - /// Profile Render Loop - /// - /// There is no need to call the base implementation. - 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; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Profiler/UI/ProfileWindowBars.cs b/Ryujinx.Profiler/UI/ProfileWindowBars.cs deleted file mode 100644 index ab5b4fd131..0000000000 --- a/Ryujinx.Profiler/UI/ProfileWindowBars.cs +++ /dev/null @@ -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 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); - } - } - } -} diff --git a/Ryujinx.Profiler/UI/ProfileWindowGraph.cs b/Ryujinx.Profiler/UI/ProfileWindowGraph.cs deleted file mode 100644 index 6a4a52a998..0000000000 --- a/Ryujinx.Profiler/UI/ProfileWindowGraph.cs +++ /dev/null @@ -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); - } - } - } -} diff --git a/Ryujinx.Profiler/UI/ProfileWindowManager.cs b/Ryujinx.Profiler/UI/ProfileWindowManager.cs deleted file mode 100644 index 1360302933..0000000000 --- a/Ryujinx.Profiler/UI/ProfileWindowManager.cs +++ /dev/null @@ -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); - } - } - } -} diff --git a/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs b/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs deleted file mode 100644 index 32846977e9..0000000000 --- a/Ryujinx.Profiler/UI/SharpFontHelpers/FontService.cs +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj b/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj index 2f9c777278..10d66cd281 100644 --- a/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj +++ b/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj @@ -12,12 +12,12 @@ - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING false diff --git a/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj b/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj index 36310f3d2a..f99f504a75 100644 --- a/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj +++ b/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj @@ -12,12 +12,12 @@ - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING false diff --git a/Ryujinx.Tests/Ryujinx.Tests.csproj b/Ryujinx.Tests/Ryujinx.Tests.csproj index 83ec2e9647..b256cc6c81 100644 --- a/Ryujinx.Tests/Ryujinx.Tests.csproj +++ b/Ryujinx.Tests/Ryujinx.Tests.csproj @@ -17,12 +17,12 @@ - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING false diff --git a/Ryujinx.sln b/Ryujinx.sln index 4ad74077cf..f023368ba8 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -10,9 +10,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Unicorn", "Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj", "{D8F72938-78EF-4E8C-BAFE-531C9C3C8F15}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE", "Ryujinx.HLE\Ryujinx.HLE.csproj", "{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}" - ProjectSection(ProjectDependencies) = postProject - {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34} = {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34} - EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "Ryujinx.Audio\Ryujinx.Audio.csproj", "{5C1D818E-682A-46A5-9D54-30006E26C270}" EndProject @@ -22,8 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Luea", "Ryujinx.LLE\Luea.cs EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "Ryujinx.Common\Ryujinx.Common.csproj", "{5FD4E4F6-8928-4B3C-BE07-28A675C17226}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Profiler", "Ryujinx.Profiler\Ryujinx.Profiler.csproj", "{4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ARMeilleure", "ARMeilleure\ARMeilleure.csproj", "{ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Gpu", "Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj", "{ADA7EA87-0D63-4D97-9433-922A2124401F}" @@ -37,6 +32,7 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Nvdec", "Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Debugger", "Ryujinx.Debugger\Ryujinx.Debugger.csproj", "{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -110,14 +106,6 @@ Global {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.ActiveCfg = Release|Any CPU {5FD4E4F6-8928-4B3C-BE07-28A675C17226}.Release|Any CPU.Build.0 = Release|Any CPU - {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU - {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU - {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU - {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU - {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4E69B67F-8CA7-42CF-A9E1-CCB0915DFB34}.Release|Any CPU.Build.0 = Release|Any CPU {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Debug|Any CPU.Build.0 = Debug|Any CPU {ABF09A5E-2D8B-4B6F-A51D-5CE414DDB15A}.Profile Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -174,6 +162,14 @@ Global {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Profile Release|Any CPU.Build.0 = Release|Any CPU {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.ActiveCfg = Release|Any CPU {85A0FA56-DC01-4A42-8808-70DAC76BD66D}.Release|Any CPU.Build.0 = Release|Any CPU + {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU + {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU + {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU + {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU + {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 9bab74a265..5ce33a9dd2 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -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; diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index 1ff9800155..cc8e6d5450 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -9,12 +9,12 @@ - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING true - TRACE;USE_PROFILING + TRACE;USE_DEBUGGING false @@ -72,7 +72,7 @@ - + @@ -80,8 +80,8 @@ + - diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index 295cf22f49..5e83458aef 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -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); diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 61c59d356c..84c736befc 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -3,11 +3,12 @@ using JsonPrettyPrinterPlus; using Ryujinx.Audio; using Ryujinx.Common.Logging; using Ryujinx.Configuration; +using Ryujinx.Debugger.Profiler; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.OpenGL; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem.Content; -using Ryujinx.Profiler; +using Ryujinx.HLE.FileSystem; using System; using System.Diagnostics; using System.IO; @@ -36,9 +37,12 @@ namespace Ryujinx.Ui private static bool _updatingGameTable; private static bool _gameLoaded; private static bool _ending; + private static bool _debuggerOpened; private static TreeView _treeView; + private static Debugger.Debugger _debugger; + #pragma warning disable CS0649 #pragma warning disable IDE0044 [GUI] Window _mainWin; @@ -61,6 +65,8 @@ namespace Ryujinx.Ui [GUI] Label _progressLabel; [GUI] Label _firmwareVersionLabel; [GUI] LevelBar _progressBar; + [GUI] MenuItem _openDebugger; + [GUI] MenuItem _toolsMenu; #pragma warning restore CS0649 #pragma warning restore IDE0044 @@ -118,6 +124,13 @@ namespace Ryujinx.Ui if (ConfigurationState.Instance.Ui.GuiColumns.FileSizeColumn) _fileSizeToggle.Active = true; if (ConfigurationState.Instance.Ui.GuiColumns.PathColumn) _pathToggle.Active = true; +#if USE_DEBUGGING + _debugger = new Debugger.Debugger(); + _openDebugger.Activated += _openDebugger_Opened; +#else + _openDebugger.Visible = false; +#endif + _gameTable.Model = _tableStore = new ListStore( typeof(bool), typeof(Gdk.Pixbuf), @@ -141,6 +154,36 @@ namespace Ryujinx.Ui Task.Run(RefreshFirmwareLabel); } +#if USE_DEBUGGING + private void _openDebugger_Opened(object sender, EventArgs e) + { + if (_debuggerOpened) + { + return; + } + + Window debugWindow = new Window("Debugger"); + + debugWindow.SetSizeRequest(1280, 640); + debugWindow.Child = _debugger.Widget; + debugWindow.DeleteEvent += DebugWindow_DeleteEvent; + debugWindow.ShowAll(); + + _debugger.Enable(); + + _debuggerOpened = true; + } + + private void DebugWindow_DeleteEvent(object o, DeleteEventArgs args) + { + _debuggerOpened = false; + + _debugger.Disable(); + + (_debugger.Widget.Parent as Window)?.Remove(_debugger.Widget); + } +#endif + internal static void ApplyTheme() { if (!ConfigurationState.Instance.Ui.EnableCustomTheme) @@ -307,7 +350,15 @@ namespace Ryujinx.Ui #if MACOS_BUILD CreateGameWindow(device); #else - new Thread(() => CreateGameWindow(device)).Start(); + var windowThread = new Thread(() => + { + CreateGameWindow(device); + }) + { + Name = "GUI.WindowThread" + }; + + windowThread.Start(); #endif _gameLoaded = true; @@ -366,6 +417,11 @@ namespace Ryujinx.Ui private void End(HLE.Switch device) { + +#if USE_DEBUGGING + _debugger.Dispose(); +#endif + if (_ending) { return; diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade index 8e2eab9391..d3cdc59346 100644 --- a/Ryujinx/Ui/MainWindow.glade +++ b/Ryujinx/Ui/MainWindow.glade @@ -1,5 +1,5 @@ - + @@ -8,6 +8,9 @@ center 1280 750 + + + True @@ -255,7 +258,7 @@ - + True False Tools @@ -296,6 +299,14 @@ + + + True + False + Open Debugger + True + + @@ -499,8 +510,5 @@ - - - From a906f2071cbe4caf53c697bcb7dfa91e3e7dcfae Mon Sep 17 00:00:00 2001 From: Thog Date: Thu, 6 Feb 2020 12:38:24 +0100 Subject: [PATCH 9/9] Fix a crash when closing the main UI (#904) * Fix a crash when closing the main Ui Also make sure to dispose the OpenAL context to not leak memory when unloading the emulation context. * Improve keys and 'game already running' dialogs * Make sure to dispose the page table and ThreadContext Less memory leaks! * Fix tests * Address gdk's comments --- .../Renderers/OpenAL/OpenALAudioOut.cs | 1 + Ryujinx.HLE/HOS/Horizon.cs | 5 ++++- Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 5 +++++ Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 2 ++ Ryujinx/Program.cs | 2 +- Ryujinx/Ui/GtkDialog.cs | 18 ++++++++++++++---- Ryujinx/Ui/MainWindow.cs | 17 +++++++++++++---- 7 files changed, 40 insertions(+), 10 deletions(-) diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs index 30b325a51a..ea5ce62146 100644 --- a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs +++ b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs @@ -101,6 +101,7 @@ namespace Ryujinx.Audio } _tracks.Clear(); + _context.Dispose(); } /// diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index a2bff7f622..f8d2ed71b4 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -107,6 +107,7 @@ namespace Ryujinx.HLE.HOS public Keyset KeySet => Device.FileSystem.KeySet; private bool _hasStarted; + private bool _isDisposed; public BlitStruct ControlData { get; set; } @@ -740,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); diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index f987c83c01..7807ec5a68 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -1131,5 +1131,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { throw new UndefinedInstructionException(e.Address, e.OpCode); } + + protected override void Destroy() + { + CpuMemory.Dispose(); + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index c4bd781d4e..53eb5bdc9a 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -1141,6 +1141,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { Owner.Translator.Execute(Context, entrypoint); + Context.Dispose(); + ThreadExit(); } diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 5ce33a9dd2..24fbb9b8b5 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -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(); diff --git a/Ryujinx/Ui/GtkDialog.cs b/Ryujinx/Ui/GtkDialog.cs index 7f6be8dc7a..b4e9fa1caa 100644 --- a/Ryujinx/Ui/GtkDialog.cs +++ b/Ryujinx/Ui/GtkDialog.cs @@ -5,19 +5,29 @@ 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); errorDialog.Run(); errorDialog.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); + } } } diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 84c736befc..734103fed2 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -32,6 +32,8 @@ namespace Ryujinx.Ui private static GlScreen _screen; + private static AutoResetEvent _screenExitStatus = new AutoResetEvent(false); + private static ListStore _tableStore; private static bool _updatingGameTable; @@ -278,7 +280,7 @@ namespace Ryujinx.Ui { 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 { @@ -347,6 +349,8 @@ namespace Ryujinx.Ui _emulationContext = device; + _screenExitStatus.Reset(); + #if MACOS_BUILD CreateGameWindow(device); #else @@ -393,6 +397,8 @@ namespace Ryujinx.Ui DiscordIntegrationModule.SwitchToMainMenu(); + _screenExitStatus.Set(); + Application.Invoke(delegate { _stopEmulation.Sensitive = false; @@ -432,12 +438,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(); @@ -584,13 +595,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); }