validation: clean up function attributes validation and unit-tests
- clean up function decorations unit tests
- clean up interpolate and invariant validation and unittest
- add separate unit-tests for each shader stage input and output
- add [[builtin(position)]] tests
- add validation and test for:
structures with 'location' decorated members cannot be used as compute shaders input
Bug: tint:1007
Change-Id: I12e97e163b3a77bc76ce21faba241683eec5d917
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/58942
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/resolver/builtins_validation_test.cc b/src/resolver/builtins_validation_test.cc
index 4af121d..38d04e0 100644
--- a/src/resolver/builtins_validation_test.cc
+++ b/src/resolver/builtins_validation_test.cc
@@ -48,6 +48,16 @@
return Params{DataType<T>::AST, builtin, stage, is_valid};
}
static constexpr Params cases[] = {
+ ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
+ ast::PipelineStage::kVertex,
+ false),
+ ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
+ ast::PipelineStage::kFragment,
+ true),
+ ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
+ ast::PipelineStage::kCompute,
+ false),
+
ParamsFor<u32>(ast::Builtin::kVertexIndex,
ast::PipelineStage::kVertex,
true),
@@ -183,7 +193,7 @@
TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInput_Fail) {
// [[stage(fragment)]]
// fn fs_main(
- // [[builtin(kFragDepth)]] fd: f32,
+ // [[builtin(frag_depth)]] fd: f32,
// ) -> [[location(0)]] f32 { return 1.0; }
auto* fd = Param(
"fd", ty.f32(),
@@ -197,9 +207,9 @@
"fragment pipeline stage");
}
-TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInputStruct_Fail) {
- // Struct MyInputs {
- // [[builtin(front_facing)]] ff: bool;
+TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInputStruct_Ignored) {
+ // struct MyInputs {
+ // [[builtin(frag_depth)]] ff: f32;
// };
// [[stage(fragment)]]
// fn fragShader(arg: MyInputs) -> [[location(0)]] f32 { return 1.0; }
@@ -211,11 +221,7 @@
Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(
- r()->error(),
- "12:34 error: builtin(frag_depth) cannot be used in input of fragment "
- "pipeline stage\nnote: while analysing entry point fragShader");
+ EXPECT_TRUE(r()->Resolve());
}
} // namespace StageTest
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index 48b6c8c..03639c7 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -13,11 +13,6 @@
// limitations under the License.
#include "src/ast/disable_validation_decoration.h"
-#include "src/ast/override_decoration.h"
-#include "src/ast/return_statement.h"
-#include "src/ast/stage_decoration.h"
-#include "src/ast/struct_block_decoration.h"
-#include "src/ast/workgroup_decoration.h"
#include "src/resolver/resolver.h"
#include "src/resolver/resolver_test_helper.h"
@@ -55,7 +50,6 @@
namespace DecorationTests {
namespace {
-
enum class DecorationKind {
kAlign,
kBinding,
@@ -75,7 +69,7 @@
kBindingAndGroup,
};
-bool IsBindingDecoration(DecorationKind kind) {
+static bool IsBindingDecoration(DecorationKind kind) {
switch (kind) {
case DecorationKind::kBinding:
case DecorationKind::kGroup:
@@ -106,8 +100,7 @@
return {builder.create<ast::GroupDecoration>(source, 1u)};
case DecorationKind::kInterpolate:
return {builder.Interpolate(source, ast::InterpolationType::kLinear,
- ast::InterpolationSampling::kCenter),
- builder.Location(0)};
+ ast::InterpolationSampling::kCenter)};
case DecorationKind::kInvariant:
return {builder.Invariant(source)};
case DecorationKind::kLocation:
@@ -117,7 +110,7 @@
case DecorationKind::kOffset:
return {builder.create<ast::StructMemberOffsetDecoration>(source, 4u)};
case DecorationKind::kSize:
- return {builder.create<ast::StructMemberSizeDecoration>(source, 4u)};
+ return {builder.create<ast::StructMemberSizeDecoration>(source, 16u)};
case DecorationKind::kStage:
return {builder.Stage(source, ast::PipelineStage::kCompute)};
case DecorationKind::kStride:
@@ -134,6 +127,7 @@
return {};
}
+namespace FunctionInputAndOutputTests {
using FunctionParameterDecorationTest = TestWithParams;
TEST_P(FunctionParameterDecorationTest, IsValid) {
auto& params = GetParam();
@@ -171,114 +165,6 @@
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
-using EntryPointParameterDecorationTest = TestWithParams;
-TEST_P(EntryPointParameterDecorationTest, IsValid) {
- auto& params = GetParam();
-
- Func("main",
- ast::VariableList{Param("a", ty.vec4<f32>(),
- createDecorations({}, *this, params.kind))},
- ty.void_(), {},
- ast::DecorationList{Stage(ast::PipelineStage::kFragment)});
-
- if (params.should_pass) {
- EXPECT_TRUE(r()->Resolve()) << r()->error();
- } else {
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "error: decoration is not valid for function parameters");
- }
-}
-INSTANTIATE_TEST_SUITE_P(
- ResolverDecorationValidationTest,
- EntryPointParameterDecorationTest,
- testing::Values(TestParams{DecorationKind::kAlign, false},
- TestParams{DecorationKind::kBinding, false},
- TestParams{DecorationKind::kBuiltin, true},
- TestParams{DecorationKind::kGroup, false},
- TestParams{DecorationKind::kInterpolate, true},
- // kInvariant tested separately (requires position builtin)
- TestParams{DecorationKind::kLocation, true},
- TestParams{DecorationKind::kOverride, false},
- TestParams{DecorationKind::kOffset, false},
- TestParams{DecorationKind::kSize, false},
- TestParams{DecorationKind::kStage, false},
- TestParams{DecorationKind::kStride, false},
- TestParams{DecorationKind::kStructBlock, false},
- TestParams{DecorationKind::kWorkgroup, false},
- TestParams{DecorationKind::kBindingAndGroup, false}));
-
-TEST_F(EntryPointParameterDecorationTest, DuplicateDecoration) {
- Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
- {Stage(ast::PipelineStage::kFragment)},
- {
- Location(Source{{12, 34}}, 2),
- Location(Source{{56, 78}}, 3),
- });
-
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- R"(56:78 error: duplicate location decoration
-12:34 note: first decoration declared here)");
-}
-
-TEST_F(EntryPointParameterDecorationTest, DuplicateInternalDecoration) {
- auto* s =
- Param("s", ty.sampler(ast::SamplerKind::kSampler),
- ast::DecorationList{
- create<ast::BindingDecoration>(0),
- create<ast::GroupDecoration>(0),
- ASTNodes().Create<ast::DisableValidationDecoration>(
- ID(), ast::DisabledValidation::kBindingPointCollision),
- ASTNodes().Create<ast::DisableValidationDecoration>(
- ID(), ast::DisabledValidation::kEntryPointParameter),
- });
- Func("f", {s}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
-
- EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(EntryPointParameterDecorationTest, ComputeShaderLocation) {
- auto* input = Param("input", ty.vec4<f32>(),
- ast::DecorationList{Location(Source{{12, 34}}, 1)});
- Func("main", {input}, ty.void_(), {},
- {Stage(ast::PipelineStage::kCompute),
- create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
-
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: decoration is not valid for compute shader function "
- "parameters");
-}
-
-TEST_F(EntryPointParameterDecorationTest, InvariantWithPosition) {
- auto* param = Param("p", ty.vec4<f32>(),
- {Invariant(Source{{12, 34}}),
- Builtin(Source{{56, 78}}, ast::Builtin::kPosition)});
- Func("main", ast::VariableList{param}, ty.vec4<f32>(),
- ast::StatementList{Return(Construct(ty.vec4<f32>()))},
- ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
- ast::DecorationList{
- Location(0),
- });
- EXPECT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(EntryPointParameterDecorationTest, InvariantWithoutPosition) {
- auto* param =
- Param("p", ty.vec4<f32>(), {Invariant(Source{{12, 34}}), Location(0)});
- Func("main", ast::VariableList{param}, ty.vec4<f32>(),
- ast::StatementList{Return(Construct(ty.vec4<f32>()))},
- ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
- ast::DecorationList{
- Location(0),
- });
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: invariant attribute must only be applied to a "
- "position builtin");
-}
-
using FunctionReturnTypeDecorationTest = TestWithParams;
TEST_P(FunctionReturnTypeDecorationTest, IsValid) {
auto& params = GetParam();
@@ -313,41 +199,46 @@
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
+} // namespace FunctionInputAndOutputTests
-using EntryPointReturnTypeDecorationTest = TestWithParams;
-TEST_P(EntryPointReturnTypeDecorationTest, IsValid) {
+namespace EntryPointInputAndOutputTests {
+using ComputeShaderParameterDecorationTest = TestWithParams;
+TEST_P(ComputeShaderParameterDecorationTest, IsValid) {
auto& params = GetParam();
-
- Func("main", ast::VariableList{}, ty.vec4<f32>(),
- {Return(Construct(ty.vec4<f32>(), 1.f))},
- {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)},
- createDecorations({}, *this, params.kind));
+ auto* p = Param("a", ty.vec4<f32>(),
+ createDecorations(Source{{12, 34}}, *this, params.kind));
+ Func("main", ast::VariableList{p}, ty.void_(), {},
+ {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
- if (params.kind == DecorationKind::kLocation ||
- params.kind == DecorationKind::kInterpolate) {
+ if (params.kind == DecorationKind::kBuiltin) {
EXPECT_EQ(r()->error(),
- "error: decoration is not valid for compute shader entry point "
- "return types");
+ "12:34 error: builtin(position) cannot be used in input of "
+ "compute pipeline stage");
+ } else if (params.kind == DecorationKind::kInterpolate ||
+ params.kind == DecorationKind::kLocation ||
+ params.kind == DecorationKind::kInvariant) {
+ EXPECT_EQ(
+ r()->error(),
+ "12:34 error: decoration is not valid for compute shader inputs");
} else {
EXPECT_EQ(r()->error(),
- "error: decoration is not valid for entry point return types");
+ "12:34 error: decoration is not valid for function parameters");
}
}
}
-
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
- EntryPointReturnTypeDecorationTest,
+ ComputeShaderParameterDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
- TestParams{DecorationKind::kBuiltin, true},
+ TestParams{DecorationKind::kBuiltin, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, false},
- // kInvariant tested separately (requires position builtin)
+ TestParams{DecorationKind::kInvariant, false},
TestParams{DecorationKind::kLocation, false},
TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false},
@@ -358,6 +249,271 @@
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
+using FragmentShaderParameterDecorationTest = TestWithParams;
+TEST_P(FragmentShaderParameterDecorationTest, IsValid) {
+ auto& params = GetParam();
+ auto decos = createDecorations(Source{{12, 34}}, *this, params.kind);
+ if (params.kind != DecorationKind::kBuiltin &&
+ params.kind != DecorationKind::kLocation) {
+ decos.push_back(Builtin(Source{{34, 56}}, ast::Builtin::kPosition));
+ }
+ auto* p = Param("a", ty.vec4<f32>(), decos);
+ Func("frag_main", {p}, ty.void_(), {},
+ {Stage(ast::PipelineStage::kFragment)});
+
+ if (params.should_pass) {
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+ } else {
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error: decoration is not valid for function parameters");
+ }
+}
+INSTANTIATE_TEST_SUITE_P(
+ ResolverDecorationValidationTest,
+ FragmentShaderParameterDecorationTest,
+ testing::Values(TestParams{DecorationKind::kAlign, false},
+ TestParams{DecorationKind::kBinding, false},
+ TestParams{DecorationKind::kBuiltin, true},
+ TestParams{DecorationKind::kGroup, false},
+ TestParams{DecorationKind::kInterpolate, true},
+ TestParams{DecorationKind::kInvariant, true},
+ TestParams{DecorationKind::kLocation, true},
+ TestParams{DecorationKind::kOverride, false},
+ TestParams{DecorationKind::kOffset, false},
+ TestParams{DecorationKind::kSize, false},
+ TestParams{DecorationKind::kStage, false},
+ TestParams{DecorationKind::kStride, false},
+ TestParams{DecorationKind::kStructBlock, false},
+ TestParams{DecorationKind::kWorkgroup, false},
+ TestParams{DecorationKind::kBindingAndGroup, false}));
+
+using VertexShaderParameterDecorationTest = TestWithParams;
+TEST_P(VertexShaderParameterDecorationTest, IsValid) {
+ auto& params = GetParam();
+ auto decos = createDecorations(Source{{12, 34}}, *this, params.kind);
+ if (params.kind != DecorationKind::kLocation) {
+ decos.push_back(Location(Source{{34, 56}}, 2));
+ }
+ auto* p = Param("a", ty.vec4<f32>(), decos);
+ Func("vertex_main", ast::VariableList{p}, ty.vec4<f32>(),
+ {Return(Construct(ty.vec4<f32>()))},
+ {Stage(ast::PipelineStage::kVertex)},
+ {Builtin(ast::Builtin::kPosition)});
+
+ if (params.should_pass) {
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+ } else {
+ EXPECT_FALSE(r()->Resolve());
+ if (params.kind == DecorationKind::kBuiltin) {
+ EXPECT_EQ(r()->error(),
+ "12:34 error: builtin(position) cannot be used in input of "
+ "vertex pipeline stage");
+ } else if (params.kind == DecorationKind::kInvariant) {
+ EXPECT_EQ(r()->error(),
+ "12:34 error: invariant attribute must only be applied to a "
+ "position builtin");
+ } else {
+ EXPECT_EQ(r()->error(),
+ "12:34 error: decoration is not valid for function parameters");
+ }
+ }
+}
+INSTANTIATE_TEST_SUITE_P(
+ ResolverDecorationValidationTest,
+ VertexShaderParameterDecorationTest,
+ testing::Values(TestParams{DecorationKind::kAlign, false},
+ TestParams{DecorationKind::kBinding, false},
+ TestParams{DecorationKind::kBuiltin, false},
+ TestParams{DecorationKind::kGroup, false},
+ TestParams{DecorationKind::kInterpolate, true},
+ TestParams{DecorationKind::kInvariant, false},
+ TestParams{DecorationKind::kLocation, true},
+ TestParams{DecorationKind::kOverride, false},
+ TestParams{DecorationKind::kOffset, false},
+ TestParams{DecorationKind::kSize, false},
+ TestParams{DecorationKind::kStage, false},
+ TestParams{DecorationKind::kStride, false},
+ TestParams{DecorationKind::kStructBlock, false},
+ TestParams{DecorationKind::kWorkgroup, false},
+ TestParams{DecorationKind::kBindingAndGroup, false}));
+
+using ComputeShaderReturnTypeDecorationTest = TestWithParams;
+TEST_P(ComputeShaderReturnTypeDecorationTest, IsValid) {
+ auto& params = GetParam();
+ Func("main", ast::VariableList{}, ty.vec4<f32>(),
+ {Return(Construct(ty.vec4<f32>(), 1.f))},
+ {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)},
+ createDecorations(Source{{12, 34}}, *this, params.kind));
+
+ if (params.should_pass) {
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+ } else {
+ EXPECT_FALSE(r()->Resolve());
+ if (params.kind == DecorationKind::kBuiltin) {
+ EXPECT_EQ(r()->error(),
+ "12:34 error: builtin(position) cannot be used in output of "
+ "compute pipeline stage");
+ } else if (params.kind == DecorationKind::kInterpolate ||
+ params.kind == DecorationKind::kLocation ||
+ params.kind == DecorationKind::kInvariant) {
+ EXPECT_EQ(
+ r()->error(),
+ "12:34 error: decoration is not valid for compute shader output");
+ } else {
+ EXPECT_EQ(r()->error(),
+ "12:34 error: decoration is not valid for entry point return "
+ "types");
+ }
+ }
+}
+INSTANTIATE_TEST_SUITE_P(
+ ResolverDecorationValidationTest,
+ ComputeShaderReturnTypeDecorationTest,
+ testing::Values(TestParams{DecorationKind::kAlign, false},
+ TestParams{DecorationKind::kBinding, false},
+ TestParams{DecorationKind::kBuiltin, false},
+ TestParams{DecorationKind::kGroup, false},
+ TestParams{DecorationKind::kInterpolate, false},
+ TestParams{DecorationKind::kInvariant, false},
+ TestParams{DecorationKind::kLocation, false},
+ TestParams{DecorationKind::kOverride, false},
+ TestParams{DecorationKind::kOffset, false},
+ TestParams{DecorationKind::kSize, false},
+ TestParams{DecorationKind::kStage, false},
+ TestParams{DecorationKind::kStride, false},
+ TestParams{DecorationKind::kStructBlock, false},
+ TestParams{DecorationKind::kWorkgroup, false},
+ TestParams{DecorationKind::kBindingAndGroup, false}));
+
+using FragmentShaderReturnTypeDecorationTest = TestWithParams;
+TEST_P(FragmentShaderReturnTypeDecorationTest, IsValid) {
+ auto& params = GetParam();
+ auto decos = createDecorations(Source{{12, 34}}, *this, params.kind);
+ decos.push_back(Location(Source{{34, 56}}, 2));
+ Func("frag_main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
+ {Stage(ast::PipelineStage::kFragment)}, decos);
+
+ if (params.should_pass) {
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+ } else {
+ EXPECT_FALSE(r()->Resolve());
+ if (params.kind == DecorationKind::kBuiltin) {
+ EXPECT_EQ(r()->error(),
+ "12:34 error: builtin(position) cannot be used in output of "
+ "fragment pipeline stage");
+ } else if (params.kind == DecorationKind::kInvariant) {
+ EXPECT_EQ(r()->error(),
+ "12:34 error: invariant attribute must only be applied to a "
+ "position builtin");
+ } else if (params.kind == DecorationKind::kLocation) {
+ EXPECT_EQ(r()->error(),
+ "34:56 error: duplicate location decoration\n"
+ "12:34 note: first decoration declared here");
+ } else {
+ EXPECT_EQ(r()->error(),
+ "12:34 error: decoration is not valid for entry point return "
+ "types");
+ }
+ }
+}
+INSTANTIATE_TEST_SUITE_P(
+ ResolverDecorationValidationTest,
+ FragmentShaderReturnTypeDecorationTest,
+ testing::Values(TestParams{DecorationKind::kAlign, false},
+ TestParams{DecorationKind::kBinding, false},
+ TestParams{DecorationKind::kBuiltin, false},
+ TestParams{DecorationKind::kGroup, false},
+ TestParams{DecorationKind::kInterpolate, true},
+ TestParams{DecorationKind::kInvariant, false},
+ TestParams{DecorationKind::kLocation, false},
+ TestParams{DecorationKind::kOverride, false},
+ TestParams{DecorationKind::kOffset, false},
+ TestParams{DecorationKind::kSize, false},
+ TestParams{DecorationKind::kStage, false},
+ TestParams{DecorationKind::kStride, false},
+ TestParams{DecorationKind::kStructBlock, false},
+ TestParams{DecorationKind::kWorkgroup, false},
+ TestParams{DecorationKind::kBindingAndGroup, false}));
+
+using VertexShaderReturnTypeDecorationTest = TestWithParams;
+TEST_P(VertexShaderReturnTypeDecorationTest, IsValid) {
+ auto& params = GetParam();
+ auto decos = createDecorations(Source{{12, 34}}, *this, params.kind);
+ // a vertex shader must include the 'position' builtin in its return type
+ if (params.kind != DecorationKind::kBuiltin) {
+ decos.push_back(Builtin(Source{{34, 56}}, ast::Builtin::kPosition));
+ }
+ Func("vertex_main", ast::VariableList{}, ty.vec4<f32>(),
+ {Return(Construct(ty.vec4<f32>()))},
+ {Stage(ast::PipelineStage::kVertex)}, decos);
+
+ if (params.should_pass) {
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+ } else {
+ EXPECT_FALSE(r()->Resolve());
+ if (params.kind == DecorationKind::kLocation) {
+ EXPECT_EQ(r()->error(),
+ "34:56 error: multiple entry point IO attributes\n"
+ "12:34 note: previously consumed location(1)");
+ } else {
+ EXPECT_EQ(r()->error(),
+ "12:34 error: decoration is not valid for entry point return "
+ "types");
+ }
+ }
+}
+INSTANTIATE_TEST_SUITE_P(
+ ResolverDecorationValidationTest,
+ VertexShaderReturnTypeDecorationTest,
+ testing::Values(TestParams{DecorationKind::kAlign, false},
+ TestParams{DecorationKind::kBinding, false},
+ TestParams{DecorationKind::kBuiltin, true},
+ TestParams{DecorationKind::kGroup, false},
+ TestParams{DecorationKind::kInterpolate, true},
+ TestParams{DecorationKind::kInvariant, true},
+ TestParams{DecorationKind::kLocation, false},
+ TestParams{DecorationKind::kOverride, false},
+ TestParams{DecorationKind::kOffset, false},
+ TestParams{DecorationKind::kSize, false},
+ TestParams{DecorationKind::kStage, false},
+ TestParams{DecorationKind::kStride, false},
+ TestParams{DecorationKind::kStructBlock, false},
+ TestParams{DecorationKind::kWorkgroup, false},
+ TestParams{DecorationKind::kBindingAndGroup, false}));
+
+using EntryPointParameterDecorationTest = TestWithParams;
+TEST_F(EntryPointParameterDecorationTest, DuplicateDecoration) {
+ Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
+ {Stage(ast::PipelineStage::kFragment)},
+ {
+ Location(Source{{12, 34}}, 2),
+ Location(Source{{56, 78}}, 3),
+ });
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ R"(56:78 error: duplicate location decoration
+12:34 note: first decoration declared here)");
+}
+
+TEST_F(EntryPointParameterDecorationTest, DuplicateInternalDecoration) {
+ auto* s =
+ Param("s", ty.sampler(ast::SamplerKind::kSampler),
+ ast::DecorationList{
+ create<ast::BindingDecoration>(0),
+ create<ast::GroupDecoration>(0),
+ ASTNodes().Create<ast::DisableValidationDecoration>(
+ ID(), ast::DisabledValidation::kBindingPointCollision),
+ ASTNodes().Create<ast::DisableValidationDecoration>(
+ ID(), ast::DisabledValidation::kEntryPointParameter),
+ });
+ Func("f", {s}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+using EntryPointReturnTypeDecorationTest = ResolverTest;
TEST_F(EntryPointReturnTypeDecorationTest, DuplicateDecoration) {
Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
@@ -372,73 +528,20 @@
12:34 note: first decoration declared here)");
}
-TEST_F(EntryPointReturnTypeDecorationTest, InvariantWithPosition) {
- Func("main", ast::VariableList{}, ty.vec4<f32>(),
- ast::StatementList{Return(Construct(ty.vec4<f32>()))},
- ast::DecorationList{Stage(ast::PipelineStage::kVertex)},
+TEST_F(EntryPointReturnTypeDecorationTest, DuplicateInternalDecoration) {
+ Func("f", {}, ty.i32(), {Return(1)}, {Stage(ast::PipelineStage::kFragment)},
ast::DecorationList{
- Invariant(Source{{12, 34}}),
- Builtin(Source{{56, 78}}, ast::Builtin::kPosition),
+ ASTNodes().Create<ast::DisableValidationDecoration>(
+ ID(), ast::DisabledValidation::kBindingPointCollision),
+ ASTNodes().Create<ast::DisableValidationDecoration>(
+ ID(), ast::DisabledValidation::kEntryPointParameter),
});
+
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
+} // namespace EntryPointInputAndOutputTests
-TEST_F(EntryPointReturnTypeDecorationTest, InvariantWithoutPosition) {
- Func("main", ast::VariableList{}, ty.vec4<f32>(),
- ast::StatementList{Return(Construct(ty.vec4<f32>()))},
- ast::DecorationList{Stage(ast::PipelineStage::kVertex)},
- ast::DecorationList{
- Invariant(Source{{12, 34}}),
- Location(Source{{56, 78}}, 0),
- });
-
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: invariant attribute must only be applied to a "
- "position builtin");
-}
-
-using ArrayDecorationTest = TestWithParams;
-TEST_P(ArrayDecorationTest, IsValid) {
- auto& params = GetParam();
-
- auto* arr = ty.array(ty.f32(), 0,
- createDecorations(Source{{12, 34}}, *this, params.kind));
- Structure("mystruct",
- {
- Member("a", arr),
- },
- {create<ast::StructBlockDecoration>()});
-
- WrapInFunction();
-
- if (params.should_pass) {
- EXPECT_TRUE(r()->Resolve()) << r()->error();
- } else {
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: decoration is not valid for array types");
- }
-}
-INSTANTIATE_TEST_SUITE_P(
- ResolverDecorationValidationTest,
- ArrayDecorationTest,
- testing::Values(TestParams{DecorationKind::kAlign, false},
- TestParams{DecorationKind::kBinding, false},
- TestParams{DecorationKind::kBuiltin, false},
- TestParams{DecorationKind::kGroup, false},
- TestParams{DecorationKind::kInterpolate, false},
- TestParams{DecorationKind::kInvariant, false},
- TestParams{DecorationKind::kLocation, false},
- TestParams{DecorationKind::kOverride, false},
- TestParams{DecorationKind::kOffset, false},
- TestParams{DecorationKind::kSize, false},
- TestParams{DecorationKind::kStage, false},
- TestParams{DecorationKind::kStride, true},
- TestParams{DecorationKind::kStructBlock, false},
- TestParams{DecorationKind::kWorkgroup, false},
- TestParams{DecorationKind::kBindingAndGroup, false}));
-
+namespace StructAndStructMemberTests {
using StructDecorationTest = TestWithParams;
TEST_P(StructDecorationTest, IsValid) {
auto& params = GetParam();
@@ -484,19 +587,15 @@
create<ast::StructBlockDecoration>(Source{{12, 34}}),
create<ast::StructBlockDecoration>(Source{{56, 78}}),
});
-
WrapInFunction();
-
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(56:78 error: duplicate block decoration
12:34 note: first decoration declared here)");
}
-
using StructMemberDecorationTest = TestWithParams;
TEST_P(StructMemberDecorationTest, IsValid) {
auto& params = GetParam();
-
ast::StructMemberList members;
if (params.kind == DecorationKind::kBuiltin) {
members.push_back(
@@ -507,11 +606,8 @@
{Member("a", ty.f32(),
createDecorations(Source{{12, 34}}, *this, params.kind))});
}
-
Structure("mystruct", members);
-
WrapInFunction();
-
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
@@ -538,7 +634,6 @@
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
-
TEST_F(StructMemberDecorationTest, DuplicateDecoration) {
Structure("mystruct", {
Member("a", ty.i32(),
@@ -549,15 +644,12 @@
Source{{56, 78}}, 8u),
}),
});
-
WrapInFunction();
-
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(56:78 error: duplicate align decoration
12:34 note: first decoration declared here)");
}
-
TEST_F(StructMemberDecorationTest, InvariantDecorationWithPosition) {
Structure("mystruct", {
Member("a", ty.vec4<f32>(),
@@ -566,11 +658,9 @@
Builtin(ast::Builtin::kPosition),
}),
});
-
WrapInFunction();
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
-
TEST_F(StructMemberDecorationTest, InvariantDecorationWithoutPosition) {
Structure("mystruct", {
Member("a", ty.vec4<f32>(),
@@ -578,7 +668,6 @@
Invariant(Source{{12, 34}}),
}),
});
-
WrapInFunction();
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
@@ -586,6 +675,49 @@
"position builtin");
}
+} // namespace StructAndStructMemberTests
+
+using ArrayDecorationTest = TestWithParams;
+TEST_P(ArrayDecorationTest, IsValid) {
+ auto& params = GetParam();
+
+ auto* arr = ty.array(ty.f32(), 0,
+ createDecorations(Source{{12, 34}}, *this, params.kind));
+ Structure("mystruct",
+ {
+ Member("a", arr),
+ },
+ {create<ast::StructBlockDecoration>()});
+
+ WrapInFunction();
+
+ if (params.should_pass) {
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+ } else {
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error: decoration is not valid for array types");
+ }
+}
+INSTANTIATE_TEST_SUITE_P(
+ ResolverDecorationValidationTest,
+ ArrayDecorationTest,
+ testing::Values(TestParams{DecorationKind::kAlign, false},
+ TestParams{DecorationKind::kBinding, false},
+ TestParams{DecorationKind::kBuiltin, false},
+ TestParams{DecorationKind::kGroup, false},
+ TestParams{DecorationKind::kInterpolate, false},
+ TestParams{DecorationKind::kInvariant, false},
+ TestParams{DecorationKind::kLocation, false},
+ TestParams{DecorationKind::kOverride, false},
+ TestParams{DecorationKind::kOffset, false},
+ TestParams{DecorationKind::kSize, false},
+ TestParams{DecorationKind::kStage, false},
+ TestParams{DecorationKind::kStride, true},
+ TestParams{DecorationKind::kStructBlock, false},
+ TestParams{DecorationKind::kWorkgroup, false},
+ TestParams{DecorationKind::kBindingAndGroup, false}));
+
using VariableDecorationTest = TestWithParams;
TEST_P(VariableDecorationTest, IsValid) {
auto& params = GetParam();
@@ -697,41 +829,6 @@
12:34 note: first decoration declared here)");
}
-using FunctionDecorationTest = TestWithParams;
-TEST_P(FunctionDecorationTest, IsValid) {
- auto& params = GetParam();
-
- ast::DecorationList decos =
- createDecorations(Source{{12, 34}}, *this, params.kind);
- Func("foo", ast::VariableList{}, ty.void_(), ast::StatementList{}, decos);
-
- if (params.should_pass) {
- EXPECT_TRUE(r()->Resolve()) << r()->error();
- } else {
- EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(),
- "12:34 error: decoration is not valid for functions");
- }
-}
-INSTANTIATE_TEST_SUITE_P(
- ResolverDecorationValidationTest,
- FunctionDecorationTest,
- testing::Values(TestParams{DecorationKind::kAlign, false},
- TestParams{DecorationKind::kBinding, false},
- TestParams{DecorationKind::kBuiltin, false},
- TestParams{DecorationKind::kGroup, false},
- TestParams{DecorationKind::kInterpolate, false},
- TestParams{DecorationKind::kInvariant, false},
- TestParams{DecorationKind::kLocation, false},
- TestParams{DecorationKind::kOverride, false},
- TestParams{DecorationKind::kOffset, false},
- TestParams{DecorationKind::kSize, false},
- // Skip kStage as we do not apply it in this test
- TestParams{DecorationKind::kStride, false},
- TestParams{DecorationKind::kStructBlock, false},
- // Skip kWorkgroup as this is a different error
- TestParams{DecorationKind::kBindingAndGroup, false}));
-
} // namespace
} // namespace DecorationTests
@@ -1046,6 +1143,108 @@
} // namespace
} // namespace ResourceTests
+namespace LocationDecorationTests {
+namespace {
+using LocationDecorationTests = ResolverTest;
+TEST_F(LocationDecorationTests, ComputeShaderLocation_Input) {
+ Func("main", {}, ty.i32(), {Return(Expr(1))},
+ {Stage(ast::PipelineStage::kCompute),
+ create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))},
+ ast::DecorationList{Location(Source{{12, 34}}, 1)});
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error: decoration is not valid for compute shader output");
+}
+
+TEST_F(LocationDecorationTests, ComputeShaderLocation_Output) {
+ auto* input = Param("input", ty.i32(),
+ ast::DecorationList{Location(Source{{12, 34}}, 0u)});
+ Func("main", {input}, ty.void_(), {},
+ {Stage(ast::PipelineStage::kCompute),
+ create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error: decoration is not valid for compute shader inputs");
+}
+
+TEST_F(LocationDecorationTests, ComputeShaderLocationStructMember_Output) {
+ auto* m = Member("m", ty.i32(),
+ ast::DecorationList{Location(Source{{12, 34}}, 0u)});
+ auto* s = Structure("S", {m});
+ Func(Source{{56, 78}}, "main", {}, ty.Of(s),
+ ast::StatementList{Return(Expr(Construct(ty.Of(s))))},
+ {Stage(ast::PipelineStage::kCompute),
+ create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error: decoration is not valid for compute shader output\n"
+ "56:78 note: while analysing entry point 'main'");
+}
+
+TEST_F(LocationDecorationTests, ComputeShaderLocationStructMember_Input) {
+ auto* m = Member("m", ty.i32(),
+ ast::DecorationList{Location(Source{{12, 34}}, 0u)});
+ auto* s = Structure("S", {m});
+ auto* input = Param("input", ty.Of(s));
+ Func(Source{{56, 78}}, "main", {input}, ty.void_(), {},
+ {Stage(ast::PipelineStage::kCompute),
+ create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error: decoration is not valid for compute shader inputs\n"
+ "56:78 note: while analysing entry point 'main'");
+}
+
+TEST_F(LocationDecorationTests, BadType) {
+ auto* p = Param(Source{{12, 34}}, "a", ty.mat2x2<f32>(), {Location(0)});
+ Func("frag_main", {p}, ty.void_(), {},
+ {Stage(ast::PipelineStage::kFragment)});
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error: User defined entry point IO types must be a numeric "
+ "scalar, a numeric vector, or a structure");
+}
+} // namespace
+} // namespace LocationDecorationTests
+
+namespace InvariantDecorationTests {
+namespace {
+using InvariantDecorationTests = ResolverTest;
+TEST_F(InvariantDecorationTests, InvariantWithPosition) {
+ auto* param = Param("p", ty.vec4<f32>(),
+ {Invariant(Source{{12, 34}}),
+ Builtin(Source{{56, 78}}, ast::Builtin::kPosition)});
+ Func("main", ast::VariableList{param}, ty.vec4<f32>(),
+ ast::StatementList{Return(Construct(ty.vec4<f32>()))},
+ ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
+ ast::DecorationList{
+ Location(0),
+ });
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(InvariantDecorationTests, InvariantWithoutPosition) {
+ auto* param =
+ Param("p", ty.vec4<f32>(), {Invariant(Source{{12, 34}}), Location(0)});
+ Func("main", ast::VariableList{param}, ty.vec4<f32>(),
+ ast::StatementList{Return(Construct(ty.vec4<f32>()))},
+ ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
+ ast::DecorationList{
+ Location(0),
+ });
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error: invariant attribute must only be applied to a "
+ "position builtin");
+}
+} // namespace
+} // namespace InvariantDecorationTests
+
namespace WorkgroupDecorationTests {
namespace {
diff --git a/src/resolver/entry_point_validation_test.cc b/src/resolver/entry_point_validation_test.cc
index bd0201f..27c4dd6 100644
--- a/src/resolver/entry_point_validation_test.cc
+++ b/src/resolver/entry_point_validation_test.cc
@@ -160,7 +160,7 @@
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
13:43 note: previously consumed location(0)
-12:34 note: while analysing entry point main)");
+12:34 note: while analysing entry point 'main')");
}
TEST_F(ResolverEntryPointValidationTest,
@@ -183,7 +183,7 @@
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(14:52 error: missing entry point IO attribute
-12:34 note: while analysing entry point main)");
+12:34 note: while analysing entry point 'main')");
}
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_NestedStruct) {
@@ -209,7 +209,7 @@
EXPECT_EQ(
r()->error(),
R"(14:52 error: entry point IO types cannot contain nested structures
-12:34 note: while analysing entry point main)");
+12:34 note: while analysing entry point 'main')");
}
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_RuntimeArray) {
@@ -254,7 +254,7 @@
EXPECT_EQ(
r()->error(),
R"(12:34 error: builtin(frag_depth) attribute appears multiple times as pipeline output
-12:34 note: while analysing entry point main)");
+12:34 note: while analysing entry point 'main')");
}
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateLocation) {
@@ -276,7 +276,7 @@
EXPECT_EQ(
r()->error(),
R"(12:34 error: location(1) attribute appears multiple times as pipeline output
-12:34 note: while analysing entry point main)");
+12:34 note: while analysing entry point 'main')");
}
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Location) {
@@ -369,7 +369,7 @@
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
13:43 note: previously consumed location(0)
-12:34 note: while analysing entry point main)");
+12:34 note: while analysing entry point 'main')");
}
TEST_F(ResolverEntryPointValidationTest,
@@ -389,7 +389,7 @@
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: missing entry point IO attribute
-12:34 note: while analysing entry point main)");
+12:34 note: while analysing entry point 'main')");
}
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_NestedStruct) {
@@ -413,7 +413,7 @@
EXPECT_EQ(
r()->error(),
R"(14:52 error: entry point IO types cannot contain nested structures
-12:34 note: while analysing entry point main)");
+12:34 note: while analysing entry point 'main')");
}
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_RuntimeArray) {
@@ -477,7 +477,7 @@
EXPECT_EQ(
r()->error(),
R"(12:34 error: builtin(sample_index) attribute appears multiple times as pipeline input
-12:34 note: while analysing entry point main)");
+12:34 note: while analysing entry point 'main')");
}
TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateLocation) {
@@ -515,7 +515,7 @@
EXPECT_EQ(
r()->error(),
R"(12:34 error: location(1) attribute appears multiple times as pipeline input
-12:34 note: while analysing entry point main)");
+12:34 note: while analysing entry point 'main')");
}
TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) {
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index f7bb4e4..2656568 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -1138,18 +1138,9 @@
"decoration is not valid for non-entry point function parameters",
deco->source());
return false;
- } else if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) {
- if (!ValidateInterpolateDecoration(interpolate, info->type)) {
- return false;
- }
- } else if (deco->Is<ast::LocationDecoration>()) {
- if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
- AddError(
- "decoration is not valid for compute shader function parameters",
- deco->source());
- return false;
- }
} else if (!deco->IsAnyOf<ast::BuiltinDecoration, ast::InvariantDecoration,
+ ast::LocationDecoration,
+ ast::InterpolateDecoration,
ast::InternalDecoration>() &&
(IsValidationEnabled(
info->declaration->decorations(),
@@ -1198,7 +1189,8 @@
bool Resolver::ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
const sem::Type* storage_type,
- const bool is_input) {
+ const bool is_input,
+ const bool is_struct_member) {
auto* type = storage_type->UnwrapRef();
const auto stage = current_function_
? current_function_->declaration->pipeline_stage()
@@ -1206,15 +1198,13 @@
std::stringstream stage_name;
stage_name << stage;
bool is_stage_mismatch = false;
+ bool is_output = !is_input;
switch (deco->value()) {
case ast::Builtin::kPosition:
if (stage != ast::PipelineStage::kNone &&
- !(stage == ast::PipelineStage::kFragment && is_input) &&
- !(stage == ast::PipelineStage::kVertex && !is_input)) {
- AddError(deco_to_str(deco) + " cannot be used in " +
- (is_input ? "input of " : "output of ") +
- stage_name.str() + " pipeline stage",
- deco->source());
+ !((is_input && stage == ast::PipelineStage::kFragment) ||
+ (is_output && stage == ast::PipelineStage::kVertex))) {
+ is_stage_mismatch = true;
}
if (!(type->is_float_vector() && type->As<sem::Vector>()->Width() == 4)) {
AddError("store type of " + deco_to_str(deco) + " must be 'vec4<f32>'",
@@ -1311,12 +1301,16 @@
break;
}
- if (is_stage_mismatch) {
- AddError(deco_to_str(deco) + " cannot be used in " +
- (is_input ? "input of " : "output of ") + stage_name.str() +
- " pipeline stage",
- deco->source());
- return false;
+ // ignore builtin attribute on struct members to facillate data movement
+ // between stages
+ if (!is_struct_member) {
+ if (is_stage_mismatch) {
+ AddError(deco_to_str(deco) + " cannot be used in " +
+ (is_input ? "input of " : "output of ") + stage_name.str() +
+ " pipeline stage",
+ deco->source());
+ return false;
+ }
}
return true;
@@ -1409,28 +1403,14 @@
deco->source());
return false;
}
-
- if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) {
- if (!ValidateInterpolateDecoration(interpolate, info->return_type)) {
- return false;
- }
- } else if (deco->Is<ast::LocationDecoration>()) {
- if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
- AddError(
- "decoration is not valid for compute shader entry point return "
- "types",
- deco->source());
- return false;
- }
- } else if (!deco->IsAnyOf<ast::BuiltinDecoration, ast::InternalDecoration,
- ast::InvariantDecoration>() &&
- (IsValidationEnabled(
- info->declaration->decorations(),
- ast::DisabledValidation::kEntryPointParameter) &&
- IsValidationEnabled(
- info->declaration->decorations(),
- ast::DisabledValidation::
- kIgnoreConstructibleFunctionParameter))) {
+ if (!deco->IsAnyOf<ast::BuiltinDecoration, ast::InternalDecoration,
+ ast::LocationDecoration, ast::InterpolateDecoration,
+ ast::InvariantDecoration>() &&
+ (IsValidationEnabled(info->declaration->decorations(),
+ ast::DisabledValidation::kEntryPointParameter) &&
+ IsValidationEnabled(info->declaration->decorations(),
+ ast::DisabledValidation::
+ kIgnoreConstructibleFunctionParameter))) {
AddError("decoration is not valid for entry point return types",
deco->source());
return false;
@@ -1473,6 +1453,7 @@
ast::Decoration* pipeline_io_attribute = nullptr;
ast::InvariantDecoration* invariant_attribute = nullptr;
for (auto* deco : decos) {
+ auto is_invalid_compute_shader_decoration = false;
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
if (pipeline_io_attribute) {
AddError("multiple entry point IO attributes", deco->source());
@@ -1490,14 +1471,14 @@
func->source());
return false;
}
- builtins.emplace(builtin->value());
if (!ValidateBuiltinDecoration(
builtin, ty,
- /* is_input */ param_or_ret == ParamOrRetType::kParameter)) {
+ /* is_input */ param_or_ret == ParamOrRetType::kParameter,
+ /* is_struct_member */ is_struct_member)) {
return false;
}
-
+ builtins.emplace(builtin->value());
} else if (auto* location = deco->As<ast::LocationDecoration>()) {
if (pipeline_io_attribute) {
AddError("multiple entry point IO attributes", deco->source());
@@ -1515,10 +1496,31 @@
func->source());
return false;
}
+
+ if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
+ is_invalid_compute_shader_decoration = true;
+ }
locations.emplace(location->value());
+ } else if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) {
+ if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
+ is_invalid_compute_shader_decoration = true;
+ } else if (!ValidateInterpolateDecoration(interpolate, ty)) {
+ return false;
+ }
} else if (auto* invariant = deco->As<ast::InvariantDecoration>()) {
+ if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
+ is_invalid_compute_shader_decoration = true;
+ }
invariant_attribute = invariant;
}
+ if (is_invalid_compute_shader_decoration) {
+ std::string input_or_output =
+ param_or_ret == ParamOrRetType::kParameter ? "inputs" : "output";
+ AddError(
+ "decoration is not valid for compute shader " + input_or_output,
+ deco->source());
+ return false;
+ }
}
// Check that we saw a pipeline IO attribute iff we need one.
@@ -1587,8 +1589,8 @@
if (member->Type()->Is<sem::Struct>()) {
AddError("entry point IO types cannot contain nested structures",
member->Declaration()->source());
- AddNote("while analysing entry point " +
- builder_->Symbols().NameFor(func->symbol()),
+ AddNote("while analysing entry point '" +
+ builder_->Symbols().NameFor(func->symbol()) + "'",
func->source());
return false;
}
@@ -1597,8 +1599,8 @@
if (arr->IsRuntimeSized()) {
AddError("entry point IO types cannot contain runtime sized arrays",
member->Declaration()->source());
- AddNote("while analysing entry point " +
- builder_->Symbols().NameFor(func->symbol()),
+ AddNote("while analysing entry point '" +
+ builder_->Symbols().NameFor(func->symbol()) + "'",
func->source());
return false;
}
@@ -1607,8 +1609,8 @@
if (!validate_entry_point_decorations_inner(
member->Declaration()->decorations(), member->Type(),
member->Declaration()->source(), param_or_ret, true)) {
- AddNote("while analysing entry point " +
- builder_->Symbols().NameFor(func->symbol()),
+ AddNote("while analysing entry point '" +
+ builder_->Symbols().NameFor(func->symbol()) + "'",
func->source());
return false;
}
@@ -3938,7 +3940,9 @@
invariant_attribute = invariant;
}
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
- if (!ValidateBuiltinDecoration(builtin, member->Type())) {
+ if (!ValidateBuiltinDecoration(builtin, member->Type(),
+ /* is_input */ false,
+ /* is_struct_member */ true)) {
return false;
}
if (builtin->value() == ast::Builtin::kPosition) {
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 600ef22..446bac6 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -281,7 +281,8 @@
bool ValidateAssignment(const ast::AssignmentStatement* a);
bool ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
const sem::Type* storage_type,
- const bool is_input = true);
+ const bool is_input,
+ const bool is_struct_member);
bool ValidateCallStatement(ast::CallStatement* stmt);
bool ValidateEntryPoint(const ast::Function* func, const FunctionInfo* info);
bool ValidateFunction(const ast::Function* func, const FunctionInfo* info);