Files
o3de/Gems/Atom/RHI/Code/Tests/PipelineStateTests.cpp
T
Esteban Papp 1f9b284de2 Merge branch 'development' into cmake/SPEC-7179
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>

# Conflicts:
#	Code/Editor/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin_precompiled.h
#	Code/Editor/Plugins/EditorCommon/EditorCommon_precompiled.h
#	Code/Editor/Plugins/EditorCommon/stdafx.cpp
#	Code/Editor/Plugins/FFMPEGPlugin/FFMPEGPlugin_precompiled.h
#	Code/Editor/Plugins/PerforcePlugin/PerforcePlugin_precompiled.h
#	Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsTool_precompiled.h
#	Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFramework_precompiled.h
#	Code/Tools/AssetProcessor/native/precompiled.h
#	Code/Tools/Standalone/StandaloneTools_precompiled.h
#	Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer_precompiled.h
#	Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessing_precompiled.h
#	Gems/Atom/RHI/DX12/Code/Source/RHI/Atom_RHI_DX12_precompiled.h
#	Gems/Atom/RHI/Metal/Code/Include/Platform/Mac/Atom_RHI_Metal_precompiled_Platform.h
#	Gems/Atom/RHI/Metal/Code/Include/Platform/iOS/Atom_RHI_Metal_precompiled_Platform.h
#	Gems/Atom/RHI/Metal/Code/Source/Atom_RHI_Metal_precompiled.h
#	Gems/Atom/RHI/Metal/Code/atom_rhi_metal_common_files.cmake
#	Gems/Atom/RHI/Null/Code/Source/Atom_RHI_Null_precompiled.h
#	Gems/Atom/RHI/Null/Code/atom_rhi_null_common_files.cmake
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Android/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Linux/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Mac/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Windows/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Source/Atom_RHI_Vulkan_precompiled.h
#	Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp
#	Gems/Atom/RHI/Vulkan/Code/atom_rhi_vulkan_common_files.cmake
#	Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont_precompiled.h
#	Gems/Blast/Code/Source/StdAfx.cpp
#	Gems/Camera/Code/Source/Camera_precompiled.h
#	Gems/EMotionFX/Code/Source/EMotionFX_precompiled.h
#	Gems/FastNoise/Code/Source/FastNoise_precompiled.h
#	Gems/Gestures/Code/Source/Gestures_precompiled.h
#	Gems/GradientSignal/Code/Source/GradientSignal_precompiled.h
#	Gems/GraphCanvas/Code/precompiled.h
#	Gems/ImGui/Code/Source/ImGui_precompiled.h
#	Gems/InAppPurchases/Code/Source/InAppPurchases_precompiled.h
#	Gems/LmbrCentral/Code/Source/LmbrCentral_precompiled.h
#	Gems/LmbrCentral/Code/Tests/ShapeGeometryUtilTest.cpp
#	Gems/LyShine/Code/Editor/UiCanvasEditor_precompiled.h
#	Gems/LyShine/Code/Source/Animation/LyShine_precompiled.h
#	Gems/LyShine/Code/Source/LyShine_precompiled.h
#	Gems/LyShineExamples/Code/Source/LyShineExamples_precompiled.h
#	Gems/Maestro/Code/Source/Cinematics/Maestro_precompiled.h
#	Gems/Maestro/Code/Source/Maestro_precompiled.h
#	Gems/MessagePopup/Code/Source/MessagePopup_precompiled.h
#	Gems/Metastream/Code/Source/Metastream_precompiled.h
#	Gems/Microphone/Code/Source/Microphone_precompiled.h
#	Gems/Multiplayer/Code/Source/Multiplayer_precompiled.h
#	Gems/PhysX/Code/NumericalMethods/Source/NumericalMethods_precompiled.h
#	Gems/PhysX/Code/Source/PhysXUnsupported_precompiled.h
#	Gems/PhysX/Code/Source/PhysX_precompiled.h
#	Gems/PhysX/Code/physx_unsupported_files.cmake
#	Gems/PhysXDebug/Code/Source/PhysXDebugUnsupported_precompiled.h
#	Gems/PhysXDebug/Code/Source/PhysXDebug_precompiled.h
#	Gems/ScriptCanvas/Code/Editor/precompiled.h
#	Gems/ScriptCanvas/Code/Source/precompiled.h
#	Gems/ScriptCanvasDeveloper/Code/Source/precompiled.h
#	Gems/ScriptCanvasPhysics/Code/Source/ScriptCanvasPhysics_precompiled.h
#	Gems/ScriptEvents/Code/Source/precompiled.h
#	Gems/ScriptEvents/Code/Tests/Editor/EditorTests.cpp
#	Gems/ScriptedEntityTweener/Code/Source/ScriptedEntityTweener_precompiled.h
#	Gems/SliceFavorites/Code/Source/SliceFavorites_precompiled.h
#	Gems/StartingPointCamera/Code/Source/StartingPointCamera_precompiled.h
#	Gems/StartingPointInput/Code/Source/StartingPointInput_precompiled.h
#	Gems/StartingPointMovement/Code/Source/StartingPointMovement_precompiled.h
#	Gems/SurfaceData/Code/Source/SurfaceData_precompiled.h
#	Gems/TextureAtlas/Code/Source/TextureAtlas_precompiled.h
#	Gems/TickBusOrderViewer/Code/Source/TickBusOrderViewer_precompiled.h
#	Gems/Twitch/Code/Source/Twitch_precompiled.h
#	Gems/VirtualGamepad/Code/Source/VirtualGamepad_precompiled.h
#	Gems/WhiteBox/Code/Source/WhiteBoxUnsupported_precompiled.h
#	Gems/WhiteBox/Code/Source/WhiteBox_precompiled.h
2021-07-16 15:42:37 -07:00

366 lines
14 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>
#include <Tests/ThreadTester.h>
#include <Atom/RHI/PipelineStateCache.h>
#include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
#include <AzCore/Math/Random.h>
namespace UnitTest
{
using namespace AZ;
class PipelineStateTests
: public RHITestFixture
{
static const uint32_t s_heapSizeMB = 64;
protected:
void ScrambleMemory(void* memory, size_t size, uint32_t seed)
{
SimpleLcgRandom random(seed);
uint8_t* bytes = reinterpret_cast<uint8_t*>(memory);
for (size_t i = 0; i < size; ++i)
{
bytes[i] = static_cast<uint8_t>(random.GetRandom());
}
}
// Generates random render state. Everything else is left empty or default as much as possible,
// but we do touch-up the data to make sure we don't end up with something that will fail assertions.
// The point here is to create a unique descriptor that will have a unique hash value.
RHI::PipelineStateDescriptorForDraw CreatePipelineStateDescriptor(uint32_t randomSeed)
{
RHI::PipelineStateDescriptorForDraw desc;
desc.m_inputStreamLayout.SetTopology(RHI::PrimitiveTopology::TriangleList);
desc.m_inputStreamLayout.Finalize();
desc.m_pipelineLayoutDescriptor = m_pipelineLayout;
ScrambleMemory(&desc.m_renderStates, sizeof(RHI::RenderStates), randomSeed++);
desc.m_renderStates.m_depthStencilState.m_depth.m_enable = true;
auto& renderAttachmentLayout = desc.m_renderAttachmentConfiguration.m_renderAttachmentLayout;
renderAttachmentLayout.m_attachmentCount = 2;
renderAttachmentLayout.m_attachmentFormats[0] = RHI::Format::R32_FLOAT;
renderAttachmentLayout.m_attachmentFormats[1] = RHI::Format::R8G8B8A8_SNORM;
renderAttachmentLayout.m_subpassCount = 1;
renderAttachmentLayout.m_subpassLayouts[0].m_rendertargetCount = 1;
renderAttachmentLayout.m_subpassLayouts[0].m_rendertargetDescriptors[0] = RHI::RenderAttachmentDescriptor{ 1, RHI::InvalidRenderAttachmentIndex, RHI::AttachmentLoadStoreAction() };
renderAttachmentLayout.m_subpassLayouts[0].m_depthStencilDescriptor = RHI::RenderAttachmentDescriptor{ 0, RHI::InvalidRenderAttachmentIndex, RHI::AttachmentLoadStoreAction() };
desc.m_renderAttachmentConfiguration.m_subpassIndex = 0;
return desc;
}
void ValidateCacheIntegrity(RHI::Ptr<RHI::PipelineStateCache>& cache) const
{
cache->ValidateCacheIntegrity();
}
private:
void SetUp() override
{
RHITestFixture::SetUp();
m_factory.reset(aznew Factory());
m_pipelineLayout = RHI::PipelineLayoutDescriptor::Create();
m_pipelineLayout->Finalize();
}
void TearDown() override
{
m_pipelineLayout = nullptr;
m_factory.reset();
RHITestFixture::TearDown();
}
RHI::Ptr<RHI::PipelineLayoutDescriptor> m_pipelineLayout;
AZStd::unique_ptr<Factory> m_factory;
};
TEST_F(PipelineStateTests, PipelineState_CreateEmpty_Test)
{
RHI::Ptr<RHI::PipelineState> empty = RHI::Factory::Get().CreatePipelineState();
EXPECT_NE(empty.get(), nullptr);
EXPECT_FALSE(empty->IsInitialized());
}
TEST_F(PipelineStateTests, PipelineState_Init_Test)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::PipelineState> pipelineState = RHI::Factory::Get().CreatePipelineState();
RHI::ResultCode resultCode = pipelineState->Init(*device, CreatePipelineStateDescriptor(0));
EXPECT_EQ(resultCode, RHI::ResultCode::Success);
// Second init should fail and throw validation error.
AZ_TEST_START_ASSERTTEST;
resultCode = pipelineState->Init(*device, CreatePipelineStateDescriptor(0));
AZ_TEST_STOP_ASSERTTEST(1);
EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
}
TEST_F(PipelineStateTests, PipelineState_Init_Subpass)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::PipelineState> pipelineState = RHI::Factory::Get().CreatePipelineState();
auto descriptor = CreatePipelineStateDescriptor(0);
descriptor.m_renderAttachmentConfiguration.m_subpassIndex = 1337;
AZ_TEST_START_ASSERTTEST;
RHI::ResultCode resultCode = pipelineState->Init(*device, descriptor);
AZ_TEST_STOP_ASSERTTEST(1);
EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
}
TEST_F(PipelineStateTests, PipelineState_Init_SubpassInput)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::PipelineState> pipelineState = RHI::Factory::Get().CreatePipelineState();
auto descriptor = CreatePipelineStateDescriptor(0);
descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0].m_subpassInputCount = 1;
descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0].m_subpassInputIndices[0] = 1;
RHI::ResultCode resultCode = pipelineState->Init(*device, descriptor);
EXPECT_EQ(resultCode, RHI::ResultCode::Success);
AZ_TEST_START_ASSERTTEST;
pipelineState = RHI::Factory::Get().CreatePipelineState();
descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0].m_subpassInputIndices[0] = 3;
resultCode = pipelineState->Init(*device, descriptor);
AZ_TEST_STOP_ASSERTTEST(1);
EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
}
TEST_F(PipelineStateTests, PipelineState_Init_Resolve)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::PipelineState> pipelineState = RHI::Factory::Get().CreatePipelineState();
auto descriptor = CreatePipelineStateDescriptor(0);
descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0].m_rendertargetDescriptors[0].m_resolveAttachmentIndex = 1;
RHI::ResultCode resultCode = pipelineState->Init(*device, descriptor);
EXPECT_EQ(resultCode, RHI::ResultCode::Success);
AZ_TEST_START_ASSERTTEST;
pipelineState = RHI::Factory::Get().CreatePipelineState();
descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0].m_rendertargetDescriptors[0].m_resolveAttachmentIndex = 5;
resultCode = pipelineState->Init(*device, descriptor);
AZ_TEST_STOP_ASSERTTEST(1);
EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
AZ_TEST_START_ASSERTTEST;
pipelineState = RHI::Factory::Get().CreatePipelineState();
descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0].m_rendertargetDescriptors[0].m_resolveAttachmentIndex = 0;
resultCode = pipelineState->Init(*device, descriptor);
AZ_TEST_STOP_ASSERTTEST(1);
EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
}
TEST_F(PipelineStateTests, PipelineLibrary_CreateEmpty_Test)
{
RHI::Ptr<RHI::PipelineLibrary> empty = RHI::Factory::Get().CreatePipelineLibrary();
EXPECT_NE(empty.get(), nullptr);
EXPECT_FALSE(empty->IsInitialized());
AZ_TEST_START_ASSERTTEST;
EXPECT_EQ(empty->MergeInto({}), RHI::ResultCode::InvalidOperation);
EXPECT_EQ(empty->GetSerializedData(), nullptr);
AZ_TEST_STOP_ASSERTTEST(2);
}
TEST_F(PipelineStateTests, PipelineLibrary_Init_Test)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::PipelineLibrary> pipelineLibrary = RHI::Factory::Get().CreatePipelineLibrary();
RHI::ResultCode resultCode = pipelineLibrary->Init(*device, nullptr);
EXPECT_EQ(resultCode, RHI::ResultCode::Success);
// Second init should fail and throw validation error.
AZ_TEST_START_ASSERTTEST;
resultCode = pipelineLibrary->Init(*device, nullptr);
AZ_TEST_STOP_ASSERTTEST(1);
EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
}
TEST_F(PipelineStateTests, PipelineStateCache_Init_Test)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::PipelineStateCache> pipelineStateCache = RHI::PipelineStateCache::Create(*device);
AZStd::array<RHI::PipelineLibraryHandle, RHI::PipelineStateCache::LibraryCountMax> handles;
for (size_t i = 0; i < handles.size(); ++i)
{
handles[i] = pipelineStateCache->CreateLibrary(nullptr);
EXPECT_TRUE(handles[i].IsValid());
for (size_t j = 0; j < i; ++j)
{
EXPECT_NE(handles[i], handles[j]);
}
}
// Creating more than the maximum number of libraries should assert but still function.
AZ_TEST_START_ASSERTTEST;
EXPECT_EQ(pipelineStateCache->CreateLibrary(nullptr), RHI::PipelineLibraryHandle{});
AZ_TEST_STOP_ASSERTTEST(1);
// Reset should no-op.
pipelineStateCache->Reset();
for (size_t i = 0; i < handles.size(); ++i)
{
pipelineStateCache->ResetLibrary(handles[i]);
pipelineStateCache->ReleaseLibrary(handles[i]);
}
// Test free-list by allocating another set of libraries.
for (size_t i = 0; i < handles.size(); ++i)
{
handles[i] = pipelineStateCache->CreateLibrary(nullptr);
EXPECT_FALSE(handles[i].IsNull());
}
}
TEST_F(PipelineStateTests, PipelineStateCache_NullHandle_Test)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::PipelineStateCache> pipelineStateCache = RHI::PipelineStateCache::Create(*device);
// Calling library methods with a null handle should early out.
pipelineStateCache->ResetLibrary({});
pipelineStateCache->ReleaseLibrary({});
EXPECT_EQ(pipelineStateCache->GetLibrarySerializedData({}), nullptr);
EXPECT_EQ(pipelineStateCache->AcquirePipelineState({}, CreatePipelineStateDescriptor(0)), nullptr);
pipelineStateCache->Compact();
ValidateCacheIntegrity(pipelineStateCache);
}
TEST_F(PipelineStateTests, PipelineStateCache_PipelineStateThreading_Same_Test)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::PipelineStateCache> pipelineStateCache = RHI::PipelineStateCache::Create(*device);
static const size_t IterationCountMax = 10000;
static const size_t ThreadCountMax = 8;
RHI::PipelineStateDescriptorForDraw descriptor = CreatePipelineStateDescriptor(0);
RHI::PipelineLibraryHandle libraryHandle = pipelineStateCache->CreateLibrary(nullptr);
AZStd::mutex mutex;
AZStd::unordered_set<const RHI::PipelineState*> pipelineStatesMerged;
ThreadTester::Dispatch(ThreadCountMax, [&] ([[maybe_unused]] size_t threadIndex)
{
AZStd::unordered_set<const RHI::PipelineState*> pipelineStates;
for (size_t i = 0; i < IterationCountMax; ++i)
{
pipelineStates.insert(pipelineStateCache->AcquirePipelineState(libraryHandle, descriptor));
}
EXPECT_EQ(pipelineStates.size(), 1);
EXPECT_NE(*pipelineStates.begin(), nullptr);
mutex.lock();
pipelineStatesMerged.insert(*pipelineStates.begin());
mutex.unlock();
});
pipelineStateCache->Compact();
ValidateCacheIntegrity(pipelineStateCache);
EXPECT_EQ(pipelineStatesMerged.size(), 1);
}
TEST_F(PipelineStateTests, PipelineStateCache_PipelineStateThreading_Fuzz_Test)
{
RHI::Ptr<RHI::Device> device = MakeTestDevice();
RHI::Ptr<RHI::PipelineStateCache> pipelineStateCache = RHI::PipelineStateCache::Create(*device);
static const size_t CycleIterationCountMax = 4;
static const size_t AcquireIterationCountMax = 2000;
static const size_t ThreadCountMax = 4;
static const size_t PipelineStateCountMax = 128;
static const size_t LibraryCountMax = 2;
AZStd::vector<RHI::PipelineStateDescriptorForDraw> descriptors;
descriptors.reserve(PipelineStateCountMax);
for (size_t i = 0; i < PipelineStateCountMax; ++i)
{
descriptors.push_back(CreatePipelineStateDescriptor(static_cast<uint32_t>(i)));
}
AZStd::vector<RHI::PipelineLibraryHandle> libraryHandles;
for (size_t i = 0; i < LibraryCountMax; ++i)
{
libraryHandles.push_back(pipelineStateCache->CreateLibrary(nullptr));
}
AZStd::mutex mutex;
for (size_t cycleIndex = 0; cycleIndex < CycleIterationCountMax; ++cycleIndex)
{
for (size_t libraryIndex = 0; libraryIndex < LibraryCountMax; ++libraryIndex)
{
RHI::PipelineLibraryHandle libraryHandle = libraryHandles[libraryIndex];
AZStd::unordered_set<const RHI::PipelineState*> pipelineStatesMerged;
ThreadTester::Dispatch(ThreadCountMax, [&](size_t threadIndex)
{
SimpleLcgRandom random(threadIndex);
AZStd::unordered_set<const RHI::PipelineState*> pipelineStates;
for (size_t i = 0; i < AcquireIterationCountMax; ++i)
{
size_t descriptorIndex = random.GetRandom() % descriptors.size();
pipelineStates.emplace(pipelineStateCache->AcquirePipelineState(libraryHandle, descriptors[descriptorIndex]));
}
mutex.lock();
for (const RHI::PipelineState* pipelineState : pipelineStates)
{
pipelineStatesMerged.emplace(pipelineState);
}
mutex.unlock();
});
EXPECT_EQ(pipelineStatesMerged.size(), PipelineStateCountMax);
}
pipelineStateCache->Compact();
ValidateCacheIntegrity(pipelineStateCache);
// Halfway through, reset the caches.
if (cycleIndex == (CycleIterationCountMax / 2))
{
pipelineStateCache->Reset();
}
}
}
}