blob: 9f2fe3da2c9f9efe53d4d346f198932559dad436 [file] [log] [blame]
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/ast/transform/vertex_pulling_transform.h"
#include "src/ast/array_accessor_expression.h"
#include "src/ast/as_expression.h"
#include "src/ast/assignment_statement.h"
#include "src/ast/binary_expression.h"
#include "src/ast/decorated_variable.h"
#include "src/ast/expression.h"
#include "src/ast/member_accessor_expression.h"
#include "src/ast/module.h"
#include "src/ast/scalar_constructor_expression.h"
#include "src/ast/struct.h"
#include "src/ast/struct_decoration.h"
#include "src/ast/struct_member.h"
#include "src/ast/struct_member_offset_decoration.h"
#include "src/ast/type/array_type.h"
#include "src/ast/type/f32_type.h"
#include "src/ast/type/i32_type.h"
#include "src/ast/type/struct_type.h"
#include "src/ast/type/u32_type.h"
#include "src/ast/type/vector_type.h"
#include "src/ast/type_constructor_expression.h"
#include "src/ast/uint_literal.h"
#include "src/ast/variable_decl_statement.h"
#include "src/context.h"
namespace tint {
namespace ast {
namespace transform {
namespace {
// TODO(idanr): What to do if these names are already used?
static const char kVertexBufferNamePrefix[] = "tint_pulling_vertex_buffer_";
static const char kStructBufferName[] = "data";
static const char kPullingPosVarName[] = "tint_pulling_pos";
static const char kDefaultVertexIndexName[] = "tint_pulling_vertex_index";
static const char kDefaultInstanceIndexName[] = "tint_pulling_instance_index";
} // namespace
VertexPullingTransform::VertexPullingTransform(Context* ctx, Module* mod)
: ctx_(ctx), mod_(mod) {}
VertexPullingTransform::~VertexPullingTransform() = default;
void VertexPullingTransform::SetVertexState(
std::unique_ptr<VertexStateDescriptor> vertex_state) {
vertex_state_ = std::move(vertex_state);
}
void VertexPullingTransform::SetEntryPoint(std::string entry_point) {
entry_point_name_ = std::move(entry_point);
}
void VertexPullingTransform::SetPullingBufferBindingSet(uint32_t number) {
pulling_set_ = number;
}
bool VertexPullingTransform::Run() {
// Check SetVertexState was called
if (vertex_state_ == nullptr) {
SetError("SetVertexState not called");
return false;
}
// Find entry point
EntryPoint* entry_point = nullptr;
for (const auto& entry : mod_->entry_points()) {
if (entry->name() == entry_point_name_ ||
(entry->name().empty() &&
entry->function_name() == entry_point_name_)) {
entry_point = entry.get();
break;
}
}
if (entry_point == nullptr) {
SetError("Vertex stage entry point not found");
return false;
}
// Check entry point is the right stage
if (entry_point->stage() != PipelineStage::kVertex) {
SetError("Entry point is not for vertex stage");
return false;
}
// Save the vertex function
auto* vertex_func = mod_->FindFunctionByName(entry_point->function_name());
// TODO(idanr): Need to check shader locations in descriptor cover all
// attributes
// TODO(idanr): Make sure we covered all error cases, to guarantee the
// following stages will pass
FindOrInsertVertexIndexIfUsed();
FindOrInsertInstanceIndexIfUsed();
ConvertVertexInputVariablesToPrivate();
AddVertexStorageBuffers();
AddVertexPullingPreamble(vertex_func);
return true;
}
void VertexPullingTransform::SetError(const std::string& error) {
error_ = error;
}
std::string VertexPullingTransform::GetVertexBufferName(uint32_t index) {
return kVertexBufferNamePrefix + std::to_string(index);
}
void VertexPullingTransform::FindOrInsertVertexIndexIfUsed() {
bool uses_vertex_step_mode = false;
for (const VertexBufferLayoutDescriptor& buffer_layout :
vertex_state_->vertex_buffers) {
if (buffer_layout.step_mode == InputStepMode::kVertex) {
uses_vertex_step_mode = true;
break;
}
}
if (!uses_vertex_step_mode) {
return;
}
// Look for an existing vertex index builtin
for (auto& v : mod_->global_variables()) {
if (!v->IsDecorated() || v->storage_class() != StorageClass::kInput) {
continue;
}
for (auto& d : v->AsDecorated()->decorations()) {
if (d->IsBuiltin() && d->AsBuiltin()->value() == Builtin::kVertexIdx) {
vertex_index_name_ = v->name();
return;
}
}
}
// We didn't find a vertex index builtin, so create one
vertex_index_name_ = kDefaultVertexIndexName;
auto var = std::make_unique<DecoratedVariable>(std::make_unique<Variable>(
vertex_index_name_, StorageClass::kInput, GetI32Type()));
VariableDecorationList decorations;
decorations.push_back(
std::make_unique<BuiltinDecoration>(Builtin::kVertexIdx));
var->set_decorations(std::move(decorations));
mod_->AddGlobalVariable(std::move(var));
}
void VertexPullingTransform::FindOrInsertInstanceIndexIfUsed() {
bool uses_instance_step_mode = false;
for (const VertexBufferLayoutDescriptor& buffer_layout :
vertex_state_->vertex_buffers) {
if (buffer_layout.step_mode == InputStepMode::kInstance) {
uses_instance_step_mode = true;
break;
}
}
if (!uses_instance_step_mode) {
return;
}
// Look for an existing instance index builtin
for (auto& v : mod_->global_variables()) {
if (!v->IsDecorated() || v->storage_class() != StorageClass::kInput) {
continue;
}
for (auto& d : v->AsDecorated()->decorations()) {
if (d->IsBuiltin() && d->AsBuiltin()->value() == Builtin::kInstanceIdx) {
instance_index_name_ = v->name();
return;
}
}
}
// We didn't find an instance index builtin, so create one
instance_index_name_ = kDefaultInstanceIndexName;
auto var = std::make_unique<DecoratedVariable>(std::make_unique<Variable>(
instance_index_name_, StorageClass::kInput, GetI32Type()));
VariableDecorationList decorations;
decorations.push_back(
std::make_unique<BuiltinDecoration>(Builtin::kInstanceIdx));
var->set_decorations(std::move(decorations));
mod_->AddGlobalVariable(std::move(var));
}
void VertexPullingTransform::ConvertVertexInputVariablesToPrivate() {
for (auto& v : mod_->global_variables()) {
if (!v->IsDecorated() || v->storage_class() != StorageClass::kInput) {
continue;
}
for (auto& d : v->AsDecorated()->decorations()) {
if (!d->IsLocation()) {
continue;
}
uint32_t location = d->AsLocation()->value();
// This is where the replacement happens. Expressions use identifier
// strings instead of pointers, so we don't need to update any other place
// in the AST.
v = std::make_unique<Variable>(v->name(), StorageClass::kPrivate,
v->type());
location_to_var_[location] = v.get();
break;
}
}
}
void VertexPullingTransform::AddVertexStorageBuffers() {
// TODO(idanr): Make this readonly https://github.com/gpuweb/gpuweb/issues/935
// The array inside the struct definition
auto internal_array = std::make_unique<type::ArrayType>(GetU32Type());
internal_array->set_array_stride(4u);
auto* internal_array_type = ctx_->type_mgr().Get(std::move(internal_array));
// Creating the struct type
StructMemberList members;
StructMemberDecorationList member_dec;
member_dec.push_back(std::make_unique<StructMemberOffsetDecoration>(0u));
members.push_back(std::make_unique<StructMember>(
kStructBufferName, internal_array_type, std::move(member_dec)));
auto* struct_type = ctx_->type_mgr().Get(std::make_unique<type::StructType>(
std::make_unique<Struct>(StructDecoration::kBlock, std::move(members))));
for (uint32_t i = 0; i < vertex_state_->vertex_buffers.size(); ++i) {
// The decorated variable with struct type
auto var = std::make_unique<DecoratedVariable>(std::make_unique<Variable>(
GetVertexBufferName(i), StorageClass::kStorageBuffer, struct_type));
// Add decorations
VariableDecorationList decorations;
decorations.push_back(std::make_unique<BindingDecoration>(i));
decorations.push_back(std::make_unique<SetDecoration>(pulling_set_));
var->set_decorations(std::move(decorations));
mod_->AddGlobalVariable(std::move(var));
}
}
void VertexPullingTransform::AddVertexPullingPreamble(Function* vertex_func) {
// Assign by looking at the vertex descriptor to find attributes with matching
// location.
// A block statement allowing us to use append instead of insert
auto block = std::make_unique<BlockStatement>();
// Declare the |kPullingPosVarName| variable in the shader
auto pos_declaration =
std::make_unique<VariableDeclStatement>(std::make_unique<Variable>(
kPullingPosVarName, StorageClass::kFunction, GetI32Type()));
// |kPullingPosVarName| refers to the byte location of the current read. We
// declare a variable in the shader to avoid having to reuse Expression
// objects.
block->append(std::move(pos_declaration));
for (uint32_t i = 0; i < vertex_state_->vertex_buffers.size(); ++i) {
const VertexBufferLayoutDescriptor& buffer_layout =
vertex_state_->vertex_buffers[i];
for (const VertexAttributeDescriptor& attribute_desc :
buffer_layout.attributes) {
auto it = location_to_var_.find(attribute_desc.shader_location);
if (it == location_to_var_.end()) {
continue;
}
auto* v = it->second;
// Identifier to index by
auto index_identifier = std::make_unique<IdentifierExpression>(
buffer_layout.step_mode == InputStepMode::kVertex
? vertex_index_name_
: instance_index_name_);
// An expression for the start of the read in the buffer in bytes
auto pos_value = std::make_unique<BinaryExpression>(
BinaryOp::kAdd,
std::make_unique<BinaryExpression>(
BinaryOp::kMultiply, std::move(index_identifier),
GenUint(static_cast<uint32_t>(buffer_layout.array_stride))),
GenUint(static_cast<uint32_t>(attribute_desc.offset)));
// Update position of the read
auto set_pos_expr = std::make_unique<AssignmentStatement>(
CreatePullingPositionIdent(), std::move(pos_value));
block->append(std::move(set_pos_expr));
block->append(std::make_unique<AssignmentStatement>(
std::make_unique<IdentifierExpression>(v->name()),
AccessByFormat(i, attribute_desc.format)));
}
}
vertex_func->body()->insert(0, std::move(block));
}
std::unique_ptr<Expression> VertexPullingTransform::GenUint(uint32_t value) {
return std::make_unique<ScalarConstructorExpression>(
std::make_unique<UintLiteral>(GetU32Type(), value));
}
std::unique_ptr<Expression>
VertexPullingTransform::CreatePullingPositionIdent() {
return std::make_unique<IdentifierExpression>(kPullingPosVarName);
}
std::unique_ptr<Expression> VertexPullingTransform::AccessByFormat(
uint32_t buffer,
VertexFormat format) {
// TODO(idanr): this doesn't account for the format of the attribute in the
// shader. ex: vec<u32> in shader, and attribute claims VertexFormat::Float4
// right now, we would try to assign a vec4<f32> to this attribute, but we
// really need to assign a vec4<u32> by casting.
// We could split this function to first do memory accesses and unpacking into
// int/uint/float1-4/etc, then convert that variable to a var<in> with the
// conversion defined in the WebGPU spec.
switch (format) {
case VertexFormat::kU32:
return AccessU32(buffer, CreatePullingPositionIdent());
case VertexFormat::kI32:
return AccessI32(buffer, CreatePullingPositionIdent());
case VertexFormat::kF32:
return AccessF32(buffer, CreatePullingPositionIdent());
case VertexFormat::kVec2F32:
return AccessVec(buffer, 4, GetF32Type(), VertexFormat::kF32, 2);
case VertexFormat::kVec3F32:
return AccessVec(buffer, 4, GetF32Type(), VertexFormat::kF32, 3);
case VertexFormat::kVec4F32:
return AccessVec(buffer, 4, GetF32Type(), VertexFormat::kF32, 4);
default:
return nullptr;
}
}
std::unique_ptr<Expression> VertexPullingTransform::AccessU32(
uint32_t buffer,
std::unique_ptr<Expression> pos) {
// Here we divide by 4, since the buffer is uint32 not uint8. The input buffer
// has byte offsets for each attribute, and we will convert it to u32 indexes
// by dividing. Then, that element is going to be read, and if needed,
// unpacked into an appropriate variable. All reads should end up here as a
// base case.
return std::make_unique<ArrayAccessorExpression>(
std::make_unique<MemberAccessorExpression>(
std::make_unique<IdentifierExpression>(GetVertexBufferName(buffer)),
std::make_unique<IdentifierExpression>(kStructBufferName)),
std::make_unique<BinaryExpression>(BinaryOp::kDivide, std::move(pos),
GenUint(4)));
}
std::unique_ptr<Expression> VertexPullingTransform::AccessI32(
uint32_t buffer,
std::unique_ptr<Expression> pos) {
// as<T> reinterprets bits
return std::make_unique<AsExpression>(GetI32Type(),
AccessU32(buffer, std::move(pos)));
}
std::unique_ptr<Expression> VertexPullingTransform::AccessF32(
uint32_t buffer,
std::unique_ptr<Expression> pos) {
// as<T> reinterprets bits
return std::make_unique<AsExpression>(GetF32Type(),
AccessU32(buffer, std::move(pos)));
}
std::unique_ptr<Expression> VertexPullingTransform::AccessPrimitive(
uint32_t buffer,
std::unique_ptr<Expression> pos,
VertexFormat format) {
// This function uses a position expression to read, rather than using the
// position variable. This allows us to read from offset positions relative to
// |kPullingPosVarName|. We can't call AccessByFormat because it reads only
// from the position variable.
switch (format) {
case VertexFormat::kU32:
return AccessU32(buffer, std::move(pos));
case VertexFormat::kI32:
return AccessI32(buffer, std::move(pos));
case VertexFormat::kF32:
return AccessF32(buffer, std::move(pos));
default:
return nullptr;
}
}
std::unique_ptr<Expression> VertexPullingTransform::AccessVec(
uint32_t buffer,
uint32_t element_stride,
type::Type* base_type,
VertexFormat base_format,
uint32_t count) {
ExpressionList expr_list;
for (uint32_t i = 0; i < count; ++i) {
// Offset read position by element_stride for each component
auto cur_pos = std::make_unique<BinaryExpression>(
BinaryOp::kAdd, CreatePullingPositionIdent(),
GenUint(element_stride * i));
expr_list.push_back(
AccessPrimitive(buffer, std::move(cur_pos), base_format));
}
return std::make_unique<TypeConstructorExpression>(
ctx_->type_mgr().Get(
std::make_unique<type::VectorType>(base_type, count)),
std::move(expr_list));
}
type::Type* VertexPullingTransform::GetU32Type() {
return ctx_->type_mgr().Get(std::make_unique<type::U32Type>());
}
type::Type* VertexPullingTransform::GetI32Type() {
return ctx_->type_mgr().Get(std::make_unique<type::I32Type>());
}
type::Type* VertexPullingTransform::GetF32Type() {
return ctx_->type_mgr().Get(std::make_unique<type::F32Type>());
}
VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor() = default;
VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor(
uint64_t in_array_stride,
InputStepMode in_step_mode,
std::vector<VertexAttributeDescriptor> in_attributes)
: array_stride(std::move(in_array_stride)),
step_mode(std::move(in_step_mode)),
attributes(std::move(in_attributes)) {}
VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor(
const VertexBufferLayoutDescriptor& other)
: array_stride(other.array_stride),
step_mode(other.step_mode),
attributes(other.attributes) {}
VertexBufferLayoutDescriptor::~VertexBufferLayoutDescriptor() = default;
VertexStateDescriptor::VertexStateDescriptor() = default;
VertexStateDescriptor::VertexStateDescriptor(
std::vector<VertexBufferLayoutDescriptor> in_vertex_buffers)
: vertex_buffers(std::move(in_vertex_buffers)) {}
VertexStateDescriptor::VertexStateDescriptor(const VertexStateDescriptor& other)
: vertex_buffers(other.vertex_buffers) {}
VertexStateDescriptor::~VertexStateDescriptor() = default;
} // namespace transform
} // namespace ast
} // namespace tint