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.
374 lines
13 KiB
C++
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;
|
|
});
|
|
}
|
|
}
|