You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/RHI/Code/Tests/BufferTests.cpp

397 lines
15 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "RHITestFixture.h"
#include <Tests/Factory.h>
#include <Tests/Device.h>
namespace UnitTest
{
using namespace AZ;
class BufferTests
: public RHITestFixture
{
public:
BufferTests()
: RHITestFixture()
{}
void SetUp() override
{
RHITestFixture::SetUp();
m_factory.reset(aznew Factory());
}
void TearDown() override
{
m_factory.reset();
RHITestFixture::TearDown();
}
private:
AZStd::unique_ptr<Factory> m_factory;
};
TEST_F(BufferTests, TestNoop)
{
RHI::Ptr<RHI::Buffer> noopBuffer;
noopBuffer = RHI::Factory::Get().CreateBuffer();
}
TEST_F(BufferTests, Test)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::Buffer> bufferA;
bufferA = RHI::Factory::Get().CreateBuffer();
bufferA->SetName(Name("BufferA"));
AZ_TEST_ASSERT(bufferA->GetName().GetStringView() == "BufferA");
AZ_TEST_ASSERT(bufferA->use_count() == 1);
{
RHI::Ptr<RHI::BufferPool> bufferPool;
bufferPool = RHI::Factory::Get().CreateBufferPool();
AZ_TEST_ASSERT(bufferPool->use_count() == 1);
RHI::Ptr<RHI::Buffer> bufferB;
bufferB = RHI::Factory::Get().CreateBuffer();
AZ_TEST_ASSERT(bufferB->use_count() == 1);
RHI::BufferPoolDescriptor bufferPoolDesc;
bufferPoolDesc.m_bindFlags = RHI::BufferBindFlags::Constant;
bufferPool->Init(*device, bufferPoolDesc);
AZStd::vector<uint8_t> testData(32);
for (uint32_t i = 0; i < 32; ++i)
{
testData[i] = (uint8_t)i * 2;
}
AZ_TEST_ASSERT(bufferA->IsInitialized() == false);
AZ_TEST_ASSERT(bufferB->IsInitialized() == false);
RHI::BufferInitRequest initRequest;
initRequest.m_buffer = bufferA.get();
initRequest.m_descriptor = RHI::BufferDescriptor(RHI::BufferBindFlags::Constant, 32);
initRequest.m_initialData = testData.data();
bufferPool->InitBuffer(initRequest);
RHI::Ptr<RHI::BufferView> bufferView;
bufferView = bufferA->GetBufferView(RHI::BufferViewDescriptor::CreateRaw(0, 32));
AZ_TEST_ASSERT(bufferView->IsInitialized());
ASSERT_FALSE(bufferView->IsStale());
AZ_TEST_ASSERT(bufferView->IsFullView());
AZ_TEST_ASSERT(bufferA->use_count() == 2);
AZ_TEST_ASSERT(bufferA->IsInitialized());
AZ_TEST_ASSERT(static_cast<Buffer&>(*bufferA).IsMapped() == false);
initRequest.m_buffer = bufferB.get();
initRequest.m_descriptor = RHI::BufferDescriptor(RHI::BufferBindFlags::Constant, 16);
initRequest.m_initialData = testData.data() + 16;
bufferPool->InitBuffer(initRequest);
AZ_TEST_ASSERT(bufferB->IsInitialized());
AZ_TEST_ASSERT(AZStd::equal(testData.begin(), testData.end(), static_cast<Buffer&>(*bufferA).GetData().begin()));
AZ_TEST_ASSERT(AZStd::equal(testData.begin() + 16, testData.end(), static_cast<Buffer&>(*bufferB).GetData().begin()));
AZ_TEST_ASSERT(bufferA->GetPool() == bufferPool.get());
AZ_TEST_ASSERT(bufferB->GetPool() == bufferPool.get());
AZ_TEST_ASSERT(bufferPool->GetResourceCount() == 2);
{
uint32_t bufferIndex = 0;
const RHI::Buffer* buffers[] =
{
bufferA.get(),
bufferB.get()
};
bufferPool->ForEach<RHI::Buffer>([&bufferIndex, &buffers]([[maybe_unused]] RHI::Buffer& buffer)
{
AZ_UNUSED(buffers); // Prevent unused warning in release builds
AZ_Assert(buffers[bufferIndex] == &buffer, "buffers don't match");
bufferIndex++;
});
}
bufferB->Shutdown();
AZ_TEST_ASSERT(bufferB->GetPool() == nullptr);
RHI::Ptr<RHI::BufferPool> bufferPoolB;
bufferPoolB = RHI::Factory::Get().CreateBufferPool();
bufferPoolB->Init(*device, bufferPoolDesc);
initRequest.m_buffer = bufferB.get();
initRequest.m_descriptor = RHI::BufferDescriptor(RHI::BufferBindFlags::Constant, 16);
initRequest.m_initialData = testData.data() + 16;
bufferPoolB->InitBuffer(initRequest);
AZ_TEST_ASSERT(bufferB->GetPool() == bufferPoolB.get());
//Since we are switching bufferpools for bufferB it adds a refcount and invalidates the views.
//We need this to ensure the views are fully invalidated in order to release the refcount and avoid a leak.
RHI::ResourceInvalidateBus::ExecuteQueuedEvents();
bufferPoolB->Shutdown();
AZ_TEST_ASSERT(bufferPoolB->GetResourceCount() == 0);
}
AZ_TEST_ASSERT(bufferA->GetPool() == nullptr);
AZ_TEST_ASSERT(bufferA->use_count() == 1);
}
TEST_F(BufferTests, TestViews)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::BufferView> bufferViewA;
{
RHI::Ptr<RHI::BufferPool> bufferPool;
bufferPool = RHI::Factory::Get().CreateBufferPool();
RHI::BufferPoolDescriptor bufferPoolDesc;
bufferPoolDesc.m_bindFlags = RHI::BufferBindFlags::Constant;
bufferPool->Init(*device, bufferPoolDesc);
RHI::Ptr<RHI::Buffer> buffer;
buffer = RHI::Factory::Get().CreateBuffer();
RHI::BufferInitRequest initRequest;
initRequest.m_buffer = buffer.get();
initRequest.m_descriptor = RHI::BufferDescriptor(RHI::BufferBindFlags::Constant, 32);
bufferPool->InitBuffer(initRequest);
// Should report initialized and not stale.
bufferViewA = buffer->GetBufferView(RHI::BufferViewDescriptor::CreateRaw(0, 32));
AZ_TEST_ASSERT(bufferViewA->IsInitialized());
AZ_TEST_ASSERT(bufferViewA->IsStale() == false);
// Should report as still initialized and also stale.
buffer->Shutdown();
AZ_TEST_ASSERT(bufferViewA->IsInitialized());
AZ_TEST_ASSERT(bufferViewA->IsStale());
// Should *still* report as stale since resource invalidation events are queued.
bufferPool->InitBuffer(initRequest);
AZ_TEST_ASSERT(bufferViewA->IsInitialized());
AZ_TEST_ASSERT(bufferViewA->IsStale());
// This should re-initialize the views.
RHI::ResourceInvalidateBus::ExecuteQueuedEvents();
AZ_TEST_ASSERT(bufferViewA->IsInitialized());
AZ_TEST_ASSERT(bufferViewA->IsStale() == false);
// Explicit invalidation should mark it stale.
buffer->InvalidateViews();
AZ_TEST_ASSERT(bufferViewA->IsInitialized());
AZ_TEST_ASSERT(bufferViewA->IsStale());
// This should re-initialize the views.
RHI::ResourceInvalidateBus::ExecuteQueuedEvents();
AZ_TEST_ASSERT(bufferViewA->IsInitialized());
AZ_TEST_ASSERT(bufferViewA->IsStale() == false);
}
}
struct BufferAndViewBindFlags
{
RHI::BufferBindFlags bufferBindFlags;
RHI::BufferBindFlags viewBindFlags;
};
class BufferBindFlagTests
: public BufferTests
, public ::testing::WithParamInterface <BufferAndViewBindFlags>
{
public:
void SetUp() override
{
BufferTests::SetUp();
m_device = MakeTestDevice();
// Create a pool and buffer with the buffer bind flags from the parameterized test
m_bufferPool = RHI::Factory::Get().CreateBufferPool();
RHI::BufferPoolDescriptor bufferPoolDesc;
bufferPoolDesc.m_bindFlags = GetParam().bufferBindFlags;
m_bufferPool->Init(*m_device, bufferPoolDesc);
m_buffer = RHI::Factory::Get().CreateBuffer();
RHI::BufferInitRequest initRequest;
initRequest.m_buffer = m_buffer.get();
initRequest.m_descriptor = RHI::BufferDescriptor(GetParam().bufferBindFlags, 32);
m_bufferPool->InitBuffer(initRequest);
}
RHI::Ptr<RHI::Device> m_device;
RHI::Ptr<RHI::BufferPool> m_bufferPool;
RHI::Ptr<RHI::Buffer> m_buffer;
RHI::Ptr<RHI::BufferView> m_bufferView;
};
TEST_P(BufferBindFlagTests, InitView_ViewIsCreated)
{
RHI::BufferViewDescriptor bufferViewDescriptor;
bufferViewDescriptor.m_overrideBindFlags = GetParam().viewBindFlags;
m_bufferView = m_buffer->GetBufferView(bufferViewDescriptor);
EXPECT_EQ(m_bufferView.get()!=nullptr, true);
}
// This test fixture is the same as BufferBindFlagTests, but exists separately so that
// we can instantiate different test cases that are expected to fail
class BufferBindFlagFailureCases
: public BufferBindFlagTests
{
};
TEST_P(BufferBindFlagFailureCases, InitView_ViewIsNotCreated)
{
RHI::BufferViewDescriptor bufferViewDescriptor;
bufferViewDescriptor.m_overrideBindFlags = GetParam().viewBindFlags;
m_bufferView = m_buffer->GetBufferView(bufferViewDescriptor);
EXPECT_EQ(m_bufferView.get()==nullptr, true);
}
// These combinations should result in a successful creation of the buffer view
std::vector<BufferAndViewBindFlags> GenerateCompatibleBufferBindFlagCombinations()
{
std::vector<BufferAndViewBindFlags> testCases;
BufferAndViewBindFlags flags;
// When the buffer bind flags are equal to or a superset of the buffer view bind flags, the view is compatible with the buffer
flags.bufferBindFlags = RHI::BufferBindFlags::Constant;
flags.viewBindFlags = RHI::BufferBindFlags::Constant;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderReadWrite;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderRead;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderReadWrite;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderWrite;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderReadWrite;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderReadWrite;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderRead;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderRead;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderWrite;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderWrite;
testCases.push_back(flags);
// When the buffer view bind flags are None, they have no effect and should work with any bind flag used by the buffer
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderRead;
flags.viewBindFlags = RHI::BufferBindFlags::None;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderWrite;
flags.viewBindFlags = RHI::BufferBindFlags::None;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderReadWrite;
flags.viewBindFlags = RHI::BufferBindFlags::None;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::None;
flags.viewBindFlags = RHI::BufferBindFlags::None;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::Constant;
flags.viewBindFlags = RHI::BufferBindFlags::None;
testCases.push_back(flags);
return testCases;
};
// These combinations should fail during BufferView::Init
std::vector<BufferAndViewBindFlags> GenerateIncompatibleBufferBindFlagCombinations()
{
std::vector<BufferAndViewBindFlags> testCases;
BufferAndViewBindFlags flags;
flags.bufferBindFlags = RHI::BufferBindFlags::Constant;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderRead;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderRead;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderWrite;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderRead;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderReadWrite;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderWrite;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderRead;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::ShaderWrite;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderReadWrite;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::None;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderRead;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::None;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderWrite;
testCases.push_back(flags);
flags.bufferBindFlags = RHI::BufferBindFlags::None;
flags.viewBindFlags = RHI::BufferBindFlags::ShaderReadWrite;
testCases.push_back(flags);
return testCases;
}
std::string BufferBindFlagsToString(RHI::BufferBindFlags bindFlags)
{
switch (bindFlags)
{
case RHI::BufferBindFlags::None:
return "None";
case RHI::BufferBindFlags::Constant:
return "Constant";
case RHI::BufferBindFlags::ShaderRead:
return "ShaderRead";
case RHI::BufferBindFlags::ShaderWrite:
return "ShaderWrite";
case RHI::BufferBindFlags::ShaderReadWrite:
return "ShaderReadWrite";
default:
AZ_Assert(false, "No string conversion was created for this bind flag setting.")
break;
}
return "";
}
std::string GenerateBufferBindFlagTestCaseName(const ::testing::TestParamInfo<BufferAndViewBindFlags>& info)
{
return BufferBindFlagsToString(info.param.bufferBindFlags) + "BufferWith" + BufferBindFlagsToString(info.param.viewBindFlags) + "BufferView";
}
INSTANTIATE_TEST_CASE_P(BufferView, BufferBindFlagTests, ::testing::ValuesIn(GenerateCompatibleBufferBindFlagCombinations()), GenerateBufferBindFlagTestCaseName);
INSTANTIATE_TEST_CASE_P(BufferView, BufferBindFlagFailureCases, ::testing::ValuesIn(GenerateIncompatibleBufferBindFlagCombinations()), GenerateBufferBindFlagTestCaseName);
}