From 33bb84f6504c6e7bf9da3cb8e5fb3fab35baa3ab Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 6 Apr 2026 16:15:19 +0800 Subject: [PATCH] Lower final color into final output stage --- engine/CMakeLists.txt | 8 + .../shaders/final-color/final-color.frag.glsl | 65 +++ .../final-color/final-color.frag.vk.glsl | 66 +++ .../shaders/final-color/final-color.ps.hlsl | 65 +++ .../shaders/final-color/final-color.shader | 31 ++ .../shaders/final-color/final-color.vert.glsl | 16 + .../final-color/final-color.vert.vk.glsl | 18 + .../shaders/final-color/final-color.vs.hlsl | 22 + .../Rendering/Execution/SceneRenderer.h | 7 +- .../Rendering/Passes/BuiltinFinalColorPass.h | 68 +++ .../Planning/FinalColorPassFactory.h | 24 + .../XCEngine/Resources/BuiltinResources.h | 1 + .../src/Rendering/Execution/SceneRenderer.cpp | 117 +++-- .../Passes/BuiltinFinalColorPass.cpp | 452 ++++++++++++++++++ engine/src/Resources/BuiltinResources.cpp | 21 +- .../unit/test_builtin_forward_pipeline.cpp | 47 ++ .../unit/test_camera_scene_renderer.cpp | 163 +++++++ 17 files changed, 1149 insertions(+), 42 deletions(-) create mode 100644 engine/assets/builtin/shaders/final-color/final-color.frag.glsl create mode 100644 engine/assets/builtin/shaders/final-color/final-color.frag.vk.glsl create mode 100644 engine/assets/builtin/shaders/final-color/final-color.ps.hlsl create mode 100644 engine/assets/builtin/shaders/final-color/final-color.shader create mode 100644 engine/assets/builtin/shaders/final-color/final-color.vert.glsl create mode 100644 engine/assets/builtin/shaders/final-color/final-color.vert.vk.glsl create mode 100644 engine/assets/builtin/shaders/final-color/final-color.vs.hlsl create mode 100644 engine/include/XCEngine/Rendering/Passes/BuiltinFinalColorPass.h create mode 100644 engine/include/XCEngine/Rendering/Planning/FinalColorPassFactory.h create mode 100644 engine/src/Rendering/Passes/BuiltinFinalColorPass.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 3634a062..3738c3f7 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -438,6 +438,8 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/RenderSceneData.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/FrameData/VisibleRenderItem.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Planning/CameraRenderRequest.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Planning/FinalColorPassFactory.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Planning/FinalColorSettings.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Planning/SceneRenderRequestPlanner.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Planning/SceneRenderRequestUtils.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Picking/ObjectIdCodec.h @@ -456,6 +458,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinShadowCasterPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinColorScalePostProcessPass.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinFinalColorPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinInfiniteGridPass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Passes/BuiltinObjectIdOutlinePass.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Rendering/Pipelines/BuiltinForwardPipeline.h @@ -469,6 +472,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdPassResources.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinColorScalePostProcessPass.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinFinalColorPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinInfiniteGridPass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/Passes/BuiltinObjectIdOutlinePass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rendering/RenderSurface.cpp @@ -508,6 +512,7 @@ add_library(XCEngine STATIC # UI ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Types.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Layout/UITabStripLayout.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Core/UIInvalidation.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Core/UIViewModel.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Core/UIBuildContext.h @@ -519,9 +524,11 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/Theme.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/StyleSet.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/StyleResolver.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Style/DocumentStyleCompiler.h ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Style/StyleTypes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Style/Theme.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Style/StyleResolver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Style/DocumentStyleCompiler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Input/UIInputPath.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Input/UIFocusController.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Input/UIInputRouter.h @@ -540,6 +547,7 @@ add_library(XCEngine STATIC ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UIKeyboardNavigationModel.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UIPropertyEditModel.h ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UISelectionModel.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/UI/Widgets/UITabStripModel.h ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIExpansionModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIKeyboardNavigationModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/UI/Widgets/UIPropertyEditModel.cpp diff --git a/engine/assets/builtin/shaders/final-color/final-color.frag.glsl b/engine/assets/builtin/shaders/final-color/final-color.frag.glsl new file mode 100644 index 00000000..891e0949 --- /dev/null +++ b/engine/assets/builtin/shaders/final-color/final-color.frag.glsl @@ -0,0 +1,65 @@ +// XC_BUILTIN_FINAL_COLOR_OPENGL_PS +#version 430 + +layout(binding = 0) uniform sampler2D uSourceColorTexture; + +layout(std140, binding = 0) uniform FinalColorConstants { + vec4 gColorScale; + vec4 gFinalColorParams; +}; + +in vec2 vTexCoord; + +layout(location = 0) out vec4 fragColor; + +float SRGBEncodeChannel(float value) { + float linear = max(value, 0.0); + return linear <= 0.0031308 + ? linear * 12.92 + : 1.055 * pow(linear, 1.0 / 2.4) - 0.055; +} + +vec3 ApplyLinearToSRGB(vec3 color) { + return vec3( + SRGBEncodeChannel(color.r), + SRGBEncodeChannel(color.g), + SRGBEncodeChannel(color.b)); +} + +vec3 ApplyNeutralToneMapping(vec3 color) { + return color / (color + vec3(1.0)); +} + +vec3 ApplyAcesToneMapping(vec3 color) { + const float a = 2.51; + const float b = 0.03; + const float c = 2.43; + const float d = 0.59; + const float e = 0.14; + return clamp((color * (a * color + b)) / (color * (c * color + d) + e), 0.0, 1.0); +} + +vec3 ApplyToneMapping(vec3 color, float toneMappingMode) { + if (toneMappingMode > 1.5) { + return ApplyAcesToneMapping(color); + } + + if (toneMappingMode > 0.5) { + return ApplyNeutralToneMapping(color); + } + + return color; +} + +void main() { + vec4 color = texture(uSourceColorTexture, vTexCoord); + color.rgb *= max(gFinalColorParams.x, 0.0); + color *= gColorScale; + color.rgb = ApplyToneMapping(color.rgb, gFinalColorParams.z); + + if (gFinalColorParams.y > 0.5) { + color.rgb = ApplyLinearToSRGB(color.rgb); + } + + fragColor = color; +} diff --git a/engine/assets/builtin/shaders/final-color/final-color.frag.vk.glsl b/engine/assets/builtin/shaders/final-color/final-color.frag.vk.glsl new file mode 100644 index 00000000..b4e595a3 --- /dev/null +++ b/engine/assets/builtin/shaders/final-color/final-color.frag.vk.glsl @@ -0,0 +1,66 @@ +// XC_BUILTIN_FINAL_COLOR_VULKAN_PS +#version 450 + +layout(set = 0, binding = 0, std140) uniform FinalColorConstants { + vec4 gColorScale; + vec4 gFinalColorParams; +}; + +layout(set = 1, binding = 0) uniform texture2D uSourceColorTexture; +layout(set = 2, binding = 0) uniform sampler uLinearClampSampler; + +layout(location = 0) in vec2 vTexCoord; + +layout(location = 0) out vec4 fragColor; + +float SRGBEncodeChannel(float value) { + float linear = max(value, 0.0); + return linear <= 0.0031308 + ? linear * 12.92 + : 1.055 * pow(linear, 1.0 / 2.4) - 0.055; +} + +vec3 ApplyLinearToSRGB(vec3 color) { + return vec3( + SRGBEncodeChannel(color.r), + SRGBEncodeChannel(color.g), + SRGBEncodeChannel(color.b)); +} + +vec3 ApplyNeutralToneMapping(vec3 color) { + return color / (color + vec3(1.0)); +} + +vec3 ApplyAcesToneMapping(vec3 color) { + const float a = 2.51; + const float b = 0.03; + const float c = 2.43; + const float d = 0.59; + const float e = 0.14; + return clamp((color * (a * color + b)) / (color * (c * color + d) + e), 0.0, 1.0); +} + +vec3 ApplyToneMapping(vec3 color, float toneMappingMode) { + if (toneMappingMode > 1.5) { + return ApplyAcesToneMapping(color); + } + + if (toneMappingMode > 0.5) { + return ApplyNeutralToneMapping(color); + } + + return color; +} + +void main() { + vec4 color = texture(sampler2D(uSourceColorTexture, uLinearClampSampler), vTexCoord); + color.rgb *= max(gFinalColorParams.x, 0.0); + color *= gColorScale; + color.rgb = ApplyToneMapping(color.rgb, gFinalColorParams.z); + + if (gFinalColorParams.y > 0.5) { + color.rgb = ApplyLinearToSRGB(color.rgb); + } + + fragColor = color; +} diff --git a/engine/assets/builtin/shaders/final-color/final-color.ps.hlsl b/engine/assets/builtin/shaders/final-color/final-color.ps.hlsl new file mode 100644 index 00000000..e6321828 --- /dev/null +++ b/engine/assets/builtin/shaders/final-color/final-color.ps.hlsl @@ -0,0 +1,65 @@ +// XC_BUILTIN_FINAL_COLOR_D3D12_PS +Texture2D gSourceColorTexture : register(t0); +SamplerState gLinearClampSampler : register(s0); + +cbuffer FinalColorConstants : register(b0) { + float4 gColorScale; + float4 gFinalColorParams; +}; + +struct PSInput { + float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; +}; + +float SRGBEncodeChannel(float value) { + const float linear = max(value, 0.0f); + return linear <= 0.0031308f + ? linear * 12.92f + : 1.055f * pow(linear, 1.0f / 2.4f) - 0.055f; +} + +float3 ApplyLinearToSRGB(float3 color) { + return float3( + SRGBEncodeChannel(color.r), + SRGBEncodeChannel(color.g), + SRGBEncodeChannel(color.b)); +} + +float3 ApplyNeutralToneMapping(float3 color) { + return color / (color + 1.0f); +} + +float3 ApplyAcesToneMapping(float3 color) { + const float a = 2.51f; + const float b = 0.03f; + const float c = 2.43f; + const float d = 0.59f; + const float e = 0.14f; + return saturate((color * (a * color + b)) / (color * (c * color + d) + e)); +} + +float3 ApplyToneMapping(float3 color, float toneMappingMode) { + if (toneMappingMode > 1.5f) { + return ApplyAcesToneMapping(color); + } + + if (toneMappingMode > 0.5f) { + return ApplyNeutralToneMapping(color); + } + + return color; +} + +float4 MainPS(PSInput input) : SV_TARGET { + float4 color = gSourceColorTexture.Sample(gLinearClampSampler, input.texcoord); + color.rgb *= max(gFinalColorParams.x, 0.0f); + color *= gColorScale; + color.rgb = ApplyToneMapping(color.rgb, gFinalColorParams.z); + + if (gFinalColorParams.y > 0.5f) { + color.rgb = ApplyLinearToSRGB(color.rgb); + } + + return color; +} diff --git a/engine/assets/builtin/shaders/final-color/final-color.shader b/engine/assets/builtin/shaders/final-color/final-color.shader new file mode 100644 index 00000000..e7c55d09 --- /dev/null +++ b/engine/assets/builtin/shaders/final-color/final-color.shader @@ -0,0 +1,31 @@ +Shader "Builtin Final Color" +{ + Properties + { + _ColorScale ("Color Scale", Color) = (1.0,1.0,1.0,1.0) + _Exposure ("Exposure", Float) = 1.0 + _OutputTransferMode ("Output Transfer Mode", Float) = 0.0 + _ToneMappingMode ("Tone Mapping Mode", Float) = 0.0 + } + SubShader + { + Pass + { + Name "FinalColor" + Tags { "LightMode" = "FinalOutput" } + Resources + { + FinalColorConstants (ConstantBuffer, 0, 0) + SourceColorTexture (Texture2D, 1, 0) + LinearClampSampler (Sampler, 2, 0) + } + HLSLPROGRAM + #pragma vertex MainVS + #pragma fragment MainPS + #pragma backend D3D12 HLSL "final-color.vs.hlsl" "final-color.ps.hlsl" vs_5_0 ps_5_0 + #pragma backend OpenGL GLSL "final-color.vert.glsl" "final-color.frag.glsl" + #pragma backend Vulkan GLSL "final-color.vert.vk.glsl" "final-color.frag.vk.glsl" + ENDHLSL + } + } +} diff --git a/engine/assets/builtin/shaders/final-color/final-color.vert.glsl b/engine/assets/builtin/shaders/final-color/final-color.vert.glsl new file mode 100644 index 00000000..708404f2 --- /dev/null +++ b/engine/assets/builtin/shaders/final-color/final-color.vert.glsl @@ -0,0 +1,16 @@ +// XC_BUILTIN_FINAL_COLOR_OPENGL_VS +#version 430 + +out vec2 vTexCoord; + +void main() { + const vec2 positions[3] = vec2[3]( + vec2(-1.0, -1.0), + vec2(-1.0, 3.0), + vec2( 3.0, -1.0) + ); + + vec2 position = positions[gl_VertexID]; + gl_Position = vec4(position, 1.0, 1.0); + vTexCoord = position * 0.5 + 0.5; +} diff --git a/engine/assets/builtin/shaders/final-color/final-color.vert.vk.glsl b/engine/assets/builtin/shaders/final-color/final-color.vert.vk.glsl new file mode 100644 index 00000000..ef33e95d --- /dev/null +++ b/engine/assets/builtin/shaders/final-color/final-color.vert.vk.glsl @@ -0,0 +1,18 @@ +// XC_BUILTIN_FINAL_COLOR_VULKAN_VS +#version 450 + +layout(location = 0) out vec2 vTexCoord; + +void main() { + const vec2 positions[3] = vec2[3]( + vec2(-1.0, -1.0), + vec2(-1.0, 3.0), + vec2( 3.0, -1.0) + ); + + vec2 position = positions[gl_VertexIndex]; + gl_Position = vec4(position, 1.0, 1.0); + vTexCoord = vec2( + position.x * 0.5 + 0.5, + 1.0 - (position.y * 0.5 + 0.5)); +} diff --git a/engine/assets/builtin/shaders/final-color/final-color.vs.hlsl b/engine/assets/builtin/shaders/final-color/final-color.vs.hlsl new file mode 100644 index 00000000..2c2b687b --- /dev/null +++ b/engine/assets/builtin/shaders/final-color/final-color.vs.hlsl @@ -0,0 +1,22 @@ +// XC_BUILTIN_FINAL_COLOR_D3D12_VS +struct VSOutput { + float4 position : SV_POSITION; + float2 texcoord : TEXCOORD0; +}; + +VSOutput MainVS(uint vertexId : SV_VertexID) { + const float2 positions[3] = { + float2(-1.0f, -1.0f), + float2(-1.0f, 3.0f), + float2( 3.0f, -1.0f) + }; + + const float2 position = positions[vertexId]; + + VSOutput output; + output.position = float4(position, 1.0f, 1.0f); + output.texcoord = float2( + position.x * 0.5f + 0.5f, + 1.0f - (position.y * 0.5f + 0.5f)); + return output; +} diff --git a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h index 706fd109..099da603 100644 --- a/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h +++ b/engine/include/XCEngine/Rendering/Execution/SceneRenderer.h @@ -45,17 +45,18 @@ public: const RenderSurface& surface); private: - void PrepareOwnedCameraPostProcessState(size_t requestCount); + void PrepareOwnedFullscreenStageState(size_t requestCount); void ResolveCameraFinalColorPolicies( std::vector& requests) const; - void AttachCameraPostProcessRequests( + void AttachFullscreenStageRequests( const RenderContext& context, std::vector& requests); SceneRenderRequestPlanner m_requestPlanner; CameraRenderer m_cameraRenderer; std::vector> m_ownedPostProcessSequences; - std::vector> m_ownedPostProcessSourceSurfaces; + std::vector> m_ownedFinalOutputSequences; + std::vector> m_ownedFullscreenStageSurfaces; }; } // namespace Rendering diff --git a/engine/include/XCEngine/Rendering/Passes/BuiltinFinalColorPass.h b/engine/include/XCEngine/Rendering/Passes/BuiltinFinalColorPass.h new file mode 100644 index 00000000..ee41b455 --- /dev/null +++ b/engine/include/XCEngine/Rendering/Passes/BuiltinFinalColorPass.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace XCEngine { +namespace RHI { +class RHIDescriptorPool; +class RHIDescriptorSet; +class RHIPipelineLayout; +class RHIPipelineState; +class RHISampler; +class RHIDevice; +} // namespace RHI + +namespace Rendering { +namespace Passes { + +class BuiltinFinalColorPass final : public RenderPass { +public: + struct OwnedDescriptorSet { + RHI::RHIDescriptorPool* pool = nullptr; + RHI::RHIDescriptorSet* set = nullptr; + }; + + explicit BuiltinFinalColorPass( + const FinalColorSettings& settings = {}, + Containers::String shaderPath = {}); + ~BuiltinFinalColorPass() override; + + const char* GetName() const override; + bool Execute(const RenderPassContext& context) override; + void Shutdown() override; + + void SetSettings(const FinalColorSettings& settings); + const FinalColorSettings& GetSettings() const; + + void SetShaderPath(const Containers::String& shaderPath); + const Containers::String& GetShaderPath() const; + +private: + bool EnsureInitialized(const RenderContext& renderContext, RHI::Format renderTargetFormat); + bool CreateResources(const RenderContext& renderContext, RHI::Format renderTargetFormat); + void DestroyResources(); + void DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet); + + Containers::String m_shaderPath; + FinalColorSettings m_settings = {}; + RHI::RHIDevice* m_device = nullptr; + RHI::RHIType m_backendType = RHI::RHIType::D3D12; + RHI::Format m_renderTargetFormat = RHI::Format::Unknown; + Resources::ResourceHandle m_shader; + RHI::RHISampler* m_sampler = nullptr; + RHI::RHIPipelineLayout* m_pipelineLayout = nullptr; + RHI::RHIPipelineState* m_pipelineState = nullptr; + OwnedDescriptorSet m_constantsSet = {}; + OwnedDescriptorSet m_textureSet = {}; + OwnedDescriptorSet m_samplerSet = {}; + RHI::RHIResourceView* m_boundSourceColorView = nullptr; +}; + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Rendering/Planning/FinalColorPassFactory.h b/engine/include/XCEngine/Rendering/Planning/FinalColorPassFactory.h new file mode 100644 index 00000000..a51ee6a3 --- /dev/null +++ b/engine/include/XCEngine/Rendering/Planning/FinalColorPassFactory.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +#include + +namespace XCEngine { +namespace Rendering { + +inline std::unique_ptr BuildFinalColorPassSequence( + const ResolvedFinalColorPolicy& finalColorPolicy) { + if (!finalColorPolicy.RequiresProcessing()) { + return nullptr; + } + + std::unique_ptr sequence = std::make_unique(); + sequence->AddPass(std::make_unique(finalColorPolicy)); + return sequence; +} + +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/include/XCEngine/Resources/BuiltinResources.h b/engine/include/XCEngine/Resources/BuiltinResources.h index cd017248..4e121450 100644 --- a/engine/include/XCEngine/Resources/BuiltinResources.h +++ b/engine/include/XCEngine/Resources/BuiltinResources.h @@ -30,6 +30,7 @@ Containers::String GetBuiltinShadowCasterShaderPath(); Containers::String GetBuiltinObjectIdShaderPath(); Containers::String GetBuiltinSkyboxShaderPath(); Containers::String GetBuiltinColorScalePostProcessShaderPath(); +Containers::String GetBuiltinFinalColorShaderPath(); Containers::String GetBuiltinDefaultPrimitiveTexturePath(); bool TryParseBuiltinPrimitiveType(const Containers::String& path, BuiltinPrimitiveType& outPrimitiveType); diff --git a/engine/src/Rendering/Execution/SceneRenderer.cpp b/engine/src/Rendering/Execution/SceneRenderer.cpp index f8f12010..9b685ef1 100644 --- a/engine/src/Rendering/Execution/SceneRenderer.cpp +++ b/engine/src/Rendering/Execution/SceneRenderer.cpp @@ -3,12 +3,40 @@ #include "Components/CameraComponent.h" #include "Rendering/Caches/FullscreenPassSurfaceCache.h" #include "Rendering/Planning/CameraPostProcessPassFactory.h" +#include "Rendering/Planning/FinalColorPassFactory.h" #include "Rendering/Planning/SceneRenderRequestUtils.h" #include "Rendering/RenderPipelineAsset.h" namespace XCEngine { namespace Rendering { +namespace { + +RenderSurface ConfigureFullscreenStageSurface( + const FullscreenPassSurfaceCache::SurfaceEntry& entry, + const RenderSurface& templateSurface, + bool copyDepthAttachment) { + RenderSurface surface = entry.surface; + if (copyDepthAttachment) { + surface.SetDepthAttachment(templateSurface.GetDepthAttachment()); + if (templateSurface.HasClearColorOverride()) { + surface.SetClearColorOverride(templateSurface.GetClearColorOverride()); + } + } + + if (templateSurface.HasCustomRenderArea()) { + surface.SetRenderArea(templateSurface.GetRenderArea()); + } else { + surface.ResetRenderArea(); + } + + surface.SetColorStateBefore(RHI::ResourceStates::Common); + surface.SetColorStateAfter(RHI::ResourceStates::PixelShaderResource); + return surface; +} + +} // namespace + SceneRenderer::SceneRenderer() = default; SceneRenderer::SceneRenderer(std::unique_ptr pipeline) @@ -37,7 +65,7 @@ std::vector SceneRenderer::BuildRenderRequests( std::vector requests = m_requestPlanner.BuildRequests(scene, overrideCamera, context, surface); ResolveCameraFinalColorPolicies(requests); - AttachCameraPostProcessRequests(context, requests); + AttachFullscreenStageRequests(context, requests); return requests; } @@ -78,17 +106,19 @@ bool SceneRenderer::Render( return Render(BuildRenderRequests(scene, overrideCamera, context, surface)); } -void SceneRenderer::PrepareOwnedCameraPostProcessState(size_t requestCount) { +void SceneRenderer::PrepareOwnedFullscreenStageState(size_t requestCount) { m_ownedPostProcessSequences.clear(); m_ownedPostProcessSequences.resize(requestCount); + m_ownedFinalOutputSequences.clear(); + m_ownedFinalOutputSequences.resize(requestCount); - if (m_ownedPostProcessSourceSurfaces.size() < requestCount) { - m_ownedPostProcessSourceSurfaces.resize(requestCount); + if (m_ownedFullscreenStageSurfaces.size() < requestCount) { + m_ownedFullscreenStageSurfaces.resize(requestCount); } for (size_t index = 0; index < requestCount; ++index) { - if (m_ownedPostProcessSourceSurfaces[index] == nullptr) { - m_ownedPostProcessSourceSurfaces[index] = std::make_unique(); + if (m_ownedFullscreenStageSurfaces[index] == nullptr) { + m_ownedFullscreenStageSurfaces[index] = std::make_unique(); } } } @@ -111,10 +141,10 @@ void SceneRenderer::ResolveCameraFinalColorPolicies( } } -void SceneRenderer::AttachCameraPostProcessRequests( +void SceneRenderer::AttachFullscreenStageRequests( const RenderContext& context, std::vector& requests) { - PrepareOwnedCameraPostProcessState(requests.size()); + PrepareOwnedFullscreenStageState(requests.size()); for (size_t index = 0; index < requests.size(); ++index) { CameraRenderRequest& request = requests[index]; @@ -124,8 +154,16 @@ void SceneRenderer::AttachCameraPostProcessRequests( continue; } - const CameraPostProcessStack& postProcessPasses = request.camera->GetPostProcessPasses(); - if (postProcessPasses.empty()) { + std::unique_ptr postProcessSequence = + BuildCameraPostProcessPassSequence(request.camera->GetPostProcessPasses()); + std::unique_ptr finalOutputSequence = + BuildFinalColorPassSequence(request.finalColorPolicy); + + const bool hasPostProcess = + postProcessSequence != nullptr && postProcessSequence->GetPassCount() > 0u; + const bool hasFinalOutput = + finalOutputSequence != nullptr && finalOutputSequence->GetPassCount() > 0u; + if (!hasPostProcess && !hasFinalOutput) { continue; } @@ -135,45 +173,54 @@ void SceneRenderer::AttachCameraPostProcessRequests( continue; } - FullscreenPassSurfaceCache* sourceSurfaceCache = m_ownedPostProcessSourceSurfaces[index].get(); - if (sourceSurfaceCache == nullptr || - !sourceSurfaceCache->EnsureSurfaces( + const size_t fullscreenSurfaceCount = hasPostProcess && hasFinalOutput ? 2u : 1u; + FullscreenPassSurfaceCache* surfaceCache = m_ownedFullscreenStageSurfaces[index].get(); + if (surfaceCache == nullptr || + !surfaceCache->EnsureSurfaces( context, request.surface.GetWidth(), request.surface.GetHeight(), colorFormat, - 1u)) { + fullscreenSurfaceCount)) { continue; } - const FullscreenPassSurfaceCache::SurfaceEntry* sourceEntry = sourceSurfaceCache->GetSurfaceEntry(0u); - if (sourceEntry == nullptr || sourceEntry->shaderResourceView == nullptr) { + const FullscreenPassSurfaceCache::SurfaceEntry* sceneColorEntry = surfaceCache->GetSurfaceEntry(0u); + if (sceneColorEntry == nullptr || sceneColorEntry->shaderResourceView == nullptr) { continue; } - std::unique_ptr postProcessSequence = - BuildCameraPostProcessPassSequence(postProcessPasses); - if (postProcessSequence == nullptr || postProcessSequence->GetPassCount() == 0u) { + const FullscreenPassSurfaceCache::SurfaceEntry* postProcessOutputEntry = + hasPostProcess && hasFinalOutput ? surfaceCache->GetSurfaceEntry(1u) : nullptr; + if (hasPostProcess && hasFinalOutput && + (postProcessOutputEntry == nullptr || postProcessOutputEntry->shaderResourceView == nullptr)) { continue; } - 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()); + + if (hasPostProcess) { + request.postProcess.sourceSurface = + ConfigureFullscreenStageSurface(*sceneColorEntry, request.surface, true); + request.postProcess.sourceColorView = sceneColorEntry->shaderResourceView; + request.postProcess.destinationSurface = + hasFinalOutput + ? ConfigureFullscreenStageSurface(*postProcessOutputEntry, request.surface, false) + : request.surface; + m_ownedPostProcessSequences[index] = std::move(postProcessSequence); + request.postProcess.passes = m_ownedPostProcessSequences[index].get(); } - 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(); + if (hasFinalOutput) { + const FullscreenPassSurfaceCache::SurfaceEntry* finalOutputSourceEntry = + hasPostProcess ? postProcessOutputEntry : sceneColorEntry; + request.finalOutput.sourceSurface = + hasPostProcess + ? request.postProcess.destinationSurface + : ConfigureFullscreenStageSurface(*sceneColorEntry, request.surface, true); + request.finalOutput.sourceColorView = finalOutputSourceEntry->shaderResourceView; + request.finalOutput.destinationSurface = request.surface; + m_ownedFinalOutputSequences[index] = std::move(finalOutputSequence); + request.finalOutput.passes = m_ownedFinalOutputSequences[index].get(); + } } } diff --git a/engine/src/Rendering/Passes/BuiltinFinalColorPass.cpp b/engine/src/Rendering/Passes/BuiltinFinalColorPass.cpp new file mode 100644 index 00000000..c4318d64 --- /dev/null +++ b/engine/src/Rendering/Passes/BuiltinFinalColorPass.cpp @@ -0,0 +1,452 @@ +#include "Rendering/Passes/BuiltinFinalColorPass.h" + +#include "Core/Asset/ResourceManager.h" +#include "Debug/Logger.h" +#include "Rendering/Detail/ShaderVariantUtils.h" +#include "Rendering/RenderSurface.h" +#include "Resources/BuiltinResources.h" +#include "RHI/RHICommandList.h" +#include "RHI/RHIDescriptorPool.h" +#include "RHI/RHIDescriptorSet.h" +#include "RHI/RHIDevice.h" +#include "RHI/RHIPipelineLayout.h" +#include "RHI/RHIPipelineState.h" +#include "RHI/RHIResourceView.h" +#include "RHI/RHISampler.h" + +#include +#include + +namespace XCEngine { +namespace Rendering { +namespace Passes { + +namespace { + +struct FinalColorConstants { + Math::Vector4 colorScale = Math::Vector4::One(); + Math::Vector4 params = Math::Vector4(1.0f, 0.0f, 0.0f, 0.0f); +}; + +const Resources::ShaderPass* FindCompatiblePass( + const Resources::Shader& shader, + Resources::ShaderBackend backend) { + const Resources::ShaderPass* finalColorPass = shader.FindPass("FinalColor"); + if (finalColorPass != nullptr && + ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, finalColorPass->name, backend)) { + return finalColorPass; + } + + if (shader.GetPassCount() > 0 && + ::XCEngine::Rendering::Detail::ShaderPassHasGraphicsVariants(shader, shader.GetPasses()[0].name, backend)) { + return &shader.GetPasses()[0]; + } + + return nullptr; +} + +RHI::GraphicsPipelineDesc CreatePipelineDesc( + RHI::RHIType backendType, + RHI::RHIPipelineLayout* pipelineLayout, + const Resources::Shader& shader, + const Containers::String& passName, + RHI::Format renderTargetFormat) { + RHI::GraphicsPipelineDesc pipelineDesc = {}; + pipelineDesc.pipelineLayout = pipelineLayout; + pipelineDesc.topologyType = static_cast(RHI::PrimitiveTopologyType::Triangle); + pipelineDesc.renderTargetCount = 1; + pipelineDesc.renderTargetFormats[0] = static_cast(renderTargetFormat); + pipelineDesc.depthStencilFormat = static_cast(RHI::Format::Unknown); + pipelineDesc.sampleCount = 1; + + pipelineDesc.rasterizerState.fillMode = static_cast(RHI::FillMode::Solid); + pipelineDesc.rasterizerState.cullMode = static_cast(RHI::CullMode::None); + pipelineDesc.rasterizerState.frontFace = static_cast(RHI::FrontFace::CounterClockwise); + pipelineDesc.rasterizerState.depthClipEnable = true; + + pipelineDesc.blendState.blendEnable = false; + pipelineDesc.blendState.srcBlend = static_cast(RHI::BlendFactor::One); + pipelineDesc.blendState.dstBlend = static_cast(RHI::BlendFactor::Zero); + pipelineDesc.blendState.srcBlendAlpha = static_cast(RHI::BlendFactor::One); + pipelineDesc.blendState.dstBlendAlpha = static_cast(RHI::BlendFactor::Zero); + pipelineDesc.blendState.blendOp = static_cast(RHI::BlendOp::Add); + pipelineDesc.blendState.blendOpAlpha = static_cast(RHI::BlendOp::Add); + pipelineDesc.blendState.colorWriteMask = static_cast(RHI::ColorWriteMask::All); + + pipelineDesc.depthStencilState.depthTestEnable = false; + pipelineDesc.depthStencilState.depthWriteEnable = false; + pipelineDesc.depthStencilState.depthFunc = static_cast(RHI::ComparisonFunc::Always); + + const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(backendType); + if (const Resources::ShaderStageVariant* vertexVariant = + shader.FindVariant(passName, Resources::ShaderType::Vertex, backend)) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*vertexVariant, pipelineDesc.vertexShader); + } + if (const Resources::ShaderStageVariant* fragmentVariant = + shader.FindVariant(passName, Resources::ShaderType::Fragment, backend)) { + ::XCEngine::Rendering::Detail::ApplyShaderStageVariant(*fragmentVariant, pipelineDesc.fragmentShader); + } + + return pipelineDesc; +} + +float ResolveExposureMultiplier(const FinalColorSettings& settings) { + return settings.exposureMode == FinalColorExposureMode::Fixed + ? std::max(0.0f, settings.exposureValue) + : 1.0f; +} + +} // namespace + +BuiltinFinalColorPass::BuiltinFinalColorPass( + const FinalColorSettings& settings, + Containers::String shaderPath) + : m_shaderPath(std::move(shaderPath)) + , m_settings(settings) { + if (m_shaderPath.Empty()) { + m_shaderPath = Resources::GetBuiltinFinalColorShaderPath(); + } +} + +BuiltinFinalColorPass::~BuiltinFinalColorPass() { + Shutdown(); +} + +const char* BuiltinFinalColorPass::GetName() const { + return "BuiltinFinalColorPass"; +} + +bool BuiltinFinalColorPass::Execute(const RenderPassContext& context) { + if (!context.renderContext.IsValid() || context.sourceColorView == nullptr) { + return false; + } + + const std::vector& colorAttachments = context.surface.GetColorAttachments(); + if (colorAttachments.empty() || colorAttachments[0] == nullptr) { + return false; + } + + const Math::RectInt renderArea = context.surface.GetRenderArea(); + if (renderArea.width <= 0 || renderArea.height <= 0) { + return false; + } + + const RHI::Format renderTargetFormat = colorAttachments[0]->GetFormat(); + if (!EnsureInitialized(context.renderContext, renderTargetFormat)) { + return false; + } + + FinalColorConstants constants = {}; + constants.colorScale = m_settings.finalColorScale; + constants.params = Math::Vector4( + ResolveExposureMultiplier(m_settings), + static_cast(m_settings.outputTransferMode), + static_cast(m_settings.toneMappingMode), + 0.0f); + m_constantsSet.set->WriteConstant(0, &constants, sizeof(constants)); + + if (m_boundSourceColorView != context.sourceColorView) { + m_textureSet.set->Update(0, context.sourceColorView); + m_boundSourceColorView = context.sourceColorView; + } + + RHI::RHICommandList* commandList = context.renderContext.commandList; + RHI::RHIResourceView* renderTarget = colorAttachments[0]; + + if (context.surface.IsAutoTransitionEnabled()) { + commandList->TransitionBarrier( + renderTarget, + context.surface.GetColorStateBefore(), + RHI::ResourceStates::RenderTarget); + } + + commandList->SetRenderTargets(1, &renderTarget, nullptr); + + const RHI::Viewport viewport = { + static_cast(renderArea.x), + static_cast(renderArea.y), + static_cast(renderArea.width), + static_cast(renderArea.height), + 0.0f, + 1.0f + }; + const RHI::Rect scissorRect = { + renderArea.x, + renderArea.y, + renderArea.x + renderArea.width, + renderArea.y + renderArea.height + }; + + commandList->SetViewport(viewport); + commandList->SetScissorRect(scissorRect); + commandList->SetPrimitiveTopology(RHI::PrimitiveTopology::TriangleList); + commandList->SetPipelineState(m_pipelineState); + + RHI::RHIDescriptorSet* descriptorSets[] = { + m_constantsSet.set, + m_textureSet.set, + m_samplerSet.set + }; + commandList->SetGraphicsDescriptorSets(0, 3, descriptorSets, m_pipelineLayout); + commandList->Draw(3, 1, 0, 0); + commandList->EndRenderPass(); + + if (context.surface.IsAutoTransitionEnabled()) { + commandList->TransitionBarrier( + renderTarget, + RHI::ResourceStates::RenderTarget, + context.surface.GetColorStateAfter()); + } + + return true; +} + +void BuiltinFinalColorPass::Shutdown() { + DestroyResources(); +} + +void BuiltinFinalColorPass::SetSettings(const FinalColorSettings& settings) { + m_settings = settings; +} + +const FinalColorSettings& BuiltinFinalColorPass::GetSettings() const { + return m_settings; +} + +void BuiltinFinalColorPass::SetShaderPath(const Containers::String& shaderPath) { + if (m_shaderPath == shaderPath) { + return; + } + + DestroyResources(); + m_shaderPath = shaderPath; +} + +const Containers::String& BuiltinFinalColorPass::GetShaderPath() const { + return m_shaderPath; +} + +bool BuiltinFinalColorPass::EnsureInitialized( + const RenderContext& renderContext, + RHI::Format renderTargetFormat) { + if (m_device == renderContext.device && + m_backendType == renderContext.backendType && + m_renderTargetFormat == renderTargetFormat && + m_pipelineLayout != nullptr && + m_pipelineState != nullptr && + m_sampler != nullptr && + m_constantsSet.set != nullptr && + m_textureSet.set != nullptr && + m_samplerSet.set != nullptr) { + return true; + } + + DestroyResources(); + return CreateResources(renderContext, renderTargetFormat); +} + +bool BuiltinFinalColorPass::CreateResources( + const RenderContext& renderContext, + RHI::Format renderTargetFormat) { + if (!renderContext.IsValid()) { + return false; + } + + if (m_shaderPath.Empty()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinFinalColorPass requires a shader path before resource creation"); + return false; + } + + m_shader = Resources::ResourceManager::Get().Load(m_shaderPath); + if (!m_shader.IsValid()) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinFinalColorPass failed to load configured final-color shader resource"); + DestroyResources(); + return false; + } + + const Resources::ShaderBackend backend = ::XCEngine::Rendering::Detail::ToShaderBackend(renderContext.backendType); + const Resources::ShaderPass* finalColorPass = FindCompatiblePass(*m_shader.Get(), backend); + if (finalColorPass == nullptr) { + Debug::Logger::Get().Error( + Debug::LogCategory::Rendering, + "BuiltinFinalColorPass could not resolve a valid FinalColor shader pass"); + DestroyResources(); + return false; + } + + m_device = renderContext.device; + m_backendType = renderContext.backendType; + m_renderTargetFormat = renderTargetFormat; + + RHI::DescriptorSetLayoutBinding constantBinding = {}; + constantBinding.binding = 0; + constantBinding.type = static_cast(RHI::DescriptorType::CBV); + constantBinding.count = 1; + constantBinding.visibility = static_cast(RHI::ShaderVisibility::All); + + RHI::DescriptorSetLayoutBinding textureBinding = {}; + textureBinding.binding = 0; + textureBinding.type = static_cast(RHI::DescriptorType::SRV); + textureBinding.count = 1; + textureBinding.visibility = static_cast(RHI::ShaderVisibility::All); + + RHI::DescriptorSetLayoutBinding samplerBinding = {}; + samplerBinding.binding = 0; + samplerBinding.type = static_cast(RHI::DescriptorType::Sampler); + samplerBinding.count = 1; + samplerBinding.visibility = static_cast(RHI::ShaderVisibility::All); + + RHI::DescriptorSetLayoutDesc constantLayout = {}; + constantLayout.bindings = &constantBinding; + constantLayout.bindingCount = 1; + + RHI::DescriptorSetLayoutDesc textureLayout = {}; + textureLayout.bindings = &textureBinding; + textureLayout.bindingCount = 1; + + RHI::DescriptorSetLayoutDesc samplerLayout = {}; + samplerLayout.bindings = &samplerBinding; + samplerLayout.bindingCount = 1; + + RHI::DescriptorSetLayoutDesc setLayouts[] = { + constantLayout, + textureLayout, + samplerLayout + }; + + RHI::RHIPipelineLayoutDesc pipelineLayoutDesc = {}; + pipelineLayoutDesc.setLayouts = setLayouts; + pipelineLayoutDesc.setLayoutCount = 3; + m_pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc); + if (m_pipelineLayout == nullptr) { + DestroyResources(); + return false; + } + + RHI::SamplerDesc samplerDesc = {}; + samplerDesc.filter = static_cast(RHI::FilterMode::Linear); + samplerDesc.addressU = static_cast(RHI::TextureAddressMode::Clamp); + samplerDesc.addressV = static_cast(RHI::TextureAddressMode::Clamp); + samplerDesc.addressW = static_cast(RHI::TextureAddressMode::Clamp); + samplerDesc.mipLodBias = 0.0f; + samplerDesc.maxAnisotropy = 1; + samplerDesc.comparisonFunc = static_cast(RHI::ComparisonFunc::Always); + samplerDesc.minLod = 0.0f; + samplerDesc.maxLod = 1000.0f; + m_sampler = m_device->CreateSampler(samplerDesc); + if (m_sampler == nullptr) { + DestroyResources(); + return false; + } + + auto createOwnedDescriptorSet = + [this](const RHI::DescriptorSetLayoutDesc& layout, + RHI::DescriptorHeapType heapType, + bool shaderVisible, + OwnedDescriptorSet& ownedSet) -> bool { + RHI::DescriptorPoolDesc poolDesc = {}; + poolDesc.type = heapType; + poolDesc.descriptorCount = 1; + poolDesc.shaderVisible = shaderVisible; + ownedSet.pool = m_device->CreateDescriptorPool(poolDesc); + if (ownedSet.pool == nullptr) { + return false; + } + + ownedSet.set = ownedSet.pool->AllocateSet(layout); + if (ownedSet.set == nullptr) { + DestroyOwnedDescriptorSet(ownedSet); + return false; + } + + return true; + }; + + if (!createOwnedDescriptorSet( + constantLayout, + RHI::DescriptorHeapType::CBV_SRV_UAV, + false, + m_constantsSet) || + !createOwnedDescriptorSet( + textureLayout, + RHI::DescriptorHeapType::CBV_SRV_UAV, + true, + m_textureSet) || + !createOwnedDescriptorSet( + samplerLayout, + RHI::DescriptorHeapType::Sampler, + true, + m_samplerSet)) { + DestroyResources(); + return false; + } + + m_samplerSet.set->UpdateSampler(0, m_sampler); + + m_pipelineState = m_device->CreatePipelineState( + CreatePipelineDesc( + m_backendType, + m_pipelineLayout, + *m_shader.Get(), + finalColorPass->name, + m_renderTargetFormat)); + if (m_pipelineState == nullptr || !m_pipelineState->IsValid()) { + DestroyResources(); + return false; + } + + return true; +} + +void BuiltinFinalColorPass::DestroyResources() { + m_boundSourceColorView = nullptr; + + if (m_pipelineState != nullptr) { + m_pipelineState->Shutdown(); + delete m_pipelineState; + m_pipelineState = nullptr; + } + + DestroyOwnedDescriptorSet(m_samplerSet); + DestroyOwnedDescriptorSet(m_textureSet); + DestroyOwnedDescriptorSet(m_constantsSet); + + if (m_pipelineLayout != nullptr) { + m_pipelineLayout->Shutdown(); + delete m_pipelineLayout; + m_pipelineLayout = nullptr; + } + + if (m_sampler != nullptr) { + m_sampler->Shutdown(); + delete m_sampler; + m_sampler = nullptr; + } + + m_shader.Reset(); + m_device = nullptr; + m_backendType = RHI::RHIType::D3D12; + m_renderTargetFormat = RHI::Format::Unknown; +} + +void BuiltinFinalColorPass::DestroyOwnedDescriptorSet(OwnedDescriptorSet& descriptorSet) { + if (descriptorSet.set != nullptr) { + descriptorSet.set->Shutdown(); + delete descriptorSet.set; + descriptorSet.set = nullptr; + } + + if (descriptorSet.pool != nullptr) { + descriptorSet.pool->Shutdown(); + delete descriptorSet.pool; + descriptorSet.pool = nullptr; + } +} + +} // namespace Passes +} // namespace Rendering +} // namespace XCEngine diff --git a/engine/src/Resources/BuiltinResources.cpp b/engine/src/Resources/BuiltinResources.cpp index 5e376d42..acefc3cf 100644 --- a/engine/src/Resources/BuiltinResources.cpp +++ b/engine/src/Resources/BuiltinResources.cpp @@ -35,6 +35,7 @@ constexpr const char* kBuiltinObjectIdShaderPath = "builtin://shaders/object-id" constexpr const char* kBuiltinSkyboxShaderPath = "builtin://shaders/skybox"; constexpr const char* kBuiltinColorScalePostProcessShaderPath = "builtin://shaders/color-scale-post-process"; +constexpr const char* kBuiltinFinalColorShaderPath = "builtin://shaders/final-color"; constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo"; constexpr float kPi = 3.14159265358979323846f; @@ -59,6 +60,8 @@ constexpr const char* kBuiltinSkyboxShaderManifestRelativePath = "engine/assets/builtin/shaders/skybox/skybox.shader"; constexpr const char* kBuiltinColorScalePostProcessShaderManifestRelativePath = "engine/assets/builtin/shaders/color-scale-post-process/color-scale-post-process.shader"; +constexpr const char* kBuiltinFinalColorShaderManifestRelativePath = + "engine/assets/builtin/shaders/final-color/final-color.shader"; Containers::String NormalizeBuiltinAssetPath(const std::filesystem::path& path) { return Containers::String(path.lexically_normal().generic_string().c_str()); @@ -143,6 +146,9 @@ const char* GetBuiltinShaderManifestRelativePath(const Containers::String& built if (builtinShaderPath == Containers::String(kBuiltinColorScalePostProcessShaderPath)) { return kBuiltinColorScalePostProcessShaderManifestRelativePath; } + if (builtinShaderPath == Containers::String(kBuiltinFinalColorShaderPath)) { + return kBuiltinFinalColorShaderManifestRelativePath; + } return nullptr; } @@ -691,6 +697,10 @@ Shader* BuildBuiltinColorScalePostProcessShader(const Containers::String& path) return TryLoadBuiltinShaderFromManifest(path); } +Shader* BuildBuiltinFinalColorShader(const Containers::String& path) { + return TryLoadBuiltinShaderFromManifest(path); +} + Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) { auto* material = new Material(); IResource::ConstructParams params; @@ -817,6 +827,10 @@ Containers::String GetBuiltinColorScalePostProcessShaderPath() { return Containers::String(kBuiltinColorScalePostProcessShaderPath); } +Containers::String GetBuiltinFinalColorShaderPath() { + return Containers::String(kBuiltinFinalColorShaderPath); +} + Containers::String GetBuiltinDefaultPrimitiveTexturePath() { return Containers::String(kBuiltinDefaultPrimitiveTexturePath); } @@ -880,10 +894,7 @@ LoadResult CreateBuiltinMeshResource(const Containers::String& path) { return LoadResult(Containers::String("Unsupported builtin mesh: ") + path); } - // The UV sphere generator already emits triangles in the runtime's front-face convention. - if (primitiveType != BuiltinPrimitiveType::Sphere) { - FlipTriangleWinding(buffers); - } + FlipTriangleWinding(buffers); Mesh* mesh = BuildMeshResource(path, GetBuiltinPrimitiveDisplayName(primitiveType), std::move(buffers)); if (mesh == nullptr) { @@ -922,6 +933,8 @@ LoadResult CreateBuiltinShaderResource(const Containers::String& path) { shader = BuildBuiltinSkyboxShader(path); } else if (path == GetBuiltinColorScalePostProcessShaderPath()) { shader = BuildBuiltinColorScalePostProcessShader(path); + } else if (path == GetBuiltinFinalColorShaderPath()) { + shader = BuildBuiltinFinalColorShader(path); } else { return LoadResult(Containers::String("Unknown builtin shader: ") + path); } diff --git a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp index d7ac9496..3b0cdfec 100644 --- a/tests/Rendering/unit/test_builtin_forward_pipeline.cpp +++ b/tests/Rendering/unit/test_builtin_forward_pipeline.cpp @@ -211,6 +211,53 @@ TEST(BuiltinForwardPipeline_Test, BuiltinSkyboxShaderDeclaresExplicitEnvironment delete shader; } +TEST(BuiltinForwardPipeline_Test, BuiltinFinalColorShaderDeclaresExplicitFullscreenResourceContract) { + ShaderLoader loader; + LoadResult result = loader.Load(GetBuiltinFinalColorShaderPath()); + ASSERT_TRUE(result); + ASSERT_NE(result.resource, nullptr); + + Shader* shader = static_cast(result.resource); + ASSERT_NE(shader, nullptr); + + const ShaderPass* pass = shader->FindPass("FinalColor"); + ASSERT_NE(pass, nullptr); + ASSERT_EQ(pass->resources.Size(), 3u); + + const ShaderPropertyDesc* colorScale = shader->FindProperty("_ColorScale"); + ASSERT_NE(colorScale, nullptr); + EXPECT_EQ(colorScale->type, ShaderPropertyType::Color); + + const ShaderPropertyDesc* exposure = shader->FindProperty("_Exposure"); + ASSERT_NE(exposure, nullptr); + EXPECT_EQ(exposure->type, ShaderPropertyType::Float); + + const ShaderPropertyDesc* outputTransferMode = shader->FindProperty("_OutputTransferMode"); + ASSERT_NE(outputTransferMode, nullptr); + EXPECT_EQ(outputTransferMode->type, ShaderPropertyType::Float); + + const ShaderPropertyDesc* toneMappingMode = shader->FindProperty("_ToneMappingMode"); + ASSERT_NE(toneMappingMode, nullptr); + EXPECT_EQ(toneMappingMode->type, ShaderPropertyType::Float); + + EXPECT_STREQ(pass->resources[0].name.CStr(), "FinalColorConstants"); + EXPECT_EQ(pass->resources[0].type, ShaderResourceType::ConstantBuffer); + EXPECT_EQ(pass->resources[0].set, 0u); + EXPECT_EQ(pass->resources[0].binding, 0u); + + EXPECT_STREQ(pass->resources[1].name.CStr(), "SourceColorTexture"); + EXPECT_EQ(pass->resources[1].type, ShaderResourceType::Texture2D); + EXPECT_EQ(pass->resources[1].set, 1u); + EXPECT_EQ(pass->resources[1].binding, 0u); + + EXPECT_STREQ(pass->resources[2].name.CStr(), "LinearClampSampler"); + EXPECT_EQ(pass->resources[2].type, ShaderResourceType::Sampler); + EXPECT_EQ(pass->resources[2].set, 2u); + EXPECT_EQ(pass->resources[2].binding, 0u); + + delete shader; +} + TEST(BuiltinForwardPipeline_Test, BuildsBuiltinPassResourceBindingPlanFromExplicitForwardResources) { ShaderLoader loader; LoadResult result = loader.Load(GetBuiltinForwardLitShaderPath()); diff --git a/tests/Rendering/unit/test_camera_scene_renderer.cpp b/tests/Rendering/unit/test_camera_scene_renderer.cpp index 29fdf9b1..97aea378 100644 --- a/tests/Rendering/unit/test_camera_scene_renderer.cpp +++ b/tests/Rendering/unit/test_camera_scene_renderer.cpp @@ -1887,6 +1887,169 @@ TEST(SceneRenderer_Test, ResolvesFinalColorPolicyFromPipelineDefaultsAndCameraOv EXPECT_FALSE(request.finalOutput.IsRequested()); } +TEST(SceneRenderer_Test, BuildsFinalOutputRequestFromResolvedFinalColorPolicy) { + Scene scene("SceneRendererFinalOutputScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetDepth(2.0f); + + FinalColorOverrideSettings cameraOverrides = {}; + cameraOverrides.overrideExposureMode = true; + cameraOverrides.exposureMode = FinalColorExposureMode::Fixed; + cameraOverrides.overrideExposureValue = true; + cameraOverrides.exposureValue = 1.6f; + cameraOverrides.overrideFinalColorScale = true; + cameraOverrides.finalColorScale = XCEngine::Math::Vector4(0.95f, 0.9f, 0.85f, 1.0f); + 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); + + auto assetState = std::make_shared(); + assetState->defaultFinalColorSettings.outputTransferMode = + FinalColorOutputTransferMode::LinearToSRGB; + + RenderContext context = CreateValidContext(); + context.device = &device; + + RenderSurface surface(800, 600); + surface.SetColorAttachment(backBufferColorView); + surface.SetDepthAttachment(depthView); + + SceneRenderer renderer(std::make_shared(assetState)); + const std::vector requests = + renderer.BuildRenderRequests(scene, nullptr, context, surface); + + ASSERT_EQ(requests.size(), 1u); + const CameraRenderRequest& request = requests[0]; + EXPECT_FALSE(request.postProcess.IsRequested()); + EXPECT_TRUE(request.finalOutput.IsRequested()); + EXPECT_TRUE(request.finalOutput.IsValid()); + ASSERT_NE(request.finalOutput.passes, nullptr); + 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.sourceSurface.GetDepthAttachment(), depthView); + 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); + ASSERT_FALSE(request.finalOutput.sourceSurface.GetColorAttachments().empty()); + EXPECT_NE(request.finalOutput.sourceSurface.GetColorAttachments()[0], backBufferColorView); + EXPECT_EQ(allocationState->createTextureCalls, 1); + EXPECT_EQ(allocationState->createRenderTargetViewCalls, 1); + EXPECT_EQ(allocationState->createShaderViewCalls, 1); + + delete depthView; + delete backBufferColorView; +} + +TEST(SceneRenderer_Test, RoutesPostProcessIntoIntermediateSurfaceBeforeFinalOutput) { + Scene scene("SceneRendererPostProcessFinalOutputScene"); + + 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->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; + cameraOverrides.overrideFinalColorScale = true; + cameraOverrides.finalColorScale = XCEngine::Math::Vector4(0.95f, 0.9f, 0.85f, 1.0f); + 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); + + 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.finalOutput.IsRequested()); + ASSERT_NE(request.postProcess.passes, nullptr); + ASSERT_NE(request.finalOutput.passes, nullptr); + EXPECT_EQ(request.postProcess.passes->GetPassCount(), 1u); + EXPECT_EQ(request.finalOutput.passes->GetPassCount(), 1u); + + ASSERT_FALSE(request.postProcess.sourceSurface.GetColorAttachments().empty()); + ASSERT_FALSE(request.postProcess.destinationSurface.GetColorAttachments().empty()); + ASSERT_FALSE(request.finalOutput.sourceSurface.GetColorAttachments().empty()); + + EXPECT_NE( + request.postProcess.sourceSurface.GetColorAttachments()[0], + request.postProcess.destinationSurface.GetColorAttachments()[0]); + EXPECT_EQ( + request.finalOutput.sourceSurface.GetColorAttachments()[0], + request.postProcess.destinationSurface.GetColorAttachments()[0]); + EXPECT_EQ(request.finalOutput.destinationSurface.GetColorAttachments()[0], backBufferColorView); + + EXPECT_EQ(request.postProcess.sourceSurface.GetDepthAttachment(), depthView); + EXPECT_EQ(request.postProcess.destinationSurface.GetDepthAttachment(), nullptr); + EXPECT_EQ(request.finalOutput.sourceSurface.GetDepthAttachment(), nullptr); + EXPECT_EQ(request.finalOutput.destinationSurface.GetDepthAttachment(), depthView); + + const XCEngine::Math::RectInt postProcessSourceArea = request.postProcess.sourceSurface.GetRenderArea(); + EXPECT_EQ(postProcessSourceArea.x, 200); + EXPECT_EQ(postProcessSourceArea.y, 75); + EXPECT_EQ(postProcessSourceArea.width, 400); + EXPECT_EQ(postProcessSourceArea.height, 375); + const XCEngine::Math::RectInt finalOutputSourceArea = request.finalOutput.sourceSurface.GetRenderArea(); + EXPECT_EQ(finalOutputSourceArea.x, 200); + EXPECT_EQ(finalOutputSourceArea.y, 75); + EXPECT_EQ(finalOutputSourceArea.width, 400); + EXPECT_EQ(finalOutputSourceArea.height, 375); + + EXPECT_NE(request.postProcess.sourceColorView, nullptr); + EXPECT_NE(request.finalOutput.sourceColorView, nullptr); + EXPECT_NE(request.postProcess.sourceColorView, request.finalOutput.sourceColorView); + EXPECT_EQ(allocationState->createTextureCalls, 2); + EXPECT_EQ(allocationState->createRenderTargetViewCalls, 2); + EXPECT_EQ(allocationState->createShaderViewCalls, 2); + + delete depthView; + delete backBufferColorView; +} + TEST(CameraRenderer_Test, UsesResolvedRenderAreaForCameraViewportDimensions) { Scene scene("CameraRendererViewportRectScene");