Add reading and writing of pipeline validation chunk

This commit is contained in:
baldurk
2024-08-19 12:00:14 +01:00
parent 946df28742
commit 20f5725bbd
2 changed files with 495 additions and 45 deletions
@@ -62,6 +62,11 @@ struct StringBuffer
stringblob.push_back('\0'); // starts with an empty string
}
void Reset()
{
stringblob.clear();
stringblob.push_back('\0');
}
void Load(const void *data, size_t size) { stringblob.assign((const char *)data, size); }
const char *GetString(IndexReference offs) { return stringblob.c_str() + offs.offset; }
@@ -86,6 +91,12 @@ struct StringBuffer
offs += curlen + 1;
}
}
else
{
// note: empty strings are deduplicated (unlike full strings) to offset 0 even in PSV...
if(str.empty())
return {0};
}
uint32_t ret = (uint32_t)stringblob.length();
stringblob.append(str);
@@ -119,6 +130,8 @@ struct IndexArrays
size_t size() const { return length; }
T &operator[](size_t i) { return idxs[i]; }
operator rdcarray<T>() { return rdcarray<T>(idxs, length); }
};
template <typename T>
@@ -347,6 +360,11 @@ static void BakeRuntimeTablePart(rdcarray<bytebuf> &parts, DXIL::RDATData::Part
};
void DXIL::PSVData::Bitmask::WriteTable(StreamWriter &writer, uint32_t numVectors) const
{
writer.Write(bitmask, TableByteSize(numVectors));
}
namespace DXBC
{
bool DXBCContainer::GetPipelineValidation(DXIL::PSVData &psv) const
@@ -364,11 +382,409 @@ bool DXBCContainer::GetPipelineValidation(DXIL::PSVData &psv) const
if(m_PSVOffset == 0)
return false;
const byte *in = m_ShaderBlob.data() + m_PSVOffset;
const byte *end = in + m_PSVSize;
const uint32_t headerSize = *(uint32_t *)in;
in += sizeof(uint32_t);
DXIL::PSVData0 *header = (DXIL::PSVData0 *)in;
in += headerSize;
if(headerSize == sizeof(DXIL::PSVData0))
{
memcpy(&psv, header, headerSize);
psv.version = PSVData::Version::Version0;
psv.shaderType = m_Type;
}
else if(headerSize == sizeof(DXIL::PSVData1))
{
memcpy(&psv, header, headerSize);
psv.version = PSVData::Version::Version1;
}
else if(headerSize == sizeof(DXIL::PSVData2))
{
memcpy(&psv, header, headerSize);
psv.version = PSVData::Version::Version2;
}
else if(headerSize > sizeof(DXIL::PSVData2))
{
RDCWARN("Unexpected PSV header size %u, only reading ver2", headerSize);
memcpy(&psv, header, sizeof(DXIL::PSVData2));
psv.version = PSVData::Version::Version2;
}
else
{
// size is not larger than ver2, which means it's invalid
RDCERR("Invalid PSV header size %u", headerSize);
return false;
}
// resources are always present
const uint32_t resourceCount = *(uint32_t *)in;
in += sizeof(uint32_t);
if(resourceCount)
{
const uint32_t resourceStride = *(uint32_t *)in;
in += sizeof(uint32_t);
if(resourceStride > sizeof(PSVResource1))
{
RDCWARN("Unexpected PSV resource stride %u, only reading ver1", resourceStride);
}
else if(resourceStride != sizeof(PSVResource0) && resourceStride != sizeof(PSVResource1))
{
RDCERR("Invalid PSV resource stride %u", resourceStride);
return false;
}
psv.resourceVersion = PSVData::ResourceVersion::Version0;
if(resourceStride >= sizeof(PSVResource1))
psv.resourceVersion = PSVData::ResourceVersion::Version1;
const byte *resources = in;
in += resourceStride * resourceCount;
psv.resources.reserve(resourceCount);
for(uint32_t i = 0; i < resourceCount; i++)
{
PSVResource res;
memcpy(&res, resources, resourceStride);
psv.resources.push_back(res);
resources += resourceStride;
}
}
if(psv.version >= PSVData::Version::Version1)
{
const uint32_t stringBufSize = *(uint32_t *)in;
in += sizeof(uint32_t);
StringBuffer stringbuf(false); // not deduplicated, though it doesn't matter on read
stringbuf.Load(in, stringBufSize);
in += AlignUp4(stringBufSize); // should already be aligned but let's be safe
const uint32_t idxArraySize = *(uint32_t *)in;
in += sizeof(uint32_t);
IndexArrays idxArrays(true, false); // deduplicated and not length-prefixed
idxArrays.Load(
in, idxArraySize * sizeof(uint32_t)); // length is given as number of dwords not bytes
in += idxArraySize * sizeof(uint32_t);
if(psv.inputSigElems || psv.outputSigElems || psv.patchConstPrimSigElems)
{
const uint32_t sigStride = *(uint32_t *)in;
in += sizeof(uint32_t);
if(sigStride > PSVSignature0::SigStride)
{
RDCWARN("Unexpected PSV signature element stride %u, only reading ver1", sigStride);
}
else if(sigStride != PSVSignature0::SigStride)
{
RDCERR("Invalid PSV signature element stride %u", sigStride);
return false;
}
psv.signatureVersion = PSVData::SignatureVersion::Version0;
psv.inputSig.reserve(psv.inputSigElems);
for(uint8_t i = 0; i < psv.inputSigElems; i++)
{
IndexReference name, semIndices;
memcpy(&name, in, sizeof(IndexReference));
in += sizeof(IndexReference);
memcpy(&semIndices, in, sizeof(IndexReference));
in += sizeof(IndexReference);
PSVSignature sig;
memcpy(&sig.properties, in, sizeof(sig.properties));
in += sizeof(sig.properties);
sig.name = stringbuf.GetString(name);
sig.semIndices = idxArrays.GetSpan<uint32_t>(semIndices);
sig.semIndices.resize(sig.properties.rows);
psv.inputSig.push_back(sig);
}
psv.outputSig.reserve(psv.outputSigElems);
for(uint8_t i = 0; i < psv.outputSigElems; i++)
{
IndexReference name, semIndices;
memcpy(&name, in, sizeof(IndexReference));
in += sizeof(IndexReference);
memcpy(&semIndices, in, sizeof(IndexReference));
in += sizeof(IndexReference);
PSVSignature sig;
memcpy(&sig.properties, in, sizeof(sig.properties));
in += sizeof(sig.properties);
sig.name = stringbuf.GetString(name);
sig.semIndices = idxArrays.GetSpan<uint32_t>(semIndices);
sig.semIndices.resize(sig.properties.rows);
psv.outputSig.push_back(sig);
}
psv.patchConstPrimSig.reserve(psv.patchConstPrimSigElems);
for(uint8_t i = 0; i < psv.patchConstPrimSigElems; i++)
{
IndexReference name, semIndices;
memcpy(&name, in, sizeof(IndexReference));
in += sizeof(IndexReference);
memcpy(&semIndices, in, sizeof(IndexReference));
in += sizeof(IndexReference);
PSVSignature sig;
memcpy(&sig.properties, in, sizeof(sig.properties));
in += sizeof(sig.properties);
sig.name = stringbuf.GetString(name);
sig.semIndices = idxArrays.GetSpan<uint32_t>(semIndices);
sig.semIndices.resize(sig.properties.rows);
psv.patchConstPrimSig.push_back(sig);
}
}
// view ID dependence table
if(psv.useViewID)
{
for(uint32_t stream = 0; stream < PSVData::NumOutputStreams; stream++)
{
if(psv.outputSigVectors[stream])
{
psv.viewIDAffects.outputMask[stream].ReadTable(in, psv.outputSigVectors[stream]);
}
}
// same union member
RDCCOMPILE_ASSERT(offsetof(DXIL::PSVData1, hs1.sigPatchConstVectors) ==
offsetof(DXIL::PSVData1, ms1.sigPrimVectors),
"sigPatchConstVectors is not at the same offset as sigPrimVectors");
if((m_Type == DXBC::ShaderType::Hull || m_Type == DXBC::ShaderType::Mesh) &&
psv.hs1.sigPatchConstVectors)
{
psv.viewIDAffects.patchConstOrPrimMask.ReadTable(in, psv.hs1.sigPatchConstVectors);
}
}
// IO dependence table
for(uint32_t stream = 0; stream < PSVData::NumOutputStreams; stream++)
{
if(psv.inputSigVectors && psv.outputSigVectors[stream])
{
rdcarray<PSVData::Bitmask> &bitmask = psv.IODependencies[stream].dependentOutputsForInput;
bitmask.resize(psv.inputSigVectors * 4);
for(size_t i = 0; i < bitmask.size(); i++)
{
bitmask[i].ReadTable(in, psv.outputSigVectors[stream]);
}
}
}
// patch constant output on input dependence table
if(m_Type == DXBC::ShaderType::Hull && psv.hs1.sigPatchConstVectors && psv.inputSigVectors)
{
rdcarray<PSVData::Bitmask> &bitmask = psv.PCIODependencies.dependentPCOutputsForInput;
bitmask.resize(psv.inputSigVectors * 4);
for(size_t i = 0; i < bitmask.size(); i++)
{
bitmask[i].ReadTable(in, psv.hs1.sigPatchConstVectors);
}
}
// output on patch constant input dependence table
if(m_Type == DXBC::ShaderType::Domain && psv.outputSigVectors[0] && psv.ds1.sigPatchConstVectors)
{
rdcarray<PSVData::Bitmask> &bitmask = psv.PCIODependencies.dependentPCOutputsForInput;
bitmask.resize(psv.ds1.sigPatchConstVectors * 4);
for(size_t i = 0; i < bitmask.size(); i++)
{
bitmask[i].ReadTable(in, psv.outputSigVectors[0]);
}
}
}
RDCASSERT(in == end);
return true;
}
void DXBCContainer::SetPipelineValidation(bytebuf &ByteCode, const DXIL::PSVData &psv)
{
using namespace DXIL;
StreamWriter writer(256);
uint32_t headerSize = sizeof(PSVData::PSVData0);
switch(psv.version)
{
default:
case PSVData::Version::Version0: headerSize = sizeof(PSVData::PSVData0); break;
case PSVData::Version::Version1: headerSize = sizeof(PSVData::PSVData1); break;
case PSVData::Version::Version2: headerSize = sizeof(PSVData::PSVData2); break;
}
// PSV does not deduplicate
StringBuffer stringbuf(false);
// PSV does deduplicate index arrays, but does not length prefix them
IndexArrays idxArrays(true, false);
// write header
writer.Write<uint32_t>(headerSize);
writer.Write(&psv, headerSize);
// write resources
const uint32_t resourceCount = psv.resources.count();
writer.Write(resourceCount);
if(resourceCount)
{
uint32_t resourceStride = sizeof(PSVResource1);
if(psv.resourceVersion == PSVData::ResourceVersion::Version0)
resourceStride = sizeof(PSVResource0);
writer.Write(resourceStride);
for(uint32_t i = 0; i < resourceCount; i++)
writer.Write(&psv.resources[i], resourceStride);
}
if(psv.version >= PSVData::Version::Version1)
{
// gather string buffer and index arrays first so we can write them
RDCASSERT(psv.inputSigElems == psv.inputSig.size());
for(uint8_t i = 0; i < psv.inputSigElems; i++)
{
stringbuf.MakeRef(psv.inputSig[i].name);
idxArrays.MakeRef(psv.inputSig[i].semIndices, false);
}
RDCASSERT(psv.outputSigElems == psv.outputSig.size());
for(uint8_t i = 0; i < psv.outputSigElems; i++)
{
stringbuf.MakeRef(psv.outputSig[i].name);
idxArrays.MakeRef(psv.outputSig[i].semIndices, false);
}
RDCASSERT(psv.patchConstPrimSigElems == psv.patchConstPrimSig.size());
for(uint8_t i = 0; i < psv.patchConstPrimSigElems; i++)
{
stringbuf.MakeRef(psv.patchConstPrimSig[i].name);
idxArrays.MakeRef(psv.patchConstPrimSig[i].semIndices, false);
}
const uint32_t stringBufSize = AlignUp4(stringbuf.GetBlob().count());
writer.Write(stringBufSize);
writer.Write(stringbuf.GetBlob().data(), stringbuf.GetBlob().size());
writer.AlignTo<sizeof(uint32_t)>();
// length is given as number of dwords not bytes
const uint32_t idxArraySize = idxArrays.GetBlob().count();
writer.Write(idxArraySize);
writer.Write(idxArrays.GetBlob().data(), idxArraySize * sizeof(uint32_t));
// since it's not deduplicated, reset the string buffer and we'll "recreate" it the same, to not
// have to store all the references above. The index arrays will naturally deduplicate to be the same.
stringbuf.Reset();
// string buffer and index array are unconditionally written but we only write the signature
// data with stride if there is some data to write
if(psv.inputSigElems || psv.outputSigElems || psv.patchConstPrimSigElems)
{
uint32_t sigStride = PSVSignature0::SigStride;
writer.Write(sigStride);
for(uint8_t i = 0; i < psv.inputSigElems; i++)
{
writer.Write(stringbuf.MakeRef(psv.inputSig[i].name).offset);
writer.Write(idxArrays.MakeRef(psv.inputSig[i].semIndices, false).offset);
writer.Write(&psv.inputSig[i].properties, sizeof(PSVSignature0::Properties));
}
for(uint8_t i = 0; i < psv.outputSigElems; i++)
{
writer.Write(stringbuf.MakeRef(psv.outputSig[i].name).offset);
writer.Write(idxArrays.MakeRef(psv.outputSig[i].semIndices, false).offset);
writer.Write(&psv.outputSig[i].properties, sizeof(PSVSignature0::Properties));
}
for(uint8_t i = 0; i < psv.patchConstPrimSigElems; i++)
{
writer.Write(stringbuf.MakeRef(psv.patchConstPrimSig[i].name).offset);
writer.Write(idxArrays.MakeRef(psv.patchConstPrimSig[i].semIndices, false).offset);
writer.Write(&psv.patchConstPrimSig[i].properties, sizeof(PSVSignature0::Properties));
}
}
// view ID dependence table
if(psv.useViewID)
{
for(uint32_t stream = 0; stream < PSVData::NumOutputStreams; stream++)
{
if(psv.outputSigVectors[stream])
{
psv.viewIDAffects.outputMask[stream].WriteTable(writer, psv.outputSigVectors[stream]);
}
}
// same union member
RDCCOMPILE_ASSERT(offsetof(DXIL::PSVData1, hs1.sigPatchConstVectors) ==
offsetof(DXIL::PSVData1, ms1.sigPrimVectors),
"sigPatchConstVectors is not at the same offset as sigPrimVectors");
if((psv.shaderType == DXBC::ShaderType::Hull || psv.shaderType == DXBC::ShaderType::Mesh) &&
psv.hs1.sigPatchConstVectors)
{
psv.viewIDAffects.patchConstOrPrimMask.WriteTable(writer, psv.hs1.sigPatchConstVectors);
}
}
// IO dependence table
for(uint32_t stream = 0; stream < PSVData::NumOutputStreams; stream++)
{
if(psv.inputSigVectors && psv.outputSigVectors[stream])
{
const rdcarray<PSVData::Bitmask> &bitmask =
psv.IODependencies[stream].dependentOutputsForInput;
for(size_t i = 0; i < bitmask.size(); i++)
{
bitmask[i].WriteTable(writer, psv.outputSigVectors[stream]);
}
}
}
// patch constant output on input dependence table
if(psv.shaderType == DXBC::ShaderType::Hull && psv.hs1.sigPatchConstVectors && psv.inputSigVectors)
{
const rdcarray<PSVData::Bitmask> &bitmask = psv.PCIODependencies.dependentPCOutputsForInput;
for(size_t i = 0; i < bitmask.size(); i++)
{
bitmask[i].WriteTable(writer, psv.hs1.sigPatchConstVectors);
}
}
// output on patch constant input dependence table
if(psv.shaderType == DXBC::ShaderType::Domain && psv.outputSigVectors[0] &&
psv.ds1.sigPatchConstVectors)
{
const rdcarray<PSVData::Bitmask> &bitmask = psv.PCIODependencies.dependentPCOutputsForInput;
for(size_t i = 0; i < bitmask.size(); i++)
{
bitmask[i].WriteTable(writer, psv.outputSigVectors[0]);
}
}
}
DXBC::DXBCContainer::ReplaceChunk(ByteCode, DXBC::FOURCC_PSV0, writer.GetData(),
(size_t)writer.GetOffset());
}
bool DXBCContainer::GetRuntimeData(DXIL::RDATData &rdat) const
+79 -45
View File
@@ -28,6 +28,8 @@
#include "driver/shaders/dxbc/dxbc_common.h"
#include "dxil_common.h"
class StreamWriter;
namespace DXBC
{
enum class ShaderType : uint8_t;
@@ -39,6 +41,8 @@ namespace DXIL
struct PSVData0
{
PSVData0() { ms = MSInfo(); }
struct VSInfo
{
bool SVPositionOutput;
@@ -97,8 +101,8 @@ struct PSVData0
ASInfo as;
MSInfo ms;
};
uint32_t minWaveCount;
uint32_t maxWaveCount;
uint32_t minWaveCount = 0;
uint32_t maxWaveCount = ~0U;
static const size_t ExpectedSize = sizeof(uint32_t) * 6;
};
@@ -107,8 +111,10 @@ struct PSVData1 : public PSVData0
{
static const uint32_t NumOutputStreams = 4;
DXBC::ShaderType shaderType;
bool useViewID;
PSVData1() { gs1.maxVerts = 0; }
DXBC::ShaderType shaderType = DXBC::ShaderType::Max;
bool useViewID = false;
union
{
@@ -120,28 +126,28 @@ struct PSVData1 : public PSVData0
struct
{
uint8_t sigPatchConstVectors;
} hs, gs;
} hs1, ds1;
struct
{
uint8_t sigPrimVectors;
DXBC::TessellatorDomain topology;
} ms;
} ms1;
};
uint8_t inputSigElems;
uint8_t outputSigElems;
uint8_t patchConstPrimSigElems;
uint8_t inputSigElems = 0;
uint8_t outputSigElems = 0;
uint8_t patchConstPrimSigElems = 0;
uint8_t inputSigVectors;
uint8_t outputSigVectors[NumOutputStreams]; // one per geometry stream
uint8_t inputSigVectors = 0;
uint8_t outputSigVectors[NumOutputStreams] = {}; // one per geometry stream
static const size_t ExpectedSize = sizeof(PSVData0) + sizeof(uint16_t) + 10 * sizeof(uint8_t);
};
struct PSVData2 : public PSVData1
{
uint32_t threadCount[3];
uint32_t threadCount[3] = {};
static const size_t ExpectedSize = sizeof(PSVData1) + 3 * sizeof(uint32_t);
};
@@ -162,8 +168,8 @@ enum class PSVResourceFlags
struct PSVResource1 : public PSVResource0
{
DXIL::ResourceKind kind;
PSVResourceFlags flags;
DXIL::ResourceKind kind = DXIL::ResourceKind::Unknown;
PSVResourceFlags flags = PSVResourceFlags::None;
};
using PSVResource = PSVResource1;
@@ -172,26 +178,28 @@ struct PSVSignature0
{
rdcstr name;
rdcarray<uint32_t> semIndices;
uint8_t rows;
uint8_t firstRow;
uint8_t cols; // : 4
uint8_t startCol; // :2
uint8_t alloc; // :2
SigSemantic semantic;
DXBC::SigCompType compType;
DXBC::InterpolationMode interpMode;
uint8_t dynamicMask; // :4
uint8_t stream; // :2
uint8_t padding;
// we make a properties struct for the data which is directly serialisable to make this easier to load/save
struct Properties
{
uint8_t rows;
uint8_t firstRow;
uint8_t cols : 4;
uint8_t startCol : 2;
uint8_t alloc : 2;
SigSemantic semantic;
DXBC::SigCompType compType;
DXBC::InterpolationMode interpMode;
uint8_t dynamicMask : 4;
uint8_t stream : 2;
uint8_t padding;
} properties;
static const size_t SigStride = sizeof(Properties) + sizeof(uint32_t) * 2;
};
using PSVSignature = PSVSignature0;
inline const uint32_t VectorBitmaskBitSize(uint32_t numVectors)
{
return AlignUp(numVectors * 4, 64U);
}
struct PSVData : public PSVData2
{
enum class Version
@@ -202,6 +210,19 @@ struct PSVData : public PSVData2
VersionLatest = Version2,
} version = Version::VersionLatest;
enum class ResourceVersion
{
Version0 = 0,
Version1,
VersionLatest = Version1,
} resourceVersion = ResourceVersion::VersionLatest;
enum class SignatureVersion
{
Version0 = 0,
VersionLatest = Version0,
} signatureVersion = SignatureVersion::VersionLatest;
rdcarray<PSVResource> resources;
// stringtable
@@ -211,35 +232,48 @@ struct PSVData : public PSVData2
rdcarray<PSVSignature> outputSig;
rdcarray<PSVSignature> patchConstPrimSig;
// in the tables below we assume no more than 64 dwords in any signature (16 vectors) so store
// bitmasks as 64-bit.
// bitmask could be larger than 64-bit (more than 16 vectors) but at least not larger than 32-bit.
// rather than having some horrible arrays here we just worst-case allocate
struct Bitmask
{
uint64_t bitmask[2] = {};
inline size_t TableByteSize(uint32_t numVectors) const
{
return (AlignUp(numVectors * 4, 32U)) / 8;
}
void WriteTable(StreamWriter &writer, uint32_t numVectors) const;
inline void ReadTable(const byte *&in, uint32_t numVectors)
{
memcpy(bitmask, in, TableByteSize(numVectors));
in += TableByteSize(numVectors);
}
};
// if view ID is used, a bitmask per output stream, the bitmask containing one bit per dword as
// in PSVData1::outputSigVectors indicating if that output vector depends on view ID
struct
{
uint64_t outputMask;
uint64_t patchConstMask;
} viewIDAffects[PSVData1::NumOutputStreams];
Bitmask outputMask[PSVData1::NumOutputStreams];
// dependency of patch constant outputs or per-primitive outputs (for mesh shader) on viewID
Bitmask patchConstOrPrimMask;
} viewIDAffects;
// for each stream, a bitmask for each input vector with the bitmask containing which output
// vectors have a dependency on the input vector.
// this array becomes inefficient because there's waste in every bitmask but a bitmask per input dword.
struct
{
rdcarray<uint64_t> dependentOutputsForInput;
rdcarray<Bitmask> dependentOutputsForInput;
} IODependencies[PSVData1::NumOutputStreams];
// same as above, but for patch constant outputs on inputs - HS only
// same as above, but for:
// - patch constant outputs on inputs - HS only
// - outputs on patch constant inputs - DS only
struct
{
rdcarray<uint64_t> dependentPCOutputsForInput;
} PCOutDependencies;
// same as above, but for outputs on patch constant inputs - DS only
struct
{
rdcarray<uint64_t> dependentOutputsForPCInput;
} PCInDependencies;
rdcarray<Bitmask> dependentPCOutputsForInput;
} PCIODependencies;
};
struct RDATData