Import Tint changes from Dawn
Changes:
- b383cf0629faeda3a75c1a6c8eb5b5859040671d [tint][gn] Hook up tint_info to the build by Ben Clayton <bclayton@google.com>
- d8f6206dc6e0f9de2fb30dd9b73f49faf1f65f93 [tint][resolver] Add validation test for @color and @loca... by Ben Clayton <bclayton@google.com>
- 570461ecdae1e36c61aa830034506bebb57b6186 [tint][transform] Update CanonicalizeEntryPointIO tests by Ben Clayton <bclayton@google.com>
- 0e4aee6e79ee3c5c24d2a34b41b1398cc7a277cb [tint][wgsl] Parse @color by Ben Clayton <bclayton@google.com>
- e73b3936268202c1baf069830ab3ef867c9ee681 [tint][msl] Implement chromium_experimental_framebuffer_f... by Ben Clayton <bclayton@google.com>
- 7836e1b2d5e834a5eac09c4bcc3b0079e127f081 [tint][resolver] Fix false-positive dual-source-blending ... by Ben Clayton <bclayton@google.com>
- 013fd37bcd800eab06da0893a9f3a560d0bc7540 [tint] Fix conflict between two CLs that landed, breaking... by Ben Clayton <bclayton@google.com>
- 4348a05e24ea85775b808b0068d97d1648a9f046 [spirv-writer] Support clamp_frag_depth for multiple entr... by James Price <jrprice@google.com>
- 2af581505700219f2f7dcfc501b0027215e7c111 [tint][inspector] Reflect the @color attribute by Ben Clayton <bclayton@google.com>
- 45f5c607bdcdef17b8c382f8a279bd48490bab0e [tint] Resolve ColorAttribute by Ben Clayton <bclayton@google.com>
- c36093e932c33b6269204461ebbf864bd7e717de [tint][wgsl] Add ColorAttribute by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: b383cf0629faeda3a75c1a6c8eb5b5859040671d
Change-Id: If54ace3ab35b5ed717aed8e90ba81199a70a57dc
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/160340
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 549aefc..d083bef 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -262,6 +262,7 @@
deps = []
if (tint_build_cmds) {
deps += [
+ "${tint_src_dir}/cmd/info",
"${tint_src_dir}/cmd/remote_compile",
"${tint_src_dir}/cmd/tint",
]
diff --git a/src/tint/cmd/common/helper.cc b/src/tint/cmd/common/helper.cc
index 41fc79a..4570f41 100644
--- a/src/tint/cmd/common/helper.cc
+++ b/src/tint/cmd/common/helper.cc
@@ -270,8 +270,12 @@
for (const auto& var : entry_point.input_variables) {
std::cout << "\t";
- if (var.has_location_attribute) {
- std::cout << "@location(" << var.location_attribute << ") ";
+ if (auto location = var.attributes.location) {
+ std::cout << "@location(" << location.value() << ") ";
+ }
+
+ if (auto color = var.attributes.color) {
+ std::cout << "@color(" << color.value() << ") ";
}
std::cout << var.name << std::endl;
}
@@ -282,8 +286,8 @@
for (const auto& var : entry_point.output_variables) {
std::cout << "\t";
- if (var.has_location_attribute) {
- std::cout << "@location(" << var.location_attribute << ") ";
+ if (auto location = var.attributes.location) {
+ std::cout << "@location(" << location.value() << ") ";
}
std::cout << var.name << std::endl;
}
diff --git a/src/tint/cmd/info/main.cc b/src/tint/cmd/info/main.cc
index 3160358..a68a46b 100644
--- a/src/tint/cmd/info/main.cc
+++ b/src/tint/cmd/info/main.cc
@@ -108,14 +108,9 @@
std::cout << std::endl;
std::cout << "{" << std::endl;
std::cout << "\"name\": \"" << var.name << "\"";
- if (var.has_location_attribute) {
- std::cout << "," << std::endl;
- std::cout << "\"location\": " << var.location_attribute << "," << std::endl;
- std::cout << "\"component_type\": \""
- << tint::cmd::ComponentTypeToString(var.component_type) << "\"," << std::endl;
- std::cout << "\"composition_type\": \""
- << tint::cmd::CompositionTypeToString(var.composition_type) << "\","
- << std::endl;
+ std::cout << "," << std::endl;
+ if (auto location = var.attributes.location) {
+ std::cout << "\"location\": " << location.value() << "," << std::endl;
std::cout << "\"interpolation\": {" << std::endl;
std::cout << "\"type\": \""
<< tint::cmd::InterpolationTypeToString(var.interpolation_type) << "\","
@@ -123,8 +118,15 @@
std::cout << "\"sampling\": \""
<< tint::cmd::InterpolationSamplingToString(var.interpolation_sampling)
<< "\"" << std::endl;
- std::cout << "}" << std::endl;
+ std::cout << "}," << std::endl;
}
+ if (auto color = var.attributes.color) {
+ std::cout << "\"color\": " << color.value() << "," << std::endl;
+ }
+ std::cout << "\"component_type\": \""
+ << tint::cmd::ComponentTypeToString(var.component_type) << "\"," << std::endl;
+ std::cout << "\"composition_type\": \""
+ << tint::cmd::CompositionTypeToString(var.composition_type) << "\"" << std::endl;
std::cout << std::endl;
std::cout << "}";
};
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index cc3aba1..19a9c85 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -734,7 +734,8 @@
if (enable->HasExtension(tint::wgsl::Extension::kChromiumExperimentalSubgroups)) {
msl_version = std::max(msl_version, tint::msl::validate::MslVersion::kMsl_2_1);
}
- if (enable->HasExtension(tint::wgsl::Extension::kChromiumExperimentalPixelLocal)) {
+ if (enable->HasExtension(tint::wgsl::Extension::kChromiumExperimentalPixelLocal) ||
+ enable->HasExtension(tint::wgsl::Extension::kChromiumExperimentalFramebufferFetch)) {
msl_version = std::max(msl_version, tint::msl::validate::MslVersion::kMsl_2_3);
}
}
diff --git a/src/tint/lang/core/attribute.cc b/src/tint/lang/core/attribute.cc
index 7b18915..2568294 100644
--- a/src/tint/lang/core/attribute.cc
+++ b/src/tint/lang/core/attribute.cc
@@ -51,6 +51,9 @@
if (str == "builtin") {
return Attribute::kBuiltin;
}
+ if (str == "color") {
+ return Attribute::kColor;
+ }
if (str == "compute") {
return Attribute::kCompute;
}
@@ -103,6 +106,8 @@
return "binding";
case Attribute::kBuiltin:
return "builtin";
+ case Attribute::kColor:
+ return "color";
case Attribute::kCompute:
return "compute";
case Attribute::kDiagnostic:
diff --git a/src/tint/lang/core/attribute.h b/src/tint/lang/core/attribute.h
index 3e7b087..46d7619 100644
--- a/src/tint/lang/core/attribute.h
+++ b/src/tint/lang/core/attribute.h
@@ -52,6 +52,7 @@
kAlign,
kBinding,
kBuiltin,
+ kColor,
kCompute,
kDiagnostic,
kFragment,
@@ -85,9 +86,9 @@
Attribute ParseAttribute(std::string_view str);
constexpr std::string_view kAttributeStrings[] = {
- "align", "binding", "builtin", "compute", "diagnostic", "fragment",
- "group", "id", "index", "interpolate", "invariant", "location",
- "must_use", "size", "vertex", "workgroup_size",
+ "align", "binding", "builtin", "color", "compute", "diagnostic",
+ "fragment", "group", "id", "index", "interpolate", "invariant",
+ "location", "must_use", "size", "vertex", "workgroup_size",
};
} // namespace tint::core
diff --git a/src/tint/lang/core/attribute_bench.cc b/src/tint/lang/core/attribute_bench.cc
index 1621a94..16a660c 100644
--- a/src/tint/lang/core/attribute_bench.cc
+++ b/src/tint/lang/core/attribute_bench.cc
@@ -66,97 +66,104 @@
"Euiltin",
"bPTTltin",
"builtdxx",
- "c44mpute",
- "coSSpuVVe",
- "RomR22e",
+ "c44lor",
+ "coVVSSr",
+ "22RRr",
+ "color",
+ "cFor",
+ "colr",
+ "ROOHVr",
+ "copuye",
+ "llnorrp77te",
+ "comp4t00",
"compute",
- "cFpu9e",
- "comute",
- "VOORRHte",
- "dyagnstic",
- "d77agnnnsllrrc",
- "dia400ostic",
+ "opooe",
+ "zzpute",
+ "ciimppu1",
+ "XXiagnostic",
+ "IIia99nonnt55c",
+ "dYagSSrrstHHac",
"diagnostic",
- "danstooc",
- "dignszzic",
- "d11ansppiic",
- "XXragment",
- "fIIa9955nnnt",
- "aarHHgmenYSS",
+ "dakkoHtc",
+ "jiagnsgRR",
+ "diagbost",
+ "fragjent",
+ "fragmnt",
+ "frqent",
"fragment",
- "fkkaet",
- "gjamRRn",
- "fabmnt",
- "gjoup",
- "goup",
- "goq",
+ "fragenNN",
+ "ravvent",
+ "frgmQQnt",
+ "grof",
+ "grojp",
+ "NNrw2u",
"group",
- "Nroup",
- "govv",
- "gruQQ",
- "r",
- "jd",
- "NNw",
+ "grup",
+ "grroup",
+ "Group",
+ "iFF",
+ "NN",
+ "iAA",
"id",
- "i",
- "rrd",
- "iG",
- "inFFex",
- "iE",
- "inrrx",
+ "d",
+ "L",
+ "yy",
+ "nek",
+ "indx",
+ "Jndx",
"index",
- "inx",
- "inJJD",
- "ie",
- "inerpklae",
- "intrpolate",
- "inJerpolae",
+ "incex",
+ "iOdex",
+ "__nttKKvv",
+ "int8rpoxx5e",
+ "inteqq__lte",
+ "interpqlate",
"interpolate",
- "interpocate",
- "interpolaOe",
- "__nttevvpoKKate",
- "xnvari5n8",
- "inFq__ant",
- "iqqariant",
+ "33ntOpolat66",
+ "intoott6QQlate",
+ "66terpolate",
+ "zzxvO6rint",
+ "invayyiant",
+ "HHnariZt",
"invariant",
- "invar6a33O",
- "i96arQttanoo",
- "inari66nt",
- "lOxati6zz",
- "locyytion",
- "lHHtion",
+ "iWW44rianq",
+ "iOOvaiant",
+ "ivariYnt",
+ "ltion",
+ "loaFion",
+ "wocatio",
"location",
- "qWW4caton",
- "locOOton",
- "ocatiYn",
- "m_use",
- "mutFuse",
- "wust_us",
+ "Kcatoff",
+ "qocKKtion",
+ "lFcmmt3on",
+ "mustuse",
+ "must_se",
+ "ubbt_ube",
"must_use",
- "Kst_sff",
- "qusKK_use",
- "mFsmm_3se",
- "ize",
- "sze",
- "sbbb",
+ "mstiius",
+ "muqt_uOe",
+ "muTTt_usvv",
+ "FFize",
+ "QP00",
+ "siPe",
"size",
- "iie",
- "siqe",
- "svvTTe",
- "vertFFx",
- "vrQ00P",
- "vePtex",
+ "sis77",
+ "CiRbbe",
+ "sizXX",
+ "CCrtOOOO",
+ "vrsuL",
+ "verteX",
"vertex",
- "vsste77",
- "veCtRRbb",
- "verteXX",
- "workgqou_siCCOOO",
- "worsgroupsuzL",
- "wXrkgroup_size",
+ "verte",
+ "qqrx",
+ "verte22",
+ "workgou0yzzizXX",
+ "workgrop_VPize",
+ "wokgroupnnsCze",
"workgroup_size",
- "workgroup_sze",
- "wqqrOgoupize",
- "workg22oup_size",
+ "workgrouq_sizHA",
+ "workgrup_size",
+ "forroupKKsize",
};
for (auto _ : state) {
for (auto* str : kStrings) {
diff --git a/src/tint/lang/core/attribute_test.cc b/src/tint/lang/core/attribute_test.cc
index 58e03ab..ac6b338 100644
--- a/src/tint/lang/core/attribute_test.cc
+++ b/src/tint/lang/core/attribute_test.cc
@@ -57,14 +57,23 @@
}
static constexpr Case kValidCases[] = {
- {"align", Attribute::kAlign}, {"binding", Attribute::kBinding},
- {"builtin", Attribute::kBuiltin}, {"compute", Attribute::kCompute},
- {"diagnostic", Attribute::kDiagnostic}, {"fragment", Attribute::kFragment},
- {"group", Attribute::kGroup}, {"id", Attribute::kId},
- {"index", Attribute::kIndex}, {"interpolate", Attribute::kInterpolate},
- {"invariant", Attribute::kInvariant}, {"location", Attribute::kLocation},
- {"must_use", Attribute::kMustUse}, {"size", Attribute::kSize},
- {"vertex", Attribute::kVertex}, {"workgroup_size", Attribute::kWorkgroupSize},
+ {"align", Attribute::kAlign},
+ {"binding", Attribute::kBinding},
+ {"builtin", Attribute::kBuiltin},
+ {"color", Attribute::kColor},
+ {"compute", Attribute::kCompute},
+ {"diagnostic", Attribute::kDiagnostic},
+ {"fragment", Attribute::kFragment},
+ {"group", Attribute::kGroup},
+ {"id", Attribute::kId},
+ {"index", Attribute::kIndex},
+ {"interpolate", Attribute::kInterpolate},
+ {"invariant", Attribute::kInvariant},
+ {"location", Attribute::kLocation},
+ {"must_use", Attribute::kMustUse},
+ {"size", Attribute::kSize},
+ {"vertex", Attribute::kVertex},
+ {"workgroup_size", Attribute::kWorkgroupSize},
};
static constexpr Case kInvalidCases[] = {
@@ -77,45 +86,48 @@
{"ppqqiliHH", Attribute::kUndefined},
{"bucv", Attribute::kUndefined},
{"biltGn", Attribute::kUndefined},
- {"compiive", Attribute::kUndefined},
- {"8WWmpute", Attribute::kUndefined},
- {"cxxpute", Attribute::kUndefined},
- {"dXagnosigg", Attribute::kUndefined},
- {"dagnXuVc", Attribute::kUndefined},
- {"diagnosti3", Attribute::kUndefined},
- {"fraEment", Attribute::kUndefined},
- {"PPagTTent", Attribute::kUndefined},
- {"xxragddnt", Attribute::kUndefined},
- {"g44oup", Attribute::kUndefined},
- {"grVVSSp", Attribute::kUndefined},
- {"22RRp", Attribute::kUndefined},
- {"d", Attribute::kUndefined},
- {"i", Attribute::kUndefined},
- {"OVd", Attribute::kUndefined},
- {"ndyx", Attribute::kUndefined},
- {"n77rrldGx", Attribute::kUndefined},
- {"inde40", Attribute::kUndefined},
- {"itooolate", Attribute::kUndefined},
- {"intezplate", Attribute::kUndefined},
- {"ppnerii1olat", Attribute::kUndefined},
- {"invarianXX", Attribute::kUndefined},
- {"inv55ria99nII", Attribute::kUndefined},
- {"irrvariaSSaHH", Attribute::kUndefined},
- {"lkkcin", Attribute::kUndefined},
- {"gjctRRo", Attribute::kUndefined},
- {"lcbton", Attribute::kUndefined},
- {"mustjuse", Attribute::kUndefined},
- {"must_se", Attribute::kUndefined},
- {"muquse", Attribute::kUndefined},
- {"szNN", Attribute::kUndefined},
- {"zvv", Attribute::kUndefined},
- {"QQze", Attribute::kUndefined},
- {"eterf", Attribute::kUndefined},
- {"vertjx", Attribute::kUndefined},
- {"v82wNNx", Attribute::kUndefined},
- {"worgroup_size", Attribute::kUndefined},
- {"workgrourr_size", Attribute::kUndefined},
- {"workgroGp_size", Attribute::kUndefined},
+ {"covior", Attribute::kUndefined},
+ {"co8WWr", Attribute::kUndefined},
+ {"Mxxlo", Attribute::kUndefined},
+ {"cXputgg", Attribute::kUndefined},
+ {"opuXe", Attribute::kUndefined},
+ {"comp3te", Attribute::kUndefined},
+ {"diagnostiE", Attribute::kUndefined},
+ {"TTiagnosPPi", Attribute::kUndefined},
+ {"diagdoxxtic", Attribute::kUndefined},
+ {"44ragment", Attribute::kUndefined},
+ {"fSSagmenVV", Attribute::kUndefined},
+ {"Rag2Rent", Attribute::kUndefined},
+ {"gFup", Attribute::kUndefined},
+ {"grop", Attribute::kUndefined},
+ {"ROOHVp", Attribute::kUndefined},
+ {"y", Attribute::kUndefined},
+ {"Gn77rl", Attribute::kUndefined},
+ {"04d", Attribute::kUndefined},
+ {"oox", Attribute::kUndefined},
+ {"inzz", Attribute::kUndefined},
+ {"1ippex", Attribute::kUndefined},
+ {"interpoXXate", Attribute::kUndefined},
+ {"intII99r55olate", Attribute::kUndefined},
+ {"intaarpoSSrHHYe", Attribute::kUndefined},
+ {"kkvHant", Attribute::kUndefined},
+ {"jgaianRR", Attribute::kUndefined},
+ {"inaianb", Attribute::kUndefined},
+ {"locajion", Attribute::kUndefined},
+ {"locaton", Attribute::kUndefined},
+ {"loqion", Attribute::kUndefined},
+ {"mustusNN", Attribute::kUndefined},
+ {"usvvuse", Attribute::kUndefined},
+ {"mut_QQse", Attribute::kUndefined},
+ {"srf", Attribute::kUndefined},
+ {"sije", Attribute::kUndefined},
+ {"NNz2w", Attribute::kUndefined},
+ {"vrtex", Attribute::kUndefined},
+ {"rrertex", Attribute::kUndefined},
+ {"vGrtex", Attribute::kUndefined},
+ {"workgroup_sizFF", Attribute::kUndefined},
+ {"wErkrp_size", Attribute::kUndefined},
+ {"worgrroup_size", Attribute::kUndefined},
};
using AttributeParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/lang/core/core.def b/src/tint/lang/core/core.def
index 4e8ced4..2337b79 100644
--- a/src/tint/lang/core/core.def
+++ b/src/tint/lang/core/core.def
@@ -212,6 +212,9 @@
size
vertex
workgroup_size
+
+ // framebuffer-fetch input
+ color
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/lang/core/ir/transform/shader_io.cc b/src/tint/lang/core/ir/transform/shader_io.cc
index 368dcf0..6e5f8a1 100644
--- a/src/tint/lang/core/ir/transform/shader_io.cc
+++ b/src/tint/lang/core/ir/transform/shader_io.cc
@@ -120,10 +120,11 @@
vertex_point_size_index =
backend->AddOutput(ir.symbols.New("vertex_point_size"), ty.f32(),
core::type::StructMemberAttributes{
- /* location */ {},
- /* index */ {},
+ /* location */ std::nullopt,
+ /* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kPointSize,
- /* interpolation */ {},
+ /* interpolation */ std::nullopt,
/* invariant */ false,
});
}
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
index e16f35e..3546b1e 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
@@ -1492,10 +1492,11 @@
mod.symbols.New("global_id"),
ty.vec3<u32>(),
core::type::StructMemberAttributes{
- /* location */ {},
- /* index */ {},
+ /* location */ std::nullopt,
+ /* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kGlobalInvocationId,
- /* interpolation */ {},
+ /* interpolation */ std::nullopt,
/* invariant */ false,
},
},
@@ -1503,10 +1504,11 @@
mod.symbols.New("index"),
ty.u32(),
core::type::StructMemberAttributes{
- /* location */ {},
- /* index */ {},
+ /* location */ std::nullopt,
+ /* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kLocalInvocationIndex,
- /* interpolation */ {},
+ /* interpolation */ std::nullopt,
/* invariant */ false,
},
},
diff --git a/src/tint/lang/core/type/struct.h b/src/tint/lang/core/type/struct.h
index f4ff396..228c8b8 100644
--- a/src/tint/lang/core/type/struct.h
+++ b/src/tint/lang/core/type/struct.h
@@ -206,6 +206,8 @@
std::optional<uint32_t> location;
/// The value of a `@index` attribute
std::optional<uint32_t> index;
+ /// The value of a `@color` attribute
+ std::optional<uint32_t> color;
/// The value of a `@builtin` attribute
std::optional<core::BuiltinValue> builtin;
/// The values of a `@interpolate` attribute
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
index 6ef2020..3b09a66 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
@@ -208,6 +208,15 @@
manager.Add<ast::transform::ZeroInitWorkgroupMemory>();
}
+ {
+ PixelLocal::Config cfg;
+ for (auto it : options.pixel_local_options.attachments) {
+ cfg.attachments.Add(it.first, it.second);
+ }
+ data.Add<PixelLocal::Config>(cfg);
+ manager.Add<PixelLocal>();
+ }
+
// CanonicalizeEntryPointIO must come after Robustness
manager.Add<ast::transform::CanonicalizeEntryPointIO>();
data.Add<ast::transform::CanonicalizeEntryPointIO::Config>(std::move(entry_point_io_cfg));
@@ -225,15 +234,6 @@
// SubgroupBallot() must come after CanonicalizeEntryPointIO.
manager.Add<SubgroupBallot>();
- {
- PixelLocal::Config cfg;
- for (auto it : options.pixel_local_options.attachments) {
- cfg.attachments.Add(it.first, it.second);
- }
- data.Add<PixelLocal::Config>(cfg);
- manager.Add<PixelLocal>();
- }
-
// ArrayLengthFromUniform must come after SimplifyPointers, as
// it assumes that the form of the array length argument is &var.array.
manager.Add<ast::transform::ArrayLengthFromUniform>();
@@ -275,6 +275,7 @@
wgsl::Extension::kChromiumExperimentalPixelLocal,
wgsl::Extension::kChromiumExperimentalReadWriteStorageTexture,
wgsl::Extension::kChromiumExperimentalSubgroups,
+ wgsl::Extension::kChromiumExperimentalFramebufferFetch,
wgsl::Extension::kChromiumInternalDualSourceBlending,
wgsl::Extension::kChromiumInternalRelaxedUniformLayout,
wgsl::Extension::kF16,
@@ -1969,7 +1970,7 @@
TINT_ICE() << "missing binding attributes for entry point parameter";
return kInvalidBindingIndex;
}
- auto* param_sem = builder_.Sem().Get<sem::Parameter>(param);
+ auto* param_sem = builder_.Sem().Get(param);
auto bp = param_sem->Attributes().binding_point;
if (TINT_UNLIKELY(bp->group != 0)) {
TINT_ICE() << "encountered non-zero resource group index (use BindingRemapper to fix)";
@@ -2005,20 +2006,8 @@
bool ok = Switch(
type, //
- [&](const core::type::Struct* str) {
- bool is_pixel_local = false;
- if (auto* sem_str = str->As<sem::Struct>()) {
- for (auto* member : sem_str->Members()) {
- if (ast::HasAttribute<PixelLocal::Attachment>(
- member->Declaration()->attributes)) {
- is_pixel_local = true;
- break;
- }
- }
- }
- if (!is_pixel_local) {
- out << " [[stage_in]]";
- }
+ [&](const core::type::Struct*) {
+ out << " [[stage_in]]";
return true;
},
[&](const core::type::Texture*) {
@@ -2838,6 +2827,10 @@
}
}
+ if (auto color = attributes.color) {
+ out << " [[color(" + std::to_string(color.value()) + ")]]";
+ }
+
if (auto interpolation = attributes.interpolation) {
auto name = InterpolationToAttribute(interpolation->type, interpolation->sampling);
if (name.empty()) {
@@ -2852,13 +2845,6 @@
out << " " << invariant_define_name_;
}
- if (auto* sem_mem = mem->As<sem::StructMember>()) {
- if (auto* attachment =
- ast::GetAttribute<PixelLocal::Attachment>(sem_mem->Declaration()->attributes)) {
- out << " [[color(" << attachment->index << ")]]";
- }
- }
-
out << ";";
if (is_host_shareable) {
diff --git a/src/tint/lang/msl/writer/ast_raise/pixel_local.cc b/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
index 55d110b..1d6fb0a 100644
--- a/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
+++ b/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
@@ -39,7 +39,6 @@
#include "src/tint/utils/containers/transform.h"
TINT_INSTANTIATE_TYPEINFO(tint::msl::writer::PixelLocal);
-TINT_INSTANTIATE_TYPEINFO(tint::msl::writer::PixelLocal::Attachment);
TINT_INSTANTIATE_TYPEINFO(tint::msl::writer::PixelLocal::Config);
using namespace tint::core::number_suffixes; // NOLINT
@@ -110,10 +109,10 @@
// Obtain struct of the pixel local.
auto* pixel_local_str = global->Type()->UnwrapRef()->As<sem::Struct>();
- // Add an attachment decoration to each member of the pixel_local structure.
+ // Add an Color attribute to each member of the pixel_local structure.
for (auto* member : pixel_local_str->Members()) {
ctx.InsertBack(member->Declaration()->attributes,
- Attachment(AttachmentIndex(member->Index())));
+ b.Color(u32(AttachmentIndex(member->Index()))));
ctx.InsertBack(member->Declaration()->attributes,
b.Disable(ast::DisabledValidation::kEntryPointParameter));
}
@@ -130,6 +129,19 @@
return SkipTransform;
}
+ // At this point, the `var<pixel_local>` will have been replaced with `var<private>`, and
+ // the entry point will use `@color`, which requires the framebuffer fetch extension.
+ // Replace the `chromium_experimental_pixel_local` enable with
+ // `chromium_experimental_framebuffer_fetch`.
+ for (auto* enable : src.AST().Enables()) {
+ for (auto* ext : enable->extensions) {
+ if (ext->name == wgsl::Extension::kChromiumExperimentalPixelLocal) {
+ ctx.Replace(ext, b.create<ast::Extension>(
+ wgsl::Extension::kChromiumExperimentalFramebufferFetch));
+ }
+ }
+ }
+
ctx.Clone();
return resolver::Resolve(b);
}
@@ -240,12 +252,6 @@
Vector{b.Stage(ast::PipelineStage::kFragment)});
}
- /// @returns a new Attachment attribute
- /// @param index the index of the attachment
- PixelLocal::Attachment* Attachment(uint32_t index) {
- return b.ASTNodes().Create<PixelLocal::Attachment>(b.ID(), b.AllocateNodeID(), index);
- }
-
/// @returns the attachment index for the pixel local field with the given index
/// @param field_index the pixel local field index
uint32_t AttachmentIndex(uint32_t field_index) {
@@ -284,17 +290,4 @@
PixelLocal::Config::~Config() = default;
-PixelLocal::Attachment::Attachment(GenerationID pid, ast::NodeID nid, uint32_t idx)
- : Base(pid, nid, Empty), index(idx) {}
-
-PixelLocal::Attachment::~Attachment() = default;
-
-std::string PixelLocal::Attachment::InternalName() const {
- return "attachment(" + std::to_string(index) + ")";
-}
-
-const PixelLocal::Attachment* PixelLocal::Attachment::Clone(ast::CloneContext& ctx) const {
- return ctx.dst->ASTNodes().Create<Attachment>(ctx.dst->ID(), ctx.dst->AllocateNodeID(), index);
-}
-
} // namespace tint::msl::writer
diff --git a/src/tint/lang/msl/writer/ast_raise/pixel_local.h b/src/tint/lang/msl/writer/ast_raise/pixel_local.h
index 4808a33..4459e59 100644
--- a/src/tint/lang/msl/writer/ast_raise/pixel_local.h
+++ b/src/tint/lang/msl/writer/ast_raise/pixel_local.h
@@ -39,6 +39,9 @@
/// PixelLocal transforms module-scope `var<pixel_local>`s and fragment entry point functions that
/// use them:
/// * `var<pixel_local>` will be transformed to `var<private>`.
+/// * All of the members of the pixel local struct will have an additional `@color` attribute added.
+/// * The chromium_experimental_pixel_local extension enable will be replaced with an enable for
+/// chromium_experimental_framebuffer_fetch.
/// * The entry point function will be wrapped with another function ('outer') that calls the
/// 'inner' function.
/// * The outer function will have an additional parameter of the pixel local struct type, which is
@@ -63,32 +66,6 @@
Hashmap<uint32_t, uint32_t, 8> attachments;
};
- /// Intrinsic is an InternalAttribute that's used to decorate a pixel local attachment
- /// parameter, return value or structure member.
- class Attachment final : public Castable<Attachment, ast::InternalAttribute> {
- public:
- /// Constructor
- /// @param pid the identifier of the program that owns this node
- /// @param nid the unique node identifier
- /// @param idx the attachment index
- Attachment(GenerationID pid, ast::NodeID nid, uint32_t idx);
-
- /// Destructor
- ~Attachment() override;
-
- /// @return a short description of the internal attribute which will be
- /// displayed as `@internal(<name>)`
- std::string InternalName() const override;
-
- /// Performs a deep clone of this object using the program::CloneContext `ctx`.
- /// @param ctx the clone context
- /// @return the newly cloned object
- const Attachment* Clone(ast::CloneContext& ctx) const override;
-
- /// The attachment index
- const uint32_t index;
- };
-
/// Constructor
PixelLocal();
diff --git a/src/tint/lang/msl/writer/ast_raise/pixel_local_test.cc b/src/tint/lang/msl/writer/ast_raise/pixel_local_test.cc
index 1d2c51b..fba0af3 100644
--- a/src/tint/lang/msl/writer/ast_raise/pixel_local_test.cc
+++ b/src/tint/lang/msl/writer/ast_raise/pixel_local_test.cc
@@ -70,7 +70,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct PixelLocal {
a : i32,
@@ -102,7 +102,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -117,7 +117,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -155,7 +155,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -170,7 +170,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -211,7 +211,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -230,11 +230,11 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
- @internal(attachment(0)) @internal(disable_validation__entry_point_parameter)
+ @color(0u) @internal(disable_validation__entry_point_parameter)
b : i32,
- @internal(attachment(10)) @internal(disable_validation__entry_point_parameter)
+ @color(10u) @internal(disable_validation__entry_point_parameter)
c : f32,
}
@@ -269,7 +269,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -284,7 +284,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -318,7 +318,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -333,7 +333,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -371,7 +371,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -386,7 +386,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -429,7 +429,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -444,7 +444,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -483,7 +483,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -498,7 +498,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -537,7 +537,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -552,7 +552,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -593,7 +593,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -608,7 +608,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -647,7 +647,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -662,7 +662,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -704,7 +704,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -721,7 +721,7 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
}
@@ -763,7 +763,7 @@
auto* expect =
R"(
-enable chromium_experimental_pixel_local;
+enable chromium_experimental_framebuffer_fetch;
struct F_res {
@location(1)
@@ -784,9 +784,9 @@
}
struct PixelLocal {
- @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+ @color(1u) @internal(disable_validation__entry_point_parameter)
a : u32,
- @internal(attachment(5)) @internal(disable_validation__entry_point_parameter)
+ @color(5u) @internal(disable_validation__entry_point_parameter)
b : u32,
}
diff --git a/src/tint/lang/spirv/writer/function_test.cc b/src/tint/lang/spirv/writer/function_test.cc
index fc66154..85904d4 100644
--- a/src/tint/lang/spirv/writer/function_test.cc
+++ b/src/tint/lang/spirv/writer/function_test.cc
@@ -355,6 +355,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ 0u,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -366,6 +367,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ 1u,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.cc b/src/tint/lang/spirv/writer/raise/shader_io.cc
index 3de0a6b..c44fdee 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io.cc
@@ -43,6 +43,12 @@
namespace {
+/// State that persists across the whole module and can be shared between entry points.
+struct PerModuleState {
+ /// The frag_depth clamp arguments.
+ core::ir::Value* frag_depth_clamp_args = nullptr;
+};
+
/// PIMPL state for the parts of the shader IO transform specific to SPIR-V.
/// For SPIR-V, we declare a global variable for each input and output. The wrapper entry point then
/// loads from and stores to these variables. We also modify the type of the SampleMask builtin to
@@ -56,12 +62,15 @@
/// The configuration options.
const ShaderIOConfig& config;
- /// The frag_depth clamp arguments.
- core::ir::Value* frag_depth_clamp_args = nullptr;
+ /// The per-module state object.
+ PerModuleState& module_state;
/// Constructor
- StateImpl(core::ir::Module& mod, core::ir::Function* f, const ShaderIOConfig& cfg)
- : ShaderIOBackendState(mod, f), config(cfg) {}
+ StateImpl(core::ir::Module& mod,
+ core::ir::Function* f,
+ const ShaderIOConfig& cfg,
+ PerModuleState& mod_state)
+ : ShaderIOBackendState(mod, f), config(cfg), module_state(mod_state) {}
/// Destructor
~StateImpl() override {}
@@ -94,7 +103,8 @@
io.type->is_integer_scalar_or_vector()) {
io.attributes.interpolation = {core::InterpolationType::kFlat};
}
- } else {
+ }
+ if (io.attributes.location) {
name << "_loc" << io.attributes.location.value();
if (io.attributes.index.has_value()) {
name << "_idx" << io.attributes.index.value();
@@ -172,7 +182,7 @@
}
// Create the clamp args struct and variable.
- if (!frag_depth_clamp_args) {
+ if (!module_state.frag_depth_clamp_args) {
// Check that there are no push constants in the module already.
for (auto* inst : *ir.root_block) {
if (auto* var = inst->As<core::ir::Var>()) {
@@ -194,11 +204,11 @@
// Declare the variable.
auto* var = b.Var("tint_frag_depth_clamp_args", ty.ptr(push_constant, str));
ir.root_block->Append(var);
- frag_depth_clamp_args = var->Result();
+ module_state.frag_depth_clamp_args = var->Result();
}
// Clamp the value.
- auto* args = builder.Load(frag_depth_clamp_args);
+ auto* args = builder.Load(module_state.frag_depth_clamp_args);
auto* frag_depth_min = builder.Access(ty.f32(), args, 0_u);
auto* frag_depth_max = builder.Access(ty.f32(), args, 1_u);
return builder
@@ -217,8 +227,9 @@
return result;
}
+ PerModuleState module_state;
core::ir::transform::RunShaderIOBase(ir, [&](core::ir::Module& mod, core::ir::Function* func) {
- return std::make_unique<StateImpl>(mod, func, config);
+ return std::make_unique<StateImpl>(mod, func, config, module_state);
});
return Success;
diff --git a/src/tint/lang/spirv/writer/raise/shader_io_test.cc b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
index edbde41..bfea71b 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
@@ -154,6 +154,7 @@
core::type::StructMemberAttributes{
/* location */ std::nullopt,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kFrontFacing,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -165,6 +166,7 @@
core::type::StructMemberAttributes{
/* location */ std::nullopt,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kPosition,
/* interpolation */ std::nullopt,
/* invariant */ true,
@@ -176,6 +178,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -187,6 +190,7 @@
core::type::StructMemberAttributes{
/* location */ 1u,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */
core::Interpolation{
@@ -302,6 +306,7 @@
core::type::StructMemberAttributes{
/* location */ std::nullopt,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kPosition,
/* interpolation */ std::nullopt,
/* invariant */ true,
@@ -313,6 +318,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -514,6 +520,7 @@
core::type::StructMemberAttributes{
/* location */ std::nullopt,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kPosition,
/* interpolation */ std::nullopt,
/* invariant */ true,
@@ -525,6 +532,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -536,6 +544,7 @@
core::type::StructMemberAttributes{
/* location */ 1u,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */
core::Interpolation{
@@ -621,6 +630,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ 0u,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -632,6 +642,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ 1u,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -707,6 +718,7 @@
core::type::StructMemberAttributes{
/* location */ std::nullopt,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kPosition,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -718,6 +730,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -846,6 +859,7 @@
core::type::StructMemberAttributes{
/* location */ std::nullopt,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kPosition,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -857,6 +871,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -939,6 +954,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -950,6 +966,7 @@
core::type::StructMemberAttributes{
/* location */ std::nullopt,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kSampleMask,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -1033,6 +1050,7 @@
core::type::StructMemberAttributes{
/* location */ 1u,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */
core::Interpolation{
@@ -1180,6 +1198,7 @@
core::type::StructMemberAttributes{
/* location */ 0u,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ std::nullopt,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -1191,6 +1210,7 @@
core::type::StructMemberAttributes{
/* location */ std::nullopt,
/* index */ std::nullopt,
+ /* color */ std::nullopt,
/* builtin */ core::BuiltinValue::kFragDepth,
/* interpolation */ std::nullopt,
/* invariant */ false,
@@ -1266,6 +1286,163 @@
EXPECT_EQ(expect, str());
}
+TEST_F(SpirvWriter_ShaderIOTest, ClampFragDepth_MultipleFragmentShaders) {
+ auto* str_ty = ty.Struct(mod.symbols.New("Outputs"),
+ {
+ {
+ mod.symbols.New("color"),
+ ty.f32(),
+ core::type::StructMemberAttributes{
+ /* location */ 0u,
+ /* index */ std::nullopt,
+ /* color */ std::nullopt,
+ /* builtin */ std::nullopt,
+ /* interpolation */ std::nullopt,
+ /* invariant */ false,
+ },
+ },
+ {
+ mod.symbols.New("depth"),
+ ty.f32(),
+ core::type::StructMemberAttributes{
+ /* location */ std::nullopt,
+ /* index */ std::nullopt,
+ /* color */ std::nullopt,
+ /* builtin */ core::BuiltinValue::kFragDepth,
+ /* interpolation */ std::nullopt,
+ /* invariant */ false,
+ },
+ },
+ });
+
+ auto make_entry_point = [&](std::string_view name) {
+ auto* ep = b.Function(name, str_ty);
+ ep->SetStage(core::ir::Function::PipelineStage::kFragment);
+ b.Append(ep->Block(), [&] { //
+ b.Return(ep, b.Construct(str_ty, 0.5_f, 2_f));
+ });
+ };
+ make_entry_point("ep1");
+ make_entry_point("ep2");
+ make_entry_point("ep3");
+
+ auto* src = R"(
+Outputs = struct @align(4) {
+ color:f32 @offset(0), @location(0)
+ depth:f32 @offset(4), @builtin(frag_depth)
+}
+
+%ep1 = @fragment func():Outputs -> %b1 {
+ %b1 = block {
+ %2:Outputs = construct 0.5f, 2.0f
+ ret %2
+ }
+}
+%ep2 = @fragment func():Outputs -> %b2 {
+ %b2 = block {
+ %4:Outputs = construct 0.5f, 2.0f
+ ret %4
+ }
+}
+%ep3 = @fragment func():Outputs -> %b3 {
+ %b3 = block {
+ %6:Outputs = construct 0.5f, 2.0f
+ ret %6
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+Outputs = struct @align(4) {
+ color:f32 @offset(0)
+ depth:f32 @offset(4)
+}
+
+FragDepthClampArgs = struct @align(4), @block {
+ min:f32 @offset(0)
+ max:f32 @offset(4)
+}
+
+%b1 = block { # root
+ %ep1_loc0_Output:ptr<__out, f32, write> = var @location(0)
+ %ep1_frag_depth_Output:ptr<__out, f32, write> = var @builtin(frag_depth)
+ %tint_frag_depth_clamp_args:ptr<push_constant, FragDepthClampArgs, read_write> = var
+ %ep2_loc0_Output:ptr<__out, f32, write> = var @location(0)
+ %ep2_frag_depth_Output:ptr<__out, f32, write> = var @builtin(frag_depth)
+ %ep3_loc0_Output:ptr<__out, f32, write> = var @location(0)
+ %ep3_frag_depth_Output:ptr<__out, f32, write> = var @builtin(frag_depth)
+}
+
+%ep1_inner = func():Outputs -> %b2 {
+ %b2 = block {
+ %9:Outputs = construct 0.5f, 2.0f
+ ret %9
+ }
+}
+%ep2_inner = func():Outputs -> %b3 {
+ %b3 = block {
+ %11:Outputs = construct 0.5f, 2.0f
+ ret %11
+ }
+}
+%ep3_inner = func():Outputs -> %b4 {
+ %b4 = block {
+ %13:Outputs = construct 0.5f, 2.0f
+ ret %13
+ }
+}
+%ep1 = @fragment func():void -> %b5 {
+ %b5 = block {
+ %15:Outputs = call %ep1_inner
+ %16:f32 = access %15, 0u
+ store %ep1_loc0_Output, %16
+ %17:f32 = access %15, 1u
+ %18:FragDepthClampArgs = load %tint_frag_depth_clamp_args
+ %19:f32 = access %18, 0u
+ %20:f32 = access %18, 1u
+ %21:f32 = clamp %17, %19, %20
+ store %ep1_frag_depth_Output, %21
+ ret
+ }
+}
+%ep2 = @fragment func():void -> %b6 {
+ %b6 = block {
+ %23:Outputs = call %ep2_inner
+ %24:f32 = access %23, 0u
+ store %ep2_loc0_Output, %24
+ %25:f32 = access %23, 1u
+ %26:FragDepthClampArgs = load %tint_frag_depth_clamp_args
+ %27:f32 = access %26, 0u
+ %28:f32 = access %26, 1u
+ %29:f32 = clamp %25, %27, %28
+ store %ep2_frag_depth_Output, %29
+ ret
+ }
+}
+%ep3 = @fragment func():void -> %b7 {
+ %b7 = block {
+ %31:Outputs = call %ep3_inner
+ %32:f32 = access %31, 0u
+ store %ep3_loc0_Output, %32
+ %33:f32 = access %31, 1u
+ %34:FragDepthClampArgs = load %tint_frag_depth_clamp_args
+ %35:f32 = access %34, 0u
+ %36:f32 = access %34, 1u
+ %37:f32 = clamp %33, %35, %36
+ store %ep3_frag_depth_Output, %37
+ ret
+ }
+}
+)";
+
+ ShaderIOConfig config;
+ config.clamp_frag_depth = true;
+ Run(ShaderIO, config);
+
+ EXPECT_EQ(expect, str());
+}
+
TEST_F(SpirvWriter_ShaderIOTest, EmitVertexPointSize) {
auto* ep = b.Function("foo", ty.vec4<f32>());
ep->SetStage(core::ir::Function::PipelineStage::kVertex);
diff --git a/src/tint/lang/wgsl/ast/BUILD.bazel b/src/tint/lang/wgsl/ast/BUILD.bazel
index 31601b6..f7514c9 100644
--- a/src/tint/lang/wgsl/ast/BUILD.bazel
+++ b/src/tint/lang/wgsl/ast/BUILD.bazel
@@ -57,6 +57,7 @@
"case_selector.cc",
"case_statement.cc",
"clone_context.cc",
+ "color_attribute.cc",
"compound_assignment_statement.cc",
"const.cc",
"const_assert.cc",
@@ -137,6 +138,7 @@
"case_selector.h",
"case_statement.h",
"clone_context.h",
+ "color_attribute.h",
"compound_assignment_statement.h",
"const.h",
"const_assert.h",
@@ -244,6 +246,7 @@
"case_selector_test.cc",
"case_statement_test.cc",
"clone_context_test.cc",
+ "color_attribute_test.cc",
"compound_assignment_statement_test.cc",
"const_assert_test.cc",
"continue_statement_test.cc",
diff --git a/src/tint/lang/wgsl/ast/BUILD.cmake b/src/tint/lang/wgsl/ast/BUILD.cmake
index ebad7e9..fe4eef9 100644
--- a/src/tint/lang/wgsl/ast/BUILD.cmake
+++ b/src/tint/lang/wgsl/ast/BUILD.cmake
@@ -77,6 +77,8 @@
lang/wgsl/ast/case_statement.h
lang/wgsl/ast/clone_context.cc
lang/wgsl/ast/clone_context.h
+ lang/wgsl/ast/color_attribute.cc
+ lang/wgsl/ast/color_attribute.h
lang/wgsl/ast/compound_assignment_statement.cc
lang/wgsl/ast/compound_assignment_statement.h
lang/wgsl/ast/const.cc
@@ -244,6 +246,7 @@
lang/wgsl/ast/case_selector_test.cc
lang/wgsl/ast/case_statement_test.cc
lang/wgsl/ast/clone_context_test.cc
+ lang/wgsl/ast/color_attribute_test.cc
lang/wgsl/ast/compound_assignment_statement_test.cc
lang/wgsl/ast/const_assert_test.cc
lang/wgsl/ast/continue_statement_test.cc
diff --git a/src/tint/lang/wgsl/ast/BUILD.gn b/src/tint/lang/wgsl/ast/BUILD.gn
index 4580e46..6cc263d 100644
--- a/src/tint/lang/wgsl/ast/BUILD.gn
+++ b/src/tint/lang/wgsl/ast/BUILD.gn
@@ -80,6 +80,8 @@
"case_statement.h",
"clone_context.cc",
"clone_context.h",
+ "color_attribute.cc",
+ "color_attribute.h",
"compound_assignment_statement.cc",
"compound_assignment_statement.h",
"const.cc",
@@ -244,6 +246,7 @@
"case_selector_test.cc",
"case_statement_test.cc",
"clone_context_test.cc",
+ "color_attribute_test.cc",
"compound_assignment_statement_test.cc",
"const_assert_test.cc",
"continue_statement_test.cc",
diff --git a/src/tint/lang/wgsl/ast/builder.h b/src/tint/lang/wgsl/ast/builder.h
index 1cbdfba..1b80ec9 100644
--- a/src/tint/lang/wgsl/ast/builder.h
+++ b/src/tint/lang/wgsl/ast/builder.h
@@ -67,6 +67,7 @@
#include "src/tint/lang/wgsl/ast/call_expression.h"
#include "src/tint/lang/wgsl/ast/call_statement.h"
#include "src/tint/lang/wgsl/ast/case_statement.h"
+#include "src/tint/lang/wgsl/ast/color_attribute.h"
#include "src/tint/lang/wgsl/ast/compound_assignment_statement.h"
#include "src/tint/lang/wgsl/ast/const.h"
#include "src/tint/lang/wgsl/ast/const_assert.h"
@@ -3167,6 +3168,23 @@
return create<ast::LocationAttribute>(source, Expr(std::forward<EXPR>(location)));
}
+ /// Creates an ast::ColorAttribute
+ /// @param index the index value expression
+ /// @returns the index attribute pointer
+ template <typename EXPR>
+ const ast::ColorAttribute* Color(EXPR&& index) {
+ return create<ast::ColorAttribute>(source_, Expr(std::forward<EXPR>(index)));
+ }
+
+ /// Creates an ast::ColorAttribute
+ /// @param source the source information
+ /// @param index the index value expression
+ /// @returns the index attribute pointer
+ template <typename EXPR>
+ const ast::ColorAttribute* Color(const Source& source, EXPR&& index) {
+ return create<ast::ColorAttribute>(source, Expr(std::forward<EXPR>(index)));
+ }
+
/// Creates an ast::LocationAttribute
/// @param location the location value expression
/// @returns the location attribute pointer
diff --git a/src/tint/lang/wgsl/ast/color_attribute.cc b/src/tint/lang/wgsl/ast/color_attribute.cc
new file mode 100644
index 0000000..daa1597
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/color_attribute.cc
@@ -0,0 +1,60 @@
+// Copyright 2023 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 "src/tint/lang/wgsl/ast/color_attribute.h"
+
+#include <string>
+
+#include "src/tint/lang/wgsl/ast/builder.h"
+#include "src/tint/lang/wgsl/ast/clone_context.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ast::ColorAttribute);
+
+namespace tint::ast {
+
+ColorAttribute::ColorAttribute(GenerationID pid,
+ NodeID nid,
+ const Source& src,
+ const Expression* exp)
+ : Base(pid, nid, src), expr(exp) {
+ TINT_ASSERT_GENERATION_IDS_EQUAL(exp, generation_id);
+}
+
+ColorAttribute::~ColorAttribute() = default;
+
+std::string ColorAttribute::Name() const {
+ return "color";
+}
+
+const ColorAttribute* ColorAttribute::Clone(CloneContext& ctx) const {
+ // Clone arguments outside of create() call to have deterministic ordering
+ auto src = ctx.Clone(source);
+ auto e = ctx.Clone(expr);
+ return ctx.dst->create<ColorAttribute>(src, e);
+}
+
+} // namespace tint::ast
diff --git a/src/tint/lang/wgsl/ast/color_attribute.h b/src/tint/lang/wgsl/ast/color_attribute.h
new file mode 100644
index 0000000..d0a21e4
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/color_attribute.h
@@ -0,0 +1,68 @@
+// Copyright 2023 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.
+
+#ifndef SRC_TINT_LANG_WGSL_AST_COLOR_ATTRIBUTE_H_
+#define SRC_TINT_LANG_WGSL_AST_COLOR_ATTRIBUTE_H_
+
+#include <string>
+
+#include "src/tint/lang/wgsl/ast/attribute.h"
+
+// Forward declarations
+namespace tint::ast {
+class Expression;
+}
+
+namespace tint::ast {
+
+/// A color attribute (enabled with the frame-buffer fetch extension)
+class ColorAttribute final : public Castable<ColorAttribute, Attribute> {
+ public:
+ /// constructor
+ /// @param pid the identifier of the program that owns this node
+ /// @param nid the unique node identifier
+ /// @param src the source of this node
+ /// @param expr the frame-buffer index value
+ ColorAttribute(GenerationID pid, NodeID nid, const Source& src, const Expression* expr);
+ ~ColorAttribute() override;
+
+ /// @returns the WGSL name for the attribute
+ std::string Name() const override;
+
+ /// Clones this node and all transitive child nodes using the `CloneContext`
+ /// `ctx`.
+ /// @param ctx the clone context
+ /// @return the newly cloned node
+ const ColorAttribute* Clone(CloneContext& ctx) const override;
+
+ /// The index value expression
+ const Expression* const expr;
+};
+
+} // namespace tint::ast
+
+#endif // SRC_TINT_LANG_WGSL_AST_COLOR_ATTRIBUTE_H_
diff --git a/src/tint/lang/wgsl/ast/color_attribute_test.cc b/src/tint/lang/wgsl/ast/color_attribute_test.cc
new file mode 100644
index 0000000..9afba5f
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/color_attribute_test.cc
@@ -0,0 +1,66 @@
+// Copyright 2023 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 "gtest/gtest-spi.h"
+
+#include "src/tint/lang/wgsl/ast/color_attribute.h"
+#include "src/tint/lang/wgsl/ast/helper_test.h"
+
+using namespace tint::core::number_suffixes; // NOLINT
+
+namespace tint::ast {
+namespace {
+
+using ColorAttributeTest = TestHelper;
+
+TEST_F(ColorAttributeTest, Creation) {
+ auto* expr = Expr(1_u);
+ auto* c = Color(expr);
+ EXPECT_EQ(c->expr, expr);
+}
+
+TEST_F(ColorAttributeTest, Assert_Null_Builtin) {
+ EXPECT_FATAL_FAILURE(
+ {
+ ProgramBuilder b;
+ b.Color(nullptr);
+ },
+ "internal compiler error");
+}
+
+TEST_F(ColorAttributeTest, Assert_DifferentGenerationID_Color) {
+ EXPECT_FATAL_FAILURE(
+ {
+ ProgramBuilder b1;
+ ProgramBuilder b2;
+ b1.Color(b2.Expr(1_u));
+ },
+ "internal compiler error");
+}
+
+} // namespace
+} // namespace tint::ast
diff --git a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
index 45c270c..1408dcc 100644
--- a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
@@ -41,6 +41,7 @@
#include "src/tint/lang/wgsl/program/program_builder.h"
#include "src/tint/lang/wgsl/resolver/resolve.h"
#include "src/tint/lang/wgsl/sem/function.h"
+#include "src/tint/utils/containers/transform.h"
using namespace tint::core::number_suffixes; // NOLINT
using namespace tint::core::fluent_types; // NOLINT
@@ -64,6 +65,8 @@
std::optional<uint32_t> location;
/// The struct member index if provided
std::optional<uint32_t> index;
+ /// The struct member color if provided
+ std::optional<uint32_t> color;
};
/// FXC is sensitive to field order in structures, this is used by StructMemberComparator to ensure
@@ -105,7 +108,7 @@
// Returns true if `attr` is a shader IO attribute.
bool IsShaderIOAttribute(const Attribute* attr) {
return attr->IsAnyOf<BuiltinAttribute, InterpolateAttribute, InvariantAttribute,
- LocationAttribute, IndexAttribute>();
+ LocationAttribute, ColorAttribute, IndexAttribute>();
}
} // namespace
@@ -284,11 +287,13 @@
/// @param name the name of the shader input
/// @param type the type of the shader input
/// @param location the location if provided
+ /// @param color the color if provided
/// @param attrs the attributes to apply to the shader input
/// @returns an expression which evaluates to the value of the shader input
const Expression* AddInput(std::string name,
const core::type::Type* type,
std::optional<uint32_t> location,
+ std::optional<uint32_t> color,
tint::Vector<const Attribute*, 8> attrs) {
auto ast_type = CreateASTTypeFor(ctx, type);
@@ -347,8 +352,8 @@
// Otherwise, move it to the new structure member list.
Symbol symbol = input_names.emplace(name).second ? b.Symbols().Register(name)
: b.Symbols().New(name);
- wrapper_struct_param_members.Push(
- {b.Member(symbol, ast_type, std::move(attrs)), location, std::nullopt});
+ wrapper_struct_param_members.Push({b.Member(symbol, ast_type, std::move(attrs)),
+ location, /* index */ std::nullopt, color});
return b.MemberAccessor(InputStructSymbol(), symbol);
}
}
@@ -428,8 +433,8 @@
}
auto name = param->Declaration()->name->symbol.Name();
- auto* input_expr =
- AddInput(name, param->Type(), param->Attributes().location, std::move(attributes));
+ auto* input_expr = AddInput(name, param->Type(), param->Attributes().location,
+ param->Attributes().color, std::move(attributes));
inner_call_parameters.Push(input_expr);
}
@@ -463,7 +468,7 @@
auto attributes =
CloneShaderIOAttributes(member->Declaration()->attributes, do_interpolate);
auto* input_expr = AddInput(name, member->Type(), member->Attributes().location,
- std::move(attributes));
+ member->Attributes().color, std::move(attributes));
inner_struct_values.Push(input_expr);
}
@@ -546,44 +551,57 @@
}
/// Comparison function used to reorder struct members such that all members with
- /// location attributes appear first (ordered by location slot), followed by
- /// those with builtin attributes.
+ /// color attributes appear first (ordered by color slot), then location attributes (ordered by
+ /// location slot), followed by those with builtin attributes (ordered by BuiltinOrder).
/// @param x a struct member
/// @param y another struct member
/// @returns true if a comes before b
bool StructMemberComparator(const MemberInfo& x, const MemberInfo& y) {
- auto* x_loc = GetAttribute<LocationAttribute>(x.member->attributes);
- auto* y_loc = GetAttribute<LocationAttribute>(y.member->attributes);
- auto* x_blt = GetAttribute<BuiltinAttribute>(x.member->attributes);
- auto* y_blt = GetAttribute<BuiltinAttribute>(y.member->attributes);
- if (x_loc) {
- if (!y_loc) {
- // `a` has location attribute and `b` does not: `a` goes first.
- return true;
- }
+ if (x.color.has_value() && y.color.has_value()) {
+ // Both have color attributes: smallest goes first.
+ return x.color < y.color;
+ }
+ if (x.color.has_value() != y.color.has_value()) {
+ // The member with the color goes first
+ return x.color.has_value();
+ }
+
+ if (x.location.has_value() && y.location.has_value()) {
// Both have location attributes: smallest goes first.
return x.location < y.location;
- } else {
- if (y_loc) {
- // `b` has location attribute and `a` does not: `b` goes first.
- return false;
- }
- // Both are builtins: order matters for FXC.
- auto builtin_a = BuiltinOf(x_blt);
- auto builtin_b = BuiltinOf(y_blt);
- return BuiltinOrder(builtin_a) < BuiltinOrder(builtin_b);
}
+ if (x.location.has_value() != y.location.has_value()) {
+ // The member with the location goes first
+ return x.location.has_value();
+ }
+
+ {
+ auto* x_blt = GetAttribute<BuiltinAttribute>(x.member->attributes);
+ auto* y_blt = GetAttribute<BuiltinAttribute>(y.member->attributes);
+ if (x_blt && y_blt) {
+ // Both are builtins: order matters for FXC.
+ auto builtin_a = BuiltinOf(x_blt);
+ auto builtin_b = BuiltinOf(y_blt);
+ return BuiltinOrder(builtin_a) < BuiltinOrder(builtin_b);
+ }
+ if ((x_blt != nullptr) != (y_blt != nullptr)) {
+ // The member with the builtin goes first
+ return x_blt != nullptr;
+ }
+ }
+
+ TINT_UNREACHABLE();
+ return false;
}
+
/// Create the wrapper function's struct parameter and type objects.
void CreateInputStruct() {
// Sort the struct members to satisfy HLSL interfacing matching rules.
std::sort(wrapper_struct_param_members.begin(), wrapper_struct_param_members.end(),
[&](auto& x, auto& y) { return StructMemberComparator(x, y); });
- tint::Vector<const StructMember*, 8> members;
- for (auto& mem : wrapper_struct_param_members) {
- members.Push(mem.member);
- }
+ auto members =
+ tint::Transform(wrapper_struct_param_members, [](auto& info) { return info.member; });
// Create the new struct type.
auto struct_name = b.Sym();
@@ -614,9 +632,12 @@
}
member_names.insert(name.Name());
- wrapper_struct_output_members.Push(
- {b.Member(name, outval.type, std::move(outval.attributes)), outval.location,
- std::nullopt});
+ wrapper_struct_output_members.Push({
+ /* member */ b.Member(name, outval.type, std::move(outval.attributes)),
+ /* location */ outval.location,
+ /* color */ std::nullopt,
+ /* index */ std::nullopt,
+ });
assignments.Push(b.Assign(b.MemberAccessor(wrapper_result, name), outval.value));
}
diff --git a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io_test.cc b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io_test.cc
index dd75db3..f30d07f 100644
--- a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io_test.cc
@@ -70,28 +70,35 @@
TEST_F(CanonicalizeEntryPointIOTest, Parameters_Spirv) {
auto* src = R"(
+enable chromium_experimental_framebuffer_fetch;
+
@fragment
fn frag_main(@location(1) loc1 : f32,
@location(2) @interpolate(flat) loc2 : vec4<u32>,
- @builtin(position) coord : vec4<f32>) {
- var col : f32 = (coord.x * loc1);
+ @builtin(position) coord : vec4<f32>,
+ @color(3) color : vec4<f32>) {
+ var col : f32 = (coord.x * loc1) + color.g;
}
)";
auto* expect = R"(
+enable chromium_experimental_framebuffer_fetch;
+
@location(1) @internal(disable_validation__ignore_address_space) var<__in> loc1_1 : f32;
@location(2) @interpolate(flat) @internal(disable_validation__ignore_address_space) var<__in> loc2_1 : vec4<u32>;
@builtin(position) @internal(disable_validation__ignore_address_space) var<__in> coord_1 : vec4<f32>;
-fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>) {
- var col : f32 = (coord.x * loc1);
+@color(3) @internal(disable_validation__ignore_address_space) var<__in> color_1 : vec4<f32>;
+
+fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>, color : vec4<f32>) {
+ var col : f32 = ((coord.x * loc1) + color.g);
}
@fragment
fn frag_main() {
- frag_main_inner(loc1_1, loc2_1, coord_1);
+ frag_main_inner(loc1_1, loc2_1, coord_1, color_1);
}
)";
@@ -104,29 +111,36 @@
TEST_F(CanonicalizeEntryPointIOTest, Parameters_Msl) {
auto* src = R"(
+enable chromium_experimental_framebuffer_fetch;
+
@fragment
fn frag_main(@location(1) loc1 : f32,
@location(2) @interpolate(flat) loc2 : vec4<u32>,
- @builtin(position) coord : vec4<f32>) {
- var col : f32 = (coord.x * loc1);
+ @builtin(position) coord : vec4<f32>,
+ @color(3) color : vec4<f32>) {
+ var col : f32 = (coord.x * loc1) + color.g;
}
)";
auto* expect = R"(
+enable chromium_experimental_framebuffer_fetch;
+
struct tint_symbol_1 {
+ @color(3)
+ color : vec4<f32>,
@location(1)
loc1 : f32,
@location(2) @interpolate(flat)
loc2 : vec4<u32>,
}
-fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>) {
- var col : f32 = (coord.x * loc1);
+fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>, color : vec4<f32>) {
+ var col : f32 = ((coord.x * loc1) + color.g);
}
@fragment
fn frag_main(@builtin(position) coord : vec4<f32>, tint_symbol : tint_symbol_1) {
- frag_main_inner(tint_symbol.loc1, tint_symbol.loc2, coord);
+ frag_main_inner(tint_symbol.loc1, tint_symbol.loc2, coord, tint_symbol.color);
}
)";
@@ -139,16 +153,23 @@
TEST_F(CanonicalizeEntryPointIOTest, Parameters_Hlsl) {
auto* src = R"(
+enable chromium_experimental_framebuffer_fetch;
+
@fragment
fn frag_main(@location(1) loc1 : f32,
@location(2) @interpolate(flat) loc2 : vec4<u32>,
- @builtin(position) coord : vec4<f32>) {
- var col : f32 = (coord.x * loc1);
+ @builtin(position) coord : vec4<f32>,
+ @color(3) color : vec4<f32>) {
+ var col : f32 = (coord.x * loc1) + color.g;
}
)";
auto* expect = R"(
+enable chromium_experimental_framebuffer_fetch;
+
struct tint_symbol_1 {
+ @color(3)
+ color : vec4<f32>,
@location(1)
loc1 : f32,
@location(2) @interpolate(flat)
@@ -157,13 +178,13 @@
coord : vec4<f32>,
}
-fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>) {
- var col : f32 = (coord.x * loc1);
+fn frag_main_inner(loc1 : f32, loc2 : vec4<u32>, coord : vec4<f32>, color : vec4<f32>) {
+ var col : f32 = ((coord.x * loc1) + color.g);
}
@fragment
fn frag_main(tint_symbol : tint_symbol_1) {
- frag_main_inner(tint_symbol.loc1, tint_symbol.loc2, tint_symbol.coord);
+ frag_main_inner(tint_symbol.loc1, tint_symbol.loc2, tint_symbol.coord, tint_symbol.color);
}
)";
@@ -246,6 +267,8 @@
TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Spirv) {
auto* src = R"(
+enable chromium_experimental_framebuffer_fetch;
+
struct FragBuiltins {
@builtin(position) coord : vec4<f32>,
};
@@ -253,16 +276,24 @@
@location(1) loc1 : f32,
@location(2) @interpolate(flat) loc2 : vec4<u32>,
};
+struct FragColors {
+ @color(3) col3 : vec4<f32>,
+ @color(1) col1 : vec4<u32>,
+ @color(2) col2 : vec4<i32>,
+};
@fragment
fn frag_main(@location(0) loc0 : f32,
locations : FragLocations,
- builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+ builtins : FragBuiltins,
+ colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
)";
auto* expect = R"(
+enable chromium_experimental_framebuffer_fetch;
+
@location(0) @internal(disable_validation__ignore_address_space) var<__in> loc0_1 : f32;
@location(1) @internal(disable_validation__ignore_address_space) var<__in> loc1_1 : f32;
@@ -271,6 +302,12 @@
@builtin(position) @internal(disable_validation__ignore_address_space) var<__in> coord_1 : vec4<f32>;
+@color(3) @internal(disable_validation__ignore_address_space) var<__in> col3_1 : vec4<f32>;
+
+@color(1) @interpolate(flat) @internal(disable_validation__ignore_address_space) var<__in> col1_1 : vec4<u32>;
+
+@color(2) @interpolate(flat) @internal(disable_validation__ignore_address_space) var<__in> col2_1 : vec4<i32>;
+
struct FragBuiltins {
coord : vec4<f32>,
}
@@ -280,13 +317,19 @@
loc2 : vec4<u32>,
}
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+struct FragColors {
+ col3 : vec4<f32>,
+ col1 : vec4<u32>,
+ col2 : vec4<i32>,
+}
+
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins, colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
@fragment
fn frag_main() {
- frag_main_inner(loc0_1, FragLocations(loc1_1, loc2_1), FragBuiltins(coord_1));
+ frag_main_inner(loc0_1, FragLocations(loc1_1, loc2_1), FragBuiltins(coord_1), FragColors(col3_1, col1_1, col2_1));
}
)";
@@ -299,11 +342,14 @@
TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Spirv_OutOfOrder) {
auto* src = R"(
+enable chromium_experimental_framebuffer_fetch;
+
@fragment
fn frag_main(@location(0) loc0 : f32,
locations : FragLocations,
- builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+ builtins : FragBuiltins,
+ colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
struct FragBuiltins {
@@ -313,9 +359,16 @@
@location(1) loc1 : f32,
@location(2) @interpolate(flat) loc2 : vec4<u32>,
};
+struct FragColors {
+ @color(3) col3 : vec4<f32>,
+ @color(1) col1 : vec4<u32>,
+ @color(2) col2 : vec4<i32>,
+};
)";
auto* expect = R"(
+enable chromium_experimental_framebuffer_fetch;
+
@location(0) @internal(disable_validation__ignore_address_space) var<__in> loc0_1 : f32;
@location(1) @internal(disable_validation__ignore_address_space) var<__in> loc1_1 : f32;
@@ -324,13 +377,19 @@
@builtin(position) @internal(disable_validation__ignore_address_space) var<__in> coord_1 : vec4<f32>;
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+@color(3) @internal(disable_validation__ignore_address_space) var<__in> col3_1 : vec4<f32>;
+
+@color(1) @interpolate(flat) @internal(disable_validation__ignore_address_space) var<__in> col1_1 : vec4<u32>;
+
+@color(2) @interpolate(flat) @internal(disable_validation__ignore_address_space) var<__in> col2_1 : vec4<i32>;
+
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins, colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
@fragment
fn frag_main() {
- frag_main_inner(loc0_1, FragLocations(loc1_1, loc2_1), FragBuiltins(coord_1));
+ frag_main_inner(loc0_1, FragLocations(loc1_1, loc2_1), FragBuiltins(coord_1), FragColors(col3_1, col1_1, col2_1));
}
struct FragBuiltins {
@@ -341,6 +400,12 @@
loc1 : f32,
loc2 : vec4<u32>,
}
+
+struct FragColors {
+ col3 : vec4<f32>,
+ col1 : vec4<u32>,
+ col2 : vec4<i32>,
+}
)";
DataMap data;
@@ -352,6 +417,8 @@
TEST_F(CanonicalizeEntryPointIOTest, StructParameters_kMsl) {
auto* src = R"(
+enable chromium_experimental_framebuffer_fetch;
+
struct FragBuiltins {
@builtin(position) coord : vec4<f32>,
};
@@ -359,16 +426,24 @@
@location(1) loc1 : f32,
@location(2) @interpolate(flat) loc2 : vec4<u32>,
};
+struct FragColors {
+ @color(3) col3 : vec4<f32>,
+ @color(1) col1 : vec4<u32>,
+ @color(2) col2 : vec4<i32>,
+};
@fragment
fn frag_main(@location(0) loc0 : f32,
locations : FragLocations,
- builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+ builtins : FragBuiltins,
+ colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
)";
auto* expect = R"(
+enable chromium_experimental_framebuffer_fetch;
+
struct FragBuiltins {
coord : vec4<f32>,
}
@@ -378,7 +453,19 @@
loc2 : vec4<u32>,
}
+struct FragColors {
+ col3 : vec4<f32>,
+ col1 : vec4<u32>,
+ col2 : vec4<i32>,
+}
+
struct tint_symbol_1 {
+ @color(1)
+ col1 : vec4<u32>,
+ @color(2)
+ col2 : vec4<i32>,
+ @color(3)
+ col3 : vec4<f32>,
@location(0)
loc0 : f32,
@location(1)
@@ -387,13 +474,13 @@
loc2 : vec4<u32>,
}
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins, colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
@fragment
fn frag_main(@builtin(position) coord : vec4<f32>, tint_symbol : tint_symbol_1) {
- frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(coord));
+ frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(coord), FragColors(tint_symbol.col3, tint_symbol.col1, tint_symbol.col2));
}
)";
@@ -406,11 +493,14 @@
TEST_F(CanonicalizeEntryPointIOTest, StructParameters_kMsl_OutOfOrder) {
auto* src = R"(
+enable chromium_experimental_framebuffer_fetch;
+
@fragment
fn frag_main(@location(0) loc0 : f32,
locations : FragLocations,
- builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+ builtins : FragBuiltins,
+ colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
struct FragBuiltins {
@@ -420,10 +510,23 @@
@location(1) loc1 : f32,
@location(2) @interpolate(flat) loc2 : vec4<u32>,
};
+struct FragColors {
+ @color(3) col3 : vec4<f32>,
+ @color(1) col1 : vec4<u32>,
+ @color(2) col2 : vec4<i32>,
+};
)";
auto* expect = R"(
+enable chromium_experimental_framebuffer_fetch;
+
struct tint_symbol_1 {
+ @color(1)
+ col1 : vec4<u32>,
+ @color(2)
+ col2 : vec4<i32>,
+ @color(3)
+ col3 : vec4<f32>,
@location(0)
loc0 : f32,
@location(1)
@@ -432,13 +535,13 @@
loc2 : vec4<u32>,
}
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins, colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
@fragment
fn frag_main(@builtin(position) coord : vec4<f32>, tint_symbol : tint_symbol_1) {
- frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(coord));
+ frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(coord), FragColors(tint_symbol.col3, tint_symbol.col1, tint_symbol.col2));
}
struct FragBuiltins {
@@ -449,6 +552,12 @@
loc1 : f32,
loc2 : vec4<u32>,
}
+
+struct FragColors {
+ col3 : vec4<f32>,
+ col1 : vec4<u32>,
+ col2 : vec4<i32>,
+}
)";
DataMap data;
@@ -460,6 +569,8 @@
TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Hlsl) {
auto* src = R"(
+enable chromium_experimental_framebuffer_fetch;
+
struct FragBuiltins {
@builtin(position) coord : vec4<f32>,
};
@@ -467,16 +578,24 @@
@location(1) loc1 : f32,
@location(2) @interpolate(flat) loc2 : vec4<u32>,
};
+struct FragColors {
+ @color(3) col3 : vec4<f32>,
+ @color(1) col1 : vec4<u32>,
+ @color(2) col2 : vec4<i32>,
+};
@fragment
fn frag_main(@location(0) loc0 : f32,
locations : FragLocations,
- builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+ builtins : FragBuiltins,
+ colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
)";
auto* expect = R"(
+enable chromium_experimental_framebuffer_fetch;
+
struct FragBuiltins {
coord : vec4<f32>,
}
@@ -486,7 +605,19 @@
loc2 : vec4<u32>,
}
+struct FragColors {
+ col3 : vec4<f32>,
+ col1 : vec4<u32>,
+ col2 : vec4<i32>,
+}
+
struct tint_symbol_1 {
+ @color(1)
+ col1 : vec4<u32>,
+ @color(2)
+ col2 : vec4<i32>,
+ @color(3)
+ col3 : vec4<f32>,
@location(0)
loc0 : f32,
@location(1)
@@ -497,13 +628,13 @@
coord : vec4<f32>,
}
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins, colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
@fragment
fn frag_main(tint_symbol : tint_symbol_1) {
- frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(tint_symbol.coord));
+ frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(tint_symbol.coord), FragColors(tint_symbol.col3, tint_symbol.col1, tint_symbol.col2));
}
)";
@@ -516,11 +647,14 @@
TEST_F(CanonicalizeEntryPointIOTest, StructParameters_Hlsl_OutOfOrder) {
auto* src = R"(
+enable chromium_experimental_framebuffer_fetch;
+
@fragment
fn frag_main(@location(0) loc0 : f32,
locations : FragLocations,
- builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+ builtins : FragBuiltins,
+ colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
struct FragBuiltins {
@@ -530,10 +664,23 @@
@location(1) loc1 : f32,
@location(2) @interpolate(flat) loc2 : vec4<u32>,
};
+struct FragColors {
+ @color(3) col3 : vec4<f32>,
+ @color(1) col1 : vec4<u32>,
+ @color(2) col2 : vec4<i32>,
+};
)";
auto* expect = R"(
+enable chromium_experimental_framebuffer_fetch;
+
struct tint_symbol_1 {
+ @color(1)
+ col1 : vec4<u32>,
+ @color(2)
+ col2 : vec4<i32>,
+ @color(3)
+ col3 : vec4<f32>,
@location(0)
loc0 : f32,
@location(1)
@@ -544,13 +691,13 @@
coord : vec4<f32>,
}
-fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins) {
- var col : f32 = ((builtins.coord.x * locations.loc1) + loc0);
+fn frag_main_inner(loc0 : f32, locations : FragLocations, builtins : FragBuiltins, colors : FragColors) {
+ var col : f32 = (((builtins.coord.x * locations.loc1) + loc0) + colors.col3.g);
}
@fragment
fn frag_main(tint_symbol : tint_symbol_1) {
- frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(tint_symbol.coord));
+ frag_main_inner(tint_symbol.loc0, FragLocations(tint_symbol.loc1, tint_symbol.loc2), FragBuiltins(tint_symbol.coord), FragColors(tint_symbol.col3, tint_symbol.col1, tint_symbol.col2));
}
struct FragBuiltins {
@@ -561,6 +708,12 @@
loc1 : f32,
loc2 : vec4<u32>,
}
+
+struct FragColors {
+ col3 : vec4<f32>,
+ col1 : vec4<u32>,
+ col2 : vec4<i32>,
+}
)";
DataMap data;
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
index 0ebf6fa..b3e4aa6 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
@@ -793,7 +793,7 @@
LocationInfo info;
info.expr = [this, func_var] { return b.Expr(func_var); };
- auto* sem = src.Sem().Get<sem::Parameter>(param);
+ auto* sem = src.Sem().Get(param);
info.type = sem->Type();
if (TINT_UNLIKELY(!sem->Attributes().location.has_value())) {
diff --git a/src/tint/lang/wgsl/inspector/entry_point.cc b/src/tint/lang/wgsl/inspector/entry_point.cc
index 080f418..a93a83b 100644
--- a/src/tint/lang/wgsl/inspector/entry_point.cc
+++ b/src/tint/lang/wgsl/inspector/entry_point.cc
@@ -30,16 +30,7 @@
namespace tint::inspector {
StageVariable::StageVariable() = default;
-StageVariable::StageVariable(const StageVariable& other)
- : name(other.name),
- variable_name(other.variable_name),
- has_location_attribute(other.has_location_attribute),
- location_attribute(other.location_attribute),
- component_type(other.component_type),
- composition_type(other.composition_type),
- interpolation_type(other.interpolation_type),
- interpolation_sampling(other.interpolation_sampling) {}
-
+StageVariable::StageVariable(const StageVariable& other) = default;
StageVariable::~StageVariable() = default;
EntryPoint::EntryPoint() = default;
diff --git a/src/tint/lang/wgsl/inspector/entry_point.h b/src/tint/lang/wgsl/inspector/entry_point.h
index 1dee5c1..24164d0 100644
--- a/src/tint/lang/wgsl/inspector/entry_point.h
+++ b/src/tint/lang/wgsl/inspector/entry_point.h
@@ -86,11 +86,13 @@
std::string name;
/// Name of the variable itself. (e.g. 'var')
std::string variable_name;
- /// Is location attribute present
- bool has_location_attribute = false;
- /// Value of the location attribute, only valid if #has_location_attribute is
- /// true.
- uint32_t location_attribute;
+ /// Attributes applied to the variable
+ struct {
+ /// Value of the location attribute, if set.
+ std::optional<uint32_t> location;
+ /// Value of the color attribute, if set.
+ std::optional<uint32_t> color;
+ } attributes;
/// Scalar type that the variable is composed of.
ComponentType component_type = ComponentType::kUnknown;
/// How the scalars are composed for the variable.
diff --git a/src/tint/lang/wgsl/inspector/inspector.cc b/src/tint/lang/wgsl/inspector/inspector.cc
index 7f49f66..c07001a 100644
--- a/src/tint/lang/wgsl/inspector/inspector.cc
+++ b/src/tint/lang/wgsl/inspector/inspector.cc
@@ -178,7 +178,7 @@
AddEntryPointInOutVariables(param->Declaration()->name->symbol.Name(),
param->Declaration()->name->symbol.Name(), param->Type(),
param->Declaration()->attributes, param->Attributes().location,
- entry_point.input_variables);
+ param->Attributes().color, entry_point.input_variables);
entry_point.input_position_used |= ContainsBuiltin(
core::BuiltinValue::kPosition, param->Type(), param->Declaration()->attributes);
@@ -198,7 +198,8 @@
if (!sem->ReturnType()->Is<core::type::Void>()) {
AddEntryPointInOutVariables("<retval>", "", sem->ReturnType(), func->return_type_attributes,
- sem->ReturnLocation(), entry_point.output_variables);
+ sem->ReturnLocation(), /* @color */ std::nullopt,
+ entry_point.output_variables);
entry_point.output_sample_mask_used = ContainsBuiltin(
core::BuiltinValue::kSampleMask, sem->ReturnType(), func->return_type_attributes);
@@ -581,6 +582,7 @@
const core::type::Type* type,
VectorRef<const ast::Attribute*> attributes,
std::optional<uint32_t> location,
+ std::optional<uint32_t> color,
std::vector<StageVariable>& variables) const {
// Skip builtins.
if (ast::HasAttribute<ast::BuiltinAttribute>(attributes)) {
@@ -594,7 +596,8 @@
for (auto* member : struct_ty->Members()) {
AddEntryPointInOutVariables(name + "." + member->Name().Name(), member->Name().Name(),
member->Type(), member->Declaration()->attributes,
- member->Attributes().location, variables);
+ member->Attributes().location, member->Attributes().color,
+ variables);
}
return;
}
@@ -607,9 +610,8 @@
std::tie(stage_variable.component_type, stage_variable.composition_type) =
CalculateComponentAndComposition(type);
- TINT_ASSERT(location.has_value());
- stage_variable.has_location_attribute = true;
- stage_variable.location_attribute = location.value();
+ stage_variable.attributes.location = location;
+ stage_variable.attributes.color = color;
std::tie(stage_variable.interpolation_type, stage_variable.interpolation_sampling) =
CalculateInterpolationData(type, attributes);
diff --git a/src/tint/lang/wgsl/inspector/inspector.h b/src/tint/lang/wgsl/inspector/inspector.h
index 1af117d..14c5ef6 100644
--- a/src/tint/lang/wgsl/inspector/inspector.h
+++ b/src/tint/lang/wgsl/inspector/inspector.h
@@ -174,13 +174,15 @@
/// @param variable_name the name of the variable being added
/// @param type the type of the variable
/// @param attributes the variable attributes
- /// @param location the location value if provided
+ /// @param location the location attribute value if provided
+ /// @param color the color attribute value if provided
/// @param variables the list to add the variables to
void AddEntryPointInOutVariables(std::string name,
std::string variable_name,
const core::type::Type* type,
VectorRef<const ast::Attribute*> attributes,
std::optional<uint32_t> location,
+ std::optional<uint32_t> color,
std::vector<StageVariable>& variables) const;
/// Recursively determine if the type contains builtin.
diff --git a/src/tint/lang/wgsl/inspector/inspector_test.cc b/src/tint/lang/wgsl/inspector/inspector_test.cc
index 0eb5c8e..c33f950 100644
--- a/src/tint/lang/wgsl/inspector/inspector_test.cc
+++ b/src/tint/lang/wgsl/inspector/inspector_test.cc
@@ -443,15 +443,13 @@
ASSERT_EQ(1u, result[0].input_variables.size());
EXPECT_EQ("in_var", result[0].input_variables[0].name);
EXPECT_EQ("in_var", result[0].input_variables[0].variable_name);
- EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
EXPECT_EQ(component, result[0].input_variables[0].component_type);
ASSERT_EQ(1u, result[0].output_variables.size());
EXPECT_EQ("<retval>", result[0].output_variables[0].name);
EXPECT_EQ("", result[0].output_variables[0].variable_name);
- EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
EXPECT_EQ(component, result[0].output_variables[0].component_type);
}
INSTANTIATE_TEST_SUITE_P(InspectorGetEntryPointTest,
@@ -466,6 +464,8 @@
CompositionType::kVec4)));
TEST_F(InspectorGetEntryPointTest, MultipleInOutVariables) {
+ Enable(wgsl::Extension::kChromiumExperimentalFramebufferFetch);
+
auto* in_var0 = Param("in_var0", ty.u32(),
Vector{
Location(0_u),
@@ -478,8 +478,7 @@
});
auto* in_var4 = Param("in_var4", ty.u32(),
Vector{
- Location(4_u),
- Flat(),
+ Color(2_u),
});
Func("foo", Vector{in_var0, in_var1, in_var4}, ty.u32(),
Vector{
@@ -501,28 +500,28 @@
ASSERT_EQ(3u, result[0].input_variables.size());
EXPECT_EQ("in_var0", result[0].input_variables[0].name);
EXPECT_EQ("in_var0", result[0].input_variables[0].variable_name);
- EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
+ EXPECT_EQ(std::nullopt, result[0].input_variables[0].attributes.color);
EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[0].interpolation_type);
EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
EXPECT_EQ("in_var1", result[0].input_variables[1].name);
EXPECT_EQ("in_var1", result[0].input_variables[1].variable_name);
- EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
- EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
+ EXPECT_EQ(1u, result[0].input_variables[1].attributes.location);
+ EXPECT_EQ(std::nullopt, result[0].input_variables[1].attributes.color);
EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[1].interpolation_type);
EXPECT_EQ(ComponentType::kU32, result[0].input_variables[1].component_type);
EXPECT_EQ("in_var4", result[0].input_variables[2].name);
EXPECT_EQ("in_var4", result[0].input_variables[2].variable_name);
- EXPECT_TRUE(result[0].input_variables[2].has_location_attribute);
- EXPECT_EQ(4u, result[0].input_variables[2].location_attribute);
+ EXPECT_EQ(std::nullopt, result[0].input_variables[2].attributes.location);
+ EXPECT_EQ(2u, result[0].input_variables[2].attributes.color);
EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[2].interpolation_type);
EXPECT_EQ(ComponentType::kU32, result[0].input_variables[2].component_type);
ASSERT_EQ(1u, result[0].output_variables.size());
EXPECT_EQ("<retval>", result[0].output_variables[0].name);
EXPECT_EQ("", result[0].output_variables[0].variable_name);
- EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
+ EXPECT_EQ(std::nullopt, result[0].output_variables[0].attributes.color);
EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
}
@@ -569,31 +568,27 @@
ASSERT_EQ(1u, result[0].input_variables.size());
EXPECT_EQ("in_var_foo", result[0].input_variables[0].name);
EXPECT_EQ("in_var_foo", result[0].input_variables[0].variable_name);
- EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[0].interpolation_type);
EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
ASSERT_EQ(1u, result[0].output_variables.size());
EXPECT_EQ("<retval>", result[0].output_variables[0].name);
EXPECT_EQ("", result[0].output_variables[0].variable_name);
- EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
ASSERT_EQ(1u, result[1].input_variables.size());
EXPECT_EQ("in_var_bar", result[1].input_variables[0].name);
EXPECT_EQ("in_var_bar", result[1].input_variables[0].variable_name);
- EXPECT_TRUE(result[1].input_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[1].input_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[1].input_variables[0].attributes.location);
EXPECT_EQ(InterpolationType::kFlat, result[1].input_variables[0].interpolation_type);
EXPECT_EQ(ComponentType::kU32, result[1].input_variables[0].component_type);
ASSERT_EQ(1u, result[1].output_variables.size());
EXPECT_EQ("<retval>", result[1].output_variables[0].name);
EXPECT_EQ("", result[1].output_variables[0].variable_name);
- EXPECT_TRUE(result[1].output_variables[0].has_location_attribute);
- EXPECT_EQ(1u, result[1].output_variables[0].location_attribute);
+ EXPECT_EQ(1u, result[1].output_variables[0].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[1].output_variables[0].component_type);
}
@@ -626,8 +621,7 @@
ASSERT_EQ(1u, result[0].input_variables.size());
EXPECT_EQ("in_var1", result[0].input_variables[0].name);
EXPECT_EQ("in_var1", result[0].input_variables[0].variable_name);
- EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
EXPECT_EQ(ComponentType::kF32, result[0].input_variables[0].component_type);
ASSERT_EQ(0u, result[0].output_variables.size());
@@ -659,25 +653,21 @@
ASSERT_EQ(2u, result[0].input_variables.size());
EXPECT_EQ("param.a", result[0].input_variables[0].name);
EXPECT_EQ("a", result[0].input_variables[0].variable_name);
- EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
EXPECT_EQ("param.b", result[0].input_variables[1].name);
EXPECT_EQ("b", result[0].input_variables[1].variable_name);
- EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
- EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
+ EXPECT_EQ(1u, result[0].input_variables[1].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].input_variables[1].component_type);
ASSERT_EQ(2u, result[0].output_variables.size());
EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
EXPECT_EQ("a", result[0].output_variables[0].variable_name);
- EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
EXPECT_EQ("b", result[0].output_variables[1].variable_name);
- EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
- EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
+ EXPECT_EQ(1u, result[0].output_variables[1].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].output_variables[1].component_type);
}
@@ -709,25 +699,21 @@
ASSERT_EQ(2u, result[0].output_variables.size());
EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
EXPECT_EQ("a", result[0].output_variables[0].variable_name);
- EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
EXPECT_EQ("b", result[0].output_variables[1].variable_name);
- EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
- EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
+ EXPECT_EQ(1u, result[0].output_variables[1].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].output_variables[1].component_type);
ASSERT_EQ(2u, result[1].input_variables.size());
EXPECT_EQ("param.a", result[1].input_variables[0].name);
EXPECT_EQ("a", result[1].input_variables[0].variable_name);
- EXPECT_TRUE(result[1].input_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[1].input_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[1].input_variables[0].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[1].input_variables[0].component_type);
EXPECT_EQ("param.b", result[1].input_variables[1].name);
EXPECT_EQ("b", result[1].input_variables[1].variable_name);
- EXPECT_TRUE(result[1].input_variables[1].has_location_attribute);
- EXPECT_EQ(1u, result[1].input_variables[1].location_attribute);
+ EXPECT_EQ(1u, result[1].input_variables[1].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[1].input_variables[1].component_type);
ASSERT_EQ(0u, result[1].output_variables.size());
@@ -765,40 +751,33 @@
ASSERT_EQ(5u, result[0].input_variables.size());
EXPECT_EQ("param_a.a", result[0].input_variables[0].name);
EXPECT_EQ("a", result[0].input_variables[0].variable_name);
- EXPECT_TRUE(result[0].input_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].input_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
EXPECT_EQ("param_a.b", result[0].input_variables[1].name);
EXPECT_EQ("b", result[0].input_variables[1].variable_name);
- EXPECT_TRUE(result[0].input_variables[1].has_location_attribute);
- EXPECT_EQ(1u, result[0].input_variables[1].location_attribute);
+ EXPECT_EQ(1u, result[0].input_variables[1].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].input_variables[1].component_type);
EXPECT_EQ("param_b.a", result[0].input_variables[2].name);
EXPECT_EQ("a", result[0].input_variables[2].variable_name);
- EXPECT_TRUE(result[0].input_variables[2].has_location_attribute);
- EXPECT_EQ(2u, result[0].input_variables[2].location_attribute);
+ EXPECT_EQ(2u, result[0].input_variables[2].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].input_variables[2].component_type);
EXPECT_EQ("param_c", result[0].input_variables[3].name);
EXPECT_EQ("param_c", result[0].input_variables[3].variable_name);
- EXPECT_TRUE(result[0].input_variables[3].has_location_attribute);
- EXPECT_EQ(3u, result[0].input_variables[3].location_attribute);
+ EXPECT_EQ(3u, result[0].input_variables[3].attributes.location);
EXPECT_EQ(ComponentType::kF32, result[0].input_variables[3].component_type);
EXPECT_EQ("param_d", result[0].input_variables[4].name);
EXPECT_EQ("param_d", result[0].input_variables[4].variable_name);
- EXPECT_TRUE(result[0].input_variables[4].has_location_attribute);
- EXPECT_EQ(4u, result[0].input_variables[4].location_attribute);
+ EXPECT_EQ(4u, result[0].input_variables[4].attributes.location);
EXPECT_EQ(ComponentType::kF32, result[0].input_variables[4].component_type);
ASSERT_EQ(2u, result[0].output_variables.size());
EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
EXPECT_EQ("a", result[0].output_variables[0].variable_name);
- EXPECT_TRUE(result[0].output_variables[0].has_location_attribute);
- EXPECT_EQ(0u, result[0].output_variables[0].location_attribute);
+ EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
EXPECT_EQ("b", result[0].output_variables[1].variable_name);
- EXPECT_TRUE(result[0].output_variables[1].has_location_attribute);
- EXPECT_EQ(1u, result[0].output_variables[1].location_attribute);
+ EXPECT_EQ(1u, result[0].output_variables[1].attributes.location);
EXPECT_EQ(ComponentType::kU32, result[0].output_variables[1].component_type);
}
diff --git a/src/tint/lang/wgsl/reader/parser/error_resync_test.cc b/src/tint/lang/wgsl/reader/parser/error_resync_test.cc
index 0a368a9..a38fd87 100644
--- a/src/tint/lang/wgsl/reader/parser/error_resync_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/error_resync_test.cc
@@ -67,7 +67,7 @@
^
test.wgsl:4:2 error: expected attribute
-Possible values: 'align', 'binding', 'builtin', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size'
+Possible values: 'align', 'binding', 'builtin', 'color', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size'
@_ fn -> {}
^
)");
@@ -135,7 +135,7 @@
^^^^
test.wgsl:7:6 error: expected attribute
-Possible values: 'align', 'binding', 'builtin', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size'
+Possible values: 'align', 'binding', 'builtin', 'color', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size'
@- x : i32,
^
)");
diff --git a/src/tint/lang/wgsl/reader/parser/function_attribute_list_test.cc b/src/tint/lang/wgsl/reader/parser/function_attribute_list_test.cc
index 259f1a1..4fae501 100644
--- a/src/tint/lang/wgsl/reader/parser/function_attribute_list_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/function_attribute_list_test.cc
@@ -66,7 +66,7 @@
EXPECT_TRUE(attrs.value.IsEmpty());
EXPECT_EQ(p->error(), R"(1:2: expected attribute
Did you mean 'invariant'?
-Possible values: 'align', 'binding', 'builtin', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size')");
+Possible values: 'align', 'binding', 'builtin', 'color', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size')");
}
} // namespace
diff --git a/src/tint/lang/wgsl/reader/parser/parser.cc b/src/tint/lang/wgsl/reader/parser/parser.cc
index 6a10c72..8cc0bba 100644
--- a/src/tint/lang/wgsl/reader/parser/parser.cc
+++ b/src/tint/lang/wgsl/reader/parser/parser.cc
@@ -3088,6 +3088,8 @@
return create<ast::BindingAttribute>(t.source(), args[0]);
case core::Attribute::kBuiltin:
return create<ast::BuiltinAttribute>(t.source(), args[0]);
+ case core::Attribute::kColor:
+ return create<ast::ColorAttribute>(t.source(), args[0]);
case core::Attribute::kCompute:
return create<ast::StageAttribute>(t.source(), ast::PipelineStage::kCompute);
case core::Attribute::kFragment:
diff --git a/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc b/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc
index f10f725..417ab1f 100644
--- a/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc
+++ b/src/tint/lang/wgsl/reader/parser/variable_attribute_list_test.cc
@@ -64,7 +64,7 @@
EXPECT_TRUE(attrs.value.IsEmpty());
EXPECT_EQ(p->error(), R"(1:2: expected attribute
Did you mean 'invariant'?
-Possible values: 'align', 'binding', 'builtin', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size')");
+Possible values: 'align', 'binding', 'builtin', 'color', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size')");
}
} // namespace
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
index 5e99225..e2c1c43 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -459,6 +459,9 @@
if (param_sem->Attributes().location.has_value()) {
param->SetLocation(param_sem->Attributes().location.value(), interpolation);
}
+ if (param_sem->Attributes().color.has_value()) {
+ TINT_UNIMPLEMENTED() << "IR does not currently support texel fetch extension";
+ }
}
scopes_.Set(p->name->symbol, param);
diff --git a/src/tint/lang/wgsl/resolver/BUILD.bazel b/src/tint/lang/wgsl/resolver/BUILD.bazel
index 1d19962..4187995 100644
--- a/src/tint/lang/wgsl/resolver/BUILD.bazel
+++ b/src/tint/lang/wgsl/resolver/BUILD.bazel
@@ -118,6 +118,7 @@
"evaluation_stage_test.cc",
"expression_kind_test.cc",
"f16_extension_test.cc",
+ "framebuffer_fetch_extension_test.cc",
"function_validation_test.cc",
"host_shareable_validation_test.cc",
"increment_decrement_validation_test.cc",
diff --git a/src/tint/lang/wgsl/resolver/BUILD.cmake b/src/tint/lang/wgsl/resolver/BUILD.cmake
index 4ffe57c..745d508 100644
--- a/src/tint/lang/wgsl/resolver/BUILD.cmake
+++ b/src/tint/lang/wgsl/resolver/BUILD.cmake
@@ -116,6 +116,7 @@
lang/wgsl/resolver/evaluation_stage_test.cc
lang/wgsl/resolver/expression_kind_test.cc
lang/wgsl/resolver/f16_extension_test.cc
+ lang/wgsl/resolver/framebuffer_fetch_extension_test.cc
lang/wgsl/resolver/function_validation_test.cc
lang/wgsl/resolver/host_shareable_validation_test.cc
lang/wgsl/resolver/increment_decrement_validation_test.cc
diff --git a/src/tint/lang/wgsl/resolver/BUILD.gn b/src/tint/lang/wgsl/resolver/BUILD.gn
index 28b1f3f..f5e4097 100644
--- a/src/tint/lang/wgsl/resolver/BUILD.gn
+++ b/src/tint/lang/wgsl/resolver/BUILD.gn
@@ -118,6 +118,7 @@
"evaluation_stage_test.cc",
"expression_kind_test.cc",
"f16_extension_test.cc",
+ "framebuffer_fetch_extension_test.cc",
"function_validation_test.cc",
"host_shareable_validation_test.cc",
"increment_decrement_validation_test.cc",
diff --git a/src/tint/lang/wgsl/resolver/attribute_validation_test.cc b/src/tint/lang/wgsl/resolver/attribute_validation_test.cc
index d88daa5..92151d0 100644
--- a/src/tint/lang/wgsl/resolver/attribute_validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/attribute_validation_test.cc
@@ -60,6 +60,7 @@
kAlign,
kBinding,
kBuiltinPosition,
+ kColor,
kDiagnostic,
kGroup,
kId,
@@ -82,6 +83,8 @@
return o << "@binding";
case AttributeKind::kBuiltinPosition:
return o << "@builtin(position)";
+ case AttributeKind::kColor:
+ return o << "@color";
case AttributeKind::kDiagnostic:
return o << "@diagnostic";
case AttributeKind::kGroup:
@@ -144,6 +147,10 @@
"1:2 error: @builtin is not valid for " + thing,
},
TestParams{
+ {AttributeKind::kColor},
+ "1:2 error: @color is not valid for " + thing,
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
Pass,
},
@@ -215,6 +222,8 @@
return builder.Binding(source, 1_a);
case AttributeKind::kBuiltinPosition:
return builder.Builtin(source, core::BuiltinValue::kPosition);
+ case AttributeKind::kColor:
+ return builder.Color(source, 2_a);
case AttributeKind::kDiagnostic:
return builder.DiagnosticAttribute(source, wgsl::DiagnosticSeverity::kInfo, "chromium",
"unreachable_code");
@@ -250,8 +259,15 @@
struct TestWithParams : ResolverTestWithParam<TestParams> {
void EnableExtensionIfNecessary(AttributeKind attribute) {
- if (attribute == AttributeKind::kIndex) {
- Enable(wgsl::Extension::kChromiumInternalDualSourceBlending);
+ switch (attribute) {
+ case AttributeKind::kColor:
+ Enable(wgsl::Extension::kChromiumExperimentalFramebufferFetch);
+ break;
+ case AttributeKind::kIndex:
+ Enable(wgsl::Extension::kChromiumInternalDualSourceBlending);
+ break;
+ default:
+ break;
}
}
@@ -310,6 +326,10 @@
R"(1:2 error: @builtin is not valid for functions)",
},
TestParams{
+ {AttributeKind::kColor},
+ R"(1:2 error: @color is not valid for functions)",
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
Pass,
},
@@ -390,6 +410,10 @@
R"(1:2 error: @builtin is not valid for functions)",
},
TestParams{
+ {AttributeKind::kColor},
+ R"(1:2 error: @color is not valid for functions)",
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
Pass,
},
@@ -477,6 +501,10 @@
R"(1:2 error: @builtin is not valid for non-entry point function parameters)",
},
TestParams{
+ {AttributeKind::kColor},
+ R"(1:2 error: @color is not valid for function parameters)",
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for function parameters)",
},
@@ -558,6 +586,10 @@
R"(1:2 error: @builtin is not valid for non-entry point function return types)",
},
TestParams{
+ {AttributeKind::kColor},
+ R"(1:2 error: @color is not valid for non-entry point function return types)",
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for non-entry point function return types)",
},
@@ -644,6 +676,10 @@
R"(1:2 error: @builtin(position) cannot be used for compute shader input)",
},
TestParams{
+ {AttributeKind::kColor},
+ R"(1:2 error: @color can only be used for fragment shader input)",
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for function parameters)",
},
@@ -724,6 +760,15 @@
Pass,
},
TestParams{
+ {AttributeKind::kColor},
+ Pass,
+ },
+ TestParams{
+ {AttributeKind::kColor, AttributeKind::kLocation},
+ R"(3:4 error: multiple entry point IO attributes
+1:2 note: previously consumed @color)",
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for function parameters)",
},
@@ -823,6 +868,10 @@
R"(1:2 error: @builtin(position) cannot be used for vertex shader input)",
},
TestParams{
+ {AttributeKind::kColor},
+ R"(1:2 error: @color can only be used for fragment shader input)",
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for function parameters)",
},
@@ -924,6 +973,10 @@
R"(1:2 error: @builtin(position) cannot be used for compute shader output)",
},
TestParams{
+ {AttributeKind::kColor},
+ R"(1:2 error: @color is not valid for entry point return types)",
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for entry point return types)",
},
@@ -1006,6 +1059,10 @@
R"(1:2 error: @builtin(position) cannot be used for fragment shader output)",
},
TestParams{
+ {AttributeKind::kColor},
+ R"(1:2 error: @color is not valid for entry point return types)",
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for entry point return types)",
},
@@ -1114,6 +1171,10 @@
Pass,
},
TestParams{
+ {AttributeKind::kColor},
+ R"(1:2 error: @color is not valid for entry point return types)",
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for entry point return types)",
},
@@ -1239,6 +1300,10 @@
R"(1:2 error: @diagnostic is not valid for struct declarations)",
},
TestParams{
+ {AttributeKind::kColor},
+ R"(1:2 error: @color is not valid for struct declarations)",
+ },
+ TestParams{
{AttributeKind::kGroup},
R"(1:2 error: @group is not valid for struct declarations)",
},
@@ -1314,6 +1379,10 @@
Pass,
},
TestParams{
+ {AttributeKind::kColor},
+ Pass,
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for struct members)",
},
diff --git a/src/tint/lang/wgsl/resolver/dependency_graph.cc b/src/tint/lang/wgsl/resolver/dependency_graph.cc
index dda9251..bb07be0 100644
--- a/src/tint/lang/wgsl/resolver/dependency_graph.cc
+++ b/src/tint/lang/wgsl/resolver/dependency_graph.cc
@@ -40,6 +40,7 @@
#include "src/tint/lang/wgsl/ast/break_if_statement.h"
#include "src/tint/lang/wgsl/ast/break_statement.h"
#include "src/tint/lang/wgsl/ast/call_statement.h"
+#include "src/tint/lang/wgsl/ast/color_attribute.h"
#include "src/tint/lang/wgsl/ast/compound_assignment_statement.h"
#include "src/tint/lang/wgsl/ast/const.h"
#include "src/tint/lang/wgsl/ast/continue_statement.h"
@@ -386,6 +387,7 @@
attr, //
[&](const ast::BindingAttribute* binding) { TraverseExpression(binding->expr); },
[&](const ast::BuiltinAttribute* builtin) { TraverseExpression(builtin->builtin); },
+ [&](const ast::ColorAttribute* color) { TraverseExpression(color->expr); },
[&](const ast::GroupAttribute* group) { TraverseExpression(group->expr); },
[&](const ast::IdAttribute* id) { TraverseExpression(id->expr); },
[&](const ast::IndexAttribute* index) { TraverseExpression(index->expr); },
diff --git a/src/tint/lang/wgsl/resolver/dependency_graph_test.cc b/src/tint/lang/wgsl/resolver/dependency_graph_test.cc
index 05a83d4..360ffda 100644
--- a/src/tint/lang/wgsl/resolver/dependency_graph_test.cc
+++ b/src/tint/lang/wgsl/resolver/dependency_graph_test.cc
@@ -1692,6 +1692,7 @@
Param(Sym(), T,
Vector{
Location(V), // Parameter attributes
+ Color(V),
Builtin(V),
Interpolate(V),
Interpolate(V, V),
diff --git a/src/tint/lang/wgsl/resolver/dual_source_blending_extension_test.cc b/src/tint/lang/wgsl/resolver/dual_source_blending_extension_test.cc
index 9ea0b05..223744a 100644
--- a/src/tint/lang/wgsl/resolver/dual_source_blending_extension_test.cc
+++ b/src/tint/lang/wgsl/resolver/dual_source_blending_extension_test.cc
@@ -190,6 +190,29 @@
EXPECT_EQ(r()->error(), "12:34 error: @index can only be used with @location(0)");
}
+TEST_F(DualSourceBlendingExtensionTests, NoNonZeroCollisionsBetweenInAndOut) {
+ // struct NonZeroLocation {
+ // @location(1) a : vec4<f32>,
+ // };
+ // struct NonZeroIndex {
+ // @location(0) @index(1) a : vec4<f32>,
+ // };
+ // fn X(in : NonZeroLocation) -> NonZeroIndex { return NonZeroIndex(); }
+ // fn Y(in : NonZeroIndex) -> NonZeroLocation { return NonZeroLocation(); }
+ Structure("NonZeroLocation", Vector{
+ Member("a", ty.vec4<f32>(), Vector{Location(1_a)}),
+ });
+ Structure("NonZeroIndex", Vector{
+ Member("a", ty.vec4<f32>(), Vector{Location(0_a), Index(1_a)}),
+ });
+ Func("X", Vector{Param("in", ty("NonZeroLocation"))}, ty("NonZeroIndex"),
+ Vector{Return(Call("NonZeroIndex"))}, Vector{Stage(ast::PipelineStage::kFragment)});
+ Func("Y", Vector{Param("in", ty("NonZeroIndex"))}, ty("NonZeroLocation"),
+ Vector{Return(Call("NonZeroLocation"))}, Vector{Stage(ast::PipelineStage::kFragment)});
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
class DualSourceBlendingExtensionTestWithParams : public ResolverTestWithParam<int> {
public:
DualSourceBlendingExtensionTestWithParams() {
@@ -200,6 +223,12 @@
// Rendering to multiple render targets while using dual source blending should fail.
TEST_P(DualSourceBlendingExtensionTestWithParams,
MultipleRenderTargetsNotAllowed_IndexThenNonZeroLocation) {
+ // struct S {
+ // @location(0) @index(0) a : vec4<f32>,
+ // @location(0) @index(1) b : vec4<f32>,
+ // @location(n) c : vec4<f32>,
+ // };
+ // fn F() -> S { return S(); }
Structure("S",
Vector{
Member("a", ty.vec4<f32>(), Vector{Location(0_a), Index(0_a)}),
@@ -218,6 +247,12 @@
TEST_P(DualSourceBlendingExtensionTestWithParams,
MultipleRenderTargetsNotAllowed_NonZeroLocationThenIndex) {
+ // struct S {
+ // @location(n) a : vec4<f32>,
+ // @location(0) @index(0) b : vec4<f32>,
+ // @location(0) @index(1) c : vec4<f32>,
+ // };
+ // fn F() -> S { return S(); }
Structure("S",
Vector{
Member("a", ty.vec4<f32>(), Vector{Location(Source{{1, 2}}, AInt(GetParam()))}),
diff --git a/src/tint/lang/wgsl/resolver/framebuffer_fetch_extension_test.cc b/src/tint/lang/wgsl/resolver/framebuffer_fetch_extension_test.cc
new file mode 100644
index 0000000..a9c6576
--- /dev/null
+++ b/src/tint/lang/wgsl/resolver/framebuffer_fetch_extension_test.cc
@@ -0,0 +1,256 @@
+// Copyright 2023 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 "src/tint/lang/wgsl/resolver/resolver.h"
+#include "src/tint/lang/wgsl/resolver/resolver_helper_test.h"
+
+#include "gmock/gmock.h"
+
+namespace tint::resolver {
+namespace {
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+using FramebufferFetchExtensionTest = ResolverTest;
+
+TEST_F(FramebufferFetchExtensionTest, ColorParamUsedWithExtension) {
+ // enable chromium_experimental_framebuffer_fetch;
+ // fn f(@color(2) p : vec4<f32>) {}
+
+ Enable(Source{{12, 34}}, wgsl::Extension::kChromiumExperimentalFramebufferFetch);
+
+ auto* ast_param = Param("p", ty.vec4<f32>(), Vector{Color(2_a)});
+ Func("f", Vector{ast_param}, ty.void_(), Empty, Vector{Stage(ast::PipelineStage::kFragment)});
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* sem_param = Sem().Get(ast_param);
+ ASSERT_NE(sem_param, nullptr);
+ EXPECT_EQ(sem_param->Attributes().color, 2u);
+}
+
+TEST_F(FramebufferFetchExtensionTest, ColorParamUsedWithoutExtension) {
+ // enable chromium_experimental_framebuffer_fetch;
+ // struct S {
+ // @color(2) c : vec4<f32>,
+ // }
+
+ Func("f", Vector{Param("p", ty.vec4<f32>(), Vector{Color(Source{{12, 34}}, 2_a)})}, ty.void_(),
+ Empty, Vector{Stage(ast::PipelineStage::kFragment)});
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ R"(12:34 error: use of @color requires enabling extension 'chromium_experimental_framebuffer_fetch')");
+}
+
+TEST_F(FramebufferFetchExtensionTest, ColorMemberUsedWithExtension) {
+ // enable chromium_experimental_framebuffer_fetch;
+ // fn f(@color(2) p : vec4<f32>) {}
+
+ Enable(Source{{12, 34}}, wgsl::Extension::kChromiumExperimentalFramebufferFetch);
+
+ auto* ast_member = Member("c", ty.vec4<f32>(), Vector{Color(2_a)});
+ Structure("S", Vector{ast_member});
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+ auto* sem_member = Sem().Get(ast_member);
+ ASSERT_NE(sem_member, nullptr);
+ EXPECT_EQ(sem_member->Attributes().color, 2u);
+}
+
+TEST_F(FramebufferFetchExtensionTest, ColorMemberUsedWithoutExtension) {
+ // enable chromium_experimental_framebuffer_fetch;
+ // fn f(@color(2) p : vec4<f32>) {}
+
+ Structure("S", Vector{Member("c", ty.vec4<f32>(), Vector{Color(Source{{12, 34}}, 2_a)})});
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ R"(12:34 error: use of @color requires enabling extension 'chromium_experimental_framebuffer_fetch')");
+}
+
+TEST_F(FramebufferFetchExtensionTest, DuplicateColorParams) {
+ // enable chromium_experimental_framebuffer_fetch;
+ // fn f(@color(1) a : vec4<f32>, @color(2) b : vec4<f32>, @color(1) c : vec4<f32>) {}
+
+ Enable(Source{{12, 34}}, wgsl::Extension::kChromiumExperimentalFramebufferFetch);
+
+ Func("f",
+ Vector{
+ Param("a", ty.vec4<f32>(), Vector{Color(1_a)}),
+ Param("b", ty.vec4<f32>(), Vector{Color(2_a)}),
+ Param("c", ty.vec4<f32>(), Vector{Color(Source{{1, 2}}, 1_a)}),
+ },
+ ty.void_(), Empty, Vector{Stage(ast::PipelineStage::kFragment)});
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(), R"(1:2 error: @color(1) appears multiple times)");
+}
+
+TEST_F(FramebufferFetchExtensionTest, DuplicateColorStruct) {
+ // enable chromium_experimental_framebuffer_fetch;
+ // struct S {
+ // @color(1) a : vec4<f32>,
+ // @color(2) b : vec4<f32>,
+ // @color(1) c : vec4<f32>,
+ // }
+ // fn f(s : S) {}
+
+ Enable(Source{{12, 34}}, wgsl::Extension::kChromiumExperimentalFramebufferFetch);
+
+ Structure("S", Vector{
+ Member("a", ty.vec4<f32>(), Vector{Color(1_a)}),
+ Member("b", ty.vec4<f32>(), Vector{Color(2_a)}),
+ Member("c", ty.vec4<f32>(), Vector{Color(Source{{1, 2}}, 1_a)}),
+ });
+
+ Func("f", Vector{Param("s", ty("S"))}, ty.void_(), Empty,
+ Vector{Stage(ast::PipelineStage::kFragment)});
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(), R"(1:2 error: @color(1) appears multiple times)");
+}
+
+TEST_F(FramebufferFetchExtensionTest, DuplicateColorParamAndStruct) {
+ // enable chromium_experimental_framebuffer_fetch;
+ // struct S {
+ // @color(1) b : vec4<f32>,
+ // @color(2) c : vec4<f32>,
+ // }
+ // fn f(@color(2) a : vec4<f32>, s : S, @color(3) d : vec4<f32>) {}
+
+ Enable(Source{{12, 34}}, wgsl::Extension::kChromiumExperimentalFramebufferFetch);
+
+ Structure("S", Vector{
+ Member("b", ty.vec4<f32>(), Vector{Color(1_a)}),
+ Member("c", ty.vec4<f32>(), Vector{Color(Source{{1, 2}}, 2_a)}),
+ });
+
+ Func("f",
+ Vector{
+ Param("a", ty.vec4<f32>(), Vector{Color(2_a)}),
+ Param("s", ty("S")),
+ Param("d", ty.vec4<f32>(), Vector{Color(3_a)}),
+ },
+ ty.void_(), Empty, Vector{Stage(ast::PipelineStage::kFragment)});
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(), R"(1:2 error: @color(2) appears multiple times
+note: while analyzing entry point 'f')");
+}
+
+namespace type_tests {
+struct Case {
+ builder::ast_type_func_ptr type;
+ std::string name;
+ bool pass;
+};
+
+static std::ostream& operator<<(std::ostream& o, const Case& c) {
+ return o << c.name;
+}
+
+template <typename T>
+Case Pass() {
+ return Case{builder::DataType<T>::AST, builder::DataType<T>::Name(), true};
+}
+
+template <typename T>
+Case Fail() {
+ return Case{builder::DataType<T>::AST, builder::DataType<T>::Name(), false};
+}
+
+using FramebufferFetchExtensionTest_Types = ResolverTestWithParam<Case>;
+
+TEST_P(FramebufferFetchExtensionTest_Types, Param) {
+ // enable chromium_experimental_framebuffer_fetch;
+ // fn f(@color(1) a : <type>) {}
+
+ Enable(wgsl::Extension::kChromiumExperimentalFramebufferFetch);
+
+ Func("f",
+ Vector{Param(Source{{12, 34}}, "p", GetParam().type(*this),
+ Vector{Color(Source{{56, 78}}, 2_a)})},
+ ty.void_(), Empty, Vector{Stage(ast::PipelineStage::kFragment)});
+
+ if (GetParam().pass) {
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+ } else {
+ EXPECT_FALSE(r()->Resolve());
+ auto expected =
+ ReplaceAll(R"(12:34 error: cannot apply @color to declaration of type '$TYPE'
+56:78 note: @color must only be applied to declarations of numeric scalar or numeric vector type)",
+ "$TYPE", GetParam().name);
+ EXPECT_EQ(r()->error(), expected);
+ }
+}
+
+TEST_P(FramebufferFetchExtensionTest_Types, Struct) {
+ // struct S {
+ // @color(2) c : <type>,
+ // }
+
+ Enable(wgsl::Extension::kChromiumExperimentalFramebufferFetch);
+
+ Structure("S", Vector{
+ Member(Source{{12, 34}}, "c", GetParam().type(*this),
+ Vector{Color(Source{{56, 78}}, 2_a)}),
+ });
+
+ if (GetParam().pass) {
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+ } else {
+ EXPECT_FALSE(r()->Resolve());
+ auto expected =
+ ReplaceAll(R"(12:34 error: cannot apply @color to declaration of type '$TYPE'
+56:78 note: @color must only be applied to declarations of numeric scalar or numeric vector type)",
+ "$TYPE", GetParam().name);
+ EXPECT_EQ(r()->error(), expected);
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(Valid,
+ FramebufferFetchExtensionTest_Types,
+ testing::Values(Pass<i32>(),
+ Pass<u32>(),
+ Pass<f32>(),
+ Pass<vec2<f32>>(),
+ Pass<vec3<i32>>(),
+ Pass<vec4<u32>>()));
+
+INSTANTIATE_TEST_SUITE_P(Invalid,
+ FramebufferFetchExtensionTest_Types,
+ testing::Values(Fail<bool>(), Fail<array<u32, 4>>()));
+
+} // namespace type_tests
+
+} // namespace
+} // namespace tint::resolver
diff --git a/src/tint/lang/wgsl/resolver/pixel_local_extension_test.cc b/src/tint/lang/wgsl/resolver/pixel_local_extension_test.cc
index c70c6d5..eceb5ae 100644
--- a/src/tint/lang/wgsl/resolver/pixel_local_extension_test.cc
+++ b/src/tint/lang/wgsl/resolver/pixel_local_extension_test.cc
@@ -38,6 +38,20 @@
using ResolverPixelLocalExtensionTest = ResolverTest;
+TEST_F(ResolverPixelLocalExtensionTest, UseWithFramebufferFetch) {
+ // enable chromium_experimental_pixel_local;
+ // enable chromium_experimental_framebuffer_fetch;
+
+ Enable(Source{{12, 34}}, wgsl::Extension::kChromiumExperimentalPixelLocal);
+ Enable(Source{{56, 78}}, wgsl::Extension::kChromiumExperimentalFramebufferFetch);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ R"(12:34 error: extension 'chromium_experimental_pixel_local' cannot be used with extension 'chromium_experimental_framebuffer_fetch'
+56:78 note: 'chromium_experimental_framebuffer_fetch' enabled here)");
+}
+
TEST_F(ResolverPixelLocalExtensionTest, AddressSpaceUsedWithExtension) {
// enable chromium_experimental_pixel_local;
// struct S { a : i32 }
@@ -308,6 +322,7 @@
using ResolverPixelLocalExtensionTest_Types = ResolverTestWithParam<Case>;
TEST_P(ResolverPixelLocalExtensionTest_Types, Direct) {
+ // enable chromium_experimental_pixel_local;
// var<pixel_local> v : <type>;
Enable(wgsl::Extension::kChromiumExperimentalPixelLocal);
@@ -319,6 +334,7 @@
}
TEST_P(ResolverPixelLocalExtensionTest_Types, Struct) {
+ // enable chromium_experimental_pixel_local;
// struct S {
// a : i32,
// m : <type>,
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index d5a0e93..aa54ccf 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -160,6 +160,10 @@
return false;
}
+ if (!validator_.Enables(b.AST().Enables())) {
+ return false;
+ }
+
// Create the semantic module. Don't be tempted to std::move() these, they're used below.
auto* mod = b.create<sem::Module>(dependencies_.ordered_globals, enabled_extensions_);
ApplyDiagnosticSeverities(mod);
@@ -640,6 +644,17 @@
global->Attributes().index = value.Get();
return kSuccess;
},
+ [&](const ast::ColorAttribute* attr) {
+ if (!has_io_address_space) {
+ return kInvalid;
+ }
+ auto value = ColorAttribute(attr);
+ if (!value) {
+ return kErrored;
+ }
+ global->Attributes().color = value.Get();
+ return kSuccess;
+ },
[&](const ast::BuiltinAttribute* attr) {
if (!has_io_address_space) {
return kInvalid;
@@ -723,6 +738,14 @@
sem->Attributes().location = value.Get();
return true;
},
+ [&](const ast::ColorAttribute* attr) {
+ auto value = ColorAttribute(attr);
+ if (TINT_UNLIKELY(!value)) {
+ return false;
+ }
+ sem->Attributes().color = value.Get();
+ return true;
+ },
[&](const ast::BuiltinAttribute* attr) -> bool { return BuiltinAttribute(attr); },
[&](const ast::InvariantAttribute* attr) -> bool {
return InvariantAttribute(attr);
@@ -3709,6 +3732,29 @@
return static_cast<uint32_t>(value);
}
+tint::Result<uint32_t> Resolver::ColorAttribute(const ast::ColorAttribute* attr) {
+ ExprEvalStageConstraint constraint{core::EvaluationStage::kConstant, "@color value"};
+ TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
+
+ auto* materialized = Materialize(ValueExpression(attr->expr));
+ if (!materialized) {
+ return Failure{};
+ }
+
+ if (!materialized->Type()->IsAnyOf<core::type::I32, core::type::U32>()) {
+ AddError("@color must be an i32 or u32 value", attr->source);
+ return Failure{};
+ }
+
+ auto const_value = materialized->ConstantValue();
+ auto value = const_value->ValueAs<AInt>();
+ if (value < 0) {
+ AddError("@color value must be non-negative", attr->source);
+ return Failure{};
+ }
+
+ return static_cast<uint32_t>(value);
+}
tint::Result<uint32_t> Resolver::IndexAttribute(const ast::IndexAttribute* attr) {
ExprEvalStageConstraint constraint{core::EvaluationStage::kConstant, "@index value"};
TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
@@ -4342,6 +4388,14 @@
attributes.index = value.Get();
return true;
},
+ [&](const ast::ColorAttribute* attr) {
+ auto value = ColorAttribute(attr);
+ if (!value) {
+ return false;
+ }
+ attributes.color = value.Get();
+ return true;
+ },
[&](const ast::BuiltinAttribute* attr) {
auto value = BuiltinAttribute(attr);
if (!value) {
diff --git a/src/tint/lang/wgsl/resolver/resolver.h b/src/tint/lang/wgsl/resolver/resolver.h
index c489465..344c5af 100644
--- a/src/tint/lang/wgsl/resolver/resolver.h
+++ b/src/tint/lang/wgsl/resolver/resolver.h
@@ -419,6 +419,10 @@
/// @returns the location value on success.
tint::Result<uint32_t> LocationAttribute(const ast::LocationAttribute* attr);
+ /// Resolves the `@color` attribute @p attr
+ /// @returns the color value on success.
+ tint::Result<uint32_t> ColorAttribute(const ast::ColorAttribute* attr);
+
/// Resolves the `@index` attribute @p attr
/// @returns the index value on success.
tint::Result<uint32_t> IndexAttribute(const ast::IndexAttribute* attr);
diff --git a/src/tint/lang/wgsl/resolver/uniformity.cc b/src/tint/lang/wgsl/resolver/uniformity.cc
index 08259f7..0cea0be 100644
--- a/src/tint/lang/wgsl/resolver/uniformity.cc
+++ b/src/tint/lang/wgsl/resolver/uniformity.cc
@@ -210,7 +210,7 @@
for (size_t i = 0; i < func->params.Length(); i++) {
auto* param = func->params[i];
auto param_name = param->name->symbol.Name();
- auto* sem = b.Sem().Get<sem::Parameter>(param);
+ auto* sem = b.Sem().Get(param);
parameters[i].sem = sem;
parameters[i].value = CreateNode({"param_", param_name});
@@ -543,7 +543,7 @@
// we do not skip the `i==j` case.
for (size_t j = 0; j < func->params.Length(); j++) {
auto tag = get_param_tag(reachable, j);
- auto* source_param = sem_.Get<sem::Parameter>(func->params[j]);
+ auto* source_param = sem_.Get(func->params[j]);
if (tag == ParameterTag::ParameterContentsRequiredToBeUniform) {
param_info.ptr_output_source_param_contents.Push(source_param);
} else if (tag == ParameterTag::ParameterValueRequiredToBeUniform) {
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index 67ba42d..05b2a44 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -291,6 +291,40 @@
return nullptr;
}
+bool Validator::Enables(VectorRef<const ast::Enable*> enables) const {
+ auto source_of = [&](wgsl::Extension ext) {
+ for (auto* enable : enables) {
+ for (auto* extension : enable->extensions) {
+ if (extension->name == ext) {
+ return extension->source;
+ }
+ }
+ }
+ return Source{};
+ };
+
+ // List of extensions that cannot be used together.
+ std::pair<wgsl::Extension, wgsl::Extension> incompatible[] = {
+ {
+ wgsl::Extension::kChromiumExperimentalPixelLocal,
+ wgsl::Extension::kChromiumExperimentalFramebufferFetch,
+ },
+ };
+
+ for (auto pair : incompatible) {
+ if (enabled_extensions_.Contains(pair.first) && enabled_extensions_.Contains(pair.second)) {
+ std::string a{ToString(pair.first)};
+ std::string b{ToString(pair.second)};
+ AddError("extension '" + a + "' cannot be used with extension '" + b + "'",
+ source_of(pair.first));
+ AddNote("'" + b + "' enabled here", source_of(pair.second));
+ return false;
+ }
+ }
+
+ return true;
+}
+
bool Validator::Atomic(const ast::TemplatedIdentifier* a, const core::type::Atomic* s) const {
// https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
// T must be either u32 or i32.
@@ -1116,6 +1150,7 @@
Hashset<std::pair<uint32_t, uint32_t>, 8> locations_and_indices;
const ast::LocationAttribute* first_nonzero_location = nullptr;
const ast::IndexAttribute* first_nonzero_index = nullptr;
+ Hashset<uint32_t, 4> colors;
enum class ParamOrRetType {
kParameter,
kReturnType,
@@ -1127,11 +1162,13 @@
ParamOrRetType param_or_ret,
bool is_struct_member,
std::optional<uint32_t> location,
- std::optional<uint32_t> index) {
+ std::optional<uint32_t> index,
+ std::optional<uint32_t> color) {
// Scan attributes for pipeline IO attributes.
// Check for overlap with attributes that have been seen previously.
const ast::Attribute* pipeline_io_attribute = nullptr;
const ast::LocationAttribute* location_attribute = nullptr;
+ const ast::ColorAttribute* color_attribute = nullptr;
const ast::IndexAttribute* index_attribute = nullptr;
const ast::InterpolateAttribute* interpolate_attribute = nullptr;
const ast::InvariantAttribute* invariant_attribute = nullptr;
@@ -1184,8 +1221,33 @@
},
[&](const ast::IndexAttribute* index_attr) {
index_attribute = index_attr;
+
+ if (TINT_UNLIKELY(!index.has_value())) {
+ TINT_ICE() << "@index has no value";
+ return false;
+ }
+
return IndexAttribute(index_attr, stage);
},
+ [&](const ast::ColorAttribute* col_attr) {
+ color_attribute = col_attr;
+ if (pipeline_io_attribute) {
+ AddError("multiple entry point IO attributes", attr->source);
+ AddNote("previously consumed " + AttrToStr(pipeline_io_attribute),
+ pipeline_io_attribute->source);
+ return false;
+ }
+ pipeline_io_attribute = attr;
+
+ bool is_input = param_or_ret == ParamOrRetType::kParameter;
+
+ if (TINT_UNLIKELY(!color.has_value())) {
+ TINT_ICE() << "@color has no value";
+ return false;
+ }
+
+ return ColorAttribute(col_attr, ty, stage, source, is_input);
+ },
[&](const ast::InterpolateAttribute* interpolate) {
interpolate_attribute = interpolate;
return InterpolateAttribute(interpolate, ty, stage);
@@ -1276,6 +1338,13 @@
}
}
+ if (color_attribute && !colors.Add(color.value())) {
+ StringStream err;
+ err << "@color(" << color.value() << ") appears multiple times";
+ AddError(err.str(), color_attribute->source);
+ return false;
+ }
+
if (interpolate_attribute) {
if (!pipeline_io_attribute ||
!pipeline_io_attribute->Is<ast::LocationAttribute>()) {
@@ -1304,39 +1373,39 @@
};
// Outer lambda for validating the entry point attributes for a type.
- auto validate_entry_point_attributes = [&](VectorRef<const ast::Attribute*> attrs,
- const core::type::Type* ty, Source source,
- ParamOrRetType param_or_ret,
- std::optional<uint32_t> location,
- std::optional<uint32_t> index) {
- if (!validate_entry_point_attributes_inner(attrs, ty, source, param_or_ret,
- /*is_struct_member*/ false, location, index)) {
- return false;
- }
+ auto validate_entry_point_attributes =
+ [&](VectorRef<const ast::Attribute*> attrs, const core::type::Type* ty, Source source,
+ ParamOrRetType param_or_ret, std::optional<uint32_t> location,
+ std::optional<uint32_t> index, std::optional<uint32_t> color) {
+ if (!validate_entry_point_attributes_inner(attrs, ty, source, param_or_ret,
+ /*is_struct_member*/ false, location, index,
+ color)) {
+ return false;
+ }
- if (auto* str = ty->As<sem::Struct>()) {
- for (auto* member : str->Members()) {
- if (!validate_entry_point_attributes_inner(
- member->Declaration()->attributes, member->Type(),
- member->Declaration()->source, param_or_ret,
- /*is_struct_member*/ true, member->Attributes().location,
- member->Attributes().index)) {
- AddNote("while analyzing entry point '" + decl->name->symbol.Name() + "'",
- decl->source);
- return false;
+ if (auto* str = ty->As<sem::Struct>()) {
+ for (auto* member : str->Members()) {
+ if (!validate_entry_point_attributes_inner(
+ member->Declaration()->attributes, member->Type(),
+ member->Declaration()->source, param_or_ret,
+ /*is_struct_member*/ true, member->Attributes().location,
+ member->Attributes().index, member->Attributes().color)) {
+ AddNote("while analyzing entry point '" + decl->name->symbol.Name() + "'",
+ decl->source);
+ return false;
+ }
}
}
- }
- return true;
- };
+ return true;
+ };
for (auto* param : func->Parameters()) {
auto* param_decl = param->Declaration();
auto& attrs = param->Attributes();
if (!validate_entry_point_attributes(param_decl->attributes, param->Type(),
param_decl->source, ParamOrRetType::kParameter,
- attrs.location, attrs.index)) {
+ attrs.location, attrs.index, attrs.color)) {
return false;
}
}
@@ -1345,11 +1414,14 @@
// should be validated independently from those used in parameters.
builtins.Clear();
locations_and_indices.Clear();
+ first_nonzero_location = nullptr;
+ first_nonzero_index = nullptr;
if (!func->ReturnType()->Is<core::type::Void>()) {
if (!validate_entry_point_attributes(decl->return_type_attributes, func->ReturnType(),
decl->source, ParamOrRetType::kReturnType,
- func->ReturnLocation(), func->ReturnIndex())) {
+ func->ReturnLocation(), func->ReturnIndex(),
+ /* color */ std::nullopt)) {
return false;
}
}
@@ -2154,6 +2226,7 @@
}
Hashset<std::pair<uint32_t, uint32_t>, 8> locations_and_indices;
+ Hashset<uint32_t, 4> colors;
for (auto* member : str->Members()) {
if (auto* r = member->Type()->As<sem::Array>()) {
if (r->Count()->Is<core::type::RuntimeArrayCount>()) {
@@ -2178,6 +2251,7 @@
auto has_position = false;
const ast::IndexAttribute* index_attribute = nullptr;
const ast::LocationAttribute* location_attribute = nullptr;
+ const ast::ColorAttribute* color_attribute = nullptr;
const ast::InvariantAttribute* invariant_attribute = nullptr;
const ast::InterpolateAttribute* interpolate_attribute = nullptr;
for (auto* attr : member->Declaration()->attributes) {
@@ -2197,6 +2271,11 @@
index_attribute = index;
return IndexAttribute(index, stage);
},
+ [&](const ast::ColorAttribute* color) {
+ color_attribute = color;
+ return ColorAttribute(color, member->Type(), stage,
+ member->Declaration()->source);
+ },
[&](const ast::BuiltinAttribute* builtin_attr) {
if (!BuiltinAttribute(builtin_attr, member->Type(), stage,
/* is_input */ false)) {
@@ -2266,6 +2345,16 @@
return false;
}
}
+
+ if (color_attribute) {
+ uint32_t color = member->Attributes().color.value();
+ if (!colors.Add(color)) {
+ StringStream err;
+ err << "@color(" << color << ") appears multiple times";
+ AddError(err.str(), color_attribute->source);
+ return false;
+ }
+ }
}
return true;
@@ -2293,6 +2382,38 @@
return true;
}
+bool Validator::ColorAttribute(const ast::ColorAttribute* attr,
+ const core::type::Type* type,
+ ast::PipelineStage stage,
+ const Source& source,
+ const std::optional<bool> is_input) const {
+ if (!enabled_extensions_.Contains(wgsl::Extension::kChromiumExperimentalFramebufferFetch)) {
+ AddError(
+ "use of @color requires enabling extension 'chromium_experimental_framebuffer_fetch'",
+ attr->source);
+ return false;
+ }
+
+ bool is_stage_non_fragment =
+ stage != ast::PipelineStage::kNone && stage != ast::PipelineStage::kFragment;
+ bool is_output = !is_input.value_or(true);
+ if (is_stage_non_fragment || is_output) {
+ AddError("@color can only be used for fragment shader input", attr->source);
+ return false;
+ }
+
+ if (!type->is_numeric_scalar_or_vector()) {
+ std::string invalid_type = sem_.TypeNameOf(type);
+ AddError("cannot apply @color to declaration of type '" + invalid_type + "'", source);
+ AddNote(
+ "@color must only be applied to declarations of numeric scalar or numeric vector type",
+ attr->source);
+ return false;
+ }
+
+ return true;
+}
+
bool Validator::IndexAttribute(const ast::IndexAttribute* attr,
ast::PipelineStage stage,
const std::optional<bool> is_input) const {
diff --git a/src/tint/lang/wgsl/resolver/validator.h b/src/tint/lang/wgsl/resolver/validator.h
index 03466ee..8bf5af3 100644
--- a/src/tint/lang/wgsl/resolver/validator.h
+++ b/src/tint/lang/wgsl/resolver/validator.h
@@ -168,6 +168,11 @@
/// @returns true if the given type is host-shareable
bool IsHostShareable(const core::type::Type* type) const;
+ /// Validates the enabled extensions
+ /// @param enables the extension enables
+ /// @returns true on success, false otherwise.
+ bool Enables(VectorRef<const ast::Enable*> enables) const;
+
/// Validates pipeline stages
/// @param entry_points the entry points to the module
/// @returns true on success, false otherwise.
@@ -351,6 +356,20 @@
const ast::PipelineStage stage,
const Source& source) const;
+ /// Validates a color attribute
+ /// @param attr the color attribute to validate
+ /// @param type the variable type
+ /// @param stage the current pipeline stage
+ /// @param source the source of declaration using the attribute
+ /// @param is_input true if is an input variable, false if output variable, std::nullopt is
+ /// unknown.
+ /// @returns true on success, false otherwise.
+ bool ColorAttribute(const ast::ColorAttribute* attr,
+ const core::type::Type* type,
+ ast::PipelineStage stage,
+ const Source& source,
+ const std::optional<bool> is_input = std::nullopt) const;
+
/// Validates a index attribute
/// @param index_attr the index attribute to validate
/// @param stage the current pipeline stage
diff --git a/src/tint/lang/wgsl/resolver/variable_test.cc b/src/tint/lang/wgsl/resolver/variable_test.cc
index c87c4df..d3d9f10 100644
--- a/src/tint/lang/wgsl/resolver/variable_test.cc
+++ b/src/tint/lang/wgsl/resolver/variable_test.cc
@@ -700,7 +700,7 @@
ASSERT_TRUE(r()->Resolve()) << r()->error();
- auto* param = Sem().Get<sem::Parameter>(p);
+ auto* param = Sem().Get(p);
auto* local = Sem().Get<sem::LocalVariable>(l);
ASSERT_NE(param, nullptr);
@@ -898,7 +898,7 @@
ASSERT_TRUE(r()->Resolve()) << r()->error();
- auto* param = Sem().Get<sem::Parameter>(p);
+ auto* param = Sem().Get(p);
auto* local = Sem().Get<sem::LocalVariable>(c);
ASSERT_NE(param, nullptr);
@@ -1222,7 +1222,7 @@
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* func = Sem().Get(f);
- auto* param = Sem().Get<sem::Parameter>(p);
+ auto* param = Sem().Get(p);
ASSERT_NE(func, nullptr);
ASSERT_NE(param, nullptr);
@@ -1243,7 +1243,7 @@
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* global = Sem().Get(g);
- auto* param = Sem().Get<sem::Parameter>(p);
+ auto* param = Sem().Get(p);
ASSERT_NE(global, nullptr);
ASSERT_NE(param, nullptr);
@@ -1264,7 +1264,7 @@
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* global = Sem().Get(g);
- auto* param = Sem().Get<sem::Parameter>(p);
+ auto* param = Sem().Get(p);
ASSERT_NE(global, nullptr);
ASSERT_NE(param, nullptr);
@@ -1285,7 +1285,7 @@
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* alias = Sem().Get(a);
- auto* param = Sem().Get<sem::Parameter>(p);
+ auto* param = Sem().Get(p);
ASSERT_NE(alias, nullptr);
ASSERT_NE(param, nullptr);
diff --git a/src/tint/lang/wgsl/sem/type_mappings.h b/src/tint/lang/wgsl/sem/type_mappings.h
index dcee79b..fed219f 100644
--- a/src/tint/lang/wgsl/sem/type_mappings.h
+++ b/src/tint/lang/wgsl/sem/type_mappings.h
@@ -50,6 +50,7 @@
class LiteralExpression;
class Node;
class Override;
+class Parameter;
class PhonyExpression;
class Statement;
class Struct;
@@ -71,6 +72,7 @@
class GlobalVariable;
class IfStatement;
class Node;
+class Parameter;
class Statement;
class Struct;
class StructMember;
@@ -100,6 +102,7 @@
Function* operator()(ast::Function*);
GlobalVariable* operator()(ast::Override*);
IfStatement* operator()(ast::IfStatement*);
+ Parameter* operator()(ast::Parameter*);
Statement* operator()(ast::Statement*);
Struct* operator()(ast::Struct*);
StructMember* operator()(ast::StructMember*);
diff --git a/src/tint/lang/wgsl/sem/variable.h b/src/tint/lang/wgsl/sem/variable.h
index 3ee24f3..9809fcb 100644
--- a/src/tint/lang/wgsl/sem/variable.h
+++ b/src/tint/lang/wgsl/sem/variable.h
@@ -165,6 +165,10 @@
/// @note a GlobalVariable generally doesn't have a `index` in WGSL, as it isn't allowed by
/// the spec. The location maybe attached by transforms such as CanonicalizeEntryPointIO.
std::optional<uint32_t> index;
+ /// The `color` attribute value for the variable, if set
+ /// @note a GlobalVariable generally doesn't have a `color` in WGSL, as it isn't allowed by
+ /// the spec. The location maybe attached by transforms such as CanonicalizeEntryPointIO.
+ std::optional<uint32_t> color;
};
/// GlobalVariable is a module-scope variable
@@ -210,6 +214,8 @@
std::optional<uint32_t> location;
/// The `index` attribute value for the variable, if set
std::optional<uint32_t> index;
+ /// The `color` attribute value for the variable, if set
+ std::optional<uint32_t> color;
};
/// Parameter is a function parameter
diff --git a/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
index ec38e3d..12688b4 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
@@ -40,6 +40,7 @@
#include "src/tint/lang/wgsl/ast/break_statement.h"
#include "src/tint/lang/wgsl/ast/call_expression.h"
#include "src/tint/lang/wgsl/ast/call_statement.h"
+#include "src/tint/lang/wgsl/ast/color_attribute.h"
#include "src/tint/lang/wgsl/ast/compound_assignment_statement.h"
#include "src/tint/lang/wgsl/ast/const.h"
#include "src/tint/lang/wgsl/ast/continue_statement.h"
@@ -505,6 +506,11 @@
EmitExpression(out, location->expr);
out << ")";
},
+ [&](const ast::ColorAttribute* color) {
+ out << "color(";
+ EmitExpression(out, color->expr);
+ out << ")";
+ },
[&](const ast::IndexAttribute* index) {
out << "index(";
EmitExpression(out, index->expr);