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);
+}