diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt
index 861699db9..557d1ea95 100644
--- a/util/test/demos/CMakeLists.txt
+++ b/util/test/demos/CMakeLists.txt
@@ -166,6 +166,7 @@ set(VULKAN_SRC
vk/vk_simple_triangle.cpp
vk/vk_spec_constants.cpp
vk/vk_spirv_13_shaders.cpp
+ vk/vk_stripped_shaders.cpp
vk/vk_subgroup_zoo.cpp
vk/vk_structured_buffer_nested.cpp
vk/vk_sync2.cpp
diff --git a/util/test/demos/d3d11/d3d11_amd_shader_extensions.cpp b/util/test/demos/d3d11/d3d11_amd_shader_extensions.cpp
index c1ac95513..02ee6d920 100644
--- a/util/test/demos/d3d11/d3d11_amd_shader_extensions.cpp
+++ b/util/test/demos/d3d11/d3d11_amd_shader_extensions.cpp
@@ -272,8 +272,9 @@ void main(uint3 threadID : SV_DispatchThreadID)
ID3DBlobPtr vsblob = Compile(D3DDefaultVertex, "main", "vs_4_0");
// can't skip optimising and still have the extensions work, sadly
- ID3DBlobPtr psblob = Compile(ags_header + BaryCentricPixel, "main", "ps_5_0", false);
- ID3DBlobPtr csblob = Compile(ags_header + MaxCompute, "main", "cs_5_0", false);
+ ID3DBlobPtr psblob =
+ Compile(ags_header + BaryCentricPixel, "main", "ps_5_0", CompileOptionFlags::None);
+ ID3DBlobPtr csblob = Compile(ags_header + MaxCompute, "main", "cs_5_0", CompileOptionFlags::None);
CreateDefaultInputLayout(vsblob);
diff --git a/util/test/demos/d3d11/d3d11_groupshared.cpp b/util/test/demos/d3d11/d3d11_groupshared.cpp
index e1692624f..82c9f8005 100644
--- a/util/test/demos/d3d11/d3d11_groupshared.cpp
+++ b/util/test/demos/d3d11/d3d11_groupshared.cpp
@@ -163,7 +163,7 @@ void main(uint3 gid : SV_GroupThreadID)
ID3D11UnorderedAccessViewPtr inUAV = MakeUAV(inBuf);
ID3D11UnorderedAccessViewPtr outUAV = MakeUAV(outBuf);
- ID3D11ComputeShaderPtr shad = CreateCS(Compile(comp, "main", "cs_5_0", true));
+ ID3D11ComputeShaderPtr shad = CreateCS(Compile(comp, "main", "cs_5_0"));
int cbufferdata[4];
memset(cbufferdata, 0, sizeof(cbufferdata));
diff --git a/util/test/demos/d3d11/d3d11_shader_debug_zoo.cpp b/util/test/demos/d3d11/d3d11_shader_debug_zoo.cpp
index 2475f2e07..44e33a14d 100644
--- a/util/test/demos/d3d11/d3d11_shader_debug_zoo.cpp
+++ b/util/test/demos/d3d11/d3d11_shader_debug_zoo.cpp
@@ -1072,8 +1072,9 @@ float4 main(v2f IN, uint samp : SV_SampleIndex) : SV_Target0
vsblob->GetBufferSize(), &layout));
ID3D11VertexShaderPtr vs = CreateVS(vsblob);
- ID3D11PixelShaderPtr ps = CreatePS(Compile(common + pixel, "main", "ps_5_0", true));
- ID3D11PixelShaderPtr psopt = CreatePS(Compile(common + pixel, "main", "ps_5_0", false));
+ ID3D11PixelShaderPtr ps = CreatePS(Compile(common + pixel, "main", "ps_5_0"));
+ ID3D11PixelShaderPtr psopt =
+ CreatePS(Compile(common + pixel, "main", "ps_5_0", CompileOptionFlags::None));
ID3D11PixelShaderPtr flowps = CreatePS(Compile(common + flowPixel, "main", "ps_5_0"));
static const uint32_t texDim = AlignUp(numTests, 64U) * 4;
diff --git a/util/test/demos/d3d11/d3d11_stripped_shaders.cpp b/util/test/demos/d3d11/d3d11_stripped_shaders.cpp
index bd916b761..7208af556 100644
--- a/util/test/demos/d3d11/d3d11_stripped_shaders.cpp
+++ b/util/test/demos/d3d11/d3d11_stripped_shaders.cpp
@@ -35,23 +35,45 @@ RD_TEST(D3D11_Stripped_Shaders, D3D11GraphicsTest)
if(!Init())
return 3;
- ID3DBlobPtr vsblob = Compile(D3DDefaultVertex, "main", "vs_5_0");
- ID3DBlobPtr psblob = Compile(D3DDefaultPixel, "main", "ps_5_0");
+ MakeDir((GetCWD() + "/tmp/D3D11_Stripped_Shaders").c_str());
- WriteBlob(GetCWD() + "tmp/D3D11_Stripped_Shaders/shader_debug.vs", vsblob, false);
- WriteBlob(GetCWD() + "tmp/D3D11_Stripped_Shaders/shader_debug.ps", psblob, true);
+ // several variables:
+ //
+ // legacy whole unstripped blob, or dxc-style just pdb
+ // (for dxc-style) manual named or auto-named
+ // (for legacy-style) lz4 compressed or not
+ // (for legacy-style) PRIV pointed or SetPrivateData
+ // absolute or relative path
+ //
+ // we don't test all possible combinations but check at least one of each
- Strip(vsblob);
- Strip(psblob);
-
- SetBlobPath(GetCWD() + "tmp/D3D11_Stripped_Shaders/shader_debug.vs", vsblob);
+ // PRIV will be set with absolute path
+ ID3DBlobPtr vsblob =
+ Compile(D3DDefaultVertex + "\n// shader source // vertex", "main", "vs_5_0",
+ CompileOptionFlags::SeparateDebugLegacy | CompileOptionFlags::SeparateDebugSetPRIV);
+ // we'll set relative SetPrivateData path below
+ ID3DBlobPtr psblob =
+ Compile(D3DDefaultPixel + "\n// shader source // pixel", "main", "ps_5_0",
+ CompileOptionFlags::SeparateDebugLegacy | CompileOptionFlags::ManualDebugName |
+ CompileOptionFlags::LZ4Compress);
CreateDefaultInputLayout(vsblob);
ID3D11VertexShaderPtr vs = CreateVS(vsblob);
ID3D11PixelShaderPtr ps = CreatePS(psblob);
- SetBlobPath("lz4#tmp/D3D11_Stripped_Shaders/shader_debug.ps", ps);
+ SetBlobPath("lz4#shader_ps.dbg", ps);
+
+ // manual name will be set absolute in a manual ILDN
+ ID3DBlobPtr vsblob2 =
+ Compile(D3DDefaultVertex + "\n// shader source 2 // vertex", "main", "vs_5_0",
+ CompileOptionFlags::SeparateDebug | CompileOptionFlags::ManualDebugName);
+ // the fxc-generated ILDN will be relative
+ ID3DBlobPtr psblob2 = Compile(D3DDefaultPixel + "\n// shader source 2 // pixel", "main",
+ "ps_5_0", CompileOptionFlags::SeparateDebug);
+
+ ID3D11VertexShaderPtr vs2 = CreateVS(vsblob2);
+ ID3D11PixelShaderPtr ps2 = CreatePS(psblob2);
ID3D11BufferPtr vb = MakeBuffer().Vertex().Data(DefaultTri);
@@ -70,6 +92,17 @@ RD_TEST(D3D11_Stripped_Shaders, D3D11GraphicsTest)
ctx->OMSetRenderTargets(1, &bbRTV.GetInterfacePtr(), NULL);
+ setMarker("test: shader source");
+ setMarker("vertex auto absolute");
+ setMarker("pixel manual relative");
+ ctx->Draw(3, 0);
+
+ ctx->VSSetShader(vs2, NULL, 0);
+ ctx->PSSetShader(ps2, NULL, 0);
+
+ setMarker("test: shader source 2");
+ setMarker("vertex manual absolute");
+ setMarker("pixel auto relative");
ctx->Draw(3, 0);
Present();
diff --git a/util/test/demos/d3d11/d3d11_test.cpp b/util/test/demos/d3d11/d3d11_test.cpp
index c6e4d194c..ea0edb812 100644
--- a/util/test/demos/d3d11/d3d11_test.cpp
+++ b/util/test/demos/d3d11/d3d11_test.cpp
@@ -51,6 +51,7 @@ struct Capabilities
pD3DCompile dyn_D3DCompile = NULL;
pD3DStripShader dyn_D3DStripShader = NULL;
pD3DSetBlobPart dyn_D3DSetBlobPart = NULL;
+pD3DGetBlobPart dyn_D3DGetBlobPart = NULL;
PFN_D3D11_CREATE_DEVICE dyn_D3D11CreateDevice = NULL;
PFN_D3D11_CREATE_DEVICE_AND_SWAP_CHAIN dyn_D3D11CreateDeviceAndSwapChain = NULL;
@@ -94,6 +95,7 @@ void D3D11GraphicsTest::Prepare(int argc, char **argv)
dyn_D3DCompile = (pD3DCompile)GetProcAddress(d3dcompiler, "D3DCompile");
dyn_D3DStripShader = (pD3DStripShader)GetProcAddress(d3dcompiler, "D3DStripShader");
dyn_D3DSetBlobPart = (pD3DSetBlobPart)GetProcAddress(d3dcompiler, "D3DSetBlobPart");
+ dyn_D3DGetBlobPart = (pD3DGetBlobPart)GetProcAddress(d3dcompiler, "D3DGetBlobPart");
}
HRESULT hr = S_OK;
@@ -136,7 +138,7 @@ void D3D11GraphicsTest::Prepare(int argc, char **argv)
else if(!factory)
Avail = "Couldn't create DXGI factory";
else if(!dyn_D3D11CreateDevice || !dyn_D3D11CreateDeviceAndSwapChain || !dyn_D3DCompile ||
- !dyn_D3DStripShader || !dyn_D3DSetBlobPart)
+ !dyn_D3DStripShader || !dyn_D3DSetBlobPart || !dyn_D3DGetBlobPart)
Avail = "Missing required entry point";
}
@@ -643,13 +645,24 @@ void D3D11GraphicsTest::SetStencilRef(UINT ref)
}
ID3DBlobPtr D3D11GraphicsTest::Compile(std::string src, std::string entry, std::string profile,
- bool skipoptimise)
+ uint32_t compileOptions)
{
+ const bool skipoptimise = ((compileOptions & CompileOptionFlags::SkipOptimise) != 0);
+ const bool separateDebugLegacy = ((compileOptions & CompileOptionFlags::SeparateDebugLegacy) != 0);
+ const bool separateDebugSetPRIV =
+ ((compileOptions & CompileOptionFlags::SeparateDebugSetPRIV) != 0);
+ const bool separateDebug = ((compileOptions & CompileOptionFlags::SeparateDebug) != 0);
+ const bool manualDebugName = ((compileOptions & CompileOptionFlags::ManualDebugName) != 0);
+ const bool lz4Compress = ((compileOptions & CompileOptionFlags::LZ4Compress) != 0);
+
ID3DBlobPtr blob = NULL;
ID3DBlobPtr error = NULL;
UINT flags = D3DCOMPILE_WARNINGS_ARE_ERRORS | D3DCOMPILE_DEBUG;
+ if((separateDebugLegacy || separateDebug) && !manualDebugName)
+ flags |= D3DCOMPILE_DEBUG_NAME_FOR_SOURCE;
+
if(skipoptimise)
flags |= D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_OPTIMIZATION_LEVEL0;
else
@@ -668,6 +681,76 @@ ID3DBlobPtr D3D11GraphicsTest::Compile(std::string src, std::string entry, std::
return NULL;
}
+ if(separateDebugLegacy || separateDebug)
+ {
+ std::string debugName;
+ if(manualDebugName)
+ {
+ debugName = "shader_" + profile.substr(0, 2) + ".dbg";
+ }
+ else
+ {
+ ID3DBlobPtr nameBlob;
+ LPVOID bufferPointer = blob->GetBufferPointer();
+ hr = dyn_D3DGetBlobPart(bufferPointer, blob->GetBufferSize(), D3D_BLOB_DEBUG_NAME, 0,
+ &nameBlob);
+
+ if(SUCCEEDED(hr))
+ {
+ const uint16_t *flags_len = (const uint16_t *)nameBlob->GetBufferPointer();
+
+ const char *name_str = (const char *)(flags_len + 2);
+
+ debugName.assign(name_str, flags_len[1]);
+ }
+ else
+ {
+ TEST_ERROR("Couldn't get automatic name");
+ debugName = "shader_" + profile.substr(0, 2) + ".dbg";
+ }
+ }
+
+ std::string path = tmpPath + "/" + debugName;
+
+ if(separateDebug)
+ {
+ ID3DBlobPtr debugBlob;
+ dyn_D3DGetBlobPart(blob->GetBufferPointer(), blob->GetBufferSize(), D3D_BLOB_PDB, 0,
+ &debugBlob);
+
+ WriteBlob(path, debugBlob, lz4Compress);
+
+ if(manualDebugName)
+ {
+ std::string nameBlob = path;
+ // pad the blob to multiple of 4
+ nameBlob.resize((path.size() + 0x3) & ~0x3);
+
+ nameBlob.insert(0, 4, '\0');
+ nameBlob[2] += path.size() & 0xff;
+
+ if(path.size() > 255)
+ TEST_ERROR("tmp path is too long!");
+
+ ID3DBlobPtr newBlob;
+
+ dyn_D3DSetBlobPart(blob->GetBufferPointer(), blob->GetBufferSize(), D3D_BLOB_DEBUG_NAME, 0,
+ nameBlob.c_str(), nameBlob.size(), &newBlob);
+
+ blob = newBlob;
+ }
+ }
+ else if(separateDebugLegacy)
+ {
+ WriteBlob(path, blob, lz4Compress);
+
+ if(separateDebugSetPRIV)
+ SetBlobPath(path, blob);
+ }
+
+ Strip(blob);
+ }
+
return blob;
}
diff --git a/util/test/demos/d3d11/d3d11_test.h b/util/test/demos/d3d11/d3d11_test.h
index 76397bf13..3baa2ed29 100644
--- a/util/test/demos/d3d11/d3d11_test.h
+++ b/util/test/demos/d3d11/d3d11_test.h
@@ -29,16 +29,6 @@
#include "dx/official/dxgi.h"
#include "d3d11_helpers.h"
-#include "dx/official/d3dcompiler.h"
-
-typedef HRESULT(WINAPI *pD3DStripShader)(_In_reads_bytes_(BytecodeLength) LPCVOID pShaderBytecode,
- _In_ SIZE_T BytecodeLength, _In_ UINT uStripFlags,
- _Out_ ID3DBlob **ppStrippedBlob);
-typedef HRESULT(WINAPI *pD3DSetBlobPart)(_In_reads_bytes_(SrcDataSize) LPCVOID pSrcData,
- _In_ SIZE_T SrcDataSize, _In_ D3D_BLOB_PART Part,
- _In_ UINT Flags, _In_reads_bytes_(PartSize) LPCVOID pPart,
- _In_ SIZE_T PartSize, _Out_ ID3DBlob **ppNewShader);
-
struct Win32Window;
struct D3D11GraphicsTest : public GraphicsTest
@@ -74,8 +64,25 @@ struct D3D11GraphicsTest : public GraphicsTest
BufUAVType = 0xf00,
};
+ enum CompileOptionFlags
+ {
+ None = 0,
+ SkipOptimise = 1 << 0,
+ // the legacy path, where we write the unstripped blob somewhere and set a path either via PRIV
+ // name or SetPrivateData
+ SeparateDebugLegacy = 1 << 1,
+ SeparateDebugSetPRIV = 1 << 2,
+ // the 'new' path which is still manual but matches dxc's behaviour, where we pull out the PDB and set an ILDN name
+ SeparateDebug = 1 << 3,
+ // don't use the auto-generated name for a debug file, name it custom shader_Xs.dbg (so only one
+ // per stage per demo allowed)
+ ManualDebugName = 1 << 4,
+ // only possible for SeparateDebugLegacy and ManualDebugName, lz4 compress the resulting blob
+ LZ4Compress = 1 << 5,
+ };
+
ID3DBlobPtr Compile(std::string src, std::string entry, std::string profile,
- bool skipoptimise = true);
+ uint32_t compileOptions = CompileOptionFlags::SkipOptimise);
void Strip(ID3DBlobPtr &ptr);
void WriteBlob(std::string name, ID3DBlobPtr blob, bool compress);
diff --git a/util/test/demos/d3d11/d3d11_workgroup_zoo.cpp b/util/test/demos/d3d11/d3d11_workgroup_zoo.cpp
index 37152d622..4ab4f8402 100644
--- a/util/test/demos/d3d11/d3d11_workgroup_zoo.cpp
+++ b/util/test/demos/d3d11/d3d11_workgroup_zoo.cpp
@@ -198,8 +198,8 @@ void main(uint3 inGTid : SV_GroupThreadID)
compsizes[i].x, compsizes[i].y, compsizes[i].z);
comppipe_name[i] = fmt::format("{}x{}x{}", compsizes[i].x, compsizes[i].y, compsizes[i].z);
- testShaders[i] = CreateCS(Compile(defines + sizedefine + testShader, "main", "cs_5_0", true));
- perfShaders[i] = CreateCS(Compile(defines + sizedefine + perfShader, "main", "cs_5_0", true));
+ testShaders[i] = CreateCS(Compile(defines + sizedefine + testShader, "main", "cs_5_0"));
+ perfShaders[i] = CreateCS(Compile(defines + sizedefine + perfShader, "main", "cs_5_0"));
}
while(Running())
diff --git a/util/test/demos/d3d12/d3d12_stripped_shaders.cpp b/util/test/demos/d3d12/d3d12_stripped_shaders.cpp
new file mode 100644
index 000000000..c86d15630
--- /dev/null
+++ b/util/test/demos/d3d12/d3d12_stripped_shaders.cpp
@@ -0,0 +1,102 @@
+/******************************************************************************
+ * 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_Stripped_Shaders, D3D12GraphicsTest)
+{
+ static constexpr const char *Description =
+ "Tests shaders with their debug/reflection info stripped out and stored in separate blobs.";
+
+ void Prepare(int argc, char **argv)
+ {
+ D3D12GraphicsTest::Prepare(argc, argv);
+
+ if(!Avail.empty())
+ return;
+
+ if(!m_DXILSupport)
+ Avail = "DXIL can't be compiled";
+ }
+
+ int main()
+ {
+ // initialise, create window, create device, etc
+ if(!Init())
+ return 3;
+
+ MakeDir((GetCWD() + "/tmp/D3D12_Stripped_Shaders").c_str());
+
+ // only a couple of variables:
+ //
+ // auto-named or manual named
+ // absolute or relative path
+
+ // manual debug name will be absolute
+ ID3DBlobPtr vsblob =
+ Compile(D3DDefaultVertex + "\n// shader source // vertex", "main", "vs_6_0",
+ CompileOptionFlags::SeparateDebug | CompileOptionFlags::ManualDebugName);
+ // auto name will be relative
+ ID3DBlobPtr psblob = Compile(D3DDefaultPixel + "\n// shader source // pixel", "main", "ps_6_0",
+ CompileOptionFlags::SeparateDebug);
+
+ ID3D12PipelineStatePtr pso = MakePSO().RootSig(DefaultTriSig).InputLayout().VS(vsblob).PS(psblob);
+
+ while(Running())
+ {
+ ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer();
+
+ Reset(cmd);
+
+ ID3D12ResourcePtr bb = StartUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET);
+
+ ClearRenderTargetView(cmd, BBRTV, {0.2f, 0.2f, 0.2f, 1.0f});
+
+ cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ IASetVertexBuffer(cmd, DefaultTriVB, sizeof(DefaultA2V), 0);
+ cmd->SetPipelineState(pso);
+ cmd->SetGraphicsRootSignature(DefaultTriSig);
+
+ SetMainWindowViewScissor(cmd);
+
+ OMSetRenderTargets(cmd, {BBRTV}, {});
+
+ setMarker(cmd, "test: shader source");
+ setMarker(cmd, "vertex manual absolute");
+ setMarker(cmd, "pixel auto relative");
+ 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/d3d12/d3d12_test.cpp b/util/test/demos/d3d12/d3d12_test.cpp
index 5d53f9755..bf8d04527 100644
--- a/util/test/demos/d3d12/d3d12_test.cpp
+++ b/util/test/demos/d3d12/d3d12_test.cpp
@@ -52,6 +52,7 @@ bool d3d12on7 = false;
pD3DCompile dyn_D3DCompile = NULL;
pD3DStripShader dyn_D3DStripShader = NULL;
pD3DSetBlobPart dyn_D3DSetBlobPart = NULL;
+pD3DGetBlobPart dyn_D3DGetBlobPart = NULL;
pD3DCreateBlob dyn_CreateBlob = NULL;
PFN_D3D12_CREATE_DEVICE dyn_D3D12CreateDevice = NULL;
@@ -382,6 +383,7 @@ void D3D12GraphicsTest::Prepare(int argc, char **argv)
dyn_D3DCompile = (pD3DCompile)GetProcAddress(d3dcompiler, "D3DCompile");
dyn_D3DStripShader = (pD3DStripShader)GetProcAddress(d3dcompiler, "D3DStripShader");
dyn_D3DSetBlobPart = (pD3DSetBlobPart)GetProcAddress(d3dcompiler, "D3DSetBlobPart");
+ dyn_D3DGetBlobPart = (pD3DGetBlobPart)GetProcAddress(d3dcompiler, "D3DGetBlobPart");
dyn_CreateBlob = (pD3DCreateBlob)GetProcAddress(d3dcompiler, "D3DCreateBlob");
}
@@ -441,7 +443,7 @@ void D3D12GraphicsTest::Prepare(int argc, char **argv)
else if(!factory)
Avail = "Couldn't create DXGI factory";
else if(!dyn_D3D12CreateDevice || !dyn_D3DCompile || !dyn_D3DStripShader || !dyn_D3DSetBlobPart ||
- !dyn_CreateBlob)
+ !dyn_D3DGetBlobPart || !dyn_CreateBlob)
Avail = "Missing required entry point";
else if(!dyn_serializeRootSig && !dyn_serializeRootSigOld)
Avail = "Missing required root signature serialize entry point";
@@ -1441,9 +1443,10 @@ ID3DBlobPtr D3D12GraphicsTest::Compile(std::string src, std::string entry, std::
uint32_t compileOptions)
{
ID3DBlobPtr blob = NULL;
- bool skipoptimise = ((compileOptions & CompileOptionFlags::SkipOptimise) != 0);
- bool enable16BitTypes = ((compileOptions & CompileOptionFlags::Enable16BitTypes) != 0);
- bool separateDebug = ((compileOptions & CompileOptionFlags::SeparateDebug) != 0);
+ const bool skipoptimise = ((compileOptions & CompileOptionFlags::SkipOptimise) != 0);
+ const bool enable16BitTypes = ((compileOptions & CompileOptionFlags::Enable16BitTypes) != 0);
+ const bool separateDebug = ((compileOptions & CompileOptionFlags::SeparateDebug) != 0);
+ const bool manualDebugName = ((compileOptions & CompileOptionFlags::ManualDebugName) != 0);
if(profile[3] >= '6')
{
@@ -1516,6 +1519,10 @@ ID3DBlobPtr D3D12GraphicsTest::Compile(std::string src, std::string entry, std::
argStorage.push_back(L"-select-validator internal");
}
+ // use source for debug name so we can have just a comment and get different debug info if we want
+ if(!manualDebugName)
+ argStorage.push_back(L"-Zss");
+
// Must be the final option
argStorage.push_back(L"-Qembed_debug");
@@ -1529,6 +1536,8 @@ ID3DBlobPtr D3D12GraphicsTest::Compile(std::string src, std::string entry, std::
IDxcOperationResultPtr result;
HRESULT hrStatus = E_NOINTERFACE;
+ std::string debugBlobPath;
+
if(separateDebug)
{
IDxcCompiler2Ptr compiler2 = compiler;
@@ -1554,20 +1563,16 @@ ID3DBlobPtr D3D12GraphicsTest::Compile(std::string src, std::string entry, std::
CoTaskMemFree(debugBlobWideName);
}
+ if(manualDebugName)
+ {
+ debugBlobName = "shader_" + profile.substr(0, 2) + ".dbg";
+ }
+
if(debugBlob)
{
- std::string path = GetExecutableName();
- path.erase(path.find_last_of("/\\"));
- path += "/tmp/";
- MakeDir(path.c_str());
- path += demoName;
- path += "/";
- MakeDir(path.c_str());
- path += "dxcDebugBlobs/";
- MakeDir(path.c_str());
+ debugBlobPath = tmpPath + "/" + debugBlobName;
- WriteBlob(path + debugBlobName, debugBlob->GetBufferPointer(), debugBlob->GetBufferSize(),
- false);
+ WriteBlob(debugBlobPath, debugBlob->GetBufferPointer(), debugBlob->GetBufferSize(), false);
}
if(result)
@@ -1629,6 +1634,26 @@ ID3DBlobPtr D3D12GraphicsTest::Compile(std::string src, std::string entry, std::
TEST_ERROR("No compilation result found from DXC compile: %x", hr);
}
}
+
+ if(manualDebugName)
+ {
+ std::string nameBlob = debugBlobPath;
+ // pad the blob to multiple of 4
+ nameBlob.resize((debugBlobPath.size() + 0x3) & ~0x3);
+
+ nameBlob.insert(0, 4, '\0');
+ nameBlob[2] += debugBlobPath.size() & 0xff;
+
+ if(debugBlobPath.size() > 255)
+ TEST_ERROR("tmp path is too long!");
+
+ ID3DBlobPtr newBlob;
+
+ hr = dyn_D3DSetBlobPart(blob->GetBufferPointer(), blob->GetBufferSize(), D3D_BLOB_DEBUG_NAME,
+ 0, nameBlob.c_str(), nameBlob.size(), &newBlob);
+
+ blob = newBlob;
+ }
}
else
{
diff --git a/util/test/demos/d3d12/d3d12_test.h b/util/test/demos/d3d12/d3d12_test.h
index a82c3b46a..71de188be 100644
--- a/util/test/demos/d3d12/d3d12_test.h
+++ b/util/test/demos/d3d12/d3d12_test.h
@@ -30,17 +30,6 @@
#include "dx/official/dxgi.h"
#include "d3d12_helpers.h"
-#include "dx/official/d3dcompiler.h"
-
-typedef HRESULT(WINAPI *pD3DStripShader)(_In_reads_bytes_(BytecodeLength) LPCVOID pShaderBytecode,
- _In_ SIZE_T BytecodeLength, _In_ UINT uStripFlags,
- _Out_ ID3DBlob **ppStrippedBlob);
-typedef HRESULT(WINAPI *pD3DSetBlobPart)(_In_reads_bytes_(SrcDataSize) LPCVOID pSrcData,
- _In_ SIZE_T SrcDataSize, _In_ D3D_BLOB_PART Part,
- _In_ UINT Flags, _In_reads_bytes_(PartSize) LPCVOID pPart,
- _In_ SIZE_T PartSize, _Out_ ID3DBlob **ppNewShader);
-typedef HRESULT(WINAPI *pD3DCreateBlob)(SIZE_T Size, ID3DBlob **ppBlob);
-
struct Win32Window;
struct D3D12GraphicsTest : public GraphicsTest
@@ -83,6 +72,7 @@ struct D3D12GraphicsTest : public GraphicsTest
SkipOptimise = 1 << 0,
Enable16BitTypes = 1 << 1,
SeparateDebug = 1 << 2,
+ ManualDebugName = 1 << 3,
};
ID3DBlobPtr Compile(std::string src, std::string entry, std::string profile,
diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj
index 3c438e96a..9d3870ecb 100644
--- a/util/test/demos/demos.vcxproj
+++ b/util/test/demos/demos.vcxproj
@@ -214,6 +214,7 @@
+
@@ -348,6 +349,7 @@
+
diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters
index 0496c145d..0f76c0aa3 100644
--- a/util/test/demos/demos.vcxproj.filters
+++ b/util/test/demos/demos.vcxproj.filters
@@ -769,6 +769,12 @@
Vulkan\demos
+
+ D3D12\demos
+
+
+ Vulkan\demos
+
diff --git a/util/test/demos/dx/d3d_helpers.h b/util/test/demos/dx/d3d_helpers.h
index 63669ba39..7cbced931 100644
--- a/util/test/demos/dx/d3d_helpers.h
+++ b/util/test/demos/dx/d3d_helpers.h
@@ -29,10 +29,24 @@
#include
#include "dx/official/dxgi1_4.h"
+#include "dx/official/d3dcompiler.h"
+
extern std::string D3DFullscreenQuadVertex;
extern std::string D3DDefaultVertex;
extern std::string D3DDefaultPixel;
+typedef HRESULT(WINAPI *pD3DStripShader)(_In_reads_bytes_(BytecodeLength) LPCVOID pShaderBytecode,
+ _In_ SIZE_T BytecodeLength, _In_ UINT uStripFlags,
+ _Out_ ID3DBlob **ppStrippedBlob);
+typedef HRESULT(WINAPI *pD3DSetBlobPart)(_In_reads_bytes_(SrcDataSize) LPCVOID pSrcData,
+ _In_ SIZE_T SrcDataSize, _In_ D3D_BLOB_PART Part,
+ _In_ UINT Flags, _In_reads_bytes_(PartSize) LPCVOID pPart,
+ _In_ SIZE_T PartSize, _Out_ ID3DBlob **ppNewShader);
+typedef HRESULT(WINAPI *pD3DGetBlobPart)(_In_reads_bytes_(SrcDataSize) LPCVOID pSrcData,
+ _In_ SIZE_T SrcDataSize, _In_ D3D_BLOB_PART Part,
+ _In_ UINT Flags, _Out_ ID3DBlob **ppPart);
+typedef HRESULT(WINAPI *pD3DCreateBlob)(SIZE_T Size, ID3DBlob **ppBlob);
+
#define COM_SMARTPTR(classname) _COM_SMARTPTR_TYPEDEF(classname, __uuidof(classname))
COM_SMARTPTR(IDXGISwapChain);
diff --git a/util/test/demos/main.cpp b/util/test/demos/main.cpp
index 45963385f..5f5045981 100644
--- a/util/test/demos/main.cpp
+++ b/util/test/demos/main.cpp
@@ -691,8 +691,8 @@ Usage: %s Test_Name [test_options]
if(testchoice == name)
{
TEST_LOG("Running '%s'", test.Name);
- test.test->Prepare(argc, argv);
test.test->SetName(test.Name);
+ test.test->Prepare(argc, argv);
if(!test.IsAvailable())
{
diff --git a/util/test/demos/test_common.cpp b/util/test/demos/test_common.cpp
index a2b6b6bd3..07bbe2431 100644
--- a/util/test/demos/test_common.cpp
+++ b/util/test/demos/test_common.cpp
@@ -284,7 +284,8 @@ bool SpvCompilationSupported()
std::vector CompileShaderToSpv(const char *demo_name, const std::string &source_text,
SPIRVTarget target, ShaderLang lang, ShaderStage stage,
const char *entry_point,
- const std::map ¯os)
+ const std::map ¯os,
+ bool debug_info)
{
std::vector ret;
@@ -303,7 +304,9 @@ std::vector CompileShaderToSpv(const char *demo_name, const std::strin
if(externalCompiler == "glslc")
{
- command_line = "glslc" EXECUTABLE_SUFFIX " -g -O0";
+ command_line = "glslc" EXECUTABLE_SUFFIX;
+ if(debug_info)
+ command_line += " -g -O0";
command_line += " -fentry-point=";
command_line += entry_point;
@@ -349,7 +352,9 @@ std::vector CompileShaderToSpv(const char *demo_name, const std::strin
}
else if(externalCompiler == "glslangValidator")
{
- command_line = "glslangValidator" EXECUTABLE_SUFFIX " -g ";
+ command_line = "glslangValidator" EXECUTABLE_SUFFIX;
+ if(debug_info)
+ command_line += " -g ";
command_line += " --entry-point ";
command_line += entry_point;
@@ -458,6 +463,11 @@ void GraphicsTest::Prepare(int argc, char **argv)
if(dataRoot.empty())
dataRoot = GetCWD() + "/data/demos/";
+ tmpPath = GetExecutableName();
+ tmpPath.erase(tmpPath.find_last_of("/\\"));
+ tmpPath += "/tmp";
+ MakeDir(tmpPath.c_str());
+
// parse parameters
for(int i = 0; i < argc; i++)
{
@@ -477,6 +487,11 @@ void GraphicsTest::Prepare(int argc, char **argv)
logFile = fopen(argv[i + 1], "w");
}
+ if(i + 1 < argc && !strcmp(argv[i], "--tmp"))
+ {
+ tmpPath = argv[i + 1];
+ }
+
if(i + 1 < argc && (!strcmp(argv[i], "--width") || !strcmp(argv[i], "-w")))
{
screenWidth = atoi(argv[i + 1]);
@@ -506,6 +521,10 @@ void GraphicsTest::Prepare(int argc, char **argv)
}
}
+ tmpPath += "/";
+ tmpPath += demoName;
+ MakeDir(tmpPath.c_str());
+
pRENDERDOC_GetAPI RENDERDOC_GetAPI = NULL;
#if defined(WIN32)
diff --git a/util/test/demos/test_common.h b/util/test/demos/test_common.h
index 684e255b7..308002b8e 100644
--- a/util/test/demos/test_common.h
+++ b/util/test/demos/test_common.h
@@ -87,7 +87,8 @@ bool SpvCompilationSupported();
std::vector CompileShaderToSpv(const char *demo_name, const std::string &source_text,
SPIRVTarget target, ShaderLang lang, ShaderStage stage,
const char *entry_point,
- const std::map ¯os = {});
+ const std::map ¯os = {},
+ bool debug_info = true);
struct Vec2f
{
@@ -224,6 +225,8 @@ struct GraphicsTest
RENDERDOC_API_1_7_0 *rdoc = NULL;
+ std::string tmpPath;
+
// shared parameters
static int maxFrameCount;
static std::string dataRoot;
diff --git a/util/test/demos/vk/vk_stripped_shaders.cpp b/util/test/demos/vk/vk_stripped_shaders.cpp
new file mode 100644
index 000000000..5dda19a2a
--- /dev/null
+++ b/util/test/demos/vk/vk_stripped_shaders.cpp
@@ -0,0 +1,97 @@
+/******************************************************************************
+ * 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_Stripped_Shaders, VulkanGraphicsTest)
+{
+ static constexpr const char *Description =
+ "Tests shaders with their debug/reflection info stripped out and stored in separate blobs.";
+
+ int main()
+ {
+ // initialise, create window, create context, etc
+ if(!Init())
+ return 3;
+
+ vkh::GraphicsPipelineCreateInfo pipeCreateInfo;
+
+ VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo());
+
+ pipeCreateInfo.layout = layout;
+ pipeCreateInfo.renderPass = mainWindow->rp;
+
+ pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)};
+ pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = {
+ vkh::vertexAttr(0, 0, DefaultA2V, pos),
+ vkh::vertexAttr(1, 0, DefaultA2V, col),
+ vkh::vertexAttr(2, 0, DefaultA2V, uv),
+ };
+
+ pipeCreateInfo.stages = {
+ CompileShaderModule(VKDefaultVertex + "\n// shader source // vertex", ShaderLang::glsl,
+ ShaderStage::vert, "main", {}, SPIRVTarget::vulkan,
+ CompileOptionFlags::SeparateDebug),
+ CompileShaderModule(VKDefaultPixel + "\n// shader source // pixel", ShaderLang::glsl,
+ ShaderStage::frag, "main", {}, SPIRVTarget::vulkan,
+ CompileOptionFlags::SeparateDebug | CompileOptionFlags::RelativeDebugName),
+ };
+
+ VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo);
+
+ while(Running())
+ {
+ VkCommandBuffer cmd = GetCommandBuffer();
+
+ vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
+
+ VkImage swapimg = StartUsingBackbuffer(cmd);
+
+ vkh::cmdClearImage(cmd, swapimg, vkh::ClearColorValue(0.2f, 0.2f, 0.2f, 1.0f));
+
+ vkCmdBeginRenderPass(cmd, mainWindow->beginRP(), VK_SUBPASS_CONTENTS_INLINE);
+
+ vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
+ mainWindow->setViewScissor(cmd);
+ vkh::cmdBindVertexBuffers(cmd, {DefaultTriVB.buffer});
+
+ setMarker(cmd, "test: shader source");
+ setMarker(cmd, "vertex manual absolute");
+ setMarker(cmd, "pixel manual relative");
+ 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_test.cpp b/util/test/demos/vk/vk_test.cpp
index d758baedf..4449521be 100644
--- a/util/test/demos/vk/vk_test.cpp
+++ b/util/test/demos/vk/vk_test.cpp
@@ -1009,18 +1009,63 @@ VkPipelineShaderStageCreateInfo VulkanGraphicsTest::LoadShaderModule(const std::
VkPipelineShaderStageCreateInfo VulkanGraphicsTest::CompileShaderModule(
const std::string &source_text, ShaderLang lang, ShaderStage stage, const char *entry_point,
- const std::map ¯os, SPIRVTarget target)
+ const std::map ¯os, SPIRVTarget target, uint32_t compileOptions)
{
VkShaderModule ret = VK_NULL_HANDLE;
+ const bool separateDebug = ((compileOptions & CompileOptionFlags::SeparateDebug) != 0);
+ const bool relativeDebugName = ((compileOptions & CompileOptionFlags::RelativeDebugName) != 0);
+
std::vector spirv =
::CompileShaderToSpv(demoName, source_text, target, lang, stage, entry_point, macros);
if(spirv.empty())
return {};
+ std::string path;
+ if(separateDebug)
+ {
+ path = tmpPath + "/";
+
+ std::string filenames[] = {
+ "shader_vs.dbg", "shader_hs.dbg", "shader_ds.dbg", "shader_gs.dbg",
+ "shader_ps.dbg", "shader_cs.dbg", "shader_ms.dbg", "shader_as.dbg",
+ };
+
+ path += filenames[(int)stage];
+
+ FILE *f = fopen(path.c_str(), "wb");
+ if(f)
+ {
+ fwrite(spirv.data(), 1, spirv.size() * sizeof(uint32_t), f);
+ fclose(f);
+ }
+
+ spirv =
+ ::CompileShaderToSpv(demoName, source_text, target, lang, stage, entry_point, macros, false);
+ }
+
CHECK_VKR(vkCreateShaderModule(device, vkh::ShaderModuleCreateInfo(spirv), NULL, &ret));
+ if(relativeDebugName)
+ {
+ path.erase(0, path.find_last_of("/\\") + 1);
+ // keep the demo name so we don't have to set up tons of search paths
+ path = std::string(demoName) + "/" + path;
+ }
+
+ if(vkSetDebugUtilsObjectTagEXT)
+ {
+ VkDebugUtilsObjectTagInfoEXT info = {};
+ info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_TAG_INFO_EXT;
+ info.objectType = VK_OBJECT_TYPE_SHADER_MODULE;
+ info.objectHandle = (uint64_t)ret;
+ info.pTag = path.c_str();
+ info.tagSize = path.size();
+ info.tagName = RENDERDOC_ShaderDebugMagicValue_truncated;
+ vkSetDebugUtilsObjectTagEXT(device, &info);
+ }
+
shaders.push_back(ret);
VkShaderStageFlagBits vkstage[] = {
diff --git a/util/test/demos/vk/vk_test.h b/util/test/demos/vk/vk_test.h
index fe3e6135b..0b5604898 100644
--- a/util/test/demos/vk/vk_test.h
+++ b/util/test/demos/vk/vk_test.h
@@ -194,6 +194,13 @@ struct VulkanGraphicsTest : public GraphicsTest
{
static const TestAPI API = TestAPI::Vulkan;
+ enum CompileOptionFlags
+ {
+ None = 0,
+ SeparateDebug = 1 << 0,
+ RelativeDebugName = 1 << 1,
+ };
+
VulkanGraphicsTest();
void Prepare(int argc, char **argv);
@@ -222,7 +229,7 @@ struct VulkanGraphicsTest : public GraphicsTest
VkPipelineShaderStageCreateInfo CompileShaderModule(
const std::string &source_text, ShaderLang lang, ShaderStage stage,
const char *entry_point = "main", const std::map ¯os = {},
- SPIRVTarget target = SPIRVTarget::vulkan);
+ SPIRVTarget target = SPIRVTarget::vulkan, uint32_t compileOptions = CompileOptionFlags::None);
VkCommandBuffer GetCommandBuffer(VkCommandBufferLevel level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
VulkanWindow *window = NULL);
diff --git a/util/test/rdtest/__init__.py b/util/test/rdtest/__init__.py
index ef1d26134..701e42c59 100644
--- a/util/test/rdtest/__init__.py
+++ b/util/test/rdtest/__init__.py
@@ -14,3 +14,4 @@ from .shared.Pixel_History import *
from .shared.Workgroup_Zoo import *
from .shared.Groupshared import *
from .shared.Annotations import *
+from .shared.Stripped_Shaders import *
diff --git a/util/test/rdtest/analyse.py b/util/test/rdtest/analyse.py
index c56f76879..9b06cf2f5 100644
--- a/util/test/rdtest/analyse.py
+++ b/util/test/rdtest/analyse.py
@@ -6,7 +6,7 @@ from . import util
def open_capture(
- filename="", cap: rd.CaptureFile | None = None, opts: rd.ReplayOptions | None = None
+ filename="", opts: rd.ReplayOptions | None = None
):
"""
Opens a capture file and begins a replay.
@@ -21,32 +21,28 @@ def open_capture(
if opts is None:
opts = rd.ReplayOptions()
- # Open a capture file handle
- own_cap = False
+ cap = None
api = "Unknown"
result = None
controller = None
if util.get_remote_server() is None:
- if cap is None:
- own_cap = True
+ cap = rd.OpenCaptureFile()
- cap = rd.OpenCaptureFile()
+ # Open a particular file
+ result = cap.OpenFile(filename, '', None)
- # Open a particular file
- result = cap.OpenFile(filename, '', None)
+ # Make sure the file opened successfully
+ if not result:
+ cap.Shutdown()
+ raise RuntimeError(f"Couldn't open '{filename}': {result!s}")
- # Make sure the file opened successfully
- if not result:
- cap.Shutdown()
- raise RuntimeError(f"Couldn't open '{filename}': {result!s}")
+ api = cap.DriverName()
- api = cap.DriverName()
-
- # Make sure we can replay
- if not cap.LocalReplaySupport():
- cap.Shutdown()
- raise RuntimeError(f"{api} capture cannot be replayed")
+ # Make sure we can replay
+ if not cap.LocalReplaySupport():
+ cap.Shutdown()
+ raise RuntimeError(f"{api} capture cannot be replayed")
result, controller = cap.OpenCapture(opts, None)
else:
@@ -58,13 +54,10 @@ def open_capture(
if result:
api = util.get_remote_server().remote.DriverName()
- if own_cap:
- cap.Shutdown()
-
if not result:
raise RuntimeError(f"Couldn't initialise replay for {api}: {result!s}")
- return controller
+ return cap, controller
def fetch_indices(controller: rd.ReplayController, action: rd.ActionDescription, mesh: rd.MeshFormat, index_offset: int, first_index: int, num_indices: int) -> List[int | None]:
diff --git a/util/test/rdtest/shared/Stripped_Shaders.py b/util/test/rdtest/shared/Stripped_Shaders.py
new file mode 100644
index 000000000..9c5b5e02c
--- /dev/null
+++ b/util/test/rdtest/shared/Stripped_Shaders.py
@@ -0,0 +1,140 @@
+import rdtest
+import shutil
+import renderdoc as rd
+import re
+
+
+# Not a real test, re-used by API-specific tests
+class Stripped_Shaders(rdtest.TestCase):
+ internal = True
+ demos_test_name = None
+
+ def get_capture_options(self):
+ # We don't actually need to set any capture-time options but we'll use this to change the search dir paths
+ paths = rd.SetConfigSetting("DXBC.Debug.SearchDirPaths")
+
+ paths.DeleteChildren()
+ paths.AddChild(rd.makeSDString("path", rdtest.get_tmp_dir()))
+
+ # this won't automatically save and we won't call SaveConfigSettings()
+
+ return rd.CaptureOptions()
+
+ def check_capture(self):
+ self.check_debug_symbols(False)
+
+ filename = self.retrieve_capture()
+ embed_path = rdtest.get_tmp_path("embedded.rdc")
+
+ shutil.copy(filename, embed_path)
+
+ # shut down the controller that is for the old file, to keep it the same
+ self.controller.Shutdown()
+ self.controller = None
+ if self.cap is not None:
+ self.cap.Shutdown()
+ self.cap = None
+
+ # open the version we're going to embed into
+ self.cap, self.controller = rdtest.open_capture(embed_path)
+
+ assert self.cap is not None
+ assert len(self.cap.GetPendingDependenciesNicknames()) > 0
+
+ # embed the dependencies
+ assert self.cap.EmbedDependenciesIntoCapture().OK()
+
+ self.controller.Shutdown()
+ self.cap.Shutdown()
+
+ # now clear the search paths, this should make at least one fail
+ rd.SetConfigSetting("DXBC.Debug.SearchDirPaths").DeleteChildren()
+
+ self.cap, self.controller = rdtest.open_capture(embed_path)
+ self.sdfile = self.controller.GetStructuredFile()
+
+ # check everything is still OK
+ self.check_debug_symbols(True)
+
+ def check_debug_symbols(self, embedded: bool):
+ test = self.find_action("test:", 0)
+
+ while test is not None:
+ pix_cfg = self.get_action(test.eventId + 2)
+ assert pix_cfg is not None
+ draw = self.get_action(test.eventId + 3)
+ assert draw is not None
+
+ search_text = test.customName.replace("test: ", "")
+ assert "pixel" in pix_cfg.customName
+
+ self.set_event(draw.eventId, False)
+
+ pipe = self.controller.GetPipelineState()
+
+ stages = [
+ (1, rd.ShaderStage.Vertex, "vertex", "shader_vs.dbg"),
+ (2, rd.ShaderStage.Pixel, "pixel", "shader_ps.dbg"),
+ ]
+
+ for offs, stage, name, manual_filename in stages:
+ cfg = self.get_action(test.eventId + offs)
+ assert cfg is not None
+
+ assert name in cfg.customName
+
+ refl = pipe.GetShaderReflection(stage)
+
+ assert len(refl.debugInfo.files) == 1
+
+ src = refl.debugInfo.files[0].contents
+
+ assert f"// {search_text} // {name}" in src
+
+ log = refl.debugInfo.debugInfoLoadingLog
+
+ assert re.match("Found ", log, re.IGNORECASE)
+
+ if "auto" in cfg.customName:
+ assert re.search("[a-f0-9]{32}.pdb", log) is not None
+ elif "manual" in cfg.customName:
+ assert f"{manual_filename}'" in log
+ else:
+ raise rdtest.TestFailureException(
+ f"Unexpected {name} config {cfg.customName}"
+ )
+
+ filepath = re.search("Shader filepath '([^']*)'", log)
+ assert filepath is not None
+
+ filepath = filepath[1].replace("\\", "/")
+ tmp_dir = rdtest.get_tmp_dir().replace("\\", "/")
+
+ if "absolute" in cfg.customName:
+ assert tmp_dir in filepath
+ assert "File found using filepath" in log
+ assert "File found in search directory" not in log
+ assert "File found in recursive directory search" not in log
+ assert "embedded in the capture using nickname" not in log
+ elif "relative" in cfg.customName:
+ assert tmp_dir not in filepath
+ assert "File found using filepath" not in log
+ if embedded:
+ assert "embedded in the capture using nickname" in log
+ assert "File found in search directory" not in log
+ assert "File found in recursive directory search" not in log
+ else:
+ assert (
+ "File found in search directory" in log
+ or "File found in recursive directory search" in log
+ )
+ else:
+ raise rdtest.TestFailureException(
+ f"Unexpected {name} config {cfg.customName}"
+ )
+
+ rdtest.log.success(f"{name} shader debug info and loading log looks OK")
+
+ test = self.find_action("test:", draw.eventId)
+
+ rdtest.log.success("All draws had proper shader debug info")
diff --git a/util/test/rdtest/testcase.py b/util/test/rdtest/testcase.py
index 7e66b5ab0..4098e2ec5 100644
--- a/util/test/rdtest/testcase.py
+++ b/util/test/rdtest/testcase.py
@@ -263,6 +263,7 @@ class TestCase:
self.capture_filename = ""
self.worker_thread = 0
self.controller: rd.ReplayController | None = None
+ self.cap: rd.CaptureFile | None = None
self.sdfile: rd.SDFile | None = None
self.contexts: List[ScopedContext] = []
self.cur_event = 0
@@ -319,7 +320,7 @@ class TestCase:
if timeout is None:
timeout = util.get_demos_timeout()
return capture.run_and_capture(exe,
- self.demos_test_name + " --log " + remote_logfile,
+ f"{self.demos_test_name} --log {remote_logfile} --tmp {util.get_tmp_dir()}",
self.demos_frame_cap, frame_count=self.demos_frame_count,
captures_expected=self.demos_captures_expected, logfile=logfile,
opts=self.get_capture_options(), timeout=timeout)
@@ -761,7 +762,7 @@ class TestCase:
log.print("Loading capture")
- self.controller = analyse.open_capture(self.capture_filename, opts=self.get_replay_options())
+ self.cap, self.controller = analyse.open_capture(self.capture_filename, opts=self.get_replay_options())
self.sdfile = self.controller.GetStructuredFile()
if not self.validate_eventids(self.controller):
@@ -782,6 +783,9 @@ class TestCase:
else:
self.controller.Shutdown()
+ if self.cap is not None:
+ self.cap.Shutdown()
+
def invoketest(self, debugMode: bool):
start_time = self.get_time()
self.run()
diff --git a/util/test/tests/D3D11/D3D11_Stripped_Shaders.py b/util/test/tests/D3D11/D3D11_Stripped_Shaders.py
new file mode 100644
index 000000000..513e29607
--- /dev/null
+++ b/util/test/tests/D3D11/D3D11_Stripped_Shaders.py
@@ -0,0 +1,6 @@
+import rdtest
+
+
+class D3D11_Stripped_Shaders(rdtest.Stripped_Shaders):
+ demos_test_name = 'D3D11_Stripped_Shaders'
+ internal = False
\ No newline at end of file
diff --git a/util/test/tests/D3D12/D3D12_Stripped_Shaders.py b/util/test/tests/D3D12/D3D12_Stripped_Shaders.py
new file mode 100644
index 000000000..3577c8259
--- /dev/null
+++ b/util/test/tests/D3D12/D3D12_Stripped_Shaders.py
@@ -0,0 +1,6 @@
+import rdtest
+
+
+class D3D12_Stripped_Shaders(rdtest.Stripped_Shaders):
+ demos_test_name = 'D3D12_Stripped_Shaders'
+ internal = False
\ No newline at end of file
diff --git a/util/test/tests/GL/GL_Buffer_Updates.py b/util/test/tests/GL/GL_Buffer_Updates.py
index 27297fb02..4a44e94f5 100644
--- a/util/test/tests/GL/GL_Buffer_Updates.py
+++ b/util/test/tests/GL/GL_Buffer_Updates.py
@@ -30,19 +30,12 @@ class GL_Buffer_Updates(rdtest.TestCase):
rdtest.log.success("Draws are all green")
- # Open the capture and grab the thumbnail, check that it is all green too (dirty way of verifying we didn't
+ # grab the thumbnail, check that it is all green too (dirty way of verifying we didn't
# break in-app updates but somehow end up with the right data)
- cap = rd.OpenCaptureFile()
+ if self.cap is None:
+ raise rdtest.TestFailureException(f"Don't have open capture {self.capture_filename}")
- # Open a particular file
- result = cap.OpenFile(self.capture_filename, '', None)
-
- # Make sure the file opened successfully
- if not result:
- cap.Shutdown()
- raise rdtest.TestFailureException(f"Couldn't open '{self.capture_filename}': {result!s}")
-
- thumb = cap.GetThumbnail(rd.FileType.PNG, 0)
+ thumb = self.cap.GetThumbnail(rd.FileType.PNG, 0)
tmp_path = rdtest.get_tmp_path('thumbnail.png')
diff --git a/util/test/tests/GL/GL_Callstacks.py b/util/test/tests/GL/GL_Callstacks.py
index d97722947..1085e9b26 100644
--- a/util/test/tests/GL/GL_Callstacks.py
+++ b/util/test/tests/GL/GL_Callstacks.py
@@ -21,21 +21,13 @@ class GL_Callstacks(rdtest.TestCase):
return ret
def check_capture(self):
- # Need capture access. Rather than trying to keep the original around, we just open a new one
- cap = rd.OpenCaptureFile()
+ if self.cap is None:
+ raise rdtest.TestFailureException(f"Don't have open capture {self.capture_filename}")
- # Open a particular file
- result = cap.OpenFile(self.capture_filename, '', None)
-
- # Make sure the file opened successfully
- if not result:
- cap.Shutdown()
- raise rdtest.TestFailureException(f"Couldn't open capture {self.capture_filename} for access: {result!s}")
-
- if not cap.HasCallstacks():
+ if not self.cap.HasCallstacks():
raise rdtest.TestFailureException("Capture does not report having callstacks")
- if not cap.InitResolver(False, resolve_progress):
+ if not self.cap.InitResolver(False, resolve_progress):
raise rdtest.TestFailureException("Failed to initialise callstack resolver")
action = self.find_action("Draw")
@@ -59,7 +51,7 @@ class GL_Callstacks(rdtest.TestCase):
chunk = sdfile.chunks[event.chunkIndex]
- callstack = cap.GetResolve(list(chunk.metadata.callstack))
+ callstack = self.cap.GetResolve(list(chunk.metadata.callstack))
if len(callstack) < len(expected_funcs):
raise rdtest.TestFailureException(f"Resolved callstack isn't long enough ({len(callstack)} stack frames), expected at least {len(expected_funcs)}")
diff --git a/util/test/tests/Iter_Test.py b/util/test/tests/Iter_Test.py
index 1e855e47e..95fe98a64 100644
--- a/util/test/tests/Iter_Test.py
+++ b/util/test/tests/Iter_Test.py
@@ -436,7 +436,7 @@ class Iter_Test(rdtest.TestCase):
rdtest.log.print(f"Opening '{file.name}'.")
try:
- self.controller = rdtest.open_capture(file.path)
+ self.cap, self.controller = rdtest.open_capture(file.path)
except RuntimeError as err:
rdtest.log.print(f"Skipping. Can't open {file.path}: {err}")
continue
@@ -450,6 +450,8 @@ class Iter_Test(rdtest.TestCase):
rdtest.log.end_section(section_name)
self.controller.Shutdown()
+ if self.cap is not None:
+ self.cap.Shutdown()
rdtest.log.success("Iterated all files")
diff --git a/util/test/tests/Repeat_Load.py b/util/test/tests/Repeat_Load.py
index e61ab3879..5fc7faeda 100644
--- a/util/test/tests/Repeat_Load.py
+++ b/util/test/tests/Repeat_Load.py
@@ -13,7 +13,7 @@ class Repeat_Load(rdtest.TestCase):
rdtest.log.print(f"Loading for iteration {i + 1}")
try:
- controller = rdtest.open_capture(path)
+ cap, controller = rdtest.open_capture(path)
except RuntimeError as err:
rdtest.log.print(f"Skipping. Can't open {path}: {err}")
return
@@ -31,6 +31,8 @@ class Repeat_Load(rdtest.TestCase):
memory_baseline = memory_usage
controller.Shutdown()
+ if cap is not None:
+ cap.Shutdown()
pct_over = 'N/A'
diff --git a/util/test/tests/Vulkan/VK_Large_Descriptor_Sets.py b/util/test/tests/Vulkan/VK_Large_Descriptor_Sets.py
index 4fcc92a22..4bb4db4c4 100644
--- a/util/test/tests/Vulkan/VK_Large_Descriptor_Sets.py
+++ b/util/test/tests/Vulkan/VK_Large_Descriptor_Sets.py
@@ -15,7 +15,7 @@ class VK_Large_Descriptor_Sets(rdtest.TestCase):
memory_before = rd.GetCurrentProcessMemoryUsage()
start_time = self.get_time()
- self.controller = rdtest.open_capture(self.capture_filename, opts=self.get_replay_options())
+ self.cap, self.controller = rdtest.open_capture(self.capture_filename, opts=self.get_replay_options())
duration = self.get_time() - start_time
memory_after = rd.GetCurrentProcessMemoryUsage()
@@ -37,3 +37,5 @@ class VK_Large_Descriptor_Sets(rdtest.TestCase):
rdtest.log.print("Not checking time to load in non-release build")
self.controller.Shutdown()
+ if self.cap is not None:
+ self.cap.Shutdown()
diff --git a/util/test/tests/Vulkan/VK_Stripped_Shaders.py b/util/test/tests/Vulkan/VK_Stripped_Shaders.py
new file mode 100644
index 000000000..f4577849c
--- /dev/null
+++ b/util/test/tests/Vulkan/VK_Stripped_Shaders.py
@@ -0,0 +1,6 @@
+import rdtest
+
+
+class VK_Stripped_Shaders(rdtest.Stripped_Shaders):
+ demos_test_name = 'VK_Stripped_Shaders'
+ internal = False
\ No newline at end of file