[spirv-reader] Handle gl_Position
Emits it as a module-level variable. Deconstruct and throw away
the gl_PerVertex struct.
Not handled: unusual patterns that are technically valid but
which don't occur in practice:
- loading, storing, or producing intermediate values of the whole structure.
- multiple definitions of the per-vertex structure (e.g. if someone had
put both a vertex shader and a tessellation shader in the same
module.)
Bug: tint:3, tint:99
Change-Id: I3ad9ff6ab780a002367f01f385bfa7d6ddba6db9
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/24880
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index c6ed309..f62c967 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -2512,9 +2512,21 @@
bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
const auto result_id = inst.result_id();
+ const auto type_id = inst.type_id();
+
+ if (type_id != 0) {
+ const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
+ if ((type_id == builtin_position_info.struct_type_id) ||
+ (type_id == builtin_position_info.pointer_type_id)) {
+ return Fail() << "operations producing a per-vertex structure are not "
+ "supported: "
+ << inst.PrettyPrint();
+ }
+ }
+
// Handle combinatorial instructions.
- auto combinatorial_expr = MaybeEmitCombinatorialValue(inst);
const auto* def_info = GetDefInfo(result_id);
+ auto combinatorial_expr = MaybeEmitCombinatorialValue(inst);
if (combinatorial_expr.expr != nullptr) {
if (def_info == nullptr) {
return Fail() << "internal error: result ID %" << result_id
@@ -2542,9 +2554,19 @@
return true;
case SpvOpStore: {
+ const auto ptr_id = inst.GetSingleWordInOperand(0);
+ const auto value_id = inst.GetSingleWordInOperand(1);
+ const auto ptr_type_id = def_use_mgr_->GetDef(ptr_id)->type_id();
+ const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
+ if (ptr_type_id == builtin_position_info.pointer_type_id) {
+ return Fail()
+ << "storing to the whole per-vertex structure is not supported: "
+ << inst.PrettyPrint();
+ }
+
// TODO(dneto): Order of evaluation?
- auto lhs = MakeExpression(inst.GetSingleWordInOperand(0));
- auto rhs = MakeExpression(inst.GetSingleWordInOperand(1));
+ auto lhs = MakeExpression(ptr_id);
+ auto rhs = MakeExpression(value_id);
AddStatement(std::make_unique<ast::AssignmentStatement>(
std::move(lhs.expr), std::move(rhs.expr)));
return success();
@@ -2737,7 +2759,56 @@
static const char* swizzles[] = {"x", "y", "z", "w"};
const auto base_id = inst.GetSingleWordInOperand(0);
- const auto ptr_ty_id = def_use_mgr_->GetDef(base_id)->type_id();
+ auto ptr_ty_id = def_use_mgr_->GetDef(base_id)->type_id();
+ uint32_t first_index = 1;
+ const auto num_in_operands = inst.NumInOperands();
+
+ // If the variable was originally gl_PerVertex, then in the AST we
+ // have instead emitted a gl_Position variable.
+ {
+ const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
+ if (base_id == builtin_position_info.per_vertex_var_id) {
+ // We only support the Position member.
+ const auto* member_index_inst =
+ def_use_mgr_->GetDef(inst.GetSingleWordInOperand(first_index));
+ if (member_index_inst == nullptr) {
+ Fail()
+ << "first index of access chain does not reference an instruction: "
+ << inst.PrettyPrint();
+ return {};
+ }
+ const auto* member_index_const =
+ constant_mgr_->GetConstantFromInst(member_index_inst);
+ if (member_index_const == nullptr) {
+ Fail() << "first index of access chain into per-vertex structure is "
+ "not a constant: "
+ << inst.PrettyPrint();
+ return {};
+ }
+ const auto* member_index_const_int = member_index_const->AsIntConstant();
+ if (member_index_const_int == nullptr) {
+ Fail() << "first index of access chain into per-vertex structure is "
+ "not a constant integer: "
+ << inst.PrettyPrint();
+ return {};
+ }
+ const auto member_index_value =
+ member_index_const_int->GetZeroExtendedValue();
+ if (member_index_value != builtin_position_info.member_index) {
+ Fail() << "accessing per-vertex member " << member_index_value
+ << " is not supported. Only Position is supported";
+ return {};
+ }
+
+ // Skip past the member index that gets us to Position.
+ first_index = first_index + 1;
+ // Replace the gl_PerVertex reference with the gl_Position reference
+ current_expr.expr =
+ std::make_unique<ast::IdentifierExpression>(namer_.Name(base_id));
+ ptr_ty_id = builtin_position_info.member_pointer_type_id;
+ }
+ }
+
const auto* ptr_type = type_mgr_->GetType(ptr_ty_id);
if (!ptr_type || !ptr_type->AsPointer()) {
Fail() << "Access chain %" << inst.result_id()
@@ -2745,8 +2816,7 @@
return {};
}
const auto* pointee_type = ptr_type->AsPointer()->pointee_type();
- const auto num_in_operands = inst.NumInOperands();
- for (uint32_t index = 1; index < num_in_operands; ++index) {
+ for (uint32_t index = first_index; index < num_in_operands; ++index) {
const auto* index_const =
constants[index] ? constants[index]->AsIntConstant() : nullptr;
const int64_t index_const_val =
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 6794a37..27cac0a 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -36,6 +36,7 @@
#include "src/ast/as_expression.h"
#include "src/ast/binary_expression.h"
#include "src/ast/bool_literal.h"
+#include "src/ast/builtin.h"
#include "src/ast/builtin_decoration.h"
#include "src/ast/decorated_variable.h"
#include "src/ast/float_literal.h"
@@ -280,8 +281,8 @@
auto save = [this, type_id, spirv_type](ast::type::Type* type) {
if (type != nullptr) {
id_to_type_[type_id] = type;
+ MaybeGenerateAlias(type_id, spirv_type);
}
- MaybeGenerateAlias(type_id, spirv_type);
return type;
};
@@ -305,7 +306,7 @@
case spvtools::opt::analysis::Type::kStruct:
return save(ConvertType(type_id, spirv_type->AsStruct()));
case spvtools::opt::analysis::Type::kPointer:
- return save(ConvertType(spirv_type->AsPointer()));
+ return save(ConvertType(type_id, spirv_type->AsPointer()));
case spvtools::opt::analysis::Type::kFunction:
// Tint doesn't have a Function type.
// We need to convert the result type and parameter types.
@@ -489,6 +490,7 @@
case SpvOpName:
namer_.SuggestSanitizedName(inst.GetSingleWordInOperand(0),
inst.GetInOperand(1).AsString());
+
break;
case SpvOpMemberName:
namer_.SuggestSanitizedMemberName(inst.GetSingleWordInOperand(0),
@@ -690,19 +692,43 @@
const auto members = struct_ty->element_types();
for (uint32_t member_index = 0; member_index < members.size();
++member_index) {
- auto* ast_member_ty = ConvertType(type_mgr_->GetId(members[member_index]));
+ const auto member_type_id = type_mgr_->GetId(members[member_index]);
+ auto* ast_member_ty = ConvertType(member_type_id);
if (ast_member_ty == nullptr) {
// Already emitted diagnostics.
return nullptr;
}
ast::StructMemberDecorationList ast_member_decorations;
- for (auto& deco : GetDecorationsForMember(type_id, member_index)) {
- auto ast_member_decoration = ConvertMemberDecoration(deco);
- if (ast_member_decoration == nullptr) {
- // Already emitted diagnostics.
+ for (auto& decoration : GetDecorationsForMember(type_id, member_index)) {
+ if (decoration.empty()) {
+ Fail() << "malformed SPIR-V decoration: it's empty";
return nullptr;
}
- ast_member_decorations.push_back(std::move(ast_member_decoration));
+ if ((decoration[0] == SpvDecorationBuiltIn) && (decoration.size() > 1)) {
+ switch (decoration[1]) {
+ case SpvBuiltInPosition:
+ // Record this built-in variable specially.
+ builtin_position_.struct_type_id = type_id;
+ builtin_position_.member_index = member_index;
+ builtin_position_.member_type_id = member_type_id;
+ // Don't map the struct type. But this is not an error either.
+ return nullptr;
+ case SpvBuiltInPointSize: // not supported in WGSL
+ case SpvBuiltInCullDistance: // not supported in WGSL
+ case SpvBuiltInClipDistance: // not supported in WGSL
+ default:
+ break;
+ }
+ Fail() << "unrecognized builtin " << decoration[1];
+ return nullptr;
+ } else {
+ auto ast_member_decoration = ConvertMemberDecoration(decoration);
+ if (ast_member_decoration == nullptr) {
+ // Already emitted diagnostics.
+ return nullptr;
+ }
+ ast_member_decorations.push_back(std::move(ast_member_decoration));
+ }
}
const auto member_name = namer_.GetMemberName(type_id, member_index);
auto ast_struct_member = std::make_unique<ast::StructMember>(
@@ -723,20 +749,27 @@
}
ast::type::Type* ParserImpl::ConvertType(
- const spvtools::opt::analysis::Pointer* ptr_ty) {
- auto* ast_elem_ty = ConvertType(type_mgr_->GetId(ptr_ty->pointee_type()));
- if (ast_elem_ty == nullptr) {
- Fail() << "SPIR-V pointer type with ID " << type_mgr_->GetId(ptr_ty)
- << " has invalid pointee type "
- << type_mgr_->GetId(ptr_ty->pointee_type());
+ uint32_t type_id,
+ const spvtools::opt::analysis::Pointer*) {
+ const auto* inst = def_use_mgr_->GetDef(type_id);
+ const auto pointee_ty_id = inst->GetSingleWordInOperand(1);
+ const auto storage_class = SpvStorageClass(inst->GetSingleWordInOperand(0));
+ if (pointee_ty_id == builtin_position_.struct_type_id) {
+ builtin_position_.pointer_type_id = type_id;
+ builtin_position_.storage_class = storage_class;
return nullptr;
}
- auto ast_storage_class =
- enum_converter_.ToStorageClass(ptr_ty->storage_class());
+ auto* ast_elem_ty = ConvertType(pointee_ty_id);
+ if (ast_elem_ty == nullptr) {
+ Fail() << "SPIR-V pointer type with ID " << type_id
+ << " has invalid pointee type " << pointee_ty_id;
+ return nullptr;
+ }
+ auto ast_storage_class = enum_converter_.ToStorageClass(storage_class);
if (ast_storage_class == ast::StorageClass::kNone) {
- Fail() << "SPIR-V pointer type with ID " << type_mgr_->GetId(ptr_ty)
+ Fail() << "SPIR-V pointer type with ID " << type_id
<< " has invalid storage class "
- << static_cast<uint32_t>(ptr_ty->storage_class());
+ << static_cast<uint32_t>(storage_class);
return nullptr;
}
return ctx_.type_mgr().Get(
@@ -754,6 +787,13 @@
}
ConvertType(type_or_const.result_id());
}
+ // Manufacture a type for the gl_Position varible if we have to.
+ if ((builtin_position_.struct_type_id != 0) &&
+ (builtin_position_.member_pointer_type_id == 0)) {
+ builtin_position_.member_pointer_type_id = type_mgr_->FindPointerToType(
+ builtin_position_.member_type_id, builtin_position_.storage_class);
+ ConvertType(builtin_position_.member_pointer_type_id);
+ }
return success_;
}
@@ -809,12 +849,22 @@
}
const auto& var = type_or_value;
const auto spirv_storage_class = var.GetSingleWordInOperand(0);
+
+ uint32_t type_id = var.type_id();
+ if ((type_id == builtin_position_.pointer_type_id) &&
+ ((spirv_storage_class == SpvStorageClassInput) ||
+ (spirv_storage_class == SpvStorageClassOutput))) {
+ // Skip emitting gl_PerVertex.
+ builtin_position_.per_vertex_var_id = var.result_id();
+ continue;
+ }
+
auto ast_storage_class = enum_converter_.ToStorageClass(
static_cast<SpvStorageClass>(spirv_storage_class));
if (!success_) {
return false;
}
- auto* ast_type = id_to_type_[var.type_id()];
+ auto* ast_type = id_to_type_[type_id];
if (ast_type == nullptr) {
return Fail() << "internal error: failed to register Tint AST type for "
"SPIR-V type with ID: "
@@ -837,6 +887,23 @@
// TODO(dneto): initializers (a.k.a. constructor expression)
ast_module_.AddGlobalVariable(std::move(ast_var));
}
+
+ // Emit gl_Position instead of gl_PerVertex
+ if (builtin_position_.per_vertex_var_id) {
+ // Make sure the variable has a name.
+ namer_.SuggestSanitizedName(builtin_position_.per_vertex_var_id,
+ "gl_Position");
+ auto var = std::make_unique<ast::DecoratedVariable>(MakeVariable(
+ builtin_position_.per_vertex_var_id,
+ enum_converter_.ToStorageClass(builtin_position_.storage_class),
+ ConvertType(builtin_position_.member_type_id)));
+ ast::VariableDecorationList decos;
+ decos.push_back(
+ std::make_unique<ast::BuiltinDecoration>(ast::Builtin::kPosition));
+ var->set_decorations(std::move(decos));
+
+ ast_module_.AddGlobalVariable(std::move(var));
+ }
return success_;
}
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 281adaa..6fc60a5 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -133,6 +133,8 @@
std::string GlslStd450Prefix() const { return "std::glsl"; }
/// Converts a SPIR-V type to a Tint type, and saves it for fast lookup.
+ /// If the type is only used for builtins, then register that specially,
+ /// and return null.
/// On failure, logs an error and returns null. This should only be called
/// after the internal representation of the module has been built.
/// @param type_id the SPIR-V ID of a type.
@@ -316,6 +318,30 @@
/// @returns the registered boolean type.
ast::type::Type* BoolType() const { return bool_type_; }
+ /// Bookkeeping used for tracking the "position" builtin variable.
+ struct BuiltInPositionInfo {
+ /// The ID for the gl_PerVertex struct containing the Position builtin.
+ uint32_t struct_type_id = 0;
+ /// The member index for the Position builtin within the struct.
+ uint32_t member_index = 0;
+ /// The ID for the member type, which should map to vec4<f32>.
+ uint32_t member_type_id = 0;
+ /// The ID of the type of a pointer to the struct in the Output storage
+ /// class class.
+ uint32_t pointer_type_id = 0;
+ /// The SPIR-V storage class.
+ SpvStorageClass storage_class = SpvStorageClassOutput;
+ /// The ID of the type of a pointer to the Position member.
+ uint32_t member_pointer_type_id = 0;
+ /// The ID of the gl_PerVertex variable, if it was declared.
+ /// We'll use this for the gl_Position variable instead.
+ uint32_t per_vertex_var_id = 0;
+ };
+ /// @returns info about the gl_Position builtin variable.
+ const BuiltInPositionInfo& GetBuiltInPositionInfo() {
+ return builtin_position_;
+ }
+
private:
/// Converts a specific SPIR-V type to a Tint type. Integer case
ast::type::Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);
@@ -347,8 +373,12 @@
uint32_t type_id,
const spvtools::opt::analysis::Struct* struct_ty);
/// Converts a specific SPIR-V type to a Tint type. Pointer case
+ /// The pointer to gl_PerVertex maps to nullptr, and instead is recorded
+ /// in member |builtin_position_|.
+ /// @param type_id the SPIR-V ID for the type.
/// @param ptr_ty the Tint type
- ast::type::Type* ConvertType(const spvtools::opt::analysis::Pointer* ptr_ty);
+ ast::type::Type* ConvertType(uint32_t type_id,
+ const spvtools::opt::analysis::Pointer* ptr_ty);
/// Applies SPIR-V decorations to the given array or runtime-array type.
/// @param spv_type the SPIR-V aray or runtime-array type.
@@ -402,6 +432,13 @@
std::unordered_map<ast::type::Type*, ast::type::Type*> signed_type_for_;
// Maps an signed type corresponding to the given unsigned type.
std::unordered_map<ast::type::Type*, ast::type::Type*> unsigned_type_for_;
+
+ // Bookkeeping for the gl_Position builtin.
+ // In Vulkan SPIR-V, it's the 0 member of the gl_PerVertex structure.
+ // But in WGSL we make a module-scope variable:
+ // [[position]] var<in> gl_Position : vec4<f32>;
+ // The builtin variable was detected if and only if the struct_id is non-zero.
+ BuiltInPositionInfo builtin_position_;
};
} // namespace spirv
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index b0c9d00..11e0f4a 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -24,6 +24,7 @@
namespace spirv {
namespace {
+using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::Not;
@@ -169,7 +170,7 @@
})"));
}
-TEST_F(SpvParserTest, ModuleScopeVar_BuiltinVerteIndex) {
+TEST_F(SpvParserTest, ModuleScopeVar_BuiltinVertexIndex) {
auto* p = parser(test::Assemble(R"(
OpDecorate %52 BuiltIn VertexIndex
%uint = OpTypeInt 32 0
@@ -191,6 +192,299 @@
})"));
}
+std::string PerVertexPreamble() {
+ return R"(
+ OpCapability Shader
+ OpCapability Linkage ; so we don't have to declare an entry point
+ OpMemoryModel Logical Simple
+
+ OpMemberDecorate %10 0 BuiltIn Position
+ OpMemberDecorate %10 1 BuiltIn PointSize
+ OpMemberDecorate %10 2 BuiltIn ClipDistance
+ OpMemberDecorate %10 3 BuiltIn CullDistance
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %12 = OpTypeVector %float 4
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_1 = OpConstant %uint 1
+ %arr = OpTypeArray %float %uint_1
+ %10 = OpTypeStruct %12 %float %arr %arr
+ %11 = OpTypePointer Output %10
+ %1 = OpVariable %11 Output
+)";
+}
+
+TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPosition_MapsToModuleScopeVec4Var) {
+ // In Vulkan SPIR-V, Position is the first member of gl_PerVertex
+ const std::string assembly = PerVertexPreamble();
+ auto* p = parser(test::Assemble(assembly));
+
+ EXPECT_TRUE(p->BuildAndParseInternalModule()) << assembly;
+ EXPECT_TRUE(p->error().empty()) << p->error();
+ const auto& position_info = p->GetBuiltInPositionInfo();
+ EXPECT_EQ(position_info.struct_type_id, 10u);
+ EXPECT_EQ(position_info.member_index, 0u);
+ EXPECT_EQ(position_info.member_type_id, 12u);
+ EXPECT_EQ(position_info.pointer_type_id, 11u);
+ EXPECT_EQ(position_info.storage_class, SpvStorageClassOutput);
+ EXPECT_EQ(position_info.per_vertex_var_id, 1u);
+ const auto module_str = p->module().to_str();
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ DecoratedVariable{
+ Decorations{
+ BuiltinDecoration{position}
+ }
+ gl_Position
+ out
+ __vec_4__f32
+ })"))
+ << module_str;
+}
+
+TEST_F(SpvParserTest,
+ ModuleScopeVar_BuiltinPosition_StoreWholeStruct_NotSupported) {
+ // Glslang does not generate this code pattern.
+ const std::string assembly = PerVertexPreamble() + R"(
+ %nil = OpConstantNull %10 ; the whole struct
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ OpStore %1 %nil ; store the whole struct
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
+ EXPECT_THAT(p->error(), Eq("storing to the whole per-vertex structure is not "
+ "supported: OpStore %1 %9"))
+ << p->error();
+}
+
+TEST_F(SpvParserTest,
+ ModuleScopeVar_BuiltinPosition_IntermediateWholeStruct_NotSupported) {
+ const std::string assembly = PerVertexPreamble() + R"(
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %1000 = OpUndef %10
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_FALSE(p->BuildAndParseInternalModule()) << assembly;
+ EXPECT_THAT(p->error(), Eq("operations producing a per-vertex structure are "
+ "not supported: %1000 = OpUndef %10"))
+ << p->error();
+}
+
+TEST_F(SpvParserTest,
+ ModuleScopeVar_BuiltinPosition_IntermediatePtrWholeStruct_NotSupported) {
+ const std::string assembly = PerVertexPreamble() + R"(
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %1000 = OpUndef %11
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_FALSE(p->BuildAndParseInternalModule());
+ EXPECT_THAT(p->error(), Eq("operations producing a per-vertex structure are "
+ "not supported: %1000 = OpUndef %11"))
+ << p->error();
+}
+
+TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPosition_StorePosition) {
+ const std::string assembly = PerVertexPreamble() + R"(
+ %ptr_v4float = OpTypePointer Output %12
+ %nil = OpConstantNull %12
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %100 = OpAccessChain %ptr_v4float %1 %uint_0 ; address of the Position member
+ OpStore %100 %nil
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_TRUE(p->BuildAndParseInternalModule());
+ EXPECT_TRUE(p->error().empty());
+ const auto module_str = p->module().to_str();
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ Assignment{
+ Identifier{gl_Position}
+ TypeConstructor{
+ __vec_4__f32
+ ScalarConstructor{0.000000}
+ ScalarConstructor{0.000000}
+ ScalarConstructor{0.000000}
+ ScalarConstructor{0.000000}
+ }
+ })"))
+ << module_str;
+}
+
+TEST_F(SpvParserTest,
+ ModuleScopeVar_BuiltinPosition_StorePositionMember_OneAccessChain) {
+ const std::string assembly = PerVertexPreamble() + R"(
+ %ptr_float = OpTypePointer Output %float
+ %nil = OpConstantNull %float
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %100 = OpAccessChain %ptr_float %1 %uint_0 %uint_1 ; address of the Position.y member
+ OpStore %100 %nil
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_TRUE(p->BuildAndParseInternalModule());
+ EXPECT_TRUE(p->error().empty());
+ const auto module_str = p->module().to_str();
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ Assignment{
+ MemberAccessor{
+ Identifier{gl_Position}
+ Identifier{y}
+ }
+ ScalarConstructor{0.000000}
+ })"))
+ << module_str;
+}
+
+TEST_F(SpvParserTest,
+ ModuleScopeVar_BuiltinPosition_StorePositionMember_TwoAccessChain) {
+ // The algorithm is smart enough to collapse it down.
+ const std::string assembly = PerVertexPreamble() + R"(
+ %ptr_v4float = OpTypePointer Output %12
+ %ptr_float = OpTypePointer Output %float
+ %nil = OpConstantNull %float
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %100 = OpAccessChain %ptr_v4float %1 %uint_0 ; address of the Position member
+ %101 = OpAccessChain %ptr_float %100 %uint_1 ; address of the Position.y member
+ OpStore %101 %nil
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_TRUE(p->BuildAndParseInternalModule());
+ EXPECT_TRUE(p->error().empty());
+ const auto module_str = p->module().to_str();
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ {
+ Assignment{
+ MemberAccessor{
+ Identifier{gl_Position}
+ Identifier{y}
+ }
+ ScalarConstructor{0.000000}
+ }
+ Return{}
+ })"))
+ << module_str;
+}
+
+TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPointSize_NotSupported) {
+ const std::string assembly = PerVertexPreamble() + R"(
+ %ptr_v4float = OpTypePointer Output %12
+ %nil = OpConstantNull %12
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %100 = OpAccessChain %ptr_v4float %1 %uint_1 ; address of the PointSize member
+ OpStore %100 %nil
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_FALSE(p->BuildAndParseInternalModule());
+ EXPECT_THAT(p->error(), Eq("accessing per-vertex member 1 is not supported. "
+ "Only Position is supported"));
+}
+
+TEST_F(SpvParserTest, ModuleScopeVar_BuiltinClipDistance_NotSupported) {
+ const std::string assembly = PerVertexPreamble() + R"(
+ %ptr_float = OpTypePointer Output %float
+ %nil = OpConstantNull %float
+ %uint_2 = OpConstant %uint 2
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+; address of the first entry in ClipDistance
+ %100 = OpAccessChain %ptr_float %1 %uint_2 %uint_0
+ OpStore %100 %nil
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_FALSE(p->BuildAndParseInternalModule());
+ EXPECT_THAT(p->error(), Eq("accessing per-vertex member 2 is not supported. "
+ "Only Position is supported"));
+}
+
+TEST_F(SpvParserTest, ModuleScopeVar_BuiltinCullDistance_NotSupported) {
+ const std::string assembly = PerVertexPreamble() + R"(
+ %ptr_float = OpTypePointer Output %float
+ %nil = OpConstantNull %float
+ %uint_3 = OpConstant %uint 3
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+; address of the first entry in CullDistance
+ %100 = OpAccessChain %ptr_float %1 %uint_3 %uint_0
+ OpStore %100 %nil
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_FALSE(p->BuildAndParseInternalModule());
+ EXPECT_THAT(p->error(), Eq("accessing per-vertex member 3 is not supported. "
+ "Only Position is supported"));
+}
+
+TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPerVertex_MemberIndex_NotConstant) {
+ const std::string assembly = PerVertexPreamble() + R"(
+ %ptr_float = OpTypePointer Output %float
+ %nil = OpConstantNull %float
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %sum = OpIAdd %uint %uint_0 %uint_0
+ %100 = OpAccessChain %ptr_float %1 %sum
+ OpStore %100 %nil
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_FALSE(p->BuildAndParseInternalModule());
+ EXPECT_THAT(p->error(),
+ Eq("first index of access chain into per-vertex structure is not "
+ "a constant: %100 = OpAccessChain %9 %1 %16"));
+}
+
+TEST_F(SpvParserTest,
+ ModuleScopeVar_BuiltinPerVertex_MemberIndex_NotConstantInteger) {
+ const std::string assembly = PerVertexPreamble() + R"(
+ %ptr_float = OpTypePointer Output %float
+ %nil = OpConstantNull %float
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+; nil is bad here!
+ %100 = OpAccessChain %ptr_float %1 %nil
+ OpStore %100 %nil
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto* p = parser(test::Assemble(assembly));
+ EXPECT_FALSE(p->BuildAndParseInternalModule());
+ EXPECT_THAT(p->error(),
+ Eq("first index of access chain into per-vertex structure is not "
+ "a constant integer: %100 = OpAccessChain %9 %1 %13"));
+}
+
TEST_F(SpvParserTest, ModuleScopeVar_ScalarInitializers) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%1 = OpVariable %ptr_bool Private %true