diff --git a/src/Ryujinx.Graphics.Vulkan/Auto.cs b/src/Ryujinx.Graphics.Vulkan/Auto.cs index 606c088e9..4e6cbaa62 100644 --- a/src/Ryujinx.Graphics.Vulkan/Auto.cs +++ b/src/Ryujinx.Graphics.Vulkan/Auto.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.Logging; using System; using System.Diagnostics; using System.Threading; @@ -114,7 +115,7 @@ namespace Ryujinx.Graphics.Vulkan cbs.AddDependant(this); // We need to add a dependency on the command buffer to all objects this object - // references aswell. + // references as well. if (_referencedObjs != null) { for (int i = 0; i < _referencedObjs.Length; i++) @@ -176,6 +177,8 @@ namespace Ryujinx.Graphics.Vulkan } } + // This can somehow become -1. + // Logger.Info?.PrintMsg(LogClass.Gpu, $"_referenceCount: {_referenceCount}"); Debug.Assert(_referenceCount >= 0); } diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs index 66d127dfc..1b40ab519 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs @@ -52,6 +52,7 @@ namespace Ryujinx.HLE.Loaders.Processes if (metaLoader is not null) { + Logger.Info?.Print(LogClass.Application,$"metaLoader: {metaLoader}"); ulong programId = metaLoader.GetProgramId(); Name = ApplicationControlProperties.Title[(int)titleLanguage].NameString.ToString(); @@ -70,7 +71,14 @@ namespace Ryujinx.HLE.Loaders.Processes DisplayVersion = ApplicationControlProperties.DisplayVersionString.ToString(); ProgramId = programId; ProgramIdText = $"{programId:x16}"; - Is64Bit = metaLoader.IsProgram64Bit(); + Is64Bit = metaLoader.IsProgram64Bit; + } + + else + { + Logger.Error?.Print(LogClass.Application,$"metaLoader is null !!!"); + ProcessId = 0; + return; } DiskCacheEnabled = diskCacheEnabled; diff --git a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs index 7ad58da48..728d49ff1 100644 --- a/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs +++ b/src/Ryujinx.UI.Common/App/ApplicationLibrary.cs @@ -1115,8 +1115,14 @@ namespace Ryujinx.UI.App.Common ApplicationCountUpdated?.Invoke(null, e); } - public static ApplicationMetadata LoadAndSaveMetaData(string titleId, Action modifyFunction = null) + public static Gommon.Optional LoadAndSaveMetaData(string titleId, Action modifyFunction = null) { + if (titleId is null) + { + Logger.Warning?.PrintMsg(LogClass.Application, "Cannot save metadata because title ID is invalid."); + return null; + } + string metadataFolder = Path.Combine(AppDataManager.GamesDirPath, titleId, "gui"); string metadataFile = Path.Combine(metadataFolder, "metadata.json"); @@ -1124,6 +1130,7 @@ namespace Ryujinx.UI.App.Common if (!File.Exists(metadataFile)) { + Logger.Info?.Print(LogClass.Application, $"Metadata file does not exist. Creating metadata for {titleId}..."); Directory.CreateDirectory(metadataFolder); appMetadata = new ApplicationMetadata(); @@ -1133,12 +1140,12 @@ namespace Ryujinx.UI.App.Common try { + Logger.Debug?.Print(LogClass.Application, $"Deserializing metadata for {titleId}..."); appMetadata = JsonHelper.DeserializeFromFile(metadataFile, _serializerContext.ApplicationMetadata); } catch (JsonException) { Logger.Warning?.Print(LogClass.Application, $"Failed to parse metadata json for {titleId}. Loading defaults."); - appMetadata = new ApplicationMetadata(); } diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index b98435f55..55ff6d777 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -3,6 +3,8 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; using Avalonia.Threading; +using LibHac.Common; +using LibHac.Ns; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.Dummy; using Ryujinx.Audio.Backends.OpenAL; @@ -62,7 +64,7 @@ using VSyncMode = Ryujinx.Common.Configuration.VSyncMode; namespace Ryujinx.Ava { - internal class AppHost + internal class AppHost : IDisposable { private const int CursorHideIdleTime = 5; // Hide Cursor seconds. private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. @@ -447,7 +449,7 @@ namespace Ryujinx.Ava SaveBitmapAsPng(bitmapToSave, path); - Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); + Logger.Notice.Print(LogClass.Application, $"Screenshot saved to '{path}'.", "Screenshot"); } }); } @@ -578,27 +580,37 @@ namespace Ryujinx.Ava _isActive = false; - // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. - // We only need to wait for all commands submitted during the main gpu loop to be processed. - _gpuDoneEvent.WaitOne(); - _gpuDoneEvent.Dispose(); - DisplaySleep.Restore(); NpadManager.Dispose(); TouchScreenManager.Dispose(); Device.Dispose(); - DisposeGpu(); + // NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose. + // We only need to wait for all commands submitted during the main gpu loop to be processed. + // If the GPU has no work and is cancelled, we need to handle that as well. + WaitHandle.WaitAny(new[] { _gpuDoneEvent, _gpuCancellationTokenSource.Token.WaitHandle }); + _gpuCancellationTokenSource.Dispose(); + + // Waiting for work to be finished before we dispose. + if (_renderingStarted) + { + Device.Gpu.WaitUntilGpuReady(); + } + + _gpuDoneEvent.Dispose(); + + DisposeGpu(); AppExit?.Invoke(this, EventArgs.Empty); } - private void Dispose() + // MUST be public to inherit from IDisposable + public void Dispose() { if (Device.Processes != null) { - MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText); + MainWindowViewModel.UpdateGameMetadata(Device.Processes.ActiveApplication?.ProgramIdText); } ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; @@ -615,7 +627,6 @@ namespace Ryujinx.Ava _topLevel.PointerExited -= TopLevel_PointerExited; _gpuCancellationTokenSource.Cancel(); - _gpuCancellationTokenSource.Dispose(); _chrono.Stop(); } @@ -640,6 +651,12 @@ namespace Ryujinx.Ava } else { + // No use waiting on something that never started work + if (_renderingStarted) + { + Device.Gpu.WaitUntilGpuReady(); + } + Device.DisposeGpu(); } } @@ -654,7 +671,7 @@ namespace Ryujinx.Ava _cursorState = CursorStates.ForceChangeCursor; } - public async Task LoadGuestApplication() + public async Task LoadGuestApplication(CancellationTokenSource cts, BlitStruct? customNacpData = null) { InitializeSwitchInstance(); MainWindow.UpdateGraphicsConfig(); @@ -682,7 +699,8 @@ namespace Ryujinx.Ava await UserErrorDialog.ShowUserErrorDialog(userError); Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } } @@ -691,10 +709,11 @@ namespace Ryujinx.Ava await UserErrorDialog.ShowUserErrorDialog(userError); Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } - // Tell the user that we installed a firmware for them. + // Tell the user that we installed firmware for them. if (userError == UserError.NoFirmware) { firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); @@ -714,7 +733,8 @@ namespace Ryujinx.Ava await UserErrorDialog.ShowUserErrorDialog(userError); Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } } } @@ -730,7 +750,8 @@ namespace Ryujinx.Ava { Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } } else if (Directory.Exists(ApplicationPath)) @@ -748,20 +769,24 @@ namespace Ryujinx.Ava if (!Device.LoadCart(ApplicationPath, romFsFiles[0])) { + await ContentDialogHelper.CreateErrorDialog( + "Please specify an unpacked game directory with a valid exefs or NSO/NRO."); Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } } else { Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); - if (!Device.LoadCart(ApplicationPath)) { + await ContentDialogHelper.CreateErrorDialog( + "Please specify an unpacked game directory with a valid exefs or NSO/NRO."); Device.Dispose(); - - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } } } @@ -777,7 +802,8 @@ namespace Ryujinx.Ava { Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } break; @@ -790,7 +816,8 @@ namespace Ryujinx.Ava { Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } break; @@ -804,7 +831,8 @@ namespace Ryujinx.Ava { Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } break; @@ -819,7 +847,8 @@ namespace Ryujinx.Ava { Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } } catch (ArgumentOutOfRangeException) @@ -828,7 +857,8 @@ namespace Ryujinx.Ava Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } break; @@ -837,11 +867,12 @@ namespace Ryujinx.Ava } else { - Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NSO/NRO file."); Device.Dispose(); - return false; + cts.Cancel(); + throw new OperationCanceledException(cts.Token); } ApplicationMetadata appMeta = ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, @@ -849,8 +880,6 @@ namespace Ryujinx.Ava ); DiscordIntegrationModule.SwitchToPlayingState(appMeta, Device.Processes.ActiveApplication); - - return true; } internal void Resume() @@ -859,7 +888,7 @@ namespace Ryujinx.Ava _viewModel.IsPaused = false; _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version); - Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed"); + Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed."); } internal void Pause() @@ -868,7 +897,7 @@ namespace Ryujinx.Ava _viewModel.IsPaused = true; _viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, LocaleManager.Instance[LocaleKeys.Paused]); - Logger.Info?.Print(LogClass.Emulation, "Emulation was paused"); + Logger.Info?.Print(LogClass.Emulation, "Emulation was paused."); } private void InitializeSwitchInstance() @@ -1095,7 +1124,9 @@ namespace Ryujinx.Ava // Make sure all commands in the run loop are fully executed before leaving the loop. if (Device.Gpu.Renderer is ThreadedRenderer threaded) { + Logger.Info?.PrintMsg(LogClass.Gpu, "Flushing threaded commands..."); threaded.FlushThreadedCommands(); + Logger.Info?.PrintMsg(LogClass.Gpu, "Flushed!"); } _gpuDoneEvent.Set(); diff --git a/src/Ryujinx/UI/Models/SaveModel.cs b/src/Ryujinx/UI/Models/SaveModel.cs index faeb16377..b8e5fd868 100644 --- a/src/Ryujinx/UI/Models/SaveModel.cs +++ b/src/Ryujinx/UI/Models/SaveModel.cs @@ -57,8 +57,15 @@ namespace Ryujinx.Ava.UI.Models } else { - var appMetadata = ApplicationLibrary.LoadAndSaveMetaData(TitleIdString); - Title = appMetadata.Title ?? TitleIdString; + Gommon.Optional appMetadata = ApplicationLibrary.LoadAndSaveMetaData(TitleIdString); + if (appMetadata != null) + { + Title = appMetadata.Value.Title ?? TitleIdString; + } + else + { + Title = ""; + } } Task.Run(() => diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 6dc4a1131..7d6962c97 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -2067,10 +2067,6 @@ namespace Ryujinx.Ava.UI.ViewModels Logger.RestartTime(); - SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id); - - PrepareLoadScreen(); - RendererHostControl = new RendererHost(); AppHost = new AppHost( @@ -2085,18 +2081,34 @@ namespace Ryujinx.Ava.UI.ViewModels this, TopLevel); - if (!await AppHost.LoadGuestApplication()) + CancellationTokenSource cts = new CancellationTokenSource(); + + try { + await AppHost.LoadGuestApplication(cts); + } + catch (OperationCanceledException exception) + { + Logger.Info?.Print(LogClass.Application, + "LoadGuestApplication was interrupted !!! " + exception.Message); AppHost.DisposeContext(); AppHost = null; - return; } + finally + { + cts.Dispose(); + } CanUpdate = false; LoadHeading = application.Name; + SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, + ConfigurationState.Instance.System.Language, application.Id); + + PrepareLoadScreen(); + if (string.IsNullOrWhiteSpace(application.Name)) { LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);