spirv-reader: support remaining interpolation decorations
NoPerspective interpolation maps to 'linear'
Centroid maps to 'centroid'
Sample maps to 'sample'
Otherwise, allow 'center' to be defaulted.
Fixed: tint:935
Change-Id: I0b040da0c57d2a363f9dc9474c1ac889e0fe2278
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56840
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: James Price <jrprice@google.com>
Auto-Submit: David Neto <dneto@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index ca610e4..0998926 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -949,6 +949,7 @@
tip_type = ref_type->type;
}
+ // Recursively flatten matrices, arrays, and structures.
if (auto* matrix_type = tip_type->As<Matrix>()) {
index_prefix.push_back(0);
const auto num_columns = static_cast<int>(matrix_type->columns);
@@ -981,13 +982,21 @@
for (int i = 0; i < static_cast<int>(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 @@
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 @@
}
for (auto*& deco : *decos) {
if (deco->Is<ast::LocationDecoration>()) {
- // 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 @@
return nullptr;
}
+ast::Decoration* FunctionEmitter::GetLocation(
+ const ast::DecorationList& decos) {
+ for (auto* const& deco : decos) {
+ if (deco->Is<ast::LocationDecoration>()) {
+ return deco;
+ }
+ }
+ return nullptr;
+}
+
bool FunctionEmitter::EmitPipelineOutput(std::string var_name,
const Type* var_type,
ast::DecorationList* decos,
@@ -1083,12 +1102,12 @@
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<Reference>()) {
tip_type = ref_type->type;
}
+ // Recursively flatten matrices, arrays, and structures.
if (auto* matrix_type = tip_type->As<Matrix>()) {
index_prefix.push_back(0);
const auto num_columns = static_cast<int>(matrix_type->columns);
@@ -1123,13 +1142,21 @@
for (int i = 0; i < static_cast<int>(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 8bfa7db..9d6a82e 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -479,10 +479,16 @@
/// 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 608ba3f..25a67bf 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -233,6 +233,24 @@
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 @@
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 @@
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 @@
create<ast::LocationDecoration>(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<ast::InterpolateDecoration>(
- 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 @@
create<ast::BindingDecoration>(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<ast::InterpolateDecoration>(
+ 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<ast::InterpolateDecoration>(type, sampling));
+ }
+ }
+
return success();
}
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index b041605..b93a993 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -252,6 +252,15 @@
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 @@
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 941129c..54c8468 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 CommonCapabilities() {
return R"(
OpCapability Shader
+ OpCapability SampleRateShading
OpMemoryModel Logical Simple
)";
}
@@ -7760,6 +7761,633 @@
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