From ff49120ffe8f68b48b3b0888b1dfd980780b1d51 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 6 Apr 2026 14:14:11 +0800 Subject: [PATCH] Wire camera-config post-process requests --- .../XCEngine/Components/CameraComponent.h | 9 + .../Rendering/Execution/SceneRenderer.h | 13 +- engine/src/Components/CameraComponent.cpp | 15 ++ .../Caches/FullscreenPassSurfaceCache.cpp | 157 +----------------- .../Caches/FullscreenPassSurfaceCache.h | 135 ++++++++++++++- .../src/Rendering/Execution/SceneRenderer.cpp | 89 +++++++++- .../test_camera_light_component.cpp | 12 ++ .../unit/test_camera_scene_renderer.cpp | 64 +++++++ 8 files changed, 330 insertions(+), 164 deletions(-) diff --git a/engine/include/XCEngine/Components/CameraComponent.h b/engine/include/XCEngine/Components/CameraComponent.h index f2eca9a1..c0081715 100644 --- a/engine/include/XCEngine/Components/CameraComponent.h +++ b/engine/include/XCEngine/Components/CameraComponent.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -89,6 +90,12 @@ public: const Math::Color& GetSkyboxBottomColor() const { return m_skyboxBottomColor; } void SetSkyboxBottomColor(const Math::Color& value) { m_skyboxBottomColor = value; } + bool IsColorScalePostProcessEnabled() const { return m_colorScalePostProcessEnabled; } + void SetColorScalePostProcessEnabled(bool value) { m_colorScalePostProcessEnabled = value; } + + const Math::Vector4& GetColorScalePostProcessScale() const { return m_colorScalePostProcessScale; } + void SetColorScalePostProcessScale(const Math::Vector4& value) { m_colorScalePostProcessScale = value; } + void Serialize(std::ostream& os) const override; void Deserialize(std::istream& is) override; @@ -112,6 +119,8 @@ private: Math::Color m_skyboxTopColor = Math::Color(0.18f, 0.36f, 0.74f, 1.0f); Math::Color m_skyboxHorizonColor = Math::Color(0.78f, 0.84f, 0.92f, 1.0f); Math::Color m_skyboxBottomColor = Math::Color(0.92f, 0.93f, 0.95f, 1.0f); + bool m_colorScalePostProcessEnabled = false; + Math::Vector4 m_colorScalePostProcessScale = Math::Vector4::One(); }; } // namespace Components diff --git a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h index fd6b03d1..b732a768 100644 --- a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h +++ b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h @@ -4,6 +4,7 @@ #include #include +#include #include namespace XCEngine { @@ -15,13 +16,14 @@ class Scene; namespace Rendering { class RenderPipelineAsset; +class FullscreenPassSurfaceCache; class SceneRenderer { public: SceneRenderer(); explicit SceneRenderer(std::unique_ptr pipeline); explicit SceneRenderer(std::shared_ptr pipelineAsset); - ~SceneRenderer() = default; + ~SceneRenderer(); void SetPipeline(std::unique_ptr pipeline); void SetPipelineAsset(std::shared_ptr pipelineAsset); @@ -32,7 +34,7 @@ public: const Components::Scene& scene, Components::CameraComponent* overrideCamera, const RenderContext& context, - const RenderSurface& surface) const; + const RenderSurface& surface); bool Render(const CameraRenderRequest& request); bool Render(const std::vector& requests); @@ -43,8 +45,15 @@ public: const RenderSurface& surface); private: + void PrepareOwnedCameraPostProcessState(size_t requestCount); + void AttachCameraPostProcessRequests( + const RenderContext& context, + std::vector& requests); + SceneRenderRequestPlanner m_requestPlanner; CameraRenderer m_cameraRenderer; + std::vector> m_ownedPostProcessSequences; + std::vector> m_ownedPostProcessSourceSurfaces; }; } // namespace Rendering diff --git a/engine/src/Components/CameraComponent.cpp b/engine/src/Components/CameraComponent.cpp index f67621f9..59798e22 100644 --- a/engine/src/Components/CameraComponent.cpp +++ b/engine/src/Components/CameraComponent.cpp @@ -149,6 +149,12 @@ void CameraComponent::Serialize(std::ostream& os) const { os << "skyboxTopColor=" << m_skyboxTopColor.r << "," << m_skyboxTopColor.g << "," << m_skyboxTopColor.b << "," << m_skyboxTopColor.a << ";"; os << "skyboxHorizonColor=" << m_skyboxHorizonColor.r << "," << m_skyboxHorizonColor.g << "," << m_skyboxHorizonColor.b << "," << m_skyboxHorizonColor.a << ";"; os << "skyboxBottomColor=" << m_skyboxBottomColor.r << "," << m_skyboxBottomColor.g << "," << m_skyboxBottomColor.b << "," << m_skyboxBottomColor.a << ";"; + os << "colorScalePostProcessEnabled=" << (m_colorScalePostProcessEnabled ? 1 : 0) << ";"; + os << "colorScalePostProcessScale=" + << m_colorScalePostProcessScale.x << "," + << m_colorScalePostProcessScale.y << "," + << m_colorScalePostProcessScale.z << "," + << m_colorScalePostProcessScale.w << ";"; } void CameraComponent::Deserialize(std::istream& is) { @@ -220,6 +226,15 @@ void CameraComponent::Deserialize(std::istream& is) { std::replace(value.begin(), value.end(), ',', ' '); std::istringstream ss(value); ss >> m_skyboxBottomColor.r >> m_skyboxBottomColor.g >> m_skyboxBottomColor.b >> m_skyboxBottomColor.a; + } else if (key == "colorScalePostProcessEnabled") { + m_colorScalePostProcessEnabled = (std::stoi(value) != 0); + } else if (key == "colorScalePostProcessScale") { + std::replace(value.begin(), value.end(), ',', ' '); + std::istringstream ss(value); + ss >> m_colorScalePostProcessScale.x + >> m_colorScalePostProcessScale.y + >> m_colorScalePostProcessScale.z + >> m_colorScalePostProcessScale.w; } } diff --git a/engine/src/Rendering/Caches/FullscreenPassSurfaceCache.cpp b/engine/src/Rendering/Caches/FullscreenPassSurfaceCache.cpp index d8ba22fc..a668ac05 100644 --- a/engine/src/Rendering/Caches/FullscreenPassSurfaceCache.cpp +++ b/engine/src/Rendering/Caches/FullscreenPassSurfaceCache.cpp @@ -1,156 +1,5 @@ #include "Rendering/Caches/FullscreenPassSurfaceCache.h" -#include "Rendering/RenderContext.h" -#include "RHI/RHIDevice.h" -#include "RHI/RHIResourceView.h" -#include "RHI/RHITexture.h" - -namespace XCEngine { -namespace Rendering { - -namespace { - -void DestroySurfaceEntry(FullscreenPassSurfaceCache::SurfaceEntry& entry) { - if (entry.renderTargetView != nullptr) { - entry.renderTargetView->Shutdown(); - delete entry.renderTargetView; - entry.renderTargetView = nullptr; - } - - if (entry.shaderResourceView != nullptr) { - entry.shaderResourceView->Shutdown(); - delete entry.shaderResourceView; - entry.shaderResourceView = nullptr; - } - - if (entry.texture != nullptr) { - entry.texture->Shutdown(); - delete entry.texture; - entry.texture = nullptr; - } - - entry.surface = RenderSurface(); - entry.currentColorState = RHI::ResourceStates::Common; -} - -} // namespace - -FullscreenPassSurfaceCache::~FullscreenPassSurfaceCache() { - Reset(); -} - -bool FullscreenPassSurfaceCache::EnsureSurfaces( - const RenderContext& context, - uint32_t width, - uint32_t height, - RHI::Format format, - size_t surfaceCount) { - if (!context.IsValid() || - width == 0 || - height == 0 || - format == RHI::Format::Unknown || - surfaceCount == 0) { - return false; - } - - if (Matches(context, width, height, format, surfaceCount)) { - return true; - } - - std::vector newEntries(surfaceCount); - for (size_t index = 0; index < surfaceCount; ++index) { - SurfaceEntry& entry = newEntries[index]; - - RHI::TextureDesc textureDesc = {}; - textureDesc.width = width; - textureDesc.height = height; - textureDesc.depth = 1; - textureDesc.mipLevels = 1; - textureDesc.arraySize = 1; - textureDesc.format = static_cast(format); - textureDesc.textureType = static_cast(RHI::TextureType::Texture2D); - textureDesc.sampleCount = 1; - textureDesc.sampleQuality = 0; - textureDesc.flags = 0; - - entry.texture = context.device->CreateTexture(textureDesc); - if (entry.texture == nullptr) { - for (SurfaceEntry& createdEntry : newEntries) { - DestroySurfaceEntry(createdEntry); - } - return false; - } - - RHI::ResourceViewDesc viewDesc = {}; - viewDesc.format = static_cast(format); - viewDesc.dimension = RHI::ResourceViewDimension::Texture2D; - viewDesc.mipLevel = 0; - - entry.renderTargetView = context.device->CreateRenderTargetView(entry.texture, viewDesc); - if (entry.renderTargetView == nullptr) { - for (SurfaceEntry& createdEntry : newEntries) { - DestroySurfaceEntry(createdEntry); - } - return false; - } - - entry.shaderResourceView = context.device->CreateShaderResourceView(entry.texture, viewDesc); - if (entry.shaderResourceView == nullptr) { - for (SurfaceEntry& createdEntry : newEntries) { - DestroySurfaceEntry(createdEntry); - } - return false; - } - - entry.surface = RenderSurface(width, height); - entry.surface.SetColorAttachment(entry.renderTargetView); - entry.surface.SetAutoTransitionEnabled(true); - entry.surface.SetColorStateBefore(RHI::ResourceStates::Common); - entry.surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource); - entry.currentColorState = RHI::ResourceStates::Common; - } - - Reset(); - m_device = context.device; - m_width = width; - m_height = height; - m_format = format; - m_entries = std::move(newEntries); - return true; -} - -FullscreenPassSurfaceCache::SurfaceEntry* FullscreenPassSurfaceCache::GetSurfaceEntry(size_t index) { - return index < m_entries.size() ? &m_entries[index] : nullptr; -} - -const FullscreenPassSurfaceCache::SurfaceEntry* FullscreenPassSurfaceCache::GetSurfaceEntry(size_t index) const { - return index < m_entries.size() ? &m_entries[index] : nullptr; -} - -bool FullscreenPassSurfaceCache::Matches( - const RenderContext& context, - uint32_t width, - uint32_t height, - RHI::Format format, - size_t surfaceCount) const { - return m_device == context.device && - m_width == width && - m_height == height && - m_format == format && - m_entries.size() == surfaceCount; -} - -void FullscreenPassSurfaceCache::Reset() { - for (SurfaceEntry& entry : m_entries) { - DestroySurfaceEntry(entry); - } - - m_entries.clear(); - m_device = nullptr; - m_width = 0; - m_height = 0; - m_format = RHI::Format::Unknown; -} - -} // namespace Rendering -} // namespace XCEngine +// This translation unit remains intentionally light. The cache is implemented +// inline in the header so existing generated build files do not require a +// CMake regeneration just to pick up method definitions. diff --git a/engine/src/Rendering/Caches/FullscreenPassSurfaceCache.h b/engine/src/Rendering/Caches/FullscreenPassSurfaceCache.h index 686d3b76..f63f92fb 100644 --- a/engine/src/Rendering/Caches/FullscreenPassSurfaceCache.h +++ b/engine/src/Rendering/Caches/FullscreenPassSurfaceCache.h @@ -1,7 +1,11 @@ #pragma once #include "Rendering/RenderSurface.h" +#include "Rendering/RenderContext.h" +#include "RHI/RHIDevice.h" #include "RHI/RHIEnums.h" +#include "RHI/RHIResourceView.h" +#include "RHI/RHITexture.h" #include #include @@ -30,27 +34,146 @@ public: FullscreenPassSurfaceCache() = default; FullscreenPassSurfaceCache(const FullscreenPassSurfaceCache&) = delete; FullscreenPassSurfaceCache& operator=(const FullscreenPassSurfaceCache&) = delete; - ~FullscreenPassSurfaceCache(); + ~FullscreenPassSurfaceCache() { + Reset(); + } bool EnsureSurfaces( const RenderContext& context, uint32_t width, uint32_t height, RHI::Format format, - size_t surfaceCount); + size_t surfaceCount) { + if (!context.IsValid() || + width == 0 || + height == 0 || + format == RHI::Format::Unknown || + surfaceCount == 0) { + return false; + } + + if (Matches(context, width, height, format, surfaceCount)) { + return true; + } + + std::vector newEntries(surfaceCount); + for (size_t index = 0; index < surfaceCount; ++index) { + SurfaceEntry& entry = newEntries[index]; + + RHI::TextureDesc textureDesc = {}; + textureDesc.width = width; + textureDesc.height = height; + textureDesc.depth = 1; + textureDesc.mipLevels = 1; + textureDesc.arraySize = 1; + textureDesc.format = static_cast(format); + textureDesc.textureType = static_cast(RHI::TextureType::Texture2D); + textureDesc.sampleCount = 1; + textureDesc.sampleQuality = 0; + textureDesc.flags = 0; + + entry.texture = context.device->CreateTexture(textureDesc); + if (entry.texture == nullptr) { + for (SurfaceEntry& createdEntry : newEntries) { + DestroySurfaceEntry(createdEntry); + } + return false; + } + + RHI::ResourceViewDesc viewDesc = {}; + viewDesc.format = static_cast(format); + viewDesc.dimension = RHI::ResourceViewDimension::Texture2D; + viewDesc.mipLevel = 0; + + entry.renderTargetView = context.device->CreateRenderTargetView(entry.texture, viewDesc); + if (entry.renderTargetView == nullptr) { + for (SurfaceEntry& createdEntry : newEntries) { + DestroySurfaceEntry(createdEntry); + } + return false; + } + + entry.shaderResourceView = context.device->CreateShaderResourceView(entry.texture, viewDesc); + if (entry.shaderResourceView == nullptr) { + for (SurfaceEntry& createdEntry : newEntries) { + DestroySurfaceEntry(createdEntry); + } + return false; + } + + entry.surface = RenderSurface(width, height); + entry.surface.SetColorAttachment(entry.renderTargetView); + entry.surface.SetAutoTransitionEnabled(true); + entry.surface.SetColorStateBefore(RHI::ResourceStates::Common); + entry.surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource); + entry.currentColorState = RHI::ResourceStates::Common; + } + + Reset(); + m_device = context.device; + m_width = width; + m_height = height; + m_format = format; + m_entries = std::move(newEntries); + return true; + } size_t GetSurfaceCount() const { return m_entries.size(); } - SurfaceEntry* GetSurfaceEntry(size_t index); - const SurfaceEntry* GetSurfaceEntry(size_t index) const; + SurfaceEntry* GetSurfaceEntry(size_t index) { + return index < m_entries.size() ? &m_entries[index] : nullptr; + } + const SurfaceEntry* GetSurfaceEntry(size_t index) const { + return index < m_entries.size() ? &m_entries[index] : nullptr; + } private: + static void DestroySurfaceEntry(SurfaceEntry& entry) { + if (entry.renderTargetView != nullptr) { + entry.renderTargetView->Shutdown(); + delete entry.renderTargetView; + entry.renderTargetView = nullptr; + } + + if (entry.shaderResourceView != nullptr) { + entry.shaderResourceView->Shutdown(); + delete entry.shaderResourceView; + entry.shaderResourceView = nullptr; + } + + if (entry.texture != nullptr) { + entry.texture->Shutdown(); + delete entry.texture; + entry.texture = nullptr; + } + + entry.surface = RenderSurface(); + entry.currentColorState = RHI::ResourceStates::Common; + } + bool Matches( const RenderContext& context, uint32_t width, uint32_t height, RHI::Format format, - size_t surfaceCount) const; - void Reset(); + size_t surfaceCount) const { + return m_device == context.device && + m_width == width && + m_height == height && + m_format == format && + m_entries.size() == surfaceCount; + } + + void Reset() { + for (SurfaceEntry& entry : m_entries) { + DestroySurfaceEntry(entry); + } + + m_entries.clear(); + m_device = nullptr; + m_width = 0; + m_height = 0; + m_format = RHI::Format::Unknown; + } RHI::RHIDevice* m_device = nullptr; uint32_t m_width = 0; diff --git a/engine/src/Rendering/Execution/SceneRenderer.cpp b/engine/src/Rendering/Execution/SceneRenderer.cpp index 521a50b6..ee27acc2 100644 --- a/engine/src/Rendering/Execution/SceneRenderer.cpp +++ b/engine/src/Rendering/Execution/SceneRenderer.cpp @@ -1,4 +1,8 @@ #include "Rendering/Execution/SceneRenderer.h" + +#include "Components/CameraComponent.h" +#include "Rendering/Caches/FullscreenPassSurfaceCache.h" +#include "Rendering/Passes/BuiltinColorScalePostProcessPass.h" #include "Rendering/Planning/SceneRenderRequestUtils.h" namespace XCEngine { @@ -14,6 +18,8 @@ SceneRenderer::SceneRenderer(std::shared_ptr pipeline : m_cameraRenderer(std::move(pipelineAsset)) { } +SceneRenderer::~SceneRenderer() = default; + void SceneRenderer::SetPipeline(std::unique_ptr pipeline) { m_cameraRenderer.SetPipeline(std::move(pipeline)); } @@ -26,8 +32,11 @@ std::vector SceneRenderer::BuildRenderRequests( const Components::Scene& scene, Components::CameraComponent* overrideCamera, const RenderContext& context, - const RenderSurface& surface) const { - return m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface); + const RenderSurface& surface) { + std::vector requests = + m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface); + AttachCameraPostProcessRequests(context, requests); + return requests; } bool SceneRenderer::Render(const CameraRenderRequest& request) { @@ -67,5 +76,81 @@ bool SceneRenderer::Render( return Render(BuildRenderRequests(scene, overrideCamera, context, surface)); } +void SceneRenderer::PrepareOwnedCameraPostProcessState(size_t requestCount) { + m_ownedPostProcessSequences.clear(); + m_ownedPostProcessSequences.resize(requestCount); + + if (m_ownedPostProcessSourceSurfaces.size() < requestCount) { + m_ownedPostProcessSourceSurfaces.resize(requestCount); + } + + for (size_t index = 0; index < requestCount; ++index) { + if (m_ownedPostProcessSourceSurfaces[index] == nullptr) { + m_ownedPostProcessSourceSurfaces[index] = std::make_unique(); + } + } +} + +void SceneRenderer::AttachCameraPostProcessRequests( + const RenderContext& context, + std::vector& requests) { + PrepareOwnedCameraPostProcessState(requests.size()); + + for (size_t index = 0; index < requests.size(); ++index) { + CameraRenderRequest& request = requests[index]; + if (request.camera == nullptr || + !request.camera->IsColorScalePostProcessEnabled() || + request.context.device == nullptr || + !HasValidColorTarget(request.surface)) { + continue; + } + + const std::vector& colorAttachments = request.surface.GetColorAttachments(); + const RHI::Format colorFormat = colorAttachments[0]->GetFormat(); + if (colorFormat == RHI::Format::Unknown) { + continue; + } + + FullscreenPassSurfaceCache* sourceSurfaceCache = m_ownedPostProcessSourceSurfaces[index].get(); + if (sourceSurfaceCache == nullptr || + !sourceSurfaceCache->EnsureSurfaces( + context, + request.surface.GetWidth(), + request.surface.GetHeight(), + colorFormat, + 1u)) { + continue; + } + + const FullscreenPassSurfaceCache::SurfaceEntry* sourceEntry = sourceSurfaceCache->GetSurfaceEntry(0u); + if (sourceEntry == nullptr || sourceEntry->shaderResourceView == nullptr) { + continue; + } + + std::unique_ptr postProcessSequence = std::make_unique(); + postProcessSequence->AddPass(std::make_unique( + request.camera->GetColorScalePostProcessScale())); + + RenderSurface sourceSurface = sourceEntry->surface; + sourceSurface.SetDepthAttachment(request.surface.GetDepthAttachment()); + sourceSurface.SetColorStateBefore(RHI::ResourceStates::Common); + sourceSurface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource); + if (request.surface.HasCustomRenderArea()) { + sourceSurface.SetRenderArea(request.surface.GetRenderArea()); + } else { + sourceSurface.ResetRenderArea(); + } + if (request.surface.HasClearColorOverride()) { + sourceSurface.SetClearColorOverride(request.surface.GetClearColorOverride()); + } + + request.postProcess.sourceSurface = sourceSurface; + request.postProcess.sourceColorView = sourceEntry->shaderResourceView; + request.postProcess.destinationSurface = request.surface; + m_ownedPostProcessSequences[index] = std::move(postProcessSequence); + request.postProcess.passes = m_ownedPostProcessSequences[index].get(); + } +} + } // namespace Rendering } // namespace XCEngine diff --git a/tests/Components/test_camera_light_component.cpp b/tests/Components/test_camera_light_component.cpp index cc5436fb..4ae8b5b0 100644 --- a/tests/Components/test_camera_light_component.cpp +++ b/tests/Components/test_camera_light_component.cpp @@ -32,6 +32,11 @@ TEST(CameraComponent_Test, DefaultValues) { EXPECT_FLOAT_EQ(camera.GetSkyboxTopColor().r, 0.18f); EXPECT_FLOAT_EQ(camera.GetSkyboxHorizonColor().g, 0.84f); EXPECT_FLOAT_EQ(camera.GetSkyboxBottomColor().b, 0.95f); + EXPECT_FALSE(camera.IsColorScalePostProcessEnabled()); + EXPECT_FLOAT_EQ(camera.GetColorScalePostProcessScale().x, 1.0f); + EXPECT_FLOAT_EQ(camera.GetColorScalePostProcessScale().y, 1.0f); + EXPECT_FLOAT_EQ(camera.GetColorScalePostProcessScale().z, 1.0f); + EXPECT_FLOAT_EQ(camera.GetColorScalePostProcessScale().w, 1.0f); } TEST(CameraComponent_Test, SetterClamping) { @@ -69,6 +74,8 @@ TEST(CameraComponent_Test, SerializeRoundTripPreservesViewportAndClearState) { source.SetSkyboxTopColor(XCEngine::Math::Color(0.12f, 0.21f, 0.64f, 1.0f)); source.SetSkyboxHorizonColor(XCEngine::Math::Color(0.71f, 0.76f, 0.88f, 1.0f)); source.SetSkyboxBottomColor(XCEngine::Math::Color(0.92f, 0.82f, 0.58f, 1.0f)); + source.SetColorScalePostProcessEnabled(true); + source.SetColorScalePostProcessScale(XCEngine::Math::Vector4(0.55f, 0.8f, 1.2f, 1.0f)); std::stringstream stream; source.Serialize(stream); @@ -88,6 +95,11 @@ TEST(CameraComponent_Test, SerializeRoundTripPreservesViewportAndClearState) { EXPECT_FLOAT_EQ(target.GetSkyboxTopColor().b, 0.64f); EXPECT_FLOAT_EQ(target.GetSkyboxHorizonColor().g, 0.76f); EXPECT_FLOAT_EQ(target.GetSkyboxBottomColor().r, 0.92f); + EXPECT_TRUE(target.IsColorScalePostProcessEnabled()); + EXPECT_FLOAT_EQ(target.GetColorScalePostProcessScale().x, 0.55f); + EXPECT_FLOAT_EQ(target.GetColorScalePostProcessScale().y, 0.8f); + EXPECT_FLOAT_EQ(target.GetColorScalePostProcessScale().z, 1.2f); + EXPECT_FLOAT_EQ(target.GetColorScalePostProcessScale().w, 1.0f); } TEST(LightComponent_Test, DefaultValues) { diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 5cee886d..a617a0a9 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -1770,6 +1770,70 @@ TEST(SceneRenderer_Test, PreservesExistingSurfaceRenderAreaForFullViewportCamera EXPECT_EQ(renderArea.height, 240); } +TEST(SceneRenderer_Test, BuildsCameraColorScalePostProcessRequestFromCameraSettings) { + Scene scene("SceneRendererCameraPostProcessScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(2.0f); + camera->SetViewportRect(XCEngine::Math::Rect(0.25f, 0.125f, 0.5f, 0.625f)); + camera->SetColorScalePostProcessEnabled(true); + camera->SetColorScalePostProcessScale(XCEngine::Math::Vector4(0.55f, 0.8f, 1.1f, 1.0f)); + + 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); + + SceneRenderer renderer; + const std::vector requests = + renderer.BuildRenderRequests(scene, nullptr, context, surface); + + ASSERT_EQ(requests.size(), 1u); + const CameraRenderRequest& request = requests[0]; + EXPECT_TRUE(request.postProcess.IsRequested()); + EXPECT_TRUE(request.postProcess.IsValid()); + EXPECT_NE(request.postProcess.passes, nullptr); + ASSERT_EQ(request.postProcess.passes->GetPassCount(), 1u); + EXPECT_EQ(request.postProcess.destinationSurface.GetColorAttachments()[0], backBufferColorView); + EXPECT_EQ(request.postProcess.destinationSurface.GetDepthAttachment(), depthView); + EXPECT_EQ(request.postProcess.sourceSurface.GetDepthAttachment(), depthView); + EXPECT_EQ(request.postProcess.sourceSurface.GetWidth(), 800u); + EXPECT_EQ(request.postProcess.sourceSurface.GetHeight(), 600u); + const XCEngine::Math::RectInt sourceRenderArea = request.postProcess.sourceSurface.GetRenderArea(); + EXPECT_EQ(sourceRenderArea.x, 200); + EXPECT_EQ(sourceRenderArea.y, 75); + EXPECT_EQ(sourceRenderArea.width, 400); + EXPECT_EQ(sourceRenderArea.height, 375); + EXPECT_NE(request.postProcess.sourceColorView, nullptr); + EXPECT_NE(request.postProcess.sourceColorView, backBufferColorView); + ASSERT_FALSE(request.postProcess.sourceSurface.GetColorAttachments().empty()); + EXPECT_NE(request.postProcess.sourceSurface.GetColorAttachments()[0], backBufferColorView); + EXPECT_EQ(allocationState->createTextureCalls, 1); + EXPECT_EQ(allocationState->createRenderTargetViewCalls, 1); + EXPECT_EQ(allocationState->createShaderViewCalls, 1); + + delete depthView; + delete backBufferColorView; +} + TEST(CameraRenderer_Test, UsesResolvedRenderAreaForCameraViewportDimensions) { Scene scene("CameraRendererViewportRectScene");