// Copyright Epic Games, Inc. All Rights Reserved. #include "PostProcess/PostProcessDownsample.h" #include "PostProcess/PostProcessEyeAdaptation.h" #include "PixelShaderUtils.h" namespace { const int32 GDownsampleTileSizeX = 8; const int32 GDownsampleTileSizeY = 8; BEGIN_SHADER_PARAMETER_STRUCT(FDownsampleParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Input) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Output) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, InputTexture) SHADER_PARAMETER_SAMPLER(SamplerState, InputSampler) END_SHADER_PARAMETER_STRUCT() FDownsampleParameters GetDownsampleParameters(const FViewInfo& View, FScreenPassTexture Output, FScreenPassTexture Input, EDownsampleQuality DownsampleMethod) { check(Output.IsValid()); check(Input.IsValid()); const FScreenPassTextureViewportParameters InputParameters = GetScreenPassTextureViewportParameters(FScreenPassTextureViewport(Input)); const FScreenPassTextureViewportParameters OutputParameters = GetScreenPassTextureViewportParameters(FScreenPassTextureViewport(Output)); FDownsampleParameters Parameters; Parameters.ViewUniformBuffer = View.ViewUniformBuffer; Parameters.Input = InputParameters; Parameters.Output = OutputParameters; Parameters.InputTexture = Input.Texture; Parameters.InputSampler = TStaticSamplerState::GetRHI(); return Parameters; } class FDownsampleQualityDimension : SHADER_PERMUTATION_ENUM_CLASS("DOWNSAMPLE_QUALITY", EDownsampleQuality); using FDownsamplePermutationDomain = TShaderPermutationDomain; class FDownsamplePS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FDownsamplePS); SHADER_USE_PARAMETER_STRUCT(FDownsamplePS, FGlobalShader); using FPermutationDomain = FDownsamplePermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FDownsampleParameters, Common) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; } }; IMPLEMENT_GLOBAL_SHADER(FDownsamplePS, "/Engine/Private/PostProcessDownsample.usf", "MainPS", SF_Pixel); class FDownsampleCS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FDownsampleCS); SHADER_USE_PARAMETER_STRUCT(FDownsampleCS, FGlobalShader); using FPermutationDomain = FDownsamplePermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FDownsampleParameters, Common) SHADER_PARAMETER(FScreenTransform, DispatchThreadIdToInputUV) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutComputeTexture) 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"), GDownsampleTileSizeX); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), GDownsampleTileSizeY); } }; IMPLEMENT_GLOBAL_SHADER(FDownsampleCS, "/Engine/Private/PostProcessDownsample.usf", "MainCS", SF_Compute); } //! namespace FScreenPassTexture AddDownsamplePass( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FDownsamplePassInputs& Inputs) { check(Inputs.SceneColor.IsValid()); bool bIsComputePass = View.bUseComputePasses; if ((Inputs.Flags & EDownsampleFlags::ForceRaster) == EDownsampleFlags::ForceRaster) { bIsComputePass = false; } FScreenPassRenderTarget Output; // Construct the output texture to be half resolution (rounded up to even) with an optional format override. { FRDGTextureDesc Desc = Inputs.SceneColor.Texture->Desc; Desc.Reset(); Desc.Extent = FIntPoint::DivideAndRoundUp(Desc.Extent, 2); Desc.Extent.X = FMath::Max(1, Desc.Extent.X); Desc.Extent.Y = FMath::Max(1, Desc.Extent.Y); Desc.Flags &= ~(TexCreate_RenderTargetable | TexCreate_UAV | TexCreate_Presentable); Desc.Flags |= bIsComputePass ? TexCreate_UAV : TexCreate_RenderTargetable; Desc.Flags |= GFastVRamConfig.Downsample; Desc.ClearValue = FClearValueBinding(FLinearColor(0, 0, 0, 0)); if (Inputs.FormatOverride != PF_Unknown) { Desc.Format = Inputs.FormatOverride; } if (Inputs.UserSuppliedOutput && Translate(Inputs.UserSuppliedOutput->GetDesc()) == Desc) { Output.Texture = GraphBuilder.RegisterExternalTexture(Inputs.UserSuppliedOutput, Inputs.Name); } else { Output.Texture = GraphBuilder.CreateTexture(Desc, Inputs.Name); } Output.ViewRect = FIntRect::DivideAndRoundUp(Inputs.SceneColor.ViewRect, 2); Output.LoadAction = ERenderTargetLoadAction::ENoAction; } if (bIsComputePass) { AddDownsampleComputePass(GraphBuilder, View, Inputs.SceneColor, Output, Inputs.Quality, bIsComputePass ? ERDGPassFlags::Compute : ERDGPassFlags::Raster); } else { FDownsamplePermutationDomain PermutationVector; PermutationVector.Set(Inputs.Quality); FDownsamplePS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->Common = GetDownsampleParameters(View, Output, Inputs.SceneColor, Inputs.Quality); PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); TShaderMapRef PixelShader(View.ShaderMap, PermutationVector); FPixelShaderUtils::AddFullscreenPass( GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("Downsample(%s Quality=%s PS) %dx%d -> %dx%d", Output.Texture->Name, Inputs.Quality == EDownsampleQuality::High ? TEXT("High") : TEXT("Bilinear"), Inputs.SceneColor.ViewRect.Width(), Inputs.SceneColor.ViewRect.Height(), Output.ViewRect.Width(), Output.ViewRect.Height()), PixelShader, PassParameters, Output.ViewRect); } return MoveTemp(Output); } void AddDownsampleComputePass(FRDGBuilder& GraphBuilder, const FViewInfo& View, FScreenPassTexture Input, FScreenPassTexture Output, EDownsampleQuality Quality, ERDGPassFlags PassFlags) { check(PassFlags == ERDGPassFlags::Compute || PassFlags == ERDGPassFlags::AsyncCompute); FDownsampleCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->Common = GetDownsampleParameters(View, Output, Input, Quality); PassParameters->DispatchThreadIdToInputUV = ((FScreenTransform::Identity + 0.5f) / Output.ViewRect.Size()) * FScreenTransform::ChangeTextureBasisFromTo(FScreenPassTextureViewport(Input), FScreenTransform::ETextureBasis::ViewportUV, FScreenTransform::ETextureBasis::TextureUV); PassParameters->OutComputeTexture = GraphBuilder.CreateUAV(Output.Texture); FDownsamplePermutationDomain PermutationVector; PermutationVector.Set(Quality); TShaderMapRef ComputeShader(View.ShaderMap, PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("Downsample(%s Quality=%s CS) %dx%d -> %dx%d", Output.Texture->Name, Quality == EDownsampleQuality::High ? TEXT("High") : TEXT("Bilinear"), Input.ViewRect.Width(), Input.ViewRect.Height(), Output.ViewRect.Width(), Output.ViewRect.Height()), PassFlags, ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(Output.ViewRect.Size(), FIntPoint(GDownsampleTileSizeX, GDownsampleTileSizeY))); } void FSceneDownsampleChain::Init( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FEyeAdaptationParameters& EyeAdaptationParameters, FScreenPassTexture HalfResolutionSceneColor, EDownsampleQuality DownsampleQuality, bool bLogLumaInAlpha) { check(HalfResolutionSceneColor.IsValid()); RDG_EVENT_SCOPE(GraphBuilder, "SceneDownsample"); static const TCHAR* PassNames[StageCount] = { nullptr, TEXT("Scene(1/4)"), TEXT("Scene(1/8)"), TEXT("Scene(1/16)"), TEXT("Scene(1/32)"), TEXT("Scene(1/64)") }; static_assert(UE_ARRAY_COUNT(PassNames) == StageCount, "PassNames size must equal StageCount"); // The first stage is the input. Textures[0] = HalfResolutionSceneColor; for (uint32 StageIndex = 1; StageIndex < StageCount; StageIndex++) { const uint32 PreviousStageIndex = StageIndex - 1; FDownsamplePassInputs PassInputs; PassInputs.Name = PassNames[StageIndex]; PassInputs.SceneColor = Textures[PreviousStageIndex]; PassInputs.Quality = DownsampleQuality; Textures[StageIndex] = AddDownsamplePass(GraphBuilder, View, PassInputs); if (bLogLumaInAlpha) { bLogLumaInAlpha = false; Textures[StageIndex] = AddBasicEyeAdaptationSetupPass(GraphBuilder, View, EyeAdaptationParameters, Textures[StageIndex]); } } bInitialized = true; }