using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
namespace Ryujinx.Graphics.RenderDocApi
{
public static unsafe partial class RenderDoc
{
///
/// True if the API is available.
///
public static bool IsAvailable => Api != null;
///
/// Set the minimum version of the API you require.
///
/// Set this before you do anything else with the RenderDoc API, including .
public static RenderDocVersion MinimumRequired { get; set; } = RenderDocVersion.Version_1_0_0;
///
/// Set to true to assert versions.
///
public static bool AssertVersionEnabled { get; set; } = true;
///
/// Version of the API available.
///
[MemberNotNullWhen(true, nameof(IsAvailable))]
public static Version? Version
{
get
{
if (!IsAvailable)
return null;
int major, minor, build;
Api->GetApiVersion(&major, &minor, &build);
return new Version(major, minor, build);
}
}
///
/// The current mask which determines what sections of the overlay render on each window.
///
[RenderDocApiVersion(1, 0)]
public static OverlayBits OverlayBits
{
get => Api->GetOverlayBits();
set
{
Api->MaskOverlayBits(~value, value);
}
}
///
/// The template for new captures.
/// The template can either be a relative or absolute path, which determines where captures will be saved and how they will be named.
/// If the path template is 'my_captures/example', then captures saved will be e.g.
/// 'my_captures/example_frame123.rdc' and 'my_captures/example_frame456.rdc'.
/// Relative paths will be saved relative to the process’s current working directory.
///
/// The default template is in a folder controlled by the UI - initially the system temporary folder, and the filename is the executable’s filename.
[RenderDocApiVersion(1, 0)]
public static string CaptureFilePathTemplate
{
get
{
byte* ptr = Api->GetCaptureFilePathTemplate();
return Marshal.PtrToStringUTF8((nint)ptr)!;
}
set
{
fixed (byte* ptr = value.ToNullTerminatedByteArray())
{
Api->SetCaptureFilePathTemplate(ptr);
}
}
}
///
/// The amount of frame captures that have been made.
///
[RenderDocApiVersion(1, 0)]
public static int CaptureCount => Api->GetNumCaptures();
///
/// Checks if the RenderDoc UI is currently connected to this process.
///
[RenderDocApiVersion(1, 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 is not null && Api->IsFrameCapturing() != 0;
///
/// Set one of the options for tweaking some behaviors of capturing.
///
/// specifies which capture option should be set.
/// the unsigned integer value to set for the option.
/// Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.
///
/// true, if the is valid, and the value set on the option is within valid ranges.
/// false, if the option is not a , or the value is not valid for the option.
///
[RenderDocApiVersion(1, 0)]
public static bool SetCaptureOption(CaptureOption option, uint integer)
{
return Api is not null && Api->SetCaptureOptionU32(option, integer) != 0;
}
///
/// Set one of the options for tweaking some behaviors of capturing.
///
/// specifies which capture option should be set.
/// the value to set for the option, converted to a 0 or 1 before setting.
/// Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.
///
/// true, if the is valid, and the value set on the option is within valid ranges.
/// false, if the option is not a , or the value is not valid for the option.
///
[RenderDocApiVersion(1, 0)]
public static bool SetCaptureOption(CaptureOption option, bool boolean)
=> SetCaptureOption(option, boolean ? 1 : 0);
///
/// Set one of the options for tweaking some behaviors of capturing.
///
/// specifies which capture option should be set.
/// the floating point value to set for the option.
/// Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.
///
/// true, if the is valid, and the value set on the option is within valid ranges.
/// false, if the option is not a , or the value is not valid for the option.
///
[RenderDocApiVersion(1, 0)]
public static bool SetCaptureOption(CaptureOption option, float single)
{
return Api is not null && Api->SetCaptureOptionF32(option, single) != 0;
}
///
/// Gets the current value of one of the different options in , writing it to an out parameter.
///
/// specifies which capture option should be retrieved.
/// the value of the capture option, if the option is a valid enum. Otherwise, .
[RenderDocApiVersion(1, 0)]
public static void GetCaptureOption(CaptureOption option, out uint integer)
{
integer = Api->GetCaptureOptionU32(option);
}
///
/// Gets the current value of one of the different options in , writing it to an out parameter.
///
/// specifies which capture option should be retrieved.
/// the value of the capture option, if the option is a valid enum. Otherwise, -.
[RenderDocApiVersion(1, 0)]
public static void GetCaptureOption(CaptureOption option, out float single)
{
single = Api->GetCaptureOptionF32(option);
}
///
/// Gets the current value of one of the different options in ,
/// converted to a boolean.
///
/// specifies which capture option should be retrieved.
///
/// the value of the capture option, converted to bool, if the option is a valid enum.
/// Otherwise, returns null.
///
[RenderDocApiVersion(1, 0)]
public static bool? GetCaptureOptionBool(CaptureOption option)
{
if (Api is null) return false;
uint returnVal = GetCaptureOptionU32(option);
if (returnVal == uint.MaxValue)
return null;
return returnVal is not 0;
}
///
/// Gets the current value of one of the different options in .
///
/// specifies which capture option should be retrieved.
///
/// the value of the capture option, if the option is a valid enum.
/// Otherwise, returns .
///
[RenderDocApiVersion(1, 0)]
public static uint GetCaptureOptionU32(CaptureOption option) => Api->GetCaptureOptionU32(option);
///
/// Gets the current value of one of the different options in .
///
/// specifies which capture option should be retrieved.
///
/// the value of the capture option, if the option is a valid enum.
/// Otherwise, returns -.
///
[RenderDocApiVersion(1, 0)]
public static float GetCaptureOptionF32(CaptureOption option) => Api->GetCaptureOptionF32(option);
///
/// Changes the key bindings in-application for changing the focussed window.
///
/// lists the keys to bind.
[RenderDocApiVersion(1, 0)]
public static void SetFocusToggleKeys(ReadOnlySpan buttons)
{
if (Api is null) return;
fixed (InputButton* ptr = buttons)
{
Api->SetFocusToggleKeys(ptr, buttons.Length);
}
}
///
/// Changes the key bindings in-application for triggering a capture on the current window.
///
/// lists the keys to bind.
[RenderDocApiVersion(1, 0)]
public static void SetCaptureKeys(ReadOnlySpan buttons)
{
if (Api is null) return;
fixed (InputButton* ptr = buttons)
{
Api->SetCaptureKeys(ptr, buttons.Length);
}
}
///
/// Attempts to remove RenderDoc and its hooks from the target process.
/// It must be called as early as possible in the process, and will have undefined results
/// if any graphics API functions have been called.
///
[RenderDocApiVersion(1, 0)]
public static void RemoveHooks()
{
if (Api is null) return;
Api->RemoveHooks();
}
///
/// Remove RenderDoc’s crash handler from the target process.
/// If you have your own crash handler that you want to handle any exceptions,
/// RenderDoc’s handler could interfere; so it can be disabled.
///
[RenderDocApiVersion(1, 0)]
public static void UnloadCrashHandler()
{
if (Api is null) return;
Api->UnloadCrashHandler();
}
///
/// Trigger a capture as if the user had pressed one of the capture hotkeys.
/// The capture will be taken from the next frame presented to whichever window is considered current.
///
[RenderDocApiVersion(1, 0)]
public static void TriggerCapture()
{
if (Api is null) return;
Api->TriggerCapture();
}
///
/// Gets the details of all frame capture in the current session.
/// This simply calls for each index available as specified by .
///
/// An immutable array of structs representing RenderDoc Captures.
public static ImmutableArray GetCaptures()
{
if (Api is null) return [];
int captureCount = CaptureCount;
if (captureCount is 0) return [];
ImmutableArray.Builder captures = ImmutableArray.CreateBuilder(captureCount);
for (int captureIndex = 0; captureIndex < captureCount; captureIndex++)
{
if (GetCapture(captureIndex) is { } capture)
captures.Add(capture);
}
return captures.DrainToImmutable();
}
///
/// Gets the details of a particular frame capture, as specified by an index from 0 to - 1.
///
/// specifies which capture to return the details of. Must be less than the value returned by .
/// A struct representing a RenderDoc Capture.
[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)
{
return null;
}
Span bytes = stackalloc byte[length + 1];
long timestamp;
fixed (byte* ptr = bytes)
Api->GetCapture(index, ptr, &length, ×tamp);
string fileName = Encoding.UTF8.GetString(bytes[length..]);
return new Capture(index, fileName, DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime);
}
///
/// Determine the closest matching replay UI executable for the current RenderDoc module, and launch it.
///
/// if the UI should immediately connect to the application.
/// string to be appended to the command line, e.g. a capture filename. If this parameter is null, the command line will be unmodified.
/// true if the UI was successfully launched; false otherwise.
[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 ? 1u : 0u, null) != 0;
}
fixed (byte* ptr = commandLine.ToNullTerminatedByteArray())
{
return Api->LaunchReplayUI(connectTargetControl ? 1u : 0u, ptr) != 0;
}
}
///
/// Explicitly sets which window is considered active.
/// The active window is the one that will be captured when the keybind to trigger a capture is pressed.
///
/// a handle to the API ‘device’ object that will be set active. Must be valid.
/// a handle to the platform window handle that will be set active. Must be valid.
[RenderDocApiVersion(1, 0)]
public static void SetActiveWindow(nint hDevice, nint hWindow)
{
if (Api is null) return;
Api->SetActiveWindow((void*)hDevice, (void*)hWindow);
}
///
/// Immediately begin a capture for the specified device/window combination.
///
/// a handle to the API ‘device’ object that will be set active. May be to wildcard match.
/// a handle to the platform window handle that will be set active. May be to wildcard match.
[RenderDocApiVersion(1, 0)]
public static void StartFrameCapture(nint hDevice, nint hWindow)
{
if (Api is null) return;
Api->StartFrameCapture((void*)hDevice, (void*)hWindow);
}
///
/// Immediately end an active capture for the specified device/window combination.
///
/// a handle to the API ‘device’ object that will be set active. May be to wildcard match.
/// a handle to the platform window handle that will be set active. May be to wildcard match.
/// true if the capture succeeded; false otherwise.
[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;
}
///
/// Trigger multiple sequential frame captures as if the user had pressed one of the capture hotkeys before each frame.
/// The captures will be taken from the next frames presented to whichever window is considered current.
/// Each capture will be taken independently and saved to a separate file, with no reference to the other frames.
///
/// the number of frames to capture.
/// Requires RenderDoc API version 1.1
[RenderDocApiVersion(1, 1)]
public static void TriggerMultiFrameCapture(uint numFrames)
{
if (Api is null) return;
AssertAtLeast(1, 1);
Api->TriggerMultiFrameCapture(numFrames);
}
///
/// Adds an arbitrary comments field to the most recent capture,
/// which will then be displayed in the UI to anyone opening the capture.
///
/// This is equivalent to calling with a null first (fileName) parameter.
///
/// the comments to set in the capture file.
/// Requires RenderDoc API version 1.2
public static void SetMostRecentCaptureFileComments(string comments)
{
if (Api is null) return;
AssertAtLeast(1, 2);
byte[] commentBytes = comments.ToNullTerminatedByteArray();
fixed (byte* pcomment = commentBytes)
{
Api->SetCaptureFileComments((byte*)nint.Zero, pcomment);
}
}
///
/// Adds an arbitrary comments field to an existing capture on disk,
/// which will then be displayed in the UI to anyone opening the capture.
///
/// the path to the capture file to set comments in. If this path is null or an empty string, the most recent capture file that has been created will be used.
/// the comments to set in the capture file.
/// Requires RenderDoc API version 1.2
[RenderDocApiVersion(1, 2)]
public static void SetCaptureFileComments(string? fileName, string comments)
{
if (Api is null) return;
AssertAtLeast(1, 2);
byte[] commentBytes = comments.ToNullTerminatedByteArray();
fixed (byte* pcomment = commentBytes)
{
if (fileName is null)
{
Api->SetCaptureFileComments((byte*)nint.Zero, pcomment);
}
else
{
byte[] fileBytes = fileName.ToNullTerminatedByteArray();
fixed (byte* pfile = fileBytes)
{
Api->SetCaptureFileComments(pfile, pcomment);
}
}
}
}
///
/// Similar to , but the capture contents will be discarded immediately, and not processed and written to disk.
/// This will be more efficient than if the frame capture is not needed.
///
/// a handle to the API ‘device’ object that will be set active. May be to wildcard match.
/// a handle to the platform window handle that will be set active. May be to wildcard match.
/// true if the capture was discarded; false if there was an error or no capture was in progress.
/// Requires RenderDoc API version 1.4
[RenderDocApiVersion(1, 4)]
public static bool DiscardFrameCapture(nint hDevice, nint hWindow)
{
if (Api is null) return false;
AssertAtLeast(1, 4);
return Api->DiscardFrameCapture((void*)hDevice, (void*)hWindow) != 0;
}
///
/// Requests that the currently connected replay UI raise its window to the top.
/// This is only possible if an instance of the replay UI is currently connected, otherwise this method does nothing.
/// This can be used in conjunction with and ,
to intelligently handle showing the UI after making a capture.
/// Given OS differences, it is not guaranteed that the UI will be successfully raised even if the request is passed on.
/// On some systems it may only be highlighted or otherwise indicated to the user.
///
/// true if the request was passed onto the UI successfully; false if there is no UI connected or some other error occurred.
/// Requires RenderDoc API version 1.5
[RenderDocApiVersion(1, 5)]
public static bool ShowReplayUI()
{
if (Api is null) return false;
AssertAtLeast(1, 5);
return Api->ShowReplayUI() != 0;
}
///
/// Sets a given title for the currently in-progress capture, which will be displayed in the UI.
/// This can be used either with a user-defined capture using a manual start and end,
/// or an automatic capture triggered by or a keypress.
/// If multiple captures are ongoing at once, the title will be applied to the first capture to end only.
/// Any subsequent captures will not get any title unless the function is called again.
/// This function can only be called while a capture is in-progress,
/// after and before .
/// If it is called elsewhere it will have no effect.
/// If it is called multiple times within a capture, only the last title will have any effect.
///
/// The title to set for the in-progress capture.
/// Requires RenderDoc API version 1.6
[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);
}
#region Dynamic Library loading
///
/// Reload the internal RenderDoc API structure. Useful for manually refreshing while using process injection.
///
/// Ignores the existing API function structure and overwrites it with a re-request.
/// The version of the RenderDoc API required by your application.
public static void ReloadApi(bool ignoreAlreadyLoaded = false, RenderDocVersion? requiredVersion = null)
{
if (_loaded && !ignoreAlreadyLoaded)
return;
lock (typeof(RenderDoc))
{
// Prevent double loads.
if (_loaded && !ignoreAlreadyLoaded)
return;
if (requiredVersion.HasValue)
MinimumRequired = requiredVersion.Value;
_loaded = true;
_api = GetApi(MinimumRequired);
if (_api != null)
AssertAtLeast(MinimumRequired);
}
}
private static RenderDocApi* _api = null;
private static bool _loaded;
private static RenderDocApi* Api
{
get
{
ReloadApi();
return _api;
}
}
private static readonly Regex _dynamicLibraryPattern = RenderDocApiDynamicLibraryRegex();
private static RenderDocApi* GetApi(RenderDocVersion minimumRequired = RenderDocVersion.Version_1_0_0)
{
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
string moduleName = module.FileName ?? string.Empty;
if (!_dynamicLibraryPattern.IsMatch(moduleName))
continue;
if (!NativeLibrary.TryLoad(moduleName, out nint moduleHandle))
return null;
if (!NativeLibrary.TryGetExport(moduleHandle, "RENDERDOC_GetAPI", out nint procAddress))
return null;
var RENDERDOC_GetApi = (delegate* unmanaged[Cdecl])procAddress;
RenderDocApi* api;
return RENDERDOC_GetApi(minimumRequired, &api) != 0 ? api : null;
}
return null;
}
private static void AssertAtLeast(RenderDocVersion rdv, [CallerMemberName] string callee = "")
{
Version ver = rdv.SystemVersion;
AssertAtLeast(ver.Major, ver.Minor, ver.Build, callee);
}
private static void AssertAtLeast(int major, int minor, int patch = 0, [CallerMemberName] string callee = "")
{
if (!AssertVersionEnabled)
return;
if (Version!.Major < major)
goto fail;
if (Version.Major > major)
goto success;
if (Version.Minor < minor)
goto fail;
if (Version.Minor > minor)
goto success;
if (Version.Build < patch)
goto fail;
success:
return;
fail:
Version minVersion =
typeof(RenderDoc).GetMethod(callee)!.GetCustomAttribute()!.MinVersion;
throw new NotSupportedException(
$"This API was introduced in RenderDoc API {minVersion}. Current API version is {Version}.");
}
private static byte[] ToNullTerminatedByteArray(this string str, Encoding? encoding = null)
{
encoding ??= Encoding.UTF8;
return encoding.GetBytes(str + '\0');
}
[GeneratedRegex(@"(lib)?renderdoc(\.dll|\.so|\.dylib)(\.\d+)?",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex RenderDocApiDynamicLibraryRegex();
#endregion
}
}