[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>
This commit is contained in:
David Neto 2020-07-16 20:15:22 +00:00
parent 31cfdb111e
commit 5f43fedcdd
4 changed files with 495 additions and 27 deletions

View File

@ -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<ast::AssignmentStatement>(
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<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 @@ 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 =

View File

@ -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<ast::StructMember>(
@ -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<uint32_t>(ptr_ty->storage_class());
<< static_cast<uint32_t>(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<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 @@ 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<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_;
}

View File

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

View File

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