[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:
parent
31cfdb111e
commit
5f43fedcdd
|
@ -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 =
|
||||
|
|
|
@ -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_;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue