Tint: Add support for snorm10-10-10-2 vertex format Implements the snorm10-10-10-2 vertex format decoding in the HLSL pipeline and adds support to the vertex pulling transform with corresponding compiler unit tests. Test: tint_unittests --gtest_filter="*HlslWriterDecomposeSnorm10_10_10_2Test.*" Test: tint_unittests --gtest_filter="*MslWriter_VertexPullingTest.*" Bug: b/535283909 Change-Id: Ibfd9e0e3899e60a410c20bcd4516ef1eb6319387 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/324835 Commit-Queue: Ran Wang <wangra@google.com> Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/api/common/vertex_pulling_config.h b/src/tint/api/common/vertex_pulling_config.h index 418975a..407b02f 100644 --- a/src/tint/api/common/vertex_pulling_config.h +++ b/src/tint/api/common/vertex_pulling_config.h
@@ -78,6 +78,7 @@ kSint32x4, // sint32x4 kUnorm10_10_10_2, // unorm10-10-10-2 kUnorm8x4BGRA, // unorm8x4-bgra + kSnorm10_10_10_2, // snorm10-10-10-2 }; /// @param out the stream to write to @@ -210,6 +211,9 @@ case VertexFormat::kUnorm8x4BGRA: out << "unorm8x4BGRA"; break; + case VertexFormat::kSnorm10_10_10_2: + out << "snorm10_10_10_2"; + break; } return out; } @@ -292,7 +296,7 @@ }; /// Reflection for VertexFormat. -TINT_REFLECT_ENUM_RANGE(tint::VertexFormat, kUint8, kUnorm8x4BGRA); +TINT_REFLECT_ENUM_RANGE(tint::VertexFormat, kUint8, kSnorm10_10_10_2); /// Reflection for VertexStepMode. TINT_REFLECT_ENUM_RANGE(tint::VertexStepMode, kVertex, kInstance);
diff --git a/src/tint/lang/core/ir/transform/vertex_pulling.cc b/src/tint/lang/core/ir/transform/vertex_pulling.cc index aad27e9..1d59029 100644 --- a/src/tint/lang/core/ir/transform/vertex_pulling.cc +++ b/src/tint/lang/core/ir/transform/vertex_pulling.cc
@@ -85,6 +85,7 @@ case VertexFormat::kUint32x4: case VertexFormat::kSint32x4: case VertexFormat::kUnorm10_10_10_2: + case VertexFormat::kSnorm10_10_10_2: case VertexFormat::kUnorm8x4BGRA: return 4; } @@ -678,6 +679,17 @@ auto* div = b.Composite<vec4f>(1023_f, 1023_f, 1023_f, 3_f); return float_value(b.Divide(b.Convert<vec4f>(mask), div)->Result()); } + case VertexFormat::kSnorm10_10_10_2: { + auto* s32 = b.Bitcast<i32>(load_u32(0)); + auto* s32s = b.Construct<vec4i>(s32); + auto* shl = b.ShiftLeft(s32s, b.Composite<vec4u>(22_u, 12_u, 2_u, 0_u)); + auto* shr = b.ShiftRight(shl, b.Composite<vec4u>(22_u, 22_u, 22_u, 30_u)); + auto* div = b.Composite<vec4f>(511_f, 511_f, 511_f, 1_f); + auto* normalized = b.Divide(b.Convert<vec4f>(shr), div); + auto* clamped = + b.Call<vec4f>(core::BuiltinFn::kMax, normalized, b.Splat<vec4f>(-1_f)); + return float_value(clamped->Result()); + } } TINT_IR_UNREACHABLE(ir); }
diff --git a/src/tint/lang/core/ir/transform/vertex_pulling_test.cc b/src/tint/lang/core/ir/transform/vertex_pulling_test.cc index 35dd0f1..05d8eff 100644 --- a/src/tint/lang/core/ir/transform/vertex_pulling_test.cc +++ b/src/tint/lang/core/ir/transform/vertex_pulling_test.cc
@@ -3160,5 +3160,97 @@ EXPECT_EQ(expect, str()); } +TEST_F(MslWriter_VertexPullingTest, OneAttribute_Snorm10_10_10_2) { + auto* ep = b.Function("foo", ty.vec4f(), core::ir::Function::PipelineStage::kVertex); + auto* param = b.FunctionParam("input", ty.vec4f()); + param->SetLocation(0); + ep->AppendParam(param); + ep->SetReturnBuiltin(core::BuiltinValue::kPosition); + b.Append(ep->Block(), [&] { // + b.Return(ep, param); + }); + + auto* src = R"( +%foo = @vertex func(%input:vec4<f32> [@location(0)]):vec4<f32> [@position] { + $B1: { + ret %input + } +} +)"; + EXPECT_EQ(src, str()); + + VertexPullingConfig cfg; + cfg.pulling_group = 4u; + cfg.vertex_state = {{{4, VertexStepMode::kVertex, {{VertexFormat::kSnorm10_10_10_2, 0, 0}}}}}; + Run(VertexPulling, cfg); + + auto* expect = R"( +$B1: { # root + %tint_vertex_buffer_0:ptr<storage, array<u32>, read> = var undef @binding_point(4, 0) +} + +%foo = @vertex func(%tint_vertex_index:u32 [@vertex_index]):vec4<f32> [@position] { + $B2: { + %4:ptr<storage, u32, read> = access %tint_vertex_buffer_0, %tint_vertex_index + %5:u32 = load %4 + %6:i32 = bitcast<i32> %5 + %7:vec4<i32> = construct %6 + %8:vec4<i32> = shl %7, vec4<u32>(22u, 12u, 2u, 0u) + %9:vec4<i32> = shr %8, vec4<u32>(22u, 22u, 22u, 30u) + %10:vec4<f32> = convert %9 + %11:vec4<f32> = div %10, vec4<f32>(511.0f, 511.0f, 511.0f, 1.0f) + %12:vec4<f32> = max %11, vec4<f32>(-1.0f) + ret %12 + } +} +)"; + EXPECT_EQ(expect, str()); +} + +TEST_F(MslWriter_VertexPullingTest, OneAttribute_Unorm10_10_10_2) { + auto* ep = b.Function("foo", ty.vec4f(), core::ir::Function::PipelineStage::kVertex); + auto* param = b.FunctionParam("input", ty.vec4f()); + param->SetLocation(0); + ep->AppendParam(param); + ep->SetReturnBuiltin(core::BuiltinValue::kPosition); + b.Append(ep->Block(), [&] { // + b.Return(ep, param); + }); + + auto* src = R"( +%foo = @vertex func(%input:vec4<f32> [@location(0)]):vec4<f32> [@position] { + $B1: { + ret %input + } +} +)"; + EXPECT_EQ(src, str()); + + VertexPullingConfig cfg; + cfg.pulling_group = 4u; + cfg.vertex_state = {{{4, VertexStepMode::kVertex, {{VertexFormat::kUnorm10_10_10_2, 0, 0}}}}}; + Run(VertexPulling, cfg); + + auto* expect = R"( +$B1: { # root + %tint_vertex_buffer_0:ptr<storage, array<u32>, read> = var undef @binding_point(4, 0) +} + +%foo = @vertex func(%tint_vertex_index:u32 [@vertex_index]):vec4<f32> [@position] { + $B2: { + %4:ptr<storage, u32, read> = access %tint_vertex_buffer_0, %tint_vertex_index + %5:u32 = load %4 + %6:vec4<u32> = construct %5 + %7:vec4<u32> = shr %6, vec4<u32>(0u, 10u, 20u, 30u) + %8:vec4<u32> = and %7, vec4<u32>(1023u, 1023u, 1023u, 3u) + %9:vec4<f32> = convert %8 + %10:vec4<f32> = div %9, vec4<f32>(1023.0f, 1023.0f, 1023.0f, 3.0f) + ret %10 + } +} +)"; + EXPECT_EQ(expect, str()); +} + } // namespace } // namespace tint::core::ir::transform
diff --git a/src/tint/lang/hlsl/writer/common/options.h b/src/tint/lang/hlsl/writer/common/options.h index c829c97..a7b5661 100644 --- a/src/tint/lang/hlsl/writer/common/options.h +++ b/src/tint/lang/hlsl/writer/common/options.h
@@ -262,6 +262,9 @@ /// The binding points that will be ignored by the rebustness transform. std::vector<BindingPoint> ignored_by_robustness_transform; + /// Vertex shader locations that use the snorm10-10-10-2 format (which is emulated on D3D). + std::vector<uint32_t> snorm10_10_10_2_locations = {}; + /// Pixel local configuration PixelLocalOptions pixel_local; @@ -294,6 +297,7 @@ num_workgroups_start_offset, bindings, ignored_by_robustness_transform, + snorm10_10_10_2_locations, pixel_local, resource_table, substitute_overrides_config);
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.bazel b/src/tint/lang/hlsl/writer/raise/BUILD.bazel index a1b3083..67c18d7 100644 --- a/src/tint/lang/hlsl/writer/raise/BUILD.bazel +++ b/src/tint/lang/hlsl/writer/raise/BUILD.bazel
@@ -47,6 +47,7 @@ "array_offset_from_uniform.cc", "binary_polyfill.cc", "builtin_polyfill.cc", + "decompose_snorm10_10_10_2.cc", "decompose_storage_access.cc", "extract_ternary_values.cc", "localize_struct_array_assignment.cc", @@ -64,6 +65,7 @@ "array_offset_from_uniform.h", "binary_polyfill.h", "builtin_polyfill.h", + "decompose_snorm10_10_10_2.h", "decompose_storage_access.h", "extract_ternary_values.h", "localize_struct_array_assignment.h", @@ -113,6 +115,7 @@ "array_offset_from_uniform_test.cc", "binary_polyfill_test.cc", "builtin_polyfill_test.cc", + "decompose_snorm10_10_10_2_test.cc", "decompose_storage_access_test.cc", "extract_ternary_values_test.cc", "localize_struct_array_assignment_test.cc",
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.cmake b/src/tint/lang/hlsl/writer/raise/BUILD.cmake index 782f314..1fad0be 100644 --- a/src/tint/lang/hlsl/writer/raise/BUILD.cmake +++ b/src/tint/lang/hlsl/writer/raise/BUILD.cmake
@@ -47,6 +47,8 @@ lang/hlsl/writer/raise/binary_polyfill.h lang/hlsl/writer/raise/builtin_polyfill.cc lang/hlsl/writer/raise/builtin_polyfill.h + lang/hlsl/writer/raise/decompose_snorm10_10_10_2.cc + lang/hlsl/writer/raise/decompose_snorm10_10_10_2.h lang/hlsl/writer/raise/decompose_storage_access.cc lang/hlsl/writer/raise/decompose_storage_access.h lang/hlsl/writer/raise/extract_ternary_values.cc @@ -110,6 +112,7 @@ lang/hlsl/writer/raise/array_offset_from_uniform_test.cc lang/hlsl/writer/raise/binary_polyfill_test.cc lang/hlsl/writer/raise/builtin_polyfill_test.cc + lang/hlsl/writer/raise/decompose_snorm10_10_10_2_test.cc lang/hlsl/writer/raise/decompose_storage_access_test.cc lang/hlsl/writer/raise/extract_ternary_values_test.cc lang/hlsl/writer/raise/localize_struct_array_assignment_test.cc
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.gn b/src/tint/lang/hlsl/writer/raise/BUILD.gn index 8b62428..6aa7006 100644 --- a/src/tint/lang/hlsl/writer/raise/BUILD.gn +++ b/src/tint/lang/hlsl/writer/raise/BUILD.gn
@@ -53,6 +53,8 @@ "binary_polyfill.h", "builtin_polyfill.cc", "builtin_polyfill.h", + "decompose_snorm10_10_10_2.cc", + "decompose_snorm10_10_10_2.h", "decompose_storage_access.cc", "decompose_storage_access.h", "extract_ternary_values.cc", @@ -110,6 +112,7 @@ "array_offset_from_uniform_test.cc", "binary_polyfill_test.cc", "builtin_polyfill_test.cc", + "decompose_snorm10_10_10_2_test.cc", "decompose_storage_access_test.cc", "extract_ternary_values_test.cc", "localize_struct_array_assignment_test.cc",
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_snorm10_10_10_2.cc b/src/tint/lang/hlsl/writer/raise/decompose_snorm10_10_10_2.cc new file mode 100644 index 0000000..8ee6c8c --- /dev/null +++ b/src/tint/lang/hlsl/writer/raise/decompose_snorm10_10_10_2.cc
@@ -0,0 +1,152 @@ +// Copyright 2026 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/hlsl/writer/raise/decompose_snorm10_10_10_2.h" + +#include <algorithm> +#include <unordered_set> +#include <utility> + +#include "src/tint/lang/core/ir/builder.h" +#include "src/tint/lang/core/ir/module.h" +#include "src/tint/lang/core/ir/validator.h" +#include "src/tint/lang/core/type/struct.h" +#include "src/tint/utils/ice/ice.h" +#include "src/tint/utils/rtti/switch.h" + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::hlsl::writer::raise { +namespace { + +struct State { + core::ir::Module& ir; + const std::vector<uint32_t>& locations; + core::ir::Builder b{ir}; + core::type::Manager& ty{ir.Types()}; + + struct DecodeResult { + core::ir::InstructionResult* value = nullptr; + core::ir::Instruction* multiply_inst = nullptr; + }; + + void Process() { + std::unordered_set<uint32_t> emulated_locs(locations.begin(), locations.end()); + + for (auto* func : ir.functions) { + if (!func->IsVertex()) { + continue; + } + + for (auto* param : func->Params()) { + auto* struct_ty = param->Type()->As<core::type::Struct>(); + // Vertex shader parameters are always structs after ShaderIO + TINT_ASSERT(struct_ty); + + for (auto* member : struct_ty->Members()) { + auto member_loc = member->Attributes().location; + if (!member_loc.has_value() || !emulated_locs.contains(member_loc.value())) { + continue; + } + uint32_t member_index = member->Index(); + + param->ForEachUseUnsorted([&](core::ir::Usage u) { + auto* access = u.instruction->As<core::ir::Access>(); + if (!access || access->Indices().size() != 1) { + return; + } + auto* const_idx = access->Indices()[0]->As<core::ir::Constant>(); + if (!const_idx || const_idx->Value()->ValueAs<uint32_t>() != member_index) { + return; + } + + // Decode the access locally close to its usage to minimize register + // liveness/pressure. Duplicate access decodes will be merged later by + // Common Subexpression Elimination (CSE) and instruction deduplication + // at the downstream native shader compilers + auto* access_val = access->Result(); + b.InsertAfter(access, [&] { + auto decoded = Decode(access_val); + + // Replace all uses of the access result with the decoded result, except + // the use in the decoding logic's first instruction. We manually + // iterate a copy of the usages rather than using + // Value::ReplaceAllUsesWith(replacer) because the latter expects every + // usage to be replaced with a different value, and would infinite loop + // if we returned the original value for the decoder's first + // instruction. + auto usages = access_val->UsagesSorted(); + for (auto& use : usages) { + if (use.instruction != decoded.multiply_inst) { + use.instruction->SetOperand(use.operand_index, decoded.value); + } + } + }); + }); + } + } + } + } + + DecodeResult Decode(core::ir::Value* input) { + // Reconstruct integer values: + auto* scaled = b.Multiply(input, b.Composite<vec4f>(1023_f, 1023_f, 1023_f, 3_f)); + auto* rounded = b.Call<vec4f>(core::BuiltinFn::kRound, scaled); + + // Sign-extend: + // The shift left values (22, 22, 22, 30) here differ from vertex pulling's + // (22, 12, 2, 0) because the input components have already been unpacked into separate + // vector lanes by the hardware fetcher before reaching the shader. We only need to + // sign-extend each lane's value (10 bits for XYZ, 2 bits for W) in-place. + auto* s32s = b.Convert<vec4i>(rounded); + auto* shl = b.ShiftLeft(s32s, b.Composite<vec4u>(22_u, 22_u, 22_u, 30_u)); + auto* shr = b.ShiftRight(shl, b.Composite<vec4u>(22_u, 22_u, 22_u, 30_u)); + + // Normalize: + auto* normalized = + b.Divide(b.Convert<vec4f>(shr), b.Composite<vec4f>(511_f, 511_f, 511_f, 1_f)); + auto* decoded = b.Call<vec4f>(core::BuiltinFn::kMax, normalized, b.Splat<vec4f>(-1_f)); + + return {decoded->Result(), scaled}; + } +}; + +} // namespace + +Result<SuccessType> DecomposeSnorm10_10_10_2(core::ir::Module& ir, + const std::vector<uint32_t>& locations) { + AssertValid(ir, "before hlsl.DecomposeSnorm10_10_10_2"); + + if (!locations.empty()) { + State{ir, locations}.Process(); + } + + return Success; +} + +} // namespace tint::hlsl::writer::raise
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_snorm10_10_10_2.h b/src/tint/lang/hlsl/writer/raise/decompose_snorm10_10_10_2.h new file mode 100644 index 0000000..07f8789 --- /dev/null +++ b/src/tint/lang/hlsl/writer/raise/decompose_snorm10_10_10_2.h
@@ -0,0 +1,51 @@ +// Copyright 2026 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. + +#ifndef SRC_TINT_LANG_HLSL_WRITER_RAISE_DECOMPOSE_SNORM10_10_10_2_H_ +#define SRC_TINT_LANG_HLSL_WRITER_RAISE_DECOMPOSE_SNORM10_10_10_2_H_ + +#include <vector> + +#include "src/tint/utils/result.h" + +// Forward declarations. +namespace tint::core::ir { +class Module; +} // namespace tint::core::ir + +namespace tint::hlsl::writer::raise { + +/// DecomposeSnorm10_10_10_2 is a transform that polyfills snorm10-10-10-2 vertex inputs. +/// @param module the module to transform +/// @param locations the locations to apply the polyfill +/// @returns success or failure +Result<SuccessType> DecomposeSnorm10_10_10_2(core::ir::Module& module, + const std::vector<uint32_t>& locations); + +} // namespace tint::hlsl::writer::raise + +#endif // SRC_TINT_LANG_HLSL_WRITER_RAISE_DECOMPOSE_SNORM10_10_10_2_H_
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_snorm10_10_10_2_test.cc b/src/tint/lang/hlsl/writer/raise/decompose_snorm10_10_10_2_test.cc new file mode 100644 index 0000000..9c93bcc --- /dev/null +++ b/src/tint/lang/hlsl/writer/raise/decompose_snorm10_10_10_2_test.cc
@@ -0,0 +1,207 @@ +// Copyright 2026 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/hlsl/writer/raise/decompose_snorm10_10_10_2.h" + +#include <gtest/gtest.h> + +#include "src/tint/lang/core/fluent_types.h" +#include "src/tint/lang/core/ir/function.h" +#include "src/tint/lang/core/ir/transform/helper_test.h" +#include "src/tint/lang/core/number.h" + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +namespace tint::hlsl::writer::raise { +namespace { + +class HlslWriterDecomposeSnorm10_10_10_2Test : public core::ir::transform::TransformTest { + protected: + core::IOAttributes Location(uint32_t loc) { + core::IOAttributes attrs; + attrs.location = loc; + return attrs; + } +}; + +TEST_F(HlslWriterDecomposeSnorm10_10_10_2Test, NoLocations) { + auto* inputs = + ty.Struct(mod.symbols.New("Inputs"), { + {mod.symbols.New("pos"), ty.vec4f(), Location(0u)}, + }); + + auto* func = b.Function("foo", ty.vec4f(), core::ir::Function::PipelineStage::kVertex); + func->SetReturnBuiltin(core::BuiltinValue::kPosition); + auto* param = b.FunctionParam("input", inputs); + func->AppendParam(param); + + b.Append(func->Block(), [&] { + auto* pos_val = b.Access(ty.vec4f(), param, 0_u); + b.Return(func, pos_val); + }); + + auto* src = R"( +Inputs = struct @align(16) { + pos:vec4<f32> @offset(0), @location(0) +} + +%foo = @vertex func(%input:Inputs):vec4<f32> [@position] { + $B1: { + %3:vec4<f32> = access %input, 0u + ret %3 + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = src; + Run(DecomposeSnorm10_10_10_2, std::vector<uint32_t>{}); + + EXPECT_EQ(expect, str()); +} + +TEST_F(HlslWriterDecomposeSnorm10_10_10_2Test, DecomposeLocation0) { + auto* inputs = ty.Struct(mod.symbols.New("Inputs"), + { + {mod.symbols.New("pos"), ty.vec4f(), Location(0u)}, + {mod.symbols.New("norm"), ty.vec4f(), Location(1u)}, + }); + + auto* func = b.Function("foo", ty.vec4f(), core::ir::Function::PipelineStage::kVertex); + func->SetReturnBuiltin(core::BuiltinValue::kPosition); + auto* param = b.FunctionParam("input", inputs); + func->AppendParam(param); + + b.Append(func->Block(), [&] { + auto* pos_val = b.Access(ty.vec4f(), param, 0_u); + b.Return(func, pos_val); + }); + + auto* src = R"( +Inputs = struct @align(16) { + pos:vec4<f32> @offset(0), @location(0) + norm:vec4<f32> @offset(16), @location(1) +} + +%foo = @vertex func(%input:Inputs):vec4<f32> [@position] { + $B1: { + %3:vec4<f32> = access %input, 0u + ret %3 + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +Inputs = struct @align(16) { + pos:vec4<f32> @offset(0), @location(0) + norm:vec4<f32> @offset(16), @location(1) +} + +%foo = @vertex func(%input:Inputs):vec4<f32> [@position] { + $B1: { + %3:vec4<f32> = access %input, 0u + %4:vec4<f32> = mul %3, vec4<f32>(1023.0f, 1023.0f, 1023.0f, 3.0f) + %5:vec4<f32> = round %4 + %6:vec4<i32> = convert %5 + %7:vec4<i32> = shl %6, vec4<u32>(22u, 22u, 22u, 30u) + %8:vec4<i32> = shr %7, vec4<u32>(22u, 22u, 22u, 30u) + %9:vec4<f32> = convert %8 + %10:vec4<f32> = div %9, vec4<f32>(511.0f, 511.0f, 511.0f, 1.0f) + %11:vec4<f32> = max %10, vec4<f32>(-1.0f) + ret %11 + } +} +)"; + + Run(DecomposeSnorm10_10_10_2, std::vector<uint32_t>{0u}); + + EXPECT_EQ(expect, str()); +} + +TEST_F(HlslWriterDecomposeSnorm10_10_10_2Test, DecomposeStructMember) { + auto* inputs = ty.Struct(mod.symbols.New("Inputs"), + { + {mod.symbols.New("pos"), ty.vec4f(), Location(0u)}, + {mod.symbols.New("norm"), ty.vec4f(), Location(1u)}, + }); + + auto* func = b.Function("foo", ty.vec4f(), core::ir::Function::PipelineStage::kVertex); + func->SetReturnBuiltin(core::BuiltinValue::kPosition); + auto* param = b.FunctionParam("input", inputs); + func->AppendParam(param); + + b.Append(func->Block(), [&] { + auto* norm_val = b.Access(ty.vec4f(), param, 1_u); + b.Return(func, norm_val); + }); + + auto* src = R"( +Inputs = struct @align(16) { + pos:vec4<f32> @offset(0), @location(0) + norm:vec4<f32> @offset(16), @location(1) +} + +%foo = @vertex func(%input:Inputs):vec4<f32> [@position] { + $B1: { + %3:vec4<f32> = access %input, 1u + ret %3 + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +Inputs = struct @align(16) { + pos:vec4<f32> @offset(0), @location(0) + norm:vec4<f32> @offset(16), @location(1) +} + +%foo = @vertex func(%input:Inputs):vec4<f32> [@position] { + $B1: { + %3:vec4<f32> = access %input, 1u + %4:vec4<f32> = mul %3, vec4<f32>(1023.0f, 1023.0f, 1023.0f, 3.0f) + %5:vec4<f32> = round %4 + %6:vec4<i32> = convert %5 + %7:vec4<i32> = shl %6, vec4<u32>(22u, 22u, 22u, 30u) + %8:vec4<i32> = shr %7, vec4<u32>(22u, 22u, 22u, 30u) + %9:vec4<f32> = convert %8 + %10:vec4<f32> = div %9, vec4<f32>(511.0f, 511.0f, 511.0f, 1.0f) + %11:vec4<f32> = max %10, vec4<f32>(-1.0f) + ret %11 + } +} +)"; + + Run(DecomposeSnorm10_10_10_2, std::vector<uint32_t>{1u}); + + EXPECT_EQ(expect, str()); +} + +} // namespace +} // namespace tint::hlsl::writer::raise
diff --git a/src/tint/lang/hlsl/writer/raise/raise.cc b/src/tint/lang/hlsl/writer/raise/raise.cc index 5e7eabb..1cbe672 100644 --- a/src/tint/lang/hlsl/writer/raise/raise.cc +++ b/src/tint/lang/hlsl/writer/raise/raise.cc
@@ -66,6 +66,7 @@ #include "src/tint/lang/hlsl/writer/raise/array_offset_from_uniform.h" #include "src/tint/lang/hlsl/writer/raise/binary_polyfill.h" #include "src/tint/lang/hlsl/writer/raise/builtin_polyfill.h" +#include "src/tint/lang/hlsl/writer/raise/decompose_snorm10_10_10_2.h" #include "src/tint/lang/hlsl/writer/raise/decompose_storage_access.h" #include "src/tint/lang/hlsl/writer/raise/extract_ternary_values.h" #include "src/tint/lang/hlsl/writer/raise/localize_struct_array_assignment.h" @@ -290,6 +291,8 @@ TINT_CHECK_RESULT(raise::ShaderIO(module, config)); } + TINT_CHECK_RESULT(raise::DecomposeSnorm10_10_10_2(module, options.snorm10_10_10_2_locations)); + // DemoteToHelper must come before any transform that introduces non-core instructions. // Run after ShaderIO to ensure the discards are added to the entry point it introduces. // TODO(crbug.com/42250787): This is only necessary when FXC is being used.