diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc index ca610e47d2..0998926ae1 100644 --- a/src/reader/spirv/function.cc +++ b/src/reader/spirv/function.cc @@ -949,6 +949,7 @@ bool FunctionEmitter::EmitPipelineInput(std::string var_name, tip_type = ref_type->type; } + // Recursively flatten matrices, arrays, and structures. if (auto* matrix_type = tip_type->As()) { index_prefix.push_back(0); const auto num_columns = static_cast(matrix_type->columns); @@ -981,13 +982,21 @@ bool FunctionEmitter::EmitPipelineInput(std::string var_name, for (int i = 0; i < static_cast(members.size()); ++i) { index_prefix.back() = i; auto* location = parser_impl_.GetMemberLocation(*struct_type, i); - auto* saved_location = SetLocation(decos, location); - if (!EmitPipelineInput(var_name, var_type, decos, index_prefix, + SetLocation(decos, location); + ast::DecorationList member_decos(*decos); + if (!parser_impl_.ConvertInterpolationDecorations( + struct_type, + parser_impl_.GetMemberInterpolationDecorations(*struct_type, i), + &member_decos)) { + return false; + } + if (!EmitPipelineInput(var_name, var_type, &member_decos, index_prefix, members[i], forced_param_type, params, statements)) { return false; } - SetLocation(decos, saved_location); + // Copy the location as updated by nested expansion of the member. + SetLocation(decos, GetLocation(member_decos)); } return success(); } @@ -999,7 +1008,7 @@ bool FunctionEmitter::EmitPipelineInput(std::string var_name, const auto param_name = namer_.MakeDerivedName(var_name + "_param"); // Create the parameter. // TODO(dneto): Note: If the parameter has non-location decorations, - // then those decoration AST nodes will be reused between multiple elements + // then those decoration AST nodes will be reused between multiple elements // of a matrix, array, or structure. Normally that's disallowed but currently // the SPIR-V reader will make duplicates when the entire AST is cloned // at the top level of the SPIR-V reader flow. Consider rewriting this @@ -1062,7 +1071,7 @@ ast::Decoration* FunctionEmitter::SetLocation(ast::DecorationList* decos, } for (auto*& deco : *decos) { if (deco->Is()) { - // Replace this location decoration with a new one with one higher index. + // Replace this location decoration with the replacement. // The old one doesn't leak because it's kept in the builder's AST node // list. ast::Decoration* result = deco; @@ -1075,6 +1084,16 @@ ast::Decoration* FunctionEmitter::SetLocation(ast::DecorationList* decos, return nullptr; } +ast::Decoration* FunctionEmitter::GetLocation( + const ast::DecorationList& decos) { + for (auto* const& deco : decos) { + if (deco->Is()) { + return deco; + } + } + return nullptr; +} + bool FunctionEmitter::EmitPipelineOutput(std::string var_name, const Type* var_type, ast::DecorationList* decos, @@ -1083,12 +1102,12 @@ bool FunctionEmitter::EmitPipelineOutput(std::string var_name, const Type* forced_member_type, ast::StructMemberList* return_members, ast::ExpressionList* return_exprs) { - // TODO(dneto): Handle structs where the locations are annotated on members. tip_type = tip_type->UnwrapAlias(); if (auto* ref_type = tip_type->As()) { tip_type = ref_type->type; } + // Recursively flatten matrices, arrays, and structures. if (auto* matrix_type = tip_type->As()) { index_prefix.push_back(0); const auto num_columns = static_cast(matrix_type->columns); @@ -1123,13 +1142,21 @@ bool FunctionEmitter::EmitPipelineOutput(std::string var_name, for (int i = 0; i < static_cast(members.size()); ++i) { index_prefix.back() = i; auto* location = parser_impl_.GetMemberLocation(*struct_type, i); - auto* saved_location = SetLocation(decos, location); - if (!EmitPipelineOutput(var_name, var_type, decos, index_prefix, + SetLocation(decos, location); + ast::DecorationList member_decos(*decos); + if (!parser_impl_.ConvertInterpolationDecorations( + struct_type, + parser_impl_.GetMemberInterpolationDecorations(*struct_type, i), + &member_decos)) { + return false; + } + if (!EmitPipelineOutput(var_name, var_type, &member_decos, index_prefix, members[i], forced_member_type, return_members, return_exprs)) { return false; } - SetLocation(decos, saved_location); + // Copy the location as updated by nested expansion of the member. + SetLocation(decos, GetLocation(member_decos)); } return success(); } diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h index 8bfa7dbee3..9d6a82e976 100644 --- a/src/reader/spirv/function.h +++ b/src/reader/spirv/function.h @@ -479,10 +479,16 @@ class FunctionEmitter { /// Assumes the list contains at most one Location decoration. /// @param decos the decoration list to modify /// @param replacement the location decoration to place into the list - /// @returns the location decoration that was replaced, if one was replaced. + /// @returns the location decoration that was replaced, if one was replaced, + /// or null otherwise. ast::Decoration* SetLocation(ast::DecorationList* decos, ast::Decoration* replacement); + /// Returns the Location dcoration, if it exists. + /// @param decos the list of decorations to search + /// @returns the Location decoration, or nullptr if it doesn't exist + ast::Decoration* GetLocation(const ast::DecorationList& decos); + /// Create an ast::BlockStatement representing the body of the function. /// This creates the statement stack, which is non-empty for the lifetime /// of the function. diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc index 608ba3faaa..25a67bfe21 100644 --- a/src/reader/spirv/parser_impl.cc +++ b/src/reader/spirv/parser_impl.cc @@ -233,6 +233,24 @@ bool AssumesResultSignednessMatchesFirstOperand(GLSLstd450 extended_opcode) { return false; } +// @param a SPIR-V decoration +// @return true when the given decoration is an interpolation decoration. +bool IsInterpolationDecoration(const Decoration& deco) { + if (deco.size() < 1) { + return false; + } + switch (deco[0]) { + case SpvDecorationFlat: + case SpvDecorationNoPerspective: + case SpvDecorationCentroid: + case SpvDecorationSample: + return true; + default: + break; + } + return false; +} + } // namespace TypedExpression::TypedExpression() = default; @@ -1104,6 +1122,9 @@ const Type* ParserImpl::ConvertType( break; case SpvDecorationLocation: case SpvDecorationFlat: + case SpvDecorationNoPerspective: + case SpvDecorationCentroid: + case SpvDecorationSample: // IO decorations are handled when emitting the entry point. break; default: { @@ -1571,6 +1592,7 @@ bool ParserImpl::ConvertDecorationsForVariable(uint32_t id, const Type** store_type, ast::DecorationList* decorations, bool transfer_pipeline_io) { + DecorationList interpolation_decorations; for (auto& deco : GetDecorationsFor(id)) { if (deco.empty()) { return Fail() << "malformed decoration on ID " << id << ": it is empty"; @@ -1643,16 +1665,8 @@ bool ParserImpl::ConvertDecorationsForVariable(uint32_t id, create(Source{}, deco[1])); } } - if (deco[0] == SpvDecorationFlat) { - if (transfer_pipeline_io) { - // In WGSL, integral types are always flat, and so the decoration - // is never specified. - if (!(*store_type)->IsIntegerScalarOrVector()) { - decorations->emplace_back(create( - Source{}, ast::InterpolationType::kFlat, - ast::InterpolationSampling::kNone)); - } - } + if (transfer_pipeline_io && IsInterpolationDecoration(deco)) { + interpolation_decorations.push_back(deco); } if (deco[0] == SpvDecorationDescriptorSet) { if (deco.size() == 1) { @@ -1671,6 +1685,98 @@ bool ParserImpl::ConvertDecorationsForVariable(uint32_t id, create(Source{}, deco[1])); } } + + if (transfer_pipeline_io) { + if (!ConvertInterpolationDecorations(*store_type, interpolation_decorations, + decorations)) { + return false; + } + } + + return success(); +} + +DecorationList ParserImpl::GetMemberInterpolationDecorations( + const Struct& struct_type, + int member_index) { + // Yes, I could have used std::copy_if or std::copy_if. + DecorationList result; + for (const auto& deco : GetDecorationsForMember( + struct_id_for_symbol_[struct_type.name], member_index)) { + if (IsInterpolationDecoration(deco)) { + result.emplace_back(deco); + } + } + return result; +} + +bool ParserImpl::ConvertInterpolationDecorations( + const Type* store_type, + const DecorationList& decorations, + ast::DecorationList* ast_decos) { + bool has_interpolate_no_perspective = false; + bool has_interpolate_sampling_centroid = false; + bool has_interpolate_sampling_sample = false; + + for (const auto& deco : decorations) { + if (deco[0] == SpvDecorationFlat) { + // In WGSL, integral types are always flat, and so the decoration + // is never specified. + if (!store_type->IsIntegerScalarOrVector()) { + ast_decos->emplace_back(create( + Source{}, ast::InterpolationType::kFlat, + ast::InterpolationSampling::kNone)); + // Only one interpolate attribute is allowed. + return true; + } + } + if (deco[0] == SpvDecorationNoPerspective) { + if (store_type->IsIntegerScalarOrVector()) { + // This doesn't capture the array or struct case. + return Fail() << "NoPerspective is invalid on integral IO"; + } + has_interpolate_no_perspective = true; + } + if (deco[0] == SpvDecorationCentroid) { + if (store_type->IsIntegerScalarOrVector()) { + // This doesn't capture the array or struct case. + return Fail() + << "Centroid interpolation sampling is invalid on integral IO"; + } + has_interpolate_sampling_centroid = true; + } + if (deco[0] == SpvDecorationSample) { + if (store_type->IsIntegerScalarOrVector()) { + // This doesn't capture the array or struct case. + return Fail() + << "Sample interpolation sampling is invalid on integral IO"; + } + has_interpolate_sampling_sample = true; + } + } + + // Apply non-integral interpolation. + if (has_interpolate_no_perspective || has_interpolate_sampling_centroid || + has_interpolate_sampling_sample) { + const ast::InterpolationType type = + has_interpolate_no_perspective ? ast::InterpolationType::kLinear + : ast::InterpolationType::kPerspective; + const ast::InterpolationSampling sampling = + has_interpolate_sampling_centroid + ? ast::InterpolationSampling::kCentroid + : (has_interpolate_sampling_sample + ? ast::InterpolationSampling::kSample + : ast::InterpolationSampling:: + kNone /* Center is the default */); + if (type == ast::InterpolationType::kPerspective && + sampling == ast::InterpolationSampling::kNone) { + // This is the default. Don't add a decoration. + } else { + ast_decos->emplace_back( + create(type, sampling)); + } + } + return success(); } diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h index b041605f45..b93a993e67 100644 --- a/src/reader/spirv/parser_impl.h +++ b/src/reader/spirv/parser_impl.h @@ -252,6 +252,15 @@ class ParserImpl : Reader { ast::DecorationList* ast_decos, bool transfer_pipeline_io); + /// Converts SPIR-V interpolation decorations into AST decorations. + /// @param store_type the store type for the variable or member + /// @param decorations the SPIR-V interpolation decorations + /// @param ast_decos the decoration list to populate. + /// @returns false if conversion fails + bool ConvertInterpolationDecorations(const Type* store_type, + const DecorationList& decorations, + ast::DecorationList* ast_decos); + /// Converts a SPIR-V struct member decoration. If the decoration is /// recognized but deliberately dropped, then returns nullptr without a /// diagnostic. On failure, emits a diagnostic and returns nullptr. @@ -386,6 +395,13 @@ class ParserImpl : Reader { ast::Decoration* GetMemberLocation(const Struct& struct_type, int member_index); + /// Returns the SPIR-V interpolation decorations, if any, on a struct member. + /// @param struct_type the parser's structure type. + /// @param member_index the member index + /// @returns a list of SPIR-V decorations. + DecorationList GetMemberInterpolationDecorations(const Struct& struct_type, + int member_index); + /// Creates an AST Variable node for a SPIR-V ID, including any attached /// decorations, unless it's an ignorable builtin variable. /// @param id the SPIR-V result ID diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc index 941129cb4e..54c8468287 100644 --- a/src/reader/spirv/parser_impl_module_var_test.cc +++ b/src/reader/spirv/parser_impl_module_var_test.cc @@ -55,6 +55,7 @@ std::string MainBody() { std::string CommonCapabilities() { return R"( OpCapability Shader + OpCapability SampleRateShading OpMemoryModel Logical Simple )"; } @@ -7760,6 +7761,633 @@ TEST_F(SpvModuleScopeVarParserTest, EXPECT_EQ(got, expected) << got; } +TEST_F(SpvModuleScopeVarParserTest, + EntryPointWrapping_Interpolation_Floating_Fragment_In) { + // Flat decorations are dropped for integral + const auto assembly = CommonCapabilities() + R"( + OpEntryPoint Fragment %main "main" %1 %2 %3 %4 %5 %6 + OpExecutionMode %main OriginUpperLeft + OpDecorate %1 Location 1 + OpDecorate %2 Location 2 + OpDecorate %3 Location 3 + OpDecorate %4 Location 4 + OpDecorate %5 Location 5 + OpDecorate %6 Location 6 + + ; %1 perspective center + + OpDecorate %2 Centroid ; perspective centroid + + OpDecorate %3 Sample ; perspective sample + + OpDecorate %4 NoPerspective; linear center + + OpDecorate %5 NoPerspective ; linear centroid + OpDecorate %5 Centroid + + OpDecorate %6 NoPerspective ; linear sample + OpDecorate %6 Sample + +)" + CommonTypes() + + R"( + %ptr_in_float = OpTypePointer Input %float + %1 = OpVariable %ptr_in_float Input + %2 = OpVariable %ptr_in_float Input + %3 = OpVariable %ptr_in_float Input + %4 = OpVariable %ptr_in_float Input + %5 = OpVariable %ptr_in_float Input + %6 = OpVariable %ptr_in_float Input + + %main = OpFunction %void None %voidfn + %entry = OpLabel + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + + ASSERT_TRUE(p->BuildAndParseInternalModule()); + EXPECT_TRUE(p->error().empty()); + const auto got = p->program().to_str(); + const std::string expected = + R"(Module{ + Variable{ + x_1 + private + undefined + __f32 + } + Variable{ + x_2 + private + undefined + __f32 + } + Variable{ + x_3 + private + undefined + __f32 + } + Variable{ + x_4 + private + undefined + __f32 + } + Variable{ + x_5 + private + undefined + __f32 + } + Variable{ + x_6 + private + undefined + __f32 + } + Function main_1 -> __void + () + { + Return{} + } + Function main -> __void + StageDecoration{fragment} + ( + VariableConst{ + Decorations{ + LocationDecoration{1} + } + x_1_param + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{2} + InterpolateDecoration{perspective centroid} + } + x_2_param + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{3} + InterpolateDecoration{perspective sample} + } + x_3_param + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{4} + InterpolateDecoration{linear none} + } + x_4_param + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{5} + InterpolateDecoration{linear centroid} + } + x_5_param + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{6} + InterpolateDecoration{linear sample} + } + x_6_param + none + undefined + __f32 + } + ) + { + Assignment{ + Identifier[not set]{x_1} + Identifier[not set]{x_1_param} + } + Assignment{ + Identifier[not set]{x_2} + Identifier[not set]{x_2_param} + } + Assignment{ + Identifier[not set]{x_3} + Identifier[not set]{x_3_param} + } + Assignment{ + Identifier[not set]{x_4} + Identifier[not set]{x_4_param} + } + Assignment{ + Identifier[not set]{x_5} + Identifier[not set]{x_5_param} + } + Assignment{ + Identifier[not set]{x_6} + Identifier[not set]{x_6_param} + } + Call[not set]{ + Identifier[not set]{main_1} + ( + ) + } + } +} +)"; + EXPECT_EQ(got, expected) << got; +} + +TEST_F(SpvModuleScopeVarParserTest, + EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_In) { + const auto assembly = CommonCapabilities() + R"( + OpEntryPoint Fragment %main "main" %1 + OpExecutionMode %main OriginUpperLeft + OpDecorate %1 Location 1 + + ; member 0 perspective center + + OpMemberDecorate %10 1 Centroid ; perspective centroid + + OpMemberDecorate %10 2 Sample ; perspective sample + + OpMemberDecorate %10 3 NoPerspective; linear center + + OpMemberDecorate %10 4 NoPerspective ; linear centroid + OpMemberDecorate %10 4 Centroid + + OpMemberDecorate %10 5 NoPerspective ; linear sample + OpMemberDecorate %10 5 Sample + +)" + CommonTypes() + + R"( + + %10 = OpTypeStruct %float %float %float %float %float %float + %ptr_in_strct = OpTypePointer Input %10 + %1 = OpVariable %ptr_in_strct Input + + %main = OpFunction %void None %voidfn + %entry = OpLabel + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + + ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly << p->error(); + EXPECT_TRUE(p->error().empty()); + const auto got = p->program().to_str(); + const std::string expected = + R"(Module{ + Struct S { + StructMember{field0: __f32} + StructMember{field1: __f32} + StructMember{field2: __f32} + StructMember{field3: __f32} + StructMember{field4: __f32} + StructMember{field5: __f32} + } + Variable{ + x_1 + private + undefined + __type_name_S + } + Function main_1 -> __void + () + { + Return{} + } + Function main -> __void + StageDecoration{fragment} + ( + VariableConst{ + Decorations{ + LocationDecoration{1} + } + x_1_param + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{2} + InterpolateDecoration{perspective centroid} + } + x_1_param_1 + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{3} + InterpolateDecoration{perspective sample} + } + x_1_param_2 + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{4} + InterpolateDecoration{linear none} + } + x_1_param_3 + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{5} + InterpolateDecoration{linear centroid} + } + x_1_param_4 + none + undefined + __f32 + } + VariableConst{ + Decorations{ + LocationDecoration{6} + InterpolateDecoration{linear sample} + } + x_1_param_5 + none + undefined + __f32 + } + ) + { + Assignment{ + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field0} + } + Identifier[not set]{x_1_param} + } + Assignment{ + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field1} + } + Identifier[not set]{x_1_param_1} + } + Assignment{ + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field2} + } + Identifier[not set]{x_1_param_2} + } + Assignment{ + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field3} + } + Identifier[not set]{x_1_param_3} + } + Assignment{ + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field4} + } + Identifier[not set]{x_1_param_4} + } + Assignment{ + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field5} + } + Identifier[not set]{x_1_param_5} + } + Call[not set]{ + Identifier[not set]{main_1} + ( + ) + } + } +} +)"; + EXPECT_EQ(got, expected) << got; +} + +TEST_F(SpvModuleScopeVarParserTest, + EntryPointWrapping_Interpolation_Floating_Fragment_Out) { + // Flat decorations are dropped for integral + const auto assembly = CommonCapabilities() + R"( + OpEntryPoint Fragment %main "main" %1 %2 %3 %4 %5 %6 + OpExecutionMode %main OriginUpperLeft + OpDecorate %1 Location 1 + OpDecorate %2 Location 2 + OpDecorate %3 Location 3 + OpDecorate %4 Location 4 + OpDecorate %5 Location 5 + OpDecorate %6 Location 6 + + ; %1 perspective center + + OpDecorate %2 Centroid ; perspective centroid + + OpDecorate %3 Sample ; perspective sample + + OpDecorate %4 NoPerspective; linear center + + OpDecorate %5 NoPerspective ; linear centroid + OpDecorate %5 Centroid + + OpDecorate %6 NoPerspective ; linear sample + OpDecorate %6 Sample + +)" + CommonTypes() + + R"( + %ptr_out_float = OpTypePointer Output %float + %1 = OpVariable %ptr_out_float Output + %2 = OpVariable %ptr_out_float Output + %3 = OpVariable %ptr_out_float Output + %4 = OpVariable %ptr_out_float Output + %5 = OpVariable %ptr_out_float Output + %6 = OpVariable %ptr_out_float Output + + %main = OpFunction %void None %voidfn + %entry = OpLabel + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + + ASSERT_TRUE(p->BuildAndParseInternalModule()); + EXPECT_TRUE(p->error().empty()); + const auto got = p->program().to_str(); + const std::string expected = + R"(Module{ + Struct main_out { + StructMember{[[ LocationDecoration{1} + ]] x_1_1: __f32} + StructMember{[[ LocationDecoration{2} + InterpolateDecoration{perspective centroid} + ]] x_2_1: __f32} + StructMember{[[ LocationDecoration{3} + InterpolateDecoration{perspective sample} + ]] x_3_1: __f32} + StructMember{[[ LocationDecoration{4} + InterpolateDecoration{linear none} + ]] x_4_1: __f32} + StructMember{[[ LocationDecoration{5} + InterpolateDecoration{linear centroid} + ]] x_5_1: __f32} + StructMember{[[ LocationDecoration{6} + InterpolateDecoration{linear sample} + ]] x_6_1: __f32} + } + Variable{ + x_1 + private + undefined + __f32 + } + Variable{ + x_2 + private + undefined + __f32 + } + Variable{ + x_3 + private + undefined + __f32 + } + Variable{ + x_4 + private + undefined + __f32 + } + Variable{ + x_5 + private + undefined + __f32 + } + Variable{ + x_6 + private + undefined + __f32 + } + Function main_1 -> __void + () + { + Return{} + } + Function main -> __type_name_main_out + StageDecoration{fragment} + () + { + Call[not set]{ + Identifier[not set]{main_1} + ( + ) + } + Return{ + { + TypeConstructor[not set]{ + __type_name_main_out + Identifier[not set]{x_1} + Identifier[not set]{x_2} + Identifier[not set]{x_3} + Identifier[not set]{x_4} + Identifier[not set]{x_5} + Identifier[not set]{x_6} + } + } + } + } +} +)"; + EXPECT_EQ(got, expected) << got; +} + +TEST_F(SpvModuleScopeVarParserTest, + EntryPointWrapping_Flatten_Interpolation_Floating_Fragment_Out) { + const auto assembly = CommonCapabilities() + R"( + OpEntryPoint Fragment %main "main" %1 + OpExecutionMode %main OriginUpperLeft + + OpDecorate %1 Location 1 + + ; member 0 perspective center + + OpMemberDecorate %10 1 Centroid ; perspective centroid + + OpMemberDecorate %10 2 Sample ; perspective sample + + OpMemberDecorate %10 3 NoPerspective; linear center + + OpMemberDecorate %10 4 NoPerspective ; linear centroid + OpMemberDecorate %10 4 Centroid + + OpMemberDecorate %10 5 NoPerspective ; linear sample + OpMemberDecorate %10 5 Sample + +)" + CommonTypes() + + R"( + + %10 = OpTypeStruct %float %float %float %float %float %float + %ptr_in_strct = OpTypePointer Output %10 + %1 = OpVariable %ptr_in_strct Output + + %main = OpFunction %void None %voidfn + %entry = OpLabel + OpReturn + OpFunctionEnd + )"; + auto p = parser(test::Assemble(assembly)); + + ASSERT_TRUE(p->BuildAndParseInternalModule()); + EXPECT_TRUE(p->error().empty()); + const auto got = p->program().to_str(); + const std::string expected = + R"(Module{ + Struct S { + StructMember{field0: __f32} + StructMember{field1: __f32} + StructMember{field2: __f32} + StructMember{field3: __f32} + StructMember{field4: __f32} + StructMember{field5: __f32} + } + Struct main_out { + StructMember{[[ LocationDecoration{1} + ]] x_1_1: __f32} + StructMember{[[ LocationDecoration{2} + InterpolateDecoration{perspective centroid} + ]] x_1_2: __f32} + StructMember{[[ LocationDecoration{3} + InterpolateDecoration{perspective sample} + ]] x_1_3: __f32} + StructMember{[[ LocationDecoration{4} + InterpolateDecoration{linear none} + ]] x_1_4: __f32} + StructMember{[[ LocationDecoration{5} + InterpolateDecoration{linear centroid} + ]] x_1_5: __f32} + StructMember{[[ LocationDecoration{6} + InterpolateDecoration{linear sample} + ]] x_1_6: __f32} + } + Variable{ + x_1 + private + undefined + __type_name_S + } + Function main_1 -> __void + () + { + Return{} + } + Function main -> __type_name_main_out + StageDecoration{fragment} + () + { + Call[not set]{ + Identifier[not set]{main_1} + ( + ) + } + Return{ + { + TypeConstructor[not set]{ + __type_name_main_out + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field0} + } + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field1} + } + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field2} + } + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field3} + } + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field4} + } + MemberAccessor[not set]{ + Identifier[not set]{x_1} + Identifier[not set]{field5} + } + } + } + } + } +} +)"; + EXPECT_EQ(got, expected) << got; +} + } // namespace } // namespace spirv } // namespace reader