Require both `blend_src(0)` and `blend_src(1)` must be used in one struct
This patch adds a new check on dual source blending that both
`@location(0) @blend_src(0)` and `@location(0) @blend_src(1)` must be
used in one struct, and other members (if exist) must not have
`@location` attribute, or neither of them are used.
As now this rule is applied to all structs, the original checks on the
entry point are all removed.
Bug: chromium:341973423
Test: tint_unittests
Change-Id: I71a7b088e57bfd67eb1adf9f1c053f1592c3ce5a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/191766
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: James Price <jrprice@google.com>
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 ded56eb..5a53f76 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
@@ -123,20 +123,35 @@
EXPECT_EQ(r()->error(), "12:34 error: '@blend_src' can only be used with '@location(0)'");
}
-// Using an index attribute on a struct member should pass.
-TEST_F(DualSourceBlendingExtensionTests, StructMemberBlendSrcAttribute) {
+// Using a @blend_src attribute on a struct member while there is only one member in the struct
+// should fail.
+TEST_F(DualSourceBlendingExtensionTests, StructMemberBlendSrcAttribute_OnlyBlendSrc0) {
Structure("Output", Vector{
Member("a", ty.vec4<f32>(),
Vector{Location(0_a), BlendSrc(Source{{12, 34}}, 0_a)}),
});
- EXPECT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(), "12:34 error: '@blend_src(1)' is missing when '@blend_src' is used");
}
-// Using an index attribute on a global variable should pass. This is needed internally when using
-// @blend_src with the canonicalize_entry_point transform. This test uses an internal attribute to
-// ignore address space, which is how it is used with the canonicalize_entry_point transform.
-TEST_F(DualSourceBlendingExtensionTests, GlobalVariableBlendSrcAttribute) {
+// Using a @blend_src attribute on a struct member while there is only one member in the struct
+// should fail.
+TEST_F(DualSourceBlendingExtensionTests, StructMemberBlendSrcAttribute_OnlyBlendSrc1) {
+ Structure("Output", Vector{
+ Member("a", ty.vec4<f32>(),
+ Vector{Location(0_a), BlendSrc(Source{{12, 34}}, 1_a)}),
+ });
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(), "12:34 error: '@blend_src(0)' is missing when '@blend_src' is used");
+}
+
+// Using a @blend_src attribute on a global variable should pass. This is needed internally when
+// using @blend_src with the canonicalize_entry_point transform. This test uses an internal
+// attribute to ignore address space, which is how it is used with the canonicalize_entry_point
+// transform.
+TEST_F(DualSourceBlendingExtensionTests, GlobalVariableBlendSrcAttributeAfterInternalTransform) {
GlobalVar(
"var", ty.vec4<f32>(),
Vector{Location(0_a), BlendSrc(0_a), Disable(ast::DisabledValidation::kIgnoreAddressSpace)},
@@ -156,87 +171,139 @@
EXPECT_EQ(r()->error(), "12:34 error: '@blend_src' can only be used with '@location(0)'");
}
-TEST_F(DualSourceBlendingExtensionTests, MixedBlendSrcAndNonBlendSrcOnLocationZero_Struct) {
+// Using @blend_src and @location(0) on two members and having another without @location should not
+// fail.
+TEST_F(DualSourceBlendingExtensionTests, BlendSrcAndNonLocationNonBlendSrc) {
// struct S {
- // @location(0) a : vec4<f32>,
- // @location(0) @blend_src(1) b : vec4<f32>,
+ // a : vec4<f32>,
+ // @location(0) @blend_src(0) b : vec4<f32>,
+ // @location(0) @blend_src(1) c : vec4<f32>,
// };
- Structure("S", Vector{
- Member("a", ty.vec4<f32>(),
- Vector{
- Location(Source{{12, 34}}, 0_a),
- }),
- Member("b", ty.vec4<f32>(),
- Vector{Location(0_a), BlendSrc(Source{{56, 78}}, 1_a)}),
- });
+ Structure("S",
+ Vector{
+ Member("a", ty.vec4<f32>()),
+ Member("b", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(Source{{3, 4}}, 0_a)}),
+ Member("c", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(Source{{5, 6}}, 1_a)}),
+ });
EXPECT_TRUE(r()->Resolve());
}
-TEST_F(DualSourceBlendingExtensionTests,
- MixedBlendSrcAndNonBlendSrcOnLocationZero_StructUsedInEntryPoint) {
+// Using @blend_src and @location(0) on two members and having another member with @location but
+// without @blend_src should fail.
+TEST_F(DualSourceBlendingExtensionTests, ZeroLocationAndNonBlendSrcBeforeBlendSrc) {
// struct S {
// @location(0) a : vec4<f32>,
- // @location(0) @blend_src(1) b : vec4<f32>,
+ // @location(0) @blend_src(0) b : vec4<f32>,
+ // @location(0) @blend_src(1) c : vec4<f32>,
// };
- // fn F() -> S { return S(); }
Structure("S", Vector{
- Member("a", ty.vec4<f32>(),
- Vector{
- Location(Source{{12, 34}}, 0_a),
- }),
- Member("b", ty.vec4<f32>(),
- Vector{Location(0_a), BlendSrc(Source{{56, 78}}, 1_a)}),
+ Member("a", ty.vec4<f32>(), Vector{Location(0_a)}),
+ Member(Source{{12, 34}}, "b", ty.vec4<f32>(),
+ Vector{Location(0_a), BlendSrc(0_a)}),
+ Member("c", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(1_a)}),
});
- Func("F", Empty, ty("S"), Vector{Return(Call("S"))},
- Vector{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
- R"(12:34 error: use of '@blend_src' requires all the output '@location' attributes of the entry point to be paired with a '@blend_src' attribute
-56:78 note: use of '@blend_src' here
-note: while analyzing entry point 'F')");
+ R"(12:34 error: '@blend_src' and '@location' are used on one member while another member with '@location' doesn't use '@blend_src')");
}
+// Using @blend_src and @location(0) on two members and having another member with @location but
+// without @blend_src should fail.
+TEST_F(DualSourceBlendingExtensionTests, ZeroLocationAndNonBlendSrcAfterBlendSrc) {
+ // struct S {
+ // @location(0) @blend_src(0) a : vec4<f32>,
+ // @location(0) b : vec4<f32>,
+ // @location(0) @blend_src(1) c : vec4<f32>,
+ // };
+ Structure("S", Vector{
+ Member("a", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(0_a)}),
+ Member(Source{{12, 34}}, "b", ty.vec4<f32>(), Vector{Location(0_a)}),
+ Member("c", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(1_a)}),
+ });
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ R"(12:34 error: '@blend_src' and '@location' are used on one member while another member with '@location' doesn't use '@blend_src')");
+}
+
+// Using @blend_src and @location(0) on two members and having another member with @location but
+// without @blend_src should fail.
+TEST_F(DualSourceBlendingExtensionTests, NonZeroLocationAndNonBlendSrcBeforeBlendSrc) {
+ // struct S {
+ // @location(1) a : vec4<f32>,
+ // @location(0) @blend_src(0) b : vec4<f32>,
+ // @location(0) @blend_src(1) c : vec4<f32>,
+ // };
+ Structure("S", Vector{
+ Member("a", ty.vec4<f32>(), Vector{Location(1_a)}),
+ Member(Source{{12, 34}}, "b", ty.vec4<f32>(),
+ Vector{Location(0_a), BlendSrc(0_a)}),
+ Member("c", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(1_a)}),
+ });
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ R"(12:34 error: '@blend_src' and '@location' are used on one member while another member with '@location' doesn't use '@blend_src')");
+}
+
+// Using @blend_src and @location(0) on two members and having another member with @location but
+// without @blend_src should fail.
+TEST_F(DualSourceBlendingExtensionTests, NonZeroLocationAndNonBlendSrcAfterBlendSrc) {
+ // struct S {
+ // @location(0) @blend_src(0) a : vec4<f32>,
+ // @location(1) b : vec4<f32>,
+ // @location(0) @blend_src(1) c : vec4<f32>,
+ // };
+ Structure("S", Vector{
+ Member("a", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(0_a)}),
+ Member(Source{{12, 34}}, "b", ty.vec4<f32>(), Vector{Location(1_a)}),
+ Member("c", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(1_a)}),
+ });
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ R"(12:34 error: '@blend_src' and '@location' are used on one member while another member with '@location' doesn't use '@blend_src')");
+}
+
+// The members with @blend_src and @location(0) must have same type.
TEST_F(DualSourceBlendingExtensionTests, BlendSrcTypes_DifferentWidth) {
// struct S {
// @location(0) @blend_src(0) a : vec4<f32>,
// @location(0) @blend_src(1) b : vec2<f32>,
// };
- // @fragment fn F() -> S { return S(); }
Structure("S",
Vector{
Member("a", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(0_a)}),
Member("b", ty.vec2<f32>(), Vector{Location(0_a), BlendSrc(Source{{1, 2}}, 1_a)}),
});
- Func("F", Empty, ty("S"), Vector{Return(Call("S"))},
- Vector{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), R"(1:2 error: Use of '@blend_src' requires all outputs have same type
-note: while analyzing entry point 'F')");
+ EXPECT_EQ(r()->error(), R"(1:2 error: All the outputs with '@blend_src' must have same type)");
}
+// The members with @blend_src and @location(0) must have same type.
TEST_F(DualSourceBlendingExtensionTests, BlendSrcTypes_DifferentElementType) {
// struct S {
// @location(0) @blend_src(0) a : vec4<f32>,
// @location(0) @blend_src(1) b : vec4<i32>,
// };
- // @fragment fn F() -> S { return S(); }
Structure("S",
Vector{
Member("a", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(0_a)}),
Member("b", ty.vec4<i32>(), Vector{Location(0_a), BlendSrc(Source{{1, 2}}, 1_a)}),
});
- Func("F", Empty, ty("S"), Vector{Return(Call("S"))},
- Vector{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), R"(1:2 error: Use of '@blend_src' requires all outputs have same type
-note: while analyzing entry point 'F')");
+ EXPECT_EQ(r()->error(), R"(1:2 error: All the outputs with '@blend_src' must have same type)");
}
+// It is not allowed to use a struct with @blend_src as fragment shader input.
TEST_F(DualSourceBlendingExtensionTests, BlendSrcAsFragmentInput) {
// struct S {
// @location(0) @blend_src(0) a : vec4<f32>,
@@ -255,6 +322,7 @@
note: while analyzing entry point 'F')");
}
+// It is not allowed to use @blend_src on the fragment output declaration.
TEST_F(DualSourceBlendingExtensionTest, BlendSrcOnNonStructFragmentOutput) {
// enable dual_source_blending;
// @fragment fn F() -> @location(0) @blend_src(0) vec4<f32> {
@@ -287,7 +355,7 @@
Vector{
Member("a", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(0_a)}),
Member("b", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(Source{{1, 2}}, 1_a)}),
- Member("c", ty.vec4<f32>(), Vector{Location(Source{{3, 4}}, AInt(GetParam()))}),
+ Member(Source{{3, 4}}, "c", ty.vec4<f32>(), Vector{Location(AInt(GetParam()))}),
});
Func("F", Empty, ty("S"), Vector{Return(Call("S"))},
Vector{Stage(ast::PipelineStage::kFragment)});
@@ -295,9 +363,7 @@
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
- R"(3:4 error: use of '@blend_src' requires all the output '@location' attributes of the entry point to be paired with a '@blend_src' attribute
-1:2 note: use of '@blend_src' here
-note: while analyzing entry point 'F')");
+ R"(3:4 error: '@blend_src' and '@location' are used on one member while another member with '@location' doesn't use '@blend_src')");
}
TEST_P(DualSourceBlendingExtensionTestWithParams,
@@ -310,8 +376,8 @@
// fn F() -> S { return S(); }
Structure("S",
Vector{
- Member("a", ty.vec4<f32>(), Vector{Location(Source{{1, 2}}, AInt(GetParam()))}),
- Member("b", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(0_a)}),
+ Member("a", ty.vec4<f32>(), Vector{Location(AInt(GetParam()))}),
+ Member(Source{{1, 2}}, "b", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(0_a)}),
Member("c", ty.vec4<f32>(), Vector{Location(0_a), BlendSrc(Source{{3, 4}}, 1_a)}),
});
Func(Source{{5, 6}}, "F", Empty, ty("S"), Vector{Return(Call("S"))},
@@ -320,9 +386,7 @@
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
- R"(1:2 error: use of '@blend_src' requires all the output '@location' attributes of the entry point to be paired with a '@blend_src' attribute
-note: use of '@blend_src' here
-5:6 note: while analyzing entry point 'F')");
+ R"(1:2 error: '@blend_src' and '@location' are used on one member while another member with '@location' doesn't use '@blend_src')");
}
INSTANTIATE_TEST_SUITE_P(DualSourceBlendingExtensionTests,
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index a19c181..6de0336 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -28,6 +28,7 @@
#include "src/tint/lang/wgsl/resolver/validator.h"
#include <algorithm>
+#include <bitset>
#include <limits>
#include <string_view>
#include <tuple>
@@ -1235,10 +1236,6 @@
// sem::Function since it would be useful there too.
Hashset<core::BuiltinValue, 4> builtins;
Hashset<std::pair<uint32_t, uint32_t>, 8> locations_and_blend_srcs;
- const ast::LocationAttribute* first_nonzero_location = nullptr;
- const ast::BlendSrcAttribute* first_blend_src = nullptr;
- const core::type::Type* first_blend_src_type = nullptr;
- const ast::LocationAttribute* first_location_without_blend_src = nullptr;
Hashset<uint32_t, 4> colors;
enum class ParamOrRetType {
kParameter,
@@ -1390,54 +1387,7 @@
}
}
- if (blend_src_attribute) {
- if (location.value_or(1) != 0) {
- AddError(blend_src_attribute->source)
- << style::Attribute("@blend_src") << " can only be used with "
- << style::Attribute("@location")
- << style::Code("(", style::Literal("0"), ")");
- return false;
- }
- if (first_blend_src_type == nullptr) {
- first_blend_src_type = ty;
- } else if (!first_blend_src_type->Equals(*ty)) {
- AddError(blend_src_attribute->source)
- << "Use of " << style::Attribute("@blend_src")
- << " requires all outputs have same type";
- return false;
- }
- }
-
- if (blend_src_attribute) {
- first_blend_src = blend_src_attribute;
- } else if (location_attribute) {
- first_location_without_blend_src = location_attribute;
- }
-
- if (first_blend_src && first_location_without_blend_src) {
- AddError(first_location_without_blend_src->source)
- << "use of " << style::Attribute("@blend_src") << " requires all the output "
- << style::Attribute("@location")
- << " attributes of the entry point to be paired with a "
- << style::Attribute("@blend_src") << " attribute";
- AddNote(first_blend_src->source)
- << "use of " << style::Attribute("@blend_src") << " here";
- return false;
- }
-
if (location_attribute) {
- if (!first_nonzero_location && location > 0u) {
- first_nonzero_location = location_attribute;
- }
- if (first_nonzero_location && first_blend_src) {
- AddError(first_blend_src->source)
- << "pipeline cannot use both a " << style::Attribute("@blend_src")
- << " and non-zero " << style::Attribute("@location");
- AddNote(first_nonzero_location->source)
- << "non-zero " << style::Attribute("@location") << " declared here";
- return false;
- }
-
std::pair<uint32_t, uint32_t> location_and_blend_src(location.value(),
blend_src.value_or(0));
if (!locations_and_blend_srcs.Add(location_and_blend_src)) {
@@ -1533,10 +1483,6 @@
// should be validated independently from those used in parameters.
builtins.Clear();
locations_and_blend_srcs.Clear();
- first_nonzero_location = nullptr;
- first_blend_src = nullptr;
- first_blend_src_type = nullptr;
- first_location_without_blend_src = nullptr;
if (!func->ReturnType()->Is<core::type::Void>()) {
if (!validate_entry_point_attributes(decl->return_type_attributes, func->ReturnType(),
@@ -2323,6 +2269,10 @@
return false;
}
+ std::bitset<2> blend_src_appearance_mask;
+ const ast::BlendSrcAttribute* first_blend_src = nullptr;
+ const core::type::Type* first_blend_src_type = nullptr;
+ const ast::LocationAttribute* first_location_without_blend_src = nullptr;
Hashset<std::pair<uint32_t, std::optional<uint32_t>>, 8> locations_and_blend_srcs;
Hashset<uint32_t, 4> colors;
for (auto* member : str->Members()) {
@@ -2411,15 +2361,38 @@
}
if (blend_src_attribute) {
- // Because HLSL specifies dual source blending targets with SV_Target0 and 1, we should
- // restrict targets with index attributes to location 0 for easy translation in the
- // backend writers.
if (member->Attributes().location.value_or(1) != 0) {
AddError(blend_src_attribute->source)
<< style::Attribute("@blend_src") << " can only be used with "
<< style::Attribute("@location") << style::Code("(", style::Literal("0"), ")");
return false;
}
+
+ if (first_blend_src == nullptr) {
+ first_blend_src = blend_src_attribute;
+ first_blend_src_type = member->Type();
+ } else if (!first_blend_src_type->Equals(*member->Type())) {
+ AddError(blend_src_attribute->source)
+ << "All the outputs with " << style::Attribute("@blend_src")
+ << " must have same type";
+ return false;
+ }
+
+ TINT_ASSERT(member->Attributes().blend_src.has_value() &&
+ *member->Attributes().blend_src <= 1u);
+ uint32_t blend_src = *member->Attributes().blend_src;
+ blend_src_appearance_mask.set(blend_src);
+ } else if (location_attribute) {
+ first_location_without_blend_src = location_attribute;
+ }
+
+ if (first_blend_src && first_location_without_blend_src) {
+ AddError(member->Declaration()->source)
+ << style::Attribute("@blend_src") << " and " << style::Attribute("@location")
+ << " are used on one member while another member with "
+ << style::Attribute("@location") << " doesn't use "
+ << style::Attribute("@blend_src");
+ return false;
}
if (interpolate_attribute && !location_attribute) {
@@ -2458,6 +2431,17 @@
}
}
+ if (first_blend_src != nullptr && !blend_src_appearance_mask.all()) {
+ for (uint32_t i = 0; i < blend_src_appearance_mask.size(); ++i) {
+ if (!blend_src_appearance_mask.test(i)) {
+ AddError(first_blend_src->source)
+ << style::Attribute("@blend_src") << style::Code("(", style::Literal(i), ")")
+ << " is missing when " << style::Attribute("@blend_src") << " is used";
+ }
+ }
+ return false;
+ }
+
return true;
}