4505 lines
192 KiB
C++
4505 lines
192 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <XCEngine/Components/CameraComponent.h>
|
|
#include <XCEngine/Components/LightComponent.h>
|
|
#include <XCEngine/RHI/RHICommandList.h>
|
|
#include <XCEngine/RHI/RHICommandQueue.h>
|
|
#include <XCEngine/RHI/RHIDevice.h>
|
|
#include <XCEngine/RHI/RHIResourceView.h>
|
|
#include <XCEngine/RHI/RHITexture.h>
|
|
#include <XCEngine/Core/Math/Vector4.h>
|
|
#include <XCEngine/Rendering/Execution/CameraRenderer.h>
|
|
#include <XCEngine/Rendering/Execution/RenderPipelineHost.h>
|
|
#include <XCEngine/Rendering/Graph/RenderGraph.h>
|
|
#include <XCEngine/Rendering/Pipelines/ManagedScriptableRenderPipelineAsset.h>
|
|
#include <XCEngine/Rendering/Pipelines/ScriptableRenderPipelineHost.h>
|
|
#include <XCEngine/Rendering/RenderPassGraphContract.h>
|
|
#include <XCEngine/Rendering/RenderPipelineAsset.h>
|
|
#include <XCEngine/Rendering/RenderSurface.h>
|
|
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
|
|
#include <XCEngine/Resources/Material/Material.h>
|
|
#include <XCEngine/Resources/Shader/ShaderKeywordTypes.h>
|
|
#include <XCEngine/Scene/Scene.h>
|
|
|
|
#include "Rendering/Execution/Internal/CameraFrameGraph/SurfaceResolver.h"
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace XCEngine::Components;
|
|
using namespace XCEngine::Rendering;
|
|
|
|
namespace {
|
|
|
|
const RenderSurface* ResolveStageOutputSurface(
|
|
CameraFrameStage stage,
|
|
const CameraFramePlan& plan) {
|
|
return ResolveCameraFrameStageOutputSurface(
|
|
stage,
|
|
plan,
|
|
DirectionalShadowExecutionState{});
|
|
}
|
|
|
|
CameraFrameStageSourceBinding ResolveStageSourceBinding(
|
|
CameraFrameStage stage,
|
|
const CameraFramePlan& plan) {
|
|
return ResolveCameraFrameStageSourceBinding(stage, plan);
|
|
}
|
|
|
|
struct MockPipelineState {
|
|
int initializeCalls = 0;
|
|
int shutdownCalls = 0;
|
|
int configureRenderSceneDataCalls = 0;
|
|
int configureDirectionalShadowExecutionStateCalls = 0;
|
|
int renderCalls = 0;
|
|
int recordMainSceneCalls = 0;
|
|
int executeRecordedMainSceneCalls = 0;
|
|
bool renderResult = true;
|
|
bool supportsMainSceneRenderGraph = false;
|
|
bool recordMainSceneResult = true;
|
|
bool lastSurfaceAutoTransitionEnabled = true;
|
|
uint32_t lastSurfaceWidth = 0;
|
|
uint32_t lastSurfaceHeight = 0;
|
|
int32_t lastRenderAreaX = 0;
|
|
int32_t lastRenderAreaY = 0;
|
|
int32_t lastRenderAreaWidth = 0;
|
|
int32_t lastRenderAreaHeight = 0;
|
|
uint32_t lastCameraViewportWidth = 0;
|
|
uint32_t lastCameraViewportHeight = 0;
|
|
CameraComponent* lastCamera = nullptr;
|
|
size_t lastVisibleItemCount = 0;
|
|
RenderClearFlags lastClearFlags = RenderClearFlags::All;
|
|
XCEngine::Math::Color lastClearColor = XCEngine::Math::Color::Black();
|
|
bool lastHasMainDirectionalShadow = false;
|
|
XCEngine::RHI::RHIResourceView* lastShadowMap = nullptr;
|
|
XCEngine::Math::Matrix4x4 lastShadowViewProjection = XCEngine::Math::Matrix4x4::Identity();
|
|
RenderDirectionalShadowMapMetrics lastShadowMapMetrics = {};
|
|
RenderDirectionalShadowSamplingData lastShadowSampling = {};
|
|
bool lastHasMainDirectionalShadowKeyword = false;
|
|
RenderEnvironmentMode lastEnvironmentMode = RenderEnvironmentMode::None;
|
|
bool lastHasSkybox = false;
|
|
const XCEngine::Resources::Material* lastSkyboxMaterial = nullptr;
|
|
bool lastRecordedMainSceneShadowHandleValid = false;
|
|
bool lastReceivedRenderGraphBlackboard = false;
|
|
bool lastBlackboardMainSceneColorValid = false;
|
|
bool lastBlackboardMainSceneDepthValid = false;
|
|
bool lastBlackboardMainDirectionalShadowValid = false;
|
|
bool lastBlackboardObjectIdColorValid = false;
|
|
std::function<void(const CameraFramePlan&, RenderSceneData&)> configureRenderSceneData = {};
|
|
std::function<bool(
|
|
const CameraFramePlan&,
|
|
const DirectionalShadowSurfaceAllocation&,
|
|
DirectionalShadowExecutionState&)> configureDirectionalShadowExecutionState = {};
|
|
std::vector<CameraComponent*> renderedCameras;
|
|
std::vector<RenderClearFlags> renderedClearFlags;
|
|
std::vector<XCEngine::Math::Color> renderedClearColors;
|
|
std::vector<std::string> eventLog;
|
|
};
|
|
|
|
struct MockShadowAllocationState {
|
|
int createTextureCalls = 0;
|
|
int shutdownTextureCalls = 0;
|
|
int destroyTextureCalls = 0;
|
|
int createRenderTargetViewCalls = 0;
|
|
int shutdownRenderTargetViewCalls = 0;
|
|
int destroyRenderTargetViewCalls = 0;
|
|
int createDepthViewCalls = 0;
|
|
int shutdownDepthViewCalls = 0;
|
|
int destroyDepthViewCalls = 0;
|
|
int createShaderViewCalls = 0;
|
|
int shutdownShaderViewCalls = 0;
|
|
int destroyShaderViewCalls = 0;
|
|
uint32_t lastTextureWidth = 0;
|
|
uint32_t lastTextureHeight = 0;
|
|
XCEngine::RHI::Format lastTextureFormat = XCEngine::RHI::Format::Unknown;
|
|
XCEngine::RHI::Format lastRenderTargetViewFormat = XCEngine::RHI::Format::Unknown;
|
|
XCEngine::RHI::Format lastDepthViewFormat = XCEngine::RHI::Format::Unknown;
|
|
XCEngine::RHI::Format lastShaderViewFormat = XCEngine::RHI::Format::Unknown;
|
|
};
|
|
|
|
class MockShadowTexture final : public XCEngine::RHI::RHITexture {
|
|
public:
|
|
MockShadowTexture(
|
|
std::shared_ptr<MockShadowAllocationState> state,
|
|
uint32_t width,
|
|
uint32_t height,
|
|
XCEngine::RHI::Format format)
|
|
: m_state(std::move(state))
|
|
, m_width(width)
|
|
, m_height(height)
|
|
, m_format(format) {
|
|
}
|
|
|
|
~MockShadowTexture() 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 1; }
|
|
uint32_t GetMipLevels() const override { return 1; }
|
|
XCEngine::RHI::Format GetFormat() const override { return m_format; }
|
|
XCEngine::RHI::TextureType GetTextureType() const override { return XCEngine::RHI::TextureType::Texture2D; }
|
|
XCEngine::RHI::ResourceStates GetState() const override { return m_stateValue; }
|
|
void SetState(XCEngine::RHI::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<MockShadowAllocationState> m_state;
|
|
uint32_t m_width = 0;
|
|
uint32_t m_height = 0;
|
|
XCEngine::RHI::Format m_format = XCEngine::RHI::Format::Unknown;
|
|
XCEngine::RHI::ResourceStates m_stateValue = XCEngine::RHI::ResourceStates::DepthWrite;
|
|
std::string m_name;
|
|
};
|
|
|
|
class MockShadowView final : public XCEngine::RHI::RHIResourceView {
|
|
public:
|
|
MockShadowView(
|
|
std::shared_ptr<MockShadowAllocationState> state,
|
|
XCEngine::RHI::ResourceViewType viewType,
|
|
XCEngine::RHI::Format format,
|
|
XCEngine::RHI::ResourceViewDimension dimension)
|
|
: m_state(std::move(state))
|
|
, m_viewType(viewType)
|
|
, m_format(format)
|
|
, m_dimension(dimension) {
|
|
}
|
|
|
|
~MockShadowView() override {
|
|
if (m_viewType == XCEngine::RHI::ResourceViewType::ShaderResource) {
|
|
++m_state->destroyShaderViewCalls;
|
|
} else if (m_viewType == XCEngine::RHI::ResourceViewType::RenderTarget) {
|
|
++m_state->destroyRenderTargetViewCalls;
|
|
} else {
|
|
++m_state->destroyDepthViewCalls;
|
|
}
|
|
}
|
|
|
|
void Shutdown() override {
|
|
if (m_viewType == XCEngine::RHI::ResourceViewType::ShaderResource) {
|
|
++m_state->shutdownShaderViewCalls;
|
|
} else if (m_viewType == XCEngine::RHI::ResourceViewType::RenderTarget) {
|
|
++m_state->shutdownRenderTargetViewCalls;
|
|
} else {
|
|
++m_state->shutdownDepthViewCalls;
|
|
}
|
|
}
|
|
|
|
void* GetNativeHandle() override { return nullptr; }
|
|
bool IsValid() const override { return true; }
|
|
XCEngine::RHI::ResourceViewType GetViewType() const override { return m_viewType; }
|
|
XCEngine::RHI::ResourceViewDimension GetDimension() const override { return m_dimension; }
|
|
XCEngine::RHI::Format GetFormat() const override { return m_format; }
|
|
|
|
private:
|
|
std::shared_ptr<MockShadowAllocationState> m_state;
|
|
XCEngine::RHI::ResourceViewType m_viewType = XCEngine::RHI::ResourceViewType::DepthStencil;
|
|
XCEngine::RHI::Format m_format = XCEngine::RHI::Format::Unknown;
|
|
XCEngine::RHI::ResourceViewDimension m_dimension = XCEngine::RHI::ResourceViewDimension::Unknown;
|
|
};
|
|
|
|
class MockShadowDevice final : public XCEngine::RHI::RHIDevice {
|
|
public:
|
|
explicit MockShadowDevice(std::shared_ptr<MockShadowAllocationState> state)
|
|
: m_state(std::move(state)) {
|
|
}
|
|
|
|
bool Initialize(const XCEngine::RHI::RHIDeviceDesc&) override { return true; }
|
|
void Shutdown() override {}
|
|
|
|
XCEngine::RHI::RHIBuffer* CreateBuffer(const XCEngine::RHI::BufferDesc&) override { return nullptr; }
|
|
|
|
XCEngine::RHI::RHITexture* CreateTexture(const XCEngine::RHI::TextureDesc& desc) override {
|
|
++m_state->createTextureCalls;
|
|
m_state->lastTextureWidth = desc.width;
|
|
m_state->lastTextureHeight = desc.height;
|
|
m_state->lastTextureFormat = static_cast<XCEngine::RHI::Format>(desc.format);
|
|
return new MockShadowTexture(
|
|
m_state,
|
|
desc.width,
|
|
desc.height,
|
|
static_cast<XCEngine::RHI::Format>(desc.format));
|
|
}
|
|
|
|
XCEngine::RHI::RHITexture* CreateTexture(
|
|
const XCEngine::RHI::TextureDesc& desc,
|
|
const void*,
|
|
size_t,
|
|
uint32_t) override {
|
|
return CreateTexture(desc);
|
|
}
|
|
|
|
XCEngine::RHI::RHISwapChain* CreateSwapChain(
|
|
const XCEngine::RHI::SwapChainDesc&,
|
|
XCEngine::RHI::RHICommandQueue*) override { return nullptr; }
|
|
XCEngine::RHI::RHICommandList* CreateCommandList(const XCEngine::RHI::CommandListDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHICommandQueue* CreateCommandQueue(const XCEngine::RHI::CommandQueueDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHIShader* CreateShader(const XCEngine::RHI::ShaderCompileDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHIPipelineState* CreatePipelineState(const XCEngine::RHI::GraphicsPipelineDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHIPipelineLayout* CreatePipelineLayout(const XCEngine::RHI::RHIPipelineLayoutDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHIFence* CreateFence(const XCEngine::RHI::FenceDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHISampler* CreateSampler(const XCEngine::RHI::SamplerDesc&) override { return nullptr; }
|
|
|
|
XCEngine::RHI::RHIRenderPass* CreateRenderPass(
|
|
uint32_t,
|
|
const XCEngine::RHI::AttachmentDesc*,
|
|
const XCEngine::RHI::AttachmentDesc*) override { return nullptr; }
|
|
XCEngine::RHI::RHIFramebuffer* CreateFramebuffer(
|
|
XCEngine::RHI::RHIRenderPass*,
|
|
uint32_t,
|
|
uint32_t,
|
|
uint32_t,
|
|
XCEngine::RHI::RHIResourceView**,
|
|
XCEngine::RHI::RHIResourceView*) override { return nullptr; }
|
|
|
|
XCEngine::RHI::RHIDescriptorPool* CreateDescriptorPool(const XCEngine::RHI::DescriptorPoolDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHIDescriptorSet* CreateDescriptorSet(
|
|
XCEngine::RHI::RHIDescriptorPool*,
|
|
const XCEngine::RHI::DescriptorSetLayoutDesc&) override { return nullptr; }
|
|
|
|
XCEngine::RHI::RHIResourceView* CreateVertexBufferView(
|
|
XCEngine::RHI::RHIBuffer*,
|
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHIResourceView* CreateIndexBufferView(
|
|
XCEngine::RHI::RHIBuffer*,
|
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHIResourceView* CreateRenderTargetView(
|
|
XCEngine::RHI::RHITexture*,
|
|
const XCEngine::RHI::ResourceViewDesc& desc) override {
|
|
++m_state->createRenderTargetViewCalls;
|
|
m_state->lastRenderTargetViewFormat = static_cast<XCEngine::RHI::Format>(desc.format);
|
|
return new MockShadowView(
|
|
m_state,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
static_cast<XCEngine::RHI::Format>(desc.format),
|
|
desc.dimension);
|
|
}
|
|
|
|
XCEngine::RHI::RHIResourceView* CreateDepthStencilView(
|
|
XCEngine::RHI::RHITexture*,
|
|
const XCEngine::RHI::ResourceViewDesc& desc) override {
|
|
++m_state->createDepthViewCalls;
|
|
m_state->lastDepthViewFormat = static_cast<XCEngine::RHI::Format>(desc.format);
|
|
return new MockShadowView(
|
|
m_state,
|
|
XCEngine::RHI::ResourceViewType::DepthStencil,
|
|
static_cast<XCEngine::RHI::Format>(desc.format),
|
|
desc.dimension);
|
|
}
|
|
|
|
XCEngine::RHI::RHIResourceView* CreateShaderResourceView(
|
|
XCEngine::RHI::RHIBuffer*,
|
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHIResourceView* CreateShaderResourceView(
|
|
XCEngine::RHI::RHITexture*,
|
|
const XCEngine::RHI::ResourceViewDesc& desc) override {
|
|
++m_state->createShaderViewCalls;
|
|
m_state->lastShaderViewFormat = static_cast<XCEngine::RHI::Format>(desc.format);
|
|
return new MockShadowView(
|
|
m_state,
|
|
XCEngine::RHI::ResourceViewType::ShaderResource,
|
|
static_cast<XCEngine::RHI::Format>(desc.format),
|
|
desc.dimension);
|
|
}
|
|
XCEngine::RHI::RHIResourceView* CreateUnorderedAccessView(
|
|
XCEngine::RHI::RHIBuffer*,
|
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
|
XCEngine::RHI::RHIResourceView* CreateUnorderedAccessView(
|
|
XCEngine::RHI::RHITexture*,
|
|
const XCEngine::RHI::ResourceViewDesc&) override { return nullptr; }
|
|
|
|
const XCEngine::RHI::RHICapabilities& GetCapabilities() const override { return m_capabilities; }
|
|
const XCEngine::RHI::RHIDeviceInfo& GetDeviceInfo() const override { return m_deviceInfo; }
|
|
void* GetNativeDevice() override { return nullptr; }
|
|
|
|
private:
|
|
std::shared_ptr<MockShadowAllocationState> m_state;
|
|
XCEngine::RHI::RHICapabilities m_capabilities = {};
|
|
XCEngine::RHI::RHIDeviceInfo m_deviceInfo = {};
|
|
};
|
|
|
|
class MockNoopCommandList final : public XCEngine::RHI::RHICommandList {
|
|
public:
|
|
void Shutdown() override {}
|
|
void Reset() override {}
|
|
void Close() override {}
|
|
|
|
void TransitionBarrier(
|
|
XCEngine::RHI::RHIResourceView*,
|
|
XCEngine::RHI::ResourceStates,
|
|
XCEngine::RHI::ResourceStates) override {
|
|
}
|
|
|
|
void BeginRenderPass(
|
|
XCEngine::RHI::RHIRenderPass*,
|
|
XCEngine::RHI::RHIFramebuffer*,
|
|
const XCEngine::RHI::Rect&,
|
|
uint32_t,
|
|
const XCEngine::RHI::ClearValue*) override {
|
|
}
|
|
|
|
void EndRenderPass() override {}
|
|
void SetShader(XCEngine::RHI::RHIShader*) override {}
|
|
void SetPipelineState(XCEngine::RHI::RHIPipelineState*) override {}
|
|
void SetGraphicsDescriptorSets(
|
|
uint32_t,
|
|
uint32_t,
|
|
XCEngine::RHI::RHIDescriptorSet**,
|
|
XCEngine::RHI::RHIPipelineLayout*) override {
|
|
}
|
|
void SetComputeDescriptorSets(
|
|
uint32_t,
|
|
uint32_t,
|
|
XCEngine::RHI::RHIDescriptorSet**,
|
|
XCEngine::RHI::RHIPipelineLayout*) override {
|
|
}
|
|
void SetPrimitiveTopology(XCEngine::RHI::PrimitiveTopology) override {}
|
|
void SetViewport(const XCEngine::RHI::Viewport&) override {}
|
|
void SetViewports(uint32_t, const XCEngine::RHI::Viewport*) override {}
|
|
void SetScissorRect(const XCEngine::RHI::Rect&) override {}
|
|
void SetScissorRects(uint32_t, const XCEngine::RHI::Rect*) override {}
|
|
void SetRenderTargets(
|
|
uint32_t,
|
|
XCEngine::RHI::RHIResourceView**,
|
|
XCEngine::RHI::RHIResourceView*) override {
|
|
}
|
|
void SetStencilRef(uint8_t) override {}
|
|
void SetBlendFactor(const float[4]) override {}
|
|
void SetVertexBuffers(
|
|
uint32_t,
|
|
uint32_t,
|
|
XCEngine::RHI::RHIResourceView**,
|
|
const uint64_t*,
|
|
const uint32_t*) override {
|
|
}
|
|
void SetIndexBuffer(XCEngine::RHI::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(
|
|
XCEngine::RHI::RHIResourceView*,
|
|
const float[4],
|
|
uint32_t,
|
|
const XCEngine::RHI::Rect*) override {
|
|
}
|
|
void ClearDepthStencil(
|
|
XCEngine::RHI::RHIResourceView*,
|
|
float,
|
|
uint8_t,
|
|
uint32_t,
|
|
const XCEngine::RHI::Rect*) override {
|
|
}
|
|
void CopyResource(XCEngine::RHI::RHIResourceView*, XCEngine::RHI::RHIResourceView*) override {}
|
|
void Dispatch(uint32_t, uint32_t, uint32_t) override {}
|
|
};
|
|
|
|
class MockNoopCommandQueue final : public XCEngine::RHI::RHICommandQueue {
|
|
public:
|
|
void Shutdown() override {}
|
|
void ExecuteCommandLists(uint32_t, void**) override {}
|
|
void Signal(XCEngine::RHI::RHIFence*, uint64_t) override {}
|
|
void Wait(XCEngine::RHI::RHIFence*, uint64_t) override {}
|
|
uint64_t GetCompletedValue() override { return 0u; }
|
|
void WaitForIdle() override {}
|
|
XCEngine::RHI::CommandQueueType GetType() const override {
|
|
return XCEngine::RHI::CommandQueueType::Direct;
|
|
}
|
|
uint64_t GetTimestampFrequency() const override { return 0u; }
|
|
void* GetNativeHandle() override { return nullptr; }
|
|
void WaitForPreviousFrame() override {}
|
|
uint64_t GetCurrentFrame() const override { return 0u; }
|
|
};
|
|
|
|
struct MockPipelineAssetState {
|
|
int createCalls = 0;
|
|
int configureCameraRenderRequestCalls = 0;
|
|
int configureCameraFramePlanCalls = 0;
|
|
bool createNullPipeline = false;
|
|
std::shared_ptr<MockPipelineState> lastCreatedPipelineState;
|
|
FinalColorSettings defaultFinalColorSettings = {};
|
|
std::function<void(
|
|
CameraRenderRequest&,
|
|
size_t,
|
|
size_t,
|
|
const DirectionalShadowPlanningSettings&)> configureCameraRenderRequest = {};
|
|
std::function<void(CameraFramePlan&)> configureCameraFramePlan = {};
|
|
};
|
|
|
|
struct MockStageRecorderState {
|
|
int initializeCalls = 0;
|
|
int shutdownCalls = 0;
|
|
int recordMainSceneCalls = 0;
|
|
bool supportsMainSceneRenderGraph = false;
|
|
bool recordMainSceneResult = true;
|
|
bool lastReceivedRenderGraphBlackboard = false;
|
|
};
|
|
|
|
class MockPipeline final : public RenderPipeline {
|
|
public:
|
|
explicit MockPipeline(std::shared_ptr<MockPipelineState> state)
|
|
: m_state(std::move(state)) {
|
|
}
|
|
|
|
bool Initialize(const RenderContext&) override {
|
|
++m_state->initializeCalls;
|
|
return true;
|
|
}
|
|
|
|
void ConfigureRenderSceneData(
|
|
const CameraFramePlan& plan,
|
|
RenderSceneData& sceneData) const override {
|
|
++m_state->configureRenderSceneDataCalls;
|
|
if (m_state->configureRenderSceneData) {
|
|
m_state->configureRenderSceneData(plan, sceneData);
|
|
return;
|
|
}
|
|
|
|
RenderPipeline::ConfigureRenderSceneData(plan, sceneData);
|
|
}
|
|
|
|
bool ConfigureDirectionalShadowExecutionState(
|
|
const CameraFramePlan& plan,
|
|
const DirectionalShadowSurfaceAllocation& shadowAllocation,
|
|
DirectionalShadowExecutionState& shadowState) const override {
|
|
++m_state->configureDirectionalShadowExecutionStateCalls;
|
|
if (m_state->configureDirectionalShadowExecutionState) {
|
|
return m_state->configureDirectionalShadowExecutionState(
|
|
plan,
|
|
shadowAllocation,
|
|
shadowState);
|
|
}
|
|
|
|
return RenderPipeline::ConfigureDirectionalShadowExecutionState(
|
|
plan,
|
|
shadowAllocation,
|
|
shadowState);
|
|
}
|
|
|
|
void Shutdown() override {
|
|
++m_state->shutdownCalls;
|
|
ShutdownCameraFrameStandalonePasses();
|
|
}
|
|
|
|
bool SupportsStageRenderGraph(CameraFrameStage stage) const override {
|
|
return SupportsCameraFramePipelineGraphRecording(stage) &&
|
|
m_state->supportsMainSceneRenderGraph;
|
|
}
|
|
|
|
bool RecordStageRenderGraph(
|
|
const RenderPipelineStageRenderGraphContext& context) override {
|
|
++m_state->recordMainSceneCalls;
|
|
m_state->lastReceivedRenderGraphBlackboard = context.blackboard != nullptr;
|
|
if (const CameraFrameRenderGraphResources* frameResources =
|
|
TryGetCameraFrameRenderGraphResources(context.blackboard)) {
|
|
m_state->lastBlackboardMainSceneColorValid =
|
|
frameResources->mainScene.color.IsValid();
|
|
m_state->lastBlackboardMainSceneDepthValid =
|
|
frameResources->mainScene.depth.IsValid();
|
|
m_state->lastBlackboardMainDirectionalShadowValid =
|
|
frameResources->mainDirectionalShadow.IsValid();
|
|
m_state->lastBlackboardObjectIdColorValid =
|
|
frameResources->objectId.color.IsValid();
|
|
m_state->lastRecordedMainSceneShadowHandleValid =
|
|
frameResources->mainDirectionalShadow.IsValid();
|
|
}
|
|
if (!m_state->recordMainSceneResult) {
|
|
return false;
|
|
}
|
|
|
|
const RenderSceneData* const sceneData = &context.sceneData;
|
|
RenderSurface surface = context.surfaceTemplate;
|
|
surface.SetAutoTransitionEnabled(false);
|
|
const std::vector<RenderGraphTextureHandle> colorTargets = context.colorTargets;
|
|
const RenderGraphTextureHandle depthTarget = context.depthTarget;
|
|
const CameraFrameRenderGraphResources* const frameResources =
|
|
TryGetCameraFrameRenderGraphResources(context.blackboard);
|
|
const RenderGraphTextureHandle mainDirectionalShadowTexture =
|
|
frameResources != nullptr
|
|
? frameResources->mainDirectionalShadow
|
|
: RenderGraphTextureHandle{};
|
|
bool* const executionSucceeded = context.executionSucceeded;
|
|
const std::shared_ptr<MockPipelineState> state = m_state;
|
|
|
|
context.graphBuilder.AddRasterPass(
|
|
context.passName,
|
|
[state,
|
|
surface,
|
|
sceneData,
|
|
colorTargets,
|
|
depthTarget,
|
|
mainDirectionalShadowTexture,
|
|
executionSucceeded](
|
|
RenderGraphPassBuilder& passBuilder) {
|
|
for (RenderGraphTextureHandle colorTarget : colorTargets) {
|
|
if (colorTarget.IsValid()) {
|
|
passBuilder.WriteTexture(colorTarget);
|
|
}
|
|
}
|
|
if (depthTarget.IsValid()) {
|
|
passBuilder.WriteDepthTexture(depthTarget);
|
|
}
|
|
if (mainDirectionalShadowTexture.IsValid()) {
|
|
passBuilder.ReadTexture(mainDirectionalShadowTexture);
|
|
}
|
|
|
|
passBuilder.SetExecuteCallback(
|
|
[state,
|
|
surface,
|
|
sceneData,
|
|
executionSucceeded](
|
|
const RenderGraphExecutionContext&) {
|
|
if (executionSucceeded != nullptr && !(*executionSucceeded)) {
|
|
return;
|
|
}
|
|
|
|
state->eventLog.push_back("pipelineGraph");
|
|
++state->executeRecordedMainSceneCalls;
|
|
state->lastSurfaceAutoTransitionEnabled =
|
|
surface.IsAutoTransitionEnabled();
|
|
state->lastSurfaceWidth = surface.GetWidth();
|
|
state->lastSurfaceHeight = surface.GetHeight();
|
|
const XCEngine::Math::RectInt renderArea = surface.GetRenderArea();
|
|
state->lastRenderAreaX = renderArea.x;
|
|
state->lastRenderAreaY = renderArea.y;
|
|
state->lastRenderAreaWidth = renderArea.width;
|
|
state->lastRenderAreaHeight = renderArea.height;
|
|
state->lastCamera = sceneData->camera;
|
|
state->lastCameraViewportWidth = sceneData->cameraData.viewportWidth;
|
|
state->lastCameraViewportHeight = sceneData->cameraData.viewportHeight;
|
|
state->lastVisibleItemCount = sceneData->visibleItems.size();
|
|
state->lastClearFlags = sceneData->cameraData.clearFlags;
|
|
state->lastClearColor = sceneData->cameraData.clearColor;
|
|
state->lastHasMainDirectionalShadow =
|
|
sceneData->lighting.HasMainDirectionalShadow();
|
|
state->lastShadowMap = sceneData->lighting.mainDirectionalShadow.shadowMap;
|
|
state->lastShadowViewProjection =
|
|
sceneData->lighting.mainDirectionalShadow.viewProjection;
|
|
state->lastShadowMapMetrics =
|
|
sceneData->lighting.mainDirectionalShadow.mapMetrics;
|
|
state->lastShadowSampling =
|
|
sceneData->lighting.mainDirectionalShadow.sampling;
|
|
state->lastHasMainDirectionalShadowKeyword =
|
|
XCEngine::Resources::ShaderKeywordSetContains(
|
|
sceneData->globalShaderKeywords,
|
|
"XC_MAIN_LIGHT_SHADOWS");
|
|
state->lastEnvironmentMode = sceneData->environment.mode;
|
|
state->lastHasSkybox = sceneData->environment.HasSkybox();
|
|
state->lastSkyboxMaterial =
|
|
sceneData->environment.materialSkybox.material;
|
|
state->renderedCameras.push_back(sceneData->camera);
|
|
state->renderedClearFlags.push_back(sceneData->cameraData.clearFlags);
|
|
state->renderedClearColors.push_back(sceneData->cameraData.clearColor);
|
|
if (executionSucceeded != nullptr) {
|
|
*executionSucceeded = state->renderResult;
|
|
}
|
|
});
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Render(
|
|
const RenderContext&,
|
|
const RenderSurface& surface,
|
|
const RenderSceneData& sceneData) override {
|
|
m_state->eventLog.push_back("pipeline");
|
|
++m_state->renderCalls;
|
|
m_state->lastSurfaceAutoTransitionEnabled = surface.IsAutoTransitionEnabled();
|
|
m_state->lastSurfaceWidth = surface.GetWidth();
|
|
m_state->lastSurfaceHeight = surface.GetHeight();
|
|
const XCEngine::Math::RectInt renderArea = surface.GetRenderArea();
|
|
m_state->lastRenderAreaX = renderArea.x;
|
|
m_state->lastRenderAreaY = renderArea.y;
|
|
m_state->lastRenderAreaWidth = renderArea.width;
|
|
m_state->lastRenderAreaHeight = renderArea.height;
|
|
m_state->lastCamera = sceneData.camera;
|
|
m_state->lastCameraViewportWidth = sceneData.cameraData.viewportWidth;
|
|
m_state->lastCameraViewportHeight = sceneData.cameraData.viewportHeight;
|
|
m_state->lastVisibleItemCount = sceneData.visibleItems.size();
|
|
m_state->lastClearFlags = sceneData.cameraData.clearFlags;
|
|
m_state->lastClearColor = sceneData.cameraData.clearColor;
|
|
m_state->lastHasMainDirectionalShadow = sceneData.lighting.HasMainDirectionalShadow();
|
|
m_state->lastShadowMap = sceneData.lighting.mainDirectionalShadow.shadowMap;
|
|
m_state->lastShadowViewProjection = sceneData.lighting.mainDirectionalShadow.viewProjection;
|
|
m_state->lastShadowMapMetrics = sceneData.lighting.mainDirectionalShadow.mapMetrics;
|
|
m_state->lastShadowSampling = sceneData.lighting.mainDirectionalShadow.sampling;
|
|
m_state->lastHasMainDirectionalShadowKeyword =
|
|
XCEngine::Resources::ShaderKeywordSetContains(
|
|
sceneData.globalShaderKeywords,
|
|
"XC_MAIN_LIGHT_SHADOWS");
|
|
m_state->lastEnvironmentMode = sceneData.environment.mode;
|
|
m_state->lastHasSkybox = sceneData.environment.HasSkybox();
|
|
m_state->lastSkyboxMaterial = sceneData.environment.materialSkybox.material;
|
|
m_state->renderedCameras.push_back(sceneData.camera);
|
|
m_state->renderedClearFlags.push_back(sceneData.cameraData.clearFlags);
|
|
m_state->renderedClearColors.push_back(sceneData.cameraData.clearColor);
|
|
return m_state->renderResult;
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineState> m_state;
|
|
};
|
|
|
|
class MockStageRecorder final : public RenderPipelineStageRecorder {
|
|
public:
|
|
explicit MockStageRecorder(std::shared_ptr<MockStageRecorderState> state)
|
|
: m_state(std::move(state)) {
|
|
}
|
|
|
|
bool Initialize(const RenderContext&) override {
|
|
++m_state->initializeCalls;
|
|
return true;
|
|
}
|
|
|
|
void Shutdown() override {
|
|
++m_state->shutdownCalls;
|
|
}
|
|
|
|
bool SupportsStageRenderGraph(CameraFrameStage stage) const override {
|
|
return SupportsCameraFramePipelineGraphRecording(stage) &&
|
|
m_state->supportsMainSceneRenderGraph;
|
|
}
|
|
|
|
bool RecordStageRenderGraph(
|
|
const RenderPipelineStageRenderGraphContext& context) override {
|
|
++m_state->recordMainSceneCalls;
|
|
m_state->lastReceivedRenderGraphBlackboard = context.blackboard != nullptr;
|
|
return m_state->recordMainSceneResult;
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockStageRecorderState> m_state;
|
|
};
|
|
|
|
template <typename PassT, typename... Args>
|
|
PassT* InstallStandaloneStagePass(
|
|
RenderPipeline& pipeline,
|
|
CameraFrameStage stage,
|
|
Args&&... args) {
|
|
auto pass = std::make_unique<PassT>(std::forward<Args>(args)...);
|
|
PassT* const passRaw = pass.get();
|
|
pipeline.SetCameraFrameStandalonePass(stage, std::move(pass));
|
|
return passRaw;
|
|
}
|
|
|
|
class MockPipelineAsset final : public RenderPipelineAsset {
|
|
public:
|
|
explicit MockPipelineAsset(std::shared_ptr<MockPipelineAssetState> state)
|
|
: m_state(std::move(state)) {
|
|
}
|
|
|
|
std::unique_ptr<RenderPipeline> CreatePipeline() const override {
|
|
++m_state->createCalls;
|
|
if (m_state->createNullPipeline) {
|
|
m_state->lastCreatedPipelineState.reset();
|
|
return nullptr;
|
|
}
|
|
|
|
m_state->lastCreatedPipelineState = std::make_shared<MockPipelineState>();
|
|
return std::make_unique<MockPipeline>(m_state->lastCreatedPipelineState);
|
|
}
|
|
|
|
FinalColorSettings GetDefaultFinalColorSettings() const override {
|
|
return m_state->defaultFinalColorSettings;
|
|
}
|
|
|
|
void ConfigureCameraRenderRequest(
|
|
CameraRenderRequest& request,
|
|
size_t renderedBaseCameraCount,
|
|
size_t renderedRequestCount,
|
|
const DirectionalShadowPlanningSettings& directionalShadowSettings) const override {
|
|
++m_state->configureCameraRenderRequestCalls;
|
|
if (m_state->configureCameraRenderRequest) {
|
|
m_state->configureCameraRenderRequest(
|
|
request,
|
|
renderedBaseCameraCount,
|
|
renderedRequestCount,
|
|
directionalShadowSettings);
|
|
return;
|
|
}
|
|
|
|
RenderPipelineAsset::ConfigureCameraRenderRequest(
|
|
request,
|
|
renderedBaseCameraCount,
|
|
renderedRequestCount,
|
|
directionalShadowSettings);
|
|
}
|
|
|
|
void ConfigureCameraFramePlan(CameraFramePlan& plan) const override {
|
|
++m_state->configureCameraFramePlanCalls;
|
|
if (m_state->configureCameraFramePlan) {
|
|
m_state->configureCameraFramePlan(plan);
|
|
return;
|
|
}
|
|
|
|
RenderPipelineAsset::ConfigureCameraFramePlan(plan);
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineAssetState> m_state;
|
|
};
|
|
|
|
struct MockManagedRenderPipelineBridgeState {
|
|
int createStageRecorderCalls = 0;
|
|
Pipelines::ManagedRenderPipelineAssetDescriptor lastDescriptor = {};
|
|
std::shared_ptr<MockStageRecorderState> lastCreatedStageRecorderState;
|
|
};
|
|
|
|
class MockManagedRenderPipelineBridge final
|
|
: public Pipelines::ManagedRenderPipelineBridge {
|
|
public:
|
|
explicit MockManagedRenderPipelineBridge(
|
|
std::shared_ptr<MockManagedRenderPipelineBridgeState> state)
|
|
: m_state(std::move(state)) {
|
|
}
|
|
|
|
std::unique_ptr<RenderPipelineStageRecorder> CreateStageRecorder(
|
|
const Pipelines::ManagedRenderPipelineAssetDescriptor& descriptor) const override {
|
|
++m_state->createStageRecorderCalls;
|
|
m_state->lastDescriptor = descriptor;
|
|
m_state->lastCreatedStageRecorderState =
|
|
std::make_shared<MockStageRecorderState>();
|
|
return std::make_unique<MockStageRecorder>(
|
|
m_state->lastCreatedStageRecorderState);
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockManagedRenderPipelineBridgeState> m_state;
|
|
};
|
|
|
|
class MockObjectIdPass final : public RenderPass {
|
|
public:
|
|
MockObjectIdPass(
|
|
std::shared_ptr<MockPipelineState> state,
|
|
bool renderResult = true,
|
|
bool supportsRenderGraph = false)
|
|
: m_state(std::move(state))
|
|
, m_renderResult(renderResult)
|
|
, m_supportsRenderGraph(supportsRenderGraph) {
|
|
}
|
|
|
|
const char* GetName() const override {
|
|
return "MockObjectIdPass";
|
|
}
|
|
|
|
bool SupportsRenderGraph() const override {
|
|
return m_supportsRenderGraph;
|
|
}
|
|
|
|
bool RecordRenderGraph(const RenderPassRenderGraphContext& context) override {
|
|
m_state->eventLog.push_back("record:objectId");
|
|
return RecordColorDepthRasterPass(
|
|
*this,
|
|
context);
|
|
}
|
|
|
|
bool Execute(const RenderPassContext& context) override {
|
|
m_state->eventLog.push_back("objectId");
|
|
lastSurfaceAutoTransitionEnabled = context.surface.IsAutoTransitionEnabled();
|
|
lastSurfaceWidth = context.surface.GetRenderAreaWidth();
|
|
lastSurfaceHeight = context.surface.GetRenderAreaHeight();
|
|
return m_renderResult;
|
|
}
|
|
|
|
void Shutdown() override {
|
|
m_state->eventLog.push_back("shutdown:objectId");
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineState> m_state;
|
|
bool m_renderResult = true;
|
|
bool m_supportsRenderGraph = false;
|
|
|
|
public:
|
|
bool lastSurfaceAutoTransitionEnabled = true;
|
|
uint32_t lastSurfaceWidth = 0;
|
|
uint32_t lastSurfaceHeight = 0;
|
|
};
|
|
|
|
class MockScenePass final : public RenderPass {
|
|
public:
|
|
MockScenePass(
|
|
std::shared_ptr<MockPipelineState> state,
|
|
const char* label,
|
|
bool initializeResult = true,
|
|
bool executeResult = true,
|
|
bool supportsRenderGraph = false)
|
|
: m_state(std::move(state))
|
|
, m_label(label)
|
|
, m_initializeResult(initializeResult)
|
|
, m_executeResult(executeResult)
|
|
, m_supportsRenderGraph(supportsRenderGraph) {
|
|
}
|
|
|
|
const char* GetName() const override {
|
|
return m_label;
|
|
}
|
|
|
|
bool SupportsRenderGraph() const override {
|
|
return m_supportsRenderGraph;
|
|
}
|
|
|
|
bool RecordRenderGraph(const RenderPassRenderGraphContext& context) override {
|
|
m_state->eventLog.push_back(std::string("record:") + m_label);
|
|
return RecordColorDepthRasterPass(
|
|
*this,
|
|
context);
|
|
}
|
|
|
|
bool Initialize(const RenderContext&) override {
|
|
m_state->eventLog.push_back(std::string("init:") + m_label);
|
|
return m_initializeResult;
|
|
}
|
|
|
|
bool Execute(const RenderPassContext& context) override {
|
|
m_state->eventLog.push_back(m_label);
|
|
lastViewportWidth = context.sceneData.cameraData.viewportWidth;
|
|
lastViewportHeight = context.sceneData.cameraData.viewportHeight;
|
|
lastClearFlags = context.sceneData.cameraData.clearFlags;
|
|
lastClearColor = context.sceneData.cameraData.clearColor;
|
|
lastWorldPosition = context.sceneData.cameraData.worldPosition;
|
|
lastSurfaceWidth = context.surface.GetRenderAreaWidth();
|
|
lastSurfaceHeight = context.surface.GetRenderAreaHeight();
|
|
lastSurfaceAutoTransitionEnabled = context.surface.IsAutoTransitionEnabled();
|
|
lastHasSourceSurface = context.sourceSurface != nullptr;
|
|
if (context.sourceSurface != nullptr) {
|
|
lastSourceSurfaceWidth = context.sourceSurface->GetRenderAreaWidth();
|
|
lastSourceSurfaceHeight = context.sourceSurface->GetRenderAreaHeight();
|
|
lastSourceSurfaceAutoTransitionEnabled =
|
|
context.sourceSurface->IsAutoTransitionEnabled();
|
|
}
|
|
lastSourceColorView = context.sourceColorView;
|
|
lastSourceColorState = context.sourceColorState;
|
|
return m_executeResult;
|
|
}
|
|
|
|
void Shutdown() override {
|
|
m_state->eventLog.push_back(std::string("shutdown:") + m_label);
|
|
}
|
|
|
|
uint32_t lastViewportWidth = 0;
|
|
uint32_t lastViewportHeight = 0;
|
|
RenderClearFlags lastClearFlags = RenderClearFlags::All;
|
|
XCEngine::Math::Color lastClearColor = XCEngine::Math::Color::Black();
|
|
XCEngine::Math::Vector3 lastWorldPosition = XCEngine::Math::Vector3::Zero();
|
|
uint32_t lastSurfaceWidth = 0;
|
|
uint32_t lastSurfaceHeight = 0;
|
|
bool lastSurfaceAutoTransitionEnabled = true;
|
|
bool lastHasSourceSurface = false;
|
|
uint32_t lastSourceSurfaceWidth = 0;
|
|
uint32_t lastSourceSurfaceHeight = 0;
|
|
bool lastSourceSurfaceAutoTransitionEnabled = true;
|
|
XCEngine::RHI::RHIResourceView* lastSourceColorView = nullptr;
|
|
XCEngine::RHI::ResourceStates lastSourceColorState = XCEngine::RHI::ResourceStates::Common;
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineState> m_state;
|
|
const char* m_label = "";
|
|
bool m_initializeResult = true;
|
|
bool m_executeResult = true;
|
|
bool m_supportsRenderGraph = false;
|
|
};
|
|
|
|
class TrackingPass final : public RenderPass {
|
|
public:
|
|
TrackingPass(
|
|
std::shared_ptr<MockPipelineState> state,
|
|
const char* label,
|
|
bool initializeResult = true,
|
|
bool executeResult = true,
|
|
bool supportsRenderGraph = false)
|
|
: m_state(std::move(state))
|
|
, m_label(label)
|
|
, m_initializeResult(initializeResult)
|
|
, m_executeResult(executeResult)
|
|
, m_supportsRenderGraph(supportsRenderGraph) {
|
|
}
|
|
|
|
const char* GetName() const override {
|
|
return m_label;
|
|
}
|
|
|
|
bool SupportsRenderGraph() const override {
|
|
return m_supportsRenderGraph;
|
|
}
|
|
|
|
bool RecordRenderGraph(const RenderPassRenderGraphContext& context) override {
|
|
m_state->eventLog.push_back(std::string("record:") + m_label);
|
|
lastRecordedSourceColorTextureValid = context.sourceColorTexture.IsValid();
|
|
lastRecordedRenderGraphBlackboard = context.blackboard != nullptr;
|
|
if (const CameraFrameRenderGraphResources* frameResources =
|
|
TryGetCameraFrameRenderGraphResources(context.blackboard)) {
|
|
lastRecordedBlackboardMainSceneColorValid =
|
|
frameResources->mainScene.color.IsValid();
|
|
lastRecordedBlackboardPostProcessColorValid =
|
|
frameResources->postProcess.color.IsValid();
|
|
} else {
|
|
lastRecordedBlackboardMainSceneColorValid = false;
|
|
lastRecordedBlackboardPostProcessColorValid = false;
|
|
}
|
|
return RecordDeclaredRasterPass(
|
|
*this,
|
|
context);
|
|
}
|
|
|
|
bool Initialize(const RenderContext&) override {
|
|
m_state->eventLog.push_back(std::string("init:") + m_label);
|
|
return m_initializeResult;
|
|
}
|
|
|
|
bool Execute(const RenderPassContext& context) override {
|
|
m_state->eventLog.push_back(m_label);
|
|
lastSurfaceWidth = context.surface.GetRenderAreaWidth();
|
|
lastSurfaceHeight = context.surface.GetRenderAreaHeight();
|
|
lastSurfaceAutoTransitionEnabled = context.surface.IsAutoTransitionEnabled();
|
|
lastHasSourceSurface = context.sourceSurface != nullptr;
|
|
if (context.sourceSurface != nullptr) {
|
|
lastSourceSurfaceWidth = context.sourceSurface->GetRenderAreaWidth();
|
|
lastSourceSurfaceHeight = context.sourceSurface->GetRenderAreaHeight();
|
|
lastSourceSurfaceAutoTransitionEnabled =
|
|
context.sourceSurface->IsAutoTransitionEnabled();
|
|
}
|
|
lastSourceColorView = context.sourceColorView;
|
|
lastSourceColorState = context.sourceColorState;
|
|
return m_executeResult;
|
|
}
|
|
|
|
void Shutdown() override {
|
|
m_state->eventLog.push_back(std::string("shutdown:") + m_label);
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineState> m_state;
|
|
const char* m_label = "";
|
|
bool m_initializeResult = true;
|
|
bool m_executeResult = true;
|
|
bool m_supportsRenderGraph = false;
|
|
|
|
public:
|
|
uint32_t lastSurfaceWidth = 0;
|
|
uint32_t lastSurfaceHeight = 0;
|
|
bool lastSurfaceAutoTransitionEnabled = true;
|
|
bool lastHasSourceSurface = false;
|
|
uint32_t lastSourceSurfaceWidth = 0;
|
|
uint32_t lastSourceSurfaceHeight = 0;
|
|
bool lastSourceSurfaceAutoTransitionEnabled = true;
|
|
XCEngine::RHI::RHIResourceView* lastSourceColorView = nullptr;
|
|
XCEngine::RHI::ResourceStates lastSourceColorState = XCEngine::RHI::ResourceStates::Common;
|
|
bool lastRecordedSourceColorTextureValid = false;
|
|
bool lastRecordedRenderGraphBlackboard = false;
|
|
bool lastRecordedBlackboardMainSceneColorValid = false;
|
|
bool lastRecordedBlackboardPostProcessColorValid = false;
|
|
};
|
|
|
|
RenderContext CreateValidContext() {
|
|
static MockNoopCommandList s_commandList;
|
|
static MockNoopCommandQueue s_commandQueue;
|
|
|
|
RenderContext context;
|
|
context.device = reinterpret_cast<XCEngine::RHI::RHIDevice*>(1);
|
|
context.commandList = &s_commandList;
|
|
context.commandQueue = &s_commandQueue;
|
|
return context;
|
|
}
|
|
|
|
RenderSceneData CreateSceneDataForCamera(
|
|
Scene&,
|
|
CameraComponent& camera,
|
|
const RenderSurface& surface) {
|
|
RenderSceneData sceneData = {};
|
|
sceneData.camera = &camera;
|
|
sceneData.cameraData.viewportWidth = surface.GetRenderAreaWidth();
|
|
sceneData.cameraData.viewportHeight = surface.GetRenderAreaHeight();
|
|
sceneData.cameraData.clearFlags = RenderClearFlags::All;
|
|
return sceneData;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) {
|
|
CameraRenderRequest request;
|
|
|
|
RenderPassSequence prePasses;
|
|
RenderPassSequence postProcessPasses;
|
|
RenderPassSequence finalOutputPasses;
|
|
RenderPassSequence postPasses;
|
|
RenderPassSequence overlayPasses;
|
|
request.preScenePasses = &prePasses;
|
|
request.postProcess.passes = &postProcessPasses;
|
|
request.finalOutput.passes = &finalOutputPasses;
|
|
request.postScenePasses = &postPasses;
|
|
request.overlayPasses = &overlayPasses;
|
|
|
|
request.directionalShadow.enabled = true;
|
|
request.directionalShadow.mapWidth = 128;
|
|
request.directionalShadow.mapHeight = 64;
|
|
request.directionalShadow.cameraData.viewportWidth = 128;
|
|
request.directionalShadow.cameraData.viewportHeight = 64;
|
|
|
|
request.depthOnly.surface = RenderSurface(96, 48);
|
|
request.depthOnly.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
|
|
request.postProcess.sourceSurface = RenderSurface(256, 128);
|
|
request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
request.postProcess.sourceColorView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(20);
|
|
request.postProcess.sourceColorState = XCEngine::RHI::ResourceStates::PixelShaderResource;
|
|
request.postProcess.destinationSurface = RenderSurface(512, 256);
|
|
request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
|
|
|
|
request.finalOutput.sourceSurface = RenderSurface(512, 256);
|
|
request.finalOutput.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(4));
|
|
request.finalOutput.sourceColorView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(40);
|
|
request.finalOutput.sourceColorState = XCEngine::RHI::ResourceStates::PixelShaderResource;
|
|
request.finalOutput.destinationSurface = RenderSurface(640, 360);
|
|
request.finalOutput.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(5));
|
|
|
|
request.objectId.surface = RenderSurface(320, 180);
|
|
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(6));
|
|
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(7));
|
|
|
|
ASSERT_EQ(kOrderedCameraFrameStages.size(), 9u);
|
|
EXPECT_EQ(kOrderedCameraFrameStages[0].stage, CameraFrameStage::PreScenePasses);
|
|
EXPECT_EQ(kOrderedCameraFrameStages[1].stage, CameraFrameStage::ShadowCaster);
|
|
EXPECT_EQ(kOrderedCameraFrameStages[2].stage, CameraFrameStage::DepthOnly);
|
|
EXPECT_EQ(kOrderedCameraFrameStages[3].stage, CameraFrameStage::MainScene);
|
|
EXPECT_EQ(kOrderedCameraFrameStages[4].stage, CameraFrameStage::PostProcess);
|
|
EXPECT_EQ(kOrderedCameraFrameStages[5].stage, CameraFrameStage::FinalOutput);
|
|
EXPECT_EQ(kOrderedCameraFrameStages[6].stage, CameraFrameStage::ObjectId);
|
|
EXPECT_EQ(kOrderedCameraFrameStages[7].stage, CameraFrameStage::PostScenePasses);
|
|
EXPECT_EQ(kOrderedCameraFrameStages[8].stage, CameraFrameStage::OverlayPasses);
|
|
EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::MainScene), "MainScene");
|
|
EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::PostProcess), "PostProcess");
|
|
EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::FinalOutput), "FinalOutput");
|
|
EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::OverlayPasses), "OverlayPasses");
|
|
|
|
const CameraFramePlan plan = CameraFramePlan::FromRequest(request);
|
|
EXPECT_TRUE(plan.HasFrameStage(CameraFrameStage::PreScenePasses));
|
|
EXPECT_TRUE(plan.HasFrameStage(CameraFrameStage::ShadowCaster));
|
|
EXPECT_TRUE(plan.HasFrameStage(CameraFrameStage::DepthOnly));
|
|
EXPECT_TRUE(plan.HasFrameStage(CameraFrameStage::MainScene));
|
|
EXPECT_TRUE(plan.HasFrameStage(CameraFrameStage::PostProcess));
|
|
EXPECT_TRUE(plan.HasFrameStage(CameraFrameStage::FinalOutput));
|
|
EXPECT_TRUE(plan.HasFrameStage(CameraFrameStage::ObjectId));
|
|
EXPECT_TRUE(plan.HasFrameStage(CameraFrameStage::PostScenePasses));
|
|
EXPECT_TRUE(plan.HasFrameStage(CameraFrameStage::OverlayPasses));
|
|
|
|
EXPECT_EQ(plan.GetPassSequence(CameraFrameStage::PreScenePasses), &prePasses);
|
|
EXPECT_EQ(plan.GetPassSequence(CameraFrameStage::PostProcess), &postProcessPasses);
|
|
EXPECT_EQ(plan.GetPassSequence(CameraFrameStage::FinalOutput), &finalOutputPasses);
|
|
EXPECT_EQ(plan.GetPassSequence(CameraFrameStage::PostScenePasses), &postPasses);
|
|
EXPECT_EQ(plan.GetPassSequence(CameraFrameStage::OverlayPasses), &overlayPasses);
|
|
EXPECT_EQ(plan.GetFullscreenStagePlan(CameraFrameStage::PostProcess), &plan.colorChain.postProcess);
|
|
EXPECT_EQ(plan.GetFullscreenStagePlan(CameraFrameStage::FinalOutput), &plan.colorChain.finalOutput);
|
|
EXPECT_EQ(plan.GetFullscreenStagePlan(CameraFrameStage::MainScene), nullptr);
|
|
EXPECT_EQ(plan.GetFullscreenPassRequest(CameraFrameStage::PostProcess), &plan.postProcess);
|
|
EXPECT_EQ(plan.GetFullscreenPassRequest(CameraFrameStage::FinalOutput), &plan.finalOutput);
|
|
EXPECT_EQ(plan.GetFullscreenPassRequest(CameraFrameStage::MainScene), nullptr);
|
|
EXPECT_EQ(plan.GetScenePassRequest(CameraFrameStage::ShadowCaster), &plan.shadowCaster);
|
|
EXPECT_EQ(plan.GetScenePassRequest(CameraFrameStage::DepthOnly), &plan.request.depthOnly);
|
|
EXPECT_EQ(plan.GetScenePassRequest(CameraFrameStage::MainScene), nullptr);
|
|
EXPECT_EQ(plan.GetObjectIdRequest(CameraFrameStage::ObjectId), &plan.request.objectId);
|
|
EXPECT_EQ(plan.GetObjectIdRequest(CameraFrameStage::MainScene), nullptr);
|
|
EXPECT_EQ(plan.GetSharedStageOutputSurface(CameraFrameStage::PreScenePasses), &plan.GetMainSceneSurface());
|
|
EXPECT_EQ(plan.GetSharedStageOutputSurface(CameraFrameStage::OverlayPasses), &plan.GetFinalCompositedSurface());
|
|
EXPECT_EQ(plan.GetSharedStageOutputSurface(CameraFrameStage::PostProcess), nullptr);
|
|
EXPECT_TRUE(plan.RequiresIntermediateSceneColor());
|
|
EXPECT_EQ(plan.GetMainSceneSurface().GetRenderAreaWidth(), 256u);
|
|
EXPECT_EQ(plan.GetMainSceneSurface().GetRenderAreaHeight(), 128u);
|
|
EXPECT_EQ(plan.GetFinalCompositedSurface().GetRenderAreaWidth(), 640u);
|
|
EXPECT_EQ(plan.GetFinalCompositedSurface().GetRenderAreaHeight(), 360u);
|
|
const RenderSurface* const postProcessOutput =
|
|
ResolveStageOutputSurface(CameraFrameStage::PostProcess, plan);
|
|
const CameraFrameStageSourceBinding postProcessSource =
|
|
ResolveStageSourceBinding(CameraFrameStage::PostProcess, plan);
|
|
ASSERT_NE(postProcessOutput, nullptr);
|
|
EXPECT_EQ(postProcessOutput->GetRenderAreaWidth(), 512u);
|
|
ASSERT_NE(postProcessSource.sourceSurface, nullptr);
|
|
EXPECT_EQ(postProcessSource.sourceSurface->GetRenderAreaWidth(), 256u);
|
|
EXPECT_EQ(
|
|
postProcessSource.sourceColorView,
|
|
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(20));
|
|
EXPECT_EQ(
|
|
postProcessSource.sourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
const CameraFrameStageSourceBinding finalOutputSource =
|
|
ResolveStageSourceBinding(CameraFrameStage::FinalOutput, plan);
|
|
ASSERT_NE(finalOutputSource.sourceSurface, nullptr);
|
|
EXPECT_EQ(finalOutputSource.sourceSurface->GetRenderAreaWidth(), 512u);
|
|
EXPECT_EQ(
|
|
finalOutputSource.sourceColorView,
|
|
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(40));
|
|
EXPECT_EQ(
|
|
finalOutputSource.sourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
}
|
|
|
|
TEST(CameraRenderRequest_Test, RejectsFullscreenRequestWithoutReadableSourceStateWhenAutoTransitionIsDisabled) {
|
|
RenderPassSequence passes;
|
|
|
|
FullscreenPassRenderRequest request = {};
|
|
request.sourceSurface = RenderSurface(256, 128);
|
|
request.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.sourceSurface.SetAutoTransitionEnabled(false);
|
|
request.sourceColorView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2);
|
|
request.sourceColorState = XCEngine::RHI::ResourceStates::RenderTarget;
|
|
request.destinationSurface = RenderSurface(512, 256);
|
|
request.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
|
|
request.passes = &passes;
|
|
|
|
EXPECT_FALSE(request.IsValid());
|
|
|
|
request.sourceColorState = XCEngine::RHI::ResourceStates::PixelShaderResource;
|
|
EXPECT_TRUE(request.IsValid());
|
|
}
|
|
|
|
TEST(CameraRenderRequest_Test, AcceptsAutoTransitionedFullscreenSourceAndRejectsMultisampledSource) {
|
|
RenderPassSequence passes;
|
|
|
|
FullscreenPassRenderRequest request = {};
|
|
request.sourceSurface = RenderSurface(256, 128);
|
|
request.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.sourceColorView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2);
|
|
request.sourceColorState = XCEngine::RHI::ResourceStates::RenderTarget;
|
|
request.destinationSurface = RenderSurface(512, 256);
|
|
request.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
|
|
request.passes = &passes;
|
|
|
|
EXPECT_TRUE(request.IsValid());
|
|
|
|
request.sourceSurface.SetSampleDesc(4u, 0u);
|
|
EXPECT_FALSE(request.IsValid());
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) {
|
|
Scene scene("CameraRendererScene");
|
|
|
|
GameObject* primaryCameraObject = scene.CreateGameObject("PrimaryCamera");
|
|
auto* primaryCamera = primaryCameraObject->AddComponent<CameraComponent>();
|
|
primaryCamera->SetPrimary(true);
|
|
primaryCamera->SetDepth(10.0f);
|
|
|
|
GameObject* overrideCameraObject = scene.CreateGameObject("OverrideCamera");
|
|
auto* overrideCamera = overrideCameraObject->AddComponent<CameraComponent>();
|
|
overrideCamera->SetPrimary(false);
|
|
overrideCamera->SetDepth(-1.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = overrideCamera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(640, 480);
|
|
request.cameraDepth = overrideCamera->GetDepth();
|
|
request.clearFlags = RenderClearFlags::None;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(state->renderCalls, 1);
|
|
EXPECT_EQ(state->lastSurfaceWidth, 640u);
|
|
EXPECT_EQ(state->lastSurfaceHeight, 480u);
|
|
EXPECT_EQ(state->lastRenderAreaX, 0);
|
|
EXPECT_EQ(state->lastRenderAreaY, 0);
|
|
EXPECT_EQ(state->lastRenderAreaWidth, 640);
|
|
EXPECT_EQ(state->lastRenderAreaHeight, 480);
|
|
EXPECT_EQ(state->lastCameraViewportWidth, 640u);
|
|
EXPECT_EQ(state->lastCameraViewportHeight, 480u);
|
|
EXPECT_EQ(state->lastCamera, overrideCamera);
|
|
EXPECT_NE(state->lastCamera, primaryCamera);
|
|
EXPECT_EQ(state->lastVisibleItemCount, 0u);
|
|
EXPECT_EQ(state->lastClearFlags, RenderClearFlags::None);
|
|
EXPECT_FLOAT_EQ(state->lastClearColor.r, overrideCamera->GetClearColor().r);
|
|
EXPECT_FLOAT_EQ(state->lastClearColor.g, overrideCamera->GetClearColor().g);
|
|
EXPECT_FLOAT_EQ(state->lastClearColor.b, overrideCamera->GetClearColor().b);
|
|
EXPECT_FLOAT_EQ(state->lastClearColor.a, overrideCamera->GetClearColor().a);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, AppliesRequestClearColorOverrideToSceneData) {
|
|
Scene scene("CameraRendererClearColorOverrideScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(1.0f);
|
|
camera->SetClearColor(XCEngine::Math::Color(0.05f, 0.10f, 0.15f, 1.0f));
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 200);
|
|
request.hasClearColorOverride = true;
|
|
request.clearColorOverride = XCEngine::Math::Color(0.27f, 0.27f, 0.27f, 1.0f);
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_FLOAT_EQ(state->lastClearColor.r, 0.27f);
|
|
EXPECT_FLOAT_EQ(state->lastClearColor.g, 0.27f);
|
|
EXPECT_FLOAT_EQ(state->lastClearColor.b, 0.27f);
|
|
EXPECT_FLOAT_EQ(state->lastClearColor.a, 1.0f);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, PromotesSkyboxMaterialIntoEnvironmentFrameData) {
|
|
Scene scene("CameraRendererSkyboxMaterialScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetSkyboxEnabled(true);
|
|
|
|
XCEngine::Resources::Material skyboxMaterial;
|
|
camera->SetSkyboxMaterial(&skyboxMaterial);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 200);
|
|
request.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_TRUE(state->lastHasSkybox);
|
|
EXPECT_EQ(state->lastEnvironmentMode, RenderEnvironmentMode::MaterialSkybox);
|
|
EXPECT_EQ(state->lastSkyboxMaterial, &skyboxMaterial);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, UsesPipelineSceneDataConfigurationHook) {
|
|
Scene scene("CameraRendererSceneDataHookScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetSkyboxEnabled(true);
|
|
|
|
XCEngine::Resources::Material skyboxMaterial;
|
|
camera->SetSkyboxMaterial(&skyboxMaterial);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
state->configureRenderSceneData = [](
|
|
const CameraFramePlan&,
|
|
RenderSceneData& sceneData) {
|
|
sceneData.environment = {};
|
|
sceneData.globalShaderKeywords = {};
|
|
};
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 200);
|
|
request.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(state->configureRenderSceneDataCalls, 1);
|
|
EXPECT_FALSE(state->lastHasSkybox);
|
|
EXPECT_EQ(state->lastEnvironmentMode, RenderEnvironmentMode::None);
|
|
EXPECT_EQ(state->lastSkyboxMaterial, nullptr);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ExecutesInjectedPreAndPostPassSequencesAroundPipelineRender) {
|
|
Scene scene("CameraRendererPassScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::make_unique<TrackingPass>(state, "post"));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
request.postScenePasses = &postPasses;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:pre",
|
|
"pre",
|
|
"pipeline",
|
|
"init:post",
|
|
"post" }));
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ExecutesObjectIdPassBetweenPipelineAndPostPassesWhenRequested) {
|
|
Scene scene("CameraRendererObjectIdPassScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
auto pipeline = std::make_unique<MockPipeline>(state);
|
|
MockObjectIdPass* objectIdPassRaw =
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
state);
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::make_unique<TrackingPass>(state, "post"));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
request.postScenePasses = &postPasses;
|
|
request.objectId.surface = RenderSurface(320, 180);
|
|
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:pre",
|
|
"pre",
|
|
"pipeline",
|
|
"objectId",
|
|
"init:post",
|
|
"post" }));
|
|
ASSERT_NE(objectIdPassRaw, nullptr);
|
|
EXPECT_FALSE(objectIdPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(objectIdPassRaw->lastSurfaceWidth, 320u);
|
|
EXPECT_EQ(objectIdPassRaw->lastSurfaceHeight, 180u);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, RoutesSceneColorThroughPostProcessAndFinalOutputStages) {
|
|
Scene scene("CameraRendererPostProcessScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
auto postProcessPass = std::make_unique<MockScenePass>(state, "postProcess");
|
|
MockScenePass* postProcessPassRaw = postProcessPass.get();
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::move(postProcessPass));
|
|
|
|
auto finalOutputPass = std::make_unique<MockScenePass>(state, "finalOutput");
|
|
MockScenePass* finalOutputPassRaw = finalOutputPass.get();
|
|
RenderPassSequence finalOutputPasses;
|
|
finalOutputPasses.AddPass(std::move(finalOutputPass));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(800, 600);
|
|
request.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.cameraDepth = camera->GetDepth();
|
|
|
|
request.postProcess.sourceSurface = RenderSurface(256, 128);
|
|
request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
request.postProcess.sourceColorView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(20);
|
|
request.postProcess.destinationSurface = RenderSurface(512, 256);
|
|
request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
|
|
request.postProcess.passes = &postProcessPasses;
|
|
|
|
request.finalOutput.sourceSurface = request.postProcess.destinationSurface;
|
|
request.finalOutput.sourceColorView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(30);
|
|
request.finalOutput.destinationSurface = request.surface;
|
|
request.finalOutput.passes = &finalOutputPasses;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(state->lastSurfaceWidth, 256u);
|
|
EXPECT_EQ(state->lastSurfaceHeight, 128u);
|
|
EXPECT_EQ(state->lastCameraViewportWidth, 256u);
|
|
EXPECT_EQ(state->lastCameraViewportHeight, 128u);
|
|
ASSERT_NE(postProcessPassRaw, nullptr);
|
|
EXPECT_TRUE(postProcessPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceWidth, 256u);
|
|
EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceHeight, 128u);
|
|
EXPECT_FALSE(postProcessPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(
|
|
postProcessPassRaw->lastSourceColorView,
|
|
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(20));
|
|
EXPECT_EQ(
|
|
postProcessPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(postProcessPassRaw->lastSurfaceWidth, 512u);
|
|
EXPECT_EQ(postProcessPassRaw->lastSurfaceHeight, 256u);
|
|
EXPECT_FALSE(postProcessPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
ASSERT_NE(finalOutputPassRaw, nullptr);
|
|
EXPECT_TRUE(finalOutputPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceWidth, 512u);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceHeight, 256u);
|
|
EXPECT_FALSE(finalOutputPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(
|
|
finalOutputPassRaw->lastSourceColorView,
|
|
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(30));
|
|
EXPECT_EQ(
|
|
finalOutputPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSurfaceWidth, 800u);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSurfaceHeight, 600u);
|
|
EXPECT_FALSE(finalOutputPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"pipeline",
|
|
"init:postProcess",
|
|
"postProcess",
|
|
"init:finalOutput",
|
|
"finalOutput" }));
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ChainsMultiPassPostProcessThroughIntermediateSurface) {
|
|
Scene scene("CameraRendererMultiPassPostProcessScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(4.0f);
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(pipelineState));
|
|
|
|
auto firstPass = std::make_unique<MockScenePass>(pipelineState, "postProcessTint");
|
|
MockScenePass* firstPassRaw = firstPass.get();
|
|
auto secondPass = std::make_unique<MockScenePass>(pipelineState, "postProcessComposite");
|
|
MockScenePass* secondPassRaw = secondPass.get();
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::move(firstPass));
|
|
postProcessPasses.AddPass(std::move(secondPass));
|
|
|
|
auto* sourceColorAttachment = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* sourceColorShaderView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::ShaderResource,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* destinationColorAttachment = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = context;
|
|
request.surface = RenderSurface(512, 256);
|
|
request.surface.SetColorAttachment(destinationColorAttachment);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.postProcess.sourceSurface = RenderSurface(256, 128);
|
|
request.postProcess.sourceSurface.SetColorAttachment(sourceColorAttachment);
|
|
request.postProcess.sourceColorView = sourceColorShaderView;
|
|
request.postProcess.destinationSurface = RenderSurface(512, 256);
|
|
request.postProcess.destinationSurface.SetColorAttachment(destinationColorAttachment);
|
|
request.postProcess.passes = &postProcessPasses;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
|
|
ASSERT_NE(firstPassRaw, nullptr);
|
|
EXPECT_TRUE(firstPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(firstPassRaw->lastSourceSurfaceWidth, 256u);
|
|
EXPECT_EQ(firstPassRaw->lastSourceSurfaceHeight, 128u);
|
|
EXPECT_FALSE(firstPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(firstPassRaw->lastSourceColorView, sourceColorShaderView);
|
|
EXPECT_EQ(
|
|
firstPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(firstPassRaw->lastSurfaceWidth, 512u);
|
|
EXPECT_EQ(firstPassRaw->lastSurfaceHeight, 256u);
|
|
EXPECT_FALSE(firstPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
|
|
ASSERT_NE(secondPassRaw, nullptr);
|
|
EXPECT_TRUE(secondPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(secondPassRaw->lastSourceSurfaceWidth, 512u);
|
|
EXPECT_EQ(secondPassRaw->lastSourceSurfaceHeight, 256u);
|
|
EXPECT_FALSE(secondPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_NE(secondPassRaw->lastSourceColorView, nullptr);
|
|
EXPECT_NE(secondPassRaw->lastSourceColorView, sourceColorShaderView);
|
|
EXPECT_EQ(
|
|
secondPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(secondPassRaw->lastSurfaceWidth, 512u);
|
|
EXPECT_EQ(secondPassRaw->lastSurfaceHeight, 256u);
|
|
EXPECT_FALSE(secondPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
|
|
EXPECT_EQ(allocationState->createTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 1);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
|
|
EXPECT_EQ(
|
|
pipelineState->eventLog,
|
|
(std::vector<std::string>{
|
|
"pipeline",
|
|
"init:postProcessTint",
|
|
"init:postProcessComposite",
|
|
"postProcessTint",
|
|
"postProcessComposite" }));
|
|
|
|
delete destinationColorAttachment;
|
|
delete sourceColorShaderView;
|
|
delete sourceColorAttachment;
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, KeepsPostProcessAndFinalOutputScratchSurfacesIndependentPerStage) {
|
|
Scene scene("CameraRendererIndependentFullscreenStagesScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(5.0f);
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(pipelineState));
|
|
|
|
auto postFirstPass = std::make_unique<MockScenePass>(pipelineState, "postA");
|
|
auto postSecondPass = std::make_unique<MockScenePass>(pipelineState, "postB");
|
|
MockScenePass* postSecondPassRaw = postSecondPass.get();
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::move(postFirstPass));
|
|
postProcessPasses.AddPass(std::move(postSecondPass));
|
|
|
|
auto finalFirstPass = std::make_unique<MockScenePass>(pipelineState, "finalA");
|
|
MockScenePass* finalFirstPassRaw = finalFirstPass.get();
|
|
auto finalSecondPass = std::make_unique<MockScenePass>(pipelineState, "finalB");
|
|
MockScenePass* finalSecondPassRaw = finalSecondPass.get();
|
|
RenderPassSequence finalOutputPasses;
|
|
finalOutputPasses.AddPass(std::move(finalFirstPass));
|
|
finalOutputPasses.AddPass(std::move(finalSecondPass));
|
|
|
|
auto* sourceColorAttachment = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* sourceColorShaderView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::ShaderResource,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* postProcessDestination = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* finalOutputSource = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::ShaderResource,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* finalOutputDestination = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = context;
|
|
request.surface = RenderSurface(800, 600);
|
|
request.surface.SetColorAttachment(finalOutputDestination);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.postProcess.sourceSurface = RenderSurface(256, 128);
|
|
request.postProcess.sourceSurface.SetColorAttachment(sourceColorAttachment);
|
|
request.postProcess.sourceColorView = sourceColorShaderView;
|
|
request.postProcess.destinationSurface = RenderSurface(512, 256);
|
|
request.postProcess.destinationSurface.SetColorAttachment(postProcessDestination);
|
|
request.postProcess.passes = &postProcessPasses;
|
|
request.finalOutput.sourceSurface = request.postProcess.destinationSurface;
|
|
request.finalOutput.sourceColorView = finalOutputSource;
|
|
request.finalOutput.destinationSurface = RenderSurface(800, 600);
|
|
request.finalOutput.destinationSurface.SetColorAttachment(finalOutputDestination);
|
|
request.finalOutput.passes = &finalOutputPasses;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
|
|
ASSERT_NE(postSecondPassRaw, nullptr);
|
|
EXPECT_EQ(postSecondPassRaw->lastSourceSurfaceWidth, 512u);
|
|
EXPECT_EQ(postSecondPassRaw->lastSourceSurfaceHeight, 256u);
|
|
EXPECT_FALSE(postSecondPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(
|
|
postSecondPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_FALSE(postSecondPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
ASSERT_NE(finalFirstPassRaw, nullptr);
|
|
EXPECT_EQ(finalFirstPassRaw->lastSourceSurfaceWidth, 512u);
|
|
EXPECT_EQ(finalFirstPassRaw->lastSourceSurfaceHeight, 256u);
|
|
EXPECT_FALSE(finalFirstPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(finalFirstPassRaw->lastSourceColorView, finalOutputSource);
|
|
EXPECT_EQ(
|
|
finalFirstPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_FALSE(finalFirstPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
ASSERT_NE(finalSecondPassRaw, nullptr);
|
|
EXPECT_EQ(finalSecondPassRaw->lastSourceSurfaceWidth, 800u);
|
|
EXPECT_EQ(finalSecondPassRaw->lastSourceSurfaceHeight, 600u);
|
|
EXPECT_FALSE(finalSecondPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_NE(finalSecondPassRaw->lastSourceColorView, nullptr);
|
|
EXPECT_NE(finalSecondPassRaw->lastSourceColorView, finalOutputSource);
|
|
EXPECT_EQ(
|
|
finalSecondPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_FALSE(finalSecondPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
|
|
EXPECT_EQ(allocationState->createTextureCalls, 2);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 2);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 2);
|
|
EXPECT_EQ(allocationState->shutdownTextureCalls, 2);
|
|
EXPECT_EQ(allocationState->shutdownRenderTargetViewCalls, 2);
|
|
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 2);
|
|
EXPECT_EQ(allocationState->destroyTextureCalls, 2);
|
|
EXPECT_EQ(allocationState->destroyRenderTargetViewCalls, 2);
|
|
EXPECT_EQ(allocationState->destroyShaderViewCalls, 2);
|
|
EXPECT_EQ(
|
|
pipelineState->eventLog,
|
|
(std::vector<std::string>{
|
|
"pipeline",
|
|
"init:postA",
|
|
"init:postB",
|
|
"postA",
|
|
"postB",
|
|
"init:finalA",
|
|
"init:finalB",
|
|
"finalA",
|
|
"finalB" }));
|
|
|
|
delete finalOutputDestination;
|
|
delete finalOutputSource;
|
|
delete postProcessDestination;
|
|
delete sourceColorShaderView;
|
|
delete sourceColorAttachment;
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, RecordsGraphCapableFullscreenSequencePassesInStageOrder) {
|
|
Scene scene("CameraRendererGraphFullscreenSequences");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(5.0f);
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(pipelineState));
|
|
|
|
auto postFirstPass = std::make_unique<TrackingPass>(pipelineState, "postA", true, true, true);
|
|
auto postSecondPass = std::make_unique<TrackingPass>(pipelineState, "postB", true, true, true);
|
|
TrackingPass* postSecondPassRaw = postSecondPass.get();
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::move(postFirstPass));
|
|
postProcessPasses.AddPass(std::move(postSecondPass));
|
|
|
|
auto finalFirstPass = std::make_unique<TrackingPass>(pipelineState, "finalA", true, true, true);
|
|
TrackingPass* finalFirstPassRaw = finalFirstPass.get();
|
|
auto finalSecondPass = std::make_unique<TrackingPass>(pipelineState, "finalB", true, true, true);
|
|
TrackingPass* finalSecondPassRaw = finalSecondPass.get();
|
|
RenderPassSequence finalOutputPasses;
|
|
finalOutputPasses.AddPass(std::move(finalFirstPass));
|
|
finalOutputPasses.AddPass(std::move(finalSecondPass));
|
|
|
|
auto* sourceColorAttachment = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* sourceColorShaderView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::ShaderResource,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* postProcessDestination = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* finalOutputSource = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::ShaderResource,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* finalOutputDestination = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = context;
|
|
request.surface = RenderSurface(800, 600);
|
|
request.surface.SetColorAttachment(finalOutputDestination);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.postProcess.sourceSurface = RenderSurface(256, 128);
|
|
request.postProcess.sourceSurface.SetColorAttachment(sourceColorAttachment);
|
|
request.postProcess.sourceColorView = sourceColorShaderView;
|
|
request.postProcess.destinationSurface = RenderSurface(512, 256);
|
|
request.postProcess.destinationSurface.SetColorAttachment(postProcessDestination);
|
|
request.postProcess.passes = &postProcessPasses;
|
|
request.finalOutput.sourceSurface = request.postProcess.destinationSurface;
|
|
request.finalOutput.sourceColorView = finalOutputSource;
|
|
request.finalOutput.destinationSurface = RenderSurface(800, 600);
|
|
request.finalOutput.destinationSurface.SetColorAttachment(finalOutputDestination);
|
|
request.finalOutput.passes = &finalOutputPasses;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
|
|
ASSERT_NE(postSecondPassRaw, nullptr);
|
|
EXPECT_TRUE(postSecondPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(postSecondPassRaw->lastSourceSurfaceWidth, 512u);
|
|
EXPECT_EQ(postSecondPassRaw->lastSourceSurfaceHeight, 256u);
|
|
EXPECT_EQ(
|
|
postSecondPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
ASSERT_NE(finalFirstPassRaw, nullptr);
|
|
EXPECT_TRUE(finalFirstPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(finalFirstPassRaw->lastSourceSurfaceWidth, 512u);
|
|
EXPECT_EQ(finalFirstPassRaw->lastSourceSurfaceHeight, 256u);
|
|
EXPECT_EQ(finalFirstPassRaw->lastSourceColorView, finalOutputSource);
|
|
EXPECT_EQ(
|
|
finalFirstPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
ASSERT_NE(finalSecondPassRaw, nullptr);
|
|
EXPECT_TRUE(finalSecondPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(finalSecondPassRaw->lastSourceSurfaceWidth, 800u);
|
|
EXPECT_EQ(finalSecondPassRaw->lastSourceSurfaceHeight, 600u);
|
|
EXPECT_NE(finalSecondPassRaw->lastSourceColorView, nullptr);
|
|
EXPECT_NE(finalSecondPassRaw->lastSourceColorView, finalOutputSource);
|
|
EXPECT_EQ(
|
|
finalSecondPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
|
|
EXPECT_EQ(
|
|
pipelineState->eventLog,
|
|
(std::vector<std::string>{
|
|
"record:postA",
|
|
"record:postB",
|
|
"record:finalA",
|
|
"record:finalB",
|
|
"pipeline",
|
|
"init:postA",
|
|
"init:postB",
|
|
"postA",
|
|
"postB",
|
|
"init:finalA",
|
|
"init:finalB",
|
|
"finalA",
|
|
"finalB" }));
|
|
|
|
delete finalOutputDestination;
|
|
delete finalOutputSource;
|
|
delete postProcessDestination;
|
|
delete sourceColorShaderView;
|
|
delete sourceColorAttachment;
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) {
|
|
Scene scene("CameraRendererFormalFrameStageScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
auto pipeline = std::make_unique<MockPipeline>(state);
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
state);
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::ShadowCaster,
|
|
state,
|
|
"shadowCaster");
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::DepthOnly,
|
|
state,
|
|
"depthOnly");
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::make_unique<TrackingPass>(state, "postProcess"));
|
|
|
|
RenderPassSequence finalOutputPasses;
|
|
finalOutputPasses.AddPass(std::make_unique<TrackingPass>(state, "finalOutput"));
|
|
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::make_unique<TrackingPass>(state, "post"));
|
|
|
|
RenderPassSequence overlayPasses;
|
|
overlayPasses.AddPass(std::make_unique<TrackingPass>(state, "overlay"));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
request.postProcess.passes = &postProcessPasses;
|
|
request.finalOutput.passes = &finalOutputPasses;
|
|
request.postScenePasses = &postPasses;
|
|
request.overlayPasses = &overlayPasses;
|
|
request.shadowCaster.surface = RenderSurface(128, 64);
|
|
request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.depthOnly.surface = RenderSurface(96, 48);
|
|
request.depthOnly.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
request.postProcess.sourceSurface = RenderSurface(256, 128);
|
|
request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(4));
|
|
request.postProcess.sourceColorView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(40);
|
|
request.postProcess.destinationSurface = RenderSurface(320, 180);
|
|
request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(5));
|
|
request.finalOutput.sourceSurface = request.postProcess.destinationSurface;
|
|
request.finalOutput.sourceColorView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(50);
|
|
request.finalOutput.destinationSurface = request.surface;
|
|
request.objectId.surface = RenderSurface(320, 180);
|
|
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(6));
|
|
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(7));
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:pre",
|
|
"pre",
|
|
"init:shadowCaster",
|
|
"shadowCaster",
|
|
"init:depthOnly",
|
|
"depthOnly",
|
|
"pipeline",
|
|
"init:postProcess",
|
|
"postProcess",
|
|
"init:finalOutput",
|
|
"finalOutput",
|
|
"objectId",
|
|
"init:post",
|
|
"post",
|
|
"init:overlay",
|
|
"overlay" }));
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, RecordsGraphCapableSinglePassSequenceStagesInDocumentedOrder) {
|
|
Scene scene("CameraRendererGraphSinglePassSequenceStages");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
auto prePass = std::make_unique<TrackingPass>(state, "pre", true, true, true);
|
|
TrackingPass* prePassRaw = prePass.get();
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::move(prePass));
|
|
|
|
auto postProcessPass = std::make_unique<TrackingPass>(state, "postProcess", true, true, true);
|
|
TrackingPass* postProcessPassRaw = postProcessPass.get();
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::move(postProcessPass));
|
|
|
|
auto finalOutputPass = std::make_unique<TrackingPass>(state, "finalOutput", true, true, true);
|
|
TrackingPass* finalOutputPassRaw = finalOutputPass.get();
|
|
RenderPassSequence finalOutputPasses;
|
|
finalOutputPasses.AddPass(std::move(finalOutputPass));
|
|
|
|
auto postPass = std::make_unique<TrackingPass>(state, "post", true, true, true);
|
|
TrackingPass* postPassRaw = postPass.get();
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::move(postPass));
|
|
|
|
auto overlayPass = std::make_unique<TrackingPass>(state, "overlay", true, true, true);
|
|
TrackingPass* overlayPassRaw = overlayPass.get();
|
|
RenderPassSequence overlayPasses;
|
|
overlayPasses.AddPass(std::move(overlayPass));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
request.postProcess.passes = &postProcessPasses;
|
|
request.finalOutput.passes = &finalOutputPasses;
|
|
request.postScenePasses = &postPasses;
|
|
request.overlayPasses = &overlayPasses;
|
|
request.postProcess.sourceSurface = RenderSurface(256, 128);
|
|
request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(4));
|
|
request.postProcess.sourceColorView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(40);
|
|
request.postProcess.destinationSurface = RenderSurface(320, 180);
|
|
request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(5));
|
|
request.finalOutput.sourceSurface = request.postProcess.destinationSurface;
|
|
request.finalOutput.sourceColorView = reinterpret_cast<XCEngine::RHI::RHIResourceView*>(50);
|
|
request.finalOutput.destinationSurface = request.surface;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"record:pre",
|
|
"record:postProcess",
|
|
"record:finalOutput",
|
|
"record:post",
|
|
"record:overlay",
|
|
"init:pre",
|
|
"pre",
|
|
"pipeline",
|
|
"init:postProcess",
|
|
"postProcess",
|
|
"init:finalOutput",
|
|
"finalOutput",
|
|
"init:post",
|
|
"post",
|
|
"init:overlay",
|
|
"overlay" }));
|
|
|
|
ASSERT_NE(prePassRaw, nullptr);
|
|
EXPECT_TRUE(prePassRaw->lastHasSourceSurface == false);
|
|
EXPECT_FALSE(prePassRaw->lastSurfaceAutoTransitionEnabled);
|
|
ASSERT_NE(postProcessPassRaw, nullptr);
|
|
EXPECT_TRUE(postProcessPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceWidth, 256u);
|
|
EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceHeight, 128u);
|
|
EXPECT_FALSE(postProcessPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(postProcessPassRaw->lastSourceColorView, reinterpret_cast<XCEngine::RHI::RHIResourceView*>(40));
|
|
EXPECT_EQ(
|
|
postProcessPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_FALSE(postProcessPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
ASSERT_NE(finalOutputPassRaw, nullptr);
|
|
EXPECT_TRUE(finalOutputPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceWidth, 320u);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceHeight, 180u);
|
|
EXPECT_FALSE(finalOutputPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSourceColorView, reinterpret_cast<XCEngine::RHI::RHIResourceView*>(50));
|
|
EXPECT_EQ(
|
|
finalOutputPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_FALSE(finalOutputPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
ASSERT_NE(postPassRaw, nullptr);
|
|
EXPECT_FALSE(postPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
ASSERT_NE(overlayPassRaw, nullptr);
|
|
EXPECT_FALSE(overlayPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ResolvesGraphManagedFullscreenSequenceSourcesFromFrameResources) {
|
|
Scene scene("CameraRendererGraphManagedFullscreenSources");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
state->supportsMainSceneRenderGraph = true;
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
MockShadowView colorView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
MockShadowView depthView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::DepthStencil,
|
|
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
auto postProcessPass = std::make_unique<TrackingPass>(state, "postProcess", true, true, true);
|
|
TrackingPass* postProcessPassRaw = postProcessPass.get();
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::move(postProcessPass));
|
|
|
|
auto finalOutputPass = std::make_unique<TrackingPass>(state, "finalOutput", true, true, true);
|
|
TrackingPass* finalOutputPassRaw = finalOutputPass.get();
|
|
RenderPassSequence finalOutputPasses;
|
|
finalOutputPasses.AddPass(std::move(finalOutputPass));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.context.device = &device;
|
|
request.surface = RenderSurface(320, 180);
|
|
request.surface.SetColorAttachment(&colorView);
|
|
request.surface.SetDepthAttachment(&depthView);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.postProcess.passes = &postProcessPasses;
|
|
request.finalOutput.passes = &finalOutputPasses;
|
|
request.finalOutput.destinationSurface = request.surface;
|
|
|
|
CameraFramePlan plan = CameraFramePlan::FromRequest(request);
|
|
plan.colorChain.usesGraphManagedSceneColor = true;
|
|
plan.ConfigureGraphManagedSceneSurface();
|
|
plan.colorChain.postProcess.usesGraphManagedOutputColor = true;
|
|
plan.colorChain.postProcess.source = CameraFrameColorSource::MainSceneColor;
|
|
plan.colorChain.finalOutput.source = CameraFrameColorSource::PostProcessColor;
|
|
|
|
ASSERT_TRUE(plan.IsPostProcessStageValid());
|
|
ASSERT_TRUE(plan.IsFinalOutputStageValid());
|
|
const bool renderResult = renderer.Render(plan);
|
|
EXPECT_TRUE(renderResult);
|
|
EXPECT_EQ(state->recordMainSceneCalls, 1);
|
|
EXPECT_EQ(state->executeRecordedMainSceneCalls, 1);
|
|
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"record:postProcess",
|
|
"record:finalOutput",
|
|
"pipelineGraph",
|
|
"init:postProcess",
|
|
"postProcess",
|
|
"init:finalOutput",
|
|
"finalOutput" }));
|
|
|
|
ASSERT_NE(postProcessPassRaw, nullptr);
|
|
EXPECT_TRUE(postProcessPassRaw->lastRecordedRenderGraphBlackboard);
|
|
EXPECT_TRUE(postProcessPassRaw->lastRecordedSourceColorTextureValid);
|
|
EXPECT_TRUE(postProcessPassRaw->lastRecordedBlackboardMainSceneColorValid);
|
|
EXPECT_NE(postProcessPassRaw->lastSourceColorView, nullptr);
|
|
EXPECT_TRUE(postProcessPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceWidth, 320u);
|
|
EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceHeight, 180u);
|
|
EXPECT_FALSE(postProcessPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(
|
|
postProcessPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
|
|
ASSERT_NE(finalOutputPassRaw, nullptr);
|
|
EXPECT_TRUE(finalOutputPassRaw->lastRecordedRenderGraphBlackboard);
|
|
EXPECT_TRUE(finalOutputPassRaw->lastRecordedSourceColorTextureValid);
|
|
EXPECT_TRUE(finalOutputPassRaw->lastRecordedBlackboardPostProcessColorValid);
|
|
EXPECT_NE(finalOutputPassRaw->lastSourceColorView, nullptr);
|
|
EXPECT_TRUE(finalOutputPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceWidth, 320u);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceHeight, 180u);
|
|
EXPECT_FALSE(finalOutputPassRaw->lastSourceSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(
|
|
finalOutputPassRaw->lastSourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipeline) {
|
|
Scene scene("CameraRendererDepthAndShadowScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
auto pipeline = std::make_unique<MockPipeline>(state);
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
state);
|
|
MockScenePass* shadowPassRaw =
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::ShadowCaster,
|
|
state,
|
|
"shadowCaster");
|
|
MockScenePass* depthPassRaw =
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::DepthOnly,
|
|
state,
|
|
"depthOnly");
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
|
|
request.shadowCaster.surface = RenderSurface(128, 64);
|
|
request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
request.shadowCaster.hasCameraDataOverride = true;
|
|
request.shadowCaster.cameraDataOverride.worldPosition = XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f);
|
|
|
|
request.depthOnly.surface = RenderSurface(96, 48);
|
|
request.depthOnly.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(4));
|
|
request.depthOnly.hasClearColorOverride = true;
|
|
request.depthOnly.clearColorOverride = XCEngine::Math::Color(0.3f, 0.2f, 0.1f, 1.0f);
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:shadowCaster",
|
|
"shadowCaster",
|
|
"init:depthOnly",
|
|
"depthOnly",
|
|
"pipeline" }));
|
|
EXPECT_EQ(shadowPassRaw->lastViewportWidth, 128u);
|
|
EXPECT_EQ(shadowPassRaw->lastViewportHeight, 64u);
|
|
EXPECT_EQ(shadowPassRaw->lastSurfaceWidth, 128u);
|
|
EXPECT_EQ(shadowPassRaw->lastSurfaceHeight, 64u);
|
|
EXPECT_FALSE(shadowPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(shadowPassRaw->lastClearFlags, RenderClearFlags::Depth);
|
|
EXPECT_EQ(shadowPassRaw->lastWorldPosition, XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f));
|
|
EXPECT_EQ(depthPassRaw->lastViewportWidth, 96u);
|
|
EXPECT_EQ(depthPassRaw->lastViewportHeight, 48u);
|
|
EXPECT_FALSE(depthPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(depthPassRaw->lastClearFlags, RenderClearFlags::Depth);
|
|
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.r, 0.3f);
|
|
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.g, 0.2f);
|
|
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.b, 0.1f);
|
|
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.a, 1.0f);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, RecordsGraphCapableStandalonePassStagesBeforeExecution) {
|
|
Scene scene("CameraRendererGraphStandaloneStages");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
auto pipeline = std::make_unique<MockPipeline>(state);
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
state,
|
|
true,
|
|
true);
|
|
MockScenePass* shadowPassRaw =
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::ShadowCaster,
|
|
state,
|
|
"shadowCaster",
|
|
true,
|
|
true,
|
|
true);
|
|
MockScenePass* depthPassRaw =
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::DepthOnly,
|
|
state,
|
|
"depthOnly",
|
|
true,
|
|
true,
|
|
true);
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.cameraDepth = camera->GetDepth();
|
|
|
|
request.shadowCaster.surface = RenderSurface(128, 64);
|
|
request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
request.shadowCaster.hasCameraDataOverride = true;
|
|
request.shadowCaster.cameraDataOverride.worldPosition = XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f);
|
|
|
|
request.depthOnly.surface = RenderSurface(96, 48);
|
|
request.depthOnly.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(4));
|
|
request.depthOnly.hasClearColorOverride = true;
|
|
request.depthOnly.clearColorOverride = XCEngine::Math::Color(0.3f, 0.2f, 0.1f, 1.0f);
|
|
|
|
request.objectId.surface = RenderSurface(320, 180);
|
|
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(6));
|
|
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(7));
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"record:shadowCaster",
|
|
"record:depthOnly",
|
|
"record:objectId",
|
|
"init:shadowCaster",
|
|
"shadowCaster",
|
|
"init:depthOnly",
|
|
"depthOnly",
|
|
"pipeline",
|
|
"objectId" }));
|
|
ASSERT_NE(shadowPassRaw, nullptr);
|
|
EXPECT_EQ(shadowPassRaw->lastViewportWidth, 128u);
|
|
EXPECT_EQ(shadowPassRaw->lastViewportHeight, 64u);
|
|
EXPECT_FALSE(shadowPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(shadowPassRaw->lastWorldPosition, XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f));
|
|
ASSERT_NE(depthPassRaw, nullptr);
|
|
EXPECT_EQ(depthPassRaw->lastViewportWidth, 96u);
|
|
EXPECT_EQ(depthPassRaw->lastViewportHeight, 48u);
|
|
EXPECT_FALSE(depthPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.r, 0.3f);
|
|
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.g, 0.2f);
|
|
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.b, 0.1f);
|
|
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.a, 1.0f);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, UsesGraphManagedSurfaceForMainSceneWhenOutputAttachmentsExist) {
|
|
Scene scene("CameraRendererGraphManagedMainScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
request.cameraDepth = camera->GetDepth();
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(state->renderCalls, 1);
|
|
EXPECT_FALSE(state->lastSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(state->lastSurfaceWidth, 320u);
|
|
EXPECT_EQ(state->lastSurfaceHeight, 180u);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, UsesPipelineRecordedMainSceneWhenSupported) {
|
|
Scene scene("CameraRendererPipelineRecordedMainScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(3.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
state->supportsMainSceneRenderGraph = true;
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
request.cameraDepth = camera->GetDepth();
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(state->recordMainSceneCalls, 1);
|
|
EXPECT_EQ(state->executeRecordedMainSceneCalls, 1);
|
|
EXPECT_EQ(state->renderCalls, 0);
|
|
EXPECT_EQ(state->eventLog, (std::vector<std::string>{ "pipelineGraph" }));
|
|
EXPECT_TRUE(state->lastReceivedRenderGraphBlackboard);
|
|
EXPECT_TRUE(state->lastBlackboardMainSceneColorValid);
|
|
EXPECT_TRUE(state->lastBlackboardMainSceneDepthValid);
|
|
EXPECT_FALSE(state->lastBlackboardMainDirectionalShadowValid);
|
|
EXPECT_FALSE(state->lastBlackboardObjectIdColorValid);
|
|
EXPECT_FALSE(state->lastSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(state->lastSurfaceWidth, 320u);
|
|
EXPECT_EQ(state->lastSurfaceHeight, 180u);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) {
|
|
Scene scene("CameraRendererAutoDirectionalShadowScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
{
|
|
auto pipeline = std::make_unique<MockPipeline>(pipelineState);
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
pipelineState);
|
|
MockScenePass* shadowPassRaw =
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::ShadowCaster,
|
|
pipelineState,
|
|
"shadowCaster");
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = context;
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.directionalShadow.enabled = true;
|
|
request.directionalShadow.mapWidth = 256;
|
|
request.directionalShadow.mapHeight = 128;
|
|
request.directionalShadow.cameraData.viewportWidth = 256;
|
|
request.directionalShadow.cameraData.viewportHeight = 128;
|
|
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
|
|
request.directionalShadow.cameraData.worldPosition = XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f);
|
|
request.directionalShadow.cameraData.viewProjection =
|
|
XCEngine::Math::Matrix4x4::Translation(XCEngine::Math::Vector3(11.0f, 12.0f, 13.0f));
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(
|
|
pipelineState->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:shadowCaster",
|
|
"shadowCaster",
|
|
"pipeline" }));
|
|
EXPECT_EQ(shadowPassRaw->lastViewportWidth, 256u);
|
|
EXPECT_EQ(shadowPassRaw->lastViewportHeight, 128u);
|
|
EXPECT_EQ(shadowPassRaw->lastSurfaceWidth, 256u);
|
|
EXPECT_EQ(shadowPassRaw->lastSurfaceHeight, 128u);
|
|
EXPECT_FALSE(shadowPassRaw->lastSurfaceAutoTransitionEnabled);
|
|
EXPECT_EQ(shadowPassRaw->lastClearFlags, RenderClearFlags::Depth);
|
|
EXPECT_EQ(shadowPassRaw->lastWorldPosition, XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f));
|
|
EXPECT_EQ(allocationState->createTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->createDepthViewCalls, 1);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->lastTextureWidth, 256u);
|
|
EXPECT_EQ(allocationState->lastTextureHeight, 128u);
|
|
EXPECT_EQ(allocationState->lastTextureFormat, XCEngine::RHI::Format::D32_Float);
|
|
EXPECT_EQ(allocationState->lastDepthViewFormat, XCEngine::RHI::Format::D32_Float);
|
|
EXPECT_EQ(allocationState->lastShaderViewFormat, XCEngine::RHI::Format::Unknown);
|
|
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 0);
|
|
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 0);
|
|
EXPECT_EQ(allocationState->shutdownTextureCalls, 0);
|
|
EXPECT_EQ(allocationState->destroyDepthViewCalls, 0);
|
|
EXPECT_EQ(allocationState->destroyShaderViewCalls, 0);
|
|
EXPECT_EQ(allocationState->destroyTextureCalls, 0);
|
|
EXPECT_TRUE(pipelineState->lastHasMainDirectionalShadow);
|
|
EXPECT_NE(pipelineState->lastShadowMap, nullptr);
|
|
EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[0][3], 11.0f);
|
|
EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[1][3], 12.0f);
|
|
EXPECT_FLOAT_EQ(pipelineState->lastShadowViewProjection.m[2][3], 13.0f);
|
|
EXPECT_FLOAT_EQ(pipelineState->lastShadowSampling.settings.receiverDepthBias, 0.0010f);
|
|
EXPECT_FLOAT_EQ(pipelineState->lastShadowMapMetrics.inverseMapSize.x, 1.0f / 256.0f);
|
|
EXPECT_FLOAT_EQ(pipelineState->lastShadowMapMetrics.inverseMapSize.y, 1.0f / 128.0f);
|
|
EXPECT_FLOAT_EQ(pipelineState->lastShadowSampling.settings.shadowStrength, 0.85f);
|
|
EXPECT_TRUE(pipelineState->lastHasMainDirectionalShadowKeyword);
|
|
}
|
|
|
|
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyDepthViewCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyTextureCalls, 1);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, AllowsPipelineToOverrideAutoAllocatedDirectionalShadowExecutionState) {
|
|
Scene scene("CameraRendererDirectionalShadowExecutionPolicyScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
pipelineState->configureDirectionalShadowExecutionState =
|
|
[](
|
|
const CameraFramePlan& plan,
|
|
const DirectionalShadowSurfaceAllocation& shadowAllocation,
|
|
DirectionalShadowExecutionState& shadowState) {
|
|
ApplyDefaultRenderPipelineDirectionalShadowExecutionPolicy(
|
|
plan,
|
|
shadowAllocation,
|
|
shadowState);
|
|
shadowState.shadowCasterRequest.clearFlags = RenderClearFlags::All;
|
|
shadowState.shadowCasterRequest.hasCameraDataOverride = true;
|
|
shadowState.shadowCasterRequest.cameraDataOverride =
|
|
plan.directionalShadow.cameraData;
|
|
shadowState.shadowCasterRequest.cameraDataOverride.worldPosition =
|
|
XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f);
|
|
shadowState.shadowData.sampling.settings.shadowStrength = 0.25f;
|
|
return true;
|
|
};
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
{
|
|
auto pipeline = std::make_unique<MockPipeline>(pipelineState);
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
pipelineState);
|
|
MockScenePass* shadowPassRaw =
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::ShadowCaster,
|
|
pipelineState,
|
|
"shadowCaster");
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = context;
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.directionalShadow.enabled = true;
|
|
request.directionalShadow.mapWidth = 256;
|
|
request.directionalShadow.mapHeight = 128;
|
|
request.directionalShadow.cameraData.viewportWidth = 256;
|
|
request.directionalShadow.cameraData.viewportHeight = 128;
|
|
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
|
|
request.directionalShadow.cameraData.worldPosition =
|
|
XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f);
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(
|
|
pipelineState->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:shadowCaster",
|
|
"shadowCaster",
|
|
"pipeline" }));
|
|
EXPECT_EQ(
|
|
pipelineState->configureDirectionalShadowExecutionStateCalls,
|
|
1);
|
|
EXPECT_EQ(shadowPassRaw->lastClearFlags, RenderClearFlags::All);
|
|
EXPECT_EQ(
|
|
shadowPassRaw->lastWorldPosition,
|
|
XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f));
|
|
EXPECT_FLOAT_EQ(
|
|
pipelineState->lastShadowSampling.settings.shadowStrength,
|
|
0.25f);
|
|
}
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, PassesShadowDependencyToPipelineRecordedMainScene) {
|
|
Scene scene("CameraRendererPipelineRecordedShadowDependency");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
pipelineState->supportsMainSceneRenderGraph = true;
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
auto pipeline = std::make_unique<MockPipeline>(pipelineState);
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
pipelineState);
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::ShadowCaster,
|
|
pipelineState,
|
|
"shadowCaster");
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = context;
|
|
request.surface = RenderSurface(320, 180);
|
|
request.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.directionalShadow.enabled = true;
|
|
request.directionalShadow.mapWidth = 256;
|
|
request.directionalShadow.mapHeight = 128;
|
|
request.directionalShadow.cameraData.viewportWidth = 256;
|
|
request.directionalShadow.cameraData.viewportHeight = 128;
|
|
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
|
|
request.directionalShadow.cameraData.worldPosition = XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f);
|
|
request.directionalShadow.cameraData.viewProjection =
|
|
XCEngine::Math::Matrix4x4::Translation(XCEngine::Math::Vector3(11.0f, 12.0f, 13.0f));
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(pipelineState->recordMainSceneCalls, 1);
|
|
EXPECT_EQ(pipelineState->executeRecordedMainSceneCalls, 1);
|
|
EXPECT_TRUE(pipelineState->lastRecordedMainSceneShadowHandleValid);
|
|
EXPECT_TRUE(pipelineState->lastReceivedRenderGraphBlackboard);
|
|
EXPECT_TRUE(pipelineState->lastBlackboardMainSceneColorValid);
|
|
EXPECT_TRUE(pipelineState->lastBlackboardMainSceneDepthValid);
|
|
EXPECT_TRUE(pipelineState->lastBlackboardMainDirectionalShadowValid);
|
|
EXPECT_FALSE(pipelineState->lastBlackboardObjectIdColorValid);
|
|
EXPECT_EQ(
|
|
pipelineState->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:shadowCaster",
|
|
"shadowCaster",
|
|
"pipelineGraph" }));
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ReusesDirectionalShadowSurfaceWhenPlanMatches) {
|
|
Scene scene("CameraRendererDirectionalShadowReuseScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
auto pipeline = std::make_unique<MockPipeline>(pipelineState);
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
pipelineState);
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::ShadowCaster,
|
|
pipelineState,
|
|
"shadowCaster");
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = context;
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.directionalShadow.enabled = true;
|
|
request.directionalShadow.mapWidth = 256;
|
|
request.directionalShadow.mapHeight = 128;
|
|
request.directionalShadow.cameraData.viewportWidth = 256;
|
|
request.directionalShadow.cameraData.viewportHeight = 128;
|
|
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
XCEngine::RHI::RHIResourceView* firstShadowMap = pipelineState->lastShadowMap;
|
|
ASSERT_NE(firstShadowMap, nullptr);
|
|
EXPECT_EQ(allocationState->createTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->createDepthViewCalls, 1);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 0);
|
|
EXPECT_EQ(allocationState->destroyTextureCalls, 0);
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(allocationState->createTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->createDepthViewCalls, 1);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 0);
|
|
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 0);
|
|
EXPECT_EQ(allocationState->shutdownTextureCalls, 0);
|
|
EXPECT_EQ(allocationState->destroyDepthViewCalls, 0);
|
|
EXPECT_EQ(allocationState->destroyShaderViewCalls, 0);
|
|
EXPECT_EQ(allocationState->destroyTextureCalls, 0);
|
|
EXPECT_EQ(pipelineState->lastShadowMap, firstShadowMap);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, RecreatesDirectionalShadowSurfaceWhenPlanSizeChanges) {
|
|
Scene scene("CameraRendererDirectionalShadowResizeScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
{
|
|
auto pipeline = std::make_unique<MockPipeline>(pipelineState);
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
pipelineState);
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::ShadowCaster,
|
|
pipelineState,
|
|
"shadowCaster");
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = context;
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.directionalShadow.enabled = true;
|
|
request.directionalShadow.mapWidth = 256;
|
|
request.directionalShadow.mapHeight = 128;
|
|
request.directionalShadow.cameraData.viewportWidth = 256;
|
|
request.directionalShadow.cameraData.viewportHeight = 128;
|
|
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
XCEngine::RHI::RHIResourceView* firstShadowMap = pipelineState->lastShadowMap;
|
|
ASSERT_NE(firstShadowMap, nullptr);
|
|
|
|
request.directionalShadow.mapWidth = 512;
|
|
request.directionalShadow.mapHeight = 256;
|
|
request.directionalShadow.cameraData.viewportWidth = 512;
|
|
request.directionalShadow.cameraData.viewportHeight = 256;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(allocationState->createTextureCalls, 2);
|
|
EXPECT_EQ(allocationState->createDepthViewCalls, 2);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 2);
|
|
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyDepthViewCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyTextureCalls, 1);
|
|
EXPECT_NE(pipelineState->lastShadowMap, firstShadowMap);
|
|
EXPECT_EQ(allocationState->lastTextureWidth, 512u);
|
|
EXPECT_EQ(allocationState->lastTextureHeight, 256u);
|
|
EXPECT_EQ(allocationState->lastTextureFormat, XCEngine::RHI::Format::D32_Float);
|
|
}
|
|
|
|
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 2);
|
|
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 2);
|
|
EXPECT_EQ(allocationState->shutdownTextureCalls, 2);
|
|
EXPECT_EQ(allocationState->destroyDepthViewCalls, 2);
|
|
EXPECT_EQ(allocationState->destroyShaderViewCalls, 2);
|
|
EXPECT_EQ(allocationState->destroyTextureCalls, 2);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, EnablesDirectionalShadowSurfaceAfterInitialFrameWithoutShadows) {
|
|
Scene scene("CameraRendererDirectionalShadowToggleScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
{
|
|
auto pipeline = std::make_unique<MockPipeline>(pipelineState);
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
pipelineState);
|
|
InstallStandaloneStagePass<MockScenePass>(
|
|
*pipeline,
|
|
CameraFrameStage::ShadowCaster,
|
|
pipelineState,
|
|
"shadowCaster");
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = context;
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(pipelineState->eventLog, (std::vector<std::string>{ "pipeline" }));
|
|
EXPECT_FALSE(pipelineState->lastHasMainDirectionalShadow);
|
|
EXPECT_EQ(pipelineState->lastShadowMap, nullptr);
|
|
EXPECT_EQ(allocationState->createTextureCalls, 0);
|
|
EXPECT_EQ(allocationState->createDepthViewCalls, 0);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 0);
|
|
|
|
pipelineState->eventLog.clear();
|
|
|
|
request.directionalShadow.enabled = true;
|
|
request.directionalShadow.mapWidth = 256;
|
|
request.directionalShadow.mapHeight = 128;
|
|
request.directionalShadow.cameraData.viewportWidth = 256;
|
|
request.directionalShadow.cameraData.viewportHeight = 128;
|
|
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(
|
|
pipelineState->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:shadowCaster",
|
|
"shadowCaster",
|
|
"pipeline" }));
|
|
EXPECT_TRUE(pipelineState->lastHasMainDirectionalShadow);
|
|
EXPECT_NE(pipelineState->lastShadowMap, nullptr);
|
|
EXPECT_EQ(allocationState->createTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->createDepthViewCalls, 1);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 0);
|
|
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 0);
|
|
EXPECT_EQ(allocationState->shutdownTextureCalls, 0);
|
|
}
|
|
|
|
EXPECT_EQ(allocationState->shutdownDepthViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->shutdownTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyDepthViewCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyShaderViewCalls, 1);
|
|
EXPECT_EQ(allocationState->destroyTextureCalls, 1);
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, StopsRenderingWhenShadowCasterRequestIsInvalid) {
|
|
Scene scene("CameraRendererInvalidShadowScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.shadowCaster.surface = RenderSurface(64, 64);
|
|
request.shadowCaster.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
|
|
EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_TRUE(state->eventLog.empty());
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, StopsRenderingWhenPostProcessRequestIsInvalid) {
|
|
Scene scene("CameraRendererInvalidPostProcessScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::make_unique<TrackingPass>(state, "postProcess"));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.postProcess.passes = &postProcessPasses;
|
|
request.postProcess.sourceSurface = RenderSurface(256, 128);
|
|
request.postProcess.destinationSurface = RenderSurface(320, 180);
|
|
request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
|
|
EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_TRUE(state->eventLog.empty());
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ShutsDownInitializedPassesWhenPipelineRenderFails) {
|
|
Scene scene("CameraRendererFailureScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
state->renderResult = false;
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
|
|
EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{ "init:pre", "pre", "pipeline" }));
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, StopsRenderingWhenObjectIdPassFails) {
|
|
Scene scene("CameraRendererObjectIdFailureScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
auto pipeline = std::make_unique<MockPipeline>(state);
|
|
InstallStandaloneStagePass<MockObjectIdPass>(
|
|
*pipeline,
|
|
CameraFrameStage::ObjectId,
|
|
state,
|
|
false);
|
|
CameraRenderer renderer(std::move(pipeline));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::make_unique<TrackingPass>(state, "post"));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
request.postScenePasses = &postPasses;
|
|
request.objectId.surface = RenderSurface(320, 180);
|
|
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
|
|
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
|
|
|
EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{ "init:pre", "pre", "pipeline", "objectId" }));
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, ShutsDownSequencesWhenPostPassInitializationFails) {
|
|
Scene scene("CameraRendererPostPassInitFailureScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(4.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
RenderPassSequence prePasses;
|
|
prePasses.AddPass(std::make_unique<TrackingPass>(state, "pre"));
|
|
|
|
RenderPassSequence postPasses;
|
|
postPasses.AddPass(std::make_unique<TrackingPass>(state, "post", false, true));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(512, 512);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.preScenePasses = &prePasses;
|
|
request.postScenePasses = &postPasses;
|
|
|
|
EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:pre",
|
|
"pre",
|
|
"pipeline",
|
|
"init:post",
|
|
"shutdown:post" }));
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, BuildsSortedRequestsForAllUsableCamerasAndHonorsOverrideCamera) {
|
|
Scene scene("SceneRendererRequestScene");
|
|
|
|
GameObject* lowCameraObject = scene.CreateGameObject("LowCamera");
|
|
auto* lowCamera = lowCameraObject->AddComponent<CameraComponent>();
|
|
lowCamera->SetPrimary(true);
|
|
lowCamera->SetDepth(1.0f);
|
|
|
|
GameObject* highCameraObject = scene.CreateGameObject("HighCamera");
|
|
auto* highCamera = highCameraObject->AddComponent<CameraComponent>();
|
|
highCamera->SetPrimary(true);
|
|
highCamera->SetDepth(5.0f);
|
|
highCamera->SetClearMode(CameraClearMode::None);
|
|
|
|
SceneRenderer renderer;
|
|
const RenderContext context = CreateValidContext();
|
|
const RenderSurface surface(320, 180);
|
|
|
|
const std::vector<CameraFramePlan> defaultPlans =
|
|
renderer.BuildFramePlans(scene, nullptr, context, surface);
|
|
ASSERT_EQ(defaultPlans.size(), 2u);
|
|
EXPECT_EQ(defaultPlans[0].request.camera, lowCamera);
|
|
EXPECT_EQ(defaultPlans[0].request.cameraDepth, 1.0f);
|
|
EXPECT_EQ(defaultPlans[0].request.clearFlags, RenderClearFlags::All);
|
|
EXPECT_EQ(defaultPlans[0].request.surface.GetWidth(), 320u);
|
|
EXPECT_EQ(defaultPlans[0].request.surface.GetHeight(), 180u);
|
|
EXPECT_EQ(defaultPlans[1].request.camera, highCamera);
|
|
EXPECT_EQ(defaultPlans[1].request.cameraDepth, 5.0f);
|
|
EXPECT_EQ(defaultPlans[1].request.clearFlags, RenderClearFlags::None);
|
|
|
|
const std::vector<CameraFramePlan> overridePlans =
|
|
renderer.BuildFramePlans(scene, lowCamera, context, surface);
|
|
ASSERT_EQ(overridePlans.size(), 1u);
|
|
EXPECT_EQ(overridePlans[0].request.camera, lowCamera);
|
|
EXPECT_EQ(overridePlans[0].request.clearFlags, RenderClearFlags::All);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, RendersBaseCamerasBeforeOverlayCamerasAndResolvesAutoClearPerStackType) {
|
|
Scene scene("SceneRendererCameraStackScene");
|
|
|
|
GameObject* lateBaseCameraObject = scene.CreateGameObject("LateBaseCamera");
|
|
auto* lateBaseCamera = lateBaseCameraObject->AddComponent<CameraComponent>();
|
|
lateBaseCamera->SetDepth(10.0f);
|
|
lateBaseCamera->SetStackType(CameraStackType::Base);
|
|
|
|
GameObject* earlyBaseCameraObject = scene.CreateGameObject("EarlyBaseCamera");
|
|
auto* earlyBaseCamera = earlyBaseCameraObject->AddComponent<CameraComponent>();
|
|
earlyBaseCamera->SetDepth(1.0f);
|
|
earlyBaseCamera->SetStackType(CameraStackType::Base);
|
|
|
|
GameObject* overlayCameraObject = scene.CreateGameObject("OverlayCamera");
|
|
auto* overlayCamera = overlayCameraObject->AddComponent<CameraComponent>();
|
|
overlayCamera->SetDepth(-10.0f);
|
|
overlayCamera->SetStackType(CameraStackType::Overlay);
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(plans.size(), 3u);
|
|
EXPECT_EQ(plans[0].request.camera, earlyBaseCamera);
|
|
EXPECT_EQ(plans[0].request.cameraStackOrder, 0u);
|
|
EXPECT_EQ(plans[0].request.clearFlags, RenderClearFlags::All);
|
|
EXPECT_EQ(plans[1].request.camera, lateBaseCamera);
|
|
EXPECT_EQ(plans[1].request.cameraStackOrder, 0u);
|
|
EXPECT_EQ(plans[1].request.clearFlags, RenderClearFlags::Depth);
|
|
EXPECT_EQ(plans[2].request.camera, overlayCamera);
|
|
EXPECT_EQ(plans[2].request.cameraStackOrder, 1u);
|
|
EXPECT_EQ(plans[2].request.clearFlags, RenderClearFlags::Depth);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, PreservesSceneTraversalOrderForEqualPriorityCameras) {
|
|
Scene scene("SceneRendererStableSceneOrder");
|
|
|
|
GameObject* firstCameraObject = scene.CreateGameObject("FirstCamera");
|
|
auto* firstCamera = firstCameraObject->AddComponent<CameraComponent>();
|
|
firstCamera->SetPrimary(false);
|
|
firstCamera->SetDepth(2.0f);
|
|
firstCamera->SetStackType(CameraStackType::Base);
|
|
|
|
GameObject* secondCameraObject = scene.CreateGameObject("SecondCamera");
|
|
auto* secondCamera = secondCameraObject->AddComponent<CameraComponent>();
|
|
secondCamera->SetPrimary(false);
|
|
secondCamera->SetDepth(2.0f);
|
|
secondCamera->SetStackType(CameraStackType::Base);
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(plans.size(), 2u);
|
|
EXPECT_EQ(plans[0].request.camera, firstCamera);
|
|
EXPECT_EQ(plans[1].request.camera, secondCamera);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, FallsBackToColorClearForFirstOverlayCameraWhenNoBaseCameraExists) {
|
|
Scene scene("SceneRendererOverlayOnlyScene");
|
|
|
|
GameObject* firstOverlayObject = scene.CreateGameObject("FirstOverlay");
|
|
auto* firstOverlay = firstOverlayObject->AddComponent<CameraComponent>();
|
|
firstOverlay->SetDepth(1.0f);
|
|
firstOverlay->SetStackType(CameraStackType::Overlay);
|
|
|
|
GameObject* secondOverlayObject = scene.CreateGameObject("SecondOverlay");
|
|
auto* secondOverlay = secondOverlayObject->AddComponent<CameraComponent>();
|
|
secondOverlay->SetDepth(2.0f);
|
|
secondOverlay->SetStackType(CameraStackType::Overlay);
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, CreateValidContext(), RenderSurface(320, 180));
|
|
|
|
ASSERT_EQ(plans.size(), 2u);
|
|
EXPECT_EQ(plans[0].request.camera, firstOverlay);
|
|
EXPECT_EQ(plans[0].request.clearFlags, RenderClearFlags::All);
|
|
EXPECT_EQ(plans[1].request.camera, secondOverlay);
|
|
EXPECT_EQ(plans[1].request.clearFlags, RenderClearFlags::Depth);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, HonorsExplicitOverrideCameraClearMode) {
|
|
Scene scene("SceneRendererOverrideClearModeScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetClearMode(CameraClearMode::DepthOnly);
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, camera, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
EXPECT_EQ(plans[0].request.camera, camera);
|
|
EXPECT_EQ(plans[0].request.clearFlags, RenderClearFlags::Depth);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, ResolvesNormalizedCameraViewportRectToPerRequestRenderArea) {
|
|
Scene scene("SceneRendererViewportRectScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetViewportRect(XCEngine::Math::Rect(0.25f, 0.1f, 0.5f, 0.4f));
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, CreateValidContext(), RenderSurface(800, 600));
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
const XCEngine::Math::RectInt renderArea = plans[0].request.surface.GetRenderArea();
|
|
EXPECT_EQ(renderArea.x, 200);
|
|
EXPECT_EQ(renderArea.y, 60);
|
|
EXPECT_EQ(renderArea.width, 400);
|
|
EXPECT_EQ(renderArea.height, 240);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, ComposesCameraViewportRectWithinExistingSurfaceRenderArea) {
|
|
Scene scene("SceneRendererNestedViewportRectScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetViewportRect(XCEngine::Math::Rect(0.25f, 0.1f, 0.5f, 0.4f));
|
|
|
|
RenderSurface surface(800, 600);
|
|
surface.SetRenderArea(XCEngine::Math::RectInt(100, 50, 400, 300));
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, CreateValidContext(), surface);
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
const XCEngine::Math::RectInt renderArea = plans[0].request.surface.GetRenderArea();
|
|
EXPECT_EQ(renderArea.x, 200);
|
|
EXPECT_EQ(renderArea.y, 80);
|
|
EXPECT_EQ(renderArea.width, 200);
|
|
EXPECT_EQ(renderArea.height, 120);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, PreservesExistingSurfaceRenderAreaForFullViewportCamera) {
|
|
Scene scene("SceneRendererFullViewportNestedSurfaceScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetViewportRect(XCEngine::Math::Rect(0.0f, 0.0f, 1.0f, 1.0f));
|
|
|
|
RenderSurface surface(1024, 768);
|
|
surface.SetRenderArea(XCEngine::Math::RectInt(80, 120, 320, 240));
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, CreateValidContext(), surface);
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
const XCEngine::Math::RectInt renderArea = plans[0].request.surface.GetRenderArea();
|
|
EXPECT_EQ(renderArea.x, 80);
|
|
EXPECT_EQ(renderArea.y, 120);
|
|
EXPECT_EQ(renderArea.width, 320);
|
|
EXPECT_EQ(renderArea.height, 240);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, BuildsCameraColorScalePostProcessRequestFromCameraPassStack) {
|
|
Scene scene("SceneRendererCameraPostProcessScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetViewportRect(XCEngine::Math::Rect(0.25f, 0.125f, 0.5f, 0.625f));
|
|
camera->SetPostProcessPasses({
|
|
XCEngine::Rendering::CameraPostProcessPassDesc::MakeColorScale(
|
|
XCEngine::Math::Vector4(1.0f, 0.75f, 0.75f, 1.0f)),
|
|
XCEngine::Rendering::CameraPostProcessPassDesc::MakeColorScale(
|
|
XCEngine::Math::Vector4(0.55f, 0.95f, 1.1f, 1.0f))
|
|
});
|
|
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
auto* backBufferColorView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* depthView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::DepthStencil,
|
|
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
RenderSurface surface(800, 600);
|
|
surface.SetColorAttachment(backBufferColorView);
|
|
surface.SetDepthAttachment(depthView);
|
|
surface.SetDepthStateBefore(XCEngine::RHI::ResourceStates::Common);
|
|
surface.SetDepthStateAfter(XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, context, surface);
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
const CameraFramePlan& plan = plans[0];
|
|
EXPECT_TRUE(plan.postProcess.IsRequested());
|
|
EXPECT_TRUE(plan.IsPostProcessStageValid());
|
|
EXPECT_NE(plan.postProcess.passes, nullptr);
|
|
ASSERT_EQ(plan.postProcess.passes->GetPassCount(), 2u);
|
|
EXPECT_TRUE(plan.UsesGraphManagedSceneColor());
|
|
EXPECT_FALSE(plan.UsesGraphManagedOutputColor(CameraFrameStage::PostProcess));
|
|
EXPECT_EQ(
|
|
plan.ResolveStageColorSource(CameraFrameStage::PostProcess),
|
|
CameraFrameColorSource::MainSceneColor);
|
|
EXPECT_EQ(plan.postProcess.destinationSurface.GetColorAttachments()[0], backBufferColorView);
|
|
EXPECT_EQ(plan.postProcess.destinationSurface.GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
plan.postProcess.destinationSurface.GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
plan.postProcess.destinationSurface.GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(plan.GetMainSceneSurface().GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
plan.GetMainSceneSurface().GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
plan.GetMainSceneSurface().GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(plan.GetMainSceneSurface().GetWidth(), 800u);
|
|
EXPECT_EQ(plan.GetMainSceneSurface().GetHeight(), 600u);
|
|
const XCEngine::Math::RectInt sourceRenderArea = plan.GetMainSceneSurface().GetRenderArea();
|
|
EXPECT_EQ(sourceRenderArea.x, 200);
|
|
EXPECT_EQ(sourceRenderArea.y, 75);
|
|
EXPECT_EQ(sourceRenderArea.width, 400);
|
|
EXPECT_EQ(sourceRenderArea.height, 375);
|
|
const CameraFrameStageSourceBinding postProcessSource =
|
|
ResolveStageSourceBinding(CameraFrameStage::PostProcess, plan);
|
|
EXPECT_EQ(postProcessSource.sourceSurface, nullptr);
|
|
EXPECT_EQ(postProcessSource.sourceColorView, nullptr);
|
|
EXPECT_EQ(
|
|
postProcessSource.sourceColorState,
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(allocationState->createTextureCalls, 0);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 0);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 0);
|
|
|
|
delete depthView;
|
|
delete backBufferColorView;
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, ResolvesFinalColorPolicyFromPipelineDefaultsAndCameraOverrides) {
|
|
Scene scene("SceneRendererFinalColorPolicyScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
FinalColorOverrideSettings cameraOverrides = {};
|
|
cameraOverrides.overrideExposureValue = true;
|
|
cameraOverrides.exposureValue = 1.8f;
|
|
cameraOverrides.overrideFinalColorScale = true;
|
|
cameraOverrides.finalColorScale = XCEngine::Math::Vector4(0.95f, 0.9f, 0.85f, 1.0f);
|
|
camera->SetFinalColorOverrides(cameraOverrides);
|
|
|
|
auto assetState = std::make_shared<MockPipelineAssetState>();
|
|
assetState->defaultFinalColorSettings.outputTransferMode =
|
|
FinalColorOutputTransferMode::LinearToSRGB;
|
|
assetState->defaultFinalColorSettings.exposureMode =
|
|
FinalColorExposureMode::Fixed;
|
|
assetState->defaultFinalColorSettings.exposureValue = 1.25f;
|
|
assetState->defaultFinalColorSettings.finalColorScale =
|
|
XCEngine::Math::Vector4(1.0f, 0.95f, 0.9f, 1.0f);
|
|
|
|
SceneRenderer renderer(std::make_shared<MockPipelineAsset>(assetState));
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
const CameraFramePlan& plan = plans[0];
|
|
EXPECT_TRUE(plan.finalColorPolicy.hasPipelineDefaults);
|
|
EXPECT_TRUE(plan.finalColorPolicy.hasCameraOverrides);
|
|
EXPECT_EQ(
|
|
plan.finalColorPolicy.outputTransferMode,
|
|
FinalColorOutputTransferMode::LinearToSRGB);
|
|
EXPECT_EQ(
|
|
plan.finalColorPolicy.exposureMode,
|
|
FinalColorExposureMode::Fixed);
|
|
EXPECT_FLOAT_EQ(plan.finalColorPolicy.exposureValue, 1.8f);
|
|
EXPECT_FLOAT_EQ(plan.finalColorPolicy.finalColorScale.x, 0.95f);
|
|
EXPECT_FALSE(plan.finalOutput.IsRequested());
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, UsesPipelineAssetCameraFramePlanConfigurationHook) {
|
|
Scene scene("SceneRendererPlanConfigurationHookScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetPostProcessPasses({
|
|
XCEngine::Rendering::CameraPostProcessPassDesc::MakeColorScale(
|
|
XCEngine::Math::Vector4(1.0f, 0.75f, 0.75f, 1.0f))
|
|
});
|
|
|
|
FinalColorOverrideSettings cameraOverrides = {};
|
|
cameraOverrides.overrideOutputTransferMode = true;
|
|
cameraOverrides.outputTransferMode = FinalColorOutputTransferMode::LinearToSRGB;
|
|
camera->SetFinalColorOverrides(cameraOverrides);
|
|
|
|
auto assetState = std::make_shared<MockPipelineAssetState>();
|
|
assetState->configureCameraFramePlan = [](
|
|
CameraFramePlan& plan) {
|
|
plan.ClearOwnedPostProcessSequence();
|
|
plan.ClearOwnedFinalOutputSequence();
|
|
plan.finalColorPolicy = {};
|
|
plan.postProcess = {};
|
|
plan.finalOutput = {};
|
|
plan.colorChain = {};
|
|
};
|
|
|
|
SceneRenderer renderer(std::make_shared<MockPipelineAsset>(assetState));
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
EXPECT_EQ(assetState->configureCameraFramePlanCalls, 1);
|
|
const CameraFramePlan& plan = plans[0];
|
|
EXPECT_FALSE(plan.finalColorPolicy.hasPipelineDefaults);
|
|
EXPECT_FALSE(plan.finalColorPolicy.hasCameraOverrides);
|
|
EXPECT_FALSE(plan.postProcess.IsRequested());
|
|
EXPECT_FALSE(plan.finalOutput.IsRequested());
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, UsesPipelineAssetCameraRenderRequestConfigurationHook) {
|
|
Scene scene("SceneRendererRequestConfigurationHookScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
GameObject* shadowLightObject = scene.CreateGameObject("ShadowLight");
|
|
auto* shadowLight = shadowLightObject->AddComponent<LightComponent>();
|
|
shadowLight->SetLightType(LightType::Directional);
|
|
shadowLight->SetCastsShadows(true);
|
|
|
|
auto assetState = std::make_shared<MockPipelineAssetState>();
|
|
assetState->configureCameraRenderRequest =
|
|
[](
|
|
CameraRenderRequest& request,
|
|
size_t renderedBaseCameraCount,
|
|
size_t renderedRequestCount,
|
|
const DirectionalShadowPlanningSettings& directionalShadowSettings) {
|
|
ApplyDefaultRenderPipelineAssetCameraRenderRequestPolicy(
|
|
request,
|
|
renderedBaseCameraCount,
|
|
renderedRequestCount,
|
|
directionalShadowSettings);
|
|
request.directionalShadow = {};
|
|
};
|
|
|
|
SceneRenderer renderer(std::make_shared<MockPipelineAsset>(assetState));
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
EXPECT_EQ(assetState->configureCameraRenderRequestCalls, 1);
|
|
EXPECT_FALSE(plans[0].directionalShadow.IsValid());
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, BuildsFinalOutputRequestFromResolvedFinalColorPolicy) {
|
|
Scene scene("SceneRendererFinalOutputScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
FinalColorOverrideSettings cameraOverrides = {};
|
|
cameraOverrides.overrideExposureMode = true;
|
|
cameraOverrides.exposureMode = FinalColorExposureMode::Fixed;
|
|
cameraOverrides.overrideExposureValue = true;
|
|
cameraOverrides.exposureValue = 1.6f;
|
|
cameraOverrides.overrideFinalColorScale = true;
|
|
cameraOverrides.finalColorScale = XCEngine::Math::Vector4(0.95f, 0.9f, 0.85f, 1.0f);
|
|
camera->SetFinalColorOverrides(cameraOverrides);
|
|
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
auto* backBufferColorView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* depthView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::DepthStencil,
|
|
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
auto assetState = std::make_shared<MockPipelineAssetState>();
|
|
assetState->defaultFinalColorSettings.outputTransferMode =
|
|
FinalColorOutputTransferMode::LinearToSRGB;
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
RenderSurface surface(800, 600);
|
|
surface.SetColorAttachment(backBufferColorView);
|
|
surface.SetDepthAttachment(depthView);
|
|
surface.SetDepthStateBefore(XCEngine::RHI::ResourceStates::Common);
|
|
surface.SetDepthStateAfter(XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
|
|
SceneRenderer renderer(std::make_shared<MockPipelineAsset>(assetState));
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, context, surface);
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
const CameraFramePlan& plan = plans[0];
|
|
EXPECT_FALSE(plan.postProcess.IsRequested());
|
|
EXPECT_TRUE(plan.finalOutput.IsRequested());
|
|
EXPECT_TRUE(plan.IsFinalOutputStageValid());
|
|
ASSERT_NE(plan.finalOutput.passes, nullptr);
|
|
EXPECT_EQ(plan.finalOutput.passes->GetPassCount(), 1u);
|
|
EXPECT_TRUE(plan.UsesGraphManagedSceneColor());
|
|
EXPECT_EQ(
|
|
plan.ResolveStageColorSource(CameraFrameStage::FinalOutput),
|
|
CameraFrameColorSource::MainSceneColor);
|
|
EXPECT_EQ(plan.finalOutput.destinationSurface.GetColorAttachments()[0], backBufferColorView);
|
|
EXPECT_EQ(plan.finalOutput.destinationSurface.GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
plan.finalOutput.destinationSurface.GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
plan.finalOutput.destinationSurface.GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(plan.GetMainSceneSurface().GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
plan.GetMainSceneSurface().GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
plan.GetMainSceneSurface().GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(plan.GetMainSceneSurface().GetWidth(), 800u);
|
|
EXPECT_EQ(plan.GetMainSceneSurface().GetHeight(), 600u);
|
|
const CameraFrameStageSourceBinding finalOutputSource =
|
|
ResolveStageSourceBinding(CameraFrameStage::FinalOutput, plan);
|
|
EXPECT_EQ(finalOutputSource.sourceSurface, nullptr);
|
|
EXPECT_EQ(finalOutputSource.sourceColorView, nullptr);
|
|
EXPECT_EQ(
|
|
finalOutputSource.sourceColorState,
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(allocationState->createTextureCalls, 0);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 0);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 0);
|
|
|
|
delete depthView;
|
|
delete backBufferColorView;
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, RoutesPostProcessIntoIntermediateSurfaceBeforeFinalOutput) {
|
|
Scene scene("SceneRendererPostProcessFinalOutputScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetViewportRect(XCEngine::Math::Rect(0.25f, 0.125f, 0.5f, 0.625f));
|
|
camera->SetPostProcessPasses({
|
|
XCEngine::Rendering::CameraPostProcessPassDesc::MakeColorScale(
|
|
XCEngine::Math::Vector4(1.0f, 0.75f, 0.75f, 1.0f))
|
|
});
|
|
|
|
FinalColorOverrideSettings cameraOverrides = {};
|
|
cameraOverrides.overrideOutputTransferMode = true;
|
|
cameraOverrides.outputTransferMode = FinalColorOutputTransferMode::LinearToSRGB;
|
|
cameraOverrides.overrideFinalColorScale = true;
|
|
cameraOverrides.finalColorScale = XCEngine::Math::Vector4(0.95f, 0.9f, 0.85f, 1.0f);
|
|
camera->SetFinalColorOverrides(cameraOverrides);
|
|
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
auto* backBufferColorView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* depthView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::DepthStencil,
|
|
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
RenderSurface surface(800, 600);
|
|
surface.SetColorAttachment(backBufferColorView);
|
|
surface.SetDepthAttachment(depthView);
|
|
surface.SetDepthStateBefore(XCEngine::RHI::ResourceStates::Common);
|
|
surface.SetDepthStateAfter(XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, context, surface);
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
const CameraFramePlan& plan = plans[0];
|
|
EXPECT_TRUE(plan.postProcess.IsRequested());
|
|
EXPECT_TRUE(plan.finalOutput.IsRequested());
|
|
EXPECT_TRUE(plan.IsPostProcessStageValid());
|
|
EXPECT_TRUE(plan.IsFinalOutputStageValid());
|
|
ASSERT_NE(plan.postProcess.passes, nullptr);
|
|
ASSERT_NE(plan.finalOutput.passes, nullptr);
|
|
EXPECT_EQ(plan.postProcess.passes->GetPassCount(), 1u);
|
|
EXPECT_EQ(plan.finalOutput.passes->GetPassCount(), 1u);
|
|
EXPECT_TRUE(plan.UsesGraphManagedSceneColor());
|
|
EXPECT_TRUE(plan.UsesGraphManagedOutputColor(CameraFrameStage::PostProcess));
|
|
EXPECT_EQ(
|
|
plan.ResolveStageColorSource(CameraFrameStage::PostProcess),
|
|
CameraFrameColorSource::MainSceneColor);
|
|
EXPECT_EQ(
|
|
plan.ResolveStageColorSource(CameraFrameStage::FinalOutput),
|
|
CameraFrameColorSource::PostProcessColor);
|
|
|
|
const CameraFrameStageSourceBinding postProcessSource =
|
|
ResolveStageSourceBinding(CameraFrameStage::PostProcess, plan);
|
|
const RenderSurface* const postProcessOutput =
|
|
ResolveStageOutputSurface(CameraFrameStage::PostProcess, plan);
|
|
const CameraFrameStageSourceBinding finalOutputSource =
|
|
ResolveStageSourceBinding(CameraFrameStage::FinalOutput, plan);
|
|
EXPECT_EQ(postProcessSource.sourceSurface, nullptr);
|
|
EXPECT_EQ(postProcessOutput, nullptr);
|
|
EXPECT_EQ(finalOutputSource.sourceSurface, nullptr);
|
|
EXPECT_EQ(plan.finalOutput.destinationSurface.GetColorAttachments()[0], backBufferColorView);
|
|
EXPECT_EQ(plan.finalOutput.destinationSurface.GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
plan.finalOutput.destinationSurface.GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
plan.finalOutput.destinationSurface.GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
const XCEngine::Math::RectInt postProcessSourceArea = plan.GetMainSceneSurface().GetRenderArea();
|
|
EXPECT_EQ(postProcessSourceArea.x, 200);
|
|
EXPECT_EQ(postProcessSourceArea.y, 75);
|
|
EXPECT_EQ(postProcessSourceArea.width, 400);
|
|
EXPECT_EQ(postProcessSourceArea.height, 375);
|
|
const XCEngine::Math::RectInt finalOutputSourceArea = plan.GetMainSceneSurface().GetRenderArea();
|
|
EXPECT_EQ(finalOutputSourceArea.x, 200);
|
|
EXPECT_EQ(finalOutputSourceArea.y, 75);
|
|
EXPECT_EQ(finalOutputSourceArea.width, 400);
|
|
EXPECT_EQ(finalOutputSourceArea.height, 375);
|
|
EXPECT_EQ(postProcessSource.sourceColorView, nullptr);
|
|
EXPECT_EQ(finalOutputSource.sourceColorView, nullptr);
|
|
EXPECT_EQ(
|
|
postProcessSource.sourceColorState,
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
finalOutputSource.sourceColorState,
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(allocationState->createTextureCalls, 0);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 0);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 0);
|
|
|
|
delete depthView;
|
|
delete backBufferColorView;
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, ReturnedPlansOwnGeneratedFullscreenSequences) {
|
|
Scene scene("SceneRendererOwnsGeneratedFullscreenSequencesScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetPostProcessPasses({
|
|
XCEngine::Rendering::CameraPostProcessPassDesc::MakeColorScale(
|
|
XCEngine::Math::Vector4(1.0f, 0.75f, 0.75f, 1.0f))
|
|
});
|
|
|
|
FinalColorOverrideSettings cameraOverrides = {};
|
|
cameraOverrides.overrideOutputTransferMode = true;
|
|
cameraOverrides.outputTransferMode = FinalColorOutputTransferMode::LinearToSRGB;
|
|
camera->SetFinalColorOverrides(cameraOverrides);
|
|
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
auto* backBufferColorView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* depthView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::DepthStencil,
|
|
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
RenderSurface surface(800, 600);
|
|
surface.SetColorAttachment(backBufferColorView);
|
|
surface.SetDepthAttachment(depthView);
|
|
|
|
CameraFramePlan preservedPlan = {};
|
|
{
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, context, surface);
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
ASSERT_TRUE(static_cast<bool>(plans[0].GetOwnedPostProcessSequence()));
|
|
ASSERT_TRUE(static_cast<bool>(plans[0].GetOwnedFinalOutputSequence()));
|
|
preservedPlan = plans[0];
|
|
}
|
|
|
|
ASSERT_TRUE(static_cast<bool>(preservedPlan.GetOwnedPostProcessSequence()));
|
|
ASSERT_TRUE(static_cast<bool>(preservedPlan.GetOwnedFinalOutputSequence()));
|
|
ASSERT_NE(preservedPlan.postProcess.passes, nullptr);
|
|
ASSERT_NE(preservedPlan.finalOutput.passes, nullptr);
|
|
EXPECT_EQ(
|
|
preservedPlan.postProcess.passes,
|
|
preservedPlan.GetOwnedPostProcessSequence().get());
|
|
EXPECT_EQ(
|
|
preservedPlan.finalOutput.passes,
|
|
preservedPlan.GetOwnedFinalOutputSequence().get());
|
|
EXPECT_EQ(preservedPlan.postProcess.passes->GetPassCount(), 1u);
|
|
EXPECT_EQ(preservedPlan.finalOutput.passes->GetPassCount(), 1u);
|
|
|
|
delete depthView;
|
|
delete backBufferColorView;
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, DoesNotBuildFullscreenStagesForMultisampledMainSceneSurface) {
|
|
Scene scene("SceneRendererMultisampledFullscreenStageScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetPostProcessPasses({
|
|
XCEngine::Rendering::CameraPostProcessPassDesc::MakeColorScale(
|
|
XCEngine::Math::Vector4(1.0f, 0.75f, 0.75f, 1.0f))
|
|
});
|
|
|
|
FinalColorOverrideSettings cameraOverrides = {};
|
|
cameraOverrides.overrideOutputTransferMode = true;
|
|
cameraOverrides.outputTransferMode = FinalColorOutputTransferMode::LinearToSRGB;
|
|
camera->SetFinalColorOverrides(cameraOverrides);
|
|
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
auto* backBufferColorView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* depthView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::DepthStencil,
|
|
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
RenderSurface surface(800, 600);
|
|
surface.SetColorAttachment(backBufferColorView);
|
|
surface.SetDepthAttachment(depthView);
|
|
surface.SetSampleDesc(4u, 0u);
|
|
|
|
SceneRenderer renderer;
|
|
const std::vector<CameraFramePlan> plans =
|
|
renderer.BuildFramePlans(scene, nullptr, context, surface);
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
const CameraFramePlan& plan = plans[0];
|
|
EXPECT_EQ(plan.request.surface.GetSampleCount(), 4u);
|
|
EXPECT_FALSE(plan.postProcess.IsRequested());
|
|
EXPECT_FALSE(plan.finalOutput.IsRequested());
|
|
EXPECT_EQ(allocationState->createTextureCalls, 0);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 0);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 0);
|
|
|
|
delete depthView;
|
|
delete backBufferColorView;
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, KeepsMainSceneGraphManagedWhenPostProcessIsEnabledAcrossFrames) {
|
|
Scene scene("SceneRendererTrackedSceneColorStateScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetPostProcessPasses({
|
|
XCEngine::Rendering::CameraPostProcessPassDesc::MakeColorScale(
|
|
XCEngine::Math::Vector4(1.0f, 0.9f, 0.8f, 1.0f))
|
|
});
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
pipelineState->supportsMainSceneRenderGraph = true;
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
auto* backBufferColorView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* depthView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::DepthStencil,
|
|
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
RenderSurface surface(800, 600);
|
|
surface.SetColorAttachment(backBufferColorView);
|
|
surface.SetDepthAttachment(depthView);
|
|
|
|
SceneRenderer renderer(std::make_unique<MockPipeline>(pipelineState));
|
|
|
|
std::vector<CameraFramePlan> firstFramePlans =
|
|
renderer.BuildFramePlans(scene, nullptr, context, surface);
|
|
ASSERT_EQ(firstFramePlans.size(), 1u);
|
|
CameraFramePlan firstFramePlan = firstFramePlans[0];
|
|
EXPECT_TRUE(firstFramePlan.UsesGraphManagedSceneColor());
|
|
EXPECT_EQ(
|
|
firstFramePlan.ResolveStageColorSource(CameraFrameStage::PostProcess),
|
|
CameraFrameColorSource::MainSceneColor);
|
|
EXPECT_EQ(
|
|
firstFramePlan.GetMainSceneSurface().GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::make_unique<MockScenePass>(pipelineState, "postProcess"));
|
|
firstFramePlans[0].postProcess.passes = &postProcessPasses;
|
|
|
|
ASSERT_TRUE(renderer.Render(firstFramePlans));
|
|
const int createTextureCallsAfterFirstRender = allocationState->createTextureCalls;
|
|
const int createRenderTargetViewCallsAfterFirstRender =
|
|
allocationState->createRenderTargetViewCalls;
|
|
const int createShaderViewCallsAfterFirstRender = allocationState->createShaderViewCalls;
|
|
|
|
const std::vector<CameraFramePlan> secondFramePlans =
|
|
renderer.BuildFramePlans(scene, nullptr, context, surface);
|
|
ASSERT_EQ(secondFramePlans.size(), 1u);
|
|
const CameraFramePlan secondFramePlan = secondFramePlans[0];
|
|
EXPECT_TRUE(secondFramePlan.UsesGraphManagedSceneColor());
|
|
EXPECT_EQ(
|
|
secondFramePlan.ResolveStageColorSource(CameraFrameStage::PostProcess),
|
|
CameraFrameColorSource::MainSceneColor);
|
|
EXPECT_EQ(
|
|
secondFramePlan.GetMainSceneSurface().GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(allocationState->createTextureCalls, createTextureCallsAfterFirstRender);
|
|
EXPECT_EQ(
|
|
allocationState->createRenderTargetViewCalls,
|
|
createRenderTargetViewCallsAfterFirstRender);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, createShaderViewCallsAfterFirstRender);
|
|
|
|
delete depthView;
|
|
delete backBufferColorView;
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, KeepsPostProcessOutputGraphManagedWhenFinalOutputIsEnabledAcrossFrames) {
|
|
Scene scene("SceneRendererTrackedPostProcessOutputStateScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
camera->SetPostProcessPasses({
|
|
XCEngine::Rendering::CameraPostProcessPassDesc::MakeColorScale(
|
|
XCEngine::Math::Vector4(1.0f, 0.9f, 0.8f, 1.0f))
|
|
});
|
|
|
|
FinalColorOverrideSettings finalColorOverrides = {};
|
|
finalColorOverrides.overrideOutputTransferMode = true;
|
|
finalColorOverrides.outputTransferMode = FinalColorOutputTransferMode::LinearToSRGB;
|
|
camera->SetFinalColorOverrides(finalColorOverrides);
|
|
|
|
auto pipelineState = std::make_shared<MockPipelineState>();
|
|
pipelineState->supportsMainSceneRenderGraph = true;
|
|
auto allocationState = std::make_shared<MockShadowAllocationState>();
|
|
MockShadowDevice device(allocationState);
|
|
|
|
RenderContext context = CreateValidContext();
|
|
context.device = &device;
|
|
|
|
auto* backBufferColorView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::RenderTarget,
|
|
XCEngine::RHI::Format::R8G8B8A8_UNorm,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
auto* depthView = new MockShadowView(
|
|
allocationState,
|
|
XCEngine::RHI::ResourceViewType::DepthStencil,
|
|
XCEngine::RHI::Format::D24_UNorm_S8_UInt,
|
|
XCEngine::RHI::ResourceViewDimension::Texture2D);
|
|
|
|
RenderSurface surface(800, 600);
|
|
surface.SetColorAttachment(backBufferColorView);
|
|
surface.SetDepthAttachment(depthView);
|
|
|
|
SceneRenderer renderer(std::make_unique<MockPipeline>(pipelineState));
|
|
|
|
std::vector<CameraFramePlan> firstFramePlans =
|
|
renderer.BuildFramePlans(scene, nullptr, context, surface);
|
|
ASSERT_EQ(firstFramePlans.size(), 1u);
|
|
CameraFramePlan firstFramePlan = firstFramePlans[0];
|
|
EXPECT_TRUE(firstFramePlans[0].postProcess.IsRequested());
|
|
EXPECT_TRUE(firstFramePlans[0].finalOutput.IsRequested());
|
|
EXPECT_TRUE(firstFramePlan.UsesGraphManagedSceneColor());
|
|
EXPECT_TRUE(firstFramePlan.UsesGraphManagedOutputColor(CameraFrameStage::PostProcess));
|
|
EXPECT_EQ(
|
|
firstFramePlan.ResolveStageColorSource(CameraFrameStage::PostProcess),
|
|
CameraFrameColorSource::MainSceneColor);
|
|
EXPECT_EQ(
|
|
firstFramePlan.ResolveStageColorSource(CameraFrameStage::FinalOutput),
|
|
CameraFrameColorSource::PostProcessColor);
|
|
EXPECT_EQ(
|
|
firstFramePlan.GetMainSceneSurface().GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
ResolveStageOutputSurface(CameraFrameStage::PostProcess, firstFramePlan),
|
|
nullptr);
|
|
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::make_unique<MockScenePass>(pipelineState, "postProcess"));
|
|
RenderPassSequence finalOutputPasses;
|
|
finalOutputPasses.AddPass(std::make_unique<MockScenePass>(pipelineState, "finalOutput"));
|
|
firstFramePlans[0].postProcess.passes = &postProcessPasses;
|
|
firstFramePlans[0].finalOutput.passes = &finalOutputPasses;
|
|
|
|
ASSERT_TRUE(renderer.Render(firstFramePlans));
|
|
const int createTextureCallsAfterFirstRender = allocationState->createTextureCalls;
|
|
const int createRenderTargetViewCallsAfterFirstRender =
|
|
allocationState->createRenderTargetViewCalls;
|
|
const int createShaderViewCallsAfterFirstRender = allocationState->createShaderViewCalls;
|
|
|
|
const std::vector<CameraFramePlan> secondFramePlans =
|
|
renderer.BuildFramePlans(scene, nullptr, context, surface);
|
|
ASSERT_EQ(secondFramePlans.size(), 1u);
|
|
const CameraFramePlan secondFramePlan = secondFramePlans[0];
|
|
EXPECT_TRUE(secondFramePlan.UsesGraphManagedSceneColor());
|
|
EXPECT_TRUE(secondFramePlan.UsesGraphManagedOutputColor(CameraFrameStage::PostProcess));
|
|
EXPECT_EQ(
|
|
secondFramePlan.ResolveStageColorSource(CameraFrameStage::PostProcess),
|
|
CameraFrameColorSource::MainSceneColor);
|
|
EXPECT_EQ(
|
|
secondFramePlan.ResolveStageColorSource(CameraFrameStage::FinalOutput),
|
|
CameraFrameColorSource::PostProcessColor);
|
|
EXPECT_EQ(
|
|
secondFramePlan.GetMainSceneSurface().GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
ResolveStageOutputSurface(CameraFrameStage::PostProcess, secondFramePlan),
|
|
nullptr);
|
|
EXPECT_EQ(
|
|
ResolveStageSourceBinding(CameraFrameStage::FinalOutput, secondFramePlan).sourceSurface,
|
|
nullptr);
|
|
EXPECT_EQ(allocationState->createTextureCalls, createTextureCallsAfterFirstRender);
|
|
EXPECT_EQ(
|
|
allocationState->createRenderTargetViewCalls,
|
|
createRenderTargetViewCallsAfterFirstRender);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, createShaderViewCallsAfterFirstRender);
|
|
|
|
delete depthView;
|
|
delete backBufferColorView;
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, UsesResolvedRenderAreaForCameraViewportDimensions) {
|
|
Scene scene("CameraRendererViewportRectScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetViewportRect(XCEngine::Math::Rect(0.125f, 0.25f, 0.5f, 0.5f));
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(640, 480);
|
|
request.surface.SetRenderArea(XCEngine::Math::RectInt(80, 120, 320, 240));
|
|
|
|
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
|
|
EXPECT_EQ(state->lastRenderAreaX, 80);
|
|
EXPECT_EQ(state->lastRenderAreaY, 120);
|
|
EXPECT_EQ(state->lastRenderAreaWidth, 320);
|
|
EXPECT_EQ(state->lastRenderAreaHeight, 240);
|
|
EXPECT_EQ(state->lastCameraViewportWidth, 320u);
|
|
EXPECT_EQ(state->lastCameraViewportHeight, 240u);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRenderer) {
|
|
Scene scene("SceneRendererScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto initialState = std::make_shared<MockPipelineState>();
|
|
auto replacementState = std::make_shared<MockPipelineState>();
|
|
|
|
{
|
|
auto initialPipeline = std::make_unique<MockPipeline>(initialState);
|
|
MockPipeline* initialPipelineRaw = initialPipeline.get();
|
|
SceneRenderer renderer(std::move(initialPipeline));
|
|
EXPECT_EQ(renderer.GetPipeline(), initialPipelineRaw);
|
|
|
|
auto replacementPipeline = std::make_unique<MockPipeline>(replacementState);
|
|
MockPipeline* replacementPipelineRaw = replacementPipeline.get();
|
|
renderer.SetPipeline(std::move(replacementPipeline));
|
|
|
|
EXPECT_EQ(initialState->shutdownCalls, 1);
|
|
EXPECT_EQ(renderer.GetPipeline(), replacementPipelineRaw);
|
|
|
|
const RenderSurface surface(800, 600);
|
|
ASSERT_TRUE(renderer.Render(scene, nullptr, CreateValidContext(), surface));
|
|
EXPECT_EQ(replacementState->renderCalls, 1);
|
|
EXPECT_EQ(replacementState->lastSurfaceWidth, 800u);
|
|
EXPECT_EQ(replacementState->lastSurfaceHeight, 600u);
|
|
EXPECT_EQ(replacementState->lastCamera, camera);
|
|
}
|
|
|
|
EXPECT_EQ(initialState->shutdownCalls, 1);
|
|
EXPECT_EQ(replacementState->shutdownCalls, 1);
|
|
}
|
|
|
|
TEST(RenderPipelineHost_Test, BuildsFramePlansFromRequestsUsingPipelineAssetDefaults) {
|
|
Scene scene("RenderPipelineHostBuildPlans");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
FinalColorOverrideSettings cameraOverrides = {};
|
|
cameraOverrides.overrideExposureValue = true;
|
|
cameraOverrides.exposureValue = 1.8f;
|
|
camera->SetFinalColorOverrides(cameraOverrides);
|
|
|
|
auto assetState = std::make_shared<MockPipelineAssetState>();
|
|
assetState->defaultFinalColorSettings.outputTransferMode =
|
|
FinalColorOutputTransferMode::LinearToSRGB;
|
|
assetState->defaultFinalColorSettings.exposureMode =
|
|
FinalColorExposureMode::Fixed;
|
|
assetState->defaultFinalColorSettings.exposureValue = 1.25f;
|
|
|
|
CameraRenderRequest request = {};
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(640, 360);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.clearFlags = RenderClearFlags::All;
|
|
|
|
RenderPipelineHost host(std::make_shared<MockPipelineAsset>(assetState));
|
|
const std::vector<CameraFramePlan> plans =
|
|
host.BuildFramePlans({ request });
|
|
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
const CameraFramePlan& plan = plans[0];
|
|
EXPECT_TRUE(plan.finalColorPolicy.hasPipelineDefaults);
|
|
EXPECT_TRUE(plan.finalColorPolicy.hasCameraOverrides);
|
|
EXPECT_EQ(
|
|
plan.finalColorPolicy.outputTransferMode,
|
|
FinalColorOutputTransferMode::LinearToSRGB);
|
|
EXPECT_EQ(
|
|
plan.finalColorPolicy.exposureMode,
|
|
FinalColorExposureMode::Fixed);
|
|
EXPECT_FLOAT_EQ(plan.finalColorPolicy.exposureValue, 1.8f);
|
|
}
|
|
|
|
TEST(RenderPipelineHost_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRenderer) {
|
|
Scene scene("RenderPipelineHostScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto initialState = std::make_shared<MockPipelineState>();
|
|
auto replacementState = std::make_shared<MockPipelineState>();
|
|
|
|
{
|
|
auto initialPipeline = std::make_unique<MockPipeline>(initialState);
|
|
MockPipeline* initialPipelineRaw = initialPipeline.get();
|
|
RenderPipelineHost host(std::move(initialPipeline));
|
|
EXPECT_EQ(host.GetPipeline(), initialPipelineRaw);
|
|
|
|
auto replacementPipeline = std::make_unique<MockPipeline>(replacementState);
|
|
MockPipeline* replacementPipelineRaw = replacementPipeline.get();
|
|
host.SetPipeline(std::move(replacementPipeline));
|
|
|
|
EXPECT_EQ(initialState->shutdownCalls, 1);
|
|
EXPECT_EQ(host.GetPipeline(), replacementPipelineRaw);
|
|
|
|
CameraRenderRequest request = {};
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(800, 600);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.clearFlags = RenderClearFlags::All;
|
|
|
|
const std::vector<CameraFramePlan> plans =
|
|
host.BuildFramePlans({ request });
|
|
ASSERT_EQ(plans.size(), 1u);
|
|
ASSERT_TRUE(host.Render(plans));
|
|
EXPECT_EQ(replacementState->renderCalls, 1);
|
|
EXPECT_EQ(replacementState->lastSurfaceWidth, 800u);
|
|
EXPECT_EQ(replacementState->lastSurfaceHeight, 600u);
|
|
EXPECT_EQ(replacementState->lastCamera, camera);
|
|
}
|
|
|
|
EXPECT_EQ(initialState->shutdownCalls, 1);
|
|
EXPECT_EQ(replacementState->shutdownCalls, 1);
|
|
}
|
|
|
|
TEST(RenderPipelineHost_Test, SortsManualFramePlansByDepthBeforeRendering) {
|
|
Scene scene("RenderPipelineHostManualRequests");
|
|
|
|
GameObject* farCameraObject = scene.CreateGameObject("FarCamera");
|
|
auto* farCamera = farCameraObject->AddComponent<CameraComponent>();
|
|
farCamera->SetPrimary(true);
|
|
farCamera->SetDepth(10.0f);
|
|
|
|
GameObject* nearCameraObject = scene.CreateGameObject("NearCamera");
|
|
auto* nearCamera = nearCameraObject->AddComponent<CameraComponent>();
|
|
nearCamera->SetPrimary(false);
|
|
nearCamera->SetDepth(1.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
RenderPipelineHost host(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest farRequest;
|
|
farRequest.scene = &scene;
|
|
farRequest.camera = farCamera;
|
|
farRequest.context = CreateValidContext();
|
|
farRequest.surface = RenderSurface(800, 600);
|
|
farRequest.cameraDepth = farCamera->GetDepth();
|
|
farRequest.cameraStackOrder = 1;
|
|
farRequest.clearFlags = RenderClearFlags::None;
|
|
|
|
CameraRenderRequest nearRequest = farRequest;
|
|
nearRequest.camera = nearCamera;
|
|
nearRequest.cameraDepth = nearCamera->GetDepth();
|
|
nearRequest.cameraStackOrder = 0;
|
|
nearRequest.clearFlags = RenderClearFlags::Depth;
|
|
|
|
const std::vector<CameraFramePlan> plans =
|
|
host.BuildFramePlans({ farRequest, nearRequest });
|
|
ASSERT_TRUE(host.Render(plans));
|
|
ASSERT_EQ(state->renderedCameras.size(), 2u);
|
|
ASSERT_EQ(state->renderedClearFlags.size(), 2u);
|
|
EXPECT_EQ(state->renderedCameras[0], nearCamera);
|
|
EXPECT_EQ(state->renderedClearFlags[0], RenderClearFlags::Depth);
|
|
EXPECT_EQ(state->renderedCameras[1], farCamera);
|
|
EXPECT_EQ(state->renderedClearFlags[1], RenderClearFlags::None);
|
|
}
|
|
|
|
TEST(ScriptableRenderPipelineHost_Test, ForwardsRendererLifetimeAndFrameRendering) {
|
|
Scene scene("ScriptableRenderPipelineHostScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto initialState = std::make_shared<MockPipelineState>();
|
|
auto replacementState = std::make_shared<MockPipelineState>();
|
|
|
|
{
|
|
auto initialRenderer = std::make_unique<MockPipeline>(initialState);
|
|
MockPipeline* initialRendererRaw = initialRenderer.get();
|
|
Pipelines::ScriptableRenderPipelineHost host(std::move(initialRenderer));
|
|
EXPECT_EQ(host.GetPipelineRenderer(), initialRendererRaw);
|
|
|
|
auto replacementRenderer = std::make_unique<MockPipeline>(replacementState);
|
|
MockPipeline* replacementRendererRaw = replacementRenderer.get();
|
|
host.SetPipelineRenderer(std::move(replacementRenderer));
|
|
|
|
EXPECT_EQ(initialState->shutdownCalls, 1);
|
|
EXPECT_EQ(host.GetPipelineRenderer(), replacementRendererRaw);
|
|
|
|
CameraRenderRequest request = {};
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = CreateValidContext();
|
|
request.surface = RenderSurface(800, 600);
|
|
request.cameraDepth = camera->GetDepth();
|
|
request.clearFlags = RenderClearFlags::All;
|
|
|
|
RenderSceneData sceneData =
|
|
CreateSceneDataForCamera(scene, *camera, request.surface);
|
|
ASSERT_TRUE(host.Render(
|
|
request.context,
|
|
request.surface,
|
|
sceneData));
|
|
EXPECT_EQ(replacementState->initializeCalls, 1);
|
|
EXPECT_EQ(replacementState->renderCalls, 1);
|
|
EXPECT_EQ(replacementState->lastSurfaceWidth, 800u);
|
|
EXPECT_EQ(replacementState->lastSurfaceHeight, 600u);
|
|
EXPECT_EQ(replacementState->lastCamera, camera);
|
|
|
|
ASSERT_TRUE(host.Render(FrameExecutionContext(
|
|
request.context,
|
|
request.surface,
|
|
sceneData)));
|
|
EXPECT_EQ(replacementState->initializeCalls, 1);
|
|
EXPECT_EQ(replacementState->renderCalls, 2);
|
|
|
|
replacementState->supportsMainSceneRenderGraph = true;
|
|
RenderGraph graph = {};
|
|
RenderGraphBuilder graphBuilder(graph);
|
|
RenderGraphBlackboard blackboard = {};
|
|
bool executionSucceeded = true;
|
|
RenderSceneData graphSceneData =
|
|
CreateSceneDataForCamera(scene, *camera, request.surface);
|
|
const RenderPipelineStageRenderGraphContext graphContext = {
|
|
graphBuilder,
|
|
"MainScene",
|
|
CameraFrameStage::MainScene,
|
|
request.context,
|
|
graphSceneData,
|
|
request.surface,
|
|
nullptr,
|
|
nullptr,
|
|
XCEngine::RHI::ResourceStates::Common,
|
|
{},
|
|
{},
|
|
{},
|
|
&executionSucceeded,
|
|
&blackboard
|
|
};
|
|
EXPECT_TRUE(host.SupportsStageRenderGraph(CameraFrameStage::MainScene));
|
|
EXPECT_TRUE(host.RecordStageRenderGraph(graphContext));
|
|
EXPECT_EQ(replacementState->initializeCalls, 1);
|
|
EXPECT_EQ(replacementState->recordMainSceneCalls, 1);
|
|
}
|
|
|
|
EXPECT_EQ(initialState->shutdownCalls, 1);
|
|
EXPECT_EQ(replacementState->shutdownCalls, 1);
|
|
}
|
|
|
|
TEST(ScriptableRenderPipelineHost_Test, PrefersStageRecorderBeforeFallbackRenderer) {
|
|
auto rendererState = std::make_shared<MockPipelineState>();
|
|
auto initialRecorderState = std::make_shared<MockStageRecorderState>();
|
|
auto replacementRecorderState = std::make_shared<MockStageRecorderState>();
|
|
|
|
rendererState->supportsMainSceneRenderGraph = true;
|
|
initialRecorderState->supportsMainSceneRenderGraph = true;
|
|
replacementRecorderState->supportsMainSceneRenderGraph = true;
|
|
|
|
{
|
|
Pipelines::ScriptableRenderPipelineHost host(
|
|
std::make_unique<MockPipeline>(rendererState));
|
|
|
|
auto initialRecorder =
|
|
std::make_unique<MockStageRecorder>(initialRecorderState);
|
|
MockStageRecorder* initialRecorderRaw = initialRecorder.get();
|
|
host.SetStageRecorder(std::move(initialRecorder));
|
|
EXPECT_EQ(host.GetStageRecorder(), initialRecorderRaw);
|
|
|
|
auto replacementRecorder =
|
|
std::make_unique<MockStageRecorder>(replacementRecorderState);
|
|
MockStageRecorder* replacementRecorderRaw = replacementRecorder.get();
|
|
host.SetStageRecorder(std::move(replacementRecorder));
|
|
|
|
EXPECT_EQ(initialRecorderState->shutdownCalls, 1);
|
|
EXPECT_EQ(host.GetStageRecorder(), replacementRecorderRaw);
|
|
|
|
Scene scene("ScriptableRenderPipelineRecorderScene");
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(1.0f);
|
|
|
|
const RenderContext context = CreateValidContext();
|
|
const RenderSurface surface(800, 600);
|
|
RenderSceneData sceneData =
|
|
CreateSceneDataForCamera(scene, *camera, surface);
|
|
RenderGraph graph = {};
|
|
RenderGraphBuilder graphBuilder(graph);
|
|
RenderGraphBlackboard blackboard = {};
|
|
bool executionSucceeded = true;
|
|
const RenderPipelineStageRenderGraphContext graphContext = {
|
|
graphBuilder,
|
|
"MainScene",
|
|
CameraFrameStage::MainScene,
|
|
context,
|
|
sceneData,
|
|
surface,
|
|
nullptr,
|
|
nullptr,
|
|
XCEngine::RHI::ResourceStates::Common,
|
|
{},
|
|
{},
|
|
{},
|
|
&executionSucceeded,
|
|
&blackboard
|
|
};
|
|
|
|
EXPECT_TRUE(host.SupportsStageRenderGraph(CameraFrameStage::MainScene));
|
|
EXPECT_TRUE(host.RecordStageRenderGraph(graphContext));
|
|
EXPECT_EQ(rendererState->initializeCalls, 1);
|
|
EXPECT_EQ(replacementRecorderState->initializeCalls, 1);
|
|
EXPECT_EQ(replacementRecorderState->recordMainSceneCalls, 1);
|
|
EXPECT_TRUE(host.RecordStageRenderGraph(graphContext));
|
|
EXPECT_EQ(rendererState->initializeCalls, 1);
|
|
EXPECT_EQ(replacementRecorderState->initializeCalls, 1);
|
|
EXPECT_EQ(replacementRecorderState->recordMainSceneCalls, 2);
|
|
EXPECT_TRUE(replacementRecorderState->lastReceivedRenderGraphBlackboard);
|
|
EXPECT_EQ(rendererState->recordMainSceneCalls, 0);
|
|
}
|
|
|
|
EXPECT_EQ(rendererState->shutdownCalls, 1);
|
|
EXPECT_EQ(replacementRecorderState->shutdownCalls, 1);
|
|
}
|
|
|
|
TEST(ScriptableRenderPipelineHost_Test, DirectHostDoesNotInstallBuiltinStandaloneStagePasses) {
|
|
auto rendererState = std::make_shared<MockPipelineState>();
|
|
Pipelines::ScriptableRenderPipelineHost host(
|
|
std::make_unique<MockPipeline>(rendererState));
|
|
|
|
EXPECT_EQ(host.GetCameraFrameStandalonePass(CameraFrameStage::ObjectId), nullptr);
|
|
EXPECT_EQ(host.GetCameraFrameStandalonePass(CameraFrameStage::DepthOnly), nullptr);
|
|
EXPECT_EQ(host.GetCameraFrameStandalonePass(CameraFrameStage::ShadowCaster), nullptr);
|
|
}
|
|
|
|
TEST(ScriptableRenderPipelineHostAsset_Test, CreatesHostFromRendererAssetAndForwardsDefaults) {
|
|
auto assetState = std::make_shared<MockPipelineAssetState>();
|
|
assetState->defaultFinalColorSettings.outputTransferMode =
|
|
FinalColorOutputTransferMode::LinearToSRGB;
|
|
assetState->defaultFinalColorSettings.exposureMode =
|
|
FinalColorExposureMode::Fixed;
|
|
assetState->defaultFinalColorSettings.exposureValue = 1.5f;
|
|
|
|
Pipelines::ScriptableRenderPipelineHostAsset asset(
|
|
std::make_shared<MockPipelineAsset>(assetState));
|
|
EXPECT_EQ(
|
|
asset.GetDefaultFinalColorSettings().outputTransferMode,
|
|
FinalColorOutputTransferMode::LinearToSRGB);
|
|
EXPECT_EQ(
|
|
asset.GetDefaultFinalColorSettings().exposureMode,
|
|
FinalColorExposureMode::Fixed);
|
|
EXPECT_FLOAT_EQ(
|
|
asset.GetDefaultFinalColorSettings().exposureValue,
|
|
1.5f);
|
|
|
|
std::unique_ptr<RenderPipeline> pipeline = asset.CreatePipeline();
|
|
ASSERT_NE(pipeline, nullptr);
|
|
auto* host =
|
|
dynamic_cast<Pipelines::ScriptableRenderPipelineHost*>(pipeline.get());
|
|
ASSERT_NE(host, nullptr);
|
|
EXPECT_NE(host->GetPipelineRenderer(), nullptr);
|
|
EXPECT_EQ(assetState->createCalls, 1);
|
|
EXPECT_NE(host->GetCameraFrameStandalonePass(CameraFrameStage::ObjectId), nullptr);
|
|
EXPECT_NE(host->GetCameraFrameStandalonePass(CameraFrameStage::DepthOnly), nullptr);
|
|
EXPECT_NE(host->GetCameraFrameStandalonePass(CameraFrameStage::ShadowCaster), nullptr);
|
|
}
|
|
|
|
TEST(ManagedScriptableRenderPipelineAsset_Test, CreatesHostWithStageRecorderFromManagedBridge) {
|
|
Pipelines::ClearManagedRenderPipelineBridge();
|
|
|
|
const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = {
|
|
"GameScripts",
|
|
"Gameplay",
|
|
"ManagedRenderPipelineProbeAsset"
|
|
};
|
|
auto bridgeState = std::make_shared<MockManagedRenderPipelineBridgeState>();
|
|
Pipelines::SetManagedRenderPipelineBridge(
|
|
std::make_shared<MockManagedRenderPipelineBridge>(bridgeState));
|
|
|
|
{
|
|
Pipelines::ManagedScriptableRenderPipelineAsset asset(descriptor);
|
|
std::unique_ptr<RenderPipeline> pipeline = asset.CreatePipeline();
|
|
ASSERT_NE(pipeline, nullptr);
|
|
|
|
auto* host =
|
|
dynamic_cast<Pipelines::ScriptableRenderPipelineHost*>(pipeline.get());
|
|
ASSERT_NE(host, nullptr);
|
|
EXPECT_NE(host->GetPipelineRenderer(), nullptr);
|
|
EXPECT_NE(host->GetStageRecorder(), nullptr);
|
|
EXPECT_EQ(bridgeState->createStageRecorderCalls, 1);
|
|
EXPECT_EQ(bridgeState->lastDescriptor.assemblyName, "GameScripts");
|
|
EXPECT_EQ(bridgeState->lastDescriptor.namespaceName, "Gameplay");
|
|
EXPECT_EQ(bridgeState->lastDescriptor.className, "ManagedRenderPipelineProbeAsset");
|
|
|
|
ASSERT_NE(bridgeState->lastCreatedStageRecorderState, nullptr);
|
|
bridgeState->lastCreatedStageRecorderState->supportsMainSceneRenderGraph = true;
|
|
EXPECT_TRUE(host->SupportsStageRenderGraph(CameraFrameStage::MainScene));
|
|
}
|
|
|
|
Pipelines::ClearManagedRenderPipelineBridge();
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, DefaultPipelineAssetUsesManagedSelectionWhenPresent) {
|
|
const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = {
|
|
"GameScripts",
|
|
"Gameplay",
|
|
"ManagedRenderPipelineProbeAsset"
|
|
};
|
|
Pipelines::SetManagedRenderPipelineAssetDescriptor(descriptor);
|
|
|
|
CameraRenderer renderer;
|
|
auto* asset =
|
|
dynamic_cast<const Pipelines::ManagedScriptableRenderPipelineAsset*>(
|
|
renderer.GetPipelineAsset());
|
|
ASSERT_NE(asset, nullptr);
|
|
EXPECT_EQ(asset->GetDescriptor().assemblyName, "GameScripts");
|
|
EXPECT_EQ(asset->GetDescriptor().namespaceName, "Gameplay");
|
|
EXPECT_EQ(asset->GetDescriptor().className, "ManagedRenderPipelineProbeAsset");
|
|
EXPECT_NE(
|
|
dynamic_cast<Pipelines::ScriptableRenderPipelineHost*>(renderer.GetPipeline()),
|
|
nullptr);
|
|
|
|
Pipelines::ClearManagedRenderPipelineAssetDescriptor();
|
|
}
|
|
|
|
TEST(CameraRenderer_Test, RebindsToResolvedDefaultAssetWhenPreferredAssetCannotCreatePipeline) {
|
|
const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = {
|
|
"GameScripts",
|
|
"Gameplay",
|
|
"ManagedRenderPipelineProbeAsset"
|
|
};
|
|
Pipelines::SetManagedRenderPipelineAssetDescriptor(descriptor);
|
|
|
|
auto failingAssetState = std::make_shared<MockPipelineAssetState>();
|
|
failingAssetState->createNullPipeline = true;
|
|
|
|
CameraRenderer renderer(std::make_shared<MockPipelineAsset>(failingAssetState));
|
|
EXPECT_EQ(failingAssetState->createCalls, 1);
|
|
EXPECT_NE(renderer.GetPipeline(), nullptr);
|
|
EXPECT_NE(
|
|
dynamic_cast<Pipelines::ScriptableRenderPipelineHost*>(renderer.GetPipeline()),
|
|
nullptr);
|
|
|
|
auto* asset =
|
|
dynamic_cast<const Pipelines::ManagedScriptableRenderPipelineAsset*>(
|
|
renderer.GetPipelineAsset());
|
|
ASSERT_NE(asset, nullptr);
|
|
EXPECT_EQ(asset->GetDescriptor().assemblyName, "GameScripts");
|
|
EXPECT_EQ(asset->GetDescriptor().namespaceName, "Gameplay");
|
|
EXPECT_EQ(asset->GetDescriptor().className, "ManagedRenderPipelineProbeAsset");
|
|
|
|
Pipelines::ClearManagedRenderPipelineAssetDescriptor();
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, CreatesPipelineInstancesFromPipelineAssetsAndShutsDownReplacedPipelines) {
|
|
Scene scene("SceneRendererAssetScene");
|
|
|
|
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
|
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
|
camera->SetPrimary(true);
|
|
camera->SetDepth(2.0f);
|
|
|
|
auto initialAssetState = std::make_shared<MockPipelineAssetState>();
|
|
auto replacementAssetState = std::make_shared<MockPipelineAssetState>();
|
|
|
|
{
|
|
SceneRenderer renderer(std::make_shared<MockPipelineAsset>(initialAssetState));
|
|
ASSERT_NE(renderer.GetPipeline(), nullptr);
|
|
ASSERT_NE(renderer.GetPipelineAsset(), nullptr);
|
|
EXPECT_EQ(initialAssetState->createCalls, 1);
|
|
|
|
const RenderSurface surface(800, 600);
|
|
ASSERT_TRUE(renderer.Render(scene, nullptr, CreateValidContext(), surface));
|
|
ASSERT_NE(initialAssetState->lastCreatedPipelineState, nullptr);
|
|
EXPECT_EQ(initialAssetState->lastCreatedPipelineState->renderCalls, 1);
|
|
EXPECT_EQ(initialAssetState->lastCreatedPipelineState->lastCamera, camera);
|
|
|
|
renderer.SetPipelineAsset(std::make_shared<MockPipelineAsset>(replacementAssetState));
|
|
ASSERT_NE(initialAssetState->lastCreatedPipelineState, nullptr);
|
|
EXPECT_EQ(initialAssetState->lastCreatedPipelineState->shutdownCalls, 1);
|
|
EXPECT_EQ(replacementAssetState->createCalls, 1);
|
|
|
|
ASSERT_TRUE(renderer.Render(scene, nullptr, CreateValidContext(), surface));
|
|
ASSERT_NE(replacementAssetState->lastCreatedPipelineState, nullptr);
|
|
EXPECT_EQ(replacementAssetState->lastCreatedPipelineState->renderCalls, 1);
|
|
EXPECT_EQ(replacementAssetState->lastCreatedPipelineState->lastCamera, camera);
|
|
}
|
|
|
|
ASSERT_NE(replacementAssetState->lastCreatedPipelineState, nullptr);
|
|
EXPECT_EQ(replacementAssetState->lastCreatedPipelineState->shutdownCalls, 1);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, SortsManualCameraRequestsByDepthBeforeRendering) {
|
|
Scene scene("SceneRendererManualRequests");
|
|
|
|
GameObject* farCameraObject = scene.CreateGameObject("FarCamera");
|
|
auto* farCamera = farCameraObject->AddComponent<CameraComponent>();
|
|
farCamera->SetPrimary(true);
|
|
farCamera->SetDepth(10.0f);
|
|
|
|
GameObject* nearCameraObject = scene.CreateGameObject("NearCamera");
|
|
auto* nearCamera = nearCameraObject->AddComponent<CameraComponent>();
|
|
nearCamera->SetPrimary(false);
|
|
nearCamera->SetDepth(1.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
SceneRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest farRequest;
|
|
farRequest.scene = &scene;
|
|
farRequest.camera = farCamera;
|
|
farRequest.context = CreateValidContext();
|
|
farRequest.surface = RenderSurface(800, 600);
|
|
farRequest.cameraDepth = farCamera->GetDepth();
|
|
farRequest.cameraStackOrder = 1;
|
|
farRequest.clearFlags = RenderClearFlags::None;
|
|
|
|
CameraRenderRequest nearRequest = farRequest;
|
|
nearRequest.camera = nearCamera;
|
|
nearRequest.cameraDepth = nearCamera->GetDepth();
|
|
nearRequest.cameraStackOrder = 0;
|
|
nearRequest.clearFlags = RenderClearFlags::Depth;
|
|
|
|
const std::vector<CameraFramePlan> plans = {
|
|
CameraFramePlan::FromRequest(farRequest),
|
|
CameraFramePlan::FromRequest(nearRequest)
|
|
};
|
|
ASSERT_TRUE(renderer.Render(plans));
|
|
ASSERT_EQ(state->renderedCameras.size(), 2u);
|
|
ASSERT_EQ(state->renderedClearFlags.size(), 2u);
|
|
EXPECT_EQ(state->renderedCameras[0], nearCamera);
|
|
EXPECT_EQ(state->renderedClearFlags[0], RenderClearFlags::Depth);
|
|
EXPECT_EQ(state->renderedCameras[1], farCamera);
|
|
EXPECT_EQ(state->renderedClearFlags[1], RenderClearFlags::None);
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, PreservesManualSubmissionOrderForEqualPriorityRequests) {
|
|
Scene scene("SceneRendererManualSubmissionOrder");
|
|
|
|
GameObject* firstCameraObject = scene.CreateGameObject("FirstCamera");
|
|
auto* firstCamera = firstCameraObject->AddComponent<CameraComponent>();
|
|
firstCamera->SetPrimary(false);
|
|
firstCamera->SetDepth(2.0f);
|
|
|
|
GameObject* secondCameraObject = scene.CreateGameObject("SecondCamera");
|
|
auto* secondCamera = secondCameraObject->AddComponent<CameraComponent>();
|
|
secondCamera->SetPrimary(false);
|
|
secondCamera->SetDepth(2.0f);
|
|
|
|
auto state = std::make_shared<MockPipelineState>();
|
|
SceneRenderer renderer(std::make_unique<MockPipeline>(state));
|
|
|
|
CameraRenderRequest firstRequest;
|
|
firstRequest.scene = &scene;
|
|
firstRequest.camera = firstCamera;
|
|
firstRequest.context = CreateValidContext();
|
|
firstRequest.surface = RenderSurface(800, 600);
|
|
firstRequest.cameraDepth = 2.0f;
|
|
firstRequest.cameraStackOrder = 0;
|
|
firstRequest.clearFlags = RenderClearFlags::All;
|
|
|
|
CameraRenderRequest secondRequest = firstRequest;
|
|
secondRequest.camera = secondCamera;
|
|
|
|
const std::vector<CameraFramePlan> plans = {
|
|
CameraFramePlan::FromRequest(secondRequest),
|
|
CameraFramePlan::FromRequest(firstRequest)
|
|
};
|
|
ASSERT_TRUE(renderer.Render(plans));
|
|
ASSERT_EQ(state->renderedCameras.size(), 2u);
|
|
EXPECT_EQ(state->renderedCameras[0], secondCamera);
|
|
EXPECT_EQ(state->renderedCameras[1], firstCamera);
|
|
}
|
|
|