From 86ce5625945c027e3c8b661c2cc9d1d30d12c9e2 Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 22 Apr 2024 16:13:32 +0100 Subject: [PATCH] Use STAT dxil module for reflection where possible/necessary * The STAT module has some metadata unstripped which is useful for type annotations. It's better than plain stripped DXIL but not as good as ILDB. --- .../driver/shaders/dxbc/dxbc_container.cpp | 78 +++++++++++++++---- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/renderdoc/driver/shaders/dxbc/dxbc_container.cpp b/renderdoc/driver/shaders/dxbc/dxbc_container.cpp index df776e81c..bd15c1cee 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_container.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_container.cpp @@ -1652,6 +1652,23 @@ DXBCContainer::DXBCContainer(const bytebuf &ByteCode, const rdcstr &debugInfoPat } } + // DXIL can have three(!) different programs in different chunks. + // ILDB is the best, it contains everything + // STAT is better for reflection only + // DXIL is the executable code and most stripped version + // + // Since decoding DXIL is expensive we want to do it as few times as possible. If we can get ILDB + // we do and don't get anything else. Otherwise we grab both STAT (for reflection) and DXIL (for disassembly) + + // only one of these will be allocated + DXIL::Program *dxilILDBProgram = NULL; + DXIL::Program *dxilDXILProgram = NULL; + + // this will be allocated if DXIL is, and will be deleted below after reflection is fetched from it + DXIL::Program *dxilSTATProgram = NULL; + + DXIL::Program *dxilReflectProgram = NULL; + if(m_DXBCByteCode == NULL) { // prefer ILDB if present @@ -1664,13 +1681,13 @@ DXBCContainer::DXBCContainer(const bytebuf &ByteCode, const rdcstr &debugInfoPat if(*fourcc == FOURCC_ILDB) { - m_DXILByteCode = new DXIL::Program((const byte *)chunkContents, *chunkSize); + dxilILDBProgram = new DXIL::Program((const byte *)chunkContents, *chunkSize); } } // next search the debug file if it exists for(uint32_t chunkIdx = 0; - debugHeader && m_DXILByteCode == NULL && chunkIdx < debugHeader->numChunks; chunkIdx++) + debugHeader && dxilILDBProgram == NULL && chunkIdx < debugHeader->numChunks; chunkIdx++) { uint32_t *fourcc = (uint32_t *)(debugData + debugChunkOffsets[chunkIdx]); uint32_t *chunkSize = (uint32_t *)(debugData + debugChunkOffsets[chunkIdx] + sizeof(uint32_t)); @@ -1678,13 +1695,15 @@ DXBCContainer::DXBCContainer(const bytebuf &ByteCode, const rdcstr &debugInfoPat char *chunkContents = (char *)(debugData + debugChunkOffsets[chunkIdx] + sizeof(uint32_t) * 2); if(*fourcc == FOURCC_ILDB) - m_DXILByteCode = new DXIL::Program((const byte *)chunkContents, *chunkSize); + { + dxilILDBProgram = new DXIL::Program((const byte *)chunkContents, *chunkSize); + } } // if we didn't find ILDB then we have to get the bytecode from DXIL. However we look for the // STAT chunk and if we find it get reflection from there, since it will have better // information. What a mess. - if(m_DXILByteCode == NULL) + if(dxilILDBProgram == NULL) { for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++) { @@ -1696,18 +1715,45 @@ DXBCContainer::DXBCContainer(const bytebuf &ByteCode, const rdcstr &debugInfoPat if(*fourcc == FOURCC_DXIL) { - m_DXILByteCode = new DXIL::Program(chunkContents, *chunkSize); + dxilDXILProgram = new DXIL::Program(chunkContents, *chunkSize); } else if(*fourcc == FOURCC_STAT) { - if(DXIL::Program::Valid(chunkContents, *chunkSize)) - { - // unfortunate that we have to parse the whole blob just to get reflection as well as - // parsing the DXIL bytecode. - m_Reflection = DXIL::Program(chunkContents, *chunkSize).GetReflection(); - } + dxilSTATProgram = new DXIL::Program((const byte *)chunkContents, *chunkSize); } } + + // if there's a debug file we'd have expected to find an ILDB but just in case look for a STAT + // if we didn't get it + for(uint32_t chunkIdx = 0; + debugHeader && dxilSTATProgram == NULL && chunkIdx < debugHeader->numChunks; chunkIdx++) + { + uint32_t *fourcc = (uint32_t *)(debugData + debugChunkOffsets[chunkIdx]); + uint32_t *chunkSize = + (uint32_t *)(debugData + debugChunkOffsets[chunkIdx] + sizeof(uint32_t)); + + char *chunkContents = + (char *)(debugData + debugChunkOffsets[chunkIdx] + sizeof(uint32_t) * 2); + + if(*fourcc == FOURCC_STAT) + { + dxilSTATProgram = new DXIL::Program((const byte *)chunkContents, *chunkSize); + } + } + } + + // if we got the full debug program we don't need the stat program + if(dxilILDBProgram) + { + SAFE_DELETE(dxilSTATProgram); + SAFE_DELETE(dxilDXILProgram); + dxilReflectProgram = m_DXILByteCode = dxilILDBProgram; + } + else if(dxilDXILProgram) + { + // prefer STAT for reflection, but otherwise use DXIL + dxilReflectProgram = dxilSTATProgram ? dxilSTATProgram : dxilDXILProgram; + m_DXILByteCode = dxilDXILProgram; } } @@ -1727,19 +1773,21 @@ DXBCContainer::DXBCContainer(const bytebuf &ByteCode, const rdcstr &debugInfoPat m_Version.Minor = m_DXILByteCode->GetMinorVersion(); } - // if reflection information was stripped, attempt to reverse engineer basic info from - // declarations + // if reflection information was stripped (or never emitted with DXIL), attempt to reverse + // engineer basic info from declarations or read it from the DXIL if(m_Reflection == NULL) { // need to disassemble now to guess resources if(m_DXBCByteCode) m_Reflection = m_DXBCByteCode->GuessReflection(); - else if(m_DXILByteCode) - m_Reflection = m_DXILByteCode->GetReflection(); + else if(dxilReflectProgram) + m_Reflection = dxilReflectProgram->GetReflection(); else m_Reflection = new Reflection; } + SAFE_DELETE(dxilSTATProgram); + for(uint32_t chunkIdx = 0; chunkIdx < header->numChunks; chunkIdx++) { uint32_t *fourcc = (uint32_t *)(data + chunkOffsets[chunkIdx]);