// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= PostProcessTonemap.cpp: Post processing tone mapping implementation. =============================================================================*/ #include "PostProcess/PostProcessTonemap.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/PostProcessLocalExposure.h" #include "PostProcess/PostProcessMobile.h" #include "PostProcess/PostProcessing.h" #include "ClearQuad.h" #include "PipelineStateCache.h" #include "Rendering/Texture2DResource.h" #include "Math/Halton.h" #include "SystemTextures.h" #include "HDRHelper.h" #include "VariableRateShadingImageManager.h" #include "DataDrivenShaderPlatformInfo.h" #include "CommonRenderResources.h" bool SupportsFilmGrain(EShaderPlatform Platform) { return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5); } namespace { TAutoConsoleVariable CVarTonemapperSharpen( TEXT("r.Tonemapper.Sharpen"), -1, TEXT("Sharpening in the tonemapper (not for mobile), actual implementation is work in progress, clamped at 10\n") TEXT(" <0: inherit from PostProcessVolume settings (default)\n") TEXT(" 0: off\n") TEXT(" 0.5: half strength\n") TEXT(" 1: full strength"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable CVarTonemapperGamma( TEXT("r.TonemapperGamma"), 0.0f, TEXT("0: Default behavior\n") TEXT("#: Use fixed gamma # instead of sRGB or Rec709 transform"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable CVarGamma( TEXT("r.Gamma"), 1.0f, TEXT("Gamma on output"), ECVF_RenderThreadSafe); TAutoConsoleVariable CVarFilmGrainSequenceLength( TEXT("r.FilmGrain.SequenceLength"), 97, TEXT("Length of the random sequence for film grain (preferably a prime number, default=97)."), ECVF_RenderThreadSafe); TAutoConsoleVariable CVarFilmGrainCacheTextureConstants( TEXT("r.FilmGrain.CacheTextureConstants"), 1, TEXT("Wether the constants related to the film grain should be cached."), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarBackbufferQuantizationDitheringOverride( TEXT("r.BackbufferQuantizationDitheringOverride"), 0, TEXT("Override the bitdepth in bits of each channel of the backbuffer targeted by the quantization dithering. ") TEXT("Disabled by default. Instead is automatically found out by FSceneViewFamily::RenderTarget's pixel format of the backbuffer."), ECVF_RenderThreadSafe); const int32 GTonemapComputeTileSizeX = 8; const int32 GTonemapComputeTileSizeY = 8; struct FOutputLuminance { FRDGTextureRef Texture = nullptr; }; namespace TonemapperPermutation { // Shared permutation dimensions between deferred and mobile renderer. class FTonemapperGammaOnlyDim : SHADER_PERMUTATION_BOOL("USE_GAMMA_ONLY"); class FTonemapperLocalExposureDim : SHADER_PERMUTATION_INT("LOCAL_EXPOSURE_MODE", 3); class FTonemapperSharpenDim : SHADER_PERMUTATION_BOOL("USE_SHARPEN"); class FTonemapperFilmGrainDim : SHADER_PERMUTATION_BOOL("USE_FILM_GRAIN"); class FTonemapperMsaaDim : SHADER_PERMUTATION_BOOL("METAL_MSAA_HDR_DECODE"); class FTonemapperAlphaChannelDim : SHADER_PERMUTATION_BOOL("DIM_ALPHA_CHANNEL"); using FCommonDomain = TShaderPermutationDomain< FTonemapperGammaOnlyDim, FTonemapperLocalExposureDim, FTonemapperSharpenDim, FTonemapperFilmGrainDim, FTonemapperMsaaDim, FTonemapperAlphaChannelDim>; bool ShouldCompileCommonPermutation(const FGlobalShaderPermutationParameters& Parameters, const FCommonDomain& PermutationVector) { // MSAA pre-resolve step only used on iOS atm if (PermutationVector.Get() && !IsMetalMobilePlatform(Parameters.Platform)) { return false; } // If GammaOnly, don't compile any other dimmension == true. if (PermutationVector.Get()) { return !PermutationVector.Get() && !PermutationVector.Get() && !PermutationVector.Get() && !PermutationVector.Get(); } return true; } static float GetSharpenSetting(const FPostProcessSettings& Settings) { float CVarSharpen = CVarTonemapperSharpen.GetValueOnRenderThread(); float Sharpen = CVarSharpen >= 0.0 ? CVarSharpen : Settings.Sharpen; return FMath::Clamp(Sharpen, 0.0f, 10.0f); } // Common conversion of engine settings into. FCommonDomain BuildCommonPermutationDomain(const FViewInfo& View, bool bGammaOnly, bool bLocalExposure, bool bMetalMSAAHDRDecode) { const FSceneViewFamily* Family = View.Family; FCommonDomain PermutationVector; PermutationVector.Set(IsPostProcessingWithAlphaChannelSupported()); // Gamma if (bGammaOnly || (Family->EngineShowFlags.Tonemapper == 0) || (Family->EngineShowFlags.PostProcessing == 0)) { PermutationVector.Set(true); return PermutationVector; } const FPostProcessSettings& Settings = View.FinalPostProcessSettings; PermutationVector.Set(View.FilmGrainTexture != nullptr); PermutationVector.Set(GetSharpenSetting(Settings) > 0.0f); PermutationVector.Set(bMetalMSAAHDRDecode); if (bLocalExposure) { PermutationVector.Set(View.FinalPostProcessSettings.LocalExposureMethod == ELocalExposureMethod::Bilateral ? 1 : 2); } else { PermutationVector.Set(0); } return PermutationVector; } // Desktop renderer permutation dimensions. class FTonemapperColorFringeDim : SHADER_PERMUTATION_BOOL("USE_COLOR_FRINGE"); class FTonemapperOutputDeviceSRGB : SHADER_PERMUTATION_BOOL("OUTPUT_DEVICE_SRGB"); class FTonemapperOutputLuminance : SHADER_PERMUTATION_BOOL("OUTPUT_LUMINANCE"); using FDesktopDomain = TShaderPermutationDomain< FCommonDomain, FTonemapperColorFringeDim, FTonemapperOutputLuminance, FTonemapperOutputDeviceSRGB>; FDesktopDomain RemapPermutation(FDesktopDomain PermutationVector, ERHIFeatureLevel::Type FeatureLevel) { FCommonDomain CommonPermutationVector = PermutationVector.Get(); // No remapping if gamma only. if (CommonPermutationVector.Get()) { return PermutationVector; } bool bFallbackToSlowest = CommonPermutationVector.Get(); if (bFallbackToSlowest) { CommonPermutationVector.Set(true); CommonPermutationVector.Set(true); PermutationVector.Set(true); } if (FeatureLevel < ERHIFeatureLevel::SM5) { // Mobile doesn't support film grain. CommonPermutationVector.Set(false); } PermutationVector.Set(CommonPermutationVector); return PermutationVector; } bool ShouldCompileDesktopPermutation(const FGlobalShaderPermutationParameters& Parameters, FDesktopDomain PermutationVector) { auto CommonPermutationVector = PermutationVector.Get(); if (RemapPermutation(PermutationVector, GetMaxSupportedFeatureLevel(Parameters.Platform)) != PermutationVector) { return false; } if (!ShouldCompileCommonPermutation(Parameters, CommonPermutationVector)) { return false; } if (CommonPermutationVector.Get()) { return !PermutationVector.Get(); } return true; } } // namespace TonemapperPermutation } // namespace RDG_REGISTER_BLACKBOARD_STRUCT(FOutputLuminance); FTonemapperOutputDeviceParameters GetTonemapperOutputDeviceParameters(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; } FVector3f InvDisplayGammaValue; InvDisplayGammaValue.X = 1.0f / Family.RenderTarget->GetDisplayGamma(); InvDisplayGammaValue.Y = 2.2f / Family.RenderTarget->GetDisplayGamma(); InvDisplayGammaValue.Z = 1.0f / FMath::Max(Gamma, 1.0f); FTonemapperOutputDeviceParameters Parameters; Parameters.InverseGamma = InvDisplayGammaValue; Parameters.OutputDevice = static_cast(OutputDeviceValue); Parameters.OutputGamut = static_cast(Family.RenderTarget->GetDisplayColorGamut()); Parameters.OutputMaxLuminance = HDRGetDisplayMaximumLuminance(); return Parameters; } BEGIN_SHADER_PARAMETER_STRUCT(FFilmGrainParameters, ) SHADER_PARAMETER(FVector3f, GrainRandomFull) SHADER_PARAMETER(float, FilmGrainIntensityShadows) SHADER_PARAMETER(float, FilmGrainIntensityMidtones) SHADER_PARAMETER(float, FilmGrainIntensityHighlights) SHADER_PARAMETER(float, FilmGrainShadowsMax) SHADER_PARAMETER(float, FilmGrainHighlightsMin) SHADER_PARAMETER(float, FilmGrainHighlightsMax) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, FilmGrainTexture) SHADER_PARAMETER_SAMPLER(SamplerState, FilmGrainSampler) SHADER_PARAMETER(FScreenTransform, ScreenPosToFilmGrainTextureUV) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, FilmGrainTextureConstants) END_SHADER_PARAMETER_STRUCT() BEGIN_SHADER_PARAMETER_STRUCT(FTonemapParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View) SHADER_PARAMETER_STRUCT_INCLUDE(FFilmGrainParameters, FilmGrain) SHADER_PARAMETER_STRUCT_INCLUDE(FTonemapperOutputDeviceParameters, OutputDevice) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Color) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Output) SHADER_PARAMETER_STRUCT(FEyeAdaptationParameters, EyeAdaptation) SHADER_PARAMETER_STRUCT(FLocalExposureParameters, LocalExposure) SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, ColorTexture) // Parameters to apply to the scene color. SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, SceneColorApplyParamaters) // Bloom texture SHADER_PARAMETER(FScreenTransform, ColorToBloom) SHADER_PARAMETER(FVector2f, BloomUVViewportBilinearMin) SHADER_PARAMETER(FVector2f, BloomUVViewportBilinearMax) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, BloomTexture) SHADER_PARAMETER_SAMPLER(SamplerState, BloomSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture3D, LumBilateralGrid) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, BlurredLogLum) SHADER_PARAMETER_SAMPLER(SamplerState, LumBilateralGridSampler) SHADER_PARAMETER_SAMPLER(SamplerState, BlurredLogLumSampler) SHADER_PARAMETER(FScreenTransform, ColorToExposureFusion) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, ExposureFusion) SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, ExposureFusionTexture) SHADER_PARAMETER_SAMPLER(SamplerState, ExposureFusionSampler) SHADER_PARAMETER(float, FilmSlope) SHADER_PARAMETER(float, FilmToe) SHADER_PARAMETER(float, FilmShoulder) SHADER_PARAMETER(float, FilmBlackClip) SHADER_PARAMETER(float, FilmWhiteClip) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, EyeAdaptationBuffer) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ColorGradingLUT) SHADER_PARAMETER_TEXTURE(Texture2D, BloomDirtMaskTexture) SHADER_PARAMETER_SAMPLER(SamplerState, ColorSampler) SHADER_PARAMETER_SAMPLER(SamplerState, ColorGradingLUTSampler) SHADER_PARAMETER_SAMPLER(SamplerState, BloomDirtMaskSampler) SHADER_PARAMETER(FVector4f, ColorScale0) SHADER_PARAMETER(FVector4f, BloomDirtMaskTint) SHADER_PARAMETER(FVector4f, ChromaticAberrationParams) SHADER_PARAMETER(FVector4f, TonemapperParams) SHADER_PARAMETER(FVector4f, LensPrincipalPointOffsetScale) SHADER_PARAMETER(FVector4f, LensPrincipalPointOffsetScaleInverse) SHADER_PARAMETER(float, LUTSize) SHADER_PARAMETER(float, InvLUTSize) SHADER_PARAMETER(float, LUTScale) SHADER_PARAMETER(float, LUTOffset) SHADER_PARAMETER(float, EditorNITLevel) SHADER_PARAMETER(float, BackbufferQuantizationDithering) END_SHADER_PARAMETER_STRUCT() class FFilmGrainReduceCS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FFilmGrainReduceCS); SHADER_USE_PARAMETER_STRUCT(FFilmGrainReduceCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(FIntPoint, FilmGrainTextureSize) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, FilmGrainTexture) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, Output) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return SupportsFilmGrain(Parameters.Platform); } }; class FFilmGrainPackConstantsCS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FFilmGrainPackConstantsCS); SHADER_USE_PARAMETER_STRUCT(FFilmGrainPackConstantsCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(FIntPoint, OriginalFilmGrainTextureSize) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ReducedFilmGrainTexture) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, FilmGrainConstantsOutput) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return SupportsFilmGrain(Parameters.Platform); } }; class FTonemapVS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FTonemapVS); // FDrawRectangleParameters is filled by DrawScreenPass. SHADER_USE_PARAMETER_STRUCT_WITH_LEGACY_BASE(FTonemapVS, FGlobalShader); using FParameters = FTonemapParameters; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return true; } }; class FTonemapPS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FTonemapPS); SHADER_USE_PARAMETER_STRUCT(FTonemapPS, FGlobalShader); using FPermutationDomain = TonemapperPermutation::FDesktopDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FTonemapParameters, Tonemap) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { if (!IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::ES3_1)) { return false; } return TonemapperPermutation::ShouldCompileDesktopPermutation(Parameters, FPermutationDomain(Parameters.PermutationId)); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { const int UseVolumeLut = PipelineVolumeTextureLUTSupportGuaranteedAtRuntime(Parameters.Platform) ? 1 : 0; OutEnvironment.SetDefine(TEXT("USE_VOLUME_LUT"), UseVolumeLut); OutEnvironment.SetDefine(TEXT("SUPPORTS_SCENE_COLOR_APPLY_PARAMETERS"), FTonemapInputs::SupportsSceneColorApplyParametersBuffer(Parameters.Platform) ? 1 : 0); OutEnvironment.SetDefine(TEXT("SUPPORTS_FILM_GRAIN"), SupportsFilmGrain(Parameters.Platform) ? 1 : 0); } }; class FTonemapCS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FTonemapCS); SHADER_USE_PARAMETER_STRUCT(FTonemapCS, FGlobalShader); using FPermutationDomain = TonemapperPermutation::FDesktopDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FTonemapParameters, Tonemap) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWOutputTexture) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWOutputLuminance) END_SHADER_PARAMETER_STRUCT() static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { if (!IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5)) { return false; } FPermutationDomain PermutationVector(Parameters.PermutationId); return TonemapperPermutation::ShouldCompileDesktopPermutation(Parameters, PermutationVector); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), GTonemapComputeTileSizeX); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), GTonemapComputeTileSizeY); const int UseVolumeLut = PipelineVolumeTextureLUTSupportGuaranteedAtRuntime(Parameters.Platform) ? 1 : 0; OutEnvironment.SetDefine(TEXT("USE_VOLUME_LUT"), UseVolumeLut); OutEnvironment.SetDefine(TEXT("SUPPORTS_SCENE_COLOR_APPLY_PARAMETERS"), FTonemapInputs::SupportsSceneColorApplyParametersBuffer(Parameters.Platform) ? 1 : 0); OutEnvironment.SetDefine(TEXT("SUPPORTS_FILM_GRAIN"), SupportsFilmGrain(Parameters.Platform) ? 1 : 0); } }; IMPLEMENT_GLOBAL_SHADER(FFilmGrainReduceCS, "/Engine/Private/PostProcessing/FilmGrainReduce.usf", "MainCS", SF_Compute); IMPLEMENT_GLOBAL_SHADER(FFilmGrainPackConstantsCS, "/Engine/Private/PostProcessing/FilmGrainPackConstants.usf", "MainCS", SF_Compute); IMPLEMENT_GLOBAL_SHADER(FTonemapVS, "/Engine/Private/PostProcessTonemap.usf", "MainVS", SF_Vertex); IMPLEMENT_GLOBAL_SHADER(FTonemapPS, "/Engine/Private/PostProcessTonemap.usf", "MainPS", SF_Pixel); IMPLEMENT_GLOBAL_SHADER(FTonemapCS, "/Engine/Private/PostProcessTonemap.usf", "MainCS", SF_Compute); bool FTonemapInputs::SupportsSceneColorApplyParametersBuffer(EShaderPlatform Platform) { return FDataDrivenShaderPlatformInfo::GetSupportsFFTBloom(Platform); } static FRDGBufferRef BuildFilmGrainConstants(FRDGBuilder& GraphBuilder, const FViewInfo& View, FRDGTextureRef FilmGrainTexture) { RDG_EVENT_SCOPE(GraphBuilder, "FilmGrain BuildTextureConstants"); FRDGTextureRef ReducedFilmGrainTexture = FilmGrainTexture; for (int32 PassId = 0; ReducedFilmGrainTexture->Desc.Extent.X > 1 && ReducedFilmGrainTexture->Desc.Extent.Y > 1; PassId++) { FRDGTextureRef NewReducedFilmGrainTexture; { FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( FIntPoint::DivideAndRoundUp(ReducedFilmGrainTexture->Desc.Extent, 8), PF_A32B32G32R32F, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV); NewReducedFilmGrainTexture = GraphBuilder.CreateTexture(Desc, TEXT("FilmGrain.Reduce")); } FFilmGrainReduceCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->FilmGrainTextureSize = ReducedFilmGrainTexture->Desc.Extent; PassParameters->FilmGrainTexture = ReducedFilmGrainTexture; PassParameters->Output = GraphBuilder.CreateUAV(NewReducedFilmGrainTexture); TShaderMapRef ComputeShader(View.ShaderMap); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("FilmGrain Reduce %dx%d -> %dx%d", ReducedFilmGrainTexture->Desc.Extent.X, ReducedFilmGrainTexture->Desc.Extent.Y, NewReducedFilmGrainTexture->Desc.Extent.X, NewReducedFilmGrainTexture->Desc.Extent.Y), ComputeShader, PassParameters, FIntVector(NewReducedFilmGrainTexture->Desc.Extent.X, NewReducedFilmGrainTexture->Desc.Extent.Y, 1)); ReducedFilmGrainTexture = NewReducedFilmGrainTexture; } FRDGBufferRef FilmGrainConstantsBuffer; { FilmGrainConstantsBuffer = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateStructuredDesc(sizeof(FLinearColor), /* NumElements = */ 1), TEXT("FilmGrain.TextureConstants")); FFilmGrainPackConstantsCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->OriginalFilmGrainTextureSize = FilmGrainTexture->Desc.Extent; PassParameters->ReducedFilmGrainTexture = ReducedFilmGrainTexture; PassParameters->FilmGrainConstantsOutput = GraphBuilder.CreateUAV(FilmGrainConstantsBuffer); TShaderMapRef ComputeShader(View.ShaderMap); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("FilmGrain PackConstants"), ComputeShader, PassParameters, FIntVector(1, 1, 1)); } return FilmGrainConstantsBuffer; } bool ShouldWriteAlphaChannel(const FViewInfo& View, const FTonemapInputs& Inputs, EPixelFormat Format) { // 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); const bool bFormatNeedsAlphaWrite = Format == PF_R9G9B9EXP5; return (Inputs.bWriteAlphaChannel || bIsStereo || bFormatNeedsAlphaWrite); } bool ShouldOverrideOutputLoadActionToFastClear(const FScreenPassRenderTarget& Output, bool bShouldWriteAlphaChannel) { bool bShouldOverride = false; EPixelFormatChannelFlags OutputFlags = GetPixelFormatValidChannels(Output.Texture->Desc.Format); // If we do not write through alpha channel but the output texture has alpha channel // need to override to fast clear load action ERenderTargetLoadAction::Clear, otherwise, // the alpha channel can be garbage data in terms of different driver implementation. if ( (!bShouldWriteAlphaChannel) && EnumHasAnyFlags(OutputFlags, EPixelFormatChannelFlags::A)) { bShouldOverride = true; } // If the load action is already load, there should be content in the texture. Disable clear overriding. if (Output.LoadAction == ERenderTargetLoadAction::ELoad) { bShouldOverride = false; } return bShouldOverride; } FScreenPassTexture AddTonemapPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FTonemapInputs& Inputs) { if (!Inputs.bGammaOnly) { check(Inputs.ColorGradingTexture); } check(Inputs.SceneColor.IsValid()); const FSceneViewFamily& ViewFamily = *(View.Family); const FPostProcessSettings& PostProcessSettings = View.FinalPostProcessSettings; const bool bIsEyeAdaptationResource = Inputs.EyeAdaptationBuffer != nullptr; const bool bEyeAdaptation = ViewFamily.EngineShowFlags.EyeAdaptation && bIsEyeAdaptationResource; FRDGBufferRef EyeAdaptationBuffer = Inputs.EyeAdaptationBuffer; if (!bEyeAdaptation) { const float DefaultEyeAdaptation = GetEyeAdaptationFixedExposure(View); const FVector4f DefaultEyeAdaptationData(DefaultEyeAdaptation, 0, 0, DefaultEyeAdaptation); EyeAdaptationBuffer = CreateStructuredBuffer(GraphBuilder, TEXT("DefaultEyeAdaptationBuffer"), sizeof(DefaultEyeAdaptationData), 1, &DefaultEyeAdaptationData, sizeof(DefaultEyeAdaptationData), ERDGInitialDataFlags::None); } const FScreenPassTextureViewport SceneColorViewport(Inputs.SceneColor); FScreenPassRenderTarget Output = Inputs.OverrideOutput; if (!Output.IsValid()) { FRDGTextureDesc OutputDesc = FRDGTextureDesc::Create2D( SceneColorViewport.Extent, Inputs.SceneColor.TextureSRV->Desc.Texture->Desc.Format, FClearValueBinding(FLinearColor(0, 0, 0, 0)), GFastVRamConfig.Tonemap | TexCreate_ShaderResource | TexCreate_RenderTargetable | (View.bUseComputePasses ? TexCreate_UAV : TexCreate_None));; const FTonemapperOutputDeviceParameters OutputDeviceParameters = GetTonemapperOutputDeviceParameters(*View.Family); const EDisplayOutputFormat OutputDevice = static_cast(OutputDeviceParameters.OutputDevice); const bool bPostProcessingAlpha = IsPostProcessingWithAlphaChannelSupported(); // If scene color is high-precision and alpha is supported, we make sure to preserve at least half precision in the alpha channel until the end of post-processing. const bool bPreserveHalfPrecisionAlpha = bPostProcessingAlpha && IsHDR(OutputDesc.Format); if (OutputDevice == EDisplayOutputFormat::HDR_LinearEXR) { OutputDesc.Format = PF_A32B32G32R32F; } else if (OutputDevice == EDisplayOutputFormat::HDR_LinearNoToneCurve || OutputDevice == EDisplayOutputFormat::HDR_LinearWithToneCurve || bPreserveHalfPrecisionAlpha) { OutputDesc.Format = PF_FloatRGBA; } else if (View.Family->RenderTarget && View.Family->RenderTarget->GetRenderTargetTexture()) { // Render into a pixel format that do not loose bit depth precision for the view family. OutputDesc.Format = View.Family->RenderTarget->GetRenderTargetTexture()->GetFormat(); } else if (bPostProcessingAlpha) { // Make sure there is no loss for a 10bit bit-depth using the 10bit of mantissa of halfs OutputDesc.Format = PF_FloatRGBA; } else { // Make sure there is no loss for a 10bit bit-depth OutputDesc.Format = PF_A2B10G10R10; } if ( (OutputDesc.Format == PF_R8G8B8A8 || OutputDesc.Format == PF_B8G8R8A8) && !ShouldWriteAlphaChannel(View, Inputs, OutputDesc.Format) && GPixelFormats[PF_R8G8B8].Supported && !!(GPixelFormats[PF_R8G8B8].Capabilities & EPixelFormatCapabilities::RenderTarget) ) { OutputDesc.Format = PF_R8G8B8; } Output = FScreenPassRenderTarget( GraphBuilder.CreateTexture(OutputDesc, TEXT("Tonemap")), Inputs.SceneColor.ViewRect, View.GetOverwriteLoadAction()); } const bool bShouldWriteAlphaChannel = ShouldWriteAlphaChannel(View, Inputs, Output.Texture->Desc.Format); if (ShouldOverrideOutputLoadActionToFastClear(Output, bShouldWriteAlphaChannel)) { Output.LoadAction = ERenderTargetLoadAction::EClear; } const FScreenPassTextureViewport OutputViewport(Output); FRHISamplerState* BilinearClampSampler = TStaticSamplerState::GetRHI(); FRHISamplerState* PointClampSampler = TStaticSamplerState::GetRHI(); const float SharpenDiv6 = TonemapperPermutation::GetSharpenSetting(View.FinalPostProcessSettings) / 6.0f; FVector4f ChromaticAberrationParams; { // for scene color fringe // from percent to fraction float Offset = 0.0f; float StartOffset = 0.0f; float Multiplier = 1.0f; if (PostProcessSettings.ChromaticAberrationStartOffset < 1.0f - KINDA_SMALL_NUMBER) { Offset = PostProcessSettings.SceneFringeIntensity * 0.01f; StartOffset = PostProcessSettings.ChromaticAberrationStartOffset; Multiplier = 1.0f / (1.0f - StartOffset); } // Wavelength of primaries in nm const float PrimaryR = 611.3f; const float PrimaryG = 549.1f; const float PrimaryB = 464.3f; // Simple lens chromatic aberration is roughly linear in wavelength float ScaleR = 0.007f * (PrimaryR - PrimaryB); float ScaleG = 0.007f * (PrimaryG - PrimaryB); ChromaticAberrationParams = FVector4f(Offset * ScaleR * Multiplier, Offset * ScaleG * Multiplier, StartOffset, 0.f); } float EditorNITLevel = 160.0f; #if WITH_EDITOR { static auto CVarHDRNITLevel = IConsoleManager::Get().FindConsoleVariable(TEXT("Editor.HDRNITLevel")); if (CVarHDRNITLevel) { EditorNITLevel = CVarHDRNITLevel->GetFloat(); } } #endif FTonemapParameters CommonParameters; CommonParameters.View = View.GetShaderParameters(); { uint8 OutputFrameIndexMod8 = 0; if (View.ViewState) { // Grain should be temporally stable when accumulating multiple samples per output frame, so we use OutputFrameIndex instead of FrameIndex. // Without this, the effect of grain is softened which goes against artistic intent after they tune it to the real-time viewport. OutputFrameIndexMod8 = View.ViewState->GetOutputFrameIndex(8); } GrainRandomFromFrame(&CommonParameters.FilmGrain.GrainRandomFull, OutputFrameIndexMod8); } if (View.FilmGrainTexture) { check(SupportsFilmGrain(View.GetShaderPlatform())); FRHITexture* FilmGrainTextureRHI = View.FilmGrainTexture->GetTexture2DRHI(); FRDGTextureRef FilmGrainTexture = RegisterExternalTexture(GraphBuilder, FilmGrainTextureRHI, TEXT("FilmGrain.OriginalTexture")); FRDGBufferRef FilmGrainConstantsBuffer; if (View.ViewState && View.ViewState->FilmGrainCache.TextureRHI == FilmGrainTextureRHI && CVarFilmGrainCacheTextureConstants.GetValueOnRenderThread() != 0) { FilmGrainConstantsBuffer = GraphBuilder.RegisterExternalBuffer(View.ViewState->FilmGrainCache.ConstantsBuffer, TEXT("FilmGrain.TextureConstants")); } else { FilmGrainConstantsBuffer = BuildFilmGrainConstants(GraphBuilder, View, FilmGrainTexture); if (View.ViewState) { View.ViewState->FilmGrainCache.TextureRHI = FilmGrainTextureRHI; GraphBuilder.QueueBufferExtraction(FilmGrainConstantsBuffer, &View.ViewState->FilmGrainCache.ConstantsBuffer); } } // (FilmGrainTexture * FilmGrainDecodeMultiply + FilmGrainDecodeAdd) CommonParameters.FilmGrain.FilmGrainIntensityShadows = View.FinalPostProcessSettings.FilmGrainIntensity * View.FinalPostProcessSettings.FilmGrainIntensityShadows; CommonParameters.FilmGrain.FilmGrainIntensityMidtones = View.FinalPostProcessSettings.FilmGrainIntensity * View.FinalPostProcessSettings.FilmGrainIntensityMidtones; CommonParameters.FilmGrain.FilmGrainIntensityHighlights = View.FinalPostProcessSettings.FilmGrainIntensity * View.FinalPostProcessSettings.FilmGrainIntensityHighlights; CommonParameters.FilmGrain.FilmGrainShadowsMax = View.FinalPostProcessSettings.FilmGrainShadowsMax; CommonParameters.FilmGrain.FilmGrainHighlightsMin = View.FinalPostProcessSettings.FilmGrainHighlightsMin; CommonParameters.FilmGrain.FilmGrainHighlightsMax = View.FinalPostProcessSettings.FilmGrainHighlightsMax; CommonParameters.FilmGrain.FilmGrainTexture = FilmGrainTexture; CommonParameters.FilmGrain.FilmGrainSampler = TStaticSamplerState::GetRHI(); // Grain should be temporally stable when accumulating multiple samples per output frame, so we use OutputFrameIndex instead of FrameIndex. int32 RandomSequenceLength = CVarFilmGrainSequenceLength.GetValueOnRenderThread(); int32 RandomSequenceIndex = (View.ViewState ? View.ViewState->GetOutputFrameIndex() : 0) % RandomSequenceLength; FVector2f RandomGrainTextureUVOffset; RandomGrainTextureUVOffset.X = Halton(RandomSequenceIndex + 1, 2); RandomGrainTextureUVOffset.Y = Halton(RandomSequenceIndex + 1, 3); FVector2f TextureSize(View.FilmGrainTexture->GetSizeX(), View.FilmGrainTexture->GetSizeY()); FVector2f OutputSizeF(1920.0f, 1920.0f * float(OutputViewport.Rect.Height()) / float(OutputViewport.Rect.Width())); CommonParameters.FilmGrain.ScreenPosToFilmGrainTextureUV = FScreenTransform::ScreenPosToViewportUV * ((OutputSizeF / TextureSize) * (1.0f / View.FinalPostProcessSettings.FilmGrainTexelSize)) + RandomGrainTextureUVOffset; CommonParameters.FilmGrain.FilmGrainTextureConstants = GraphBuilder.CreateSRV(FilmGrainConstantsBuffer); } else { // Release the film grain cache if (View.ViewState) { View.ViewState->FilmGrainCache.SafeRelease(); } CommonParameters.FilmGrain.FilmGrainIntensityShadows = 0.0f; CommonParameters.FilmGrain.FilmGrainIntensityMidtones = 0.0f; CommonParameters.FilmGrain.FilmGrainIntensityHighlights = 0.0f; CommonParameters.FilmGrain.FilmGrainShadowsMax = 0.09f; CommonParameters.FilmGrain.FilmGrainHighlightsMin = 0.5f; CommonParameters.FilmGrain.FilmGrainHighlightsMax = 1.0f; CommonParameters.FilmGrain.FilmGrainTexture = GSystemTextures.GetWhiteDummy(GraphBuilder); CommonParameters.FilmGrain.FilmGrainSampler = TStaticSamplerState::GetRHI(); CommonParameters.FilmGrain.ScreenPosToFilmGrainTextureUV = FScreenTransform::ScreenPosToViewportUV; // Set a dummy texture constants if (SupportsFilmGrain(View.GetShaderPlatform())) { FRDGBufferRef DummyFilmGrainTextureConstants = GSystemTextures.GetDefaultStructuredBuffer( GraphBuilder, sizeof(FVector4f), FVector4f(FLinearColor::White)); CommonParameters.FilmGrain.FilmGrainTextureConstants = GraphBuilder.CreateSRV(DummyFilmGrainTextureConstants); } else { CommonParameters.FilmGrain.FilmGrainTextureConstants = nullptr; } } const float LUTSize = Inputs.ColorGradingTexture ? (float)Inputs.ColorGradingTexture->Desc.Extent.Y : /* unused (default): */ 32.0f; const bool bBilateralLocalExposureEnabled = Inputs.LocalExposureBilateralGridTexture != nullptr; const bool bExposureFusionEnabled = Inputs.ExposureFusion.IsValid(); const bool bLocalExposureEnabled = bBilateralLocalExposureEnabled || bExposureFusionEnabled; if (bLocalExposureEnabled) { checkf(Inputs.LocalExposureParameters != nullptr, TEXT("When Local Exposure is enabled, corresponding parameters must be provided")); CommonParameters.LocalExposure = *Inputs.LocalExposureParameters; } CommonParameters.OutputDevice = GetTonemapperOutputDeviceParameters(ViewFamily); CommonParameters.Color = GetScreenPassTextureViewportParameters(SceneColorViewport); CommonParameters.Output = GetScreenPassTextureViewportParameters(OutputViewport); CommonParameters.ColorTexture = Inputs.SceneColor.TextureSRV; CommonParameters.LumBilateralGrid = Inputs.LocalExposureBilateralGridTexture; CommonParameters.BlurredLogLum = Inputs.BlurredLogLuminanceTexture; CommonParameters.LumBilateralGridSampler = BilinearClampSampler; CommonParameters.BlurredLogLumSampler = BilinearClampSampler; if(Inputs.ExposureFusion.IsValid()) { const FScreenPassTextureViewport ExposureFusionViewport(Inputs.ExposureFusion); CommonParameters.ColorToExposureFusion = FScreenTransform::ChangeTextureUVCoordinateFromTo(SceneColorViewport, ExposureFusionViewport); CommonParameters.ExposureFusion = GetScreenPassTextureViewportParameters(ExposureFusionViewport); CommonParameters.ExposureFusionTexture = Inputs.ExposureFusion.TextureSRV; CommonParameters.ExposureFusionSampler = BilinearClampSampler; } CommonParameters.EyeAdaptationBuffer = GraphBuilder.CreateSRV(EyeAdaptationBuffer); CommonParameters.EyeAdaptation = *Inputs.EyeAdaptationParameters; CommonParameters.ColorGradingLUT = Inputs.ColorGradingTexture; CommonParameters.ColorSampler = BilinearClampSampler; CommonParameters.ColorGradingLUTSampler = BilinearClampSampler; CommonParameters.ColorScale0 = PostProcessSettings.SceneColorTint; CommonParameters.ChromaticAberrationParams = ChromaticAberrationParams; CommonParameters.TonemapperParams = FVector4f(PostProcessSettings.VignetteIntensity, SharpenDiv6, 0.0f, 0.0f); CommonParameters.EditorNITLevel = EditorNITLevel; CommonParameters.FilmSlope = PostProcessSettings.FilmSlope; CommonParameters.FilmToe = PostProcessSettings.FilmToe; CommonParameters.FilmShoulder = PostProcessSettings.FilmShoulder; CommonParameters.FilmBlackClip = PostProcessSettings.FilmBlackClip; CommonParameters.FilmWhiteClip = PostProcessSettings.FilmWhiteClip; { const bool bSupportsBackbufferQuantizationDithering = EDisplayOutputFormat(CommonParameters.OutputDevice.OutputDevice) != EDisplayOutputFormat::HDR_LinearNoToneCurve && EDisplayOutputFormat(CommonParameters.OutputDevice.OutputDevice) != EDisplayOutputFormat::HDR_LinearWithToneCurve; const int32 BitdepthOverride = CVarBackbufferQuantizationDitheringOverride.GetValueOnRenderThread(); if (!bSupportsBackbufferQuantizationDithering) { CommonParameters.BackbufferQuantizationDithering = 0.0f; } else if (BitdepthOverride > 0) { CommonParameters.BackbufferQuantizationDithering = 1.0f / (FMath::Pow(2.0f, float(BitdepthOverride)) - 1.0f); } else { CommonParameters.BackbufferQuantizationDithering = 1.0f / 1023.0f; if (View.Family->RenderTarget && View.Family->RenderTarget->GetRenderTargetTexture()) { EPixelFormat BackbufferPixelFormat = View.Family->RenderTarget->GetRenderTargetTexture()->GetFormat(); if (BackbufferPixelFormat == PF_B8G8R8A8 || BackbufferPixelFormat == PF_R8G8B8A8) { CommonParameters.BackbufferQuantizationDithering = 1.0f / 255.0f; } } } } CommonParameters.LUTSize = LUTSize; CommonParameters.InvLUTSize = 1.0f / LUTSize; CommonParameters.LUTScale = (LUTSize - 1.0f) / LUTSize; CommonParameters.LUTOffset = 0.5f / LUTSize; CommonParameters.LensPrincipalPointOffsetScale = View.LensPrincipalPointOffsetScale; // Bloom parameters { const bool bUseBloom = Inputs.Bloom.Texture != nullptr; if (bUseBloom) { const FScreenPassTextureViewport BloomViewport(Inputs.Bloom); CommonParameters.ColorToBloom = FScreenTransform::ChangeTextureUVCoordinateFromTo(SceneColorViewport, BloomViewport); CommonParameters.BloomUVViewportBilinearMin = GetScreenPassTextureViewportParameters(BloomViewport).UVViewportBilinearMin; CommonParameters.BloomUVViewportBilinearMax = GetScreenPassTextureViewportParameters(BloomViewport).UVViewportBilinearMax; CommonParameters.BloomTexture = Inputs.Bloom.Texture; CommonParameters.BloomSampler = BilinearClampSampler; } else { CommonParameters.ColorToBloom = FScreenTransform::Identity; CommonParameters.BloomUVViewportBilinearMin = FVector2f::ZeroVector; CommonParameters.BloomUVViewportBilinearMax = FVector2f::UnitVector; CommonParameters.BloomTexture = GSystemTextures.GetBlackDummy(GraphBuilder); CommonParameters.BloomSampler = BilinearClampSampler; } if (!FTonemapInputs::SupportsSceneColorApplyParametersBuffer(View.GetShaderPlatform())) { check(Inputs.SceneColorApplyParamaters == nullptr); } else if (Inputs.SceneColorApplyParamaters) { CommonParameters.SceneColorApplyParamaters = GraphBuilder.CreateSRV(Inputs.SceneColorApplyParamaters); } else { FRDGBufferRef ApplyParametersBuffer = GSystemTextures.GetDefaultStructuredBuffer(GraphBuilder, sizeof(FVector4f), FVector4f(1.0f, 1.0f, 1.0f, 1.0f)); CommonParameters.SceneColorApplyParamaters = GraphBuilder.CreateSRV(ApplyParametersBuffer); } // TODO: PostProcessSettings.BloomDirtMask->GetResource() is not thread safe if (bUseBloom && PostProcessSettings.BloomDirtMask && PostProcessSettings.BloomDirtMask->GetResource() && PostProcessSettings.BloomDirtMaskIntensity > 0 && PostProcessSettings.BloomIntensity > 0.0) { CommonParameters.BloomDirtMaskTint = PostProcessSettings.BloomDirtMaskTint * PostProcessSettings.BloomDirtMaskIntensity / PostProcessSettings.BloomIntensity; CommonParameters.BloomDirtMaskTexture = PostProcessSettings.BloomDirtMask->GetResource()->TextureRHI; CommonParameters.BloomDirtMaskSampler = BilinearClampSampler; } else { CommonParameters.BloomDirtMaskTint = FLinearColor::Black; CommonParameters.BloomDirtMaskTexture = GBlackTexture->TextureRHI; CommonParameters.BloomDirtMaskSampler = BilinearClampSampler; } } // forward transformation from shader: //return LensPrincipalPointOffsetScale.xy + UV * LensPrincipalPointOffsetScale.zw; // reverse transformation from shader: //return UV*(1.0f/LensPrincipalPointOffsetScale.zw) - LensPrincipalPointOffsetScale.xy/LensPrincipalPointOffsetScale.zw; CommonParameters.LensPrincipalPointOffsetScaleInverse.X = -View.LensPrincipalPointOffsetScale.X / View.LensPrincipalPointOffsetScale.Z; CommonParameters.LensPrincipalPointOffsetScaleInverse.Y = -View.LensPrincipalPointOffsetScale.Y / View.LensPrincipalPointOffsetScale.W; CommonParameters.LensPrincipalPointOffsetScaleInverse.Z = 1.0f / View.LensPrincipalPointOffsetScale.Z; CommonParameters.LensPrincipalPointOffsetScaleInverse.W = 1.0f / View.LensPrincipalPointOffsetScale.W; // Generate permutation vector for the desktop tonemapper. TonemapperPermutation::FDesktopDomain DesktopPermutationVector; { TonemapperPermutation::FCommonDomain CommonDomain = TonemapperPermutation::BuildCommonPermutationDomain(View, Inputs.bGammaOnly, bLocalExposureEnabled, Inputs.bMetalMSAAHDRDecode); DesktopPermutationVector.Set(CommonDomain); if (!CommonDomain.Get()) { DesktopPermutationVector.Set(PostProcessSettings.SceneFringeIntensity > 0.01f); } DesktopPermutationVector.Set(!View.bIsMobileMultiViewEnabled && GVRSImageManager.IsVRSEnabledForFrame() && FVariableRateShadingImageManager::IsVRSCompatibleWithView(View)); const bool bOutputDeviceSRGB = (CommonParameters.OutputDevice.OutputDevice == (uint32)EDisplayOutputFormat::SDR_sRGB); DesktopPermutationVector.Set(bOutputDeviceSRGB); DesktopPermutationVector = TonemapperPermutation::RemapPermutation(DesktopPermutationVector, View.GetFeatureLevel()); } // Override output might not support UAVs. const bool bComputePass = (Output.Texture->Desc.Flags & TexCreate_UAV) == TexCreate_UAV ? View.bUseComputePasses : false; FRDGTextureRef OutputLuminance = {}; const bool bOutputLuminance = DesktopPermutationVector.Get(); if (bOutputLuminance) { // Due to the way split-screen shares the same render target for all views, make sure // that we use the same texture for saving out the luminance as well to keep the memory // footprint small - but only if we're outputting directly to the final render target for both views // (that is, neither view has any post-processing steps after tonemapping) const FOutputLuminance* CachedOutputLuminance = GraphBuilder.Blackboard.Get(); if (CachedOutputLuminance && Inputs.OverrideOutput.IsValid() && CachedOutputLuminance->Texture->Desc.Extent == Output.Texture->Desc.Extent) { OutputLuminance = CachedOutputLuminance->Texture; } else { FIntPoint OutputSize = Output.Texture->Desc.Extent; const FIntPoint SDROutputSize = FIntPoint(OutputSize.X, OutputSize.Y); FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( SDROutputSize, PF_R8, FClearValueBinding::Black, bComputePass ? TexCreate_UAV : TexCreate_RenderTargetable); OutputLuminance = GraphBuilder.CreateTexture(Desc, TEXT("Final Luminance")); if (CachedOutputLuminance == nullptr) { FOutputLuminance* NewOutputLuminance = &GraphBuilder.Blackboard.Create(); NewOutputLuminance->Texture = OutputLuminance; } } } if (bComputePass) { FTonemapCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->Tonemap = CommonParameters; PassParameters->RWOutputTexture = GraphBuilder.CreateUAV(Output.Texture); if (OutputLuminance) { PassParameters->RWOutputLuminance = GraphBuilder.CreateUAV(OutputLuminance); } else { check(DesktopPermutationVector.Get() == false); } TShaderMapRef ComputeShader(View.ShaderMap, DesktopPermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("Tonemap %dx%d (CS GammaOnly=%d)", OutputViewport.Rect.Width(), OutputViewport.Rect.Height(), Inputs.bGammaOnly), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(OutputViewport.Rect.Size(), FIntPoint(GTonemapComputeTileSizeX, GTonemapComputeTileSizeY))); } else { FTonemapPS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->Tonemap = CommonParameters; PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); PassParameters->RenderTargets.MultiViewCount = View.bIsMobileMultiViewEnabled ? 2 : 0; if (OutputLuminance) { PassParameters->RenderTargets[1] = FRenderTargetBinding(OutputLuminance, ERenderTargetLoadAction::ELoad); } TShaderMapRef VertexShader(View.ShaderMap); TShaderMapRef PixelShader(View.ShaderMap, DesktopPermutationVector); FRHIBlendState* BlendState = bShouldWriteAlphaChannel ? FScreenPassPipelineState::FDefaultBlendState::GetRHI() : TStaticBlendStateWriteMask::GetRHI(); FRHIDepthStencilState* DepthStencilState = FScreenPassPipelineState::FDefaultDepthStencilState::GetRHI(); EScreenPassDrawFlags DrawFlags = EScreenPassDrawFlags::AllowHMDHiddenAreaMask; AddDrawScreenPass( GraphBuilder, RDG_EVENT_NAME("Tonemap %dx%d (PS GammaOnly=%d)", OutputViewport.Rect.Width(), OutputViewport.Rect.Height(), Inputs.bGammaOnly), View, OutputViewport, SceneColorViewport, FScreenPassPipelineState(VertexShader, PixelShader, BlendState, DepthStencilState), PassParameters, DrawFlags, [VertexShader, PixelShader, PassParameters](FRHICommandList& RHICmdList) { SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), PassParameters->Tonemap); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters); }); } if (OutputLuminance && View.ViewState) { View.ViewState->PrevFrameViewInfo.LuminanceViewRectHistory = OutputViewport.Rect; GraphBuilder.QueueTextureExtraction(OutputLuminance, &View.ViewState->PrevFrameViewInfo.LuminanceHistory); } return MoveTemp(Output); } // MSAA custom resolve shader that does tonemapping class FMobileCustomResolvePS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FMobileCustomResolvePS); SHADER_USE_PARAMETER_STRUCT(FMobileCustomResolvePS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ColorTexture) SHADER_PARAMETER_SAMPLER(SamplerState, ColorSampler) SHADER_PARAMETER(FVector4f, ColorScale0) SHADER_PARAMETER_TEXTURE(Texture2D, ColorGradingLUT) SHADER_PARAMETER_SAMPLER(SamplerState, ColorGradingLUTSampler) SHADER_PARAMETER(float, LUTSize) SHADER_PARAMETER(float, InvLUTSize) SHADER_PARAMETER(float, LUTScale) SHADER_PARAMETER(float, LUTOffset) END_SHADER_PARAMETER_STRUCT() class FTonemapperSubpassMsaaDim : SHADER_PERMUTATION_SPARSE_INT("SUBPASS_MSAA_SAMPLES", 0, 1, 2, 4, 8); using FPermutationDomain = TShaderPermutationDomain; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsMobilePlatform(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { const int UseVolumeLut = PipelineVolumeTextureLUTSupportGuaranteedAtRuntime(Parameters.Platform) ? 1 : 0; OutEnvironment.SetDefine(TEXT("USE_VOLUME_LUT"), UseVolumeLut); OutEnvironment.SetDefine(TEXT("OUTPUT_DEVICE_SRGB"), 1); } }; IMPLEMENT_GLOBAL_SHADER(FMobileCustomResolvePS, "/Engine/Private/PostProcessTonemap.usf", "MobileCustomResolve_MainPS", SF_Pixel); void RenderMobileCustomResolve(FRHICommandList& RHICmdList, const FViewInfo& View, const int32 SubpassMSAASamples, FSceneTextures& SceneTextures) { // Part of scene rendering pass check(RHICmdList.IsInsideRenderPass()); SCOPED_DRAW_EVENT(RHICmdList, MobileTonemapSubpass); IPooledRenderTarget* ColorGradingLUT = View.GetTonemappingLUT(); const FIntPoint TargetSize = SceneTextures.Color.Target->Desc.Extent; const float LUTSize = ColorGradingLUT ? (float)ColorGradingLUT->GetDesc().GetSize().Y : /* unused (default): */ 32.0f; const FPostProcessSettings& Settings = View.FinalPostProcessSettings; TShaderMapRef VertexShader(View.ShaderMap); FMobileCustomResolvePS::FPermutationDomain PermutationVector; PermutationVector.Set(SubpassMSAASamples); TShaderMapRef PixelShader(View.ShaderMap, PermutationVector); FMobileCustomResolvePS::FParameters PSShaderParameters; PSShaderParameters.View = View.GetShaderParameters(); PSShaderParameters.ColorScale0 = FVector4f(Settings.SceneColorTint.R, Settings.SceneColorTint.G, Settings.SceneColorTint.B, 0); PSShaderParameters.ColorGradingLUT = ColorGradingLUT ? ColorGradingLUT->GetRHI() : GBlackTexture->TextureRHI.GetReference(); PSShaderParameters.ColorGradingLUTSampler = TStaticSamplerState::GetRHI(); PSShaderParameters.LUTSize = LUTSize; PSShaderParameters.InvLUTSize = 1.0f / LUTSize; PSShaderParameters.LUTScale = (LUTSize - 1.0f) / LUTSize; PSShaderParameters.LUTOffset = 0.5f / LUTSize; if (SubpassMSAASamples == 0u) { PSShaderParameters.ColorTexture = SceneTextures.Color.Resolve; PSShaderParameters.ColorSampler = TStaticSamplerState::GetRHI(); } FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI(); GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI(); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PSShaderParameters); RHICmdList.SetViewport(0, 0, 0.0f, TargetSize.X, TargetSize.Y, 1.0f); DrawRectangle( RHICmdList, 0, 0, TargetSize.X, TargetSize.Y, 0, 0, TargetSize.X, TargetSize.Y, TargetSize, TargetSize, VertexShader, EDRF_UseTriangleOptimization, View.GetStereoPassInstanceFactor()); } BEGIN_SHADER_PARAMETER_STRUCT(FMobileCustomResolveParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ColorTexture) RDG_TEXTURE_ACCESS(ColorGradingLUT, ERHIAccess::SRVGraphics) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() void AddMobileCustomResolvePass(FRDGBuilder& GraphBuilder, const FViewInfo& View, FSceneTextures& SceneTextures, FRDGTextureRef ViewFamilyTexture) { FRDGTextureRef ColorGradingLUT = AddCombineLUTPass(GraphBuilder, View); FMobileCustomResolveParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->View = View.GetShaderParameters(); PassParameters->ColorTexture = SceneTextures.Color.Resolve; PassParameters->ColorGradingLUT = ColorGradingLUT; PassParameters->RenderTargets[0] = FRenderTargetBinding(ViewFamilyTexture, ERenderTargetLoadAction::EClear); // Need to specify multi view count for when the custom resolve pass is a separate pass and is not within the main pass. PassParameters->RenderTargets.MultiViewCount = View.bIsMobileMultiViewEnabled ? 2 : 0; GraphBuilder.AddPass( RDG_EVENT_NAME("MobileCustomResolvePass"), PassParameters, ERDGPassFlags::Raster, [&View, &SceneTextures](FRHICommandListImmediate& RHICmdList) { const uint32 SubpassMSAASamples = 0u; // not using subpass resolve RenderMobileCustomResolve(RHICmdList, View, SubpassMSAASamples, SceneTextures); }); }