| // 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/tint/transform/vertex_pulling.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "src/tint/ast/assignment_statement.h" |
| #include "src/tint/ast/bitcast_expression.h" |
| #include "src/tint/ast/variable_decl_statement.h" |
| #include "src/tint/program_builder.h" |
| #include "src/tint/sem/variable.h" |
| #include "src/tint/utils/map.h" |
| #include "src/tint/utils/math.h" |
| |
| TINT_INSTANTIATE_TYPEINFO(tint::transform::VertexPulling); |
| TINT_INSTANTIATE_TYPEINFO(tint::transform::VertexPulling::Config); |
| |
| using namespace tint::number_suffixes; // NOLINT |
| |
| namespace tint::transform { |
| |
| namespace { |
| |
| /// The base type of a component. |
| /// The format type is either this type or a vector of this type. |
| enum class BaseType { |
| kInvalid, |
| kU32, |
| kI32, |
| kF32, |
| }; |
| |
| /// Writes the BaseType to the std::ostream. |
| /// @param out the std::ostream to write to |
| /// @param format the BaseType to write |
| /// @returns out so calls can be chained |
| std::ostream& operator<<(std::ostream& out, BaseType format) { |
| switch (format) { |
| case BaseType::kInvalid: |
| return out << "invalid"; |
| case BaseType::kU32: |
| return out << "u32"; |
| case BaseType::kI32: |
| return out << "i32"; |
| case BaseType::kF32: |
| return out << "f32"; |
| } |
| return out << "<unknown>"; |
| } |
| |
| /// Writes the VertexFormat to the std::ostream. |
| /// @param out the std::ostream to write to |
| /// @param format the VertexFormat to write |
| /// @returns out so calls can be chained |
| std::ostream& operator<<(std::ostream& out, VertexFormat format) { |
| switch (format) { |
| case VertexFormat::kUint8x2: |
| return out << "uint8x2"; |
| case VertexFormat::kUint8x4: |
| return out << "uint8x4"; |
| case VertexFormat::kSint8x2: |
| return out << "sint8x2"; |
| case VertexFormat::kSint8x4: |
| return out << "sint8x4"; |
| case VertexFormat::kUnorm8x2: |
| return out << "unorm8x2"; |
| case VertexFormat::kUnorm8x4: |
| return out << "unorm8x4"; |
| case VertexFormat::kSnorm8x2: |
| return out << "snorm8x2"; |
| case VertexFormat::kSnorm8x4: |
| return out << "snorm8x4"; |
| case VertexFormat::kUint16x2: |
| return out << "uint16x2"; |
| case VertexFormat::kUint16x4: |
| return out << "uint16x4"; |
| case VertexFormat::kSint16x2: |
| return out << "sint16x2"; |
| case VertexFormat::kSint16x4: |
| return out << "sint16x4"; |
| case VertexFormat::kUnorm16x2: |
| return out << "unorm16x2"; |
| case VertexFormat::kUnorm16x4: |
| return out << "unorm16x4"; |
| case VertexFormat::kSnorm16x2: |
| return out << "snorm16x2"; |
| case VertexFormat::kSnorm16x4: |
| return out << "snorm16x4"; |
| case VertexFormat::kFloat16x2: |
| return out << "float16x2"; |
| case VertexFormat::kFloat16x4: |
| return out << "float16x4"; |
| case VertexFormat::kFloat32: |
| return out << "float32"; |
| case VertexFormat::kFloat32x2: |
| return out << "float32x2"; |
| case VertexFormat::kFloat32x3: |
| return out << "float32x3"; |
| case VertexFormat::kFloat32x4: |
| return out << "float32x4"; |
| case VertexFormat::kUint32: |
| return out << "uint32"; |
| case VertexFormat::kUint32x2: |
| return out << "uint32x2"; |
| case VertexFormat::kUint32x3: |
| return out << "uint32x3"; |
| case VertexFormat::kUint32x4: |
| return out << "uint32x4"; |
| case VertexFormat::kSint32: |
| return out << "sint32"; |
| case VertexFormat::kSint32x2: |
| return out << "sint32x2"; |
| case VertexFormat::kSint32x3: |
| return out << "sint32x3"; |
| case VertexFormat::kSint32x4: |
| return out << "sint32x4"; |
| } |
| return out << "<unknown>"; |
| } |
| |
| /// A vertex attribute data format. |
| struct DataType { |
| BaseType base_type; |
| uint32_t width; // 1 for scalar, 2+ for a vector |
| }; |
| |
| DataType DataTypeOf(const sem::Type* ty) { |
| if (ty->Is<sem::I32>()) { |
| return {BaseType::kI32, 1}; |
| } |
| if (ty->Is<sem::U32>()) { |
| return {BaseType::kU32, 1}; |
| } |
| if (ty->Is<sem::F32>()) { |
| return {BaseType::kF32, 1}; |
| } |
| if (auto* vec = ty->As<sem::Vector>()) { |
| return {DataTypeOf(vec->type()).base_type, vec->Width()}; |
| } |
| return {BaseType::kInvalid, 0}; |
| } |
| |
| DataType DataTypeOf(VertexFormat format) { |
| switch (format) { |
| case VertexFormat::kUint32: |
| return {BaseType::kU32, 1}; |
| case VertexFormat::kUint8x2: |
| case VertexFormat::kUint16x2: |
| case VertexFormat::kUint32x2: |
| return {BaseType::kU32, 2}; |
| case VertexFormat::kUint32x3: |
| return {BaseType::kU32, 3}; |
| case VertexFormat::kUint8x4: |
| case VertexFormat::kUint16x4: |
| case VertexFormat::kUint32x4: |
| return {BaseType::kU32, 4}; |
| case VertexFormat::kSint32: |
| return {BaseType::kI32, 1}; |
| case VertexFormat::kSint8x2: |
| case VertexFormat::kSint16x2: |
| case VertexFormat::kSint32x2: |
| return {BaseType::kI32, 2}; |
| case VertexFormat::kSint32x3: |
| return {BaseType::kI32, 3}; |
| case VertexFormat::kSint8x4: |
| case VertexFormat::kSint16x4: |
| case VertexFormat::kSint32x4: |
| return {BaseType::kI32, 4}; |
| case VertexFormat::kFloat32: |
| return {BaseType::kF32, 1}; |
| case VertexFormat::kUnorm8x2: |
| case VertexFormat::kSnorm8x2: |
| case VertexFormat::kUnorm16x2: |
| case VertexFormat::kSnorm16x2: |
| case VertexFormat::kFloat16x2: |
| case VertexFormat::kFloat32x2: |
| return {BaseType::kF32, 2}; |
| case VertexFormat::kFloat32x3: |
| return {BaseType::kF32, 3}; |
| case VertexFormat::kUnorm8x4: |
| case VertexFormat::kSnorm8x4: |
| case VertexFormat::kUnorm16x4: |
| case VertexFormat::kSnorm16x4: |
| case VertexFormat::kFloat16x4: |
| case VertexFormat::kFloat32x4: |
| return {BaseType::kF32, 4}; |
| } |
| return {BaseType::kInvalid, 0}; |
| } |
| |
| 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; |
| }; |
| |
| struct LocationInfo { |
| std::function<const ast::Expression*()> expr; |
| const sem::Type* type; |
| }; |
| |
| CloneContext& ctx; |
| VertexPulling::Config const cfg; |
| std::unordered_map<uint32_t, LocationInfo> location_info; |
| std::function<const ast::Expression*()> vertex_index_expr = nullptr; |
| std::function<const ast::Expression*()> instance_index_expr = nullptr; |
| Symbol pulling_position_name; |
| Symbol struct_buffer_name; |
| std::unordered_map<uint32_t, Symbol> vertex_buffer_names; |
| utils::Vector<const ast::Parameter*, 8> 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 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; |
| } |
| |
| /// Adds storage buffer decorated variables for the vertex buffers |
| void AddVertexStorageBuffers() { |
| // Creating the struct type |
| static const char kStructName[] = "TintVertexData"; |
| auto* struct_type = |
| ctx.dst->Structure(ctx.dst->Symbols().New(kStructName), |
| utils::Vector{ |
| ctx.dst->Member(GetStructBufferName(), ctx.dst->ty.array<u32>()), |
| }); |
| for (uint32_t i = 0; i < cfg.vertex_state.size(); ++i) { |
| // The decorated variable with struct type |
| ctx.dst->GlobalVar(GetVertexBufferName(i), ctx.dst->ty.Of(struct_type), |
| ast::AddressSpace::kStorage, ast::Access::kRead, |
| ctx.dst->Binding(AInt(i)), ctx.dst->Group(AInt(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. |
| |
| utils::Vector<const ast::Statement*, 8> stmts; |
| |
| for (uint32_t buffer_idx = 0; buffer_idx < cfg.vertex_state.size(); ++buffer_idx) { |
| const VertexBufferLayoutDescriptor& buffer_layout = cfg.vertex_state[buffer_idx]; |
| |
| if ((buffer_layout.array_stride & 3) != 0) { |
| ctx.dst->Diagnostics().add_error( |
| diag::System::Transform, |
| "WebGPU requires that vertex stride must be a multiple of 4 bytes, " |
| "but VertexPulling array stride for buffer " + |
| std::to_string(buffer_idx) + " was " + |
| std::to_string(buffer_layout.array_stride) + " bytes"); |
| return nullptr; |
| } |
| |
| auto* index_expr = buffer_layout.step_mode == VertexStepMode::kVertex |
| ? vertex_index_expr() |
| : instance_index_expr(); |
| |
| // buffer_array_base is the base array offset for all the vertex |
| // attributes. These are units of uint (4 bytes). |
| auto buffer_array_base = |
| ctx.dst->Symbols().New("buffer_array_base_" + std::to_string(buffer_idx)); |
| |
| auto* attribute_offset = index_expr; |
| if (buffer_layout.array_stride != 4) { |
| attribute_offset = ctx.dst->Mul(index_expr, u32(buffer_layout.array_stride / 4u)); |
| } |
| |
| // let pulling_offset_n = <attribute_offset> |
| stmts.Push(ctx.dst->Decl(ctx.dst->Let(buffer_array_base, attribute_offset))); |
| |
| for (const VertexAttributeDescriptor& attribute_desc : buffer_layout.attributes) { |
| auto it = location_info.find(attribute_desc.shader_location); |
| if (it == location_info.end()) { |
| continue; |
| } |
| auto& var = it->second; |
| |
| // Data type of the target WGSL variable |
| auto var_dt = DataTypeOf(var.type); |
| // Data type of the vertex stream attribute |
| auto fmt_dt = DataTypeOf(attribute_desc.format); |
| |
| // Base types must match between the vertex stream and the WGSL variable |
| if (var_dt.base_type != fmt_dt.base_type) { |
| std::stringstream err; |
| err << "VertexAttributeDescriptor for location " |
| << std::to_string(attribute_desc.shader_location) << " has format " |
| << attribute_desc.format << " but shader expects " |
| << var.type->FriendlyName(ctx.src->Symbols()); |
| ctx.dst->Diagnostics().add_error(diag::System::Transform, err.str()); |
| return nullptr; |
| } |
| |
| // Load the attribute value |
| auto* fetch = Fetch(buffer_array_base, attribute_desc.offset, buffer_idx, |
| attribute_desc.format); |
| |
| // The attribute value may not be of the desired vector width. If it is |
| // not, we'll need to either reduce the width with a swizzle, or append |
| // 0's and / or a 1. |
| auto* value = fetch; |
| if (var_dt.width < fmt_dt.width) { |
| // WGSL variable vector width is smaller than the loaded vector width |
| switch (var_dt.width) { |
| case 1: |
| value = ctx.dst->MemberAccessor(fetch, "x"); |
| break; |
| case 2: |
| value = ctx.dst->MemberAccessor(fetch, "xy"); |
| break; |
| case 3: |
| value = ctx.dst->MemberAccessor(fetch, "xyz"); |
| break; |
| default: |
| TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics()) << var_dt.width; |
| return nullptr; |
| } |
| } else if (var_dt.width > fmt_dt.width) { |
| // WGSL variable vector width is wider than the loaded vector width |
| const ast::Type* ty = nullptr; |
| utils::Vector<const ast::Expression*, 8> values{fetch}; |
| switch (var_dt.base_type) { |
| case BaseType::kI32: |
| ty = ctx.dst->ty.i32(); |
| for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) { |
| values.Push(ctx.dst->Expr((i == 3) ? 1_i : 0_i)); |
| } |
| break; |
| case BaseType::kU32: |
| ty = ctx.dst->ty.u32(); |
| for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) { |
| values.Push(ctx.dst->Expr((i == 3) ? 1_u : 0_u)); |
| } |
| break; |
| case BaseType::kF32: |
| ty = ctx.dst->ty.f32(); |
| for (uint32_t i = fmt_dt.width; i < var_dt.width; i++) { |
| values.Push(ctx.dst->Expr((i == 3) ? 1_f : 0_f)); |
| } |
| break; |
| default: |
| TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics()) << var_dt.base_type; |
| return nullptr; |
| } |
| value = ctx.dst->Construct(ctx.dst->ty.vec(ty, var_dt.width), values); |
| } |
| |
| // Assign the value to the WGSL variable |
| stmts.Push(ctx.dst->Assign(var.expr(), value)); |
| } |
| } |
| |
| if (stmts.IsEmpty()) { |
| return nullptr; |
| } |
| |
| return ctx.dst->create<ast::BlockStatement>(std::move(stmts)); |
| } |
| |
| /// Generates an expression reading from a buffer a specific format. |
| /// @param array_base the symbol of the variable holding the base array offset |
| /// of the vertex array (each index is 4-bytes). |
| /// @param offset the byte offset of the data from `buffer_base` |
| /// @param buffer the index of the vertex buffer |
| /// @param format the format to read |
| const ast::Expression* Fetch(Symbol array_base, |
| uint32_t offset, |
| uint32_t buffer, |
| VertexFormat format) { |
| // Returns a u32 loaded from buffer_base + offset. |
| auto load_u32 = [&] { |
| return LoadPrimitive(array_base, offset, buffer, VertexFormat::kUint32); |
| }; |
| |
| // Returns a i32 loaded from buffer_base + offset. |
| auto load_i32 = [&] { return ctx.dst->Bitcast<i32>(load_u32()); }; |
| |
| // Returns a u32 loaded from buffer_base + offset + 4. |
| auto load_next_u32 = [&] { |
| return LoadPrimitive(array_base, offset + 4, buffer, VertexFormat::kUint32); |
| }; |
| |
| // Returns a i32 loaded from buffer_base + offset + 4. |
| auto load_next_i32 = [&] { return ctx.dst->Bitcast<i32>(load_next_u32()); }; |
| |
| // Returns a u16 loaded from offset, packed in the high 16 bits of a u32. |
| // The low 16 bits are 0. |
| // `min_alignment` must be a power of two. |
| // `offset` must be `min_alignment` bytes aligned. |
| auto load_u16_h = [&] { |
| auto low_u32_offset = offset & ~3u; |
| auto* low_u32 = |
| LoadPrimitive(array_base, low_u32_offset, buffer, VertexFormat::kUint32); |
| switch (offset & 3) { |
| case 0: |
| return ctx.dst->Shl(low_u32, 16_u); |
| case 1: |
| return ctx.dst->And(ctx.dst->Shl(low_u32, 8_u), 0xffff0000_u); |
| case 2: |
| return ctx.dst->And(low_u32, 0xffff0000_u); |
| default: { // 3: |
| auto* high_u32 = LoadPrimitive(array_base, low_u32_offset + 4, buffer, |
| VertexFormat::kUint32); |
| auto* shr = ctx.dst->Shr(low_u32, 8_u); |
| auto* shl = ctx.dst->Shl(high_u32, 24_u); |
| return ctx.dst->And(ctx.dst->Or(shl, shr), 0xffff0000_u); |
| } |
| } |
| }; |
| |
| // Returns a u16 loaded from offset, packed in the low 16 bits of a u32. |
| // The high 16 bits are 0. |
| auto load_u16_l = [&] { |
| auto low_u32_offset = offset & ~3u; |
| auto* low_u32 = |
| LoadPrimitive(array_base, low_u32_offset, buffer, VertexFormat::kUint32); |
| switch (offset & 3) { |
| case 0: |
| return ctx.dst->And(low_u32, 0xffff_u); |
| case 1: |
| return ctx.dst->And(ctx.dst->Shr(low_u32, 8_u), 0xffff_u); |
| case 2: |
| return ctx.dst->Shr(low_u32, 16_u); |
| default: { // 3: |
| auto* high_u32 = LoadPrimitive(array_base, low_u32_offset + 4, buffer, |
| VertexFormat::kUint32); |
| auto* shr = ctx.dst->Shr(low_u32, 24_u); |
| auto* shl = ctx.dst->Shl(high_u32, 8_u); |
| return ctx.dst->And(ctx.dst->Or(shl, shr), 0xffff_u); |
| } |
| } |
| }; |
| |
| // Returns a i16 loaded from offset, packed in the high 16 bits of a u32. |
| // The low 16 bits are 0. |
| auto load_i16_h = [&] { return ctx.dst->Bitcast<i32>(load_u16_h()); }; |
| |
| // Assumptions are made that alignment must be at least as large as the size |
| // of a single component. |
| switch (format) { |
| // Basic primitives |
| case VertexFormat::kUint32: |
| case VertexFormat::kSint32: |
| case VertexFormat::kFloat32: |
| return LoadPrimitive(array_base, offset, buffer, format); |
| |
| // Vectors of basic primitives |
| case VertexFormat::kUint32x2: |
| return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.u32(), |
| VertexFormat::kUint32, 2); |
| case VertexFormat::kUint32x3: |
| return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.u32(), |
| VertexFormat::kUint32, 3); |
| case VertexFormat::kUint32x4: |
| return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.u32(), |
| VertexFormat::kUint32, 4); |
| case VertexFormat::kSint32x2: |
| return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.i32(), |
| VertexFormat::kSint32, 2); |
| case VertexFormat::kSint32x3: |
| return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.i32(), |
| VertexFormat::kSint32, 3); |
| case VertexFormat::kSint32x4: |
| return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.i32(), |
| VertexFormat::kSint32, 4); |
| case VertexFormat::kFloat32x2: |
| return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.f32(), |
| VertexFormat::kFloat32, 2); |
| case VertexFormat::kFloat32x3: |
| return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.f32(), |
| VertexFormat::kFloat32, 3); |
| case VertexFormat::kFloat32x4: |
| return LoadVec(array_base, offset, buffer, 4, ctx.dst->ty.f32(), |
| VertexFormat::kFloat32, 4); |
| |
| case VertexFormat::kUint8x2: { |
| // yyxx0000, yyxx0000 |
| auto* u16s = ctx.dst->vec2<u32>(load_u16_h()); |
| // xx000000, yyxx0000 |
| auto* shl = ctx.dst->Shl(u16s, ctx.dst->vec2<u32>(8_u, 0_u)); |
| // 000000xx, 000000yy |
| return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(24_u)); |
| } |
| case VertexFormat::kUint8x4: { |
| // wwzzyyxx, wwzzyyxx, wwzzyyxx, wwzzyyxx |
| auto* u32s = ctx.dst->vec4<u32>(load_u32()); |
| // xx000000, yyxx0000, zzyyxx00, wwzzyyxx |
| auto* shl = ctx.dst->Shl(u32s, ctx.dst->vec4<u32>(24_u, 16_u, 8_u, 0_u)); |
| // 000000xx, 000000yy, 000000zz, 000000ww |
| return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(24_u)); |
| } |
| case VertexFormat::kUint16x2: { |
| // yyyyxxxx, yyyyxxxx |
| auto* u32s = ctx.dst->vec2<u32>(load_u32()); |
| // xxxx0000, yyyyxxxx |
| auto* shl = ctx.dst->Shl(u32s, ctx.dst->vec2<u32>(16_u, 0_u)); |
| // 0000xxxx, 0000yyyy |
| return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(16_u)); |
| } |
| case VertexFormat::kUint16x4: { |
| // yyyyxxxx, wwwwzzzz |
| auto* u32s = ctx.dst->vec2<u32>(load_u32(), load_next_u32()); |
| // yyyyxxxx, yyyyxxxx, wwwwzzzz, wwwwzzzz |
| auto* xxyy = ctx.dst->MemberAccessor(u32s, "xxyy"); |
| // xxxx0000, yyyyxxxx, zzzz0000, wwwwzzzz |
| auto* shl = ctx.dst->Shl(xxyy, ctx.dst->vec4<u32>(16_u, 0_u, 16_u, 0_u)); |
| // 0000xxxx, 0000yyyy, 0000zzzz, 0000wwww |
| return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(16_u)); |
| } |
| case VertexFormat::kSint8x2: { |
| // yyxx0000, yyxx0000 |
| auto* i16s = ctx.dst->vec2<i32>(load_i16_h()); |
| // xx000000, yyxx0000 |
| auto* shl = ctx.dst->Shl(i16s, ctx.dst->vec2<u32>(8_u, 0_u)); |
| // ssssssxx, ssssssyy |
| return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(24_u)); |
| } |
| case VertexFormat::kSint8x4: { |
| // wwzzyyxx, wwzzyyxx, wwzzyyxx, wwzzyyxx |
| auto* i32s = ctx.dst->vec4<i32>(load_i32()); |
| // xx000000, yyxx0000, zzyyxx00, wwzzyyxx |
| auto* shl = ctx.dst->Shl(i32s, ctx.dst->vec4<u32>(24_u, 16_u, 8_u, 0_u)); |
| // ssssssxx, ssssssyy, sssssszz, ssssssww |
| return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(24_u)); |
| } |
| case VertexFormat::kSint16x2: { |
| // yyyyxxxx, yyyyxxxx |
| auto* i32s = ctx.dst->vec2<i32>(load_i32()); |
| // xxxx0000, yyyyxxxx |
| auto* shl = ctx.dst->Shl(i32s, ctx.dst->vec2<u32>(16_u, 0_u)); |
| // ssssxxxx, ssssyyyy |
| return ctx.dst->Shr(shl, ctx.dst->vec2<u32>(16_u)); |
| } |
| case VertexFormat::kSint16x4: { |
| // yyyyxxxx, wwwwzzzz |
| auto* i32s = ctx.dst->vec2<i32>(load_i32(), load_next_i32()); |
| // yyyyxxxx, yyyyxxxx, wwwwzzzz, wwwwzzzz |
| auto* xxyy = ctx.dst->MemberAccessor(i32s, "xxyy"); |
| // xxxx0000, yyyyxxxx, zzzz0000, wwwwzzzz |
| auto* shl = ctx.dst->Shl(xxyy, ctx.dst->vec4<u32>(16_u, 0_u, 16_u, 0_u)); |
| // ssssxxxx, ssssyyyy, sssszzzz, sssswwww |
| return ctx.dst->Shr(shl, ctx.dst->vec4<u32>(16_u)); |
| } |
| case VertexFormat::kUnorm8x2: |
| return ctx.dst->MemberAccessor(ctx.dst->Call("unpack4x8unorm", load_u16_l()), "xy"); |
| case VertexFormat::kSnorm8x2: |
| return ctx.dst->MemberAccessor(ctx.dst->Call("unpack4x8snorm", load_u16_l()), "xy"); |
| case VertexFormat::kUnorm8x4: |
| return ctx.dst->Call("unpack4x8unorm", load_u32()); |
| case VertexFormat::kSnorm8x4: |
| return ctx.dst->Call("unpack4x8snorm", load_u32()); |
| case VertexFormat::kUnorm16x2: |
| return ctx.dst->Call("unpack2x16unorm", load_u32()); |
| case VertexFormat::kSnorm16x2: |
| return ctx.dst->Call("unpack2x16snorm", load_u32()); |
| case VertexFormat::kFloat16x2: |
| return ctx.dst->Call("unpack2x16float", load_u32()); |
| case VertexFormat::kUnorm16x4: |
| return ctx.dst->vec4<f32>(ctx.dst->Call("unpack2x16unorm", load_u32()), |
| ctx.dst->Call("unpack2x16unorm", load_next_u32())); |
| case VertexFormat::kSnorm16x4: |
| return ctx.dst->vec4<f32>(ctx.dst->Call("unpack2x16snorm", load_u32()), |
| ctx.dst->Call("unpack2x16snorm", load_next_u32())); |
| case VertexFormat::kFloat16x4: |
| return ctx.dst->vec4<f32>(ctx.dst->Call("unpack2x16float", load_u32()), |
| ctx.dst->Call("unpack2x16float", load_next_u32())); |
| } |
| |
| TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics()) |
| << "format " << static_cast<int>(format); |
| return nullptr; |
| } |
| |
| /// Generates an expression reading an aligned basic type (u32, i32, f32) from |
| /// a vertex buffer. |
| /// @param array_base the symbol of the variable holding the base array offset |
| /// of the vertex array (each index is 4-bytes). |
| /// @param offset the byte offset of the data from `buffer_base` |
| /// @param buffer the index of the vertex buffer |
| /// @param format VertexFormat::kUint32, VertexFormat::kSint32 or |
| /// VertexFormat::kFloat32 |
| const ast::Expression* LoadPrimitive(Symbol array_base, |
| uint32_t offset, |
| uint32_t buffer, |
| VertexFormat format) { |
| const ast::Expression* u = nullptr; |
| if ((offset & 3) == 0) { |
| // Aligned load. |
| |
| const ast ::Expression* index = nullptr; |
| if (offset > 0) { |
| index = ctx.dst->Add(array_base, u32(offset / 4)); |
| } else { |
| index = ctx.dst->Expr(array_base); |
| } |
| u = ctx.dst->IndexAccessor( |
| ctx.dst->MemberAccessor(GetVertexBufferName(buffer), GetStructBufferName()), index); |
| |
| } else { |
| // Unaligned load |
| uint32_t offset_aligned = offset & ~3u; |
| auto* low = LoadPrimitive(array_base, offset_aligned, buffer, VertexFormat::kUint32); |
| auto* high = |
| LoadPrimitive(array_base, offset_aligned + 4u, buffer, VertexFormat::kUint32); |
| |
| uint32_t shift = 8u * (offset & 3u); |
| |
| auto* low_shr = ctx.dst->Shr(low, u32(shift)); |
| auto* high_shl = ctx.dst->Shl(high, u32(32u - shift)); |
| u = ctx.dst->Or(low_shr, high_shl); |
| } |
| |
| switch (format) { |
| case VertexFormat::kUint32: |
| return u; |
| case VertexFormat::kSint32: |
| return ctx.dst->Bitcast(ctx.dst->ty.i32(), u); |
| case VertexFormat::kFloat32: |
| return ctx.dst->Bitcast(ctx.dst->ty.f32(), u); |
| default: |
| break; |
| } |
| TINT_UNREACHABLE(Transform, ctx.dst->Diagnostics()) |
| << "invalid format for LoadPrimitive" << static_cast<int>(format); |
| return nullptr; |
| } |
| |
| /// Generates an expression reading a vec2/3/4 from a vertex buffer. |
| /// @param array_base the symbol of the variable holding the base array offset |
| /// of the vertex array (each index is 4-bytes). |
| /// @param offset the byte offset of the data from `buffer_base` |
| /// @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 |
| const ast::Expression* LoadVec(Symbol array_base, |
| uint32_t offset, |
| uint32_t buffer, |
| uint32_t element_stride, |
| const ast::Type* base_type, |
| VertexFormat base_format, |
| uint32_t count) { |
| utils::Vector<const ast::Expression*, 8> expr_list; |
| for (uint32_t i = 0; i < count; ++i) { |
| // Offset read position by element_stride for each component |
| uint32_t primitive_offset = offset + element_stride * i; |
| expr_list.Push(LoadPrimitive(array_base, primitive_offset, buffer, base_format)); |
| } |
| |
| return ctx.dst->Construct(ctx.dst->create<ast::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(const ast::Function* func, const ast::Parameter* param) { |
| if (ast::HasAttribute<ast::LocationAttribute>(param->attributes)) { |
| // Create a function-scope variable to replace the parameter. |
| auto func_var_sym = ctx.Clone(param->symbol); |
| auto* func_var_type = ctx.Clone(param->type); |
| auto* func_var = ctx.dst->Var(func_var_sym, func_var_type); |
| ctx.InsertFront(func->body->statements, ctx.dst->Decl(func_var)); |
| // Capture mapping from location to the new variable. |
| LocationInfo info; |
| info.expr = [this, func_var]() { return ctx.dst->Expr(func_var); }; |
| |
| auto* sem = ctx.src->Sem().Get<sem::Parameter>(param); |
| info.type = sem->Type(); |
| |
| if (!sem->Location().has_value()) { |
| TINT_ICE(Transform, ctx.dst->Diagnostics()) << "Location missing value"; |
| return; |
| } |
| location_info[sem->Location().value()] = info; |
| } else if (auto* builtin = ast::GetAttribute<ast::BuiltinAttribute>(param->attributes)) { |
| // Check for existing vertex_index and instance_index builtins. |
| if (builtin->builtin == ast::BuiltinValue::kVertexIndex) { |
| vertex_index_expr = [this, param]() { |
| return ctx.dst->Expr(ctx.Clone(param->symbol)); |
| }; |
| } else if (builtin->builtin == ast::BuiltinValue::kInstanceIndex) { |
| instance_index_expr = [this, param]() { |
| return ctx.dst->Expr(ctx.Clone(param->symbol)); |
| }; |
| } |
| new_function_parameters.Push(ctx.Clone(param)); |
| } else { |
| TINT_ICE(Transform, 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 |
| /// @param struct_ty the structure type |
| void ProcessStructParameter(const ast::Function* func, |
| const ast::Parameter* param, |
| const ast::Struct* struct_ty) { |
| auto param_sym = ctx.Clone(param->symbol); |
| |
| // Process the struct members. |
| bool has_locations = false; |
| utils::Vector<const ast::StructMember*, 8> members_to_clone; |
| for (auto* member : struct_ty->members) { |
| auto member_sym = ctx.Clone(member->symbol); |
| std::function<const ast::Expression*()> member_expr = [this, param_sym, member_sym]() { |
| return ctx.dst->MemberAccessor(param_sym, member_sym); |
| }; |
| |
| if (ast::HasAttribute<ast::LocationAttribute>(member->attributes)) { |
| // Capture mapping from location to struct member. |
| LocationInfo info; |
| info.expr = member_expr; |
| |
| auto* sem = ctx.src->Sem().Get(member); |
| info.type = sem->Type(); |
| |
| TINT_ASSERT(Transform, sem->Location().has_value()); |
| location_info[sem->Location().value()] = info; |
| has_locations = true; |
| } else if (auto* builtin = |
| ast::GetAttribute<ast::BuiltinAttribute>(member->attributes)) { |
| // Check for existing vertex_index and instance_index builtins. |
| if (builtin->builtin == ast::BuiltinValue::kVertexIndex) { |
| vertex_index_expr = member_expr; |
| } else if (builtin->builtin == ast::BuiltinValue::kInstanceIndex) { |
| instance_index_expr = member_expr; |
| } |
| members_to_clone.Push(member); |
| } else { |
| TINT_ICE(Transform, ctx.dst->Diagnostics()) << "Invalid entry point parameter"; |
| } |
| } |
| |
| if (!has_locations) { |
| // Nothing to do. |
| new_function_parameters.Push(ctx.Clone(param)); |
| return; |
| } |
| |
| // Create a function-scope variable to replace the parameter. |
| auto* func_var = ctx.dst->Var(param_sym, ctx.Clone(param->type)); |
| ctx.InsertFront(func->body->statements, ctx.dst->Decl(func_var)); |
| |
| if (!members_to_clone.IsEmpty()) { |
| // Create a new struct without the location attributes. |
| utils::Vector<const ast::StructMember*, 8> new_members; |
| for (auto* member : members_to_clone) { |
| auto member_sym = ctx.Clone(member->symbol); |
| auto* member_type = ctx.Clone(member->type); |
| auto member_attrs = ctx.Clone(member->attributes); |
| new_members.Push(ctx.dst->Member(member_sym, member_type, std::move(member_attrs))); |
| } |
| 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(), ctx.dst->ty.Of(new_struct)); |
| new_function_parameters.Push(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.InsertFront(func->body->statements, |
| 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(const 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 (auto* str = sem->Type()->As<sem::Struct>()) { |
| ProcessStructParameter(func, param, str->Declaration()); |
| } 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 == VertexStepMode::kVertex) { |
| auto name = ctx.dst->Symbols().New("tint_pulling_vertex_index"); |
| new_function_parameters.Push(ctx.dst->Param( |
| name, ctx.dst->ty.u32(), |
| utils::Vector{ctx.dst->Builtin(ast::BuiltinValue::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 == VertexStepMode::kInstance) { |
| auto name = ctx.dst->Symbols().New("tint_pulling_instance_index"); |
| new_function_parameters.Push(ctx.dst->Param( |
| name, ctx.dst->ty.u32(), |
| utils::Vector{ctx.dst->Builtin(ast::BuiltinValue::kInstanceIndex)})); |
| instance_index_expr = [this, name]() { return ctx.dst->Expr(name); }; |
| break; |
| } |
| } |
| } |
| |
| // Generate vertex pulling preamble. |
| if (auto* block = CreateVertexPullingPreamble()) { |
| ctx.InsertFront(func->body->statements, block); |
| } |
| |
| // 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 attrs = ctx.Clone(func->attributes); |
| auto ret_attrs = ctx.Clone(func->return_type_attributes); |
| auto* new_func = |
| ctx.dst->create<ast::Function>(func->source, func_sym, new_function_parameters, |
| ret_type, body, std::move(attrs), std::move(ret_attrs)); |
| ctx.Replace(func, new_func); |
| } |
| }; |
| |
| } // namespace |
| |
| VertexPulling::VertexPulling() = default; |
| VertexPulling::~VertexPulling() = default; |
| |
| void VertexPulling::Run(CloneContext& ctx, const DataMap& inputs, DataMap&) const { |
| auto cfg = cfg_; |
| if (auto* cfg_data = inputs.Get<Config>()) { |
| cfg = *cfg_data; |
| } |
| |
| // Find entry point |
| const ast::Function* func = nullptr; |
| for (auto* fn : ctx.src->AST().Functions()) { |
| if (fn->PipelineStage() == ast::PipelineStage::kVertex) { |
| if (func != nullptr) { |
| ctx.dst->Diagnostics().add_error( |
| diag::System::Transform, |
| "VertexPulling found more than one vertex entry point"); |
| return; |
| } |
| func = fn; |
| } |
| } |
| if (func == nullptr) { |
| ctx.dst->Diagnostics().add_error(diag::System::Transform, |
| "Vertex stage entry point not found"); |
| return; |
| } |
| |
| // 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 |
| |
| State state{ctx, cfg}; |
| state.AddVertexStorageBuffers(); |
| state.Process(func); |
| |
| ctx.Clone(); |
| } |
| |
| 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( |
| uint32_t in_array_stride, |
| VertexStepMode 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 tint::transform |