From 650458beae2f3a8ca00d076767354dd1ad280362 Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 11 Aug 2025 16:21:21 +0100 Subject: [PATCH] Add helpers for managing annotations in structured data --- renderdoc/api/replay/data_types.h | 20 +++ renderdoc/api/replay/structured_data.h | 133 ++++++++++++++++- renderdoc/core/core.cpp | 173 +++++++++++++++++++++++ renderdoc/core/core.h | 3 + renderdoc/replay/renderdoc_serialise.inl | 11 +- 5 files changed, 329 insertions(+), 11 deletions(-) diff --git a/renderdoc/api/replay/data_types.h b/renderdoc/api/replay/data_types.h index 36449ebbd..dc8634980 100644 --- a/renderdoc/api/replay/data_types.h +++ b/renderdoc/api/replay/data_types.h @@ -669,6 +669,13 @@ typically it is one parent to many derived. )"); rdcarray parentResources; + DOCUMENT(R"(An optional set of annotations associated with this resource, may be ``None`` if +annotations are not used. + +:type: SDObject +)"); + SDObject *annotations = NULL; + DOCUMENT(R"(Utility function for setting up a custom name to overwrite the auto-generated one. :param str givenName: The custom name to use. @@ -964,6 +971,13 @@ markers added to the capture after load. )"); uint64_t fileOffset = 0; + DOCUMENT(R"(An optional set of annotations associated with this event, may be ``None`` if +annotations are not used. + +:type: SDObject +)"); + SDObject *annotations = NULL; + static const uint32_t NoChunk = ~0U; }; @@ -1855,6 +1869,12 @@ this counts the frame number when the capture was made. )"); rdcarray debugMessages; + DOCUMENT(R"(Whether or not the capture contains any annotations. + +:type: bool +)"); + bool containsAnnotations = false; + static const uint32_t NoFrameNumber = ~0U; }; diff --git a/renderdoc/api/replay/structured_data.h b/renderdoc/api/replay/structured_data.h index ba7d1b6a3..a236cb8a5 100644 --- a/renderdoc/api/replay/structured_data.h +++ b/renderdoc/api/replay/structured_data.h @@ -619,9 +619,9 @@ struct SDObject PopulateAllChildren(); } - ret->data.children.resize(data.children.size()); + ret->data.children.reserve(data.children.size()); for(size_t i = 0; i < data.children.size(); i++) - ret->data.children[i] = data.children[i]->Duplicate(); + ret->AddAndOwnChild(data.children[i]->Duplicate()); return ret; } @@ -691,8 +691,7 @@ recursively through children. // fully owned children. This shouldn't happen, but just in case we'll evaluate the lazy array // here. PopulateAllChildren(); - data.children.push_back(child->Duplicate()); - data.children.back()->m_Parent = this; + AddAndOwnChild(child->Duplicate()); } DOCUMENT(R"(Find a child object by a given name. If no matching child is found, ``None`` is returned. @@ -734,6 +733,127 @@ The order of the search is not guaranteed, so care should be taken when the name return NULL; } + DOCUMENT(R"(Create a child object by a key path. Children will be created as necessary to ensure +that the key path exists, but if they already exist then the existing object will be returned. + +Key paths are dotted recursive references, e.g. ``foo.bar`` is the member ``bar`` in parent ``foo``. + +Arrays are represented by simple numeric entries e.g. ``foo.0.bar``, ``foo.1.bar``. + +Empty entries are ignored so ``foo..bar`` and ``foo.bar`` refer to the same object. + +Key paths must not be empty and must not begin with a dot. + +If any path element is not unique in any child the behaviour is undefined, so objects should only be +manipulated by key path exclusively or not at all. + +:param str keyPath: The key path to search for and return. +:return: A reference to the object at the relative key path +:rtype: SDObject +)"); + inline SDObject *CreateChildByKeyPath(const rdcstr &keyPath) + { + if(keyPath.empty() || keyPath[0] == '.') + return this; + + int dotIdx = keyPath.indexOf('.'); + rdcstr element = keyPath.substr(0, dotIdx); + + SDObject *child = NULL; + for(size_t i = 0; i < data.children.size(); i++) + if(GetChild(i)->name == element) + child = GetChild(i); + + if(!child) + child = AddAndOwnChild(new SDObject(element, ""_lit)); + + while(dotIdx >= 0 && keyPath[dotIdx] == '.') + dotIdx++; + + if(dotIdx >= 0 && dotIdx < keyPath.count()) + return child->CreateChildByKeyPath(keyPath.substr(dotIdx)); + + return child; + } + + DOCUMENT(R"(Find if a child object if it exists by a key path. Children will not be created +if any element of the path doesn't exist and instead ``None`` will be returned. + +Key paths are dotted recursive references, e.g. ``foo.bar`` is the member ``bar`` in parent ``foo``. + +Arrays are represented by simple numeric entries e.g. ``foo.0.bar``, ``foo.1.bar``. + +Empty entries are ignored so ``foo..bar`` and ``foo.bar`` refer to the same object. + +Key paths must not be empty and must not begin with a dot. + +If any path element is not unique in any child the behaviour is undefined, so objects should only be +manipulated by key path exclusively or not at all. + +:param str keyPath: The key path to search for and return. +:return: Whether or not a child exists at the given key path +:rtype: SDObject +)"); + inline const SDObject *FindChildByKeyPath(const rdcstr &keyPath) const + { + if(keyPath.empty() || keyPath[0] == '.') + return this; + + int dotIdx = keyPath.indexOf('.'); + rdcstr element = keyPath.substr(0, dotIdx); + + const SDObject *child = NULL; + for(size_t i = 0; i < data.children.size(); i++) + if(GetChild(i)->name == element) + child = GetChild(i); + + if(!child) + return NULL; + + while(dotIdx >= 0 && keyPath[dotIdx] == '.') + dotIdx++; + + if(dotIdx >= 0 && dotIdx < keyPath.count()) + return child->FindChildByKeyPath(keyPath.substr(dotIdx)); + + return child; + } + + DOCUMENT(R"(Delete a child object by a key path. All children of the resulting path will be deleted +as well. If no such path exists, nothing will be changed. + +If any path element is not unique in any child the behaviour is undefined, so objects should only be +manipulated by key path exclusively or not at all. + +:param str keyPath: The key path to search for and delete. +)"); + inline void EraseChildByKeyPath(const rdcstr &keyPath) + { + // shouldn't happen since we delete the found child (if it exists) from the parent, but return to be fault-tolerant + if(keyPath.empty() || keyPath[0] == '.') + return; + + int dotIdx = keyPath.indexOf('.'); + rdcstr element = keyPath.substr(0, dotIdx); + + int childIdx = -1; + for(int i = 0; i < data.children.count(); i++) + if(GetChild(i)->name == element) + childIdx = i; + + // if the child doesn't exist we can stop now + if(childIdx < 0) + return; + + while(dotIdx >= 0 && keyPath[dotIdx] == '.') + dotIdx++; + + if(dotIdx >= 0 && dotIdx < keyPath.count()) + return GetChild(childIdx)->EraseChildByKeyPath(keyPath.substr(dotIdx)); + + RemoveChild(childIdx); + } + DOCUMENT(R"(Find a child object by a given index. If the index is out of bounds, ``None`` is returned. @@ -1500,12 +1620,11 @@ struct SDChunk : public SDObject ret->data.basic = data.basic; ret->data.str = data.str; - ret->data.children.resize(data.children.size()); - PopulateAllChildren(); + ret->data.children.reserve(data.children.size()); for(size_t i = 0; i < data.children.size(); i++) - ret->data.children[i] = data.children[i]->Duplicate(); + ret->AddAndOwnChild(data.children[i]->Duplicate()); return ret; } diff --git a/renderdoc/core/core.cpp b/renderdoc/core/core.cpp index b5e61744d..5d0129283 100644 --- a/renderdoc/core/core.cpp +++ b/renderdoc/core/core.cpp @@ -68,6 +68,179 @@ RDOC_CONFIG(rdcarray, Replay_Shader_LimitedSearchDirPaths, {}, "Companion array to DXBC.Debug.SearchDirPaths - listing paths which should not be " "searched exhaustively but only used for simple lookups."); +void WriteAnnotation(SDObject *obj, RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + RENDERDOC_AnnotationValue value) +{ + if(valueType == eRENDERDOC_Empty) + { + RDCERR("Invalid type of annotation to write"); + return; + } + + const rdcinflexiblestr types[eRENDERDOC_APIObject + 1][4] = { + // eRENDERDOC_Empty, + {""_lit, ""_lit, ""_lit, ""_lit}, + // eRENDERDOC_Bool, + {"bool"_lit, "bool2"_lit, "bool3"_lit, "bool4"_lit}, + // eRENDERDOC_Int32, + {"int"_lit, "int2"_lit, "int3"_lit, "int4"_lit}, + // eRENDERDOC_UInt32, + {"uint"_lit, "uint2"_lit, "uint3"_lit, "uint4"_lit}, + // eRENDERDOC_Int64, + {"long"_lit, "long2"_lit, "long3"_lit, "long4"_lit}, + // eRENDERDOC_UInt64, + {"ulong"_lit, "ulong2"_lit, "ulong3"_lit, "ulong4"_lit}, + // eRENDERDOC_Float, + {"float"_lit, "float2"_lit, "float3"_lit, "float4"_lit}, + // eRENDERDOC_Double, + {"double"_lit, "double2"_lit, "double3"_lit, "double4"_lit}, + // eRENDERDOC_String, + {"string"_lit, ""_lit, ""_lit, ""_lit}, + // eRENDERDOC_APIObject, + {"ResourceId"_lit, ""_lit, ""_lit, ""_lit}, + }; + + const uint32_t byteSize[eRENDERDOC_APIObject + 1] = { + // eRENDERDOC_Empty, + 0, + // eRENDERDOC_Bool, + 1, + // eRENDERDOC_Int32, + 4, + // eRENDERDOC_UInt32, + 4, + // eRENDERDOC_Int64, + 8, + // eRENDERDOC_UInt64, + 8, + // eRENDERDOC_Float, + 4, + // eRENDERDOC_Double, + 8, + // eRENDERDOC_String, + 0, + // eRENDERDOC_APIObject, + 8, + }; + + const SDBasic basetype[eRENDERDOC_APIObject + 1] = { + // eRENDERDOC_Empty, + SDBasic::Null, + // eRENDERDOC_Bool, + SDBasic::Boolean, + // eRENDERDOC_Int32, + SDBasic::SignedInteger, + // eRENDERDOC_UInt32, + SDBasic::UnsignedInteger, + // eRENDERDOC_Int64, + SDBasic::SignedInteger, + // eRENDERDOC_UInt64, + SDBasic::UnsignedInteger, + // eRENDERDOC_Float, + SDBasic::Float, + // eRENDERDOC_Double, + SDBasic::Float, + // eRENDERDOC_String, + SDBasic::String, + // eRENDERDOC_APIObject, + SDBasic::Resource, + }; + + if(valueVectorWidth > 1) + { + if(valueType == eRENDERDOC_APIObject) + { + RDCERR("Invalid vector width for API object"); + return; + } + else if(valueType == eRENDERDOC_String) + { + RDCERR("Invalid vector width for string"); + return; + } + + const rdcinflexiblestr comps[] = {"x"_lit, "y"_lit, "z"_lit, "w"_lit}; + + obj->type.basetype = SDBasic::Struct; + obj->type.name = types[valueType][valueVectorWidth - 1]; + + RENDERDOC_AnnotationValue tmp = {}; + for(uint32_t i = 0; i < valueVectorWidth; i++) + { + SDObject *child = obj->CreateChildByKeyPath(comps[i]); + + if(byteSize[valueType] == 1) + memcpy(&tmp.boolean, &value.vector.boolean[i], sizeof(tmp.boolean)); + if(byteSize[valueType] == 4) + memcpy(&tmp.uint32, &value.vector.uint32[i], sizeof(tmp.uint32)); + if(byteSize[valueType] == 8) + memcpy(&tmp.uint64, &value.vector.uint64[i], sizeof(tmp.uint64)); + + WriteAnnotation(child, valueType, 0, tmp); + if(i == 0) + obj->type.byteSize = child->type.byteSize * valueVectorWidth; + } + + return; + } + + obj->type.name = types[valueType][0]; + obj->type.basetype = basetype[valueType]; + obj->type.byteSize = byteSize[valueType]; + + switch(valueType) + { + case eRENDERDOC_Empty: + case eRENDERDOC_AnnotationMax: RDCERR("Invalid annotation type"); return; + case eRENDERDOC_Bool: + { + obj->data.basic.b = value.boolean; + break; + } + case eRENDERDOC_Int32: + { + obj->data.basic.i = value.int32; + break; + } + case eRENDERDOC_UInt32: + { + obj->data.basic.u = value.uint32; + break; + } + case eRENDERDOC_Int64: + { + obj->data.basic.u = value.int64; + break; + } + case eRENDERDOC_UInt64: + { + obj->data.basic.u = value.uint64; + break; + } + case eRENDERDOC_Float: + { + obj->data.basic.d = value.float32; + break; + } + case eRENDERDOC_Double: + { + obj->data.basic.d = value.float64; + break; + } + case eRENDERDOC_String: + { + obj->type.byteSize = strlen(value.string); + obj->data.str = value.string; + break; + } + case eRENDERDOC_APIObject: + { + memcpy(&obj->data.basic.id, &value.uint64, sizeof(ResourceId)); + break; + } + } +} + void LogReplayOptions(const ReplayOptions &opts) { RDCLOG("%s API validation during replay", (opts.apiValidation ? "Enabling" : "Not enabling")); diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index 50ccdfd1f..943af13b1 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -44,6 +44,9 @@ struct RDCThumb; struct ReplayOptions; struct SDObject; +void WriteAnnotation(SDObject *obj, RENDERDOC_AnnotationType valueType, uint32_t valueVectorWidth, + RENDERDOC_AnnotationValue value); + // not provided by tinyexr, just do by hand bool is_exr_file(const byte *headerBuffer, size_t size); void LogReplayOptions(const ReplayOptions &opts); diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 26a7aef3a..dc74e9316 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -442,8 +442,9 @@ void DoSerialise(SerialiserType &ser, ResourceDescription &el) SERIALISE_MEMBER(initialisationChunks); SERIALISE_MEMBER(derivedResources); SERIALISE_MEMBER(parentResources); + // SERIALISE_MEMBER(annotations); - SIZE_CHECK(112); + SIZE_CHECK(120); } template @@ -539,8 +540,9 @@ void DoSerialise(SerialiserType &ser, APIEvent &el) SERIALISE_MEMBER(eventId); SERIALISE_MEMBER(chunkIndex); SERIALISE_MEMBER(fileOffset); + // SERIALISE_MEMBER(annotations); - SIZE_CHECK(16); + SIZE_CHECK(24); } template @@ -773,8 +775,9 @@ void DoSerialise(SerialiserType &ser, FrameDescription &el) SERIALISE_MEMBER(captureTime); SERIALISE_MEMBER(stats); SERIALISE_MEMBER(debugMessages); + SERIALISE_MEMBER(containsAnnotations); - SIZE_CHECK(504); + SIZE_CHECK(512); } template @@ -783,7 +786,7 @@ void DoSerialise(SerialiserType &ser, FrameRecord &el) SERIALISE_MEMBER(frameInfo); SERIALISE_MEMBER(actionList); - SIZE_CHECK(528); + SIZE_CHECK(536); } template