blob: 233b55a51c939c8bcbb82163dd3027500523b3fe [file]
// Copyright 2020 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <charconv>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#if TINT_BUILD_SPV_READER || TINT_BUILD_SPV_WRITER
#include "spirv-tools/libspirv.hpp"
#endif // TINT_BUILD_SPV_READER || TINT_BUILD_SPV_WRITER
#include "src/tint/api/common/substitute_overrides_config.h"
#include "src/tint/api/helpers/generate_bindings.h"
#include "src/tint/api/tint.h"
#include "src/tint/cmd/common/helper.h"
#include "src/tint/lang/core/ir/disassembler.h"
#include "src/tint/lang/core/ir/referenced_module_vars.h"
#include "src/tint/lang/core/ir/transform/resource_table_helper.h"
#include "src/tint/lang/core/ir/var.h"
#include "src/tint/lang/core/type/f16.h"
#include "src/tint/lang/core/type/pointer.h"
#include "src/tint/utils/command/args.h"
#include "src/tint/utils/command/cli.h"
#include "src/tint/utils/command/command.h"
#include "src/tint/utils/containers/transform.h"
#include "src/tint/utils/diagnostic/diagnostic.h"
#include "src/tint/utils/diagnostic/formatter.h"
#include "src/tint/utils/macros/defer.h"
#include "src/tint/utils/text/color_mode.h"
#include "src/tint/utils/text/string.h"
#include "src/tint/utils/text/styled_text.h"
#include "src/tint/utils/text/styled_text_printer.h"
#if TINT_BUILD_WGSL_READER
#include "src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h"
#include "src/tint/lang/wgsl/reader/reader.h"
#endif // TINT_BUILD_WGSL_READER
#if TINT_BUILD_SPV_WRITER
#include "src/tint/lang/spirv/writer/writer.h"
#endif // TINT_BUILD_SPV_WRITER
#if TINT_BUILD_WGSL_WRITER
#include "src/tint/lang/wgsl/writer/writer.h"
#endif // TINT_BUILD_WGSL_WRITER
#if TINT_BUILD_MSL_WRITER
#include "src/tint/lang/msl/validate/validate.h"
#include "src/tint/lang/msl/writer/writer.h"
#endif // TINT_BUILD_MSL_WRITER
#if TINT_BUILD_HLSL_WRITER
#include "src/tint/lang/hlsl/validate/validate.h"
#include "src/tint/lang/hlsl/writer/writer.h"
#endif // TINT_BUILD_HLSL_WRITER
#if TINT_BUILD_GLSL_WRITER
#include "src/tint/lang/glsl/writer/helpers/generate_bindings.h"
#include "src/tint/lang/glsl/writer/writer.h"
#endif // TINT_BUILD_GLSL_WRITER
#if TINT_BUILD_GLSL_VALIDATOR
#include "src/tint/lang/glsl/validate/validate.h"
#endif // TINT_BUILD_GLSL_VALIDATOR
namespace {
/// Prints the given hash value in a format string that the end-to-end test runner can parse.
[[maybe_unused]] void PrintHash(uint32_t hash) {
std::cout << "<<HASH: 0x" << std::hex << hash << ">>\n";
}
enum class Format : uint8_t {
kUnknown,
kNone,
kSpirv,
kSpvAsm,
kWgsl,
kMsl,
kHlsl,
kHlslFxc,
kGlsl,
kIr,
};
enum class ExeMode : uint8_t {
kStandalone,
kServer,
};
#if TINT_BUILD_HLSL_WRITER
constexpr uint32_t kMinShaderModelForDXC = 60u;
constexpr uint32_t kMaxSupportedShaderModelForDXC = 66u;
constexpr uint32_t kMinShaderModelForDP4aInHLSL = 64u;
constexpr uint32_t kMinShaderModelForPackUnpack4x8InHLSL = 66u;
#endif // TINT_BUILD_HLSL_WRITER
struct Options {
std::unique_ptr<tint::StyledTextPrinter> printer;
std::string input_filename;
std::string output_file = "-"; // Default to stdout
std::unordered_set<uint32_t> skip_hash;
tint::Hashmap<std::string, double, 8> overrides;
std::string ep_name;
bool emit_single_entry_point = false;
Format format = Format::kUnknown;
tint::cmd::InputFormat input_format = tint::cmd::InputFormat::kUnknown;
bool verbose = false;
bool parse_only = false;
bool disable_workgroup_init = false;
bool disable_demote_to_helper = false;
bool validate = false;
bool print_hash = false;
bool dump_inspector_bindings = false;
bool rename_all = false;
bool enable_robustness = true;
bool dump_ir = false;
bool ir_roundtrip = false;
#if TINT_BUILD_SPV_READER
tint::spirv::reader::Options spirv_reader_options;
#endif // TINT_BUILD_SPV_READER
#if TINT_BUILD_WGSL_WRITER
tint::wgsl::writer::Options wgsl_writer_options;
#endif // TINT_BUILD_SPV_READER
#if TINT_BUILD_SPV_WRITER
bool use_storage_input_output_16 = true;
tint::spirv::writer::SpvVersion spirv_version = tint::spirv::writer::SpvVersion::kSpv13;
std::unordered_set<tint::BindingPoint> ycbcr_bindings;
#endif // TINT_BULD_SPV_WRITER
#if TINT_BUILD_MSL_WRITER
std::string xcrun_path;
bool use_argument_buffers = false;
std::unordered_map<uint32_t, tint::msl::writer::ArgumentBufferInfo>
group_to_argument_buffer_info;
std::unordered_map<uint32_t, uint32_t> pixel_local_attachments;
tint::msl::validate::MslVersion msl_version = tint::msl::validate::MslVersion::kMsl_2_3;
#endif
#if TINT_BUILD_HLSL_WRITER
std::string fxc_path;
std::string dxc_path;
uint32_t hlsl_shader_model = kMinShaderModelForDXC;
tint::hlsl::writer::PixelLocalOptions pixel_local_options;
#endif // TINT_BUILD_HLSL_WRITER
#if TINT_BUILD_GLSL_WRITER
bool glsl_desktop = false;
std::vector<uint32_t> bgra_swizzle;
#endif // TINT_BUILD_GLSL_WRITER
};
/// @param filename the filename to inspect
/// @returns the inferred format for the filename suffix
Format InferFormat(const std::string& filename) {
(void)filename;
#if TINT_BUILD_SPV_WRITER
if (filename.ends_with(".spv")) {
return Format::kSpirv;
}
if (filename.ends_with(".spvasm")) {
return Format::kSpvAsm;
}
#endif // TINT_BUILD_SPV_WRITER
#if TINT_BUILD_WGSL_WRITER
if (filename.ends_with(".wgsl")) {
return Format::kWgsl;
}
#endif // TINT_BUILD_WGSL_WRITER
#if TINT_BUILD_MSL_WRITER
if (filename.ends_with(".metal")) {
return Format::kMsl;
}
#endif // TINT_BUILD_MSL_WRITER
#if TINT_BUILD_HLSL_WRITER
if (filename.ends_with(".hlsl")) {
return Format::kHlsl;
}
#endif // TINT_BUILD_HLSL_WRITER
return Format::kUnknown;
}
// The actual warning occurs on `std::from_chars(hash.data(), hash.data() + hash.size(), value,
// base);`, but disabling/enabling warnings cannot be done within function scope
TINT_BEGIN_DISABLE_WARNING(UNSAFE_BUFFER_USAGE);
bool ParseArgs(tint::VectorRef<std::string_view> arguments, Options* opts, ExeMode exe_mode) {
using namespace tint::cli; // NOLINT(build/namespaces)
tint::Vector<EnumName<Format>, 8> format_enum_names{
EnumName(Format::kNone, "none"),
};
#if TINT_BUILD_WGSL_WRITER
format_enum_names.Emplace(Format::kWgsl, "wgsl");
#endif
#if TINT_BUILD_WGSL_READER
format_enum_names.Emplace(Format::kIr, "ir");
#endif
#if TINT_BUILD_SPV_WRITER
format_enum_names.Emplace(Format::kSpirv, "spirv");
format_enum_names.Emplace(Format::kSpvAsm, "spvasm");
#endif
#if TINT_BUILD_MSL_WRITER
format_enum_names.Emplace(Format::kMsl, "msl");
#endif
#if TINT_BUILD_HLSL_WRITER
format_enum_names.Emplace(Format::kHlsl, "hlsl");
format_enum_names.Emplace(Format::kHlslFxc, "hlsl-fxc");
#endif
#if TINT_BUILD_GLSL_WRITER
format_enum_names.Emplace(Format::kGlsl, "glsl");
#endif
OptionSet options;
auto& fmt = options.Add<EnumOption<Format>>("format",
R"(Output format.
If not provided, will be inferred from output filename extension:
.spvasm -> spvasm
.spv -> spirv
.wgsl -> wgsl
.metal -> msl
.hlsl -> hlsl)",
format_enum_names, ShortName{"f"});
TINT_DEFER(opts->format = fmt.value.value_or(Format::kUnknown));
tint::Vector<EnumName<tint::cmd::InputFormat>, 3> input_format_enum_names;
#if TINT_BUILD_WGSL_READER
input_format_enum_names.Emplace(tint::cmd::InputFormat::kWgsl, "wgsl");
#endif
#if TINT_BUILD_SPV_READER
input_format_enum_names.Emplace(tint::cmd::InputFormat::kSpirvBin, "spirv");
input_format_enum_names.Emplace(tint::cmd::InputFormat::kSpirvAsm, "spvasm");
#endif
auto& input_fmt =
options.Add<EnumOption<tint::cmd::InputFormat>>("input-format",
R"(Input format.
If not provided, will be inferred from input filename extension:
.spvasm -> spvasm
.spv -> spirv
.wgsl -> wgsl)",
input_format_enum_names, ShortName{"if"});
TINT_DEFER(opts->input_format = input_fmt.value.value_or(tint::cmd::InputFormat::kUnknown));
const auto default_color_mode =
exe_mode == ExeMode::kServer ? tint::ColorMode::kPlain : tint::ColorModeDefault();
auto& col =
options.Add<EnumOption<tint::ColorMode>>("color", "Use colored output",
tint::Vector{
EnumName{tint::ColorMode::kPlain, "off"},
EnumName{tint::ColorMode::kDark, "dark"},
EnumName{tint::ColorMode::kLight, "light"},
},
ShortName{"col"}, Default{default_color_mode});
TINT_DEFER(opts->printer = CreatePrinter(*col.value));
auto& ep = options.Add<StringOption>("entry-point", "Output single entry point",
ShortName{"ep"}, Parameter{"name"});
TINT_DEFER({
if (ep.value.has_value()) {
opts->ep_name = *ep.value;
opts->emit_single_entry_point = true;
}
});
auto& output = options.Add<StringOption>("output-name", "Output file name", ShortName{"o"},
Parameter{"name"});
TINT_DEFER(opts->output_file = output.value.value_or(""));
auto& disable_wg_init = options.Add<BoolOption>(
"disable-workgroup-init", "Disable workgroup memory zero initialization", Default{false});
TINT_DEFER(opts->disable_workgroup_init = *disable_wg_init.value);
auto& disable_demote_to_helper = options.Add<BoolOption>(
"disable-demote-to-helper", "Disable demote to helper for discard", Default{false});
TINT_DEFER(opts->disable_demote_to_helper = *disable_demote_to_helper.value);
auto& disable_robustness =
options.Add<BoolOption>("disable-robustness", "Disable the robustness transform");
TINT_DEFER({
auto disable = disable_robustness.value.value_or(false);
opts->enable_robustness = !disable;
});
auto& rename_all = options.Add<BoolOption>("rename-all", "Renames all symbols", Default{false});
TINT_DEFER(opts->rename_all = *rename_all.value);
#if TINT_BUILD_WGSL_WRITER
auto& minify = options.Add<BoolOption>("minify", "Minify the output WGSL", Default{false});
TINT_DEFER(opts->wgsl_writer_options.minify = *minify.value);
#endif
auto& overrides = options.Add<StringOption>(
"overrides", "Override values as IDENTIFIER=VALUE, comma-separated");
#if TINT_BUILD_HLSL_WRITER
auto& pixel_local_attachment_formats =
options.Add<StringOption>("pixel-local-attachment-formats",
R"(Pixel local storage attachment formats, comma-separated
Each binding is of the form MEMBER_INDEX=ATTACHMENT_FORMAT,
where MEMBER_INDEX is the pixel-local structure member
index and ATTACHMENT_FORMAT is the format of the emitted
attachment, which can only be one of the below value:
R32Sint, R32Uint, R32Float.
)");
std::stringstream hlslShaderModelStream;
hlslShaderModelStream << R"(
An integer value to set the HLSL shader model for the generated HLSL
shader, which will only be used with option `--dxc`. Now only integers
in the range [)" << kMinShaderModelForDXC
<< ", " << kMaxSupportedShaderModelForDXC
<< "] are accepted. The integer \"6x\" represents shader model 6.x.";
auto& hlsl_shader_model = options.Add<ValueOption<uint32_t>>(
"hlsl-shader-model", hlslShaderModelStream.str(), Default{kMinShaderModelForDXC});
#endif // TINT_BUILD_HLSL_WRITER
#if TINT_BUILD_HLSL_WRITER || TINT_BUILD_MSL_WRITER
auto& pixel_local_attachments =
options.Add<StringOption>("pixel-local-attachments",
R"(Pixel local storage attachment bindings, comma-separated
Each binding is of the form MEMBER_INDEX=ATTACHMENT_INDEX,
where MEMBER_INDEX is the pixel-local structure member
index and ATTACHMENT_INDEX is the index of the emitted
attachment.
)");
#endif
auto& print_hash = options.Add<BoolOption>("print-hash", "Emit the hash of the output program",
Default{false});
TINT_DEFER(opts->print_hash = *print_hash.value);
auto& skip_hash = options.Add<StringOption>(
"skip-hash", R"(Skips validation if the hash of the output is equal to any
of the hash codes in the comma separated list of hashes)");
TINT_DEFER({
if (skip_hash.value.has_value()) {
for (auto hash : tint::Split(*skip_hash.value, ",")) {
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);
}
}
});
#if TINT_BUILD_WGSL_WRITER
auto& allow_nud = options.Add<BoolOption>(
"allow-non-uniform-derivatives",
R"(Allow non-uniform derivatives by inserting a module-scope directive to suppress any uniformity
violations that may be produced)",
Default{false});
TINT_DEFER({
if (allow_nud.value.value_or(false)) {
opts->wgsl_writer_options.allow_non_uniform_derivatives = true;
}
});
auto& ignore_unreachable = options.Add<BoolOption>(
"disable-unreachable-code-warning",
R"(Disable the warning for unreachable code when converting to WGSL)", Default{false});
TINT_DEFER({
if (ignore_unreachable.value.value_or(false)) {
opts->wgsl_writer_options.disable_unreachable_code_warning = true;
}
});
#endif
#if TINT_BUILD_SPV_READER
auto& sampler_mapping = options.Add<StringOption>(
"sampler-mapping",
"Allows remapping the binding points of samplers from the SPIR-V file. "
"This allows setting a correct binding point for samplers which were part of a combined "
"texture/sampler pair. Entries are provided as binding point pairs (group, binding) and "
"each provides a (source:destination) mapping. (e.g. 1,2:3,4) Multiple entries should "
"be separated with a space.",
Default{""});
#endif
#if TINT_BUILD_SPV_WRITER
auto& use_storage_input_output_16 =
options.Add<BoolOption>("use-storage-input-output-16",
"Use the StorageInputOutput16 SPIR-V capability", Default{true});
TINT_DEFER(opts->use_storage_input_output_16 = *use_storage_input_output_16.value);
tint::Vector<EnumName<tint::spirv::writer::SpvVersion>, 2> version_enum_names{
EnumName(tint::spirv::writer::SpvVersion::kSpv13, "1.3"),
EnumName(tint::spirv::writer::SpvVersion::kSpv14, "1.4"),
};
auto& spirv_version = options.Add<EnumOption<tint::spirv::writer::SpvVersion>>(
"spirv-version", R"(Specify the SPIR-V binary version.
Valid values are 1.3 and 1.4)",
version_enum_names, Default{tint::spirv::writer::SpvVersion::kSpv13});
TINT_DEFER(opts->spirv_version = *spirv_version.value);
auto& ycbcr_binding_data = options.Add<StringOption>(
"ycbcr-bindings",
"Allows setting an external texture as YCBCR. "
"This allows specifying that the external texture at the given binding point should be "
"considered a YCBCR texture. Entries are provided as binding point pairs (group, binding) "
"(e.g. 1,2). Multiple entries should be separated with a space.",
Default{""});
#endif // TINT_BUILD_SPV_WRITER
#if TINT_BUILD_GLSL_WRITER
auto& glsl_desktop = options.Add<BoolOption>(
"glsl-desktop", "Set the version to the desktop GL instead of ES", Default{false});
TINT_DEFER(opts->glsl_desktop = *glsl_desktop.value);
auto& bgra_swizzle =
options.Add<StringOption>("bgra-swizzle", "BGRA swizzle indices", Default{""});
#endif // TINT_BUILD_GLSL_WRITER
#if TINT_BUILD_MSL_WRITER
auto& xcrun =
options.Add<StringOption>("xcrun", R"(Path to xcrun executable, used to validate MSL output.
When specified, automatically enables MSL validation)",
Parameter{"path"});
TINT_DEFER({
if (xcrun.value.has_value()) {
opts->xcrun_path = *xcrun.value;
opts->validate = true;
}
});
auto& use_argument_buffers = options.Add<BoolOption>(
"use-argument-buffers", "Use the Argument Buffers in MSL", Default{false});
TINT_DEFER(opts->use_argument_buffers = *use_argument_buffers.value);
auto& arg_buffer = options.Add<StringOption>(
"argument-buffer",
R"(Mapping for an argument buffer, format is GROUP=ARGUMENT_BUFFER_ID, comma separated)");
auto& dynamic_buffer = options.Add<StringOption>(
"dynamic-offset-buffer",
R"(Mapping for a dynamic offset buffer, format is GROUP=DYNAMIC_BUFFER_ID, comma separated))");
auto& dynamic_offset = options.Add<StringOption>(
"dynamic-offset",
R"(Mapping for dynamic buffers to be attached to the entry point, format is GROUP.BINDING=OFFSET, comma separated. BINDING is the BindingIndex, not @binding BindingNumber))");
// Default to validating against MSL 2.3, which corresponds to macOS 11.0.
tint::Vector<EnumName<tint::msl::validate::MslVersion>, 2> msl_version_enum_names{
EnumName(tint::msl::validate::MslVersion::kMsl_2_3, "2.3"),
EnumName(tint::msl::validate::MslVersion::kMsl_2_4, "2.4"),
EnumName(tint::msl::validate::MslVersion::kMsl_3_2, "3.2"),
};
auto& msl_version = options.Add<EnumOption<tint::msl::validate::MslVersion>>(
"msl-version", R"(Specify the MSL version.
Valid values are 2.3, 2.4, and 3.2)",
msl_version_enum_names, Default{tint::msl::validate::MslVersion::kMsl_2_3});
TINT_DEFER(opts->msl_version = *msl_version.value);
#endif // TINT_BUILD_MSL_WRITER
#if TINT_BUILD_HLSL_WRITER
auto& fxc_path =
options.Add<StringOption>("fxc", R"(Path to FXC dll, used to validate HLSL output.
When specified, automatically enables HLSL validation)",
Parameter{"path"});
TINT_DEFER(opts->fxc_path = fxc_path.value.value_or(""));
auto& dxc_path =
options.Add<StringOption>("dxc", R"(Path to DXC dll, used to validate HLSL output.
When specified, automatically enables HLSL validation)",
Parameter{"path"});
TINT_DEFER(opts->dxc_path = dxc_path.value.value_or(""));
#endif // TINT_BUILD_HLSL_WRITER
auto& validate = options.Add<BoolOption>(
"validate", "Validates the generated shader with all available validators", Default{false});
TINT_DEFER(opts->validate = *validate.value);
auto& dump_ir = options.Add<BoolOption>("dump-ir", "Writes the IR to stdout", Alias{"emit-ir"},
Default{false});
TINT_DEFER(opts->dump_ir = *dump_ir.value);
auto& ir_roundtrip = options.Add<BoolOption>(
"ir-roundtrip", "Converts the Program to IR and then back to a Program", Default{false});
TINT_DEFER(opts->ir_roundtrip = *ir_roundtrip.value);
auto& dump_inspector_bindings = options.Add<BoolOption>(
"dump-inspector-bindings", "Dump reflection data about bindings to stdout",
Alias{"emit-inspector-bindings"}, Default{false});
TINT_DEFER(opts->dump_inspector_bindings = *dump_inspector_bindings.value);
auto& parse_only =
options.Add<BoolOption>("parse-only", "Stop after parsing the input", Default{false});
TINT_DEFER(opts->parse_only = *parse_only.value);
auto& verbose =
options.Add<BoolOption>("verbose", "Verbose output", ShortName{"v"}, Default{false});
TINT_DEFER(opts->verbose = *verbose.value);
auto& help = options.Add<BoolOption>("help", "Show usage", ShortName{"h"});
auto show_usage = [&] {
std::cout << R"(Usage: tint [options] <input-file>
Options:
)";
options.ShowHelp(std::cout);
};
auto result = options.Parse(arguments);
if (result != tint::Success) {
std::cerr << result.Failure() << "\n";
show_usage();
return false;
}
if (help.value.value_or(false)) {
show_usage();
return false;
}
if (overrides.value.has_value()) {
for (const auto& o : tint::Split(*overrides.value, ",")) {
auto parts = tint::Split(o, "=");
if (parts.Length() != 2) {
std::cerr << "override values must be of the form IDENTIFIER=VALUE";
return false;
}
auto value = tint::strconv::ParseNumber<double>(parts[1]);
if (value != tint::Success) {
std::cerr << "invalid override value: " << parts[1];
return false;
}
opts->overrides.Add(std::string(parts[0]), value.Get());
}
}
#if TINT_BUILD_SPV_READER
if (!sampler_mapping.value->empty()) {
auto str_to_bp = [](const std::string_view& str) -> std::optional<tint::BindingPoint> {
auto parts = tint::Split(str, ",");
if (parts.Length() != 2) {
std::cerr << "A binding point requires a 'group,binding' pair, found "
<< parts.Length() << " components instead of 2.\n";
return std::nullopt;
}
uint32_t group = 0;
std::from_chars(parts[0].data(), parts[0].data() + parts[0].size(), group);
uint32_t binding = 0;
std::from_chars(parts[1].data(), parts[1].data() + parts[0].size(), binding);
return {tint::BindingPoint{group, binding}};
};
for (auto mapping : tint::Split(*sampler_mapping.value, " ")) {
auto parts = tint::Split(mapping, ":");
if (parts.Length() != 2) {
std::cerr << "Expected source and destination binding points separated by a ':'\n";
return false;
}
auto opt_src = str_to_bp(parts[0]);
if (!opt_src.has_value()) {
return false;
}
tint::BindingPoint src_bp = opt_src.value();
auto opt_dst = str_to_bp(parts[1]);
if (!opt_dst.has_value()) {
return false;
}
tint::BindingPoint dst_bp = opt_dst.value();
opts->spirv_reader_options.sampler_mappings.insert({src_bp, dst_bp});
}
}
#endif // TINT_BUILD_SPV_READER
#if TINT_BUILD_SPV_WRITER
if (!ycbcr_binding_data.value->empty()) {
auto str_to_bp = [](const std::string_view& str) -> std::optional<tint::BindingPoint> {
auto parts = tint::Split(str, ",");
if (parts.Length() != 2) {
std::cerr << "A binding point requires a 'group,binding' pair, found "
<< parts.Length() << " components instead of 2.\n";
return std::nullopt;
}
uint32_t group = 0;
std::from_chars(parts[0].data(), parts[0].data() + parts[0].size(), group);
uint32_t binding = 0;
std::from_chars(parts[1].data(), parts[1].data() + parts[1].size(), binding);
return {tint::BindingPoint{group, binding}};
};
for (auto mapping : tint::Split(*ycbcr_binding_data.value, " ")) {
auto opt_src = str_to_bp(mapping);
if (!opt_src.has_value()) {
return false;
}
tint::BindingPoint src_bp = opt_src.value();
opts->ycbcr_bindings.emplace(src_bp);
}
}
#endif
#if TINT_BUILD_MSL_WRITER
if (arg_buffer.value.has_value()) {
for (auto ab : tint::Split(*arg_buffer.value, ",")) {
auto parts = tint::Split(ab, "=");
if (parts.Length() != 2) {
std::cerr << "argument-buffer values must be of the form GROUP=ARGUMENT_BUFFER_ID";
return false;
}
uint32_t group = 0;
std::from_chars(parts[0].data(), parts[0].data() + parts[0].size(), group, 10);
uint32_t idx = 0;
std::from_chars(parts[1].data(), parts[1].data() + parts[1].size(), idx, 10);
if (!opts->group_to_argument_buffer_info.contains(group)) {
opts->group_to_argument_buffer_info.insert({group, {}});
}
opts->group_to_argument_buffer_info[group].id = idx;
}
}
if (dynamic_buffer.value.has_value()) {
for (auto db : tint::Split(*dynamic_buffer.value, ",")) {
auto parts = tint::Split(db, "=");
if (parts.Length() != 2) {
std::cerr
<< "dynamic-offset-buffer values must be of the form GROUP=DYNAMIC_BUFFER_ID";
return false;
}
uint32_t group = 0;
std::from_chars(parts[0].data(), parts[0].data() + parts[0].size(), group, 10);
uint32_t idx = 0;
std::from_chars(parts[1].data(), parts[1].data() + parts[1].size(), idx, 10);
if (!opts->group_to_argument_buffer_info.contains(group)) {
opts->group_to_argument_buffer_info.insert({group, {}});
}
opts->group_to_argument_buffer_info[group].dynamic_buffer_id = idx;
}
}
if (dynamic_offset.value.has_value()) {
for (auto val : tint::Split(*dynamic_offset.value, ",")) {
auto parts = tint::Split(val, "=");
if (parts.Length() != 2) {
std::cerr << "dynamic-offset values must be of the form GROUP.BINDING=OFFSET";
return false;
}
auto bind_point = tint::Split(parts[0], ".");
if (bind_point.Length() != 2) {
std::cerr << "dynamic-offset values must be of the form GROUP.BINDING=OFFSET";
return false;
}
uint32_t group = 0;
std::from_chars(bind_point[0].data(), bind_point[0].data() + bind_point[0].size(),
group, 10);
uint32_t binding = 0;
std::from_chars(bind_point[0].data(), bind_point[0].data() + bind_point[0].size(),
binding, 10);
uint32_t offset = 0;
std::from_chars(parts[1].data(), parts[1].data() + parts[1].size(), offset, 10);
if (!opts->group_to_argument_buffer_info.contains(group)) {
opts->group_to_argument_buffer_info.insert({group, {}});
}
opts->group_to_argument_buffer_info[group].binding_info_to_offset_index.insert(
{binding, offset});
}
}
#endif
#if TINT_BUILD_HLSL_WRITER
if (pixel_local_attachment_formats.value.has_value()) {
auto binding_formats = tint::Split(*pixel_local_attachment_formats.value, ",");
for (auto& binding_format : binding_formats) {
auto values = tint::Split(binding_format, "=");
if (values.Length() != 2) {
std::cerr << "Invalid binding format " << pixel_local_attachment_formats.name
<< ": " << binding_format << "\n";
return false;
}
auto member_index = tint::strconv::ParseUint32(values[0]);
if (member_index != tint::Success) {
std::cerr << "Invalid member index for " << pixel_local_attachment_formats.name
<< ": " << values[0] << "\n";
return false;
}
auto format = values[1];
tint::hlsl::writer::PixelLocalAttachment::TexelFormat texel_format =
tint::hlsl::writer::PixelLocalAttachment::TexelFormat::kUndefined;
if (format == "R32Sint") {
texel_format = tint::hlsl::writer::PixelLocalAttachment::TexelFormat::kR32Sint;
} else if (format == "R32Uint") {
texel_format = tint::hlsl::writer::PixelLocalAttachment::TexelFormat::kR32Uint;
} else if (format == "R32Float") {
texel_format = tint::hlsl::writer::PixelLocalAttachment::TexelFormat::kR32Float;
} else {
std::cerr << "Invalid texel format for " << pixel_local_attachments.name << ": "
<< format << "\n";
return false;
}
// Add an entry with just the texel format for now. We will fill in the attachment index
// below.
tint::hlsl::writer::PixelLocalAttachment attachment{~0u, texel_format};
opts->pixel_local_options.attachments.emplace(member_index.Get(), attachment);
}
}
if (hlsl_shader_model.value.has_value()) {
const uint32_t shader_model = *hlsl_shader_model.value;
if (shader_model < kMinShaderModelForDXC || shader_model > kMaxSupportedShaderModelForDXC) {
std::cerr << "Invalid HLSL shader model: " << shader_model << "\n";
return false;
}
opts->hlsl_shader_model = shader_model;
}
#endif // TINT_BUILD_HLSL_WRITER
#if TINT_BUILD_HLSL_WRITER || TINT_BUILD_MSL_WRITER
if (pixel_local_attachments.value.has_value()) {
auto bindings = tint::Split(*pixel_local_attachments.value, ",");
for (auto& binding : bindings) {
auto values = tint::Split(binding, "=");
if (values.Length() != 2) {
std::cerr << "Invalid binding " << pixel_local_attachments.name << ": " << binding
<< "\n";
return false;
}
auto member_index = tint::strconv::ParseUint32(values[0]);
if (member_index != tint::Success) {
std::cerr << "Invalid member index for " << pixel_local_attachments.name << ": "
<< values[0] << "\n";
return false;
}
auto attachment_index = tint::strconv::ParseUint32(values[1]);
if (attachment_index != tint::Success) {
std::cerr << "Invalid attachment index for " << pixel_local_attachments.name << ": "
<< values[1] << "\n";
return false;
}
#if TINT_BUILD_MSL_WRITER
opts->pixel_local_attachments.emplace(member_index.Get(), attachment_index.Get());
#endif // TINT_BUILD_MSL_WRITER
#if TINT_BUILD_HLSL_WRITER
// We've already set the format for this member index, now set the attachment index
auto iter = opts->pixel_local_options.attachments.find(member_index.Get());
if (iter == opts->pixel_local_options.attachments.end()) {
std::cerr << "Missing pixel local format for member index: " << member_index.Get()
<< "\n";
return false;
}
iter->second.index = attachment_index.Get();
#endif // TINT_BUILD_HLSL_WRITER
}
}
#endif // TINT_BUILD_HLSL_WRITER || TINT_BUILD_MSL_WRITER
#if TINT_BUILD_GLSL_WRITER
if (bgra_swizzle.value.has_value() && !bgra_swizzle.value.value().empty()) {
for (auto val : tint::Split(*bgra_swizzle.value, ",")) {
auto bgra_val = tint::strconv::ParseUint32(val);
if (bgra_val != tint::Success) {
std::cerr << "invalid bgra_swizzle value: " << val;
return false;
}
opts->bgra_swizzle.push_back(bgra_val.Get());
}
}
#endif // TINT_BUILD_GLSL_WRITER
auto files = result.Get();
if (files.Length() > 1) {
std::cerr << "More than one input file specified: "
<< tint::Join(Transform(files, tint::cmd::Quote), ", ") << "\n";
return false;
}
if (files.Length() == 1) {
opts->input_filename = files[0];
}
return true;
}
TINT_END_DISABLE_WARNING(UNSAFE_BUFFER_USAGE);
[[maybe_unused]] tint::diag::Result<tint::SubstituteOverridesConfig> CreateOverrideMap(
const Options& options,
tint::inspector::Inspector& inspector) {
auto override_names = inspector.GetNamedOverrideIds();
tint::SubstituteOverridesConfig cfg;
cfg.map.reserve(options.overrides.Count());
for (auto& override : options.overrides) {
const auto& override_name = override.key.Value();
const auto& override_value = override.value;
if (override_name.empty()) {
return tint::diag::Failure("empty override name");
}
auto num = tint::strconv::ParseNumber<decltype(tint::OverrideId::value)>(override_name);
if (num == tint::Success) {
tint::OverrideId id{num.Get()};
cfg.map.emplace(id, override_value);
continue;
}
auto it = override_names.find(override_name);
if (it == override_names.end()) {
return tint::diag::Failure("unknown override '" + override_name + "'");
}
cfg.map.emplace(it->second, override_value);
}
return cfg;
}
#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_VULKAN_1_1;
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 << "\n";
}
return result;
}
#endif // TINT_BUILD_SPV_WRITER
/// Generate SPIR-V code for a program.
/// @param options the options that Tint was invoked with
/// @param inspector the inspector
/// @param ir the module to generate
/// @returns true on success
[[maybe_unused]] bool GenerateSpirv([[maybe_unused]] const Options& options,
[[maybe_unused]] tint::inspector::Inspector& inspector,
[[maybe_unused]] tint::core::ir::Module& ir) {
#if TINT_BUILD_SPV_WRITER
tint::spirv::writer::Options gen_options;
if (options.rename_all) {
gen_options.remapped_entry_point_name = "tint_entry_point";
gen_options.strip_all_names = true;
}
gen_options.entry_point_name = options.ep_name;
gen_options.disable_robustness = !options.enable_robustness;
gen_options.disable_workgroup_init = options.disable_workgroup_init;
gen_options.extensions.use_storage_input_output_16 = options.use_storage_input_output_16;
gen_options.spirv_version = options.spirv_version;
auto entry_point = inspector.GetEntryPoint(options.ep_name);
// Run SubstituteOverrides to replace override instructions with constants.
// This needs to run after SingleEntryPoint which removes unused overrides.
auto substitute_override_cfg = CreateOverrideMap(options, inspector);
if (substitute_override_cfg != tint::Success) {
std::cerr << "Failed to create override map: " << substitute_override_cfg.Failure() << "\n";
return false;
}
gen_options.substitute_overrides_config = substitute_override_cfg.Get();
std::unordered_map<uint32_t, tint::BindingPoint> colour_binding_points{};
uint32_t binding = 0;
for (auto& var : entry_point.input_variables) {
if (var.attributes.color.has_value()) {
// TODO(dsinclair): At some point, we may want to add a config option to set the default
// group number for input attachment bindings
colour_binding_points.emplace(var.attributes.color.value(), tint::BindingPoint{
.group = 66,
.binding = binding++,
});
}
}
gen_options.colour_index_to_binding_point = colour_binding_points;
// Immediate data Offset must be 4-byte aligned.
uint32_t offset = tint::RoundUp(4u, entry_point.immediate_data_size);
if (entry_point.frag_depth_used) {
// Place the RangeOffset immediate data member after user-defined immediate data (if
// any).
gen_options.depth_range_offsets = {offset + 0, offset + 4};
offset += 8;
}
gen_options.bindings =
tint::GenerateBindings(ir, options.ep_name, false, false, options.ycbcr_bindings);
gen_options.resource_table = tint::core::ir::transform::GenerateResourceTableConfig(ir);
// Enable the Vulkan Memory Model if needed.
for (auto* ty : ir.Types()) {
if (ty->Is<tint::core::type::SubgroupMatrix>()) {
gen_options.extensions.use_vulkan_memory_model = true;
}
}
// Check that the module and options are supported by the backend.
auto check = tint::spirv::writer::CanGenerate(ir, gen_options);
if (check != tint::Success) {
std::cerr << check.Failure() << "\n";
return false;
}
// Generate SPIR-V from Tint IR.
auto result = tint::spirv::writer::Generate(ir, gen_options);
if (result != tint::Success) {
options.printer->Print(tint::core::ir::Disassembler(ir).Text());
std::cerr << "Failed to generate SPIR-V: " << result.Failure() << "\n";
return false;
}
if (options.format == Format::kSpvAsm) {
if (!tint::cmd::WriteFile(options.output_file, "w", Disassemble(result.Get().spirv))) {
return false;
}
} else {
if (!tint::cmd::WriteFile(options.output_file, "wb", result.Get().spirv)) {
return false;
}
}
const auto hash = tint::CRC32(result.Get().spirv.data(), result.Get().spirv.size());
if (options.print_hash) {
PrintHash(hash);
}
if (options.validate && options.skip_hash.count(hash) == 0) {
// Use Vulkan 1.1, since this is the minimum version required by Dawn.
spv_target_env target_env = SPV_ENV_MAX;
switch (options.spirv_version) {
case tint::spirv::writer::SpvVersion::kSpv13:
target_env = SPV_ENV_VULKAN_1_1;
break;
case tint::spirv::writer::SpvVersion::kSpv14:
target_env = SPV_ENV_VULKAN_1_1_SPIRV_1_4;
break;
case tint::spirv::writer::SpvVersion::kSpv15:
TINT_UNREACHABLE() << "SPIR-V 1.5 validation not yet supported";
}
spvtools::SpirvTools tools(target_env);
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 << "\n";
});
if (!tools.Validate(result.Get().spirv.data(), result.Get().spirv.size(),
spvtools::ValidatorOptions())) {
return false;
}
}
return true;
#else
std::cerr << "SPIR-V writer not enabled in tint build\n";
return false;
#endif // TINT_BUILD_SPV_WRITER
}
/// Generate WGSL code for a program.
/// @param options the options that Tint was invoked with
/// @param inspector the inspector
/// @param program the program to generate
/// @returns true on success
bool GenerateWgsl([[maybe_unused]] Options& options,
[[maybe_unused]] tint::inspector::Inspector& inspector,
[[maybe_unused]] tint::Program& program) {
#if TINT_BUILD_WGSL_WRITER
auto result = tint::wgsl::writer::Generate(program, options.wgsl_writer_options);
if (result != tint::Success) {
std::cerr << "Failed to generate: " << result.Failure() << "\n";
return false;
}
if (!tint::cmd::WriteFile(options.output_file, "w", result->wgsl)) {
return false;
}
const auto hash = tint::CRC32(result->wgsl.data(), result->wgsl.size());
if (options.print_hash) {
PrintHash(hash);
}
#if TINT_BUILD_WGSL_READER
if (options.validate && options.skip_hash.count(hash) == 0) {
// Attempt to re-parse the output program with Tint's WGSL reader.
tint::wgsl::reader::Options parser_options;
parser_options.allowed_features = tint::wgsl::AllowedFeatures::Everything();
auto source = std::make_unique<tint::Source::File>(options.input_filename, result->wgsl);
auto reparsed_program = tint::wgsl::reader::Parse(source.get(), parser_options);
if (!reparsed_program.IsValid()) {
tint::diag::Formatter diag_formatter;
options.printer->Print(diag_formatter.Format(reparsed_program.Diagnostics()));
return false;
}
}
#endif // TINT_BUILD_WGSL_READER
return true;
#else
std::cerr << "WGSL writer not enabled in tint build\n";
return false;
#endif // TINT_BUILD_WGSL_WRITER
}
#if TINT_BUILD_MSL_WRITER
tint::msl::writer::ArrayLengthOptions GenerateArrayLengthFromConstants(tint::core::ir::Module& ir,
const std::string& ep_name) {
tint::msl::writer::ArrayLengthOptions options{
.ubo_binding = 30,
};
tint::core::ir::Function* ep_func = nullptr;
for (auto* f : ir.functions) {
if (!f->IsEntryPoint()) {
continue;
}
if (ir.NameOf(f).NameView() == ep_name) {
ep_func = f;
break;
}
}
TINT_ASSERT(ep_func);
tint::core::ir::ReferencedModuleVars<const tint::core::ir::Module> referenced_module_vars{ir};
auto& refs = referenced_module_vars.TransitiveReferences(ep_func);
// Add array_length_from_constants entries for all storage buffers with runtime sized
// arrays used by the given entry point.
std::unordered_set<tint::BindingPoint> storage_bindings;
for (auto* var : refs) {
auto bp = var->BindingPoint();
if (!bp.has_value()) {
continue;
}
auto* ty = var->Result()->Type()->As<tint::core::type::Pointer>();
if (ty && ty->AddressSpace() == tint::core::AddressSpace::kStorage &&
!ty->HasFixedFootprint()) {
if (storage_bindings.insert(*bp).second) {
options.bindpoint_to_size_index.emplace(
*bp, static_cast<uint32_t>(storage_bindings.size() - 1));
}
}
}
return options;
}
#endif // TINT_BUILD_MSL_WRITER
/// Generate MSL code for a program.
/// @param options the options that Tint was invoked with
/// @param inspector the inspector
/// @param ir the module to generate
/// @returns true on success
[[maybe_unused]] bool GenerateMsl([[maybe_unused]] const Options& options,
[[maybe_unused]] tint::inspector::Inspector& inspector,
[[maybe_unused]] tint::core::ir::Module& ir) {
#if TINT_BUILD_MSL_WRITER
// Set up the backend options.
tint::msl::writer::Options gen_options;
if (options.rename_all) {
gen_options.remapped_entry_point_name = "tint_entry_point";
gen_options.strip_all_names = true;
}
gen_options.entry_point_name = options.ep_name;
gen_options.disable_robustness = !options.enable_robustness;
gen_options.disable_workgroup_init = options.disable_workgroup_init;
gen_options.pixel_local_attachments = options.pixel_local_attachments;
gen_options.bindings = tint::GenerateBindings(
ir, options.ep_name, !options.use_argument_buffers, !options.use_argument_buffers);
// TODO(crbug.com/366291600): Replace ubo with immediate block for end2end tests
gen_options.immediate_binding_point = tint::BindingPoint{.group = 0u, .binding = 30u};
gen_options.extensions.disable_demote_to_helper = options.disable_demote_to_helper;
gen_options.use_argument_buffers = options.use_argument_buffers;
gen_options.group_to_argument_buffer_info = options.group_to_argument_buffer_info;
gen_options.array_length_from_constants = GenerateArrayLengthFromConstants(ir, options.ep_name);
// Run SubstituteOverrides to replace override instructions with constants.
// This needs to run after SingleEntryPoint which removes unused overrides.
auto substitute_override_cfg = CreateOverrideMap(options, inspector);
if (substitute_override_cfg != tint::Success) {
std::cerr << "Failed to create override map: " << substitute_override_cfg.Failure() << "\n";
return false;
}
gen_options.substitute_overrides_config = substitute_override_cfg.Get();
auto result = tint::msl::writer::Generate(ir, gen_options);
if (result != tint::Success) {
options.printer->Print(tint::core::ir::Disassembler(ir).Text());
std::cerr << "Failed to generate: " << result.Failure() << "\n";
return false;
}
if (!tint::cmd::WriteFile(options.output_file, "w", result->msl)) {
return false;
}
const auto hash = tint::CRC32(result->msl.c_str());
if (options.print_hash) {
PrintHash(hash);
}
if (options.validate && options.skip_hash.count(hash) == 0) {
tint::Result<tint::SuccessType> res;
#if TINT_BUILD_IS_MAC
res = tint::msl::validate::ValidateUsingMetal(result->msl, options.msl_version);
#else
#ifdef _WIN32
const char* default_xcrun_exe = "metal.exe";
#else
const char* default_xcrun_exe = "xcrun";
#endif // _WIN32
auto xcrun = tint::Command::LookPath(
options.xcrun_path.empty() ? default_xcrun_exe : std::string(options.xcrun_path));
if (xcrun.Found()) {
res = tint::msl::validate::Validate(xcrun.Path(), result->msl, options.msl_version);
} else {
res = tint::Failure{"xcrun executable not found. Cannot validate."};
}
#endif // TINT_BUILD_IS_MAC
if (res != tint::Success) {
std::cerr << res.Failure() << "\n";
return false;
}
}
return true;
#else
std::cerr << "MSL writer not enabled in tint build\n";
return false;
#endif // TINT_BUILD_MSL_WRITER
}
/// Generate HLSL code for a program.
/// @param options the options that Tint was invoked with
/// @param inspector the inspector
/// @param ir the module to generate
/// @returns true on success
[[maybe_unused]] bool GenerateHlsl([[maybe_unused]] const Options& options,
[[maybe_unused]] tint::inspector::Inspector& inspector,
[[maybe_unused]] tint::core::ir::Module& ir) {
#if TINT_BUILD_HLSL_WRITER
const bool for_fxc = options.format == Format::kHlslFxc;
// Set up the backend options.
tint::hlsl::writer::Options gen_options;
if (options.rename_all) {
gen_options.remapped_entry_point_name = "tint_entry_point";
gen_options.strip_all_names = true;
}
gen_options.entry_point_name = options.ep_name;
gen_options.disable_robustness = !options.enable_robustness;
gen_options.disable_workgroup_init = options.disable_workgroup_init;
gen_options.pixel_local = options.pixel_local_options;
gen_options.extensions.polyfill_dot_4x8_packed =
options.hlsl_shader_model < kMinShaderModelForDP4aInHLSL;
gen_options.extensions.polyfill_pack_unpack_4x8 =
options.hlsl_shader_model < kMinShaderModelForPackUnpack4x8InHLSL;
gen_options.compiler = for_fxc ? tint::hlsl::writer::Options::Compiler::kFXC
: tint::hlsl::writer::Options::Compiler::kDXC;
gen_options.bindings = tint::GenerateBindings(ir, options.ep_name, false, false);
gen_options.resource_table = tint::core::ir::transform::GenerateResourceTableConfig(ir);
// Run SubstituteOverrides to replace override instructions with constants.
// This needs to run after SingleEntryPoint which removes unused overrides.
auto substitute_override_cfg = CreateOverrideMap(options, inspector);
if (substitute_override_cfg != tint::Success) {
std::cerr << "Failed to create override map: " << substitute_override_cfg.Failure() << "\n";
return false;
}
gen_options.substitute_overrides_config = substitute_override_cfg.Get();
// Check that the module and options are supported by the backend.
auto check = tint::hlsl::writer::CanGenerate(ir, gen_options);
if (check != tint::Success) {
std::cerr << check.Failure() << "\n";
return false;
}
auto result = tint::hlsl::writer::Generate(ir, gen_options);
if (result != tint::Success) {
options.printer->Print(tint::core::ir::Disassembler(ir).Text());
std::cerr << "Failed to generate: " << result.Failure() << "\n";
return false;
}
if (!tint::cmd::WriteFile(options.output_file, "w", result->hlsl)) {
return false;
}
const auto hash = tint::CRC32(result->hlsl.c_str());
if (options.print_hash) {
PrintHash(hash);
}
const bool validate =
(options.validate || !options.fxc_path.empty() || !options.dxc_path.empty()) &&
(options.skip_hash.count(hash) == 0);
if (validate && !for_fxc) {
// DXC validation
tint::hlsl::validate::Result dxc_res;
const std::string dxc_path =
options.dxc_path.empty() ? tint::hlsl::validate::kDxcDLLName : options.dxc_path;
auto dxc = tint::Command::LookPath(dxc_path);
if (dxc.Found()) {
uint32_t hlsl_shader_model = options.hlsl_shader_model;
bool dxc_require_16bit_types = false;
for (auto* ty : ir.Types()) {
if (ty->Is<tint::core::type::F16>()) {
dxc_require_16bit_types = true;
break;
}
}
if (options.verbose) {
std::cout << "Validating with DXC: " << dxc.Path() << "\n";
}
dxc_res = tint::hlsl::validate::ValidateUsingDXC(
dxc.Path(), result->hlsl, result->entry_point_name, result->pipeline_stage,
dxc_require_16bit_types, hlsl_shader_model);
} else {
dxc_res.failed = true;
dxc_res.output = "DXC executable '" + dxc_path + "' not found. Cannot validate.";
}
if (dxc_res.failed) {
std::cerr << "DXC validation failure:\n" << dxc_res.output << "\n";
return false;
}
if (options.verbose) {
std::cout << "Passed DXC validation. Compiler output:\n" << dxc_res.output << "\n";
}
}
if (validate && for_fxc) {
// FXC validation
#ifndef _WIN32
std::cerr << "FXC can only be used on Windows.\n";
return false;
#else
tint::hlsl::validate::Result fxc_res;
auto fxc = tint::Command::LookPath(
options.fxc_path.empty() ? tint::hlsl::validate::kFxcDLLName : options.fxc_path);
if (fxc.Found()) {
if (options.verbose) {
std::cout << "Validating with FXC: " << fxc.Path() << "\n";
}
fxc_res = tint::hlsl::validate::ValidateUsingFXC(
fxc.Path(), result->hlsl, result->entry_point_name, result->pipeline_stage);
} else {
fxc_res.failed = true;
fxc_res.output = "FXC DLL '" + options.fxc_path + "' not found. Cannot validate.";
}
if (fxc_res.failed) {
std::cerr << "FXC validation failure:\n" << fxc_res.output << "\n";
return false;
}
if (options.verbose) {
std::cout << "Passed FXC validation. Compiler output:\n" << fxc_res.output << "\n";
}
#endif // _WIN32
}
return true;
#else
std::cerr << "HLSL writer not enabled in tint build\n";
return false;
#endif // TINT_BUILD_HLSL_WRITER
}
/// Generate GLSL code for a program.
/// @param options the options that Tint was invoked with
/// @param inspector the inspector
/// @param ir the module to generate
/// @returns true on success
[[maybe_unused]] bool GenerateGlsl([[maybe_unused]] const Options& options,
[[maybe_unused]] tint::inspector::Inspector& inspector,
[[maybe_unused]] tint::core::ir::Module& ir) {
#if TINT_BUILD_GLSL_WRITER
tint::glsl::writer::Options gen_options;
gen_options.strip_all_names = options.rename_all;
if (options.glsl_desktop) {
gen_options.version =
tint::glsl::writer::Version(tint::glsl::writer::Version::Standard::kDesktop, 4, 6);
} else {
gen_options.version = tint::glsl::writer::Version();
}
gen_options.entry_point_name = options.ep_name;
gen_options.disable_robustness = !options.enable_robustness;
// Run SubstituteOverrides to replace override instructions with constants.
// This needs to run after SingleEntryPoint which removes unused overrides.
auto substitute_override_cfg = CreateOverrideMap(options, inspector);
if (substitute_override_cfg != tint::Success) {
std::cerr << "Failed to create override map: " << substitute_override_cfg.Failure() << "\n";
return false;
}
gen_options.substitute_overrides_config = substitute_override_cfg.Get();
auto entry_point = inspector.GetEntryPoint(options.ep_name);
// Immediate data Offset must be 4-byte aligned.
uint32_t offset = tint::RoundUp(4u, entry_point.immediate_data_size);
if (entry_point.instance_index_used) {
// Place the first_instance immediate data member after user-defined immediate data (if
// any).
gen_options.first_instance_offset = offset;
offset += 4;
}
if (entry_point.frag_depth_used) {
gen_options.depth_range_offsets = {offset + 0, offset + 4};
offset += 8;
}
for (auto idx : options.bgra_swizzle) {
gen_options.bgra_swizzle_locations.insert({idx});
}
// Generate binding options.
auto data = tint::glsl::writer::GenerateBindings(ir, options.ep_name);
gen_options.bindings = std::move(data.bindings);
gen_options.texture_builtins_from_uniform = std::move(data.texture_builtins_from_uniform);
// Check that the module and options are supported by the backend.
auto check = tint::glsl::writer::CanGenerate(ir, gen_options);
if (check != tint::Success) {
std::cerr << check.Failure() << "\n";
return false;
}
// Generate GLSL.
auto result = tint::glsl::writer::Generate(ir, gen_options);
if (result != tint::Success) {
std::cerr << "Failed to generate: " << result.Failure() << "\n";
return false;
}
if (!tint::cmd::WriteFile(options.output_file, "w", result->glsl)) {
return false;
}
const auto hash = tint::CRC32(result->glsl.c_str());
if (options.print_hash) {
PrintHash(hash);
}
if (options.validate && options.skip_hash.count(hash) == 0) {
#if !TINT_BUILD_GLSL_VALIDATOR
std::cerr << "GLSL validator not enabled in tint build\n";
return false;
#else
// If there is no entry point name there is nothing to validate
if (options.ep_name != "") {
tint::core::ir::Function::PipelineStage stage =
tint::core::ir::Function::PipelineStage::kCompute;
switch (entry_point.stage) {
case tint::inspector::PipelineStage::kCompute:
stage = tint::core::ir::Function::PipelineStage::kCompute;
break;
case tint::inspector::PipelineStage::kVertex:
stage = tint::core::ir::Function::PipelineStage::kVertex;
break;
case tint::inspector::PipelineStage::kFragment:
stage = tint::core::ir::Function::PipelineStage::kFragment;
break;
}
auto val = tint::glsl::validate::Validate(result->glsl, stage);
if (val != tint::Success) {
std::cerr << val.Failure();
return false;
}
}
#endif // !TINT_BUILD_GLSL_VALIDATOR
}
return true;
#else
std::cerr << "GLSL writer not enabled in tint build\n";
return false;
#endif // TINT_BUILD_GLSL_WRITER
}
/// Generate IR code for a program.
/// @param program the program to generate
/// @param options the options that Tint was invoked with
/// @returns true on success
bool DumpIR([[maybe_unused]] const tint::Program& program,
[[maybe_unused]] const Options& options) {
#if TINT_BUILD_WGSL_READER
auto result = tint::wgsl::reader::ProgramToLoweredIR(program);
if (result != tint::Success) {
std::cerr << "Failed to build IR from program: " << result.Failure() << "\n";
return false;
}
options.printer->Print(tint::core::ir::Disassembler(result.Get()).Text());
options.printer->Print(tint::StyledText{} << "\n");
return true;
#else
std::cerr << "WGSL reader not enabled in tint build\n";
return false;
#endif
}
/// Generate backend code for a program.
/// @param options the options that Tint was invoked with
/// @param inspector the inspector
/// @param program the program to generate
/// @returns true on success
bool Generate([[maybe_unused]] const Options& options,
[[maybe_unused]] tint::inspector::Inspector& inspector,
[[maybe_unused]] const tint::Program& program) {
#if TINT_BUILD_WGSL_READER
// Convert the AST program to an IR module.
auto ir = tint::wgsl::reader::ProgramToLoweredIR(program);
if (ir != tint::Success) {
std::cerr << "Failed to generate IR: " << ir << "\n";
return false;
}
switch (options.format) {
case Format::kHlsl:
case Format::kHlslFxc:
return GenerateHlsl(options, inspector, ir.Get());
case Format::kMsl:
return GenerateMsl(options, inspector, ir.Get());
case Format::kSpirv:
case Format::kSpvAsm:
return GenerateSpirv(options, inspector, ir.Get());
case Format::kGlsl:
return GenerateGlsl(options, inspector, ir.Get());
case Format::kWgsl:
TINT_UNREACHABLE();
default:
std::cerr << "Unknown output format specified\n";
break;
}
#else
std::cerr << "Cannot convert WGSL programs to Tint IR without the WGSL reader\n";
#endif // TINT_BUILD_WGSL_READER
return false;
}
int Run(tint::VectorRef<std::string_view> arguments, ExeMode exe_mode) {
Options options;
if (!ParseArgs(arguments, &options, exe_mode)) {
return 1;
}
if (exe_mode == ExeMode::kServer && options.format == Format::kSpirv) {
std::cerr << "Cannot emit binary SPIR-V to stdout in server mode\n";
return 1;
}
if (exe_mode == ExeMode::kServer && options.input_format == tint::cmd::InputFormat::kSpirvBin) {
std::cerr << "Cannot read binary SPIR-V from stdin in server mode\n";
return 1;
}
// Implement output format defaults.
if (options.format == Format::kUnknown) {
// Try inferring from filename.
options.format = InferFormat(options.output_file);
}
if (options.format == Format::kUnknown) {
// Ultimately, default to SPIR-V assembly. That's nice for interactive use.
options.format = Format::kSpvAsm;
}
tint::cmd::LoadProgramOptions opts{
.filename = options.input_filename,
.input_format = options.input_format,
#if TINT_BUILD_SPV_READER
.spirv_reader_options = options.spirv_reader_options,
#endif
#if TINT_BUILD_WGSL_WRITER
.wgsl_writer_options = options.wgsl_writer_options,
#endif
.printer = options.printer.get(),
};
#if TINT_BUILD_WGSL_WRITER
// Allow the shader-f16 extension
opts.wgsl_writer_options.allowed_features = tint::wgsl::AllowedFeatures::Everything();
#endif
auto info = tint::cmd::LoadProgramInfo(opts);
if (!info.program.IsValid()) {
return 1;
}
if (options.parse_only) {
return 1;
}
if (options.dump_ir || options.format == Format::kIr) {
auto res = DumpIR(info.program, options);
if (options.format == Format::kIr) {
return static_cast<int>(res);
}
}
#if TINT_BUILD_WGSL_WRITER && TINT_BUILD_WGSL_READER
if (options.ir_roundtrip) {
auto ir = tint::wgsl::reader::ProgramToLoweredIR(info.program);
if (ir != tint::Success) {
std::cerr << "Failed convert program to IR: " << ir.Failure() << "\n";
return 1;
}
auto p = tint::wgsl::writer::ProgramFromIR(ir.Get(), {});
if (p != tint::Success) {
std::cerr << "Failed converting IR to program: " << p.Failure() << "\n";
return 1;
}
}
#endif
tint::inspector::Inspector inspector(info.program);
if (options.dump_inspector_bindings) {
tint::cmd::PrintInspectorBindings(inspector);
}
// Handle WGSL special as we want multiple shaders in a single file, unlike other formats where
// we run single entry point.
if (options.format == Format::kWgsl) {
return GenerateWgsl(options, inspector, info.program) ? 0 : 1;
}
auto entry_points = inspector.GetEntryPoints();
if (entry_points.empty()) {
std::cerr << "no entry point found, entry point required\n";
return 1;
}
if (!tint::cmd::IsStdout(options.output_file) && !options.emit_single_entry_point) {
if (entry_points.size() > 1) {
std::cerr
<< "Emitting to a file but the module has multiple entry points. Please provide "
"the `--ep <name>` option to specific the entry point to emit\n";
return 1;
}
// Set the emitted entry point to the name from the inspector
options.ep_name = entry_points.front().name;
options.emit_single_entry_point = true;
}
bool success = true;
for (auto& entry_point : entry_points) {
if (options.emit_single_entry_point && entry_point.name != options.ep_name) {
continue;
}
// If we're emitting to stdout, add a separator between the entry points.
if (tint::cmd::IsStdout(options.output_file)) {
if (entry_points.size() > 1) {
if (options.format == Format::kSpvAsm) {
std::cout << ";\n; ";
} else {
std::cout << "//\n// ";
}
std::cout << entry_point.name << "\n";
if (options.format == Format::kSpvAsm) {
std::cout << ";\n";
} else {
std::cout << "//\n";
}
}
}
options.ep_name = entry_point.name;
success &= Generate(options, inspector, info.program);
if (options.emit_single_entry_point) {
break;
}
}
return success ? 0 : 1;
}
/// Run a server that accepts arguments on stdin.
/// @returns 0 on success, non-zero on failure
int RunServer() {
// Each line read from stdin will invoke Tint with the supplied arguments.
// Output on stdout and stderr will be delimited with \0 characters.
// The server will exit on failure or if stdin is closed.
while (!std::cin.eof()) {
// Read the next set of arguments.
std::string arg_line;
std::getline(std::cin, arg_line);
// Split the arguments by whitespace, taking double-quotes into account.
std::istringstream arg_in(arg_line);
tint::Vector<std::string, 8> arg_tokens;
while (!arg_in.eof()) {
std::string arg;
arg_in >> std::quoted(arg, '"', '\0');
if (!arg.empty()) {
arg_tokens.Push(arg);
}
}
// Run Tint with the provided arguments.
auto arguments =
tint::Transform(arg_tokens, [](const std::string& arg) -> std::string_view {
return std::string_view(arg); //
});
auto ret = Run(arguments, ExeMode::kServer);
if (ret != 0) {
// The Tint invocation failed, so exit the server.
return ret;
}
// Delimit stdout and stderr with \0 and flush them.
std::cout << '\0' << std::flush;
std::cerr << '\0' << std::flush;
}
return 0;
}
} // namespace
int main(int argc, const char** argv) {
tint::Vector<std::string_view, 8> arguments = tint::args::Vectorize(argc, argv);
tint::Initialize();
if (arguments.Length() > 0 && arguments[0] == "--server") {
if (arguments.Length() > 1) {
std::cerr << "--server must not be used with any other arguments\n";
return 1;
}
return RunServer();
}
return Run(arguments, ExeMode::kStandalone);
}