[tint] Resolve ColorAttribute
Bug: tint:2085
Change-Id: I85a008fca5aa33c9a672551bac398173e48e1b70
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/159923
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/core/ir/transform/shader_io.cc b/src/tint/lang/core/ir/transform/shader_io.cc
index 368dcf0..d1790d4 100644
--- a/src/tint/lang/core/ir/transform/shader_io.cc
+++ b/src/tint/lang/core/ir/transform/shader_io.cc
@@ -122,6 +122,7 @@
core::type::StructMemberAttributes{
/* location */ {},
/* index */ {},
+ /* color */ {},
/* builtin */ core::BuiltinValue::kPointSize,
/* interpolation */ {},
/* 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..0f63af0 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
@@ -1494,6 +1494,7 @@
core::type::StructMemberAttributes{
/* location */ {},
/* index */ {},
+ /* color */ {},
/* builtin */ core::BuiltinValue::kGlobalInvocationId,
/* interpolation */ {},
/* invariant */ false,
@@ -1505,6 +1506,7 @@
core::type::StructMemberAttributes{
/* location */ {},
/* index */ {},
+ /* color */ {},
/* builtin */ core::BuiltinValue::kLocalInvocationIndex,
/* interpolation */ {},
/* 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..230cb58 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
@@ -1969,7 +1969,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)";
@@ -2838,6 +2838,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()) {
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_test.cc b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
index edbde41..e630557 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,
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/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..7d59187 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,10 @@
Pass,
},
TestParams{
+ {AttributeKind::kColor},
+ Pass,
+ },
+ TestParams{
{AttributeKind::kDiagnostic},
R"(1:2 error: @diagnostic is not valid for function parameters)",
},
@@ -823,6 +863,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 +968,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 +1054,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 +1166,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 +1295,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 +1374,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/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..5046bd1 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;
}
}
@@ -1349,7 +1418,8 @@
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 +2224,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 +2249,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 +2269,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 +2343,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 +2380,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