Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessFFTBloom.cpp
2025-05-18 13:04:45 +08:00

916 lines
39 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PostProcess/PostProcessFFTBloom.h"
#include "PostProcess/PostProcessTonemap.h"
#include "PostProcess/PostProcessLocalExposure.h"
#include "PostProcess/PostProcessDownsample.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "GPUFastFourierTransform.h"
#include "RendererModule.h"
#include "Rendering/Texture2DResource.h"
#include "ScenePrivate.h"
#include "PostProcessing.h"
namespace
{
TAutoConsoleVariable<float> CVarBloomScreenPercentage(
TEXT("r.Bloom.ScreenPercentage"), 100.0f,
TEXT("Controles the axis resolution of the FFT convolution for bloom.\n"),
ECVF_Scalability | ECVF_RenderThreadSafe);
TAutoConsoleVariable<int32> CVarBloomCacheKernel(
TEXT("r.Bloom.CacheKernel"), 1,
TEXT("Whether to cache the kernel in spectral domain."),
ECVF_RenderThreadSafe);
TAutoConsoleVariable<int32> CVarAsynComputeFFTBloom(
TEXT("r.Bloom.AsyncCompute"), 1,
TEXT("Whether to run FFT bloom on async compute.\n"),
ECVF_RenderThreadSafe);
TAutoConsoleVariable<int32> CVarBloomWarnKernelResolution(
TEXT("r.Bloom.WarnKernelResolution"), 1,
TEXT("Whether to emit a warning when the resolution of the kernel is unecessary to high.\n")
TEXT(" 0: Disabled;\n")
TEXT(" 1: Emit the warning on consoles (default);\n")
TEXT(" 2: Emit the warning on all platforms;\n"),
ECVF_RenderThreadSafe);
static bool DoesPlatformSupportFFTBloom(EShaderPlatform Platform)
{
return FDataDrivenShaderPlatformInfo::GetSupportsFFTBloom(Platform);
}
class FFFTBloomShader : public FGlobalShader
{
public:
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return DoesPlatformSupportFFTBloom(Parameters.Platform);
}
FFFTBloomShader() = default;
FFFTBloomShader(const CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{}
};
class FBloomFindKernelCenterCS : public FFFTBloomShader
{
public:
DECLARE_GLOBAL_SHADER(FBloomFindKernelCenterCS);
SHADER_USE_PARAMETER_STRUCT(FBloomFindKernelCenterCS, FFFTBloomShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(FIntPoint, KernelSpatialTextureSize)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, KernelSpatialTexture)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, KernelCenterCoordOutput)
END_SHADER_PARAMETER_STRUCT()
};
class FBloomSurveyMaxScatterDispersionCS : public FFFTBloomShader
{
public:
DECLARE_GLOBAL_SHADER(FBloomSurveyMaxScatterDispersionCS);
SHADER_USE_PARAMETER_STRUCT(FBloomSurveyMaxScatterDispersionCS, FFFTBloomShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(float, ViewTexelRadiusInKernelTexels)
SHADER_PARAMETER(int32, SurveyGroupGridSize)
SHADER_PARAMETER(FIntPoint, KernelSpatialTextureSize)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, KernelSpatialTexture)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, KernelCenterCoordBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, SurveyOutput)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, DebugOutput)
END_SHADER_PARAMETER_STRUCT()
};
class FBloomSurveyKernelCenterEnergyCS : public FFFTBloomShader
{
public:
DECLARE_GLOBAL_SHADER(FBloomSurveyKernelCenterEnergyCS);
SHADER_USE_PARAMETER_STRUCT(FBloomSurveyKernelCenterEnergyCS, FFFTBloomShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(float, ViewTexelRadiusInKernelTexels)
SHADER_PARAMETER(int32, SurveyGroupGridSize)
SHADER_PARAMETER(FIntPoint, KernelSpatialTextureSize)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, KernelSpatialTexture)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, KernelCenterCoordBuffer)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, MaxScatterDispersionBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, SurveyOutput)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, DebugOutput)
END_SHADER_PARAMETER_STRUCT()
};
class FBloomReduceKernelSurveyCS : public FFFTBloomShader
{
public:
DECLARE_GLOBAL_SHADER(FBloomReduceKernelSurveyCS);
SHADER_USE_PARAMETER_STRUCT(FBloomReduceKernelSurveyCS, FFFTBloomShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(int32, SurveyReduceOp)
SHADER_PARAMETER(int32, SurveyGroupCount)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, SurveyOutput)
END_SHADER_PARAMETER_STRUCT()
};
class FBloomSumScatterDispersionEnergyCS : public FFFTBloomShader
{
public:
DECLARE_GLOBAL_SHADER(FBloomSumScatterDispersionEnergyCS);
SHADER_USE_PARAMETER_STRUCT(FBloomSumScatterDispersionEnergyCS, FFFTBloomShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(int32, PassId)
SHADER_PARAMETER(FIntPoint, ScatterDispersionTextureSize)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ScatterDispersionTexture)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, MaxScatterDispersionBuffer)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, ScatterDispersionOutput)
END_SHADER_PARAMETER_STRUCT()
};
class FBloomPackKernelConstantsCS : public FFFTBloomShader
{
public:
DECLARE_GLOBAL_SHADER(FBloomPackKernelConstantsCS);
SHADER_USE_PARAMETER_STRUCT(FBloomPackKernelConstantsCS, FFFTBloomShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, KernelCenterCoordBuffer)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, KernelCenterEnergyBuffer)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, MaxScatterDispersionBuffer)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ScatterDispersionTexture)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, KernelConstantsOutput)
END_SHADER_PARAMETER_STRUCT()
};
class FBloomClampKernelCS : public FFFTBloomShader
{
public:
DECLARE_GLOBAL_SHADER(FBloomClampKernelCS);
SHADER_USE_PARAMETER_STRUCT(FBloomClampKernelCS, FFFTBloomShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(int32, bProcessAlpha)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, KernelSpatialTexture)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, KernelConstantsBuffer)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, ClampedKernelSpatialOutput)
END_SHADER_PARAMETER_STRUCT()
};
class FBloomDownsampleKernelCS : public FFFTBloomShader
{
public:
DECLARE_GLOBAL_SHADER(FBloomDownsampleKernelCS);
SHADER_USE_PARAMETER_STRUCT(FBloomDownsampleKernelCS, FFFTBloomShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(FIntPoint, KernelSpatialTextureSize)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, KernelSpatialTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, KernelSpatialSampler)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, DownsampleKernelSpatialOutput)
END_SHADER_PARAMETER_STRUCT()
};
class FBloomResizeKernelCS : public FFFTBloomShader
{
public:
DECLARE_GLOBAL_SHADER(FBloomResizeKernelCS);
SHADER_USE_PARAMETER_STRUCT(FBloomResizeKernelCS, FFFTBloomShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(FIntPoint, DstExtent)
SHADER_PARAMETER(FIntPoint, ImageExtent)
SHADER_PARAMETER(FVector2f, KernelSpatialTextureInvSize)
SHADER_PARAMETER(FIntPoint, DstBufferExtent)
SHADER_PARAMETER(float, KernelSupportScale)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, KernelConstantsBuffer)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SrcTexture)
SHADER_PARAMETER_SAMPLER(SamplerState, SrcSampler)
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, DstTexture)
END_SHADER_PARAMETER_STRUCT()
};
class FBloomFinalizeApplyConstantsCS : public FFFTBloomShader
{
public:
DECLARE_GLOBAL_SHADER(FBloomFinalizeApplyConstantsCS);
SHADER_USE_PARAMETER_STRUCT(FBloomFinalizeApplyConstantsCS, FFFTBloomShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(float, ScatterDispersionIntensity)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, KernelConstantsBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, SceneColorApplyOutput)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, FFTMulitplyOutput)
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FBloomFindKernelCenterCS, "/Engine/Private/Bloom/BloomFindKernelCenter.usf", "MainCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FBloomSurveyMaxScatterDispersionCS, "/Engine/Private/Bloom/BloomSurveyMaxScatterDispersion.usf", "MainCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FBloomSurveyKernelCenterEnergyCS, "/Engine/Private/Bloom/BloomSurveyKernelCenterEnergy.usf", "MainCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FBloomReduceKernelSurveyCS, "/Engine/Private/Bloom/BloomReduceKernelSurvey.usf", "MainCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FBloomSumScatterDispersionEnergyCS, "/Engine/Private/Bloom/BloomSumScatterDispersionEnergy.usf", "MainCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FBloomPackKernelConstantsCS, "/Engine/Private/Bloom/BloomPackKernelConstants.usf", "MainCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FBloomClampKernelCS, "/Engine/Private/Bloom/BloomClampKernel.usf", "MainCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FBloomDownsampleKernelCS, "/Engine/Private/Bloom/BloomDownsampleKernel.usf", "MainCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FBloomResizeKernelCS, "/Engine/Private/Bloom/BloomResizeKernel.usf", "MainCS", SF_Compute);
IMPLEMENT_GLOBAL_SHADER(FBloomFinalizeApplyConstantsCS, "/Engine/Private/Bloom/BloomFinalizeApplyConstants.usf", "MainCS", SF_Compute);
} //! namespace
float GetFFTBloomResolutionFraction(const FIntPoint& ViewSize)
{
float MaxResolutionFraction = FMath::Min(float(GMaxTextureDimensions) * 0.5f / float(FMath::Max(ViewSize.X, ViewSize.Y)), 1.0f);
return FMath::Clamp(CVarBloomScreenPercentage.GetValueOnRenderThread() / 100.0f, 0.1f, MaxResolutionFraction);
}
bool IsFFTBloomEnabled(const FViewInfo& View)
{
const bool bUseFFTBloom = View.FinalPostProcessSettings.BloomMethod == EBloomMethod::BM_FFT && View.ViewState != nullptr && DoesPlatformSupportFFTBloom(View.GetShaderPlatform());
static bool bWarnAboutOldMetalFFTOnce = false;
if (bUseFFTBloom)
{
return View.FFTBloomKernelTexture != nullptr;
}
else
{
return false;
}
}
FRDGTextureRef TransformKernelFFT(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
FRDGTextureRef ResizedKernel,
bool bDoHorizontalFirst,
FIntPoint FrequencySize)
{
// Our frequency storage layout adds two elements to the first transform direction.
const FIntPoint FrequencyPadding = (bDoHorizontalFirst) ? FIntPoint(2, 0) : FIntPoint(0, 2);
const FIntPoint PaddedFrequencySize = FrequencySize + FrequencyPadding;
// Should read / write to PF_G16R16F or PF_G32R32F (float2 formats)
// Need to set the render target description before we "request surface"
FRDGTextureRef SpectralKernel;
{
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
PaddedFrequencySize,
GPUFFT::PixelFormat(),
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
SpectralKernel = GraphBuilder.CreateTexture(Desc, TEXT("Bloom.FFT.SpectralKernel"));
}
FIntRect SrcRect(FIntPoint(0, 0), FrequencySize);
GPUFFT::FFTImage2D(
GraphBuilder,
View.ShaderMap,
FrequencySize, bDoHorizontalFirst,
GraphBuilder.CreateSRV(FRDGTextureSRVDesc(ResizedKernel)), SrcRect,
SpectralKernel);
return SpectralKernel;
}
struct FFFTBloomIntermediates
{
// The sub-domain of the Input/Output buffers
// where the image lives, i.e. the region of interest
FIntPoint ImageSize;
// Image space, padded by black for kernel and rounded up to powers of two
// this defines the size of the FFT in each direction.
FIntPoint FrequencySize;
FVector3f PreFilter;
float KernelSupportScale = 0.0f;
float KernelSupportScaleClamp = 0.0f;
// The order of the two-dimensional transform. This implicitly defines
// the data layout in transform space for both the kernel and image transform
bool bDoHorizontalFirst = false;
ERDGPassFlags ComputePassFlags = ERDGPassFlags::Compute;
};
void InitDomainAndGetKernel(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
const FFFTBloomIntermediates& Intermediates,
FRDGTextureRef* OutSpectralKernelTexture,
FRDGBufferRef* OutKernelConstantsBuffer)
{
FSceneViewState* ViewState = View.ViewState;
const auto& PPSettings = View.FinalPostProcessSettings;
// This should exist if we called IsFFTBloomPhysicalKernelReady.
check(View.FFTBloomKernelTexture && View.FFTBloomKernelTexture->TextureRHI);
const FTexture2DResource* BloomConvolutionTextureResource = View.FFTBloomKernelTexture;
const FTextureRHIRef& PhysicalSpaceKernelTextureRef = BloomConvolutionTextureResource->TextureRHI;
const float BloomConvolutionSize = PPSettings.BloomConvolutionSize;
const FVector2f CenterUV = FVector2f(PPSettings.BloomConvolutionCenterUV); // TODO: remove
// Our frequency storage layout adds two elements to the first transform direction.
const FIntPoint FrequencyPadding = (Intermediates.bDoHorizontalFirst) ? FIntPoint(2, 0) : FIntPoint(0, 2);
const FIntPoint PaddedFrequencySize = Intermediates.FrequencySize + FrequencyPadding;
// Should read / write to PF_G16R16F or PF_G32R32F (float2 formats)
// Need to set the render target description before we "request surface"
const FRDGTextureDesc TransformDesc = FRDGTextureDesc::Create2D(
PaddedFrequencySize,
GPUFFT::PixelFormat(),
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
if (ViewState && ViewState->BloomFFTKernel.Spectral && CVarBloomCacheKernel.GetValueOnRenderThread() != 0)
{
auto& FFTKernel = ViewState->BloomFFTKernel;
FRDGTextureRef PrevCachedSpectralKernel = GraphBuilder.RegisterExternalTexture(FFTKernel.Spectral);
const bool bSameTexture = (FFTKernel.PhysicalRHI == PhysicalSpaceKernelTextureRef);
const bool bSameSpectralBuffer = TransformDesc.ClearValue == PrevCachedSpectralKernel->Desc.ClearValue
&& TransformDesc.Flags == PrevCachedSpectralKernel->Desc.Flags
&& TransformDesc.Format == PrevCachedSpectralKernel->Desc.Format
&& TransformDesc.Extent == PrevCachedSpectralKernel->Desc.Extent;
const bool bSameKernelSize = FMath::IsNearlyEqual(FFTKernel.Scale, BloomConvolutionSize, float(1.e-6) /*tol*/);
const bool bSameImageSize = (Intermediates.ImageSize == FFTKernel.ImageSize);
const bool bSameMipLevel = bSameTexture && FFTKernel.PhysicalMipLevel == BloomConvolutionTextureResource->GetCurrentMipCount();
if (bSameTexture && bSameSpectralBuffer && bSameKernelSize && bSameImageSize && bSameMipLevel)
{
*OutSpectralKernelTexture = PrevCachedSpectralKernel;
*OutKernelConstantsBuffer = GraphBuilder.RegisterExternalBuffer(FFTKernel.ConstantsBuffer);
return;
}
}
// Re-transform the kernel if needed -- needs to run on all GPUs in case view's GPU assignment changes later.
RDG_EVENT_SCOPE(GraphBuilder, "InitBloomKernel");
RDG_GPU_MASK_SCOPE(GraphBuilder, FRHIGPUMask::All());
FRDGTextureRef SpatialKernelTexture = RegisterExternalTexture(GraphBuilder, PhysicalSpaceKernelTextureRef, TEXT("Bloom.FFT.OriginalKernel"));
// Find the center of the kernel
FRDGBufferRef KernelCenterCoordBuffer;
{
KernelCenterCoordBuffer = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateStructuredDesc(sizeof(int32_t), /* NumElements = */ 4),
TEXT("Bloom.FFT.KernelCenterCoord"));
FBloomFindKernelCenterCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FBloomFindKernelCenterCS::FParameters>();
PassParameters->KernelSpatialTextureSize = SpatialKernelTexture->Desc.Extent;
PassParameters->KernelSpatialTexture = SpatialKernelTexture;
PassParameters->KernelCenterCoordOutput = GraphBuilder.CreateUAV(KernelCenterCoordBuffer);
AddClearUAVPass(GraphBuilder, PassParameters->KernelCenterCoordOutput, /* Value = */ 0);
TShaderMapRef<FBloomFindKernelCenterCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("FFTBloom FindKernelCenter %dx%d", SpatialKernelTexture->Desc.Extent.X, SpatialKernelTexture->Desc.Extent.Y),
Intermediates.ComputePassFlags,
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(SpatialKernelTexture->Desc.Extent, 8));
}
// Find information about the kernel's energy distribution
FRDGBufferRef MaxScatterDispersionBuffer;
FRDGBufferRef KernelCenterEnergyBuffer;
{
const int32 SurveyTileSize = 8;
float KernelSizeInDstPixels = FMath::Max(float(Intermediates.ImageSize.X) * Intermediates.KernelSupportScale, 1.0f);
// Diameter of a view texel in the kernel.
float ViewTexelDiameterInKernelTexels = FMath::Max(SpatialKernelTexture->Desc.Extent.X / KernelSizeInDstPixels, 1.0f);
float ViewTexelRadiusInKernelTexels = ViewTexelDiameterInKernelTexels * 0.5f;
int32 SurveyGroupGridSize = 2 * FMath::DivideAndRoundUp(FMath::CeilToInt(ViewTexelRadiusInKernelTexels) + 4, SurveyTileSize);
int32 SurveyGroupCount = SurveyGroupGridSize * SurveyGroupGridSize;
FRDGTextureUAVRef DebugTextureUAV;
{
FRDGTextureDesc DebugDesc = FRDGTextureDesc::Create2D(
FIntPoint(SurveyGroupGridSize * 8, SurveyGroupGridSize * 8),
PF_FloatRGBA,
FClearValueBinding::None,
/* InFlags = */ TexCreate_ShaderResource | TexCreate_UAV);
FRDGTextureRef DebugTexture = GraphBuilder.CreateTexture(DebugDesc, TEXT("Debug.Bloom.Survey"));
DebugTextureUAV = GraphBuilder.CreateUAV(DebugTexture);
}
RDG_EVENT_SCOPE(GraphBuilder, "FFTBloom SurveyKernel(TexelDiameter=%f)", ViewTexelDiameterInKernelTexels);
// Reduce a survey buffer to a single value.
auto ReduceSurveyBuffer = [&](FRDGBufferRef SurveyBuffer, int32 Op)
{
FBloomReduceKernelSurveyCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FBloomReduceKernelSurveyCS::FParameters>();
PassParameters->SurveyReduceOp = Op;
PassParameters->SurveyGroupCount = SurveyGroupCount;
PassParameters->SurveyOutput = GraphBuilder.CreateUAV(SurveyBuffer);
TShaderMapRef<FBloomReduceKernelSurveyCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("FFTBloom ReduceKernelSurvey(Op=%d) %d", Op, SurveyGroupCount),
Intermediates.ComputePassFlags,
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(SurveyGroupCount, 64));
};
// Find the max scatter dispersion to use around the footprint of the view pixel in the kernel.
{
MaxScatterDispersionBuffer = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateStructuredDesc(sizeof(FLinearColor), /* NumElements = */ SurveyGroupCount),
TEXT("Bloom.FFT.MaxScatterDispersion"));
FBloomSurveyMaxScatterDispersionCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FBloomSurveyMaxScatterDispersionCS::FParameters>();
PassParameters->ViewTexelRadiusInKernelTexels = ViewTexelRadiusInKernelTexels;
PassParameters->SurveyGroupGridSize = SurveyGroupGridSize;
PassParameters->KernelSpatialTextureSize = SpatialKernelTexture->Desc.Extent;
PassParameters->KernelSpatialTexture = SpatialKernelTexture;
PassParameters->KernelCenterCoordBuffer = GraphBuilder.CreateSRV(KernelCenterCoordBuffer);
PassParameters->SurveyOutput = GraphBuilder.CreateUAV(MaxScatterDispersionBuffer);
PassParameters->DebugOutput = DebugTextureUAV;
TShaderMapRef<FBloomSurveyMaxScatterDispersionCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("FFTBloom SurveyMaxScatterDispersion"),
Intermediates.ComputePassFlags,
ComputeShader,
PassParameters,
FIntVector(SurveyGroupGridSize, SurveyGroupGridSize, 1));
ReduceSurveyBuffer(MaxScatterDispersionBuffer, /* Op = */ 0);
}
// Find the amount of energy at the center in the footprint of the view pixel in the kernel.
{
KernelCenterEnergyBuffer = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateStructuredDesc(sizeof(FLinearColor), /* NumElements = */ SurveyGroupCount),
TEXT("Bloom.FFT.KernelCenterEnergy"));
FBloomSurveyKernelCenterEnergyCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FBloomSurveyKernelCenterEnergyCS::FParameters>();
PassParameters->ViewTexelRadiusInKernelTexels = ViewTexelRadiusInKernelTexels;
PassParameters->SurveyGroupGridSize = SurveyGroupGridSize;
PassParameters->KernelSpatialTextureSize = SpatialKernelTexture->Desc.Extent;
PassParameters->KernelSpatialTexture = SpatialKernelTexture;
PassParameters->KernelCenterCoordBuffer = GraphBuilder.CreateSRV(KernelCenterCoordBuffer);
PassParameters->MaxScatterDispersionBuffer = GraphBuilder.CreateSRV(MaxScatterDispersionBuffer);
PassParameters->SurveyOutput = GraphBuilder.CreateUAV(KernelCenterEnergyBuffer);
PassParameters->DebugOutput = DebugTextureUAV;
TShaderMapRef<FBloomSurveyKernelCenterEnergyCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("FFTBloom SurveyKernelCenterEnergy"),
Intermediates.ComputePassFlags,
ComputeShader,
PassParameters,
FIntVector(SurveyGroupGridSize, SurveyGroupGridSize, 1));
ReduceSurveyBuffer(KernelCenterEnergyBuffer, /* Op = */ 1);
}
}
// Find out the total energy of the kernel - center.
// Implemented in such away to prioritize numerical error rather than speed.
FRDGTextureRef ScatterDispersionTexture;
{
RDG_EVENT_SCOPE(GraphBuilder, "FFTBloom SumScatterDispersionEnergy %dx%d",
SpatialKernelTexture->Desc.Extent.X, SpatialKernelTexture->Desc.Extent.Y);
ScatterDispersionTexture = SpatialKernelTexture;
for (int32 PassId = 0; ScatterDispersionTexture->Desc.Extent.X > 1 && ScatterDispersionTexture->Desc.Extent.Y > 1; PassId++)
{
FRDGTextureRef NewScatterDispersionTexture;
{
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
FIntPoint::DivideAndRoundUp(ScatterDispersionTexture->Desc.Extent, 8),
PF_A32B32G32R32F,
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
NewScatterDispersionTexture = GraphBuilder.CreateTexture(Desc, TEXT("Bloom.FFT.KernelIntensity"));
}
FBloomSumScatterDispersionEnergyCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FBloomSumScatterDispersionEnergyCS::FParameters>();
PassParameters->PassId = PassId;
PassParameters->ScatterDispersionTextureSize = ScatterDispersionTexture->Desc.Extent;
PassParameters->ScatterDispersionTexture = ScatterDispersionTexture;
PassParameters->MaxScatterDispersionBuffer = GraphBuilder.CreateSRV(MaxScatterDispersionBuffer);
PassParameters->ScatterDispersionOutput = GraphBuilder.CreateUAV(NewScatterDispersionTexture);
TShaderMapRef<FBloomSumScatterDispersionEnergyCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("FFTBloom SumScatterDispersionEnergy %dx%d -> %dx%d",
ScatterDispersionTexture->Desc.Extent.X, ScatterDispersionTexture->Desc.Extent.Y,
NewScatterDispersionTexture->Desc.Extent.X, NewScatterDispersionTexture->Desc.Extent.Y),
Intermediates.ComputePassFlags,
ComputeShader,
PassParameters,
FIntVector(NewScatterDispersionTexture->Desc.Extent.X, NewScatterDispersionTexture->Desc.Extent.Y, 1));
ScatterDispersionTexture = NewScatterDispersionTexture;
}
}
// Packs all the kernel information into the same buffer.
FRDGBufferRef KernelConstantsBuffer = nullptr;
{
KernelConstantsBuffer = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateStructuredDesc(sizeof(float) * 16, /* NumElements = */ 1),
TEXT("Bloom.FFT.KernelConstants"));
FBloomPackKernelConstantsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FBloomPackKernelConstantsCS::FParameters>();
PassParameters->KernelCenterCoordBuffer = GraphBuilder.CreateSRV(KernelCenterCoordBuffer);
PassParameters->KernelCenterEnergyBuffer = GraphBuilder.CreateSRV(KernelCenterEnergyBuffer);
PassParameters->MaxScatterDispersionBuffer = GraphBuilder.CreateSRV(MaxScatterDispersionBuffer);
PassParameters->ScatterDispersionTexture = ScatterDispersionTexture;
PassParameters->KernelConstantsOutput = GraphBuilder.CreateUAV(KernelConstantsBuffer);
TShaderMapRef<FBloomPackKernelConstantsCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("FFTBloom PackKernelConstants"),
Intermediates.ComputePassFlags,
ComputeShader,
PassParameters,
FIntVector(1, 1, 1));
}
// Preprocess the original kernel for Fourier transformation
FRDGTextureRef ResizedKernel;
{
RDG_EVENT_SCOPE(GraphBuilder, "FFTBloom PreprocessKernel");
// Clamp the kernel to avoid highlight contamination in the resize.
FRDGTextureRef ClampedKernelTexture;
{
{
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
SpatialKernelTexture->Desc.Extent,
PF_FloatRGBA,
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
ClampedKernelTexture = GraphBuilder.CreateTexture(Desc, TEXT("Bloom.FFT.ClampedKernel"));
}
FBloomClampKernelCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FBloomClampKernelCS::FParameters>();
PassParameters->bProcessAlpha = IsPostProcessingWithAlphaChannelSupported();
PassParameters->KernelSpatialTexture = SpatialKernelTexture;
PassParameters->KernelConstantsBuffer = GraphBuilder.CreateSRV(KernelConstantsBuffer);
PassParameters->ClampedKernelSpatialOutput = GraphBuilder.CreateUAV(ClampedKernelTexture);
TShaderMapRef<FBloomClampKernelCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("FFTBloom ClampedKernel %dx%d",
SpatialKernelTexture->Desc.Extent.X,
SpatialKernelTexture->Desc.Extent.Y),
Intermediates.ComputePassFlags,
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(SpatialKernelTexture->Desc.Extent, 8));
}
// Downscale the clamped kernel succesively to have nice anti-aliased
{
int32 MajorAxisPixelCount = FMath::Max(Intermediates.ImageSize.X, Intermediates.ImageSize.Y);
float KernelSizeInDstPixels = FMath::Max(float(MajorAxisPixelCount) * Intermediates.KernelSupportScale, 1.f);
float DownscaleFactor = float(SpatialKernelTexture->Desc.Extent.X) / KernelSizeInDstPixels;
int32 RecommendedKernelDownsscale = 1;
for (float RemainingDownscaleFactor = DownscaleFactor; RemainingDownscaleFactor > 2.0f; RemainingDownscaleFactor *= 0.5f)
{
FRDGTextureRef NewClampedKernelTexture;
{
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
ClampedKernelTexture->Desc.Extent / 2,
PF_FloatRGBA,
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
NewClampedKernelTexture = GraphBuilder.CreateTexture(Desc, TEXT("Bloom.FFT.DownsampleKernel"));
}
FBloomDownsampleKernelCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FBloomDownsampleKernelCS::FParameters>();
PassParameters->KernelSpatialTextureSize = ClampedKernelTexture->Desc.Extent;
PassParameters->KernelSpatialTexture = ClampedKernelTexture;
PassParameters->KernelSpatialSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
PassParameters->DownsampleKernelSpatialOutput = GraphBuilder.CreateUAV(NewClampedKernelTexture);
TShaderMapRef<FBloomDownsampleKernelCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("FFTBloom DownsampleKernel %dx%d -> %dx%d",
ClampedKernelTexture->Desc.Extent.X,
ClampedKernelTexture->Desc.Extent.Y,
NewClampedKernelTexture->Desc.Extent.X,
NewClampedKernelTexture->Desc.Extent.Y),
Intermediates.ComputePassFlags,
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(NewClampedKernelTexture->Desc.Extent, 8));
ClampedKernelTexture = NewClampedKernelTexture;
RecommendedKernelDownsscale *= 2;
}
if (RecommendedKernelDownsscale > 1 && (CVarBloomWarnKernelResolution.GetValueOnRenderThread() > 1 || (CVarBloomWarnKernelResolution.GetValueOnRenderThread() == 1 && FDataDrivenShaderPlatformInfo::GetIsConsole(View.GetShaderPlatform()))))
{
FString WarningTexturePathName = "";
if (View.FinalPostProcessSettings.BloomConvolutionTexture)
{
WarningTexturePathName = "(" + View.FinalPostProcessSettings.BloomConvolutionTexture.GetPathName() + ")";
}
static bool bLogged = false;
UE_CLOG(!bLogged, LogRenderer, Warning, TEXT("The FPostProcessSettings::BloomConvolutionTexture %s could have it's resolution lowered by a factor of %d to save memory."), *WarningTexturePathName, RecommendedKernelDownsscale);
bLogged = true;
}
}
// Final resize the kernel for Fourier transformation
{
ResizedKernel = GraphBuilder.CreateTexture(TransformDesc, TEXT("Bloom.FFT.ResizedKernel"));
FBloomResizeKernelCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FBloomResizeKernelCS::FParameters>();
PassParameters->DstExtent = Intermediates.FrequencySize;
PassParameters->ImageExtent = Intermediates.ImageSize;
PassParameters->KernelSpatialTextureInvSize.X = 1.0f / float(SpatialKernelTexture->Desc.Extent.X);
PassParameters->KernelSpatialTextureInvSize.Y = 1.0f / float(SpatialKernelTexture->Desc.Extent.Y);
PassParameters->DstBufferExtent = Intermediates.FrequencySize;
PassParameters->KernelSupportScale = Intermediates.KernelSupportScale;
PassParameters->KernelConstantsBuffer = GraphBuilder.CreateSRV(KernelConstantsBuffer);
PassParameters->SrcTexture = ClampedKernelTexture;
PassParameters->SrcSampler = TStaticSamplerState<SF_Bilinear, AM_Wrap, AM_Wrap, AM_Wrap>::GetRHI();
PassParameters->DstTexture = GraphBuilder.CreateUAV(ResizedKernel);
TShaderMapRef<FBloomResizeKernelCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("FFTBloom PreProcessKernel %dx%d", Intermediates.FrequencySize.X, Intermediates.FrequencySize.Y),
Intermediates.ComputePassFlags,
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(PaddedFrequencySize, 8));
}
}
// Two Dimensional FFT of the physical space kernel.
FRDGTextureRef SpectralKernelTexture = TransformKernelFFT(
GraphBuilder, View,
ResizedKernel,
Intermediates.bDoHorizontalFirst,
Intermediates.FrequencySize);
// Update the data on the ViewState
if (ViewState && CVarBloomCacheKernel.GetValueOnRenderThread() != 0)
{
ViewState->BloomFFTKernel.Scale = BloomConvolutionSize;
ViewState->BloomFFTKernel.ImageSize = Intermediates.ImageSize;
ViewState->BloomFFTKernel.PhysicalRHI = PhysicalSpaceKernelTextureRef;
ViewState->BloomFFTKernel.PhysicalMipLevel = BloomConvolutionTextureResource->GetCurrentMipCount();
{
ViewState->BloomFFTKernel.Spectral.SafeRelease();
GraphBuilder.QueueTextureExtraction(SpectralKernelTexture, &ViewState->BloomFFTKernel.Spectral);
}
{
ViewState->BloomFFTKernel.ConstantsBuffer.SafeRelease();
GraphBuilder.QueueBufferExtraction(KernelConstantsBuffer, &ViewState->BloomFFTKernel.ConstantsBuffer);
}
}
*OutSpectralKernelTexture = SpectralKernelTexture;
*OutKernelConstantsBuffer = KernelConstantsBuffer;
}
FFFTBloomOutput AddFFTBloomPass(
FRDGBuilder& GraphBuilder,
const FViewInfo& View,
const FScreenPassTextureSlice& InputSceneColor,
float InputResolutionFraction,
const FEyeAdaptationParameters& EyeAdaptationParameters,
FRDGBufferRef EyeAdaptationBuffer,
const FLocalExposureParameters& LocalExposureParameters,
FRDGTextureRef LocalExposureTexture,
FRDGTextureRef BlurredLogLuminanceTexture)
{
check(InputSceneColor.IsValid());
const float ResolutionFraction = GetFFTBloomResolutionFraction(InputSceneColor.ViewRect.Size());
const float DownscaleResolutionFraction = ResolutionFraction / InputResolutionFraction;
check(DownscaleResolutionFraction <= 1.0f);
FFFTBloomIntermediates Intermediates;
{
FSceneViewState* ViewState = (FSceneViewState*)View.State;
check(ViewState);
const auto& PPSettings = View.FinalPostProcessSettings;
// The kernel parameters on the FinalPostProcess.
const float BloomConvolutionSize = PPSettings.BloomConvolutionSize;
const float KernelSupportScaleClamp = FMath::Clamp(PPSettings.BloomConvolutionBufferScale, 0.f, 1.f);
// Clip the Kernel support (i.e. bloom size) to 100% the screen width
const float MaxBloomSize = 1.f;
const float KernelSupportScale = FMath::Clamp(BloomConvolutionSize, 0.f, MaxBloomSize);
// We padd by 1/2 the number of pixels the kernel needs in the x-direction
// so if the kernel is being applied on the edge of the image it will see padding and not periodicity
// NB: If the kernel padding would force a transform buffer that is too big for group shared memory (> 4096)
// we clamp it. This could result in a wrap-around in the bloom (from one side of the screen to the other),
// but since the amplitude of the bloom kernel tails is usually very small, this shouldn't be too bad.
auto KernelRadiusSupportFunctor = [KernelSupportScale, KernelSupportScaleClamp](const FIntPoint& Size) ->int32
{
float ClampedKernelSupportScale = (KernelSupportScaleClamp > 0) ? FMath::Min(KernelSupportScale, KernelSupportScaleClamp) : KernelSupportScale;
int32 FilterRadius = FMath::CeilToInt(0.5 * ClampedKernelSupportScale * Size.X);
const int32 MaxFFTSize = GPUFFT::MaxScanLineLength();
int32 MaxDim = FMath::Max(Size.X, Size.Y);
if (MaxDim + FilterRadius > MaxFFTSize && MaxDim < MaxFFTSize) FilterRadius = MaxFFTSize - MaxDim;
return FilterRadius;
};
Intermediates.ImageSize.X = FMath::CeilToInt(InputSceneColor.ViewRect.Width() * DownscaleResolutionFraction);
Intermediates.ImageSize.Y = FMath::CeilToInt(InputSceneColor.ViewRect.Height() * DownscaleResolutionFraction);
Intermediates.KernelSupportScale = KernelSupportScale;
Intermediates.KernelSupportScaleClamp = KernelSupportScaleClamp;
// The pre-filter boost parameters for bright pixels. Because the Convolution PP work in pre-exposure space, the min and max needs adjustment.
Intermediates.PreFilter = FVector3f(PPSettings.BloomConvolutionPreFilterMin, PPSettings.BloomConvolutionPreFilterMax, PPSettings.BloomConvolutionPreFilterMult);
// The length of the a side of the square kernel image in pixels
const int32 KernelSize = FMath::CeilToInt(KernelSupportScale * FMath::Max(Intermediates.ImageSize.X, Intermediates.ImageSize.Y));
const int32 SpectralPadding = KernelRadiusSupportFunctor(Intermediates.ImageSize);
// The following are mathematically equivalent
// 1) Horizontal FFT / Vertical FFT / Filter / Vertical InvFFT / Horizontal InvFFT
// 2) Vertical FFT / Horizontal FFT / Filter / Horizontal InvFFT / Vertical InvFFT
// but we choose the one with the smallest intermediate buffer size
// The size of the input image plus padding that accounts for
// the width of the kernel. The ImageRect is virtually padded
// with black to account for the gather action of the convolution.
FIntPoint PaddedImageSize = Intermediates.ImageSize + FIntPoint(SpectralPadding, SpectralPadding);
PaddedImageSize.X = FMath::Max(PaddedImageSize.X, KernelSize);
PaddedImageSize.Y = FMath::Max(PaddedImageSize.Y, KernelSize);
Intermediates.FrequencySize = FIntPoint(FMath::RoundUpToPowerOfTwo(PaddedImageSize.X), FMath::RoundUpToPowerOfTwo(PaddedImageSize.Y));
// Choose to do to transform in the direction that results in writing the least amount of data to main memory.
Intermediates.bDoHorizontalFirst = ((Intermediates.FrequencySize.Y * PaddedImageSize.X) > (Intermediates.FrequencySize.X * PaddedImageSize.Y));
Intermediates.ComputePassFlags = (GSupportsEfficientAsyncCompute && CVarAsynComputeFFTBloom.GetValueOnRenderThread() != 0) ? ERDGPassFlags::AsyncCompute : ERDGPassFlags::Compute;
}
RDG_EVENT_SCOPE(GraphBuilder, "Bloom %dx%d", Intermediates.ImageSize.X, Intermediates.ImageSize.Y);
// Downscale the input to the final resolution fraction
FScreenPassTextureSlice FFTInputSceneColor;
if (ResolutionFraction != InputResolutionFraction)
{
FIntPoint Extent;
QuantizeSceneBufferSize(Intermediates.ImageSize, Extent);
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
Extent,
InputSceneColor.TextureSRV->Desc.Texture->Desc.Format,
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
FFTInputSceneColor.TextureSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc(
GraphBuilder.CreateTexture(Desc, TEXT("Bloom.FFT.Input"))));
FFTInputSceneColor.ViewRect = FIntRect(FIntPoint::ZeroValue, Intermediates.ImageSize);
AddDownsampleComputePass(GraphBuilder, View, InputSceneColor, FScreenPassTexture(FFTInputSceneColor.TextureSRV->Desc.Texture, FFTInputSceneColor.ViewRect), EDownsampleQuality::Low, Intermediates.ComputePassFlags);
}
else
{
FFTInputSceneColor = InputSceneColor;
}
if (LocalExposureTexture != nullptr)
{
FScreenPassTextureSlice Temp = FFTInputSceneColor;
FRDGTextureDesc Desc = FRDGTextureDesc::Create2D(
Temp.TextureSRV->Desc.Texture->Desc.Extent,
Temp.TextureSRV->Desc.Texture->Desc.Format,
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV);
FFTInputSceneColor.TextureSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc(
GraphBuilder.CreateTexture(Desc, TEXT("Bloom.FFT.Input"))));
AddApplyLocalExposurePass(
GraphBuilder,
View,
EyeAdaptationParameters,
EyeAdaptationBuffer,
LocalExposureParameters,
LocalExposureTexture,
BlurredLogLuminanceTexture,
Temp,
FFTInputSceneColor,
Intermediates.ComputePassFlags);
}
// Init the domain data update the cached kernel if needed.
FRDGTextureRef SpectralKernelTexture;
FRDGBufferRef KernelConstantsBuffer;
InitDomainAndGetKernel(
GraphBuilder, View, Intermediates,
/* out */ &SpectralKernelTexture,
/* out */ &KernelConstantsBuffer);
FFFTBloomOutput BloomOutput;
FRDGBufferRef FFTMulitplyParameters;
// Generate the apply constant buffer for the tone-maping pass.
{
check(FTonemapInputs::SupportsSceneColorApplyParametersBuffer(View.GetShaderPlatform()));
BloomOutput.SceneColorApplyParameters = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateStructuredDesc(sizeof(FLinearColor), /* NumElements = */ 1),
TEXT("Bloom.FFT.SceneColorApplyParameters"));
FFTMulitplyParameters = GraphBuilder.CreateBuffer(
FRDGBufferDesc::CreateStructuredDesc(sizeof(FLinearColor), /* NumElements = */ 1),
TEXT("Bloom.FFT.FFTMulitplyParameters"));
FBloomFinalizeApplyConstantsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FBloomFinalizeApplyConstantsCS::FParameters>();
PassParameters->ScatterDispersionIntensity = View.FinalPostProcessSettings.BloomConvolutionScatterDispersion * View.FinalPostProcessSettings.BloomIntensity;
PassParameters->KernelConstantsBuffer = GraphBuilder.CreateSRV(KernelConstantsBuffer);
PassParameters->SceneColorApplyOutput = GraphBuilder.CreateUAV(BloomOutput.SceneColorApplyParameters);
PassParameters->FFTMulitplyOutput = GraphBuilder.CreateUAV(FFTMulitplyParameters);
TShaderMapRef<FBloomFinalizeApplyConstantsCS> ComputeShader(View.ShaderMap);
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("FFTBloom FinalizeApplyConstants(ScatterDispersion=%f)", PassParameters->ScatterDispersionIntensity),
Intermediates.ComputePassFlags,
ComputeShader,
PassParameters,
FIntVector(1, 1, 1));
}
FRDGTextureDesc OutputSceneColorDesc = FRDGTextureDesc::Create2D(
FFTInputSceneColor.TextureSRV->Desc.Texture->Desc.Extent,
FFTInputSceneColor.TextureSRV->Desc.Texture->Desc.Format,
FClearValueBinding::None,
TexCreate_ShaderResource | TexCreate_UAV | GFastVRamConfig.Bloom);
BloomOutput.BloomTexture.Texture = GraphBuilder.CreateTexture(OutputSceneColorDesc, TEXT("Bloom.FFT.ScatterDispersion"));
BloomOutput.BloomTexture.ViewRect = FFTInputSceneColor.ViewRect;
GPUFFT::ConvolutionWithTextureImage2D(
GraphBuilder,
Intermediates.ComputePassFlags,
View.ShaderMap,
Intermediates.FrequencySize,
Intermediates.bDoHorizontalFirst,
SpectralKernelTexture,
FFTInputSceneColor.TextureSRV, FFTInputSceneColor.ViewRect,
BloomOutput.BloomTexture.Texture, BloomOutput.BloomTexture.ViewRect,
Intermediates.PreFilter,
FFTMulitplyParameters,
GFastVRamConfig.Bloom);
return BloomOutput;
}