3
0

StreamingImageTests.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  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 <AzTest/AzTest.h>
  9. #include <Atom/RPI.Reflect/Image/ImageMipChainAsset.h>
  10. #include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
  11. #include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
  12. #include <Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h>
  13. #include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
  14. #include <Atom/RPI.Reflect/Image/StreamingImagePoolAsset.h>
  15. #include <Atom/RPI.Reflect/Image/StreamingImagePoolAssetCreator.h>
  16. #include <Atom/RPI.Reflect/Asset/BuiltInAssetHandler.h>
  17. #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
  18. #include <Atom/RPI.Public/Image/StreamingImage.h>
  19. #include <Atom/RPI.Public/Image/StreamingImagePool.h>
  20. #include <Atom/RPI.Public/RPIUtils.h>
  21. #include <AtomCore/Instance/InstanceDatabase.h>
  22. #include <AzCore/Interface/Interface.h>
  23. #include <AzCore/std/containers/intrusive_list.h>
  24. #include <Common/RPITestFixture.h>
  25. #include <Common/ErrorMessageFinder.h>
  26. #include <Common/SerializeTester.h>
  27. namespace AZ
  28. {
  29. namespace RPI
  30. {
  31. class StreamingImageAssetTester
  32. : public UnitTest::AssetTester<StreamingImageAsset>
  33. {
  34. public:
  35. StreamingImageAssetTester() = default;
  36. ~StreamingImageAssetTester() override = default;
  37. void SetAssetReady(Data::Asset<StreamingImageAsset>& asset) override
  38. {
  39. asset->SetReady();
  40. }
  41. };
  42. class ImageMipChainAssetTester
  43. : public UnitTest::AssetTester<ImageMipChainAsset>
  44. {
  45. public:
  46. ImageMipChainAssetTester() = default;
  47. ~ImageMipChainAssetTester() override = default;
  48. void SetAssetReady(Data::Asset<ImageMipChainAsset>& asset) override
  49. {
  50. asset->SetReady();
  51. }
  52. };
  53. class StreamingImagePoolAssetTester
  54. : public UnitTest::SerializeTester<StreamingImagePoolAsset>
  55. {
  56. using Base = UnitTest::SerializeTester<StreamingImagePoolAsset>;
  57. public:
  58. StreamingImagePoolAssetTester(AZ::SerializeContext* serializeContext)
  59. : Base(serializeContext)
  60. {}
  61. AZ::Data::Asset<StreamingImagePoolAsset> SerializeInHelper(const AZ::Data::AssetId& assetId)
  62. {
  63. AZ::Data::Asset<StreamingImagePoolAsset> asset = Base::SerializeIn(assetId);
  64. asset->SetReady();
  65. return asset;
  66. }
  67. };
  68. }
  69. }
  70. namespace UnitTest
  71. {
  72. struct TestStreamingImagePoolDescriptor
  73. : public AZ::RHI::StreamingImagePoolDescriptor
  74. {
  75. AZ_CLASS_ALLOCATOR(TestStreamingImagePoolDescriptor, AZ::SystemAllocator);
  76. AZ_RTTI(TestStreamingImagePoolDescriptor, "{8D0CA5A2-F886-42EF-9B00-09E6C9F6B90B}", AZ::RHI::StreamingImagePoolDescriptor);
  77. static constexpr uint32_t Magic = 0x1234;
  78. static void Reflect(AZ::ReflectContext* context)
  79. {
  80. if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  81. {
  82. serializeContext->Class<TestStreamingImagePoolDescriptor, AZ::RHI::StreamingImagePoolDescriptor>()
  83. ->Version(0)
  84. ->Field("m_magic", &TestStreamingImagePoolDescriptor::m_magic)
  85. ;
  86. }
  87. }
  88. TestStreamingImagePoolDescriptor() = default;
  89. TestStreamingImagePoolDescriptor(size_t budgetInBytes)
  90. {
  91. m_budgetInBytes = budgetInBytes;
  92. }
  93. // A test value to ensure that serialization occurred correctly.
  94. uint32_t m_magic = Magic;
  95. };
  96. class TestStreamingImageContext
  97. : public AZ::RPI::StreamingImageContext
  98. , public AZStd::intrusive_list_node<TestStreamingImageContext>
  99. {
  100. public:
  101. AZ_CLASS_ALLOCATOR(TestStreamingImageContext, AZ::SystemAllocator);
  102. AZ_RTTI(TestStreamingImageContext, "{E2FC3EB5-4F66-41D0-9ABE-6EDD2622DD88}", AZ::RPI::StreamingImageContext);
  103. };
  104. class StreamingImageTests
  105. : public RPITestFixture
  106. {
  107. protected:
  108. AZ::Data::AssetHandler* m_imageHandler = nullptr;
  109. AZ::Data::AssetHandler* m_mipChainHandler = nullptr;
  110. AZ::Data::Instance<AZ::RPI::StreamingImagePool> m_defaultPool = nullptr;
  111. StreamingImageTests()
  112. {}
  113. void SetUp() override
  114. {
  115. using namespace AZ;
  116. RPITestFixture::SetUp();
  117. auto* serializeContext = GetSerializeContext();
  118. TestStreamingImagePoolDescriptor::Reflect(serializeContext);
  119. m_imageHandler = Data::AssetManager::Instance().GetHandler(RPI::StreamingImageAsset::RTTI_Type());
  120. m_mipChainHandler = Data::AssetManager::Instance().GetHandler(RPI::ImageMipChainAsset::RTTI_Type());
  121. AZ::Data::Asset<AZ::RPI::StreamingImagePoolAsset> poolAsset = BuildImagePoolAsset(16 * 1024 * 1024);
  122. m_defaultPool = AZ::RPI::StreamingImagePool::FindOrCreate(poolAsset);
  123. }
  124. void TearDown() override
  125. {
  126. using namespace AZ;
  127. RHI::ResourceInvalidateBus::ExecuteQueuedEvents();
  128. m_defaultPool = nullptr;
  129. RPITestFixture::TearDown();
  130. }
  131. AZStd::vector<uint8_t> BuildImageData(uint32_t width, uint32_t height, uint32_t pixelSize)
  132. {
  133. const size_t imageSize = width * height * pixelSize;
  134. AZStd::vector<uint8_t> image;
  135. image.reserve(imageSize);
  136. uint8_t testValue = 0;
  137. for (uint32_t y = 0; y < height; ++y)
  138. {
  139. for (uint32_t x = 0; x < width; ++x)
  140. {
  141. for (uint32_t channel = 0; channel < pixelSize; ++channel)
  142. {
  143. image.push_back(testValue);
  144. testValue++;
  145. }
  146. }
  147. }
  148. EXPECT_EQ(image.size(), imageSize);
  149. return image;
  150. }
  151. void ValidateImageData(AZStd::span<const uint8_t> data, const AZ::RHI::ImageSubresourceLayout& layout)
  152. {
  153. const uint32_t pixelSize = layout.m_size.m_width / layout.m_bytesPerRow;
  154. uint32_t byteOffset = 0;
  155. for (uint32_t y = 0; y < layout.m_size.m_height; ++y)
  156. {
  157. for (uint32_t x = 0; x < layout.m_size.m_width; ++x)
  158. {
  159. for (uint32_t channel = 0; channel < pixelSize; ++channel)
  160. {
  161. uint8_t value = data[byteOffset];
  162. EXPECT_EQ(value, static_cast<uint8_t>(byteOffset));
  163. byteOffset++;
  164. }
  165. }
  166. }
  167. }
  168. void ValidateMipChainAsset(
  169. AZ::RPI::ImageMipChainAsset* mipChain,
  170. uint16_t expectedMipLevels,
  171. uint16_t expectedArraySize,
  172. uint32_t expectedPixelSize)
  173. {
  174. using namespace AZ;
  175. EXPECT_NE(mipChain, nullptr);
  176. EXPECT_EQ(expectedMipLevels, mipChain->GetMipLevelCount());
  177. EXPECT_EQ(expectedArraySize, mipChain->GetArraySize());
  178. EXPECT_EQ(expectedMipLevels * expectedArraySize, mipChain->GetSubImageCount());
  179. const uint32_t imageSize = 1 << mipChain->GetMipLevelCount();
  180. for (uint16_t mipLevel = 0; mipLevel < mipChain->GetMipLevelCount(); ++mipLevel)
  181. {
  182. RHI::ImageSubresourceLayout layout = BuildSubImageLayout(imageSize >> mipLevel, expectedPixelSize);
  183. EXPECT_EQ(memcmp(&layout, &mipChain->GetSubImageLayout(mipLevel), sizeof(RHI::ImageSubresourceLayout)), 0);
  184. for (uint16_t arrayIndex = 0; arrayIndex < mipChain->GetArraySize(); ++arrayIndex)
  185. {
  186. AZStd::span<const uint8_t> imageData = mipChain->GetSubImageData(mipLevel, arrayIndex);
  187. ValidateImageData(imageData, layout);
  188. }
  189. }
  190. }
  191. void ValidateImageAsset(AZ::RPI::StreamingImageAsset* imageAsset)
  192. {
  193. using namespace AZ;
  194. EXPECT_NE(imageAsset, nullptr);
  195. RHI::ImageDescriptor imageDesc = imageAsset->GetImageDescriptor();
  196. size_t mipCountTotal = 0;
  197. for (size_t i = 0; i < imageAsset->GetMipChainCount(); ++i)
  198. {
  199. // The last mip chain asset (tail mip chain) is expected to be empty since the actual mip chain asset data is embedded in StreamingImageAsset
  200. if (i != imageAsset->GetMipChainCount() - 1)
  201. {
  202. EXPECT_TRUE(imageAsset->GetMipChainAsset(i).GetId().IsValid());
  203. }
  204. EXPECT_EQ(imageAsset->GetMipLevel(i), mipCountTotal);
  205. mipCountTotal += imageAsset->GetMipCount(i);
  206. }
  207. EXPECT_EQ(imageDesc.m_mipLevels, mipCountTotal);
  208. }
  209. void ValidateImagePoolAsset(AZ::RPI::StreamingImagePoolAsset* poolAsset, size_t budgetInBytes)
  210. {
  211. using namespace AZ;
  212. EXPECT_EQ(poolAsset->GetPoolDescriptor().m_budgetInBytes, budgetInBytes);
  213. {
  214. const auto* desc = azrtti_cast<const TestStreamingImagePoolDescriptor*>(&poolAsset->GetPoolDescriptor());
  215. EXPECT_NE(desc, nullptr);
  216. EXPECT_EQ(desc->m_magic, UnitTest::TestStreamingImagePoolDescriptor::Magic);
  217. }
  218. }
  219. void ValidateImageResidency(AZ::RPI::StreamingImage* imageInstance, AZ::RPI::StreamingImageAsset* imageAsset)
  220. {
  221. using namespace AZ;
  222. auto imageSystem = RPI::ImageSystemInterface::Get();
  223. const size_t mipChainTailIndex = imageAsset->GetMipChainCount() - 1;
  224. RHI::Ptr<RHI::Image> rhiImage = imageInstance->GetRHIImage();
  225. // This should no-op.
  226. imageInstance->TrimToMipChainLevel(mipChainTailIndex);
  227. // Validate that nothing was actually evicted, since we've set to NoEvict.
  228. for (size_t i = 0; i < mipChainTailIndex; ++i)
  229. {
  230. EXPECT_TRUE(imageAsset->GetMipChainAsset(i).IsReady());
  231. }
  232. EXPECT_EQ(rhiImage->GetResidentMipLevel(), imageAsset->GetMipLevel(mipChainTailIndex));
  233. // Expand to the most detailed mip chain.
  234. imageInstance->QueueExpandToMipChainLevel(0);
  235. // We should still be the same residency level, since it's queued.
  236. EXPECT_EQ(rhiImage->GetResidentMipLevel(), imageAsset->GetMipLevel(mipChainTailIndex));
  237. // Tick the streaming system.
  238. imageSystem->Update();
  239. // Now we should be at the desired residency level.
  240. EXPECT_EQ(rhiImage->GetResidentMipLevel(), 0);
  241. // Expanding 'down' is a no-op.
  242. imageInstance->QueueExpandToMipChainLevel(1);
  243. imageSystem->Update();
  244. EXPECT_EQ(rhiImage->GetResidentMipLevel(), 0);
  245. // Trimming down a notch. This happens instantly.
  246. imageInstance->TrimToMipChainLevel(1);
  247. EXPECT_EQ(rhiImage->GetResidentMipLevel(), imageAsset->GetMipLevel(1));
  248. // Trim down again.
  249. imageInstance->TrimToMipChainLevel(2);
  250. EXPECT_EQ(rhiImage->GetResidentMipLevel(), imageAsset->GetMipLevel(2));
  251. // Expanding back up to 1.
  252. imageInstance->QueueExpandToMipChainLevel(1);
  253. imageSystem->Update();
  254. EXPECT_EQ(rhiImage->GetResidentMipLevel(), imageAsset->GetMipLevel(1));
  255. // Expanding back up to 0.
  256. imageInstance->QueueExpandToMipChainLevel(0);
  257. imageSystem->Update();
  258. EXPECT_EQ(rhiImage->GetResidentMipLevel(), 0);
  259. }
  260. AZ::RHI::ImageSubresourceLayout BuildSubImageLayout(uint32_t imageSize, uint32_t pixelSize)
  261. {
  262. using namespace AZ;
  263. RHI::ImageSubresourceLayout layout;
  264. layout.m_size = RHI::Size{ imageSize, imageSize, 1 };
  265. layout.m_rowCount = imageSize;
  266. layout.m_bytesPerRow = imageSize * pixelSize;
  267. layout.m_bytesPerImage = imageSize * imageSize * pixelSize;
  268. return layout;
  269. }
  270. AZ::Data::Asset<AZ::RPI::ImageMipChainAsset> BuildMipChainAsset(uint16_t mipOffset, uint16_t mipLevels, uint16_t arraySize, uint32_t pixelSize)
  271. {
  272. using namespace AZ;
  273. RPI::ImageMipChainAssetCreator assetCreator;
  274. const uint32_t imageSize = 1 << (mipLevels + mipOffset);
  275. assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
  276. for (uint32_t mipLevel = 0; mipLevel < mipLevels; ++mipLevel)
  277. {
  278. const uint32_t mipSize = imageSize >> mipLevel;
  279. RHI::ImageSubresourceLayout layout = BuildSubImageLayout(mipSize, pixelSize);
  280. assetCreator.BeginMip(layout);
  281. for (uint32_t arrayIndex = 0; arrayIndex < arraySize; ++arrayIndex)
  282. {
  283. AZStd::vector<uint8_t> data = BuildImageData(mipSize, mipSize, pixelSize);
  284. assetCreator.AddSubImage(data.data(), data.size());
  285. }
  286. assetCreator.EndMip();
  287. }
  288. Data::Asset<RPI::ImageMipChainAsset> asset;
  289. EXPECT_TRUE(assetCreator.End(asset));
  290. EXPECT_TRUE(asset.IsReady());
  291. EXPECT_NE(asset.Get(), nullptr);
  292. return asset;
  293. }
  294. AZ::Data::Asset<AZ::RPI::StreamingImagePoolAsset> BuildImagePoolAsset(size_t budgetInBytes)
  295. {
  296. using namespace AZ;
  297. RPI::StreamingImagePoolAssetCreator assetCreator;
  298. assetCreator.Begin(Data::AssetId(Uuid::CreateRandom()));
  299. assetCreator.SetPoolDescriptor(AZStd::make_unique<TestStreamingImagePoolDescriptor>(budgetInBytes));
  300. Data::Asset<RPI::StreamingImagePoolAsset> poolAsset;
  301. EXPECT_TRUE(assetCreator.End(poolAsset));
  302. EXPECT_TRUE(poolAsset.IsReady());
  303. EXPECT_NE(poolAsset.Get(), nullptr);
  304. return poolAsset;
  305. }
  306. AZ::Data::Asset<AZ::RPI::StreamingImageAsset> BuildTestImage(AZ::RHI::Format format = AZ::RHI::Format::R8G8B8A8_UNORM)
  307. {
  308. using namespace AZ;
  309. const uint32_t arraySize = 2;
  310. const uint32_t pixelSize = RHI::GetFormatSize(format);
  311. const uint32_t mipCountHead = 1;
  312. const uint32_t mipCountMiddle = 2;
  313. const uint32_t mipCountTail = 3;
  314. const uint32_t mipCountTotal = mipCountHead + mipCountMiddle + mipCountTail;
  315. const uint32_t imageWidth = 1 << mipCountTotal;
  316. const uint32_t imageHeight = 1 << mipCountTotal;
  317. Data::Asset<RPI::ImageMipChainAsset> mipTail = BuildMipChainAsset(0, mipCountTail, arraySize, pixelSize);
  318. Data::Asset<RPI::ImageMipChainAsset> mipMiddle = BuildMipChainAsset(mipCountTail, mipCountMiddle, arraySize, pixelSize);
  319. Data::Asset<RPI::ImageMipChainAsset> mipHead = BuildMipChainAsset(mipCountTail + mipCountMiddle, mipCountHead, arraySize, pixelSize);
  320. RPI::StreamingImageAssetCreator assetCreator;
  321. assetCreator.Begin(Data::AssetId(Uuid::CreateRandom()));
  322. RHI::ImageDescriptor imageDesc = RHI::ImageDescriptor::Create2DArray(RHI::ImageBindFlags::ShaderRead, imageWidth, imageHeight, arraySize, format);
  323. imageDesc.m_mipLevels = static_cast<uint16_t>(mipCountTotal);
  324. assetCreator.SetImageDescriptor(imageDesc);
  325. assetCreator.AddMipChainAsset(*mipHead.Get());
  326. assetCreator.AddMipChainAsset(*mipMiddle.Get());
  327. assetCreator.AddMipChainAsset(*mipTail.Get());
  328. assetCreator.SetPoolAssetId(m_defaultPool->GetAssetId());
  329. Data::Asset<RPI::StreamingImageAsset> imageAsset;
  330. EXPECT_TRUE(assetCreator.End(imageAsset));
  331. EXPECT_TRUE(imageAsset.IsReady());
  332. EXPECT_NE(imageAsset.Get(), nullptr);
  333. return imageAsset;
  334. }
  335. };
  336. TEST_F(StreamingImageTests, MipChainCreate)
  337. {
  338. using namespace AZ;
  339. const uint16_t mipLevels = 5;
  340. const uint16_t arraySize = 4;
  341. const uint16_t pixelSize = 4;
  342. Data::Asset<RPI::ImageMipChainAsset> mipChain = BuildMipChainAsset(0, mipLevels, arraySize, pixelSize);
  343. ValidateMipChainAsset(mipChain.Get(), mipLevels, arraySize, pixelSize);
  344. }
  345. TEST_F(StreamingImageTests, MipChainAssetSuccessAfterErrorCases)
  346. {
  347. using namespace AZ;
  348. const uint16_t mipLevels = 1;
  349. const uint16_t arraySize = 1;
  350. Data::Asset<RPI::ImageMipChainAsset> mipChain;
  351. {
  352. RPI::ImageMipChainAssetCreator assetCreator;
  353. ErrorMessageFinder messageFinder("Begin() was not called");
  354. assetCreator.EndMip();
  355. }
  356. {
  357. RPI::ImageMipChainAssetCreator assetCreator;
  358. ErrorMessageFinder messageFinder("Begin() was not called");
  359. assetCreator.End(mipChain);
  360. }
  361. {
  362. RPI::ImageMipChainAssetCreator assetCreator;
  363. assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
  364. assetCreator.BeginMip(RHI::ImageSubresourceLayout());
  365. ErrorMessageFinder messageFinder("Expected 1 sub-images in mip, but got 0.");
  366. assetCreator.EndMip();
  367. }
  368. {
  369. RPI::ImageMipChainAssetCreator assetCreator;
  370. assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
  371. assetCreator.BeginMip(RHI::ImageSubresourceLayout());
  372. ErrorMessageFinder messageFinder("You must supply a valid data payload.");
  373. assetCreator.AddSubImage(nullptr, 0);
  374. }
  375. {
  376. RPI::ImageMipChainAssetCreator assetCreator;
  377. assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
  378. assetCreator.BeginMip(RHI::ImageSubresourceLayout());
  379. ErrorMessageFinder messageFinder("You must supply a valid data payload.");
  380. assetCreator.AddSubImage(nullptr, 10);
  381. }
  382. uint8_t data[4] = { 0, 5, 10, 15 };
  383. const uint8_t dataSize = AZ_ARRAY_SIZE(data);
  384. {
  385. RPI::ImageMipChainAssetCreator assetCreator;
  386. assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
  387. assetCreator.BeginMip(RHI::ImageSubresourceLayout());
  388. assetCreator.AddSubImage(data, dataSize);
  389. ErrorMessageFinder messageFinder("Exceeded the 1 array slices declared in Begin().");
  390. assetCreator.AddSubImage(data, dataSize);
  391. }
  392. {
  393. RPI::ImageMipChainAssetCreator assetCreator;
  394. assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
  395. assetCreator.BeginMip(RHI::ImageSubresourceLayout());
  396. assetCreator.AddSubImage(data, dataSize);
  397. ErrorMessageFinder messageFinder("Already building a mip. You must call EndMip() first.");
  398. assetCreator.BeginMip(RHI::ImageSubresourceLayout());
  399. }
  400. // Finally, build a valid one
  401. {
  402. RPI::ImageMipChainAssetCreator assetCreator;
  403. assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
  404. assetCreator.BeginMip(RHI::ImageSubresourceLayout());
  405. assetCreator.AddSubImage(data, dataSize);
  406. assetCreator.EndMip();
  407. EXPECT_TRUE(assetCreator.End(mipChain));
  408. EXPECT_NE(mipChain.Get(), nullptr);
  409. EXPECT_EQ(mipChain->GetMipLevelCount(), mipLevels);
  410. EXPECT_EQ(mipChain->GetArraySize(), arraySize);
  411. EXPECT_EQ(mipChain->GetSubImageCount(), mipLevels * arraySize);
  412. AZStd::span<const uint8_t> dataView = mipChain->GetSubImageData(0);
  413. EXPECT_EQ(dataView[0], data[0]);
  414. EXPECT_EQ(dataView[1], data[1]);
  415. EXPECT_EQ(dataView[2], data[2]);
  416. EXPECT_EQ(dataView[3], data[3]);
  417. }
  418. }
  419. TEST_F(StreamingImageTests, MipChainAssetSerialize)
  420. {
  421. using namespace AZ;
  422. const uint16_t mipLevels = 6;
  423. const uint16_t arraySize = 2;
  424. const uint16_t pixelSize = 2;
  425. Data::Asset<RPI::ImageMipChainAsset> mipChain = BuildMipChainAsset(0, mipLevels, arraySize, pixelSize);
  426. RPI::ImageMipChainAssetTester tester;
  427. tester.SerializeOut(mipChain);
  428. Data::Asset<RPI::ImageMipChainAsset> serializedMipChain = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  429. ValidateMipChainAsset(serializedMipChain.Get(), mipLevels, arraySize, pixelSize);
  430. }
  431. TEST_F(StreamingImageTests, PoolAssetCreation)
  432. {
  433. using namespace AZ;
  434. const size_t budgetInBytes = 16 * 1024 * 1024;
  435. Data::Asset<RPI::StreamingImagePoolAsset> poolAsset = BuildImagePoolAsset(budgetInBytes);
  436. ValidateImagePoolAsset(poolAsset.Get(), budgetInBytes);
  437. }
  438. TEST_F(StreamingImageTests, PoolAssetSerialize)
  439. {
  440. using namespace AZ;
  441. const size_t budgetInBytes = 16 * 1024 * 1024;
  442. Data::Asset<RPI::StreamingImagePoolAsset> poolAsset = BuildImagePoolAsset(budgetInBytes);
  443. RPI::StreamingImagePoolAssetTester tester(GetSerializeContext());
  444. tester.SerializeOut(poolAsset.Get());
  445. Data::Asset<RPI::StreamingImagePoolAsset> serializedPoolAsset = tester.SerializeInHelper(Data::AssetId(Uuid::CreateRandom()));
  446. ValidateImagePoolAsset(serializedPoolAsset.Get(), budgetInBytes);
  447. }
  448. TEST_F(StreamingImageTests, PoolInstanceCreation)
  449. {
  450. using namespace AZ;
  451. const size_t budgetInBytes = 16 * 1024 * 1024;
  452. Data::Asset<RPI::StreamingImagePoolAsset> poolAsset = BuildImagePoolAsset(budgetInBytes);
  453. Data::Instance<RPI::StreamingImagePool> poolInstance = RPI::StreamingImagePool::FindOrCreate(poolAsset);
  454. EXPECT_NE(poolInstance.get(), nullptr);
  455. EXPECT_NE(poolInstance->GetRHIPool(), nullptr);
  456. }
  457. TEST_F(StreamingImageTests, ImageAssetCreation)
  458. {
  459. using namespace AZ;
  460. Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
  461. ValidateImageAsset(imageAsset.Get());
  462. }
  463. TEST_F(StreamingImageTests, ImageAssetSerialize)
  464. {
  465. using namespace AZ;
  466. Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
  467. RPI::StreamingImageAssetTester tester;
  468. tester.SerializeOut(imageAsset);
  469. Data::Asset<RPI::StreamingImageAsset> serializedImageAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  470. ValidateImageAsset(serializedImageAsset.Get());
  471. }
  472. TEST_F(StreamingImageTests, ImageInstanceCreation)
  473. {
  474. using namespace AZ;
  475. Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
  476. Data::Instance<RPI::StreamingImage> imageInstance = RPI::StreamingImage::FindOrCreate(imageAsset);
  477. EXPECT_NE(imageInstance.get(), nullptr);
  478. EXPECT_NE(imageInstance->GetRHIImage(), nullptr);
  479. EXPECT_NE(imageInstance->GetImageView(), nullptr);
  480. EXPECT_GE(imageAsset->GetMipChainCount(), 0);
  481. const size_t mipChainTailIndex = imageAsset->GetMipChainCount() - 1;
  482. EXPECT_EQ(imageInstance->GetRHIImage()->GetResidentMipLevel(), imageAsset->GetMipLevel(mipChainTailIndex));
  483. for (size_t i = 0; i < mipChainTailIndex; ++i)
  484. {
  485. Data::Asset<RPI::ImageMipChainAsset> mipChainAsset = imageAsset->GetMipChainAsset(i);
  486. EXPECT_TRUE(mipChainAsset.IsReady());
  487. }
  488. }
  489. TEST_F(StreamingImageTests, ImageInstanceResidency)
  490. {
  491. using namespace AZ;
  492. Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
  493. Data::Instance<RPI::StreamingImage> imageInstance = RPI::StreamingImage::FindOrCreate(imageAsset);
  494. ValidateImageResidency(imageInstance.get(), imageAsset.Get());
  495. }
  496. TEST_F(StreamingImageTests, ImageInstanceResidencyWithSerialization)
  497. {
  498. using namespace AZ;
  499. // Keep the original around, which holds references to the image mip chain assets and pool asset. We need to
  500. // keep them in memory to avoid the asset manager trying to hit the catalog.
  501. Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
  502. RPI::StreamingImageAssetTester tester;
  503. tester.SerializeOut(imageAsset);
  504. Data::Asset<RPI::StreamingImageAsset> serializedImageAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
  505. Data::Instance<RPI::StreamingImage> imageInstance = RPI::StreamingImage::FindOrCreate(serializedImageAsset);
  506. ValidateImageResidency(imageInstance.get(), imageAsset.Get());
  507. }
  508. TEST_F(StreamingImageTests, ImageInternalReferenceTracking)
  509. {
  510. using namespace AZ;
  511. Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
  512. Data::Instance<RPI::StreamingImagePool> imagePoolInstance;
  513. {
  514. Data::Instance<RPI::StreamingImage> imageInstance = RPI::StreamingImage::FindOrCreate(imageAsset);
  515. // Hold the pool instance to keep it around.
  516. imagePoolInstance = imageInstance->GetPool();
  517. // Tests that we can safely destroy an image after queueing something to the system,
  518. // and the system will properly avoid touching that data.
  519. imageInstance->QueueExpandToMipChainLevel(0);
  520. }
  521. RPI::ImageSystemInterface::Get()->Update();
  522. }
  523. TEST_F(StreamingImageTests, GetSubImagePixelValues)
  524. {
  525. using namespace AZ;
  526. Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage(AZ::RHI::Format::R8_UNORM);
  527. auto streamingImageAsset = imageAsset.Get();
  528. EXPECT_NE(streamingImageAsset, nullptr);
  529. // Validate retrieving one pixel at a time
  530. auto size = streamingImageAsset->GetImageDescriptor().m_size;
  531. for (uint32_t y = 0; y < size.m_height; ++y)
  532. {
  533. for (uint32_t x = 0; x < size.m_width; ++x)
  534. {
  535. auto pixelDataValue = RPI::GetSubImagePixelValue<float>(imageAsset, x, y);
  536. auto pixelExpectedValue = static_cast<uint8_t>(y * size.m_width + x) / static_cast<float>(std::numeric_limits<AZ::u8>::max());
  537. EXPECT_NEAR(pixelDataValue, pixelExpectedValue, Constants::Tolerance);
  538. }
  539. }
  540. // Validate retrieving a region of pixels
  541. AZStd::vector<float> pixelValues;
  542. pixelValues.reserve(size.m_width * size.m_height);
  543. auto topLeft = AZStd::make_pair(0U, 0U);
  544. auto bottomRight = AZStd::make_pair(size.m_width, size.m_height);
  545. RPI::GetSubImagePixelValues(imageAsset, topLeft, bottomRight, [&pixelValues]([[maybe_unused]] const AZ::u32& x, [[maybe_unused]] const AZ::u32& y, const float& value) {
  546. pixelValues.push_back(value);
  547. });
  548. for (uint32_t index = 0; index < pixelValues.size(); ++index)
  549. {
  550. auto pixelDataValue = pixelValues[index];
  551. auto pixelExpectedValue = static_cast<uint8_t>(index) / static_cast<float>(std::numeric_limits<AZ::u8>::max());
  552. EXPECT_NEAR(pixelDataValue, pixelExpectedValue, Constants::Tolerance);
  553. }
  554. }
  555. }