Log start/stop capture, give captures a title

and make the RenderDoc API more resistant to being called without an underlying Api struct
This commit is contained in:
GreemDev 2025-12-28 00:38:55 -06:00
parent 7a1c7b714e
commit 801dcd5237
4 changed files with 65 additions and 13 deletions

View file

@ -92,13 +92,13 @@ namespace Ryujinx.Graphics.RenderDocApi
/// Checks if the RenderDoc UI is currently connected to this process.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static bool IsTargetControlConnected => Api->IsTargetControlConnected() != 0;
public static bool IsTargetControlConnected => Api is not null && Api->IsTargetControlConnected() != 0;
/// <summary>
/// Checks if the current frame is capturing.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static bool IsFrameCapturing => Api->IsFrameCapturing() != 0;
public static bool IsFrameCapturing => Api is not null && Api->IsFrameCapturing() != 0;
/// <summary>
/// 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;
}
/// <summary>
@ -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;
}
/// <summary>
@ -183,6 +183,8 @@ namespace Ryujinx.Graphics.RenderDocApi
[RenderDocApiVersion(1, 0)]
public static void SetFocusToggleKeys(ReadOnlySpan<InputButton> 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<InputButton> 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

View file

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

View file

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

View file

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