// Copyright Epic Games, Inc. All Rights Reserved. /*========================================================================================= PostProcessDeviceEncodingOnly.cpp: Post processing device encoding only implementation. ===========================================================================================*/ #include "PostProcess/PostProcessDeviceEncodingOnly.h" #include "DataDrivenShaderPlatformInfo.h" #include "EngineGlobals.h" #include "ScenePrivate.h" #include "RendererModule.h" #include "PostProcess/PostProcessing.h" #include "PostProcess/SceneFilterRendering.h" #include "PostProcess/PostProcessCombineLUTs.h" #include "PostProcess/PostProcessMobile.h" #include "PostProcess/PostProcessing.h" #include "ClearQuad.h" #include "PipelineStateCache.h" #include "HDRHelper.h" #include "ACESUtils.h" namespace { const int32 GDeviceEncodingOnlyComputeTileSizeX = 8; const int32 GDeviceEncodingOnlyComputeTileSizeY = 8; namespace DeviceEncodingOnlyPermutation { // Desktop renderer permutation dimensions. class FDeviceEncodingOnlyOutputDeviceSRGB : SHADER_PERMUTATION_BOOL("OUTPUT_DEVICE_SRGB"); class FDeviceEncodingUseACES2 : SHADER_PERMUTATION_BOOL("USE_ACES_2"); using FDesktopDomain = TShaderPermutationDomain; } // namespace DeviceEncodingOnlyPermutation } // namespace FDeviceEncodingOnlyOutputDeviceParameters GetDeviceEncodingOnlyOutputDeviceParameters(const FSceneViewFamily& Family) { static TConsoleVariableData* CVarOutputGamma = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.TonemapperGamma")); EDisplayOutputFormat OutputDeviceValue; if (Family.SceneCaptureSource == SCS_FinalColorHDR) { OutputDeviceValue = EDisplayOutputFormat::HDR_LinearNoToneCurve; } else if (Family.SceneCaptureSource == SCS_FinalToneCurveHDR) { OutputDeviceValue = EDisplayOutputFormat::HDR_LinearWithToneCurve; } else { OutputDeviceValue = Family.RenderTarget->GetDisplayOutputFormat(); } float Gamma = CVarOutputGamma->GetValueOnRenderThread(); // In case gamma is unspecified, fall back to 2.2 which is the most common case if ((PLATFORM_APPLE || OutputDeviceValue == EDisplayOutputFormat::SDR_ExplicitGammaMapping) && Gamma == 0.0f) { Gamma = 2.2f; } // Enforce user-controlled ramp over sRGB or Rec709 if (Gamma > 0.0f && (OutputDeviceValue == EDisplayOutputFormat::SDR_sRGB || OutputDeviceValue == EDisplayOutputFormat::SDR_Rec709)) { OutputDeviceValue = EDisplayOutputFormat::SDR_ExplicitGammaMapping; } FVector InvDisplayGammaValue; InvDisplayGammaValue.X = 1.0f / Family.RenderTarget->GetDisplayGamma(); InvDisplayGammaValue.Y = 2.2f / Family.RenderTarget->GetDisplayGamma(); InvDisplayGammaValue.Z = 1.0f / FMath::Max(Gamma, 1.0f); FDeviceEncodingOnlyOutputDeviceParameters Parameters; Parameters.InverseGamma = (FVector3f)InvDisplayGammaValue; Parameters.OutputDevice = static_cast(OutputDeviceValue); Parameters.OutputGamut = static_cast(Family.RenderTarget->GetDisplayColorGamut()); Parameters.OutputMaxLuminance = HDRGetDisplayMaximumLuminance(); return Parameters; } BEGIN_SHADER_PARAMETER_STRUCT(FDeviceEncodingACESTonemapShaderParameters, ) SHADER_PARAMETER(FVector4f, ACESMinMaxData) // xy = min ACES/luminance, zw = max ACES/luminance SHADER_PARAMETER(FVector4f, ACESMidData) // x = mid ACES, y = mid luminance, z = mid slope SHADER_PARAMETER(FVector4f, ACESCoefsLow_0) // coeflow 0-3 SHADER_PARAMETER(FVector4f, ACESCoefsHigh_0) // coefhigh 0-3 SHADER_PARAMETER(float, ACESCoefsLow_4) SHADER_PARAMETER(float, ACESCoefsHigh_4) SHADER_PARAMETER(float, ACESSceneColorMultiplier) SHADER_PARAMETER(float, ACESGamutCompression) SHADER_PARAMETER_SRV(Texture2D, ACESReachTable) SHADER_PARAMETER_SRV(Texture2D, ACESGamutTable) SHADER_PARAMETER_SRV(Texture2D, ACESGammaTable) END_SHADER_PARAMETER_STRUCT() BEGIN_SHADER_PARAMETER_STRUCT(FDeviceEncodingOnlyParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_STRUCT_REF(FWorkingColorSpaceShaderParameters, WorkingColorSpace) SHADER_PARAMETER_STRUCT_INCLUDE(FDeviceEncodingOnlyOutputDeviceParameters, OutputDevice) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Color) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Output) SHADER_PARAMETER_STRUCT_INCLUDE(FDeviceEncodingACESTonemapShaderParameters, ACESTonemapParameters) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ColorTexture) SHADER_PARAMETER_SAMPLER(SamplerState, ColorSampler) SHADER_PARAMETER(float, EditorNITLevel) END_SHADER_PARAMETER_STRUCT() class FDeviceEncodingOnlyPS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FDeviceEncodingOnlyPS); SHADER_USE_PARAMETER_STRUCT(FDeviceEncodingOnlyPS, FGlobalShader); using FPermutationDomain = DeviceEncodingOnlyPermutation::FDesktopDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FDeviceEncodingOnlyParameters, DeviceEncoding) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; } }; IMPLEMENT_GLOBAL_SHADER(FDeviceEncodingOnlyPS, "/Engine/Private/PostProcessDeviceEncodingOnly.usf", "MainPS", SF_Pixel); class FDeviceEncodingOnlyCS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FDeviceEncodingOnlyCS); SHADER_USE_PARAMETER_STRUCT(FDeviceEncodingOnlyCS, FGlobalShader); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FDeviceEncodingOnlyParameters, DeviceEncoding) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWOutputTexture) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { if (!IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5)) { return false; } return true; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), GDeviceEncodingOnlyComputeTileSizeX); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), GDeviceEncodingOnlyComputeTileSizeY); } }; IMPLEMENT_GLOBAL_SHADER(FDeviceEncodingOnlyCS, "/Engine/Private/PostProcessDeviceEncodingOnly.usf", "MainCS", SF_Compute); FScreenPassTexture AddDeviceEncodingOnlyPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FDeviceEncodingOnlyInputs& Inputs) { check(Inputs.SceneColor.IsValid()); const FSceneViewFamily& ViewFamily = *(View.Family); const FPostProcessSettings& PostProcessSettings = View.FinalPostProcessSettings; const FScreenPassTextureViewport SceneColorViewport(Inputs.SceneColor); FScreenPassRenderTarget Output = Inputs.OverrideOutput; if (!Output.IsValid()) { FRDGTextureDesc OutputDesc = Inputs.SceneColor.Texture->Desc; OutputDesc.Reset(); // RGB is the color in LDR, A is the luminance for PostprocessAA OutputDesc.Format = Inputs.bOutputInHDR ? GRHIHDRDisplayOutputFormat : PF_B8G8R8A8; OutputDesc.ClearValue = FClearValueBinding(FLinearColor(0, 0, 0, 0)); OutputDesc.Flags |= View.bUseComputePasses ? TexCreate_UAV : TexCreate_RenderTargetable; OutputDesc.Flags |= GFastVRamConfig.Tonemap; const FDeviceEncodingOnlyOutputDeviceParameters OutputDeviceParameters = GetDeviceEncodingOnlyOutputDeviceParameters(*View.Family); const EDisplayOutputFormat OutputDevice = static_cast(OutputDeviceParameters.OutputDevice); if (OutputDevice == EDisplayOutputFormat::HDR_LinearEXR) { OutputDesc.Format = PF_A32B32G32R32F; } if (OutputDevice == EDisplayOutputFormat::HDR_LinearNoToneCurve || OutputDevice == EDisplayOutputFormat::HDR_LinearWithToneCurve) { OutputDesc.Format = PF_FloatRGBA; } Output = FScreenPassRenderTarget( GraphBuilder.CreateTexture(OutputDesc, TEXT("DeviceEncodingOnly")), Inputs.SceneColor.ViewRect, View.GetOverwriteLoadAction()); } const FScreenPassTextureViewport OutputViewport(Output); FRHISamplerState* BilinearClampSampler = TStaticSamplerState::GetRHI(); FRHISamplerState* PointClampSampler = TStaticSamplerState::GetRHI(); float EditorNITLevel = 160.0f; #if WITH_EDITOR { static auto CVarHDRNITLevel = IConsoleManager::Get().FindConsoleVariable(TEXT("Editor.HDRNITLevel")); if (CVarHDRNITLevel) { EditorNITLevel = CVarHDRNITLevel->GetFloat(); } } #endif FDeviceEncodingOnlyParameters CommonParameters; CommonParameters.View = View.ViewUniformBuffer; CommonParameters.WorkingColorSpace = GDefaultWorkingColorSpaceUniformBuffer.GetUniformBufferRef(); CommonParameters.OutputDevice = GetDeviceEncodingOnlyOutputDeviceParameters(ViewFamily); CommonParameters.Color = GetScreenPassTextureViewportParameters(SceneColorViewport); CommonParameters.Output = GetScreenPassTextureViewportParameters(OutputViewport); GetACESTonemapParameters(CommonParameters.ACESTonemapParameters); UE::Color::ACES::GetTransformResources( GraphBuilder, HDRGetDisplayMaximumLuminance(), CommonParameters.ACESTonemapParameters.ACESReachTable, CommonParameters.ACESTonemapParameters.ACESGamutTable, CommonParameters.ACESTonemapParameters.ACESGammaTable ); CommonParameters.ColorTexture = Inputs.SceneColor.Texture; CommonParameters.ColorSampler = BilinearClampSampler; CommonParameters.EditorNITLevel = EditorNITLevel; // Generate permutation vector for the desktop tonemapper. DeviceEncodingOnlyPermutation::FDesktopDomain DesktopPermutationVector; const bool bOutputDeviceSRGB = (CommonParameters.OutputDevice.OutputDevice == (uint32)EDisplayOutputFormat::SDR_sRGB); DesktopPermutationVector.Set(bOutputDeviceSRGB); static const auto CVarAcesVersion = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.HDR.Aces.Version")); DesktopPermutationVector.Set(CVarAcesVersion->GetValueOnRenderThread() > 1); // Override output might not support UAVs. const bool bComputePass = (Output.Texture->Desc.Flags & TexCreate_UAV) == TexCreate_UAV ? View.bUseComputePasses : false; if (bComputePass) { FDeviceEncodingOnlyCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->DeviceEncoding = CommonParameters; PassParameters->RWOutputTexture = GraphBuilder.CreateUAV(Output.Texture); FDeviceEncodingOnlyCS::FPermutationDomain PermutationVector; PermutationVector.Set(DesktopPermutationVector); TShaderMapRef ComputeShader(View.ShaderMap, PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("DeviceEncodingOnly %dx%d (CS)", OutputViewport.Rect.Width(), OutputViewport.Rect.Height()), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(OutputViewport.Rect.Size(), FIntPoint(GDeviceEncodingOnlyComputeTileSizeX, GDeviceEncodingOnlyComputeTileSizeY))); } else { FDeviceEncodingOnlyPS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->DeviceEncoding = CommonParameters; PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); TShaderMapRef VertexShader(View.ShaderMap); TShaderMapRef PixelShader(View.ShaderMap, DesktopPermutationVector); // If this is a stereo view, there's a good chance we need alpha out of the tonemapper // @todo: Remove this once Oculus fix the bug in their runtime that requires alpha here. //const bool bIsStereo = IStereoRendering::IsStereoEyeView(View); //FRHIBlendState* BlendState = bIsStereo ? FScreenPassPipelineState::FDefaultBlendState::GetRHI() : TStaticBlendStateWriteMask::GetRHI(); FRHIBlendState* BlendState = TStaticBlendStateWriteMask::GetRHI(); FRHIDepthStencilState* DepthStencilState = FScreenPassPipelineState::FDefaultDepthStencilState::GetRHI(); EScreenPassDrawFlags DrawFlags = EScreenPassDrawFlags::AllowHMDHiddenAreaMask; AddDrawScreenPass( GraphBuilder, RDG_EVENT_NAME("DeviceEncodingOnly %dx%d (PS)", OutputViewport.Rect.Width(), OutputViewport.Rect.Height()), View, OutputViewport, SceneColorViewport, FScreenPassPipelineState(VertexShader, PixelShader, BlendState, DepthStencilState), PassParameters, DrawFlags, [PixelShader, PassParameters](FRHICommandList& RHICmdList) { SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters); }); } return MoveTemp(Output); }