MultiDevicePipelineStateTests.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "RHITestFixture.h"
  9. #include <Tests/Device.h>
  10. #include <Tests/ThreadTester.h>
  11. #include <Atom/RHI.Reflect/PipelineLayoutDescriptor.h>
  12. #include <Atom/RHI/PipelineLibrary.h>
  13. #include <Atom/RHI/PipelineState.h>
  14. #include <Atom/RHI/PipelineStateCache.h>
  15. #include <AzCore/Math/Random.h>
  16. namespace UnitTest
  17. {
  18. using namespace AZ;
  19. class MultiDevicePipelineStateTests : public MultiDeviceRHITestFixture
  20. {
  21. static const uint32_t s_heapSizeMB = 64;
  22. protected:
  23. void ScrambleMemory(void* memory, size_t size, uint32_t seed)
  24. {
  25. SimpleLcgRandom random(seed);
  26. uint8_t* bytes = reinterpret_cast<uint8_t*>(memory);
  27. for (size_t i = 0; i < size; ++i)
  28. {
  29. bytes[i] = static_cast<uint8_t>(random.GetRandom());
  30. }
  31. }
  32. // Generates random render state. Everything else is left empty or default as much as possible,
  33. // but we do touch-up the data to make sure we don't end up with something that will fail assertions.
  34. // The point here is to create a unique descriptor that will have a unique hash value.
  35. RHI::PipelineStateDescriptorForDraw CreatePipelineStateDescriptor(uint32_t randomSeed)
  36. {
  37. RHI::PipelineStateDescriptorForDraw desc;
  38. desc.m_inputStreamLayout.SetTopology(RHI::PrimitiveTopology::TriangleList);
  39. desc.m_inputStreamLayout.Finalize();
  40. desc.m_pipelineLayoutDescriptor = m_pipelineLayout;
  41. ScrambleMemory(&desc.m_renderStates, sizeof(RHI::RenderStates), randomSeed++);
  42. desc.m_renderStates.m_depthStencilState.m_depth.m_enable = true;
  43. auto& renderAttachmentLayout = desc.m_renderAttachmentConfiguration.m_renderAttachmentLayout;
  44. renderAttachmentLayout.m_attachmentCount = 2;
  45. renderAttachmentLayout.m_attachmentFormats[0] = RHI::Format::R32_FLOAT;
  46. renderAttachmentLayout.m_attachmentFormats[1] = RHI::Format::R8G8B8A8_SNORM;
  47. renderAttachmentLayout.m_subpassCount = 1;
  48. renderAttachmentLayout.m_subpassLayouts[0].m_rendertargetCount = 1;
  49. renderAttachmentLayout.m_subpassLayouts[0].m_rendertargetDescriptors[0] =
  50. RHI::RenderAttachmentDescriptor{ 1, RHI::InvalidRenderAttachmentIndex, RHI::AttachmentLoadStoreAction() };
  51. renderAttachmentLayout.m_subpassLayouts[0].m_depthStencilDescriptor =
  52. RHI::RenderAttachmentDescriptor{ 0, RHI::InvalidRenderAttachmentIndex, RHI::AttachmentLoadStoreAction() };
  53. desc.m_renderAttachmentConfiguration.m_subpassIndex = 0;
  54. return desc;
  55. }
  56. void ValidateCacheIntegrity(RHI::Ptr<RHI::PipelineStateCache>& cache) const
  57. {
  58. cache->ValidateCacheIntegrity();
  59. }
  60. private:
  61. void SetUp() override
  62. {
  63. MultiDeviceRHITestFixture::SetUp();
  64. m_pipelineLayout = RHI::PipelineLayoutDescriptor::Create();
  65. m_pipelineLayout->Finalize();
  66. }
  67. void TearDown() override
  68. {
  69. m_pipelineLayout = nullptr;
  70. MultiDeviceRHITestFixture::TearDown();
  71. }
  72. RHI::Ptr<RHI::PipelineLayoutDescriptor> m_pipelineLayout;
  73. };
  74. TEST_F(MultiDevicePipelineStateTests, PipelineState_CreateEmpty_Test)
  75. {
  76. RHI::Ptr<RHI::PipelineState> empty = aznew RHI::PipelineState;
  77. EXPECT_NE(empty.get(), nullptr);
  78. EXPECT_FALSE(empty->IsInitialized());
  79. }
  80. TEST_F(MultiDevicePipelineStateTests, PipelineState_Init_Test)
  81. {
  82. RHI::Ptr<RHI::PipelineState> pipelineState = aznew RHI::PipelineState;
  83. RHI::ResultCode resultCode = pipelineState->Init(DeviceMask, CreatePipelineStateDescriptor(0));
  84. EXPECT_EQ(resultCode, RHI::ResultCode::Success);
  85. // Second init should fail and throw validation error.
  86. AZ_TEST_START_ASSERTTEST;
  87. resultCode = pipelineState->Init(DeviceMask, CreatePipelineStateDescriptor(0));
  88. AZ_TEST_STOP_ASSERTTEST(1);
  89. EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
  90. }
  91. TEST_F(MultiDevicePipelineStateTests, PipelineState_Init_Subpass)
  92. {
  93. RHI::Ptr<RHI::PipelineState> pipelineState = aznew RHI::PipelineState;
  94. auto descriptor = CreatePipelineStateDescriptor(0);
  95. descriptor.m_renderAttachmentConfiguration.m_subpassIndex = 1337;
  96. AZ_TEST_START_ASSERTTEST;
  97. RHI::ResultCode resultCode = pipelineState->Init(DeviceMask, descriptor);
  98. AZ_TEST_STOP_ASSERTTEST(1);
  99. EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
  100. }
  101. TEST_F(MultiDevicePipelineStateTests, PipelineState_Init_SubpassInput)
  102. {
  103. RHI::Ptr<RHI::PipelineState> pipelineState = aznew RHI::PipelineState;
  104. auto descriptor = CreatePipelineStateDescriptor(0);
  105. descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0].m_subpassInputCount = 1;
  106. descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0]
  107. .m_subpassInputDescriptors[0]
  108. .m_attachmentIndex = 1;
  109. RHI::ResultCode resultCode = pipelineState->Init(DeviceMask, descriptor);
  110. EXPECT_EQ(resultCode, RHI::ResultCode::Success);
  111. AZ_TEST_START_ASSERTTEST;
  112. pipelineState = aznew RHI::PipelineState;
  113. descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0]
  114. .m_subpassInputDescriptors[0]
  115. .m_attachmentIndex = 3;
  116. resultCode = pipelineState->Init(DeviceMask, descriptor);
  117. AZ_TEST_STOP_ASSERTTEST(1);
  118. EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
  119. }
  120. TEST_F(MultiDevicePipelineStateTests, PipelineState_Init_Resolve)
  121. {
  122. RHI::Ptr<RHI::PipelineState> pipelineState = aznew RHI::PipelineState;
  123. auto descriptor = CreatePipelineStateDescriptor(0);
  124. descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0]
  125. .m_rendertargetDescriptors[0]
  126. .m_resolveAttachmentIndex = 1;
  127. RHI::ResultCode resultCode = pipelineState->Init(DeviceMask, descriptor);
  128. EXPECT_EQ(resultCode, RHI::ResultCode::Success);
  129. AZ_TEST_START_ASSERTTEST;
  130. pipelineState = aznew RHI::PipelineState;
  131. descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0]
  132. .m_rendertargetDescriptors[0]
  133. .m_resolveAttachmentIndex = 5;
  134. resultCode = pipelineState->Init(DeviceMask, descriptor);
  135. AZ_TEST_STOP_ASSERTTEST(1);
  136. EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
  137. AZ_TEST_START_ASSERTTEST;
  138. pipelineState = aznew RHI::PipelineState;
  139. descriptor.m_renderAttachmentConfiguration.m_renderAttachmentLayout.m_subpassLayouts[0]
  140. .m_rendertargetDescriptors[0]
  141. .m_resolveAttachmentIndex = 0;
  142. resultCode = pipelineState->Init(DeviceMask, descriptor);
  143. AZ_TEST_STOP_ASSERTTEST(1);
  144. EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
  145. }
  146. TEST_F(MultiDevicePipelineStateTests, PipelineLibrary_CreateEmpty_Test)
  147. {
  148. RHI::Ptr<RHI::PipelineLibrary> empty = aznew RHI::PipelineLibrary;
  149. EXPECT_NE(empty.get(), nullptr);
  150. EXPECT_FALSE(empty->IsInitialized());
  151. AZ_TEST_START_ASSERTTEST;
  152. EXPECT_EQ(empty->MergeInto({}), RHI::ResultCode::InvalidOperation);
  153. EXPECT_TRUE(empty->GetSerializedDataMap().empty());
  154. AZ_TEST_STOP_ASSERTTEST(1);
  155. }
  156. TEST_F(MultiDevicePipelineStateTests, PipelineLibrary_Init_Test)
  157. {
  158. RHI::Ptr<RHI::PipelineLibrary> pipelineLibrary = aznew RHI::PipelineLibrary;
  159. AZ_TEST_START_ASSERTTEST; // Suppress asserts from default constructed library descriptor
  160. RHI::ResultCode resultCode = pipelineLibrary->Init(DeviceMask, RHI::PipelineLibraryDescriptor{});
  161. AZ_TEST_STOP_ASSERTTEST(DeviceCount);
  162. EXPECT_EQ(resultCode, RHI::ResultCode::Success);
  163. // Second init should fail and throw validation error.
  164. AZ_TEST_START_ASSERTTEST;
  165. resultCode = pipelineLibrary->Init(DeviceMask, RHI::PipelineLibraryDescriptor{});
  166. AZ_TEST_STOP_ASSERTTEST(1);
  167. EXPECT_EQ(resultCode, RHI::ResultCode::InvalidOperation);
  168. }
  169. TEST_F(MultiDevicePipelineStateTests, PipelineStateCache_Init_Test)
  170. {
  171. RHI::Ptr<RHI::PipelineStateCache> pipelineStateCache =
  172. RHI::PipelineStateCache::Create(DeviceMask);
  173. AZStd::array<RHI::PipelineLibraryHandle, RHI::PipelineStateCache::LibraryCountMax> handles;
  174. for (size_t i = 0; i < handles.size(); ++i)
  175. {
  176. handles[i] = pipelineStateCache->CreateLibrary({}, {});
  177. EXPECT_TRUE(handles[i].IsValid());
  178. for (size_t j = 0; j < i; ++j)
  179. {
  180. EXPECT_NE(handles[i], handles[j]);
  181. }
  182. }
  183. // Creating more than the maximum number of libraries should assert but still function.
  184. AZ_TEST_START_ASSERTTEST;
  185. EXPECT_EQ(pipelineStateCache->CreateLibrary({}, {}), RHI::PipelineLibraryHandle{});
  186. AZ_TEST_STOP_ASSERTTEST(1);
  187. // Reset should no-op.
  188. pipelineStateCache->Reset();
  189. for (size_t i = 0; i < handles.size(); ++i)
  190. {
  191. pipelineStateCache->ResetLibrary(handles[i]);
  192. pipelineStateCache->ReleaseLibrary(handles[i]);
  193. }
  194. // Test free-list by allocating another set of libraries.
  195. for (size_t i = 0; i < handles.size(); ++i)
  196. {
  197. handles[i] = pipelineStateCache->CreateLibrary({}, {});
  198. EXPECT_FALSE(handles[i].IsNull());
  199. }
  200. }
  201. TEST_F(MultiDevicePipelineStateTests, PipelineStateCache_NullHandle_Test)
  202. {
  203. RHI::Ptr<RHI::PipelineStateCache> pipelineStateCache = RHI::PipelineStateCache::Create(DeviceMask);
  204. // Calling library methods with a null handle should early out.
  205. pipelineStateCache->ResetLibrary({});
  206. pipelineStateCache->ReleaseLibrary({});
  207. EXPECT_EQ(pipelineStateCache->GetMergedLibrary({}), nullptr);
  208. EXPECT_EQ(pipelineStateCache->AcquirePipelineState({}, CreatePipelineStateDescriptor(0)), nullptr);
  209. pipelineStateCache->Compact();
  210. ValidateCacheIntegrity(pipelineStateCache);
  211. }
  212. TEST_F(MultiDevicePipelineStateTests, PipelineStateCache_PipelineStateThreading_Same_Test)
  213. {
  214. RHI::Ptr<RHI::PipelineStateCache> pipelineStateCache = RHI::PipelineStateCache::Create(DeviceMask);
  215. static const size_t IterationCountMax = 10000;
  216. static const size_t ThreadCountMax = 8;
  217. RHI::PipelineStateDescriptorForDraw descriptor = CreatePipelineStateDescriptor(0);
  218. RHI::PipelineLibraryHandle libraryHandle = pipelineStateCache->CreateLibrary({}, {});
  219. AZStd::mutex mutex;
  220. AZStd::unordered_set<const RHI::PipelineState*> pipelineStatesMerged;
  221. ThreadTester::Dispatch(
  222. ThreadCountMax,
  223. [&]([[maybe_unused]] size_t threadIndex)
  224. {
  225. AZStd::unordered_set<const RHI::PipelineState*> pipelineStates;
  226. for (size_t i = 0; i < IterationCountMax; ++i)
  227. {
  228. pipelineStates.insert(pipelineStateCache->AcquirePipelineState(libraryHandle, descriptor));
  229. }
  230. EXPECT_EQ(pipelineStates.size(), 1);
  231. EXPECT_NE(*pipelineStates.begin(), nullptr);
  232. mutex.lock();
  233. pipelineStatesMerged.insert(*pipelineStates.begin());
  234. mutex.unlock();
  235. });
  236. pipelineStateCache->Compact();
  237. ValidateCacheIntegrity(pipelineStateCache);
  238. EXPECT_EQ(pipelineStatesMerged.size(), 1);
  239. }
  240. TEST_F(MultiDevicePipelineStateTests, PipelineStateCache_PipelineStateThreading_Fuzz_Test)
  241. {
  242. RHI::Ptr<RHI::PipelineStateCache> pipelineStateCache = RHI::PipelineStateCache::Create(DeviceMask);
  243. static const size_t CycleIterationCountMax = 4;
  244. static const size_t AcquireIterationCountMax = 2000;
  245. static const size_t ThreadCountMax = 4;
  246. static const size_t PipelineStateCountMax = 128;
  247. static const size_t LibraryCountMax = 2;
  248. AZStd::vector<RHI::PipelineStateDescriptorForDraw> descriptors;
  249. descriptors.reserve(PipelineStateCountMax);
  250. for (size_t i = 0; i < PipelineStateCountMax; ++i)
  251. {
  252. descriptors.push_back(CreatePipelineStateDescriptor(static_cast<uint32_t>(i)));
  253. }
  254. AZStd::vector<RHI::PipelineLibraryHandle> libraryHandles;
  255. for (size_t i = 0; i < LibraryCountMax; ++i)
  256. {
  257. libraryHandles.push_back(pipelineStateCache->CreateLibrary({}, {}));
  258. }
  259. AZStd::mutex mutex;
  260. for (size_t cycleIndex = 0; cycleIndex < CycleIterationCountMax; ++cycleIndex)
  261. {
  262. for (size_t libraryIndex = 0; libraryIndex < LibraryCountMax; ++libraryIndex)
  263. {
  264. RHI::PipelineLibraryHandle libraryHandle = libraryHandles[libraryIndex];
  265. AZStd::unordered_set<const RHI::PipelineState*> pipelineStatesMerged;
  266. ThreadTester::Dispatch(
  267. ThreadCountMax,
  268. [&](size_t threadIndex)
  269. {
  270. SimpleLcgRandom random(threadIndex);
  271. AZStd::unordered_set<const RHI::PipelineState*> pipelineStates;
  272. for (size_t i = 0; i < AcquireIterationCountMax; ++i)
  273. {
  274. size_t descriptorIndex = random.GetRandom() % descriptors.size();
  275. pipelineStates.emplace(pipelineStateCache->AcquirePipelineState(libraryHandle,
  276. descriptors[descriptorIndex]));
  277. }
  278. mutex.lock();
  279. for (const RHI::PipelineState* pipelineState : pipelineStates)
  280. {
  281. pipelineStatesMerged.emplace(pipelineState);
  282. }
  283. mutex.unlock();
  284. });
  285. EXPECT_EQ(pipelineStatesMerged.size(), PipelineStateCountMax);
  286. }
  287. pipelineStateCache->Compact();
  288. ValidateCacheIntegrity(pipelineStateCache);
  289. // Halfway through, reset the caches.
  290. if (cycleIndex == (CycleIterationCountMax / 2))
  291. {
  292. pipelineStateCache->Reset();
  293. }
  294. }
  295. }
  296. } // namespace UnitTest