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>
This commit is contained in:
parent
f76c227cee
commit
465c5aa51d
|
@ -48,6 +48,16 @@ constexpr Params ParamsFor(ast::Builtin builtin,
|
|||
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 @@ INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
|
|||
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 @@ TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInput_Fail) {
|
|||
"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 @@ TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInputStruct_Fail) {
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -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 @@ using u32 = builder::u32;
|
|||
|
||||
namespace DecorationTests {
|
||||
namespace {
|
||||
|
||||
enum class DecorationKind {
|
||||
kAlign,
|
||||
kBinding,
|
||||
|
@ -75,7 +69,7 @@ enum class DecorationKind {
|
|||
kBindingAndGroup,
|
||||
};
|
||||
|
||||
bool IsBindingDecoration(DecorationKind kind) {
|
||||
static bool IsBindingDecoration(DecorationKind kind) {
|
||||
switch (kind) {
|
||||
case DecorationKind::kBinding:
|
||||
case DecorationKind::kGroup:
|
||||
|
@ -106,8 +100,7 @@ static ast::DecorationList createDecorations(const Source& source,
|
|||
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 @@ static ast::DecorationList createDecorations(const Source& source,
|
|||
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 @@ static ast::DecorationList createDecorations(const Source& source,
|
|||
return {};
|
||||
}
|
||||
|
||||
namespace FunctionInputAndOutputTests {
|
||||
using FunctionParameterDecorationTest = TestWithParams;
|
||||
TEST_P(FunctionParameterDecorationTest, IsValid) {
|
||||
auto& params = GetParam();
|
||||
|
@ -171,114 +165,6 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
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 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
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 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
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 @@ TEST_F(EntryPointReturnTypeDecorationTest, DuplicateDecoration) {
|
|||
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),
|
||||
});
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
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),
|
||||
ASTNodes().Create<ast::DisableValidationDecoration>(
|
||||
ID(), ast::DisabledValidation::kBindingPointCollision),
|
||||
ASTNodes().Create<ast::DisableValidationDecoration>(
|
||||
ID(), ast::DisabledValidation::kEntryPointParameter),
|
||||
});
|
||||
|
||||
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 EntryPointInputAndOutputTests
|
||||
|
||||
namespace StructAndStructMemberTests {
|
||||
using StructDecorationTest = TestWithParams;
|
||||
TEST_P(StructDecorationTest, IsValid) {
|
||||
auto& params = GetParam();
|
||||
|
@ -484,19 +587,15 @@ TEST_F(StructDecorationTest, DuplicateDecoration) {
|
|||
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 @@ TEST_P(StructMemberDecorationTest, IsValid) {
|
|||
{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 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
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 @@ TEST_F(StructMemberDecorationTest, DuplicateDecoration) {
|
|||
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 @@ TEST_F(StructMemberDecorationTest, InvariantDecorationWithPosition) {
|
|||
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 @@ TEST_F(StructMemberDecorationTest, InvariantDecorationWithoutPosition) {
|
|||
Invariant(Source{{12, 34}}),
|
||||
}),
|
||||
});
|
||||
|
||||
WrapInFunction();
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
|
@ -586,6 +675,49 @@ TEST_F(StructMemberDecorationTest, InvariantDecorationWithoutPosition) {
|
|||
"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 @@ TEST_F(ConstantDecorationTest, DuplicateDecoration) {
|
|||
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 @@ TEST_F(ResourceDecorationTest, BindingPointOnNonResource) {
|
|||
} // 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 {
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ TEST_F(ResolverEntryPointValidationTest,
|
|||
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 @@ TEST_F(ResolverEntryPointValidationTest,
|
|||
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 @@ TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_NestedStruct) {
|
|||
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 @@ TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateBuiltins) {
|
|||
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 @@ TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateLocation) {
|
|||
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 @@ TEST_F(ResolverEntryPointValidationTest,
|
|||
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 @@ TEST_F(ResolverEntryPointValidationTest,
|
|||
|
||||
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 @@ TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_NestedStruct) {
|
|||
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 @@ TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateBuiltins) {
|
|||
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 @@ TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateLocation) {
|
|||
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) {
|
||||
|
|
|
@ -1138,18 +1138,9 @@ bool Resolver::ValidateFunctionParameter(const ast::Function* func,
|
|||
"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::ValidateFunctionParameter(const ast::Function* func,
|
|||
|
||||
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 @@ bool Resolver::ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
|
|||
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,6 +1301,9 @@ bool Resolver::ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
|
|||
break;
|
||||
}
|
||||
|
||||
// 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() +
|
||||
|
@ -1318,6 +1311,7 @@ bool Resolver::ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
|
|||
deco->source());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1409,26 +1403,12 @@ bool Resolver::ValidateFunction(const ast::Function* func,
|
|||
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,
|
||||
if (!deco->IsAnyOf<ast::BuiltinDecoration, ast::InternalDecoration,
|
||||
ast::LocationDecoration, ast::InterpolateDecoration,
|
||||
ast::InvariantDecoration>() &&
|
||||
(IsValidationEnabled(
|
||||
info->declaration->decorations(),
|
||||
(IsValidationEnabled(info->declaration->decorations(),
|
||||
ast::DisabledValidation::kEntryPointParameter) &&
|
||||
IsValidationEnabled(
|
||||
info->declaration->decorations(),
|
||||
IsValidationEnabled(info->declaration->decorations(),
|
||||
ast::DisabledValidation::
|
||||
kIgnoreConstructibleFunctionParameter))) {
|
||||
AddError("decoration is not valid for entry point return types",
|
||||
|
@ -1473,6 +1453,7 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
|
|||
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 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
|
|||
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 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
|
|||
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 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
|
|||
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 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
|
|||
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 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
|
|||
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 @@ bool Resolver::ValidateStructure(const sem::Struct* str) {
|
|||
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) {
|
||||
|
|
|
@ -281,7 +281,8 @@ class Resolver {
|
|||
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);
|
||||
|
|
Loading…
Reference in New Issue