blob: 09f36c896d30eaa554aa9476c48797c241546356 [file] [edit]
// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/core/ir/transform/vertex_pulling.h"
#include <utility>
#include "src/tint/lang/core/ir/builder.h"
#include "src/tint/lang/core/ir/validator.h"
#include "src/tint/lang/core/type/manager.h"
namespace tint::core::ir::transform {
namespace {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
/// @returns the vector width of @p format
uint32_t FormatWidth(VertexFormat format) {
switch (format) {
case VertexFormat::kUint8:
case VertexFormat::kSint8:
case VertexFormat::kUnorm8:
case VertexFormat::kSnorm8:
case VertexFormat::kUint16:
case VertexFormat::kSint16:
case VertexFormat::kUnorm16:
case VertexFormat::kSnorm16:
case VertexFormat::kFloat16:
case VertexFormat::kFloat32:
case VertexFormat::kUint32:
case VertexFormat::kSint32:
return 1;
case VertexFormat::kUint8x2:
case VertexFormat::kSint8x2:
case VertexFormat::kUnorm8x2:
case VertexFormat::kSnorm8x2:
case VertexFormat::kUint16x2:
case VertexFormat::kSint16x2:
case VertexFormat::kUnorm16x2:
case VertexFormat::kSnorm16x2:
case VertexFormat::kFloat16x2:
case VertexFormat::kFloat32x2:
case VertexFormat::kUint32x2:
case VertexFormat::kSint32x2:
return 2;
case VertexFormat::kFloat32x3:
case VertexFormat::kUint32x3:
case VertexFormat::kSint32x3:
return 3;
case VertexFormat::kUint8x4:
case VertexFormat::kSint8x4:
case VertexFormat::kUnorm8x4:
case VertexFormat::kSnorm8x4:
case VertexFormat::kUint16x4:
case VertexFormat::kSint16x4:
case VertexFormat::kUnorm16x4:
case VertexFormat::kSnorm16x4:
case VertexFormat::kFloat16x4:
case VertexFormat::kFloat32x4:
case VertexFormat::kUint32x4:
case VertexFormat::kSint32x4:
case VertexFormat::kUnorm10_10_10_2:
case VertexFormat::kUnorm8x4BGRA:
return 4;
}
TINT_UNREACHABLE();
}
/// PIMPL state for the transform.
struct State {
/// The vertex pulling configuration.
const VertexPullingConfig& config;
/// The IR module.
core::ir::Module& ir;
/// The IR builder.
core::ir::Builder b{ir};
/// The type manager.
core::type::Manager& ty{ir.Types()};
/// A map from location to a descriptor that holds the format, buffer, the offset of the
/// vertex data from the start of the buffer, and the offset of the attribute from the start of
/// the element.
struct LocationInfo {
VertexFormat format;
core::ir::Value* buffer = nullptr;
// Word offset within the buffer to the start of the data for this invocation.
core::ir::Value* base_offset = nullptr;
// Byte offset within the data for this invocation to the start of the attribute data.
uint32_t attr_byte_offset;
};
Hashmap<uint32_t, LocationInfo, 4> locations_{};
/// The vertex index function parameter.
core::ir::FunctionParam* vertex_index_ = nullptr;
/// The instance index function parameter.
core::ir::FunctionParam* instance_index_ = nullptr;
/// Process the module.
void Process() {
// Find the vertex shader entry point. There should be at most one.
core::ir::Function* ep = nullptr;
for (auto& func : ir.functions) {
if (func->IsVertex()) {
TINT_ASSERT(!ep);
ep = func;
}
}
if (!ep) {
return;
}
Vector<core::ir::FunctionParam*, 4> new_params;
b.InsertBefore(ep->Block()->Front(), [&] { //
// Create the storage buffers and record vertex attribute information.
CreateBuffers();
// Pull vertex attributes out of the entry point parameters and replace them.
for (auto* param : ep->Params()) {
if (auto* str = param->Type()->As<core::type::Struct>()) {
ProcessStructParameter(param, str);
param->Destroy();
} else if (auto loc = param->Attributes().location) {
// Load the vertex attribute and replace uses of the parameter with it.
auto* input = Load(*loc, param->Type());
param->ReplaceAllUsesWith(input);
param->Destroy();
} else {
// Other parameters should be builtins, which can only be the vertex and
// instance indices. Replace any user-declared indices with the ones that we
// created when setting up the buffers.
auto builtin = param->Builtin();
TINT_ASSERT(builtin);
switch (*builtin) {
case core::BuiltinValue::kVertexIndex:
if (vertex_index_) {
param->ReplaceAllUsesWith(vertex_index_);
param->Destroy();
} else {
new_params.Push(param);
}
break;
case core::BuiltinValue::kInstanceIndex:
if (instance_index_) {
param->ReplaceAllUsesWith(instance_index_);
param->Destroy();
} else {
new_params.Push(param);
}
break;
default:
TINT_UNREACHABLE();
}
}
}
});
// Update the entry point with the new parameter list.
if (vertex_index_) {
new_params.Push(vertex_index_);
}
if (instance_index_) {
new_params.Push(instance_index_);
}
ep->SetParams(std::move(new_params));
}
/// @returns the vertex index parameter, creating one if needed
core::ir::FunctionParam* GetVertexIndex() {
if (!vertex_index_) {
vertex_index_ = b.FunctionParam<u32>("tint_vertex_index");
vertex_index_->SetBuiltin(core::BuiltinValue::kVertexIndex);
}
return vertex_index_;
}
/// @returns the instance index parameter, creating one if needed
core::ir::FunctionParam* GetInstanceIndex() {
if (!instance_index_) {
instance_index_ = b.FunctionParam<u32>("tint_instance_index");
instance_index_->SetBuiltin(core::BuiltinValue::kInstanceIndex);
}
return instance_index_;
}
/// Create storage buffers and record vertex attribute information.
/// Record instructions that need vertex and instance indices.
void CreateBuffers() {
for (uint32_t i = 0; i < config.vertex_state.size(); i++) {
// Create the storage buffer.
auto& buffer = config.vertex_state[i];
auto buffer_name = "tint_vertex_buffer_" + std::to_string(i);
core::ir::Var* var = nullptr;
b.Append(ir.root_block, [&] {
var = b.Var(buffer_name, ty.ptr<storage, array<u32>, read>());
var->SetBindingPoint(config.pulling_group, i);
});
// Determine the base offset of the vertex data in the storage buffer.
core::ir::Value* index = nullptr;
if (buffer.step_mode == VertexStepMode::kVertex) {
index = GetVertexIndex();
} else if (buffer.step_mode == VertexStepMode::kInstance) {
index = GetInstanceIndex();
}
if (buffer.array_stride != 4) {
// Multiply the index by the stride in words.
TINT_ASSERT((buffer.array_stride & 3u) == 0u);
index = b.Multiply<u32>(index, u32(buffer.array_stride / 4))->Result();
ir.SetName(index, buffer_name + "_base");
}
// Register the format, buffer, and offset for each location slot.
for (auto& attr : buffer.attributes) {
locations_.Add(attr.shader_location,
LocationInfo{attr.format, var->Result(), index, attr.offset});
}
}
}
/// Pull vertex attributes out of structure parameter.
/// Replace the parameter with a new structure created from the attributes.
/// @param param the parameter
/// @param str the structure type
void ProcessStructParameter(core::ir::FunctionParam* param, const core::type::Struct* str) {
Vector<core::ir::Value*, 4> construct_args;
for (auto* member : str->Members()) {
if (auto loc = member->Attributes().location) {
construct_args.Push(Load(*loc, member->Type()));
} else {
// Other parameters should be builtins, which can only be the vertex and instance
// indices. Use the separate parameters that we created for these indices. Because
// there are no duplicates, this doesn't conflict with the param handling above.
TINT_ASSERT(member->Attributes().builtin);
switch (*member->Attributes().builtin) {
case core::BuiltinValue::kVertexIndex:
construct_args.Push(GetVertexIndex());
break;
case core::BuiltinValue::kInstanceIndex:
construct_args.Push(GetInstanceIndex());
break;
default:
TINT_UNREACHABLE();
}
}
}
param->ReplaceAllUsesWith(b.Construct(str, std::move(construct_args))->Result());
}
/// Load a vertex attribute.
/// @param location the location index of the attribute
/// @param shader_type the type of the attribute declared in the shader
/// @returns the loaded attribute value
core::ir::Value* Load(uint32_t location, const core::type::Type* shader_type) {
auto info = locations_.Get(location);
TINT_ASSERT(info);
// Load the attribute data from the buffer.
auto* value = LoadFromBuffer(*info, shader_type->DeepestElement());
// The shader type may have a different component width to the vertex attribute, so we may
// need to add or remove components.
auto src_width = FormatWidth(info->format);
auto dst_width = 1u;
if (auto* vec = shader_type->As<core::type::Vector>()) {
dst_width = vec->Width();
}
if (dst_width < src_width) {
// The type declared in the shader is narrower than the vertex attribute format, so
// truncate the value with a swizzle.
switch (dst_width) {
case 1:
value = b.Swizzle(shader_type, value, Vector{0u})->Result();
break;
case 2:
value = b.Swizzle(shader_type, value, Vector{0u, 1u})->Result();
break;
case 3:
value = b.Swizzle(shader_type, value, Vector{0u, 1u, 2u})->Result();
break;
default:
TINT_UNREACHABLE() << dst_width;
}
} else if (dst_width > src_width) {
// The type declared in the shader is wider than the vertex attribute format, so append
// values to pad it out. Append a `1` value for the fourth element of a vector,
// otherwise append zero.
auto* elem_ty = shader_type->DeepestElement();
auto one = [&] {
return tint::Switch(
elem_ty, //
[&](const core::type::I32*) { return b.Constant(1_i); }, //
[&](const core::type::U32*) { return b.Constant(1_u); }, //
[&](const core::type::F32*) { return b.Constant(1_f); }, //
[&](const core::type::F16*) { return b.Constant(1_h); }, //
TINT_ICE_ON_NO_MATCH);
};
Vector<core::ir::Value*, 4> values{value};
for (uint32_t i = src_width; i < dst_width; i++) {
values.Push(i == 3 ? one() : b.Zero(elem_ty));
}
value = b.Construct(shader_type, std::move(values))->Result();
}
return value;
}
/// Load attribute data from a buffer.
/// @param info the location descriptor
/// @param shader_element_type the element type of the attribute declared in the shader
/// @returns the loaded attribute data
core::ir::Value* LoadFromBuffer(const LocationInfo& info,
const core::type::Type* shader_element_type) {
// Helper for loading a single word from the buffer at an offset.
auto load_u32 = [&](uint32_t offset) {
auto offset_value = info.base_offset;
offset += (info.attr_byte_offset / 4u);
if (offset > 0) {
offset_value = b.Add<u32>(offset_value, u32(offset))->Result();
}
auto* word =
b.Load(b.Access<ptr<storage, u32, read>>(info.buffer, offset_value))->Result();
// If the offset is not 4-byte aligned, shift the word so that the requested data starts
// at the first byte. The shift amount is the offset of the byte within a word
// multiplied by 8 to get the bit offset.
if (info.attr_byte_offset & 3) {
word = b.ShiftRight<u32>(word, u32((info.attr_byte_offset & 3) * 8))->Result();
}
return word;
};
// Helpers for loading non-u32 data from the buffer.
auto load_i32 = [&](uint32_t offset) { return b.Bitcast<i32>(load_u32(offset))->Result(); };
auto load_f32 = [&](uint32_t offset) { return b.Bitcast<f32>(load_u32(offset))->Result(); };
auto load_ivec = [&](uint32_t offset, uint32_t bits, const core::type::Vector* vec) {
// For a vec2<u32>, we read the `xxxx'yyyy` u32 word. We then splat to a vec2 and left
// shift so we have `(xxxx'yyyy, yyyy'xxxx)`. Finally, we right shift to produce
// `(0000'xxxx, 0000'yyyy)`
auto* uvec = ty.MatchWidth(ty.u32(), vec);
// yyyyxxxx
auto* word = load_u32(offset);
if (vec->Type()->Is<core::type::I32>()) {
word = b.Bitcast<i32>(word)->Result();
}
// yyyyxxxx, yyyyxxxx
auto* splat = b.Construct(vec, word);
// xxxxyyyy, yyyyxxxx
core::ir::Instruction* shift_left = nullptr;
switch (vec->Width()) {
case 2:
if (bits == 8) {
shift_left = b.ShiftLeft(vec, splat, b.Composite(uvec, 24_u, 16_u));
} else if (bits == 16) {
shift_left = b.ShiftLeft(vec, splat, b.Composite(uvec, 16_u, 0_u));
} else {
TINT_UNREACHABLE();
}
break;
case 4:
TINT_ASSERT(bits == 8);
shift_left = b.ShiftLeft(vec, splat, b.Composite(uvec, 24_u, 16_u, 8_u, 0_u));
break;
default:
TINT_UNREACHABLE();
}
// 0000xxxx, 0000yyyy
return b.ShiftRight(vec, shift_left, b.Splat(uvec, u32(32 - bits)))->Result();
};
// Helper to convert a value to f16 if required by the shader, otherwise returns the f32.
auto float_value = [&](core::ir::Value* value) -> core::ir::Value* {
// If the shader expects an f16 value, convert the value.
if (shader_element_type->Is<core::type::F16>()) {
return b.Convert(ty.MatchWidth(ty.f16(), value->Type()), value)->Result();
}
return value;
};
switch (info.format) {
// Formats that are always u32 in the shader (or vectors of u32).
// Shift/mask values to expand to 32-bits.
case VertexFormat::kUint8:
return b.And<u32>(load_u32(0), 0xFF_u)->Result();
case VertexFormat::kUint8x2:
return load_ivec(0, 8, ty.vec2<u32>());
case VertexFormat::kUint8x4:
return load_ivec(0, 8, ty.vec4<u32>());
case VertexFormat::kUint16:
return b.And<u32>(load_u32(0), 0xFFFF_u)->Result();
case VertexFormat::kUint16x2:
return load_ivec(0, 16, ty.vec2<u32>());
case VertexFormat::kUint16x4: {
auto* xy = load_ivec(0, 16, ty.vec2<u32>());
auto* zw = load_ivec(1, 16, ty.vec2<u32>());
return b.Construct<vec4<u32>>(xy, zw)->Result();
}
case VertexFormat::kUint32:
return load_u32(0);
case VertexFormat::kUint32x2: {
auto* x = load_u32(0);
auto* y = load_u32(1);
return b.Construct<vec2<u32>>(x, y)->Result();
}
case VertexFormat::kUint32x3: {
auto* x = load_u32(0);
auto* y = load_u32(1);
auto* z = load_u32(2);
return b.Construct<vec3<u32>>(x, y, z)->Result();
}
case VertexFormat::kUint32x4: {
auto* x = load_u32(0);
auto* y = load_u32(1);
auto* z = load_u32(2);
auto* w = load_u32(3);
return b.Construct<vec4<u32>>(x, y, z, w)->Result();
}
// Formats that are always i32 in the shader (or vectors of i32).
// Shift values to expand to 32-bits.
case VertexFormat::kSint8: {
// ******xx
auto* word = b.Bitcast<i32>(load_u32(0));
// 000000xx
return b.ShiftRight<i32>(b.ShiftLeft<i32>(word, 24_u), 24_u)->Result();
}
case VertexFormat::kSint8x2:
return load_ivec(0, 8, ty.vec2<i32>());
case VertexFormat::kSint8x4:
return load_ivec(0, 8, ty.vec4<i32>());
case VertexFormat::kSint16: {
// ****xxxx
auto* word = b.Bitcast<i32>(load_u32(0));
// 0000xxxx
return b.ShiftRight<i32>(b.ShiftLeft<i32>(word, 16_u), 16_u)->Result();
}
case VertexFormat::kSint16x2:
return load_ivec(0, 16, ty.vec2<i32>());
case VertexFormat::kSint16x4: {
auto* xy = load_ivec(0, 16, ty.vec2<i32>());
auto* zw = load_ivec(1, 16, ty.vec2<i32>());
return b.Construct<vec4<i32>>(xy, zw)->Result();
}
case VertexFormat::kSint32:
return load_i32(0);
case VertexFormat::kSint32x2: {
auto* x = load_i32(0);
auto* y = load_i32(1);
return b.Construct<vec2<i32>>(x, y)->Result();
}
case VertexFormat::kSint32x3: {
auto* x = load_i32(0);
auto* y = load_i32(1);
auto* z = load_i32(2);
return b.Construct<vec3<i32>>(x, y, z)->Result();
}
case VertexFormat::kSint32x4: {
auto* x = load_i32(0);
auto* y = load_i32(1);
auto* z = load_i32(2);
auto* w = load_i32(3);
return b.Construct<vec4<i32>>(x, y, z, w)->Result();
}
// Unsigned normalized formats.
// Use unpack builtins to convert to f32.
case VertexFormat::kUnorm8: {
// ******xx
auto* word = load_u32(0);
// 000000xx, ********, ********, ********
auto* unpack = b.Call<vec4<f32>>(core::BuiltinFn::kUnpack4X8Unorm, word);
// 000000xx
return float_value(b.Access<f32>(unpack, 0_u)->Result());
}
case VertexFormat::kUnorm8x2: {
// ****yyxx
auto* word = load_u32(0);
// 000000xx, 000000yy, ********, ********
auto* unpack = b.Call<vec4<f32>>(core::BuiltinFn::kUnpack4X8Unorm, word);
// 000000xx, 000000yy
return float_value(b.Swizzle<vec2<f32>>(unpack, Vector{0u, 1u})->Result());
}
case VertexFormat::kUnorm8x4: {
// wwzzyyxx
auto* word = load_u32(0);
// 000000xx, 000000yy, 000000zz, 000000ww
auto* unpack = b.Call<vec4<f32>>(core::BuiltinFn::kUnpack4X8Unorm, word);
return float_value(unpack->Result());
}
case VertexFormat::kUnorm8x4BGRA: {
// wwzzyyxx
auto* word = load_u32(0);
// 000000xx, 000000yy, 000000zz, 000000ww
auto* unpack = b.Call<vec4<f32>>(core::BuiltinFn::kUnpack4X8Unorm, word);
// 000000zz, 000000yy, 000000xx, 000000ww
return float_value(b.Swizzle<vec4<f32>>(unpack, Vector{2u, 1u, 0u, 3u})->Result());
}
case VertexFormat::kUnorm16: {
// ****xxxx
auto* word = load_u32(0);
// 0000xxxx, ********
auto* unpack = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Unorm, word);
// 0000xxxx
return float_value(b.Access<f32>(unpack, 0_u)->Result());
}
case VertexFormat::kUnorm16x2: {
// yyyyxxxx
auto* word = load_u32(0);
// 0000xxxx, 0000yyyy
auto* unpack = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Unorm, word);
return float_value(unpack->Result());
}
case VertexFormat::kUnorm16x4: {
// yyyyxxxx, wwwwzzzz
auto* word0 = load_u32(0);
auto* word1 = load_u32(1);
// 0000xxxx, 0000yyyy, 0000zzzz, 0000wwww
auto* unpack0 = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Unorm, word0);
auto* unpack1 = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Unorm, word1);
return float_value(b.Construct<vec4<f32>>(unpack0, unpack1)->Result());
}
// Signed normalized formats.
// Use unpack builtins to expand to f32.
case VertexFormat::kSnorm8: {
// ******xx
auto* word = load_u32(0);
// 000000xx, ********, ********, ********
auto* unpack = b.Call<vec4<f32>>(core::BuiltinFn::kUnpack4X8Snorm, word);
// 000000xx
return float_value(b.Access<f32>(unpack, 0_u)->Result());
}
case VertexFormat::kSnorm8x2: {
// ****yyxx
auto* word = load_u32(0);
// 000000xx, 000000yy, ********, ********
auto* unpack = b.Call<vec4<f32>>(core::BuiltinFn::kUnpack4X8Snorm, word);
// 000000xx, 000000yy
return float_value(b.Swizzle<vec2<f32>>(unpack, Vector{0u, 1u})->Result());
}
case VertexFormat::kSnorm8x4: {
// wwzzyyxx
auto* word = load_u32(0);
// 000000xx, 000000yy, 000000zz, 000000ww
auto* unpack = b.Call<vec4<f32>>(core::BuiltinFn::kUnpack4X8Snorm, word);
return float_value(unpack->Result());
}
case VertexFormat::kSnorm16: {
// ****xxxx
auto* word = load_u32(0);
// 0000xxxx, ********
auto* unpack = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Snorm, word);
// 0000xxxx
return float_value(b.Access<f32>(unpack, 0_u)->Result());
}
case VertexFormat::kSnorm16x2: {
// yyyyxxxx
auto* word = load_u32(0);
// 0000xxxx, 0000yyyy
auto* unpack = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Snorm, word);
return float_value(unpack->Result());
}
case VertexFormat::kSnorm16x4: {
// yyyyxxxx, wwwwzzzz
auto* word0 = load_u32(0);
auto* word1 = load_u32(1);
// 0000xxxx, 0000yyyy, 0000zzzz, 0000wwww
auto* unpack0 = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Snorm, word0);
auto* unpack1 = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Snorm, word1);
return float_value(b.Construct<vec4<f32>>(unpack0, unpack1)->Result());
}
// F16 formats that can either be f16 or f32 in the shader.
// If f16 is expected just bitcast, otherwise use unpack builtins to convert to f32.
case VertexFormat::kFloat16: {
// ****xxxx
auto* word = load_u32(0);
if (shader_element_type->Is<core::type::F16>()) {
// xxxx, ****
auto* bitcast = b.Bitcast<vec2<f16>>(word);
// xxxx
return b.Access<f16>(bitcast, 0_u)->Result();
} else {
// 0000xxxx, ********
auto* unpack = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Float, word);
// 0000xxxx
return b.Access<f32>(unpack, 0_u)->Result();
}
}
case VertexFormat::kFloat16x2: {
// yyyyxxxx
auto* word = load_u32(0);
if (shader_element_type->Is<core::type::F16>()) {
// xxxx, yyyy
return b.Bitcast<vec2<f16>>(word)->Result();
} else {
// 0000xxxx, 0000yyyy
auto* unpack = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Float, word);
return unpack->Result();
}
}
case VertexFormat::kFloat16x4: {
// yyyyxxxx, wwwwzzzz
auto* word0 = load_u32(0);
auto* word1 = load_u32(1);
if (shader_element_type->Is<core::type::F16>()) {
// xxxx, yyyy, zzzz, wwww
auto* bitcast0 = b.Bitcast<vec2<f16>>(word0);
auto* bitcast1 = b.Bitcast<vec2<f16>>(word1);
return b.Construct<vec4<f16>>(bitcast0, bitcast1)->Result();
} else {
// 0000xxxx, 0000yyyy, 0000zzzz, 0000wwww
auto* unpack0 = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Float, word0);
auto* unpack1 = b.Call<vec2<f32>>(core::BuiltinFn::kUnpack2X16Float, word1);
return b.Construct<vec4<f32>>(unpack0, unpack1)->Result();
}
}
// F32 formats that can either be f16 or f32 in the shader.
// Load the f32 data and downconvert to f16 if needed.
case VertexFormat::kFloat32:
return float_value(load_f32(0));
case VertexFormat::kFloat32x2: {
auto* x = load_f32(0);
auto* y = load_f32(1);
return float_value(b.Construct<vec2<f32>>(x, y)->Result());
}
case VertexFormat::kFloat32x3: {
auto* x = load_f32(0);
auto* y = load_f32(1);
auto* z = load_f32(2);
return float_value(b.Construct<vec3<f32>>(x, y, z)->Result());
}
case VertexFormat::kFloat32x4: {
auto* x = load_f32(0);
auto* y = load_f32(1);
auto* z = load_f32(2);
auto* w = load_f32(3);
return float_value(b.Construct<vec4<f32>>(x, y, z, w)->Result());
}
// Miscellaneous other formats that need custom handling.
case VertexFormat::kUnorm10_10_10_2: {
auto* u32s = b.Construct<vec4<u32>>(load_u32(0));
// shr = u32s >> vec4u(0, 10, 20, 30);
auto* shr =
b.ShiftRight<vec4<u32>>(u32s, b.Composite<vec4<u32>>(0_u, 10_u, 20_u, 30_u));
// mask = shr & vec4u(0x3FF, 0x3FF, 0x3FF, 0x3);
auto* mask =
b.And<vec4<u32>>(shr, b.Composite<vec4<u32>>(0x3FF_u, 0x3FF_u, 0x3FF_u, 0x3_u));
// vec4f(mask) / vec4f(1023, 1023, 1023, 3);
auto* div = b.Composite<vec4<f32>>(1023_f, 1023_f, 1023_f, 3_f);
return float_value(b.Divide<vec4<f32>>(b.Convert<vec4<f32>>(mask), div)->Result());
}
}
TINT_UNREACHABLE();
}
};
} // namespace
Result<SuccessType> VertexPulling(core::ir::Module& ir, const VertexPullingConfig& config) {
auto result = ValidateAndDumpIfNeeded(ir, "core.VertexPulling", kVertexPullingCapabilities);
if (result != Success) {
return result.Failure();
}
State{config, ir}.Process();
return Success;
}
} // namespace tint::core::ir::transform