//===------ HLSLBindingTest.cpp - Resource binding tests ------------------===// // // 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/Frontend/HLSL/HLSLBinding.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/DXILABI.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using namespace llvm; using namespace llvm::dxil; MATCHER_P(HasSpecificValue, Value, "") { return arg.has_value() && *arg == Value; } static void checkExpectedSpaceAndFreeRanges(hlsl::BindingInfo::RegisterSpace &RegSpace, uint32_t ExpSpace, ArrayRef ExpValues) { EXPECT_EQ(RegSpace.Space, ExpSpace); EXPECT_EQ(RegSpace.FreeRanges.size() * 2, ExpValues.size()); unsigned I = 0; for (auto &R : RegSpace.FreeRanges) { EXPECT_EQ(R.LowerBound, ExpValues[I]); EXPECT_EQ(R.UpperBound, ExpValues[I + 1]); I += 2; } } TEST(HLSLBindingTest, TestTrivialCase) { hlsl::BindingInfoBuilder Builder; Builder.trackBinding(ResourceClass::UAV, /*Space=*/0, /*LowerBound=*/5, /*UpperBound=*/5, /*Cookie=*/nullptr); bool HasOverlap; hlsl::BindingInfo Info = Builder.calculateBindingInfo(HasOverlap); EXPECT_FALSE(HasOverlap); // check that UAV has exactly one gap hlsl::BindingInfo::BindingSpaces &UAVSpaces = Info.getBindingSpaces(ResourceClass::UAV); EXPECT_EQ(UAVSpaces.RC, ResourceClass::UAV); EXPECT_EQ(UAVSpaces.Spaces.size(), 1u); checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[0], 0, {0u, 4u, 6u, ~0u}); // check that other kinds of register spaces are all available for (auto RC : {ResourceClass::SRV, ResourceClass::CBuffer, ResourceClass::Sampler}) { hlsl::BindingInfo::BindingSpaces &Spaces = Info.getBindingSpaces(RC); EXPECT_EQ(Spaces.RC, RC); EXPECT_EQ(Spaces.Spaces.size(), 0u); } } TEST(HLSLBindingTest, TestManyBindings) { hlsl::BindingInfoBuilder Builder; // cbuffer CB : register(b3) { int a; } // RWBuffer A[5] : register(u10, space20); // StructuredBuffer B : register(t5); // RWBuffer C : register(u5); // StructuredBuffer D[5] : register(t0); // RWBuffer E[2] : register(u2); // SamplerState S1 : register(s5, space2); // SamplerState S2 : register(s4, space2); Builder.trackBinding(ResourceClass::CBuffer, /*Space=*/0, /*LowerBound=*/3, /*UpperBound=*/3, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::UAV, /*Space=*/20, /*LowerBound=*/10, /*UpperBound=*/14, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::SRV, /*Space=*/0, /*LowerBound=*/5, /*UpperBound=*/5, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::UAV, /*Space=*/0, /*LowerBound=*/5, /*UpperBound=*/5, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::SRV, /*Space=*/0, /*LowerBound=*/0, /*UpperBound=*/4, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::UAV, /*Space=*/0, /*LowerBound=*/2, /*UpperBound=*/3, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::Sampler, /*Space=*/2, /*LowerBound=*/5, /*UpperBound=*/5, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::Sampler, /*Space=*/2, /*LowerBound=*/4, /*UpperBound=*/4, /*Cookie=*/nullptr); bool HasOverlap; hlsl::BindingInfo Info = Builder.calculateBindingInfo(HasOverlap); EXPECT_FALSE(HasOverlap); hlsl::BindingInfo::BindingSpaces &SRVSpaces = Info.getBindingSpaces(ResourceClass::SRV); EXPECT_EQ(SRVSpaces.RC, ResourceClass::SRV); EXPECT_EQ(SRVSpaces.Spaces.size(), 1u); // verify that consecutive bindings are merged // (SRVSpaces has only one free space range {6, ~0u}). checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[0], 0, {6u, ~0u}); hlsl::BindingInfo::BindingSpaces &UAVSpaces = Info.getBindingSpaces(ResourceClass::UAV); EXPECT_EQ(UAVSpaces.RC, ResourceClass::UAV); EXPECT_EQ(UAVSpaces.Spaces.size(), 2u); checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[0], 0, {0u, 1u, 4u, 4u, 6u, ~0u}); checkExpectedSpaceAndFreeRanges(UAVSpaces.Spaces[1], 20, {0u, 9u, 15u, ~0u}); hlsl::BindingInfo::BindingSpaces &CBufferSpaces = Info.getBindingSpaces(ResourceClass::CBuffer); EXPECT_EQ(CBufferSpaces.RC, ResourceClass::CBuffer); EXPECT_EQ(CBufferSpaces.Spaces.size(), 1u); checkExpectedSpaceAndFreeRanges(CBufferSpaces.Spaces[0], 0, {0u, 2u, 4u, ~0u}); hlsl::BindingInfo::BindingSpaces &SamplerSpaces = Info.getBindingSpaces(ResourceClass::Sampler); EXPECT_EQ(SamplerSpaces.RC, ResourceClass::Sampler); EXPECT_EQ(SamplerSpaces.Spaces.size(), 1u); checkExpectedSpaceAndFreeRanges(SamplerSpaces.Spaces[0], 2, {0u, 3u, 6u, ~0u}); } TEST(HLSLBindingTest, TestUnboundedAndOverlap) { hlsl::BindingInfoBuilder Builder; // StructuredBuffer A[] : register(t5); // StructuredBuffer B[3] : register(t0); // StructuredBuffer C[] : register(t0, space2); // StructuredBuffer D : register(t4, space2); /* overlapping */ Builder.trackBinding(ResourceClass::SRV, /*Space=*/0, /*LowerBound=*/5, /*UpperBound=*/~0u, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::SRV, /*Space=*/0, /*LowerBound=*/0, /*UpperBound=*/2, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::SRV, /*Space=*/2, /*LowerBound=*/0, /*UpperBound=*/~0u, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::SRV, /*Space=*/2, /*LowerBound=*/4, /*UpperBound=*/4, /*Cookie=*/nullptr); bool HasOverlap; hlsl::BindingInfo Info = Builder.calculateBindingInfo(HasOverlap); EXPECT_TRUE(HasOverlap); hlsl::BindingInfo::BindingSpaces &SRVSpaces = Info.getBindingSpaces(ResourceClass::SRV); EXPECT_EQ(SRVSpaces.RC, ResourceClass::SRV); EXPECT_EQ(SRVSpaces.Spaces.size(), 2u); checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[0], 0, {3, 4}); checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[1], 2, {}); } TEST(HLSLBindingTest, TestExactOverlap) { hlsl::BindingInfoBuilder Builder; // Since the bindings overlap exactly we need sigil values to differentiate // them. // Note: We initialize these to 0 to suppress a -Wuninitialized-const-pointer, // but we really are just using the stack addresses here. char ID1 = 0; char ID2 = 0; // StructuredBuffer A : register(t5); // StructuredBuffer B : register(t5); Builder.trackBinding(ResourceClass::SRV, /*Space=*/0, /*LowerBound=*/5, /*UpperBound=*/5, /*Cookie=*/&ID1); Builder.trackBinding(ResourceClass::SRV, /*Space=*/0, /*LowerBound=*/5, /*UpperBound=*/5, /*Cookie=*/&ID2); bool HasOverlap; hlsl::BindingInfo Info = Builder.calculateBindingInfo(HasOverlap); EXPECT_TRUE(HasOverlap); hlsl::BindingInfo::BindingSpaces &SRVSpaces = Info.getBindingSpaces(ResourceClass::SRV); EXPECT_EQ(SRVSpaces.RC, ResourceClass::SRV); EXPECT_EQ(SRVSpaces.Spaces.size(), 1u); checkExpectedSpaceAndFreeRanges(SRVSpaces.Spaces[0], 0, {0u, 4u, 6u, ~0u}); } TEST(HLSLBindingTest, TestEndOfRange) { hlsl::BindingInfoBuilder Builder; // RWBuffer A : register(u4294967295); /* UINT32_MAX */ // RWBuffer B[10] : register(u4294967286, space1); // /* range (UINT32_MAX - 9, UINT32_MAX )*/ // RWBuffer C[10] : register(u2147483647, space2); // /* range (INT32_MAX, INT32_MAX + 9) */ Builder.trackBinding(ResourceClass::UAV, /*Space=*/0, /*LowerBound=*/~0u, /*UpperBound=*/~0u, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::UAV, /*Space=*/1, /*LowerBound=*/~0u - 9u, /*UpperBound=*/~0u, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::UAV, /*Space=*/2, /*LowerBound=*/2147483647u, /*UpperBound=*/2147483647u + 9u, /*Cookie=*/nullptr); bool HasOverlap; hlsl::BindingInfo Info = Builder.calculateBindingInfo(HasOverlap); EXPECT_FALSE(HasOverlap); hlsl::BindingInfo::BindingSpaces &UAVSpaces = Info.getBindingSpaces(ResourceClass::UAV); EXPECT_EQ(UAVSpaces.RC, ResourceClass::UAV); EXPECT_EQ(UAVSpaces.Spaces.size(), 3u); checkExpectedSpaceAndFreeRanges( UAVSpaces.Spaces[0], 0, {0, std::numeric_limits::max() - 1}); checkExpectedSpaceAndFreeRanges( UAVSpaces.Spaces[1], 1, {0, std::numeric_limits::max() - 10}); checkExpectedSpaceAndFreeRanges( UAVSpaces.Spaces[2], 2, {0, static_cast(std::numeric_limits::max()) - 1u, static_cast(std::numeric_limits::max()) + 10u, std::numeric_limits::max()}); } TEST(HLSLBindingTest, TestFindAvailable) { hlsl::BindingInfoBuilder Builder; // RWBuffer A : register(u5); // RWBuffer B : register(u5, space1); // RWBuffer C : register(u11, space1); // RWBuffer D[] : register(u1, space2); Builder.trackBinding(ResourceClass::UAV, /*Space=*/0, /*LowerBound=*/5u, /*UpperBound=*/5u, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::UAV, /*Space=*/1, /*LowerBound=*/2u, /*UpperBound=*/2u, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::UAV, /*Space=*/1, /*LowerBound=*/6u, /*UpperBound=*/6u, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::UAV, /*Space=*/2, /*LowerBound=*/1u, /*UpperBound=*/~0u, /*Cookie=*/nullptr); Builder.trackBinding(ResourceClass::UAV, /*Space=*/3, /*LowerBound=*/~0u - 1, /*UpperBound=*/~0u - 1, /*Cookie=*/nullptr); bool HasOverlap; hlsl::BindingInfo Info = Builder.calculateBindingInfo(HasOverlap); EXPECT_FALSE(HasOverlap); // In space 0, we find room for a small binding at the beginning and // a large binding after `A`'s binding. std::optional V = Info.findAvailableBinding(ResourceClass::UAV, /*Space=*/0, /*Size=*/1); EXPECT_THAT(V, HasSpecificValue(0u)); V = Info.findAvailableBinding(ResourceClass::UAV, /*Space=*/0, /*Size=*/100); EXPECT_THAT(V, HasSpecificValue(6u)); // In space 1, we try to fit larger bindings in the fill the gaps. Note that // we do this largest to smallest and observe that the gaps that are earlier // still exist. V = Info.findAvailableBinding(ResourceClass::UAV, /*Space=*/1, /*Size=*/4); EXPECT_THAT(V, HasSpecificValue(7u)); V = Info.findAvailableBinding(ResourceClass::UAV, /*Space=*/1, /*Size=*/3); EXPECT_THAT(V, HasSpecificValue(3u)); V = Info.findAvailableBinding(ResourceClass::UAV, /*Space=*/1, /*Size=*/2); EXPECT_THAT(V, HasSpecificValue(0u)); // At this point, we've used all of the contiguous space up to 11u V = Info.findAvailableBinding(ResourceClass::UAV, /*Space=*/1, /*Size=*/1); EXPECT_THAT(V, HasSpecificValue(11u)); // Space 2 is mostly full, we can only fit into the room at the beginning. V = Info.findAvailableBinding(ResourceClass::UAV, /*Space=*/2, /*Size=*/2); EXPECT_FALSE(V.has_value()); V = Info.findAvailableBinding(ResourceClass::UAV, /*Space=*/2, /*Size=*/1); EXPECT_THAT(V, HasSpecificValue(0u)); // Finding space for an unbounded array is a bit funnier. it always needs to // go a the end of the available space. V = Info.findAvailableBinding(ResourceClass::UAV, /*Space=*/3, /*Size=*/~0u); // Note that we end up with a size 1 array here, starting at ~0u. EXPECT_THAT(V, HasSpecificValue(~0u)); V = Info.findAvailableBinding(ResourceClass::UAV, /*Space=*/4, /*Size=*/~0u); // In an empty space we find the slot at the beginning. EXPECT_THAT(V, HasSpecificValue(0u)); }