diff --git a/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp b/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp index 2dc0dd0ba..41a7e437d 100644 --- a/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp +++ b/renderdoc/driver/d3d11/d3d11_shaderdebug.cpp @@ -2199,6 +2199,19 @@ ShaderDebugTrace *D3D11Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t uint32_t overdrawLevels = 100; // maximum number of overdraw levels + // If the pipe contains a geometry shader, then SV_PrimitiveID cannot be used in the pixel + // shader without being emitted from the geometry shader. For now, check if this semantic + // will succeed in a new pixel shader with the rest of the pipe unchanged + bool usePrimitiveID = (prevdxbc->m_Type != DXBC::ShaderType::Geometry); + for(const PSInputElement &e : initialValues) + { + if(e.sysattribute == ShaderBuiltin::PrimitiveIndex) + { + usePrimitiveID = true; + break; + } + } + uint32_t uavslot = 0; ID3D11DepthStencilView *depthView = NULL; @@ -2355,19 +2368,37 @@ struct PSInitialData extractHlsl += "RWBuffer PSEvalBuffer : register(u" + ToStr(uavslot + 1) + ");\n\n"; } - extractHlsl += R"( + if(usePrimitiveID) + { + extractHlsl += R"( void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, uint prim : SV_PrimitiveID, uint sample : SV_SampleIndex, uint covge : SV_Coverage, bool fface : SV_IsFrontFace) { )"; + } + else + { + extractHlsl += R"( +void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, + uint sample : SV_SampleIndex, uint covge : SV_Coverage, + bool fface : SV_IsFrontFace) +{ +)"; + } + extractHlsl += " uint idx = " + ToStr(overdrawLevels) + ";\n"; extractHlsl += StringFormat::Fmt( " if(abs(debug_pixelPos.x - %u.5) < 0.5f && abs(debug_pixelPos.y - %u.5) < 0.5f)\n", x, y); extractHlsl += " InterlockedAdd(PSInitialBuffer[0].hit, 1, idx);\n\n"; extractHlsl += " idx = min(idx, " + ToStr(overdrawLevels) + ");\n\n"; extractHlsl += " PSInitialBuffer[idx].pos = debug_pixelPos.xyz;\n"; - extractHlsl += " PSInitialBuffer[idx].prim = prim;\n"; + + if(usePrimitiveID) + extractHlsl += " PSInitialBuffer[idx].prim = prim;\n"; + else + extractHlsl += " PSInitialBuffer[idx].prim = 0;\n"; + extractHlsl += " PSInitialBuffer[idx].fface = fface;\n"; extractHlsl += " PSInitialBuffer[idx].covge = covge;\n"; extractHlsl += " PSInitialBuffer[idx].sample = sample;\n"; diff --git a/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp b/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp index fb31f7b9e..fdf7dc071 100644 --- a/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp +++ b/renderdoc/driver/d3d12/d3d12_shaderdebug.cpp @@ -1216,6 +1216,19 @@ ShaderDebugTrace *D3D12Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t uint32_t overdrawLevels = 100; // maximum number of overdraw levels + // If the pipe contains a geometry shader, then SV_PrimitiveID cannot be used in the pixel + // shader without being emitted from the geometry shader. For now, check if this semantic + // will succeed in a new pixel shader with the rest of the pipe unchanged + bool usePrimitiveID = (prevDxbc->m_Type != ShaderType::Geometry); + for(const PSInputElement &e : initialValues) + { + if(e.sysattribute == ShaderBuiltin::PrimitiveIndex) + { + usePrimitiveID = true; + break; + } + } + // get the multisample count uint32_t outputSampleCount = RDCMAX(1U, pipelineState->outputMerger.multiSampleCount); @@ -1251,12 +1264,24 @@ struct PSInitialData extractHlsl += "RWStructuredBuffer PSInitialBuffer : register(u0);\n\n"; - extractHlsl += R"( + if(usePrimitiveID) + { + extractHlsl += R"( void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, uint prim : SV_PrimitiveID, uint sample : SV_SampleIndex, uint covge : SV_Coverage, bool fface : SV_IsFrontFace) { )"; + } + else + { + extractHlsl += R"( +void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, + uint sample : SV_SampleIndex, uint covge : SV_Coverage, + bool fface : SV_IsFrontFace) +{ +)"; + } extractHlsl += " uint idx = " + ToStr(overdrawLevels) + ";\n"; extractHlsl += StringFormat::Fmt( @@ -1264,7 +1289,12 @@ void ExtractInputsPS(PSInput IN, float4 debug_pixelPos : SV_Position, uint prim extractHlsl += " InterlockedAdd(PSInitialBuffer[0].hit, 1, idx);\n\n"; extractHlsl += " idx = min(idx, " + ToStr(overdrawLevels) + ");\n\n"; extractHlsl += " PSInitialBuffer[idx].pos = debug_pixelPos.xyz;\n"; - extractHlsl += " PSInitialBuffer[idx].prim = prim;\n"; + + if(usePrimitiveID) + extractHlsl += " PSInitialBuffer[idx].prim = prim;\n"; + else + extractHlsl += " PSInitialBuffer[idx].prim = 0;\n"; + extractHlsl += " PSInitialBuffer[idx].fface = fface;\n"; extractHlsl += " PSInitialBuffer[idx].covge = covge;\n"; extractHlsl += " PSInitialBuffer[idx].sample = sample;\n"; diff --git a/util/test/demos/d3d11/d3d11_primitiveid.cpp b/util/test/demos/d3d11/d3d11_primitiveid.cpp new file mode 100644 index 000000000..ec23181a1 --- /dev/null +++ b/util/test/demos/d3d11/d3d11_primitiveid.cpp @@ -0,0 +1,191 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2020 Baldur Karlsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include "d3d11_test.h" + +RD_TEST(D3D11_PrimitiveID, D3D11GraphicsTest) +{ + static constexpr const char *Description = + "Exercises pixel shader debugging with various primitive ID scenarios."; + + std::string common = R"EOSHADER( +struct v2f +{ + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; +}; + +struct prim2f +{ + v2f data; + uint prim : SV_PrimitiveID; +}; + +)EOSHADER"; + + std::string geomNoPrim = R"EOSHADER( + +[maxvertexcount(6)] +void main(triangle v2f input[3], inout TriangleStream TriStream) +{ + // Output the original triangle + int i; + for(i = 0; i < 3; i++) + { + v2f output = input[i]; + TriStream.Append(output); + } + TriStream.RestartStrip(); + + // Output the original triangle, shifted to the right + for(i = 0; i < 3; i++) + { + v2f output = input[i]; + output.pos.x += 0.5f; + TriStream.Append(output); + } + TriStream.RestartStrip(); +} + +)EOSHADER"; + + std::string geomPrim = R"EOSHADER( + +[maxvertexcount(6)] +void main(triangle v2f input[3], inout TriangleStream TriStream) +{ + // Output the original triangle + int i; + for(i = 0; i < 3; i++) + { + prim2f output; + output.prim = 2; + output.data = input[i]; + TriStream.Append(output); + } + TriStream.RestartStrip(); + + // Output the original triangle, shifted to the right + for(i = 0; i < 3; i++) + { + prim2f output; + output.prim = 3; + output.data = input[i]; + output.data.pos.x += 0.5f; + TriStream.Append(output); + } + TriStream.RestartStrip(); +} + +)EOSHADER"; + + std::string pixelNoPrim = R"EOSHADER( + +float4 main(in v2f IN) : SV_Target0 +{ + return float4(0.0f, 1.0f, 0.0f, 1.0f); +} + +)EOSHADER"; + + std::string pixelPrim = R"EOSHADER( + +float4 main(in prim2f IN) : SV_Target0 +{ + return float4(IN.prim / 4.0f, 1.0f, 0.0f, 1.0f); +} + +)EOSHADER"; + + int main() + { + // initialise, create window, create device, etc + if(!Init()) + return 3; + + ID3DBlobPtr vsBlob = Compile(D3DDefaultVertex, "main", "vs_4_0"); + ID3DBlobPtr gsNoPrimBlob = Compile(common + geomNoPrim, "main", "gs_5_0"); + ID3DBlobPtr gsPrimBlob = Compile(common + geomPrim, "main", "gs_5_0"); + ID3DBlobPtr psNoPrimBlob = Compile(common + pixelNoPrim, "main", "ps_5_0"); + ID3DBlobPtr psPrimBlob = Compile(common + pixelPrim, "main", "ps_5_0"); + + CreateDefaultInputLayout(vsBlob); + ID3D11BufferPtr vb = MakeBuffer().Vertex().Data(DefaultTri); + + ID3D11VertexShaderPtr vs = CreateVS(vsBlob); + ID3D11GeometryShaderPtr gsNoPrim = CreateGS(gsNoPrimBlob); + ID3D11GeometryShaderPtr gsPrim = CreateGS(gsPrimBlob); + ID3D11PixelShaderPtr psNoPrim = CreatePS(psNoPrimBlob); + ID3D11PixelShaderPtr psPrim = CreatePS(psPrimBlob); + + float halfWidth = (float)screenWidth * 0.5f; + float halfHeight = (float)screenHeight * 0.5f; + D3D11_VIEWPORT views[4] = {{0.0f, 0.0f, halfWidth, halfHeight, 0.0f, 1.0f}, + {halfWidth, 0.0f, halfWidth, halfHeight, 0.0f, 1.0f}, + {0.0f, halfHeight, halfWidth, halfHeight, 0.0f, 1.0f}, + {halfWidth, halfHeight, halfWidth, halfHeight, 0.0f, 1.0f}}; + while(Running()) + { + ctx->OMSetRenderTargets(1, &bbRTV.GetInterfacePtr(), NULL); + ClearRenderTargetView(bbRTV, {0.2f, 0.2f, 0.2f, 1.0f}); + + IASetVertexBuffer(vb, sizeof(DefaultA2V), 0); + ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + ctx->IASetInputLayout(defaultLayout); + + annot->SetMarker(L"Test"); + + // Draw with no GS, PS without prim + RSSetViewport(views[0]); + ctx->VSSetShader(vs, NULL, 0); + ctx->GSSetShader(NULL, NULL, 0); + ctx->PSSetShader(psNoPrim, NULL, 0); + ctx->Draw(3, 0); + + // Draw with no GS, PS with prim + RSSetViewport(views[1]); + ctx->PSSetShader(psPrim, NULL, 0); + ctx->Draw(3, 0); + + // Draw with GS, PS both without prim + RSSetViewport(views[2]); + ctx->GSSetShader(gsNoPrim, NULL, 0); + ctx->PSSetShader(psNoPrim, NULL, 0); + ctx->Draw(3, 0); + + // Draw with GS, PS both with prim + RSSetViewport(views[3]); + ctx->GSSetShader(gsPrim, NULL, 0); + ctx->PSSetShader(psPrim, NULL, 0); + ctx->Draw(3, 0); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/demos/d3d12/d3d12_primitiveid.cpp b/util/test/demos/d3d12/d3d12_primitiveid.cpp new file mode 100644 index 000000000..78022825a --- /dev/null +++ b/util/test/demos/d3d12/d3d12_primitiveid.cpp @@ -0,0 +1,188 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2020 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_PrimitiveID, D3D12GraphicsTest) +{ + static constexpr const char *Description = + "Exercises pixel shader debugging with various primitive ID scenarios."; + + std::string common = R"EOSHADER( +struct v2f +{ + float4 pos : SV_POSITION; + float4 col : COLOR0; + float2 uv : TEXCOORD0; +}; + +struct prim2f +{ + v2f data; + uint prim : SV_PrimitiveID; +}; + +)EOSHADER"; + + std::string geomNoPrim = R"EOSHADER( + +[maxvertexcount(6)] +void main(triangle v2f input[3], inout TriangleStream TriStream) +{ + // Output the original triangle + int i; + for(i = 0; i < 3; i++) + { + v2f output = input[i]; + TriStream.Append(output); + } + TriStream.RestartStrip(); + + // Output the original triangle, shifted to the right + for(i = 0; i < 3; i++) + { + v2f output = input[i]; + output.pos.x += 0.5f; + TriStream.Append(output); + } + TriStream.RestartStrip(); +} + +)EOSHADER"; + + std::string geomPrim = R"EOSHADER( + +[maxvertexcount(6)] +void main(triangle v2f input[3], inout TriangleStream TriStream) +{ + // Output the original triangle + int i; + for(i = 0; i < 3; i++) + { + prim2f output; + output.prim = 2; + output.data = input[i]; + TriStream.Append(output); + } + TriStream.RestartStrip(); + + // Output the original triangle, shifted to the right + for(i = 0; i < 3; i++) + { + prim2f output; + output.prim = 3; + output.data = input[i]; + output.data.pos.x += 0.5f; + TriStream.Append(output); + } + TriStream.RestartStrip(); +} + +)EOSHADER"; + + std::string pixelNoPrim = R"EOSHADER( + +float4 main(in v2f IN) : SV_Target0 +{ + float3 color = IN.col.bgr; + color.r *= 0.5f; + return float4(color.bgr, 1.0f); +} + +)EOSHADER"; + + std::string pixelPrim = R"EOSHADER( + +float4 main(in prim2f IN) : SV_Target0 +{ + float r = IN.prim; + return float4(r / 4.0f, 1.0f, 0.0f, 1.0f); +} + +)EOSHADER"; + + int main() + { + // initialise, create window, create device, etc + if(!Init()) + return 3; + + ID3DBlobPtr vsBlob = Compile(D3DDefaultVertex, "main", "vs_4_0"); + ID3DBlobPtr gsNoPrimBlob = Compile(common + geomNoPrim, "main", "gs_5_0"); + ID3DBlobPtr gsPrimBlob = Compile(common + geomPrim, "main", "gs_5_0"); + ID3DBlobPtr psNoPrimBlob = Compile(common + pixelNoPrim, "main", "ps_5_0"); + ID3DBlobPtr psPrimBlob = Compile(common + pixelPrim, "main", "ps_5_0"); + + ID3D12ResourcePtr vb = MakeBuffer().Data(DefaultTri); + + ID3D12RootSignaturePtr sig = MakeSig({}); + ID3D12PipelineStatePtr pso[4] = { + MakePSO().RootSig(sig).InputLayout().VS(vsBlob).PS(psNoPrimBlob), + MakePSO().RootSig(sig).InputLayout().VS(vsBlob).PS(psPrimBlob), + MakePSO().RootSig(sig).InputLayout().VS(vsBlob).GS(gsNoPrimBlob).PS(psNoPrimBlob), + MakePSO().RootSig(sig).InputLayout().VS(vsBlob).GS(gsPrimBlob).PS(psPrimBlob)}; + + ResourceBarrier(vb, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); + + float halfWidth = (float)screenWidth * 0.5f; + float halfHeight = (float)screenHeight * 0.5f; + D3D12_VIEWPORT views[4] = {{0.0f, 0.0f, halfWidth, halfHeight, 0.0f, 1.0f}, + {halfWidth, 0.0f, halfWidth, halfHeight, 0.0f, 1.0f}, + {0.0f, halfHeight, halfWidth, halfHeight, 0.0f, 1.0f}, + {halfWidth, halfHeight, halfWidth, halfHeight, 0.0f, 1.0f}}; + while(Running()) + { + ID3D12GraphicsCommandListPtr cmd = GetCommandBuffer(); + Reset(cmd); + + ID3D12ResourcePtr bb = StartUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); + D3D12_CPU_DESCRIPTOR_HANDLE rtv = + MakeRTV(bb).Format(DXGI_FORMAT_R8G8B8A8_UNORM_SRGB).CreateCPU(0); + + cmd->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + IASetVertexBuffer(cmd, vb, sizeof(DefaultA2V), 0); + cmd->SetGraphicsRootSignature(sig); + OMSetRenderTargets(cmd, {rtv}, {}); + ClearRenderTargetView(cmd, rtv, {0.2f, 0.2f, 0.2f, 1.0f}); + + RSSetScissorRect(cmd, {0, 0, screenWidth, screenHeight}); + cmd->SetMarker(1, "Test", (UINT)strlen("Test")); + for(int i = 0; i < 4; ++i) + { + RSSetViewport(cmd, views[i]); + cmd->SetPipelineState(pso[i]); + cmd->DrawInstanced(3, 1, 0, 0); + } + + FinishUsingBackbuffer(cmd, D3D12_RESOURCE_STATE_RENDER_TARGET); + cmd->Close(); + Submit({cmd}); + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index c8a07f8cf..55ab53b98 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -139,6 +139,7 @@ + @@ -166,6 +167,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 13b0896f5..70ba8e466 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -427,6 +427,12 @@ D3D12\demos + + D3D11\demos + + + D3D12\demos + diff --git a/util/test/tests/D3D11/D3D11_PrimitiveID.py b/util/test/tests/D3D11/D3D11_PrimitiveID.py new file mode 100644 index 000000000..6f4980db7 --- /dev/null +++ b/util/test/tests/D3D11/D3D11_PrimitiveID.py @@ -0,0 +1,76 @@ +import renderdoc as rd +from typing import List +import rdtest + + +class D3D11_PrimitiveID(rdtest.TestCase): + demos_test_name = 'D3D11_PrimitiveID' + + def test_draw(self, draw: rd.DrawcallDescription, x, y, prim, expected_prim, expected_output): + self.controller.SetFrameEvent(draw.eventId, True) + pipe: rd.PipeState = self.controller.GetPipelineState() + + trace: rd.ShaderDebugTrace = self.controller.DebugPixel(x, y, rd.ReplayController.NoPreference, prim) + + sourceVars: List[rd.SourceVariableMapping] = list(trace.sourceVars) + cycles, variables = self.process_trace(trace) + + # Find the SV_PrimitiveID variable + primInput = [var for var in sourceVars if var.builtin == rd.ShaderBuiltin.PrimitiveIndex] + if primInput == []: + # If we didn't find it, then we should be expecting a 0 + if len(expected_prim) > 1 or expected_prim[0] is not 0: + rdtest.log.error("Expected prim {} at {},{} did not match actual prim {}.".format( + str(expected_prim), x, y, prim)) + return False + else: + # Look up the matching register in the inputs, and see if the expected value matches + inputs: List[rd.ShaderVariable] = list(trace.inputs) + primValue = [var for var in inputs if var.name == primInput[0].variables[0].name][0] + if primValue.value.uv[0] not in expected_prim: + rdtest.log.error("Expected prim {} at {},{} did not match actual prim {}.".format( + str(expected_prim), x, y, primValue.value.uv[0])) + return False + + # Compare shader debug output against an expected value instead of the RT's output, + # since we're testing overlapping primitives in a single draw + if expected_output is not None: + output = [var for var in sourceVars if var.builtin == rd.ShaderBuiltin.ColorOutput and var.offset == 0][0] + debugged = self.evalute_source_var(output, variables) + if debugged.value.fv[0:4] != expected_output: + rdtest.log.error("Expected value {} at {},{} did not match actual {}.".format( + expected_output, x, y, debugged.value.fv[0:4])) + return False + + rdtest.log.success("Test at {},{} matched as expected".format(x, y)) + return True + + def check_capture(self): + success = True + + # Jump to the draw + test_marker: rd.DrawcallDescription = self.find_draw("Test") + + # Draw 1: No GS, PS without prim + draw = test_marker.next + success &= self.test_draw(draw, 100, 80, rd.ReplayController.NoPreference, [0], [0, 1, 0, 1]) + + # Draw 2: No GS, PS with prim + draw = draw.next + success &= self.test_draw(draw, 300, 80, rd.ReplayController.NoPreference, [0], [0, 1, 0, 1]) + + # Draw 3: GS, PS without prim + draw = draw.next + success &= self.test_draw(draw, 125, 250, rd.ReplayController.NoPreference, [0], [0, 1, 0, 1]) + + # Draw 4: GS, PS with prim + draw = draw.next + success &= self.test_draw(draw, 325, 250, 2, [2], [0.5, 1, 0, 1]) + success &= self.test_draw(draw, 325, 250, 3, [3], [0.75, 1, 0, 1]) + # No expected output here, since it's nondeterministic which primitive gets selected + success &= self.test_draw(draw, 325, 250, rd.ReplayController.NoPreference, [2, 3], None) + + if not success: + raise rdtest.TestFailureException("Some tests were not as expected") + + rdtest.log.success("All tests matched") diff --git a/util/test/tests/D3D12/D3D12_PrimitiveID.py b/util/test/tests/D3D12/D3D12_PrimitiveID.py new file mode 100644 index 000000000..1720d4d98 --- /dev/null +++ b/util/test/tests/D3D12/D3D12_PrimitiveID.py @@ -0,0 +1,79 @@ +import renderdoc as rd +from typing import List +import rdtest + + +class D3D12_PrimitiveID(rdtest.TestCase): + demos_test_name = 'D3D12_PrimitiveID' + + def check_support(self): + return False, 'shader debugging is not yet enabled for D3D12' + + def test_draw(self, draw: rd.DrawcallDescription, x, y, prim, expected_prim, expected_output): + self.controller.SetFrameEvent(draw.eventId, True) + pipe: rd.PipeState = self.controller.GetPipelineState() + + trace: rd.ShaderDebugTrace = self.controller.DebugPixel(x, y, rd.ReplayController.NoPreference, prim) + + sourceVars: List[rd.SourceVariableMapping] = list(trace.sourceVars) + cycles, variables = self.process_trace(trace) + + # Find the SV_PrimitiveID variable + primInput = [var for var in sourceVars if var.builtin == rd.ShaderBuiltin.PrimitiveIndex] + if primInput == []: + # If we didn't find it, then we should be expecting a 0 + if len(expected_prim) > 1 or expected_prim[0] is not 0: + rdtest.log.error("Expected prim {} at {},{} did not match actual prim {}.".format( + str(expected_prim), x, y, prim)) + return False + else: + # Look up the matching register in the inputs, and see if the expected value matches + inputs: List[rd.ShaderVariable] = list(trace.inputs) + primValue = [var for var in inputs if var.name == primInput[0].variables[0].name][0] + if primValue.value.uv[0] not in expected_prim: + rdtest.log.error("Expected prim {} at {},{} did not match actual prim {}.".format( + str(expected_prim), x, y, primValue.value.uv[0])) + return False + + # Compare shader debug output against an expected value instead of the RT's output, + # since we're testing overlapping primitives in a single draw + if expected_output is not None: + output = [var for var in sourceVars if var.builtin == rd.ShaderBuiltin.ColorOutput and var.offset == 0][0] + debugged = self.evalute_source_var(output, variables) + if debugged.value.fv[0:4] != expected_output: + rdtest.log.error("Expected value {} at {},{} did not match actual {}.".format( + expected_output, x, y, debugged.value.fv[0:4])) + return False + + rdtest.log.success("Test at {},{} matched as expected".format(x, y)) + return True + + def check_capture(self): + success = True + + # Jump to the draw + test_marker: rd.DrawcallDescription = self.find_draw("Test") + + # Draw 1: No GS, PS without prim + draw = test_marker.next + success &= self.test_draw(draw, 100, 80, rd.ReplayController.NoPreference, [0], [0, 1, 0, 1]) + + # Draw 2: No GS, PS with prim + draw = draw.next + success &= self.test_draw(draw, 300, 80, rd.ReplayController.NoPreference, [0], [0, 1, 0, 1]) + + # Draw 3: GS, PS without prim + draw = draw.next + success &= self.test_draw(draw, 125, 250, rd.ReplayController.NoPreference, [0], [0, 1, 0, 1]) + + # Draw 4: GS, PS with prim + draw = draw.next + success &= self.test_draw(draw, 325, 250, 2, [2], [0.5, 1, 0, 1]) + success &= self.test_draw(draw, 325, 250, 3, [3], [0.75, 1, 0, 1]) + # No expected output here, since it's nondeterministic which primitive gets selected + success &= self.test_draw(draw, 325, 250, rd.ReplayController.NoPreference, [2, 3], None) + + if not success: + raise rdtest.TestFailureException("Some tests were not as expected") + + rdtest.log.success("All tests matched")