From 09737023a13fcbbb9b2156f77a00c37f808f70f9 Mon Sep 17 00:00:00 2001 From: Muzz Date: Thu, 29 Aug 2024 19:01:37 +0200 Subject: [PATCH] Add **Contrast Adaptive Sharpening (CAS)** filter pass #2423 --- Content/Shaders/CAS.flax | 3 + .../Engine/Graphics/PostProcessSettings.cpp | 4 + Source/Engine/Graphics/PostProcessSettings.h | 46 ++++++++- Source/Engine/Renderer/AntiAliasing/FXAA.cpp | 3 +- .../ContrastAdaptiveSharpeningPass.cpp | 97 +++++++++++++++++++ .../Renderer/ContrastAdaptiveSharpeningPass.h | 39 ++++++++ Source/Engine/Renderer/Renderer.cpp | 47 +++++++-- Source/Shaders/CAS.shader | 47 +++++++++ 8 files changed, 273 insertions(+), 13 deletions(-) create mode 100644 Content/Shaders/CAS.flax create mode 100644 Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.cpp create mode 100644 Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.h create mode 100644 Source/Shaders/CAS.shader diff --git a/Content/Shaders/CAS.flax b/Content/Shaders/CAS.flax new file mode 100644 index 000000000..238fbc6e9 --- /dev/null +++ b/Content/Shaders/CAS.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026dcdb363bcb61780b63255c780dee2e0a1c6934229764c5db8664d39244ecb +size 2075 diff --git a/Source/Engine/Graphics/PostProcessSettings.cpp b/Source/Engine/Graphics/PostProcessSettings.cpp index 6a243d960..738cc4505 100644 --- a/Source/Engine/Graphics/PostProcessSettings.cpp +++ b/Source/Engine/Graphics/PostProcessSettings.cpp @@ -198,6 +198,10 @@ void AntiAliasingSettings::BlendWith(AntiAliasingSettings& other, float weight) BLEND_FLOAT(TAA_Sharpness); BLEND_FLOAT(TAA_StationaryBlending); BLEND_FLOAT(TAA_MotionBlending); + BLEND_FLOAT(CAS_SharpeningAmount); + BLEND_FLOAT(CAS_EdgeSharpening); + BLEND_FLOAT(CAS_MinEdgeThreshold); + BLEND_FLOAT(CAS_OverBlurLimit); } void PostFxMaterialsSettings::BlendWith(PostFxMaterialsSettings& other, float weight) diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index 19b3cec93..5feb5dd9a 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -1858,10 +1858,30 @@ API_ENUM(Attributes="Flags") enum class AntiAliasingSettingsOverride : int32 /// TAA_MotionBlending = 1 << 4, + /// + /// Overrides property. + /// + CAS_SharpeningAmount = 1 << 5, + + /// + /// Overrides property. + /// + CAS_EdgeSharpening = 1 << 6, + + /// + /// Overrides property. + /// + CAS_MinEdgeThreshold = 1 << 7, + + /// + /// Overrides property. + /// + CAS_OverBlurLimit = 1 << 8, + /// /// All properties. /// - All = Mode | TAA_JitterSpread | TAA_Sharpness | TAA_StationaryBlending | TAA_MotionBlending, + All = Mode | TAA_JitterSpread | TAA_Sharpness | TAA_StationaryBlending | TAA_MotionBlending | CAS_SharpeningAmount | CAS_EdgeSharpening | CAS_MinEdgeThreshold | CAS_OverBlurLimit, }; /// @@ -1909,6 +1929,30 @@ API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable API_FIELD(Attributes="Limit(0, 0.99f, 0.001f), EditorOrder(4), PostProcessSetting((int)AntiAliasingSettingsOverride.TAA_MotionBlending), EditorDisplay(null, \"TAA Motion Blending\"), VisibleIf(nameof(ShowTAASettings))") float TAA_MotionBlending = 0.85f; + /// + /// The sharpening strength for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. + /// + API_FIELD(Attributes = "Limit(0, 10f, 0.001f), EditorOrder(10), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_SharpeningAmount), EditorDisplay(null, \"CAS Sharpening Amount\"), VisibleIf(nameof(ShowTAASettings), true)") + float CAS_SharpeningAmount = 0.0f; + + /// + /// The edge sharpening strength for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. + /// + API_FIELD(Attributes = "Limit(0, 10f, 0.001f), EditorOrder(11), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_EdgeSharpening), EditorDisplay(null, \"CAS Edge Sharpening\"), VisibleIf(nameof(ShowTAASettings), true)") + float CAS_EdgeSharpening = 0.5f; + + /// + /// The minimum edge threshold for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. + /// + API_FIELD(Attributes = "Limit(0, 10f, 0.001f), EditorOrder(12), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_MinEdgeThreshold), EditorDisplay(null, \"CAS Min Edge Threshold\"), VisibleIf(nameof(ShowTAASettings), true)") + float CAS_MinEdgeThreshold = 0.03f; + + /// + /// The over-blur limit for the Contrast Adaptive Sharpening (CAS) pass. Ignored when using TAA that contains own contrast filter. + /// + API_FIELD(Attributes = "Limit(0, 100f, 0.001f), EditorOrder(13), PostProcessSetting((int)AntiAliasingSettingsOverride.CAS_OverBlurLimit), EditorDisplay(null, \"CAS Over-blur Limit\"), VisibleIf(nameof(ShowTAASettings), true)") + float CAS_OverBlurLimit = 1.0f; + public: /// /// Blends the settings using given weight. diff --git a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp index 57da136c1..83fae260f 100644 --- a/Source/Engine/Renderer/AntiAliasing/FXAA.cpp +++ b/Source/Engine/Renderer/AntiAliasing/FXAA.cpp @@ -65,10 +65,10 @@ void FXAA::Dispose() void FXAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output) { auto context = GPUDevice::Instance->GetMainContext(); + context->SetRenderTarget(output); if (checkIfSkipPass()) { // Resources are missing. Do not perform rendering, just copy input frame. - context->SetRenderTarget(output); context->Draw(input); return; } @@ -83,7 +83,6 @@ void FXAA::Render(RenderContext& renderContext, GPUTexture* input, GPUTextureVie context->BindSR(0, input); // Render - context->SetRenderTarget(output); const auto qualityLevel = Math::Clamp(static_cast(Graphics::AAQuality), 0, static_cast(Quality::MAX) - 1); context->SetState(_psFXAA.Get(qualityLevel)); context->DrawFullscreenTriangle(); diff --git a/Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.cpp b/Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.cpp new file mode 100644 index 000000000..2d93d525d --- /dev/null +++ b/Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.cpp @@ -0,0 +1,97 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "ContrastAdaptiveSharpeningPass.h" +#include "RenderList.h" +#include "Engine/Content/Assets/Shader.h" +#include "Engine/Content/Content.h" +#include "Engine/Graphics/GPUContext.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/Shaders/GPUShader.h" + +GPU_CB_STRUCT(Data { + Float2 InputSizeInv; + Float2 Padding; + float SharpeningAmount; + float EdgeSharpening; + float MinEdgeThreshold; + float OverBlurLimit; + }); + +String ContrastAdaptiveSharpeningPass::ToString() const +{ + return TEXT("ContrastAdaptiveSharpening"); +} + +void ContrastAdaptiveSharpeningPass::Dispose() +{ + RendererPass::Dispose(); + + SAFE_DELETE_GPU_RESOURCE(_psCAS); + _shader = nullptr; +} + +bool ContrastAdaptiveSharpeningPass::setupResources() +{ + // Lazy-load shader + if (!_shader) + { + _shader = Content::LoadAsyncInternal(TEXT("Shaders/CAS")); + if (!_shader) + return false; +#if COMPILE_WITH_DEV_ENV + _shader.Get()->OnReloading.Bind(this); +#endif + } + if (!_shader->IsLoaded()) + return true; + const auto shader = _shader->GetShader(); + + // Validate shader constant buffer size + if (shader->GetCB(0)->GetSize() != sizeof(Data)) + { + REPORT_INVALID_SHADER_PASS_CB_SIZE(shader, 0, Data); + return true; + } + + // Create pipeline stage + auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + psDesc.PS = shader->GetPS("PS_CAS"); + _psCAS = GPUDevice::Instance->CreatePipelineState(); + if (_psCAS->Init(psDesc)) + return true; + + return false; +} + +bool ContrastAdaptiveSharpeningPass::CanRender(const RenderContext& renderContext) +{ + const AntiAliasingSettings& antiAliasing = renderContext.List->Settings.AntiAliasing; + return EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::AntiAliasing) && + antiAliasing.CAS_SharpeningAmount > ZeroTolerance && + !checkIfSkipPass(); +} + +void ContrastAdaptiveSharpeningPass::Render(const RenderContext& renderContext, GPUTexture* input, GPUTextureView* output) +{ + ASSERT_LOW_LAYER(CanRender(renderContext)); + PROFILE_GPU_CPU("Contrast Adaptive Sharpening"); + auto device = GPUDevice::Instance; + auto context = device->GetMainContext(); + const AntiAliasingSettings& antiAliasing = renderContext.List->Settings.AntiAliasing; + + Data data; + data.InputSizeInv = Float2::One / input->Size(); + data.SharpeningAmount = antiAliasing.CAS_SharpeningAmount; + data.EdgeSharpening = antiAliasing.CAS_EdgeSharpening; + data.MinEdgeThreshold = antiAliasing.CAS_MinEdgeThreshold; + data.OverBlurLimit = antiAliasing.CAS_OverBlurLimit; + const auto cb = _shader->GetShader()->GetCB(0); + context->UpdateCB(cb, &data); + context->BindCB(0, cb); + context->BindSR(0, input); + context->SetState(_psCAS); + context->SetRenderTarget(output); + context->DrawFullscreenTriangle(); +} diff --git a/Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.h b/Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.h new file mode 100644 index 000000000..8a449f6cd --- /dev/null +++ b/Source/Engine/Renderer/ContrastAdaptiveSharpeningPass.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "RendererPass.h" +#include "Engine/Graphics/PostProcessSettings.h" + +/// +/// Contrast Adaptive Sharpening (CAS) provides a mixed ability to sharpen and optionally scale an image. Based on AMD FidelityFX implementation. +/// +class ContrastAdaptiveSharpeningPass : public RendererPass +{ +private: + bool _loadShader = true; + AssetReference _shader; + GPUPipelineState* _psCAS = nullptr; + +public: + bool CanRender(const RenderContext& renderContext); + void Render(const RenderContext& renderContext, GPUTexture* input, GPUTextureView* output); + +private: +#if COMPILE_WITH_DEV_ENV + void OnShaderReloading(Asset* obj) + { + _psCAS->ReleaseGPU(); + invalidateResources(); + } +#endif + +public: + // [RendererPass] + String ToString() const override; + void Dispose() override; + +protected: + // [RendererPass] + bool setupResources() override; +}; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 13921e079..c1a87385f 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -22,6 +22,7 @@ #include "VolumetricFogPass.h" #include "HistogramPass.h" #include "AtmospherePreCompute.h" +#include "ContrastAdaptiveSharpeningPass.h" #include "GlobalSignDistanceFieldPass.h" #include "GI/GlobalSurfaceAtlasPass.h" #include "GI/DynamicDiffuseGlobalIllumination.h" @@ -126,21 +127,47 @@ void RendererService::Dispose() void RenderAntiAliasingPass(RenderContext& renderContext, GPUTexture* input, GPUTextureView* output, const Viewport& outputViewport) { auto context = GPUDevice::Instance->GetMainContext(); - context->SetViewportAndScissors(outputViewport); const auto aaMode = renderContext.List->Settings.AntiAliasing.Mode; - if (aaMode == AntialiasingMode::FastApproximateAntialiasing) + if (ContrastAdaptiveSharpeningPass::Instance()->CanRender(renderContext)) { - FXAA::Instance()->Render(renderContext, input, output); - } - else if (aaMode == AntialiasingMode::SubpixelMorphologicalAntialiasing) - { - SMAA::Instance()->Render(renderContext, input, output); + if (aaMode == AntialiasingMode::FastApproximateAntialiasing || + aaMode == AntialiasingMode::SubpixelMorphologicalAntialiasing) + { + // AA -> CAS -> Output + auto tmpImage = RenderTargetPool::Get(input->GetDescription()); + RENDER_TARGET_POOL_SET_NAME(tmpImage, "TmpImage"); + context->SetViewportAndScissors((float)input->Width(), (float)input->Height()); + if (aaMode == AntialiasingMode::FastApproximateAntialiasing) + FXAA::Instance()->Render(renderContext, input, tmpImage->View()); + else + SMAA::Instance()->Render(renderContext, input, tmpImage->View()); + context->ResetSR(); + context->ResetRenderTarget(); + context->SetViewportAndScissors(outputViewport); + ContrastAdaptiveSharpeningPass::Instance()->Render(renderContext, tmpImage, output); + RenderTargetPool::Release(tmpImage); + } + else + { + // CAS -> Output + context->SetViewportAndScissors(outputViewport); + ContrastAdaptiveSharpeningPass::Instance()->Render(renderContext, input, output); + } } else { - PROFILE_GPU("Copy frame"); - context->SetRenderTarget(output); - context->Draw(input); + // AA -> Output + context->SetViewportAndScissors(outputViewport); + if (aaMode == AntialiasingMode::FastApproximateAntialiasing) + FXAA::Instance()->Render(renderContext, input, output); + else if (aaMode == AntialiasingMode::SubpixelMorphologicalAntialiasing) + SMAA::Instance()->Render(renderContext, input, output); + else + { + PROFILE_GPU("Copy frame"); + context->SetRenderTarget(output); + context->Draw(input); + } } } diff --git a/Source/Shaders/CAS.shader b/Source/Shaders/CAS.shader new file mode 100644 index 000000000..93036107a --- /dev/null +++ b/Source/Shaders/CAS.shader @@ -0,0 +1,47 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#include "./Flax/Common.hlsl" + +META_CB_BEGIN(0, Data) +float2 InputSizeInv; +float2 Padding; +float SharpeningAmount; +float EdgeSharpening; +float MinEdgeThreshold; +float OverBlurLimit; +META_CB_END + +Texture2D Input : register(t0); + +// Pixel Shader for Contrast Adaptive Sharpening (CAS) filter. Based on AMD FidelityFX implementation. +META_PS(true, FEATURE_LEVEL_ES2) +float4 PS_CAS(Quad_VS2PS input) : SV_Target0 +{ + // Sample the color texture + float4 color = Input.SampleLevel(SamplerLinearClamp, input.TexCoord, 0); + + // Sample neighboring pixels + float3 blurred = color.rgb; + float3 edges = 0.0; + for (int x = -2; x <= 2; x++) + { + for (int y = -2; y <= 2; y++) + { + float2 uv = float2(x, y) * InputSizeInv + input.TexCoord; + float3 neighbor = Input.SampleLevel(SamplerLinearClamp, uv, 0).rgb; + blurred += neighbor; + edges += abs(neighbor - color.rgb); + } + } + blurred /= 25.0; + edges /= 25.0; + + // Sharpen based on edge detection + float edgeAmount = saturate((dot(edges, edges) - MinEdgeThreshold) / (0.001 + dot(edges, edges))); + float sharpen = (1.0 - edgeAmount) * SharpeningAmount + edgeAmount * EdgeSharpening; + float3 sharpened = color.rgb + (color.rgb - blurred) * sharpen; + + // Limit sharpening to avoid over-blurring + sharpened = lerp(color.rgb, sharpened, saturate(OverBlurLimit / (OverBlurLimit + dot(abs(sharpened - color.rgb), float3(1.0, 1.0, 1.0))))); + return float4(sharpened, color.a); +}