diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt
index 87af43c06..3f24d7116 100644
--- a/util/test/demos/CMakeLists.txt
+++ b/util/test/demos/CMakeLists.txt
@@ -101,6 +101,7 @@ set(VULKAN_SRC
vk/vk_test.cpp
vk/vk_test.h
vk/vk_adv_cbuffer_zoo.cpp
+ vk/vk_annotations.cpp
vk/vk_blend.cpp
vk/vk_buffer_truncation.cpp
vk/vk_cbuffer_zoo.cpp
@@ -174,6 +175,7 @@ set(OPENGL_SRC
3rdparty/glad/glad.c
gl/gl_test.cpp
gl/gl_test.h
+ gl/gl_annotations.cpp
gl/gl_buffer_resizing.cpp
gl/gl_buffer_spam.cpp
gl/gl_buffer_truncation.cpp
diff --git a/util/test/demos/d3d11/d3d11_annotations.cpp b/util/test/demos/d3d11/d3d11_annotations.cpp
new file mode 100644
index 000000000..32bfbdf31
--- /dev/null
+++ b/util/test/demos/d3d11/d3d11_annotations.cpp
@@ -0,0 +1,191 @@
+/******************************************************************************
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2026 Baldur Karlsson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+#include "d3d11_test.h"
+
+RD_TEST(D3D11_Annotations, D3D11GraphicsTest)
+{
+ static constexpr const char *Description = "Test annotations via the D3D11 API.";
+
+ int main()
+ {
+ // initialise, create window, create device, etc
+ if(!Init())
+ return 3;
+
+ ID3D11Texture2DPtr img = MakeTexture(DXGI_FORMAT_R32G32B32A32_FLOAT, 4, 4).RTV();
+ ID3D11RenderTargetViewPtr imgRTV = MakeRTV(img);
+
+ SetDebugName(img, "Annotated Image");
+ SetDebugName(DefaultTriVB, "Vertex Buffer");
+
+ // cache the device pointer we pass in
+ void *d = dev;
+
+ if(rdoc)
+ {
+ rdoc->SetObjectAnnotation(d, img, "basic.bool", eRENDERDOC_Bool, 0, RDAnnotationHelper(true));
+ rdoc->SetObjectAnnotation(d, img, "basic.int32", eRENDERDOC_Int32, 0, RDAnnotationHelper(-3));
+ rdoc->SetObjectAnnotation(d, img, "basic.int64", eRENDERDOC_Int64, 0,
+ RDAnnotationHelper(-3000000000000LL));
+ rdoc->SetObjectAnnotation(d, img, "basic.uint32", eRENDERDOC_UInt32, 0, RDAnnotationHelper(3));
+ rdoc->SetObjectAnnotation(d, img, "basic.uint64", eRENDERDOC_UInt64, 0,
+ RDAnnotationHelper(3000000000000LL));
+ rdoc->SetObjectAnnotation(d, img, "basic.float", eRENDERDOC_Float, 0,
+ RDAnnotationHelper(3.25f));
+ rdoc->SetObjectAnnotation(d, img, "basic.double", eRENDERDOC_Double, 0,
+ RDAnnotationHelper(3.25000000001));
+ rdoc->SetObjectAnnotation(d, img, "basic.string", eRENDERDOC_String, 0,
+ RDAnnotationHelper("Hello, World!"));
+
+ RENDERDOC_AnnotationValue val;
+ val.apiObject = (void *)DefaultTriVB;
+ rdoc->SetObjectAnnotation(d, img, "basic.object", eRENDERDOC_APIObject, 0, &val);
+
+ rdoc->SetObjectAnnotation(d, img, "basic.object.__offset", eRENDERDOC_UInt32, 0,
+ RDAnnotationHelper(64));
+ rdoc->SetObjectAnnotation(d, img, "basic.object.__size", eRENDERDOC_UInt32, 0,
+ RDAnnotationHelper(32));
+ rdoc->SetObjectAnnotation(d, img, "basic.object.__rd_format", eRENDERDOC_String, 0,
+ RDAnnotationHelper("float4 vertex_data;"));
+
+ rdoc->SetObjectAnnotation(d, DefaultTriVB, "__rd_format", eRENDERDOC_String, 0,
+ RDAnnotationHelper("float3 pos;\n"
+ "float4 col;\n"
+ "float2 uv;\n"));
+
+ val = {};
+ val.vector.float32[0] = 1.1f;
+ val.vector.float32[1] = 2.2f;
+ val.vector.float32[2] = 3.3f;
+ val.vector.float32[3] = 4.4f; // should be ignored
+ rdoc->SetObjectAnnotation(d, img, "basic.vec3", eRENDERDOC_Float, 3, &val);
+
+ rdoc->SetObjectAnnotation(d, img, "deep.nested.path.to.annotation", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-4));
+ rdoc->SetObjectAnnotation(d, img, "deep.nested.path.to.annotation2", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-5));
+ rdoc->SetObjectAnnotation(d, img, "deep.alternate.path.to.annotation", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-6));
+
+ // deleted paths should not stay around
+ rdoc->SetObjectAnnotation(d, img, "deleteme", eRENDERDOC_Int32, 0, RDAnnotationHelper(-7));
+ rdoc->SetObjectAnnotation(d, img, "deleteme", eRENDERDOC_Empty, 0, NULL);
+
+ rdoc->SetObjectAnnotation(d, img, "path.deleted.by.parent", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-8));
+ rdoc->SetObjectAnnotation(d, img, "path.deleted.by.parent2", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-9));
+
+ // this will delete all children. `path` will still exist, but will be empty
+ rdoc->SetObjectAnnotation(d, img, "path.deleted", eRENDERDOC_Empty, 0, NULL);
+ }
+
+ while(Running())
+ {
+ if(rdoc)
+ {
+ // queue annotations don't exist in D3D11, but we set these to share the same test code.
+ // we specify the immediate context here specifically. Later we'll use NULL (which is equivalent on D3D11)
+ if(curFrame == 2)
+ rdoc->SetCommandAnnotation(d, ctx, "queue.too_old", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1000));
+
+ rdoc->SetCommandAnnotation(d, ctx, "queue.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1000));
+
+ rdoc->SetCommandAnnotation(d, ctx, "command.overwritten", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(9999));
+
+ rdoc->SetCommandAnnotation(d, ctx, "command.inherited", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1234));
+
+ rdoc->SetCommandAnnotation(d, ctx, "command.deleted", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(50));
+ }
+
+ setMarker("Start");
+
+ if(rdoc)
+ {
+ rdoc->SetCommandAnnotation(d, NULL, "new.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(2000));
+
+ rdoc->SetCommandAnnotation(d, NULL, "command.overwritten", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-3333));
+
+ rdoc->SetCommandAnnotation(d, NULL, "command.new", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(3333));
+
+ rdoc->SetCommandAnnotation(d, NULL, "command.deleted", eRENDERDOC_Empty, 0, NULL);
+ }
+
+ setMarker("Initial");
+
+ ClearRenderTargetView(bbRTV, {0.2f, 0.2f, 0.2f, 1.0f});
+ ClearRenderTargetView(imgRTV, {0.2f, 0.2f, 0.2f, 1.0f});
+
+ if(rdoc)
+ rdoc->SetCommandAnnotation(d, NULL, "command.new", eRENDERDOC_Float, 0,
+ RDAnnotationHelper(1.75f));
+
+ IASetVertexBuffer(DefaultTriVB, sizeof(DefaultA2V), 0);
+ ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+ ctx->IASetInputLayout(defaultLayout);
+
+ ctx->VSSetShader(DefaultTriVS, NULL, 0);
+ ctx->PSSetShader(DefaultTriPS, NULL, 0);
+
+ RSSetViewport({0.0f, 0.0f, (float)screenWidth, (float)screenHeight, 0.0f, 1.0f});
+
+ ctx->OMSetRenderTargets(1, &bbRTV.GetInterfacePtr(), NULL);
+
+ setMarker("Pre-Draw");
+
+ // deleting a value is fine if it's re-added before the next event
+ if(rdoc)
+ {
+ rdoc->SetCommandAnnotation(d, NULL, "new.value", eRENDERDOC_Empty, 0, NULL);
+ rdoc->SetCommandAnnotation(d, NULL, "new.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(4000));
+ }
+
+ setMarker("Draw 1");
+
+ ctx->Draw(3, 0);
+
+ RSSetViewport({0.0f, 0.0f, (float)screenWidth / 2.0f, (float)screenHeight / 2.0f, 0.0f, 1.0f});
+
+ setMarker("Draw 2");
+
+ ctx->Draw(3, 0);
+
+ Present();
+ }
+
+ return 0;
+ }
+};
+
+REGISTER_TEST();
diff --git a/util/test/demos/d3d12/d3d12_annotations.cpp b/util/test/demos/d3d12/d3d12_annotations.cpp
new file mode 100644
index 000000000..909eb59d1
--- /dev/null
+++ b/util/test/demos/d3d12/d3d12_annotations.cpp
@@ -0,0 +1,205 @@
+/******************************************************************************
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2026 Baldur Karlsson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+#include "d3d12_test.h"
+
+RD_TEST(D3D12_Annotations, D3D12GraphicsTest)
+{
+ static constexpr const char *Description = "Test annotations via the D3D12 API.";
+
+ int main()
+ {
+ // initialise, create window, create device, etc
+ if(!Init())
+ return 3;
+
+ ID3D12ResourcePtr img = MakeTexture(DXGI_FORMAT_R32G32B32A32_FLOAT, 4, 4)
+ .RTV()
+ .InitialState(D3D12_RESOURCE_STATE_RENDER_TARGET);
+
+ img->SetName(L"Annotated Image");
+ DefaultTriVB->SetName(L"Vertex Buffer");
+
+ // cache the device pointer we pass in
+ void *d = dev;
+
+ if(rdoc)
+ {
+ rdoc->SetObjectAnnotation(d, img, "basic.bool", eRENDERDOC_Bool, 0, RDAnnotationHelper(true));
+ rdoc->SetObjectAnnotation(d, img, "basic.int32", eRENDERDOC_Int32, 0, RDAnnotationHelper(-3));
+ rdoc->SetObjectAnnotation(d, img, "basic.int64", eRENDERDOC_Int64, 0,
+ RDAnnotationHelper(-3000000000000LL));
+ rdoc->SetObjectAnnotation(d, img, "basic.uint32", eRENDERDOC_UInt32, 0, RDAnnotationHelper(3));
+ rdoc->SetObjectAnnotation(d, img, "basic.uint64", eRENDERDOC_UInt64, 0,
+ RDAnnotationHelper(3000000000000LL));
+ rdoc->SetObjectAnnotation(d, img, "basic.float", eRENDERDOC_Float, 0,
+ RDAnnotationHelper(3.25f));
+ rdoc->SetObjectAnnotation(d, img, "basic.double", eRENDERDOC_Double, 0,
+ RDAnnotationHelper(3.25000000001));
+ rdoc->SetObjectAnnotation(d, img, "basic.string", eRENDERDOC_String, 0,
+ RDAnnotationHelper("Hello, World!"));
+
+ RENDERDOC_AnnotationValue val;
+ val.apiObject = (void *)DefaultTriVB;
+ rdoc->SetObjectAnnotation(d, img, "basic.object", eRENDERDOC_APIObject, 0, &val);
+
+ rdoc->SetObjectAnnotation(d, img, "basic.object.__offset", eRENDERDOC_UInt32, 0,
+ RDAnnotationHelper(64));
+ rdoc->SetObjectAnnotation(d, img, "basic.object.__size", eRENDERDOC_UInt32, 0,
+ RDAnnotationHelper(32));
+ rdoc->SetObjectAnnotation(d, img, "basic.object.__rd_format", eRENDERDOC_String, 0,
+ RDAnnotationHelper("float4 vertex_data;"));
+
+ rdoc->SetObjectAnnotation(d, DefaultTriVB, "__rd_format", eRENDERDOC_String, 0,
+ RDAnnotationHelper("float3 pos;\n"
+ "float4 col;\n"
+ "float2 uv;\n"));
+
+ val = {};
+ val.vector.float32[0] = 1.1f;
+ val.vector.float32[1] = 2.2f;
+ val.vector.float32[2] = 3.3f;
+ val.vector.float32[3] = 4.4f; // should be ignored
+ rdoc->SetObjectAnnotation(d, img, "basic.vec3", eRENDERDOC_Float, 3, &val);
+
+ rdoc->SetObjectAnnotation(d, img, "deep.nested.path.to.annotation", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-4));
+ rdoc->SetObjectAnnotation(d, img, "deep.nested.path.to.annotation2", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-5));
+ rdoc->SetObjectAnnotation(d, img, "deep.alternate.path.to.annotation", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-6));
+
+ // deleted paths should not stay around
+ rdoc->SetObjectAnnotation(d, img, "deleteme", eRENDERDOC_Int32, 0, RDAnnotationHelper(-7));
+ rdoc->SetObjectAnnotation(d, img, "deleteme", eRENDERDOC_Empty, 0, NULL);
+
+ rdoc->SetObjectAnnotation(d, img, "path.deleted.by.parent", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-8));
+ rdoc->SetObjectAnnotation(d, img, "path.deleted.by.parent2", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-9));
+
+ // this will delete all children. `path` will still exist, but will be empty
+ rdoc->SetObjectAnnotation(d, img, "path.deleted", eRENDERDOC_Empty, 0, NULL);
+ }
+
+ while(Running())
+ {
+ if(rdoc)
+ {
+ // queue annotations are only included when in the captured frame
+ if(curFrame == 2)
+ rdoc->SetCommandAnnotation(d, queue, "queue.too_old", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1000));
+
+ rdoc->SetCommandAnnotation(d, queue, "queue.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1000));
+
+ rdoc->SetCommandAnnotation(d, queue, "command.overwritten", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(9999));
+
+ rdoc->SetCommandAnnotation(d, queue, "command.inherited", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1234));
+
+ rdoc->SetCommandAnnotation(d, queue, "command.deleted", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(50));
+ }
+
+ ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer();
+
+ Reset(cmd);
+
+ setMarker(cmd, "Start");
+
+ if(rdoc)
+ {
+ rdoc->SetCommandAnnotation(d, cmd, "new.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(2000));
+
+ rdoc->SetCommandAnnotation(d, cmd, "command.overwritten", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-3333));
+
+ rdoc->SetCommandAnnotation(d, cmd, "command.new", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(3333));
+
+ rdoc->SetCommandAnnotation(d, cmd, "command.deleted", eRENDERDOC_Empty, 0, NULL);
+ }
+
+ setMarker(cmd, "Initial");
+
+ D3D12_CPU_DESCRIPTOR_HANDLE rtv =
+ MakeRTV(img).Format(DXGI_FORMAT_R32G32B32A32_FLOAT).CreateCPU(0);
+
+ ClearRenderTargetView(cmd, rtv, {0.2f, 0.2f, 0.2f, 1.0f});
+
+ ID3D12ResourcePtr bb = StartUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET);
+
+ ClearRenderTargetView(cmd, BBRTV, {0.2f, 0.2f, 0.2f, 1.0f});
+
+ if(rdoc)
+ rdoc->SetCommandAnnotation(d, cmd, "command.new", eRENDERDOC_Float, 0,
+ RDAnnotationHelper(1.75f));
+
+ cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ IASetVertexBuffer(cmd, DefaultTriVB, sizeof(DefaultA2V), 0);
+ cmd->SetPipelineState(DefaultTriPSO);
+ cmd->SetGraphicsRootSignature(DefaultTriSig);
+
+ SetMainWindowViewScissor(cmd);
+
+ OMSetRenderTargets(cmd, {BBRTV}, {});
+
+ setMarker(cmd, "Pre-Draw");
+
+ // deleting a value is fine if it's re-added before the next event
+ if(rdoc)
+ {
+ rdoc->SetCommandAnnotation(d, cmd, "new.value", eRENDERDOC_Empty, 0, NULL);
+ rdoc->SetCommandAnnotation(d, cmd, "new.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(4000));
+ }
+
+ setMarker(cmd, "Draw 1");
+
+ cmd->DrawInstanced(3, 1, 0, 0);
+
+ RSSetViewport(
+ cmd, {0.0f, 0.0f, (float)screenWidth / 2.0f, (float)screenHeight / 2.0f, 0.0f, 1.0f});
+
+ setMarker(cmd, "Draw 2");
+
+ cmd->DrawInstanced(3, 1, 0, 0);
+
+ FinishUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET);
+
+ cmd->Close();
+
+ SubmitAndPresent({cmd});
+ }
+
+ return 0;
+ }
+};
+
+REGISTER_TEST();
diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj
index b39934009..b75e59c5e 100644
--- a/util/test/demos/demos.vcxproj
+++ b/util/test/demos/demos.vcxproj
@@ -135,6 +135,7 @@
+
@@ -192,6 +193,7 @@
+
@@ -252,6 +254,7 @@
true
+
@@ -315,6 +318,7 @@
+
diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters
index 8d1a84458..cc6cd559c 100644
--- a/util/test/demos/demos.vcxproj.filters
+++ b/util/test/demos/demos.vcxproj.filters
@@ -742,6 +742,18 @@
OpenGL\demos
+
+ Vulkan\demos
+
+
+ OpenGL\demos
+
+
+ D3D12\demos
+
+
+ D3D11\demos
+
diff --git a/util/test/demos/gl/gl_annotations.cpp b/util/test/demos/gl/gl_annotations.cpp
new file mode 100644
index 000000000..26d30f960
--- /dev/null
+++ b/util/test/demos/gl/gl_annotations.cpp
@@ -0,0 +1,189 @@
+/******************************************************************************
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2026 Baldur Karlsson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+#include "gl_test.h"
+
+RD_TEST(GL_Annotations, OpenGLGraphicsTest)
+{
+ static constexpr const char *Description = "Test annotations via the OpenGL API.";
+
+ int main()
+ {
+ // initialise, create window, create context, etc
+ if(!Init())
+ return 3;
+
+ GLuint tex = MakeTexture();
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 4, 4);
+
+ glObjectLabel(GL_TEXTURE, tex, -1, "Annotated Image");
+ glObjectLabel(GL_BUFFER, DefaultTriVB, -1, "Vertex Buffer");
+
+ // cache the device pointer we pass in
+ void *d = mainContext;
+
+ if(rdoc)
+ {
+ // GL needs a helper struct to specify the type and handle together
+ RDGLObjectHelper img(GL_TEXTURE, tex);
+ RDGLObjectHelper buf(GL_BUFFER, DefaultTriVB);
+
+ rdoc->SetObjectAnnotation(d, img, "basic.bool", eRENDERDOC_Bool, 0, RDAnnotationHelper(true));
+ rdoc->SetObjectAnnotation(d, img, "basic.int32", eRENDERDOC_Int32, 0, RDAnnotationHelper(-3));
+ rdoc->SetObjectAnnotation(d, img, "basic.int64", eRENDERDOC_Int64, 0,
+ RDAnnotationHelper(-3000000000000LL));
+ rdoc->SetObjectAnnotation(d, img, "basic.uint32", eRENDERDOC_UInt32, 0, RDAnnotationHelper(3));
+ rdoc->SetObjectAnnotation(d, img, "basic.uint64", eRENDERDOC_UInt64, 0,
+ RDAnnotationHelper(3000000000000LL));
+ rdoc->SetObjectAnnotation(d, img, "basic.float", eRENDERDOC_Float, 0,
+ RDAnnotationHelper(3.25f));
+ rdoc->SetObjectAnnotation(d, img, "basic.double", eRENDERDOC_Double, 0,
+ RDAnnotationHelper(3.25000000001));
+ rdoc->SetObjectAnnotation(d, img, "basic.string", eRENDERDOC_String, 0,
+ RDAnnotationHelper("Hello, World!"));
+
+ RENDERDOC_AnnotationValue val;
+ val.apiObject = (void *)(RENDERDOC_GLResourceReference *)buf;
+ rdoc->SetObjectAnnotation(d, img, "basic.object", eRENDERDOC_APIObject, 0, &val);
+
+ rdoc->SetObjectAnnotation(d, img, "basic.object.__offset", eRENDERDOC_UInt32, 0,
+ RDAnnotationHelper(64));
+ rdoc->SetObjectAnnotation(d, img, "basic.object.__size", eRENDERDOC_UInt32, 0,
+ RDAnnotationHelper(32));
+ rdoc->SetObjectAnnotation(d, img, "basic.object.__rd_format", eRENDERDOC_String, 0,
+ RDAnnotationHelper("float4 vertex_data;"));
+
+ rdoc->SetObjectAnnotation(d, buf, "__rd_format", eRENDERDOC_String, 0,
+ RDAnnotationHelper("float3 pos;\n"
+ "float4 col;\n"
+ "float2 uv;\n"));
+
+ val = {};
+ val.vector.float32[0] = 1.1f;
+ val.vector.float32[1] = 2.2f;
+ val.vector.float32[2] = 3.3f;
+ val.vector.float32[3] = 4.4f; // should be ignored
+ rdoc->SetObjectAnnotation(d, img, "basic.vec3", eRENDERDOC_Float, 3, &val);
+
+ rdoc->SetObjectAnnotation(d, img, "deep.nested.path.to.annotation", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-4));
+ rdoc->SetObjectAnnotation(d, img, "deep.nested.path.to.annotation2", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-5));
+ rdoc->SetObjectAnnotation(d, img, "deep.alternate.path.to.annotation", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-6));
+
+ // deleted paths should not stay around
+ rdoc->SetObjectAnnotation(d, img, "deleteme", eRENDERDOC_Int32, 0, RDAnnotationHelper(-7));
+ rdoc->SetObjectAnnotation(d, img, "deleteme", eRENDERDOC_Empty, 0, NULL);
+
+ rdoc->SetObjectAnnotation(d, img, "path.deleted.by.parent", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-8));
+ rdoc->SetObjectAnnotation(d, img, "path.deleted.by.parent2", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-9));
+
+ // this will delete all children. `path` will still exist, but will be empty
+ rdoc->SetObjectAnnotation(d, img, "path.deleted", eRENDERDOC_Empty, 0, NULL);
+ }
+
+ while(Running())
+ {
+ if(rdoc)
+ {
+ // queue annotations don't exist in GL, but we set these to share the same test code.
+ if(curFrame == 2)
+ rdoc->SetCommandAnnotation(d, NULL, "queue.too_old", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1000));
+
+ rdoc->SetCommandAnnotation(d, NULL, "queue.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1000));
+
+ rdoc->SetCommandAnnotation(d, NULL, "command.overwritten", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(9999));
+
+ rdoc->SetCommandAnnotation(d, NULL, "command.inherited", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1234));
+
+ rdoc->SetCommandAnnotation(d, NULL, "command.deleted", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(50));
+ }
+
+ setMarker("Start");
+
+ if(rdoc)
+ {
+ rdoc->SetCommandAnnotation(d, NULL, "new.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(2000));
+
+ rdoc->SetCommandAnnotation(d, NULL, "command.overwritten", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-3333));
+
+ rdoc->SetCommandAnnotation(d, NULL, "command.new", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(3333));
+
+ rdoc->SetCommandAnnotation(d, NULL, "command.deleted", eRENDERDOC_Empty, 0, NULL);
+ }
+
+ setMarker("Initial");
+
+ glClearBufferfv(GL_COLOR, 0, DefaultClearCol);
+
+ glBindTexture(GL_TEXTURE_2D, tex);
+
+ if(rdoc)
+ rdoc->SetCommandAnnotation(d, NULL, "command.new", eRENDERDOC_Float, 0,
+ RDAnnotationHelper(1.75f));
+
+ glBindVertexArray(DefaultTriVAO);
+ glUseProgram(DefaultTriProgram);
+ glViewport(0, 0, GLsizei(screenWidth), GLsizei(screenHeight));
+
+ setMarker("Pre-Draw");
+
+ // deleting a value is fine if it's re-added before the next event
+ if(rdoc)
+ {
+ rdoc->SetCommandAnnotation(d, NULL, "new.value", eRENDERDOC_Empty, 0, NULL);
+ rdoc->SetCommandAnnotation(d, NULL, "new.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(4000));
+ }
+
+ setMarker("Draw 1");
+
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+
+ glViewport(0, 0, GLsizei(screenWidth) / 2, GLsizei(screenHeight) / 2);
+
+ setMarker("Draw 2");
+
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+
+ Present();
+ }
+
+ return 0;
+ }
+};
+
+REGISTER_TEST();
diff --git a/util/test/demos/main.cpp b/util/test/demos/main.cpp
index 60389a1a0..c15209464 100644
--- a/util/test/demos/main.cpp
+++ b/util/test/demos/main.cpp
@@ -390,6 +390,8 @@ int main(int argc, char **argv)
{
std::vector &tests = test_list();
+ LoadLibraryA("P:/renderdoc/x64/development/renderdoc.dll");
+
std::sort(tests.begin(), tests.end());
if(argc >= 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h") || !strcmp(argv[1], "-?") ||
diff --git a/util/test/demos/vk/vk_annotations.cpp b/util/test/demos/vk/vk_annotations.cpp
new file mode 100644
index 000000000..dd5e638f2
--- /dev/null
+++ b/util/test/demos/vk/vk_annotations.cpp
@@ -0,0 +1,239 @@
+/******************************************************************************
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2023-2026 Baldur Karlsson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ ******************************************************************************/
+
+#include "vk_test.h"
+
+RD_TEST(VK_Annotations, VulkanGraphicsTest)
+{
+ static constexpr const char *Description = "Test annotations via the vulkan API.";
+
+ int main()
+ {
+ // initialise, create window, create context, etc
+ if(!Init())
+ return 3;
+
+ // these tags provide the layer a way (for dispatchable objects at least) to identify
+ // dispatchable objects that may be wrapped by the loader
+ VkDebugUtilsObjectTagInfoEXT tag = {
+ VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_TAG_INFO_EXT,
+ NULL,
+ VK_OBJECT_TYPE_INSTANCE,
+ (uint64_t)instance,
+ RENDERDOC_APIObjectAnnotationHelper,
+ sizeof(instance),
+ instance,
+ };
+
+ vkSetDebugUtilsObjectTagEXT(device, &tag);
+
+ tag.objectHandle = (uint64_t)phys;
+ tag.objectType = VK_OBJECT_TYPE_PHYSICAL_DEVICE;
+ tag.pTag = phys;
+ vkSetDebugUtilsObjectTagEXT(device, &tag);
+
+ tag.objectHandle = (uint64_t)device;
+ tag.objectType = VK_OBJECT_TYPE_DEVICE;
+ tag.pTag = device;
+ vkSetDebugUtilsObjectTagEXT(device, &tag);
+
+ AllocatedImage img(this,
+ vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R32G32B32A32_SFLOAT,
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT),
+ VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY}));
+
+ setName(img.image, "Annotated Image");
+ setName(DefaultTriVB.buffer, "Vertex Buffer");
+
+ // cache the device pointer we pass in
+ void *d = RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance);
+
+ if(rdoc)
+ {
+ rdoc->SetObjectAnnotation(d, img.image, "basic.bool", eRENDERDOC_Bool, 0,
+ RDAnnotationHelper(true));
+ rdoc->SetObjectAnnotation(d, img.image, "basic.int32", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-3));
+ rdoc->SetObjectAnnotation(d, img.image, "basic.int64", eRENDERDOC_Int64, 0,
+ RDAnnotationHelper(-3000000000000LL));
+ rdoc->SetObjectAnnotation(d, img.image, "basic.uint32", eRENDERDOC_UInt32, 0,
+ RDAnnotationHelper(3));
+ rdoc->SetObjectAnnotation(d, img.image, "basic.uint64", eRENDERDOC_UInt64, 0,
+ RDAnnotationHelper(3000000000000LL));
+ rdoc->SetObjectAnnotation(d, img.image, "basic.float", eRENDERDOC_Float, 0,
+ RDAnnotationHelper(3.25f));
+ rdoc->SetObjectAnnotation(d, img.image, "basic.double", eRENDERDOC_Double, 0,
+ RDAnnotationHelper(3.25000000001));
+ rdoc->SetObjectAnnotation(d, img.image, "basic.string", eRENDERDOC_String, 0,
+ RDAnnotationHelper("Hello, World!"));
+
+ RENDERDOC_AnnotationValue val;
+ val.apiObject = (void *)DefaultTriVB.buffer;
+ rdoc->SetObjectAnnotation(d, img.image, "basic.object", eRENDERDOC_APIObject, 0, &val);
+
+ rdoc->SetObjectAnnotation(d, img.image, "basic.object.__offset", eRENDERDOC_UInt32, 0,
+ RDAnnotationHelper(64));
+ rdoc->SetObjectAnnotation(d, img.image, "basic.object.__size", eRENDERDOC_UInt32, 0,
+ RDAnnotationHelper(32));
+ rdoc->SetObjectAnnotation(d, img.image, "basic.object.__rd_format", eRENDERDOC_String, 0,
+ RDAnnotationHelper("float4 vertex_data;"));
+
+ rdoc->SetObjectAnnotation(d, DefaultTriVB.buffer, "__rd_format", eRENDERDOC_String, 0,
+ RDAnnotationHelper("float3 pos;\n"
+ "float4 col;\n"
+ "float2 uv;\n"));
+
+ val = {};
+ val.vector.float32[0] = 1.1f;
+ val.vector.float32[1] = 2.2f;
+ val.vector.float32[2] = 3.3f;
+ val.vector.float32[3] = 4.4f; // should be ignored
+ rdoc->SetObjectAnnotation(d, img.image, "basic.vec3", eRENDERDOC_Float, 3, &val);
+
+ rdoc->SetObjectAnnotation(d, img.image, "deep.nested.path.to.annotation", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-4));
+ rdoc->SetObjectAnnotation(d, img.image, "deep.nested.path.to.annotation2", eRENDERDOC_Int32,
+ 0, RDAnnotationHelper(-5));
+ rdoc->SetObjectAnnotation(d, img.image, "deep.alternate.path.to.annotation", eRENDERDOC_Int32,
+ 0, RDAnnotationHelper(-6));
+
+ // deleted paths should not stay around
+ rdoc->SetObjectAnnotation(d, img.image, "deleteme", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-7));
+ rdoc->SetObjectAnnotation(d, img.image, "deleteme", eRENDERDOC_Empty, 0, NULL);
+
+ rdoc->SetObjectAnnotation(d, img.image, "path.deleted.by.parent", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-8));
+ rdoc->SetObjectAnnotation(d, img.image, "path.deleted.by.parent2", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-9));
+
+ // this will delete all children. `path` will still exist, but will be empty
+ rdoc->SetObjectAnnotation(d, img.image, "path.deleted", eRENDERDOC_Empty, 0, NULL);
+ }
+
+ while(Running())
+ {
+ if(rdoc)
+ {
+ // queue annotations are only included when in the captured frame
+ if(curFrame == 2)
+ rdoc->SetCommandAnnotation(d, queue, "queue.too_old", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1000));
+
+ rdoc->SetCommandAnnotation(d, queue, "queue.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1000));
+
+ rdoc->SetCommandAnnotation(d, queue, "command.overwritten", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(9999));
+
+ rdoc->SetCommandAnnotation(d, queue, "command.inherited", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(1234));
+
+ rdoc->SetCommandAnnotation(d, queue, "command.deleted", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(50));
+ }
+
+ VkCommandBuffer cmd = GetCommandBuffer();
+
+ vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
+
+ setMarker(cmd, "Start");
+
+ if(rdoc)
+ {
+ rdoc->SetCommandAnnotation(d, cmd, "new.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(2000));
+
+ rdoc->SetCommandAnnotation(d, cmd, "command.overwritten", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(-3333));
+
+ rdoc->SetCommandAnnotation(d, cmd, "command.new", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(3333));
+
+ rdoc->SetCommandAnnotation(d, cmd, "command.deleted", eRENDERDOC_Empty, 0, NULL);
+ }
+
+ setMarker(cmd, "Initial");
+
+ VkImage swapimg = StartUsingBackbuffer(cmd);
+
+ vkh::cmdClearImage(cmd, swapimg, vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f));
+
+ vkh::cmdPipelineBarrier(cmd, {
+ vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_IMAGE_LAYOUT_GENERAL, img.image),
+ });
+
+ vkCmdClearColorImage(cmd, img.image, VK_IMAGE_LAYOUT_GENERAL,
+ vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f), 1,
+ vkh::ImageSubresourceRange());
+
+ vkCmdBeginRenderPass(cmd, mainWindow->beginRP(), VK_SUBPASS_CONTENTS_INLINE);
+
+ if(rdoc)
+ rdoc->SetCommandAnnotation(d, cmd, "command.new", eRENDERDOC_Float, 0,
+ RDAnnotationHelper(1.75f));
+
+ vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, DefaultTriPipe);
+ mainWindow->setViewScissor(cmd);
+ vkh::cmdBindVertexBuffers(cmd, {DefaultTriVB.buffer});
+
+ setMarker(cmd, "Pre-Draw");
+
+ // deleting a value is fine if it's re-added before the next event
+ if(rdoc)
+ {
+ rdoc->SetCommandAnnotation(d, cmd, "new.value", eRENDERDOC_Empty, 0, NULL);
+ rdoc->SetCommandAnnotation(d, cmd, "new.value", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(4000));
+ }
+
+ setMarker(cmd, "Draw 1");
+
+ vkCmdDraw(cmd, 3, 1, 0, 0);
+
+ VkViewport view = mainWindow->viewport;
+ view.width /= 2;
+ view.height /= 2;
+ vkCmdSetViewport(cmd, 0, 1, &view);
+
+ setMarker(cmd, "Draw 2");
+
+ vkCmdDraw(cmd, 3, 1, 0, 0);
+
+ vkCmdEndRenderPass(cmd);
+
+ FinishUsingBackbuffer(cmd);
+
+ vkEndCommandBuffer(cmd);
+
+ SubmitAndPresent({cmd});
+ }
+
+ return 0;
+ }
+};
+
+REGISTER_TEST();
diff --git a/util/test/demos/vk/vk_simple_triangle.cpp b/util/test/demos/vk/vk_simple_triangle.cpp
index ea885404a..7ebfb7781 100644
--- a/util/test/demos/vk/vk_simple_triangle.cpp
+++ b/util/test/demos/vk/vk_simple_triangle.cpp
@@ -76,8 +76,45 @@ RD_TEST(VK_Simple_Triangle, VulkanGraphicsTest)
VK_IMAGE_USAGE_TRANSFER_DST_BIT, 1, 1, VK_SAMPLE_COUNT_4_BIT),
VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY}));
+ VkDebugUtilsObjectTagInfoEXT tag = {
+ VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_TAG_INFO_EXT,
+ NULL,
+ VK_OBJECT_TYPE_INSTANCE,
+ (uint64_t)instance,
+ RENDERDOC_APIObjectAnnotationHelper,
+ sizeof(instance),
+ instance,
+ };
+
+ vkSetDebugUtilsObjectTagEXT(device, &tag);
+ tag.objectHandle = (uint64_t)phys;
+ tag.objectType = VK_OBJECT_TYPE_PHYSICAL_DEVICE;
+ tag.pTag = phys;
+ vkSetDebugUtilsObjectTagEXT(device, &tag);
+
+ RENDERDOC_AnnotationValue val;
+
+ rdoc->SetObjectAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), instance,
+ "someInstData", eRENDERDOC_Bool, 0, RDAnnotationHelper(true));
+ rdoc->SetObjectAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), phys, "physData",
+ eRENDERDOC_String, 0, RDAnnotationHelper("my physical device ha ha"));
+
+ rdoc->SetObjectAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), offimg.image,
+ "cool.secretInfo", eRENDERDOC_Float, 0, RDAnnotationHelper(3.14f));
+ val.apiObject = offimgMS.image;
+ rdoc->SetObjectAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), offimg.image,
+ "cool.otherimage", eRENDERDOC_APIObject, 0, &val);
+
while(Running())
{
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), queue,
+ "global.coolQueue", eRENDERDOC_Bool, 0, RDAnnotationHelper(true));
+
+ int counter = 1;
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), queue,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
+
VkCommandBuffer cmd = GetCommandBuffer();
vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
@@ -85,20 +122,45 @@ RD_TEST(VK_Simple_Triangle, VulkanGraphicsTest)
VkImage swapimg =
StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
+
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.coolCommand", eRENDERDOC_Bool, 0, RDAnnotationHelper(true));
+
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "draw.isClear", eRENDERDOC_Bool, 0, RDAnnotationHelper(true));
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "draw.clearingswap", eRENDERDOC_Float, 0, RDAnnotationHelper(1.1f));
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "draw.notclearing", eRENDERDOC_APIObject, 0, &val);
+
vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL,
vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f), 1,
vkh::ImageSubresourceRange());
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd, "draw",
+ eRENDERDOC_Empty, 0, NULL);
+
vkh::cmdPipelineBarrier(
cmd, {
vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_GENERAL, offimg.image),
});
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
+
vkCmdClearColorImage(cmd, offimg.image, VK_IMAGE_LAYOUT_GENERAL,
vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f), 1,
vkh::ImageSubresourceRange());
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
+
vkh::cmdPipelineBarrier(
cmd, {
vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED,
@@ -109,20 +171,52 @@ RD_TEST(VK_Simple_Triangle, VulkanGraphicsTest)
vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f), 1,
vkh::ImageSubresourceRange());
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
+
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "renderpass.type", eRENDERDOC_String, 0,
+ RDAnnotationHelper("boring"));
+
vkCmdBeginRenderPass(
cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor),
VK_SUBPASS_CONTENTS_INLINE);
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
+
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport);
+
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor);
vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0});
+
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
vkCmdDraw(cmd, 3, 1, 0, 0);
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
+
vkCmdEndRenderPass(cmd);
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
+
FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);
+ rdoc->SetCommandAnnotation(RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(instance), cmd,
+ "global.counter", eRENDERDOC_Int32, 0,
+ RDAnnotationHelper(counter++));
+
vkEndCommandBuffer(cmd);
Submit(0, 1, {cmd});
diff --git a/util/test/rdtest/__init__.py b/util/test/rdtest/__init__.py
index d02604561..ef1d26134 100644
--- a/util/test/rdtest/__init__.py
+++ b/util/test/rdtest/__init__.py
@@ -13,3 +13,4 @@ from .shared.Subgroup_Zoo import *
from .shared.Pixel_History import *
from .shared.Workgroup_Zoo import *
from .shared.Groupshared import *
+from .shared.Annotations import *
diff --git a/util/test/rdtest/shared/Annotations.py b/util/test/rdtest/shared/Annotations.py
new file mode 100644
index 000000000..c1f95d4d6
--- /dev/null
+++ b/util/test/rdtest/shared/Annotations.py
@@ -0,0 +1,252 @@
+import renderdoc as rd
+import rdtest
+
+
+# Not a direct test, re-used by API-specific tests
+class Annotations(rdtest.TestCase):
+ internal = True
+
+ def check_resource_annotations(self):
+ annot = lambda x: annots.FindChildByKeyPath(x)
+
+ with rdtest.log.auto_section('Resource annotations'):
+ res = self.get_resource_by_name('Annotated Image')
+
+ annots = res.annotations
+ self.check(annots is not None)
+
+ self.check_eq(annot("basic.bool").type.basetype, rd.SDBasic.Boolean)
+ self.check_eq(annot("basic.bool").AsBool(), True)
+
+ self.check_eq(annot("basic.int32").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("basic.int32").type.byteSize, 4)
+ self.check_eq(annot("basic.int32").AsInt(), -3)
+
+ self.check_eq(annot("basic.int64").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("basic.int64").type.byteSize, 8)
+ self.check_eq(annot("basic.int64").AsInt(), -3000000000000)
+
+ self.check_eq(annot("basic.uint32").type.basetype, rd.SDBasic.UnsignedInteger)
+ self.check_eq(annot("basic.uint32").type.byteSize, 4)
+ self.check_eq(annot("basic.uint32").AsInt(), 3)
+
+ self.check_eq(annot("basic.uint64").type.basetype, rd.SDBasic.UnsignedInteger)
+ self.check_eq(annot("basic.uint64").type.byteSize, 8)
+ self.check_eq(annot("basic.uint64").AsInt(), 3000000000000)
+
+ self.check_eq(annot("basic.float").type.basetype, rd.SDBasic.Float)
+ self.check_eq(annot("basic.float").type.byteSize, 4)
+ self.check_eq(annot("basic.float").AsFloat(), 3.25)
+
+ self.check_eq(annot("basic.double").type.basetype, rd.SDBasic.Float)
+ self.check_eq(annot("basic.double").type.byteSize, 8)
+ self.check(rdtest.value_compare(annot("basic.double").AsFloat(), 3.25000000001))
+
+ self.check_eq(annot("basic.string").type.basetype, rd.SDBasic.String)
+ self.check_eq(annot("basic.string").AsString(), "Hello, World!")
+
+ self.check_eq(annot("basic.object").type.basetype, rd.SDBasic.Resource)
+ obj = self.get_resource(annot("basic.object").AsResourceId())
+ self.check_eq(obj.name, "Vertex Buffer")
+
+ self.check_eq(annot("basic.object.__offset").AsInt(), 64)
+ self.check_eq(annot("basic.object.__size").AsInt(), 32)
+
+ self.check(rdtest.value_compare(annot("basic.vec3.x").AsFloat(), 1.1))
+ self.check(rdtest.value_compare(annot("basic.vec3.y").AsFloat(), 2.2))
+ self.check(rdtest.value_compare(annot("basic.vec3.z").AsFloat(), 3.3))
+
+ self.check_eq(annot("deep.nested.path.to.annotation").AsInt(), -4)
+ self.check_eq(annot("deep.nested.path.to.annotation2").AsInt(), -5)
+ self.check_eq(annot("deep.alternate.path.to.annotation").AsInt(), -6)
+
+ self.check(annot("deleteme") is None)
+
+ self.check(annot("path.deleted.by.parent") is None)
+ self.check(annot("path.deleted.by.parent2") is None)
+ self.check(annot("path.deleted") is None)
+
+ def check_command_annotations(self, cmd_buffers: bool):
+ annot = lambda x: annots.FindChildByKeyPath(x)
+
+ with rdtest.log.auto_section('Event annotations'):
+ action = self.find_action("Start")
+ rdtest.log.print(f"Checking {action.customName}")
+
+ annots = action.events[-1].annotations
+ self.check(annots is not None)
+
+ # Should not have this annotation, it happened prior to the capture
+ self.check(annot("queue.too_old") is None)
+
+ # normal value set on the queue
+ self.check_eq(annot("queue.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("queue.value").AsInt(), 1000)
+
+ # this will later be overwritten by the commands, but for now has the queue value
+ self.check_eq(annot("command.overwritten").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.overwritten").AsInt(), 9999)
+
+ # this is inherited by the commands even as other siblings are modified
+ self.check_eq(annot("command.inherited").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.inherited").AsInt(), 1234)
+
+ # this will be deleted
+ self.check_eq(annot("command.deleted").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.deleted").AsInt(), 50)
+
+ action = self.find_action("Initial")
+ rdtest.log.print(f"Checking {action.customName}")
+
+ annots = action.events[-1].annotations
+ self.check(annots is not None)
+
+ # normal value set on the queue
+ self.check_eq(annot("queue.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("queue.value").AsInt(), 1000)
+
+ # this has the overwritten value now
+ self.check_eq(annot("command.overwritten").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.overwritten").AsInt(), -3333)
+
+ # this is inherited by the commands even as other siblings are modified
+ self.check_eq(annot("command.inherited").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.inherited").AsInt(), 1234)
+
+ # this has now been deleted
+ self.check(annot("command.deleted") is None)
+
+ # this is a new command-local value
+ self.check_eq(annot("command.new").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.new").AsInt(), 3333)
+
+ # this is a new command-local value
+ self.check_eq(annot("new.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("new.value").AsInt(), 2000)
+
+ action = self.find_action("Pre-Draw")
+ rdtest.log.print(f"Checking {action.customName}")
+
+ annots = action.events[-1].annotations
+ self.check(annots is not None)
+
+ # normal value set on the queue
+ self.check_eq(annot("queue.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("queue.value").AsInt(), 1000)
+
+ # this has the overwritten value now
+ self.check_eq(annot("command.overwritten").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.overwritten").AsInt(), -3333)
+
+ # this is inherited by the commands even as other siblings are modified
+ self.check_eq(annot("command.inherited").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.inherited").AsInt(), 1234)
+
+ # this has now been deleted
+ self.check(annot("command.deleted") is None)
+
+ # this is a command-local value, which has changed type
+ self.check_eq(annot("command.new").type.basetype, rd.SDBasic.Float)
+ self.check_eq(annot("command.new").AsFloat(), 1.75)
+
+ # this value has't changed
+ self.check_eq(annot("new.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("new.value").AsInt(), 2000)
+
+ predraw_id = action.eventId
+
+ action = self.find_action("Draw 1")
+ rdtest.log.print(f"Checking {action.customName}")
+
+ annots = action.events[-1].annotations
+ self.check(annots is not None)
+
+ # should be no events between these
+ self.check_eq(predraw_id + 1, action.eventId)
+
+ # this value was deleted and re-added, so it should be present as the new value
+ self.check_eq(annot("new.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("new.value").AsInt(), 4000)
+
+ # normal value set on the queue
+ self.check_eq(annot("queue.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("queue.value").AsInt(), 1000)
+
+ # this has the overwritten value now
+ self.check_eq(annot("command.overwritten").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.overwritten").AsInt(), -3333)
+
+ # this is inherited by the commands even as other siblings are modified
+ self.check_eq(annot("command.inherited").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.inherited").AsInt(), 1234)
+
+ # this has now been deleted
+ self.check(annot("command.deleted") is None)
+
+ # this is a command-local value, which has changed type
+ self.check_eq(annot("command.new").type.basetype, rd.SDBasic.Float)
+ self.check_eq(annot("command.new").AsFloat(), 1.75)
+
+ self.check_eq(annot("new.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("new.value").AsInt(), 4000)
+
+ action = self.find_action("Draw 2")
+ rdtest.log.print(f"Checking {action.customName}")
+
+ annots = action.events[-1].annotations
+ self.check(annots is not None)
+
+ # all of the values should still be present
+
+ self.check_eq(annot("queue.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("queue.value").AsInt(), 1000)
+
+ self.check_eq(annot("command.overwritten").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.overwritten").AsInt(), -3333)
+
+ self.check_eq(annot("command.inherited").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.inherited").AsInt(), 1234)
+
+ self.check(annot("command.deleted") is None)
+
+ self.check_eq(annot("command.new").type.basetype, rd.SDBasic.Float)
+ self.check_eq(annot("command.new").AsFloat(), 1.75)
+
+ self.check_eq(annot("new.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("new.value").AsInt(), 4000)
+
+ action = self.get_last_action()
+ rdtest.log.print(f"Checking {action.customName}")
+
+ annots = action.events[-1].annotations
+ self.check(annots is not None)
+
+ # normal value set on the queue
+ self.check_eq(annot("queue.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("queue.value").AsInt(), 1000)
+
+ self.check_eq(annot("command.inherited").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.inherited").AsInt(), 1234)
+
+ # if we have real command buffers, the annotations revert back to where they were on queues
+ if cmd_buffers:
+ self.check_eq(annot("command.overwritten").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.overwritten").AsInt(), 9999)
+
+ self.check_eq(annot("command.deleted").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.deleted").AsInt(), 50)
+
+ self.check(annot("command.new") is None)
+ self.check(annot("new.value") is None)
+ # otherwise they will be the same as on the command buffer
+ else:
+ self.check_eq(annot("command.overwritten").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("command.overwritten").AsInt(), -3333)
+
+ self.check(annot("command.deleted") is None)
+
+ self.check_eq(annot("command.new").type.basetype, rd.SDBasic.Float)
+ self.check_eq(annot("command.new").AsFloat(), 1.75)
+
+ self.check_eq(annot("new.value").type.basetype, rd.SDBasic.SignedInteger)
+ self.check_eq(annot("new.value").AsInt(), 4000)
diff --git a/util/test/tests/D3D11/D3D11_Annotations.py b/util/test/tests/D3D11/D3D11_Annotations.py
new file mode 100644
index 000000000..cfde35b98
--- /dev/null
+++ b/util/test/tests/D3D11/D3D11_Annotations.py
@@ -0,0 +1,11 @@
+import renderdoc as rd
+import rdtest
+
+
+class D3D11_Annotations(rdtest.Annotations):
+ demos_test_name = 'D3D11_Annotations'
+ internal = False
+
+ def check_capture(self):
+ super().check_resource_annotations()
+ super().check_command_annotations(False)
\ No newline at end of file
diff --git a/util/test/tests/D3D12/D3D12_Annotations.py b/util/test/tests/D3D12/D3D12_Annotations.py
new file mode 100644
index 000000000..5ec7003ba
--- /dev/null
+++ b/util/test/tests/D3D12/D3D12_Annotations.py
@@ -0,0 +1,11 @@
+import renderdoc as rd
+import rdtest
+
+
+class D3D12_Annotations(rdtest.Annotations):
+ demos_test_name = 'D3D12_Annotations'
+ internal = False
+
+ def check_capture(self):
+ super().check_resource_annotations()
+ super().check_command_annotations(True)
\ No newline at end of file
diff --git a/util/test/tests/GL/GL_Annotations.py b/util/test/tests/GL/GL_Annotations.py
new file mode 100644
index 000000000..267fb01c9
--- /dev/null
+++ b/util/test/tests/GL/GL_Annotations.py
@@ -0,0 +1,11 @@
+import renderdoc as rd
+import rdtest
+
+
+class GL_Annotations(rdtest.Annotations):
+ demos_test_name = 'GL_Annotations'
+ internal = False
+
+ def check_capture(self):
+ super().check_resource_annotations()
+ super().check_command_annotations(False)
\ No newline at end of file
diff --git a/util/test/tests/Vulkan/VK_Annotations.py b/util/test/tests/Vulkan/VK_Annotations.py
new file mode 100644
index 000000000..fe5d816a2
--- /dev/null
+++ b/util/test/tests/Vulkan/VK_Annotations.py
@@ -0,0 +1,11 @@
+import renderdoc as rd
+import rdtest
+
+
+class VK_Annotations(rdtest.Annotations):
+ demos_test_name = 'VK_Annotations'
+ internal = False
+
+ def check_capture(self):
+ super().check_resource_annotations()
+ super().check_command_annotations(True)
\ No newline at end of file