| // 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 <charconv> |
| #include <cstdio> |
| #include <fstream> |
| #include <iostream> |
| #include <limits> |
| #include <memory> |
| #include <optional> |
| #include <sstream> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| #if TINT_BUILD_GLSL_WRITER |
| #include "glslang/Public/ResourceLimits.h" |
| #include "glslang/Public/ShaderLang.h" |
| #endif // TINT_BUILD_GLSL_WRITER |
| |
| #if TINT_BUILD_SPV_READER |
| #include "spirv-tools/libspirv.hpp" |
| #endif // TINT_BUILD_SPV_READER |
| |
| #include "src/tint/ast/module.h" |
| #include "src/tint/utils/io/command.h" |
| #include "src/tint/utils/string.h" |
| #include "src/tint/utils/transform.h" |
| #include "src/tint/val/val.h" |
| #include "tint/tint.h" |
| |
| #if TINT_BUILD_IR |
| #include "src/tint/ir/debug.h" |
| #include "src/tint/ir/disassembler.h" |
| #include "src/tint/ir/module.h" |
| #endif // TINT_BUILD_IR |
| |
| 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); |
| } |
| |
| /// Prints the given hash value in a format string that the end-to-end test runner can parse. |
| void PrintHash(uint32_t hash) { |
| std::cout << "<<HASH: 0x" << std::hex << hash << ">>" << std::endl; |
| } |
| |
| enum class Format { |
| kUnknown, |
| kNone, |
| kSpirv, |
| kSpvAsm, |
| kWgsl, |
| kMsl, |
| kHlsl, |
| kGlsl, |
| }; |
| |
| struct Options { |
| bool show_help = false; |
| bool verbose = 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 print_hash = false; |
| bool demangle = false; |
| bool dump_inspector_bindings = false; |
| |
| std::unordered_set<uint32_t> skip_hash; |
| |
| Format format = Format::kUnknown; |
| |
| bool emit_single_entry_point = false; |
| std::string ep_name; |
| |
| bool rename_all = false; |
| |
| #if TINT_BUILD_SPV_READER |
| tint::reader::spirv::Options spirv_reader_options; |
| #endif |
| |
| std::vector<std::string> transforms; |
| |
| std::string fxc_path; |
| std::string dxc_path; |
| std::string xcrun_path; |
| std::unordered_map<std::string, double> overrides; |
| std::optional<tint::sem::BindingPoint> hlsl_root_constant_binding_point; |
| |
| #if TINT_BUILD_IR |
| bool dump_ir = false; |
| bool dump_ir_graph = false; |
| #endif // TINT_BUILD_IR |
| }; |
| |
| const char kUsage[] = R"(Usage: tint [options] <input-file> |
| |
| options: |
| --format <spirv|spvasm|wgsl|msl|hlsl|none> -- 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 <name> -- Output single entry point |
| --output-file <name> -- Output file name. Use "-" for standard output |
| -o <name> -- Output file name. Use "-" for standard output |
| --transform <name list> -- Runs transforms, name list is comma separated |
| Available transforms: |
| ${transforms} --parse-only -- Stop after parsing the input |
| --allow-non-uniform-derivatives -- When using SPIR-V input, allow non-uniform derivatives by |
| inserting a module-scope directive to suppress any uniformity |
| violations that may be produced. |
| --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 |
| --hlsl-root-constant-binding-point <group>,<binding> -- Binding point for root constant. |
| Specify the binding point for generated uniform buffer |
| used for num_workgroups in HLSL. If not specified, then |
| default to binding 0 of the largest used group plus 1, |
| or group 0 if no resource bound. |
| --validate -- Validates the generated shader with all available validators |
| --skip-hash <hash list> -- Skips validation if the hash of the output is equal to any |
| of the hash codes in the comma separated list of hashes |
| --print-hash -- Emit the hash of the output program |
| --fxc -- Path to FXC dll, used to validate HLSL output. |
| When specified, automatically enables HLSL validation with FXC |
| --dxc -- Path to DXC executable, used to validate HLSL output. |
| When specified, automatically enables HLSL validation with DXC |
| --xcrun -- Path to xcrun executable, used to validate MSL output. |
| When specified, automatically enables MSL validation |
| --overrides -- Override values as IDENTIFIER=VALUE, comma-separated. |
| --rename-all -- Renames all symbols. |
| )"; |
| |
| 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 |
| |
| if (fmt == "none") { |
| return Format::kNone; |
| } |
| |
| return Format::kUnknown; |
| } |
| |
| #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::kUnknown; |
| } |
| |
| std::vector<std::string> split_on_char(std::string list, char c) { |
| std::vector<std::string> res; |
| |
| std::stringstream str(list); |
| while (str.good()) { |
| std::string substr; |
| getline(str, substr, c); |
| res.push_back(substr); |
| } |
| return res; |
| } |
| |
| std::vector<std::string> split_on_comma(std::string list) { |
| return split_on_char(list, ','); |
| } |
| |
| std::vector<std::string> split_on_equal(std::string list) { |
| return split_on_char(list, '='); |
| } |
| |
| std::optional<uint64_t> parse_unsigned_number(std::string number) { |
| for (char c : number) { |
| if (!std::isdigit(c)) { |
| // Found a non-digital char, return nullopt |
| return std::nullopt; |
| } |
| } |
| |
| errno = 0; |
| char* p_end; |
| uint64_t result; |
| // std::strtoull will not throw exception. |
| result = std::strtoull(number.c_str(), &p_end, 10); |
| if ((errno != 0) || (static_cast<size_t>(p_end - number.c_str()) != number.length())) { |
| // Unexpected conversion result |
| return std::nullopt; |
| } |
| |
| return result; |
| } |
| |
| 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 TexelFormatToString(tint::inspector::ResourceBinding::TexelFormat format) { |
| switch (format) { |
| case tint::inspector::ResourceBinding::TexelFormat::kR32Uint: |
| return "R32Uint"; |
| case tint::inspector::ResourceBinding::TexelFormat::kR32Sint: |
| return "R32Sint"; |
| case tint::inspector::ResourceBinding::TexelFormat::kR32Float: |
| return "R32Float"; |
| case tint::inspector::ResourceBinding::TexelFormat::kBgra8Unorm: |
| return "Bgra8Unorm"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRgba8Unorm: |
| return "Rgba8Unorm"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRgba8Snorm: |
| return "Rgba8Snorm"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRgba8Uint: |
| return "Rgba8Uint"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRgba8Sint: |
| return "Rgba8Sint"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRg32Uint: |
| return "Rg32Uint"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRg32Sint: |
| return "Rg32Sint"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRg32Float: |
| return "Rg32Float"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRgba16Uint: |
| return "Rgba16Uint"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRgba16Sint: |
| return "Rgba16Sint"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRgba16Float: |
| return "Rgba16Float"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRgba32Uint: |
| return "Rgba32Uint"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRgba32Sint: |
| return "Rgba32Sint"; |
| case tint::inspector::ResourceBinding::TexelFormat::kRgba32Float: |
| return "Rgba32Float"; |
| case tint::inspector::ResourceBinding::TexelFormat::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<std::string>& 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::kUnknown) { |
| 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 == "-v" || arg == "--verbose") { |
| opts->verbose = true; |
| } else if (arg == "--transform") { |
| ++i; |
| if (i >= args.size()) { |
| std::cerr << "Missing value for " << arg << std::endl; |
| return false; |
| } |
| opts->transforms = split_on_comma(args[i]); |
| } else if (arg == "--parse-only") { |
| opts->parse_only = true; |
| } else if (arg == "--allow-non-uniform-derivatives") { |
| #if TINT_BUILD_SPV_READER |
| opts->spirv_reader_options.allow_non_uniform_derivatives = true; |
| #else |
| std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl; |
| return false; |
| #endif |
| } 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 == "--skip-hash") { |
| ++i; |
| if (i >= args.size()) { |
| std::cerr << "Missing hash value for " << arg << std::endl; |
| return false; |
| } |
| for (auto hash : split_on_comma(args[i])) { |
| uint32_t value = 0; |
| int base = 10; |
| if (hash.size() > 2 && hash[0] == '0' && (hash[1] == 'x' || hash[1] == 'X')) { |
| hash = hash.substr(2); |
| base = 16; |
| } |
| std::from_chars(hash.data(), hash.data() + hash.size(), value, base); |
| opts->skip_hash.emplace(value); |
| } |
| } else if (arg == "--print-hash") { |
| opts->print_hash = true; |
| } else if (arg == "--fxc") { |
| ++i; |
| if (i >= args.size()) { |
| std::cerr << "Missing value for " << arg << std::endl; |
| return false; |
| } |
| opts->fxc_path = args[i]; |
| } else if (arg == "--dxc") { |
| ++i; |
| if (i >= args.size()) { |
| std::cerr << "Missing value for " << arg << std::endl; |
| return false; |
| } |
| opts->dxc_path = args[i]; |
| #if TINT_BUILD_IR |
| } else if (arg == "--dump-ir") { |
| opts->dump_ir = true; |
| } else if (arg == "--dump-ir-graph") { |
| opts->dump_ir_graph = true; |
| #endif // TINT_BUILD_IR |
| } 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 == "--overrides") { |
| ++i; |
| if (i >= args.size()) { |
| std::cerr << "Missing value for " << arg << std::endl; |
| return false; |
| } |
| for (const auto& o : split_on_comma(args[i])) { |
| auto parts = split_on_equal(o); |
| opts->overrides.insert({parts[0], std::stod(parts[1])}); |
| } |
| } else if (arg == "--rename-all") { |
| ++i; |
| opts->rename_all = true; |
| } else if (arg == "--hlsl-root-constant-binding-point") { |
| ++i; |
| if (i >= args.size()) { |
| std::cerr << "Missing value for " << arg << std::endl; |
| return false; |
| } |
| auto binding_points = split_on_comma(args[i]); |
| if (binding_points.size() != 2) { |
| std::cerr << "Invalid binding point for " << arg << ": " << args[i] << std::endl; |
| return false; |
| } |
| auto group = parse_unsigned_number(binding_points[0]); |
| if ((!group.has_value()) || (group.value() > std::numeric_limits<uint32_t>::max())) { |
| std::cerr << "Invalid group for " << arg << ": " << binding_points[0] << std::endl; |
| return false; |
| } |
| auto binding = parse_unsigned_number(binding_points[1]); |
| if ((!binding.has_value()) || |
| (binding.value() > std::numeric_limits<uint32_t>::max())) { |
| std::cerr << "Invalid binding for " << arg << ": " << binding_points[1] |
| << std::endl; |
| return false; |
| } |
| opts->hlsl_root_constant_binding_point = tint::sem::BindingPoint{ |
| static_cast<uint32_t>(group.value()), static_cast<uint32_t>(binding.value())}; |
| } 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 <typename T> |
| bool ReadFile(const std::string& input_file, std::vector<T>* 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); |
| const auto file_size = static_cast<size_t>(ftell(file)); |
| 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 <typename ContainerT> |
| 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<uint32_t>& 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; |
| gen_options.generate_external_texture_bindings = true; |
| 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; |
| } |
| } |
| |
| const auto hash = tint::utils::CRC32(result.spirv.data(), result.spirv.size()); |
| if (options.print_hash) { |
| PrintHash(hash); |
| } |
| |
| if (options.validate && options.skip_hash.count(hash) == 0) { |
| // 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; |
| } |
| |
| if (!WriteFile(options.output_file, "w", result.wgsl)) { |
| return false; |
| } |
| |
| const auto hash = tint::utils::CRC32(result.wgsl.data(), result.wgsl.size()); |
| if (options.print_hash) { |
| PrintHash(hash); |
| } |
| |
| if (options.validate && options.skip_hash.count(hash) == 0) { |
| // Attempt to re-parse the output program with Tint's WGSL reader. |
| auto source = std::make_unique<tint::Source::File>(options.input_filename, result.wgsl); |
| auto reparsed_program = tint::reader::wgsl::Parse(source.get()); |
| if (!reparsed_program.IsValid()) { |
| auto diag_printer = tint::diag::Printer::create(stderr, true); |
| tint::diag::Formatter diag_formatter; |
| diag_formatter.format(reparsed_program.Diagnostics(), diag_printer.get()); |
| return false; |
| } |
| } |
| |
| return true; |
| #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 |
| // Remap resource numbers to a flat namespace. |
| // TODO(crbug.com/tint/1501): Do this via Options::BindingMap. |
| const tint::Program* input_program = program; |
| auto flattened = tint::writer::FlattenBindings(program); |
| if (flattened) { |
| input_program = &*flattened; |
| } |
| |
| // 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; |
| gen_options.generate_external_texture_bindings = true; |
| 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; |
| } |
| |
| const auto hash = tint::utils::CRC32(result.msl.c_str()); |
| if (options.print_hash) { |
| PrintHash(hash); |
| } |
| |
| if (options.validate && options.skip_hash.count(hash) == 0) { |
| 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; |
| gen_options.generate_external_texture_bindings = true; |
| gen_options.root_constant_binding_point = options.hlsl_root_constant_binding_point; |
| 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; |
| } |
| |
| const auto hash = tint::utils::CRC32(result.hlsl.c_str()); |
| if (options.print_hash) { |
| PrintHash(hash); |
| } |
| |
| // If --fxc or --dxc was passed, then we must explicitly find and validate with that respective |
| // compiler. |
| const bool must_validate_dxc = !options.dxc_path.empty(); |
| const bool must_validate_fxc = !options.fxc_path.empty(); |
| if ((options.validate || must_validate_dxc || must_validate_fxc) && |
| (options.skip_hash.count(hash) == 0)) { |
| tint::val::Result dxc_res; |
| bool dxc_found = false; |
| if (options.validate || must_validate_dxc) { |
| auto dxc = |
| tint::utils::Command::LookPath(options.dxc_path.empty() ? "dxc" : options.dxc_path); |
| if (dxc.Found()) { |
| dxc_found = true; |
| |
| auto enable_list = program->AST().Enables(); |
| bool dxc_require_16bit_types = false; |
| for (auto enable : enable_list) { |
| if (enable->extension == tint::ast::Extension::kF16) { |
| dxc_require_16bit_types = true; |
| break; |
| } |
| } |
| |
| dxc_res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl, result.entry_points, |
| dxc_require_16bit_types); |
| } else if (must_validate_dxc) { |
| // DXC was explicitly requested. Error if it could not be found. |
| dxc_res.failed = true; |
| dxc_res.output = |
| "DXC executable '" + options.dxc_path + "' not found. Cannot validate"; |
| } |
| } |
| |
| tint::val::Result fxc_res; |
| bool fxc_found = false; |
| if (options.validate || must_validate_fxc) { |
| auto fxc = tint::utils::Command::LookPath( |
| options.fxc_path.empty() ? tint::val::kFxcDLLName : options.fxc_path); |
| |
| #ifdef _WIN32 |
| if (fxc.Found()) { |
| fxc_found = true; |
| fxc_res = tint::val::HlslUsingFXC(fxc.Path(), result.hlsl, result.entry_points); |
| } else if (must_validate_fxc) { |
| // FXC was explicitly requested. Error if it could not be found. |
| fxc_res.failed = true; |
| fxc_res.output = "FXC DLL '" + options.fxc_path + "' not found. Cannot validate"; |
| } |
| #else |
| if (must_validate_dxc) { |
| fxc_res.failed = true; |
| fxc_res.output = "FXC can only be used on Windows."; |
| } |
| #endif // _WIN32 |
| } |
| |
| if (fxc_res.failed) { |
| std::cerr << "FXC validation failure:" << std::endl << fxc_res.output << std::endl; |
| } |
| if (dxc_res.failed) { |
| std::cerr << "DXC validation failure:" << std::endl << dxc_res.output << std::endl; |
| } |
| if (fxc_res.failed || dxc_res.failed) { |
| return false; |
| } |
| if (!fxc_found && !dxc_found) { |
| std::cerr << "Couldn't find FXC or DXC. Cannot validate" << std::endl; |
| return false; |
| } |
| if (options.verbose) { |
| if (fxc_found && !fxc_res.failed) { |
| std::cout << "Passed FXC validation" << std::endl; |
| std::cout << fxc_res.output; |
| std::cout << std::endl; |
| } |
| if (dxc_found && !dxc_res.failed) { |
| std::cout << "Passed DXC validation" << std::endl; |
| std::cout << dxc_res.output; |
| std::cout << std::endl; |
| } |
| } |
| } |
| |
| 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 |
| if (options.validate) { |
| glslang::InitializeProcess(); |
| } |
| |
| auto generate = [&](const tint::Program* prg, const std::string entry_point_name) -> bool { |
| tint::writer::glsl::Options gen_options; |
| gen_options.generate_external_texture_bindings = true; |
| auto result = tint::writer::glsl::Generate(prg, gen_options, entry_point_name); |
| if (!result.success) { |
| PrintWGSL(std::cerr, *prg); |
| std::cerr << "Failed to generate: " << result.error << std::endl; |
| return false; |
| } |
| |
| if (!WriteFile(options.output_file, "w", result.glsl)) { |
| return false; |
| } |
| |
| const auto hash = tint::utils::CRC32(result.glsl.c_str()); |
| if (options.print_hash) { |
| PrintHash(hash); |
| } |
| |
| if (options.validate && options.skip_hash.count(hash) == 0) { |
| 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<int>(result.glsl.length())}; |
| shader.setStringsWithLengths(strings, lengths, 1); |
| shader.setEntryPoint("main"); |
| bool glslang_result = shader.parse(GetDefaultResources(), 310, EEsProfile, false, |
| false, EShMsgDefault); |
| if (!glslang_result) { |
| std::cerr << "Error parsing GLSL shader:\n" |
| << shader.getInfoLog() << "\n" |
| << shader.getInfoDebugLog() << "\n"; |
| return false; |
| } |
| } |
| } |
| return true; |
| }; |
| |
| tint::inspector::Inspector inspector(program); |
| |
| if (inspector.GetEntryPoints().empty()) { |
| // Pass empty string here so that the GLSL generator will generate |
| // code for all functions, reachable or not. |
| return generate(program, ""); |
| } |
| |
| bool success = true; |
| for (auto& entry_point : inspector.GetEntryPoints()) { |
| success &= generate(program, entry_point.name); |
| } |
| 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<std::string> 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; |
| } |
| |
| struct TransformFactory { |
| const char* name; |
| /// Build and adds the transform to the transform manager. |
| /// Parameters: |
| /// inspector - an inspector created from the parsed program |
| /// manager - the transform manager. Add transforms to this. |
| /// inputs - the input data to the transform manager. Add inputs to this. |
| /// Returns true on success, false on error (program will immediately exit) |
| std::function<bool(tint::inspector::Inspector& inspector, |
| tint::transform::Manager& manager, |
| tint::transform::DataMap& inputs)> |
| make; |
| }; |
| std::vector<TransformFactory> transforms = { |
| {"first_index_offset", |
| [](tint::inspector::Inspector&, tint::transform::Manager& m, tint::transform::DataMap& i) { |
| i.Add<tint::transform::FirstIndexOffset::BindingPoint>(0, 0); |
| m.Add<tint::transform::FirstIndexOffset>(); |
| return true; |
| }}, |
| {"renamer", |
| [](tint::inspector::Inspector&, tint::transform::Manager& m, tint::transform::DataMap&) { |
| m.Add<tint::transform::Renamer>(); |
| return true; |
| }}, |
| {"robustness", |
| [](tint::inspector::Inspector&, tint::transform::Manager& m, tint::transform::DataMap&) { |
| m.Add<tint::transform::Robustness>(); |
| return true; |
| }}, |
| {"substitute_override", |
| [&](tint::inspector::Inspector& inspector, tint::transform::Manager& m, |
| tint::transform::DataMap& i) { |
| tint::transform::SubstituteOverride::Config cfg; |
| |
| std::unordered_map<tint::OverrideId, double> values; |
| values.reserve(options.overrides.size()); |
| |
| for (const auto& [name, value] : options.overrides) { |
| if (name.empty()) { |
| std::cerr << "empty override name"; |
| return false; |
| } |
| if (isdigit(name[0])) { |
| tint::OverrideId id{ |
| static_cast<decltype(tint::OverrideId::value)>(atoi(name.c_str()))}; |
| values.emplace(id, value); |
| } else { |
| auto override_names = inspector.GetNamedOverrideIds(); |
| auto it = override_names.find(name); |
| if (it == override_names.end()) { |
| std::cerr << "unknown override '" << name << "'"; |
| return false; |
| } |
| values.emplace(it->second, value); |
| } |
| } |
| |
| cfg.map = std::move(values); |
| |
| i.Add<tint::transform::SubstituteOverride::Config>(cfg); |
| m.Add<tint::transform::SubstituteOverride>(); |
| return true; |
| }}, |
| {"multiplaner_external_texture", |
| [](tint::inspector::Inspector& inspector, tint::transform::Manager& m, |
| tint::transform::DataMap& i) { |
| using MET = tint::transform::MultiplanarExternalTexture; |
| |
| // Generate the MultiplanarExternalTexture::NewBindingPoints by finding two free |
| // binding points. We may wish to expose these binding points via a command line flag |
| // in the future. |
| |
| // Set of all the group-0 bindings in use. |
| std::unordered_set<uint32_t> group0_bindings_in_use; |
| auto allocate_binding = [&] { |
| for (uint32_t idx = 0;; idx++) { |
| auto binding = tint::transform::BindingPoint{0u, idx}; |
| if (group0_bindings_in_use.emplace(idx).second) { |
| return binding; |
| } |
| } |
| }; |
| // Populate group0_bindings_in_use with the existing bindings across all entry points. |
| for (auto ep : inspector.GetEntryPoints()) { |
| for (auto binding : inspector.GetResourceBindings(ep.name)) { |
| if (binding.bind_group == 0) { |
| group0_bindings_in_use.emplace(binding.binding); |
| } |
| } |
| } |
| // Allocate new binding points for the external texture's planes and parameters. |
| MET::BindingsMap met_bindings; |
| for (auto ep : inspector.GetEntryPoints()) { |
| for (auto ext_tex : inspector.GetExternalTextureResourceBindings(ep.name)) { |
| auto binding = tint::transform::BindingPoint{ |
| ext_tex.bind_group, |
| ext_tex.binding, |
| }; |
| if (met_bindings.count(binding)) { |
| continue; |
| } |
| met_bindings.emplace(binding, MET::BindingPoints{ |
| /* plane_1 */ allocate_binding(), |
| /* params */ allocate_binding(), |
| }); |
| } |
| } |
| |
| i.Add<MET::NewBindingPoints>(std::move(met_bindings)); |
| m.Add<MET>(); |
| return true; |
| }}, |
| }; |
| auto transform_names = [&] { |
| std::stringstream names; |
| for (auto& t : transforms) { |
| names << " " << t.name << std::endl; |
| } |
| return names.str(); |
| }; |
| |
| if (options.show_help) { |
| std::string usage = tint::utils::ReplaceAll(kUsage, "${transforms}", transform_names()); |
| #if TINT_BUILD_IR |
| usage += |
| " --dump-ir -- Writes the IR to stdout\n" |
| " --dump-ir-graph -- Writes the IR graph to 'tint.dot' as a dot graph\n"; |
| #endif // TINT_BUILD_IR |
| |
| std::cout << usage << std::endl; |
| return 0; |
| } |
| |
| // Implement output format defaults. |
| if (options.format == Format::kUnknown) { |
| // Try inferring from filename. |
| options.format = infer_format(options.output_file); |
| } |
| if (options.format == Format::kUnknown) { |
| // 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<tint::Program> program; |
| std::unique_ptr<tint::Source::File> 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<uint8_t> data; |
| if (!ReadFile<uint8_t>(options.input_filename, &data)) { |
| return 1; |
| } |
| source_file = std::make_unique<tint::Source::File>( |
| options.input_filename, std::string(data.begin(), data.end())); |
| program = std::make_unique<tint::Program>(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<uint32_t> data; |
| if (!ReadFile<uint32_t>(options.input_filename, &data)) { |
| return 1; |
| } |
| program = std::make_unique<tint::Program>( |
| tint::reader::spirv::Parse(data, options.spirv_reader_options)); |
| 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<char> text; |
| if (!ReadFile<char>(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<uint32_t> data; |
| if (!tools.Assemble(text.data(), text.size(), &data, |
| SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)) { |
| return 1; |
| } |
| program = std::make_unique<tint::Program>( |
| tint::reader::spirv::Parse(data, options.spirv_reader_options)); |
| 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; |
| } |
| |
| #if TINT_BUILD_IR |
| if (options.dump_ir || options.dump_ir_graph) { |
| auto result = tint::ir::Module::FromProgram(program.get()); |
| if (!result) { |
| std::cerr << "Failed to build IR from program: " << result.Failure() << std::endl; |
| } else { |
| auto mod = result.Move(); |
| if (options.dump_ir) { |
| tint::ir::Disassembler d(mod); |
| std::cout << d.Disassemble() << std::endl; |
| } |
| if (options.dump_ir_graph) { |
| auto graph = tint::ir::Debug::AsDotGraph(&mod); |
| WriteFile("tint.dot", "w", graph); |
| } |
| } |
| } |
| #endif // TINT_BUILD_IR |
| |
| tint::inspector::Inspector inspector(program.get()); |
| |
| if (options.dump_inspector_bindings) { |
| std::cout << std::string(80, '-') << std::endl; |
| 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 = " << TexelFormatToString(binding.image_format) |
| << std::endl; |
| } |
| } |
| std::cout << std::string(80, '-') << std::endl; |
| } |
| |
| tint::transform::Manager transform_manager; |
| tint::transform::DataMap transform_inputs; |
| |
| // Renaming must always come first |
| switch (options.format) { |
| case Format::kMsl: { |
| #if TINT_BUILD_MSL_WRITER |
| transform_inputs.Add<tint::transform::Renamer::Config>( |
| options.rename_all ? tint::transform::Renamer::Target::kAll |
| : tint::transform::Renamer::Target::kMslKeywords, |
| /* preserve_unicode */ false); |
| transform_manager.Add<tint::transform::Renamer>(); |
| #endif // TINT_BUILD_MSL_WRITER |
| break; |
| } |
| #if TINT_BUILD_GLSL_WRITER |
| case Format::kGlsl: { |
| transform_inputs.Add<tint::transform::Renamer::Config>( |
| options.rename_all ? tint::transform::Renamer::Target::kAll |
| : tint::transform::Renamer::Target::kGlslKeywords, |
| /* preserve_unicode */ false); |
| transform_manager.Add<tint::transform::Renamer>(); |
| break; |
| } |
| #endif // TINT_BUILD_GLSL_WRITER |
| case Format::kHlsl: { |
| #if TINT_BUILD_HLSL_WRITER |
| transform_inputs.Add<tint::transform::Renamer::Config>( |
| options.rename_all ? tint::transform::Renamer::Target::kAll |
| : tint::transform::Renamer::Target::kHlslKeywords, |
| /* preserve_unicode */ false); |
| transform_manager.Add<tint::transform::Renamer>(); |
| #endif // TINT_BUILD_HLSL_WRITER |
| break; |
| } |
| default: { |
| if (options.rename_all) { |
| transform_manager.Add<tint::transform::Renamer>(); |
| } |
| break; |
| } |
| } |
| |
| auto enable_transform = [&](std::string_view name) { |
| for (auto& t : transforms) { |
| if (t.name == name) { |
| return t.make(inspector, transform_manager, transform_inputs); |
| } |
| } |
| |
| std::cerr << "Unknown transform: " << name << std::endl; |
| std::cerr << "Available transforms: " << std::endl << transform_names(); |
| return false; |
| }; |
| |
| // If overrides are provided, add the SubstituteOverride transform. |
| if (!options.overrides.empty()) { |
| if (!enable_transform("substitute_override")) { |
| return 1; |
| } |
| } |
| |
| 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 (!enable_transform(name)) { |
| return 1; |
| } |
| } |
| |
| if (options.emit_single_entry_point) { |
| transform_manager.append(std::make_unique<tint::transform::SingleEntryPoint>()); |
| transform_inputs.Add<tint::transform::SingleEntryPoint::Config>(options.ep_name); |
| } |
| |
| 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); |
| |
| 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; |
| case Format::kNone: |
| break; |
| default: |
| std::cerr << "Unknown output format specified" << std::endl; |
| return 1; |
| } |
| if (!success) { |
| return 1; |
| } |
| |
| return 0; |
| } |