|  | // 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/transform/vertex_pulling.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "src/ast/assignment_statement.h" | 
|  | #include "src/ast/bitcast_expression.h" | 
|  | #include "src/ast/struct_block_decoration.h" | 
|  | #include "src/ast/variable_decl_statement.h" | 
|  | #include "src/program_builder.h" | 
|  | #include "src/sem/variable.h" | 
|  | #include "src/utils/get_or_create.h" | 
|  |  | 
|  | TINT_INSTANTIATE_TYPEINFO(tint::transform::VertexPulling::Config); | 
|  |  | 
|  | namespace tint { | 
|  | namespace transform { | 
|  | namespace { | 
|  |  | 
|  | struct State { | 
|  | State(CloneContext& context, const VertexPulling::Config& c) | 
|  | : ctx(context), cfg(c) {} | 
|  | State(const State&) = default; | 
|  | ~State() = default; | 
|  |  | 
|  | /// LocationReplacement describes an ast::Variable replacement for a | 
|  | /// location input. | 
|  | struct LocationReplacement { | 
|  | /// The variable to replace in the source Program | 
|  | ast::Variable* from; | 
|  | /// The replacement to use in the target ProgramBuilder | 
|  | ast::Variable* to; | 
|  | }; | 
|  |  | 
|  | CloneContext& ctx; | 
|  | VertexPulling::Config const cfg; | 
|  | std::unordered_map<uint32_t, std::function<ast::Expression*()>> | 
|  | location_to_expr; | 
|  | std::function<ast::Expression*()> vertex_index_expr = nullptr; | 
|  | std::function<ast::Expression*()> instance_index_expr = nullptr; | 
|  | Symbol pulling_position_name; | 
|  | Symbol struct_buffer_name; | 
|  | std::unordered_map<uint32_t, Symbol> vertex_buffer_names; | 
|  | ast::VariableList new_function_parameters; | 
|  |  | 
|  | /// Generate the vertex buffer binding name | 
|  | /// @param index index to append to buffer name | 
|  | Symbol GetVertexBufferName(uint32_t index) { | 
|  | return utils::GetOrCreate(vertex_buffer_names, index, [&] { | 
|  | static const char kVertexBufferNamePrefix[] = | 
|  | "tint_pulling_vertex_buffer_"; | 
|  | return ctx.dst->Symbols().New(kVertexBufferNamePrefix + | 
|  | std::to_string(index)); | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// Lazily generates the pulling position symbol | 
|  | Symbol GetPullingPositionName() { | 
|  | if (!pulling_position_name.IsValid()) { | 
|  | static const char kPullingPosVarName[] = "tint_pulling_pos"; | 
|  | pulling_position_name = ctx.dst->Symbols().New(kPullingPosVarName); | 
|  | } | 
|  | return pulling_position_name; | 
|  | } | 
|  |  | 
|  | /// Lazily generates the structure buffer symbol | 
|  | Symbol GetStructBufferName() { | 
|  | if (!struct_buffer_name.IsValid()) { | 
|  | static const char kStructBufferName[] = "tint_vertex_data"; | 
|  | struct_buffer_name = ctx.dst->Symbols().New(kStructBufferName); | 
|  | } | 
|  | return struct_buffer_name; | 
|  | } | 
|  |  | 
|  | /// Inserts vertex_index binding, or finds the existing one | 
|  | void FindOrInsertVertexIndexIfUsed() { | 
|  | bool uses_vertex_step_mode = false; | 
|  | for (const VertexBufferLayoutDescriptor& buffer_layout : cfg.vertex_state) { | 
|  | 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 : ctx.src->AST().GlobalVariables()) { | 
|  | auto* sem = ctx.src->Sem().Get(v); | 
|  | if (sem->StorageClass() != ast::StorageClass::kInput) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | for (auto* d : v->decorations()) { | 
|  | if (auto* builtin = d->As<ast::BuiltinDecoration>()) { | 
|  | if (builtin->value() == ast::Builtin::kVertexIndex) { | 
|  | vertex_index_expr = [this, v]() { | 
|  | return ctx.dst->Expr(ctx.Clone(v->symbol())); | 
|  | }; | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // We didn't find a vertex index builtin, so create one | 
|  | auto name = ctx.dst->Symbols().New("tint_pulling_vertex_index"); | 
|  | vertex_index_expr = [this, name]() { return ctx.dst->Expr(name); }; | 
|  |  | 
|  | ctx.dst->Global(name, ctx.dst->ty.u32(), ast::StorageClass::kInput, nullptr, | 
|  | ast::DecorationList{ | 
|  | ctx.dst->Builtin(ast::Builtin::kVertexIndex), | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// Inserts instance_index binding, or finds the existing one | 
|  | void FindOrInsertInstanceIndexIfUsed() { | 
|  | bool uses_instance_step_mode = false; | 
|  | for (const VertexBufferLayoutDescriptor& buffer_layout : cfg.vertex_state) { | 
|  | 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 : ctx.src->AST().GlobalVariables()) { | 
|  | auto* sem = ctx.src->Sem().Get(v); | 
|  | if (sem->StorageClass() != ast::StorageClass::kInput) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | for (auto* d : v->decorations()) { | 
|  | if (auto* builtin = d->As<ast::BuiltinDecoration>()) { | 
|  | if (builtin->value() == ast::Builtin::kInstanceIndex) { | 
|  | instance_index_expr = [this, v]() { | 
|  | return ctx.dst->Expr(ctx.Clone(v->symbol())); | 
|  | }; | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // We didn't find an instance index builtin, so create one | 
|  | auto name = ctx.dst->Symbols().New("tint_pulling_instance_index"); | 
|  | instance_index_expr = [this, name]() { return ctx.dst->Expr(name); }; | 
|  |  | 
|  | ctx.dst->Global(name, ctx.dst->ty.u32(), ast::StorageClass::kInput, nullptr, | 
|  | ast::DecorationList{ | 
|  | ctx.dst->Builtin(ast::Builtin::kInstanceIndex), | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// Converts var<in> with a location decoration to var<private> | 
|  | void ConvertVertexInputVariablesToPrivate() { | 
|  | for (auto* v : ctx.src->AST().GlobalVariables()) { | 
|  | auto* sem = ctx.src->Sem().Get(v); | 
|  | if (sem->StorageClass() != ast::StorageClass::kInput) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | for (auto* d : v->decorations()) { | 
|  | if (auto* l = d->As<ast::LocationDecoration>()) { | 
|  | uint32_t location = l->value(); | 
|  | // This is where the replacement is created. Expressions use | 
|  | // identifier strings instead of pointers, so we don't need to update | 
|  | // any other place in the AST. | 
|  | auto name = ctx.Clone(v->symbol()); | 
|  | auto* replacement = ctx.dst->Var(name, ctx.Clone(v->declared_type()), | 
|  | ast::StorageClass::kPrivate); | 
|  | location_to_expr[location] = [this, name]() { | 
|  | return ctx.dst->Expr(name); | 
|  | }; | 
|  | ctx.Replace(v, replacement); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Adds storage buffer decorated variables for the vertex buffers | 
|  | void AddVertexStorageBuffers() { | 
|  | // TODO(idanr): Make this readonly | 
|  | // https://github.com/gpuweb/gpuweb/issues/935 | 
|  |  | 
|  | // Creating the struct type | 
|  | static const char kStructName[] = "TintVertexData"; | 
|  | auto struct_type = ctx.dst->Structure( | 
|  | ctx.dst->Symbols().New(kStructName), | 
|  | { | 
|  | ctx.dst->Member(GetStructBufferName(), | 
|  | ctx.dst->ty.array<ProgramBuilder::u32, 0>(4)), | 
|  | }, | 
|  | { | 
|  | ctx.dst->create<ast::StructBlockDecoration>(), | 
|  | }); | 
|  | auto access = | 
|  | ctx.dst->ty.access(ast::AccessControl::kReadOnly, struct_type); | 
|  | for (uint32_t i = 0; i < cfg.vertex_state.size(); ++i) { | 
|  | // The decorated variable with struct type | 
|  | ctx.dst->Global( | 
|  | GetVertexBufferName(i), access, ast::StorageClass::kStorage, nullptr, | 
|  | ast::DecorationList{ | 
|  | ctx.dst->create<ast::BindingDecoration>(i), | 
|  | ctx.dst->create<ast::GroupDecoration>(cfg.pulling_group), | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Creates and returns the assignment to the variables from the buffers | 
|  | ast::BlockStatement* CreateVertexPullingPreamble() { | 
|  | // Assign by looking at the vertex descriptor to find attributes with | 
|  | // matching location. | 
|  |  | 
|  | ast::StatementList stmts; | 
|  |  | 
|  | // Declare the pulling position variable in the shader | 
|  | stmts.emplace_back( | 
|  | ctx.dst->Decl(ctx.dst->Var(GetPullingPositionName(), ctx.dst->ty.u32(), | 
|  | ast::StorageClass::kFunction))); | 
|  |  | 
|  | for (uint32_t i = 0; i < cfg.vertex_state.size(); ++i) { | 
|  | const VertexBufferLayoutDescriptor& buffer_layout = cfg.vertex_state[i]; | 
|  |  | 
|  | for (const VertexAttributeDescriptor& attribute_desc : | 
|  | buffer_layout.attributes) { | 
|  | auto it = location_to_expr.find(attribute_desc.shader_location); | 
|  | if (it == location_to_expr.end()) { | 
|  | continue; | 
|  | } | 
|  | auto* ident = it->second(); | 
|  |  | 
|  | auto* index_expr = buffer_layout.step_mode == InputStepMode::kVertex | 
|  | ? vertex_index_expr() | 
|  | : instance_index_expr(); | 
|  |  | 
|  | // An expression for the start of the read in the buffer in bytes | 
|  | auto* pos_value = ctx.dst->Add( | 
|  | ctx.dst->Mul(index_expr, | 
|  | static_cast<uint32_t>(buffer_layout.array_stride)), | 
|  | static_cast<uint32_t>(attribute_desc.offset)); | 
|  |  | 
|  | // Update position of the read | 
|  | auto* set_pos_expr = | 
|  | ctx.dst->Assign(ctx.dst->Expr(GetPullingPositionName()), pos_value); | 
|  | stmts.emplace_back(set_pos_expr); | 
|  |  | 
|  | stmts.emplace_back( | 
|  | ctx.dst->Assign(ident, AccessByFormat(i, attribute_desc.format))); | 
|  | } | 
|  | } | 
|  |  | 
|  | return ctx.dst->create<ast::BlockStatement>(stmts); | 
|  | } | 
|  |  | 
|  | /// Generates an expression reading from a buffer a specific format. | 
|  | /// This reads the value wherever `kPullingPosVarName` points to at the time | 
|  | /// of the read. | 
|  | /// @param buffer the index of the vertex buffer | 
|  | /// @param format the format to read | 
|  | ast::Expression* 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, ctx.dst->Expr(GetPullingPositionName())); | 
|  | case VertexFormat::kI32: | 
|  | return AccessI32(buffer, ctx.dst->Expr(GetPullingPositionName())); | 
|  | case VertexFormat::kF32: | 
|  | return AccessF32(buffer, ctx.dst->Expr(GetPullingPositionName())); | 
|  | case VertexFormat::kVec2F32: | 
|  | return AccessVec(buffer, 4, ctx.dst->ty.f32(), VertexFormat::kF32, 2); | 
|  | case VertexFormat::kVec3F32: | 
|  | return AccessVec(buffer, 4, ctx.dst->ty.f32(), VertexFormat::kF32, 3); | 
|  | case VertexFormat::kVec4F32: | 
|  | return AccessVec(buffer, 4, ctx.dst->ty.f32(), VertexFormat::kF32, 4); | 
|  | default: | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Generates an expression reading a uint32 from a vertex buffer | 
|  | /// @param buffer the index of the vertex buffer | 
|  | /// @param pos an expression for the position of the access, in bytes | 
|  | ast::Expression* AccessU32(uint32_t buffer, ast::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 ctx.dst->create<ast::ArrayAccessorExpression>( | 
|  | ctx.dst->MemberAccessor(GetVertexBufferName(buffer), | 
|  | GetStructBufferName()), | 
|  | ctx.dst->Div(pos, 4u)); | 
|  | } | 
|  |  | 
|  | /// Generates an expression reading an int32 from a vertex buffer | 
|  | /// @param buffer the index of the vertex buffer | 
|  | /// @param pos an expression for the position of the access, in bytes | 
|  | ast::Expression* AccessI32(uint32_t buffer, ast::Expression* pos) { | 
|  | // as<T> reinterprets bits | 
|  | return ctx.dst->create<ast::BitcastExpression>(ctx.dst->ty.i32(), | 
|  | AccessU32(buffer, pos)); | 
|  | } | 
|  |  | 
|  | /// Generates an expression reading a float from a vertex buffer | 
|  | /// @param buffer the index of the vertex buffer | 
|  | /// @param pos an expression for the position of the access, in bytes | 
|  | ast::Expression* AccessF32(uint32_t buffer, ast::Expression* pos) { | 
|  | // as<T> reinterprets bits | 
|  | return ctx.dst->create<ast::BitcastExpression>(ctx.dst->ty.f32(), | 
|  | AccessU32(buffer, pos)); | 
|  | } | 
|  |  | 
|  | /// Generates an expression reading a basic type (u32, i32, f32) from a | 
|  | /// vertex buffer | 
|  | /// @param buffer the index of the vertex buffer | 
|  | /// @param pos an expression for the position of the access, in bytes | 
|  | /// @param format the underlying vertex format | 
|  | ast::Expression* AccessPrimitive(uint32_t buffer, | 
|  | ast::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, pos); | 
|  | case VertexFormat::kI32: | 
|  | return AccessI32(buffer, pos); | 
|  | case VertexFormat::kF32: | 
|  | return AccessF32(buffer, pos); | 
|  | default: | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Generates an expression reading a vec2/3/4 from a vertex buffer. | 
|  | /// This reads the value wherever `kPullingPosVarName` points to at the time | 
|  | /// of the read. | 
|  | /// @param buffer the index of the vertex buffer | 
|  | /// @param element_stride stride between elements, in bytes | 
|  | /// @param base_type underlying AST type | 
|  | /// @param base_format underlying vertex format | 
|  | /// @param count how many elements the vector has | 
|  | ast::Expression* AccessVec(uint32_t buffer, | 
|  | uint32_t element_stride, | 
|  | sem::Type* base_type, | 
|  | VertexFormat base_format, | 
|  | uint32_t count) { | 
|  | ast::ExpressionList expr_list; | 
|  | for (uint32_t i = 0; i < count; ++i) { | 
|  | // Offset read position by element_stride for each component | 
|  | auto* cur_pos = | 
|  | ctx.dst->Add(GetPullingPositionName(), element_stride * i); | 
|  | expr_list.push_back(AccessPrimitive(buffer, cur_pos, base_format)); | 
|  | } | 
|  |  | 
|  | return ctx.dst->create<ast::TypeConstructorExpression>( | 
|  | ctx.dst->create<sem::Vector>(base_type, count), std::move(expr_list)); | 
|  | } | 
|  |  | 
|  | /// Process a non-struct entry point parameter. | 
|  | /// Generate function-scope variables for location parameters, and record | 
|  | /// vertex_index and instance_index builtins if present. | 
|  | /// @param func the entry point function | 
|  | /// @param param the parameter to process | 
|  | void ProcessNonStructParameter(ast::Function* func, ast::Variable* param) { | 
|  | if (auto* location = | 
|  | ast::GetDecoration<ast::LocationDecoration>(param->decorations())) { | 
|  | // Create a function-scope variable to replace the parameter. | 
|  | auto func_var_sym = ctx.Clone(param->symbol()); | 
|  | auto* func_var_type = ctx.Clone(param->declared_type()); | 
|  | auto* func_var = ctx.dst->Var(func_var_sym, func_var_type, | 
|  | ast::StorageClass::kFunction); | 
|  | ctx.InsertBefore(func->body()->statements(), *func->body()->begin(), | 
|  | ctx.dst->Decl(func_var)); | 
|  | // Capture mapping from location to the new variable. | 
|  | location_to_expr[location->value()] = [this, func_var]() { | 
|  | return ctx.dst->Expr(func_var); | 
|  | }; | 
|  | } else if (auto* builtin = ast::GetDecoration<ast::BuiltinDecoration>( | 
|  | param->decorations())) { | 
|  | // Check for existing vertex_index and instance_index builtins. | 
|  | if (builtin->value() == ast::Builtin::kVertexIndex) { | 
|  | vertex_index_expr = [this, param]() { | 
|  | return ctx.dst->Expr(ctx.Clone(param->symbol())); | 
|  | }; | 
|  | } else if (builtin->value() == ast::Builtin::kInstanceIndex) { | 
|  | instance_index_expr = [this, param]() { | 
|  | return ctx.dst->Expr(ctx.Clone(param->symbol())); | 
|  | }; | 
|  | } | 
|  | new_function_parameters.push_back(ctx.Clone(param)); | 
|  | } else { | 
|  | TINT_ICE(ctx.dst->Diagnostics()) << "Invalid entry point parameter"; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Process a struct entry point parameter. | 
|  | /// If the struct has members with location attributes, push the parameter to | 
|  | /// a function-scope variable and create a new struct parameter without those | 
|  | /// attributes. Record expressions for members that are vertex_index and | 
|  | /// instance_index builtins. | 
|  | /// @param func the entry point function | 
|  | /// @param param the parameter to process | 
|  | void ProcessStructParameter(ast::Function* func, ast::Variable* param) { | 
|  | auto* struct_ty = param->declared_type()->As<sem::StructType>(); | 
|  | if (!struct_ty) { | 
|  | TINT_ICE(ctx.dst->Diagnostics()) << "Invalid struct parameter"; | 
|  | } | 
|  |  | 
|  | auto param_sym = ctx.Clone(param->symbol()); | 
|  |  | 
|  | // Process the struct members. | 
|  | bool has_locations = false; | 
|  | ast::StructMemberList members_to_clone; | 
|  | for (auto* member : struct_ty->impl()->members()) { | 
|  | auto member_sym = ctx.Clone(member->symbol()); | 
|  | std::function<ast::Expression*()> member_expr = [this, param_sym, | 
|  | member_sym]() { | 
|  | return ctx.dst->MemberAccessor(param_sym, member_sym); | 
|  | }; | 
|  |  | 
|  | if (auto* location = ast::GetDecoration<ast::LocationDecoration>( | 
|  | member->decorations())) { | 
|  | // Capture mapping from location to struct member. | 
|  | location_to_expr[location->value()] = member_expr; | 
|  | has_locations = true; | 
|  | } else if (auto* builtin = ast::GetDecoration<ast::BuiltinDecoration>( | 
|  | member->decorations())) { | 
|  | // Check for existing vertex_index and instance_index builtins. | 
|  | if (builtin->value() == ast::Builtin::kVertexIndex) { | 
|  | vertex_index_expr = member_expr; | 
|  | } else if (builtin->value() == ast::Builtin::kInstanceIndex) { | 
|  | instance_index_expr = member_expr; | 
|  | } | 
|  | members_to_clone.push_back(member); | 
|  | } else { | 
|  | TINT_ICE(ctx.dst->Diagnostics()) << "Invalid entry point parameter"; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!has_locations) { | 
|  | // Nothing to do. | 
|  | new_function_parameters.push_back(ctx.Clone(param)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Create a function-scope variable to replace the parameter. | 
|  | auto* func_var = ctx.dst->Var(param_sym, ctx.Clone(param->declared_type()), | 
|  | ast::StorageClass::kFunction); | 
|  | ctx.InsertBefore(func->body()->statements(), *func->body()->begin(), | 
|  | ctx.dst->Decl(func_var)); | 
|  |  | 
|  | if (!members_to_clone.empty()) { | 
|  | // Create a new struct without the location attributes. | 
|  | ast::StructMemberList new_members; | 
|  | for (auto* member : members_to_clone) { | 
|  | auto member_sym = ctx.Clone(member->symbol()); | 
|  | auto member_type = ctx.Clone(member->type()); | 
|  | auto member_decos = ctx.Clone(member->decorations()); | 
|  | new_members.push_back( | 
|  | ctx.dst->Member(member_sym, member_type, std::move(member_decos))); | 
|  | } | 
|  | auto new_struct = ctx.dst->Structure(ctx.dst->Sym(), new_members); | 
|  |  | 
|  | // Create a new function parameter with this struct. | 
|  | auto* new_param = ctx.dst->Param(ctx.dst->Sym(), new_struct); | 
|  | new_function_parameters.push_back(new_param); | 
|  |  | 
|  | // Copy values from the new parameter to the function-scope variable. | 
|  | for (auto* member : members_to_clone) { | 
|  | auto member_name = ctx.Clone(member->symbol()); | 
|  | ctx.InsertBefore( | 
|  | func->body()->statements(), *func->body()->begin(), | 
|  | ctx.dst->Assign(ctx.dst->MemberAccessor(func_var, member_name), | 
|  | ctx.dst->MemberAccessor(new_param, member_name))); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Process an entry point function. | 
|  | /// @param func the entry point function | 
|  | void Process(ast::Function* func) { | 
|  | if (func->body()->empty()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Process entry point parameters. | 
|  | for (auto* param : func->params()) { | 
|  | auto* sem = ctx.src->Sem().Get(param); | 
|  | if (sem->Type()->Is<sem::StructType>()) { | 
|  | ProcessStructParameter(func, param); | 
|  | } else { | 
|  | ProcessNonStructParameter(func, param); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Insert new parameters for vertex_index and instance_index if needed. | 
|  | if (!vertex_index_expr) { | 
|  | for (const VertexBufferLayoutDescriptor& layout : cfg.vertex_state) { | 
|  | if (layout.step_mode == InputStepMode::kVertex) { | 
|  | auto name = ctx.dst->Symbols().New("tint_pulling_vertex_index"); | 
|  | new_function_parameters.push_back( | 
|  | ctx.dst->Param(name, ctx.dst->ty.u32(), | 
|  | {ctx.dst->Builtin(ast::Builtin::kVertexIndex)})); | 
|  | vertex_index_expr = [this, name]() { return ctx.dst->Expr(name); }; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | if (!instance_index_expr) { | 
|  | for (const VertexBufferLayoutDescriptor& layout : cfg.vertex_state) { | 
|  | if (layout.step_mode == InputStepMode::kInstance) { | 
|  | auto name = ctx.dst->Symbols().New("tint_pulling_instance_index"); | 
|  | new_function_parameters.push_back( | 
|  | ctx.dst->Param(name, ctx.dst->ty.u32(), | 
|  | {ctx.dst->Builtin(ast::Builtin::kInstanceIndex)})); | 
|  | instance_index_expr = [this, name]() { return ctx.dst->Expr(name); }; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Generate vertex pulling preamble. | 
|  | ctx.InsertBefore(func->body()->statements(), *func->body()->begin(), | 
|  | CreateVertexPullingPreamble()); | 
|  |  | 
|  | // Rewrite the function header with the new parameters. | 
|  | auto func_sym = ctx.Clone(func->symbol()); | 
|  | auto ret_type = ctx.Clone(func->return_type()); | 
|  | auto* body = ctx.Clone(func->body()); | 
|  | auto decos = ctx.Clone(func->decorations()); | 
|  | auto ret_decos = ctx.Clone(func->return_type_decorations()); | 
|  | auto* new_func = ctx.dst->create<ast::Function>( | 
|  | func->source(), func_sym, new_function_parameters, ret_type, body, | 
|  | std::move(decos), std::move(ret_decos)); | 
|  | ctx.Replace(func, new_func); | 
|  | } | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | VertexPulling::VertexPulling() = default; | 
|  | VertexPulling::~VertexPulling() = default; | 
|  |  | 
|  | Output VertexPulling::Run(const Program* in, const DataMap& data) { | 
|  | ProgramBuilder out; | 
|  |  | 
|  | auto cfg = cfg_; | 
|  | if (auto* cfg_data = data.Get<Config>()) { | 
|  | cfg = *cfg_data; | 
|  | } | 
|  |  | 
|  | // Find entry point | 
|  | auto* func = in->AST().Functions().Find( | 
|  | in->Symbols().Get(cfg.entry_point_name), ast::PipelineStage::kVertex); | 
|  | if (func == nullptr) { | 
|  | out.Diagnostics().add_error("Vertex stage entry point not found"); | 
|  | return Output(Program(std::move(out))); | 
|  | } | 
|  |  | 
|  | // 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 | 
|  |  | 
|  | CloneContext ctx(&out, in); | 
|  |  | 
|  | State state{ctx, cfg}; | 
|  |  | 
|  | if (func->params().empty()) { | 
|  | // TODO(crbug.com/tint/697): Remove this path for the old shader IO syntax. | 
|  | state.FindOrInsertVertexIndexIfUsed(); | 
|  | state.FindOrInsertInstanceIndexIfUsed(); | 
|  | state.ConvertVertexInputVariablesToPrivate(); | 
|  | state.AddVertexStorageBuffers(); | 
|  |  | 
|  | ctx.ReplaceAll([&](ast::Function* f) -> ast::Function* { | 
|  | if (f == func) { | 
|  | return CloneWithStatementsAtStart( | 
|  | &ctx, f, {state.CreateVertexPullingPreamble()}); | 
|  | } | 
|  | return nullptr;  // Just clone func | 
|  | }); | 
|  | } else { | 
|  | state.AddVertexStorageBuffers(); | 
|  | state.Process(func); | 
|  | } | 
|  |  | 
|  | ctx.Clone(); | 
|  |  | 
|  | return Output(Program(std::move(out))); | 
|  | } | 
|  |  | 
|  | VertexPulling::Config::Config() = default; | 
|  | VertexPulling::Config::Config(const Config&) = default; | 
|  | VertexPulling::Config::~Config() = default; | 
|  | VertexPulling::Config& VertexPulling::Config::operator=(const Config&) = | 
|  | default; | 
|  |  | 
|  | VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor() = default; | 
|  |  | 
|  | VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor( | 
|  | uint64_t in_array_stride, | 
|  | InputStepMode in_step_mode, | 
|  | std::vector<VertexAttributeDescriptor> in_attributes) | 
|  | : array_stride(in_array_stride), | 
|  | step_mode(in_step_mode), | 
|  | attributes(std::move(in_attributes)) {} | 
|  |  | 
|  | VertexBufferLayoutDescriptor::VertexBufferLayoutDescriptor( | 
|  | const VertexBufferLayoutDescriptor& other) = default; | 
|  |  | 
|  | VertexBufferLayoutDescriptor& VertexBufferLayoutDescriptor::operator=( | 
|  | const VertexBufferLayoutDescriptor& other) = default; | 
|  |  | 
|  | VertexBufferLayoutDescriptor::~VertexBufferLayoutDescriptor() = default; | 
|  |  | 
|  | }  // namespace transform | 
|  | }  // namespace tint |