// Copyright Epic Games, Inc. All Rights Reserved. #include "PostProcess/PostProcessUpscale.h" #include "PostProcess/SceneFilterRendering.h" #include "DataDrivenShaderPlatformInfo.h" #include "SceneRendering.h" #include "PostProcessing.h" #include "RHIResourceUtils.h" #include "PostProcess/DrawRectangle.h" namespace { TAutoConsoleVariable CVarUpscaleSoftness( TEXT("r.Upscale.Softness"), 1.0f, TEXT("Amount of sharpening for Gaussian Unsharp filter (r.UpscaleQuality=5). Reduce if ringing is visible\n") TEXT(" 1: Normal sharpening (default)\n") TEXT(" 0: No sharpening (pure Gaussian)."), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable CVarUpscaleQuality( TEXT("r.Upscale.Quality"), 3, TEXT("Defines the quality in which ScreenPercentage and WindowedFullscreen scales the 3d rendering.\n") TEXT(" 0: Nearest filtering\n") TEXT(" 1: Simple Bilinear\n") TEXT(" 2: Directional blur with unsharp mask upsample.\n") TEXT(" 3: 5-tap Catmull-Rom bicubic, approximating Lanczos 2. (default)\n") TEXT(" 4: 13-tap Lanczos 3.\n") TEXT(" 5: 36-tap Gaussian-filtered unsharp mask (very expensive, but good for extreme upsampling).\n"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable CVarUpscaleComputeEnabled( TEXT("r.Upscale.ComputeEnabled"), 1, TEXT("Allow running the upscaler as a compute pass. \n"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable CVarUpscaleSharpeningQuality( TEXT("r.Upscale.SharpeningQuality"), 1, TEXT("0: off\n") TEXT("1: cheaper\n") TEXT("2: higher quality"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable CVarUpscaleSharpening( TEXT("r.Upscale.Sharpening"), 0.0f, TEXT("Increase to get more sharpening on the final upscale. Requires ComputeEnabled.\n"), ECVF_Scalability | ECVF_RenderThreadSafe); } //! namespace BEGIN_SHADER_PARAMETER_STRUCT(FUpscaleParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Input) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Output) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DistortingDisplacementTexture) SHADER_PARAMETER_SAMPLER(SamplerState, DistortingDisplacementSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, UndistortingDisplacementTexture) SHADER_PARAMETER_SAMPLER(SamplerState, UndistortingDisplacementSampler) SHADER_PARAMETER(FIntPoint, GridDimensions) SHADER_PARAMETER(uint32, bInvertAlpha) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneColorTexture) SHADER_PARAMETER_SAMPLER(SamplerState, SceneColorSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, PointSceneColorTexture) SHADER_PARAMETER_RDG_TEXTURE(Texture2DArray, PointSceneColorTextureArray) SHADER_PARAMETER_SAMPLER(SamplerState, PointSceneColorSampler) SHADER_PARAMETER(float, UpscaleSoftness) SHADER_PARAMETER(float, Sharpening) END_SHADER_PARAMETER_STRUCT() BEGIN_SHADER_PARAMETER_STRUCT(FUpscaleRasterParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FUpscaleParameters, UpscaleParameters) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() class FUpscalePS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FUpscalePS); SHADER_USE_PARAMETER_STRUCT(FUpscalePS, FGlobalShader); using FParameters = FUpscaleRasterParameters; class FAlphaChannelDim : SHADER_PERMUTATION_BOOL("DIM_ALPHA_CHANNEL"); class FMethodDimension : SHADER_PERMUTATION_ENUM_CLASS("DIM_METHOD", EUpscaleMethod); using FPermutationDomain = TShaderPermutationDomain; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { const FPermutationDomain PermutationVector(Parameters.PermutationId); const EUpscaleMethod UpscaleMethod = PermutationVector.Get(); if (UpscaleMethod == EUpscaleMethod::None) { return false; } // Always allow point and bilinear and area upscale. (Provides upscaling for mobile emulation) if (UpscaleMethod == EUpscaleMethod::Nearest || UpscaleMethod == EUpscaleMethod::Bilinear || UpscaleMethod == EUpscaleMethod::Area) { return true; } return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } }; IMPLEMENT_GLOBAL_SHADER(FUpscalePS, "/Engine/Private/PostProcessUpscale.usf", "MainPS", SF_Pixel); class FUpscaleVS : public FScreenPassVS { public: DECLARE_GLOBAL_SHADER(FUpscaleVS); // FDrawRectangleParameters is filled by DrawScreenPass. SHADER_USE_PARAMETER_STRUCT_WITH_LEGACY_BASE(FUpscaleVS, FScreenPassVS); using FParameters = FUpscaleRasterParameters; static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FScreenPassVS::ModifyCompilationEnvironment(Parameters, OutEnvironment); } }; IMPLEMENT_GLOBAL_SHADER(FUpscaleVS, "/Engine/Private/PostProcessUpscale.usf", "MainVS", SF_Vertex); const int32 GUpscaleComputeTileSizeX = 16; const int32 GUpscaleComputeTileSizeY = 16; enum class ESharpeningQuality : uint8 { Off, Low, High, MAX }; enum class EMethodSet : uint8 { None, Simple, Complex, MAX }; EMethodSet GetMethodSet(EUpscaleMethod Method) { switch (Method) { case EUpscaleMethod::Nearest: case EUpscaleMethod::Bilinear: case EUpscaleMethod::SmoothStep: case EUpscaleMethod::Area: return EMethodSet::Simple; case EUpscaleMethod::Directional: case EUpscaleMethod::CatmullRom: case EUpscaleMethod::Lanczos: case EUpscaleMethod::Gaussian: return EMethodSet::Complex; default: return EMethodSet::MAX; }; } class FUpscaleCS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FUpscaleCS); SHADER_USE_PARAMETER_STRUCT(FUpscaleCS, FGlobalShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FUpscaleParameters, UpscaleParameters) SHADER_PARAMETER(uint32, UpscaleMethod) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, RWOutputTexture) END_SHADER_PARAMETER_STRUCT() class FMethodDim : SHADER_PERMUTATION_ENUM_CLASS("DIM_METHOD", EUpscaleMethod); class FMethodSetDim : SHADER_PERMUTATION_ENUM_CLASS("DIM_METHODSET", EMethodSet); class FAlphaChannelDim : SHADER_PERMUTATION_BOOL("DIM_ALPHA_CHANNEL"); class FLensDistortionDim : SHADER_PERMUTATION_BOOL("DIM_LENS_DISTORTION"); class FSharpeningQualityDim : SHADER_PERMUTATION_ENUM_CLASS("DIM_SHARPENING_QUALITY", ESharpeningQuality); using FPermutationDomain = TShaderPermutationDomain; static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { const FPermutationDomain PermutationVector(Parameters.PermutationId); const EUpscaleMethod UpscaleMethod = PermutationVector.Get(); const EMethodSet UpscaleMethodSet = PermutationVector.Get(); if (UpscaleMethodSet != EMethodSet::None && UpscaleMethod != EUpscaleMethod::None) { return false; } else if (UpscaleMethodSet == EMethodSet::None && UpscaleMethod == EUpscaleMethod::None) { return false; } if (!IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5)) { return false; } return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } static FPermutationDomain RemapPermutation(FPermutationDomain PermutationVector) { const EUpscaleMethod UpscaleMethod = PermutationVector.Get(); // Special path for SmoothStep, as that is used in secondary upscaler if (UpscaleMethod != EUpscaleMethod::SmoothStep) { PermutationVector.Set(EUpscaleMethod::None); PermutationVector.Set(GetMethodSet(UpscaleMethod)); } return PermutationVector; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), GUpscaleComputeTileSizeX); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), GUpscaleComputeTileSizeY); } }; IMPLEMENT_GLOBAL_SHADER(FUpscaleCS, "/Engine/Private/PostProcessUpscale.usf", "MainCS", SF_Compute); EUpscaleMethod GetUpscaleMethod() { const int32 StartIndex = 1; // 0 is EUpscaleMethod::None, methods start at 1 const int32 Value = CVarUpscaleQuality.GetValueOnRenderThread() + StartIndex; return static_cast(FMath::Clamp(Value, StartIndex, static_cast(EUpscaleMethod::Gaussian))); } // static FScreenPassTexture ISpatialUpscaler::AddDefaultUpscalePass( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FInputs& Inputs, EUpscaleMethod Method, FLensDistortionLUT LensDistortionLUT) { check(Inputs.SceneColor.IsValid()); check(Method != EUpscaleMethod::None); check(Method != EUpscaleMethod::MAX); check(Inputs.Stage != EUpscaleStage::MAX); FScreenPassRenderTarget Output = Inputs.OverrideOutput; if (!Output.IsValid()) { FRDGTextureDesc OutputDesc = FRDGTextureDesc::Create2D( Inputs.SceneColor.Texture->Desc.Extent, Inputs.SceneColor.Texture->Desc.Format, FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_RenderTargetable | GFastVRamConfig.Upscale); if (Inputs.Stage == EUpscaleStage::PrimaryToSecondary) { const FIntPoint SecondaryViewRectSize = View.GetSecondaryViewRectSize(); QuantizeSceneBufferSize(SecondaryViewRectSize, OutputDesc.Extent); Output.ViewRect.Min = FIntPoint::ZeroValue; Output.ViewRect.Max = SecondaryViewRectSize; } else { OutputDesc.Extent = View.UnscaledViewRect.Max; Output.ViewRect = View.UnscaledViewRect; } Output.Texture = GraphBuilder.CreateTexture(OutputDesc, TEXT("Upscale")); Output.LoadAction = ERenderTargetLoadAction::EClear; Output.UpdateVisualizeTextureExtent(); } const FIntRect InputRect = Inputs.Stage == EUpscaleStage::SecondaryToOutput ? View.GetSecondaryViewCropRect() : Inputs.SceneColor.ViewRect; const FScreenPassTextureViewport InputViewport(Inputs.SceneColor.Texture, InputRect); const FScreenPassTextureViewport OutputViewport(Output); const bool bApplyLensDistortion = LensDistortionLUT.IsEnabled(); bool bIsUpscaleToOutput = Inputs.Stage == EUpscaleStage::PrimaryToOutput || Inputs.Stage == EUpscaleStage::SecondaryToOutput; float Sharpening = bIsUpscaleToOutput ? CVarUpscaleSharpening.GetValueOnRenderThread() : 0.0f; FUpscaleParameters PassParameters; PassParameters.Input = GetScreenPassTextureViewportParameters(InputViewport); PassParameters.Output = GetScreenPassTextureViewportParameters(OutputViewport); PassParameters.DistortingDisplacementTexture = LensDistortionLUT.DistortingDisplacementTexture; PassParameters.DistortingDisplacementSampler = TStaticSamplerState::GetRHI(); PassParameters.UndistortingDisplacementTexture = LensDistortionLUT.UndistortingDisplacementTexture; PassParameters.UndistortingDisplacementSampler = TStaticSamplerState::GetRHI(); PassParameters.GridDimensions = LensDistortionLUT.DistortionGridDimensions; PassParameters.bInvertAlpha = View.Family->EngineShowFlags.AlphaInvert; PassParameters.SceneColorTexture = Inputs.SceneColor.Texture; PassParameters.SceneColorSampler = TStaticSamplerState::GetRHI(); PassParameters.PointSceneColorTexture = Inputs.SceneColor.Texture; PassParameters.PointSceneColorTextureArray = Inputs.SceneColor.Texture; PassParameters.PointSceneColorSampler = TStaticSamplerState::GetRHI(); PassParameters.UpscaleSoftness = FMath::Clamp(CVarUpscaleSoftness.GetValueOnRenderThread(), 0.0f, 1.0f); PassParameters.Sharpening = Sharpening; PassParameters.View = View.GetShaderParameters(); const TCHAR* const StageNames[] = { TEXT("PrimaryToSecondary"), TEXT("PrimaryToOutput"), TEXT("SecondaryToOutput") }; static_assert(UE_ARRAY_COUNT(StageNames) == static_cast(EUpscaleStage::MAX), "StageNames does not match EUpscaleStage"); const TCHAR* StageName = StageNames[static_cast(Inputs.Stage)]; bool bUseCompute = bIsUpscaleToOutput && CVarUpscaleComputeEnabled.GetValueOnRenderThread() > 0 && View.bUseComputePasses; if (bUseCompute) { FRDGTextureRef ComputeRenderTarget{}; bool bOutputSupportsUAV = (Output.Texture->Desc.Flags & TexCreate_UAV) == TexCreate_UAV; if (bOutputSupportsUAV) { ComputeRenderTarget = Output.Texture; } else { const FRDGTextureDesc Desc(FRDGTextureDesc::Create2D(Output.Texture->Desc.Extent, Output.Texture->Desc.Format, FClearValueBinding::None, ETextureCreateFlags::UAV | ETextureCreateFlags::ShaderResource)); ComputeRenderTarget = GraphBuilder.CreateTexture(Desc, TEXT("SecondaryUpscalerOutput")); } FUpscaleCS::FParameters* ComputePassParameters = GraphBuilder.AllocParameters(); ComputePassParameters->UpscaleParameters = PassParameters; ComputePassParameters->RWOutputTexture = GraphBuilder.CreateUAV(ComputeRenderTarget); ComputePassParameters->UpscaleMethod = (uint32)Method; ESharpeningQuality SharpeningQuality = (ESharpeningQuality)FMath::Clamp(CVarUpscaleSharpeningQuality.GetValueOnRenderThread(), 0, static_cast(ESharpeningQuality::MAX) - 1); SharpeningQuality = Sharpening != 0.0f ? SharpeningQuality : ESharpeningQuality::Off; FUpscaleCS::FPermutationDomain PermutationVector; PermutationVector.Set(IsPostProcessingWithAlphaChannelSupported()); PermutationVector.Set(bApplyLensDistortion); PermutationVector.Set(Method); PermutationVector.Set(EMethodSet::None); // RemapPermutation sets this to the correct one. PermutationVector.Set(SharpeningQuality); TShaderMapRef ComputeShader(View.ShaderMap, PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("Upscale(CS %s Method=%d%s%s) %dx%d -> %dx%d", StageName, int32(Method), PermutationVector.Get() ? TEXT(" Alpha") : TEXT(""), bApplyLensDistortion ? TEXT(" LensDistortion") : TEXT(""), Inputs.SceneColor.ViewRect.Width(), Inputs.SceneColor.ViewRect.Height(), Output.ViewRect.Width(), Output.ViewRect.Height()), ComputeShader, ComputePassParameters, FComputeShaderUtils::GetGroupCount(OutputViewport.Rect.Size(), FIntPoint(GUpscaleComputeTileSizeX, GUpscaleComputeTileSizeY))); FRHICopyTextureInfo CopyInfo {}; CopyInfo.SourcePosition = FIntVector{ OutputViewport.Rect.Min.X, OutputViewport.Rect.Min.Y, 0 }; CopyInfo.DestPosition = CopyInfo.SourcePosition; CopyInfo.Size = FIntVector{ OutputViewport.Rect.Size().X, OutputViewport.Rect.Size().Y, 1 }; AddCopyTexturePass(GraphBuilder, ComputeRenderTarget, Output.Texture, CopyInfo); } else { FUpscaleRasterParameters* RasterPassParameters = GraphBuilder.AllocParameters(); RasterPassParameters->UpscaleParameters = PassParameters; RasterPassParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); FUpscalePS::FPermutationDomain PixelPermutationVector; PixelPermutationVector.Set(IsPostProcessingWithAlphaChannelSupported()); PixelPermutationVector.Set(Method); TShaderMapRef PixelShader(View.ShaderMap, PixelPermutationVector); GraphBuilder.AddPass( RDG_EVENT_NAME("Upscale(%s Method=%d%s%s) %dx%d -> %dx%d", StageName, int32(Method), PixelPermutationVector.Get() ? TEXT(" Alpha") : TEXT(""), bApplyLensDistortion ? TEXT(" LensDistortion") : TEXT(""), Inputs.SceneColor.ViewRect.Width(), Inputs.SceneColor.ViewRect.Height(), Output.ViewRect.Width(), Output.ViewRect.Height()), RasterPassParameters, ERDGPassFlags::Raster, [&View, bApplyLensDistortion, PixelShader, RasterPassParameters, InputViewport, OutputViewport](FRDGAsyncTask, FRHICommandList& RHICmdList) { RHICmdList.SetViewport(OutputViewport.Rect.Min.X, OutputViewport.Rect.Min.Y, 0.0f, OutputViewport.Rect.Max.X, OutputViewport.Rect.Max.Y, 1.0f); TShaderRef VertexShader; if (bApplyLensDistortion) { TShaderMapRef TypedVertexShader(View.ShaderMap); SetScreenPassPipelineState(RHICmdList, FScreenPassPipelineState(TypedVertexShader, PixelShader)); SetShaderParameters(RHICmdList, TypedVertexShader, TypedVertexShader.GetVertexShader(), *RasterPassParameters); VertexShader = TypedVertexShader; } else { TShaderMapRef TypedVertexShader(View.ShaderMap); SetScreenPassPipelineState(RHICmdList, FScreenPassPipelineState(TypedVertexShader, PixelShader)); VertexShader = TypedVertexShader; } check(VertexShader.IsValid()); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *RasterPassParameters); if (bApplyLensDistortion) { TArray IndexBuffer; const uint32 Width = RasterPassParameters->UpscaleParameters.GridDimensions.X; const uint32 Height = RasterPassParameters->UpscaleParameters.GridDimensions.Y; const uint32 NumVertices = (Width + 1) * (Height + 1); const uint32 NumTriangles = Width * Height * 2; const uint32 NumIndices = NumTriangles * 3; IndexBuffer.AddUninitialized(NumIndices); uint32* Out = (uint32*)IndexBuffer.GetData(); for(uint32 y = 0; y < Height; ++y) { for(uint32 x = 0; x < Width; ++x) { // left top to bottom right in reading order uint32 Index00 = x + y * (Width + 1); uint32 Index10 = Index00 + 1; uint32 Index01 = Index00 + (Width + 1); uint32 Index11 = Index01 + 1; // triangle A *Out++ = Index00; *Out++ = Index01; *Out++ = Index10; // triangle B *Out++ = Index11; *Out++ = Index10; *Out++ = Index01; } } // Create index buffer. Fill buffer with initial data upon creation FBufferRHIRef IndexBufferRHI = UE::RHIResourceUtils::CreateIndexBufferFromArray(RHICmdList, TEXT("LensDistortionIndexBuffer"), EBufferUsageFlags::Static, MakeConstArrayView(IndexBuffer)); FRHIBatchedShaderParameters& BatchedParameters = RHICmdList.GetScratchShaderParameters(); UE::Renderer::PostProcess::SetDrawRectangleParameters(BatchedParameters, VertexShader.GetShader(), 0, 0, OutputViewport.Rect.Width(), OutputViewport.Rect.Height(), InputViewport.Rect.Min.X, InputViewport.Rect.Min.Y, InputViewport.Rect.Width(), InputViewport.Rect.Height(), OutputViewport.Rect.Size(), InputViewport.Extent); RHICmdList.SetBatchedShaderParameters(VertexShader.GetVertexShader(), BatchedParameters); // no vertex buffer needed as we compute it in VS RHICmdList.SetStreamSource(0, nullptr, 0); RHICmdList.DrawIndexedPrimitive( IndexBufferRHI, /*BaseVertexIndex=*/ 0, /*MinIndex=*/ 0, /*NumVertices=*/ NumVertices, /*StartIndex=*/ 0, /*NumPrimitives=*/ NumTriangles, /*NumInstances=*/ 1 ); } else { DrawRectangle( RHICmdList, // Output Rect (RHI viewport relative). 0, 0, OutputViewport.Rect.Width(), OutputViewport.Rect.Height(), // Input Rect InputViewport.Rect.Min.X, InputViewport.Rect.Min.Y, InputViewport.Rect.Width(), InputViewport.Rect.Height(), OutputViewport.Rect.Size(), InputViewport.Extent, VertexShader, EDRF_UseTriangleOptimization); } }); } return MoveTemp(Output); }