Move code to gather PSInput definitions to a separate function

The code to get initial values for PS debugging includes a step to
traverse the shader stage's inputs and previous stage's outputs, to
define a struct that is used in a replacement pixel shader to collect
the initial values. This logic is API-agnostic and can be moved to a
DXBC file to allow D3D12 to use it.
This commit is contained in:
Steve Karolewics
2019-11-25 09:54:08 +00:00
committed by Baldur Karlsson
parent b222d6388c
commit 9ff7b04f6f
3 changed files with 320 additions and 288 deletions
+7 -288
View File
@@ -2161,298 +2161,17 @@ ShaderDebugTrace D3D11Replay::DebugPixel(uint32_t eventId, uint32_t x, uint32_t
prevdxbc = ds->GetDXBC();
if(prevdxbc == NULL && vs != NULL)
prevdxbc = vs->GetDXBC();
RDCASSERT(prevdxbc);
std::vector<PSInputElement> initialValues;
std::string extractHlsl = "struct PSInput\n{\n";
std::vector<std::string> floatInputs;
std::vector<std::string> inputVarNames;
std::string extractHlsl;
int structureStride = 0;
if(dxbc->GetReflection()->InputSig.empty())
{
extractHlsl += "float4 input_dummy : SV_Position;\n";
initialValues.push_back(PSInputElement(-1, 0, 4, ShaderBuiltin::Undefined, true));
structureStride += 4;
}
std::vector<std::string> floatInputs;
// name, pair<start semantic index, end semantic index>
std::vector<rdcpair<rdcstr, rdcpair<uint32_t, uint32_t>>> arrays;
std::vector<std::string> inputVarNames;
uint32_t nextreg = 0;
inputVarNames.resize(dxbc->GetReflection()->InputSig.size());
for(size_t i = 0; i < dxbc->GetReflection()->InputSig.size(); i++)
{
const SigParameter &sig = dxbc->GetReflection()->InputSig[i];
extractHlsl += " ";
bool included = true;
// handled specially to account for SV_ ordering
if(sig.systemValue == ShaderBuiltin::PrimitiveIndex ||
sig.systemValue == ShaderBuiltin::MSAACoverage ||
sig.systemValue == ShaderBuiltin::IsFrontFace ||
sig.systemValue == ShaderBuiltin::MSAASampleIndex)
{
extractHlsl += "//";
included = false;
}
int arrayIndex = -1;
for(size_t a = 0; a < arrays.size(); a++)
{
if(sig.semanticName == arrays[a].first && arrays[a].second.first <= sig.semanticIndex &&
arrays[a].second.second >= sig.semanticIndex)
{
extractHlsl += "//";
included = false;
arrayIndex = sig.semanticIndex - arrays[a].second.first;
}
}
int missingreg = int(sig.regIndex) - int(nextreg);
// fill in holes from output sig of previous shader if possible, to try and
// ensure the same register order
for(int dummy = 0; dummy < missingreg; dummy++)
{
bool filled = false;
if(prevdxbc)
{
for(size_t os = 0; os < prevdxbc->GetReflection()->OutputSig.size(); os++)
{
if(prevdxbc->GetReflection()->OutputSig[os].regIndex == nextreg + dummy)
{
filled = true;
if(prevdxbc->GetReflection()->OutputSig[os].compType == CompType::Float)
extractHlsl += "float";
else if(prevdxbc->GetReflection()->OutputSig[os].compType == CompType::SInt)
extractHlsl += "int";
else if(prevdxbc->GetReflection()->OutputSig[os].compType == CompType::UInt)
extractHlsl += "uint";
else
RDCERR("Unexpected input signature type: %d",
prevdxbc->GetReflection()->OutputSig[os].compType);
int numCols = (prevdxbc->GetReflection()->OutputSig[os].regChannelMask & 0x1 ? 1 : 0) +
(prevdxbc->GetReflection()->OutputSig[os].regChannelMask & 0x2 ? 1 : 0) +
(prevdxbc->GetReflection()->OutputSig[os].regChannelMask & 0x4 ? 1 : 0) +
(prevdxbc->GetReflection()->OutputSig[os].regChannelMask & 0x8 ? 1 : 0);
structureStride += 4 * numCols;
initialValues.push_back(PSInputElement(-1, 0, numCols, ShaderBuiltin::Undefined, true));
std::string name = prevdxbc->GetReflection()->OutputSig[os].semanticIdxName;
extractHlsl += ToStr((uint32_t)numCols) + " input_" + name + " : " + name + ";\n";
}
}
}
if(!filled)
{
std::string dummy_reg = "dummy_register";
dummy_reg += ToStr((uint32_t)nextreg + dummy);
extractHlsl += "float4 var_" + dummy_reg + " : semantic_" + dummy_reg + ";\n";
initialValues.push_back(PSInputElement(-1, 0, 4, ShaderBuiltin::Undefined, true));
structureStride += 4 * sizeof(float);
}
}
nextreg = sig.regIndex + 1;
if(sig.compType == CompType::Float)
{
// if we're packed with ints on either side, we must be nointerpolation
bool nointerp = false;
for(size_t j = 0; j < dxbc->GetReflection()->InputSig.size(); j++)
{
if(sig.regIndex == dxbc->GetReflection()->InputSig[j].regIndex &&
dxbc->GetReflection()->InputSig[j].compType != CompType::Float)
{
nointerp = true;
break;
}
}
if(nointerp)
extractHlsl += "nointerpolation ";
extractHlsl += "float";
}
else if(sig.compType == CompType::SInt)
extractHlsl += "nointerpolation int";
else if(sig.compType == CompType::UInt)
extractHlsl += "nointerpolation uint";
else
RDCERR("Unexpected input signature type: %d", sig.compType);
int numCols = (sig.regChannelMask & 0x1 ? 1 : 0) + (sig.regChannelMask & 0x2 ? 1 : 0) +
(sig.regChannelMask & 0x4 ? 1 : 0) + (sig.regChannelMask & 0x8 ? 1 : 0);
std::string name = sig.semanticIdxName;
// arrays of interpolators are handled really weirdly. They use cbuffer
// packing rules where each new value is in a new register (rather than
// e.g. 2 x float2 in a single register), but that's pointless because
// you can't dynamically index into input registers.
// If we declare those elements as a non-array, the float2s or floats
// will be packed into registers and won't match up to the previous
// shader.
// HOWEVER to add an extra bit of fun, fxc will happily pack other
// parameters not in the array into spare parts of the registers.
//
// So I think the upshot is that we can detect arrays reliably by
// whenever we encounter a float or float2 at the start of a register,
// search forward to see if the next register has an element that is the
// same semantic name and one higher semantic index. If so, there's an
// array, so keep searching to enumerate its length.
// I think this should be safe if the packing just happens to place those
// registers together.
int arrayLength = 0;
if(included && numCols <= 2 && (sig.regChannelMask & 0x1))
{
uint32_t nextIdx = sig.semanticIndex + 1;
for(size_t j = i + 1; j < dxbc->GetReflection()->InputSig.size(); j++)
{
// if we've found the 'next' semantic
if(sig.semanticName == dxbc->GetReflection()->InputSig[j].semanticName &&
nextIdx == dxbc->GetReflection()->InputSig[j].semanticIndex)
{
int jNumCols = (sig.regChannelMask & 0x1 ? 1 : 0) + (sig.regChannelMask & 0x2 ? 1 : 0) +
(sig.regChannelMask & 0x4 ? 1 : 0) + (sig.regChannelMask & 0x8 ? 1 : 0);
// if it's the same size, and it's at the start of the next register
if(jNumCols == numCols && dxbc->GetReflection()->InputSig[j].regChannelMask <= 0x3)
{
if(arrayLength == 0)
arrayLength = 2;
else
arrayLength++;
// continue searching now
nextIdx++;
j = i + 1;
continue;
}
}
}
if(arrayLength > 0)
arrays.push_back(make_rdcpair(sig.semanticName, make_rdcpair(sig.semanticIndex, nextIdx - 1)));
}
if(included)
{
// in UAV structs, arrays are packed tightly, so just multiply by arrayLength
structureStride += 4 * numCols * RDCMAX(1, arrayLength);
}
// as another side effect of the above, an element declared as a 1-length array won't be
// detected but it WILL be put in its own register (not packed together), so detect this
// case too.
// Note we have to search *backwards* because we need to know if this register should have
// been packed into the previous register, but wasn't. float/float2 can be packed after an
// array just fine.
if(included && i > 0 && arrayLength == 0 && numCols <= 2 && sig.regChannelMask <= 0x3)
{
const SigParameter &prev = dxbc->GetReflection()->InputSig[i - 1];
if(prev.regIndex != sig.regIndex && prev.compCount <= 2 && prev.regChannelMask <= 0x3)
arrayLength = 1;
}
// The compiler is also really annoying and will go to great lengths to rearrange elements
// and screw up our declaration, to pack things together. E.g.:
// float2 a : TEXCOORD1;
// float4 b : TEXCOORD2;
// float4 c : TEXCOORD3;
// float2 d : TEXCOORD4;
// the compiler will move d up and pack it into the last two components of a.
// To prevent this, we look forward and backward to check that we aren't expecting to pack
// with anything, and if not then we just make it a 1-length array to ensure no packing.
// Note the regChannelMask & 0x1 means it is using .x, so it's not the tail-end of a pack
if(included && arrayLength == 0 && numCols <= 2 && (sig.regChannelMask & 0x1))
{
if(i == dxbc->GetReflection()->InputSig.size() - 1)
{
// the last element is never packed
arrayLength = 1;
}
else
{
// if the next reg is using .x, it wasn't packed with us
if(dxbc->GetReflection()->InputSig[i + 1].regChannelMask & 0x1)
arrayLength = 1;
}
}
extractHlsl += ToStr((uint32_t)numCols) + " input_" + name;
if(arrayLength > 0)
extractHlsl += "[" + ToStr(arrayLength) + "]";
extractHlsl += " : " + name;
inputVarNames[i] = "input_" + name;
if(arrayLength > 0)
inputVarNames[i] += StringFormat::Fmt("[%d]", RDCMAX(0, arrayIndex));
if(included && sig.compType == CompType::Float)
{
if(arrayLength == 0)
{
floatInputs.push_back("input_" + name);
}
else
{
for(int a = 0; a < arrayLength; a++)
floatInputs.push_back("input_" + name + "[" + ToStr(a) + "]");
}
}
extractHlsl += ";\n";
int firstElem = sig.regChannelMask & 0x1 ? 0 : sig.regChannelMask & 0x2
? 1
: sig.regChannelMask & 0x4
? 2
: sig.regChannelMask & 0x8 ? 3 : -1;
// arrays get added all at once (because in the struct data, they are contiguous even if
// in the input signature they're not).
if(arrayIndex < 0)
{
if(arrayLength == 0)
{
initialValues.push_back(
PSInputElement(sig.regIndex, firstElem, numCols, sig.systemValue, included));
}
else
{
for(int a = 0; a < arrayLength; a++)
{
initialValues.push_back(
PSInputElement(sig.regIndex + a, firstElem, numCols, sig.systemValue, included));
}
}
}
}
extractHlsl += "};\n\n";
ShaderDebug::GatherPSInputDataForInitialValues(*dxbc->GetReflection(), *prevdxbc->GetReflection(),
initialValues, floatInputs, inputVarNames,
extractHlsl, structureStride);
uint32_t overdrawLevels = 100; // maximum number of overdraw levels
@@ -4535,6 +4535,312 @@ void LookupSRVFormatFromShaderReflection(const DXBC::Reflection &reflection,
}
}
void GatherPSInputDataForInitialValues(const DXBC::Reflection &psDxbc,
const DXBC::Reflection &prevStageDxbc,
std::vector<PSInputElement> &initialValues,
std::vector<std::string> &floatInputs,
std::vector<std::string> &inputVarNames,
std::string &psInputDefinition, int &structureStride)
{
// When debugging a pixel shader, we need to get the initial values of each pixel shader
// input for the pixel that we are debugging, from whichever the previous shader stage was
// configured in the pipeline. This function returns the input element definitions, other
// associated data, the HLSL definition to use when gathering pixel shader initial values,
// and the stride of that HLSL structure.
// This function does not provide any HLSL definitions for additional metadata that may be
// needed for gathering initial values, such as primitive ID, and also does not provide the
// shader function body.
initialValues.clear();
floatInputs.clear();
inputVarNames.clear();
psInputDefinition = "struct PSInput\n{\n";
structureStride = 0;
if(psDxbc.InputSig.empty())
{
psInputDefinition += "float4 input_dummy : SV_Position;\n";
initialValues.push_back(PSInputElement(-1, 0, 4, ShaderBuiltin::Undefined, true));
structureStride += 4;
}
// name, pair<start semantic index, end semantic index>
std::vector<rdcpair<rdcstr, rdcpair<uint32_t, uint32_t>>> arrays;
uint32_t nextreg = 0;
size_t numInputs = psDxbc.InputSig.size();
inputVarNames.resize(numInputs);
for(size_t i = 0; i < numInputs; i++)
{
const SigParameter &sig = psDxbc.InputSig[i];
psInputDefinition += " ";
bool included = true;
// handled specially to account for SV_ ordering
if(sig.systemValue == ShaderBuiltin::PrimitiveIndex ||
sig.systemValue == ShaderBuiltin::MSAACoverage ||
sig.systemValue == ShaderBuiltin::IsFrontFace ||
sig.systemValue == ShaderBuiltin::MSAASampleIndex)
{
psInputDefinition += "//";
included = false;
}
int arrayIndex = -1;
for(size_t a = 0; a < arrays.size(); a++)
{
if(sig.semanticName == arrays[a].first && arrays[a].second.first <= sig.semanticIndex &&
arrays[a].second.second >= sig.semanticIndex)
{
psInputDefinition += "//";
included = false;
arrayIndex = sig.semanticIndex - arrays[a].second.first;
}
}
int missingreg = int(sig.regIndex) - int(nextreg);
// fill in holes from output sig of previous shader if possible, to try and
// ensure the same register order
for(int dummy = 0; dummy < missingreg; dummy++)
{
bool filled = false;
size_t numPrevOutputs = prevStageDxbc.OutputSig.size();
for(size_t os = 0; os < numPrevOutputs; os++)
{
if(prevStageDxbc.OutputSig[os].regIndex == nextreg + dummy)
{
filled = true;
if(prevStageDxbc.OutputSig[os].compType == CompType::Float)
psInputDefinition += "float";
else if(prevStageDxbc.OutputSig[os].compType == CompType::SInt)
psInputDefinition += "int";
else if(prevStageDxbc.OutputSig[os].compType == CompType::UInt)
psInputDefinition += "uint";
else
RDCERR("Unexpected input signature type: %d", prevStageDxbc.OutputSig[os].compType);
int numCols = (prevStageDxbc.OutputSig[os].regChannelMask & 0x1 ? 1 : 0) +
(prevStageDxbc.OutputSig[os].regChannelMask & 0x2 ? 1 : 0) +
(prevStageDxbc.OutputSig[os].regChannelMask & 0x4 ? 1 : 0) +
(prevStageDxbc.OutputSig[os].regChannelMask & 0x8 ? 1 : 0);
structureStride += 4 * numCols;
initialValues.push_back(PSInputElement(-1, 0, numCols, ShaderBuiltin::Undefined, true));
std::string name = prevStageDxbc.OutputSig[os].semanticIdxName;
psInputDefinition += ToStr((uint32_t)numCols) + " input_" + name + " : " + name + ";\n";
}
}
if(!filled)
{
std::string dummy_reg = "dummy_register";
dummy_reg += ToStr((uint32_t)nextreg + dummy);
psInputDefinition += "float4 var_" + dummy_reg + " : semantic_" + dummy_reg + ";\n";
initialValues.push_back(PSInputElement(-1, 0, 4, ShaderBuiltin::Undefined, true));
structureStride += 4 * sizeof(float);
}
}
nextreg = sig.regIndex + 1;
if(sig.compType == CompType::Float)
{
// if we're packed with ints on either side, we must be nointerpolation
bool nointerp = false;
for(size_t j = 0; j < numInputs; j++)
{
if(sig.regIndex == psDxbc.InputSig[j].regIndex &&
psDxbc.InputSig[j].compType != CompType::Float)
{
nointerp = true;
break;
}
}
if(nointerp)
psInputDefinition += "nointerpolation ";
psInputDefinition += "float";
}
else if(sig.compType == CompType::SInt)
psInputDefinition += "nointerpolation int";
else if(sig.compType == CompType::UInt)
psInputDefinition += "nointerpolation uint";
else
RDCERR("Unexpected input signature type: %d", sig.compType);
int numCols = (sig.regChannelMask & 0x1 ? 1 : 0) + (sig.regChannelMask & 0x2 ? 1 : 0) +
(sig.regChannelMask & 0x4 ? 1 : 0) + (sig.regChannelMask & 0x8 ? 1 : 0);
std::string name = sig.semanticIdxName;
// arrays of interpolators are handled really weirdly. They use cbuffer
// packing rules where each new value is in a new register (rather than
// e.g. 2 x float2 in a single register), but that's pointless because
// you can't dynamically index into input registers.
// If we declare those elements as a non-array, the float2s or floats
// will be packed into registers and won't match up to the previous
// shader.
// HOWEVER to add an extra bit of fun, fxc will happily pack other
// parameters not in the array into spare parts of the registers.
//
// So I think the upshot is that we can detect arrays reliably by
// whenever we encounter a float or float2 at the start of a register,
// search forward to see if the next register has an element that is the
// same semantic name and one higher semantic index. If so, there's an
// array, so keep searching to enumerate its length.
// I think this should be safe if the packing just happens to place those
// registers together.
int arrayLength = 0;
if(included && numCols <= 2 && (sig.regChannelMask & 0x1))
{
uint32_t nextIdx = sig.semanticIndex + 1;
for(size_t j = i + 1; j < numInputs; j++)
{
// if we've found the 'next' semantic
if(sig.semanticName == psDxbc.InputSig[j].semanticName &&
nextIdx == psDxbc.InputSig[j].semanticIndex)
{
int jNumCols = (sig.regChannelMask & 0x1 ? 1 : 0) + (sig.regChannelMask & 0x2 ? 1 : 0) +
(sig.regChannelMask & 0x4 ? 1 : 0) + (sig.regChannelMask & 0x8 ? 1 : 0);
// if it's the same size, and it's at the start of the next register
if(jNumCols == numCols && psDxbc.InputSig[j].regChannelMask <= 0x3)
{
if(arrayLength == 0)
arrayLength = 2;
else
arrayLength++;
// continue searching now
nextIdx++;
j = i + 1;
continue;
}
}
}
if(arrayLength > 0)
arrays.push_back(make_rdcpair(sig.semanticName, make_rdcpair(sig.semanticIndex, nextIdx - 1)));
}
if(included)
{
// in UAV structs, arrays are packed tightly, so just multiply by arrayLength
structureStride += 4 * numCols * RDCMAX(1, arrayLength);
}
// as another side effect of the above, an element declared as a 1-length array won't be
// detected but it WILL be put in its own register (not packed together), so detect this
// case too.
// Note we have to search *backwards* because we need to know if this register should have
// been packed into the previous register, but wasn't. float/float2 can be packed after an
// array just fine.
if(included && i > 0 && arrayLength == 0 && numCols <= 2 && sig.regChannelMask <= 0x3)
{
const SigParameter &prev = psDxbc.InputSig[i - 1];
if(prev.regIndex != sig.regIndex && prev.compCount <= 2 && prev.regChannelMask <= 0x3)
arrayLength = 1;
}
// The compiler is also really annoying and will go to great lengths to rearrange elements
// and screw up our declaration, to pack things together. E.g.:
// float2 a : TEXCOORD1;
// float4 b : TEXCOORD2;
// float4 c : TEXCOORD3;
// float2 d : TEXCOORD4;
// the compiler will move d up and pack it into the last two components of a.
// To prevent this, we look forward and backward to check that we aren't expecting to pack
// with anything, and if not then we just make it a 1-length array to ensure no packing.
// Note the regChannelMask & 0x1 means it is using .x, so it's not the tail-end of a pack
if(included && arrayLength == 0 && numCols <= 2 && (sig.regChannelMask & 0x1))
{
if(i == numInputs - 1)
{
// the last element is never packed
arrayLength = 1;
}
else
{
// if the next reg is using .x, it wasn't packed with us
if(psDxbc.InputSig[i + 1].regChannelMask & 0x1)
arrayLength = 1;
}
}
psInputDefinition += ToStr((uint32_t)numCols) + " input_" + name;
if(arrayLength > 0)
psInputDefinition += "[" + ToStr(arrayLength) + "]";
psInputDefinition += " : " + name;
inputVarNames[i] = "input_" + name;
if(arrayLength > 0)
inputVarNames[i] += StringFormat::Fmt("[%d]", RDCMAX(0, arrayIndex));
if(included && sig.compType == CompType::Float)
{
if(arrayLength == 0)
{
floatInputs.push_back("input_" + name);
}
else
{
for(int a = 0; a < arrayLength; a++)
floatInputs.push_back("input_" + name + "[" + ToStr(a) + "]");
}
}
psInputDefinition += ";\n";
int firstElem = sig.regChannelMask & 0x1 ? 0 : sig.regChannelMask & 0x2
? 1
: sig.regChannelMask & 0x4
? 2
: sig.regChannelMask & 0x8 ? 3 : -1;
// arrays get added all at once (because in the struct data, they are contiguous even if
// in the input signature they're not).
if(arrayIndex < 0)
{
if(arrayLength == 0)
{
initialValues.push_back(
PSInputElement(sig.regIndex, firstElem, numCols, sig.systemValue, included));
}
else
{
for(int a = 0; a < arrayLength; a++)
{
initialValues.push_back(
PSInputElement(sig.regIndex + a, firstElem, numCols, sig.systemValue, included));
}
}
}
}
psInputDefinition += "};\n\n";
}
}; // namespace ShaderDebug
#if ENABLED(ENABLE_UNIT_TESTS)
@@ -193,6 +193,13 @@ void FillViewFmt(DXGI_FORMAT format, GlobalState::ViewFmt &viewFmt);
void LookupSRVFormatFromShaderReflection(const DXBC::Reflection &reflection,
uint32_t shaderRegister, GlobalState::ViewFmt &viewFmt);
void GatherPSInputDataForInitialValues(const DXBC::Reflection &psDxbc,
const DXBC::Reflection &prevStageDxbc,
std::vector<PSInputElement> &initialValues,
std::vector<std::string> &floatInputs,
std::vector<std::string> &inputVarNames,
std::string &psInputDefinition, int &structureStride);
struct SampleGatherResourceData
{
DXBCBytecode::ResourceDimension dim;