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:
parent
e9b90f755b
commit
e5d288be5e
|
@ -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>(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue