Graph-ify camera stage render passes

This commit is contained in:
2026-04-14 17:16:08 +08:00
parent 4fe456c1a2
commit 4c79554050
14 changed files with 893 additions and 19 deletions

View File

@@ -16,6 +16,8 @@
#include <XCEngine/Resources/Shader/ShaderKeywordTypes.h>
#include <XCEngine/Scene/Scene.h>
#include "Rendering/Internal/RenderPassGraphUtils.h"
#include <memory>
#include <string>
#include <vector>
@@ -591,15 +593,33 @@ class MockObjectIdPass final : public RenderPass {
public:
MockObjectIdPass(
std::shared_ptr<MockPipelineState> state,
bool renderResult = true)
bool renderResult = true,
bool supportsRenderGraph = false)
: m_state(std::move(state))
, m_renderResult(renderResult) {
, m_renderResult(renderResult)
, m_supportsRenderGraph(supportsRenderGraph) {
}
const char* GetName() const override {
return "MockObjectIdPass";
}
bool SupportsRenderGraph() const override {
return m_supportsRenderGraph;
}
bool RecordRenderGraph(const RenderPassRenderGraphContext& context) override {
m_state->eventLog.push_back("record:objectId");
return Internal::RecordRasterRenderPass(
*this,
context,
{
false,
true,
true
});
}
bool Execute(const RenderPassContext& context) override {
m_state->eventLog.push_back("objectId");
lastSurfaceAutoTransitionEnabled = context.surface.IsAutoTransitionEnabled();
@@ -615,6 +635,7 @@ public:
private:
std::shared_ptr<MockPipelineState> m_state;
bool m_renderResult = true;
bool m_supportsRenderGraph = false;
public:
bool lastSurfaceAutoTransitionEnabled = true;
@@ -628,17 +649,35 @@ public:
std::shared_ptr<MockPipelineState> state,
const char* label,
bool initializeResult = true,
bool executeResult = true)
bool executeResult = true,
bool supportsRenderGraph = false)
: m_state(std::move(state))
, m_label(label)
, m_initializeResult(initializeResult)
, m_executeResult(executeResult) {
, m_executeResult(executeResult)
, m_supportsRenderGraph(supportsRenderGraph) {
}
const char* GetName() const override {
return m_label;
}
bool SupportsRenderGraph() const override {
return m_supportsRenderGraph;
}
bool RecordRenderGraph(const RenderPassRenderGraphContext& context) override {
m_state->eventLog.push_back(std::string("record:") + m_label);
return Internal::RecordRasterRenderPass(
*this,
context,
{
false,
true,
true
});
}
bool Initialize(const RenderContext&) override {
m_state->eventLog.push_back(std::string("init:") + m_label);
return m_initializeResult;
@@ -690,6 +729,7 @@ private:
const char* m_label = "";
bool m_initializeResult = true;
bool m_executeResult = true;
bool m_supportsRenderGraph = false;
};
class TrackingPass final : public RenderPass {
@@ -698,17 +738,38 @@ public:
std::shared_ptr<MockPipelineState> state,
const char* label,
bool initializeResult = true,
bool executeResult = true)
bool executeResult = true,
bool supportsRenderGraph = false)
: m_state(std::move(state))
, m_label(label)
, m_initializeResult(initializeResult)
, m_executeResult(executeResult) {
, m_executeResult(executeResult)
, m_supportsRenderGraph(supportsRenderGraph) {
}
const char* GetName() const override {
return m_label;
}
bool SupportsRenderGraph() const override {
return m_supportsRenderGraph;
}
bool RecordRenderGraph(const RenderPassRenderGraphContext& context) override {
m_state->eventLog.push_back(std::string("record:") + m_label);
const bool writesColor =
context.sourceSurface != nullptr ||
!context.colorTargets.empty();
return Internal::RecordRasterRenderPass(
*this,
context,
{
context.sourceSurface != nullptr,
writesColor,
context.depthTarget.IsValid()
});
}
bool Initialize(const RenderContext&) override {
m_state->eventLog.push_back(std::string("init:") + m_label);
return m_initializeResult;
@@ -737,6 +798,7 @@ private:
const char* m_label = "";
bool m_initializeResult = true;
bool m_executeResult = true;
bool m_supportsRenderGraph = false;
public:
uint32_t lastSurfaceWidth = 0;
@@ -1403,6 +1465,133 @@ TEST(CameraRenderer_Test, KeepsPostProcessAndFinalOutputScratchSurfacesIndepende
delete sourceColorAttachment;
}
TEST(CameraRenderer_Test, RecordsGraphCapableFullscreenSequencePassesInStageOrder) {
Scene scene("CameraRendererGraphFullscreenSequences");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetDepth(5.0f);
auto pipelineState = std::make_shared<MockPipelineState>();
auto allocationState = std::make_shared<MockShadowAllocationState>();
MockShadowDevice device(allocationState);
CameraRenderer renderer(std::make_unique<MockPipeline>(pipelineState));
auto postFirstPass = std::make_unique<TrackingPass>(pipelineState, "postA", true, true, true);
auto postSecondPass = std::make_unique<TrackingPass>(pipelineState, "postB", true, true, true);
TrackingPass* postSecondPassRaw = postSecondPass.get();
RenderPassSequence postProcessPasses;
postProcessPasses.AddPass(std::move(postFirstPass));
postProcessPasses.AddPass(std::move(postSecondPass));
auto finalFirstPass = std::make_unique<TrackingPass>(pipelineState, "finalA", true, true, true);
TrackingPass* finalFirstPassRaw = finalFirstPass.get();
auto finalSecondPass = std::make_unique<TrackingPass>(pipelineState, "finalB", true, true, true);
TrackingPass* finalSecondPassRaw = finalSecondPass.get();
RenderPassSequence finalOutputPasses;
finalOutputPasses.AddPass(std::move(finalFirstPass));
finalOutputPasses.AddPass(std::move(finalSecondPass));
auto* sourceColorAttachment = new MockShadowView(
allocationState,
XCEngine::RHI::ResourceViewType::RenderTarget,
XCEngine::RHI::Format::R8G8B8A8_UNorm,
XCEngine::RHI::ResourceViewDimension::Texture2D);
auto* sourceColorShaderView = new MockShadowView(
allocationState,
XCEngine::RHI::ResourceViewType::ShaderResource,
XCEngine::RHI::Format::R8G8B8A8_UNorm,
XCEngine::RHI::ResourceViewDimension::Texture2D);
auto* postProcessDestination = new MockShadowView(
allocationState,
XCEngine::RHI::ResourceViewType::RenderTarget,
XCEngine::RHI::Format::R8G8B8A8_UNorm,
XCEngine::RHI::ResourceViewDimension::Texture2D);
auto* finalOutputSource = new MockShadowView(
allocationState,
XCEngine::RHI::ResourceViewType::ShaderResource,
XCEngine::RHI::Format::R8G8B8A8_UNorm,
XCEngine::RHI::ResourceViewDimension::Texture2D);
auto* finalOutputDestination = new MockShadowView(
allocationState,
XCEngine::RHI::ResourceViewType::RenderTarget,
XCEngine::RHI::Format::R8G8B8A8_UNorm,
XCEngine::RHI::ResourceViewDimension::Texture2D);
RenderContext context = CreateValidContext();
context.device = &device;
CameraRenderRequest request;
request.scene = &scene;
request.camera = camera;
request.context = context;
request.surface = RenderSurface(800, 600);
request.surface.SetColorAttachment(finalOutputDestination);
request.cameraDepth = camera->GetDepth();
request.postProcess.sourceSurface = RenderSurface(256, 128);
request.postProcess.sourceSurface.SetColorAttachment(sourceColorAttachment);
request.postProcess.sourceColorView = sourceColorShaderView;
request.postProcess.destinationSurface = RenderSurface(512, 256);
request.postProcess.destinationSurface.SetColorAttachment(postProcessDestination);
request.postProcess.passes = &postProcessPasses;
request.finalOutput.sourceSurface = request.postProcess.destinationSurface;
request.finalOutput.sourceColorView = finalOutputSource;
request.finalOutput.destinationSurface = RenderSurface(800, 600);
request.finalOutput.destinationSurface.SetColorAttachment(finalOutputDestination);
request.finalOutput.passes = &finalOutputPasses;
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
ASSERT_NE(postSecondPassRaw, nullptr);
EXPECT_TRUE(postSecondPassRaw->lastHasSourceSurface);
EXPECT_EQ(postSecondPassRaw->lastSourceSurfaceWidth, 512u);
EXPECT_EQ(postSecondPassRaw->lastSourceSurfaceHeight, 256u);
EXPECT_EQ(
postSecondPassRaw->lastSourceColorState,
XCEngine::RHI::ResourceStates::PixelShaderResource);
ASSERT_NE(finalFirstPassRaw, nullptr);
EXPECT_TRUE(finalFirstPassRaw->lastHasSourceSurface);
EXPECT_EQ(finalFirstPassRaw->lastSourceSurfaceWidth, 512u);
EXPECT_EQ(finalFirstPassRaw->lastSourceSurfaceHeight, 256u);
EXPECT_EQ(finalFirstPassRaw->lastSourceColorView, finalOutputSource);
EXPECT_EQ(
finalFirstPassRaw->lastSourceColorState,
XCEngine::RHI::ResourceStates::PixelShaderResource);
ASSERT_NE(finalSecondPassRaw, nullptr);
EXPECT_TRUE(finalSecondPassRaw->lastHasSourceSurface);
EXPECT_EQ(finalSecondPassRaw->lastSourceSurfaceWidth, 800u);
EXPECT_EQ(finalSecondPassRaw->lastSourceSurfaceHeight, 600u);
EXPECT_NE(finalSecondPassRaw->lastSourceColorView, nullptr);
EXPECT_NE(finalSecondPassRaw->lastSourceColorView, finalOutputSource);
EXPECT_EQ(
finalSecondPassRaw->lastSourceColorState,
XCEngine::RHI::ResourceStates::PixelShaderResource);
EXPECT_EQ(
pipelineState->eventLog,
(std::vector<std::string>{
"record:postA",
"record:postB",
"record:finalA",
"record:finalB",
"pipeline",
"init:postA",
"init:postB",
"postA",
"postB",
"init:finalA",
"init:finalB",
"finalA",
"finalB" }));
delete finalOutputDestination;
delete finalOutputSource;
delete postProcessDestination;
delete sourceColorShaderView;
delete sourceColorAttachment;
}
TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) {
Scene scene("CameraRendererFormalFrameStageScene");
@@ -1551,6 +1740,78 @@ TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipe
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.a, 1.0f);
}
TEST(CameraRenderer_Test, RecordsGraphCapableStandalonePassStagesBeforeExecution) {
Scene scene("CameraRendererGraphStandaloneStages");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetDepth(3.0f);
auto state = std::make_shared<MockPipelineState>();
CameraRenderer renderer(
std::make_unique<MockPipeline>(state),
std::make_unique<MockObjectIdPass>(state, true, true));
auto shadowPass = std::make_unique<MockScenePass>(state, "shadowCaster", true, true, true);
MockScenePass* shadowPassRaw = shadowPass.get();
renderer.SetShadowCasterPass(std::move(shadowPass));
auto depthPass = std::make_unique<MockScenePass>(state, "depthOnly", true, true, true);
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.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
request.cameraDepth = camera->GetDepth();
request.shadowCaster.surface = RenderSurface(128, 64);
request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
request.shadowCaster.hasCameraDataOverride = true;
request.shadowCaster.cameraDataOverride.worldPosition = XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f);
request.depthOnly.surface = RenderSurface(96, 48);
request.depthOnly.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(4));
request.depthOnly.hasClearColorOverride = true;
request.depthOnly.clearColorOverride = XCEngine::Math::Color(0.3f, 0.2f, 0.1f, 1.0f);
request.objectId.surface = RenderSurface(320, 180);
request.objectId.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(6));
request.objectId.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(7));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(
state->eventLog,
(std::vector<std::string>{
"record:shadowCaster",
"record:depthOnly",
"record:objectId",
"init:shadowCaster",
"shadowCaster",
"init:depthOnly",
"depthOnly",
"pipeline",
"objectId" }));
ASSERT_NE(shadowPassRaw, nullptr);
EXPECT_EQ(shadowPassRaw->lastViewportWidth, 128u);
EXPECT_EQ(shadowPassRaw->lastViewportHeight, 64u);
EXPECT_FALSE(shadowPassRaw->lastSurfaceAutoTransitionEnabled);
EXPECT_EQ(shadowPassRaw->lastWorldPosition, XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f));
ASSERT_NE(depthPassRaw, nullptr);
EXPECT_EQ(depthPassRaw->lastViewportWidth, 96u);
EXPECT_EQ(depthPassRaw->lastViewportHeight, 48u);
EXPECT_FALSE(depthPassRaw->lastSurfaceAutoTransitionEnabled);
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.r, 0.3f);
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.g, 0.2f);
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.b, 0.1f);
EXPECT_FLOAT_EQ(depthPassRaw->lastClearColor.a, 1.0f);
}
TEST(CameraRenderer_Test, UsesGraphManagedSurfaceForMainSceneWhenOutputAttachmentsExist) {
Scene scene("CameraRendererGraphManagedMainScene");