Add render-graph main-scene pipeline recording

This commit is contained in:
2026-04-14 16:22:58 +08:00
parent 307259091e
commit c495581878
6 changed files with 1181 additions and 20 deletions

View File

@@ -8,6 +8,7 @@
#include <XCEngine/RHI/RHITexture.h>
#include <XCEngine/Core/Math/Vector4.h>
#include <XCEngine/Rendering/Execution/CameraRenderer.h>
#include <XCEngine/Rendering/Graph/RenderGraph.h>
#include <XCEngine/Rendering/RenderPipelineAsset.h>
#include <XCEngine/Rendering/RenderSurface.h>
#include <XCEngine/Rendering/Execution/SceneRenderer.h>
@@ -28,7 +29,11 @@ struct MockPipelineState {
int initializeCalls = 0;
int shutdownCalls = 0;
int renderCalls = 0;
int recordMainSceneCalls = 0;
int executeRecordedMainSceneCalls = 0;
bool renderResult = true;
bool supportsMainSceneRenderGraph = false;
bool recordMainSceneResult = true;
bool lastSurfaceAutoTransitionEnabled = true;
uint32_t lastSurfaceWidth = 0;
uint32_t lastSurfaceHeight = 0;
@@ -51,6 +56,7 @@ struct MockPipelineState {
RenderEnvironmentMode lastEnvironmentMode = RenderEnvironmentMode::None;
bool lastHasSkybox = false;
const XCEngine::Resources::Material* lastSkyboxMaterial = nullptr;
bool lastRecordedMainSceneShadowHandleValid = false;
std::vector<CameraComponent*> renderedCameras;
std::vector<RenderClearFlags> renderedClearFlags;
std::vector<XCEngine::Math::Color> renderedClearColors;
@@ -399,6 +405,107 @@ public:
++m_state->shutdownCalls;
}
bool SupportsMainSceneRenderGraph() const override {
return m_state->supportsMainSceneRenderGraph;
}
bool RecordMainSceneRenderGraph(
const RenderPipelineMainSceneRenderGraphContext& context) override {
++m_state->recordMainSceneCalls;
m_state->lastRecordedMainSceneShadowHandleValid =
context.mainDirectionalShadowTexture.IsValid();
if (!m_state->recordMainSceneResult) {
return false;
}
const RenderSceneData* const sceneData = &context.sceneData;
RenderSurface surface = context.surfaceTemplate;
surface.SetAutoTransitionEnabled(false);
const std::vector<RenderGraphTextureHandle> colorTargets = context.colorTargets;
const RenderGraphTextureHandle depthTarget = context.depthTarget;
const RenderGraphTextureHandle mainDirectionalShadowTexture =
context.mainDirectionalShadowTexture;
bool* const executionSucceeded = context.executionSucceeded;
const std::shared_ptr<MockPipelineState> state = m_state;
context.graphBuilder.AddRasterPass(
context.passName,
[state,
surface,
sceneData,
colorTargets,
depthTarget,
mainDirectionalShadowTexture,
executionSucceeded](
RenderGraphPassBuilder& passBuilder) {
for (RenderGraphTextureHandle colorTarget : colorTargets) {
if (colorTarget.IsValid()) {
passBuilder.WriteTexture(colorTarget);
}
}
if (depthTarget.IsValid()) {
passBuilder.WriteDepthTexture(depthTarget);
}
if (mainDirectionalShadowTexture.IsValid()) {
passBuilder.ReadTexture(mainDirectionalShadowTexture);
}
passBuilder.SetExecuteCallback(
[state,
surface,
sceneData,
executionSucceeded](
const RenderGraphExecutionContext&) {
if (executionSucceeded != nullptr && !(*executionSucceeded)) {
return;
}
state->eventLog.push_back("pipelineGraph");
++state->executeRecordedMainSceneCalls;
state->lastSurfaceAutoTransitionEnabled =
surface.IsAutoTransitionEnabled();
state->lastSurfaceWidth = surface.GetWidth();
state->lastSurfaceHeight = surface.GetHeight();
const XCEngine::Math::RectInt renderArea = surface.GetRenderArea();
state->lastRenderAreaX = renderArea.x;
state->lastRenderAreaY = renderArea.y;
state->lastRenderAreaWidth = renderArea.width;
state->lastRenderAreaHeight = renderArea.height;
state->lastCamera = sceneData->camera;
state->lastCameraViewportWidth = sceneData->cameraData.viewportWidth;
state->lastCameraViewportHeight = sceneData->cameraData.viewportHeight;
state->lastVisibleItemCount = sceneData->visibleItems.size();
state->lastClearFlags = sceneData->cameraData.clearFlags;
state->lastClearColor = sceneData->cameraData.clearColor;
state->lastHasMainDirectionalShadow =
sceneData->lighting.HasMainDirectionalShadow();
state->lastShadowMap = sceneData->lighting.mainDirectionalShadow.shadowMap;
state->lastShadowViewProjection =
sceneData->lighting.mainDirectionalShadow.viewProjection;
state->lastShadowMapMetrics =
sceneData->lighting.mainDirectionalShadow.mapMetrics;
state->lastShadowSampling =
sceneData->lighting.mainDirectionalShadow.sampling;
state->lastHasMainDirectionalShadowKeyword =
XCEngine::Resources::ShaderKeywordSetContains(
sceneData->globalShaderKeywords,
"XC_MAIN_LIGHT_SHADOWS");
state->lastEnvironmentMode = sceneData->environment.mode;
state->lastHasSkybox = sceneData->environment.HasSkybox();
state->lastSkyboxMaterial =
sceneData->environment.materialSkybox.material;
state->renderedCameras.push_back(sceneData->camera);
state->renderedClearFlags.push_back(sceneData->cameraData.clearFlags);
state->renderedClearColors.push_back(sceneData->cameraData.clearColor);
if (executionSucceeded != nullptr) {
*executionSucceeded = state->renderResult;
}
});
});
return true;
}
bool Render(
const RenderContext&,
const RenderSurface& surface,
@@ -1452,6 +1559,37 @@ TEST(CameraRenderer_Test, UsesGraphManagedSurfaceForMainSceneWhenOutputAttachmen
EXPECT_EQ(state->lastSurfaceHeight, 180u);
}
TEST(CameraRenderer_Test, UsesPipelineRecordedMainSceneWhenSupported) {
Scene scene("CameraRendererPipelineRecordedMainScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetDepth(3.0f);
auto state = std::make_shared<MockPipelineState>();
state->supportsMainSceneRenderGraph = true;
CameraRenderer renderer(std::make_unique<MockPipeline>(state));
CameraRenderRequest request;
request.scene = &scene;
request.camera = camera;
request.context = CreateValidContext();
request.surface = RenderSurface(320, 180);
request.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
request.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
request.cameraDepth = camera->GetDepth();
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(state->recordMainSceneCalls, 1);
EXPECT_EQ(state->executeRecordedMainSceneCalls, 1);
EXPECT_EQ(state->renderCalls, 0);
EXPECT_EQ(state->eventLog, (std::vector<std::string>{ "pipelineGraph" }));
EXPECT_FALSE(state->lastSurfaceAutoTransitionEnabled);
EXPECT_EQ(state->lastSurfaceWidth, 320u);
EXPECT_EQ(state->lastSurfaceHeight, 180u);
}
TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) {
Scene scene("CameraRendererAutoDirectionalShadowScene");
@@ -1540,6 +1678,59 @@ TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) {
EXPECT_EQ(allocationState->destroyTextureCalls, 1);
}
TEST(CameraRenderer_Test, PassesShadowDependencyToPipelineRecordedMainScene) {
Scene scene("CameraRendererPipelineRecordedShadowDependency");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
camera->SetPrimary(true);
camera->SetDepth(2.0f);
auto pipelineState = std::make_shared<MockPipelineState>();
pipelineState->supportsMainSceneRenderGraph = true;
auto allocationState = std::make_shared<MockShadowAllocationState>();
MockShadowDevice device(allocationState);
RenderContext context = CreateValidContext();
context.device = &device;
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.surface.SetColorAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(1));
request.surface.SetDepthAttachment(reinterpret_cast<XCEngine::RHI::RHIResourceView*>(2));
request.cameraDepth = camera->GetDepth();
request.directionalShadow.enabled = true;
request.directionalShadow.mapWidth = 256;
request.directionalShadow.mapHeight = 128;
request.directionalShadow.cameraData.viewportWidth = 256;
request.directionalShadow.cameraData.viewportHeight = 128;
request.directionalShadow.cameraData.clearFlags = RenderClearFlags::Depth;
request.directionalShadow.cameraData.worldPosition = XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f);
request.directionalShadow.cameraData.viewProjection =
XCEngine::Math::Matrix4x4::Translation(XCEngine::Math::Vector3(11.0f, 12.0f, 13.0f));
ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request)));
EXPECT_EQ(pipelineState->recordMainSceneCalls, 1);
EXPECT_EQ(pipelineState->executeRecordedMainSceneCalls, 1);
EXPECT_TRUE(pipelineState->lastRecordedMainSceneShadowHandleValid);
EXPECT_EQ(
pipelineState->eventLog,
(std::vector<std::string>{
"init:shadowCaster",
"shadowCaster",
"pipelineGraph" }));
}
TEST(CameraRenderer_Test, ReusesDirectionalShadowSurfaceWhenPlanMatches) {
Scene scene("CameraRendererDirectionalShadowReuseScene");