#include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef GCN #include #include #include #endif enum class OutputType { SpirvBinary, SpirvHeader, SpirvAssembly, Glsl, Ir, }; enum class InputType { Glsl, SpirvBinary, Sb, Isa, }; static std::optional parseGlslStage(std::string_view stage) { if (stage == "library") { return shader::glsl::Stage::Library; } if (stage == "vertex") { return shader::glsl::Stage::Vertex; } if (stage == "tess-control") { return shader::glsl::Stage::TessControl; } if (stage == "tess-evaluation") { return shader::glsl::Stage::TessEvaluation; } if (stage == "geometry") { return shader::glsl::Stage::Geometry; } if (stage == "fragment") { return shader::glsl::Stage::Fragment; } if (stage == "compute") { return shader::glsl::Stage::Compute; } if (stage == "ray-gen") { return shader::glsl::Stage::RayGen; } if (stage == "intersect") { return shader::glsl::Stage::Intersect; } if (stage == "any-hit") { return shader::glsl::Stage::AnyHit; } if (stage == "closest-hit") { return shader::glsl::Stage::ClosestHit; } if (stage == "miss") { return shader::glsl::Stage::Miss; } if (stage == "callable") { return shader::glsl::Stage::Callable; } if (stage == "task") { return shader::glsl::Stage::Task; } if (stage == "mesh") { return shader::glsl::Stage::Mesh; } return {}; } static std::optional parseInputType(std::string_view type) { if (type == "glsl") { return InputType::Glsl; } if (type == "spirv-bin") { return InputType::SpirvBinary; } if (type == "sb") { return InputType::Sb; } if (type == "isa") { return InputType::Isa; } return {}; } static std::optional parseOutputType(std::string_view type) { if (type == "glsl") { return OutputType::Glsl; } if (type == "spirv-bin") { return OutputType::SpirvBinary; } if (type == "spirv-header") { return OutputType::SpirvHeader; } if (type == "spirv-asm") { return OutputType::SpirvAssembly; } if (type == "ir") { return OutputType::Ir; } return {}; } #ifdef GCN static std::optional parseGcnStage(std::string_view stage) { if (stage == "ps") { return shader::gcn::Stage::Ps; } if (stage == "vs-vs") { return shader::gcn::Stage::VsVs; } if (stage == "vs-es") { return shader::gcn::Stage::VsEs; } if (stage == "vs-ls") { return shader::gcn::Stage::VsLs; } if (stage == "cs") { return shader::gcn::Stage::Cs; } if (stage == "gs") { return shader::gcn::Stage::Gs; } if (stage == "gs-vs") { return shader::gcn::Stage::GsVs; } if (stage == "hs") { return shader::gcn::Stage::Hs; } if (stage == "ds-vs") { return shader::gcn::Stage::DsVs; } if (stage == "ds-es") { return shader::gcn::Stage::DsEs; } return {}; } #endif struct InputParam { std::optional type; std::optional glslStage; bool validate = false; #ifdef GCN std::string semanticPath; std::optional gcnStage; #endif }; struct OutputParam { std::string varName; std::optional type; bool validate = false; int optLevel = 0; }; static std::optional> readFile(const std::filesystem::path &path) { std::ifstream f(path, std::ios::binary | std::ios::ate); if (!f) { return {}; } std::vector data(f.tellg()); f.seekg(0, std::ios::beg); f.read(reinterpret_cast(data.data()), data.size()); return data; } static void writeSpvHeader(OutputParam &outputParam, std::ostream &out, std::span spv) { out << "#pragma once\n" << "#include \n\n" << "static const std::uint32_t " << outputParam.varName << "[] = {"; for (auto word : spv) { out << "0x" << std::hex << word << ", "; } out << "};\n"; } static bool writeOutput(OutputParam &outputParam, std::ostream &out, shader::ir::Region region) { switch (*outputParam.type) { case OutputType::SpirvBinary: case OutputType::SpirvHeader: case OutputType::SpirvAssembly: case OutputType::Glsl: { auto spv = shader::spv::serialize(region); if (outputParam.validate) { if (!shader::spv::validate(spv)) { return false; } } if (outputParam.optLevel >= 3) { if (auto opt = shader::spv::optimize(spv)) { spv = *opt; } } if (outputParam.type == OutputType::SpirvBinary) { out.write(reinterpret_cast(spv.data()), spv.size() * sizeof(spv[0])); } else if (outputParam.type == OutputType::SpirvHeader) { writeSpvHeader(outputParam, out, spv); } else if (outputParam.type == OutputType::SpirvAssembly) { out << shader::spv::disassembly(spv, true); } else if (outputParam.type == OutputType::Glsl) { out << shader::glsl::decompile(spv); } else { return false; } return true; } case OutputType::Ir: { shader::ir::NameStorage ns; region.print(out, ns); return true; } } return false; } #ifdef GCN static shader::ir::Region parseIsa(shader::ir::Context &context, InputParam &inputParam, OutputParam &outputParam, shader::ir::Location loc, std::span bytes) { shader::gcn::Context semanticContext; shader::spv::BinaryLayout semanticLayout; if (!inputParam.gcnStage) { inputParam.gcnStage = shader::gcn::Stage::Cs; } if (!inputParam.semanticPath.empty()) { if (auto result = shader::glsl::parseFile( semanticContext, *inputParam.glslStage, inputParam.semanticPath)) { semanticLayout = *result; } else { std::fprintf(stderr, "Failed to parse semantic '%s'\n", inputParam.semanticPath.c_str()); return {}; } } else { if (auto result = shader::spv::deserialize(semanticContext, g_rdna_semantic_spirv, loc)) { semanticLayout = *result; } else { std::fprintf(stderr, "Failed to parse builtin semantic\n"); return {}; } } shader::gcn::canonicalizeSemantic(semanticContext, semanticLayout); shader::gcn::SemanticModuleInfo gcnSemanticModuleInfo; shader::gcn::collectSemanticModuleInfo(gcnSemanticModuleInfo, semanticLayout); auto gcnSemanticInfo = shader::gcn::collectSemanticInfo(gcnSemanticModuleInfo); shader::gcn::Context isaContext; shader::gcn::Environment env; auto ir = shader::gcn::deserialize( isaContext, env, gcnSemanticInfo, 0, [&](std::uint64_t address) -> std::uint32_t { return *reinterpret_cast(bytes.data() + address); }); if (outputParam.type == OutputType::Ir) { return ir; } if (auto converted = shader::gcn::convertToSpv( isaContext, ir, gcnSemanticInfo, gcnSemanticModuleInfo, *inputParam.gcnStage, env)) { if (auto result = shader::spv::deserialize(context, converted->spv, loc)) { return result->merge(context); } } return {}; } static shader::ir::Region parseSb(shader::ir::Context &context, InputParam &inputParam, OutputParam &outputParam, shader::ir::Location loc, std::span bytes) { auto headerSize = static_cast(bytes[45]) * 4; auto instOffset = 52 + headerSize; if (!inputParam.gcnStage) { inputParam.gcnStage = static_cast(unsigned(bytes[8] >> 2) & 0xf); } return parseIsa(context, inputParam, outputParam, loc, bytes.subspan(instOffset)); } #endif static std::optional parseFile(shader::ir::Context &context, InputParam &inputParam, OutputParam &outputParam, const std::filesystem::path &path) { if (!inputParam.type) { auto ext = path.extension(); if (ext == ".glsl") { inputParam.type = InputType::Glsl; } else if (ext == ".spirv" || ext == ".spv") { inputParam.type = InputType::SpirvBinary; } else if (ext == ".sb") { inputParam.type = InputType::Sb; } else { return {}; } } if (inputParam.type == InputType::Glsl) { if (!inputParam.glslStage) { auto stageText = std::filesystem::path(path).replace_extension().extension(); if (stageText == ".vert") { inputParam.glslStage = shader::glsl::Stage::Vertex; } else if (stageText == ".comp") { inputParam.glslStage = shader::glsl::Stage::Compute; } else if (stageText == ".frag") { inputParam.glslStage = shader::glsl::Stage::Fragment; } else if (stageText == ".geom") { inputParam.glslStage = shader::glsl::Stage::Geometry; } else { inputParam.glslStage = shader::glsl::Stage::Library; } } if (auto result = shader::glsl::parseFile(context, *inputParam.glslStage, path)) { return result->merge(context); } return {}; } if (inputParam.type == InputType::SpirvBinary) { auto optFileContent = readFile(path); if (!optFileContent.has_value()) { return {}; } auto fileContent = std::move(*optFileContent); auto data = std::span{reinterpret_cast(fileContent.data()), fileContent.size() / sizeof(std::uint32_t)}; auto loc = context.getPathLocation(path.string()); if (auto result = shader::spv::deserialize(context, data, loc)) { return result->merge(context); } } #ifdef GCN if (inputParam.type == InputType::Sb) { auto loc = context.getPathLocation(path.string()); auto optFileContent = readFile(path); if (!optFileContent.has_value()) { return {}; } auto fileContent = std::move(*optFileContent); return parseSb(context, inputParam, outputParam, loc, fileContent); } if (inputParam.type == InputType::Isa) { auto loc = context.getPathLocation(path.string()); auto optFileContent = readFile(path); if (!optFileContent.has_value()) { return {}; } auto fileContent = std::move(*optFileContent); return parseIsa(context, inputParam, outputParam, loc, fileContent); } #endif return {}; } void usage(std::FILE *out, const char *argv0) { std::fprintf(out, "usage: %s [options] -i [-o ]\n", argv0); std::fprintf(out, "\n"); std::fprintf(out, " options:\n"); #ifdef GCN std::fprintf(out, " --input-type \n"); std::fprintf(out, " --semantic \n"); std::fprintf(out, " --input-isa-stage \n"); #else std::fprintf(out, " --input-type \n"); #endif std::fprintf(out, " --input-glsl-stage \n"); std::fprintf( out, " --output-type \n"); std::fprintf(out, " --validate - validate output spirv\n"); std::fprintf(out, " --output-var-name - specify variable name for " "spirv-header\n"); std::fprintf(out, " -O<0|1|2|3> - optimize spirv\n"); std::fprintf(out, "\n"); std::fprintf(out, " glsl-stage:\n"); std::fprintf(out, " library\n"); std::fprintf(out, " vertex\n"); std::fprintf(out, " tess-control\n"); std::fprintf(out, " tess-evaluation\n"); std::fprintf(out, " geometry\n"); std::fprintf(out, " fragment\n"); std::fprintf(out, " compute\n"); std::fprintf(out, " ray-gen\n"); std::fprintf(out, " intersect\n"); std::fprintf(out, " any-hit\n"); std::fprintf(out, " closest-hit\n"); std::fprintf(out, " miss\n"); std::fprintf(out, " callable\n"); std::fprintf(out, " task\n"); std::fprintf(out, " mesh\n"); #ifdef GCN std::fprintf(out, "\n"); std::fprintf(out, " isa-stage:\n"); std::fprintf(out, " ps\n"); std::fprintf(out, " vs-vs\n"); std::fprintf(out, " vs-es\n"); std::fprintf(out, " vs-ls\n"); std::fprintf(out, " cs\n"); std::fprintf(out, " gs\n"); std::fprintf(out, " gs-vs\n"); std::fprintf(out, " hs\n"); std::fprintf(out, " ds-vs\n"); std::fprintf(out, " ds-es\n"); #endif } int main(int argc, const char *argv[]) { const char *inputFile = nullptr; const char *outputFile = nullptr; InputParam inputParam; OutputParam outputParam; for (int i = 1; i < argc; ++i) { if (argv[i] == std::string_view("-h") || argv[i] == std::string_view("--help")) { usage(stdout, argv[0]); return 0; } if (argv[i] == std::string_view("-v") || argv[i] == std::string_view("--version")) { std::printf("%s\n", rx::getVersion().toString().c_str()); return 0; } if (argv[i] == std::string_view{"--validate"}) { outputParam.validate = true; continue; } if (argv[i] == std::string_view{"-O0"}) { outputParam.optLevel = 0; continue; } if (argv[i] == std::string_view{"-O1"}) { outputParam.optLevel = 1; continue; } if (argv[i] == std::string_view{"-O2"}) { outputParam.optLevel = 2; continue; } if (argv[i] == std::string_view{"-O3"}) { outputParam.optLevel = 3; continue; } if (i + 1 < argc) { const char *key = argv[i]; const char *value = argv[++i]; if (key == std::string_view{"-i"} || key == std::string_view{"--input"}) { inputFile = value; continue; } if (key == std::string_view{"-o"} || key == std::string_view{"--output"}) { outputFile = value; continue; } if (key == std::string_view{"--input-type"}) { if (auto inputType = parseInputType(value)) { inputParam.type = *inputType; continue; } } if (key == std::string_view{"--output-type"}) { if (auto outputType = parseOutputType(value)) { outputParam.type = *outputType; continue; } } if (key == std::string_view{"--input-glsl-stage"}) { if (auto glslStage = parseGlslStage(value)) { inputParam.glslStage = *glslStage; continue; } } if (key == std::string_view{"--output-var-name"}) { outputParam.varName = value; continue; } #ifdef GCN if (key == std::string_view{"--semantic"}) { inputParam.semanticPath = value; continue; } if (key == std::string_view{"--input-isa-stage"}) { if (auto stage = parseGcnStage(value)) { inputParam.gcnStage = *stage; continue; } } #endif } usage(stderr, argv[0]); return 1; } if (outputFile == nullptr) { outputFile = "-"; } if (inputFile == nullptr) { usage(stderr, argv[0]); return 1; } if (!outputParam.type) { outputParam.type = OutputType::Ir; } if (outputParam.varName.empty()) { outputParam.varName = std::filesystem::path(inputFile) .filename() .replace_extension() .string(); for (auto &c : outputParam.varName) { if (c == '.') { c = '_'; } } } shader::ir::Context context; auto ir = parseFile(context, inputParam, outputParam, inputFile); if (!ir) { std::fprintf(stderr, "failed to parse '%s'\n", inputFile); return 1; } std::ofstream outputFileStream; if (outputFile != std::string_view("-")) { outputFileStream = std::ofstream(outputFile, std::ios::binary); } std::ostream &ostream = (outputFile == std::string_view("-") ? std::cout : outputFileStream); if (!ostream) { std::fprintf(stderr, "failed to create '%s'\n", outputFile); return 1; } if (!writeOutput(outputParam, ostream, *ir)) { return 1; } if (!ostream) { std::fprintf(stderr, "failed to write to '%s'\n", outputFile); return 1; } return 0; }