279 lines
8.6 KiB
C++
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;
|
|
}
|