feat: add camera viewport rect render areas
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <XCEngine/Components/Component.h>
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Rect.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Components {
|
||||
@@ -49,6 +50,9 @@ public:
|
||||
uint32_t GetCullingMask() const { return m_cullingMask; }
|
||||
void SetCullingMask(uint32_t value) { m_cullingMask = value; }
|
||||
|
||||
const Math::Rect& GetViewportRect() const { return m_viewportRect; }
|
||||
void SetViewportRect(const Math::Rect& value);
|
||||
|
||||
const Math::Color& GetClearColor() const { return m_clearColor; }
|
||||
void SetClearColor(const Math::Color& value) { m_clearColor = value; }
|
||||
|
||||
@@ -65,6 +69,7 @@ private:
|
||||
bool m_primary = true;
|
||||
CameraClearMode m_clearMode = CameraClearMode::Auto;
|
||||
uint32_t m_cullingMask = 0xFFFFFFFFu;
|
||||
Math::Rect m_viewportRect = Math::Rect(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
Math::Color m_clearColor = Math::Color(0.192f, 0.302f, 0.475f, 1.0f);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Math/Color.h>
|
||||
#include <XCEngine/Core/Math/Rect.h>
|
||||
#include <XCEngine/RHI/RHIEnums.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -29,6 +30,13 @@ public:
|
||||
void SetDepthAttachment(RHI::RHIResourceView* depthAttachment) { m_depthAttachment = depthAttachment; }
|
||||
RHI::RHIResourceView* GetDepthAttachment() const { return m_depthAttachment; }
|
||||
|
||||
void SetRenderArea(const Math::RectInt& renderArea);
|
||||
void ResetRenderArea();
|
||||
bool HasCustomRenderArea() const { return m_hasCustomRenderArea; }
|
||||
Math::RectInt GetRenderArea() const;
|
||||
uint32_t GetRenderAreaWidth() const;
|
||||
uint32_t GetRenderAreaHeight() const;
|
||||
|
||||
void SetClearColorOverride(const Math::Color& clearColor);
|
||||
void ClearClearColorOverride();
|
||||
bool HasClearColorOverride() const { return m_hasClearColorOverride; }
|
||||
@@ -48,6 +56,8 @@ private:
|
||||
uint32_t m_height = 0;
|
||||
std::vector<RHI::RHIResourceView*> m_colorAttachments;
|
||||
RHI::RHIResourceView* m_depthAttachment = nullptr;
|
||||
bool m_hasCustomRenderArea = false;
|
||||
Math::RectInt m_renderArea;
|
||||
bool m_hasClearColorOverride = false;
|
||||
Math::Color m_clearColorOverride = Math::Color::Black();
|
||||
bool m_autoTransition = true;
|
||||
|
||||
@@ -25,6 +25,16 @@ void CameraComponent::SetFarClipPlane(float value) {
|
||||
m_farClipPlane = std::max(m_nearClipPlane + 0.001f, value);
|
||||
}
|
||||
|
||||
void CameraComponent::SetViewportRect(const Math::Rect& value) {
|
||||
const float x = std::clamp(value.x, 0.0f, 1.0f);
|
||||
const float y = std::clamp(value.y, 0.0f, 1.0f);
|
||||
const float width = std::clamp(value.width, 0.0f, 1.0f);
|
||||
const float height = std::clamp(value.height, 0.0f, 1.0f);
|
||||
const float right = std::min(1.0f, x + width);
|
||||
const float bottom = std::min(1.0f, y + height);
|
||||
m_viewportRect = Math::Rect(x, y, right - x, bottom - y);
|
||||
}
|
||||
|
||||
void CameraComponent::Serialize(std::ostream& os) const {
|
||||
os << "projection=" << static_cast<int>(m_projectionType) << ";";
|
||||
os << "fov=" << m_fieldOfView << ";";
|
||||
@@ -35,6 +45,7 @@ void CameraComponent::Serialize(std::ostream& os) const {
|
||||
os << "primary=" << (m_primary ? 1 : 0) << ";";
|
||||
os << "clearMode=" << static_cast<int>(m_clearMode) << ";";
|
||||
os << "cullingMask=" << m_cullingMask << ";";
|
||||
os << "viewportRect=" << m_viewportRect.x << "," << m_viewportRect.y << "," << m_viewportRect.width << "," << m_viewportRect.height << ";";
|
||||
os << "clearColor=" << m_clearColor.r << "," << m_clearColor.g << "," << m_clearColor.b << "," << m_clearColor.a << ";";
|
||||
}
|
||||
|
||||
@@ -71,6 +82,12 @@ void CameraComponent::Deserialize(std::istream& is) {
|
||||
m_clearMode = static_cast<CameraClearMode>(std::stoi(value));
|
||||
} else if (key == "cullingMask") {
|
||||
m_cullingMask = static_cast<uint32_t>(std::stoul(value));
|
||||
} else if (key == "viewportRect") {
|
||||
std::replace(value.begin(), value.end(), ',', ' ');
|
||||
std::istringstream ss(value);
|
||||
Math::Rect viewportRect;
|
||||
ss >> viewportRect.x >> viewportRect.y >> viewportRect.width >> viewportRect.height;
|
||||
SetViewportRect(viewportRect);
|
||||
} else if (key == "clearColor") {
|
||||
std::replace(value.begin(), value.end(), ',', ' ');
|
||||
std::istringstream ss(value);
|
||||
|
||||
@@ -101,11 +101,16 @@ bool CameraRenderer::Render(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.surface.GetRenderAreaWidth() == 0 ||
|
||||
request.surface.GetRenderAreaHeight() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderSceneData sceneData = m_sceneExtractor.ExtractForCamera(
|
||||
*request.scene,
|
||||
*request.camera,
|
||||
request.surface.GetWidth(),
|
||||
request.surface.GetHeight());
|
||||
request.surface.GetRenderAreaWidth(),
|
||||
request.surface.GetRenderAreaHeight());
|
||||
if (!sceneData.HasCamera()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -324,19 +324,24 @@ bool BuiltinForwardPipeline::ExecuteForwardOpaquePass(const RenderPassContext& p
|
||||
renderTargets.data(),
|
||||
surface.GetDepthAttachment());
|
||||
|
||||
const Math::RectInt renderArea = surface.GetRenderArea();
|
||||
if (renderArea.width <= 0 || renderArea.height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const RHI::Viewport viewport = {
|
||||
0.0f,
|
||||
0.0f,
|
||||
static_cast<float>(surface.GetWidth()),
|
||||
static_cast<float>(surface.GetHeight()),
|
||||
static_cast<float>(renderArea.x),
|
||||
static_cast<float>(renderArea.y),
|
||||
static_cast<float>(renderArea.width),
|
||||
static_cast<float>(renderArea.height),
|
||||
0.0f,
|
||||
1.0f
|
||||
};
|
||||
const RHI::Rect scissorRect = {
|
||||
0,
|
||||
0,
|
||||
static_cast<int32_t>(surface.GetWidth()),
|
||||
static_cast<int32_t>(surface.GetHeight())
|
||||
renderArea.x,
|
||||
renderArea.y,
|
||||
renderArea.x + renderArea.width,
|
||||
renderArea.y + renderArea.height
|
||||
};
|
||||
commandList->SetViewport(viewport);
|
||||
commandList->SetScissorRect(scissorRect);
|
||||
|
||||
@@ -1,8 +1,39 @@
|
||||
#include "Rendering/RenderSurface.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
|
||||
namespace {
|
||||
|
||||
Math::RectInt ClampRenderArea(
|
||||
const Math::RectInt& renderArea,
|
||||
uint32_t surfaceWidth,
|
||||
uint32_t surfaceHeight) {
|
||||
const int32_t maxWidth = static_cast<int32_t>(surfaceWidth);
|
||||
const int32_t maxHeight = static_cast<int32_t>(surfaceHeight);
|
||||
|
||||
const int32_t left = std::clamp(renderArea.x, 0, maxWidth);
|
||||
const int32_t top = std::clamp(renderArea.y, 0, maxHeight);
|
||||
const int64_t unclampedRight = static_cast<int64_t>(renderArea.x) +
|
||||
static_cast<int64_t>(std::max(renderArea.width, 0));
|
||||
const int64_t unclampedBottom = static_cast<int64_t>(renderArea.y) +
|
||||
static_cast<int64_t>(std::max(renderArea.height, 0));
|
||||
const int32_t right = std::clamp(
|
||||
static_cast<int32_t>(std::min<int64_t>(unclampedRight, maxWidth)),
|
||||
left,
|
||||
maxWidth);
|
||||
const int32_t bottom = std::clamp(
|
||||
static_cast<int32_t>(std::min<int64_t>(unclampedBottom, maxHeight)),
|
||||
top,
|
||||
maxHeight);
|
||||
return Math::RectInt(left, top, right - left, bottom - top);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderSurface::RenderSurface(uint32_t width, uint32_t height)
|
||||
: m_width(width)
|
||||
, m_height(height) {
|
||||
@@ -11,6 +42,9 @@ RenderSurface::RenderSurface(uint32_t width, uint32_t height)
|
||||
void RenderSurface::SetSize(uint32_t width, uint32_t height) {
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
if (m_hasCustomRenderArea) {
|
||||
m_renderArea = ClampRenderArea(m_renderArea, m_width, m_height);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderSurface::SetColorAttachment(RHI::RHIResourceView* colorAttachment) {
|
||||
@@ -24,6 +58,32 @@ void RenderSurface::SetColorAttachments(const std::vector<RHI::RHIResourceView*>
|
||||
m_colorAttachments = colorAttachments;
|
||||
}
|
||||
|
||||
void RenderSurface::SetRenderArea(const Math::RectInt& renderArea) {
|
||||
m_renderArea = ClampRenderArea(renderArea, m_width, m_height);
|
||||
m_hasCustomRenderArea = true;
|
||||
}
|
||||
|
||||
void RenderSurface::ResetRenderArea() {
|
||||
m_hasCustomRenderArea = false;
|
||||
m_renderArea = {};
|
||||
}
|
||||
|
||||
Math::RectInt RenderSurface::GetRenderArea() const {
|
||||
if (!m_hasCustomRenderArea) {
|
||||
return Math::RectInt(0, 0, static_cast<int32_t>(m_width), static_cast<int32_t>(m_height));
|
||||
}
|
||||
|
||||
return ClampRenderArea(m_renderArea, m_width, m_height);
|
||||
}
|
||||
|
||||
uint32_t RenderSurface::GetRenderAreaWidth() const {
|
||||
return static_cast<uint32_t>(std::max(GetRenderArea().width, 0));
|
||||
}
|
||||
|
||||
uint32_t RenderSurface::GetRenderAreaHeight() const {
|
||||
return static_cast<uint32_t>(std::max(GetRenderArea().height, 0));
|
||||
}
|
||||
|
||||
void RenderSurface::SetClearColorOverride(const Math::Color& clearColor) {
|
||||
m_hasClearColorOverride = true;
|
||||
m_clearColorOverride = clearColor;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Scene/Scene.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Rendering {
|
||||
@@ -40,6 +41,20 @@ RenderClearFlags ResolveClearFlags(const Components::CameraComponent& camera, si
|
||||
}
|
||||
}
|
||||
|
||||
Math::RectInt ResolveCameraRenderArea(
|
||||
const Components::CameraComponent& camera,
|
||||
const RenderSurface& surface) {
|
||||
const Math::Rect viewportRect = camera.GetViewportRect();
|
||||
const float surfaceWidth = static_cast<float>(surface.GetWidth());
|
||||
const float surfaceHeight = static_cast<float>(surface.GetHeight());
|
||||
|
||||
const int32_t left = static_cast<int32_t>(std::floor(viewportRect.x * surfaceWidth));
|
||||
const int32_t top = static_cast<int32_t>(std::floor(viewportRect.y * surfaceHeight));
|
||||
const int32_t right = static_cast<int32_t>(std::ceil((viewportRect.x + viewportRect.width) * surfaceWidth));
|
||||
const int32_t bottom = static_cast<int32_t>(std::ceil((viewportRect.y + viewportRect.height) * surfaceHeight));
|
||||
return Math::RectInt(left, top, right - left, bottom - top);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SceneRenderer::SceneRenderer() = default;
|
||||
@@ -101,9 +116,13 @@ std::vector<CameraRenderRequest> SceneRenderer::BuildRenderRequests(
|
||||
request.camera = camera;
|
||||
request.context = context;
|
||||
request.surface = surface;
|
||||
request.surface.SetRenderArea(ResolveCameraRenderArea(*camera, surface));
|
||||
request.cameraDepth = camera->GetDepth();
|
||||
request.clearFlags = ResolveClearFlags(*camera, cameraIndex);
|
||||
requests.push_back(request);
|
||||
if (request.surface.GetRenderAreaWidth() > 0 &&
|
||||
request.surface.GetRenderAreaHeight() > 0) {
|
||||
requests.push_back(request);
|
||||
}
|
||||
}
|
||||
|
||||
return requests;
|
||||
|
||||
@@ -20,6 +20,10 @@ TEST(CameraComponent_Test, DefaultValues) {
|
||||
EXPECT_TRUE(camera.IsPrimary());
|
||||
EXPECT_EQ(camera.GetClearMode(), CameraClearMode::Auto);
|
||||
EXPECT_EQ(camera.GetCullingMask(), 0xFFFFFFFFu);
|
||||
EXPECT_FLOAT_EQ(camera.GetViewportRect().x, 0.0f);
|
||||
EXPECT_FLOAT_EQ(camera.GetViewportRect().y, 0.0f);
|
||||
EXPECT_FLOAT_EQ(camera.GetViewportRect().width, 1.0f);
|
||||
EXPECT_FLOAT_EQ(camera.GetViewportRect().height, 1.0f);
|
||||
}
|
||||
|
||||
TEST(CameraComponent_Test, SetterClamping) {
|
||||
@@ -36,10 +40,21 @@ TEST(CameraComponent_Test, SetterClamping) {
|
||||
EXPECT_GT(camera.GetFarClipPlane(), camera.GetNearClipPlane());
|
||||
}
|
||||
|
||||
TEST(CameraComponent_Test, SerializeRoundTripPreservesClearMode) {
|
||||
TEST(CameraComponent_Test, ViewportRectIsClampedToNormalizedSurfaceRange) {
|
||||
CameraComponent camera;
|
||||
camera.SetViewportRect(XCEngine::Math::Rect(-0.25f, 0.2f, 1.5f, 1.1f));
|
||||
|
||||
EXPECT_FLOAT_EQ(camera.GetViewportRect().x, 0.0f);
|
||||
EXPECT_FLOAT_EQ(camera.GetViewportRect().y, 0.2f);
|
||||
EXPECT_FLOAT_EQ(camera.GetViewportRect().width, 1.0f);
|
||||
EXPECT_FLOAT_EQ(camera.GetViewportRect().height, 0.8f);
|
||||
}
|
||||
|
||||
TEST(CameraComponent_Test, SerializeRoundTripPreservesViewportAndClearState) {
|
||||
CameraComponent source;
|
||||
source.SetClearMode(CameraClearMode::DepthOnly);
|
||||
source.SetCullingMask(0x0000000Fu);
|
||||
source.SetViewportRect(XCEngine::Math::Rect(0.25f, 0.125f, 0.5f, 0.625f));
|
||||
|
||||
std::stringstream stream;
|
||||
source.Serialize(stream);
|
||||
@@ -49,6 +64,10 @@ TEST(CameraComponent_Test, SerializeRoundTripPreservesClearMode) {
|
||||
|
||||
EXPECT_EQ(target.GetClearMode(), CameraClearMode::DepthOnly);
|
||||
EXPECT_EQ(target.GetCullingMask(), 0x0000000Fu);
|
||||
EXPECT_FLOAT_EQ(target.GetViewportRect().x, 0.25f);
|
||||
EXPECT_FLOAT_EQ(target.GetViewportRect().y, 0.125f);
|
||||
EXPECT_FLOAT_EQ(target.GetViewportRect().width, 0.5f);
|
||||
EXPECT_FLOAT_EQ(target.GetViewportRect().height, 0.625f);
|
||||
}
|
||||
|
||||
TEST(LightComponent_Test, DefaultValues) {
|
||||
|
||||
@@ -23,6 +23,12 @@ struct MockPipelineState {
|
||||
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;
|
||||
@@ -59,7 +65,14 @@ public:
|
||||
++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->renderedCameras.push_back(sceneData.camera);
|
||||
@@ -163,6 +176,12 @@ TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) {
|
||||
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);
|
||||
@@ -330,6 +349,54 @@ TEST(SceneRenderer_Test, HonorsExplicitOverrideCameraClearMode) {
|
||||
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(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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user