2614 lines
112 KiB
C++
2614 lines
112 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <XCEngine/Components/CameraComponent.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/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 <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using namespace XCEngine::Components;
|
|
using namespace XCEngine::Rendering;
|
|
|
|
namespace {
|
|
|
|
struct MockPipelineState {
|
|
int initializeCalls = 0;
|
|
int shutdownCalls = 0;
|
|
int renderCalls = 0;
|
|
bool renderResult = 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();
|
|
XCEngine::Math::Vector4 lastShadowParams = XCEngine::Math::Vector4::Zero();
|
|
bool lastHasMainDirectionalShadowKeyword = false;
|
|
RenderEnvironmentMode lastEnvironmentMode = RenderEnvironmentMode::None;
|
|
bool lastHasSkybox = false;
|
|
const XCEngine::Resources::Material* lastSkyboxMaterial = nullptr;
|
|
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 = {};
|
|
};
|
|
|
|
struct MockPipelineAssetState {
|
|
int createCalls = 0;
|
|
std::shared_ptr<MockPipelineState> lastCreatedPipelineState;
|
|
FinalColorSettings defaultFinalColorSettings = {};
|
|
};
|
|
|
|
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 Shutdown() override {
|
|
++m_state->shutdownCalls;
|
|
}
|
|
|
|
bool Render(
|
|
const RenderContext&,
|
|
const RenderSurface& surface,
|
|
const RenderSceneData& sceneData) override {
|
|
m_state->eventLog.push_back("pipeline");
|
|
++m_state->renderCalls;
|
|
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->lastShadowParams = sceneData.lighting.mainDirectionalShadow.shadowParams;
|
|
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 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;
|
|
m_state->lastCreatedPipelineState = std::make_shared<MockPipelineState>();
|
|
return std::make_unique<MockPipeline>(m_state->lastCreatedPipelineState);
|
|
}
|
|
|
|
FinalColorSettings GetDefaultFinalColorSettings() const override {
|
|
return m_state->defaultFinalColorSettings;
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineAssetState> m_state;
|
|
};
|
|
|
|
class MockObjectIdPass final : public RenderPass {
|
|
public:
|
|
MockObjectIdPass(
|
|
std::shared_ptr<MockPipelineState> state,
|
|
bool renderResult = true)
|
|
: m_state(std::move(state))
|
|
, m_renderResult(renderResult) {
|
|
}
|
|
|
|
const char* GetName() const override {
|
|
return "MockObjectIdPass";
|
|
}
|
|
|
|
bool Execute(const RenderPassContext&) override {
|
|
m_state->eventLog.push_back("objectId");
|
|
return m_renderResult;
|
|
}
|
|
|
|
void Shutdown() override {
|
|
m_state->eventLog.push_back("shutdown:objectId");
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<MockPipelineState> m_state;
|
|
bool m_renderResult = true;
|
|
};
|
|
|
|
class MockScenePass final : public RenderPass {
|
|
public:
|
|
MockScenePass(
|
|
std::shared_ptr<MockPipelineState> state,
|
|
const char* label,
|
|
bool initializeResult = true,
|
|
bool executeResult = true)
|
|
: m_state(std::move(state))
|
|
, m_label(label)
|
|
, m_initializeResult(initializeResult)
|
|
, m_executeResult(executeResult) {
|
|
}
|
|
|
|
const char* GetName() const override {
|
|
return m_label;
|
|
}
|
|
|
|
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();
|
|
lastHasSourceSurface = context.sourceSurface != nullptr;
|
|
if (context.sourceSurface != nullptr) {
|
|
lastSourceSurfaceWidth = context.sourceSurface->GetRenderAreaWidth();
|
|
lastSourceSurfaceHeight = context.sourceSurface->GetRenderAreaHeight();
|
|
}
|
|
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 lastHasSourceSurface = false;
|
|
uint32_t lastSourceSurfaceWidth = 0;
|
|
uint32_t lastSourceSurfaceHeight = 0;
|
|
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;
|
|
};
|
|
|
|
class TrackingPass final : public RenderPass {
|
|
public:
|
|
TrackingPass(
|
|
std::shared_ptr<MockPipelineState> state,
|
|
const char* label,
|
|
bool initializeResult = true,
|
|
bool executeResult = true)
|
|
: m_state(std::move(state))
|
|
, m_label(label)
|
|
, m_initializeResult(initializeResult)
|
|
, m_executeResult(executeResult) {
|
|
}
|
|
|
|
const char* GetName() const override {
|
|
return m_label;
|
|
}
|
|
|
|
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();
|
|
lastHasSourceSurface = context.sourceSurface != nullptr;
|
|
if (context.sourceSurface != nullptr) {
|
|
lastSourceSurfaceWidth = context.sourceSurface->GetRenderAreaWidth();
|
|
lastSourceSurfaceHeight = context.sourceSurface->GetRenderAreaHeight();
|
|
}
|
|
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;
|
|
|
|
public:
|
|
uint32_t lastSurfaceWidth = 0;
|
|
uint32_t lastSurfaceHeight = 0;
|
|
bool lastHasSourceSurface = false;
|
|
uint32_t lastSourceSurfaceWidth = 0;
|
|
uint32_t lastSourceSurfaceHeight = 0;
|
|
XCEngine::RHI::RHIResourceView* lastSourceColorView = nullptr;
|
|
XCEngine::RHI::ResourceStates lastSourceColorState = XCEngine::RHI::ResourceStates::Common;
|
|
};
|
|
|
|
RenderContext CreateValidContext() {
|
|
RenderContext context;
|
|
context.device = reinterpret_cast<XCEngine::RHI::RHIDevice*>(1);
|
|
context.commandList = reinterpret_cast<XCEngine::RHI::RHICommandList*>(1);
|
|
context.commandQueue = reinterpret_cast<XCEngine::RHI::RHICommandQueue*>(1);
|
|
return context;
|
|
}
|
|
|
|
} // 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");
|
|
|
|
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::PreScenePasses));
|
|
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::ShadowCaster));
|
|
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::DepthOnly));
|
|
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::MainScene));
|
|
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::PostProcess));
|
|
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::FinalOutput));
|
|
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::ObjectId));
|
|
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::PostScenePasses));
|
|
EXPECT_TRUE(request.HasFrameStage(CameraFrameStage::OverlayPasses));
|
|
|
|
EXPECT_EQ(request.GetPassSequence(CameraFrameStage::PreScenePasses), &prePasses);
|
|
EXPECT_EQ(request.GetPassSequence(CameraFrameStage::PostProcess), &postProcessPasses);
|
|
EXPECT_EQ(request.GetPassSequence(CameraFrameStage::FinalOutput), &finalOutputPasses);
|
|
EXPECT_EQ(request.GetPassSequence(CameraFrameStage::PostScenePasses), &postPasses);
|
|
EXPECT_EQ(request.GetPassSequence(CameraFrameStage::OverlayPasses), &overlayPasses);
|
|
EXPECT_EQ(request.GetScenePassRequest(CameraFrameStage::ShadowCaster), &request.shadowCaster);
|
|
EXPECT_EQ(request.GetScenePassRequest(CameraFrameStage::DepthOnly), &request.depthOnly);
|
|
EXPECT_EQ(request.GetScenePassRequest(CameraFrameStage::MainScene), nullptr);
|
|
EXPECT_EQ(request.GetObjectIdRequest(CameraFrameStage::ObjectId), &request.objectId);
|
|
EXPECT_EQ(request.GetObjectIdRequest(CameraFrameStage::MainScene), nullptr);
|
|
EXPECT_TRUE(request.RequiresIntermediateSceneColor());
|
|
EXPECT_EQ(request.GetMainSceneSurface().GetRenderAreaWidth(), 256u);
|
|
EXPECT_EQ(request.GetMainSceneSurface().GetRenderAreaHeight(), 128u);
|
|
EXPECT_EQ(request.GetFinalCompositedSurface().GetRenderAreaWidth(), 640u);
|
|
EXPECT_EQ(request.GetFinalCompositedSurface().GetRenderAreaHeight(), 360u);
|
|
ASSERT_NE(request.GetOutputSurface(CameraFrameStage::PostProcess), nullptr);
|
|
EXPECT_EQ(request.GetOutputSurface(CameraFrameStage::PostProcess)->GetRenderAreaWidth(), 512u);
|
|
ASSERT_NE(request.GetSourceSurface(CameraFrameStage::PostProcess), nullptr);
|
|
EXPECT_EQ(request.GetSourceSurface(CameraFrameStage::PostProcess)->GetRenderAreaWidth(), 256u);
|
|
EXPECT_EQ(
|
|
request.GetSourceColorView(CameraFrameStage::PostProcess),
|
|
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(20));
|
|
EXPECT_EQ(
|
|
request.GetSourceColorState(CameraFrameStage::PostProcess),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
ASSERT_NE(request.GetSourceSurface(CameraFrameStage::FinalOutput), nullptr);
|
|
EXPECT_EQ(request.GetSourceSurface(CameraFrameStage::FinalOutput)->GetRenderAreaWidth(), 512u);
|
|
EXPECT_EQ(
|
|
request.GetSourceColorView(CameraFrameStage::FinalOutput),
|
|
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(40));
|
|
EXPECT_EQ(
|
|
request.GetSourceColorState(CameraFrameStage::FinalOutput),
|
|
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(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(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(request));
|
|
EXPECT_TRUE(state->lastHasSkybox);
|
|
EXPECT_EQ(state->lastEnvironmentMode, RenderEnvironmentMode::MaterialSkybox);
|
|
EXPECT_EQ(state->lastSkyboxMaterial, &skyboxMaterial);
|
|
}
|
|
|
|
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(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>();
|
|
CameraRenderer renderer(
|
|
std::make_unique<MockPipeline>(state),
|
|
std::make_unique<MockObjectIdPass>(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;
|
|
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(request));
|
|
EXPECT_EQ(
|
|
state->eventLog,
|
|
(std::vector<std::string>{
|
|
"init:pre",
|
|
"pre",
|
|
"pipeline",
|
|
"objectId",
|
|
"init:post",
|
|
"post" }));
|
|
}
|
|
|
|
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(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_EQ(
|
|
postProcessPassRaw->lastSourceColorView,
|
|
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(20));
|
|
EXPECT_EQ(postProcessPassRaw->lastSurfaceWidth, 512u);
|
|
EXPECT_EQ(postProcessPassRaw->lastSurfaceHeight, 256u);
|
|
ASSERT_NE(finalOutputPassRaw, nullptr);
|
|
EXPECT_TRUE(finalOutputPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceWidth, 512u);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceHeight, 256u);
|
|
EXPECT_EQ(
|
|
finalOutputPassRaw->lastSourceColorView,
|
|
reinterpret_cast<XCEngine::RHI::RHIResourceView*>(30));
|
|
EXPECT_EQ(finalOutputPassRaw->lastSurfaceWidth, 800u);
|
|
EXPECT_EQ(finalOutputPassRaw->lastSurfaceHeight, 600u);
|
|
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(request));
|
|
|
|
ASSERT_NE(firstPassRaw, nullptr);
|
|
EXPECT_TRUE(firstPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(firstPassRaw->lastSourceSurfaceWidth, 256u);
|
|
EXPECT_EQ(firstPassRaw->lastSourceSurfaceHeight, 128u);
|
|
EXPECT_EQ(firstPassRaw->lastSourceColorView, sourceColorShaderView);
|
|
EXPECT_EQ(firstPassRaw->lastSurfaceWidth, 512u);
|
|
EXPECT_EQ(firstPassRaw->lastSurfaceHeight, 256u);
|
|
|
|
ASSERT_NE(secondPassRaw, nullptr);
|
|
EXPECT_TRUE(secondPassRaw->lastHasSourceSurface);
|
|
EXPECT_EQ(secondPassRaw->lastSourceSurfaceWidth, 512u);
|
|
EXPECT_EQ(secondPassRaw->lastSourceSurfaceHeight, 256u);
|
|
EXPECT_NE(secondPassRaw->lastSourceColorView, nullptr);
|
|
EXPECT_NE(secondPassRaw->lastSourceColorView, sourceColorShaderView);
|
|
EXPECT_EQ(secondPassRaw->lastSurfaceWidth, 512u);
|
|
EXPECT_EQ(secondPassRaw->lastSurfaceHeight, 256u);
|
|
|
|
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(request));
|
|
|
|
ASSERT_NE(postSecondPassRaw, nullptr);
|
|
EXPECT_EQ(postSecondPassRaw->lastSourceSurfaceWidth, 512u);
|
|
EXPECT_EQ(postSecondPassRaw->lastSourceSurfaceHeight, 256u);
|
|
ASSERT_NE(finalFirstPassRaw, nullptr);
|
|
EXPECT_EQ(finalFirstPassRaw->lastSourceSurfaceWidth, 512u);
|
|
EXPECT_EQ(finalFirstPassRaw->lastSourceSurfaceHeight, 256u);
|
|
EXPECT_EQ(finalFirstPassRaw->lastSourceColorView, finalOutputSource);
|
|
ASSERT_NE(finalSecondPassRaw, nullptr);
|
|
EXPECT_EQ(finalSecondPassRaw->lastSourceSurfaceWidth, 800u);
|
|
EXPECT_EQ(finalSecondPassRaw->lastSourceSurfaceHeight, 600u);
|
|
EXPECT_NE(finalSecondPassRaw->lastSourceColorView, nullptr);
|
|
EXPECT_NE(finalSecondPassRaw->lastSourceColorView, finalOutputSource);
|
|
|
|
EXPECT_EQ(allocationState->createTextureCalls, 2);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 2);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 2);
|
|
EXPECT_EQ(allocationState->shutdownTextureCalls, 0);
|
|
EXPECT_EQ(allocationState->shutdownRenderTargetViewCalls, 0);
|
|
EXPECT_EQ(allocationState->shutdownShaderViewCalls, 0);
|
|
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, 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>();
|
|
CameraRenderer renderer(
|
|
std::make_unique<MockPipeline>(state),
|
|
std::make_unique<MockObjectIdPass>(state));
|
|
|
|
auto shadowPass = std::make_unique<MockScenePass>(state, "shadowCaster");
|
|
renderer.SetShadowCasterPass(std::move(shadowPass));
|
|
|
|
auto depthPass = std::make_unique<MockScenePass>(state, "depthOnly");
|
|
renderer.SetDepthOnlyPass(std::move(depthPass));
|
|
|
|
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(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, 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>();
|
|
CameraRenderer renderer(
|
|
std::make_unique<MockPipeline>(state),
|
|
std::make_unique<MockObjectIdPass>(state));
|
|
|
|
auto shadowPass = std::make_unique<MockScenePass>(state, "shadowCaster");
|
|
MockScenePass* shadowPassRaw = shadowPass.get();
|
|
renderer.SetShadowCasterPass(std::move(shadowPass));
|
|
|
|
auto depthPass = std::make_unique<MockScenePass>(state, "depthOnly");
|
|
MockScenePass* depthPassRaw = depthPass.get();
|
|
renderer.SetDepthOnlyPass(std::move(depthPass));
|
|
|
|
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(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_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_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, 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;
|
|
|
|
{
|
|
CameraRenderer renderer(
|
|
std::make_unique<MockPipeline>(pipelineState),
|
|
std::make_unique<MockObjectIdPass>(pipelineState));
|
|
|
|
auto shadowPass = std::make_unique<MockScenePass>(pipelineState, "shadowCaster");
|
|
MockScenePass* shadowPassRaw = shadowPass.get();
|
|
renderer.SetShadowCasterPass(std::move(shadowPass));
|
|
|
|
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(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_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->lastShadowParams.x, 0.0015f);
|
|
EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.y, 1.0f / 256.0f);
|
|
EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.z, 1.0f / 128.0f);
|
|
EXPECT_FLOAT_EQ(pipelineState->lastShadowParams.w, 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, 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;
|
|
|
|
CameraRenderer renderer(
|
|
std::make_unique<MockPipeline>(pipelineState),
|
|
std::make_unique<MockObjectIdPass>(pipelineState));
|
|
|
|
auto shadowPass = std::make_unique<MockScenePass>(pipelineState, "shadowCaster");
|
|
renderer.SetShadowCasterPass(std::move(shadowPass));
|
|
|
|
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(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(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;
|
|
|
|
{
|
|
CameraRenderer renderer(
|
|
std::make_unique<MockPipeline>(pipelineState),
|
|
std::make_unique<MockObjectIdPass>(pipelineState));
|
|
|
|
auto shadowPass = std::make_unique<MockScenePass>(pipelineState, "shadowCaster");
|
|
renderer.SetShadowCasterPass(std::move(shadowPass));
|
|
|
|
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(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(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;
|
|
|
|
{
|
|
CameraRenderer renderer(
|
|
std::make_unique<MockPipeline>(pipelineState),
|
|
std::make_unique<MockObjectIdPass>(pipelineState));
|
|
|
|
auto shadowPass = std::make_unique<MockScenePass>(pipelineState, "shadowCaster");
|
|
renderer.SetShadowCasterPass(std::move(shadowPass));
|
|
|
|
CameraRenderRequest request;
|
|
request.scene = &scene;
|
|
request.camera = camera;
|
|
request.context = context;
|
|
request.surface = RenderSurface(320, 180);
|
|
request.cameraDepth = camera->GetDepth();
|
|
|
|
ASSERT_TRUE(renderer.Render(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(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(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(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(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>();
|
|
CameraRenderer renderer(
|
|
std::make_unique<MockPipeline>(state),
|
|
std::make_unique<MockObjectIdPass>(state, false));
|
|
|
|
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(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(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<CameraRenderRequest> defaultRequests =
|
|
renderer.BuildRenderRequests(scene, nullptr, context, surface);
|
|
ASSERT_EQ(defaultRequests.size(), 2u);
|
|
EXPECT_EQ(defaultRequests[0].camera, lowCamera);
|
|
EXPECT_EQ(defaultRequests[0].cameraDepth, 1.0f);
|
|
EXPECT_EQ(defaultRequests[0].clearFlags, RenderClearFlags::All);
|
|
EXPECT_EQ(defaultRequests[0].surface.GetWidth(), 320u);
|
|
EXPECT_EQ(defaultRequests[0].surface.GetHeight(), 180u);
|
|
EXPECT_EQ(defaultRequests[1].camera, highCamera);
|
|
EXPECT_EQ(defaultRequests[1].cameraDepth, 5.0f);
|
|
EXPECT_EQ(defaultRequests[1].clearFlags, RenderClearFlags::None);
|
|
|
|
const std::vector<CameraRenderRequest> overrideRequests =
|
|
renderer.BuildRenderRequests(scene, lowCamera, context, surface);
|
|
ASSERT_EQ(overrideRequests.size(), 1u);
|
|
EXPECT_EQ(overrideRequests[0].camera, lowCamera);
|
|
EXPECT_EQ(overrideRequests[0].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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(requests.size(), 3u);
|
|
EXPECT_EQ(requests[0].camera, earlyBaseCamera);
|
|
EXPECT_EQ(requests[0].cameraStackOrder, 0u);
|
|
EXPECT_EQ(requests[0].clearFlags, RenderClearFlags::All);
|
|
EXPECT_EQ(requests[1].camera, lateBaseCamera);
|
|
EXPECT_EQ(requests[1].cameraStackOrder, 0u);
|
|
EXPECT_EQ(requests[1].clearFlags, RenderClearFlags::Depth);
|
|
EXPECT_EQ(requests[2].camera, overlayCamera);
|
|
EXPECT_EQ(requests[2].cameraStackOrder, 1u);
|
|
EXPECT_EQ(requests[2].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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(requests.size(), 2u);
|
|
EXPECT_EQ(requests[0].camera, firstCamera);
|
|
EXPECT_EQ(requests[1].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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), RenderSurface(320, 180));
|
|
|
|
ASSERT_EQ(requests.size(), 2u);
|
|
EXPECT_EQ(requests[0].camera, firstOverlay);
|
|
EXPECT_EQ(requests[0].clearFlags, RenderClearFlags::All);
|
|
EXPECT_EQ(requests[1].camera, secondOverlay);
|
|
EXPECT_EQ(requests[1].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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, camera, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
EXPECT_EQ(requests[0].camera, camera);
|
|
EXPECT_EQ(requests[0].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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), RenderSurface(800, 600));
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const XCEngine::Math::RectInt renderArea = requests[0].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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), surface);
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const XCEngine::Math::RectInt renderArea = requests[0].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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), surface);
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const XCEngine::Math::RectInt renderArea = requests[0].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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, context, surface);
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const CameraRenderRequest& request = requests[0];
|
|
EXPECT_TRUE(request.postProcess.IsRequested());
|
|
EXPECT_TRUE(request.postProcess.IsValid());
|
|
EXPECT_NE(request.postProcess.passes, nullptr);
|
|
ASSERT_EQ(request.postProcess.passes->GetPassCount(), 2u);
|
|
EXPECT_EQ(request.postProcess.destinationSurface.GetColorAttachments()[0], backBufferColorView);
|
|
EXPECT_EQ(request.postProcess.destinationSurface.GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
request.postProcess.destinationSurface.GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
request.postProcess.destinationSurface.GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(request.postProcess.sourceSurface.GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
request.postProcess.sourceSurface.GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
request.postProcess.sourceSurface.GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(request.postProcess.sourceSurface.GetWidth(), 800u);
|
|
EXPECT_EQ(request.postProcess.sourceSurface.GetHeight(), 600u);
|
|
const XCEngine::Math::RectInt sourceRenderArea = request.postProcess.sourceSurface.GetRenderArea();
|
|
EXPECT_EQ(sourceRenderArea.x, 200);
|
|
EXPECT_EQ(sourceRenderArea.y, 75);
|
|
EXPECT_EQ(sourceRenderArea.width, 400);
|
|
EXPECT_EQ(sourceRenderArea.height, 375);
|
|
EXPECT_NE(request.postProcess.sourceColorView, nullptr);
|
|
EXPECT_NE(request.postProcess.sourceColorView, backBufferColorView);
|
|
EXPECT_EQ(
|
|
request.postProcess.sourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
ASSERT_FALSE(request.postProcess.sourceSurface.GetColorAttachments().empty());
|
|
EXPECT_NE(request.postProcess.sourceSurface.GetColorAttachments()[0], backBufferColorView);
|
|
EXPECT_EQ(allocationState->createTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 1);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
|
|
|
|
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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, CreateValidContext(), RenderSurface(640, 360));
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const CameraRenderRequest& request = requests[0];
|
|
EXPECT_TRUE(request.finalColorPolicy.hasPipelineDefaults);
|
|
EXPECT_TRUE(request.finalColorPolicy.hasCameraOverrides);
|
|
EXPECT_EQ(
|
|
request.finalColorPolicy.outputTransferMode,
|
|
FinalColorOutputTransferMode::LinearToSRGB);
|
|
EXPECT_EQ(
|
|
request.finalColorPolicy.exposureMode,
|
|
FinalColorExposureMode::Fixed);
|
|
EXPECT_FLOAT_EQ(request.finalColorPolicy.exposureValue, 1.8f);
|
|
EXPECT_FLOAT_EQ(request.finalColorPolicy.finalColorScale.x, 0.95f);
|
|
EXPECT_FALSE(request.finalOutput.IsRequested());
|
|
}
|
|
|
|
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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, context, surface);
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const CameraRenderRequest& request = requests[0];
|
|
EXPECT_FALSE(request.postProcess.IsRequested());
|
|
EXPECT_TRUE(request.finalOutput.IsRequested());
|
|
EXPECT_TRUE(request.finalOutput.IsValid());
|
|
ASSERT_NE(request.finalOutput.passes, nullptr);
|
|
EXPECT_EQ(request.finalOutput.passes->GetPassCount(), 1u);
|
|
EXPECT_EQ(request.finalOutput.destinationSurface.GetColorAttachments()[0], backBufferColorView);
|
|
EXPECT_EQ(request.finalOutput.destinationSurface.GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
request.finalOutput.destinationSurface.GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
request.finalOutput.destinationSurface.GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(request.finalOutput.sourceSurface.GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
request.finalOutput.sourceSurface.GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
request.finalOutput.sourceSurface.GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(request.finalOutput.sourceSurface.GetWidth(), 800u);
|
|
EXPECT_EQ(request.finalOutput.sourceSurface.GetHeight(), 600u);
|
|
EXPECT_NE(request.finalOutput.sourceColorView, nullptr);
|
|
EXPECT_NE(request.finalOutput.sourceColorView, backBufferColorView);
|
|
EXPECT_EQ(
|
|
request.finalOutput.sourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
ASSERT_FALSE(request.finalOutput.sourceSurface.GetColorAttachments().empty());
|
|
EXPECT_NE(request.finalOutput.sourceSurface.GetColorAttachments()[0], backBufferColorView);
|
|
EXPECT_EQ(allocationState->createTextureCalls, 1);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 1);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 1);
|
|
|
|
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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, context, surface);
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const CameraRenderRequest& request = requests[0];
|
|
EXPECT_TRUE(request.postProcess.IsRequested());
|
|
EXPECT_TRUE(request.finalOutput.IsRequested());
|
|
ASSERT_NE(request.postProcess.passes, nullptr);
|
|
ASSERT_NE(request.finalOutput.passes, nullptr);
|
|
EXPECT_EQ(request.postProcess.passes->GetPassCount(), 1u);
|
|
EXPECT_EQ(request.finalOutput.passes->GetPassCount(), 1u);
|
|
|
|
ASSERT_FALSE(request.postProcess.sourceSurface.GetColorAttachments().empty());
|
|
ASSERT_FALSE(request.postProcess.destinationSurface.GetColorAttachments().empty());
|
|
ASSERT_FALSE(request.finalOutput.sourceSurface.GetColorAttachments().empty());
|
|
|
|
EXPECT_NE(
|
|
request.postProcess.sourceSurface.GetColorAttachments()[0],
|
|
request.postProcess.destinationSurface.GetColorAttachments()[0]);
|
|
EXPECT_EQ(
|
|
request.finalOutput.sourceSurface.GetColorAttachments()[0],
|
|
request.postProcess.destinationSurface.GetColorAttachments()[0]);
|
|
EXPECT_EQ(request.finalOutput.destinationSurface.GetColorAttachments()[0], backBufferColorView);
|
|
|
|
EXPECT_EQ(request.postProcess.sourceSurface.GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
request.postProcess.sourceSurface.GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
request.postProcess.sourceSurface.GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(request.postProcess.destinationSurface.GetDepthAttachment(), nullptr);
|
|
EXPECT_EQ(request.finalOutput.sourceSurface.GetDepthAttachment(), nullptr);
|
|
EXPECT_EQ(request.finalOutput.destinationSurface.GetDepthAttachment(), depthView);
|
|
EXPECT_EQ(
|
|
request.finalOutput.destinationSurface.GetDepthStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
request.finalOutput.destinationSurface.GetDepthStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
|
|
const XCEngine::Math::RectInt postProcessSourceArea = request.postProcess.sourceSurface.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 = request.finalOutput.sourceSurface.GetRenderArea();
|
|
EXPECT_EQ(finalOutputSourceArea.x, 200);
|
|
EXPECT_EQ(finalOutputSourceArea.y, 75);
|
|
EXPECT_EQ(finalOutputSourceArea.width, 400);
|
|
EXPECT_EQ(finalOutputSourceArea.height, 375);
|
|
|
|
EXPECT_NE(request.postProcess.sourceColorView, nullptr);
|
|
EXPECT_NE(request.finalOutput.sourceColorView, nullptr);
|
|
EXPECT_NE(request.postProcess.sourceColorView, request.finalOutput.sourceColorView);
|
|
EXPECT_EQ(
|
|
request.postProcess.sourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(
|
|
request.finalOutput.sourceColorState,
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(allocationState->createTextureCalls, 2);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 2);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 2);
|
|
|
|
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<CameraRenderRequest> requests =
|
|
renderer.BuildRenderRequests(scene, nullptr, context, surface);
|
|
|
|
ASSERT_EQ(requests.size(), 1u);
|
|
const CameraRenderRequest& request = requests[0];
|
|
EXPECT_EQ(request.surface.GetSampleCount(), 4u);
|
|
EXPECT_FALSE(request.postProcess.IsRequested());
|
|
EXPECT_FALSE(request.finalOutput.IsRequested());
|
|
EXPECT_EQ(allocationState->createTextureCalls, 0);
|
|
EXPECT_EQ(allocationState->createRenderTargetViewCalls, 0);
|
|
EXPECT_EQ(allocationState->createShaderViewCalls, 0);
|
|
|
|
delete depthView;
|
|
delete backBufferColorView;
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, ReusesTrackedSceneColorStateAcrossFramesWhenPostProcessIsEnabled) {
|
|
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>();
|
|
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<CameraRenderRequest> firstFrameRequests =
|
|
renderer.BuildRenderRequests(scene, nullptr, context, surface);
|
|
ASSERT_EQ(firstFrameRequests.size(), 1u);
|
|
EXPECT_EQ(
|
|
firstFrameRequests[0].GetMainSceneSurface().GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::make_unique<MockScenePass>(pipelineState, "postProcess"));
|
|
firstFrameRequests[0].postProcess.passes = &postProcessPasses;
|
|
|
|
ASSERT_TRUE(renderer.Render(firstFrameRequests));
|
|
|
|
const std::vector<CameraRenderRequest> secondFrameRequests =
|
|
renderer.BuildRenderRequests(scene, nullptr, context, surface);
|
|
ASSERT_EQ(secondFrameRequests.size(), 1u);
|
|
EXPECT_EQ(
|
|
secondFrameRequests[0].GetMainSceneSurface().GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(
|
|
secondFrameRequests[0].GetMainSceneSurface().GetColorStateAfter(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
|
|
delete depthView;
|
|
delete backBufferColorView;
|
|
}
|
|
|
|
TEST(SceneRenderer_Test, ReusesTrackedPostProcessOutputStateAcrossFramesWhenFinalOutputIsEnabled) {
|
|
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>();
|
|
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<CameraRenderRequest> firstFrameRequests =
|
|
renderer.BuildRenderRequests(scene, nullptr, context, surface);
|
|
ASSERT_EQ(firstFrameRequests.size(), 1u);
|
|
EXPECT_TRUE(firstFrameRequests[0].postProcess.IsRequested());
|
|
EXPECT_TRUE(firstFrameRequests[0].finalOutput.IsRequested());
|
|
EXPECT_EQ(
|
|
firstFrameRequests[0].GetMainSceneSurface().GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
EXPECT_EQ(
|
|
firstFrameRequests[0].postProcess.destinationSurface.GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::Common);
|
|
|
|
RenderPassSequence postProcessPasses;
|
|
postProcessPasses.AddPass(std::make_unique<MockScenePass>(pipelineState, "postProcess"));
|
|
RenderPassSequence finalOutputPasses;
|
|
finalOutputPasses.AddPass(std::make_unique<MockScenePass>(pipelineState, "finalOutput"));
|
|
firstFrameRequests[0].postProcess.passes = &postProcessPasses;
|
|
firstFrameRequests[0].finalOutput.passes = &finalOutputPasses;
|
|
|
|
ASSERT_TRUE(renderer.Render(firstFrameRequests));
|
|
|
|
const std::vector<CameraRenderRequest> secondFrameRequests =
|
|
renderer.BuildRenderRequests(scene, nullptr, context, surface);
|
|
ASSERT_EQ(secondFrameRequests.size(), 1u);
|
|
EXPECT_EQ(
|
|
secondFrameRequests[0].GetMainSceneSurface().GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(
|
|
secondFrameRequests[0].postProcess.destinationSurface.GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
EXPECT_EQ(
|
|
secondFrameRequests[0].finalOutput.sourceSurface.GetColorStateBefore(),
|
|
XCEngine::RHI::ResourceStates::PixelShaderResource);
|
|
|
|
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(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(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<CameraRenderRequest> requests = { farRequest, nearRequest };
|
|
ASSERT_TRUE(renderer.Render(requests));
|
|
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<CameraRenderRequest> requests = { secondRequest, firstRequest };
|
|
ASSERT_TRUE(renderer.Render(requests));
|
|
ASSERT_EQ(state->renderedCameras.size(), 2u);
|
|
EXPECT_EQ(state->renderedCameras[0], secondCamera);
|
|
EXPECT_EQ(state->renderedCameras[1], firstCamera);
|
|
}
|