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.
This commit is contained in:
baldurk
2025-02-21 13:46:58 +00:00
parent 5e3bfb29b2
commit d6c560415a
5 changed files with 197 additions and 66 deletions
+68 -15
View File
@@ -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)
{
@@ -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
{
@@ -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);
+1 -1
View File
@@ -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);
+38 -30
View File
@@ -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: