diff --git a/engine/include/XCEngine/Rendering/AGENTS.md b/engine/include/XCEngine/Rendering/AGENTS.md index 8191b75d..b8a02742 100644 --- a/engine/include/XCEngine/Rendering/AGENTS.md +++ b/engine/include/XCEngine/Rendering/AGENTS.md @@ -54,6 +54,10 @@ Unity 兼容的公开命名、对象所有权和扩展点。 - 行为变更要从 pipeline asset/request/plan 路径开始。app/editor 代码中的临时 pass execution 通常表示层级位置不对。 +- 顶层 native renderer 的公开选择和配置入口只能是 `RenderPipelineAsset`。不要新增或恢复 + `CameraRenderer`、`RenderPipelineHost` 或 `SceneRenderer` 上接受裸 `RenderPipeline` 的 public + constructor/setter;需要定制 pipeline 实例时,通过 `RenderPipelineAsset::CreatePipeline` 和 + `RenderPipelineAsset::ConfigurePipeline` 完成。 - Native headers 保持小而后端无关。可行时 forward declare RHI 类型,只在后端代码里 include 具体后端头。 - 新增 camera-stack、renderer-index、shadow、depth、post-process 或 final output 行为时,不要绕过 @@ -123,14 +127,15 @@ Unity 兼容的公开命名、对象所有权和扩展点。 Native renderer 现在是 camera frame planner 加 RenderGraph executor,managed SRP/URP recording 叠在 native scene draw backend 之上。 -- `SceneRenderer` 是 scene-level convenience entry point。它通过 `SceneRenderRequestPlanner` 收集 - camera requests,通过 `RenderPipelineHost` 构建 frame plans,然后渲染排序后的 camera plans。 +- `SceneRenderer` 是 scene-level convenience entry point。它的公开管线选择根是 `RenderPipelineAsset`; + 它通过 `SceneRenderRequestPlanner` 收集 camera requests,通过 `RenderPipelineHost` 构建 frame plans, + 然后渲染排序后的 camera plans。 - `RenderPipelineHost` 拥有一个 `CameraFramePlanBuilder` 和一个 `CameraRenderer`。它是从 - `CameraRenderRequest` 到 `CameraFramePlan` 的常规 native bridge。 -- `CameraRenderer` 拥有所选 `RenderPipeline`,提取 `RenderSceneData`,解析 directional shadow execution, - 把全部 frame stages 记录进 native `RenderGraph`,编译 graph 并执行。 -- `RenderPipelineAsset` 是 native asset contract。它创建 pipeline,配置 request policy,配置 frame plan - policy,并提供默认 final color settings。 + `CameraRenderRequest` 到 `CameraFramePlan` 的常规 native bridge,并沿用同一个 asset selection root。 +- `CameraRenderer` 拥有 asset 创建出的所选 `RenderPipeline`,提取 `RenderSceneData`,解析 directional + shadow execution,把全部 frame stages 记录进 native `RenderGraph`,编译 graph 并执行。 +- `RenderPipelineAsset` 是 native asset contract。它创建 pipeline,配置 pipeline 实例,配置 request + policy,配置 frame plan policy,并提供默认 final color settings。 - `RenderPipeline`、`RenderPipelineBackend` 和 `RenderPipelineStageRecorder` 将 backend rendering 与 graph recording 分开。Managed SRP 通常提供 stage recorder,native backend 提供 scene drawing。 - Native/managed bridge 词汇已经收口为 `PipelineBackend` 和 `SceneDrawBackend`。不要重新引入 diff --git a/engine/include/XCEngine/Rendering/Execution/CameraRenderer.h b/engine/include/XCEngine/Rendering/Execution/CameraRenderer.h index 2e37a3a3..c5ebb4cb 100644 --- a/engine/include/XCEngine/Rendering/Execution/CameraRenderer.h +++ b/engine/include/XCEngine/Rendering/Execution/CameraRenderer.h @@ -28,11 +28,9 @@ struct RenderContext; class CameraRenderer { public: CameraRenderer(); - explicit CameraRenderer(std::unique_ptr pipeline); explicit CameraRenderer(std::shared_ptr pipelineAsset); ~CameraRenderer(); - void SetPipeline(std::unique_ptr pipeline); void SetPipelineAsset(std::shared_ptr pipelineAsset); RenderPipeline* GetPipeline() const; const RenderPipelineAsset* GetPipelineAsset() const; diff --git a/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h b/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h index 87f7bd29..fbeeb402 100644 --- a/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h +++ b/engine/include/XCEngine/Rendering/Execution/RenderPipelineHost.h @@ -13,11 +13,9 @@ class CameraFramePlanBuilder; class RenderPipelineHost { public: RenderPipelineHost(); - explicit RenderPipelineHost(std::unique_ptr pipeline); explicit RenderPipelineHost(std::shared_ptr pipelineAsset); ~RenderPipelineHost(); - void SetPipeline(std::unique_ptr pipeline); void SetPipelineAsset(std::shared_ptr pipelineAsset); RenderPipeline* GetPipeline() const { return m_cameraRenderer.GetPipeline(); } const RenderPipelineAsset* GetPipelineAsset() const { return m_cameraRenderer.GetPipelineAsset(); } diff --git a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h index 5d915c04..424b706f 100644 --- a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h +++ b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h @@ -16,11 +16,9 @@ namespace Rendering { class SceneRenderer { public: SceneRenderer(); - explicit SceneRenderer(std::unique_ptr pipeline); explicit SceneRenderer(std::shared_ptr pipelineAsset); ~SceneRenderer(); - void SetPipeline(std::unique_ptr pipeline); void SetPipelineAsset(std::shared_ptr pipelineAsset); RenderPipeline* GetPipeline() const { return m_pipelineHost.GetPipeline(); } const RenderPipelineAsset* GetPipelineAsset() const { return m_pipelineHost.GetPipelineAsset(); } diff --git a/engine/src/Rendering/Execution/CameraRenderer.cpp b/engine/src/Rendering/Execution/CameraRenderer.cpp index 21c72e48..e748b999 100644 --- a/engine/src/Rendering/Execution/CameraRenderer.cpp +++ b/engine/src/Rendering/Execution/CameraRenderer.cpp @@ -36,9 +36,11 @@ void ConfigureTopLevelToolingPasses(RenderPipeline& pipeline) { #if XCENGINE_ENABLE_RENDERING_EDITOR_SUPPORT // Object-id is a tooling/editor request. It should live on the // top-level pipeline used by CameraRenderer, not on a scene backend. - pipeline.SetCameraFrameStandalonePass( - CameraFrameStage::ObjectId, - std::make_unique()); + if (pipeline.GetCameraFrameStandalonePass(CameraFrameStage::ObjectId) == nullptr) { + pipeline.SetCameraFrameStandalonePass( + CameraFrameStage::ObjectId, + std::make_unique()); + } #else (void)pipeline; #endif @@ -60,12 +62,6 @@ CameraRenderer::CameraRenderer() SetPipelineAsset(nullptr); } -CameraRenderer::CameraRenderer(std::unique_ptr pipeline) - : m_pipelineAsset(nullptr) - , m_directionalShadowRuntime(std::make_unique()) { - ResetPipeline(std::move(pipeline)); -} - CameraRenderer::CameraRenderer(std::shared_ptr pipelineAsset) : m_directionalShadowRuntime(std::make_unique()) { SetPipelineAsset(std::move(pipelineAsset)); @@ -77,13 +73,6 @@ CameraRenderer::~CameraRenderer() { } } -void CameraRenderer::SetPipeline(std::unique_ptr pipeline) { - m_pipelineAsset.reset(); - m_usesDefaultPipelineAssetSelection = false; - m_managedPipelineEnvironmentGeneration = 0u; - ResetPipeline(std::move(pipeline)); -} - void CameraRenderer::SetPipelineAsset(std::shared_ptr pipelineAsset) { m_usesDefaultPipelineAssetSelection = pipelineAsset == nullptr; ResetPipeline( diff --git a/engine/src/Rendering/Execution/RenderPipelineHost.cpp b/engine/src/Rendering/Execution/RenderPipelineHost.cpp index 86bf5ff2..30816370 100644 --- a/engine/src/Rendering/Execution/RenderPipelineHost.cpp +++ b/engine/src/Rendering/Execution/RenderPipelineHost.cpp @@ -22,11 +22,6 @@ RenderPipelineHost::RenderPipelineHost() : m_framePlanBuilder(std::make_unique()) { } -RenderPipelineHost::RenderPipelineHost(std::unique_ptr pipeline) - : m_cameraRenderer(std::move(pipeline)) - , m_framePlanBuilder(std::make_unique()) { -} - RenderPipelineHost::RenderPipelineHost(std::shared_ptr pipelineAsset) : m_cameraRenderer(std::move(pipelineAsset)) , m_framePlanBuilder(std::make_unique()) { @@ -34,10 +29,6 @@ RenderPipelineHost::RenderPipelineHost(std::shared_ptr pipeline) { - m_cameraRenderer.SetPipeline(std::move(pipeline)); -} - void RenderPipelineHost::SetPipelineAsset(std::shared_ptr pipelineAsset) { m_cameraRenderer.SetPipelineAsset(std::move(pipelineAsset)); } diff --git a/engine/src/Rendering/Execution/SceneRenderer.cpp b/engine/src/Rendering/Execution/SceneRenderer.cpp index 998a27a8..a677281d 100644 --- a/engine/src/Rendering/Execution/SceneRenderer.cpp +++ b/engine/src/Rendering/Execution/SceneRenderer.cpp @@ -9,20 +9,12 @@ namespace Rendering { SceneRenderer::SceneRenderer() { } -SceneRenderer::SceneRenderer(std::unique_ptr pipeline) - : m_pipelineHost(std::move(pipeline)) { -} - SceneRenderer::SceneRenderer(std::shared_ptr pipelineAsset) : m_pipelineHost(std::move(pipelineAsset)) { } SceneRenderer::~SceneRenderer() = default; -void SceneRenderer::SetPipeline(std::unique_ptr pipeline) { - m_pipelineHost.SetPipeline(std::move(pipeline)); -} - void SceneRenderer::SetPipelineAsset(std::shared_ptr pipelineAsset) { m_pipelineHost.SetPipelineAsset(std::move(pipelineAsset)); } diff --git a/engine/src/Rendering/Internal/RenderPipelineFactory.cpp b/engine/src/Rendering/Internal/RenderPipelineFactory.cpp index f8834350..dfcadcac 100644 --- a/engine/src/Rendering/Internal/RenderPipelineFactory.cpp +++ b/engine/src/Rendering/Internal/RenderPipelineFactory.cpp @@ -48,7 +48,12 @@ std::unique_ptr TryCreateRenderPipelineFromAsset( return nullptr; } - return asset->CreatePipeline(); + std::unique_ptr pipeline = + asset->CreatePipeline(); + if (pipeline != nullptr) { + asset->ConfigurePipeline(*pipeline); + } + return pipeline; } std::unique_ptr TryCreatePipelineBackendFromAsset( @@ -57,7 +62,12 @@ std::unique_ptr TryCreatePipelineBackendFromAsset( return nullptr; } - return asset->CreatePipeline(); + std::unique_ptr pipeline = + asset->CreatePipeline(); + if (pipeline != nullptr) { + asset->ConfigurePipeline(*pipeline); + } + return pipeline; } std::unique_ptr TryCreateSceneDrawBackendFromAsset( @@ -128,10 +138,24 @@ std::unique_ptr CreateRenderPipelineOrDefault( return pipeline; } + const std::shared_ptr defaultAsset = + ResolveRenderPipelineAssetOrDefault(nullptr); + if (defaultAsset != nullptr && + defaultAsset != resolvedAsset) { + if (std::unique_ptr pipeline = + TryCreateRenderPipelineFromAsset(defaultAsset)) { + if (outResolvedAsset != nullptr) { + *outResolvedAsset = defaultAsset; + } + return pipeline; + } + } + const std::shared_ptr fallbackAsset = CreateFallbackRenderPipelineAsset(); if (fallbackAsset != nullptr && - fallbackAsset != resolvedAsset) { + fallbackAsset != resolvedAsset && + fallbackAsset != defaultAsset) { if (std::unique_ptr pipeline = TryCreateRenderPipelineFromAsset(fallbackAsset)) { if (outResolvedAsset != nullptr) { @@ -142,9 +166,10 @@ std::unique_ptr CreateRenderPipelineOrDefault( } if (outResolvedAsset != nullptr) { - *outResolvedAsset = - fallbackAsset != nullptr - ? fallbackAsset + *outResolvedAsset = fallbackAsset != nullptr + ? fallbackAsset + : defaultAsset != nullptr + ? defaultAsset : resolvedAsset; } return Pipelines::Internal::CreateConfiguredBuiltinForwardPipeline(); diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 7adfb7a0..5e6d2950 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -25,6 +25,7 @@ #include "Rendering/Execution/Internal/CameraFrameGraph/SurfaceResolver.h" #include "Rendering/Internal/RenderPipelineFactory.h" +#include #include #include #include @@ -238,6 +239,65 @@ private: std::unique_ptr m_ownedTexture; }; +XCEngine::RHI::RHIResourceView* MockResourceViewToken( + uintptr_t id, + XCEngine::RHI::ResourceViewType viewType, + XCEngine::RHI::Format format) { + static const std::shared_ptr s_state = + std::make_shared(); + static std::vector> s_renderTargets; + static std::vector> s_depthTargets; + static std::vector> s_shaderResources; + + std::vector>* slots = nullptr; + switch (viewType) { + case XCEngine::RHI::ResourceViewType::DepthStencil: + slots = &s_depthTargets; + break; + case XCEngine::RHI::ResourceViewType::ShaderResource: + slots = &s_shaderResources; + break; + case XCEngine::RHI::ResourceViewType::RenderTarget: + default: + slots = &s_renderTargets; + break; + } + + if (id >= slots->size()) { + slots->resize(id + 1u); + } + std::unique_ptr& view = (*slots)[id]; + if (view == nullptr) { + view = std::make_unique( + s_state, + viewType, + format, + XCEngine::RHI::ResourceViewDimension::Texture2D); + } + return view.get(); +} + +XCEngine::RHI::RHIResourceView* MockRenderTargetViewToken(uintptr_t id) { + return MockResourceViewToken( + id, + XCEngine::RHI::ResourceViewType::RenderTarget, + XCEngine::RHI::Format::R8G8B8A8_UNorm); +} + +XCEngine::RHI::RHIResourceView* MockDepthStencilViewToken(uintptr_t id) { + return MockResourceViewToken( + id, + XCEngine::RHI::ResourceViewType::DepthStencil, + XCEngine::RHI::Format::D24_UNorm_S8_UInt); +} + +XCEngine::RHI::RHIResourceView* MockShaderResourceViewToken(uintptr_t id) { + return MockResourceViewToken( + id, + XCEngine::RHI::ResourceViewType::ShaderResource, + XCEngine::RHI::Format::R8G8B8A8_UNorm); +} + class MockShadowDevice final : public XCEngine::RHI::RHIDevice { public: explicit MockShadowDevice(std::shared_ptr state) @@ -458,8 +518,11 @@ struct MockPipelineAssetState { int configureCameraRenderRequestCalls = 0; int configureCameraFramePlanCalls = 0; bool createNullPipeline = false; + std::shared_ptr pipelineState; std::shared_ptr lastCreatedPipelineState; + RenderPipeline* lastCreatedPipeline = nullptr; FinalColorSettings defaultFinalColorSettings = {}; + std::function configurePipeline = {}; std::functioncreateCalls; if (m_state->createNullPipeline) { m_state->lastCreatedPipelineState.reset(); + m_state->lastCreatedPipeline = nullptr; return nullptr; } - m_state->lastCreatedPipelineState = std::make_shared(); - return std::make_unique(m_state->lastCreatedPipelineState); + m_state->lastCreatedPipelineState = m_state->pipelineState != nullptr + ? m_state->pipelineState + : std::make_shared(); + std::unique_ptr pipeline = + std::make_unique(m_state->lastCreatedPipelineState); + m_state->lastCreatedPipeline = pipeline.get(); + return pipeline; + } + + void ConfigurePipeline(RenderPipeline& pipeline) const override { + if (m_state->configurePipeline) { + m_state->configurePipeline(pipeline); + } } FinalColorSettings GetDefaultFinalColorSettings() const override { @@ -1001,6 +1076,39 @@ public: uint32_t lastSurfaceHeight = 0; }; +std::shared_ptr MakeMockPipelineAssetState( + std::shared_ptr pipelineState, + std::function configurePipeline = {}) { + auto assetState = std::make_shared(); + assetState->pipelineState = std::move(pipelineState); + assetState->configurePipeline = std::move(configurePipeline); + return assetState; +} + +std::shared_ptr MakeMockPipelineAsset( + std::shared_ptr pipelineState, + std::function configurePipeline = {}) { + std::function configuredPipeline = + [pipelineState, + configurePipeline = std::move(configurePipeline)]( + RenderPipeline& pipeline) mutable { + if (configurePipeline) { + configurePipeline(pipeline); + } + if (pipeline.GetCameraFrameStandalonePass(CameraFrameStage::ObjectId) == nullptr) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + pipelineState); + } + }; + + return std::make_shared( + MakeMockPipelineAssetState( + std::move(pipelineState), + std::move(configuredPipeline))); +} + class MockScenePass final : public RenderPass { public: MockScenePass( @@ -1222,25 +1330,25 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) { request.directionalShadow.cameraData.viewportHeight = 64; request.depthOnly.surface = RenderSurface(96, 48); - request.depthOnly.surface.SetDepthAttachment(reinterpret_cast(1)); + request.depthOnly.surface.SetDepthAttachment(MockDepthStencilViewToken(1)); request.postProcess.sourceSurface = RenderSurface(256, 128); - request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast(2)); - request.postProcess.sourceColorView = reinterpret_cast(20); + request.postProcess.sourceSurface.SetColorAttachment(MockRenderTargetViewToken(2)); + request.postProcess.sourceColorView = MockShaderResourceViewToken(20); request.postProcess.sourceColorState = XCEngine::RHI::ResourceStates::PixelShaderResource; request.postProcess.destinationSurface = RenderSurface(512, 256); - request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast(3)); + request.postProcess.destinationSurface.SetColorAttachment(MockRenderTargetViewToken(3)); request.finalOutput.sourceSurface = RenderSurface(512, 256); - request.finalOutput.sourceSurface.SetColorAttachment(reinterpret_cast(4)); - request.finalOutput.sourceColorView = reinterpret_cast(40); + request.finalOutput.sourceSurface.SetColorAttachment(MockRenderTargetViewToken(4)); + request.finalOutput.sourceColorView = MockShaderResourceViewToken(40); request.finalOutput.sourceColorState = XCEngine::RHI::ResourceStates::PixelShaderResource; request.finalOutput.destinationSurface = RenderSurface(640, 360); - request.finalOutput.destinationSurface.SetColorAttachment(reinterpret_cast(5)); + request.finalOutput.destinationSurface.SetColorAttachment(MockRenderTargetViewToken(5)); request.objectId.surface = RenderSurface(320, 180); - request.objectId.surface.SetColorAttachment(reinterpret_cast(6)); - request.objectId.surface.SetDepthAttachment(reinterpret_cast(7)); + request.objectId.surface.SetColorAttachment(MockRenderTargetViewToken(6)); + request.objectId.surface.SetDepthAttachment(MockDepthStencilViewToken(7)); ASSERT_EQ(kOrderedCameraFrameStages.size(), 9u); EXPECT_EQ(kOrderedCameraFrameStages[0].stage, CameraFrameStage::PreScenePasses); @@ -1302,7 +1410,7 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) { EXPECT_EQ(postProcessSource.sourceSurface->GetRenderAreaWidth(), 256u); EXPECT_EQ( postProcessSource.sourceColorView, - reinterpret_cast(20)); + MockShaderResourceViewToken(20)); EXPECT_EQ( postProcessSource.sourceColorState, XCEngine::RHI::ResourceStates::PixelShaderResource); @@ -1312,7 +1420,7 @@ TEST(CameraRenderRequest_Test, ReportsFormalFrameStageContract) { EXPECT_EQ(finalOutputSource.sourceSurface->GetRenderAreaWidth(), 512u); EXPECT_EQ( finalOutputSource.sourceColorView, - reinterpret_cast(40)); + MockShaderResourceViewToken(40)); EXPECT_EQ( finalOutputSource.sourceColorState, XCEngine::RHI::ResourceStates::PixelShaderResource); @@ -1323,12 +1431,12 @@ TEST(CameraRenderRequest_Test, RejectsFullscreenRequestWithoutReadableSourceStat FullscreenPassRenderRequest request = {}; request.sourceSurface = RenderSurface(256, 128); - request.sourceSurface.SetColorAttachment(reinterpret_cast(1)); + request.sourceSurface.SetColorAttachment(MockRenderTargetViewToken(1)); request.sourceSurface.SetAutoTransitionEnabled(false); - request.sourceColorView = reinterpret_cast(2); + request.sourceColorView = MockShaderResourceViewToken(2); request.sourceColorState = XCEngine::RHI::ResourceStates::RenderTarget; request.destinationSurface = RenderSurface(512, 256); - request.destinationSurface.SetColorAttachment(reinterpret_cast(3)); + request.destinationSurface.SetColorAttachment(MockRenderTargetViewToken(3)); request.passes = &passes; EXPECT_FALSE(request.IsValid()); @@ -1342,11 +1450,11 @@ TEST(CameraRenderRequest_Test, AcceptsAutoTransitionedFullscreenSourceAndRejects FullscreenPassRenderRequest request = {}; request.sourceSurface = RenderSurface(256, 128); - request.sourceSurface.SetColorAttachment(reinterpret_cast(1)); - request.sourceColorView = reinterpret_cast(2); + request.sourceSurface.SetColorAttachment(MockRenderTargetViewToken(1)); + request.sourceColorView = MockShaderResourceViewToken(2); request.sourceColorState = XCEngine::RHI::ResourceStates::RenderTarget; request.destinationSurface = RenderSurface(512, 256); - request.destinationSurface.SetColorAttachment(reinterpret_cast(3)); + request.destinationSurface.SetColorAttachment(MockRenderTargetViewToken(3)); request.passes = &passes; EXPECT_TRUE(request.IsValid()); @@ -1369,7 +1477,7 @@ TEST(CameraRenderer_Test, UsesOverrideCameraAndSurfaceSizeWhenSubmittingScene) { overrideCamera->SetDepth(-1.0f); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); CameraRenderRequest request; request.scene = &scene; @@ -1409,7 +1517,7 @@ TEST(CameraRenderer_Test, AppliesRequestClearColorOverrideToSceneData) { camera->SetClearColor(XCEngine::Math::Color(0.05f, 0.10f, 0.15f, 1.0f)); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); CameraRenderRequest request; request.scene = &scene; @@ -1438,14 +1546,14 @@ TEST(CameraRenderer_Test, PromotesSkyboxMaterialIntoEnvironmentFrameData) { camera->SetSkyboxMaterial(&skyboxMaterial); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); CameraRenderRequest request; request.scene = &scene; request.camera = camera; request.context = CreateValidContext(); request.surface = RenderSurface(320, 200); - request.surface.SetDepthAttachment(reinterpret_cast(1)); + request.surface.SetDepthAttachment(MockDepthStencilViewToken(1)); ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); EXPECT_TRUE(state->lastHasSkybox); @@ -1471,14 +1579,14 @@ TEST(CameraRenderer_Test, UsesPipelineSceneDataConfigurationHook) { sceneData.environment = {}; sceneData.globalShaderKeywords = {}; }; - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); CameraRenderRequest request; request.scene = &scene; request.camera = camera; request.context = CreateValidContext(); request.surface = RenderSurface(320, 200); - request.surface.SetDepthAttachment(reinterpret_cast(1)); + request.surface.SetDepthAttachment(MockDepthStencilViewToken(1)); ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); EXPECT_EQ(state->configureRenderSceneDataCalls, 1); @@ -1496,7 +1604,7 @@ TEST(CameraRenderer_Test, ExecutesInjectedPreAndPostPassSequencesAroundPipelineR camera->SetDepth(3.0f); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); RenderPassSequence prePasses; prePasses.AddPass(std::make_unique(state, "pre")); @@ -1533,13 +1641,17 @@ TEST(CameraRenderer_Test, ExecutesObjectIdPassBetweenPipelineAndPostPassesWhenRe camera->SetDepth(3.0f); auto state = std::make_shared(); - auto pipeline = std::make_unique(state); - MockObjectIdPass* objectIdPassRaw = - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - state); - CameraRenderer renderer(std::move(pipeline)); + MockObjectIdPass* objectIdPassRaw = nullptr; + CameraRenderer renderer( + MakeMockPipelineAsset( + state, + [state, &objectIdPassRaw](RenderPipeline& pipeline) { + objectIdPassRaw = + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + state); + })); RenderPassSequence prePasses; prePasses.AddPass(std::make_unique(state, "pre")); @@ -1556,8 +1668,8 @@ TEST(CameraRenderer_Test, ExecutesObjectIdPassBetweenPipelineAndPostPassesWhenRe request.preScenePasses = &prePasses; request.postScenePasses = &postPasses; request.objectId.surface = RenderSurface(320, 180); - request.objectId.surface.SetColorAttachment(reinterpret_cast(1)); - request.objectId.surface.SetDepthAttachment(reinterpret_cast(2)); + request.objectId.surface.SetColorAttachment(MockRenderTargetViewToken(1)); + request.objectId.surface.SetDepthAttachment(MockDepthStencilViewToken(2)); ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); EXPECT_EQ( @@ -1584,7 +1696,7 @@ TEST(CameraRenderer_Test, RoutesSceneColorThroughPostProcessAndFinalOutputStages camera->SetDepth(3.0f); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); auto postProcessPass = std::make_unique(state, "postProcess"); MockScenePass* postProcessPassRaw = postProcessPass.get(); @@ -1601,18 +1713,18 @@ TEST(CameraRenderer_Test, RoutesSceneColorThroughPostProcessAndFinalOutputStages request.camera = camera; request.context = CreateValidContext(); request.surface = RenderSurface(800, 600); - request.surface.SetColorAttachment(reinterpret_cast(1)); + request.surface.SetColorAttachment(MockRenderTargetViewToken(1)); request.cameraDepth = camera->GetDepth(); request.postProcess.sourceSurface = RenderSurface(256, 128); - request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast(2)); - request.postProcess.sourceColorView = reinterpret_cast(20); + request.postProcess.sourceSurface.SetColorAttachment(MockRenderTargetViewToken(2)); + request.postProcess.sourceColorView = MockShaderResourceViewToken(20); request.postProcess.destinationSurface = RenderSurface(512, 256); - request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast(3)); + request.postProcess.destinationSurface.SetColorAttachment(MockRenderTargetViewToken(3)); request.postProcess.passes = &postProcessPasses; request.finalOutput.sourceSurface = request.postProcess.destinationSurface; - request.finalOutput.sourceColorView = reinterpret_cast(30); + request.finalOutput.sourceColorView = MockShaderResourceViewToken(30); request.finalOutput.destinationSurface = request.surface; request.finalOutput.passes = &finalOutputPasses; @@ -1628,7 +1740,7 @@ TEST(CameraRenderer_Test, RoutesSceneColorThroughPostProcessAndFinalOutputStages EXPECT_FALSE(postProcessPassRaw->lastSourceSurfaceAutoTransitionEnabled); EXPECT_EQ( postProcessPassRaw->lastSourceColorView, - reinterpret_cast(20)); + MockShaderResourceViewToken(20)); EXPECT_EQ( postProcessPassRaw->lastSourceColorState, XCEngine::RHI::ResourceStates::PixelShaderResource); @@ -1642,7 +1754,7 @@ TEST(CameraRenderer_Test, RoutesSceneColorThroughPostProcessAndFinalOutputStages EXPECT_FALSE(finalOutputPassRaw->lastSourceSurfaceAutoTransitionEnabled); EXPECT_EQ( finalOutputPassRaw->lastSourceColorView, - reinterpret_cast(30)); + MockShaderResourceViewToken(30)); EXPECT_EQ( finalOutputPassRaw->lastSourceColorState, XCEngine::RHI::ResourceStates::PixelShaderResource); @@ -1670,7 +1782,7 @@ TEST(CameraRenderer_Test, ChainsMultiPassPostProcessThroughIntermediateSurface) auto pipelineState = std::make_shared(); auto allocationState = std::make_shared(); MockShadowDevice device(allocationState); - CameraRenderer renderer(std::make_unique(pipelineState)); + CameraRenderer renderer(MakeMockPipelineAsset(pipelineState)); auto firstPass = std::make_unique(pipelineState, "postProcessTint"); MockScenePass* firstPassRaw = firstPass.get(); @@ -1770,7 +1882,7 @@ TEST(CameraRenderer_Test, KeepsPostProcessAndFinalOutputScratchSurfacesIndepende auto pipelineState = std::make_shared(); auto allocationState = std::make_shared(); MockShadowDevice device(allocationState); - CameraRenderer renderer(std::make_unique(pipelineState)); + CameraRenderer renderer(MakeMockPipelineAsset(pipelineState)); auto postFirstPass = std::make_unique(pipelineState, "postA"); auto postSecondPass = std::make_unique(pipelineState, "postB"); @@ -1905,7 +2017,7 @@ TEST(CameraRenderer_Test, RecordsGraphCapableFullscreenSequencePassesInStageOrde auto pipelineState = std::make_shared(); auto allocationState = std::make_shared(); MockShadowDevice device(allocationState); - CameraRenderer renderer(std::make_unique(pipelineState)); + CameraRenderer renderer(MakeMockPipelineAsset(pipelineState)); auto postFirstPass = std::make_unique(pipelineState, "postA", true, true, true); auto postSecondPass = std::make_unique(pipelineState, "postB", true, true, true); @@ -2030,22 +2142,25 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) { camera->SetDepth(3.0f); auto state = std::make_shared(); - auto pipeline = std::make_unique(state); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - state); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ShadowCaster, - state, - "shadowCaster"); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::DepthOnly, - state, - "depthOnly"); - CameraRenderer renderer(std::move(pipeline)); + CameraRenderer renderer( + MakeMockPipelineAsset( + state, + [state](RenderPipeline& pipeline) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + state); + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ShadowCaster, + state, + "shadowCaster"); + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::DepthOnly, + state, + "depthOnly"); + })); RenderPassSequence prePasses; prePasses.AddPass(std::make_unique(state, "pre")); @@ -2067,7 +2182,7 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) { request.camera = camera; request.context = CreateValidContext(); request.surface = RenderSurface(320, 180); - request.surface.SetColorAttachment(reinterpret_cast(3)); + request.surface.SetColorAttachment(MockRenderTargetViewToken(3)); request.cameraDepth = camera->GetDepth(); request.preScenePasses = &prePasses; request.postProcess.passes = &postProcessPasses; @@ -2075,20 +2190,20 @@ TEST(CameraRenderer_Test, ExecutesFormalFrameStagesInDocumentedOrder) { request.postScenePasses = &postPasses; request.overlayPasses = &overlayPasses; request.shadowCaster.surface = RenderSurface(128, 64); - request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast(1)); + request.shadowCaster.surface.SetDepthAttachment(MockDepthStencilViewToken(1)); request.depthOnly.surface = RenderSurface(96, 48); - request.depthOnly.surface.SetDepthAttachment(reinterpret_cast(2)); + request.depthOnly.surface.SetDepthAttachment(MockDepthStencilViewToken(2)); request.postProcess.sourceSurface = RenderSurface(256, 128); - request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast(4)); - request.postProcess.sourceColorView = reinterpret_cast(40); + request.postProcess.sourceSurface.SetColorAttachment(MockRenderTargetViewToken(4)); + request.postProcess.sourceColorView = MockShaderResourceViewToken(40); request.postProcess.destinationSurface = RenderSurface(320, 180); - request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast(5)); + request.postProcess.destinationSurface.SetColorAttachment(MockRenderTargetViewToken(5)); request.finalOutput.sourceSurface = request.postProcess.destinationSurface; - request.finalOutput.sourceColorView = reinterpret_cast(50); + request.finalOutput.sourceColorView = MockShaderResourceViewToken(50); request.finalOutput.destinationSurface = request.surface; request.objectId.surface = RenderSurface(320, 180); - request.objectId.surface.SetColorAttachment(reinterpret_cast(6)); - request.objectId.surface.SetDepthAttachment(reinterpret_cast(7)); + request.objectId.surface.SetColorAttachment(MockRenderTargetViewToken(6)); + request.objectId.surface.SetDepthAttachment(MockDepthStencilViewToken(7)); ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); EXPECT_EQ( @@ -2121,7 +2236,7 @@ TEST(CameraRenderer_Test, RecordsGraphCapableSinglePassSequenceStagesInDocumente camera->SetDepth(3.0f); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); auto prePass = std::make_unique(state, "pre", true, true, true); TrackingPass* prePassRaw = prePass.get(); @@ -2153,7 +2268,7 @@ TEST(CameraRenderer_Test, RecordsGraphCapableSinglePassSequenceStagesInDocumente request.camera = camera; request.context = CreateValidContext(); request.surface = RenderSurface(320, 180); - request.surface.SetColorAttachment(reinterpret_cast(3)); + request.surface.SetColorAttachment(MockRenderTargetViewToken(3)); request.cameraDepth = camera->GetDepth(); request.preScenePasses = &prePasses; request.postProcess.passes = &postProcessPasses; @@ -2161,12 +2276,12 @@ TEST(CameraRenderer_Test, RecordsGraphCapableSinglePassSequenceStagesInDocumente request.postScenePasses = &postPasses; request.overlayPasses = &overlayPasses; request.postProcess.sourceSurface = RenderSurface(256, 128); - request.postProcess.sourceSurface.SetColorAttachment(reinterpret_cast(4)); - request.postProcess.sourceColorView = reinterpret_cast(40); + request.postProcess.sourceSurface.SetColorAttachment(MockRenderTargetViewToken(4)); + request.postProcess.sourceColorView = MockShaderResourceViewToken(40); request.postProcess.destinationSurface = RenderSurface(320, 180); - request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast(5)); + request.postProcess.destinationSurface.SetColorAttachment(MockRenderTargetViewToken(5)); request.finalOutput.sourceSurface = request.postProcess.destinationSurface; - request.finalOutput.sourceColorView = reinterpret_cast(50); + request.finalOutput.sourceColorView = MockShaderResourceViewToken(50); request.finalOutput.destinationSurface = request.surface; ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); @@ -2199,7 +2314,7 @@ TEST(CameraRenderer_Test, RecordsGraphCapableSinglePassSequenceStagesInDocumente EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceWidth, 256u); EXPECT_EQ(postProcessPassRaw->lastSourceSurfaceHeight, 128u); EXPECT_FALSE(postProcessPassRaw->lastSourceSurfaceAutoTransitionEnabled); - EXPECT_EQ(postProcessPassRaw->lastSourceColorView, reinterpret_cast(40)); + EXPECT_EQ(postProcessPassRaw->lastSourceColorView, MockShaderResourceViewToken(40)); EXPECT_EQ( postProcessPassRaw->lastSourceColorState, XCEngine::RHI::ResourceStates::PixelShaderResource); @@ -2209,7 +2324,7 @@ TEST(CameraRenderer_Test, RecordsGraphCapableSinglePassSequenceStagesInDocumente EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceWidth, 320u); EXPECT_EQ(finalOutputPassRaw->lastSourceSurfaceHeight, 180u); EXPECT_FALSE(finalOutputPassRaw->lastSourceSurfaceAutoTransitionEnabled); - EXPECT_EQ(finalOutputPassRaw->lastSourceColorView, reinterpret_cast(50)); + EXPECT_EQ(finalOutputPassRaw->lastSourceColorView, MockShaderResourceViewToken(50)); EXPECT_EQ( finalOutputPassRaw->lastSourceColorState, XCEngine::RHI::ResourceStates::PixelShaderResource); @@ -2230,7 +2345,7 @@ TEST(CameraRenderer_Test, ResolvesGraphManagedFullscreenSequenceSourcesFromFrame auto state = std::make_shared(); state->supportsMainSceneRenderGraph = true; - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); auto allocationState = std::make_shared(); MockShadowDevice device(allocationState); MockShadowView colorView( @@ -2341,7 +2456,7 @@ TEST(CameraRenderer_Test, PrefersPipelineStageRecordingOverLegacyPostProcessSequ XCEngine::RHI::ResourceViewDimension::Texture2D); state->supportsMainSceneRenderGraph = true; state->supportsPostProcessRenderGraph = true; - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); auto postProcessPass = std::make_unique(state, "postProcess", true, true, true); @@ -2391,24 +2506,29 @@ TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipe camera->SetDepth(3.0f); auto state = std::make_shared(); - auto pipeline = std::make_unique(state); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - state); - MockScenePass* shadowPassRaw = - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ShadowCaster, + MockScenePass* shadowPassRaw = nullptr; + MockScenePass* depthPassRaw = nullptr; + CameraRenderer renderer( + MakeMockPipelineAsset( state, - "shadowCaster"); - MockScenePass* depthPassRaw = - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::DepthOnly, - state, - "depthOnly"); - CameraRenderer renderer(std::move(pipeline)); + [state, &shadowPassRaw, &depthPassRaw](RenderPipeline& pipeline) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + state); + shadowPassRaw = + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ShadowCaster, + state, + "shadowCaster"); + depthPassRaw = + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::DepthOnly, + state, + "depthOnly"); + })); CameraRenderRequest request; request.scene = &scene; @@ -2418,12 +2538,12 @@ TEST(CameraRenderer_Test, ExecutesShadowCasterAndDepthOnlyRequestsBeforeMainPipe request.cameraDepth = camera->GetDepth(); request.shadowCaster.surface = RenderSurface(128, 64); - request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast(2)); + request.shadowCaster.surface.SetDepthAttachment(MockDepthStencilViewToken(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(4)); + request.depthOnly.surface.SetDepthAttachment(MockDepthStencilViewToken(4)); request.depthOnly.hasClearColorOverride = true; request.depthOnly.clearColorOverride = XCEngine::Math::Color(0.3f, 0.2f, 0.1f, 1.0f); @@ -2462,54 +2582,59 @@ TEST(CameraRenderer_Test, RecordsGraphCapableStandalonePassStagesBeforeExecution camera->SetDepth(3.0f); auto state = std::make_shared(); - auto pipeline = std::make_unique(state); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - state, - true, - true); - MockScenePass* shadowPassRaw = - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ShadowCaster, + MockScenePass* shadowPassRaw = nullptr; + MockScenePass* depthPassRaw = nullptr; + CameraRenderer renderer( + MakeMockPipelineAsset( state, - "shadowCaster", - true, - true, - true); - MockScenePass* depthPassRaw = - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::DepthOnly, - state, - "depthOnly", - true, - true, - true); - CameraRenderer renderer(std::move(pipeline)); + [state, &shadowPassRaw, &depthPassRaw](RenderPipeline& pipeline) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + state, + true, + true); + shadowPassRaw = + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ShadowCaster, + state, + "shadowCaster", + true, + true, + true); + depthPassRaw = + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::DepthOnly, + state, + "depthOnly", + true, + true, + true); + })); CameraRenderRequest request; request.scene = &scene; request.camera = camera; request.context = CreateValidContext(); request.surface = RenderSurface(320, 180); - request.surface.SetColorAttachment(reinterpret_cast(1)); + request.surface.SetColorAttachment(MockRenderTargetViewToken(1)); request.cameraDepth = camera->GetDepth(); request.shadowCaster.surface = RenderSurface(128, 64); - request.shadowCaster.surface.SetDepthAttachment(reinterpret_cast(2)); + request.shadowCaster.surface.SetDepthAttachment(MockDepthStencilViewToken(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(4)); + request.depthOnly.surface.SetDepthAttachment(MockDepthStencilViewToken(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(6)); - request.objectId.surface.SetDepthAttachment(reinterpret_cast(7)); + request.objectId.surface.SetColorAttachment(MockRenderTargetViewToken(6)); + request.objectId.surface.SetDepthAttachment(MockDepthStencilViewToken(7)); ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); @@ -2549,15 +2674,15 @@ TEST(CameraRenderer_Test, UsesGraphManagedSurfaceForMainSceneWhenOutputAttachmen camera->SetDepth(3.0f); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); CameraRenderRequest request; request.scene = &scene; request.camera = camera; request.context = CreateValidContext(); request.surface = RenderSurface(320, 180); - request.surface.SetColorAttachment(reinterpret_cast(1)); - request.surface.SetDepthAttachment(reinterpret_cast(2)); + request.surface.SetColorAttachment(MockRenderTargetViewToken(1)); + request.surface.SetDepthAttachment(MockDepthStencilViewToken(2)); request.cameraDepth = camera->GetDepth(); ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); @@ -2577,15 +2702,15 @@ TEST(CameraRenderer_Test, UsesPipelineRecordedMainSceneWhenSupported) { auto state = std::make_shared(); state->supportsMainSceneRenderGraph = true; - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); CameraRenderRequest request; request.scene = &scene; request.camera = camera; request.context = CreateValidContext(); request.surface = RenderSurface(320, 180); - request.surface.SetColorAttachment(reinterpret_cast(1)); - request.surface.SetDepthAttachment(reinterpret_cast(2)); + request.surface.SetColorAttachment(MockRenderTargetViewToken(1)); + request.surface.SetDepthAttachment(MockDepthStencilViewToken(2)); request.cameraDepth = camera->GetDepth(); ASSERT_TRUE(renderer.Render(CameraFramePlan::FromRequest(request))); @@ -2619,18 +2744,22 @@ TEST(CameraRenderer_Test, AutoAllocatesDirectionalShadowSurfaceFromShadowPlan) { context.device = &device; { - auto pipeline = std::make_unique(pipelineState); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - pipelineState); - MockScenePass* shadowPassRaw = - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ShadowCaster, + MockScenePass* shadowPassRaw = nullptr; + CameraRenderer renderer( + MakeMockPipelineAsset( pipelineState, - "shadowCaster"); - CameraRenderer renderer(std::move(pipeline)); + [pipelineState, &shadowPassRaw](RenderPipeline& pipeline) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + pipelineState); + shadowPassRaw = + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ShadowCaster, + pipelineState, + "shadowCaster"); + })); CameraRenderRequest request; request.scene = &scene; @@ -2730,18 +2859,22 @@ TEST(CameraRenderer_Test, AllowsPipelineToOverrideAutoAllocatedDirectionalShadow context.device = &device; { - auto pipeline = std::make_unique(pipelineState); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - pipelineState); - MockScenePass* shadowPassRaw = - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ShadowCaster, + MockScenePass* shadowPassRaw = nullptr; + CameraRenderer renderer( + MakeMockPipelineAsset( pipelineState, - "shadowCaster"); - CameraRenderer renderer(std::move(pipeline)); + [pipelineState, &shadowPassRaw](RenderPipeline& pipeline) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + pipelineState); + shadowPassRaw = + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ShadowCaster, + pipelineState, + "shadowCaster"); + })); CameraRenderRequest request; request.scene = &scene; @@ -2794,25 +2927,28 @@ TEST(CameraRenderer_Test, PassesShadowDependencyToPipelineRecordedMainScene) { RenderContext context = CreateValidContext(); context.device = &device; - auto pipeline = std::make_unique(pipelineState); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - pipelineState); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ShadowCaster, - pipelineState, - "shadowCaster"); - CameraRenderer renderer(std::move(pipeline)); + CameraRenderer renderer( + MakeMockPipelineAsset( + pipelineState, + [pipelineState](RenderPipeline& pipeline) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + pipelineState); + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ShadowCaster, + pipelineState, + "shadowCaster"); + })); CameraRenderRequest request; request.scene = &scene; request.camera = camera; request.context = context; request.surface = RenderSurface(320, 180); - request.surface.SetColorAttachment(reinterpret_cast(1)); - request.surface.SetDepthAttachment(reinterpret_cast(2)); + request.surface.SetColorAttachment(MockRenderTargetViewToken(1)); + request.surface.SetDepthAttachment(MockDepthStencilViewToken(2)); request.cameraDepth = camera->GetDepth(); request.directionalShadow.enabled = true; request.directionalShadow.mapWidth = 256; @@ -2856,17 +2992,20 @@ TEST(CameraRenderer_Test, ReusesDirectionalShadowSurfaceWhenPlanMatches) { RenderContext context = CreateValidContext(); context.device = &device; - auto pipeline = std::make_unique(pipelineState); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - pipelineState); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ShadowCaster, - pipelineState, - "shadowCaster"); - CameraRenderer renderer(std::move(pipeline)); + CameraRenderer renderer( + MakeMockPipelineAsset( + pipelineState, + [pipelineState](RenderPipeline& pipeline) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + pipelineState); + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ShadowCaster, + pipelineState, + "shadowCaster"); + })); CameraRenderRequest request; request.scene = &scene; @@ -2919,17 +3058,20 @@ TEST(CameraRenderer_Test, RecreatesDirectionalShadowSurfaceWhenPlanSizeChanges) context.device = &device; { - auto pipeline = std::make_unique(pipelineState); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - pipelineState); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ShadowCaster, - pipelineState, - "shadowCaster"); - CameraRenderer renderer(std::move(pipeline)); + CameraRenderer renderer( + MakeMockPipelineAsset( + pipelineState, + [pipelineState](RenderPipeline& pipeline) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + pipelineState); + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ShadowCaster, + pipelineState, + "shadowCaster"); + })); CameraRenderRequest request; request.scene = &scene; @@ -2993,17 +3135,20 @@ TEST(CameraRenderer_Test, EnablesDirectionalShadowSurfaceAfterInitialFrameWithou context.device = &device; { - auto pipeline = std::make_unique(pipelineState); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - pipelineState); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ShadowCaster, - pipelineState, - "shadowCaster"); - CameraRenderer renderer(std::move(pipeline)); + CameraRenderer renderer( + MakeMockPipelineAsset( + pipelineState, + [pipelineState](RenderPipeline& pipeline) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + pipelineState); + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ShadowCaster, + pipelineState, + "shadowCaster"); + })); CameraRenderRequest request; request.scene = &scene; @@ -3062,7 +3207,7 @@ TEST(CameraRenderer_Test, StopsRenderingWhenShadowCasterRequestIsInvalid) { camera->SetPrimary(true); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); CameraRenderRequest request; request.scene = &scene; @@ -3070,7 +3215,7 @@ TEST(CameraRenderer_Test, StopsRenderingWhenShadowCasterRequestIsInvalid) { request.context = CreateValidContext(); request.surface = RenderSurface(320, 180); request.shadowCaster.surface = RenderSurface(64, 64); - request.shadowCaster.surface.SetColorAttachment(reinterpret_cast(1)); + request.shadowCaster.surface.SetColorAttachment(MockRenderTargetViewToken(1)); EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request))); EXPECT_TRUE(state->eventLog.empty()); @@ -3084,7 +3229,7 @@ TEST(CameraRenderer_Test, StopsRenderingWhenPostProcessRequestIsInvalid) { camera->SetPrimary(true); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); RenderPassSequence postProcessPasses; postProcessPasses.AddPass(std::make_unique(state, "postProcess")); @@ -3097,7 +3242,7 @@ TEST(CameraRenderer_Test, StopsRenderingWhenPostProcessRequestIsInvalid) { request.postProcess.passes = &postProcessPasses; request.postProcess.sourceSurface = RenderSurface(256, 128); request.postProcess.destinationSurface = RenderSurface(320, 180); - request.postProcess.destinationSurface.SetColorAttachment(reinterpret_cast(1)); + request.postProcess.destinationSurface.SetColorAttachment(MockRenderTargetViewToken(1)); EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request))); EXPECT_TRUE(state->eventLog.empty()); @@ -3113,7 +3258,7 @@ TEST(CameraRenderer_Test, ShutsDownInitializedPassesWhenPipelineRenderFails) { auto state = std::make_shared(); state->renderResult = false; - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); RenderPassSequence prePasses; prePasses.AddPass(std::make_unique(state, "pre")); @@ -3141,13 +3286,16 @@ TEST(CameraRenderer_Test, StopsRenderingWhenObjectIdPassFails) { camera->SetDepth(2.0f); auto state = std::make_shared(); - auto pipeline = std::make_unique(state); - InstallStandaloneStagePass( - *pipeline, - CameraFrameStage::ObjectId, - state, - false); - CameraRenderer renderer(std::move(pipeline)); + CameraRenderer renderer( + MakeMockPipelineAsset( + state, + [state](RenderPipeline& pipeline) { + InstallStandaloneStagePass( + pipeline, + CameraFrameStage::ObjectId, + state, + false); + })); RenderPassSequence prePasses; prePasses.AddPass(std::make_unique(state, "pre")); @@ -3164,8 +3312,8 @@ TEST(CameraRenderer_Test, StopsRenderingWhenObjectIdPassFails) { request.preScenePasses = &prePasses; request.postScenePasses = &postPasses; request.objectId.surface = RenderSurface(320, 180); - request.objectId.surface.SetColorAttachment(reinterpret_cast(1)); - request.objectId.surface.SetDepthAttachment(reinterpret_cast(2)); + request.objectId.surface.SetColorAttachment(MockRenderTargetViewToken(1)); + request.objectId.surface.SetDepthAttachment(MockDepthStencilViewToken(2)); EXPECT_FALSE(renderer.Render(CameraFramePlan::FromRequest(request))); EXPECT_EQ( @@ -3182,7 +3330,7 @@ TEST(CameraRenderer_Test, ShutsDownSequencesWhenPostPassInitializationFails) { camera->SetDepth(4.0f); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); RenderPassSequence prePasses; prePasses.AddPass(std::make_unique(state, "pre")); @@ -3985,7 +4133,7 @@ TEST(SceneRenderer_Test, KeepsMainSceneGraphManagedWhenPostProcessIsEnabledAcros surface.SetColorAttachment(backBufferColorView); surface.SetDepthAttachment(depthView); - SceneRenderer renderer(std::make_unique(pipelineState)); + SceneRenderer renderer(MakeMockPipelineAsset(pipelineState)); std::vector firstFramePlans = renderer.BuildFramePlans(scene, nullptr, context, surface); @@ -4070,7 +4218,7 @@ TEST(SceneRenderer_Test, KeepsPostProcessOutputGraphManagedWhenFinalOutputIsEnab surface.SetColorAttachment(backBufferColorView); surface.SetDepthAttachment(depthView); - SceneRenderer renderer(std::make_unique(pipelineState)); + SceneRenderer renderer(MakeMockPipelineAsset(pipelineState)); std::vector firstFramePlans = renderer.BuildFramePlans(scene, nullptr, context, surface); @@ -4146,7 +4294,7 @@ TEST(CameraRenderer_Test, UsesResolvedRenderAreaForCameraViewportDimensions) { camera->SetViewportRect(XCEngine::Math::Rect(0.125f, 0.25f, 0.5f, 0.5f)); auto state = std::make_shared(); - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); CameraRenderRequest request; request.scene = &scene; @@ -4164,7 +4312,7 @@ TEST(CameraRenderer_Test, UsesResolvedRenderAreaForCameraViewportDimensions) { EXPECT_EQ(state->lastCameraViewportHeight, 240u); } -TEST(SceneRenderer_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRenderer) { +TEST(SceneRenderer_Test, ForwardsPipelineAssetLifetimeAndRenderCallsToCameraRenderer) { Scene scene("SceneRendererScene"); GameObject* cameraObject = scene.CreateGameObject("Camera"); @@ -4174,19 +4322,17 @@ TEST(SceneRenderer_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRenderer) auto initialState = std::make_shared(); auto replacementState = std::make_shared(); + auto initialAssetState = MakeMockPipelineAssetState(initialState); + auto replacementAssetState = MakeMockPipelineAssetState(replacementState); { - auto initialPipeline = std::make_unique(initialState); - MockPipeline* initialPipelineRaw = initialPipeline.get(); - SceneRenderer renderer(std::move(initialPipeline)); - EXPECT_EQ(renderer.GetPipeline(), initialPipelineRaw); + SceneRenderer renderer(std::make_shared(initialAssetState)); + EXPECT_EQ(renderer.GetPipeline(), initialAssetState->lastCreatedPipeline); - auto replacementPipeline = std::make_unique(replacementState); - MockPipeline* replacementPipelineRaw = replacementPipeline.get(); - renderer.SetPipeline(std::move(replacementPipeline)); + renderer.SetPipelineAsset(std::make_shared(replacementAssetState)); EXPECT_EQ(initialState->shutdownCalls, 1); - EXPECT_EQ(renderer.GetPipeline(), replacementPipelineRaw); + EXPECT_EQ(renderer.GetPipeline(), replacementAssetState->lastCreatedPipeline); const RenderSurface surface(800, 600); ASSERT_TRUE(renderer.Render(scene, nullptr, CreateValidContext(), surface)); @@ -4245,10 +4391,18 @@ TEST(RenderPipelineHost_Test, BuildsFramePlansFromRequestsUsingPipelineAssetDefa EXPECT_FLOAT_EQ(plan.finalColorPolicy.exposureValue, 1.8f); } -TEST(RenderPipelineHost_Test, PlansFullscreenStagesFromPipelineRenderGraphSupport) { - auto state = std::make_shared(); - state->supportsPostProcessRenderGraph = true; - state->supportsFinalOutputRenderGraph = true; +TEST(RenderPipelineHost_Test, PlansFullscreenStagesFromPipelineAssetPolicy) { + auto assetState = std::make_shared(); + assetState->configureCameraFramePlan = []( + CameraFramePlan& plan) { + plan.RequestFullscreenStage( + CameraFrameStage::PostProcess, + CameraFrameColorSource::MainSceneColor, + true); + plan.RequestFullscreenStage( + CameraFrameStage::FinalOutput, + CameraFrameColorSource::PostProcessColor); + }; auto allocationState = std::make_shared(); MockShadowView colorView( @@ -4268,7 +4422,7 @@ TEST(RenderPipelineHost_Test, PlansFullscreenStagesFromPipelineRenderGraphSuppor request.surface.SetColorAttachment(&colorView); request.surface.SetDepthAttachment(&depthView); - RenderPipelineHost host(std::make_unique(state)); + RenderPipelineHost host(std::make_shared(assetState)); const std::vector plans = host.BuildFramePlans({ request }); @@ -4290,7 +4444,7 @@ TEST(RenderPipelineHost_Test, PlansFullscreenStagesFromPipelineRenderGraphSuppor EXPECT_TRUE(plan.IsFinalOutputStageValid()); } -TEST(RenderPipelineHost_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRenderer) { +TEST(RenderPipelineHost_Test, ForwardsPipelineAssetLifetimeAndRenderCallsToCameraRenderer) { Scene scene("RenderPipelineHostScene"); GameObject* cameraObject = scene.CreateGameObject("Camera"); @@ -4300,19 +4454,17 @@ TEST(RenderPipelineHost_Test, ForwardsPipelineLifetimeAndRenderCallsToCameraRend auto initialState = std::make_shared(); auto replacementState = std::make_shared(); + auto initialAssetState = MakeMockPipelineAssetState(initialState); + auto replacementAssetState = MakeMockPipelineAssetState(replacementState); { - auto initialPipeline = std::make_unique(initialState); - MockPipeline* initialPipelineRaw = initialPipeline.get(); - RenderPipelineHost host(std::move(initialPipeline)); - EXPECT_EQ(host.GetPipeline(), initialPipelineRaw); + RenderPipelineHost host(std::make_shared(initialAssetState)); + EXPECT_EQ(host.GetPipeline(), initialAssetState->lastCreatedPipeline); - auto replacementPipeline = std::make_unique(replacementState); - MockPipeline* replacementPipelineRaw = replacementPipeline.get(); - host.SetPipeline(std::move(replacementPipeline)); + host.SetPipelineAsset(std::make_shared(replacementAssetState)); EXPECT_EQ(initialState->shutdownCalls, 1); - EXPECT_EQ(host.GetPipeline(), replacementPipelineRaw); + EXPECT_EQ(host.GetPipeline(), replacementAssetState->lastCreatedPipeline); CameraRenderRequest request = {}; request.scene = &scene; @@ -4350,7 +4502,7 @@ TEST(RenderPipelineHost_Test, SortsManualFramePlansByDepthBeforeRendering) { nearCamera->SetDepth(1.0f); auto state = std::make_shared(); - RenderPipelineHost host(std::make_unique(state)); + RenderPipelineHost host(MakeMockPipelineAsset(state)); CameraRenderRequest farRequest; farRequest.scene = &scene; @@ -5213,7 +5365,7 @@ TEST(CameraRenderer_Test, RendersManagedRequestedPostProcessWithoutLegacySequenc auto state = std::make_shared(); state->supportsMainSceneRenderGraph = true; state->supportsPostProcessRenderGraph = true; - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); auto allocationState = std::make_shared(); MockShadowDevice device(allocationState); @@ -5267,7 +5419,7 @@ TEST(CameraRenderer_Test, RejectsManagedRequestedPostProcessWithoutSupportedStag auto state = std::make_shared(); state->supportsMainSceneRenderGraph = true; - CameraRenderer renderer(std::make_unique(state)); + CameraRenderer renderer(MakeMockPipelineAsset(state)); auto allocationState = std::make_shared(); MockShadowDevice device(allocationState); @@ -5306,11 +5458,17 @@ TEST(CameraRenderer_Test, RejectsManagedRequestedPostProcessWithoutSupportedStag } TEST(CameraRenderer_Test, DefaultPipelineAssetUsesManagedSelectionWhenPresent) { + Pipelines::ClearManagedRenderPipelineBridge(); + Pipelines::ClearConfiguredManagedRenderPipelineAssetDescriptor(); + const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { "GameScripts", "Gameplay", "ManagedRenderPipelineProbeAsset" }; + auto bridgeState = std::make_shared(); + Pipelines::SetManagedRenderPipelineBridge( + std::make_shared(bridgeState)); Pipelines::SetConfiguredManagedRenderPipelineAssetDescriptor(descriptor); CameraRenderer renderer; @@ -5326,6 +5484,7 @@ TEST(CameraRenderer_Test, DefaultPipelineAssetUsesManagedSelectionWhenPresent) { nullptr); Pipelines::ClearConfiguredManagedRenderPipelineAssetDescriptor(); + Pipelines::ClearManagedRenderPipelineBridge(); } TEST( @@ -5353,6 +5512,7 @@ TEST( } TEST(CameraRenderer_Test, DefaultPipelineAssetRefreshesWhenManagedSelectionChanges) { + Pipelines::ClearManagedRenderPipelineBridge(); Pipelines::ClearConfiguredManagedRenderPipelineAssetDescriptor(); CameraRenderer renderer; @@ -5378,6 +5538,9 @@ TEST(CameraRenderer_Test, DefaultPipelineAssetRefreshesWhenManagedSelectionChang "Gameplay", "ManagedRenderPipelineProbeAsset" }; + auto bridgeState = std::make_shared(); + Pipelines::SetManagedRenderPipelineBridge( + std::make_shared(bridgeState)); Pipelines::SetConfiguredManagedRenderPipelineAssetDescriptor(descriptor); auto* managedAsset = @@ -5393,6 +5556,7 @@ TEST(CameraRenderer_Test, DefaultPipelineAssetRefreshesWhenManagedSelectionChang nullptr); Pipelines::ClearConfiguredManagedRenderPipelineAssetDescriptor(); + Pipelines::ClearManagedRenderPipelineBridge(); EXPECT_NE( dynamic_cast( renderer.GetPipelineAsset()), @@ -5412,11 +5576,17 @@ TEST(CameraRenderer_Test, DefaultPipelineAssetRefreshesWhenManagedSelectionChang } TEST(CameraRenderer_Test, RebindsToResolvedDefaultAssetWhenPreferredAssetCannotCreatePipeline) { + Pipelines::ClearManagedRenderPipelineBridge(); + Pipelines::ClearConfiguredManagedRenderPipelineAssetDescriptor(); + const Pipelines::ManagedRenderPipelineAssetDescriptor descriptor = { "GameScripts", "Gameplay", "ManagedRenderPipelineProbeAsset" }; + auto bridgeState = std::make_shared(); + Pipelines::SetManagedRenderPipelineBridge( + std::make_shared(bridgeState)); Pipelines::SetConfiguredManagedRenderPipelineAssetDescriptor(descriptor); auto failingAssetState = std::make_shared(); @@ -5438,6 +5608,7 @@ TEST(CameraRenderer_Test, RebindsToResolvedDefaultAssetWhenPreferredAssetCannotC EXPECT_EQ(asset->GetDescriptor().className, "ManagedRenderPipelineProbeAsset"); Pipelines::ClearConfiguredManagedRenderPipelineAssetDescriptor(); + Pipelines::ClearManagedRenderPipelineBridge(); } TEST(CameraRenderer_Test, RecreatesManagedPipelineWhenBridgeChanges) { @@ -5526,7 +5697,7 @@ TEST(SceneRenderer_Test, SortsManualCameraRequestsByDepthBeforeRendering) { nearCamera->SetDepth(1.0f); auto state = std::make_shared(); - SceneRenderer renderer(std::make_unique(state)); + SceneRenderer renderer(MakeMockPipelineAsset(state)); CameraRenderRequest farRequest; farRequest.scene = &scene; @@ -5570,7 +5741,7 @@ TEST(SceneRenderer_Test, PreservesManualSubmissionOrderForEqualPriorityRequests) secondCamera->SetDepth(2.0f); auto state = std::make_shared(); - SceneRenderer renderer(std::make_unique(state)); + SceneRenderer renderer(MakeMockPipelineAsset(state)); CameraRenderRequest firstRequest; firstRequest.scene = &scene;