diff --git a/renderdoc/driver/shaders/dxil/dxil_metadata.cpp b/renderdoc/driver/shaders/dxil/dxil_metadata.cpp index 91945dde5..b8b62669b 100644 --- a/renderdoc/driver/shaders/dxil/dxil_metadata.cpp +++ b/renderdoc/driver/shaders/dxil/dxil_metadata.cpp @@ -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() { return rdcarray(idxs, length); } }; template @@ -347,6 +360,11 @@ static void BakeRuntimeTablePart(rdcarray &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(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(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(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 &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 &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 &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(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(); + + // 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 &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 &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 &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 diff --git a/renderdoc/driver/shaders/dxil/dxil_metadata.h b/renderdoc/driver/shaders/dxil/dxil_metadata.h index 5f6b91801..6b0c5f5d3 100644 --- a/renderdoc/driver/shaders/dxil/dxil_metadata.h +++ b/renderdoc/driver/shaders/dxil/dxil_metadata.h @@ -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 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 resources; // stringtable @@ -211,35 +232,48 @@ struct PSVData : public PSVData2 rdcarray outputSig; rdcarray 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 dependentOutputsForInput; + rdcarray 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 dependentPCOutputsForInput; - } PCOutDependencies; - - // same as above, but for outputs on patch constant inputs - DS only - struct - { - rdcarray dependentOutputsForPCInput; - } PCInDependencies; + rdcarray dependentPCOutputsForInput; + } PCIODependencies; }; struct RDATData