From 8f0a408a2bd7c416f855cda7b95aa9b7ba4249cf Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 6 Aug 2025 16:22:55 +0100 Subject: [PATCH] Add a new in-application API for setting user-specified annotations --- renderdoc/api/app/renderdoc_app.h | 140 +++++++++++++++++++++++--- renderdoc/core/core.cpp | 33 ++++++ renderdoc/core/core.h | 14 ++- renderdoc/driver/d3d11/d3d11_device.h | 11 ++ renderdoc/driver/d3d12/d3d12_device.h | 11 ++ renderdoc/driver/gl/gl_driver.h | 11 ++ renderdoc/driver/vulkan/vk_core.h | 11 ++ renderdoc/replay/app_api.cpp | 123 ++++++++++++++++++---- 8 files changed, 319 insertions(+), 35 deletions(-) diff --git a/renderdoc/api/app/renderdoc_app.h b/renderdoc/api/app/renderdoc_app.h index ddeee62a1..0fd830750 100644 --- a/renderdoc/api/app/renderdoc_app.h +++ b/renderdoc/api/app/renderdoc_app.h @@ -68,6 +68,10 @@ extern "C" { // truncated version when only a uint64_t is available (e.g. Vulkan tags): #define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL +// this is a magic value for vulkan user tags to indicate which dispatchable API objects are which +// for object annotations +#define RENDERDOC_APIObjectAnnotationHelper 0xfbb3b337b664d0adULL + ////////////////////////////////////////////////////////////////////////////////////////////////// // RenderDoc capture options // @@ -560,6 +564,106 @@ typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DeviceP // multiple times only the last title will be used. typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title); +// Annotations API: +// +// These functions allow you to specify annotations either on a per-command level, or a per-object +// level. +// +// Basic types of annotations are supported, as well as vector versions and references to API objects. +// +// The annotations are stored as keys, with the key being a dot-separated path allowing arbitrary +// nesting and user organisation. The keys are sorted in human order so `foo.2.bar` will be displayed +// before `foo.10.bar` to allow creation of arrays if desired. +// +// Deleting an annotation can be done by assigning an empty value to it. + +// the type of an annotation value, or Empty to delete an annotation +typedef enum RENDERDOC_AnnotationType +{ + eRENDERDOC_Empty, + eRENDERDOC_Bool, + eRENDERDOC_Int32, + eRENDERDOC_UInt32, + eRENDERDOC_Int64, + eRENDERDOC_UInt64, + eRENDERDOC_Float, + eRENDERDOC_Double, + eRENDERDOC_String, + eRENDERDOC_APIObject, + eRENDERDOC_AnnotationMax = 0x7FFFFFFF, +} RENDERDOC_AnnotationType; + +// a union with vector annotation value data +typedef union RENDERDOC_AnnotationVectorValue +{ + bool boolean[4]; + int32_t int32[4]; + int64_t int64[4]; + uint32_t uint32[4]; + uint64_t uint64[4]; + float float32[4]; + double float64[4]; +} RENDERDOC_AnnotationVectorValue; + +// a union with scalar annotation value data +typedef union RENDERDOC_AnnotationValue +{ + bool boolean; + int32_t int32; + int64_t int64; + uint32_t uint32; + uint64_t uint64; + float float32; + double float64; + + RENDERDOC_AnnotationVectorValue vector; + + const char *string; + void *apiObject; +} RENDERDOC_AnnotationValue; + +// simple C++ helper to avoid the need for a temporary object to pass a simple value via the function +#ifdef __cplusplus +struct RDAnnotationHelper +{ + RENDERDOC_AnnotationValue val; + + RDAnnotationHelper(bool b) { val.boolean = b; } + RDAnnotationHelper(int32_t i) { val.int32 = i; } + RDAnnotationHelper(int64_t i) { val.int64 = i; } + RDAnnotationHelper(uint32_t i) { val.uint32 = i; } + RDAnnotationHelper(uint64_t i) { val.uint64 = i; } + RDAnnotationHelper(float f) { val.float32 = f; } + RDAnnotationHelper(double d) { val.float64 = d; } + RDAnnotationHelper(const char *s) { val.string = s; } + + operator RENDERDOC_AnnotationValue *() { return &val; } +}; +#endif + +// The device is specified in the same way as other API calls that take a RENDERDOC_DevicePointer +// to specify the device. +// +// The object or queue/commandbuffer will depend on the graphics API in question. +// +// Return value: +// 0 - The annotation was applied successfully. +// 1 - The device is unknown/invalid +// 2 - The device is valid but the annotation is not supported for API-specific reasons, such as an +// unrecognised or invalid object or queue/commandbuffer +// 3 - The call is ill-formed or invalid e.g. empty is specified with a value pointer, or non-empty +// is specified with a NULL value pointer +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetObjectAnnotation)(RENDERDOC_DevicePointer device, + void *object, const char *key, + RENDERDOC_AnnotationType valueType, + uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value); + +typedef uint32_t(RENDERDOC_CC *pRENDERDOC_SetCommandAnnotation)( + RENDERDOC_DevicePointer device, void *queueOrCommandBuffer, const char *key, + RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value); + ////////////////////////////////////////////////////////////////////////////////////////////////// // RenderDoc API versions // @@ -588,6 +692,7 @@ typedef enum RENDERDOC_Version eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02 eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00 eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00 + eRENDERDOC_API_Version_1_7_0 = 10700, // RENDERDOC_API_1_7_0 = 1 07 00 } RENDERDOC_Version; // API version changelog: @@ -618,8 +723,10 @@ typedef enum RENDERDOC_Version // 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected // 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a // capture made with StartFrameCapture() or EndFrameCapture() +// 1.7.0 - Added feature: SetObjectAnnotation() / SetCommandAnnotation() for adding rich +// annotations to objects and command streams -typedef struct RENDERDOC_API_1_6_0 +typedef struct RENDERDOC_API_1_7_0 { pRENDERDOC_GetAPIVersion GetAPIVersion; @@ -697,20 +804,25 @@ typedef struct RENDERDOC_API_1_6_0 // new function in 1.6.0 pRENDERDOC_SetCaptureTitle SetCaptureTitle; -} RENDERDOC_API_1_6_0; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_0; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_1; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_2; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_0; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_1; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_2; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_2_0; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_3_0; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_0; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_1; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_2; -typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_5_0; + // new functions in 1.7.0 + pRENDERDOC_SetObjectAnnotation SetObjectAnnotation; + pRENDERDOC_SetCommandAnnotation SetCommandAnnotation; +} RENDERDOC_API_1_7_0; + +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_1; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_0_2; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_1; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_1_2; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_2_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_3_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_1; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_4_2; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_5_0; +typedef RENDERDOC_API_1_7_0 RENDERDOC_API_1_6_0; ////////////////////////////////////////////////////////////////////////////////////////////////// // RenderDoc API entry point diff --git a/renderdoc/core/core.cpp b/renderdoc/core/core.cpp index 935004e3d..b5e61744d 100644 --- a/renderdoc/core/core.cpp +++ b/renderdoc/core/core.cpp @@ -301,6 +301,39 @@ rdcstr DoStringise(const SystemChunk &el) END_ENUM_STRINGISE(); } +template <> +rdcstr DoStringise(const RENDERDOC_AnnotationType &el) +{ + BEGIN_ENUM_STRINGISE(RENDERDOC_AnnotationType); + { + STRINGISE_ENUM_NAMED(eRENDERDOC_Bool, "bool"); + STRINGISE_ENUM_NAMED(eRENDERDOC_Int32, "int32"); + STRINGISE_ENUM_NAMED(eRENDERDOC_UInt32, "uint32"); + STRINGISE_ENUM_NAMED(eRENDERDOC_Int64, "int64"); + STRINGISE_ENUM_NAMED(eRENDERDOC_UInt64, "uint64"); + STRINGISE_ENUM_NAMED(eRENDERDOC_Float, "float"); + STRINGISE_ENUM_NAMED(eRENDERDOC_Double, "double"); + STRINGISE_ENUM_NAMED(eRENDERDOC_String, "string"); + STRINGISE_ENUM_NAMED(eRENDERDOC_APIObject, "object"); + } + END_ENUM_STRINGISE(); +} + +template +void DoSerialise(SerialiserType &ser, RENDERDOC_AnnotationValue &el) +{ + if(ser.GetStructArg() == eRENDERDOC_String) + { + SERIALISE_MEMBER(string).Hidden(); + } + else + { + SERIALISE_MEMBER(vector.uint64).Hidden(); + } +} + +INSTANTIATE_SERIALISE_TYPE(RENDERDOC_AnnotationValue); + RenderDoc &RenderDoc::Inst() { static RenderDoc realInst; diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index fba43d876..50ccdfd1f 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -36,6 +36,9 @@ #include "common/timing.h" #include "os/os_specific.h" +DECLARE_REFLECTION_ENUM(RENDERDOC_AnnotationType); +DECLARE_REFLECTION_STRUCT(RENDERDOC_AnnotationValue); + class Chunk; struct RDCThumb; struct ReplayOptions; @@ -103,6 +106,13 @@ struct IFrameCapturer virtual void StartFrameCapture(DeviceOwnedWindow devWnd) = 0; virtual bool EndFrameCapture(DeviceOwnedWindow devWnd) = 0; virtual bool DiscardFrameCapture(DeviceOwnedWindow devWnd) = 0; + + virtual uint32_t SetObjectAnnotation(void *object, const char *key, + RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value) = 0; + virtual uint32_t SetCommandAnnotation(void *queueOrCommandBuffer, const char *key, + RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value) = 0; }; struct IDeviceProtocolHandler; @@ -572,6 +582,8 @@ public: void AddDeviceFrameCapturer(void *dev, IFrameCapturer *cap); void RemoveDeviceFrameCapturer(void *dev); + IFrameCapturer *MatchFrameCapturer(DeviceOwnedWindow devWnd); + void StartFrameCapture(DeviceOwnedWindow devWnd); bool IsFrameCapturing() { return m_CapturesActive > 0; } void SetActiveWindow(DeviceOwnedWindow devWnd); @@ -724,8 +736,6 @@ private: DeviceOwnedWindow m_ActiveWindow; std::map m_DeviceFrameCapturers; - IFrameCapturer *MatchFrameCapturer(DeviceOwnedWindow devWnd); - bool m_VendorExts[arraydim()] = {}; volatile bool m_TargetControlThreadShutdown; diff --git a/renderdoc/driver/d3d11/d3d11_device.h b/renderdoc/driver/d3d11/d3d11_device.h index dc244761a..65b433e48 100644 --- a/renderdoc/driver/d3d11/d3d11_device.h +++ b/renderdoc/driver/d3d11/d3d11_device.h @@ -721,6 +721,17 @@ public: void StartFrameCapture(DeviceOwnedWindow devWnd); bool EndFrameCapture(DeviceOwnedWindow devWnd); bool DiscardFrameCapture(DeviceOwnedWindow devWnd); + uint32_t SetObjectAnnotation(void *object, const char *key, RENDERDOC_AnnotationType valueType, + uint32_t valueVectorWidth, const RENDERDOC_AnnotationValue *value) + { + return 2; + } + uint32_t SetCommandAnnotation(void *queueOrCommandBuffer, const char *key, + RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value) + { + return 2; + } ID3DUserDefinedAnnotation *GetAnnotations() { return m_RealAnnotations; } ID3D11InfoQueue *GetInfoQueue() { return m_pInfoQueue; } diff --git a/renderdoc/driver/d3d12/d3d12_device.h b/renderdoc/driver/d3d12/d3d12_device.h index 1423f4f8c..644819868 100644 --- a/renderdoc/driver/d3d12/d3d12_device.h +++ b/renderdoc/driver/d3d12/d3d12_device.h @@ -1116,6 +1116,17 @@ public: void StartFrameCapture(DeviceOwnedWindow devWnd); bool EndFrameCapture(DeviceOwnedWindow devWnd); bool DiscardFrameCapture(DeviceOwnedWindow devWnd); + uint32_t SetObjectAnnotation(void *object, const char *key, RENDERDOC_AnnotationType valueType, + uint32_t valueVectorWidth, const RENDERDOC_AnnotationValue *value) + { + return 2; + } + uint32_t SetCommandAnnotation(void *queueOrCommandBuffer, const char *key, + RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value) + { + return 2; + } template bool Serialise_Present(SerialiserType &ser, ID3D12Resource *PresentedImage, UINT SyncInterval, diff --git a/renderdoc/driver/gl/gl_driver.h b/renderdoc/driver/gl/gl_driver.h index d28048ea9..375d58b9f 100644 --- a/renderdoc/driver/gl/gl_driver.h +++ b/renderdoc/driver/gl/gl_driver.h @@ -705,6 +705,17 @@ public: void StartFrameCapture(DeviceOwnedWindow devWnd); bool EndFrameCapture(DeviceOwnedWindow devWnd); bool DiscardFrameCapture(DeviceOwnedWindow devWnd); + uint32_t SetObjectAnnotation(void *object, const char *key, RENDERDOC_AnnotationType valueType, + uint32_t valueVectorWidth, const RENDERDOC_AnnotationValue *value) + { + return 2; + } + uint32_t SetCommandAnnotation(void *queueOrCommandBuffer, const char *key, + RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value) + { + return 2; + } // map with key being mip level, value being stored data typedef std::map CompressedDataStore; diff --git a/renderdoc/driver/vulkan/vk_core.h b/renderdoc/driver/vulkan/vk_core.h index 2e4c1a68d..dd80f6346 100644 --- a/renderdoc/driver/vulkan/vk_core.h +++ b/renderdoc/driver/vulkan/vk_core.h @@ -1199,6 +1199,17 @@ private: void StartFrameCapture(DeviceOwnedWindow devWnd); bool EndFrameCapture(DeviceOwnedWindow devWnd); bool DiscardFrameCapture(DeviceOwnedWindow devWnd); + uint32_t SetObjectAnnotation(void *object, const char *key, RENDERDOC_AnnotationType valueType, + uint32_t valueVectorWidth, const RENDERDOC_AnnotationValue *value) + { + return 2; + } + uint32_t SetCommandAnnotation(void *queueOrCommandBuffer, const char *key, + RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value) + { + return 2; + } void AdvanceFrame(); void Present(DeviceOwnedWindow devWnd); diff --git a/renderdoc/replay/app_api.cpp b/renderdoc/replay/app_api.cpp index 1467ea87c..f3b3ffdab 100644 --- a/renderdoc/replay/app_api.cpp +++ b/renderdoc/replay/app_api.cpp @@ -251,28 +251,109 @@ static uint32_t ShowReplayUI() return RenderDoc::Inst().ShowReplayUI() ? 1 : 0; } +static uint32_t SetObjectAnnotation(void *device, void *object, const char *key, + RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value) +{ + if(object == NULL) + { + RDCWARN("Invalid annotation - object must not be NULL."); + return 3; + } + + if((valueType == eRENDERDOC_Empty && value != NULL) || + (valueType != eRENDERDOC_Empty && value == NULL)) + { + RDCWARN("Invalid annotation - value should be NULL and type should be empty"); + return 3; + } + + if((valueType == eRENDERDOC_Empty || valueType == eRENDERDOC_String || + valueType == eRENDERDOC_APIObject) && + valueVectorWidth != 0) + { + RDCWARN( + "Invalid annotation - for deletion, or setting strings and objects, vector width must be " + "0"); + return 3; + } + + if(key == NULL || key[0] == 0 || key[0] == '.') + { + RDCWARN("Invalid annotation - key should not be NULL, empty, or start with a ."); + return 3; + } + + DeviceOwnedWindow devWnd(device, NULL); + + IFrameCapturer *capturer = RenderDoc::Inst().MatchFrameCapturer(devWnd); + + if(!capturer) + return 1; + + return capturer->SetObjectAnnotation(object, key, valueType, valueVectorWidth, value); +} + +static uint32_t SetCommandAnnotation(void *device, void *queueOrCommandBuffer, const char *key, + RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + const RENDERDOC_AnnotationValue *value) +{ + if((valueType == eRENDERDOC_Empty && value != NULL) || + (valueType != eRENDERDOC_Empty && value == NULL)) + { + RDCWARN("Invalid annotation - value should be NULL and type should be empty"); + return 3; + } + + if(key == NULL || key[0] == 0 || key[0] == '.') + { + RDCWARN("Invalid annotation - key should not be NULL, empty, or start with a ."); + return 3; + } + + if((valueType == eRENDERDOC_Empty || valueType == eRENDERDOC_String || + valueType == eRENDERDOC_APIObject) && + valueVectorWidth != 0) + { + RDCWARN( + "Invalid annotation - for deletion, or setting strings and objects, vector width must be " + "0"); + return 3; + } + + DeviceOwnedWindow devWnd(device, NULL); + + IFrameCapturer *capturer = RenderDoc::Inst().MatchFrameCapturer(devWnd); + + if(!capturer) + return 1; + + return capturer->SetCommandAnnotation(queueOrCommandBuffer, key, valueType, valueVectorWidth, + value); +} + // defined in capture_options.cpp int RENDERDOC_CC SetCaptureOptionU32(RENDERDOC_CaptureOption opt, uint32_t val); int RENDERDOC_CC SetCaptureOptionF32(RENDERDOC_CaptureOption opt, float val); uint32_t RENDERDOC_CC GetCaptureOptionU32(RENDERDOC_CaptureOption opt); float RENDERDOC_CC GetCaptureOptionF32(RENDERDOC_CaptureOption opt); -void RENDERDOC_CC GetAPIVersion_1_6_0(int *major, int *minor, int *patch) +void RENDERDOC_CC GetAPIVersion_1_7_0(int *major, int *minor, int *patch) { if(major) *major = 1; if(minor) - *minor = 6; + *minor = 7; if(patch) *patch = 0; } -RENDERDOC_API_1_6_0 api_1_6_0; -void Init_1_6_0() +RENDERDOC_API_1_7_0 api_1_7_0; +void Init_1_7_0() { - RENDERDOC_API_1_6_0 &api = api_1_6_0; + RENDERDOC_API_1_7_0 &api = api_1_7_0; - api.GetAPIVersion = &GetAPIVersion_1_6_0; + api.GetAPIVersion = &GetAPIVersion_1_7_0; api.SetCaptureOptionU32 = &SetCaptureOptionU32; api.SetCaptureOptionF32 = &SetCaptureOptionF32; @@ -315,6 +396,9 @@ void Init_1_6_0() api.ShowReplayUI = &ShowReplayUI; api.SetCaptureTitle = &SetCaptureTitle; + + api.SetObjectAnnotation = &SetObjectAnnotation; + api.SetCommandAnnotation = &SetCommandAnnotation; } extern "C" RENDERDOC_API int RENDERDOC_CC RENDERDOC_GetAPI(RENDERDOC_Version version, @@ -341,19 +425,20 @@ extern "C" RENDERDOC_API int RENDERDOC_CC RENDERDOC_GetAPI(RENDERDOC_Version ver ret = 1; \ } - API_VERSION_HANDLE(1_0_0, 1_6_0); - API_VERSION_HANDLE(1_0_1, 1_6_0); - API_VERSION_HANDLE(1_0_2, 1_6_0); - API_VERSION_HANDLE(1_1_0, 1_6_0); - API_VERSION_HANDLE(1_1_1, 1_6_0); - API_VERSION_HANDLE(1_1_2, 1_6_0); - API_VERSION_HANDLE(1_2_0, 1_6_0); - API_VERSION_HANDLE(1_3_0, 1_6_0); - API_VERSION_HANDLE(1_4_0, 1_6_0); - API_VERSION_HANDLE(1_4_1, 1_6_0); - API_VERSION_HANDLE(1_4_2, 1_6_0); - API_VERSION_HANDLE(1_5_0, 1_6_0); - API_VERSION_HANDLE(1_6_0, 1_6_0); + API_VERSION_HANDLE(1_0_0, 1_7_0); + API_VERSION_HANDLE(1_0_1, 1_7_0); + API_VERSION_HANDLE(1_0_2, 1_7_0); + API_VERSION_HANDLE(1_1_0, 1_7_0); + API_VERSION_HANDLE(1_1_1, 1_7_0); + API_VERSION_HANDLE(1_1_2, 1_7_0); + API_VERSION_HANDLE(1_2_0, 1_7_0); + API_VERSION_HANDLE(1_3_0, 1_7_0); + API_VERSION_HANDLE(1_4_0, 1_7_0); + API_VERSION_HANDLE(1_4_1, 1_7_0); + API_VERSION_HANDLE(1_4_2, 1_7_0); + API_VERSION_HANDLE(1_5_0, 1_7_0); + API_VERSION_HANDLE(1_6_0, 1_7_0); + API_VERSION_HANDLE(1_7_0, 1_7_0); #undef API_VERSION_HANDLE