diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc index c6ed309d8e..f62c967f0b 100644 --- a/src/reader/spirv/function.cc +++ b/src/reader/spirv/function.cc @@ -2512,9 +2512,21 @@ bool FunctionEmitter::EmitConstDefOrWriteToHoistedVar( 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 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) { 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( std::move(lhs.expr), std::move(rhs.expr))); return success(); @@ -2737,7 +2759,56 @@ TypedExpression FunctionEmitter::MakeAccessChain( 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(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 @@ TypedExpression FunctionEmitter::MakeAccessChain( 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 6794a378fc..27cac0a7f8 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 @@ ast::type::Type* ParserImpl::ConvertType(uint32_t type_id) { 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 @@ ast::type::Type* ParserImpl::ConvertType(uint32_t type_id) { 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 @@ bool ParserImpl::RegisterUserAndStructMemberNames() { case SpvOpName: namer_.SuggestSanitizedName(inst.GetSingleWordInOperand(0), inst.GetInOperand(1).AsString()); + break; case SpvOpMemberName: namer_.SuggestSanitizedMemberName(inst.GetSingleWordInOperand(0), @@ -690,19 +692,43 @@ ast::type::Type* ParserImpl::ConvertType( 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( @@ -723,20 +749,27 @@ ast::type::Type* ParserImpl::ConvertType( } 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(ptr_ty->storage_class()); + << static_cast(storage_class); return nullptr; } return ctx_.type_mgr().Get( @@ -754,6 +787,13 @@ bool ParserImpl::RegisterTypes() { } 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 @@ bool ParserImpl::EmitModuleScopeVariables() { } 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(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 @@ bool ParserImpl::EmitModuleScopeVariables() { // 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(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::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 281adaa118..6fc60a5bff 100644 --- a/src/reader/spirv/parser_impl.h +++ b/src/reader/spirv/parser_impl.h @@ -133,6 +133,8 @@ class ParserImpl : Reader { 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 @@ class ParserImpl : Reader { /// @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. + 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 @@ class ParserImpl : Reader { 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 @@ class ParserImpl : Reader { std::unordered_map signed_type_for_; // Maps an signed type corresponding to the given unsigned type. std::unordered_map 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 gl_Position : vec4; + // 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 b0c9d00348..11e0f4a7ff 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 reader { namespace spirv { namespace { +using ::testing::Eq; using ::testing::HasSubstr; using ::testing::Not; @@ -169,7 +170,7 @@ TEST_F(SpvParserTest, ModuleScopeVar_PrivateVar) { })")); } -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 @@ TEST_F(SpvParserTest, ModuleScopeVar_BuiltinVerteIndex) { })")); } +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