You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb none #fyi will.damon #jira none [CL 8514413 by zach bethel in Dev-RenderPlat-Staging branch]
637 lines
22 KiB
C++
637 lines
22 KiB
C++
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PostProcess/PostProcessWeightedSampleSum.h"
|
|
|
|
// maximum number of sample using the shader that has the dynamic loop
|
|
#define MAX_FILTER_SAMPLES 128
|
|
#define MAX_PACKED_SAMPLES_OFFSET ((MAX_FILTER_SAMPLES + 1) / 2)
|
|
|
|
// maximum number of sample available using unrolled loop shaders
|
|
#define MAX_FILTER_COMPILE_TIME_SAMPLES 32
|
|
#define MAX_FILTER_COMPILE_TIME_SAMPLES_IOS 15
|
|
#define MAX_FILTER_COMPILE_TIME_SAMPLES_ES2 7
|
|
|
|
namespace
|
|
{
|
|
const int32 GFilterComputeTileSizeX = 8;
|
|
const int32 GFilterComputeTileSizeY = 8;
|
|
|
|
TAutoConsoleVariable<int32> CVarLoopMode(
|
|
TEXT("r.Filter.LoopMode"),
|
|
0,
|
|
TEXT("Controls when to use either dynamic or unrolled loops to iterates over the Gaussian filtering.\n")
|
|
TEXT("This passes is used for Gaussian Blur, Bloom and Depth of Field. The dynamic loop allows\n")
|
|
TEXT("up to 128 samples versus the 32 samples of unrolled loops, but add an additional cost for\n")
|
|
TEXT("the loop's stop test at every iterations.\n")
|
|
TEXT(" 0: Unrolled loop only (default; limited to 32 samples).\n")
|
|
TEXT(" 1: Fall back to dynamic loop if needs more than 32 samples.\n")
|
|
TEXT(" 2: Dynamic loop only."),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe);
|
|
|
|
TAutoConsoleVariable<float> CVarFilterSizeScale(
|
|
TEXT("r.Filter.SizeScale"),
|
|
1.0f,
|
|
TEXT("Allows to scale down or up the sample count used for bloom and Gaussian depth of field (scale is clamped to give reasonable results).\n")
|
|
TEXT("Values down to 0.6 are hard to notice\n")
|
|
TEXT(" 1 full quality (default)\n")
|
|
TEXT(" >1 more samples (slower)\n")
|
|
TEXT(" <1 less samples (faster, artifacts with HDR content or boxy results with GaussianDOF)"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe);
|
|
|
|
TAutoConsoleVariable<float> CVarFastBlurThreshold(
|
|
TEXT("r.FastBlurThreshold"),
|
|
7.0f,
|
|
TEXT("Defines at what radius the Gaussian blur optimization kicks in (estimated 25% - 40% faster).\n")
|
|
TEXT("The optimization uses slightly less memory and has a quality loss on smallblur radius.\n")
|
|
TEXT(" 0: use the optimization always (fastest, lowest quality)\n")
|
|
TEXT(" 3: use the optimization starting at a 3 pixel radius (quite fast)\n")
|
|
TEXT(" 7: use the optimization starting at a 7 pixel radius (default)\n")
|
|
TEXT(">15: barely ever use the optimization (high quality)"),
|
|
ECVF_Scalability | ECVF_RenderThreadSafe);
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FFilterInput, )
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FScreenPassTextureViewportParameters, Viewport)
|
|
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, Texture)
|
|
SHADER_PARAMETER_SAMPLER(SamplerState, Sampler)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
FFilterInput GetFilterInput(FScreenPassTextureViewport Viewport, FRDGTextureRef Texture, FRHISamplerState* Sampler)
|
|
{
|
|
FFilterInput Input;
|
|
Input.Viewport = GetScreenPassTextureViewportParameters(Viewport);
|
|
Input.Texture = Texture;
|
|
Input.Sampler = Sampler;
|
|
return Input;
|
|
}
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FFilterParameters, )
|
|
SHADER_PARAMETER_STRUCT(FFilterInput, Filter)
|
|
SHADER_PARAMETER_STRUCT(FFilterInput, Additive)
|
|
SHADER_PARAMETER_ARRAY(FVector4, SampleOffsets, [MAX_PACKED_SAMPLES_OFFSET])
|
|
SHADER_PARAMETER_ARRAY(FLinearColor, SampleWeights, [MAX_FILTER_SAMPLES])
|
|
SHADER_PARAMETER(int32, SampleCount)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
void GetFilterParameters(
|
|
FFilterParameters& OutParameters,
|
|
const FFilterInput& Filter,
|
|
const FFilterInput& Additive,
|
|
TArrayView<const FVector2D> SampleOffsets,
|
|
TArrayView<const FLinearColor> SampleWeights)
|
|
{
|
|
OutParameters.Filter = Filter;
|
|
OutParameters.Additive = Additive;
|
|
|
|
for (int32 SampleIndex = 0; SampleIndex < SampleOffsets.Num(); SampleIndex += 2)
|
|
{
|
|
OutParameters.SampleOffsets[SampleIndex / 2].X = SampleOffsets[SampleIndex + 0].X;
|
|
OutParameters.SampleOffsets[SampleIndex / 2].Y = SampleOffsets[SampleIndex + 0].Y;
|
|
|
|
if (SampleIndex + 1 < SampleOffsets.Num())
|
|
{
|
|
OutParameters.SampleOffsets[SampleIndex / 2].Z = SampleOffsets[SampleIndex + 1].X;
|
|
OutParameters.SampleOffsets[SampleIndex / 2].W = SampleOffsets[SampleIndex + 1].Y;
|
|
}
|
|
}
|
|
|
|
for (int32 SampleIndex = 0; SampleIndex < SampleWeights.Num(); ++SampleIndex)
|
|
{
|
|
OutParameters.SampleWeights[SampleIndex] = SampleWeights[SampleIndex];
|
|
}
|
|
|
|
OutParameters.SampleCount = SampleOffsets.Num();
|
|
}
|
|
|
|
// Evaluates an unnormalized normal distribution PDF around 0 at given X with Variance.
|
|
float NormalDistributionUnscaled(float X, float Sigma, float CrossCenterWeight)
|
|
{
|
|
const float DX = FMath::Abs(X);
|
|
|
|
const float ClampedOneMinusDX = FMath::Max(0.0f, 1.0f - DX);
|
|
|
|
// Tweak the gaussian shape e.g. "r.Bloom.Cross 3.5"
|
|
if (CrossCenterWeight > 1.0f)
|
|
{
|
|
return FMath::Pow(ClampedOneMinusDX, CrossCenterWeight);
|
|
}
|
|
else
|
|
{
|
|
// Constant is tweaked give a similar look to UE4 before we fix the scale bug (Some content tweaking might be needed).
|
|
// The value defines how much of the Gaussian clipped by the sample window.
|
|
// r.Filter.SizeScale allows to tweak that for performance/quality.
|
|
const float LegacyCompatibilityConstant = -16.7f;
|
|
|
|
const float Gaussian = FMath::Exp(LegacyCompatibilityConstant * FMath::Square(DX / Sigma));
|
|
|
|
return FMath::Lerp(Gaussian, ClampedOneMinusDX, CrossCenterWeight);
|
|
}
|
|
}
|
|
|
|
uint32 GetSampleCountMax(ERHIFeatureLevel::Type InFeatureLevel, EShaderPlatform InPlatform)
|
|
{
|
|
if (CVarLoopMode.GetValueOnRenderThread() != 0)
|
|
{
|
|
return MAX_FILTER_SAMPLES;
|
|
}
|
|
else if (IsMetalMRTPlatform(InPlatform))
|
|
{
|
|
return MAX_FILTER_COMPILE_TIME_SAMPLES_IOS;
|
|
}
|
|
else if (InFeatureLevel < ERHIFeatureLevel::SM5)
|
|
{
|
|
return MAX_FILTER_COMPILE_TIME_SAMPLES_ES2;
|
|
}
|
|
else
|
|
{
|
|
return MAX_FILTER_COMPILE_TIME_SAMPLES;
|
|
}
|
|
}
|
|
|
|
float GetClampedKernelRadius(uint32 SampleCountMax, float KernelRadius)
|
|
{
|
|
return FMath::Clamp<float>(KernelRadius, DELTA, SampleCountMax - 1);
|
|
}
|
|
|
|
int GetIntegerKernelRadius(uint32 SampleCountMax, float KernelRadius)
|
|
{
|
|
// Smallest radius will be 1.
|
|
return FMath::Min<int32>(FMath::CeilToInt(GetClampedKernelRadius(SampleCountMax, KernelRadius)), SampleCountMax - 1);
|
|
}
|
|
|
|
uint32 Compute1DGaussianFilterKernel(FVector2D OutOffsetAndWeight[MAX_FILTER_SAMPLES], uint32 SampleCountMax, float KernelRadius, float CrossCenterWeight)
|
|
{
|
|
const float FilterSizeScale = FMath::Clamp(CVarFilterSizeScale.GetValueOnRenderThread(), 0.1f, 10.0f);
|
|
|
|
const float ClampedKernelRadius = GetClampedKernelRadius(SampleCountMax, KernelRadius);
|
|
|
|
const int32 IntegerKernelRadius = GetIntegerKernelRadius(SampleCountMax, KernelRadius * FilterSizeScale);
|
|
|
|
uint32 SampleCount = 0;
|
|
float WeightSum = 0.0f;
|
|
|
|
for (int32 SampleIndex = -IntegerKernelRadius; SampleIndex <= IntegerKernelRadius; SampleIndex += 2)
|
|
{
|
|
float Weight0 = NormalDistributionUnscaled(SampleIndex, ClampedKernelRadius, CrossCenterWeight);
|
|
float Weight1 = 0.0f;
|
|
|
|
// We use the bilinear filter optimization for gaussian blur. However, we don't want to bias the
|
|
// last sample off the edge of the filter kernel, so the very last tap just is on the pixel center.
|
|
if(SampleIndex != IntegerKernelRadius)
|
|
{
|
|
Weight1 = NormalDistributionUnscaled(SampleIndex + 1, ClampedKernelRadius, CrossCenterWeight);
|
|
}
|
|
|
|
const float TotalWeight = Weight0 + Weight1;
|
|
OutOffsetAndWeight[SampleCount].X = SampleIndex + (Weight1 / TotalWeight);
|
|
OutOffsetAndWeight[SampleCount].Y = TotalWeight;
|
|
WeightSum += TotalWeight;
|
|
SampleCount++;
|
|
}
|
|
|
|
// Normalize blur weights.
|
|
const float WeightSumInverse = 1.0f / WeightSum;
|
|
for (uint32 SampleIndex = 0; SampleIndex < SampleCount; ++SampleIndex)
|
|
{
|
|
OutOffsetAndWeight[SampleIndex].Y *= WeightSumInverse;
|
|
}
|
|
|
|
return SampleCount;
|
|
}
|
|
|
|
float GetBlurRadius(uint32 TextureWidth, float KernelSizePercent)
|
|
{
|
|
const float PercentToScale = 0.01f;
|
|
|
|
const float DiameterToRadius = 0.5f;
|
|
|
|
return static_cast<float>(TextureWidth) * KernelSizePercent * PercentToScale * DiameterToRadius;
|
|
}
|
|
|
|
bool IsFastBlurEnabled(float BlurRadius)
|
|
{
|
|
const float FastBlurRadiusThreshold = CVarFastBlurThreshold.GetValueOnRenderThread();
|
|
|
|
return BlurRadius >= FastBlurRadiusThreshold;
|
|
}
|
|
|
|
uint32 GetStaticSampleCount(uint32 RequiredSampleCount)
|
|
{
|
|
const int32 LoopMode = CVarLoopMode.GetValueOnRenderThread();
|
|
|
|
const bool bForceStaticLoop = LoopMode == 0;
|
|
|
|
const bool bForceDynamicLoop = LoopMode == 2;
|
|
|
|
if (!bForceDynamicLoop && (bForceStaticLoop || RequiredSampleCount <= MAX_FILTER_COMPILE_TIME_SAMPLES))
|
|
{
|
|
return RequiredSampleCount;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
class FFilterShader : public FGlobalShader
|
|
{
|
|
public:
|
|
class FStaticSampleCount : SHADER_PERMUTATION_INT("STATIC_SAMPLE_COUNT", MAX_FILTER_COMPILE_TIME_SAMPLES + 1);
|
|
class FCombineAdditive : SHADER_PERMUTATION_BOOL("USE_COMBINE_ADDITIVE");
|
|
class FManualUVBorder : SHADER_PERMUTATION_BOOL("USE_MANUAL_UV_BORDER");
|
|
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("SAMPLE_COUNT_MAX"), MAX_FILTER_SAMPLES);
|
|
}
|
|
|
|
static bool ShouldCompilePermutation(EShaderPlatform Platform, uint32 StaticSampleCount)
|
|
{
|
|
if (IsMetalMRTPlatform(Platform))
|
|
{
|
|
return StaticSampleCount <= MAX_FILTER_COMPILE_TIME_SAMPLES_IOS;
|
|
}
|
|
else if (IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return StaticSampleCount <= MAX_FILTER_COMPILE_TIME_SAMPLES_ES2;
|
|
}
|
|
}
|
|
|
|
FFilterShader() = default;
|
|
FFilterShader(const CompiledShaderInitializerType& Initializer)
|
|
: FGlobalShader(Initializer)
|
|
{}
|
|
};
|
|
|
|
class FFilterVS : public FFilterShader
|
|
{
|
|
public:
|
|
DECLARE_GLOBAL_SHADER(FFilterVS);
|
|
|
|
// FDrawRectangleParameters is filled by DrawScreenPass.
|
|
SHADER_USE_PARAMETER_STRUCT_WITH_LEGACY_BASE(FFilterVS, FFilterShader);
|
|
|
|
using FPermutationDomain = TShaderPermutationDomain<FStaticSampleCount>;
|
|
using FParameters = FFilterParameters;
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
const FPermutationDomain PermutationVector(Parameters.PermutationId);
|
|
|
|
const uint32 StaticSampleCount = PermutationVector.Get<FStaticSampleCount>();
|
|
|
|
if (StaticSampleCount == 0)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return FFilterShader::ShouldCompilePermutation(Parameters.Platform, StaticSampleCount);
|
|
}
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FFilterVS, "/Engine/Private/FilterVertexShader.usf", "MainVS", SF_Vertex);
|
|
|
|
class FFilterPS : public FFilterShader
|
|
{
|
|
public:
|
|
DECLARE_GLOBAL_SHADER(FFilterPS);
|
|
SHADER_USE_PARAMETER_STRUCT(FFilterPS, FFilterShader);
|
|
|
|
using FPermutationDomain = TShaderPermutationDomain<FStaticSampleCount, FCombineAdditive, FManualUVBorder>;
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FFilterParameters, Filter)
|
|
RENDER_TARGET_BINDING_SLOTS()
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
const FPermutationDomain PermutationVector(Parameters.PermutationId);
|
|
|
|
return FFilterShader::ShouldCompilePermutation(Parameters.Platform, PermutationVector.Get<FStaticSampleCount>());
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FFilterPS, "/Engine/Private/FilterPixelShader.usf", "MainPS", SF_Pixel);
|
|
|
|
class FFilterCS : public FFilterShader
|
|
{
|
|
public:
|
|
DECLARE_GLOBAL_SHADER(FFilterCS);
|
|
SHADER_USE_PARAMETER_STRUCT(FFilterCS, FFilterShader);
|
|
|
|
using FPermutationDomain = TShaderPermutationDomain<FStaticSampleCount, FCombineAdditive, FManualUVBorder>;
|
|
|
|
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
|
|
SHADER_PARAMETER_STRUCT_INCLUDE(FFilterParameters, Filter)
|
|
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWOutputTexture)
|
|
END_SHADER_PARAMETER_STRUCT()
|
|
|
|
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
|
|
{
|
|
FFilterShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), GFilterComputeTileSizeX);
|
|
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), GFilterComputeTileSizeY);
|
|
}
|
|
|
|
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
|
|
{
|
|
return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_GLOBAL_SHADER(FFilterCS, "/Engine/Private/FilterPixelShader.usf", "MainCS", SF_Compute);
|
|
} //! namespace
|
|
|
|
FRDGTextureRef AddGaussianBlurPass(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FScreenPassViewInfo& ScreenPassView,
|
|
const TCHAR* Name,
|
|
FScreenPassTextureViewport OutputViewport,
|
|
FRDGTextureRef FilterTexture,
|
|
FIntRect FilterViewportRect,
|
|
FRDGTextureRef AdditiveTexture,
|
|
FIntRect AdditiveViewportRect,
|
|
TArrayView<const FVector2D> SampleOffsets,
|
|
TArrayView<const FLinearColor> SampleWeights)
|
|
{
|
|
check(!OutputViewport.IsEmpty());
|
|
check(FilterTexture);
|
|
check(!FilterViewportRect.IsEmpty());
|
|
check(SampleOffsets.Num() == SampleWeights.Num());
|
|
check(SampleOffsets.Num() != 0);
|
|
|
|
const uint32 SampleCount = SampleOffsets.Num();
|
|
|
|
const FViewInfo& View = ScreenPassView.View;
|
|
|
|
const bool bIsComputePass = ScreenPassView.bUseComputePasses;
|
|
|
|
const bool bCombineAdditive = AdditiveTexture != nullptr;
|
|
|
|
const bool bManualUVBorder = !OutputViewport.IsFullscreen();
|
|
|
|
const uint32 StaticSampleCount = GetStaticSampleCount(SampleCount);
|
|
|
|
FRHISamplerState* SamplerState = TStaticSamplerState<SF_Bilinear, AM_Border, AM_Border, AM_Clamp>::GetRHI();
|
|
|
|
const FScreenPassTextureViewport FilterViewport(FilterViewportRect, FilterTexture);
|
|
const FFilterInput FilterInput = GetFilterInput(FilterViewport, FilterTexture, SamplerState);
|
|
|
|
FScreenPassTextureViewport AdditiveViewport;
|
|
FFilterInput AdditiveInput;
|
|
|
|
if (bCombineAdditive)
|
|
{
|
|
AdditiveViewport = FScreenPassTextureViewport(AdditiveViewportRect, AdditiveTexture);
|
|
AdditiveInput = GetFilterInput(AdditiveViewport, AdditiveTexture, SamplerState);
|
|
}
|
|
|
|
FRDGTextureDesc OutputTextureDesc = FilterTexture->Desc;
|
|
OutputTextureDesc.Reset();
|
|
OutputTextureDesc.Extent = OutputViewport.Extent;
|
|
OutputTextureDesc.TargetableFlags |= bIsComputePass ? TexCreate_UAV : TexCreate_None;
|
|
OutputTextureDesc.Flags &= TexCreate_FastVRAM;
|
|
OutputTextureDesc.ClearValue = FClearValueBinding(FLinearColor::Transparent);
|
|
|
|
FRDGTextureRef OutputTexture = GraphBuilder.CreateTexture(OutputTextureDesc, Name);
|
|
|
|
if (bIsComputePass)
|
|
{
|
|
FFilterCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FFilterCS::FParameters>();
|
|
GetFilterParameters(PassParameters->Filter, FilterInput, AdditiveInput, SampleOffsets, SampleWeights);
|
|
PassParameters->RWOutputTexture = GraphBuilder.CreateUAV(OutputTexture);
|
|
|
|
FFilterCS::FPermutationDomain PermutationVector;
|
|
PermutationVector.Set<FFilterCS::FCombineAdditive>(bCombineAdditive);
|
|
PermutationVector.Set<FFilterCS::FStaticSampleCount>(StaticSampleCount);
|
|
PermutationVector.Set<FFilterPS::FManualUVBorder>(bManualUVBorder);
|
|
TShaderMapRef<FFilterCS> ComputeShader(View.ShaderMap, PermutationVector);
|
|
|
|
FComputeShaderUtils::AddPass(
|
|
GraphBuilder,
|
|
RDG_EVENT_NAME("GaussianBlur.%s %dx%d (CS)", Name, OutputViewport.Rect.Width(), OutputViewport.Rect.Height()),
|
|
*ComputeShader,
|
|
PassParameters,
|
|
FComputeShaderUtils::GetGroupCount(OutputViewport.Rect.Size(), FIntPoint(GFilterComputeTileSizeX, GFilterComputeTileSizeY)));
|
|
}
|
|
else
|
|
{
|
|
FFilterPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FFilterPS::FParameters>();
|
|
GetFilterParameters(PassParameters->Filter, FilterInput, AdditiveInput, SampleOffsets, SampleWeights);
|
|
PassParameters->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ScreenPassView.GetOverwriteLoadAction());
|
|
|
|
FFilterPS::FPermutationDomain PixelPermutationVector;
|
|
PixelPermutationVector.Set<FFilterPS::FCombineAdditive>(bCombineAdditive);
|
|
PixelPermutationVector.Set<FFilterPS::FStaticSampleCount>(StaticSampleCount);
|
|
PixelPermutationVector.Set<FFilterPS::FManualUVBorder>(bManualUVBorder);
|
|
TShaderMapRef<FFilterPS> PixelShader(View.ShaderMap, PixelPermutationVector);
|
|
|
|
if (StaticSampleCount != 0)
|
|
{
|
|
FFilterVS::FPermutationDomain VertexPermutationVector;
|
|
VertexPermutationVector.Set<FFilterVS::FStaticSampleCount>(StaticSampleCount);
|
|
TShaderMapRef<FFilterVS> VertexShader(View.ShaderMap, VertexPermutationVector);
|
|
|
|
const auto SetupFunction = [VertexShader, PixelShader, PassParameters]
|
|
(FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
SetShaderParameters(RHICmdList, *VertexShader, VertexShader->GetVertexShader(), PassParameters->Filter);
|
|
SetShaderParameters(RHICmdList, *PixelShader, PixelShader->GetPixelShader(), *PassParameters);
|
|
};
|
|
|
|
const FScreenPassDrawInfo ScreenPassDraw(*VertexShader, *PixelShader);
|
|
|
|
AddDrawScreenPass(
|
|
GraphBuilder,
|
|
RDG_EVENT_NAME("GaussianBlur.%s %dx%d (PS, Static)", Name, OutputViewport.Rect.Width(), OutputViewport.Rect.Height()),
|
|
ScreenPassView,
|
|
OutputViewport,
|
|
FilterViewport,
|
|
ScreenPassDraw,
|
|
PassParameters,
|
|
SetupFunction);
|
|
}
|
|
else
|
|
{
|
|
AddDrawScreenPass(
|
|
GraphBuilder,
|
|
RDG_EVENT_NAME("GaussianBlur.%s %dx%d (PS, Dynamic)", Name, OutputViewport.Rect.Width(), OutputViewport.Rect.Height()),
|
|
ScreenPassView,
|
|
OutputViewport,
|
|
FilterViewport,
|
|
*PixelShader,
|
|
PassParameters);
|
|
}
|
|
}
|
|
|
|
return OutputTexture;
|
|
}
|
|
|
|
FRDGTextureRef AddGaussianBlurPass(
|
|
FRDGBuilder& GraphBuilder,
|
|
const FScreenPassViewInfo& ScreenPassView,
|
|
const FGaussianBlurInputs& Inputs)
|
|
{
|
|
check(Inputs.FilterTexture);
|
|
|
|
const FViewInfo& View = ScreenPassView.View;
|
|
|
|
const FScreenPassTextureViewport FilterViewport(Inputs.FilterViewportRect, Inputs.FilterTexture);
|
|
|
|
const float BlurRadius = GetBlurRadius(FilterViewport.Extent.X, Inputs.KernelSizePercent);
|
|
|
|
const uint32 SampleCountMax = GetSampleCountMax(View.GetFeatureLevel(), View.GetShaderPlatform());
|
|
|
|
const FVector2D InverseFilterTextureExtent(
|
|
1.0f / static_cast<float>(FilterViewport.Extent.X),
|
|
1.0f / static_cast<float>(FilterViewport.Extent.Y));
|
|
|
|
const bool bFastBlurEnabled = IsFastBlurEnabled(BlurRadius);
|
|
|
|
// Downscale output width by half when using fast blur optimization.
|
|
const FScreenPassTextureViewport HorizontalOutputViewport = bFastBlurEnabled
|
|
? FScreenPassTextureViewport::CreateDownscaled(FilterViewport, FIntPoint(2, 1))
|
|
: FilterViewport;
|
|
|
|
FRDGTextureRef HorizontalOutputTexture = nullptr;
|
|
|
|
// Horizontal Pass
|
|
{
|
|
FVector2D OffsetAndWeight[MAX_FILTER_SAMPLES];
|
|
FLinearColor SampleWeights[MAX_FILTER_SAMPLES];
|
|
FVector2D SampleOffsets[MAX_FILTER_SAMPLES];
|
|
|
|
const uint32 SampleCount = Compute1DGaussianFilterKernel(OffsetAndWeight, SampleCountMax, BlurRadius, Inputs.CrossCenterWeight.X);
|
|
|
|
// Weights multiplied by a white tint.
|
|
for (uint32 i = 0; i < SampleCount; ++i)
|
|
{
|
|
const float Weight = OffsetAndWeight[i].Y;
|
|
|
|
SampleWeights[i] = FLinearColor(Weight, Weight, Weight, Weight);
|
|
}
|
|
|
|
for (uint32 i = 0; i < SampleCount; ++i)
|
|
{
|
|
const float Offset = OffsetAndWeight[i].X;
|
|
|
|
SampleOffsets[i] = FVector2D(InverseFilterTextureExtent.X * Offset, 0.0f);
|
|
}
|
|
|
|
// Horizontal pass doesn't use additive combine.
|
|
FRDGTextureRef AdditiveTexture = nullptr;
|
|
const FIntRect AdditiveViewportRect;
|
|
|
|
HorizontalOutputTexture = AddGaussianBlurPass(
|
|
GraphBuilder,
|
|
ScreenPassView,
|
|
Inputs.NameX,
|
|
HorizontalOutputViewport,
|
|
Inputs.FilterTexture,
|
|
Inputs.FilterViewportRect,
|
|
AdditiveTexture,
|
|
AdditiveViewportRect,
|
|
TArrayView<const FVector2D>(SampleOffsets, SampleCount),
|
|
TArrayView<const FLinearColor>(SampleWeights, SampleCount));
|
|
}
|
|
|
|
// Vertical Pass
|
|
{
|
|
FVector2D OffsetAndWeight[MAX_FILTER_SAMPLES];
|
|
FLinearColor SampleWeights[MAX_FILTER_SAMPLES];
|
|
FVector2D SampleOffsets[MAX_FILTER_SAMPLES];
|
|
|
|
const uint32 SampleCount = Compute1DGaussianFilterKernel(OffsetAndWeight, SampleCountMax, BlurRadius, Inputs.CrossCenterWeight.Y);
|
|
|
|
// Weights multiplied by a input tint color.
|
|
for (uint32 i = 0; i < SampleCount; ++i)
|
|
{
|
|
const float Weight = OffsetAndWeight[i].Y;
|
|
|
|
SampleWeights[i] = Inputs.TintColor * Weight;
|
|
}
|
|
|
|
for (uint32 i = 0; i < SampleCount; ++i)
|
|
{
|
|
const float Offset = OffsetAndWeight[i].X;
|
|
|
|
SampleOffsets[i] = FVector2D(0, InverseFilterTextureExtent.Y * Offset);
|
|
}
|
|
|
|
return AddGaussianBlurPass(
|
|
GraphBuilder,
|
|
ScreenPassView,
|
|
Inputs.NameY,
|
|
FilterViewport,
|
|
HorizontalOutputTexture,
|
|
HorizontalOutputViewport.Rect,
|
|
Inputs.AdditiveTexture,
|
|
Inputs.AdditiveViewportRect,
|
|
TArrayView<const FVector2D>(SampleOffsets, SampleCount),
|
|
TArrayView<const FLinearColor>(SampleWeights, SampleCount));
|
|
}
|
|
}
|
|
|
|
FRenderingCompositeOutputRef AddGaussianBlurPass(
|
|
FRenderingCompositionGraph& Graph,
|
|
const TCHAR *InNameX,
|
|
const TCHAR* InNameY,
|
|
FRenderingCompositeOutputRef FilterInput,
|
|
float KernelSizePercent,
|
|
FLinearColor TintColor,
|
|
FRenderingCompositeOutputRef AdditiveInput,
|
|
FVector2D CrossCenterWeight)
|
|
{
|
|
FRenderingCompositePass* Pass = Graph.RegisterPass(
|
|
new(FMemStack::Get()) TRCPassForRDG<2, 1>(
|
|
[InNameX, InNameY, TintColor, KernelSizePercent, CrossCenterWeight] (FRenderingCompositePass* InPass, FRenderingCompositePassContext& InContext)
|
|
{
|
|
FRDGBuilder GraphBuilder(InContext.RHICmdList);
|
|
|
|
FRDGTextureRef FilterTexture = InPass->CreateRDGTextureForRequiredInput(GraphBuilder, ePId_Input0, TEXT("GaussianBlurInput"));
|
|
const FIntRect FilterViewportRect = InContext.GetDownsampledSceneColorViewRectFromInputExtent(FilterTexture->Desc.Extent);
|
|
|
|
FRDGTextureRef AdditiveTexture = InPass->CreateRDGTextureForOptionalInput(GraphBuilder, ePId_Input1, TEXT("AdditiveTexture"));
|
|
FIntRect AdditiveViewportRect;
|
|
|
|
if (AdditiveTexture)
|
|
{
|
|
AdditiveViewportRect = InContext.GetDownsampledSceneColorViewRectFromInputExtent(AdditiveTexture->Desc.Extent);
|
|
}
|
|
|
|
FGaussianBlurInputs PassInputs;
|
|
PassInputs.NameX = InNameX;
|
|
PassInputs.NameY = InNameY;
|
|
PassInputs.FilterTexture = FilterTexture;
|
|
PassInputs.FilterViewportRect = FilterViewportRect;
|
|
PassInputs.AdditiveTexture = AdditiveTexture;
|
|
PassInputs.AdditiveViewportRect = AdditiveViewportRect;
|
|
PassInputs.TintColor = TintColor;
|
|
PassInputs.CrossCenterWeight = CrossCenterWeight;
|
|
PassInputs.KernelSizePercent = KernelSizePercent;
|
|
|
|
FScreenPassViewInfo ScreenPassView(InContext.View);
|
|
|
|
FRDGTextureRef OutputTexture = AddGaussianBlurPass(GraphBuilder, ScreenPassView, PassInputs);
|
|
|
|
InPass->ExtractRDGTextureForOutput(GraphBuilder, ePId_Output0, OutputTexture);
|
|
|
|
GraphBuilder.Execute();
|
|
}));
|
|
|
|
Pass->SetInput(ePId_Input0, FilterInput);
|
|
|
|
if (AdditiveInput.IsValid())
|
|
{
|
|
Pass->SetInput(ePId_Input1, AdditiveInput);
|
|
}
|
|
|
|
return Pass;
|
|
} |