diff --git a/renderdoc/driver/shaders/dxbc/dxbc_inspect.cpp b/renderdoc/driver/shaders/dxbc/dxbc_inspect.cpp index 4122d4ce4..058700fd8 100644 --- a/renderdoc/driver/shaders/dxbc/dxbc_inspect.cpp +++ b/renderdoc/driver/shaders/dxbc/dxbc_inspect.cpp @@ -1648,12 +1648,72 @@ void DXBCFile::GuessResources() } } +struct FxcArg +{ + uint32_t bit; + const char *arg; +} fxc_flags[] = { + {D3DCOMPILE_DEBUG, " /Zi "}, + {D3DCOMPILE_SKIP_VALIDATION, " /Vd "}, + {D3DCOMPILE_SKIP_OPTIMIZATION, " /Od "}, + {D3DCOMPILE_PACK_MATRIX_ROW_MAJOR, " /Zpr "}, + {D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR, " /Zpc "}, + {D3DCOMPILE_PARTIAL_PRECISION, " /Gpp "}, + //{D3DCOMPILE_FORCE_VS_SOFTWARE_NO_OPT, " /XX "}, + //{D3DCOMPILE_FORCE_PS_SOFTWARE_NO_OPT, " /XX "}, + {D3DCOMPILE_NO_PRESHADER, " /Op "}, + {D3DCOMPILE_AVOID_FLOW_CONTROL, " /Gfa "}, + {D3DCOMPILE_PREFER_FLOW_CONTROL, " /Gfp "}, + {D3DCOMPILE_ENABLE_STRICTNESS, " /Ges "}, + {D3DCOMPILE_ENABLE_BACKWARDS_COMPATIBILITY, " /Gec "}, + {D3DCOMPILE_IEEE_STRICTNESS, " /Gis "}, + {D3DCOMPILE_WARNINGS_ARE_ERRORS, " /WX "}, + {D3DCOMPILE_RESOURCES_MAY_ALIAS, " /res_may_alias "}, + {D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES, " /enable_unbounded_descriptor_tables "}, + {D3DCOMPILE_ALL_RESOURCES_BOUND, " /all_resources_bound "}, + {D3DCOMPILE_DEBUG_NAME_FOR_SOURCE, " /Zss "}, + {D3DCOMPILE_DEBUG_NAME_FOR_BINARY, " /Zsb "}, +}; + uint32_t DecodeFlags(const ShaderCompileFlags &compileFlags) { uint32_t ret = 0; - if(!compileFlags.flags.empty() && compileFlags.flags[0].name == "compileFlags") - ret = atoi(compileFlags.flags[0].value.c_str()); + for(const ShaderCompileFlag flag : compileFlags.flags) + { + if(flag.name == "@cmdline") + { + std::string cmdline = flag.value; + + // ensure cmdline is surrounded by spaces and all whitespace is spaces. This means we can + // search for our flags surrounded by space and ensure we get exact matches. + for(char &c : cmdline) + if(isspace(c)) + c = ' '; + + cmdline = " " + cmdline + " "; + + for(const FxcArg &arg : fxc_flags) + { + if(strstr(cmdline.c_str(), arg.arg)) + ret |= arg.bit; + } + + // check optimisation special case + if(strstr(cmdline.c_str(), " /O0 ")) + ret |= D3DCOMPILE_OPTIMIZATION_LEVEL0; + else if(strstr(cmdline.c_str(), " /O1 ")) + ret |= D3DCOMPILE_OPTIMIZATION_LEVEL1; + else if(strstr(cmdline.c_str(), " /O2 ")) + ret |= D3DCOMPILE_OPTIMIZATION_LEVEL2; + else if(strstr(cmdline.c_str(), " /O3 ")) + ret |= D3DCOMPILE_OPTIMIZATION_LEVEL3; + + // ignore any other flags we might not understand + + break; + } + } return ret; } @@ -1662,7 +1722,32 @@ ShaderCompileFlags EncodeFlags(const uint32_t flags) { ShaderCompileFlags ret; - ret.flags = {{"compileFlags", StringFormat::Fmt("%u", flags)}}; + std::string cmdline; + + for(const FxcArg &arg : fxc_flags) + { + if(flags & arg.bit) + cmdline += arg.arg; + } + + // optimization flags are a special case. + // + // D3DCOMPILE_OPTIMIZATION_LEVEL0 = (1 << 14) + // D3DCOMPILE_OPTIMIZATION_LEVEL1 = 0 + // D3DCOMPILE_OPTIMIZATION_LEVEL2 = ((1 << 14) | (1 << 15)) + // D3DCOMPILE_OPTIMIZATION_LEVEL3 = (1 << 15) + + uint32_t opt = (flags & D3DCOMPILE_OPTIMIZATION_LEVEL2); + if(opt == D3DCOMPILE_OPTIMIZATION_LEVEL0) + cmdline += " /O0"; + else if(opt == D3DCOMPILE_OPTIMIZATION_LEVEL1) + cmdline += " /O1"; + else if(opt == D3DCOMPILE_OPTIMIZATION_LEVEL2) + cmdline += " /O2"; + else if(opt == D3DCOMPILE_OPTIMIZATION_LEVEL3) + cmdline += " /O3"; + + ret.flags = {{"@cmdline", cmdline}}; // If D3DCOMPILE_SKIP_OPTIMIZATION is set, then prefer source-level debugging as it should be // accurate enough to work with. @@ -1677,4 +1762,101 @@ ShaderCompileFlags EncodeFlags(const DXBCDebugChunk *dbg) return EncodeFlags(dbg ? dbg->GetShaderCompileFlags() : 0); } -}; // namespace DXBC \ No newline at end of file +}; // namespace DXBC + +#if ENABLED(ENABLE_UNIT_TESTS) + +#include "3rdparty/catch/catch.hpp" + +TEST_CASE("Check DXBC flags are non-overlapping", "[dxbc]") +{ + for(const DXBC::FxcArg &a : DXBC::fxc_flags) + { + for(const DXBC::FxcArg &b : DXBC::fxc_flags) + { + if(a.arg == b.arg) + continue; + + // no argument should be a subset of another argument + std::string arga = trim(a.arg); + std::string argb = trim(b.arg); + INFO("a: '" << arga << "' b: '" << argb << "'"); + CHECK(strstr(arga.c_str(), argb.c_str()) == NULL); + CHECK(strstr(argb.c_str(), arga.c_str()) == NULL); + } + } +} + +TEST_CASE("Check DXBC flag encoding/decoding", "[dxbc]") +{ + SECTION("encode/decode identity") + { + uint32_t flags = D3DCOMPILE_PARTIAL_PRECISION | D3DCOMPILE_SKIP_OPTIMIZATION | + D3DCOMPILE_ALL_RESOURCES_BOUND | D3DCOMPILE_OPTIMIZATION_LEVEL2; + uint32_t flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags)); + + CHECK(flags == flags2); + + flags = 0; + flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags)); + + CHECK(flags == flags2); + + flags = D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_DEBUG; + flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags)); + + CHECK(flags == flags2); + }; + + SECTION("encode/decode discards unrecognised parameters") + { + uint32_t flags = D3DCOMPILE_PARTIAL_PRECISION | (1 << 30); + uint32_t flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags)); + + CHECK(flags2 == D3DCOMPILE_PARTIAL_PRECISION); + + ShaderCompileFlags compileflags; + + compileflags.flags = { + {"@cmdline", "/Zi /Z8 /JJ /WX /K other words embed/Odparam /DFoo=\"bar\""}}; + + flags2 = DXBC::DecodeFlags(compileflags); + + CHECK(flags2 == (D3DCOMPILE_DEBUG | D3DCOMPILE_WARNINGS_ARE_ERRORS)); + + flags = ~0U; + flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags)); + + uint32_t allflags = 0; + for(const DXBC::FxcArg &a : DXBC::fxc_flags) + allflags |= a.bit; + + allflags |= D3DCOMPILE_OPTIMIZATION_LEVEL2; + + CHECK(flags2 == allflags); + }; + + SECTION("optimisation flags are properly decoded and encoded") + { + uint32_t flags = D3DCOMPILE_DEBUG | D3DCOMPILE_OPTIMIZATION_LEVEL0; + uint32_t flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags)); + + CHECK(flags == flags2); + + flags = D3DCOMPILE_DEBUG | D3DCOMPILE_OPTIMIZATION_LEVEL1; + flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags)); + + CHECK(flags == flags2); + + flags = D3DCOMPILE_DEBUG | D3DCOMPILE_OPTIMIZATION_LEVEL2; + flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags)); + + CHECK(flags == flags2); + + flags = D3DCOMPILE_DEBUG | D3DCOMPILE_OPTIMIZATION_LEVEL3; + flags2 = DXBC::DecodeFlags(DXBC::EncodeFlags(flags)); + + CHECK(flags == flags2); + }; +} +#endif \ No newline at end of file diff --git a/renderdoc/driver/shaders/spirv/spirv_common.h b/renderdoc/driver/shaders/spirv/spirv_common.h index f6479b474..7daebd1b1 100644 --- a/renderdoc/driver/shaders/spirv/spirv_common.h +++ b/renderdoc/driver/shaders/spirv/spirv_common.h @@ -113,6 +113,7 @@ struct SPVModule spv::SourceLanguage sourceLang; uint32_t sourceVer; + std::string cmdline; vector> sourceFiles; vector extensions; diff --git a/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp b/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp index c457680ae..a3e645c15 100644 --- a/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp +++ b/renderdoc/driver/shaders/spirv/spirv_disassemble.cpp @@ -28,6 +28,7 @@ #include "common/common.h" #include "maths/formatpacking.h" #include "serialise/serialiser.h" +#include "strings/string_utils.h" #include "spirv_common.h" using std::pair; @@ -3981,6 +3982,9 @@ void SPVModule::MakeReflection(ShaderStage stage, const string &entryPoint, } } + if(!cmdline.empty()) + reflection.debugInfo.compileFlags.flags = {{"@cmdline", cmdline}}; + // TODO need to fetch these reflection.dispatchThreadsDimension[0] = 0; reflection.dispatchThreadsDimension[1] = 0; @@ -4649,6 +4653,15 @@ void ParseSPIRV(uint32_t *spirv, size_t spirvLength, SPVModule &module) module.generator = spirv[2]; + bool isglslang = false; + + { + uint32_t toolid = (module.generator & 0xffff0000) >> 16; + + if(toolid == 8) + isglslang = true; + } + uint32_t idbound = spirv[3]; module.ids.resize(idbound); @@ -4686,7 +4699,50 @@ void ParseSPIRV(uint32_t *spirv, size_t spirvLength, SPVModule &module) RDCASSERT(filenameInst); sourceFile.first = filenameInst->str; - sourceFile.second = (const char *)&spirv[it + 4]; + + std::string source = (const char *)&spirv[it + 4]; + + // glslang outputs command-line arguments as OpModuleProcessed - before SPIR-V 1.1 where + // it was an actual op, it was output as comments in the source. + if(isglslang) + { + const char compileFlagPrefix[] = "// OpModuleProcessed "; + const char endMarker[] = "#line 1\n"; + if(source.find(compileFlagPrefix) == 0) + { + // process compile flags + size_t nextLine = source.find('\n'); + while(nextLine != std::string::npos) + { + bool finished = false; + if(source.find(compileFlagPrefix) == 0) + { + size_t offs = sizeof(compileFlagPrefix) - 1; + module.cmdline += " --" + source.substr(offs, nextLine - offs); + } + else if(source.find(endMarker) == 0) + { + finished = true; + } + else + { + RDCERR("Unexpected preamble line with OpModuleProcessed: %s", + std::string(source.begin(), source.begin() + nextLine)); + break; + } + + // erase this line + source.erase(0, nextLine + 1); + + nextLine = source.find('\n'); + + if(finished) + break; + } + } + } + + sourceFile.second = source; module.sourceFiles.push_back(sourceFile); } @@ -4717,6 +4773,16 @@ void ParseSPIRV(uint32_t *spirv, size_t spirvLength, SPVModule &module) module.sourceexts.push_back(&op); break; } + case spv::OpModuleProcessed: + { + // glslang outputs command-line arguments as OpModuleProcessed + if(isglslang) + { + module.cmdline += " --"; + module.cmdline += (const char *)&spirv[it + 1]; + } + break; + } case spv::OpExtension: { string ext = (const char *)&spirv[it + 1];