// Copyright 2020 The Tint Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #if TINT_BUILD_GLSL_WRITER #include "StandAlone/ResourceLimits.h" #include "glslang/Public/ShaderLang.h" #endif #if TINT_BUILD_SPV_READER #include "spirv-tools/libspirv.hpp" #endif // TINT_BUILD_SPV_READER #include "src/utils/io/command.h" #include "src/val/val.h" #include "tint/tint.h" namespace { [[noreturn]] void TintInternalCompilerErrorReporter( const tint::diag::List& diagnostics) { auto printer = tint::diag::Printer::create(stderr, true); tint::diag::Formatter{}.format(diagnostics, printer.get()); tint::diag::Style bold_red{tint::diag::Color::kRed, true}; constexpr const char* please_file_bug = R"( ******************************************************************** * The tint shader compiler has encountered an unexpected error. * * * * Please help us fix this issue by submitting a bug report at * * crbug.com/tint with the source program that triggered the bug. * ******************************************************************** )"; printer->write(please_file_bug, bold_red); exit(1); } enum class Format { kNone = -1, kSpirv, kSpvAsm, kWgsl, kMsl, kHlsl, kGlsl, }; struct Options { bool show_help = false; std::string input_filename; std::string output_file = "-"; // Default to stdout bool parse_only = false; bool disable_workgroup_init = false; bool validate = false; bool demangle = false; bool dump_inspector_bindings = false; Format format = Format::kNone; bool emit_single_entry_point = false; std::string ep_name; std::vector transforms; bool use_fxc = false; std::string dxc_path; std::string xcrun_path; }; const char kUsage[] = R"(Usage: tint [options] options: --format -- Output format. If not provided, will be inferred from output filename extension: .spvasm -> spvasm .spv -> spirv .wgsl -> wgsl .metal -> msl .hlsl -> hlsl If none matches, then default to SPIR-V assembly. -ep -- Output single entry point --output-file -- Output file name. Use "-" for standard output -o -- Output file name. Use "-" for standard output --transform -- Runs transforms, name list is comma separated Available transforms: first_index_offset fold_trivial_single_use_lets renamer robustness --parse-only -- Stop after parsing the input --disable-workgroup-init -- Disable workgroup memory zero initialization. --demangle -- Preserve original source names. Demangle them. Affects AST dumping, and text-based output languages. --dump-inspector-bindings -- Dump reflection data about bindins to stdout. -h -- This help text --validate -- Validates the generated shader --fxc -- Ask to validate HLSL output using FXC instead of DXC. When specified, automatically enables --validate --dxc -- Path to DXC executable, used to validate HLSL output. When specified, automatically enables --validate --xcrun -- Path to xcrun executable, used to validate MSL output. When specified, automatically enables --validate)"; Format parse_format(const std::string& fmt) { (void)fmt; #if TINT_BUILD_SPV_WRITER if (fmt == "spirv") return Format::kSpirv; if (fmt == "spvasm") return Format::kSpvAsm; #endif // TINT_BUILD_SPV_WRITER #if TINT_BUILD_WGSL_WRITER if (fmt == "wgsl") return Format::kWgsl; #endif // TINT_BUILD_WGSL_WRITER #if TINT_BUILD_MSL_WRITER if (fmt == "msl") return Format::kMsl; #endif // TINT_BUILD_MSL_WRITER #if TINT_BUILD_HLSL_WRITER if (fmt == "hlsl") return Format::kHlsl; #endif // TINT_BUILD_HLSL_WRITER #if TINT_BUILD_GLSL_WRITER if (fmt == "glsl") return Format::kGlsl; #endif // TINT_BUILD_GLSL_WRITER return Format::kNone; } #if TINT_BUILD_SPV_WRITER || TINT_BUILD_WGSL_WRITER || \ TINT_BUILD_MSL_WRITER || TINT_BUILD_HLSL_WRITER /// @param input input string /// @param suffix potential suffix string /// @returns true if input ends with the given suffix. bool ends_with(const std::string& input, const std::string& suffix) { const auto input_len = input.size(); const auto suffix_len = suffix.size(); // Avoid integer overflow. return (input_len >= suffix_len) && (input_len - suffix_len == input.rfind(suffix)); } #endif /// @param filename the filename to inspect /// @returns the inferred format for the filename suffix Format infer_format(const std::string& filename) { (void)filename; #if TINT_BUILD_SPV_WRITER if (ends_with(filename, ".spv")) { return Format::kSpirv; } if (ends_with(filename, ".spvasm")) { return Format::kSpvAsm; } #endif // TINT_BUILD_SPV_WRITER #if TINT_BUILD_WGSL_WRITER if (ends_with(filename, ".wgsl")) { return Format::kWgsl; } #endif // TINT_BUILD_WGSL_WRITER #if TINT_BUILD_MSL_WRITER if (ends_with(filename, ".metal")) { return Format::kMsl; } #endif // TINT_BUILD_MSL_WRITER #if TINT_BUILD_HLSL_WRITER if (ends_with(filename, ".hlsl")) { return Format::kHlsl; } #endif // TINT_BUILD_HLSL_WRITER return Format::kNone; } std::vector split_transform_names(std::string list) { std::vector res; std::stringstream str(list); while (str.good()) { std::string substr; getline(str, substr, ','); res.push_back(substr); } return res; } std::string TextureDimensionToString( tint::inspector::ResourceBinding::TextureDimension dim) { switch (dim) { case tint::inspector::ResourceBinding::TextureDimension::kNone: return "None"; case tint::inspector::ResourceBinding::TextureDimension::k1d: return "1d"; case tint::inspector::ResourceBinding::TextureDimension::k2d: return "2d"; case tint::inspector::ResourceBinding::TextureDimension::k2dArray: return "2dArray"; case tint::inspector::ResourceBinding::TextureDimension::k3d: return "3d"; case tint::inspector::ResourceBinding::TextureDimension::kCube: return "Cube"; case tint::inspector::ResourceBinding::TextureDimension::kCubeArray: return "CubeArray"; } return "Unknown"; } std::string SampledKindToString( tint::inspector::ResourceBinding::SampledKind kind) { switch (kind) { case tint::inspector::ResourceBinding::SampledKind::kFloat: return "Float"; case tint::inspector::ResourceBinding::SampledKind::kUInt: return "UInt"; case tint::inspector::ResourceBinding::SampledKind::kSInt: return "SInt"; case tint::inspector::ResourceBinding::SampledKind::kUnknown: break; } return "Unknown"; } std::string ImageFormatToString( tint::inspector::ResourceBinding::ImageFormat format) { switch (format) { case tint::inspector::ResourceBinding::ImageFormat::kR8Unorm: return "R8Unorm"; case tint::inspector::ResourceBinding::ImageFormat::kR8Snorm: return "R8Snorm"; case tint::inspector::ResourceBinding::ImageFormat::kR8Uint: return "R8Uint"; case tint::inspector::ResourceBinding::ImageFormat::kR8Sint: return "R8Sint"; case tint::inspector::ResourceBinding::ImageFormat::kR16Uint: return "R16Uint"; case tint::inspector::ResourceBinding::ImageFormat::kR16Sint: return "R16Sint"; case tint::inspector::ResourceBinding::ImageFormat::kR16Float: return "R16Float"; case tint::inspector::ResourceBinding::ImageFormat::kRg8Unorm: return "Rg8Unorm"; case tint::inspector::ResourceBinding::ImageFormat::kRg8Snorm: return "Rg8Snorm"; case tint::inspector::ResourceBinding::ImageFormat::kRg8Uint: return "Rg8Uint"; case tint::inspector::ResourceBinding::ImageFormat::kRg8Sint: return "Rg8Sint"; case tint::inspector::ResourceBinding::ImageFormat::kR32Uint: return "R32Uint"; case tint::inspector::ResourceBinding::ImageFormat::kR32Sint: return "R32Sint"; case tint::inspector::ResourceBinding::ImageFormat::kR32Float: return "R32Float"; case tint::inspector::ResourceBinding::ImageFormat::kRg16Uint: return "Rg16Uint"; case tint::inspector::ResourceBinding::ImageFormat::kRg16Sint: return "Rg16Sint"; case tint::inspector::ResourceBinding::ImageFormat::kRg16Float: return "Rg16Float"; case tint::inspector::ResourceBinding::ImageFormat::kRgba8Unorm: return "Rgba8Unorm"; case tint::inspector::ResourceBinding::ImageFormat::kRgba8UnormSrgb: return "Rgba8UnormSrgb"; case tint::inspector::ResourceBinding::ImageFormat::kRgba8Snorm: return "Rgba8Snorm"; case tint::inspector::ResourceBinding::ImageFormat::kRgba8Uint: return "Rgba8Uint"; case tint::inspector::ResourceBinding::ImageFormat::kRgba8Sint: return "Rgba8Sint"; case tint::inspector::ResourceBinding::ImageFormat::kBgra8Unorm: return "Bgra8Unorm"; case tint::inspector::ResourceBinding::ImageFormat::kBgra8UnormSrgb: return "Bgra8UnormSrgb"; case tint::inspector::ResourceBinding::ImageFormat::kRgb10A2Unorm: return "Rgb10A2Unorm"; case tint::inspector::ResourceBinding::ImageFormat::kRg11B10Float: return "Rg11B10Float"; case tint::inspector::ResourceBinding::ImageFormat::kRg32Uint: return "Rg32Uint"; case tint::inspector::ResourceBinding::ImageFormat::kRg32Sint: return "Rg32Sint"; case tint::inspector::ResourceBinding::ImageFormat::kRg32Float: return "Rg32Float"; case tint::inspector::ResourceBinding::ImageFormat::kRgba16Uint: return "Rgba16Uint"; case tint::inspector::ResourceBinding::ImageFormat::kRgba16Sint: return "Rgba16Sint"; case tint::inspector::ResourceBinding::ImageFormat::kRgba16Float: return "Rgba16Float"; case tint::inspector::ResourceBinding::ImageFormat::kRgba32Uint: return "Rgba32Uint"; case tint::inspector::ResourceBinding::ImageFormat::kRgba32Sint: return "Rgba32Sint"; case tint::inspector::ResourceBinding::ImageFormat::kRgba32Float: return "Rgba32Float"; case tint::inspector::ResourceBinding::ImageFormat::kNone: return "None"; } return "Unknown"; } std::string ResourceTypeToString( tint::inspector::ResourceBinding::ResourceType type) { switch (type) { case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer: return "UniformBuffer"; case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer: return "StorageBuffer"; case tint::inspector::ResourceBinding::ResourceType::kReadOnlyStorageBuffer: return "ReadOnlyStorageBuffer"; case tint::inspector::ResourceBinding::ResourceType::kSampler: return "Sampler"; case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler: return "ComparisonSampler"; case tint::inspector::ResourceBinding::ResourceType::kSampledTexture: return "SampledTexture"; case tint::inspector::ResourceBinding::ResourceType::kMultisampledTexture: return "MultisampledTexture"; case tint::inspector::ResourceBinding::ResourceType:: kWriteOnlyStorageTexture: return "WriteOnlyStorageTexture"; case tint::inspector::ResourceBinding::ResourceType::kDepthTexture: return "DepthTexture"; case tint::inspector::ResourceBinding::ResourceType:: kDepthMultisampledTexture: return "DepthMultisampledTexture"; case tint::inspector::ResourceBinding::ResourceType::kExternalTexture: return "ExternalTexture"; } return "Unknown"; } bool ParseArgs(const std::vector& args, Options* opts) { for (size_t i = 1; i < args.size(); ++i) { const std::string& arg = args[i]; if (arg == "--format") { ++i; if (i >= args.size()) { std::cerr << "Missing value for --format argument." << std::endl; return false; } opts->format = parse_format(args[i]); if (opts->format == Format::kNone) { std::cerr << "Unknown output format: " << args[i] << std::endl; return false; } } else if (arg == "-ep") { if (i + 1 >= args.size()) { std::cerr << "Missing value for -ep" << std::endl; return false; } i++; opts->ep_name = args[i]; opts->emit_single_entry_point = true; } else if (arg == "-o" || arg == "--output-name") { ++i; if (i >= args.size()) { std::cerr << "Missing value for " << arg << std::endl; return false; } opts->output_file = args[i]; } else if (arg == "-h" || arg == "--help") { opts->show_help = true; } else if (arg == "--transform") { ++i; if (i >= args.size()) { std::cerr << "Missing value for " << arg << std::endl; return false; } opts->transforms = split_transform_names(args[i]); } else if (arg == "--parse-only") { opts->parse_only = true; } else if (arg == "--disable-workgroup-init") { opts->disable_workgroup_init = true; } else if (arg == "--demangle") { opts->demangle = true; } else if (arg == "--dump-inspector-bindings") { opts->dump_inspector_bindings = true; } else if (arg == "--validate") { opts->validate = true; } else if (arg == "--fxc") { opts->validate = true; opts->use_fxc = true; } else if (arg == "--dxc") { ++i; if (i >= args.size()) { std::cerr << "Missing value for " << arg << std::endl; return false; } opts->dxc_path = args[i]; opts->validate = true; } else if (arg == "--xcrun") { ++i; if (i >= args.size()) { std::cerr << "Missing value for " << arg << std::endl; return false; } opts->xcrun_path = args[i]; opts->validate = true; } else if (!arg.empty()) { if (arg[0] == '-') { std::cerr << "Unrecognized option: " << arg << std::endl; return false; } if (!opts->input_filename.empty()) { std::cerr << "More than one input file specified: '" << opts->input_filename << "' and '" << arg << "'" << std::endl; return false; } opts->input_filename = arg; } } return true; } /// Copies the content from the file named `input_file` to `buffer`, /// assuming each element in the file is of type `T`. If any error occurs, /// writes error messages to the standard error stream and returns false. /// Assumes the size of a `T` object is divisible by its required alignment. /// @returns true if we successfully read the file. template bool ReadFile(const std::string& input_file, std::vector* buffer) { if (!buffer) { std::cerr << "The buffer pointer was null" << std::endl; return false; } FILE* file = nullptr; #if defined(_MSC_VER) fopen_s(&file, input_file.c_str(), "rb"); #else file = fopen(input_file.c_str(), "rb"); #endif if (!file) { std::cerr << "Failed to open " << input_file << std::endl; return false; } fseek(file, 0, SEEK_END); uint64_t tell_file_size = static_cast(ftell(file)); if (tell_file_size <= 0) { std::cerr << "Input file of incorrect size: " << input_file << std::endl; fclose(file); return {}; } const auto file_size = static_cast(tell_file_size); if (0 != (file_size % sizeof(T))) { std::cerr << "File " << input_file << " does not contain an integral number of objects: " << file_size << " bytes in the file, require " << sizeof(T) << " bytes per object" << std::endl; fclose(file); return false; } fseek(file, 0, SEEK_SET); buffer->clear(); buffer->resize(file_size / sizeof(T)); size_t bytes_read = fread(buffer->data(), 1, file_size, file); fclose(file); if (bytes_read != file_size) { std::cerr << "Failed to read " << input_file << std::endl; return false; } return true; } /// Writes the given `buffer` into the file named as `output_file` using the /// given `mode`. If `output_file` is empty or "-", writes to standard /// output. If any error occurs, returns false and outputs error message to /// standard error. The ContainerT type must have data() and size() methods, /// like `std::string` and `std::vector` do. /// @returns true on success template bool WriteFile(const std::string& output_file, const std::string mode, const ContainerT& buffer) { const bool use_stdout = output_file.empty() || output_file == "-"; FILE* file = stdout; if (!use_stdout) { #if defined(_MSC_VER) fopen_s(&file, output_file.c_str(), mode.c_str()); #else file = fopen(output_file.c_str(), mode.c_str()); #endif if (!file) { std::cerr << "Could not open file " << output_file << " for writing" << std::endl; return false; } } size_t written = fwrite(buffer.data(), sizeof(typename ContainerT::value_type), buffer.size(), file); if (buffer.size() != written) { if (use_stdout) { std::cerr << "Could not write all output to standard output" << std::endl; } else { std::cerr << "Could not write to file " << output_file << std::endl; fclose(file); } return false; } if (!use_stdout) { fclose(file); } return true; } #if TINT_BUILD_SPV_WRITER std::string Disassemble(const std::vector& data) { std::string spv_errors; spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0; auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*, const spv_position_t& position, const char* message) { switch (level) { case SPV_MSG_FATAL: case SPV_MSG_INTERNAL_ERROR: case SPV_MSG_ERROR: spv_errors += "error: line " + std::to_string(position.index) + ": " + message + "\n"; break; case SPV_MSG_WARNING: spv_errors += "warning: line " + std::to_string(position.index) + ": " + message + "\n"; break; case SPV_MSG_INFO: spv_errors += "info: line " + std::to_string(position.index) + ": " + message + "\n"; break; case SPV_MSG_DEBUG: break; } }; spvtools::SpirvTools tools(target_env); tools.SetMessageConsumer(msg_consumer); std::string result; if (!tools.Disassemble(data, &result, SPV_BINARY_TO_TEXT_OPTION_INDENT | SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)) { std::cerr << spv_errors << std::endl; } return result; } #endif // TINT_BUILD_SPV_WRITER /// PrintWGSL writes the WGSL of the program to the provided ostream, if the /// WGSL writer is enabled, otherwise it does nothing. /// @param out the output stream to write the WGSL to /// @param program the program void PrintWGSL(std::ostream& out, const tint::Program& program) { #if TINT_BUILD_WGSL_WRITER tint::writer::wgsl::Options options; auto result = tint::writer::wgsl::Generate(&program, options); out << std::endl << result.wgsl << std::endl; #else (void)out; (void)program; #endif } /// Generate SPIR-V code for a program. /// @param program the program to generate /// @param options the options that Tint was invoked with /// @returns true on success bool GenerateSpirv(const tint::Program* program, const Options& options) { #if TINT_BUILD_SPV_WRITER // TODO(jrprice): Provide a way for the user to set non-default options. tint::writer::spirv::Options gen_options; gen_options.disable_workgroup_init = options.disable_workgroup_init; auto result = tint::writer::spirv::Generate(program, gen_options); if (!result.success) { PrintWGSL(std::cerr, *program); std::cerr << "Failed to generate: " << result.error << std::endl; return false; } if (options.format == Format::kSpvAsm) { if (!WriteFile(options.output_file, "w", Disassemble(result.spirv))) { return false; } } else { if (!WriteFile(options.output_file, "wb", result.spirv)) { return false; } } if (options.validate) { // Use Vulkan 1.1, since this is what Tint, internally, uses. spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); tools.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t& pos, const char* msg) { std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg << std::endl; }); if (!tools.Validate(result.spirv.data(), result.spirv.size(), spvtools::ValidatorOptions())) { return false; } } return true; #else (void)program; (void)options; std::cerr << "SPIR-V writer not enabled in tint build" << std::endl; return false; #endif // TINT_BUILD_SPV_WRITER } /// Generate WGSL code for a program. /// @param program the program to generate /// @param options the options that Tint was invoked with /// @returns true on success bool GenerateWgsl(const tint::Program* program, const Options& options) { #if TINT_BUILD_WGSL_WRITER // TODO(jrprice): Provide a way for the user to set non-default options. tint::writer::wgsl::Options gen_options; auto result = tint::writer::wgsl::Generate(program, gen_options); if (!result.success) { std::cerr << "Failed to generate: " << result.error << std::endl; return false; } return WriteFile(options.output_file, "w", result.wgsl); #else (void)program; (void)options; std::cerr << "WGSL writer not enabled in tint build" << std::endl; return false; #endif // TINT_BUILD_WGSL_WRITER } /// Generate MSL code for a program. /// @param program the program to generate /// @param options the options that Tint was invoked with /// @returns true on success bool GenerateMsl(const tint::Program* program, const Options& options) { #if TINT_BUILD_MSL_WRITER const tint::Program* input_program = program; // Remap resource numbers to a flat namespace. // TODO(crbug.com/tint/1101): Make this more robust for multiple entry points. using BindingPoint = tint::transform::BindingPoint; tint::transform::BindingRemapper::BindingPoints binding_points; uint32_t next_buffer_idx = 0; uint32_t next_sampler_idx = 0; uint32_t next_texture_idx = 0; tint::inspector::Inspector inspector(program); auto entry_points = inspector.GetEntryPoints(); for (auto& entry_point : entry_points) { auto bindings = inspector.GetResourceBindings(entry_point.name); for (auto& binding : bindings) { BindingPoint src = {binding.bind_group, binding.binding}; if (binding_points.count(src)) { continue; } switch (binding.resource_type) { case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer: case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer: case tint::inspector::ResourceBinding::ResourceType:: kReadOnlyStorageBuffer: binding_points.emplace(src, BindingPoint{0, next_buffer_idx++}); break; case tint::inspector::ResourceBinding::ResourceType::kSampler: case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler: binding_points.emplace(src, BindingPoint{0, next_sampler_idx++}); break; case tint::inspector::ResourceBinding::ResourceType::kSampledTexture: case tint::inspector::ResourceBinding::ResourceType:: kMultisampledTexture: case tint::inspector::ResourceBinding::ResourceType:: kWriteOnlyStorageTexture: case tint::inspector::ResourceBinding::ResourceType::kDepthTexture: case tint::inspector::ResourceBinding::ResourceType:: kDepthMultisampledTexture: case tint::inspector::ResourceBinding::ResourceType::kExternalTexture: binding_points.emplace(src, BindingPoint{0, next_texture_idx++}); break; } } } // Run the binding remapper transform. tint::transform::Output transform_output; if (!binding_points.empty()) { tint::transform::Manager manager; tint::transform::DataMap inputs; inputs.Add( std::move(binding_points), tint::transform::BindingRemapper::AccessControls{}, /* mayCollide */ true); manager.Add(); transform_output = manager.Run(program, inputs); input_program = &transform_output.program; } // TODO(jrprice): Provide a way for the user to set non-default options. tint::writer::msl::Options gen_options; gen_options.disable_workgroup_init = options.disable_workgroup_init; auto result = tint::writer::msl::Generate(input_program, gen_options); if (!result.success) { PrintWGSL(std::cerr, *program); std::cerr << "Failed to generate: " << result.error << std::endl; return false; } if (!WriteFile(options.output_file, "w", result.msl)) { return false; } if (options.validate) { tint::val::Result res; #ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API res = tint::val::MslUsingMetalAPI(result.msl); #else #ifdef _WIN32 const char* default_xcrun_exe = "metal.exe"; #else const char* default_xcrun_exe = "xcrun"; #endif auto xcrun = tint::utils::Command::LookPath( options.xcrun_path.empty() ? default_xcrun_exe : options.xcrun_path); if (xcrun.Found()) { res = tint::val::Msl(xcrun.Path(), result.msl); } else { res.output = "xcrun executable not found. Cannot validate."; res.failed = true; } #endif // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API if (res.failed) { std::cerr << res.output << std::endl; return false; } } return true; #else (void)program; (void)options; std::cerr << "MSL writer not enabled in tint build" << std::endl; return false; #endif // TINT_BUILD_MSL_WRITER } /// Generate HLSL code for a program. /// @param program the program to generate /// @param options the options that Tint was invoked with /// @returns true on success bool GenerateHlsl(const tint::Program* program, const Options& options) { #if TINT_BUILD_HLSL_WRITER // TODO(jrprice): Provide a way for the user to set non-default options. tint::writer::hlsl::Options gen_options; gen_options.disable_workgroup_init = options.disable_workgroup_init; auto result = tint::writer::hlsl::Generate(program, gen_options); if (!result.success) { PrintWGSL(std::cerr, *program); std::cerr << "Failed to generate: " << result.error << std::endl; return false; } if (!WriteFile(options.output_file, "w", result.hlsl)) { return false; } if (options.validate) { tint::val::Result res; if (options.use_fxc) { #ifdef _WIN32 res = tint::val::HlslUsingFXC(result.hlsl, result.entry_points); #else res.failed = true; res.output = "FXC can only be used on Windows. Sorry :X"; #endif // _WIN32 } else { auto dxc = tint::utils::Command::LookPath( options.dxc_path.empty() ? "dxc" : options.dxc_path); if (dxc.Found()) { res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl, result.entry_points); } else { res.failed = true; res.output = "DXC executable not found. Cannot validate"; } } if (res.failed) { std::cerr << res.output << std::endl; return false; } } return true; #else (void)program; (void)options; std::cerr << "HLSL writer not enabled in tint build" << std::endl; return false; #endif // TINT_BUILD_HLSL_WRITER } #if TINT_BUILD_GLSL_WRITER EShLanguage pipeline_stage_to_esh_language(tint::ast::PipelineStage stage) { switch (stage) { case tint::ast::PipelineStage::kFragment: return EShLangFragment; case tint::ast::PipelineStage::kVertex: return EShLangVertex; case tint::ast::PipelineStage::kCompute: return EShLangCompute; default: TINT_ASSERT(AST, false); return EShLangVertex; } } #endif /// Generate GLSL code for a program. /// @param program the program to generate /// @param options the options that Tint was invoked with /// @returns true on success bool GenerateGlsl(const tint::Program* program, const Options& options) { #if TINT_BUILD_GLSL_WRITER bool success = true; if (options.validate) { glslang::InitializeProcess(); } tint::writer::glsl::Options gen_options; tint::inspector::Inspector inspector(program); for (auto& entry_point : inspector.GetEntryPoints()) { auto result = tint::writer::glsl::Generate(program, gen_options, entry_point.name); if (!result.success) { PrintWGSL(std::cerr, *program); std::cerr << "Failed to generate: " << result.error << std::endl; return false; } if (!WriteFile(options.output_file, "w", result.glsl)) { return false; } if (options.validate) { for (auto entry_pt : result.entry_points) { EShLanguage lang = pipeline_stage_to_esh_language(entry_pt.second); glslang::TShader shader(lang); const char* strings[1] = {result.glsl.c_str()}; int lengths[1] = {static_cast(result.glsl.length())}; shader.setStringsWithLengths(strings, lengths, 1); shader.setEntryPoint("main"); bool glslang_result = shader.parse(&glslang::DefaultTBuiltInResource, 310, EEsProfile, false, false, EShMsgDefault); if (!glslang_result) { std::cerr << "Error parsing GLSL shader:\n" << shader.getInfoLog() << "\n" << shader.getInfoDebugLog() << "\n"; success = false; } } } } return success; #else (void)program; (void)options; std::cerr << "GLSL writer not enabled in tint build" << std::endl; return false; #endif // TINT_BUILD_GLSL_WRITER } } // namespace int main(int argc, const char** argv) { std::vector args(argv, argv + argc); Options options; tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter); #if TINT_BUILD_WGSL_WRITER tint::Program::printer = [](const tint::Program* program) { auto result = tint::writer::wgsl::Generate(program, {}); if (!result.error.empty()) { return "error: " + result.error; } return result.wgsl; }; #endif // TINT_BUILD_WGSL_WRITER if (!ParseArgs(args, &options)) { std::cerr << "Failed to parse arguments." << std::endl; return 1; } if (options.show_help) { std::cout << kUsage << std::endl; return 0; } // Implement output format defaults. if (options.format == Format::kNone) { // Try inferring from filename. options.format = infer_format(options.output_file); } if (options.format == Format::kNone) { // Ultimately, default to SPIR-V assembly. That's nice for interactive use. options.format = Format::kSpvAsm; } auto diag_printer = tint::diag::Printer::create(stderr, true); tint::diag::Formatter diag_formatter; std::unique_ptr program; std::unique_ptr source_file; enum class InputFormat { kUnknown, kWgsl, kSpirvBin, kSpirvAsm, }; auto input_format = InputFormat::kUnknown; if (options.input_filename.size() > 5 && options.input_filename.substr(options.input_filename.size() - 5) == ".wgsl") { input_format = InputFormat::kWgsl; } else if (options.input_filename.size() > 4 && options.input_filename.substr(options.input_filename.size() - 4) == ".spv") { input_format = InputFormat::kSpirvBin; } else if (options.input_filename.size() > 7 && options.input_filename.substr(options.input_filename.size() - 7) == ".spvasm") { input_format = InputFormat::kSpirvAsm; } switch (input_format) { case InputFormat::kUnknown: { std::cerr << "Unknown input format" << std::endl; return 1; } case InputFormat::kWgsl: { #if TINT_BUILD_WGSL_READER std::vector data; if (!ReadFile(options.input_filename, &data)) { return 1; } source_file = std::make_unique( options.input_filename, std::string(data.begin(), data.end())); program = std::make_unique( tint::reader::wgsl::Parse(source_file.get())); break; #else std::cerr << "Tint not built with the WGSL reader enabled" << std::endl; return 1; #endif // TINT_BUILD_WGSL_READER } case InputFormat::kSpirvBin: { #if TINT_BUILD_SPV_READER std::vector data; if (!ReadFile(options.input_filename, &data)) { return 1; } program = std::make_unique(tint::reader::spirv::Parse(data)); break; #else std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl; return 1; #endif // TINT_BUILD_SPV_READER } case InputFormat::kSpirvAsm: { #if TINT_BUILD_SPV_READER std::vector text; if (!ReadFile(options.input_filename, &text)) { return 1; } // Use Vulkan 1.1, since this is what Tint, internally, is expecting. spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); tools.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t& pos, const char* msg) { std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg << std::endl; }); std::vector data; if (!tools.Assemble(text.data(), text.size(), &data, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)) { return 1; } program = std::make_unique(tint::reader::spirv::Parse(data)); break; #else std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl; return 1; #endif // TINT_BUILD_SPV_READER } } if (!program) { std::cerr << "Failed to parse input file: " << options.input_filename << std::endl; return 1; } if (program->Diagnostics().count() > 0) { if (!program->IsValid() && input_format != InputFormat::kWgsl) { // Invalid program from a non-wgsl source. Print the WGSL, to help // understand the diagnostics. PrintWGSL(std::cout, *program); } diag_formatter.format(program->Diagnostics(), diag_printer.get()); } if (!program->IsValid()) { return 1; } if (options.parse_only) { return 1; } tint::transform::Manager transform_manager; tint::transform::DataMap transform_inputs; for (const auto& name : options.transforms) { // TODO(dsinclair): The vertex pulling transform requires setup code to // be run that needs user input. Should we find a way to support that here // maybe through a provided file? if (name == "first_index_offset") { transform_inputs.Add(0, 0); transform_manager.Add(); } else if (name == "fold_trivial_single_use_lets") { transform_manager.Add(); } else if (name == "renamer") { transform_manager.Add(); } else if (name == "robustness") { transform_manager.Add(); } else { std::cerr << "Unknown transform name: " << name << std::endl; return 1; } } if (options.emit_single_entry_point) { transform_manager.append( std::make_unique()); transform_inputs.Add( options.ep_name); } switch (options.format) { case Format::kMsl: { #if TINT_BUILD_MSL_WRITER transform_inputs.Add( tint::transform::Renamer::Target::kMslKeywords); transform_manager.Add(); #endif // TINT_BUILD_MSL_WRITER break; } #if TINT_BUILD_GLSL_WRITER case Format::kGlsl: { transform_inputs.Add( tint::transform::Renamer::Target::kGlslKeywords); transform_manager.Add(); break; } #endif // TINT_BUILD_GLSL_WRITER case Format::kHlsl: { #if TINT_BUILD_HLSL_WRITER transform_inputs.Add( tint::transform::Renamer::Target::kHlslKeywords); transform_manager.Add(); #endif // TINT_BUILD_HLSL_WRITER break; } default: break; } auto out = transform_manager.Run(program.get(), std::move(transform_inputs)); if (!out.program.IsValid()) { PrintWGSL(std::cerr, out.program); diag_formatter.format(out.program.Diagnostics(), diag_printer.get()); return 1; } *program = std::move(out.program); if (options.dump_inspector_bindings) { std::cout << std::string(80, '-') << std::endl; tint::inspector::Inspector inspector(program.get()); auto entry_points = inspector.GetEntryPoints(); if (!inspector.error().empty()) { std::cerr << "Failed to get entry points from Inspector: " << inspector.error() << std::endl; return 1; } for (auto& entry_point : entry_points) { auto bindings = inspector.GetResourceBindings(entry_point.name); if (!inspector.error().empty()) { std::cerr << "Failed to get bindings from Inspector: " << inspector.error() << std::endl; return 1; } std::cout << "Entry Point = " << entry_point.name << std::endl; for (auto& binding : bindings) { std::cout << "\t[" << binding.bind_group << "][" << binding.binding << "]:" << std::endl; std::cout << "\t\t resource_type = " << ResourceTypeToString(binding.resource_type) << std::endl; std::cout << "\t\t dim = " << TextureDimensionToString(binding.dim) << std::endl; std::cout << "\t\t sampled_kind = " << SampledKindToString(binding.sampled_kind) << std::endl; std::cout << "\t\t image_format = " << ImageFormatToString(binding.image_format) << std::endl; } } std::cout << std::string(80, '-') << std::endl; } bool success = false; switch (options.format) { case Format::kSpirv: case Format::kSpvAsm: success = GenerateSpirv(program.get(), options); break; case Format::kWgsl: success = GenerateWgsl(program.get(), options); break; case Format::kMsl: success = GenerateMsl(program.get(), options); break; case Format::kHlsl: success = GenerateHlsl(program.get(), options); break; case Format::kGlsl: success = GenerateGlsl(program.get(), options); break; default: std::cerr << "Unknown output format specified" << std::endl; return 1; } if (!success) { return 1; } return 0; }