From 801dcd5237f0de69d1afdede9dd13b3d60bae19a Mon Sep 17 00:00:00 2001 From: GreemDev Date: Sun, 28 Dec 2025 00:38:55 -0600 Subject: [PATCH] Log start/stop capture, give captures a title and make the RenderDoc API more resistant to being called without an underlying Api struct --- .../RenderDoc.cs | 41 ++++++++++++++++--- src/Ryujinx/UI/Renderer/EmbeddedWindow.cs | 15 ++++--- .../UI/ViewModels/MainWindowViewModel.cs | 9 +++- src/Ryujinx/Utilities/TitleHelper.cs | 13 ++++++ 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/Ryujinx.Graphics.RenderDocApi/RenderDoc.cs b/src/Ryujinx.Graphics.RenderDocApi/RenderDoc.cs index 594b1e9be..f49b1b737 100644 --- a/src/Ryujinx.Graphics.RenderDocApi/RenderDoc.cs +++ b/src/Ryujinx.Graphics.RenderDocApi/RenderDoc.cs @@ -92,13 +92,13 @@ namespace Ryujinx.Graphics.RenderDocApi /// Checks if the RenderDoc UI is currently connected to this process. /// [RenderDocApiVersion(1, 0)] - public static bool IsTargetControlConnected => Api->IsTargetControlConnected() != 0; + public static bool IsTargetControlConnected => Api is not null && Api->IsTargetControlConnected() != 0; /// /// Checks if the current frame is capturing. /// [RenderDocApiVersion(1, 0)] - public static bool IsFrameCapturing => Api->IsFrameCapturing() != 0; + public static bool IsFrameCapturing => Api is not null && Api->IsFrameCapturing() != 0; /// /// Set one of the options for tweaking some behaviors of capturing. @@ -113,7 +113,7 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static bool SetCaptureOption(CaptureOption option, int integer) { - return Api->SetCaptureOptionU32(option, integer) != 0; + return Api is not null && Api->SetCaptureOptionU32(option, integer) != 0; } /// @@ -129,7 +129,7 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static bool SetCaptureOption(CaptureOption option, float single) { - return Api->SetCaptureOptionF32(option, single) != 0; + return Api is not null && Api->SetCaptureOptionF32(option, single) != 0; } /// @@ -183,6 +183,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static void SetFocusToggleKeys(ReadOnlySpan buttons) { + if (Api is null) return; + fixed (InputButton* ptr = buttons) { Api->SetFocusToggleKeys(ptr, buttons.Length); @@ -196,6 +198,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static void SetCaptureKeys(ReadOnlySpan buttons) { + if (Api is null) return; + fixed (InputButton* ptr = buttons) { Api->SetCaptureKeys(ptr, buttons.Length); @@ -210,6 +214,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static void RemoveHooks() { + if (Api is null) return; + Api->RemoveHooks(); } @@ -221,6 +227,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static void UnloadCrashHandler() { + if (Api is null) return; + Api->UnloadCrashHandler(); } @@ -231,6 +239,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static void TriggerCapture() { + if (Api is null) return; + Api->TriggerCapture(); } @@ -242,6 +252,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static Capture? GetCapture(int index) { + if (Api is null) return null; + int length = 0; if (Api->GetCapture(index, null, &length, null) == 0) { @@ -267,6 +279,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static bool LaunchReplayUI(bool connectTargetControl, string? commandLine = null) { + if (Api is null) return false; + if (commandLine == null) { return Api->LaunchReplayUI(connectTargetControl ? 1 : 0, null) != 0; @@ -287,6 +301,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static void SetActiveWindow(nint hDevice, nint hWindow) { + if (Api is null) return; + Api->SetActiveWindow((void*)hDevice, (void*)hWindow); } @@ -298,6 +314,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static void StartFrameCapture(nint hDevice, nint hWindow) { + if (Api is null) return; + Api->StartFrameCapture((void*)hDevice, (void*)hWindow); } @@ -310,6 +328,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 0)] public static bool EndFrameCapture(nint hDevice, nint hWindow) { + if (Api is null) return false; + return Api->EndFrameCapture((void*)hDevice, (void*)hWindow) != 0; } @@ -323,6 +343,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 1)] public static void TriggerMultiFrameCapture(int numFrames) { + if (Api is null) return; + AssertAtLeast(1, 1); Api->TriggerMultiFrameCapture(numFrames); } @@ -337,6 +359,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 2)] public static void SetCaptureFileComments(string? fileName, string comments) { + if (Api is null) return; + AssertAtLeast(1, 2); byte[] commentBytes = comments.ToNullTerminatedByteArray(); @@ -369,6 +393,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 4)] public static void DiscardFrameCapture(nint hDevice, nint hWindow) { + if (Api is null) return; + AssertAtLeast(1, 4); Api->DiscardFrameCapture((void*)hDevice, (void*)hWindow); } @@ -386,6 +412,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 5)] public static bool ShowReplayUI() { + if (Api is null) return false; + AssertAtLeast(1, 5); return Api->ShowReplayUI() != 0; } @@ -406,6 +434,8 @@ namespace Ryujinx.Graphics.RenderDocApi [RenderDocApiVersion(1, 6)] public static void SetCaptureTitle(string title) { + if (Api is null) return; + AssertAtLeast(1, 6); fixed (byte* ptr = title.ToNullTerminatedByteArray()) Api->SetCaptureTitle(ptr); @@ -518,7 +548,8 @@ namespace Ryujinx.Graphics.RenderDocApi return encoding.GetBytes(str + '\0'); } - [GeneratedRegex(@"(lib)?renderdoc(\.dll|\.so|\.dylib)(\.\d+)?", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + [GeneratedRegex(@"(lib)?renderdoc(\.dll|\.so|\.dylib)(\.\d+)?", + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] private static partial Regex RenderDocApiDynamicLibraryRegex(); #endregion diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs index bd9cfae51..a199b8d03 100644 --- a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs @@ -2,9 +2,12 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Platform; using Ryujinx.Ava.Systems.Configuration; +using Ryujinx.Ava.Utilities; using Ryujinx.Common.Configuration; using Ryujinx.Common.Helper; +using Ryujinx.Common.Logging; using Ryujinx.Graphics.RenderDocApi; +using Ryujinx.HLE; using SPB.Graphics; using SPB.Platform; using SPB.Platform.GLX; @@ -48,21 +51,21 @@ namespace Ryujinx.Ava.UI.Renderer protected virtual void OnWindowDestroyed() { } - public void StartRenderDocCapture() + public void StartRenderDocCapture(Switch device) { if (!RenderDoc.IsAvailable) return; if (RenderDoc.IsFrameCapturing) return; - try - { - RenderDoc.StartFrameCapture(nint.Zero, WindowHandle); - } catch {} + RenderDoc.StartFrameCapture(nint.Zero, WindowHandle); + RenderDoc.SetCaptureTitle(TitleHelper.TruncatedApplicationTitle(device.Processes.ActiveApplication, Program.Version)); + + Logger.Info?.Print(LogClass.Application, "Starting RenderDoc capture."); } public bool EndRenderDocCapture() { - return RenderDoc.IsAvailable && RenderDoc.EndFrameCapture(nint.Zero, WindowHandle); + return RenderDoc.IsAvailable && RenderDoc.IsFrameCapturing && RenderDoc.EndFrameCapture(nint.Zero, WindowHandle); } protected virtual void OnWindowDestroying() diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 23037af2f..c0678e6cf 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -2500,7 +2500,7 @@ namespace Ryujinx.Ava.UI.ViewModels viewModel => { if (!RenderDoc.IsFrameCapturing) - viewModel.AppHost.RendererHost.EmbeddedWindow.StartRenderDocCapture(); + viewModel.AppHost.RendererHost.EmbeddedWindow.StartRenderDocCapture(viewModel.AppHost.Device); viewModel.RenderDocIsCapturing = true; }); @@ -2510,7 +2510,12 @@ namespace Ryujinx.Ava.UI.ViewModels viewModel => { if (RenderDoc.IsFrameCapturing) - viewModel.AppHost.RendererHost.EmbeddedWindow.EndRenderDocCapture(); + { + if (viewModel.AppHost.RendererHost.EmbeddedWindow.EndRenderDocCapture()) + { + Logger.Info?.Print(LogClass.Application, "Ended RenderDoc capture."); + } + } viewModel.RenderDocIsCapturing = false; }); diff --git a/src/Ryujinx/Utilities/TitleHelper.cs b/src/Ryujinx/Utilities/TitleHelper.cs index 5e0916c27..b03ce33f4 100644 --- a/src/Ryujinx/Utilities/TitleHelper.cs +++ b/src/Ryujinx/Utilities/TitleHelper.cs @@ -22,5 +22,18 @@ namespace Ryujinx.Ava.Utilities ? appTitle + $" ({pauseString})" : appTitle; } + + public static string TruncatedApplicationTitle(ProcessResult activeProcess, string applicationVersion) + { + if (activeProcess == null) + return string.Empty; + + string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}"; + string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}"; + string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})"; + string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)"; + + return $"{applicationVersion}\n{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; + } } }