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/HashingTests.cpp

374 lines
13 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 <AzCore/Math/Random.h>
#include <AzCore/Utils/TypeHash.h>
#include <Atom/RHI.Reflect/BufferDescriptor.h>
#include <Atom/RHI.Reflect/BufferViewDescriptor.h>
#include <Atom/RHI.Reflect/ImageDescriptor.h>
#include <Atom/RHI.Reflect/ImageViewDescriptor.h>
#include <Atom/RHI.Reflect/TransientBufferDescriptor.h>
#include <Atom/RHI.Reflect/TransientImageDescriptor.h>
#include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
#include <Atom/RHI.Reflect/SamplerState.h>
#include <Atom/RHI.Reflect/InputStreamLayout.h>
#include <Atom/RHI.Reflect/ShaderStageFunction.h>
#include <Atom/RHI/PipelineStateDescriptor.h>
namespace UnitTest
{
using namespace AZ;
class ShaderStageFunction : public RHI::ShaderStageFunction
{
public:
AZ_CLASS_ALLOCATOR(ShaderStageFunction, SystemAllocator, 0);
ShaderStageFunction(uint64_t hash, RHI::ShaderStage shaderStage)
: RHI::ShaderStageFunction(shaderStage)
, m_declaredHash{hash}
{}
RHI::ResultCode FinalizeInternal() override
{
SetHash(HashValue64(m_declaredHash));
return RHI::ResultCode::Success;
}
private:
uint64_t m_declaredHash;
};
struct BaselineStructWithPadding
{
BaselineStructWithPadding()
{
m_b = 1;
m_c = reinterpret_cast<const uint32_t*>(0x12345);
}
uint8_t m_b;
// 7 bytes of padding.
const uint32_t* m_c;
HashValue64 GetHash() const
{
return TypeHash64(*this);
}
};
static_assert(sizeof(BaselineStructWithPadding) == 16, "Baseline struct does not exhibit expected padding.");
/**
* These tests validate that RHI structs which expose a 'GetHash()' method
* will produce a reliable hash. Specifically, it tests the hash under conditions
* where the underlying memory includes pad bytes which are uninitialized.
*/
class HashingTests
: public RHITestFixture
{
public:
HashingTests()
: RHITestFixture()
{
}
protected:
enum class Expect
{
Success = 0,
Failure
};
void ScrambleMemory(void* memory, size_t size)
{
uint8_t* bytes = reinterpret_cast<uint8_t*>(memory);
for (size_t i = 0; i < size; ++i)
{
bytes[i] = static_cast<uint8_t>(m_random.GetRandom());
}
}
template <typename T, Expect expect = Expect::Success>
void TestHash(AZStd::function<void(T&)> initFunction = nullptr)
{
void* memory = azmalloc(sizeof(T));
T* type = nullptr;
uint64_t hashPrev = 0;
bool isFirstHash = true;
const size_t IterationCount = 10;
for (size_t i = 0; i < IterationCount; ++i)
{
ScrambleMemory(memory, sizeof(T));
type = new (memory) T();
if (initFunction)
{
initFunction(*type);
}
uint64_t hashCurr = static_cast<uint64_t>(type->GetHash());
if (!isFirstHash)
{
if constexpr (expect == Expect::Success)
{
EXPECT_EQ(hashCurr, hashPrev);
}
else
{
EXPECT_NE(hashCurr, hashPrev);
}
}
isFirstHash = false;
hashPrev = hashCurr;
type->~T();
}
azfree(memory);
}
RHI::ConstPtr<RHI::ShaderResourceGroupLayout> CreateShaderResourceGroupLayout()
{
RHI::Ptr<RHI::ShaderResourceGroupLayout> layout = RHI::ShaderResourceGroupLayout::Create();
// The values here are just arbitrary.
layout->SetBindingSlot(0);
layout->AddShaderInput(RHI::ShaderInputConstantDescriptor{ Name("InputA"), 0, 12, 0});
layout->AddShaderInput(RHI::ShaderInputConstantDescriptor{ Name("InputB"), 12, 12, 0 });
layout->AddShaderInput(RHI::ShaderInputConstantDescriptor{ Name("InputC"), 24, 76, 0 });
layout->AddStaticSampler(RHI::ShaderInputStaticSamplerDescriptor{ Name("StaticSamplerA"), RHI::SamplerState::CreateAnisotropic(16, RHI::AddressMode::Wrap), 1 });
layout->Finalize();
return layout;
}
RHI::ShaderResourceGroupBindingInfo CreateShaderResourceGroupBindingInfo()
{
RHI::ShaderResourceGroupBindingInfo bindingInfo;
bindingInfo.m_constantDataBindingInfo.m_registerId = 0;
bindingInfo.m_resourcesRegisterMap.insert({ "StaticSamplerA", RHI::ResourceBindingInfo{RHI::ShaderStageMask::Vertex, 1} });
return bindingInfo;
}
void InitStreamLayout(RHI::InputStreamLayout& layout)
{
// The values here are just arbitrary (Except for buffer index which is validated in Finalize())
layout.SetTopology(RHI::PrimitiveTopology::TriangleList);
layout.AddStreamBuffer(RHI::StreamBufferDescriptor{ RHI::StreamStepFunction::PerVertex, 1, 8 });
layout.AddStreamBuffer(RHI::StreamBufferDescriptor{ RHI::StreamStepFunction::PerInstance, 3, 16 });
layout.AddStreamChannel(RHI::StreamChannelDescriptor{ RHI::ShaderSemantic{Name("ChannelA"), 1}, RHI::Format::R8G8B8A8_UINT, 4, 0});
layout.AddStreamChannel(RHI::StreamChannelDescriptor{ RHI::ShaderSemantic{Name("ChannelB"), 1}, RHI::Format::R10G10B10A2_UINT, 7, 1});
layout.Finalize();
}
void InitRenderAttachmentLayout(RHI::RenderAttachmentLayout& layout)
{
// The values here are just arbitrary
layout.m_subpassCount = 2;
layout.m_attachmentFormats[0] = RHI::Format::ASTC_10x6_UNORM;
layout.m_attachmentFormats[1] = RHI::Format::ASTC_8x8_UNORM;
layout.m_attachmentFormats[2] = RHI::Format::R32G32B32_FLOAT;
layout.m_attachmentFormats[3] = RHI::Format::R16G16_UINT;
{
auto& subpassLayout = layout.m_subpassLayouts[0];
subpassLayout.m_rendertargetCount = 2;
subpassLayout.m_subpassInputCount = 0;
subpassLayout.m_depthStencilDescriptor = RHI::RenderAttachmentDescriptor{ 0, RHI::InvalidRenderAttachmentIndex, RHI::AttachmentLoadStoreAction() };
subpassLayout.m_rendertargetDescriptors[0] = RHI::RenderAttachmentDescriptor{ 1, RHI::InvalidRenderAttachmentIndex, RHI::AttachmentLoadStoreAction() };
subpassLayout.m_rendertargetDescriptors[1] = RHI::RenderAttachmentDescriptor{ 2, 3, RHI::AttachmentLoadStoreAction(RHI::ClearValue(), RHI::AttachmentLoadAction::DontCare) };
}
{
auto& subpassLayout = layout.m_subpassLayouts[1];
subpassLayout.m_rendertargetCount = 1;
subpassLayout.m_subpassInputCount = 2;
subpassLayout.m_rendertargetDescriptors[0] = RHI::RenderAttachmentDescriptor{ 0, 1, RHI::AttachmentLoadStoreAction() };
subpassLayout.m_subpassInputIndices[0] = 2;
subpassLayout.m_subpassInputIndices[0] = 3;
}
}
void InitRenderAttachmentConfiguration(RHI::RenderAttachmentConfiguration& configuration)
{
InitRenderAttachmentLayout(configuration.m_renderAttachmentLayout);
configuration.m_subpassIndex = 1;
}
private:
void SetUp() override
{
RHITestFixture::SetUp();
m_factory.reset(aznew Factory);
}
void TearDown() override
{
m_factory = nullptr;
RHITestFixture::TearDown();
}
SimpleLcgRandom m_random;
AZStd::unique_ptr<Factory> m_factory;
};
TEST_F(HashingTests, BaselineTest)
{
// We expect this to fail, because padding exists in the baseline structure.
TestHash<BaselineStructWithPadding, Expect::Failure>();
}
TEST_F(HashingTests, RenderStatesTest)
{
TestHash<RHI::RenderStates>();
}
TEST_F(HashingTests, ImageTest)
{
TestHash<RHI::ImageViewDescriptor>();
TestHash<RHI::ImageDescriptor>();
TestHash<RHI::ClearValue>();
RHI::ClearValue clearValue;
TestHash<RHI::TransientImageDescriptor>([&clearValue](RHI::TransientImageDescriptor& desc)
{
desc.m_attachmentId = RHI::AttachmentId{"ABC"};
desc.m_optimizedClearValue = &clearValue;
});
}
TEST_F(HashingTests, BufferTest)
{
TestHash<RHI::BufferViewDescriptor>();
TestHash<RHI::BufferDescriptor>();
TestHash<RHI::TransientBufferDescriptor>([](RHI::TransientBufferDescriptor& desc)
{
desc.m_attachmentId = RHI::AttachmentId{"EFG"};
});
}
TEST_F(HashingTests, SamplerStateTest)
{
TestHash<RHI::SamplerState>();
}
TEST_F(HashingTests, ShaderResourceGroupLayoutTest)
{
TestHash<RHI::ShaderInputBufferDescriptor>([](RHI::ShaderInputBufferDescriptor& input)
{
input.m_name = "InputA";
});
TestHash<RHI::ShaderInputImageDescriptor>([](RHI::ShaderInputImageDescriptor& input)
{
input.m_name = "InputA";
});
TestHash<RHI::ShaderInputSamplerDescriptor>([](RHI::ShaderInputSamplerDescriptor& input)
{
input.m_name = "InputA";
});
TestHash<RHI::ShaderInputConstantDescriptor>([](RHI::ShaderInputConstantDescriptor& input)
{
input.m_name = "InputA";
});
}
TEST_F(HashingTests, StreamLayoutTest)
{
TestHash<RHI::ShaderSemantic>([](RHI::ShaderSemantic& semantic)
{
semantic.m_name = Name{"COLOR"};
semantic.m_index = 1;
});
TestHash<RHI::StreamChannelDescriptor>([](RHI::StreamChannelDescriptor& layout)
{
layout.m_semantic = RHI::ShaderSemantic{Name("UV"), 1};
});
TestHash<RHI::StreamBufferDescriptor>();
TestHash<RHI::InputStreamLayout>([this](RHI::InputStreamLayout& layout)
{
InitStreamLayout(layout);
});
}
TEST_F(HashingTests, RenderAttachmentLayoutTest)
{
TestHash<RHI::RenderAttachmentLayout>([this](RHI::RenderAttachmentLayout& layout)
{
InitRenderAttachmentLayout(layout);
});
}
TEST_F(HashingTests, RenderAttachmentConfigurationTest)
{
TestHash<RHI::RenderAttachmentConfiguration>([this](RHI::RenderAttachmentConfiguration& configuration)
{
InitRenderAttachmentConfiguration(configuration);
});
}
TEST_F(HashingTests, PipelineStateTest)
{
// These are assigned by us, so just pick something arbitrary.
const uint64_t vertFunctionHash = 0xABCDEF00;
const uint64_t fragFunctionHash = 0xABCDEF01;
RHI::Ptr<ShaderStageFunction> vertFunction = aznew ShaderStageFunction(vertFunctionHash, RHI::ShaderStage::Vertex);
vertFunction->Finalize();
RHI::Ptr<ShaderStageFunction> fragFunction = aznew ShaderStageFunction(fragFunctionHash, RHI::ShaderStage::Fragment);
fragFunction->Finalize();
RHI::InputStreamLayout inputStreamLayout;
InitStreamLayout(inputStreamLayout);
RHI::RenderAttachmentConfiguration renderAttachmentConfiguration;
InitRenderAttachmentConfiguration(renderAttachmentConfiguration);
RHI::ConstPtr<RHI::ShaderResourceGroupLayout> srgLayout = CreateShaderResourceGroupLayout();
RHI::ShaderResourceGroupBindingInfo bindingInfo = CreateShaderResourceGroupBindingInfo();
RHI::Ptr<RHI::PipelineLayoutDescriptor> pipelineLayoutDesc = RHI::PipelineLayoutDescriptor::Create();
pipelineLayoutDesc->AddShaderResourceGroupLayoutInfo(*srgLayout, bindingInfo);
pipelineLayoutDesc->Finalize();
TestHash<RHI::PipelineStateDescriptorForDraw>([&] (RHI::PipelineStateDescriptorForDraw& desc)
{
desc.m_vertexFunction = vertFunction;
desc.m_fragmentFunction = fragFunction;
desc.m_pipelineLayoutDescriptor = pipelineLayoutDesc;
desc.m_inputStreamLayout = inputStreamLayout;
desc.m_renderAttachmentConfiguration = renderAttachmentConfiguration;
});
const uint64_t computeFunctionHash = 0xABCDEF02;
RHI::Ptr<ShaderStageFunction> computeFunction = aznew ShaderStageFunction(computeFunctionHash, RHI::ShaderStage::Compute);
TestHash<RHI::PipelineStateDescriptorForDispatch>([&](RHI::PipelineStateDescriptorForDispatch& desc)
{
desc.m_pipelineLayoutDescriptor = pipelineLayoutDesc;
desc.m_computeFunction = computeFunction;
});
}
}