Replace manual patching of PSV with decode/encode

* The DXIL program editor will now automatically decode and encode the PSV and
  RDAT chunks (if found) as part of its roundtrip
This commit is contained in:
baldurk
2024-08-20 14:49:48 +01:00
parent 20f5725bbd
commit 72ffdc01bd
3 changed files with 67 additions and 317 deletions
+37 -172
View File
@@ -600,189 +600,54 @@ void D3D12Replay::PatchQuadWritePS(D3D12_EXPANDED_PIPELINE_STATE_STREAM_DESC &pi
}
}
DXIL::PSVData rastFeedingPSV;
DXIL::PSVData patchedPSV;
if(rastFeedingDXBC.GetPipelineValidation(rastFeedingPSV) &&
quadOverdrawDXBC.GetPipelineValidation(patchedPSV))
{
// do a horrible franken-patch to merge the PSV0 chunks. We use the header from the existing
// PS,
// change the number of declared input elements, then copy the signature elements from the
// last
// shader's chunk. We can't copy the whole string table because that will likely include other
// strings and then the damned thing won't match according to the runtime's validation.
size_t rastPsv0Size = 0;
const byte *rastPsv0Bytes =
DXBC::DXBCContainer::FindChunk(rastFeedingBytes, DXBC::FOURCC_PSV0, rastPsv0Size);
StreamReader rastPsv0(rastPsv0Bytes, rastPsv0Size);
DXIL::PSVData mergedPSV = patchedPSV;
size_t psPsv0Size = 0;
const byte *psPsv0Bytes =
DXBC::DXBCContainer::FindChunk(patchedDXBC, DXBC::FOURCC_PSV0, psPsv0Size);
StreamReader psPsv0(psPsv0Bytes, psPsv0Size);
StreamWriter mergedPsv0(1024);
uint32_t rastHeaderSize = 0;
if(!rastPsv0.Read<uint32_t>(rastHeaderSize))
return;
uint32_t psHeaderSize = 0;
if(!psPsv0.Read<uint32_t>(psHeaderSize))
return;
struct PSVHeader0
// if the previous shader only uses version0, use version0 ourselves as I don't know if it's
// safe to mix and match versions in a pipeline. Version 0 doesn't have signatures so we can
// basically stop here, the resources are already all set up as we based mergedPSV on patchedPSV
if(rastFeedingPSV.version == DXIL::PSVData::Version::Version0)
{
uint32_t unused[6];
};
struct PSVHeader1 : public PSVHeader0
{
// other data
uint32_t unused1;
// signature element counts
uint8_t inputEls;
uint8_t outputEls;
uint8_t patchConstEls;
// signature vector counts
uint8_t inputVecs;
uint8_t outputVecs[4];
};
struct PSVHeader2 : public PSVHeader1
{
uint32_t NumThreadsX;
uint32_t NumThreadsY;
uint32_t NumThreadsZ;
};
bytebuf copyBuf;
PSVHeader2 rastHeader = {}, psHeader = {};
if(rastHeaderSize < sizeof(PSVHeader1))
{
// only copy the header0 part out of the ps one since we won't have signature data to copy,
// hope this is OK
// read the whole ps header
psPsv0.Read(&psHeader, psHeaderSize);
// write only the old sized header
mergedPsv0.Write(rastHeaderSize);
mergedPsv0.Write(&psHeader, rastHeaderSize);
mergedPSV.version = DXIL::PSVData::Version::Version0;
}
else
{
rastPsv0.Read(&rastHeader, rastHeaderSize);
psPsv0.Read(&psHeader, psHeaderSize);
// newer versions, we need to patch the signature elements in our new patched PS reading
// from the right input signature element to match the previous signature's outputs
mergedPSV.inputSigElems = rastFeedingPSV.outputSigElems;
mergedPSV.inputSigVectors = rastFeedingPSV.outputSigVectors[0];
mergedPSV.inputSig = rastFeedingPSV.outputSig;
// copy the previous output signature into the ps input
psHeader.inputEls = rastHeader.outputEls;
psHeader.inputVecs = rastHeader.outputVecs[0];
// the ps header should have no other elements for us to worry about
RDCASSERT(psHeader.outputEls == 0);
RDCASSERT(psHeader.patchConstEls == 0);
RDCASSERT(psHeader.outputVecs[0] == 0);
RDCASSERT(psHeader.outputVecs[1] == 0);
RDCASSERT(psHeader.outputVecs[2] == 0);
RDCASSERT(psHeader.outputVecs[3] == 0);
// we should have a table offset for each output entry
RDCASSERT(rastHeader.outputEls == stringTableOffsets.size());
RDCASSERT(rastHeader.outputEls == semanticIndexTableOffsets.size());
mergedPsv0.Write(psHeaderSize);
mergedPsv0.Write(&psHeader, psHeaderSize);
}
// skip resource counts in raster side shader
uint32_t rastResCount = 0;
if(!rastPsv0.Read<uint32_t>(rastResCount))
return;
if(rastResCount > 0)
{
uint32_t resSize = 0;
if(!rastPsv0.Read<uint32_t>(resSize))
return;
rastPsv0.SkipBytes(rastResCount * resSize);
}
uint32_t psResCount = 0;
if(!psPsv0.Read<uint32_t>(psResCount))
return;
mergedPsv0.Write(psResCount);
// copy any resources in the pixel psv0
if(psResCount > 0)
{
uint32_t resSize = 0;
if(!psPsv0.Read<uint32_t>(resSize))
return;
mergedPsv0.Write(resSize);
copyBuf.resize(psResCount * resSize);
psPsv0.Read(copyBuf.data(), copyBuf.size());
mergedPsv0.Write(copyBuf.data(), copyBuf.size());
}
// if we have a new header with signature elements (what we expect)
if(rastHeaderSize >= sizeof(PSVHeader1))
{
// we're effectively done with the rest of the ps chunk here, we're just going to copy the
// old
// chunk except skipping the input signature. There might be data we don't need in the
// string/indices tables but that's fine.
// align string table to multiple of 4 size
stringTable.resize(AlignUp4(stringTable.size()));
// skip the old string table and semantic index table
uint32_t stringTableSize = 0;
if(!rastPsv0.Read<uint32_t>(stringTableSize))
return;
rastPsv0.SkipBytes(stringTableSize);
uint32_t indexTableSize = 0;
if(!rastPsv0.Read<uint32_t>(indexTableSize))
return;
rastPsv0.SkipBytes(indexTableSize * sizeof(uint32_t));
mergedPsv0.Write((uint32_t)stringTable.size());
mergedPsv0.Write(stringTable.data(), stringTable.size());
mergedPsv0.Write((uint32_t)semanticIndexTable.size());
mergedPsv0.Write(semanticIndexTable.data(), semanticIndexTable.byteSize());
uint32_t sigElSize = 0;
if(!rastPsv0.Read<uint32_t>(sigElSize))
return;
mergedPsv0.Write(sigElSize);
// skip any inputs from the previous stage, we don't want to copy that
rastPsv0.SkipBytes(rastHeader.inputEls * sigElSize);
struct PSVSigElement
// the PS header should have no other elements for us to worry about
RDCASSERT(mergedPSV.outputSigElems == 0);
RDCASSERT(mergedPSV.patchConstPrimSigElems == 0);
for(uint32_t i = 0; i < 4; i++)
{
uint32_t stringTableOffset;
uint32_t semanticTableOffset;
};
// copy the output elements, this will become the input elements. We need to modify the
// table
// offsets to match the one we generated
for(uint8_t el = 0; el < rastHeader.outputEls; el++)
{
copyBuf.resize(sigElSize);
rastPsv0.Read(copyBuf.data(), copyBuf.size());
PSVSigElement *sigEl = (PSVSigElement *)copyBuf.data();
sigEl->stringTableOffset = stringTableOffsets[el];
sigEl->semanticTableOffset = semanticIndexTableOffsets[el];
mergedPsv0.Write(copyBuf.data(), copyBuf.size());
RDCASSERT(mergedPSV.outputSigVectors[i] == 0);
}
// similarly we should have no I/O dependency tables
for(uint32_t i = 0; i < 4; i++)
{
RDCASSERT(mergedPSV.viewIDAffects.outputMask[i].bitmask[0] == 0);
RDCASSERT(mergedPSV.viewIDAffects.outputMask[i].bitmask[1] == 0);
RDCASSERT(mergedPSV.IODependencies[i].dependentOutputsForInput.empty());
}
RDCASSERT(mergedPSV.viewIDAffects.patchConstOrPrimMask.bitmask[0] == 0);
RDCASSERT(mergedPSV.viewIDAffects.patchConstOrPrimMask.bitmask[1] == 0);
RDCASSERT(mergedPSV.PCIODependencies.dependentPCOutputsForInput.empty());
}
DXBC::DXBCContainer::ReplaceChunk(patchedDXBC, DXBC::FOURCC_PSV0, mergedPsv0.GetData(),
(size_t)mergedPsv0.GetOffset());
DXBC::DXBCContainer::SetPipelineValidation(patchedDXBC, mergedPSV);
}
else
{
RDCERR("Couldn't get PSV for raster feeding or quad write shader to patch");
}
}
else // dxbc bytecode not dxil
@@ -98,6 +98,9 @@ ProgramEditor::ProgramEditor(const DXBC::DXBCContainer *container, bytebuf &outB
}
m_Constants.removeIf([](Constant *a) { return a == NULL; });
m_PSVPresent = container->GetPipelineValidation(m_PSV);
m_RDATPresent = container->GetRuntimeData(m_RDAT);
}
ProgramEditor::~ProgramEditor()
@@ -141,6 +144,16 @@ ProgramEditor::~ProgramEditor()
// also strip STAT because it might have stale reflection info
DXBC::DXBCContainer::StripChunk(m_OutBlob, DXBC::FOURCC_STAT);
if(m_PSVPresent)
{
DXBC::DXBCContainer::SetPipelineValidation(m_OutBlob, m_PSV);
}
if(m_RDATPresent)
{
DXBC::DXBCContainer::SetRuntimeData(m_OutBlob, m_RDAT);
}
#if ENABLED(RDOC_DEVEL) && 1
// on debug builds, run through dxil for "validation" if it's available.
// we need BOTH of htese because dxil.dll's interface is incomplete, it lacks the library
@@ -1702,84 +1715,15 @@ struct ResourceBind1 : ResourceBind0
void ProgramEditor::RegisterUAV(DXILResourceType type, uint32_t space, uint32_t regBase,
uint32_t regEnd, ResourceKind kind)
{
size_t sz = 0;
const byte *psv0 = DXBC::DXBCContainer::FindChunk(m_OutBlob, DXBC::FOURCC_PSV0, sz);
ResourceBind1 bind = {};
DXIL::PSVResource bind = {};
bind.type = type;
bind.space = space;
bind.regBase = regBase;
bind.regStart = regBase;
bind.regEnd = regEnd;
bind.kind = kind;
if(psv0)
{
bytebuf psv0blob(psv0, sz);
byte *begin = psv0blob.data();
byte *end = begin + sz;
byte *cur = begin;
uint32_t *headerSize = (uint32_t *)cur;
cur += sizeof(uint32_t);
if(cur >= end)
return;
// don't need to patch the header
cur += *headerSize;
if(cur >= end)
return;
uint32_t *numResources = (uint32_t *)cur;
cur += sizeof(uint32_t);
if(cur >= end)
return;
if(*numResources > 0)
{
uint32_t *resourceBindSize = (uint32_t *)cur;
cur += sizeof(uint32_t);
if(cur >= end)
return;
// fortunately UAVs are the last entry so we don't need to walk the list to insert in the
// right place, we can just add it at the end
cur += (*resourceBindSize) * (*numResources);
if(cur >= end)
return;
// add an extra resource
(*numResources)++;
if(*resourceBindSize == sizeof(ResourceBind1) || *resourceBindSize == sizeof(ResourceBind0))
{
psv0blob.insert(cur - begin, (byte *)&bind, *resourceBindSize);
}
else
{
RDCERR("Unexpected resource bind size %u", *resourceBindSize);
return;
}
}
else
{
// from definitions in dxc
const uint32_t headerSizeVer0 = 6 * sizeof(uint32_t);
const uint32_t headerSizeVer1 = headerSizeVer0 + sizeof(uint16_t) + 10 * sizeof(uint8_t);
const uint32_t headerSizeVer2 = headerSizeVer1 + 3 * sizeof(uint32_t);
// If there is no resource in the chunk we also need to insert the size of a resource bind
*numResources = 1;
size_t insertOffset = cur - begin;
uint32_t resourceBindSize =
*headerSize == headerSizeVer2 ? sizeof(ResourceBind1) : sizeof(ResourceBind0);
psv0blob.insert(insertOffset, (byte *)&resourceBindSize, sizeof(resourceBindSize));
psv0blob.insert(insertOffset + sizeof(resourceBindSize), (byte *)&bind, resourceBindSize);
}
DXBC::DXBCContainer::ReplaceChunk(m_OutBlob, DXBC::FOURCC_PSV0, psv0blob);
}
RDCASSERT(m_PSVPresent);
m_PSV.resources.push_back(bind);
// patch SFI0 here for non-CS non-PS shaders
if(m_Type != DXBC::ShaderType::Compute && m_Type != DXBC::ShaderType::Pixel)
@@ -1794,87 +1738,22 @@ void ProgramEditor::RegisterUAV(DXILResourceType type, uint32_t space, uint32_t
void ProgramEditor::SetNumThreads(uint32_t dim[3])
{
size_t sz = 0;
const byte *psv0 = DXBC::DXBCContainer::FindChunk(m_OutBlob, DXBC::FOURCC_PSV0, sz);
RDCASSERT(m_PSVPresent);
RDCASSERT(m_PSV.version >= PSVData::Version::Version2);
if(psv0)
{
bytebuf psv0blob(psv0, sz);
byte *begin = psv0blob.data();
byte *end = begin + sz;
byte *cur = begin;
uint32_t *headerSize = (uint32_t *)cur;
cur += sizeof(uint32_t);
if(cur >= end)
return;
// from definitions in dxc
const uint32_t headerSizeVer0 = 6 * sizeof(uint32_t);
const uint32_t headerSizeVer1 = headerSizeVer0 + sizeof(uint16_t) + 10 * sizeof(uint8_t);
const uint32_t headerSizeVer2 = headerSizeVer1 + 3 * sizeof(uint32_t);
if(*headerSize >= headerSizeVer2)
{
cur += headerSizeVer1;
memcpy(cur, dim, sizeof(uint32_t) * 3);
}
DXBC::DXBCContainer::ReplaceChunk(m_OutBlob, DXBC::FOURCC_PSV0, psv0blob);
}
memcpy(m_PSV.threadCount, dim, sizeof(uint32_t) * 3);
}
void ProgramEditor::SetASPayloadSize(uint32_t payloadSize)
{
size_t sz = 0;
const byte *psv0 = DXBC::DXBCContainer::FindChunk(m_OutBlob, DXBC::FOURCC_PSV0, sz);
if(psv0)
{
bytebuf psv0blob(psv0, sz);
byte *begin = psv0blob.data();
byte *end = begin + sz;
byte *cur = begin;
cur += sizeof(uint32_t);
if(cur >= end)
return;
// the AS info with the payload size is immediately at the start of the header
memcpy(cur, &payloadSize, sizeof(uint32_t));
DXBC::DXBCContainer::ReplaceChunk(m_OutBlob, DXBC::FOURCC_PSV0, psv0blob);
}
RDCASSERT(m_PSVPresent);
m_PSV.as.payloadBytes = payloadSize;
}
void ProgramEditor::SetMSPayloadSize(uint32_t payloadSize)
{
size_t sz = 0;
const byte *psv0 = DXBC::DXBCContainer::FindChunk(m_OutBlob, DXBC::FOURCC_PSV0, sz);
if(psv0)
{
bytebuf psv0blob(psv0, sz);
byte *begin = psv0blob.data();
byte *end = begin + sz;
byte *cur = begin;
cur += sizeof(uint32_t);
if(cur >= end)
return;
// the MS info is immediately at the start of the header
// the first two uint32s are groupshared related, then comes the payload size
memcpy(cur + sizeof(uint32_t) * 2, &payloadSize, sizeof(uint32_t));
DXBC::DXBCContainer::ReplaceChunk(m_OutBlob, DXBC::FOURCC_PSV0, psv0blob);
}
RDCASSERT(m_PSVPresent);
m_PSV.ms.payloadBytes = payloadSize;
}
void ProgramEditor::PatchGlobalShaderFlags(std::function<void(DXBC::GlobalShaderFlags &)> patcher)
@@ -26,6 +26,7 @@
#include "dxil_bytecode.h"
#include "dxil_common.h"
#include "dxil_metadata.h"
namespace DXBC
{
@@ -129,6 +130,11 @@ public:
private:
bytebuf &m_OutBlob;
DXIL::RDATData m_RDAT;
DXIL::PSVData m_PSV;
bool m_RDATPresent = false;
bool m_PSVPresent = false;
rdcarray<Constant *> m_Constants;
Type *CreateNewType();