670 lines
26 KiB
C++
670 lines
26 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <XCEngine/RHI/RHICommandList.h>
|
|
#include <XCEngine/RHI/RHIDevice.h>
|
|
#include <XCEngine/RHI/RHITexture.h>
|
|
#include <XCEngine/Rendering/Graph/RenderGraphCompiler.h>
|
|
#include <XCEngine/Rendering/Graph/RenderGraphExecutor.h>
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace XCEngine::Rendering;
|
|
using namespace XCEngine::RHI;
|
|
|
|
namespace {
|
|
|
|
struct MockTransientAllocationState {
|
|
int createTextureCalls = 0;
|
|
int shutdownTextureCalls = 0;
|
|
int destroyTextureCalls = 0;
|
|
int createRenderTargetViewCalls = 0;
|
|
int shutdownRenderTargetViewCalls = 0;
|
|
int destroyRenderTargetViewCalls = 0;
|
|
int createShaderViewCalls = 0;
|
|
int shutdownShaderViewCalls = 0;
|
|
int destroyShaderViewCalls = 0;
|
|
};
|
|
|
|
class MockTransientTexture final : public RHITexture {
|
|
public:
|
|
MockTransientTexture(
|
|
std::shared_ptr<MockTransientAllocationState> state,
|
|
uint32_t width,
|
|
uint32_t height,
|
|
Format format)
|
|
: m_state(std::move(state))
|
|
, m_width(width)
|
|
, m_height(height)
|
|
, m_format(format) {
|
|
}
|
|
|
|
~MockTransientTexture() override {
|
|
++m_state->destroyTextureCalls;
|
|
}
|
|
|
|
uint32_t GetWidth() const override { return m_width; }
|
|
uint32_t GetHeight() const override { return m_height; }
|
|
uint32_t GetDepth() const override { return 1u; }
|
|
uint32_t GetMipLevels() const override { return 1u; }
|
|
Format GetFormat() const override { return m_format; }
|
|
TextureType GetTextureType() const override { return TextureType::Texture2D; }
|
|
ResourceStates GetState() const override { return m_stateValue; }
|
|
void SetState(ResourceStates state) override { m_stateValue = state; }
|
|
void* GetNativeHandle() override { return nullptr; }
|
|
const std::string& GetName() const override { return m_name; }
|
|
void SetName(const std::string& name) override { m_name = name; }
|
|
|
|
void Shutdown() override {
|
|
++m_state->shutdownTextureCalls;
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockTransientAllocationState> m_state;
|
|
uint32_t m_width = 0u;
|
|
uint32_t m_height = 0u;
|
|
Format m_format = Format::Unknown;
|
|
ResourceStates m_stateValue = ResourceStates::Common;
|
|
std::string m_name;
|
|
};
|
|
|
|
class MockTransientView final : public RHIResourceView {
|
|
public:
|
|
MockTransientView(
|
|
std::shared_ptr<MockTransientAllocationState> state,
|
|
ResourceViewType viewType,
|
|
Format format)
|
|
: m_state(std::move(state))
|
|
, m_viewType(viewType)
|
|
, m_format(format) {
|
|
}
|
|
|
|
~MockTransientView() override {
|
|
if (m_viewType == ResourceViewType::RenderTarget) {
|
|
++m_state->destroyRenderTargetViewCalls;
|
|
} else {
|
|
++m_state->destroyShaderViewCalls;
|
|
}
|
|
}
|
|
|
|
void Shutdown() override {
|
|
if (m_viewType == ResourceViewType::RenderTarget) {
|
|
++m_state->shutdownRenderTargetViewCalls;
|
|
} else {
|
|
++m_state->shutdownShaderViewCalls;
|
|
}
|
|
}
|
|
|
|
void* GetNativeHandle() override { return nullptr; }
|
|
bool IsValid() const override { return true; }
|
|
ResourceViewType GetViewType() const override { return m_viewType; }
|
|
ResourceViewDimension GetDimension() const override { return ResourceViewDimension::Texture2D; }
|
|
Format GetFormat() const override { return m_format; }
|
|
|
|
private:
|
|
std::shared_ptr<MockTransientAllocationState> m_state;
|
|
ResourceViewType m_viewType = ResourceViewType::ShaderResource;
|
|
Format m_format = Format::Unknown;
|
|
};
|
|
|
|
class MockImportedView final : public RHIResourceView {
|
|
public:
|
|
void Shutdown() override {}
|
|
void* GetNativeHandle() override { return nullptr; }
|
|
bool IsValid() const override { return true; }
|
|
ResourceViewType GetViewType() const override { return ResourceViewType::RenderTarget; }
|
|
ResourceViewDimension GetDimension() const override { return ResourceViewDimension::Texture2D; }
|
|
Format GetFormat() const override { return Format::R8G8B8A8_UNorm; }
|
|
};
|
|
|
|
class MockTransientDevice final : public RHIDevice {
|
|
public:
|
|
explicit MockTransientDevice(std::shared_ptr<MockTransientAllocationState> state)
|
|
: m_state(std::move(state)) {
|
|
}
|
|
|
|
bool Initialize(const RHIDeviceDesc&) override { return true; }
|
|
void Shutdown() override {}
|
|
|
|
RHIBuffer* CreateBuffer(const BufferDesc&) override { return nullptr; }
|
|
|
|
RHITexture* CreateTexture(const TextureDesc& desc) override {
|
|
++m_state->createTextureCalls;
|
|
return new MockTransientTexture(
|
|
m_state,
|
|
desc.width,
|
|
desc.height,
|
|
static_cast<Format>(desc.format));
|
|
}
|
|
|
|
RHITexture* CreateTexture(
|
|
const TextureDesc& desc,
|
|
const void*,
|
|
size_t,
|
|
uint32_t) override {
|
|
return CreateTexture(desc);
|
|
}
|
|
|
|
RHISwapChain* CreateSwapChain(const SwapChainDesc&, RHICommandQueue*) override { return nullptr; }
|
|
RHICommandList* CreateCommandList(const CommandListDesc&) override { return nullptr; }
|
|
RHICommandQueue* CreateCommandQueue(const CommandQueueDesc&) override { return nullptr; }
|
|
RHIShader* CreateShader(const ShaderCompileDesc&) override { return nullptr; }
|
|
RHIPipelineState* CreatePipelineState(const GraphicsPipelineDesc&) override { return nullptr; }
|
|
RHIPipelineLayout* CreatePipelineLayout(const RHIPipelineLayoutDesc&) override { return nullptr; }
|
|
RHIFence* CreateFence(const FenceDesc&) override { return nullptr; }
|
|
RHISampler* CreateSampler(const SamplerDesc&) override { return nullptr; }
|
|
RHIRenderPass* CreateRenderPass(uint32_t, const AttachmentDesc*, const AttachmentDesc*) override {
|
|
return nullptr;
|
|
}
|
|
RHIFramebuffer* CreateFramebuffer(
|
|
RHIRenderPass*,
|
|
uint32_t,
|
|
uint32_t,
|
|
uint32_t,
|
|
RHIResourceView**,
|
|
RHIResourceView*) override {
|
|
return nullptr;
|
|
}
|
|
RHIDescriptorPool* CreateDescriptorPool(const DescriptorPoolDesc&) override { return nullptr; }
|
|
RHIDescriptorSet* CreateDescriptorSet(RHIDescriptorPool*, const DescriptorSetLayoutDesc&) override {
|
|
return nullptr;
|
|
}
|
|
RHIResourceView* CreateVertexBufferView(RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
|
|
RHIResourceView* CreateIndexBufferView(RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
|
|
|
|
RHIResourceView* CreateRenderTargetView(
|
|
RHITexture*,
|
|
const ResourceViewDesc& desc) override {
|
|
++m_state->createRenderTargetViewCalls;
|
|
return new MockTransientView(
|
|
m_state,
|
|
ResourceViewType::RenderTarget,
|
|
static_cast<Format>(desc.format));
|
|
}
|
|
|
|
RHIResourceView* CreateDepthStencilView(RHITexture*, const ResourceViewDesc&) override { return nullptr; }
|
|
RHIResourceView* CreateShaderResourceView(RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
|
|
|
|
RHIResourceView* CreateShaderResourceView(
|
|
RHITexture*,
|
|
const ResourceViewDesc& desc) override {
|
|
++m_state->createShaderViewCalls;
|
|
return new MockTransientView(
|
|
m_state,
|
|
ResourceViewType::ShaderResource,
|
|
static_cast<Format>(desc.format));
|
|
}
|
|
|
|
RHIResourceView* CreateUnorderedAccessView(RHIBuffer*, const ResourceViewDesc&) override { return nullptr; }
|
|
RHIResourceView* CreateUnorderedAccessView(RHITexture*, const ResourceViewDesc&) override {
|
|
return nullptr;
|
|
}
|
|
|
|
const RHICapabilities& GetCapabilities() const override { return m_capabilities; }
|
|
const RHIDeviceInfo& GetDeviceInfo() const override { return m_deviceInfo; }
|
|
void* GetNativeDevice() override { return nullptr; }
|
|
|
|
private:
|
|
std::shared_ptr<MockTransientAllocationState> m_state;
|
|
RHICapabilities m_capabilities = {};
|
|
RHIDeviceInfo m_deviceInfo = {};
|
|
};
|
|
|
|
struct MockTransitionBarrierCall {
|
|
RHIResourceView* resource = nullptr;
|
|
ResourceStates before = ResourceStates::Common;
|
|
ResourceStates after = ResourceStates::Common;
|
|
};
|
|
|
|
class MockTransientCommandList final : public RHICommandList {
|
|
public:
|
|
void Shutdown() override {}
|
|
void Reset() override {}
|
|
void Close() override {}
|
|
|
|
void TransitionBarrier(
|
|
RHIResourceView* resource,
|
|
ResourceStates stateBefore,
|
|
ResourceStates stateAfter) override {
|
|
MockTransitionBarrierCall call = {};
|
|
call.resource = resource;
|
|
call.before = stateBefore;
|
|
call.after = stateAfter;
|
|
transitionCalls.push_back(call);
|
|
}
|
|
|
|
void BeginRenderPass(
|
|
RHIRenderPass*,
|
|
RHIFramebuffer*,
|
|
const Rect&,
|
|
uint32_t,
|
|
const ClearValue*) override {
|
|
}
|
|
|
|
void EndRenderPass() override {}
|
|
void SetShader(RHIShader*) override {}
|
|
void SetPipelineState(RHIPipelineState*) override {}
|
|
void SetGraphicsDescriptorSets(uint32_t, uint32_t, RHIDescriptorSet**, RHIPipelineLayout*) override {}
|
|
void SetComputeDescriptorSets(uint32_t, uint32_t, RHIDescriptorSet**, RHIPipelineLayout*) override {}
|
|
void SetPrimitiveTopology(PrimitiveTopology) override {}
|
|
void SetViewport(const Viewport&) override {}
|
|
void SetViewports(uint32_t, const Viewport*) override {}
|
|
void SetScissorRect(const Rect&) override {}
|
|
void SetScissorRects(uint32_t, const Rect*) override {}
|
|
void SetRenderTargets(uint32_t, RHIResourceView**, RHIResourceView*) override {}
|
|
void SetStencilRef(uint8_t) override {}
|
|
void SetBlendFactor(const float[4]) override {}
|
|
void SetVertexBuffers(uint32_t, uint32_t, RHIResourceView**, const uint64_t*, const uint32_t*) override {}
|
|
void SetIndexBuffer(RHIResourceView*, uint64_t) override {}
|
|
void Draw(uint32_t, uint32_t, uint32_t, uint32_t) override {}
|
|
void DrawIndexed(uint32_t, uint32_t, uint32_t, int32_t, uint32_t) override {}
|
|
void Clear(float, float, float, float, uint32_t) override {}
|
|
void ClearRenderTarget(RHIResourceView*, const float[4], uint32_t, const Rect*) override {}
|
|
void ClearDepthStencil(RHIResourceView*, float, uint8_t, uint32_t, const Rect*) override {}
|
|
void CopyResource(RHIResourceView*, RHIResourceView*) override {}
|
|
void Dispatch(uint32_t, uint32_t, uint32_t) override {}
|
|
|
|
std::vector<MockTransitionBarrierCall> transitionCalls = {};
|
|
};
|
|
|
|
RenderGraphTextureDesc BuildTestTextureDesc() {
|
|
RenderGraphTextureDesc desc = {};
|
|
desc.width = 1280u;
|
|
desc.height = 720u;
|
|
desc.format = static_cast<XCEngine::Core::uint32>(Format::R8G8B8A8_UNorm);
|
|
desc.textureType = static_cast<XCEngine::Core::uint32>(TextureType::Texture2D);
|
|
desc.sampleCount = 1u;
|
|
desc.sampleQuality = 0u;
|
|
return desc;
|
|
}
|
|
|
|
RenderGraphTextureDesc BuildTestDepthTextureDesc() {
|
|
RenderGraphTextureDesc desc = {};
|
|
desc.width = 1024u;
|
|
desc.height = 1024u;
|
|
desc.format = static_cast<XCEngine::Core::uint32>(Format::D24_UNorm_S8_UInt);
|
|
desc.textureType = static_cast<XCEngine::Core::uint32>(TextureType::Texture2D);
|
|
desc.sampleCount = 1u;
|
|
desc.sampleQuality = 0u;
|
|
return desc;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(RenderGraph_Test, CompilesDeclaredPassOrderAndTracksTextureLifetimes) {
|
|
RenderGraph graph;
|
|
RenderGraphBuilder builder(graph);
|
|
|
|
const RenderGraphTextureDesc desc = BuildTestTextureDesc();
|
|
const RenderGraphTextureHandle sceneColor = builder.CreateTransientTexture("SceneColor", desc);
|
|
const RenderGraphTextureHandle bloomColor = builder.CreateTransientTexture("BloomColor", desc);
|
|
const RenderGraphTextureHandle backBuffer = builder.ImportTexture("BackBuffer", desc);
|
|
|
|
builder.AddRasterPass(
|
|
"Opaque",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.WriteTexture(sceneColor);
|
|
});
|
|
|
|
builder.AddComputePass(
|
|
"Bloom",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.ReadTexture(sceneColor);
|
|
pass.WriteTexture(bloomColor);
|
|
});
|
|
|
|
builder.AddRasterPass(
|
|
"Final",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.ReadTexture(bloomColor);
|
|
pass.WriteTexture(backBuffer);
|
|
});
|
|
|
|
CompiledRenderGraph compiledGraph;
|
|
XCEngine::Containers::String errorMessage;
|
|
ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage))
|
|
<< errorMessage.CStr();
|
|
|
|
ASSERT_EQ(compiledGraph.GetPassCount(), 3u);
|
|
EXPECT_EQ(compiledGraph.GetPassName(0u), "Opaque");
|
|
EXPECT_EQ(compiledGraph.GetPassName(1u), "Bloom");
|
|
EXPECT_EQ(compiledGraph.GetPassName(2u), "Final");
|
|
EXPECT_EQ(compiledGraph.GetPassType(0u), RenderGraphPassType::Raster);
|
|
EXPECT_EQ(compiledGraph.GetPassType(1u), RenderGraphPassType::Compute);
|
|
|
|
RenderGraphTextureLifetime lifetime = {};
|
|
ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(sceneColor, lifetime));
|
|
EXPECT_TRUE(lifetime.used);
|
|
EXPECT_EQ(lifetime.kind, RenderGraphTextureKind::Transient);
|
|
EXPECT_EQ(lifetime.firstPassIndex, 0u);
|
|
EXPECT_EQ(lifetime.lastPassIndex, 1u);
|
|
|
|
ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(bloomColor, lifetime));
|
|
EXPECT_TRUE(lifetime.used);
|
|
EXPECT_EQ(lifetime.firstPassIndex, 1u);
|
|
EXPECT_EQ(lifetime.lastPassIndex, 2u);
|
|
|
|
ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(backBuffer, lifetime));
|
|
EXPECT_TRUE(lifetime.used);
|
|
EXPECT_EQ(lifetime.kind, RenderGraphTextureKind::Imported);
|
|
EXPECT_EQ(lifetime.firstPassIndex, 2u);
|
|
EXPECT_EQ(lifetime.lastPassIndex, 2u);
|
|
}
|
|
|
|
TEST(RenderGraph_Test, OrdersImportedTextureHazardsAcrossFullscreenStyleChain) {
|
|
RenderGraph graph;
|
|
RenderGraphBuilder builder(graph);
|
|
|
|
const RenderGraphTextureDesc desc = BuildTestTextureDesc();
|
|
const RenderGraphTextureHandle sceneColor = builder.ImportTexture(
|
|
"SceneColor",
|
|
desc,
|
|
reinterpret_cast<RHIResourceView*>(1));
|
|
const RenderGraphTextureHandle postColor = builder.ImportTexture(
|
|
"PostColor",
|
|
desc,
|
|
reinterpret_cast<RHIResourceView*>(2));
|
|
|
|
builder.AddRasterPass(
|
|
"MainScene",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.WriteTexture(sceneColor);
|
|
});
|
|
|
|
builder.AddRasterPass(
|
|
"PostProcess",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.ReadTexture(sceneColor);
|
|
pass.WriteTexture(postColor);
|
|
});
|
|
|
|
builder.AddRasterPass(
|
|
"FinalOutput",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.ReadTexture(postColor);
|
|
pass.WriteTexture(sceneColor);
|
|
});
|
|
|
|
CompiledRenderGraph compiledGraph;
|
|
XCEngine::Containers::String errorMessage;
|
|
ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage))
|
|
<< errorMessage.CStr();
|
|
|
|
ASSERT_EQ(compiledGraph.GetPassCount(), 3u);
|
|
EXPECT_EQ(compiledGraph.GetPassName(0u), "MainScene");
|
|
EXPECT_EQ(compiledGraph.GetPassName(1u), "PostProcess");
|
|
EXPECT_EQ(compiledGraph.GetPassName(2u), "FinalOutput");
|
|
|
|
RenderGraphTextureLifetime lifetime = {};
|
|
ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(sceneColor, lifetime));
|
|
EXPECT_EQ(lifetime.kind, RenderGraphTextureKind::Imported);
|
|
EXPECT_EQ(lifetime.firstPassIndex, 0u);
|
|
EXPECT_EQ(lifetime.lastPassIndex, 2u);
|
|
|
|
ASSERT_TRUE(compiledGraph.TryGetTextureLifetime(postColor, lifetime));
|
|
EXPECT_EQ(lifetime.kind, RenderGraphTextureKind::Imported);
|
|
EXPECT_EQ(lifetime.firstPassIndex, 1u);
|
|
EXPECT_EQ(lifetime.lastPassIndex, 2u);
|
|
}
|
|
|
|
TEST(RenderGraph_Test, PreservesImportedTextureStateContractAcrossCompile) {
|
|
RenderGraph graph;
|
|
RenderGraphBuilder builder(graph);
|
|
|
|
const RenderGraphTextureDesc desc = BuildTestTextureDesc();
|
|
RenderGraphImportedTextureOptions importedOptions = {};
|
|
importedOptions.initialState = ResourceStates::Present;
|
|
importedOptions.finalState = ResourceStates::PixelShaderResource;
|
|
importedOptions.graphOwnsTransitions = true;
|
|
|
|
const RenderGraphTextureHandle importedTexture = builder.ImportTexture(
|
|
"ImportedColor",
|
|
desc,
|
|
reinterpret_cast<RHIResourceView*>(7),
|
|
importedOptions);
|
|
|
|
builder.AddRasterPass(
|
|
"SampleImported",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.ReadTexture(importedTexture);
|
|
});
|
|
|
|
CompiledRenderGraph compiledGraph;
|
|
XCEngine::Containers::String errorMessage;
|
|
ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage))
|
|
<< errorMessage.CStr();
|
|
|
|
RenderGraphImportedTextureOptions resolvedOptions = {};
|
|
ASSERT_TRUE(compiledGraph.TryGetImportedTextureOptions(importedTexture, resolvedOptions));
|
|
EXPECT_EQ(resolvedOptions.initialState, ResourceStates::Present);
|
|
EXPECT_EQ(resolvedOptions.finalState, ResourceStates::PixelShaderResource);
|
|
EXPECT_TRUE(resolvedOptions.graphOwnsTransitions);
|
|
|
|
RenderGraphTextureTransitionPlan transitionPlan = {};
|
|
ASSERT_TRUE(compiledGraph.TryGetTextureTransitionPlan(importedTexture, transitionPlan));
|
|
EXPECT_TRUE(transitionPlan.graphOwnsTransitions);
|
|
EXPECT_TRUE(transitionPlan.hasFirstAccessState);
|
|
EXPECT_TRUE(transitionPlan.hasLastAccessState);
|
|
EXPECT_EQ(transitionPlan.initialState, ResourceStates::Present);
|
|
EXPECT_EQ(transitionPlan.firstAccessState, ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(transitionPlan.lastAccessState, ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(transitionPlan.finalState, ResourceStates::PixelShaderResource);
|
|
}
|
|
|
|
TEST(RenderGraph_Test, ExecutesGraphOwnedImportedTextureTransitionsAtGraphBoundaries) {
|
|
RenderGraph graph;
|
|
RenderGraphBuilder builder(graph);
|
|
|
|
const RenderGraphTextureDesc desc = BuildTestTextureDesc();
|
|
RenderGraphImportedTextureOptions importedOptions = {};
|
|
importedOptions.initialState = ResourceStates::Present;
|
|
importedOptions.finalState = ResourceStates::Present;
|
|
importedOptions.graphOwnsTransitions = true;
|
|
|
|
MockImportedView importedView;
|
|
const RenderGraphTextureHandle backBuffer = builder.ImportTexture(
|
|
"BackBuffer",
|
|
desc,
|
|
&importedView,
|
|
importedOptions);
|
|
|
|
builder.AddRasterPass(
|
|
"FinalBlit",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.WriteTexture(backBuffer);
|
|
});
|
|
|
|
CompiledRenderGraph compiledGraph;
|
|
XCEngine::Containers::String errorMessage;
|
|
ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage))
|
|
<< errorMessage.CStr();
|
|
|
|
MockTransientCommandList commandList;
|
|
RenderContext renderContext = {};
|
|
renderContext.device = reinterpret_cast<RHIDevice*>(1);
|
|
renderContext.commandList = &commandList;
|
|
renderContext.commandQueue = reinterpret_cast<RHICommandQueue*>(1);
|
|
ASSERT_TRUE(RenderGraphExecutor::Execute(compiledGraph, renderContext, &errorMessage))
|
|
<< errorMessage.CStr();
|
|
|
|
ASSERT_EQ(commandList.transitionCalls.size(), 2u);
|
|
EXPECT_EQ(commandList.transitionCalls[0].resource, &importedView);
|
|
EXPECT_EQ(commandList.transitionCalls[0].before, ResourceStates::Present);
|
|
EXPECT_EQ(commandList.transitionCalls[0].after, ResourceStates::RenderTarget);
|
|
EXPECT_EQ(commandList.transitionCalls[1].resource, &importedView);
|
|
EXPECT_EQ(commandList.transitionCalls[1].before, ResourceStates::RenderTarget);
|
|
EXPECT_EQ(commandList.transitionCalls[1].after, ResourceStates::Present);
|
|
}
|
|
|
|
TEST(RenderGraph_Test, RejectsTransientTextureReadBeforeWrite) {
|
|
RenderGraph graph;
|
|
RenderGraphBuilder builder(graph);
|
|
|
|
const RenderGraphTextureDesc desc = BuildTestTextureDesc();
|
|
const RenderGraphTextureHandle sceneColor = builder.CreateTransientTexture("SceneColor", desc);
|
|
|
|
builder.AddRasterPass(
|
|
"InvalidRead",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.ReadTexture(sceneColor);
|
|
});
|
|
|
|
CompiledRenderGraph compiledGraph;
|
|
XCEngine::Containers::String errorMessage;
|
|
EXPECT_FALSE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage));
|
|
EXPECT_FALSE(errorMessage.Empty());
|
|
}
|
|
|
|
TEST(RenderGraph_Test, RejectsGraphOwnedImportedTextureWithoutView) {
|
|
RenderGraph graph;
|
|
RenderGraphBuilder builder(graph);
|
|
|
|
const RenderGraphTextureDesc desc = BuildTestTextureDesc();
|
|
RenderGraphImportedTextureOptions importedOptions = {};
|
|
importedOptions.initialState = ResourceStates::Present;
|
|
importedOptions.finalState = ResourceStates::Present;
|
|
importedOptions.graphOwnsTransitions = true;
|
|
|
|
const RenderGraphTextureHandle importedTexture = builder.ImportTexture(
|
|
"BackBuffer",
|
|
desc,
|
|
nullptr,
|
|
importedOptions);
|
|
|
|
builder.AddRasterPass(
|
|
"FinalBlit",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.WriteTexture(importedTexture);
|
|
});
|
|
|
|
CompiledRenderGraph compiledGraph;
|
|
XCEngine::Containers::String errorMessage;
|
|
EXPECT_FALSE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage));
|
|
EXPECT_FALSE(errorMessage.Empty());
|
|
}
|
|
|
|
TEST(RenderGraph_Test, TracksDepthAttachmentTransitionPlan) {
|
|
RenderGraph graph;
|
|
RenderGraphBuilder builder(graph);
|
|
|
|
const RenderGraphTextureDesc depthDesc = BuildTestDepthTextureDesc();
|
|
const RenderGraphTextureHandle depthTexture =
|
|
builder.CreateTransientTexture("SceneDepth", depthDesc);
|
|
|
|
builder.AddRasterPass(
|
|
"DepthPrepass",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.WriteDepthTexture(depthTexture);
|
|
});
|
|
|
|
CompiledRenderGraph compiledGraph;
|
|
XCEngine::Containers::String errorMessage;
|
|
ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage))
|
|
<< errorMessage.CStr();
|
|
|
|
RenderGraphTextureTransitionPlan transitionPlan = {};
|
|
ASSERT_TRUE(compiledGraph.TryGetTextureTransitionPlan(depthTexture, transitionPlan));
|
|
EXPECT_TRUE(transitionPlan.graphOwnsTransitions);
|
|
EXPECT_TRUE(transitionPlan.hasFirstAccessState);
|
|
EXPECT_TRUE(transitionPlan.hasLastAccessState);
|
|
EXPECT_EQ(transitionPlan.initialState, ResourceStates::Common);
|
|
EXPECT_EQ(transitionPlan.firstAccessState, ResourceStates::DepthWrite);
|
|
EXPECT_EQ(transitionPlan.lastAccessState, ResourceStates::DepthWrite);
|
|
EXPECT_EQ(transitionPlan.finalState, ResourceStates::DepthWrite);
|
|
}
|
|
|
|
TEST(RenderGraph_Test, ExecutesCompiledPassCallbacksInCompiledOrder) {
|
|
RenderGraph graph;
|
|
RenderGraphBuilder builder(graph);
|
|
|
|
const RenderGraphTextureDesc desc = BuildTestTextureDesc();
|
|
const RenderGraphTextureHandle sceneColor = builder.CreateTransientTexture("SceneColor", desc);
|
|
const RenderGraphTextureHandle backBuffer = builder.ImportTexture(
|
|
"BackBuffer",
|
|
desc,
|
|
reinterpret_cast<RHIResourceView*>(1));
|
|
|
|
std::vector<std::string> eventLog;
|
|
auto allocationState = std::make_shared<MockTransientAllocationState>();
|
|
MockTransientDevice device(allocationState);
|
|
MockTransientCommandList commandList;
|
|
RHIResourceView* opaqueTargetView = nullptr;
|
|
RHIResourceView* finalSourceView = nullptr;
|
|
|
|
builder.AddRasterPass(
|
|
"Opaque",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.WriteTexture(sceneColor);
|
|
pass.SetExecuteCallback(
|
|
[&](const RenderGraphExecutionContext& executionContext) {
|
|
EXPECT_TRUE(executionContext.IsTransientTexture(sceneColor));
|
|
EXPECT_NE(
|
|
executionContext.ResolveTextureView(
|
|
sceneColor,
|
|
RenderGraphTextureViewType::RenderTarget),
|
|
nullptr);
|
|
opaqueTargetView =
|
|
executionContext.ResolveTextureView(
|
|
sceneColor,
|
|
RenderGraphTextureViewType::RenderTarget);
|
|
eventLog.push_back("Opaque");
|
|
});
|
|
});
|
|
|
|
builder.AddRasterPass(
|
|
"Final",
|
|
[&](RenderGraphPassBuilder& pass) {
|
|
pass.ReadTexture(sceneColor);
|
|
pass.WriteTexture(backBuffer);
|
|
pass.SetExecuteCallback(
|
|
[&](const RenderGraphExecutionContext& executionContext) {
|
|
RenderGraphTextureDesc resolvedDesc = {};
|
|
EXPECT_TRUE(executionContext.TryGetTextureDesc(sceneColor, resolvedDesc));
|
|
EXPECT_EQ(resolvedDesc.width, desc.width);
|
|
EXPECT_NE(
|
|
executionContext.ResolveTextureView(
|
|
sceneColor,
|
|
RenderGraphTextureViewType::ShaderResource),
|
|
nullptr);
|
|
finalSourceView =
|
|
executionContext.ResolveTextureView(
|
|
sceneColor,
|
|
RenderGraphTextureViewType::ShaderResource);
|
|
eventLog.push_back("Final");
|
|
});
|
|
});
|
|
|
|
CompiledRenderGraph compiledGraph;
|
|
XCEngine::Containers::String errorMessage;
|
|
ASSERT_TRUE(RenderGraphCompiler::Compile(graph, compiledGraph, &errorMessage))
|
|
<< errorMessage.CStr();
|
|
|
|
RenderContext renderContext = {};
|
|
renderContext.device = &device;
|
|
renderContext.commandList = &commandList;
|
|
renderContext.commandQueue = reinterpret_cast<RHICommandQueue*>(1);
|
|
ASSERT_TRUE(RenderGraphExecutor::Execute(compiledGraph, renderContext, &errorMessage))
|
|
<< errorMessage.CStr();
|
|
|
|
ASSERT_EQ(eventLog.size(), 2u);
|
|
EXPECT_EQ(eventLog[0], "Opaque");
|
|
EXPECT_EQ(eventLog[1], "Final");
|
|
ASSERT_EQ(commandList.transitionCalls.size(), 2u);
|
|
EXPECT_EQ(commandList.transitionCalls[0].resource, opaqueTargetView);
|
|
EXPECT_EQ(commandList.transitionCalls[0].before, ResourceStates::Common);
|
|
EXPECT_EQ(commandList.transitionCalls[0].after, ResourceStates::RenderTarget);
|
|
EXPECT_EQ(commandList.transitionCalls[1].resource, finalSourceView);
|
|
EXPECT_EQ(commandList.transitionCalls[1].before, ResourceStates::RenderTarget);
|
|
EXPECT_EQ(commandList.transitionCalls[1].after, ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(allocationState->createTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 1);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownRenderTargetViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyRenderTargetViewCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyShaderViewCalls, 1);
|
|
}
|