Formalize camera frame composition stages
This commit is contained in:
@@ -5,6 +5,9 @@
|
|||||||
#include <XCEngine/Rendering/RenderPass.h>
|
#include <XCEngine/Rendering/RenderPass.h>
|
||||||
#include <XCEngine/Rendering/RenderSurface.h>
|
#include <XCEngine/Rendering/RenderSurface.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Components {
|
namespace Components {
|
||||||
class CameraComponent;
|
class CameraComponent;
|
||||||
@@ -13,6 +16,53 @@ class Scene;
|
|||||||
|
|
||||||
namespace Rendering {
|
namespace Rendering {
|
||||||
|
|
||||||
|
enum class CameraFrameStage : uint8_t {
|
||||||
|
PreScenePasses,
|
||||||
|
ShadowCaster,
|
||||||
|
DepthOnly,
|
||||||
|
MainScene,
|
||||||
|
ObjectId,
|
||||||
|
PostScenePasses,
|
||||||
|
OverlayPasses
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CameraFrameStageInfo {
|
||||||
|
CameraFrameStage stage = CameraFrameStage::MainScene;
|
||||||
|
const char* name = "";
|
||||||
|
bool runtimeStage = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline constexpr std::array<CameraFrameStageInfo, 7> kOrderedCameraFrameStages = {{
|
||||||
|
{ CameraFrameStage::PreScenePasses, "PreScenePasses", false },
|
||||||
|
{ CameraFrameStage::ShadowCaster, "ShadowCaster", true },
|
||||||
|
{ CameraFrameStage::DepthOnly, "DepthOnly", true },
|
||||||
|
{ CameraFrameStage::MainScene, "MainScene", true },
|
||||||
|
{ CameraFrameStage::ObjectId, "ObjectId", false },
|
||||||
|
{ CameraFrameStage::PostScenePasses, "PostScenePasses", false },
|
||||||
|
{ CameraFrameStage::OverlayPasses, "OverlayPasses", false }
|
||||||
|
}};
|
||||||
|
|
||||||
|
inline constexpr const char* GetCameraFrameStageName(CameraFrameStage stage) {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PreScenePasses:
|
||||||
|
return "PreScenePasses";
|
||||||
|
case CameraFrameStage::ShadowCaster:
|
||||||
|
return "ShadowCaster";
|
||||||
|
case CameraFrameStage::DepthOnly:
|
||||||
|
return "DepthOnly";
|
||||||
|
case CameraFrameStage::MainScene:
|
||||||
|
return "MainScene";
|
||||||
|
case CameraFrameStage::ObjectId:
|
||||||
|
return "ObjectId";
|
||||||
|
case CameraFrameStage::PostScenePasses:
|
||||||
|
return "PostScenePasses";
|
||||||
|
case CameraFrameStage::OverlayPasses:
|
||||||
|
return "OverlayPasses";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ScenePassRenderRequest {
|
struct ScenePassRenderRequest {
|
||||||
RenderSurface surface;
|
RenderSurface surface;
|
||||||
RenderClearFlags clearFlags = RenderClearFlags::Depth;
|
RenderClearFlags clearFlags = RenderClearFlags::Depth;
|
||||||
@@ -93,6 +143,55 @@ struct CameraRenderRequest {
|
|||||||
RenderPassSequence* postScenePasses = nullptr;
|
RenderPassSequence* postScenePasses = nullptr;
|
||||||
RenderPassSequence* overlayPasses = nullptr;
|
RenderPassSequence* overlayPasses = nullptr;
|
||||||
|
|
||||||
|
bool HasFrameStage(CameraFrameStage stage) const {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PreScenePasses:
|
||||||
|
return preScenePasses != nullptr;
|
||||||
|
case CameraFrameStage::ShadowCaster:
|
||||||
|
return shadowCaster.IsRequested() || directionalShadow.IsValid();
|
||||||
|
case CameraFrameStage::DepthOnly:
|
||||||
|
return depthOnly.IsRequested();
|
||||||
|
case CameraFrameStage::MainScene:
|
||||||
|
return true;
|
||||||
|
case CameraFrameStage::ObjectId:
|
||||||
|
return objectId.IsRequested();
|
||||||
|
case CameraFrameStage::PostScenePasses:
|
||||||
|
return postScenePasses != nullptr;
|
||||||
|
case CameraFrameStage::OverlayPasses:
|
||||||
|
return overlayPasses != nullptr;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPassSequence* GetPassSequence(CameraFrameStage stage) const {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PreScenePasses:
|
||||||
|
return preScenePasses;
|
||||||
|
case CameraFrameStage::PostScenePasses:
|
||||||
|
return postScenePasses;
|
||||||
|
case CameraFrameStage::OverlayPasses:
|
||||||
|
return overlayPasses;
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScenePassRenderRequest* GetScenePassRequest(CameraFrameStage stage) const {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::ShadowCaster:
|
||||||
|
return &shadowCaster;
|
||||||
|
case CameraFrameStage::DepthOnly:
|
||||||
|
return &depthOnly;
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ObjectIdRenderRequest* GetObjectIdRequest(CameraFrameStage stage) const {
|
||||||
|
return stage == CameraFrameStage::ObjectId ? &objectId : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsValid() const {
|
bool IsValid() const {
|
||||||
return scene != nullptr &&
|
return scene != nullptr &&
|
||||||
camera != nullptr &&
|
camera != nullptr &&
|
||||||
|
|||||||
@@ -151,6 +151,77 @@ private:
|
|||||||
bool m_failed = false;
|
bool m_failed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CameraFrameExecutionState {
|
||||||
|
RenderPipeline* pipeline = nullptr;
|
||||||
|
RenderPass* objectIdPass = nullptr;
|
||||||
|
RenderPass* depthOnlyPass = nullptr;
|
||||||
|
RenderPass* shadowCasterPass = nullptr;
|
||||||
|
std::unique_ptr<ScopedInitializedPassSequence> preScenePasses;
|
||||||
|
std::unique_ptr<ScopedInitializedPassSequence> postScenePasses;
|
||||||
|
std::unique_ptr<ScopedInitializedPassSequence> overlayPasses;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ExecutePassSequenceStage(
|
||||||
|
std::unique_ptr<ScopedInitializedPassSequence>& activeSequence,
|
||||||
|
RenderPassSequence* sequence,
|
||||||
|
const RenderContext& context,
|
||||||
|
const RenderPassContext& passContext) {
|
||||||
|
activeSequence = std::make_unique<ScopedInitializedPassSequence>(sequence, context);
|
||||||
|
return activeSequence->IsReady() && activeSequence->Execute(passContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExecuteFrameStage(
|
||||||
|
CameraFrameStage stage,
|
||||||
|
const CameraRenderRequest& request,
|
||||||
|
const ShadowCasterRenderRequest& resolvedShadowCaster,
|
||||||
|
const RenderPassContext& passContext,
|
||||||
|
CameraFrameExecutionState& executionState) {
|
||||||
|
switch (stage) {
|
||||||
|
case CameraFrameStage::PreScenePasses:
|
||||||
|
return ExecutePassSequenceStage(
|
||||||
|
executionState.preScenePasses,
|
||||||
|
request.GetPassSequence(stage),
|
||||||
|
request.context,
|
||||||
|
passContext);
|
||||||
|
case CameraFrameStage::ShadowCaster:
|
||||||
|
return ExecuteScenePassRequest(
|
||||||
|
executionState.shadowCasterPass,
|
||||||
|
resolvedShadowCaster,
|
||||||
|
request.context,
|
||||||
|
passContext.sceneData);
|
||||||
|
case CameraFrameStage::DepthOnly:
|
||||||
|
return ExecuteScenePassRequest(
|
||||||
|
executionState.depthOnlyPass,
|
||||||
|
request.depthOnly,
|
||||||
|
request.context,
|
||||||
|
passContext.sceneData);
|
||||||
|
case CameraFrameStage::MainScene:
|
||||||
|
return executionState.pipeline != nullptr &&
|
||||||
|
executionState.pipeline->Render(request.context, request.surface, passContext.sceneData);
|
||||||
|
case CameraFrameStage::ObjectId:
|
||||||
|
return !request.objectId.IsRequested() ||
|
||||||
|
ExecuteStandalonePass(
|
||||||
|
executionState.objectIdPass,
|
||||||
|
request.context,
|
||||||
|
request.objectId.surface,
|
||||||
|
passContext.sceneData);
|
||||||
|
case CameraFrameStage::PostScenePasses:
|
||||||
|
return ExecutePassSequenceStage(
|
||||||
|
executionState.postScenePasses,
|
||||||
|
request.GetPassSequence(stage),
|
||||||
|
request.context,
|
||||||
|
passContext);
|
||||||
|
case CameraFrameStage::OverlayPasses:
|
||||||
|
return ExecutePassSequenceStage(
|
||||||
|
executionState.overlayPasses,
|
||||||
|
request.GetPassSequence(stage),
|
||||||
|
request.context,
|
||||||
|
passContext);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RenderDirectionalShadowData BuildDirectionalShadowData(
|
RenderDirectionalShadowData BuildDirectionalShadowData(
|
||||||
const DirectionalShadowRenderPlan& plan,
|
const DirectionalShadowRenderPlan& plan,
|
||||||
RHI::RHIResourceView* shadowMapView) {
|
RHI::RHIResourceView* shadowMapView) {
|
||||||
@@ -352,49 +423,26 @@ bool CameraRenderer::ExecuteRenderPlan(
|
|||||||
request.surface,
|
request.surface,
|
||||||
sceneData
|
sceneData
|
||||||
};
|
};
|
||||||
|
CameraFrameExecutionState executionState = {};
|
||||||
|
executionState.pipeline = m_pipeline.get();
|
||||||
|
executionState.objectIdPass = m_objectIdPass.get();
|
||||||
|
executionState.depthOnlyPass = m_depthOnlyPass.get();
|
||||||
|
executionState.shadowCasterPass = m_shadowCasterPass.get();
|
||||||
|
|
||||||
ScopedInitializedPassSequence preScenePasses(request.preScenePasses, request.context);
|
for (const CameraFrameStageInfo& stageInfo : kOrderedCameraFrameStages) {
|
||||||
if (!preScenePasses.IsReady() || !preScenePasses.Execute(passContext)) {
|
if (!request.HasFrameStage(stageInfo.stage) &&
|
||||||
return false;
|
stageInfo.stage != CameraFrameStage::MainScene) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ExecuteScenePassRequest(
|
if (!ExecuteFrameStage(
|
||||||
m_shadowCasterPass.get(),
|
stageInfo.stage,
|
||||||
resolvedShadowCaster,
|
request,
|
||||||
request.context,
|
resolvedShadowCaster,
|
||||||
sceneData)) {
|
passContext,
|
||||||
return false;
|
executionState)) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
if (!ExecuteScenePassRequest(
|
|
||||||
m_depthOnlyPass.get(),
|
|
||||||
request.depthOnly,
|
|
||||||
request.context,
|
|
||||||
sceneData)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_pipeline->Render(request.context, request.surface, sceneData)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.objectId.IsRequested() &&
|
|
||||||
!ExecuteStandalonePass(
|
|
||||||
m_objectIdPass.get(),
|
|
||||||
request.context,
|
|
||||||
request.objectId.surface,
|
|
||||||
sceneData)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedInitializedPassSequence postScenePasses(request.postScenePasses, request.context);
|
|
||||||
if (!postScenePasses.IsReady() || !postScenePasses.Execute(passContext)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScopedInitializedPassSequence overlayPasses(request.overlayPasses, request.context);
|
|
||||||
if (!overlayPasses.IsReady() || !overlayPasses.Execute(passContext)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -453,6 +453,58 @@ RenderContext CreateValidContext() {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) {
|
||||||
|
CameraRenderRequest request;
|
||||||
|
|
||||||
|
RenderPassSequence prePasses;
|
||||||
|
RenderPassSequence postPasses;
|
||||||
|
RenderPassSequence overlayPasses;
|
||||||
|
request.preScenePasses = &prePasses;
|
||||||
|
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.objectId.surface = RenderSurface(320, 180);
|
||||||
|
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
|
||||||
|
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
|
||||||
|
|
||||||
|
ASSERT_EQ(kOrderedCameraFrameStages.size(), 7u);
|
||||||
|
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::ObjectId);
|
||||||
|
EXPECT_EQ(kOrderedCameraFrameStages[5].stage, CameraFrameStage::PostScenePasses);
|
||||||
|
EXPECT_EQ(kOrderedCameraFrameStages[6].stage, CameraFrameStage::OverlayPasses);
|
||||||
|
EXPECT_STREQ(GetCameraFrameStageName(CameraFrameStage::MainScene), "MainScene");
|
||||||
|
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::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::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);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) {
|
TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) {
|
||||||
Scene scene("CameraRendererScene");
|
Scene scene("CameraRendererScene");
|
||||||
|
|
||||||
@@ -608,6 +660,72 @@ TEST(CameraRenderer_Test, ExecutesObjectIdPassBetweenPipelineAndPostPassesWhenRe
|
|||||||
"shutdown:pre" }));
|
"shutdown:pre" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 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.cameraDepth = camera->GetDepth();
|
||||||
|
request.preScenePasses = &prePasses;
|
||||||
|
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.objectId.surface = RenderSurface(320, 180);
|
||||||
|
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(3));
|
||||||
|
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(4));
|
||||||
|
|
||||||
|
ASSERT_TRUE(renderer.Render(request));
|
||||||
|
EXPECT_EQ(
|
||||||
|
state->eventLog,
|
||||||
|
(std::vector<std::string>{
|
||||||
|
"init:pre",
|
||||||
|
"pre",
|
||||||
|
"init:shadowCaster",
|
||||||
|
"shadowCaster",
|
||||||
|
"init:depthOnly",
|
||||||
|
"depthOnly",
|
||||||
|
"pipeline",
|
||||||
|
"objectId",
|
||||||
|
"init:post",
|
||||||
|
"post",
|
||||||
|
"init:overlay",
|
||||||
|
"overlay",
|
||||||
|
"shutdown:overlay",
|
||||||
|
"shutdown:post",
|
||||||
|
"shutdown:pre" }));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipeline) {
|
TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipeline) {
|
||||||
Scene scene("CameraRendererDepthAndShadowScene");
|
Scene scene("CameraRendererDepthAndShadowScene");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user