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/RPI/Code/Tests/Image/StreamingImageTests.cpp

730 lines
27 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 <AzTest/AzTest.h>
#include <Atom/RPI.Reflect/Image/ImageMipChainAsset.h>
#include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
#include <Atom/RPI.Reflect/Image/StreamingImagePoolAsset.h>
#include <Atom/RPI.Reflect/Image/StreamingImagePoolAssetCreator.h>
#include <Atom/RPI.Reflect/Image/DefaultStreamingImageControllerAsset.h>
#include <Atom/RPI.Reflect/Asset/BuiltInAssetHandler.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Public/Image/StreamingImage.h>
#include <Atom/RPI.Public/Image/StreamingImagePool.h>
#include <Atom/RPI.Public/Image/DefaultStreamingImageController.h>
#include <AtomCore/Instance/InstanceDatabase.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/std/containers/intrusive_list.h>
#include <Common/RPITestFixture.h>
#include <Common/ErrorMessageFinder.h>
#include <Common/SerializeTester.h>
namespace AZ
{
namespace RPI
{
class StreamingImageAssetTester
: public UnitTest::AssetTester<StreamingImageAsset>
{
public:
StreamingImageAssetTester()
{
}
void SetAssetReady(Data::Asset<StreamingImageAsset>& asset) override
{
asset->SetReady();
}
};
class ImageMipChainAssetTester
: public UnitTest::AssetTester<ImageMipChainAsset>
{
public:
ImageMipChainAssetTester() {}
void SetAssetReady(Data::Asset<ImageMipChainAsset>& asset) override
{
asset->SetReady();
}
};
class StreamingImagePoolAssetTester
: public UnitTest::SerializeTester<StreamingImagePoolAsset>
{
using Base = UnitTest::SerializeTester<StreamingImagePoolAsset>;
public:
StreamingImagePoolAssetTester(AZ::SerializeContext* serializeContext)
: Base(serializeContext)
{}
AZ::Data::Asset<StreamingImagePoolAsset> SerializeInHelper(const AZ::Data::AssetId& assetId)
{
AZ::Data::Asset<StreamingImagePoolAsset> asset = Base::SerializeIn(assetId);
asset->SetReady();
return asset;
}
};
}
}
namespace UnitTest
{
struct TestStreamingImagePoolDescriptor
: public AZ::RHI::StreamingImagePoolDescriptor
{
AZ_CLASS_ALLOCATOR(TestStreamingImagePoolDescriptor, AZ::SystemAllocator, 0);
AZ_RTTI(TestStreamingImagePoolDescriptor, "{8D0CA5A2-F886-42EF-9B00-09E6C9F6B90B}", AZ::RHI::StreamingImagePoolDescriptor);
static constexpr uint32_t Magic = 0x1234;
static void Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<TestStreamingImagePoolDescriptor, AZ::RHI::StreamingImagePoolDescriptor>()
->Version(0)
->Field("m_magic", &TestStreamingImagePoolDescriptor::m_magic)
;
}
}
TestStreamingImagePoolDescriptor() = default;
TestStreamingImagePoolDescriptor(size_t budgetInBytes)
{
m_budgetInBytes = budgetInBytes;
}
// A test value to ensure that serialization occurred correctly.
uint32_t m_magic = Magic;
};
class TestStreamingImageContext
: public AZ::RPI::StreamingImageContext
, public AZStd::intrusive_list_node<TestStreamingImageContext>
{
public:
AZ_CLASS_ALLOCATOR(TestStreamingImageContext, AZ::SystemAllocator, 0);
AZ_RTTI(TestStreamingImageContext, "{E2FC3EB5-4F66-41D0-9ABE-6EDD2622DD88}", AZ::RPI::StreamingImageContext);
};
class TestStreamingImageController final
: public AZ::RPI::StreamingImageController
{
public:
AZ_CLASS_ALLOCATOR(TestStreamingImageController, AZ::SystemAllocator, 0);
AZ_RTTI(TestStreamingImageController, "{69D1A49C-B07E-4987-86D4-79C1F4E239B8}", AZ::RPI::StreamingImageController);
TestStreamingImageController() = default;
private:
AZ::RPI::StreamingImageContextPtr CreateContextInternal() override
{
return aznew TestStreamingImageContext();
}
void UpdateInternal(size_t timestamp, const StreamingImageContextList&) override
{
EXPECT_EQ(timestamp, m_expectedTimestamp);
m_expectedTimestamp++;
}
size_t m_expectedTimestamp = 0;
};
class StreamingImageTests
: public RPITestFixture
{
private:
AZ::Data::Asset<AZ::RPI::DefaultStreamingImageControllerAsset> m_testControllerAsset;
protected:
AZ::RPI::BuiltInAssetHandler* m_testControllerAssetHandler;
AZ::Data::AssetId m_testControllerAssetId;
AZ::Data::AssetHandler* m_imageHandler = nullptr;
AZ::Data::AssetHandler* m_mipChainHandler = nullptr;
AZ::Data::Instance<AZ::RPI::StreamingImagePool> m_defaultPool = nullptr;
StreamingImageTests()
: m_testControllerAssetId(AZ::RPI::DefaultStreamingImageControllerAsset::BuiltInAssetId)
{}
void SetUp() override
{
using namespace AZ;
RPITestFixture::SetUp();
auto* serializeContext = GetSerializeContext();
TestStreamingImagePoolDescriptor::Reflect(serializeContext);
m_imageHandler = Data::AssetManager::Instance().GetHandler(RPI::StreamingImageAsset::RTTI_Type());
m_mipChainHandler = Data::AssetManager::Instance().GetHandler(RPI::ImageMipChainAsset::RTTI_Type());
AZ::Data::Asset<AZ::RPI::StreamingImagePoolAsset> poolAsset = BuildImagePoolAsset(16 * 1024 * 1024);
m_defaultPool = AZ::RPI::StreamingImagePool::FindOrCreate(poolAsset);
}
void TearDown() override
{
using namespace AZ;
RHI::ResourceInvalidateBus::ExecuteQueuedEvents();
m_defaultPool = nullptr;
RPITestFixture::TearDown();
}
AZStd::vector<uint8_t> BuildImageData(uint32_t width, uint32_t height, uint32_t pixelSize)
{
const size_t imageSize = width * height * pixelSize;
AZStd::vector<uint8_t> image;
image.reserve(imageSize);
uint8_t testValue = 0;
for (uint32_t y = 0; y < height; ++y)
{
for (uint32_t x = 0; x < width; ++x)
{
for (uint32_t channel = 0; channel < pixelSize; ++channel)
{
image.push_back(testValue);
testValue++;
}
}
}
EXPECT_EQ(image.size(), imageSize);
return image;
}
void ValidateImageData(AZStd::array_view<uint8_t> data, const AZ::RHI::ImageSubresourceLayout& layout)
{
const uint32_t pixelSize = layout.m_size.m_width / layout.m_bytesPerRow;
uint32_t byteOffset = 0;
for (uint32_t y = 0; y < layout.m_size.m_height; ++y)
{
for (uint32_t x = 0; x < layout.m_size.m_width; ++x)
{
for (uint32_t channel = 0; channel < pixelSize; ++channel)
{
uint8_t value = data[byteOffset];
EXPECT_EQ(value, static_cast<uint8_t>(byteOffset));
byteOffset++;
}
}
}
}
void ValidateMipChainAsset(
AZ::RPI::ImageMipChainAsset* mipChain,
uint16_t expectedMipLevels,
uint16_t expectedArraySize,
uint32_t expectedPixelSize)
{
using namespace AZ;
EXPECT_NE(mipChain, nullptr);
EXPECT_EQ(expectedMipLevels, mipChain->GetMipLevelCount());
EXPECT_EQ(expectedArraySize, mipChain->GetArraySize());
EXPECT_EQ(expectedMipLevels * expectedArraySize, mipChain->GetSubImageCount());
const uint32_t imageSize = 1 << mipChain->GetMipLevelCount();
for (uint16_t mipLevel = 0; mipLevel < mipChain->GetMipLevelCount(); ++mipLevel)
{
RHI::ImageSubresourceLayout layout = BuildSubImageLayout(imageSize >> mipLevel, expectedPixelSize);
EXPECT_EQ(memcmp(&layout, &mipChain->GetSubImageLayout(mipLevel), sizeof(RHI::ImageSubresourceLayout)), 0);
for (uint16_t arrayIndex = 0; arrayIndex < mipChain->GetArraySize(); ++arrayIndex)
{
AZStd::array_view<uint8_t> imageData = mipChain->GetSubImageData(mipLevel, arrayIndex);
ValidateImageData(imageData, layout);
}
}
}
void ValidateImageAsset(AZ::RPI::StreamingImageAsset* imageAsset)
{
using namespace AZ;
EXPECT_NE(imageAsset, nullptr);
RHI::ImageDescriptor imageDesc = imageAsset->GetImageDescriptor();
size_t mipCountTotal = 0;
for (size_t i = 0; i < imageAsset->GetMipChainCount(); ++i)
{
// The last mip chain asset (tail mip chain) is expected to be empty since the actual mip chain asset data is embedded in StreamingImageAsset
if (i != imageAsset->GetMipChainCount() - 1)
{
EXPECT_TRUE(imageAsset->GetMipChainAsset(i).GetId().IsValid());
}
EXPECT_EQ(imageAsset->GetMipLevel(i), mipCountTotal);
mipCountTotal += imageAsset->GetMipCount(i);
}
EXPECT_EQ(imageDesc.m_mipLevels, mipCountTotal);
}
void ValidateImagePoolAsset(AZ::RPI::StreamingImagePoolAsset* poolAsset, size_t budgetInBytes)
{
using namespace AZ;
EXPECT_EQ(poolAsset->GetPoolDescriptor().m_budgetInBytes, budgetInBytes);
{
const auto* desc = azrtti_cast<const TestStreamingImagePoolDescriptor*>(&poolAsset->GetPoolDescriptor());
EXPECT_NE(desc, nullptr);
EXPECT_EQ(desc->m_magic, UnitTest::TestStreamingImagePoolDescriptor::Magic);
}
{
Data::Asset<RPI::StreamingImageControllerAsset> asset = poolAsset->GetControllerAsset();
EXPECT_TRUE(azrtti_typeid<RPI::DefaultStreamingImageControllerAsset>() == asset.GetType());
}
}
void ValidateImageResidency(AZ::RPI::StreamingImage* imageInstance, AZ::RPI::StreamingImageAsset* imageAsset)
{
using namespace AZ;
auto imageSystem = RPI::ImageSystemInterface::Get();
const size_t mipChainTailIndex = imageAsset->GetMipChainCount() - 1;
RHI::Ptr<RHI::Image> rhiImage = imageInstance->GetRHIImage();
// This should no-op.
imageInstance->TrimToMipChainLevel(mipChainTailIndex);
// Validate that nothing was actually evicted, since we've set to NoEvict.
for (size_t i = 0; i < mipChainTailIndex; ++i)
{
EXPECT_TRUE(imageAsset->GetMipChainAsset(i).IsReady());
}
EXPECT_EQ(rhiImage->GetResidentMipLevel(), imageAsset->GetMipLevel(mipChainTailIndex));
// Expand to the most detailed mip chain.
imageInstance->QueueExpandToMipChainLevel(0);
// We should still be the same residency level, since it's queued.
EXPECT_EQ(rhiImage->GetResidentMipLevel(), imageAsset->GetMipLevel(mipChainTailIndex));
// Tick the streaming system.
imageSystem->Update();
// Now we should be at the desired residency level.
EXPECT_EQ(rhiImage->GetResidentMipLevel(), 0);
// Expanding 'down' is a no-op.
imageInstance->QueueExpandToMipChainLevel(1);
imageSystem->Update();
EXPECT_EQ(rhiImage->GetResidentMipLevel(), 0);
// Trimming down a notch. This happens instantly.
imageInstance->TrimToMipChainLevel(1);
EXPECT_EQ(rhiImage->GetResidentMipLevel(), imageAsset->GetMipLevel(1));
// Trim down again.
imageInstance->TrimToMipChainLevel(2);
EXPECT_EQ(rhiImage->GetResidentMipLevel(), imageAsset->GetMipLevel(2));
// Expanding back up to 1.
imageInstance->QueueExpandToMipChainLevel(1);
imageSystem->Update();
EXPECT_EQ(rhiImage->GetResidentMipLevel(), imageAsset->GetMipLevel(1));
// Expanding back up to 0.
imageInstance->QueueExpandToMipChainLevel(0);
imageSystem->Update();
EXPECT_EQ(rhiImage->GetResidentMipLevel(), 0);
}
AZ::RHI::ImageSubresourceLayout BuildSubImageLayout(uint32_t imageSize, uint32_t pixelSize)
{
using namespace AZ;
RHI::ImageSubresourceLayout layout;
layout.m_size = RHI::Size{ imageSize, imageSize, 1 };
layout.m_rowCount = imageSize;
layout.m_bytesPerRow = imageSize * pixelSize;
layout.m_bytesPerImage = imageSize * imageSize * pixelSize;
return layout;
}
AZ::Data::Asset<AZ::RPI::ImageMipChainAsset> BuildMipChainAsset(uint16_t mipOffset, uint16_t mipLevels, uint16_t arraySize, uint32_t pixelSize)
{
using namespace AZ;
RPI::ImageMipChainAssetCreator assetCreator;
const uint32_t imageSize = 1 << (mipLevels + mipOffset);
assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
for (uint32_t mipLevel = 0; mipLevel < mipLevels; ++mipLevel)
{
const uint32_t mipSize = imageSize >> mipLevel;
RHI::ImageSubresourceLayout layout = BuildSubImageLayout(mipSize, pixelSize);
assetCreator.BeginMip(layout);
for (uint32_t arrayIndex = 0; arrayIndex < arraySize; ++arrayIndex)
{
AZStd::vector<uint8_t> data = BuildImageData(mipSize, mipSize, pixelSize);
assetCreator.AddSubImage(data.data(), data.size());
}
assetCreator.EndMip();
}
Data::Asset<RPI::ImageMipChainAsset> asset;
EXPECT_TRUE(assetCreator.End(asset));
EXPECT_TRUE(asset.IsReady());
EXPECT_NE(asset.Get(), nullptr);
return asset;
}
AZ::Data::Asset<AZ::RPI::StreamingImagePoolAsset> BuildImagePoolAsset(size_t budgetInBytes)
{
using namespace AZ;
RPI::StreamingImagePoolAssetCreator assetCreator;
assetCreator.Begin(Data::AssetId(Uuid::CreateRandom()));
assetCreator.SetPoolDescriptor(AZStd::make_unique<TestStreamingImagePoolDescriptor>(budgetInBytes));
assetCreator.SetControllerAsset(
Data::AssetManager::Instance().GetAsset<RPI::DefaultStreamingImageControllerAsset>(
m_testControllerAssetId,
Data::AssetLoadBehavior::PreLoad)
);
Data::Asset<RPI::StreamingImagePoolAsset> poolAsset;
EXPECT_TRUE(assetCreator.End(poolAsset));
EXPECT_TRUE(poolAsset.IsReady());
EXPECT_NE(poolAsset.Get(), nullptr);
return poolAsset;
}
AZ::Data::Asset<AZ::RPI::StreamingImageAsset> BuildTestImage()
{
using namespace AZ;
const uint32_t arraySize = 2;
const uint32_t pixelSize = 4;
const uint32_t mipCountHead = 1;
const uint32_t mipCountMiddle = 2;
const uint32_t mipCountTail = 3;
const uint32_t mipCountTotal = mipCountHead + mipCountMiddle + mipCountTail;
const uint32_t imageWidth = 1 << mipCountTotal;
const uint32_t imageHeight = 1 << mipCountTotal;
Data::Asset<RPI::ImageMipChainAsset> mipTail = BuildMipChainAsset(0, mipCountTail, arraySize, pixelSize);
Data::Asset<RPI::ImageMipChainAsset> mipMiddle = BuildMipChainAsset(mipCountTail, mipCountMiddle, arraySize, pixelSize);
Data::Asset<RPI::ImageMipChainAsset> mipHead = BuildMipChainAsset(mipCountTail + mipCountMiddle, mipCountHead, arraySize, pixelSize);
RPI::StreamingImageAssetCreator assetCreator;
assetCreator.Begin(Data::AssetId(Uuid::CreateRandom()));
RHI::ImageDescriptor imageDesc = RHI::ImageDescriptor::Create2DArray(RHI::ImageBindFlags::ShaderRead, imageWidth, imageHeight, arraySize, RHI::Format::R8G8B8A8_UNORM);
imageDesc.m_mipLevels = static_cast<uint16_t>(mipCountTotal);
assetCreator.SetImageDescriptor(imageDesc);
assetCreator.AddMipChainAsset(*mipHead.Get());
assetCreator.AddMipChainAsset(*mipMiddle.Get());
assetCreator.AddMipChainAsset(*mipTail.Get());
assetCreator.SetPoolAssetId(m_defaultPool->GetAssetId());
Data::Asset<RPI::StreamingImageAsset> imageAsset;
EXPECT_TRUE(assetCreator.End(imageAsset));
EXPECT_TRUE(imageAsset.IsReady());
EXPECT_NE(imageAsset.Get(), nullptr);
return imageAsset;
}
};
TEST_F(StreamingImageTests, MipChainCreate)
{
using namespace AZ;
const uint16_t mipLevels = 5;
const uint16_t arraySize = 4;
const uint16_t pixelSize = 4;
Data::Asset<RPI::ImageMipChainAsset> mipChain = BuildMipChainAsset(0, mipLevels, arraySize, pixelSize);
ValidateMipChainAsset(mipChain.Get(), mipLevels, arraySize, pixelSize);
}
TEST_F(StreamingImageTests, MipChainAssetSuccessAfterErrorCases)
{
using namespace AZ;
const uint16_t mipLevels = 1;
const uint16_t arraySize = 1;
Data::Asset<RPI::ImageMipChainAsset> mipChain;
{
RPI::ImageMipChainAssetCreator assetCreator;
ErrorMessageFinder messageFinder("Begin() was not called");
assetCreator.EndMip();
}
{
RPI::ImageMipChainAssetCreator assetCreator;
ErrorMessageFinder messageFinder("Begin() was not called");
assetCreator.End(mipChain);
}
{
RPI::ImageMipChainAssetCreator assetCreator;
assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
assetCreator.BeginMip(RHI::ImageSubresourceLayout());
ErrorMessageFinder messageFinder("Expected 1 sub-images in mip, but got 0.");
assetCreator.EndMip();
}
{
RPI::ImageMipChainAssetCreator assetCreator;
assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
assetCreator.BeginMip(RHI::ImageSubresourceLayout());
ErrorMessageFinder messageFinder("You must supply a valid data payload.");
assetCreator.AddSubImage(nullptr, 0);
}
{
RPI::ImageMipChainAssetCreator assetCreator;
assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
assetCreator.BeginMip(RHI::ImageSubresourceLayout());
ErrorMessageFinder messageFinder("You must supply a valid data payload.");
assetCreator.AddSubImage(nullptr, 10);
}
uint8_t data[4] = { 0, 5, 10, 15 };
const uint8_t dataSize = AZ_ARRAY_SIZE(data);
{
RPI::ImageMipChainAssetCreator assetCreator;
assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
assetCreator.BeginMip(RHI::ImageSubresourceLayout());
assetCreator.AddSubImage(data, dataSize);
ErrorMessageFinder messageFinder("Exceeded the 1 array slices declared in Begin().");
assetCreator.AddSubImage(data, dataSize);
}
{
RPI::ImageMipChainAssetCreator assetCreator;
assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
assetCreator.BeginMip(RHI::ImageSubresourceLayout());
assetCreator.AddSubImage(data, dataSize);
ErrorMessageFinder messageFinder("Already building a mip. You must call EndMip() first.");
assetCreator.BeginMip(RHI::ImageSubresourceLayout());
}
// Finally, build a valid one
{
RPI::ImageMipChainAssetCreator assetCreator;
assetCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()), mipLevels, arraySize);
assetCreator.BeginMip(RHI::ImageSubresourceLayout());
assetCreator.AddSubImage(data, dataSize);
assetCreator.EndMip();
EXPECT_TRUE(assetCreator.End(mipChain));
EXPECT_NE(mipChain.Get(), nullptr);
EXPECT_EQ(mipChain->GetMipLevelCount(), mipLevels);
EXPECT_EQ(mipChain->GetArraySize(), arraySize);
EXPECT_EQ(mipChain->GetSubImageCount(), mipLevels * arraySize);
AZStd::array_view<uint8_t> dataView = mipChain->GetSubImageData(0);
EXPECT_EQ(dataView[0], data[0]);
EXPECT_EQ(dataView[1], data[1]);
EXPECT_EQ(dataView[2], data[2]);
EXPECT_EQ(dataView[3], data[3]);
}
}
TEST_F(StreamingImageTests, MipChainAssetSerialize)
{
using namespace AZ;
const uint16_t mipLevels = 6;
const uint16_t arraySize = 2;
const uint16_t pixelSize = 2;
Data::Asset<RPI::ImageMipChainAsset> mipChain = BuildMipChainAsset(0, mipLevels, arraySize, pixelSize);
RPI::ImageMipChainAssetTester tester;
tester.SerializeOut(mipChain);
Data::Asset<RPI::ImageMipChainAsset> serializedMipChain = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
ValidateMipChainAsset(serializedMipChain.Get(), mipLevels, arraySize, pixelSize);
}
TEST_F(StreamingImageTests, PoolAssetCreation)
{
using namespace AZ;
const size_t budgetInBytes = 16 * 1024 * 1024;
Data::Asset<RPI::StreamingImagePoolAsset> poolAsset = BuildImagePoolAsset(budgetInBytes);
ValidateImagePoolAsset(poolAsset.Get(), budgetInBytes);
}
TEST_F(StreamingImageTests, PoolAssetSerialize)
{
using namespace AZ;
const size_t budgetInBytes = 16 * 1024 * 1024;
Data::Asset<RPI::StreamingImagePoolAsset> poolAsset = BuildImagePoolAsset(budgetInBytes);
RPI::StreamingImagePoolAssetTester tester(GetSerializeContext());
tester.SerializeOut(poolAsset.Get());
Data::Asset<RPI::StreamingImagePoolAsset> serializedPoolAsset = tester.SerializeInHelper(Data::AssetId(Uuid::CreateRandom()));
ValidateImagePoolAsset(serializedPoolAsset.Get(), budgetInBytes);
}
TEST_F(StreamingImageTests, PoolInstanceCreation)
{
using namespace AZ;
const size_t budgetInBytes = 16 * 1024 * 1024;
Data::Asset<RPI::StreamingImagePoolAsset> poolAsset = BuildImagePoolAsset(budgetInBytes);
Data::Instance<RPI::StreamingImagePool> poolInstance = RPI::StreamingImagePool::FindOrCreate(poolAsset);
EXPECT_NE(poolInstance.get(), nullptr);
EXPECT_NE(poolInstance->GetRHIPool(), nullptr);
}
TEST_F(StreamingImageTests, ImageAssetCreation)
{
using namespace AZ;
Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
ValidateImageAsset(imageAsset.Get());
}
TEST_F(StreamingImageTests, ImageAssetSerialize)
{
using namespace AZ;
Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
RPI::StreamingImageAssetTester tester;
tester.SerializeOut(imageAsset);
Data::Asset<RPI::StreamingImageAsset> serializedImageAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
ValidateImageAsset(serializedImageAsset.Get());
}
TEST_F(StreamingImageTests, ImageInstanceCreation)
{
using namespace AZ;
Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
Data::Instance<RPI::StreamingImage> imageInstance = RPI::StreamingImage::FindOrCreate(imageAsset);
EXPECT_NE(imageInstance.get(), nullptr);
EXPECT_NE(imageInstance->GetRHIImage(), nullptr);
EXPECT_NE(imageInstance->GetImageView(), nullptr);
EXPECT_GE(imageAsset->GetMipChainCount(), 0);
const size_t mipChainTailIndex = imageAsset->GetMipChainCount() - 1;
EXPECT_EQ(imageInstance->GetRHIImage()->GetResidentMipLevel(), imageAsset->GetMipLevel(mipChainTailIndex));
for (size_t i = 0; i < mipChainTailIndex; ++i)
{
Data::Asset<RPI::ImageMipChainAsset> mipChainAsset = imageAsset->GetMipChainAsset(i);
EXPECT_TRUE(mipChainAsset.IsReady());
}
}
TEST_F(StreamingImageTests, ImageInstanceResidency)
{
using namespace AZ;
Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
Data::Instance<RPI::StreamingImage> imageInstance = RPI::StreamingImage::FindOrCreate(imageAsset);
ValidateImageResidency(imageInstance.get(), imageAsset.Get());
}
TEST_F(StreamingImageTests, ImageInstanceResidencyWithSerialization)
{
using namespace AZ;
// Keep the original around, which holds references to the image mip chain assets and pool asset. We need to
// keep them in memory to avoid the asset manager trying to hit the catalog.
Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
RPI::StreamingImageAssetTester tester;
tester.SerializeOut(imageAsset);
Data::Asset<RPI::StreamingImageAsset> serializedImageAsset = tester.SerializeIn(Data::AssetId(Uuid::CreateRandom()));
Data::Instance<RPI::StreamingImage> imageInstance = RPI::StreamingImage::FindOrCreate(serializedImageAsset);
ValidateImageResidency(imageInstance.get(), imageAsset.Get());
}
TEST_F(StreamingImageTests, ImageInternalReferenceTracking)
{
using namespace AZ;
Data::Asset<RPI::StreamingImageAsset> imageAsset = BuildTestImage();
Data::Instance<RPI::StreamingImagePool> imagePoolInstance;
{
Data::Instance<RPI::StreamingImage> imageInstance = RPI::StreamingImage::FindOrCreate(imageAsset);
// Hold the pool instance to keep it around.
imagePoolInstance = imageInstance->GetPool();
// Tests that we can safely destroy an image after queueing something to the system,
// and the system will properly avoid touching that data.
imageInstance->QueueExpandToMipChainLevel(0);
}
RPI::ImageSystemInterface::Get()->Update();
}
}