From 02779dbb4e5c47e7927896a862c32798c42a12fa Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Thu, 9 Apr 2026 22:20:42 +0800 Subject: [PATCH] Formalize fullscreen render request contract --- .../Rendering/Planning/CameraRenderRequest.h | 35 ++- .../unit/test_camera_scene_renderer.cpp | 231 ++++++++++++++++++ 2 files changed, 264 insertions(+), 2 deletions(-) diff --git a/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h b/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h index 3f105bf4..2ed8195b 100644 --- a/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h +++ b/engine/include/XCEngine/Rendering/Planning/CameraRenderRequest.h @@ -109,6 +109,20 @@ inline bool HasValidColorTarget(const RenderSurface& surface) { surface.GetRenderAreaHeight() > 0; } +inline bool HasValidSurfaceSampleDescription(const RenderSurface& surface) { + const uint32_t sampleCount = surface.GetSampleCount(); + const uint32_t sampleQuality = surface.GetSampleQuality(); + return sampleCount > 0u && + (sampleCount > 1u || sampleQuality == 0u); +} + +inline bool HasValidSingleSampleColorSource(const RenderSurface& surface) { + return HasValidColorTarget(surface) && + HasValidSurfaceSampleDescription(surface) && + surface.GetSampleCount() == 1u && + surface.GetSampleQuality() == 0u; +} + struct DirectionalShadowRenderPlan { bool enabled = false; Math::Vector3 lightDirection = Math::Vector3::Back(); @@ -149,6 +163,7 @@ struct ObjectIdRenderRequest { struct FullscreenPassRenderRequest { RenderSurface sourceSurface; RHI::RHIResourceView* sourceColorView = nullptr; + RHI::ResourceStates sourceColorState = RHI::ResourceStates::Common; RenderSurface destinationSurface; RenderPassSequence* passes = nullptr; @@ -165,10 +180,15 @@ struct FullscreenPassRenderRequest { } bool IsValid() const { + const bool sourceStateIsUsable = + sourceSurface.IsAutoTransitionEnabled() || + sourceColorState == RHI::ResourceStates::PixelShaderResource; return passes != nullptr && - HasValidColorTarget(sourceSurface) && + HasValidSingleSampleColorSource(sourceSurface) && sourceColorView != nullptr && - HasValidColorTarget(destinationSurface); + sourceStateIsUsable && + HasValidColorTarget(destinationSurface) && + HasValidSurfaceSampleDescription(destinationSurface); } }; @@ -322,6 +342,17 @@ struct CameraRenderRequest { } } + RHI::ResourceStates GetSourceColorState(CameraFrameStage stage) const { + switch (stage) { + case CameraFrameStage::PostProcess: + return postProcess.IsRequested() ? postProcess.sourceColorState : RHI::ResourceStates::Common; + case CameraFrameStage::FinalOutput: + return finalOutput.IsRequested() ? finalOutput.sourceColorState : RHI::ResourceStates::Common; + default: + return RHI::ResourceStates::Common; + } + } + bool RequiresIntermediateSceneColor() const { return postProcess.IsRequested() || finalOutput.IsRequested(); } diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 31b8b84c..692140e9 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -429,6 +429,7 @@ public: lastSourceSurfaceHeight = context.sourceSurface->GetRenderAreaHeight(); } lastSourceColorView = context.sourceColorView; + lastSourceColorState = context.sourceColorState; return m_executeResult; } @@ -447,6 +448,7 @@ public: 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 m_state; @@ -487,6 +489,7 @@ public: lastSourceSurfaceHeight = context.sourceSurface->GetRenderAreaHeight(); } lastSourceColorView = context.sourceColorView; + lastSourceColorState = context.sourceColorState; return m_executeResult; } @@ -507,6 +510,7 @@ public: uint32_t lastSourceSurfaceWidth = 0; uint32_t lastSourceSurfaceHeight = 0; XCEngine::RHI::RHIResourceView* lastSourceColorView = nullptr; + XCEngine::RHI::ResourceStates lastSourceColorState = XCEngine::RHI::ResourceStates::Common; }; RenderContext CreateValidContext() { @@ -545,12 +549,14 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) { request.postProcess.sourceSurface = RenderSurface(256, 128); request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast(2)); request.postProcess.sourceColorView = reinterpret_cast(20); + request.postProcess.sourceColorState = XCEngine::RHI::ResourceStates::PixelShaderResource; request.postProcess.destinationSurface = RenderSurface(512, 256); request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast(3)); request.finalOutput.sourceSurface = RenderSurface(512, 256); request.finalOutput.sourceSurface.SetColorAttachment(reinterpret_cast(4)); request.finalOutput.sourceColorView = reinterpret_cast(40); + request.finalOutput.sourceColorState = XCEngine::RHI::ResourceStates::PixelShaderResource; request.finalOutput.destinationSurface = RenderSurface(640, 360); request.finalOutput.destinationSurface.SetColorAttachment(reinterpret_cast(5)); @@ -605,11 +611,54 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) { EXPECT_EQ( request.GetSourceColorView(CameraFrameStage::PostProcess), reinterpret_cast(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(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(1)); + request.sourceSurface.SetAutoTransitionEnabled(false); + request.sourceColorView = reinterpret_cast(2); + request.sourceColorState = XCEngine::RHI::ResourceStates::RenderTarget; + request.destinationSurface = RenderSurface(512, 256); + request.destinationSurface.SetColorAttachment(reinterpret_cast(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(1)); + request.sourceColorView = reinterpret_cast(2); + request.sourceColorState = XCEngine::RHI::ResourceStates::RenderTarget; + request.destinationSurface = RenderSurface(512, 256); + request.destinationSurface.SetColorAttachment(reinterpret_cast(3)); + request.passes = &passes; + + EXPECT_TRUE(request.IsValid()); + + request.sourceSurface.SetSampleDesc(4u, 0u); + EXPECT_FALSE(request.IsValid()); } TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) { @@ -1430,6 +1479,78 @@ TEST(CameraRenderer_Test, RecreatesDirectionalShadowSurfaceWhenPlanSizeChanges) EXPECT_EQ(allocationState->destroyTextureCalls, 2); } +TEST(CameraRenderer_Test, EnablesDirectionalShadowSurfaceAfterInitialFrameWithoutShadows) { + Scene scene("CameraRendererDirectionalShadowToggleScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(2.0f); + + auto pipelineState = std::make_shared(); + auto allocationState = std::make_shared(); + MockShadowDevice device(allocationState); + + RenderContext context = CreateValidContext(); + context.device = &device; + + { + CameraRenderer renderer( + std::make_unique(pipelineState), + std::make_unique(pipelineState)); + + auto shadowPass = std::make_unique(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{ "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{ + "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"); @@ -1823,6 +1944,8 @@ TEST(SceneRenderer_Test, BuildsCameraColorScalePostProcessRequestFromCameraPassS 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 requests = @@ -1836,7 +1959,19 @@ TEST(SceneRenderer_Test, BuildsCameraColorScalePostProcessRequestFromCameraPassS 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(); @@ -1846,6 +1981,9 @@ TEST(SceneRenderer_Test, BuildsCameraColorScalePostProcessRequestFromCameraPassS 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); @@ -1941,6 +2079,8 @@ TEST(SceneRenderer_Test, BuildsFinalOutputRequestFromResolvedFinalColorPolicy) { 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(assetState)); const std::vector requests = @@ -1955,11 +2095,26 @@ TEST(SceneRenderer_Test, BuildsFinalOutputRequestFromResolvedFinalColorPolicy) { 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); @@ -2010,6 +2165,8 @@ TEST(SceneRenderer_Test, RoutesPostProcessIntoIntermediateSurfaceBeforeFinalOutp 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 requests = @@ -2037,9 +2194,21 @@ TEST(SceneRenderer_Test, RoutesPostProcessIntoIntermediateSurfaceBeforeFinalOutp 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); @@ -2055,6 +2224,12 @@ TEST(SceneRenderer_Test, RoutesPostProcessIntoIntermediateSurfaceBeforeFinalOutp 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); @@ -2063,6 +2238,62 @@ TEST(SceneRenderer_Test, RoutesPostProcessIntoIntermediateSurfaceBeforeFinalOutp delete backBufferColorView; } +TEST(SceneRenderer_Test, DoesNotBuildFullscreenStagesForMultisampledMainSceneSurface) { + Scene scene("SceneRendererMultisampledFullscreenStageScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + 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(); + 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 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");