| //===- HLSLSemanticSignaturePackingTest.cpp -------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/Frontend/HLSL/SemanticSignaturePacking.h" |
| #include "llvm/TargetParser/Triple.h" |
| #include "llvm/Testing/Support/Error.h" |
| #include "gtest/gtest.h" |
| #include <initializer_list> |
| #include <string> |
| |
| using namespace llvm; |
| using namespace llvm::hlsl; |
| |
| namespace { |
| |
| class HLSLSemanticSignaturePackingTest : public testing::Test { |
| protected: |
| struct ElementConfig { |
| dxbc::PSV::SemanticKind SemanticKind; |
| uint32_t Rows; |
| uint8_t Cols; |
| dxil::ElementType CompType; |
| dxbc::PSV::InterpolationMode InterpMode; |
| uint32_t SemanticIndex = 0; |
| }; |
| |
| struct ExpectedLocation { |
| uint32_t Row; |
| uint8_t Col; |
| }; |
| |
| static constexpr ExpectedLocation Unallocated = {UnallocatedRow, |
| UnallocatedCol}; |
| |
| struct TestConfig { |
| Triple::EnvironmentType ShaderStage; |
| IOType IOTy; |
| SmallVector<ElementConfig> Elements; |
| |
| TestConfig(Triple::EnvironmentType ShaderStage, IOType IOTy, |
| std::initializer_list<ElementConfig> Elements) |
| : ShaderStage(ShaderStage), IOTy(IOTy), Elements(Elements) {} |
| }; |
| |
| enum class PackingMethod { |
| Stacked, |
| Indexed, |
| }; |
| |
| SmallVector<SemanticSignatureElement> |
| makeSignature(const TestConfig &Config) { |
| SmallVector<SemanticSignatureElement> Elements; |
| for (const ElementConfig &Element : Config.Elements) { |
| SmallVector<uint32_t> SemanticIndices; |
| for (uint32_t Row = 0; Row != Element.Rows; ++Row) |
| SemanticIndices.push_back(Element.SemanticIndex + Row); |
| |
| Elements.emplace_back( |
| /*SigId=*/static_cast<uint32_t>(Elements.size()), |
| /*SemanticName=*/"TEST", |
| /*CompType=*/Element.CompType, |
| /*SemanticKind=*/Element.SemanticKind, |
| /*SemanticIndices=*/SemanticIndices, |
| /*Cols=*/Element.Cols); |
| Elements.back().InterpMode = Element.InterpMode; |
| } |
| return Elements; |
| } |
| |
| Expected<unsigned> pack(PackingMethod Method, |
| SmallVectorImpl<SemanticSignatureElement> &Elements, |
| const TestConfig &Config) { |
| switch (Method) { |
| case PackingMethod::Stacked: |
| return packSignatureStacked(Elements, Config.ShaderStage, Config.IOTy); |
| case PackingMethod::Indexed: |
| return packSignatureIndexed(Elements, Config.ShaderStage, Config.IOTy); |
| } |
| llvm_unreachable("invalid packing method"); |
| } |
| |
| void verifyPacking(PackingMethod Method, const TestConfig &Config, |
| unsigned ExpectedRows, |
| std::initializer_list<ExpectedLocation> Locations) { |
| SmallVector<SemanticSignatureElement> Elements = makeSignature(Config); |
| ASSERT_EQ(Elements.size(), Locations.size()); |
| |
| Expected<unsigned> Rows = pack(Method, Elements, Config); |
| ASSERT_THAT_EXPECTED(Rows, Succeeded()); |
| EXPECT_EQ(*Rows, ExpectedRows); |
| |
| unsigned Index = 0; |
| for (ExpectedLocation Location : Locations) { |
| EXPECT_EQ(Elements[Index].StartRow, Location.Row) << "element " << Index; |
| EXPECT_EQ(Elements[Index].StartCol, Location.Col) << "element " << Index; |
| ++Index; |
| } |
| } |
| |
| void verifyPackingError(PackingMethod Method, const TestConfig &Config, |
| SignaturePackingError::ErrorKind ExpectedKind, |
| unsigned ExpectedElementIndex) { |
| SmallVector<SemanticSignatureElement> Elements = makeSignature(Config); |
| Expected<unsigned> Rows = pack(Method, Elements, Config); |
| if (Rows) { |
| ADD_FAILURE() << "expected a SignaturePackingError"; |
| return; |
| } |
| handleAllErrors( |
| Rows.takeError(), |
| [&](const SignaturePackingError &PackingErr) { |
| EXPECT_EQ(PackingErr.getErrorKind(), ExpectedKind); |
| EXPECT_EQ(PackingErr.getElementIndex(), ExpectedElementIndex); |
| }, |
| [](const ErrorInfoBase &Other) { |
| ADD_FAILURE() << "expected a SignaturePackingError, got: " |
| << Other.message(); |
| }); |
| } |
| }; |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, CreatesSignatureFromConfig) { |
| TestConfig Config( |
| Triple::EnvironmentType::Vertex, IOType::Out, |
| {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}, |
| {dxbc::PSV::SemanticKind::Position, /*Rows=*/2, /*Cols=*/3, |
| dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Constant}}); |
| |
| EXPECT_EQ(Config.ShaderStage, Triple::EnvironmentType::Vertex); |
| EXPECT_EQ(Config.IOTy, IOType::Out); |
| |
| SmallVector<SemanticSignatureElement> Elements = makeSignature(Config); |
| ASSERT_EQ(Elements.size(), 2u); |
| |
| EXPECT_EQ(Elements[0].SigId, 0u); |
| EXPECT_EQ(Elements[0].SemanticName, "TEST"); |
| EXPECT_EQ(Elements[0].CompType, dxil::ElementType::F32); |
| EXPECT_EQ(Elements[0].SemanticKind, dxbc::PSV::SemanticKind::Arbitrary); |
| EXPECT_EQ(Elements[0].SemanticIndices, SmallVector<uint32_t>({0})); |
| EXPECT_EQ(Elements[0].InterpMode, dxbc::PSV::InterpolationMode::Linear); |
| EXPECT_EQ(Elements[0].Rows, 1u); |
| EXPECT_EQ(Elements[0].Cols, 2u); |
| EXPECT_EQ(Elements[0].StartRow, UnallocatedRow); |
| EXPECT_EQ(Elements[0].StartCol, UnallocatedCol); |
| EXPECT_EQ(Elements[0].UsageMask, 0u); |
| EXPECT_EQ(Elements[0].DynIndexMask, 0u); |
| EXPECT_EQ(Elements[0].GSStream, 0u); |
| |
| EXPECT_EQ(Elements[1].SigId, 1u); |
| EXPECT_EQ(Elements[1].SemanticKind, dxbc::PSV::SemanticKind::Position); |
| EXPECT_EQ(Elements[1].CompType, dxil::ElementType::F16); |
| EXPECT_EQ(Elements[1].InterpMode, dxbc::PSV::InterpolationMode::Constant); |
| EXPECT_EQ(Elements[1].SemanticIndices, SmallVector<uint32_t>({0, 1})); |
| EXPECT_EQ(Elements[1].Rows, 2u); |
| EXPECT_EQ(Elements[1].Cols, 3u); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Valid packing tests |
| //===----------------------------------------------------------------------===// |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, SkipsNotAllocatedElements) { |
| // Semantics accessed through dedicated intrinsics do not consume signature |
| // rows and remain unallocated. The elements around them are packed as if the |
| // unallocated element was not declared at all. |
| |
| // struct VSIn { |
| // float2 A : A; |
| // uint ViewID : SV_ViewID; |
| // float3 B : B; |
| // }; |
| TestConfig Config( |
| Triple::EnvironmentType::Vertex, IOType::In, |
| {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}, |
| {dxbc::PSV::SemanticKind::ViewID, /*Rows=*/1, /*Cols=*/1, |
| dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Undefined}, |
| {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}}); |
| |
| // Expected layout: |
| // reg0: A.xy | unused.zw |
| // reg1: B.xyz | unused.w |
| verifyPacking(PackingMethod::Stacked, Config, /*ExpectedRows=*/2, |
| {{/*Row=*/0, /*Col=*/0}, Unallocated, {/*Row=*/1, /*Col=*/0}}); |
| } |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, StacksInDeclarationOrder) { |
| // Elements are assigned whole rows in declaration order, regardless of their |
| // semantic interpretation. |
| |
| // struct VSIn { |
| // uint VertexID : SV_VertexID; |
| // float2 Data : DATA; |
| // float3 ClipDistance : SV_ClipDistance; |
| // }; |
| TestConfig Config( |
| Triple::EnvironmentType::Vertex, IOType::In, |
| {{dxbc::PSV::SemanticKind::VertexID, /*Rows=*/1, /*Cols=*/1, |
| dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Constant}, |
| {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}, |
| {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}}); |
| |
| // Expected layout: |
| // reg0: VertexID.x | unused.yzw |
| // reg1: Data.xy | unused.zw |
| // reg2: ClipDistance.xyz | unused.w |
| verifyPacking( |
| PackingMethod::Stacked, Config, /*ExpectedRows=*/3, |
| {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}}); |
| } |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, DoesNotCoPackElements) { |
| // Elements are never co-packed even when they would fit in one row. |
| |
| // struct VSIn { |
| // float A : A; |
| // float B : B; |
| // float C : C; |
| // float D : D; |
| // }; |
| TestConfig Config( |
| Triple::EnvironmentType::Vertex, IOType::In, |
| {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}, |
| {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}, |
| {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}, |
| {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}}); |
| |
| // Expected layout: |
| // reg0: A.x | unused.yzw |
| // reg1: B.x | unused.yzw |
| // reg2: C.x | unused.yzw |
| // reg3: D.x | unused.yzw |
| verifyPacking(PackingMethod::Stacked, Config, /*ExpectedRows=*/4, |
| {{/*Row=*/0, /*Col=*/0}, |
| {/*Row=*/1, /*Col=*/0}, |
| {/*Row=*/2, /*Col=*/0}, |
| {/*Row=*/3, /*Col=*/0}}); |
| } |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, StacksMultiRowElements) { |
| // A multi-row element occupies consecutive whole rows. |
| |
| // struct VSIn { |
| // float A[3] : A; |
| // float3 B[2] : B; |
| // float4 C : C; |
| // }; |
| TestConfig Config( |
| Triple::EnvironmentType::Vertex, IOType::In, |
| {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/3, /*Cols=*/1, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}, |
| {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, /*Cols=*/3, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}, |
| {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/4, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}}); |
| |
| // Expected layout: |
| // reg0: A[0].x | unused.yzw |
| // reg1: A[1].x | unused.yzw |
| // reg2: A[2].x | unused.yzw |
| // reg3: B[0].xyz | unused.w |
| // reg4: B[1].xyz | unused.w |
| // reg5: C.xyzw |
| verifyPacking( |
| PackingMethod::Stacked, Config, /*ExpectedRows=*/6, |
| {{/*Row=*/0, /*Col=*/0}, {/*Row=*/3, /*Col=*/0}, {/*Row=*/5, /*Col=*/0}}); |
| } |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, ExactlyFillsSignature) { |
| // An element may occupy all available signature rows. |
| |
| // struct VSIn { |
| // float4 A[32] : A; |
| // }; |
| TestConfig Config( |
| Triple::EnvironmentType::Vertex, IOType::In, |
| {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/MaxSignatureRows, |
| /*Cols=*/MaxSignatureCols, dxil::ElementType::F32, |
| dxbc::PSV::InterpolationMode::Linear}}); |
| |
| // Expected layout: |
| // reg0-31: A[0-31].xyzw |
| verifyPacking(PackingMethod::Stacked, Config, |
| /*ExpectedRows=*/MaxSignatureRows, {{/*Row=*/0, /*Col=*/0}}); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Packing error tests |
| //===----------------------------------------------------------------------===// |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, RejectsSignatureOverflow) { |
| // A signature that requires more than 32 rows cannot be packed. |
| |
| // struct VSIn { |
| // float4 A0 : A0; |
| // ... |
| // float4 A32 : A32; |
| // }; |
| TestConfig Config(Triple::EnvironmentType::Vertex, IOType::In, {}); |
| for (unsigned I = 0; I != MaxSignatureRows + 1; ++I) |
| Config.Elements.push_back({dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, |
| /*Cols=*/MaxSignatureCols, |
| dxil::ElementType::F32, |
| dxbc::PSV::InterpolationMode::Linear}); |
| |
| // The last element is the one that no longer fits. |
| verifyPackingError(PackingMethod::Stacked, Config, |
| SignaturePackingError::SignatureOverflow, |
| /*ExpectedElementIndex=*/MaxSignatureRows); |
| } |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, RejectsSingleElementOverflow) { |
| // A single element may also require more rows than the signature provides. |
| |
| // struct VSIn { |
| // float4 A[33] : A; |
| // }; |
| TestConfig Config(Triple::EnvironmentType::Vertex, IOType::In, |
| {{dxbc::PSV::SemanticKind::Arbitrary, |
| /*Rows=*/MaxSignatureRows + 1, |
| /*Cols=*/MaxSignatureCols, dxil::ElementType::F32, |
| dxbc::PSV::InterpolationMode::Linear}}); |
| |
| verifyPackingError(PackingMethod::Stacked, Config, |
| SignaturePackingError::SignatureOverflow, |
| /*ExpectedElementIndex=*/0); |
| } |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, RejectsMultiRowSignatureOverflow) { |
| // Each element is valid on its own, but together they require 33 rows. |
| |
| // struct VSIn { |
| // float4 A[31] : A; |
| // float4 B[2] : B; |
| // }; |
| TestConfig Config(Triple::EnvironmentType::Vertex, IOType::In, |
| {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/31, |
| /*Cols=*/MaxSignatureCols, dxil::ElementType::F32, |
| dxbc::PSV::InterpolationMode::Linear}, |
| {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, |
| /*Cols=*/MaxSignatureCols, dxil::ElementType::F32, |
| dxbc::PSV::InterpolationMode::Linear}}); |
| |
| verifyPackingError(PackingMethod::Stacked, Config, |
| SignaturePackingError::SignatureOverflow, |
| /*ExpectedElementIndex=*/1); |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Indexed packing tests |
| //===----------------------------------------------------------------------===// |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, IndexedEmptySignature) { |
| TestConfig Config(Triple::EnvironmentType::Pixel, IOType::Out, {}); |
| |
| verifyPacking(PackingMethod::Indexed, Config, /*ExpectedRows=*/0, {}); |
| } |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, IndexedUsesLastSignatureRow) { |
| // The row extent includes the unused rows before the target's semantic index. |
| TestConfig Config( |
| Triple::EnvironmentType::Pixel, IOType::Out, |
| {{dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/4, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined, |
| /*SemanticIndex=*/MaxSignatureRows - 1}}); |
| |
| verifyPacking(PackingMethod::Indexed, Config, |
| /*ExpectedRows=*/MaxSignatureRows, |
| {{/*Row=*/MaxSignatureRows - 1, /*Col=*/0}}); |
| } |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, IndexedUsesSemanticIndices) { |
| // Target elements are assigned the row denoted by their semantic index, not |
| // their declaration order. Every target starts at column zero. |
| |
| // struct PSOut { |
| // float4 Color3 : SV_Target3; |
| // float Color0 : SV_Target0; |
| // float2 Color2 : SV_Target2; |
| // }; |
| TestConfig Config( |
| Triple::EnvironmentType::Pixel, IOType::Out, |
| {{dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/4, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined, |
| /*SemanticIndex=*/3}, |
| {dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/1, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined, |
| /*SemanticIndex=*/0}, |
| {dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/2, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined, |
| /*SemanticIndex=*/2}}); |
| |
| // Expected layout: |
| // reg0: Color0.x | unused.yzw |
| // reg1: unused.xyzw |
| // reg2: Color2.xy | unused.zw |
| // reg3: Color3.xyzw |
| verifyPacking( |
| PackingMethod::Indexed, Config, /*ExpectedRows=*/4, |
| {{/*Row=*/3, /*Col=*/0}, {/*Row=*/0, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}}); |
| } |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, IndexedLeavesSemanticIndexGaps) { |
| // Rows without a corresponding target semantic remain unused. |
| |
| // struct PSOut { |
| // float4 Color1 : SV_Target1; |
| // float4 Color7 : SV_Target7; |
| // }; |
| TestConfig Config( |
| Triple::EnvironmentType::Pixel, IOType::Out, |
| {{dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/4, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined, |
| /*SemanticIndex=*/1}, |
| {dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/4, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined, |
| /*SemanticIndex=*/7}}); |
| |
| // Expected layout: |
| // reg0: unused.xyzw |
| // reg1: Color1.xyzw |
| // reg2-6: unused.xyzw |
| // reg7: Color7.xyzw |
| verifyPacking(PackingMethod::Indexed, Config, /*ExpectedRows=*/8, |
| {{/*Row=*/1, /*Col=*/0}, {/*Row=*/7, /*Col=*/0}}); |
| } |
| |
| TEST_F(HLSLSemanticSignaturePackingTest, |
| IndexedRejectsOutOfRangeSemanticIndex) { |
| // A semantic index outside the 32-row signature cannot be allocated. |
| |
| // struct PSOut { |
| // float4 Color32 : SV_Target32; |
| // }; |
| TestConfig Config( |
| Triple::EnvironmentType::Pixel, IOType::Out, |
| {{dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/4, |
| dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined, |
| /*SemanticIndex=*/MaxSignatureRows}}); |
| |
| verifyPackingError(PackingMethod::Indexed, Config, |
| SignaturePackingError::SemanticIndexOutOfRange, |
| /*ExpectedElementIndex=*/0); |
| |
| SmallVector<SemanticSignatureElement> Elements = makeSignature(Config); |
| EXPECT_THAT_EXPECTED(pack(PackingMethod::Indexed, Elements, Config), |
| FailedWithMessage("semantic index must be less than " + |
| std::to_string(MaxSignatureRows) + |
| " (element 0)")); |
| } |
| |
| } // namespace |