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