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>
This commit is contained in:
David Neto 2021-07-16 16:20:25 +00:00 committed by Tint LUCI CQ
parent 71198438ac
commit e087a95706
5 changed files with 803 additions and 20 deletions

View File

@ -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<Matrix>()) {
index_prefix.push_back(0);
const auto num_columns = static_cast<int>(matrix_type->columns);
@ -981,13 +982,21 @@ bool FunctionEmitter::EmitPipelineInput(std::string var_name,
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 @@ 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<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 @@ 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<ast::LocationDecoration>()) {
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<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 @@ bool FunctionEmitter::EmitPipelineOutput(std::string var_name,
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();
}

View File

@ -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.

View File

@ -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<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 @@ bool ParserImpl::ConvertDecorationsForVariable(uint32_t id,
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();
}

View File

@ -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

View File

@ -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