Files
mirai/tools/shader_compile/main.cpp

279 lines
8.6 KiB
C++

// tools/shader_compile/main.cpp
// MIRAI 着色器编译器命令行入口
// 自动检测所有 [shader("xxx")] 入口点并编译
#include "compiler.hpp"
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
namespace {
void print_usage(const char* program_name) {
std::cout << R"(
MIRAI Shader Compiler - Auto Mode
Usage: )" << program_name << R"( [options] <input.slang>
Options:
-o, --output <dir> Output directory for compiled shaders
-I, --include <path> Add include search path
-D, --define <MACRO> Define preprocessor macro (e.g., -DDEBUG or -DVALUE=1)
--prefix <name> Prefix for output file names (default: shader name)
-g Generate debug info
-O0 Disable optimization
--list Only list entry points, don't compile
-h, --help Show this help
-v, --version Show version info
Examples:
)" << program_name << R"( shader.slang -o ./compiled_shaders
)" << program_name << R"( shader.slang -o ./shaders --prefix myshader
)" << program_name << R"( shader.slang --list
Output Format:
Automatically compiles all entry points found via [shader("xxx")] attributes.
Output files: <prefix>.<entry_point>.<stage>.spv
<prefix>.<entry_point>.<stage>.reflect.json
)";
}
void print_version() {
std::cout << "MIRAI Shader Compiler v2.0.0 (Auto Mode)\n";
std::cout << "Based on Slang Shader Language\n";
}
struct command_line_args {
std::filesystem::path input_path;
std::filesystem::path output_dir;
std::string prefix;
mirai::tools::compile_options options;
bool show_help = false;
bool show_version = false;
bool list_only = false;
};
bool parse_args(int argc, char* argv[], command_line_args& args) {
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "-h" || arg == "--help") {
args.show_help = true;
return true;
}
if (arg == "-v" || arg == "--version") {
args.show_version = true;
return true;
}
if (arg == "-o" || arg == "--output") {
if (++i >= argc) {
std::cerr << "Error: -o requires an argument\n";
return false;
}
args.output_dir = argv[i];
}
else if (arg == "-I" || arg == "--include") {
if (++i >= argc) {
std::cerr << "Error: -I requires an argument\n";
return false;
}
args.options.include_paths.emplace_back(argv[i]);
}
else if (arg.starts_with("-I")) {
args.options.include_paths.emplace_back(arg.substr(2));
}
else if (arg == "-D" || arg == "--define") {
if (++i >= argc) {
std::cerr << "Error: -D requires an argument\n";
return false;
}
std::string def = argv[i];
auto eq_pos = def.find('=');
if (eq_pos != std::string::npos) {
args.options.defines.emplace_back(def.substr(0, eq_pos), def.substr(eq_pos + 1));
} else {
args.options.defines.emplace_back(def, "1");
}
}
else if (arg.starts_with("-D")) {
std::string def = arg.substr(2);
auto eq_pos = def.find('=');
if (eq_pos != std::string::npos) {
args.options.defines.emplace_back(def.substr(0, eq_pos), def.substr(eq_pos + 1));
} else {
args.options.defines.emplace_back(def, "1");
}
}
else if (arg == "--prefix") {
if (++i >= argc) {
std::cerr << "Error: --prefix requires an argument\n";
return false;
}
args.prefix = argv[i];
}
else if (arg == "--list") {
args.list_only = true;
}
else if (arg == "-g") {
args.options.generate_debug_info = true;
}
else if (arg == "-O0") {
args.options.optimize = false;
}
else if (!arg.starts_with("-")) {
if (!args.input_path.empty()) {
std::cerr << "Error: Multiple input files specified\n";
return false;
}
args.input_path = arg;
}
else {
std::cerr << "Error: Unknown option: " << arg << "\n";
return false;
}
}
return true;
}
std::string stage_to_suffix(mirai::tools::shader_stage stage) {
switch (stage) {
case mirai::tools::shader_stage::vertex: return "vert";
case mirai::tools::shader_stage::fragment: return "frag";
case mirai::tools::shader_stage::compute: return "comp";
case mirai::tools::shader_stage::geometry: return "geom";
case mirai::tools::shader_stage::tessellation_control: return "tesc";
case mirai::tools::shader_stage::tessellation_evaluation: return "tese";
}
return "unknown";
}
bool write_spirv(const std::filesystem::path& path, const std::vector<uint32_t>& spirv) {
std::ofstream file(path, std::ios::binary);
if (!file) {
std::cerr << "Error: Failed to open output file: " << path << "\n";
return false;
}
file.write(reinterpret_cast<const char*>(spirv.data()), spirv.size() * sizeof(uint32_t));
return file.good();
}
bool write_text(const std::filesystem::path& path, const std::string& text) {
std::ofstream file(path);
if (!file) {
std::cerr << "Error: Failed to open output file: " << path << "\n";
return false;
}
file << text;
return file.good();
}
} // anonymous namespace
int main(int argc, char* argv[]) {
command_line_args args;
if (!parse_args(argc, argv, args)) {
return 1;
}
if (args.show_help) {
print_usage(argv[0]);
return 0;
}
if (args.show_version) {
print_version();
return 0;
}
if (args.input_path.empty()) {
std::cerr << "Error: No input file specified\n";
print_usage(argv[0]);
return 1;
}
// 创建编译器
mirai::tools::shader_compiler compiler;
if (!compiler.is_available()) {
std::cerr << "Error: Shader compiler is not available\n";
return 1;
}
// 获取所有入口点及其阶段
auto entry_points = compiler.get_entry_points_with_stages(args.input_path, args.options);
if (entry_points.empty()) {
std::cout << "Warning: No entry points found in " << args.input_path << "\n";
std::cout << " (This may be a module file with no [shader(...)] attributes)\n";
return 0;
}
// 列出入口点
std::cout << "Found " << entry_points.size() << " entry point(s) in " << args.input_path.filename() << ":\n";
for (const auto& ep : entry_points) {
std::cout << " - " << ep.name << " [" << mirai::tools::shader_stage_to_string(ep.stage) << "]\n";
}
// 如果只是列出,不编译
if (args.list_only) {
return 0;
}
// 确保输出目录存在
if (args.output_dir.empty()) {
args.output_dir = ".";
}
if (!std::filesystem::exists(args.output_dir)) {
std::filesystem::create_directories(args.output_dir);
}
// 确定前缀
if (args.prefix.empty()) {
args.prefix = args.input_path.stem().string();
}
// 编译所有入口点
args.options.emit_spirv = true;
args.options.emit_reflection = true;
auto results = compiler.compile_file_all_entries(args.input_path, args.options);
int success_count = 0;
int fail_count = 0;
for (const auto& result : results) {
if (!result.success) {
std::cerr << "Error compiling " << result.entry_point << ": " << result.error_message << "\n";
fail_count++;
continue;
}
std::string suffix = stage_to_suffix(result.stage);
std::string base_name = args.prefix + "." + result.entry_point + "." + suffix;
// 写入 SPIR-V
std::filesystem::path spv_path = args.output_dir / (base_name + ".spv");
if (write_spirv(spv_path, result.spirv)) {
std::cout << "Generated: " << spv_path << " (" << result.spirv.size() * 4 << " bytes)\n";
}
// 写入反射 JSON
std::filesystem::path reflect_path = args.output_dir / (base_name + ".reflect.json");
if (write_text(reflect_path, result.reflection_json)) {
std::cout << "Generated: " << reflect_path << "\n";
}
success_count++;
}
std::cout << "\nSummary: " << success_count << " succeeded, " << fail_count << " failed\n";
return fail_count > 0 ? 1 : 0;
}