From d6c560415a64ad7656d1f4bdfa84996eb5a7ce03 Mon Sep 17 00:00:00 2001 From: baldurk Date: Thu, 20 Feb 2025 12:53:03 +0000 Subject: [PATCH] Add D3D11 handling of swizzled UAV loads/stores * Ensuring we properly clamp for non-typed UAV stores. Typed UAV stores are 'easy' because they must always write all components. --- renderdoc/driver/shaders/dxbc/dxbc_debug.cpp | 83 +++++++++++--- .../driver/shaders/dxbc/dxbc_stringise.cpp | 2 +- .../demos/d3d11/d3d11_shader_debug_zoo.cpp | 108 +++++++++++++++--- util/test/demos/d3d11/d3d11_test.cpp | 2 +- .../tests/D3D11/D3D11_Shader_Debug_Zoo.py | 68 ++++++----- 5 files changed, 197 insertions(+), 66 deletions(-) diff --git a/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp b/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp index 5381fa8c6..3a8efa34a 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_debug.cpp @@ -3342,6 +3342,7 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, firstElem = 0; if(resIndex > global.groupshared.size()) { + RDCERR("Invalid dxbc bytecode - garbage groupshared register being referenced"); numElems = 0; stride = 4; data = NULL; @@ -3439,34 +3440,86 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, { data += dataOffset; - int maxIndex = fmt.numComps; - uint32_t srcIdx = 1; - if(op.operation == OPCODE_STORE_STRUCTURED || op.operation == OPCODE_LD_STRUCTURED) + if(op.operation == OPCODE_LD_STRUCTURED) { srcIdx = 2; - maxIndex = (stride - structOffset) / sizeof(uint32_t); fmt.byteWidth = 4; + fmt.numComps = 4; + if(op.operands[0].comps[0] != 0xff && op.operands[0].comps[1] == 0xff && op.operands[0].comps[2] == 0xff && op.operands[0].comps[3] == 0xff) fmt.numComps = 1; + } + else if(op.operation == OPCODE_STORE_STRUCTURED) + { + srcIdx = 2; + fmt.byteWidth = 4; + + // set number of components based on output register write mask + if(op.operands[0].comps[1] == 0xff) + fmt.numComps = 1; + else if(op.operands[0].comps[2] == 0xff) + fmt.numComps = 2; + else if(op.operands[0].comps[3] == 0xff) + fmt.numComps = 3; + else + fmt.numComps = 4; + + // do not allow writing beyond the stride (we don't expect fxc to emit writes like this anyway) + fmt.numComps = RDCMIN(fmt.numComps, int((stride - structOffset) / sizeof(uint32_t))); + + for(int c = 0; c < 4; c++) + { + if(c < fmt.numComps) + RDCASSERTEQUAL(op.operands[0].comps[c], c); + else + RDCASSERT(op.operands[0].comps[c] == 0xff, c, op.operands[0].comps[c]); + } + fmt.fmt = CompType::UInt; } // raw loads/stores can come from any component (as long as it's within range of the data!) - if(op.operation == OPCODE_LD_RAW || op.operation == OPCODE_STORE_RAW) + else if(op.operation == OPCODE_LD_RAW) { fmt.byteWidth = 4; // normally we can read 4 elements fmt.numComps = 4; + // clamp to out of bounds based on numElems fmt.numComps = RDCMIN(fmt.numComps, int(numElems - elemIdx) / 4); - maxIndex = fmt.numComps; if(op.operands[0].comps[0] != 0xff && op.operands[0].comps[1] == 0xff && op.operands[0].comps[2] == 0xff && op.operands[0].comps[3] == 0xff) fmt.numComps = 1; + } + else if(op.operation == OPCODE_STORE_RAW) + { + fmt.byteWidth = 4; + + // set number of components based on output register write mask + if(op.operands[0].comps[1] == 0xff) + fmt.numComps = 1; + else if(op.operands[0].comps[2] == 0xff) + fmt.numComps = 2; + else if(op.operands[0].comps[3] == 0xff) + fmt.numComps = 3; + else + fmt.numComps = 4; + + // clamp to out of bounds based on numElems + fmt.numComps = RDCMIN(fmt.numComps, int(numElems - elemIdx) / 4); + + for(int c = 0; c < 4; c++) + { + if(c < fmt.numComps) + RDCASSERTEQUAL(op.operands[0].comps[c], c); + else + RDCASSERT(op.operands[0].comps[c] == 0xff, c, op.operands[0].comps[c]); + } + fmt.fmt = CompType::UInt; } @@ -3500,15 +3553,15 @@ void ThreadState::StepNext(ShaderDebugState *state, DebugAPIWrapper *apiWrapper, } else if(!Finished()) // helper/inactive pixels can't modify UAVs { - for(int i = 0; i < 4; i++) - { - uint8_t comp = op.operands[0].comps[i]; - // masks must be contiguous from x, if we reach the 'end' we're done - if(comp == 0xff || comp >= maxIndex) - break; - - TypedUAVStore(fmt, data, srcOpers[srcIdx]); - } + // from the spec on a typed UAV store: + // dstUAV always has a .xyzw write mask. + // All components must be written. + // + // for raw/structured stores, we've already set the format to be the right number of + // components + // + // so we can ignore op.operands[0].comps[] entirely and just write the data + TypedUAVStore(fmt, data, srcOpers[srcIdx]); if(gsm && state) { diff --git a/renderdoc/driver/shaders/dxbc/dxbc_stringise.cpp b/renderdoc/driver/shaders/dxbc/dxbc_stringise.cpp index 561bf2c9d..ad259d2b4 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_stringise.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_stringise.cpp @@ -538,7 +538,7 @@ rdcstr toString(const uint32_t values[], uint32_t numComps) if(floatOutput) { - str += ToStr(vf[0]); + str += StringFormat::Fmt("%0.6f", vf[0]); } else { diff --git a/util/test/demos/d3d11/d3d11_shader_debug_zoo.cpp b/util/test/demos/d3d11/d3d11_shader_debug_zoo.cpp index c082f3de7..e4216fc4e 100644 --- a/util/test/demos/d3d11/d3d11_shader_debug_zoo.cpp +++ b/util/test/demos/d3d11/d3d11_shader_debug_zoo.cpp @@ -261,8 +261,8 @@ float4 main(v2f IN) : SV_Target0 // use this to ensure the compiler doesn't know we're using fixed locations uint z = intval - IN.tri - 7; - return float4(asfloat(byterotest.Load(z+40).x), asfloat(byterotest.Load(z+44).x), - asfloat(byterotest.Load(z+48).x), float(byterotest.Load(z+4096).x)); + return float4(asfloat(byterotest.Load(z+88).x), asfloat(byterotest.Load(z+92).x), + asfloat(byterotest.Load(z+96).x), float(byterotest.Load(z+4096).x)); } // 4-uint load if(IN.tri == 37) @@ -280,7 +280,7 @@ float4 main(v2f IN) : SV_Target0 uint z = intval - IN.tri - 7; // test a 4-uint load - return asfloat(byterotest.Load4(z+40)); + return asfloat(byterotest.Load4(z+88)); } // 4-uint load out of view bounds if(IN.tri == 39) @@ -289,7 +289,7 @@ float4 main(v2f IN) : SV_Target0 uint z = intval - IN.tri - 7; // test a 4-uint load - return asfloat(byterotest.Load4(z+48)); + return asfloat(byterotest.Load4(z+96)); } // mis-aligned store @@ -325,13 +325,13 @@ float4 main(v2f IN) : SV_Target0 uint z = intval - IN.tri - 7; uint z2 = uint(zero); - byterwtest.Store(z+40, asuint(1.2345f)); - byterwtest.Store(z+44, asuint(9.8765f)); - byterwtest.Store(z+48, asuint(1.81818f)); + byterwtest.Store(z+88, asuint(1.2345f)); + byterwtest.Store(z+92, asuint(9.8765f)); + byterwtest.Store(z+96, asuint(1.81818f)); byterwtest.Store(z+4096, asuint(5.55555f)); - return float4(asfloat(byterwtest.Load(z2+40).x), asfloat(byterwtest.Load(z2+44).x), - asfloat(byterwtest.Load(z2+48).x), float(byterwtest.Load(z2+4096).x)); + return float4(asfloat(byterwtest.Load(z2+88).x), asfloat(byterwtest.Load(z2+92).x), + asfloat(byterwtest.Load(z2+96).x), float(byterwtest.Load(z2+4096).x)); } // 4-uint store if(IN.tri == 43) @@ -342,7 +342,7 @@ float4 main(v2f IN) : SV_Target0 byterwtest.Store4(z+24, uint4(99, 88, 77, 66)); - return asfloat(byterotest.Load4(z2+24)); + return asfloat(byterwtest.Load4(z2+24)); } // 4-uint store crossing view bounds if(IN.tri == 44) @@ -351,9 +351,9 @@ float4 main(v2f IN) : SV_Target0 uint z = intval - IN.tri - 7; uint z2 = uint(zero); - byterwtest.Store4(z+40, uint4(99, 88, 77, 66)); + byterwtest.Store4(z+88, uint4(99, 88, 77, 66)); - return asfloat(byterotest.Load4(z2+40)); + return asfloat(byterwtest.Load4(z2+88)); } // 4-uint store out of view bounds if(IN.tri == 45) @@ -362,9 +362,9 @@ float4 main(v2f IN) : SV_Target0 uint z = intval - IN.tri - 7; uint z2 = uint(zero); - byterwtest.Store4(z+48, uint4(99, 88, 77, 66)); + byterwtest.Store4(z+96, uint4(99, 88, 77, 66)); - return asfloat(byterotest.Load4(z2+48)); + return asfloat(byterwtest.Load4(z2+96)); } // test reading/writing structured data @@ -751,6 +751,71 @@ float4 main(v2f IN) : SV_Target0 float2 uv = posone * float2(0.55f, 0.48f); return smiley.SampleBias(unboundsamp, uv, 0.5f); } + // test UAV loads and stores only write the data they should +#ifdef TYPED_UAV_EXT + if(IN.tri == 93) + { + // typed UAVs have to write all components so this is a fairly degenerate test + typedrwtest[uint(zero) + 20] = 9.99999f.xxxx; + return typedrwtest[uint(posone) + 19]; + } +#endif + if(IN.tri == 94) + { + uint z = intval - IN.tri - 7; + uint z2 = uint(zero); + uint z3 = uint(posone) - 1; + + // fill the first component, to ensure we return the real result and not a trashed-zero + byterwtest.Store(z3+48, asuint(1.1f)); + + // unaligned raw store of less than float4 + byterwtest.Store3(z+52, asuint(float3(9.9f, 8.8f, 7.7f))); + + return asfloat(byterwtest.Load4(z2+48)); + } + if(IN.tri == 95) + { + uint z = intval - IN.tri - 7; + uint z2 = uint(zero); + uint z3 = uint(posone) - 1; + + // fill the last component, to ensure we return the real result and not a trashed-zero + byterwtest.Store(z3+44, asuint(1.1f)); + + // unaligned raw store of less than float4 + byterwtest.Store3(z+32, asuint(float3(9.9f, 8.8f, 7.7f))); + + return asfloat(byterwtest.Load4(z2+32)); + } + if(IN.tri == 96) + { + uint z = intval - IN.tri - 7; + uint z2 = uint(zero); + uint z3 = uint(posone) - 1; + + // fill the last component, to ensure we return the real result and not a trashed-zero + structrwtest[z+4].b.w = 1.1f; + + // aligned store of float3 + structrwtest[z3+4].b.xzy = float3(1.234f, 5.678f, 9.999f); + + return structrwtest[z2+4].b; + } + if(IN.tri == 97) + { + uint z = intval - IN.tri - 7; + uint z2 = uint(zero); + uint z3 = uint(posone) - 1; + + // fill the first component, to ensure we return the real result and not a trashed-zero + structrwtest[z+5].b.x = 1.1f; + + // unaligned store of float3 + structrwtest[z3+5].b.wzy = float3(1.234f, 5.678f, 9.999f); + + return structrwtest[z2+5].b; + } return float4(0.4f, 0.4f, 0.4f, 0.4f); } @@ -943,7 +1008,6 @@ float4 main(v2f IN, uint samp : SV_SampleIndex) : SV_Target0 common += "\n#define TYPED_UAV_EXT 1\n"; ID3DBlobPtr vsblob = Compile(common + vertex, "main", "vs_5_0"); - ID3DBlobPtr psblob = Compile(common + pixel, "main", "ps_5_0"); D3D11_INPUT_ELEMENT_DESC layoutdesc[] = { { @@ -989,12 +1053,13 @@ float4 main(v2f IN, uint samp : SV_SampleIndex) : SV_Target0 vsblob->GetBufferSize(), &layout)); ID3D11VertexShaderPtr vs = CreateVS(vsblob); - ID3D11PixelShaderPtr ps = CreatePS(psblob); + ID3D11PixelShaderPtr ps = CreatePS(Compile(common + pixel, "main", "ps_5_0", true)); + ID3D11PixelShaderPtr psopt = CreatePS(Compile(common + pixel, "main", "ps_5_0", false)); ID3D11PixelShaderPtr flowps = CreatePS(Compile(common + flowPixel, "main", "ps_5_0")); static const uint32_t texDim = AlignUp(numTests, 64U) * 4; - ID3D11Texture2DPtr fltTex = MakeTexture(DXGI_FORMAT_R32G32B32A32_FLOAT, texDim, 8).RTV(); + ID3D11Texture2DPtr fltTex = MakeTexture(DXGI_FORMAT_R32G32B32A32_FLOAT, texDim, 12).RTV(); ID3D11RenderTargetViewPtr fltRT = MakeRTV(fltTex); float triWidth = 8.0f / float(texDim); @@ -1035,7 +1100,7 @@ float4 main(v2f IN, uint samp : SV_SampleIndex) : SV_Target0 ID3D11BufferPtr rawBuf2 = MakeBuffer().UAV().ByteAddressed().Size(1024); ID3D11UnorderedAccessViewPtr rawuav = - MakeUAV(rawBuf2).Format(DXGI_FORMAT_R32_TYPELESS).FirstElement(4).NumElements(12); + MakeUAV(rawBuf2).Format(DXGI_FORMAT_R32_TYPELESS).FirstElement(4).NumElements(24); float structdata[220]; for(int i = 0; i < 220; i++) @@ -1050,7 +1115,7 @@ float4 main(v2f IN, uint samp : SV_SampleIndex) : SV_Target0 ID3D11BufferPtr structBuf2 = MakeBuffer().UAV().Structured(11 * sizeof(float)).Size(880); ID3D11UnorderedAccessViewPtr structuav = - MakeUAV(structBuf2).Format(DXGI_FORMAT_UNKNOWN).FirstElement(3).NumElements(5); + MakeUAV(structBuf2).Format(DXGI_FORMAT_UNKNOWN).FirstElement(3).NumElements(6); ID3D11BufferPtr rgbuavBuf = MakeBuffer().UAV().Data(structdata); ID3D11UnorderedAccessViewPtr typeuav = MakeUAV(rgbuavBuf).Format(DXGI_FORMAT_R32G32B32A32_FLOAT); @@ -1121,6 +1186,11 @@ float4 main(v2f IN, uint samp : SV_SampleIndex) : SV_Target0 ctx->DrawInstanced(3, numTests, 0, 0); RSSetViewport({0.0f, 4.0f, (float)texDim, 4.0f, 0.0f, 1.0f}); + ctx->PSSetShader(psopt, NULL, 0); + setMarker("Optimised Test"); + ctx->DrawInstanced(3, numTests, 0, 0); + + RSSetViewport({0.0f, 8.0f, (float)texDim, 4.0f, 0.0f, 1.0f}); ctx->PSSetShader(flowps, NULL, 0); setMarker("Flow Test"); ctx->DrawInstanced(3, 1, 0, 0); diff --git a/util/test/demos/d3d11/d3d11_test.cpp b/util/test/demos/d3d11/d3d11_test.cpp index 35f023153..8960e43b1 100644 --- a/util/test/demos/d3d11/d3d11_test.cpp +++ b/util/test/demos/d3d11/d3d11_test.cpp @@ -642,7 +642,7 @@ ID3DBlobPtr D3D11GraphicsTest::Compile(std::string src, std::string entry, std:: if(skipoptimise) flags |= D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_OPTIMIZATION_LEVEL0; else - flags |= D3DCOMPILE_OPTIMIZATION_LEVEL0; + flags |= D3DCOMPILE_OPTIMIZATION_LEVEL1; HRESULT hr = dyn_D3DCompile(src.c_str(), src.length(), "", NULL, NULL, entry.c_str(), profile.c_str(), flags, 0, &blob, &error); diff --git a/util/test/tests/D3D11/D3D11_Shader_Debug_Zoo.py b/util/test/tests/D3D11/D3D11_Shader_Debug_Zoo.py index cd57ba3b2..2a62da4ed 100644 --- a/util/test/tests/D3D11/D3D11_Shader_Debug_Zoo.py +++ b/util/test/tests/D3D11/D3D11_Shader_Debug_Zoo.py @@ -7,43 +7,51 @@ class D3D11_Shader_Debug_Zoo(rdtest.TestCase): demos_test_name = 'D3D11_Shader_Debug_Zoo' def check_capture(self): - # Jump to the action - action = self.find_action("Main Test").next - undefined_tests = [int(test) for test in self.find_action("Undefined tests: ").customName.split(" ")[2:]] - self.controller.SetFrameEvent(action.eventId, False) + # Jump to the action + for idx, action in enumerate([self.find_action("Main Test"), self.find_action("Optimised Test")]): + name = action.customName - pipe: rd.PipeState = self.controller.GetPipelineState() + action = action.next - failed = False + self.controller.SetFrameEvent(action.eventId, False) - # Loop over every test - rdtest.log.begin_section("General tests") - for test in range(action.numInstances): - # Debug the shader - trace: rd.ShaderDebugTrace = self.controller.DebugPixel(4 * test, 0, rd.DebugPixelInputs()) + pipe: rd.PipeState = self.controller.GetPipelineState() - cycles, variables = self.process_trace(trace) + failed = False - output = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) + # Loop over every test + rdtest.log.begin_section(name) + for test in range(action.numInstances): + # Debug the shader + trace: rd.ShaderDebugTrace = self.controller.DebugPixel(4 * test, 4 * idx, rd.DebugPixelInputs()) - debugged = self.evaluate_source_var(output, variables) + if trace.debugger is None: + rdtest.log.error("Test {} failed to debug.".format(test)) + self.controller.FreeTrace(trace) + continue - try: - self.check_pixel_value(pipe.GetOutputTargets()[0].resource, 4 * test, 0, debugged.value.f32v[0:4]) - except rdtest.TestFailureException as ex: - if test in undefined_tests: - rdtest.log.comment("Undefined test {} did not match. {}".format(test, str(ex))) - else: - rdtest.log.error("Test {} did not match. {}".format(test, str(ex))) - failed = True - continue - finally: - self.controller.FreeTrace(trace) + cycles, variables = self.process_trace(trace) - rdtest.log.success("Test {} matched as expected".format(test)) - rdtest.log.end_section("General tests") + output = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) + + debugged = self.evaluate_source_var(output, variables) + + try: + self.check_pixel_value(pipe.GetOutputTargets()[0].resource, 4 * test, 4 * idx, debugged.value.f32v[0:4]) + except rdtest.TestFailureException as ex: + if test in undefined_tests: + rdtest.log.comment("Undefined test {} did not match. {}".format(test, str(ex))) + else: + rdtest.log.error("Test {} did not match. {}".format(test, str(ex))) + failed = True + continue + finally: + self.controller.FreeTrace(trace) + + rdtest.log.success("Test {} matched as expected".format(test)) + rdtest.log.end_section(name) rdtest.log.begin_section("Flow tests") action = self.find_action("Flow Test").next @@ -51,7 +59,7 @@ class D3D11_Shader_Debug_Zoo(rdtest.TestCase): pipe: rd.PipeState = self.controller.GetPipelineState() # Debug the shader - trace: rd.ShaderDebugTrace = self.controller.DebugPixel(0, 4, rd.DebugPixelInputs()) + trace: rd.ShaderDebugTrace = self.controller.DebugPixel(0, 8, rd.DebugPixelInputs()) cycles, variables = self.process_trace(trace) @@ -60,8 +68,8 @@ class D3D11_Shader_Debug_Zoo(rdtest.TestCase): debugged = self.evaluate_source_var(output, variables) try: - self.check_pixel_value(pipe.GetOutputTargets()[0].resource, 0, 4, debugged.value.f32v[0:4]) - self.check_pixel_value(pipe.GetOutputTargets()[0].resource, 0, 4, [9.0, 66.0, 4.0, 18.0]) + self.check_pixel_value(pipe.GetOutputTargets()[0].resource, 0, 8, debugged.value.f32v[0:4]) + self.check_pixel_value(pipe.GetOutputTargets()[0].resource, 0, 8, [9.0, 66.0, 4.0, 18.0]) except rdtest.TestFailureException as ex: raise rdtest.TestFailureException("Flow test did not match. {}".format(str(ex))) finally: