// Copyright Epic Games, Inc. All Rights Reserved. #include "PostProcess/PostProcessBloomSetup.h" #include "PostProcess/PostProcessDownsample.h" #include "PostProcess/PostProcessFFTBloom.h" #include "PostProcess/PostProcessWeightedSampleSum.h" #include "PostProcess/PostProcessEyeAdaptation.h" #include "PostProcess/PostProcessLocalExposure.h" #include "PixelShaderUtils.h" #include "DataDrivenShaderPlatformInfo.h" #include "SceneRendering.h" namespace { const int32 GBloomSetupComputeTileSizeX = 8; const int32 GBloomSetupComputeTileSizeY = 8; TAutoConsoleVariable CVarBloomCross( TEXT("r.GaussianBloom.Cross"), 0.0f, TEXT("Experimental feature to give bloom kernel a more bright center sample (values between 1 and 3 work without causing aliasing)\n") TEXT("Existing bloom get lowered to match the same brightness\n") TEXT("<0 for a anisomorphic lens flare look (X only)\n") TEXT(" 0 off (default)\n") TEXT(">0 for a cross look (X and Y)"), ECVF_RenderThreadSafe); BEGIN_SHADER_PARAMETER_STRUCT(FBloomSetupParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Input) SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, InputTexture) SHADER_PARAMETER_SAMPLER(SamplerState, InputSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, LumBilateralGrid) SHADER_PARAMETER_SAMPLER(SamplerState, LumBilateralGridSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, BlurredLogLum) SHADER_PARAMETER_SAMPLER(SamplerState, BlurredLogLumSampler) SHADER_PARAMETER_STRUCT(FLocalExposureParameters, LocalExposure) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, EyeAdaptationBuffer) SHADER_PARAMETER_STRUCT(FEyeAdaptationParameters, EyeAdaptation) SHADER_PARAMETER(float, BloomThreshold) END_SHADER_PARAMETER_STRUCT() FBloomSetupParameters GetBloomSetupParameters( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FScreenPassTextureViewport& InputViewport, const FBloomSetupInputs& Inputs) { FBloomSetupParameters Parameters; Parameters.View = View.ViewUniformBuffer; Parameters.Input = GetScreenPassTextureViewportParameters(InputViewport); Parameters.InputTexture = Inputs.SceneColor.TextureSRV; Parameters.InputSampler = TStaticSamplerState::GetRHI(); Parameters.LumBilateralGrid = Inputs.LocalExposureTexture; Parameters.LumBilateralGridSampler = TStaticSamplerState::GetRHI(); Parameters.BlurredLogLum = Inputs.BlurredLogLuminanceTexture; Parameters.BlurredLogLumSampler = TStaticSamplerState::GetRHI(); Parameters.LocalExposure = Inputs.LocalExposureParameters != nullptr ? *Inputs.LocalExposureParameters : FLocalExposureParameters(); Parameters.EyeAdaptationBuffer = GraphBuilder.CreateSRV(Inputs.EyeAdaptationBuffer); Parameters.EyeAdaptation = *Inputs.EyeAdaptationParameters; Parameters.BloomThreshold = Inputs.Threshold; return Parameters; } class FBloomSetupPS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FBloomSetupPS); SHADER_USE_PARAMETER_STRUCT(FBloomSetupPS, FGlobalShader); class FLocalExposureDim : SHADER_PERMUTATION_BOOL("USE_LOCAL_EXPOSURE"); class FThresholdDim : SHADER_PERMUTATION_BOOL("USE_THRESHOLD"); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FBloomSetupParameters, BloomSetup) SHADER_PARAMETER(FScreenTransform, SvPositionToInputTextureUV) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } }; IMPLEMENT_GLOBAL_SHADER(FBloomSetupPS, "/Engine/Private/PostProcessBloom.usf", "BloomSetupPS", SF_Pixel); class FBloomSetupCS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FBloomSetupCS); SHADER_USE_PARAMETER_STRUCT(FBloomSetupCS, FGlobalShader); class FLocalExposureDim : SHADER_PERMUTATION_BOOL("USE_LOCAL_EXPOSURE"); class FThresholdDim : SHADER_PERMUTATION_BOOL("USE_THRESHOLD"); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FBloomSetupParameters, BloomSetup) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWOutputTexture) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), GBloomSetupComputeTileSizeX); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), GBloomSetupComputeTileSizeY); } }; IMPLEMENT_GLOBAL_SHADER(FBloomSetupCS, "/Engine/Private/PostProcessBloom.usf", "BloomSetupCS", SF_Compute); } //!namespace FScreenPassTexture AddBloomSetupPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FBloomSetupInputs& Inputs) { check(Inputs.SceneColor.IsValid()); check(Inputs.EyeAdaptationBuffer); const bool bIsComputePass = View.bUseComputePasses; const bool bLocalExposureEnabled = Inputs.LocalExposureTexture != nullptr; const bool bThresholdEnabled = Inputs.Threshold > -1.0f; check(bLocalExposureEnabled || bThresholdEnabled); const FRDGTextureDesc& InputDesc = Inputs.SceneColor.TextureSRV->Desc.Texture->Desc; FRDGTextureDesc OutputDesc = FRDGTextureDesc::Create2D( InputDesc.Extent, InputDesc.Format, FClearValueBinding::None, /* InFlags = */ TexCreate_ShaderResource | (bIsComputePass ? TexCreate_UAV : TexCreate_RenderTargetable) | (InputDesc.Flags & (TexCreate_FastVRAM | TexCreate_FastVRAMPartialAlloc))); const FScreenPassTextureViewport Viewport(Inputs.SceneColor); const FScreenPassRenderTarget Output(GraphBuilder.CreateTexture(OutputDesc, TEXT("BloomSetup")), Viewport.Rect, ERenderTargetLoadAction::ENoAction); if (bIsComputePass) { FBloomSetupCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->BloomSetup = GetBloomSetupParameters(GraphBuilder, View, Viewport, Inputs); PassParameters->RWOutputTexture = GraphBuilder.CreateUAV(Output.Texture); FBloomSetupCS::FPermutationDomain PermutationVector; PermutationVector.Set(bLocalExposureEnabled); PermutationVector.Set(bThresholdEnabled); auto ComputeShader = View.ShaderMap->GetShader(PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("BloomSetup %dx%d (CS)", Viewport.Rect.Width(), Viewport.Rect.Height()), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(Viewport.Rect.Size(), FIntPoint(GBloomSetupComputeTileSizeX, GBloomSetupComputeTileSizeY))); } else { FBloomSetupPS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->BloomSetup = GetBloomSetupParameters(GraphBuilder, View, Viewport, Inputs); PassParameters->SvPositionToInputTextureUV = ( FScreenTransform::ChangeTextureBasisFromTo(FScreenPassTextureViewport(Output), FScreenTransform::ETextureBasis::TexelPosition, FScreenTransform::ETextureBasis::ViewportUV) * FScreenTransform::ChangeTextureBasisFromTo(FScreenPassTextureViewport(Inputs.SceneColor), FScreenTransform::ETextureBasis::ViewportUV, FScreenTransform::ETextureBasis::TextureUV)); PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); FBloomSetupPS::FPermutationDomain PermutationVector; PermutationVector.Set(bLocalExposureEnabled); PermutationVector.Set(bThresholdEnabled); auto PixelShader = View.ShaderMap->GetShader(PermutationVector); FPixelShaderUtils::AddFullscreenPass( GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("BloomSetup %dx%d (PS)", Viewport.Rect.Width(), Viewport.Rect.Height()), PixelShader, PassParameters, Output.ViewRect); } return FScreenPassTexture(Output); } EBloomQuality GetBloomQuality() { static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.BloomQuality")); return static_cast(FMath::Clamp( CVar->GetValueOnRenderThread(), static_cast(EBloomQuality::Disabled), static_cast(EBloomQuality::MAX) - 1)); } FScreenPassTexture AddGaussianBloomPasses(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FTextureDownsampleChain* SceneDownsampleChain) { checkf(SceneDownsampleChain && SceneDownsampleChain->GetStageCount() == static_cast(EBloomQuality::MAX), TEXT("The total number of stages in the scene downsample chain and the number of bloom quality levels must match.")); check(!IsFFTBloomEnabled(View)); const FFinalPostProcessSettings& Settings = View.FinalPostProcessSettings; const EBloomQuality BloomQuality = GetBloomQuality(); FScreenPassTexture PassOutputs; if (BloomQuality != EBloomQuality::Disabled) { RDG_EVENT_SCOPE(GraphBuilder, "Bloom"); const float CrossBloom = CVarBloomCross.GetValueOnRenderThread(); const FVector2D CrossCenterWeight(FMath::Max(CrossBloom, 0.0f), FMath::Abs(CrossBloom)); check(BloomQuality != EBloomQuality::Disabled); const uint32 BloomQualityIndex = static_cast(BloomQuality); const uint32 BloomQualityCountMax = static_cast(EBloomQuality::MAX); struct FBloomStage { const float Size; const FLinearColor& Tint; }; FBloomStage BloomStages[] = { { Settings.Bloom6Size, Settings.Bloom6Tint }, { Settings.Bloom5Size, Settings.Bloom5Tint }, { Settings.Bloom4Size, Settings.Bloom4Tint }, { Settings.Bloom3Size, Settings.Bloom3Tint }, { Settings.Bloom2Size, Settings.Bloom2Tint }, { Settings.Bloom1Size, Settings.Bloom1Tint } }; const uint32 BloomQualityToSceneDownsampleStage[] = { static_cast(-1), // Disabled (sentinel entry to preserve indices) 3, // Q1 3, // Q2 4, // Q3 5, // Q4 6 // Q5 }; static_assert(UE_ARRAY_COUNT(BloomStages) == BloomQualityCountMax, "Array must be one less than the number of bloom quality entries."); static_assert(UE_ARRAY_COUNT(BloomQualityToSceneDownsampleStage) == BloomQualityCountMax, "Array must be one less than the number of bloom quality entries."); check(BloomQualityIndex < BloomQualityCountMax); // Use bloom quality to select the number of downsample stages to use for bloom. const uint32 BloomStageCount = BloomQualityToSceneDownsampleStage[BloomQualityIndex]; const float TintScale = (1.0f / BloomQualityCountMax) * Settings.BloomIntensity; for (uint32 StageIndex = 0, SourceIndex = BloomQualityCountMax - 1; StageIndex < BloomStageCount; ++StageIndex, --SourceIndex) { const FBloomStage& BloomStage = BloomStages[StageIndex]; if (BloomStage.Size > SMALL_NUMBER) { FGaussianBlurInputs PassInputs; PassInputs.NameX = TEXT("BloomX"); PassInputs.NameY = TEXT("BloomY"); PassInputs.Filter = SceneDownsampleChain->GetTexture(SourceIndex); PassInputs.Additive = PassOutputs; PassInputs.CrossCenterWeight = FVector2f(CrossCenterWeight); // LWC_TODO: Precision loss PassInputs.KernelSizePercent = BloomStage.Size * Settings.BloomSizeScale; PassInputs.TintColor = BloomStage.Tint * TintScale; PassOutputs = AddGaussianBlurPass(GraphBuilder, View, PassInputs); } } } return PassOutputs; }