Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

916 lines
43 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SlatePostProcessor.h"
#include "SlateShaders.h"
#include "RendererUtils.h"
#include "Interfaces/SlateRHIRenderingPolicyInterface.h"
//////////////////////////////////////////////////////////////////////////
CSV_DECLARE_CATEGORY_MODULE_EXTERN(SLATECORE_API, Slate);
static int32 SlatePostBlurDualKawaseFilterEnable = 1;
static float SlatePostBlurStrengthOverride = 0.f;
FAutoConsoleVariableRef CVarSlatePostBlurUseDualKawaseFilter(
TEXT("UI.SlatePostBlurUseDualKawaseFilter"),
SlatePostBlurDualKawaseFilterEnable,
TEXT("Toggles between the old Gaussian blur implementation (0) and the new optimized Dual Kawase filter blur implementation (1)"));
FAutoConsoleVariableRef CVarSlatePostBlurStrengthOverride(
TEXT("UI.SlatePostBlurStrengthOverride"),
SlatePostBlurStrengthOverride,
TEXT("Globally overrides the blur strength for all Slate post process blurs (0 = no override)"));
float GetSlateHDRUILevel()
{
static auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.HDR.UI.Level"));
return CVar ? CVar->GetFloat() : 1.0f;
}
float GetSlateHDRUILuminance()
{
static auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.HDR.UI.Luminance"));
return CVar ? CVar->GetFloat() : 300.0f;
}
int GetSlateHDRUICompositeEOTF()
{
static auto CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.HDR.UI.CompositeEOTF"));
return CVar ? CVar->GetInt() : 0;
}
ETextureCreateFlags GetSlateTransientRenderTargetFlags()
{
return ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::ShaderResource | ETextureCreateFlags::FastVRAM
// Avoid fast clear metadata when this flag is set, since we'd otherwise have to clear transient render targets instead of discard.
#if PLATFORM_REQUIRES_TYPELESS_RESOURCE_DISCARD_WORKAROUND
| ETextureCreateFlags::NoFastClear
#endif
;
}
ETextureCreateFlags GetSlateTransientDepthStencilFlags()
{
return ETextureCreateFlags::DepthStencilTargetable | ETextureCreateFlags::FastVRAM;
}
//////////////////////////////////////////////////////////////////////////
// Pixel shader to composite UI over HDR buffer prior to doing a blur
class FCompositeHDRForBlurPS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FCompositeHDRForBlurPS);
SHADER_USE_PARAMETER_STRUCT(FCompositeHDRForBlurPS, FGlobalShader);
class FUseSRGBEncoding : SHADER_PERMUTATION_BOOL("SCRGB_ENCODING");
using FPermutationDomain = TShaderPermutationDomain<FUseSRGBEncoding>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, UITexture)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, UIWriteMaskTexture)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, UISampler)
SHADER_PARAMETER(float, UILevel)
SHADER_PARAMETER(float, UILuminance)
SHADER_PARAMETER(int32, UICompositeEOTF)
SHADER_PARAMETER(FVector2f, UITextureSize)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) && (RHISupportsGeometryShaders(Parameters.Platform) || RHISupportsVertexShaderLayer(Parameters.Platform));
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("COMPOSITE_UI_FOR_BLUR_PS"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FCompositeHDRForBlurPS, "/Engine/Private/CompositeUIPixelShader.usf", "CompositeUIForBlur", SF_Pixel);
struct FSlateCompositeHDRForBlurPassInputs
{
FIntRect InputRect;
FRDGTexture* InputCompositeTexture;
FRDGTexture* InputTexture;
FIntPoint OutputExtent;
};
FScreenPassTexture AddSlateCompositeHDRForBlurPass(FRDGBuilder& GraphBuilder, const FSlateCompositeHDRForBlurPassInputs& Inputs)
{
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel;
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(FeatureLevel);
FRDGTexture* UIWriteMaskTexture = nullptr;
if (RHISupportsRenderTargetWriteMask(GMaxRHIShaderPlatform))
{
FRenderTargetWriteMask::Decode(GraphBuilder, ShaderMap, MakeArrayView({ Inputs.InputCompositeTexture }), UIWriteMaskTexture, TexCreate_None, TEXT("UIRTWriteMask"));
}
FScreenPassRenderTarget Output(
GraphBuilder.CreateTexture(
FRDGTextureDesc::Create2D(Inputs.OutputExtent, PF_FloatR11G11B10, FClearValueBinding::Black, GetSlateTransientRenderTargetFlags()),
TEXT("CompositeHDRUI")),
ERenderTargetLoadAction::ENoAction);
const FScreenPassTextureViewport InputViewport = FScreenPassTextureViewport(Inputs.InputCompositeTexture, Inputs.InputRect);
const FScreenPassTextureViewport OutputViewport = FScreenPassTextureViewport(Output);
FCompositeHDRForBlurPS::FPermutationDomain PermutationVector;
PermutationVector.Set<FCompositeHDRForBlurPS::FUseSRGBEncoding>(Inputs.InputTexture->Desc.Format == PF_FloatRGBA);
FCompositeHDRForBlurPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FCompositeHDRForBlurPS::FParameters>();
PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding();
PassParameters->SceneTexture = Inputs.InputTexture;
PassParameters->UITexture = Inputs.InputCompositeTexture;
PassParameters->UIWriteMaskTexture = UIWriteMaskTexture;
PassParameters->UISampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
PassParameters->UITextureSize = InputViewport.Extent;
PassParameters->UILevel = GetSlateHDRUILevel();
PassParameters->UILuminance = GetSlateHDRUILuminance();
PassParameters->UICompositeEOTF = GetSlateHDRUICompositeEOTF();
TShaderMapRef<FCompositeHDRForBlurPS> PixelShader(ShaderMap, PermutationVector);
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("CompositeHDR"), FeatureLevel, OutputViewport, InputViewport, PixelShader, PassParameters);
return Output;
}
//////////////////////////////////////////////////////////////////////////
class FSlatePostProcessDirectResamplePS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FSlatePostProcessDirectResamplePS);
SHADER_USE_PARAMETER_STRUCT(FSlatePostProcessDirectResamplePS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ElementTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, ElementTextureSampler)
SHADER_PARAMETER(FVector4f, UVBounds)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FSlatePostProcessDirectResamplePS, "/Engine/Private/SlatePostProcessPixelShader.usf", "Resample1Main", SF_Pixel);
class FSlatePostProcessResample2x2PS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FSlatePostProcessResample2x2PS);
SHADER_USE_PARAMETER_STRUCT(FSlatePostProcessResample2x2PS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ElementTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, ElementTextureSampler)
SHADER_PARAMETER(FVector4f, ShaderParams)
SHADER_PARAMETER(FVector4f, UVBounds)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FSlatePostProcessResample2x2PS, "/Engine/Private/SlatePostProcessPixelShader.usf", "Resample2x2Main", SF_Pixel);
struct FSlatePostProcessDownsamplePassInputs
{
FScreenPassTexture InputTexture;
FIntPoint OutputExtent;
uint32 Downscale;
};
FScreenPassTexture AddSlatePostProcessDownsamplePass(FRDGBuilder& GraphBuilder, const FSlatePostProcessDownsamplePassInputs& Inputs)
{
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel;
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(FeatureLevel);
const FScreenPassRenderTarget Output(
GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(Inputs.OutputExtent, Inputs.InputTexture.Texture->Desc.Format, FClearValueBinding::None, GetSlateTransientRenderTargetFlags()), TEXT("DownsampleUI")),
ERenderTargetLoadAction::ENoAction);
const FScreenPassTextureViewport InputViewport(Inputs.InputTexture);
// This ensures that if input resolution is not divisible by the Downscale, we actually scale exactly Downscale x Downscale pixels into one and pad the last row and column rather than just fitting to the rounded dimensions.
const FScreenPassTextureViewport ProportionalInputViewport(Inputs.InputTexture.Texture, FIntRect(Inputs.InputTexture.ViewRect.Min, Inputs.InputTexture.ViewRect.Min + FIntPoint(Inputs.Downscale) * Inputs.OutputExtent));
// Input parameters are still computed from actual InputViewport, otherwise pixels from outside the viewport get blended into the last row and column.
const FScreenPassTextureViewportParameters InputParameters = GetScreenPassTextureViewportParameters(InputViewport);
const FScreenPassTextureViewport OutputViewport(Output);
if (Inputs.Downscale <= 2) // Only take 1 sample for 2x downscale
{
TShaderMapRef<FSlatePostProcessDirectResamplePS> PixelShader(ShaderMap);
FSlatePostProcessDirectResamplePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSlatePostProcessDirectResamplePS::FParameters>();
PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding();
PassParameters->ElementTexture = Inputs.InputTexture.Texture;
PassParameters->ElementTextureSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
PassParameters->UVBounds = FVector4f(InputParameters.UVViewportBilinearMin, InputParameters.UVViewportBilinearMax);
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("DownsampleUI"), FeatureLevel, OutputViewport, ProportionalInputViewport, PixelShader, PassParameters);
}
else // 4 samples for >2x downscale (not enough for >4x !)
{
const float OffsetFactor = Inputs.Downscale == 3 ? 2.f/3.f : 1.f;
TShaderMapRef<FSlatePostProcessResample2x2PS> PixelShader(ShaderMap);
FSlatePostProcessResample2x2PS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSlatePostProcessResample2x2PS::FParameters>();
PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding();
PassParameters->ElementTexture = Inputs.InputTexture.Texture;
PassParameters->ElementTextureSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
PassParameters->ShaderParams = FVector4f(OffsetFactor * InputParameters.ExtentInverse.X, OffsetFactor * InputParameters.ExtentInverse.Y, 0.0f, 0.0f);
PassParameters->UVBounds = FVector4f(InputParameters.UVViewportBilinearMin, InputParameters.UVViewportBilinearMax);
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("DownsampleUI"), FeatureLevel, OutputViewport, ProportionalInputViewport, PixelShader, PassParameters);
}
return Output;
}
//////////////////////////////////////////////////////////////////////////
enum class ESlatePostProcessUpsampleOutputFormat
{
SDR = 0,
HDR_SCRGB,
HDR_PQ10,
MAX
};
class FSlatePostProcessUpsamplePS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FSlatePostProcessUpsamplePS);
SHADER_USE_PARAMETER_STRUCT(FSlatePostProcessUpsamplePS, FGlobalShader);
class FUpsampleOutputFormat : SHADER_PERMUTATION_ENUM_CLASS("UPSAMPLE_OUTPUT_FORMAT", ESlatePostProcessUpsampleOutputFormat);
using FPermutationDomain = TShaderPermutationDomain<FUpsampleOutputFormat>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ElementTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, ElementTextureSampler)
SHADER_PARAMETER(FVector4f, ShaderParams)
SHADER_PARAMETER(FVector4f, ShaderParams2)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FSlatePostProcessUpsamplePS, "/Engine/Private/SlatePostProcessPixelShader.usf", "UpsampleMain", SF_Pixel);
struct FSlatePostProcessUpsampleInputs
{
FScreenPassTexture InputTexture;
FRDGTexture* OutputTextureToClear = nullptr;
FRDGTexture* OutputTexture = nullptr;
ERenderTargetLoadAction OutputLoadAction = ERenderTargetLoadAction::ELoad;
const FSlateClippingOp* ClippingOp = nullptr;
const FDepthStencilBinding* ClippingStencilBinding = nullptr;
FIntRect ClippingElementsViewRect;
FIntRect OutputRect;
FVector4f CornerRadius = FVector4f::Zero();
};
void AddSlatePostProcessUpsamplePass(FRDGBuilder& GraphBuilder, const FSlatePostProcessUpsampleInputs& Inputs)
{
FSlatePostProcessUpsamplePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSlatePostProcessUpsamplePS::FParameters>();
PassParameters->RenderTargets[0] = FRenderTargetBinding(Inputs.OutputTexture, Inputs.OutputLoadAction);
if (Inputs.ClippingStencilBinding)
{
PassParameters->RenderTargets.DepthStencil = *Inputs.ClippingStencilBinding;
}
ESlatePostProcessUpsampleOutputFormat OutputFormat = ESlatePostProcessUpsampleOutputFormat::SDR;
if (Inputs.OutputTextureToClear)
{
OutputFormat = Inputs.OutputTexture->Desc.Format == PF_FloatRGBA
? ESlatePostProcessUpsampleOutputFormat::HDR_SCRGB
: ESlatePostProcessUpsampleOutputFormat::HDR_PQ10;
PassParameters->RenderTargets[1] = FRenderTargetBinding(Inputs.OutputTextureToClear, ERenderTargetLoadAction::ELoad);
}
FSlatePostProcessUpsamplePS::FPermutationDomain PermutationVector;
PermutationVector.Set<FSlatePostProcessUpsamplePS::FUpsampleOutputFormat>(OutputFormat);
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel;
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef<FScreenPassVS> VertexShader(ShaderMap);
TShaderMapRef<FSlatePostProcessUpsamplePS> PixelShader(ShaderMap, PermutationVector);
const FScreenPassTextureViewport InputViewport(Inputs.InputTexture);
const FScreenPassTextureViewport OutputViewport(Inputs.OutputTexture, Inputs.OutputRect);
const FScreenPassTextureViewportParameters InputParameters = GetScreenPassTextureViewportParameters(InputViewport);
PassParameters->ElementTexture = Inputs.InputTexture.Texture;
PassParameters->ElementTextureSampler = Inputs.InputTexture.ViewRect == Inputs.OutputRect
? TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI()
: TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->ShaderParams = FVector4f(InputParameters.ViewportSize, InputParameters.UVViewportSize);
PassParameters->ShaderParams2 = Inputs.CornerRadius;
FRHIBlendState* BlendState = Inputs.CornerRadius == FVector4f::Zero() ? TStaticBlendState<>::GetRHI() : TStaticBlendState<CW_RGBA, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha, BO_Add, BF_One, BF_InverseSourceAlpha>::GetRHI();
FScreenPassPipelineState PipelineState(VertexShader, PixelShader, BlendState);
GetSlateClippingPipelineState(Inputs.ClippingOp, PipelineState.DepthStencilState, PipelineState.StencilRef);
GraphBuilder.AddPass(
RDG_EVENT_NAME("Upsample"),
PassParameters,
ERDGPassFlags::Raster,
[OutputViewport, InputViewport, ClippingElementsViewRect = Inputs.ClippingElementsViewRect, PipelineState, PixelShader, ClippingOp = Inputs.ClippingOp, PassParameters](FRDGAsyncTask, FRHICommandList& RHICmdList)
{
if (ClippingOp && ClippingOp->Method == EClippingMethod::Stencil)
{
// Stencil clipping quads have their own viewport.
RHICmdList.SetViewport(ClippingElementsViewRect.Min.X, ClippingElementsViewRect.Min.Y, 0.0f, ClippingElementsViewRect.Max.X, ClippingElementsViewRect.Max.Y, 1.0f);
// Stencil clipping will issue its own draw calls.
SetSlateClipping(RHICmdList, ClippingOp, ClippingElementsViewRect);
}
RHICmdList.SetViewport(OutputViewport.Rect.Min.X, OutputViewport.Rect.Min.Y, 0.0f, OutputViewport.Rect.Max.X, OutputViewport.Rect.Max.Y, 1.0f);
if (ClippingOp && ClippingOp->Method == EClippingMethod::Scissor)
{
SetSlateClipping(RHICmdList, ClippingOp, ClippingElementsViewRect);
}
SetScreenPassPipelineState(RHICmdList, PipelineState);
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters);
DrawScreenPass_PostSetup(RHICmdList, FScreenPassViewInfo(), OutputViewport, InputViewport, PipelineState, EScreenPassDrawFlags::None);
});
}
//////////////////////////////////////////////////////////////////////////
class FSlatePostProcessOptimizedKawaseUpsamplePS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FSlatePostProcessOptimizedKawaseUpsamplePS);
SHADER_USE_PARAMETER_STRUCT(FSlatePostProcessOptimizedKawaseUpsamplePS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ElementTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, ElementTextureSampler)
SHADER_PARAMETER(FVector4f, ShaderParams)
SHADER_PARAMETER(FVector4f, ShaderParams2)
SHADER_PARAMETER(FVector4f, UVBounds)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FSlatePostProcessOptimizedKawaseUpsamplePS, "/Engine/Private/SlatePostProcessPixelShader.usf", "OptimizedKawaseUpsampleMain", SF_Pixel);
struct FSlateKawaseBlurUpsampleParameters
{
float NearSampleWeight;
float FarSampleWeight;
float NearSampleOffset;
float FarSampleOffset;
FVector2f SideSampleOffsets;
};
struct FSlateKawaseBlurInternalConfiguration
{
// The number of downsample & upsample stages.
uint32 NumDownsampleLevels;
// The number of same-size Kawase passes at the lowest downsample level.
uint32 LowestLevelSteps;
// The sample offset (in source pixels) for downsample passes. Also used for the first same-size pass if NumDownsampleLevels == 0 so that it can be different from the second pass.
float DownsampleOffset;
// The sample offset for same-size Kawase passes at the lowest downsample level.
float ResampleOffset;
// Parameters for upsample passes.
FSlateKawaseBlurUpsampleParameters UpsampleParameters;
};
FSlateKawaseBlurUpsampleParameters GetSlateKawaseBlurUpsampleParameters(float Sigma)
{
if (Sigma < 1.f) // Upsample pass is only done when Sigma > 1.25
{
return FSlateKawaseBlurUpsampleParameters();
}
// Function approximation from optimized datapoints - all coefficients algorithmically generated
const float X = Sigma;
const float X2 = X*X;
FSlateKawaseBlurUpsampleParameters Parameters;
if (Sigma >= 1.543f)
{
Parameters.NearSampleWeight = 0.258865f + 0.804972f * FMath::Exp(-0.035687f*X2 - 0.949113f*X + 0.065214f);
Parameters.FarSampleWeight = 0.234567f - 5.067121f * FMath::Exp(-0.194387f*X2 - 0.205952f*X - 2.948161f);
Parameters.NearSampleOffset = 0.908440f + 4.887250f/(-3.585551f*X2 + 3.686305f*X - 6.595607f);
Parameters.SideSampleOffsets.X = 0.901689f + 1.158662f/(-1.046629f*X2 + 1.466162f*X - 2.044516f);
}
else
{
Parameters.NearSampleWeight = 0.975216f - 0.713882f * FMath::Sqrt(-0.250094f*X2 + 1.061462f*X - 0.483218f);
Parameters.FarSampleWeight = -0.049043f*X2 + 0.222989f*X - 0.114601f;
Parameters.NearSampleOffset = X > 1.25f ? FMath::Max(0.171405f + 0.268551f * FMath::Sqrt(3.329612f*X2 - 7.222064f*X + 3.883447f), 0.25f) : 0.25f;
Parameters.SideSampleOffsets.X = FMath::Max(0.957531f - 2.539860f/(2.309942f*X2 - 2.981188f*X + 3.588718f), 0.25f);
}
Parameters.SideSampleOffsets.Y = FMath::Min(-1.420319f - 1.375146f/(-0.831627f*X2 + 1.249141f*X - 1.997956f), -0.75f);
Parameters.FarSampleOffset = FMath::Min(-1.408462f - 1.766000f/(-1.348784f*X2 + 2.441929f*X - 3.247581f), -0.75f);
return Parameters;
}
static FVector2f GetSlateKawaseTwoFullResolutionPassesOffsets(float Sigma) {
// Function approximation from optimized datapoints - all coefficients algorithmically generated
const float X = Sigma;
const float X2 = X*X;
const float X3 = X*X2;
const float SqrtX = FMath::Sqrt(X);
FVector2f Offsets(0.f);
if (X >= .9125f && X <= 1.18f) // Middle section
{
Offsets = FVector2f(.611335f - 1.199396f * FMath::Exp(1.f / (2.318341f*X3 - 5.224627f*X2 + 2.320031f*X + 4.521828f*SqrtX - 4.418420f)));
}
else if (X < 1.f)
{
if (X < .8f)
{
if (X >= .25f)
{
Offsets[0] = 2.581554f * FMath::Exp(1.f / (-.581899f*X2 - .030902f*X));
}
if (X >= .2f)
{
Offsets[1] = FMath::Max(-.005631f + .527485f * FMath::Exp(1.f / (-8.671486f*X3 - 6.569889f*X2 + 9.311589f*X - 4.983829f*SqrtX + .479089f)), 1.f);
}
}
else
{
Offsets[0] = 68.634628f*X3 - 162.504166f*X2 + 103.115616f*X + 47.546867f*SqrtX - 55.950523f;
Offsets[1] = .433818f - 7.626031f * FMath::Exp(1.f - 1.f / (6.921899f*X3 - .834526f*X2 - 15.626254f*X + 3.757118f*SqrtX + 6.258511f));
}
}
else
{
Offsets[0] = FMath::Min(.535894f - .009497f * FMath::Loge(FMath::Max(-3.618996f*X3 + 6.220135f*X2 + 3.839638f*X - 7.252954f, .000001f)), .594476f);
Offsets[1] = 1.263563f + .098106f * FMath::Loge(1.174112f*X3 - 4.153324f*X2 + 4.932609f*X - 1.965301f);
}
return Offsets;
}
FSlateKawaseBlurInternalConfiguration GetSlateKawaseBlurInternalConfiguration(float Sigma)
{
// Constants and coefficients found by fitting optimized datapoints
constexpr float SIGMA_TO_LEVELS_LOG_BASE = 2.043f; // The number of downscale & upscale stages increases by one every time Sigma is multiplied by this value
FSlateKawaseBlurInternalConfiguration Configuration = { };
if (Sigma <= .27f) // No blur
{
return Configuration;
}
if (Sigma <= .8f) // Single pass at full size
{
const float X = Sigma;
Configuration.LowestLevelSteps = 1;
Configuration.DownsampleOffset = Configuration.ResampleOffset = 0.255305f * FMath::Exp(1.f - 1.f / (2.392349f*X*X*X + 3.736583f*X*X - 1.020735f*X + 0.123777f));
}
else if (Sigma < 4.f/3.f) // Two passes at full size
{
const FVector2f Offsets = GetSlateKawaseTwoFullResolutionPassesOffsets(Sigma);
Configuration.LowestLevelSteps = 2;
Configuration.DownsampleOffset = Offsets[0];
Configuration.ResampleOffset = Offsets[1];
}
else
{
Configuration.NumDownsampleLevels = FMath::Max(int32((FMath::Loge(Sigma) - FMath::Loge(3.5f)) / FMath::Loge(SIGMA_TO_LEVELS_LOG_BASE) + 2.f), 1);
Configuration.DownsampleOffset = 7.f/9.f; // This has been found to be very close to the optimal value for all remaining cases.
const float StageSigma = Configuration.NumDownsampleLevels <= 1 ? Sigma : Sigma * FMath::Pow(SIGMA_TO_LEVELS_LOG_BASE, 1.f - float(Configuration.NumDownsampleLevels)) - 0.16f;
float UpsampleSigma = StageSigma;
if (StageSigma > 2.f) {
UpsampleSigma = 2.f;
Configuration.LowestLevelSteps = 1;
float X = StageSigma;
if (Configuration.NumDownsampleLevels > 1)
{
X = 1.16f * (X - SIGMA_TO_LEVELS_LOG_BASE) + SIGMA_TO_LEVELS_LOG_BASE;
}
Configuration.ResampleOffset = 1.664566f - FMath::Exp(-0.082114f*X*X - 0.387889f*X + 1.595579f);
if (Configuration.ResampleOffset > Configuration.DownsampleOffset) {
Configuration.LowestLevelSteps = 2;
Configuration.ResampleOffset = Configuration.NumDownsampleLevels <= 1 ? .625f * (StageSigma - SIGMA_TO_LEVELS_LOG_BASE) : 2.f/3.f * (StageSigma - 2.f);
}
}
Configuration.UpsampleParameters = GetSlateKawaseBlurUpsampleParameters(UpsampleSigma);
}
return Configuration;
}
static FScreenPassTexture AddSlateKawaseBlurSymmetricalPass(FRDGBuilder& GraphBuilder, const FScreenPassTexture& InputTexture, uint32 DownscaleFactor, float SampleOffset)
{
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel;
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(FeatureLevel);
const FIntPoint OutputExtent = DownscaleFactor > 1 ? GetDownscaledExtent(InputTexture.ViewRect.Size(), DownscaleFactor) : InputTexture.ViewRect.Size();
TShaderMapRef<FSlatePostProcessResample2x2PS> PixelShader(ShaderMap);
const FScreenPassRenderTarget Output(
GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(OutputExtent, InputTexture.Texture->Desc.Format, FClearValueBinding::None, GetSlateTransientRenderTargetFlags()), TEXT("PostBlurUI")),
ERenderTargetLoadAction::ENoAction);
const FScreenPassTextureViewport InputViewport(InputTexture);
// This ensures that if input resolution is not divisible by the DownscaleFactor, we actually scale exactly DownscaleFactor x DownscaleFactor pixels into one and pad the last row and column rather than just fitting to the rounded dimensions.
const FScreenPassTextureViewport ProportionalInputViewport(InputTexture.Texture, FIntRect(InputTexture.ViewRect.Min, InputTexture.ViewRect.Min + FIntPoint(DownscaleFactor) * OutputExtent));
// Input parameters are still computed from actual InputViewport, otherwise pixels from outside the viewport get blended into the last row and column.
const FScreenPassTextureViewportParameters InputParameters = GetScreenPassTextureViewportParameters(InputViewport);
const FScreenPassTextureViewport OutputViewport(Output);
FSlatePostProcessResample2x2PS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSlatePostProcessResample2x2PS::FParameters>();
PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding();
PassParameters->ElementTexture = InputTexture.Texture;
PassParameters->ElementTextureSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
PassParameters->ShaderParams = SampleOffset * FVector4f(InputParameters.ExtentInverse.X, InputParameters.ExtentInverse.Y, 0.0f, 0.0f);
PassParameters->UVBounds = FVector4f(InputParameters.UVViewportBilinearMin, InputParameters.UVViewportBilinearMax);
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("PostBlurUI"), GMaxRHIFeatureLevel, OutputViewport, ProportionalInputViewport, PixelShader, PassParameters);
return Output;
}
static FScreenPassTexture AddSlateKawaseBlurUpsamplePass(FRDGBuilder& GraphBuilder, const FScreenPassTexture& InputTexture, const FIntPoint& OutputExtent, const FSlateKawaseBlurUpsampleParameters& Parameters)
{
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel;
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef<FSlatePostProcessOptimizedKawaseUpsamplePS> PixelShader(ShaderMap);
const FScreenPassRenderTarget Output(
GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(OutputExtent, InputTexture.Texture->Desc.Format, FClearValueBinding::None, GetSlateTransientRenderTargetFlags()), TEXT("PostBlurUI")),
ERenderTargetLoadAction::ENoAction);
const FScreenPassTextureViewport InputViewport(InputTexture);
const FScreenPassTextureViewportParameters InputParameters = GetScreenPassTextureViewportParameters(InputViewport);
const FScreenPassTextureViewport OutputViewport(Output.Texture, FIntRect(FIntPoint(0), FIntPoint(2) * InputTexture.ViewRect.Size()));
FSlatePostProcessOptimizedKawaseUpsamplePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSlatePostProcessOptimizedKawaseUpsamplePS::FParameters>();
PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding();
PassParameters->ElementTexture = InputTexture.Texture;
PassParameters->ElementTextureSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
PassParameters->ShaderParams = FVector4f(InputParameters.ExtentInverse.X, InputParameters.ExtentInverse.Y, Parameters.NearSampleWeight, Parameters.FarSampleWeight);
PassParameters->ShaderParams2 = FVector4f(Parameters.SideSampleOffsets.X, Parameters.SideSampleOffsets.Y, Parameters.NearSampleOffset, Parameters.FarSampleOffset);
PassParameters->UVBounds = FVector4f(InputParameters.UVViewportBilinearMin, InputParameters.UVViewportBilinearMax);
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("PostBlurUI"), GMaxRHIFeatureLevel, OutputViewport, InputViewport, PixelShader, PassParameters);
return Output;
}
FScreenPassTexture AddSlateKawaseBlur(FRDGBuilder& GraphBuilder, const FScreenPassTexture& InputTexture, const FSlateKawaseBlurInternalConfiguration& Configuration)
{
if (Configuration.NumDownsampleLevels == 0 && Configuration.LowestLevelSteps == 0)
{
return InputTexture;
}
FScreenPassTexture BlurTexture(InputTexture);
TArray<FIntPoint, TInlineAllocator<256>> UpsampleStageExtents;
UpsampleStageExtents.SetNumUninitialized(Configuration.NumDownsampleLevels);
// Downsample passes
for (uint32 DownsampleStage = 0; DownsampleStage < Configuration.NumDownsampleLevels; ++DownsampleStage)
{
UpsampleStageExtents[DownsampleStage] = BlurTexture.ViewRect.Size();
BlurTexture = AddSlateKawaseBlurSymmetricalPass(GraphBuilder, BlurTexture, 2, Configuration.DownsampleOffset);
}
// Lowest level same-size passes
for (uint32 LowestLevelStep = 0; LowestLevelStep < Configuration.LowestLevelSteps; ++LowestLevelStep)
{
const float SampleOffset = Configuration.NumDownsampleLevels == 0 && LowestLevelStep == 0 ? Configuration.DownsampleOffset : Configuration.ResampleOffset;
BlurTexture = AddSlateKawaseBlurSymmetricalPass(GraphBuilder, BlurTexture, 1, SampleOffset);
}
// Upsample passes
for (int32 UpsampleStage = int32(Configuration.NumDownsampleLevels)-1; UpsampleStage >= 0; --UpsampleStage)
{
BlurTexture = AddSlateKawaseBlurUpsamplePass(GraphBuilder, BlurTexture, UpsampleStageExtents[UpsampleStage], Configuration.UpsampleParameters);
}
return BlurTexture;
}
//////////////////////////////////////////////////////////////////////////
class FSlatePostProcessBlurPS : public FGlobalShader
{
public:
static const int32 MAX_BLUR_SAMPLES = 127 / 2;
DECLARE_GLOBAL_SHADER(FSlatePostProcessBlurPS);
SHADER_USE_PARAMETER_STRUCT(FSlatePostProcessBlurPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ElementTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, ElementTextureSampler)
SHADER_PARAMETER_ARRAY(FVector4f, WeightAndOffsets, [MAX_BLUR_SAMPLES])
SHADER_PARAMETER(uint32, SampleCount)
SHADER_PARAMETER(FVector4f, BufferSizeAndDirection)
SHADER_PARAMETER(FVector4f, UVBounds)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FSlatePostProcessBlurPS, "/Engine/Private/SlatePostProcessPixelShader.usf", "GaussianBlurMain", SF_Pixel);
FScreenPassTexture AddSlatePostProcessOldGaussianBlur(FRDGBuilder& GraphBuilder, const FSlatePostProcessBlurPassInputs& Inputs)
{
const auto GetWeight = [](float Dist, float Strength)
{
float Strength2 = Strength*Strength;
return (1.0f / FMath::Sqrt(2 * PI*Strength2))*FMath::Exp(-(Dist*Dist) / (2 * Strength2));
};
const auto GetWeightsAndOffset = [GetWeight](float Dist, float Sigma)
{
float Offset1 = Dist;
float Weight1 = GetWeight(Offset1, Sigma);
float Offset2 = Dist + 1;
float Weight2 = GetWeight(Offset2, Sigma);
float TotalWeight = Weight1 + Weight2;
float Offset = 0;
if (TotalWeight > 0)
{
Offset = (Weight1 * Offset1 + Weight2 * Offset2) / TotalWeight;
}
return FVector2f(TotalWeight, Offset);
};
const int32 SampleCount = FMath::DivideAndRoundUp(Inputs.KernelSize, 2u);
// We need half of the sample count array because we're packing two samples into one float;
TArray<FVector4f, FRDGArrayAllocator> WeightsAndOffsets;
WeightsAndOffsets.AddUninitialized(SampleCount % 2 == 0 ? SampleCount / 2 : SampleCount / 2 + 1);
WeightsAndOffsets[0] = FVector4f(FVector2f(GetWeight(0, Inputs.Strength), 0), GetWeightsAndOffset(1, Inputs.Strength) );
for (uint32 X = 3, SampleIndex = 1; X < Inputs.KernelSize; X += 4, SampleIndex++)
{
WeightsAndOffsets[SampleIndex] = FVector4f(GetWeightsAndOffset((float)X, Inputs.Strength), GetWeightsAndOffset((float)(X + 2), Inputs.Strength));
}
FScreenPassTextureViewport OutputTextureViewport(Inputs.InputRect.Size());
const EPixelFormat InputPixelFormat = Inputs.InputTexture->Desc.Format;
// Defaults to the input UI texture unless a downsample / composite pass is needed.
FScreenPassTexture BlurInputTexture(Inputs.InputTexture, Inputs.InputRect);
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel;
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef<FSlatePostProcessBlurPS> PixelShader(ShaderMap);
FScreenPassRenderTarget BlurOutputTexture(
GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(OutputTextureViewport.Extent, InputPixelFormat, FClearValueBinding::None, GetSlateTransientRenderTargetFlags()), TEXT("SlateBlurHorizontalTexture")),
ERenderTargetLoadAction::ENoAction);
FSlatePostProcessBlurPS::FParameters* PassParameters = nullptr;
{
const FScreenPassTextureViewport BlurInputViewport(BlurInputTexture);
const FScreenPassTextureViewportParameters BlurInputParameters = GetScreenPassTextureViewportParameters(BlurInputViewport);
PassParameters = GraphBuilder.AllocParameters<FSlatePostProcessBlurPS::FParameters>();
PassParameters->RenderTargets[0] = BlurOutputTexture.GetRenderTargetBinding();
PassParameters->ElementTexture = BlurInputTexture.Texture;
PassParameters->ElementTextureSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->SampleCount = SampleCount;
PassParameters->BufferSizeAndDirection = FVector4f(BlurInputParameters.ExtentInverse, FVector2f(1.0f, 0.0f));
PassParameters->UVBounds = FVector4f(BlurInputParameters.UVViewportBilinearMin, BlurInputParameters.UVViewportBilinearMax);
check(PassParameters->WeightAndOffsets.Num() * sizeof(PassParameters->WeightAndOffsets[0]) >= WeightsAndOffsets.Num() * sizeof(WeightsAndOffsets[0]));
FPlatformMemory::Memcpy(PassParameters->WeightAndOffsets.GetData(), WeightsAndOffsets.GetData(), WeightsAndOffsets.Num() * sizeof(WeightsAndOffsets[0]));
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("Horizontal"), FeatureLevel, FScreenPassTextureViewport(BlurOutputTexture), BlurInputViewport, PixelShader, PassParameters);
}
BlurInputTexture = BlurOutputTexture;
BlurOutputTexture = FScreenPassRenderTarget(
GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(OutputTextureViewport.Extent, InputPixelFormat, FClearValueBinding::None, GetSlateTransientRenderTargetFlags()), TEXT("SlateBlurVerticalTexture")),
ERenderTargetLoadAction::ENoAction);
{
const FScreenPassTextureViewport BlurInputViewport(BlurInputTexture);
const FScreenPassTextureViewportParameters BlurInputParameters = GetScreenPassTextureViewportParameters(BlurInputViewport);
PassParameters = GraphBuilder.AllocParameters<FSlatePostProcessBlurPS::FParameters>();
PassParameters->RenderTargets[0] = BlurOutputTexture.GetRenderTargetBinding();
PassParameters->ElementTexture = BlurInputTexture.Texture;
PassParameters->ElementTextureSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->SampleCount = SampleCount;
PassParameters->BufferSizeAndDirection = FVector4f(BlurInputParameters.ExtentInverse, FVector2f(0.0f, 1.0f));
PassParameters->UVBounds = FVector4f(BlurInputParameters.UVViewportBilinearMin, BlurInputParameters.UVViewportBilinearMax);
check(PassParameters->WeightAndOffsets.Num() * sizeof(PassParameters->WeightAndOffsets[0]) >= WeightsAndOffsets.Num() * sizeof(WeightsAndOffsets[0]));
FPlatformMemory::Memcpy(PassParameters->WeightAndOffsets.GetData(), WeightsAndOffsets.GetData(), WeightsAndOffsets.Num() * sizeof(WeightsAndOffsets[0]));
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("Vertical"), FeatureLevel, FScreenPassTextureViewport(BlurOutputTexture), BlurInputViewport, PixelShader, PassParameters);
}
return BlurOutputTexture;
}
void AddSlatePostProcessBlurPass(FRDGBuilder& GraphBuilder, const FSlatePostProcessBlurPassInputs& Inputs)
{
RDG_EVENT_SCOPE(GraphBuilder, "GaussianBlur");
CSV_CUSTOM_STAT(Slate, PostProcessBlurPassCount, 1, ECsvCustomStatOp::Accumulate);
FIntRect UnscaledRect(Inputs.OutputRect);
FScreenPassTexture BlurInputTexture(Inputs.InputTexture, Inputs.InputRect);
FScreenPassTexture BlurOutputTexture;
FScreenPassTextureViewport OutputTextureViewport(Inputs.InputRect.Size());
if (Inputs.DownsampleAmount > 1)
{
OutputTextureViewport = FScreenPassTextureViewport(GetDownscaledExtent(Inputs.InputRect.Size(), Inputs.DownsampleAmount));
}
// Need to composite the HDR scene texture with a separate SDR UI texture (which also does a downsample).
if (Inputs.SDRCompositeUITexture)
{
FSlateCompositeHDRForBlurPassInputs CompositeInputs;
CompositeInputs.InputRect = Inputs.InputRect;
CompositeInputs.InputTexture = Inputs.InputTexture;
CompositeInputs.InputCompositeTexture = Inputs.SDRCompositeUITexture;
CompositeInputs.OutputExtent = OutputTextureViewport.Extent;
BlurInputTexture = AddSlateCompositeHDRForBlurPass(GraphBuilder, CompositeInputs);
}
// Need to do an explicit downsample pass.
else if (Inputs.DownsampleAmount > 1)
{
FSlatePostProcessDownsamplePassInputs DownsampleInputs;
DownsampleInputs.InputTexture = BlurInputTexture;
DownsampleInputs.OutputExtent = OutputTextureViewport.Extent;
DownsampleInputs.Downscale = Inputs.DownsampleAmount;
BlurInputTexture = AddSlatePostProcessDownsamplePass(GraphBuilder, DownsampleInputs);
// If InputRect dimensions are not divisible by DownsampleAmount, this fixes up the subpixel alignment, since AddSlatePostProcessDownsamplePass rounds up the input extent.
// For example if input width is 9 and downscale is 2x, the downsample pass actually takes 10 and shrinks to 5. Therefore, we need to upscale back to 10 and not 9 pixels.
const FIntPoint UnscaledSize = FIntPoint(Inputs.DownsampleAmount) * BlurInputTexture.ViewRect.Size();
if (Inputs.InputRect.Size() == Inputs.OutputRect.Size())
{
UnscaledRect.Max = UnscaledRect.Min + UnscaledSize;
}
else if (Inputs.InputRect.Width() != 0 && Inputs.InputRect.Height() != 0)
{
UnscaledRect.Max = UnscaledRect.Min + (FVector2f(Inputs.OutputRect.Size()) / FVector2f(Inputs.InputRect.Size()) * FVector2f(UnscaledSize)).IntPoint();
}
}
if (SlatePostBlurDualKawaseFilterEnable)
{
const FSlateKawaseBlurInternalConfiguration Configuration = GetSlateKawaseBlurInternalConfiguration(Inputs.Strength);
BlurOutputTexture = AddSlateKawaseBlur(GraphBuilder, BlurInputTexture, Configuration);
}
else
{
FSlatePostProcessBlurPassInputs DownscaledInputs(Inputs);
DownscaledInputs.InputTexture = BlurInputTexture.Texture;
DownscaledInputs.InputRect = BlurInputTexture.ViewRect;
DownscaledInputs.DownsampleAmount = 0;
BlurOutputTexture = AddSlatePostProcessOldGaussianBlur(GraphBuilder, DownscaledInputs);
}
FSlatePostProcessUpsampleInputs UpsampleInputs;
UpsampleInputs.InputTexture = BlurOutputTexture;
UpsampleInputs.OutputTextureToClear = Inputs.SDRCompositeUITexture;
UpsampleInputs.OutputTexture = Inputs.OutputTexture;
UpsampleInputs.OutputRect = UnscaledRect;
UpsampleInputs.ClippingOp = Inputs.ClippingOp;
UpsampleInputs.ClippingStencilBinding = Inputs.ClippingStencilBinding;
UpsampleInputs.ClippingElementsViewRect = Inputs.ClippingElementsViewRect;
UpsampleInputs.CornerRadius = Inputs.CornerRadius;
AddSlatePostProcessUpsamplePass(GraphBuilder, UpsampleInputs);
}
void AddSlatePostProcessCopy(FRDGBuilder& GraphBuilder, FScreenPassTexture Input, FScreenPassTexture Output)
{
if (Input.ViewRect.Size() == Output.ViewRect.Size())
{
AddDrawTexturePass(GraphBuilder, FScreenPassViewInfo(), Input, Output);
}
else
{
// Like AddDrawTexturePass but with SF_Bilinear
const FScreenPassRenderTarget OutputTarget(Output, ERenderTargetLoadAction::ELoad);
const FScreenPassTextureViewport InputViewport(Input);
const FScreenPassTextureViewport OutputViewport(Output);
TShaderMapRef<FCopyRectPS> PixelShader(GetGlobalShaderMap(FScreenPassViewInfo().FeatureLevel));
FCopyRectPS::FParameters* Parameters = GraphBuilder.AllocParameters<FCopyRectPS::FParameters>();
Parameters->InputTexture = Input.Texture;
Parameters->InputSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
Parameters->RenderTargets[0] = OutputTarget.GetRenderTargetBinding();
Parameters->RenderTargets.MultiViewCount = 1;
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("DrawTexture"), FScreenPassViewInfo(), OutputViewport, InputViewport, PixelShader, Parameters);
}
}
void AddSlatePostProcessBlurPass(FRDGBuilder& GraphBuilder, const FSlatePostProcessSimpleBlurPassInputs& SimpleInputs)
{
const int32 MinKernelSize = 3;
const int32 MaxKernelSize = 255;
const int32 Downsample2Threshold = 9; // Reached at blur strength 3.166
const int32 Downsample3Threshold = 64; // Reached at blur strength 21.166
const int32 Downsample4Threshold = 96; // Reached at blur strength 31.833
const float StrengthToKernelSize = 3.0f;
const float MinStrength = 0.5f;
float Strength = FMath::Max(MinStrength, SimpleInputs.Strength);
if (SlatePostBlurStrengthOverride > 0.f)
{
Strength = FMath::Max(MinStrength, SlatePostBlurStrengthOverride);
}
int32 KernelSize = FMath::RoundToInt(Strength * StrengthToKernelSize);
int32 DownsampleAmount = 0;
if (KernelSize > Downsample2Threshold)
{
DownsampleAmount = KernelSize >= Downsample3Threshold ? KernelSize >= Downsample4Threshold ? 4 : 3 : 2;
KernelSize /= DownsampleAmount;
}
// Kernel sizes must be odd
if (KernelSize % 2 == 0)
{
++KernelSize;
}
if (DownsampleAmount > 0)
{
Strength /= DownsampleAmount;
}
KernelSize = FMath::Clamp(KernelSize, MinKernelSize, MaxKernelSize);
FSlatePostProcessBlurPassInputs Inputs;
Inputs.InputTexture = SimpleInputs.InputTexture.Texture;
Inputs.InputRect = SimpleInputs.InputTexture.ViewRect;
Inputs.OutputTexture = SimpleInputs.OutputTexture.Texture;
Inputs.OutputRect = SimpleInputs.OutputTexture.ViewRect;
Inputs.KernelSize = KernelSize;
Inputs.Strength = Strength;
Inputs.DownsampleAmount = DownsampleAmount;
AddSlatePostProcessBlurPass(GraphBuilder, Inputs);
}
//////////////////////////////////////////////////////////////////////////
class FSlatePostProcessColorDeficiencyPS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FSlatePostProcessColorDeficiencyPS);
SHADER_USE_PARAMETER_STRUCT(FSlatePostProcessColorDeficiencyPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ElementTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, ElementTextureSampler)
SHADER_PARAMETER(float, ColorVisionDeficiencyType)
SHADER_PARAMETER(float, ColorVisionDeficiencySeverity)
SHADER_PARAMETER(float, bCorrectDeficiency)
SHADER_PARAMETER(float, bSimulateCorrectionWithDeficiency)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FSlatePostProcessColorDeficiencyPS, "/Engine/Private/SlatePostProcessColorDeficiencyPixelShader.usf", "ColorDeficiencyMain", SF_Pixel);
void AddSlatePostProcessColorDeficiencyPass(FRDGBuilder& GraphBuilder, const FSlatePostProcessColorDeficiencyPassInputs& Inputs)
{
const ERHIFeatureLevel::Type FeatureLevel = GMaxRHIFeatureLevel;
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef<FSlatePostProcessColorDeficiencyPS> PixelShader(ShaderMap);
const FRDGTextureDesc& InputDesc = Inputs.InputTexture.Texture->Desc;
const FScreenPassRenderTarget Output(
GraphBuilder.CreateTexture(FRDGTextureDesc::Create2D(InputDesc.Extent, InputDesc.Format, FClearValueBinding::None, GetSlateTransientRenderTargetFlags()), TEXT("ColorDeficiency")),
ERenderTargetLoadAction::ENoAction);
FSlatePostProcessColorDeficiencyPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FSlatePostProcessColorDeficiencyPS::FParameters>();
PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding();
PassParameters->ElementTexture = Inputs.InputTexture.Texture;
PassParameters->ElementTextureSampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->ColorVisionDeficiencyType = (float)GSlateColorDeficiencyType;
PassParameters->ColorVisionDeficiencySeverity = (float)GSlateColorDeficiencySeverity;
PassParameters->bCorrectDeficiency = GSlateColorDeficiencyCorrection ? 1.0f : 0.0f;
PassParameters->bSimulateCorrectionWithDeficiency = GSlateShowColorDeficiencyCorrectionWithDeficiency ? 1.0f : 0.0f;
const FScreenPassTextureViewport Viewport(Output);
AddDrawScreenPass(GraphBuilder, RDG_EVENT_NAME("ColorDeficiency"), FeatureLevel, Viewport, Viewport, PixelShader, PassParameters);
FSlatePostProcessUpsampleInputs UpsampleInputs;
UpsampleInputs.InputTexture = Output;
UpsampleInputs.OutputTexture = Inputs.OutputTexture.Texture;
UpsampleInputs.OutputRect = Inputs.OutputTexture.ViewRect;
AddSlatePostProcessUpsamplePass(GraphBuilder, UpsampleInputs);
}