diff --git a/src/BUILD.gn b/src/BUILD.gn index af5eb73154..5d0ae2a8a6 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -448,6 +448,7 @@ libtint_source_set("libtint_core_all_src") { "sem/access_control_type.cc", "sem/access_control_type.h", "sem/array.h", + "sem/binding_point.h", "sem/bool_type.cc", "sem/bool_type.h", "sem/call.h", @@ -496,7 +497,6 @@ libtint_source_set("libtint_core_all_src") { "symbol_table.cc", "symbol_table.h", "traits.h", - "transform/binding_point.h", "transform/binding_remapper.cc", "transform/binding_remapper.h", "transform/bound_array_accessors.cc", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2a90384f41..dc55ff0b9e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -232,6 +232,7 @@ set(TINT_LIB_SRCS scope_stack.h sem/array.cc sem/array.h + sem/binding_point.h sem/call_target.cc sem/call_target.h sem/call.cc @@ -258,7 +259,6 @@ set(TINT_LIB_SRCS symbol.h traits.h typepair.h - transform/binding_point.h transform/binding_remapper.cc transform/binding_remapper.h transform/bound_array_accessors.cc diff --git a/src/ast/module_clone_test.cc b/src/ast/module_clone_test.cc index cf76d6181d..a1f49205f5 100644 --- a/src/ast/module_clone_test.cc +++ b/src/ast/module_clone_test.cc @@ -41,16 +41,16 @@ type t1 = array>; var g0 : u32 = 20u; var g1 : f32 = 123.0; -var g2 : texture_2d; -var g3 : [[access(read)]] texture_storage_2d; -var g4 : [[access(write)]] texture_storage_2d; -var g5 : [[access(read)]] texture_storage_2d; -var g6 : [[access(write)]] texture_storage_2d; +[[group(0), binding(0)]] var g2 : texture_2d; +[[group(1), binding(0)]] var g3 : [[access(read)]] texture_storage_2d; +[[group(2), binding(0)]] var g4 : [[access(write)]] texture_storage_2d; +[[group(3), binding(0)]] var g5 : [[access(read)]] texture_storage_2d; +[[group(4), binding(0)]] var g6 : [[access(write)]] texture_storage_2d; var g7 : vec3; -[[group(10), binding(20)]] var g8 : [[access(write)]] S; -[[group(10), binding(20)]] var g9 : [[access(read)]] S; -[[group(10), binding(20)]] var g10 : [[access(read_write)]] S; +[[group(0), binding(1)]] var g8 : [[access(write)]] S; +[[group(1), binding(1)]] var g9 : [[access(read)]] S; +[[group(2), binding(1)]] var g10 : [[access(read_write)]] S; fn f0(p0 : bool) -> f32 { if (p0) { diff --git a/src/ast/storage_class.cc b/src/ast/storage_class.cc index 62e29a4685..2ec1ca2a49 100644 --- a/src/ast/storage_class.cc +++ b/src/ast/storage_class.cc @@ -17,53 +17,35 @@ namespace tint { namespace ast { -std::ostream& operator<<(std::ostream& out, StorageClass sc) { +const char* str(StorageClass sc) { switch (sc) { - case StorageClass::kInvalid: { - out << "invalid"; - break; - } - case StorageClass::kNone: { - out << "none"; - break; - } - case StorageClass::kInput: { - out << "in"; - break; - } - case StorageClass::kOutput: { - out << "out"; - break; - } - case StorageClass::kUniform: { - out << "uniform"; - break; - } - case StorageClass::kWorkgroup: { - out << "workgroup"; - break; - } - case StorageClass::kUniformConstant: { - out << "uniform_constant"; - break; - } - case StorageClass::kStorage: { - out << "storage"; - break; - } - case StorageClass::kImage: { - out << "image"; - break; - } - case StorageClass::kPrivate: { - out << "private"; - break; - } - case StorageClass::kFunction: { - out << "function"; - break; - } + case StorageClass::kInvalid: + return "invalid"; + case StorageClass::kNone: + return "none"; + case StorageClass::kInput: + return "in"; + case StorageClass::kOutput: + return "out"; + case StorageClass::kUniform: + return "uniform"; + case StorageClass::kWorkgroup: + return "workgroup"; + case StorageClass::kUniformConstant: + return "uniform_constant"; + case StorageClass::kStorage: + return "storage"; + case StorageClass::kImage: + return "image"; + case StorageClass::kPrivate: + return "private"; + case StorageClass::kFunction: + return "function"; } + return ""; +} +std::ostream& operator<<(std::ostream& out, StorageClass sc) { + out << str(sc); return out; } diff --git a/src/ast/storage_class.h b/src/ast/storage_class.h index 8a5cace355..4f263afe18 100644 --- a/src/ast/storage_class.h +++ b/src/ast/storage_class.h @@ -42,6 +42,10 @@ inline bool IsHostShareable(StorageClass sc) { return sc == ast::StorageClass::kUniform || sc == ast::StorageClass::kStorage; } +/// @param sc the StorageClass +/// @return the name of the given storage class +const char* str(StorageClass sc); + /// @param out the std::ostream to write to /// @param sc the StorageClass /// @return the std::ostream so calls can be chained diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc index e5589c8850..23e004ee07 100644 --- a/src/inspector/inspector_test.cc +++ b/src/inspector/inspector_test.cc @@ -381,7 +381,7 @@ class InspectorHelper : public ProgramBuilder { } void AddGlobalVariable(const std::string& name, ast::Type* type) { - Global(name, type, ast::StorageClass::kUniformConstant); + Global(name, type, ast::StorageClass::kPrivate); } /// Adds a depth texture variable to the program diff --git a/src/resolver/assignment_validation_test.cc b/src/resolver/assignment_validation_test.cc index f469a06580..a1fc4ec429 100644 --- a/src/resolver/assignment_validation_test.cc +++ b/src/resolver/assignment_validation_test.cc @@ -244,8 +244,16 @@ TEST_F(ResolverAssignmentValidationTest, AssignFromPointer_Fail) { return ty.access(ast::AccessControl::kReadOnly, tex_type); }; - auto* var_a = Global("a", make_type(), ast::StorageClass::kNone); - auto* var_b = Global("b", make_type(), ast::StorageClass::kNone); + auto* var_a = Global("a", make_type(), ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); + auto* var_b = Global("b", make_type(), ast::StorageClass::kNone, nullptr, + { + create(1), + create(0), + }); WrapInFunction(Assign(Source{{12, 34}}, var_a, var_b)); diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc index 2ec17c57b3..f894498738 100644 --- a/src/resolver/decoration_validation_test.cc +++ b/src/resolver/decoration_validation_test.cc @@ -43,46 +43,63 @@ enum class DecorationKind { kStride, kStructBlock, kWorkgroup, + + kBindingAndGroup, }; + +bool IsBindingDecoration(DecorationKind kind) { + switch (kind) { + case DecorationKind::kBinding: + case DecorationKind::kGroup: + case DecorationKind::kBindingAndGroup: + return true; + default: + return false; + } +} + struct TestParams { DecorationKind kind; bool should_pass; }; struct TestWithParams : ResolverTestWithParam {}; -static ast::Decoration* createDecoration(const Source& source, - ProgramBuilder& builder, - DecorationKind kind) { +static ast::DecorationList createDecorations(const Source& source, + ProgramBuilder& builder, + DecorationKind kind) { switch (kind) { case DecorationKind::kAccess: - return builder.create( - source, ast::AccessControl::kReadOnly); + return {builder.create( + source, ast::AccessControl::kReadOnly)}; case DecorationKind::kAlign: - return builder.create(source, 4u); + return {builder.create(source, 4u)}; case DecorationKind::kBinding: - return builder.create(source, 1); + return {builder.create(source, 1u)}; case DecorationKind::kBuiltin: - return builder.Builtin(source, ast::Builtin::kPosition); + return {builder.Builtin(source, ast::Builtin::kPosition)}; case DecorationKind::kGroup: - return builder.create(source, 1u); + return {builder.create(source, 1u)}; case DecorationKind::kLocation: - return builder.Location(source, 1); + return {builder.Location(source, 1)}; case DecorationKind::kOverride: - return builder.create(source, 0u); + return {builder.create(source, 0u)}; case DecorationKind::kOffset: - return builder.create(source, 4u); + return {builder.create(source, 4u)}; case DecorationKind::kSize: - return builder.create(source, 4u); + return {builder.create(source, 4u)}; case DecorationKind::kStage: - return builder.Stage(source, ast::PipelineStage::kCompute); + return {builder.Stage(source, ast::PipelineStage::kCompute)}; case DecorationKind::kStride: - return builder.create(source, 4u); + return {builder.create(source, 4u)}; case DecorationKind::kStructBlock: - return builder.create(source); + return {builder.create(source)}; case DecorationKind::kWorkgroup: - return builder.create(source, 1u, 1u, 1u); + return {builder.create(source, 1u, 1u, 1u)}; + case DecorationKind::kBindingAndGroup: + return {builder.create(source, 1u), + builder.create(source, 1u)}; } - return nullptr; + return {}; } using FunctionReturnTypeDecorationTest = TestWithParams; @@ -91,7 +108,7 @@ TEST_P(FunctionReturnTypeDecorationTest, IsValid) { Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)}, ast::DecorationList{Stage(ast::PipelineStage::kCompute)}, - ast::DecorationList{createDecoration({}, *this, params.kind)}); + createDecorations({}, *this, params.kind)); if (params.should_pass) { EXPECT_TRUE(r()->Resolve()) << r()->error(); @@ -116,17 +133,15 @@ INSTANTIATE_TEST_SUITE_P( TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStructBlock, false}, - TestParams{DecorationKind::kWorkgroup, false})); + TestParams{DecorationKind::kWorkgroup, false}, + TestParams{DecorationKind::kBindingAndGroup, false})); using ArrayDecorationTest = TestWithParams; TEST_P(ArrayDecorationTest, IsValid) { auto& params = GetParam(); - auto* arr = - ty.array(ty.f32(), 0, - { - createDecoration(Source{{12, 34}}, *this, params.kind), - }); + auto* arr = ty.array(ty.f32(), 0, + createDecorations(Source{{12, 34}}, *this, params.kind)); Structure("mystruct", { Member("a", arr), @@ -158,14 +173,15 @@ INSTANTIATE_TEST_SUITE_P( TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStride, true}, TestParams{DecorationKind::kStructBlock, false}, - TestParams{DecorationKind::kWorkgroup, false})); + TestParams{DecorationKind::kWorkgroup, false}, + TestParams{DecorationKind::kBindingAndGroup, false})); using StructDecorationTest = TestWithParams; TEST_P(StructDecorationTest, IsValid) { auto& params = GetParam(); Structure("mystruct", {}, - {createDecoration(Source{{12, 34}}, *this, params.kind)}); + createDecorations(Source{{12, 34}}, *this, params.kind)); WrapInFunction(); @@ -192,16 +208,15 @@ INSTANTIATE_TEST_SUITE_P( TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStructBlock, true}, - TestParams{DecorationKind::kWorkgroup, false})); + TestParams{DecorationKind::kWorkgroup, false}, + TestParams{DecorationKind::kBindingAndGroup, false})); using StructMemberDecorationTest = TestWithParams; TEST_P(StructMemberDecorationTest, IsValid) { auto& params = GetParam(); - ast::StructMemberList members{ - Member("a", ty.i32(), - ast::DecorationList{ - createDecoration(Source{{12, 34}}, *this, params.kind)})}; + ast::StructMemberList members{Member( + "a", ty.i32(), createDecorations(Source{{12, 34}}, *this, params.kind))}; Structure("mystruct", members); @@ -230,15 +245,21 @@ INSTANTIATE_TEST_SUITE_P( TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStructBlock, false}, - TestParams{DecorationKind::kWorkgroup, false})); + TestParams{DecorationKind::kWorkgroup, false}, + TestParams{DecorationKind::kBindingAndGroup, false})); using VariableDecorationTest = TestWithParams; TEST_P(VariableDecorationTest, IsValid) { auto& params = GetParam(); - Global("a", ty.f32(), ast::StorageClass::kInput, nullptr, - ast::DecorationList{ - createDecoration(Source{{12, 34}}, *this, params.kind)}); + if (IsBindingDecoration(params.kind)) { + Global("a", ty.sampler(ast::SamplerKind::kSampler), + ast::StorageClass::kNone, nullptr, + createDecorations(Source{{12, 34}}, *this, params.kind)); + } else { + Global("a", ty.f32(), ast::StorageClass::kInput, nullptr, + createDecorations(Source{{12, 34}}, *this, params.kind)); + } WrapInFunction(); @@ -246,8 +267,10 @@ TEST_P(VariableDecorationTest, IsValid) { EXPECT_TRUE(r()->Resolve()) << r()->error(); } else { EXPECT_FALSE(r()->Resolve()) << r()->error(); - EXPECT_EQ(r()->error(), - "12:34 error: decoration is not valid for variables"); + if (!IsBindingDecoration(params.kind)) { + EXPECT_EQ(r()->error(), + "12:34 error: decoration is not valid for variables"); + } } } INSTANTIATE_TEST_SUITE_P( @@ -255,9 +278,9 @@ INSTANTIATE_TEST_SUITE_P( VariableDecorationTest, testing::Values(TestParams{DecorationKind::kAccess, false}, TestParams{DecorationKind::kAlign, false}, - TestParams{DecorationKind::kBinding, true}, + TestParams{DecorationKind::kBinding, false}, TestParams{DecorationKind::kBuiltin, true}, - TestParams{DecorationKind::kGroup, true}, + TestParams{DecorationKind::kGroup, false}, TestParams{DecorationKind::kLocation, true}, TestParams{DecorationKind::kOverride, false}, TestParams{DecorationKind::kOffset, false}, @@ -265,15 +288,15 @@ INSTANTIATE_TEST_SUITE_P( TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStructBlock, false}, - TestParams{DecorationKind::kWorkgroup, false})); + TestParams{DecorationKind::kWorkgroup, false}, + TestParams{DecorationKind::kBindingAndGroup, true})); using ConstantDecorationTest = TestWithParams; TEST_P(ConstantDecorationTest, IsValid) { auto& params = GetParam(); - GlobalConst("a", ty.f32(), nullptr, - ast::DecorationList{ - createDecoration(Source{{12, 34}}, *this, params.kind)}); + GlobalConst("a", ty.f32(), Expr(1.23f), + createDecorations(Source{{12, 34}}, *this, params.kind)); WrapInFunction(); @@ -300,16 +323,17 @@ INSTANTIATE_TEST_SUITE_P( TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStructBlock, false}, - TestParams{DecorationKind::kWorkgroup, false})); + TestParams{DecorationKind::kWorkgroup, false}, + TestParams{DecorationKind::kBindingAndGroup, false})); using FunctionDecorationTest = TestWithParams; TEST_P(FunctionDecorationTest, IsValid) { auto& params = GetParam(); - Func("foo", ast::VariableList{}, ty.void_(), ast::StatementList{}, - ast::DecorationList{ - Stage(ast::PipelineStage::kCompute), - createDecoration(Source{{12, 34}}, *this, params.kind)}); + ast::DecorationList decos = + createDecorations(Source{{12, 34}}, *this, params.kind); + decos.emplace_back(Stage(ast::PipelineStage::kCompute)); + Func("foo", ast::VariableList{}, ty.void_(), ast::StatementList{}, decos); if (params.should_pass) { EXPECT_TRUE(r()->Resolve()) << r()->error(); @@ -334,7 +358,8 @@ INSTANTIATE_TEST_SUITE_P( // Skip kStage as we always apply it in this test TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStructBlock, false}, - TestParams{DecorationKind::kWorkgroup, true})); + TestParams{DecorationKind::kWorkgroup, true}, + TestParams{DecorationKind::kBindingAndGroup, false})); } // namespace } // namespace DecorationTests @@ -480,5 +505,158 @@ TEST_F(StructBlockTest, StructUsedAsArrayElement) { } // namespace } // namespace StructBlockTests +namespace ResourceTests { +namespace { + +using ResourceDecorationTest = ResolverTest; +TEST_F(ResourceDecorationTest, UniformBufferMissingBinding) { + auto* s = Structure("S", {Member("x", ty.i32())}, + {create()}); + Global(Source{{12, 34}}, "G", s, ast::StorageClass::kUniform); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), + "12:34 error: resource variables require [[group]] and [[binding]] " + "decorations"); +} + +TEST_F(ResourceDecorationTest, StorageBufferMissingBinding) { + auto* s = Structure("S", {Member("x", ty.i32())}, + {create()}); + auto ac = ty.access(ast::AccessControl::kReadOnly, s); + Global(Source{{12, 34}}, "G", ac, ast::StorageClass::kStorage); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), + "12:34 error: resource variables require [[group]] and [[binding]] " + "decorations"); +} + +TEST_F(ResourceDecorationTest, TextureMissingBinding) { + Global(Source{{12, 34}}, "G", ty.depth_texture(ast::TextureDimension::k2d), + ast::StorageClass::kNone); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), + "12:34 error: resource variables require [[group]] and [[binding]] " + "decorations"); +} + +TEST_F(ResourceDecorationTest, SamplerMissingBinding) { + Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler), + ast::StorageClass::kNone); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), + "12:34 error: resource variables require [[group]] and [[binding]] " + "decorations"); +} + +TEST_F(ResourceDecorationTest, BindingPairMissingBinding) { + Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler), + ast::StorageClass::kNone, nullptr, + { + create(1), + }); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), + "12:34 error: resource variables require [[group]] and [[binding]] " + "decorations"); +} + +TEST_F(ResourceDecorationTest, BindingPairMissingGroup) { + Global(Source{{12, 34}}, "G", ty.sampler(ast::SamplerKind::kSampler), + ast::StorageClass::kNone, nullptr, + { + create(1), + }); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), + "12:34 error: resource variables require [[group]] and [[binding]] " + "decorations"); +} + +TEST_F(ResourceDecorationTest, BindingPointUsedTwiceByEntryPoint) { + Global(Source{{12, 34}}, "A", + ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), + ast::StorageClass::kNone, nullptr, + { + create(1), + create(2), + }); + Global(Source{{56, 78}}, "B", + ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), + ast::StorageClass::kNone, nullptr, + { + create(1), + create(2), + }); + + Func("F", {}, ty.void_(), + { + Decl(Var("a", ty.vec4(), ast::StorageClass::kFunction, + Call("textureLoad", "A", vec2(1, 2), 0))), + Decl(Var("b", ty.vec4(), ast::StorageClass::kFunction, + Call("textureLoad", "B", vec2(1, 2), 0))), + }, + {Stage(ast::PipelineStage::kFragment)}); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ( + r()->error(), + R"(56:78 error: entry point 'F' references multiple variables that use the same resource binding [[group(2), binding(1)]] +12:34 note: first resource binding usage declared here)"); +} + +TEST_F(ResourceDecorationTest, BindingPointUsedTwiceByDifferentEntryPoints) { + Global(Source{{12, 34}}, "A", + ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), + ast::StorageClass::kNone, nullptr, + { + create(1), + create(2), + }); + Global(Source{{56, 78}}, "B", + ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()), + ast::StorageClass::kNone, nullptr, + { + create(1), + create(2), + }); + + Func("F_A", {}, ty.void_(), + { + Decl(Var("a", ty.vec4(), ast::StorageClass::kFunction, + Call("textureLoad", "A", vec2(1, 2), 0))), + }, + {Stage(ast::PipelineStage::kFragment)}); + Func("F_B", {}, ty.void_(), + { + Decl(Var("b", ty.vec4(), ast::StorageClass::kFunction, + Call("textureLoad", "B", vec2(1, 2), 0))), + }, + {Stage(ast::PipelineStage::kFragment)}); + + EXPECT_TRUE(r()->Resolve()) << r()->error(); +} + +TEST_F(ResourceDecorationTest, BindingPointOnNonResource) { + Global(Source{{12, 34}}, "G", ty.f32(), ast::StorageClass::kPrivate, nullptr, + { + create(1), + create(2), + }); + + EXPECT_FALSE(r()->Resolve()); + EXPECT_EQ(r()->error(), + "12:34 error: non-resource variables must not have [[group]] or " + "[[binding]] decorations"); +} + +} // namespace +} // namespace ResourceTests + } // namespace resolver } // namespace tint diff --git a/src/resolver/host_shareable_validation_test.cc b/src/resolver/host_shareable_validation_test.cc index 25bcfa5c6b..1f5fb08f46 100644 --- a/src/resolver/host_shareable_validation_test.cc +++ b/src/resolver/host_shareable_validation_test.cc @@ -30,7 +30,11 @@ TEST_F(ResolverHostShareableValidationTest, BoolMember) { auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())}, {create()}); auto a = ty.access(ast::AccessControl::kReadOnly, s); - Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage); + Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -45,7 +49,11 @@ TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) { auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3())}, {create()}); auto a = ty.access(ast::AccessControl::kReadOnly, s); - Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage); + Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -64,7 +72,11 @@ TEST_F(ResolverHostShareableValidationTest, Aliases) { auto ac = ty.access(ast::AccessControl::kReadOnly, s); auto* a2 = ty.alias("a2", ac); AST().AddConstructedType(a2); - Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage); + Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -83,7 +95,11 @@ TEST_F(ResolverHostShareableValidationTest, NestedStructures) { auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)}, {create()}); auto a = ty.access(ast::AccessControl::kReadOnly, s); - Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage); + Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -122,7 +138,11 @@ TEST_F(ResolverHostShareableValidationTest, NoError) { auto* s = Structure("S", {Member(Source{{7, 8}}, "m", i3)}, {create()}); auto a = ty.access(ast::AccessControl::kReadOnly, s); - Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage); + Global(Source{{9, 10}}, "g", a, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_TRUE(r()->Resolve()) << r()->error(); } diff --git a/src/resolver/intrinsic_test.cc b/src/resolver/intrinsic_test.cc index 58811f6fbe..a8215517c7 100644 --- a/src/resolver/intrinsic_test.cc +++ b/src/resolver/intrinsic_test.cc @@ -243,10 +243,17 @@ class ResolverIntrinsicTest_TextureOperation void add_call_param(std::string name, typ::Type type, ast::ExpressionList* call_params) { - ast::StorageClass storage_class = type->UnwrapAll()->is_handle() - ? ast::StorageClass::kNone - : ast::StorageClass::kPrivate; - Global(name, type, storage_class); + if (type->UnwrapAll()->is_handle()) { + Global(name, type, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); + + } else { + Global(name, type, ast::StorageClass::kPrivate); + } + call_params->push_back(Expr(name)); } typ::Type subtype(Texture type) { @@ -763,7 +770,11 @@ TEST_F(ResolverIntrinsicDataTest, ArrayLength_Vector) { auto* str = Structure("S", {Member("x", ary)}, {create()}); auto ac = ty.access(ast::AccessControl::kReadOnly, str); - Global("a", ac, ast::StorageClass::kStorage); + Global("a", ac, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); auto* call = Call("arrayLength", MemberAccessor("a", "x")); WrapInFunction(call); diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc index cfda9a5d6a..4c6eb94e8a 100644 --- a/src/resolver/resolver.cc +++ b/src/resolver/resolver.cc @@ -466,20 +466,10 @@ bool Resolver::GlobalVariable(ast::Variable* var) { for (auto* deco : var->decorations()) { Mark(deco); - if (var->is_const()) { - if (!deco->Is()) { - diagnostics_.add_error("decoration is not valid for constants", - deco->source()); - return false; - } - } else if (!(deco->Is() || - deco->Is() || - deco->Is() || - deco->Is())) { - diagnostics_.add_error("decoration is not valid for variables", - deco->source()); - return false; - } + } + + if (auto bp = var->binding_point()) { + info->binding_point = {bp.group->value(), bp.binding->value()}; } if (var->has_constructor()) { @@ -512,6 +502,53 @@ bool Resolver::GlobalVariable(ast::Variable* var) { } bool Resolver::ValidateGlobalVariable(const VariableInfo* info) { + for (auto* deco : info->declaration->decorations()) { + if (info->declaration->is_const()) { + if (!deco->Is()) { + diagnostics_.add_error("decoration is not valid for constants", + deco->source()); + return false; + } + } else { + if (!(deco->Is() || + deco->Is() || + deco->Is() || + deco->Is())) { + diagnostics_.add_error("decoration is not valid for variables", + deco->source()); + return false; + } + } + } + + auto binding_point = info->declaration->binding_point(); + switch (info->storage_class) { + case ast::StorageClass::kUniform: + case ast::StorageClass::kStorage: + case ast::StorageClass::kUniformConstant: { + // https://gpuweb.github.io/gpuweb/wgsl/#resource-interface + // Each resource variable must be declared with both group and binding + // attributes. + if (!binding_point) { + diagnostics_.add_error( + "resource variables require [[group]] and [[binding]] decorations", + info->declaration->source()); + return false; + } + break; + } + default: + if (binding_point.binding || binding_point.group) { + // https://gpuweb.github.io/gpuweb/wgsl/#attribute-binding + // Must only be applied to a resource variable + diagnostics_.add_error( + "non-resource variables must not have [[group]] or [[binding]] " + "decorations", + info->declaration->source()); + return false; + } + } + switch (info->storage_class) { case ast::StorageClass::kStorage: { // https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration @@ -945,6 +982,33 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func, } } + // Validate there are no resource variable binding collisions + std::unordered_map binding_points; + for (auto* var_info : info->referenced_module_vars) { + if (!var_info->declaration->binding_point()) { + continue; + } + auto bp = var_info->binding_point; + auto res = binding_points.emplace(bp, var_info->declaration); + if (!res.second) { + // https://gpuweb.github.io/gpuweb/wgsl/#resource-interface + // Bindings must not alias within a shader stage: two different + // variables in the resource interface of a given shader must not have + // the same group and binding values, when considered as a pair of + // values. + auto func_name = builder_->Symbols().NameFor(info->declaration->symbol()); + diagnostics_.add_error("entry point '" + func_name + + "' references multiple variables that use the " + "same resource binding [[group(" + + std::to_string(bp.group) + "), binding(" + + std::to_string(bp.binding) + ")]]", + var_info->declaration->source()); + diagnostics_.add_note("first resource binding usage declared here", + res.first->second->source()); + return false; + } + } + return true; } diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h index c537eae1a5..02dce4f1cc 100644 --- a/src/resolver/resolver.h +++ b/src/resolver/resolver.h @@ -24,6 +24,7 @@ #include "src/intrinsic_table.h" #include "src/program_builder.h" #include "src/scope_stack.h" +#include "src/sem/binding_point.h" #include "src/sem/struct.h" #include "src/utils/unique_vector.h" @@ -97,6 +98,7 @@ class Resolver { std::string const type_name; ast::StorageClass storage_class; std::vector users; + sem::BindingPoint binding_point; }; /// Structure holding semantic information about a function. diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc index 323434812f..fcfde03352 100644 --- a/src/resolver/resolver_test.cc +++ b/src/resolver/resolver_test.cc @@ -665,7 +665,11 @@ TEST_F(ResolverTest, Expr_Identifier_FunctionVariable) { auto* my_var_b = Expr("my_var"); auto* assign = Assign(my_var_a, my_var_b); - auto* var = Var("my_var", ty.f32(), ast::StorageClass::kNone); + auto* var = Var("my_var", ty.f32(), ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); Func("my_func", ast::VariableList{}, ty.void_(), ast::StatementList{ @@ -770,7 +774,11 @@ TEST_F(ResolverTest, Function_RegisterInputOutputVariables) { auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput); auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput); - auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage); + auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup); auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate); @@ -806,7 +814,11 @@ TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) { auto* in_var = Global("in_var", ty.f32(), ast::StorageClass::kInput); auto* out_var = Global("out_var", ty.f32(), ast::StorageClass::kOutput); - auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage); + auto* sb_var = Global("sb_var", a, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); auto* wg_var = Global("wg_var", ty.f32(), ast::StorageClass::kWorkgroup); auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate); @@ -1447,7 +1459,11 @@ INSTANTIATE_TEST_SUITE_P(ResolverTest, ast::UnaryOp::kNot)); TEST_F(ResolverTest, StorageClass_SetsIfMissing) { - auto* var = Var("var", ty.i32(), ast::StorageClass::kNone); + auto* var = Var("var", ty.i32(), ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); auto* stmt = Decl(var); Func("func", ast::VariableList{}, ty.void_(), ast::StatementList{stmt}, @@ -1460,7 +1476,11 @@ TEST_F(ResolverTest, StorageClass_SetsIfMissing) { TEST_F(ResolverTest, StorageClass_SetForSampler) { auto t = ty.sampler(ast::SamplerKind::kSampler); - auto* var = Global("var", t, ast::StorageClass::kNone); + auto* var = Global("var", t, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); EXPECT_TRUE(r()->Resolve()) << r()->error(); @@ -1471,7 +1491,11 @@ TEST_F(ResolverTest, StorageClass_SetForSampler) { TEST_F(ResolverTest, StorageClass_SetForTexture) { auto t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); auto ac = ty.access(ast::AccessControl::Access::kReadOnly, t); - auto* var = Global("var", ac, ast::StorageClass::kNone); + auto* var = Global("var", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); EXPECT_TRUE(r()->Resolve()) << r()->error(); diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc index 9c36597bf3..0db641021e 100644 --- a/src/resolver/storage_class_validation_test.cc +++ b/src/resolver/storage_class_validation_test.cc @@ -36,8 +36,12 @@ TEST_F(ResolverStorageClassValidationTest, GlobalVariableNoStorageClass_Fail) { } TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) { - // var g : bool; - Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage); + // var g : i32; + Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -49,7 +53,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) { TEST_F(ResolverStorageClassValidationTest, StorageBufferPointer) { // var g : ptr; Global(Source{{56, 78}}, "g", ty.pointer(ast::StorageClass::kInput), - ast::StorageClass::kStorage); + ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -63,7 +71,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferArray) { auto* s = Structure("S", {Member("a", ty.f32())}); auto* a = ty.array(s, 3); auto ac = ty.access(ast::AccessControl::kReadOnly, a); - Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kStorage); + Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -77,7 +89,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferBoolAlias) { // var g : [[access(read)]] a; auto* a = ty.alias("a", ty.bool_()); AST().AddConstructedType(a); - Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage); + Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -89,7 +105,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferBoolAlias) { TEST_F(ResolverStorageClassValidationTest, StorageBufferNoAccessControl) { // var g : S; auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())}); - Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage); + Global(Source{{56, 78}}, "g", s, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -103,7 +123,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferNoBlockDecoration) { // var g : [[access(read)]] S; auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())}); auto a = ty.access(ast::AccessControl::kReadOnly, s); - Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage); + Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -119,7 +143,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Basic) { auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())}, {create()}); auto a = ty.access(ast::AccessControl::kReadOnly, s); - Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage); + Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_TRUE(r()->Resolve()); } @@ -136,7 +164,11 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Aliases) { auto ac = ty.access(ast::AccessControl::kReadOnly, a1); auto* a2 = ty.alias("a2", ac); AST().AddConstructedType(a2); - Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage); + Global(Source{{56, 78}}, "g", a2, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); ASSERT_TRUE(r()->Resolve()); } @@ -145,7 +177,12 @@ TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Aliases) { TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) { // var g : bool; - Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform); + Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform, + nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -157,7 +194,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) { TEST_F(ResolverStorageClassValidationTest, UniformBufferPointer) { // var g : ptr; Global(Source{{56, 78}}, "g", ty.pointer(ast::StorageClass::kInput), - ast::StorageClass::kUniform); + ast::StorageClass::kUniform, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -171,7 +212,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferArray) { auto* s = Structure("S", {Member("a", ty.f32())}); auto* a = ty.array(s, 3); auto ac = ty.access(ast::AccessControl::kReadOnly, a); - Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kUniform); + Global(Source{{56, 78}}, "g", ac, ast::StorageClass::kUniform, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -185,7 +230,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferBoolAlias) { // var g : [[access(read)]] a; auto* a = ty.alias("a", ty.bool_()); AST().AddConstructedType(a); - Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform); + Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -198,7 +247,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferNoBlockDecoration) { // struct S { x : i32 }; // var g : S; auto* s = Structure(Source{{12, 34}}, "S", {Member("x", ty.i32())}); - Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform); + Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform, nullptr, + { + create(0), + create(0), + }); ASSERT_FALSE(r()->Resolve()); @@ -213,7 +266,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Basic) { // var g : S; auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())}, {create()}); - Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform); + Global(Source{{56, 78}}, "g", s, ast::StorageClass::kUniform, nullptr, + { + create(0), + create(0), + }); ASSERT_TRUE(r()->Resolve()); } @@ -226,7 +283,11 @@ TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Aliases) { {create()}); auto* a1 = ty.alias("a1", s); AST().AddConstructedType(a1); - Global(Source{{56, 78}}, "g", a1, ast::StorageClass::kUniform); + Global(Source{{56, 78}}, "g", a1, ast::StorageClass::kUniform, nullptr, + { + create(0), + create(0), + }); ASSERT_TRUE(r()->Resolve()); } diff --git a/src/resolver/struct_storage_class_use_test.cc b/src/resolver/struct_storage_class_use_test.cc index b432833964..89f58bdfa4 100644 --- a/src/resolver/struct_storage_class_use_test.cc +++ b/src/resolver/struct_storage_class_use_test.cc @@ -173,8 +173,16 @@ TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) { auto* s = Structure("S", {Member("a", ty.f32())}, {create()}); auto ac = ty.access(ast::AccessControl::kReadOnly, s); - Global("x", s, ast::StorageClass::kUniform); - Global("y", ac, ast::StorageClass::kStorage); + Global("x", s, ast::StorageClass::kUniform, nullptr, + { + create(0), + create(0), + }); + Global("y", ac, ast::StorageClass::kStorage, nullptr, + { + create(1), + create(0), + }); WrapInFunction(Var("g", s, ast::StorageClass::kFunction)); ASSERT_TRUE(r()->Resolve()) << r()->error(); diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc index ba3dde3d3c..53c087c2ce 100644 --- a/src/resolver/validation_test.cc +++ b/src/resolver/validation_test.cc @@ -377,7 +377,12 @@ TEST_F(ResolverValidationTest, StorageClass_NonFunctionClassError) { TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) { auto t = ty.sampler(ast::SamplerKind::kSampler); - Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant); + Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant, + nullptr, + { + create(0), + create(0), + }); EXPECT_FALSE(r()->Resolve()); @@ -388,7 +393,12 @@ TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) { TEST_F(ResolverValidationTest, StorageClass_TextureExplicitStorageClass) { auto t = ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); - Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant); + Global(Source{{12, 34}}, "var", t, ast::StorageClass::kUniformConstant, + nullptr, + { + create(0), + create(0), + }); EXPECT_FALSE(r()->Resolve()) << r()->error(); diff --git a/src/transform/binding_point.h b/src/sem/binding_point.h similarity index 83% rename from src/transform/binding_point.h rename to src/sem/binding_point.h index 131da44bd0..e245f8b8ba 100644 --- a/src/transform/binding_point.h +++ b/src/sem/binding_point.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef SRC_TRANSFORM_BINDING_POINT_H_ -#define SRC_TRANSFORM_BINDING_POINT_H_ +#ifndef SRC_SEM_BINDING_POINT_H_ +#define SRC_SEM_BINDING_POINT_H_ #include @@ -22,7 +22,7 @@ #include "src/utils/hash.h" namespace tint { -namespace transform { +namespace sem { /// BindingPoint holds a group and binding index. struct BindingPoint { @@ -46,25 +46,25 @@ struct BindingPoint { } }; -} // namespace transform +} // namespace sem } // namespace tint namespace std { -/// Custom std::hash specialization for tint::transform::BindingPoint so +/// Custom std::hash specialization for tint::sem::BindingPoint so /// BindingPoints can be used as keys for std::unordered_map and /// std::unordered_set. template <> -class hash { +class hash { public: /// @param binding_point the binding point to create a hash for /// @return the hash value inline std::size_t operator()( - const tint::transform::BindingPoint& binding_point) const { + const tint::sem::BindingPoint& binding_point) const { return tint::utils::Hash(binding_point.group, binding_point.binding); } }; } // namespace std -#endif // SRC_TRANSFORM_BINDING_POINT_H_ +#endif // SRC_SEM_BINDING_POINT_H_ diff --git a/src/transform/binding_remapper.h b/src/transform/binding_remapper.h index 4b6ef83cbc..1f004b00e2 100644 --- a/src/transform/binding_remapper.h +++ b/src/transform/binding_remapper.h @@ -18,12 +18,15 @@ #include #include "src/ast/access_control.h" -#include "src/transform/binding_point.h" +#include "src/sem/binding_point.h" #include "src/transform/transform.h" namespace tint { namespace transform { +/// BindingPoint is an alias to sem::BindingPoint +using BindingPoint = sem::BindingPoint; + /// BindingRemapper is a transform used to remap resource binding points and /// access controls. class BindingRemapper : public Transform { diff --git a/src/transform/bound_array_accessors_test.cc b/src/transform/bound_array_accessors_test.cc index c8cb2b49b9..00414f1890 100644 --- a/src/transform/bound_array_accessors_test.cc +++ b/src/transform/bound_array_accessors_test.cc @@ -540,7 +540,7 @@ struct S { a : f32; b : array; }; -var s : [[access(read)]] S; +[[group(0), binding(0)]] var s : [[access(read)]] S; fn f() { var d : f32 = s.b[25]; @@ -554,7 +554,7 @@ struct S { b : array; }; -var s : [[access(read)]] S; +[[group(0), binding(0)]] var s : [[access(read)]] S; fn f() { var d : f32 = s.b[min(u32(25), (arrayLength(s.b) - 1u))]; @@ -601,7 +601,7 @@ struct S { b : array; }; -var s : [[access(read)]] S; +[[group(0), binding(0)]] var s : [[access(read)]] S; let c : u32 = 1u; @@ -619,7 +619,7 @@ struct S { b : array; }; -var s : [[access(read)]] S; +[[group(0), binding(0)]] var s : [[access(read)]] S; let c : u32 = 1u; diff --git a/src/transform/calculate_array_length_test.cc b/src/transform/calculate_array_length_test.cc index 36bbfbaac9..30083c3c2b 100644 --- a/src/transform/calculate_array_length_test.cc +++ b/src/transform/calculate_array_length_test.cc @@ -30,7 +30,7 @@ struct SB { arr : array; }; -var sb : [[access(read)]] SB; +[[group(0), binding(0)]] var sb : [[access(read)]] SB; [[stage(compute)]] fn main() { @@ -48,7 +48,7 @@ struct SB { [[internal(intrinsic_buffer_size)]] fn tint_symbol(buffer : SB, result : ptr) -var sb : [[access(read)]] SB; +[[group(0), binding(0)]] var sb : [[access(read)]] SB; [[stage(compute)]] fn main() { @@ -72,7 +72,7 @@ struct SB { arr : array; }; -var sb : [[access(read)]] SB; +[[group(0), binding(0)]] var sb : [[access(read)]] SB; [[stage(compute)]] fn main() { @@ -92,7 +92,7 @@ struct SB { [[internal(intrinsic_buffer_size)]] fn tint_symbol(buffer : SB, result : ptr) -var sb : [[access(read)]] SB; +[[group(0), binding(0)]] var sb : [[access(read)]] SB; [[stage(compute)]] fn main() { @@ -119,7 +119,7 @@ struct SB { arr : [[stride(64)]] array; }; -var sb : [[access(read)]] SB; +[[group(0), binding(0)]] var sb : [[access(read)]] SB; [[stage(compute)]] fn main() { @@ -138,7 +138,7 @@ struct SB { [[internal(intrinsic_buffer_size)]] fn tint_symbol(buffer : SB, result : ptr) -var sb : [[access(read)]] SB; +[[group(0), binding(0)]] var sb : [[access(read)]] SB; [[stage(compute)]] fn main() { @@ -162,7 +162,7 @@ struct SB { arr : array; }; -var sb : [[access(read)]] SB; +[[group(0), binding(0)]] var sb : [[access(read)]] SB; [[stage(compute)]] fn main() { @@ -186,7 +186,7 @@ struct SB { [[internal(intrinsic_buffer_size)]] fn tint_symbol(buffer : SB, result : ptr) -var sb : [[access(read)]] SB; +[[group(0), binding(0)]] var sb : [[access(read)]] SB; [[stage(compute)]] fn main() { @@ -225,9 +225,9 @@ struct SB2 { arr2 : array>; }; -var sb1 : [[access(read)]] SB1; +[[group(0), binding(0)]] var sb1 : [[access(read)]] SB1; -var sb2 : [[access(read)]] SB2; +[[group(0), binding(1)]] var sb2 : [[access(read)]] SB2; [[stage(compute)]] fn main() { @@ -256,9 +256,9 @@ struct SB2 { [[internal(intrinsic_buffer_size)]] fn tint_symbol_3(buffer : SB2, result : ptr) -var sb1 : [[access(read)]] SB1; +[[group(0), binding(0)]] var sb1 : [[access(read)]] SB1; -var sb2 : [[access(read)]] SB2; +[[group(0), binding(1)]] var sb2 : [[access(read)]] SB2; [[stage(compute)]] fn main() { diff --git a/src/transform/decompose_storage_access_test.cc b/src/transform/decompose_storage_access_test.cc index dadf24c225..9de479c079 100644 --- a/src/transform/decompose_storage_access_test.cc +++ b/src/transform/decompose_storage_access_test.cc @@ -50,7 +50,7 @@ struct SB { v : array, 2>; }; -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -182,7 +182,7 @@ fn tint_symbol_21(buffer : [[access(read_write)]] SB, offset : u32) -> array, 2>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u))); } -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -244,7 +244,7 @@ struct SB { v : array, 2>; }; -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -395,7 +395,7 @@ fn tint_symbol_21(buffer : [[access(read_write)]] SB, offset : u32, value : arra tint_symbol_8(buffer, (offset + 16u), value[1u]); } -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -457,7 +457,7 @@ struct SB { v : array, 2>; }; -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -572,7 +572,7 @@ fn tint_symbol_22(buffer : [[access(read_write)]] SB, offset : u32) -> SB { return SB(tint_symbol(buffer, (offset + 0u)), tint_symbol_1(buffer, (offset + 4u)), tint_symbol_2(buffer, (offset + 8u)), tint_symbol_3(buffer, (offset + 16u)), tint_symbol_4(buffer, (offset + 24u)), tint_symbol_5(buffer, (offset + 32u)), tint_symbol_6(buffer, (offset + 48u)), tint_symbol_7(buffer, (offset + 64u)), tint_symbol_8(buffer, (offset + 80u)), tint_symbol_9(buffer, (offset + 96u)), tint_symbol_10(buffer, (offset + 112u)), tint_symbol_11(buffer, (offset + 128u)), tint_symbol_12(buffer, (offset + 144u)), tint_symbol_13(buffer, (offset + 160u)), tint_symbol_14(buffer, (offset + 192u)), tint_symbol_15(buffer, (offset + 224u)), tint_symbol_16(buffer, (offset + 256u)), tint_symbol_17(buffer, (offset + 304u)), tint_symbol_18(buffer, (offset + 352u)), tint_symbol_19(buffer, (offset + 384u)), tint_symbol_20(buffer, (offset + 448u)), tint_symbol_21(buffer, (offset + 512u))); } -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -613,7 +613,7 @@ struct SB { v : array, 2>; }; -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -768,7 +768,7 @@ fn tint_symbol_22(buffer : [[access(read_write)]] SB, offset : u32, value : SB) tint_symbol_21(buffer, (offset + 512u), value.v); } -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -802,7 +802,7 @@ struct SB { b : [[stride(256)]] array; }; -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -840,7 +840,7 @@ struct SB { [[internal(intrinsic_load_f32)]] fn tint_symbol(buffer : [[access(read_write)]] SB, offset : u32) -> f32 -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -874,7 +874,7 @@ struct SB { b : [[stride(256)]] array; }; -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -908,7 +908,7 @@ struct SB { [[internal(intrinsic_load_f32)]] fn tint_symbol(buffer : [[access(read_write)]] SB, offset : u32) -> f32 -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -953,7 +953,7 @@ struct SB { b : A2_Array; }; -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { @@ -995,7 +995,7 @@ struct SB { [[internal(intrinsic_load_f32)]] fn tint_symbol(buffer : [[access(read_write)]] SB, offset : u32) -> f32 -var sb : [[access(read_write)]] SB; +[[group(0), binding(0)]] var sb : [[access(read_write)]] SB; [[stage(compute)]] fn main() { diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc index 6354625bd6..e3252f0899 100644 --- a/src/writer/hlsl/generator_impl_type_test.cc +++ b/src/writer/hlsl/generator_impl_type_test.cc @@ -183,7 +183,11 @@ TEST_F(HlslGeneratorImplTest_Type, EmitType_StructDecl_OmittedIfStorageBuffer) { }, {create()}); Global("g", ty.access(ast::AccessControl::kReadWrite, s), - ast::StorageClass::kStorage); + ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); GeneratorImpl& gen = Build(); diff --git a/src/writer/msl/generator_impl_type_test.cc b/src/writer/msl/generator_impl_type_test.cc index 80c64c4e77..b80e3e5ebf 100644 --- a/src/writer/msl/generator_impl_type_test.cc +++ b/src/writer/msl/generator_impl_type_test.cc @@ -232,7 +232,11 @@ TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_NonComposites) { {create()}); Global("G", ty.access(ast::AccessControl::kReadOnly, s), - ast::StorageClass::kStorage); + ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); GeneratorImpl& gen = Build(); @@ -338,7 +342,11 @@ TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_Structures) { {create()}); Global("G", ty.access(ast::AccessControl::kReadOnly, s), - ast::StorageClass::kStorage); + ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); GeneratorImpl& gen = Build(); @@ -429,7 +437,11 @@ TEST_F(MslGeneratorImplTest, EmitType_Struct_Layout_ArrayDefaultStride) { ast::DecorationList{create()}); Global("G", ty.access(ast::AccessControl::kReadOnly, s), - ast::StorageClass::kStorage); + ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); GeneratorImpl& gen = Build(); @@ -532,7 +544,11 @@ TEST_F(MslGeneratorImplTest, AttemptTintPadSymbolCollision) { {create()}); Global("G", ty.access(ast::AccessControl::kReadOnly, s), - ast::StorageClass::kStorage); + ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); GeneratorImpl& gen = Build(); @@ -594,7 +610,11 @@ TEST_F(MslGeneratorImplTest, DISABLED_EmitType_Struct_WithDecoration) { {create()}); Global("G", ty.access(ast::AccessControl::kReadOnly, s), - ast::StorageClass::kStorage); + ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); GeneratorImpl& gen = Build(); @@ -745,7 +765,11 @@ TEST_P(MslStorageTexturesTest, Emit) { auto ac = ty.access(params.ro ? ast::AccessControl::kReadOnly : ast::AccessControl::kWriteOnly, s); - Global("test_var", ac, ast::StorageClass::kNone); + Global("test_var", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); GeneratorImpl& gen = Build(); diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc index 4c28e9ffdf..23e8b785e5 100644 --- a/src/writer/spirv/builder_global_variable_test.cc +++ b/src/writer/spirv/builder_global_variable_test.cc @@ -162,7 +162,8 @@ TEST_F(BuilderTest, GlobalVar_WithLocation) { } TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) { - auto* v = Global("var", ty.f32(), ast::StorageClass::kOutput, nullptr, + auto* v = Global("var", ty.sampler(ast::SamplerKind::kSampler), + ast::StorageClass::kNone, nullptr, ast::DecorationList{ create(2), create(3), @@ -176,10 +177,9 @@ TEST_F(BuilderTest, GlobalVar_WithBindingAndGroup) { EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 Binding 2 OpDecorate %1 DescriptorSet 3 )"); - EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeFloat 32 -%2 = OpTypePointer Output %3 -%4 = OpConstantNull %3 -%1 = OpVariable %2 Output %4 + EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeSampler +%2 = OpTypePointer UniformConstant %3 +%1 = OpVariable %2 UniformConstant )"); } @@ -384,7 +384,11 @@ TEST_F(BuilderTest, GlobalVar_DeclReadOnly) { {create()}); auto ac = ty.access(ast::AccessControl::kReadOnly, A); - auto* var = Global("b", ac, ast::StorageClass::kStorage); + auto* var = Global("b", ac, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -395,6 +399,8 @@ OpMemberDecorate %3 0 Offset 0 OpMemberDecorate %3 0 NonWritable OpMemberDecorate %3 1 Offset 4 OpMemberDecorate %3 1 NonWritable +OpDecorate %1 Binding 0 +OpDecorate %1 DescriptorSet 0 )"); EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A" OpMemberName %3 0 "a" @@ -420,7 +426,11 @@ TEST_F(BuilderTest, GlobalVar_TypeAliasDeclReadOnly) { auto* B = ty.alias("B", A); AST().AddConstructedType(B); auto ac = ty.access(ast::AccessControl::kReadOnly, B); - auto* var = Global("b", ac, ast::StorageClass::kStorage); + auto* var = Global("b", ac, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -429,6 +439,8 @@ TEST_F(BuilderTest, GlobalVar_TypeAliasDeclReadOnly) { EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block OpMemberDecorate %3 0 Offset 0 OpMemberDecorate %3 0 NonWritable +OpDecorate %1 Binding 0 +OpDecorate %1 DescriptorSet 0 )"); EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A" OpMemberName %3 0 "a" @@ -453,7 +465,11 @@ TEST_F(BuilderTest, GlobalVar_TypeAliasAssignReadOnly) { auto ac = ty.access(ast::AccessControl::kReadOnly, A); auto* B = ty.alias("B", ac); AST().AddConstructedType(B); - auto* var = Global("b", B, ast::StorageClass::kStorage); + auto* var = Global("b", B, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -462,6 +478,8 @@ TEST_F(BuilderTest, GlobalVar_TypeAliasAssignReadOnly) { EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %3 Block OpMemberDecorate %3 0 Offset 0 OpMemberDecorate %3 0 NonWritable +OpDecorate %1 Binding 0 +OpDecorate %1 DescriptorSet 0 )"); EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A" OpMemberName %3 0 "a" @@ -486,8 +504,16 @@ TEST_F(BuilderTest, GlobalVar_TwoVarDeclReadOnly) { auto read = ty.access(ast::AccessControl::kReadOnly, A); auto rw = ty.access(ast::AccessControl::kReadWrite, A); - auto* var_b = Global("b", read, ast::StorageClass::kStorage); - auto* var_c = Global("c", rw, ast::StorageClass::kStorage); + auto* var_b = Global("b", read, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); + auto* var_c = Global("c", rw, ast::StorageClass::kStorage, nullptr, + { + create(1), + create(0), + }); spirv::Builder& b = Build(); @@ -498,8 +524,12 @@ TEST_F(BuilderTest, GlobalVar_TwoVarDeclReadOnly) { R"(OpDecorate %3 Block OpMemberDecorate %3 0 Offset 0 OpMemberDecorate %3 0 NonWritable +OpDecorate %1 DescriptorSet 0 +OpDecorate %1 Binding 0 OpDecorate %7 Block OpMemberDecorate %7 0 Offset 0 +OpDecorate %5 DescriptorSet 1 +OpDecorate %5 Binding 0 )"); EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %3 "A" OpMemberName %3 0 "a" @@ -526,13 +556,19 @@ TEST_F(BuilderTest, GlobalVar_TextureStorageReadOnly) { auto ac = ty.access(ast::AccessControl::kReadOnly, type); - auto* var_a = Global("a", ac, ast::StorageClass::kNone); + auto* var_a = Global("a", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error(); EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonWritable +OpDecorate %1 Binding 0 +OpDecorate %1 DescriptorSet 0 )"); EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0 %3 = OpTypeImage %4 2D 0 0 0 2 R32ui @@ -549,13 +585,19 @@ TEST_F(BuilderTest, GlobalVar_TextureStorageWriteOnly) { auto ac = ty.access(ast::AccessControl::kWriteOnly, type); - auto* var_a = Global("a", ac, ast::StorageClass::kNone); + auto* var_a = Global("a", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); EXPECT_TRUE(b.GenerateGlobalVariable(var_a)) << b.error(); EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonReadable +OpDecorate %1 Binding 0 +OpDecorate %1 DescriptorSet 0 )"); EXPECT_EQ(DumpInstructions(b.types()), R"(%4 = OpTypeInt 32 0 %3 = OpTypeImage %4 2D 0 0 0 2 R32ui @@ -573,12 +615,20 @@ TEST_F(BuilderTest, GlobalVar_TextureStorageWithDifferentAccess) { auto type_a = ty.access(ast::AccessControl::kReadOnly, ty.storage_texture(ast::TextureDimension::k2d, ast::ImageFormat::kR32Uint)); - auto* var_a = Global("a", type_a, ast::StorageClass::kNone); + auto* var_a = Global("a", type_a, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); auto type_b = ty.access(ast::AccessControl::kWriteOnly, ty.storage_texture(ast::TextureDimension::k2d, ast::ImageFormat::kR32Uint)); - auto* var_b = Global("b", type_b, ast::StorageClass::kNone); + auto* var_b = Global("b", type_b, ast::StorageClass::kNone, nullptr, + { + create(1), + create(0), + }); spirv::Builder& b = Build(); @@ -586,7 +636,11 @@ TEST_F(BuilderTest, GlobalVar_TextureStorageWithDifferentAccess) { EXPECT_TRUE(b.GenerateGlobalVariable(var_b)) << b.error(); EXPECT_EQ(DumpInstructions(b.annots()), R"(OpDecorate %1 NonWritable +OpDecorate %1 Binding 0 +OpDecorate %1 DescriptorSet 0 OpDecorate %5 NonReadable +OpDecorate %5 Binding 1 +OpDecorate %5 DescriptorSet 0 )"); // There must only be one OpTypeImage declaration with the same // arguments diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc index 9e353c1765..6ce0797ac5 100644 --- a/src/writer/spirv/builder_intrinsic_test.cc +++ b/src/writer/spirv/builder_intrinsic_test.cc @@ -373,9 +373,17 @@ TEST_F(IntrinsicBuilderTest, Call_TextureSampleCompare_Twice) { auto s = ty.sampler(ast::SamplerKind::kComparisonSampler); auto t = ty.depth_texture(ast::TextureDimension::k2d); - auto* tex = Global("texture", t, ast::StorageClass::kNone); + auto* tex = Global("texture", t, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); - auto* sampler = Global("sampler", s, ast::StorageClass::kNone); + auto* sampler = Global("sampler", s, ast::StorageClass::kNone, nullptr, + { + create(1), + create(0), + }); auto* expr1 = Call("textureSampleCompare", "texture", "sampler", vec2(1.0f, 2.0f), 2.0f); diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc index 477007b897..fdb2d767d6 100644 --- a/src/writer/spirv/builder_type_test.cc +++ b/src/writer/spirv/builder_type_test.cc @@ -31,7 +31,11 @@ TEST_F(BuilderTest_Type, GenerateRuntimeArray) { auto* str = Structure("S", {Member("x", ary)}, {create()}); auto ac = ty.access(ast::AccessControl::kReadOnly, str); - Global("a", ac, ast::StorageClass::kStorage); + Global("a", ac, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -49,7 +53,11 @@ TEST_F(BuilderTest_Type, ReturnsGeneratedRuntimeArray) { auto* str = Structure("S", {Member("x", ary)}, {create()}); auto ac = ty.access(ast::AccessControl::kReadOnly, str); - Global("a", ac, ast::StorageClass::kStorage); + Global("a", ac, ast::StorageClass::kStorage, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -816,7 +824,11 @@ TEST_F(BuilderTest_Type, StorageTexture_Generate_1d) { ast::ImageFormat::kR32Float); auto ac = ty.access(ast::AccessControl::kReadOnly, s); - Global("test_var", ac, ast::StorageClass::kNone); + Global("test_var", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -832,7 +844,11 @@ TEST_F(BuilderTest_Type, StorageTexture_Generate_2d) { ast::ImageFormat::kR32Float); auto ac = ty.access(ast::AccessControl::kReadOnly, s); - Global("test_var", ac, ast::StorageClass::kNone); + Global("test_var", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -848,7 +864,11 @@ TEST_F(BuilderTest_Type, StorageTexture_Generate_2dArray) { ast::ImageFormat::kR32Float); auto ac = ty.access(ast::AccessControl::kReadOnly, s); - Global("test_var", ac, ast::StorageClass::kNone); + Global("test_var", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -864,7 +884,11 @@ TEST_F(BuilderTest_Type, StorageTexture_Generate_3d) { ast::ImageFormat::kR32Float); auto ac = ty.access(ast::AccessControl::kReadOnly, s); - Global("test_var", ac, ast::StorageClass::kNone); + Global("test_var", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -881,7 +905,11 @@ TEST_F(BuilderTest_Type, ast::ImageFormat::kR32Float); auto ac = ty.access(ast::AccessControl::kReadOnly, s); - Global("test_var", ac, ast::StorageClass::kNone); + Global("test_var", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -898,7 +926,11 @@ TEST_F(BuilderTest_Type, ast::ImageFormat::kR32Sint); auto ac = ty.access(ast::AccessControl::kReadOnly, s); - Global("test_var", ac, ast::StorageClass::kNone); + Global("test_var", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build(); @@ -915,7 +947,11 @@ TEST_F(BuilderTest_Type, ast::ImageFormat::kR32Uint); auto ac = ty.access(ast::AccessControl::kReadOnly, s); - Global("test_var", ac, ast::StorageClass::kNone); + Global("test_var", ac, ast::StorageClass::kNone, nullptr, + { + create(0), + create(0), + }); spirv::Builder& b = Build();