spirv-reader: ignore storing 1.0 to PointSize builtin

If you try to load it, return 1.0f instead.
Some cases of copy-object of intermediates are unhandled,
and will error out.

This is being done as an aid to porting GLSL Vulkan shaders
that do store 1 to gl_PointSize.

Fixed: tint:412
Change-Id: Ia33dc70bca630dccfbf11644f71d6be4b3f43f1a
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/35861
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Auto-Submit: David Neto <dneto@google.com>
This commit is contained in:
David Neto 2020-12-16 20:50:10 +00:00 committed by Commit Bot service account
parent e9b90f755b
commit e5d288be5e
5 changed files with 370 additions and 51 deletions

View File

@ -41,6 +41,7 @@
#include "src/ast/discard_statement.h" #include "src/ast/discard_statement.h"
#include "src/ast/else_statement.h" #include "src/ast/else_statement.h"
#include "src/ast/fallthrough_statement.h" #include "src/ast/fallthrough_statement.h"
#include "src/ast/float_literal.h"
#include "src/ast/identifier_expression.h" #include "src/ast/identifier_expression.h"
#include "src/ast/if_statement.h" #include "src/ast/if_statement.h"
#include "src/ast/intrinsic.h" #include "src/ast/intrinsic.h"
@ -1971,6 +1972,24 @@ TypedExpression FunctionEmitter::MakeExpression(uint32_t id) {
if (failed()) { if (failed()) {
return {}; return {};
} }
switch (GetSkipReason(id)) {
case SkipReason::kDontSkip:
break;
case SkipReason::kOpaqueObject:
Fail() << "internal error: unhandled use of opaque object with ID: "
<< id;
return {};
case SkipReason::kPointSizeBuiltinValue: {
auto* f32 = create<ast::type::F32>();
return {f32,
create<ast::ScalarConstructorExpression>(
Source{}, create<ast::FloatLiteral>(Source{}, f32, 1.0f))};
}
case SkipReason::kPointSizeBuiltinPointer:
Fail() << "unhandled use of a pointer to the PointSize builtin, with ID: "
<< id;
return {};
}
if (identifier_values_.count(id) || parser_impl_.IsScalarSpecConstant(id)) { if (identifier_values_.count(id) || parser_impl_.IsScalarSpecConstant(id)) {
auto name = namer_.Name(id); auto name = namer_.Name(id);
return TypedExpression{ return TypedExpression{
@ -2848,21 +2867,31 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
if (type_id != 0) { if (type_id != 0) {
const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo(); const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
if ((type_id == builtin_position_info.struct_type_id) || 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 " return Fail() << "operations producing a per-vertex structure are not "
"supported: " "supported: "
<< inst.PrettyPrint(); << inst.PrettyPrint();
} }
if (type_id == builtin_position_info.pointer_type_id) {
return Fail() << "operations producing a pointer to a per-vertex "
"structure are not "
"supported: "
<< inst.PrettyPrint();
}
} }
// Handle combinatorial instructions. // Handle combinatorial instructions.
const auto* def_info = GetDefInfo(result_id); const auto* def_info = GetDefInfo(result_id);
if (def_info) { if (def_info) {
TypedExpression combinatorial_expr;
if (def_info->skip == SkipReason::kDontSkip) {
combinatorial_expr = MaybeEmitCombinatorialValue(inst);
}
// An access chain or OpCopyObject can generate a skip.
if (def_info->skip != SkipReason::kDontSkip) { if (def_info->skip != SkipReason::kDontSkip) {
return true; return true;
} }
auto combinatorial_expr = MaybeEmitCombinatorialValue(inst);
if (combinatorial_expr.expr != nullptr) { if (combinatorial_expr.expr != nullptr) {
if (def_info->requires_hoisted_def || if (def_info->requires_hoisted_def ||
def_info->requires_named_const_def || def_info->num_uses != 1) { def_info->requires_named_const_def || def_info->num_uses != 1) {
@ -2892,6 +2921,23 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
case SpvOpStore: { case SpvOpStore: {
const auto ptr_id = inst.GetSingleWordInOperand(0); const auto ptr_id = inst.GetSingleWordInOperand(0);
const auto value_id = inst.GetSingleWordInOperand(1); const auto value_id = inst.GetSingleWordInOperand(1);
// Handle exceptional cases
if (GetSkipReason(ptr_id) == SkipReason::kPointSizeBuiltinPointer) {
if (const auto* c = constant_mgr_->FindDeclaredConstant(value_id)) {
// If we're writing a constant 1.0, then skip the write. That's all
// that WebGPU handles.
auto* ct = c->type();
if (ct->AsFloat() && (ct->AsFloat()->width() == 32) &&
(c->GetFloat() == 1.0f)) {
// Don't store to PointSize
return true;
}
}
return Fail() << "cannot store a value other than constant 1.0 to "
"PointSize builtin: "
<< inst.PrettyPrint();
}
const auto ptr_type_id = def_use_mgr_->GetDef(ptr_id)->type_id(); const auto ptr_type_id = def_use_mgr_->GetDef(ptr_id)->type_id();
const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo(); const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
if (ptr_type_id == builtin_position_info.pointer_type_id) { if (ptr_type_id == builtin_position_info.pointer_type_id) {
@ -2900,6 +2946,7 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
<< inst.PrettyPrint(); << inst.PrettyPrint();
} }
// Handle an ordinary store as an assignment.
// TODO(dneto): Order of evaluation? // TODO(dneto): Order of evaluation?
auto lhs = MakeExpression(ptr_id); auto lhs = MakeExpression(ptr_id);
auto rhs = MakeExpression(value_id); auto rhs = MakeExpression(value_id);
@ -2907,23 +2954,42 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
create<ast::AssignmentStatement>(Source{}, lhs.expr, rhs.expr)); create<ast::AssignmentStatement>(Source{}, lhs.expr, rhs.expr));
return success(); return success();
} }
case SpvOpLoad: { case SpvOpLoad: {
// Memory accesses must be issued in SPIR-V program order. // Memory accesses must be issued in SPIR-V program order.
// So represent a load by a new const definition. // So represent a load by a new const definition.
auto expr = MakeExpression(inst.GetSingleWordInOperand(0)); const auto ptr_id = inst.GetSingleWordInOperand(0);
if (GetSkipReason(ptr_id) == SkipReason::kPointSizeBuiltinPointer) {
GetDefInfo(inst.result_id())->skip = SkipReason::kPointSizeBuiltinValue;
return true;
}
auto expr = MakeExpression(ptr_id);
// The load result type is the pointee type of its operand. // The load result type is the pointee type of its operand.
assert(expr.type->Is<ast::type::Pointer>()); assert(expr.type->Is<ast::type::Pointer>());
expr.type = expr.type->As<ast::type::Pointer>()->type(); expr.type = expr.type->As<ast::type::Pointer>()->type();
return EmitConstDefOrWriteToHoistedVar(inst, expr); return EmitConstDefOrWriteToHoistedVar(inst, expr);
} }
case SpvOpCopyObject: { case SpvOpCopyObject: {
// Arguably, OpCopyObject is purely combinatorial. On the other hand, // Arguably, OpCopyObject is purely combinatorial. On the other hand,
// it exists to make a new name for something. So we choose to make // it exists to make a new name for something. So we choose to make
// a new named constant definition. // a new named constant definition.
auto expr = MakeExpression(inst.GetSingleWordInOperand(0)); auto value_id = inst.GetSingleWordInOperand(0);
const auto skip = GetSkipReason(value_id);
switch (skip) {
case SkipReason::kDontSkip:
break;
case SkipReason::kOpaqueObject:
case SkipReason::kPointSizeBuiltinPointer:
case SkipReason::kPointSizeBuiltinValue:
GetDefInfo(inst.result_id())->skip = skip;
return true;
}
auto expr = MakeExpression(value_id);
expr.type = RemapStorageClass(expr.type, result_id); expr.type = RemapStorageClass(expr.type, result_id);
return EmitConstDefOrWriteToHoistedVar(inst, expr); return EmitConstDefOrWriteToHoistedVar(inst, expr);
} }
case SpvOpPhi: { case SpvOpPhi: {
// Emit a read from the associated state variable. // Emit a read from the associated state variable.
TypedExpression expr{ TypedExpression expr{
@ -2933,8 +2999,10 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
def_info->phi_var)}; def_info->phi_var)};
return EmitConstDefOrWriteToHoistedVar(inst, expr); return EmitConstDefOrWriteToHoistedVar(inst, expr);
} }
case SpvOpFunctionCall: case SpvOpFunctionCall:
return EmitFunctionCall(inst); return EmitFunctionCall(inst);
default: default:
break; break;
} }
@ -3159,6 +3227,12 @@ TypedExpression FunctionEmitter::MakeAccessChain(
// If the variable was originally gl_PerVertex, then in the AST we // If the variable was originally gl_PerVertex, then in the AST we
// have instead emitted a gl_Position variable. // have instead emitted a gl_Position variable.
// If computing the pointer to the Position builtin, then emit the
// pointer to the generated gl_Position variable.
// If computing the pointer to the PointSize builtin, then mark the
// result as skippable due to being the point-size pointer.
// If computing the pointer to the ClipDistance or CullDistance builtins,
// then error out.
{ {
const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo(); const auto& builtin_position_info = parser_impl_.GetBuiltInPositionInfo();
if (base_id == builtin_position_info.per_vertex_var_id) { if (base_id == builtin_position_info.per_vertex_var_id) {
@ -3188,16 +3262,26 @@ TypedExpression FunctionEmitter::MakeAccessChain(
} }
const auto member_index_value = const auto member_index_value =
member_index_const_int->GetZeroExtendedValue(); member_index_const_int->GetZeroExtendedValue();
if (member_index_value != builtin_position_info.member_index) { if (member_index_value != builtin_position_info.position_member_index) {
Fail() << "accessing per-vertex member " << member_index_value if (member_index_value ==
<< " is not supported. Only Position is supported"; builtin_position_info.pointsize_member_index) {
if (auto* def_info = GetDefInfo(inst.result_id())) {
def_info->skip = SkipReason::kPointSizeBuiltinPointer;
return {}; return {};
} }
} else {
// TODO(dneto): Handle ClipDistance and CullDistance
Fail() << "accessing per-vertex member " << member_index_value
<< " is not supported. Only Position is supported, and "
"PointSize is ignored";
return {};
}
}
// Skip past the member index that gets us to Position. // Skip past the member index that gets us to Position.
first_index = first_index + 1; first_index = first_index + 1;
// Replace the gl_PerVertex reference with the gl_Position reference // Replace the gl_PerVertex reference with the gl_Position reference
ptr_ty_id = builtin_position_info.member_pointer_type_id; ptr_ty_id = builtin_position_info.position_member_pointer_type_id;
auto name = namer_.Name(base_id); auto name = namer_.Name(base_id);
current_expr.expr = create<ast::IdentifierExpression>( current_expr.expr = create<ast::IdentifierExpression>(

View File

@ -213,11 +213,14 @@ enum class SkipReason {
/// function parameter). /// function parameter).
kOpaqueObject, kOpaqueObject,
/// `kPointSizeBuiltin`: the value is a pointer to the Position builtin /// `kPointSizeBuiltinPointer`: the value is a pointer to the Position builtin
/// variable. Don't generate its address. Avoid generating stores to /// variable. Don't generate its address. Avoid generating stores to this
/// this pointer. When loading from the pointer, yield the value 1, /// pointer.
/// the only supported value for PointSize. kPointSizeBuiltinPointer,
kPointSizeBuiltin /// `kPointSizeBuiltinValue`: the value is the value loaded from the
/// PointSize builtin. Use 1.0f instead, because that's the only value
/// supported by WebGPU.
kPointSizeBuiltinValue,
}; };
/// Bookkeeping info for a SPIR-V ID defined in the function. /// Bookkeeping info for a SPIR-V ID defined in the function.
@ -287,6 +290,10 @@ struct DefInfo {
/// This is kNone for non-pointers. /// This is kNone for non-pointers.
ast::StorageClass storage_class = ast::StorageClass::kNone; ast::StorageClass storage_class = ast::StorageClass::kNone;
/// The reason, if any, that this value should not be generated.
/// Normally all values are generated. This field can be updated while
/// generating code because sometimes we only discover necessary facts
/// in the middle of generating code.
SkipReason skip = SkipReason::kDontSkip; SkipReason skip = SkipReason::kDontSkip;
}; };
@ -307,8 +314,11 @@ inline std::ostream& operator<<(std::ostream& o, const DefInfo& di) {
case SkipReason::kOpaqueObject: case SkipReason::kOpaqueObject:
o << " skip:opaque"; o << " skip:opaque";
break; break;
case SkipReason::kPointSizeBuiltin: case SkipReason::kPointSizeBuiltinPointer:
o << " skip:pointsize"; o << " skip:pointsize_pointer";
break;
case SkipReason::kPointSizeBuiltinValue:
o << " skip:pointsize_value";
break; break;
} }
o << "}"; o << "}";
@ -696,6 +706,15 @@ class FunctionEmitter {
} }
return where->second.get(); return where->second.get();
} }
/// Returns the skip reason for a result ID.
/// @param id SPIR-V result ID
/// @returns the skip reason for the given ID, or SkipReason::kDontSkip
SkipReason GetSkipReason(uint32_t id) const {
if (auto* def_info = GetDefInfo(id)) {
return def_info->skip;
}
return SkipReason::kDontSkip;
}
/// Returns the most deeply nested structured construct which encloses the /// Returns the most deeply nested structured construct which encloses the
/// WGSL scopes of names declared in both block positions. Each position must /// WGSL scopes of names declared in both block positions. Each position must

View File

@ -886,6 +886,7 @@ ast::type::Type* ParserImpl::ConvertType(
ast::StructMemberList ast_members; ast::StructMemberList ast_members;
const auto members = struct_ty->element_types(); const auto members = struct_ty->element_types();
unsigned num_non_writable_members = 0; unsigned num_non_writable_members = 0;
bool is_per_vertex_struct = false;
for (uint32_t member_index = 0; member_index < members.size(); for (uint32_t member_index = 0; member_index < members.size();
++member_index) { ++member_index) {
const auto member_type_id = type_mgr_->GetId(members[member_index]); const auto member_type_id = type_mgr_->GetId(members[member_index]);
@ -906,18 +907,24 @@ ast::type::Type* ParserImpl::ConvertType(
case SpvBuiltInPosition: case SpvBuiltInPosition:
// Record this built-in variable specially. // Record this built-in variable specially.
builtin_position_.struct_type_id = type_id; builtin_position_.struct_type_id = type_id;
builtin_position_.member_index = member_index; builtin_position_.position_member_index = member_index;
builtin_position_.member_type_id = member_type_id; builtin_position_.position_member_type_id = member_type_id;
// Don't map the struct type. But this is not an error either. // Don't map the struct type. But this is not an error either.
return nullptr; is_per_vertex_struct = true;
case SpvBuiltInPointSize: // not supported in WGSL
case SpvBuiltInCullDistance: // not supported in WGSL
case SpvBuiltInClipDistance: // not supported in WGSL
default:
break; break;
} case SpvBuiltInPointSize: // not supported in WGSL, but ignore
builtin_position_.pointsize_member_index = member_index;
is_per_vertex_struct = true;
break;
case SpvBuiltInClipDistance: // not supported in WGSL
case SpvBuiltInCullDistance: // not supported in WGSL
// Silently ignore, so we can detect Position and PointSize
is_per_vertex_struct = true;
break;
default:
Fail() << "unrecognized builtin " << decoration[1]; Fail() << "unrecognized builtin " << decoration[1];
return nullptr; return nullptr;
}
} else if (decoration[0] == SpvDecorationNonWritable) { } else if (decoration[0] == SpvDecorationNonWritable) {
// WGSL doesn't represent individual members as non-writable. Instead, // WGSL doesn't represent individual members as non-writable. Instead,
// apply the ReadOnly access control to the containing struct if all // apply the ReadOnly access control to the containing struct if all
@ -945,6 +952,10 @@ ast::type::Type* ParserImpl::ConvertType(
ast_member_ty, std::move(ast_member_decorations)); ast_member_ty, std::move(ast_member_decorations));
ast_members.push_back(ast_struct_member); ast_members.push_back(ast_struct_member);
} }
if (is_per_vertex_struct) {
// We're replacing it by the Position builtin alone.
return nullptr;
}
// Now make the struct. // Now make the struct.
auto* ast_struct = create<ast::Struct>(Source{}, std::move(ast_members), auto* ast_struct = create<ast::Struct>(Source{}, std::move(ast_members),
@ -1010,10 +1021,11 @@ bool ParserImpl::RegisterTypes() {
} }
// Manufacture a type for the gl_Position varible if we have to. // Manufacture a type for the gl_Position varible if we have to.
if ((builtin_position_.struct_type_id != 0) && if ((builtin_position_.struct_type_id != 0) &&
(builtin_position_.member_pointer_type_id == 0)) { (builtin_position_.position_member_pointer_type_id == 0)) {
builtin_position_.member_pointer_type_id = type_mgr_->FindPointerToType( builtin_position_.position_member_pointer_type_id =
builtin_position_.member_type_id, builtin_position_.storage_class); type_mgr_->FindPointerToType(builtin_position_.position_member_type_id,
ConvertType(builtin_position_.member_pointer_type_id); builtin_position_.storage_class);
ConvertType(builtin_position_.position_member_pointer_type_id);
} }
return success_; return success_;
} }
@ -1208,7 +1220,7 @@ bool ParserImpl::EmitModuleScopeVariables() {
auto* var = MakeVariable( auto* var = MakeVariable(
builtin_position_.per_vertex_var_id, builtin_position_.per_vertex_var_id,
enum_converter_.ToStorageClass(builtin_position_.storage_class), enum_converter_.ToStorageClass(builtin_position_.storage_class),
ConvertType(builtin_position_.member_type_id), false, nullptr, ConvertType(builtin_position_.position_member_type_id), false, nullptr,
ast::VariableDecorationList{ ast::VariableDecorationList{
create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kPosition), create<ast::BuiltinDecoration>(Source{}, ast::Builtin::kPosition),
}); });

View File

@ -373,16 +373,18 @@ class ParserImpl : Reader {
/// The ID for the gl_PerVertex struct containing the Position builtin. /// The ID for the gl_PerVertex struct containing the Position builtin.
uint32_t struct_type_id = 0; uint32_t struct_type_id = 0;
/// The member index for the Position builtin within the struct. /// The member index for the Position builtin within the struct.
uint32_t member_index = 0; uint32_t position_member_index = 0;
/// The member index for the PointSize builtin within the struct.
uint32_t pointsize_member_index = 0;
/// The ID for the member type, which should map to vec4<f32>. /// The ID for the member type, which should map to vec4<f32>.
uint32_t member_type_id = 0; uint32_t position_member_type_id = 0;
/// The ID of the type of a pointer to the struct in the Output storage /// The ID of the type of a pointer to the struct in the Output storage
/// class class. /// class class.
uint32_t pointer_type_id = 0; uint32_t pointer_type_id = 0;
/// The SPIR-V storage class. /// The SPIR-V storage class.
SpvStorageClass storage_class = SpvStorageClassOutput; SpvStorageClass storage_class = SpvStorageClassOutput;
/// The ID of the type of a pointer to the Position member. /// The ID of the type of a pointer to the Position member.
uint32_t member_pointer_type_id = 0; uint32_t position_member_pointer_type_id = 0;
/// The ID of the gl_PerVertex variable, if it was declared. /// The ID of the gl_PerVertex variable, if it was declared.
/// We'll use this for the gl_Position variable instead. /// We'll use this for the gl_Position variable instead.
uint32_t per_vertex_var_id = 0; uint32_t per_vertex_var_id = 0;

View File

@ -243,8 +243,8 @@ TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPosition_MapsToModuleScopeVec4Var) {
EXPECT_TRUE(p->error().empty()) << p->error(); EXPECT_TRUE(p->error().empty()) << p->error();
const auto& position_info = p->GetBuiltInPositionInfo(); const auto& position_info = p->GetBuiltInPositionInfo();
EXPECT_EQ(position_info.struct_type_id, 10u); EXPECT_EQ(position_info.struct_type_id, 10u);
EXPECT_EQ(position_info.member_index, 0u); EXPECT_EQ(position_info.position_member_index, 0u);
EXPECT_EQ(position_info.member_type_id, 12u); EXPECT_EQ(position_info.position_member_type_id, 12u);
EXPECT_EQ(position_info.pointer_type_id, 11u); EXPECT_EQ(position_info.pointer_type_id, 11u);
EXPECT_EQ(position_info.storage_class, SpvStorageClassOutput); EXPECT_EQ(position_info.storage_class, SpvStorageClassOutput);
EXPECT_EQ(position_info.per_vertex_var_id, 1u); EXPECT_EQ(position_info.per_vertex_var_id, 1u);
@ -307,7 +307,8 @@ TEST_F(SpvParserTest,
)"; )";
auto p = parser(test::Assemble(assembly)); auto p = parser(test::Assemble(assembly));
EXPECT_FALSE(p->BuildAndParseInternalModule()); EXPECT_FALSE(p->BuildAndParseInternalModule());
EXPECT_THAT(p->error(), Eq("operations producing a per-vertex structure are " EXPECT_THAT(p->error(),
Eq("operations producing a pointer to a per-vertex structure are "
"not supported: %1000 = OpUndef %11")) "not supported: %1000 = OpUndef %11"))
<< p->error(); << p->error();
} }
@ -343,6 +344,61 @@ TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPosition_StorePosition) {
<< module_str; << module_str;
} }
TEST_F(
SpvParserTest,
ModuleScopeVar_BuiltinPosition_StorePosition_PerVertexStructOutOfOrderDecl) {
const std::string assembly = R"(
OpCapability Shader
OpCapability Linkage ; so we don't have to declare an entry point
OpMemoryModel Logical Simple
; scramble the member indices
OpMemberDecorate %10 0 BuiltIn ClipDistance
OpMemberDecorate %10 1 BuiltIn CullDistance
OpMemberDecorate %10 2 BuiltIn Position
OpMemberDecorate %10 3 BuiltIn PointSize
%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
%uint_2 = OpConstant %uint 2
%arr = OpTypeArray %float %uint_1
%10 = OpTypeStruct %arr %arr %12 %float
%11 = OpTypePointer Output %10
%1 = OpVariable %11 Output
%ptr_v4float = OpTypePointer Output %12
%nil = OpConstantNull %12
%main = OpFunction %void None %voidfn
%entry = OpLabel
%100 = OpAccessChain %ptr_v4float %1 %uint_2 ; 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 =
Demangler().Demangle(p->get_module(), p->get_module().to_str());
EXPECT_THAT(module_str, HasSubstr(R"(
Assignment{
Identifier[not set]{gl_Position}
TypeConstructor[not set]{
__vec_4__f32
ScalarConstructor[not set]{0.000000}
ScalarConstructor[not set]{0.000000}
ScalarConstructor[not set]{0.000000}
ScalarConstructor[not set]{0.000000}
}
})"))
<< module_str;
}
TEST_F(SpvParserTest, TEST_F(SpvParserTest,
ModuleScopeVar_BuiltinPosition_StorePositionMember_OneAccessChain) { ModuleScopeVar_BuiltinPosition_StorePositionMember_OneAccessChain) {
const std::string assembly = PerVertexPreamble() + R"( const std::string assembly = PerVertexPreamble() + R"(
@ -376,13 +432,13 @@ TEST_F(SpvParserTest,
ModuleScopeVar_BuiltinPosition_StorePositionMember_TwoAccessChain) { ModuleScopeVar_BuiltinPosition_StorePositionMember_TwoAccessChain) {
// The algorithm is smart enough to collapse it down. // The algorithm is smart enough to collapse it down.
const std::string assembly = PerVertexPreamble() + R"( const std::string assembly = PerVertexPreamble() + R"(
%ptr_v4float = OpTypePointer Output %12 %ptr = OpTypePointer Output %12
%ptr_float = OpTypePointer Output %float %ptr_float = OpTypePointer Output %float
%nil = OpConstantNull %float %nil = OpConstantNull %float
%main = OpFunction %void None %voidfn %main = OpFunction %void None %voidfn
%entry = OpLabel %entry = OpLabel
%100 = OpAccessChain %ptr_v4float %1 %uint_0 ; address of the Position member %100 = OpAccessChain %ptr %1 %uint_0 ; address of the Position member
%101 = OpAccessChain %ptr_float %100 %uint_1 ; address of the Position.y member %101 = OpAccessChain %ptr_float %100 %uint_1 ; address of the Position.y member
OpStore %101 %nil OpStore %101 %nil
OpReturn OpReturn
@ -407,22 +463,166 @@ TEST_F(SpvParserTest,
<< module_str; << module_str;
} }
TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPointSize_NotSupported) { TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPointSize_Write1_IsErased) {
const std::string assembly = PerVertexPreamble() + R"( const std::string assembly = PerVertexPreamble() + R"(
%ptr_v4float = OpTypePointer Output %12 %ptr = OpTypePointer Output %float
%nil = OpConstantNull %12 %one = OpConstant %float 1.0
%main = OpFunction %void None %voidfn %main = OpFunction %void None %voidfn
%entry = OpLabel %entry = OpLabel
%100 = OpAccessChain %ptr_v4float %1 %uint_1 ; address of the PointSize member %100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
OpStore %100 %nil OpStore %100 %one
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
EXPECT_TRUE(p->BuildAndParseInternalModule());
EXPECT_TRUE(p->error().empty());
const auto module_str =
Demangler().Demangle(p->get_module(), p->get_module().to_str());
EXPECT_EQ(module_str, R"(Module{
Variable{
Decorations{
BuiltinDecoration{position}
}
gl_Position
out
__vec_4__f32
}
Function x_14 -> __void
()
{
Return{}
}
}
)") << module_str;
}
TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPointSize_WriteNon1_IsError) {
const std::string assembly = PerVertexPreamble() + R"(
%ptr = OpTypePointer Output %float
%999 = OpConstant %float 2.0
%main = OpFunction %void None %voidfn
%entry = OpLabel
%100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
OpStore %100 %999
OpReturn OpReturn
OpFunctionEnd OpFunctionEnd
)"; )";
auto p = parser(test::Assemble(assembly)); auto p = parser(test::Assemble(assembly));
EXPECT_FALSE(p->BuildAndParseInternalModule()); EXPECT_FALSE(p->BuildAndParseInternalModule());
EXPECT_THAT(p->error(), Eq("accessing per-vertex member 1 is not supported. " EXPECT_THAT(p->error(),
"Only Position is supported")); HasSubstr("cannot store a value other than constant 1.0 to "
"PointSize builtin: OpStore %100 %999"));
}
TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPointSize_ReadReplaced) {
const std::string assembly = PerVertexPreamble() + R"(
%ptr = OpTypePointer Output %float
%nil = OpConstantNull %12
%private_ptr = OpTypePointer Private %float
%900 = OpVariable %private_ptr Private
%main = OpFunction %void None %voidfn
%entry = OpLabel
%100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
%99 = OpLoad %float %100
OpStore %900 %99
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
EXPECT_TRUE(p->BuildAndParseInternalModule());
EXPECT_TRUE(p->error().empty());
const auto module_str =
Demangler().Demangle(p->get_module(), p->get_module().to_str());
EXPECT_EQ(module_str, R"(Module{
Variable{
x_900
private
__f32
}
Variable{
Decorations{
BuiltinDecoration{position}
}
gl_Position
out
__vec_4__f32
}
Function x_15 -> __void
()
{
Assignment{
Identifier[not set]{x_900}
ScalarConstructor[not set]{1.000000}
}
Return{}
}
}
)") << module_str;
}
TEST_F(
SpvParserTest,
ModuleScopeVar_BuiltinPointSize_WriteViaCopyObjectPriorAccess_Unsupported) {
const std::string assembly = PerVertexPreamble() + R"(
%ptr = OpTypePointer Output %float
%nil = OpConstantNull %12
%main = OpFunction %void None %voidfn
%entry = OpLabel
%20 = OpCopyObject %11 %1
%100 = OpAccessChain %20 %1 %uint_1 ; address of the PointSize member
OpStore %100 %nil
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
EXPECT_FALSE(p->BuildAndParseInternalModule()) << p->error();
EXPECT_THAT(
p->error(),
HasSubstr("operations producing a pointer to a per-vertex structure are "
"not supported: %20 = OpCopyObject %11 %1"));
}
TEST_F(
SpvParserTest,
ModuleScopeVar_BuiltinPointSize_WriteViaCopyObjectPostAccessChainErased) {
const std::string assembly = PerVertexPreamble() + R"(
%ptr = OpTypePointer Output %12
%one = OpConstant %float 1.0
%main = OpFunction %void None %voidfn
%entry = OpLabel
%100 = OpAccessChain %ptr %1 %uint_1 ; address of the PointSize member
%101 = OpCopyObject %ptr %100
OpStore %101 %one
OpReturn
OpFunctionEnd
)";
auto p = parser(test::Assemble(assembly));
EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
EXPECT_TRUE(p->error().empty());
const auto module_str =
Demangler().Demangle(p->get_module(), p->get_module().to_str());
EXPECT_EQ(module_str, R"(Module{
Variable{
Decorations{
BuiltinDecoration{position}
}
gl_Position
out
__vec_4__f32
}
Function x_14 -> __void
()
{
Return{}
}
}
)") << module_str;
} }
TEST_F(SpvParserTest, ModuleScopeVar_BuiltinClipDistance_NotSupported) { TEST_F(SpvParserTest, ModuleScopeVar_BuiltinClipDistance_NotSupported) {
@ -441,8 +641,9 @@ TEST_F(SpvParserTest, ModuleScopeVar_BuiltinClipDistance_NotSupported) {
)"; )";
auto p = parser(test::Assemble(assembly)); auto p = parser(test::Assemble(assembly));
EXPECT_FALSE(p->BuildAndParseInternalModule()); EXPECT_FALSE(p->BuildAndParseInternalModule());
EXPECT_THAT(p->error(), Eq("accessing per-vertex member 2 is not supported. " EXPECT_EQ(p->error(),
"Only Position is supported")); "accessing per-vertex member 2 is not supported. Only Position is "
"supported, and PointSize is ignored");
} }
TEST_F(SpvParserTest, ModuleScopeVar_BuiltinCullDistance_NotSupported) { TEST_F(SpvParserTest, ModuleScopeVar_BuiltinCullDistance_NotSupported) {
@ -461,8 +662,9 @@ TEST_F(SpvParserTest, ModuleScopeVar_BuiltinCullDistance_NotSupported) {
)"; )";
auto p = parser(test::Assemble(assembly)); auto p = parser(test::Assemble(assembly));
EXPECT_FALSE(p->BuildAndParseInternalModule()); EXPECT_FALSE(p->BuildAndParseInternalModule());
EXPECT_THAT(p->error(), Eq("accessing per-vertex member 3 is not supported. " EXPECT_EQ(p->error(),
"Only Position is supported")); "accessing per-vertex member 3 is not supported. Only Position is "
"supported, and PointSize is ignored");
} }
TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPerVertex_MemberIndex_NotConstant) { TEST_F(SpvParserTest, ModuleScopeVar_BuiltinPerVertex_MemberIndex_NotConstant) {