// Copyright Epic Games, Inc. All Rights Reserved. #include "PostProcess/PostProcessMotionBlur.h" #include "StaticBoundShaderState.h" #include "CanvasTypes.h" #include "DataDrivenShaderPlatformInfo.h" #include "Engine/Engine.h" #include "ScenePrivate.h" #include "SpriteIndexBuffer.h" #include "PostProcess/PostProcessing.h" #include "VelocityRendering.h" #include "UnrealEngine.h" #include "PixelShaderUtils.h" namespace { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) TAutoConsoleVariable CVarMotionBlurFiltering( TEXT("r.MotionBlurFiltering"), 0, TEXT("Useful developer variable\n") TEXT("0: off (default, expected by the shader for better quality)\n") TEXT("1: on"), ECVF_Cheat | ECVF_RenderThreadSafe); #endif TAutoConsoleVariable CVarMotionBlur2ndScale( TEXT("r.MotionBlur2ndScale"), 1.0f, TEXT(""), ECVF_Cheat | ECVF_RenderThreadSafe); TAutoConsoleVariable CVarMotionBlurScatter( TEXT("r.MotionBlurScatter"), 0, TEXT("Forces scatter based max velocity method (slower)."), ECVF_RenderThreadSafe); TAutoConsoleVariable CVarMotionBlurSeparable( TEXT("r.MotionBlurSeparable"), 0, TEXT("Adds a second motion blur pass that smooths noise for a higher quality blur."), ECVF_RenderThreadSafe); TAutoConsoleVariable CVarMotionBlurDirections( TEXT("r.MotionBlur.Directions"), 1, TEXT("Number of bluring direction (default = 1)."), ECVF_RenderThreadSafe); TAutoConsoleVariable CVarMotionBlurHalfResInput( TEXT("r.MotionBlur.HalfResInput"), 1, TEXT("Whether motion blur also blur with a half resolution input."), ECVF_RenderThreadSafe); TAutoConsoleVariable CVarMotionBlurHalfResGather( TEXT("r.MotionBlur.HalfResGather"), 1, TEXT("Whether to do motion blur filter dynamically at half res under heavy motion."), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable CVarMotionBlurAllowExternalVelocityFlatten( TEXT("r.MotionBlur.AllowExternalVelocityFlatten"), 1, TEXT("Whether to allow motion blur's velocity flatten into other pass."), ECVF_RenderThreadSafe); TAutoConsoleVariable CVarAllowMotionBlurInVR( TEXT("vr.AllowMotionBlurInVR"), 0, TEXT("For projects with motion blur enabled, this allows motion blur to be enabled even while in VR.")); TAutoConsoleVariable CVarVisualizeMotionBlurEnableDebugInformation( TEXT("r.MotionBlur.VisualizeDebugInformation"), true, TEXT("Enables checkerboard and debug text visualization when Visualize Motion Blur show flag is enabled.\n"), ECVF_RenderThreadSafe ); TAutoConsoleVariable CVarMotionBlurVisualize( TEXT("vr.MotionBlur.Visualize"), 0, TEXT("0: Default view.\n") TEXT("1: HSV view. Encode direction in H, and magnitude in V.\n") TEXT("2: Plasma view. Encode motion vector magnitude only.") ); TAutoConsoleVariable CVarOrthoUsePreviousMotionVelocityFlattenPass( TEXT("r.Ortho.UsePreviousMotionVelocityFlattenPass"), 0, TEXT("Enable/Disable the option for Ortho motion blur pass to use the previous flatten textures (e.g. from TSR). ") TEXT("Currently causes shimmering on distance planes if disabled"), ECVF_RenderThreadSafe); FMatrix GetPreviousWorldToClipMatrix(const FViewInfo& View) { if (View.Family->EngineShowFlags.CameraInterpolation) { // Instead of finding the world space position of the current pixel, calculate the world space position offset by the camera position, // then translate by the difference between last frame's camera position and this frame's camera position, // then apply the rest of the transforms. This effectively avoids precision issues near the extents of large levels whose world space position is very large. FVector ViewOriginDelta = View.ViewMatrices.GetViewOrigin() - View.PrevViewInfo.ViewMatrices.GetViewOrigin(); return FTranslationMatrix(ViewOriginDelta) * View.PrevViewInfo.ViewMatrices.ComputeViewRotationProjectionMatrix(); } else { return View.ViewMatrices.ComputeViewRotationProjectionMatrix(); } } int32 GetMotionBlurQualityFromCVar() { int32 MotionBlurQuality; static const auto ICVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MotionBlurQuality")); MotionBlurQuality = FMath::Clamp(ICVar->GetValueOnRenderThread(), 0, 4); return MotionBlurQuality; } DECLARE_GPU_STAT(MotionBlur); } const int32 kMotionBlurFlattenTileSize = FVelocityFlattenTextures::kTileSize; const int32 kMotionBlurFilterTileSize = 16; const int32 kMotionBlurComputeTileSizeX = 8; const int32 kMotionBlurComputeTileSizeY = 8; bool IsMotionBlurEnabled(const FViewInfo& View) { if (View.GetFeatureLevel() < ERHIFeatureLevel::SM5) { return false; } const int32 MotionBlurQuality = GetMotionBlurQualityFromCVar(); const FSceneViewFamily& ViewFamily = *View.Family; return ViewFamily.EngineShowFlags.PostProcessing && ViewFamily.EngineShowFlags.MotionBlur && View.FinalPostProcessSettings.MotionBlurAmount > 0.001f && View.FinalPostProcessSettings.MotionBlurMax > 0.001f && ViewFamily.bRealtimeUpdate && MotionBlurQuality > 0 && (CVarAllowMotionBlurInVR->GetInt() != 0 || !GEngine->StereoRenderingDevice.IsValid() || !GEngine->StereoRenderingDevice->IsStereoEnabled()); } bool IsVisualizeMotionBlurEnabled(const FViewInfo& View) { return View.Family->EngineShowFlags.VisualizeMotionBlur && View.GetFeatureLevel() >= ERHIFeatureLevel::SM5; } bool IsMotionBlurScatterRequired(const FViewInfo& View, const FScreenPassTextureViewport& SceneViewport) { const FSceneViewState* ViewState = View.ViewState; const float ViewportWidth = SceneViewport.Rect.Width(); // Normalize percentage value. const float VelocityMax = View.FinalPostProcessSettings.MotionBlurMax / 100.0f; // Scale by 0.5 due to blur samples going both ways and convert to tiles. const float VelocityMaxInTiles = VelocityMax * ViewportWidth * (0.5f / 16.0f); // Compute path only supports the immediate neighborhood of tiles. const float TileDistanceMaxGathered = 3.0f; // Scatter is used when maximum velocity exceeds the distance supported by the gather approach. const bool bIsScatterRequiredByVelocityLength = VelocityMaxInTiles > TileDistanceMaxGathered; // Cinematic is paused. const bool bInPausedCinematic = (ViewState && ViewState->SequencerState == ESS_Paused); // Use the scatter approach if requested by cvar or we're in a paused cinematic (higher quality). const bool bIsScatterRequiredByUser = CVarMotionBlurScatter.GetValueOnRenderThread() == 1 || bInPausedCinematic; return bIsScatterRequiredByUser || bIsScatterRequiredByVelocityLength; } FIntPoint GetMotionBlurTileCount(FIntPoint SizeInPixels) { const uint32 TilesX = FMath::DivideAndRoundUp(SizeInPixels.X, kMotionBlurFlattenTileSize); const uint32 TilesY = FMath::DivideAndRoundUp(SizeInPixels.Y, kMotionBlurFlattenTileSize); return FIntPoint(TilesX, TilesY); } bool DoesMotionBlurNeedsHalfResInput() { return CVarMotionBlurHalfResInput.GetValueOnRenderThread() != 0; } EMotionBlurQuality GetMotionBlurQuality() { // Quality levels begin at 1. 0 is reserved for 'off'. const int32 Quality = FMath::Clamp(GetMotionBlurQualityFromCVar(), 1, static_cast(EMotionBlurQuality::MAX)); return static_cast(Quality - 1); } EMotionBlurFilter GetMotionBlurFilter() { return CVarMotionBlurSeparable.GetValueOnRenderThread() != 0 ? EMotionBlurFilter::Separable : EMotionBlurFilter::Unified; } int32 GetMotionBlurDirections() { return FMath::Clamp(CVarMotionBlurDirections.GetValueOnRenderThread(), 1, 2); } // static bool FVelocityFlattenTextures::AllowExternal(const FViewInfo& View) { const bool bEnableCameraMotionBlur = View.bCameraMotionBlur.Get(true); const bool bOverrideCameraMotionBlur = View.ClipToPrevClipOverride.IsSet(); // Do not use external velocity flatten passes if the camera motion blur needs to be modified. if (!bEnableCameraMotionBlur || bOverrideCameraMotionBlur) { return false; } return CVarMotionBlurAllowExternalVelocityFlatten.GetValueOnRenderThread() != 0; } FRHISamplerState* GetMotionBlurColorSampler() { bool bFiltered = false; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) bFiltered = CVarMotionBlurFiltering.GetValueOnRenderThread() != 0; #endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST) if (bFiltered) { return TStaticSamplerState::GetRHI(); } else { return TStaticSamplerState::GetRHI(); } } FRHISamplerState* GetMotionBlurVelocitySampler() { return TStaticSamplerState::GetRHI(); } FRHISamplerState* GetPostMotionBlurTranslucencySampler(bool bUpscale) { if (bUpscale) { return TStaticSamplerState::GetRHI(); } else { return TStaticSamplerState::GetRHI(); } } FRDGTextureUAVRef CreateDebugUAV(FRDGBuilder& GraphBuilder, const FIntPoint& Extent, const TCHAR* DebugName) { #if !UE_BUILD_SHIPPING FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( Extent, PF_FloatRGBA, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV); FRDGTextureRef Texture = GraphBuilder.CreateTexture(Desc, DebugName); return GraphBuilder.CreateUAV(Texture); #else return nullptr; #endif } BEGIN_SHADER_PARAMETER_STRUCT(FVecocityTileTextureSRVs, ) SHADER_PARAMETER_RDG_TEXTURE_SRV_ARRAY(Texture2D, Textures, [FVelocityFlattenTextures::kMaxVelocityTileTextureCount]) END_SHADER_PARAMETER_STRUCT() FRDGTextureRef CreateVelocityTileTexture(FRDGBuilder& GraphBuilder, FIntPoint VelocityTileCount, int32 BlurDirections, const TCHAR* DebugName, bool ScatterDilatation = false) { FRDGTextureDesc Desc = FRDGTextureDesc::Create2DArray( VelocityTileCount, PF_FloatRGBA, FClearValueBinding::None, GFastVRamConfig.MotionBlur | TexCreate_ShaderResource | (ScatterDilatation ? TexCreate_RenderTargetable : TexCreate_UAV), /* ArraySize = */ BlurDirections); return GraphBuilder.CreateTexture(Desc, DebugName); } FVecocityTileTextureSRVs CreateVelocityTileTextureSRVs(FRDGBuilder& GraphBuilder, FRDGTextureRef VecocityTileArray) { FVecocityTileTextureSRVs SRVs; for (int32 i = 0; i < VecocityTileArray->Desc.ArraySize; i++) { SRVs.Textures[i] = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::CreateForSlice(VecocityTileArray, i)); } return SRVs; } // Base class for a motion blur / velocity shader. class FMotionBlurShader : public FGlobalShader { public: static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } FMotionBlurShader() = default; FMotionBlurShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) {} }; class FMotionBlurDirections : SHADER_PERMUTATION_SPARSE_INT("DIM_BLUR_DIRECTIONS", 1, 2); class FMotionBlurVelocityFlattenCS : public FMotionBlurShader { public: static const uint32 ThreadGroupSize = 16; using FPermutationDomain = TShaderPermutationDomain; DECLARE_GLOBAL_SHADER(FMotionBlurVelocityFlattenCS); SHADER_USE_PARAMETER_STRUCT(FMotionBlurVelocityFlattenCS, FMotionBlurShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Velocity) SHADER_PARAMETER_STRUCT_INCLUDE(FVelocityFlattenParameters, VelocityFlattenParameters) SHADER_PARAMETER(FMatrix44f, ClipToPrevClipOverride) SHADER_PARAMETER(FScreenTransform, ThreadIdToViewportUV) SHADER_PARAMETER(FScreenTransform, ViewportUVToPixelPos) SHADER_PARAMETER(int32, bCancelCameraMotion) SHADER_PARAMETER(int32, bAddCustomCameraMotion) SHADER_PARAMETER(int32, bLensDistortion) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DistortingDisplacementTexture) SHADER_PARAMETER_SAMPLER(SamplerState, DistortingDisplacementSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, UndistortingDisplacementTexture) SHADER_PARAMETER_SAMPLER(SamplerState, UndistortingDisplacementSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, VelocityTexture) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DepthTexture) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutVelocityFlatTexture) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2DArray, OutVelocityTileArray) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, DebugOutput) END_SHADER_PARAMETER_STRUCT() }; BEGIN_SHADER_PARAMETER_STRUCT(FMotionBlurVelocityDilateParameters, ) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, VelocityTile) SHADER_PARAMETER_STRUCT(FVecocityTileTextureSRVs, VelocityTileTextures) SHADER_PARAMETER(float, VelocityScaleForFlattenTiles) END_SHADER_PARAMETER_STRUCT() class FMotionBlurVelocityDilateGatherCS : public FMotionBlurShader { public: static const uint32 ThreadGroupSize = 16; DECLARE_GLOBAL_SHADER(FMotionBlurVelocityDilateGatherCS); SHADER_USE_PARAMETER_STRUCT(FMotionBlurVelocityDilateGatherCS, FMotionBlurShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FMotionBlurVelocityDilateParameters, Dilate) SHADER_PARAMETER_STRUCT(FVecocityTileTextureSRVs, CenterVelocityTileTextures) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2DArray, OutVelocityTileArray) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, DebugOutput) END_SHADER_PARAMETER_STRUCT() using FPermutationDomain = TShaderPermutationDomain; }; enum class EMotionBlurVelocityScatterPass : uint32 { DrawMin, DrawMax, MAX }; class FMotionBlurVelocityDilateScatterVS : public FMotionBlurShader { DECLARE_GLOBAL_SHADER(FMotionBlurVelocityDilateScatterVS); SHADER_USE_PARAMETER_STRUCT(FMotionBlurVelocityDilateScatterVS, FMotionBlurShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(uint32, ScatterPass) SHADER_PARAMETER_STRUCT_INCLUDE(FMotionBlurVelocityDilateParameters, Dilate) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() }; class FMotionBlurVelocityDilateScatterPS : public FMotionBlurShader { DECLARE_GLOBAL_SHADER(FMotionBlurVelocityDilateScatterPS); SHADER_USE_PARAMETER_STRUCT(FMotionBlurVelocityDilateScatterPS, FMotionBlurShader); using FParameters = FEmptyShaderParameters; }; class FMotionBlurFilterTileClassifyCS : public FMotionBlurShader { DECLARE_GLOBAL_SHADER(FMotionBlurFilterTileClassifyCS); SHADER_USE_PARAMETER_STRUCT(FMotionBlurFilterTileClassifyCS, FMotionBlurShader); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(FScreenTransform, FilterTileIdToFlattenTileId) SHADER_PARAMETER(FIntPoint, FlattenTileMaxId) SHADER_PARAMETER(FIntPoint, FilterTileCount) SHADER_PARAMETER(int32, bAllowHalfResGather) SHADER_PARAMETER(int32, TileListMaxSize) SHADER_PARAMETER(float, HalfResPixelVelocityThreshold) SHADER_PARAMETER_STRUCT(FVecocityTileTextureSRVs, VelocityTileTextures) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, TileListsOutput) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, TileListsSizeOutput) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, DebugOutput) END_SHADER_PARAMETER_STRUCT() }; class FSetupMotionBlurFilterCS : public FMotionBlurShader { DECLARE_GLOBAL_SHADER(FSetupMotionBlurFilterCS); SHADER_USE_PARAMETER_STRUCT(FSetupMotionBlurFilterCS, FMotionBlurShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, TileListsSizeBuffer) SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer, DispatchParametersOutput) END_SHADER_PARAMETER_STRUCT() static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { OutEnvironment.SetDefine(TEXT("DIM_BLUR_DIRECTIONS"), 1); OutEnvironment.SetDefine(TEXT("SETUP_PASS"), 1); } }; class FMotionBlurFilterCS : public FMotionBlurShader { DECLARE_GLOBAL_SHADER(FMotionBlurFilterCS); SHADER_USE_PARAMETER_STRUCT(FMotionBlurFilterCS, FMotionBlurShader); enum class ETileClassification { GatherHalfRes, GatherFullRes, ScatterAsGatherOneVelocityHalfRes, ScatterAsGatherOneVelocityFullRes, ScatterAsGatherTwoVelocityFullRes, MAX }; static bool IsHalfResTileClassification(ETileClassification TileClassification) { return TileClassification == ETileClassification::GatherHalfRes || TileClassification == ETileClassification::ScatterAsGatherOneVelocityHalfRes; } class FAlphaChannelDim : SHADER_PERMUTATION_BOOL("DIM_ALPHA_CHANNEL"); class FTileClassificationDim : SHADER_PERMUTATION_ENUM_CLASS("DIM_TILE_CLASSIFICATION", ETileClassification); using FPermutationDomain = TShaderPermutationDomain; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Color) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Velocity) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, VelocityTile) SHADER_PARAMETER(FScreenTransform, ColorToVelocity) SHADER_PARAMETER(FScreenTransform, SeparateTranslucencyUVToViewportUV) SHADER_PARAMETER(FScreenTransform, ViewportUVToSeparateTranslucencyUV) SHADER_PARAMETER(int32, MaxSampleCount) SHADER_PARAMETER(int32, OutputMip1) SHADER_PARAMETER(int32, OutputMip2) SHADER_PARAMETER(int32, bLensDistortion) SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, ColorTexture) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, VelocityFlatTexture) SHADER_PARAMETER_STRUCT(FVecocityTileTextureSRVs, VelocityTileTextures) SHADER_PARAMETER_SAMPLER(SamplerState, ColorSampler) SHADER_PARAMETER_SAMPLER(SamplerState, VelocitySampler) SHADER_PARAMETER_SAMPLER(SamplerState, VelocityTileSampler) SHADER_PARAMETER_SAMPLER(SamplerState, VelocityFlatSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, UndistortingDisplacementTexture) SHADER_PARAMETER_SAMPLER(SamplerState, UndistortingDisplacementSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, TranslucencyTexture) SHADER_PARAMETER_SAMPLER(SamplerState, TranslucencySampler) SHADER_PARAMETER(FScreenTransform, ColorToTranslucency) SHADER_PARAMETER(FVector2f, TranslucencyUVMin) SHADER_PARAMETER(FVector2f, TranslucencyUVMax) SHADER_PARAMETER(FVector2f, TranslucencyExtentInverse) SHADER_PARAMETER(int32, TileListOffset) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, TileListsBuffer) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, TileListsSizeBuffer) RDG_BUFFER_ACCESS(DispatchParameters, ERHIAccess::IndirectArgs) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, SceneColorOutputMip0) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, SceneColorOutputMip1) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, SceneColorOutputMip2) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, DebugOutput) END_SHADER_PARAMETER_STRUCT() static FPermutationDomain RemapPermutation(FPermutationDomain PermutationVector) { if (PermutationVector.Get() == ETileClassification::ScatterAsGatherTwoVelocityFullRes) { PermutationVector.Set(2); } else { PermutationVector.Set(1); } return PermutationVector; } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { FPermutationDomain PermutationVector(Parameters.PermutationId); if (RemapPermutation(PermutationVector) != PermutationVector) { return false; } return FMotionBlurShader::ShouldCompilePermutation(Parameters); } }; class FMotionBlurVisualizePS : public FMotionBlurShader { DECLARE_GLOBAL_SHADER(FMotionBlurVisualizePS); SHADER_USE_PARAMETER_STRUCT(FMotionBlurVisualizePS, FMotionBlurShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER(FMatrix44f, WorldToClipPrev) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, UndistortingDisplacementTexture) SHADER_PARAMETER_SAMPLER(SamplerState, UndistortingDisplacementSampler) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DepthTexture) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, VelocityTexture) SHADER_PARAMETER_STRUCT(FScreenPassTextureViewportParameters, Velocity) SHADER_PARAMETER_SAMPLER(SamplerState, VelocitySampler) SHADER_PARAMETER_SAMPLER(SamplerState, DepthSampler) SHADER_PARAMETER(FScreenTransform, SvPositionToScreenPos) SHADER_PARAMETER(FScreenTransform, ScreenPosToVelocityUV) SHADER_PARAMETER(int, CheckerboardEnabled) SHADER_PARAMETER(int, VisualizeMode) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FMotionBlurVelocityFlattenCS, "/Engine/Private/MotionBlur/MotionBlurVelocityFlatten.usf", "MainCS", SF_Compute); IMPLEMENT_GLOBAL_SHADER(FMotionBlurVelocityDilateGatherCS, "/Engine/Private/MotionBlur/MotionBlurTileGather.usf", "MainCS", SF_Compute); IMPLEMENT_GLOBAL_SHADER(FMotionBlurVelocityDilateScatterVS, "/Engine/Private/MotionBlur/MotionBlurTileScatter.usf", "MainVS", SF_Vertex); IMPLEMENT_GLOBAL_SHADER(FMotionBlurVelocityDilateScatterPS, "/Engine/Private/MotionBlur/MotionBlurTileScatter.usf", "MainPS", SF_Pixel); IMPLEMENT_GLOBAL_SHADER(FMotionBlurFilterTileClassifyCS, "/Engine/Private/MotionBlur/MotionBlurFilterTileClassify.usf", "MainCS", SF_Compute); IMPLEMENT_GLOBAL_SHADER(FSetupMotionBlurFilterCS, "/Engine/Private/MotionBlur/MotionBlurFilterTileClassify.usf", "MainCS", SF_Compute); IMPLEMENT_GLOBAL_SHADER(FMotionBlurFilterCS, "/Engine/Private/MotionBlur/MotionBlurApply.usf", "MainCS", SF_Compute); IMPLEMENT_GLOBAL_SHADER(FMotionBlurVisualizePS, "/Engine/Private/MotionBlur/MotionBlurVisualize.usf", "MainPS", SF_Pixel); TGlobalResource> GScatterQuadIndexBuffer; enum class EMotionBlurFilterPass : uint32 { Separable0, Separable1, Unified, MAX }; struct FMotionBlurViewports { FMotionBlurViewports( FScreenPassTextureViewport InColorViewport, FScreenPassTextureViewport InVelocityViewport) { Color = InColorViewport; Velocity = InVelocityViewport; VelocityTile = FScreenPassTextureViewport( FIntRect( FIntPoint::ZeroValue, GetMotionBlurTileCount(Velocity.Rect.Size()))); ColorParameters = GetScreenPassTextureViewportParameters(Color); VelocityParameters = GetScreenPassTextureViewportParameters(Velocity); VelocityTileParameters = GetScreenPassTextureViewportParameters(VelocityTile); ColorToVelocityTransform = FScreenTransform::ChangeTextureUVCoordinateFromTo(Color, Velocity); } FScreenPassTextureViewport Color; FScreenPassTextureViewport Velocity; FScreenPassTextureViewport VelocityTile; FScreenPassTextureViewportParameters ColorParameters; FScreenPassTextureViewportParameters VelocityParameters; FScreenPassTextureViewportParameters VelocityTileParameters; FScreenTransform ColorToVelocityTransform; }; FVelocityFlattenParameters GetVelocityFlattenParameters(const FViewInfo& View) { const FSceneViewState* ViewState = View.ViewState; const float SceneViewportSizeX = View.GetSecondaryViewRectSize().X; const float SceneViewportSizeY = View.GetSecondaryViewRectSize().Y; const float MotionBlurTimeScale = ViewState ? ViewState->MotionBlurTimeScale : 1.0f; // Scale by 0.5 due to blur samples going both ways. const float VelocityScale = MotionBlurTimeScale * View.FinalPostProcessSettings.MotionBlurAmount * 0.5f; // 0:no 1:full screen width, percent conversion const float UVVelocityMax = View.FinalPostProcessSettings.MotionBlurMax / 100.0f; FVelocityFlattenParameters VelocityFlattenParameters; VelocityFlattenParameters.VelocityScale.X = SceneViewportSizeX * 0.5f * VelocityScale; VelocityFlattenParameters.VelocityScale.Y = -SceneViewportSizeY * 0.5f * VelocityScale; VelocityFlattenParameters.VelocityMax = SceneViewportSizeX * 0.5f * UVVelocityMax; return VelocityFlattenParameters; } void AddMotionBlurVelocityPass( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FMotionBlurViewports& Viewports, const FMotionBlurInputs& Inputs, FRDGTextureRef* VelocityFlatTextureOutput, FVecocityTileTextureSRVs* VelocityTileTexturesOutput) { check(VelocityFlatTextureOutput); check(VelocityTileTexturesOutput); const int32 BlurDirections = GetMotionBlurDirections(); const FIntPoint VelocityTileCount = Viewports.VelocityTile.Extent; // Velocity flatten pass: combines depth / velocity into a single target for sampling efficiency. FRDGTextureRef VelocityFlatTexture = nullptr; FRDGTextureRef VelocityTileTextureSetup = nullptr; bool bViewUsesPreviousFlattenTexture = View.IsPerspectiveProjection() || CVarOrthoUsePreviousMotionVelocityFlattenPass.GetValueOnRenderThread() != 0; if (bViewUsesPreviousFlattenTexture && Inputs.VelocityFlattenTextures.IsValid()) { ensure(Inputs.VelocityFlattenTextures.VelocityFlatten.ViewRect == View.ViewRect); ensure(Inputs.VelocityFlattenTextures.VelocityTileArray.ViewRect == FIntRect(FIntPoint::ZeroValue, VelocityTileCount)); VelocityFlatTexture = Inputs.VelocityFlattenTextures.VelocityFlatten.Texture; VelocityTileTextureSetup = Inputs.VelocityFlattenTextures.VelocityTileArray.Texture; } else { { // NOTE: Use scene depth's dimensions because velocity can actually be a 1x1 black texture when there are no moving objects in sight. FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( Inputs.SceneDepth.Texture->Desc.Extent, View.IsPerspectiveProjection() ? PF_FloatR11G11B10 : PF_A32B32G32R32F, FClearValueBinding::None, GFastVRamConfig.VelocityFlat | TexCreate_ShaderResource | TexCreate_UAV); VelocityFlatTexture = GraphBuilder.CreateTexture(Desc, TEXT("MotionBlur.VelocityFlatten")); } VelocityTileTextureSetup = CreateVelocityTileTexture( GraphBuilder, VelocityTileCount, BlurDirections, TEXT("MotionBlur.VelocityTile")); const bool bEnableCameraMotionBlur = View.bCameraMotionBlur.Get(true); const bool bOverrideCameraMotionBlur = View.ClipToPrevClipOverride.IsSet(); FMotionBlurVelocityFlattenCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->View = View.ViewUniformBuffer; PassParameters->VelocityFlattenParameters = GetVelocityFlattenParameters(View); PassParameters->bCancelCameraMotion = !bEnableCameraMotionBlur || bOverrideCameraMotionBlur; PassParameters->bAddCustomCameraMotion = bOverrideCameraMotionBlur; if (PassParameters->bAddCustomCameraMotion) { PassParameters->ClipToPrevClipOverride = FMatrix44f(View.ClipToPrevClipOverride.GetValue()); } PassParameters->DistortingDisplacementTexture = GSystemTextures.GetBlackDummy(GraphBuilder); PassParameters->DistortingDisplacementSampler = TStaticSamplerState::GetRHI(); PassParameters->UndistortingDisplacementTexture = GSystemTextures.GetBlackDummy(GraphBuilder); PassParameters->UndistortingDisplacementSampler = TStaticSamplerState::GetRHI(); PassParameters->bLensDistortion = Inputs.LensDistortionLUT.IsEnabled(); if (Inputs.LensDistortionLUT.IsEnabled()) { PassParameters->DistortingDisplacementTexture = Inputs.LensDistortionLUT.DistortingDisplacementTexture; PassParameters->UndistortingDisplacementTexture = Inputs.LensDistortionLUT.UndistortingDisplacementTexture; } PassParameters->ThreadIdToViewportUV = FScreenTransform::DispatchThreadIdToViewportUV(Viewports.Velocity.Rect); PassParameters->ViewportUVToPixelPos = FScreenTransform::ChangeTextureBasisFromTo( FScreenPassTextureViewport(Viewports.Velocity), FScreenTransform::ETextureBasis::ViewportUV, FScreenTransform::ETextureBasis::TexelPosition); PassParameters->Velocity = Viewports.VelocityParameters; PassParameters->DepthTexture = Inputs.SceneDepth.Texture; PassParameters->VelocityTexture = Inputs.SceneVelocity.Texture; PassParameters->OutVelocityFlatTexture = GraphBuilder.CreateUAV(VelocityFlatTexture); PassParameters->OutVelocityTileArray = GraphBuilder.CreateUAV(VelocityTileTextureSetup); PassParameters->DebugOutput = CreateDebugUAV(GraphBuilder, VelocityFlatTexture->Desc.Extent, TEXT("Debug.MotionBlur.Flatten")); FMotionBlurVelocityFlattenCS::FPermutationDomain PermutationVector; PermutationVector.Set(BlurDirections); TShaderMapRef ComputeShader(View.ShaderMap, PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("Velocity Flatten(CameraMotionBlur%s) %dx%d", bEnableCameraMotionBlur ? (bOverrideCameraMotionBlur ? TEXT("Override") : TEXT("On")) : TEXT("Off"), Viewports.Velocity.Rect.Width(), Viewports.Velocity.Rect.Height()), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(Viewports.Velocity.Rect.Size(), FMotionBlurVelocityFlattenCS::ThreadGroupSize)); } bool ScatterDilatation = IsMotionBlurScatterRequired(View, Viewports.Color); FMotionBlurVelocityDilateParameters VelocityDilateParameters; VelocityDilateParameters.VelocityTile = Viewports.VelocityTileParameters; VelocityDilateParameters.VelocityTileTextures = CreateVelocityTileTextureSRVs(GraphBuilder, VelocityTileTextureSetup); VelocityDilateParameters.VelocityScaleForFlattenTiles = (1.0f / float(kMotionBlurFlattenTileSize)) * (float(Viewports.Velocity.Rect.Width()) / float(Viewports.Color.Rect.Width())); FVecocityTileTextureSRVs VelocityTileTextures; if (ScatterDilatation) { FRDGTextureRef VelocityTileArrayTexture = CreateVelocityTileTexture( GraphBuilder, VelocityTileCount, /* BlurDirections = */ 1, TEXT("MotionBlur.ScatteredVelocityTile"), /* ScatterDilatation = */ true); VelocityTileTextures.Textures[0] = GraphBuilder.CreateSRV(FRDGTextureSRVDesc::CreateForSlice(VelocityTileArrayTexture, 0)); FRDGTextureRef VelocityTileDepthTexture = GraphBuilder.CreateTexture( FRDGTextureDesc::Create2D( VelocityTileCount, PF_ShadowDepth, FClearValueBinding::DepthOne, TexCreate_DepthStencilTargetable), TEXT("MotionBlur.DilatedVelocityDepth")); FMotionBlurVelocityDilateScatterVS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->Dilate = VelocityDilateParameters; PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding( VelocityTileDepthTexture, ERenderTargetLoadAction::EClear, ERenderTargetLoadAction::ENoAction, FExclusiveDepthStencil::DepthWrite_StencilNop); PassParameters->RenderTargets[0] = FRenderTargetBinding( VelocityTileArrayTexture, ERenderTargetLoadAction::ENoAction, /* MipIndex = */ 0, /* ArraySlice = */ 0); TShaderMapRef VertexShader(View.ShaderMap); TShaderMapRef PixelShader(View.ShaderMap); ClearUnusedGraphResources(VertexShader, PassParameters); GraphBuilder.AddPass( RDG_EVENT_NAME("VelocityTileScatter %dx%d", VelocityTileCount.X, VelocityTileCount.Y), PassParameters, ERDGPassFlags::Raster, [VertexShader, PixelShader, VelocityTileCount, PassParameters](FRDGAsyncTask, FRHICommandList& RHICmdList) { FRHIVertexShader* RHIVertexShader = VertexShader.GetVertexShader(); FGraphicsPipelineStateInitializer GraphicsPSOInit; GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GEmptyVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.BoundShaderState.VertexShaderRHI = RHIVertexShader; GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI(); RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); // Max >= Min so no need to clear on second pass RHICmdList.SetViewport(0, 0, 0.0f, VelocityTileCount.X, VelocityTileCount.Y, 1.0f); // Min, Max for (uint32 ScatterPassIndex = 0; ScatterPassIndex < static_cast(EMotionBlurVelocityScatterPass::MAX); ScatterPassIndex++) { const EMotionBlurVelocityScatterPass ScatterPass = static_cast(ScatterPassIndex); if (ScatterPass == EMotionBlurVelocityScatterPass::DrawMin) { GraphicsPSOInit.BlendState = TStaticBlendStateWriteMask::GetRHI(); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); } else { GraphicsPSOInit.BlendState = TStaticBlendStateWriteMask::GetRHI(); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); } SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); PassParameters->ScatterPass = ScatterPassIndex; SetShaderParameters(RHICmdList, VertexShader, RHIVertexShader, *PassParameters); // Needs to be the same on shader side (faster on NVIDIA and AMD) const int32 QuadsPerInstance = 8; RHICmdList.SetStreamSource(0, nullptr, 0); RHICmdList.DrawIndexedPrimitive(GScatterQuadIndexBuffer.IndexBufferRHI, 0, 0, 32, 0, 2 * QuadsPerInstance, FMath::DivideAndRoundUp(VelocityTileCount.X * VelocityTileCount.Y, QuadsPerInstance)); } }); } // ScatterGather the dilatation if (!ScatterDilatation || BlurDirections > 1) { FRDGTextureRef DilatedTileTexture = CreateVelocityTileTexture( GraphBuilder, VelocityTileCount, BlurDirections, TEXT("MotionBlur.GatheredVelocityTile")); FMotionBlurVelocityDilateGatherCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->Dilate = VelocityDilateParameters; if (ScatterDilatation) { check(BlurDirections > 1); // Feed the scattered min max to make sure ReducePolarVelocityRange() is aware the highest velocity might be coming from tiles further away PassParameters->CenterVelocityTileTextures.Textures[0] = VelocityTileTextures.Textures[0]; PassParameters->CenterVelocityTileTextures.Textures[1] = VelocityDilateParameters.VelocityTileTextures.Textures[1]; } else { PassParameters->CenterVelocityTileTextures = VelocityDilateParameters.VelocityTileTextures; } PassParameters->OutVelocityTileArray = GraphBuilder.CreateUAV(DilatedTileTexture); FMotionBlurVelocityDilateGatherCS::FPermutationDomain PermutationVector; PermutationVector.Set(BlurDirections); TShaderMapRef ComputeShader(View.ShaderMap, PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("VelocityTileGatherCS %dx%d", VelocityTileCount.X, VelocityTileCount.Y), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(VelocityTileCount, FMotionBlurVelocityDilateGatherCS::ThreadGroupSize)); if (!ScatterDilatation) { VelocityTileTextures = CreateVelocityTileTextureSRVs(GraphBuilder, DilatedTileTexture); } else if (BlurDirections > 1) { VelocityTileTextures.Textures[1] = CreateVelocityTileTextureSRVs(GraphBuilder, DilatedTileTexture).Textures[1]; } } *VelocityFlatTextureOutput = VelocityFlatTexture; *VelocityTileTexturesOutput = VelocityTileTextures; } FMotionBlurOutputs AddMotionBlurFilterPass( FRDGBuilder& GraphBuilder, const FViewInfo& View, const FMotionBlurInputs& Inputs, const FMotionBlurViewports& Viewports, FRDGTextureSRVRef ColorTexture, FRDGTextureRef VelocityFlatTexture, FVecocityTileTextureSRVs VelocityTileTextures, FRDGTextureRef PostMotionBlurTranslucency, const FIntPoint& PostMotionBlurTranslucencySize, EMotionBlurFilterPass MotionBlurFilterPass, EMotionBlurQuality MotionBlurQuality) { check(ColorTexture); check(VelocityFlatTexture); check(MotionBlurFilterPass != EMotionBlurFilterPass::MAX); check(MotionBlurQuality != EMotionBlurQuality::MAX); const int32 BlurDirections = GetMotionBlurDirections(); const float MotionBlur2ndScale = CVarMotionBlur2ndScale.GetValueOnRenderThread(); const bool bUseCompute = View.bUseComputePasses; const float BlurScaleLUT[static_cast(EMotionBlurFilterPass::MAX)][static_cast(EMotionBlurQuality::MAX)] = { // Separable0 { 1.0f - 0.5f / 4.0f, 1.0f - 0.5f / 6.0f, 1.0f - 0.5f / 8.0f, 1.0f - 0.5f / 16.0f }, // Separable1 { 1.0f / 4.0f * MotionBlur2ndScale, 1.0f / 6.0f * MotionBlur2ndScale, 1.0f / 8.0f * MotionBlur2ndScale, 1.0f / 16.0f * MotionBlur2ndScale }, // Unified { 1.0f, 1.0f, 1.0f, 1.0f } }; static int32 kMaxSampleCountPerQuality[] = { 4, 8, 12, 16 }; static_assert(UE_ARRAY_COUNT(kMaxSampleCountPerQuality) == int32(EMotionBlurQuality::MAX), "Fix me!"); const int32 MaxSampleCount = kMaxSampleCountPerQuality[int32(MotionBlurQuality)]; const float BlurScale = BlurScaleLUT[static_cast(MotionBlurFilterPass)][static_cast(MotionBlurQuality)]; const FIntVector FilterTileCount = FComputeShaderUtils::GetGroupCount(Viewports.Color.Rect.Size(), kMotionBlurFilterTileSize); const bool bAlphaChannel = IsPostProcessingWithAlphaChannelSupported(); int32 TileListMaxSize = FilterTileCount.X * FilterTileCount.Y; // Tile classify the filtering FRDGBufferRef TileListsBuffer; FRDGBufferRef TileListsSizeBuffer; { TileListsBuffer = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), TileListMaxSize * int32(FMotionBlurFilterCS::ETileClassification::MAX)), TEXT("MotionBlur.TileOffsets")); TileListsSizeBuffer = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), int32(FMotionBlurFilterCS::ETileClassification::MAX)), TEXT("MotionBlur.TileCounters")); AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(TileListsSizeBuffer), 0); FMotionBlurFilterTileClassifyCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->FilterTileIdToFlattenTileId = (FScreenTransform::Identity + 0.5f) * (float(kMotionBlurFilterTileSize * Viewports.Velocity.Rect.Width()) / float(kMotionBlurFlattenTileSize * Viewports.Color.Rect.Width())); PassParameters->FlattenTileMaxId = Viewports.VelocityTile.Rect.Size() - 1; PassParameters->FilterTileCount = FIntPoint(FilterTileCount.X, FilterTileCount.Y); PassParameters->TileListMaxSize = TileListMaxSize; PassParameters->HalfResPixelVelocityThreshold = FMath::Square(float(MaxSampleCount)); PassParameters->bAllowHalfResGather = CVarMotionBlurHalfResGather.GetValueOnRenderThread() ? 1 : 0; PassParameters->VelocityTileTextures = VelocityTileTextures; PassParameters->TileListsOutput = GraphBuilder.CreateUAV(TileListsBuffer); PassParameters->TileListsSizeOutput = GraphBuilder.CreateUAV(TileListsSizeBuffer); PassParameters->DebugOutput = CreateDebugUAV(GraphBuilder, PassParameters->FilterTileCount, TEXT("Debug.MotionBlur.FilterTileClassify")); FMotionBlurFilterTileClassifyCS::FPermutationDomain PermutationVector; PermutationVector.Set(BlurDirections); TShaderMapRef ComputeShader(View.ShaderMap, PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("MotionBlur FilterTileClassify %dx%d", FilterTileCount.X, FilterTileCount.Y), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(FilterTileCount, kMotionBlurFilterTileSize)); } // Setup the filter's dispatch parameters. FRDGBufferRef DispatchParameters; { DispatchParameters = GraphBuilder.CreateBuffer( FRDGBufferDesc::CreateIndirectDesc(int32(FMotionBlurFilterCS::ETileClassification::MAX)), TEXT("MotionBlur.FilterDispatchParameters")); FSetupMotionBlurFilterCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->TileListsSizeBuffer = GraphBuilder.CreateSRV(TileListsSizeBuffer); PassParameters->DispatchParametersOutput = GraphBuilder.CreateUAV(DispatchParameters); TShaderMapRef ComputeShader(View.ShaderMap); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("MotionBlur SetupFilter"), ComputeShader, PassParameters, FIntVector(1, 1, 1)); } // Apply the filter. FMotionBlurOutputs Output; { static const TCHAR* kTileClassificationNames[] = { TEXT("GatherHalfRes"), TEXT("GatherFullRes"), TEXT("ScatterAsGatherOneVelocityHalfRes"), TEXT("ScatterAsGatherOneVelocityFullRes"), TEXT("ScatterAsGatherTwoVelocityFullRes"), }; static_assert(UE_ARRAY_COUNT(kTileClassificationNames) == int32(FMotionBlurFilterCS::ETileClassification::MAX), "Fix me!"); FMotionBlurFilterCS::FParameters OriginalPassParameters; OriginalPassParameters.Color = Viewports.ColorParameters; OriginalPassParameters.Velocity = Viewports.VelocityParameters; OriginalPassParameters.VelocityTile = Viewports.VelocityTileParameters; OriginalPassParameters.ColorToVelocity = Viewports.ColorToVelocityTransform; OriginalPassParameters.MaxSampleCount = MaxSampleCount; OriginalPassParameters.OutputMip1 = Inputs.bOutputHalfRes ? 1 : 0; OriginalPassParameters.OutputMip2 = Inputs.bOutputQuarterRes ? 1 : 0; OriginalPassParameters.ColorTexture = ColorTexture; OriginalPassParameters.VelocityFlatTexture = VelocityFlatTexture; OriginalPassParameters.VelocityTileTextures = VelocityTileTextures; OriginalPassParameters.ColorSampler = GetMotionBlurColorSampler(); OriginalPassParameters.VelocitySampler = GetMotionBlurVelocitySampler(); OriginalPassParameters.VelocityTileSampler = GetMotionBlurVelocitySampler(); OriginalPassParameters.VelocityFlatSampler = GetMotionBlurVelocitySampler(); OriginalPassParameters.TileListsBuffer = GraphBuilder.CreateSRV(TileListsBuffer); OriginalPassParameters.TileListsSizeBuffer = GraphBuilder.CreateSRV(TileListsSizeBuffer); OriginalPassParameters.DispatchParameters = DispatchParameters; OriginalPassParameters.UndistortingDisplacementTexture = GSystemTextures.GetBlackDummy(GraphBuilder); OriginalPassParameters.UndistortingDisplacementSampler = TStaticSamplerState::GetRHI(); OriginalPassParameters.bLensDistortion = 0; if (PostMotionBlurTranslucency != nullptr) { // TODO: broken with split screen const bool bScaleTranslucency = Viewports.Color.Rect.Size() != PostMotionBlurTranslucencySize; const FVector2f OutputSize(Viewports.Color.Rect.Size()); const FVector2f OutputSizeInv = FVector2f(1.0f, 1.0f) / OutputSize; const FVector2f PostMotionBlurTranslucencyExtent(PostMotionBlurTranslucency->Desc.Extent); const FVector2f PostMotionBlurTranslucencyExtentInv = FVector2f(1.0f, 1.0f) / PostMotionBlurTranslucencyExtent; OriginalPassParameters.TranslucencyTexture = PostMotionBlurTranslucency; OriginalPassParameters.TranslucencySampler = GetPostMotionBlurTranslucencySampler(bScaleTranslucency); FScreenPassTextureViewport TranslucencyViewport = FScreenPassTextureViewport(PostMotionBlurTranslucency->Desc.Extent, FIntRect(FIntPoint::ZeroValue, PostMotionBlurTranslucencySize)); OriginalPassParameters.ColorToTranslucency = FScreenTransform::ChangeTextureUVCoordinateFromTo(Viewports.Color, TranslucencyViewport); OriginalPassParameters.SeparateTranslucencyUVToViewportUV = FScreenTransform::ChangeTextureBasisFromTo( TranslucencyViewport, FScreenTransform::ETextureBasis::TextureUV, FScreenTransform::ETextureBasis::ViewportUV); OriginalPassParameters.ViewportUVToSeparateTranslucencyUV = FScreenTransform::ChangeTextureBasisFromTo( TranslucencyViewport, FScreenTransform::ETextureBasis::ViewportUV, FScreenTransform::ETextureBasis::TextureUV); OriginalPassParameters.TranslucencyUVMin = FVector2f(0.0f, 0.0f); OriginalPassParameters.TranslucencyUVMax = (FVector2f(PostMotionBlurTranslucencySize) - FVector2f(0.5f, 0.5f)) * PostMotionBlurTranslucencyExtentInv; OriginalPassParameters.TranslucencyExtentInverse = PostMotionBlurTranslucencyExtentInv; OriginalPassParameters.bLensDistortion = Inputs.LensDistortionLUT.IsEnabled(); if (Inputs.LensDistortionLUT.IsEnabled()) { OriginalPassParameters.UndistortingDisplacementTexture = Inputs.LensDistortionLUT.UndistortingDisplacementTexture; } } else { OriginalPassParameters.TranslucencyTexture = GSystemTextures.GetBlackAlphaOneDummy(GraphBuilder); OriginalPassParameters.TranslucencySampler = TStaticSamplerState::GetRHI(); OriginalPassParameters.ColorToTranslucency = FScreenTransform::Identity; OriginalPassParameters.SeparateTranslucencyUVToViewportUV = FScreenTransform::Identity; OriginalPassParameters.ViewportUVToSeparateTranslucencyUV = FScreenTransform::Identity; OriginalPassParameters.TranslucencyUVMin = FVector2f(0.0f, 0.0f); OriginalPassParameters.TranslucencyUVMax = FVector2f(0.0f, 0.0f); OriginalPassParameters.TranslucencyExtentInverse = FVector2f(0.0f, 0.0f); } { FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( ColorTexture->Desc.Texture->Desc.Extent, bAlphaChannel ? PF_FloatRGBA : PF_FloatRGB, FClearValueBinding::None, TexCreate_UAV | TexCreate_ShaderResource | TexCreate_RenderTargetable | GFastVRamConfig.MotionBlur); FRDGTextureRef FullResTexture = GraphBuilder.CreateTexture(Desc, TEXT("MotionBlur.SceneColor")); Output.FullRes.TextureSRV = GraphBuilder.CreateSRV(FRDGTextureSRVDesc(FullResTexture)); Output.FullRes.ViewRect = Viewports.Color.Rect; OriginalPassParameters.SceneColorOutputMip0 = GraphBuilder.CreateUAV(FullResTexture, ERDGUnorderedAccessViewFlags::SkipBarrier); } if (OriginalPassParameters.OutputMip1) { FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( Output.FullRes.TextureSRV->Desc.Texture->Desc.Extent / 2, Output.FullRes.TextureSRV->Desc.Texture->Desc.Format, FClearValueBinding::None, TexCreate_UAV | TexCreate_ShaderResource); Output.HalfRes.Texture = GraphBuilder.CreateTexture(Desc, TEXT("MotionBlur.SceneColor.HalfRes")); Output.HalfRes.ViewRect.Min = Viewports.Color.Rect.Min / 2; Output.HalfRes.ViewRect.Max = Output.HalfRes.ViewRect.Min + FIntPoint::DivideAndRoundUp(Viewports.Color.Rect.Size(), 2); OriginalPassParameters.SceneColorOutputMip1 = GraphBuilder.CreateUAV(Output.HalfRes.Texture, ERDGUnorderedAccessViewFlags::SkipBarrier); } else { FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( FIntPoint(1, 1), PF_FloatR11G11B10, FClearValueBinding::None, /* InFlags = */ TexCreate_ShaderResource | TexCreate_UAV); FRDGTextureRef DummyTexture = GraphBuilder.CreateTexture(Desc, TEXT("MotionBlur.DummyOutput")); OriginalPassParameters.SceneColorOutputMip1 = GraphBuilder.CreateUAV(DummyTexture, ERDGUnorderedAccessViewFlags::SkipBarrier); } if (OriginalPassParameters.OutputMip2) { FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( Output.FullRes.TextureSRV->Desc.Texture->Desc.Extent / 4, Output.FullRes.TextureSRV->Desc.Texture->Desc.Format, FClearValueBinding::None, TexCreate_UAV | TexCreate_ShaderResource); Output.QuarterRes.Texture = GraphBuilder.CreateTexture(Desc, TEXT("MotionBlur.SceneColor.QuarterRes")); Output.QuarterRes.ViewRect.Min = Viewports.Color.Rect.Min / 4; Output.QuarterRes.ViewRect.Max = Output.QuarterRes.ViewRect.Min + FIntPoint::DivideAndRoundUp(Viewports.Color.Rect.Size(), 4); OriginalPassParameters.SceneColorOutputMip2 = GraphBuilder.CreateUAV(Output.QuarterRes.Texture, ERDGUnorderedAccessViewFlags::SkipBarrier); } else { FRDGTextureDesc Desc = FRDGTextureDesc::Create2D( FIntPoint(1, 1), PF_FloatR11G11B10, FClearValueBinding::None, /* InFlags = */ TexCreate_ShaderResource | TexCreate_UAV); FRDGTextureRef DummyTexture = GraphBuilder.CreateTexture(Desc, TEXT("MotionBlur.DummyOutput")); OriginalPassParameters.SceneColorOutputMip2 = GraphBuilder.CreateUAV(DummyTexture, ERDGUnorderedAccessViewFlags::SkipBarrier); } OriginalPassParameters.DebugOutput = CreateDebugUAV(GraphBuilder, Output.FullRes.TextureSRV->Desc.Texture->Desc.Extent, TEXT("Debug.MotionBlur.Filter")); RDG_EVENT_SCOPE(GraphBuilder, "MotionBlur FullResFilter(BlurDirections=%d MaxSamples=%d%s%s%s%s) %dx%d", BlurDirections, OriginalPassParameters.MaxSampleCount, bAlphaChannel ? TEXT(" AlphaChannel") : TEXT(""), PostMotionBlurTranslucency ? TEXT(" ComposeTranslucency") : TEXT(""), OriginalPassParameters.OutputMip1 ? TEXT(" OutputMip1") : TEXT(""), OriginalPassParameters.OutputMip2 ? TEXT(" OutputMip2") : TEXT(""), Viewports.Color.Rect.Width(), Viewports.Color.Rect.Height()); for (int32 TileClassifcation = 0; TileClassifcation < int32(FMotionBlurFilterCS::ETileClassification::MAX); TileClassifcation++) { FMotionBlurFilterCS::FParameters* PassParameters = GraphBuilder.AllocParameters(); *PassParameters = OriginalPassParameters; PassParameters->TileListOffset = TileListMaxSize * TileClassifcation; FMotionBlurFilterCS::FPermutationDomain PermutationVector; PermutationVector.Set(bAlphaChannel); PermutationVector.Set(FMotionBlurFilterCS::ETileClassification(TileClassifcation)); PermutationVector.Set(BlurDirections); PermutationVector = FMotionBlurFilterCS::RemapPermutation(PermutationVector); if (PermutationVector.Get() > BlurDirections) { continue; } TShaderMapRef ComputeShader(View.ShaderMap, PermutationVector); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("MotionBlur Filter(%s)", kTileClassificationNames[int32(PermutationVector.Get())]), ComputeShader, PassParameters, DispatchParameters, /* IndirectArgsOffset = */ sizeof(FRHIDispatchIndirectParameters) * TileClassifcation); } } return Output; } FScreenPassTextureSlice AddVisualizeMotionBlurPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FMotionBlurInputs& Inputs) { check(Inputs.SceneColor.IsValid()); check(Inputs.SceneDepth.IsValid()); check(Inputs.SceneVelocity.IsValid()); checkf(Inputs.SceneDepth.ViewRect == Inputs.SceneVelocity.ViewRect, TEXT("The implementation requires that depth and velocity have the same viewport.")); FScreenPassRenderTarget Output; bool bOverrideMissingSRFlag = false; if (Inputs.OverrideOutput.IsValid()) { /** * If the override texture is used, check whether the output texture supports SRVs before assigning it, as this causes a crash in * FScreenPassTextureSlice::CreateFromScreenPassTexture at the end of this function if not. */ if(static_cast(Inputs.OverrideOutput.Texture->Desc.Flags) & static_cast(ETextureCreateFlags::ShaderResource)) { Output = Inputs.OverrideOutput; } else { bOverrideMissingSRFlag = true; } } if (!Output.IsValid()) { FIntPoint OutputExtent; EPixelFormat OutputFormat; FIntRect OutputRect; if (bOverrideMissingSRFlag) { //If the Override texture does not support SRVs, we need to create one that does so we can call/return via FScreenPassTextureSlice::CreateFromScreenPassTexture OutputExtent = Inputs.OverrideOutput.Texture->Desc.Extent; OutputFormat = Inputs.OverrideOutput.Texture->Desc.Format; OutputRect = Inputs.OverrideOutput.ViewRect; } else { OutputExtent = Inputs.SceneColor.TextureSRV->Desc.Texture->Desc.Extent; OutputFormat = Inputs.SceneColor.TextureSRV->Desc.Texture->Desc.Format; OutputRect = Inputs.SceneColor.ViewRect; } FRDGTextureDesc OutputDesc = FRDGTextureDesc::Create2D( OutputExtent, OutputFormat, FClearValueBinding::None, ETextureCreateFlags::ShaderResource | ETextureCreateFlags::RenderTargetable); Output = FScreenPassRenderTarget(GraphBuilder.CreateTexture(OutputDesc, TEXT("MotionBlur.Visualize")), OutputRect, View.GetOverwriteLoadAction()); } // NOTE: Scene depth is used as the velocity viewport because velocity can actually be a 1x1 black texture. const FMotionBlurViewports Viewports(FScreenPassTextureViewport(Inputs.SceneColor), FScreenPassTextureViewport(Inputs.SceneDepth)); const bool bVisualizeDebugInfo = CVarVisualizeMotionBlurEnableDebugInformation.GetValueOnRenderThread(); const int32 VisualizeMode = CVarMotionBlurVisualize.GetValueOnRenderThread(); { FMotionBlurVisualizePS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->WorldToClipPrev = FMatrix44f(GetPreviousWorldToClipMatrix(View)); // LWC_TODO: Precision loss PassParameters->View = View.ViewUniformBuffer; PassParameters->UndistortingDisplacementTexture = GSystemTextures.GetBlackDummy(GraphBuilder); PassParameters->UndistortingDisplacementSampler = TStaticSamplerState::GetRHI(); if (Inputs.LensDistortionLUT.IsEnabled()) { PassParameters->UndistortingDisplacementTexture = Inputs.LensDistortionLUT.UndistortingDisplacementTexture; } PassParameters->DepthTexture = Inputs.SceneDepth.Texture; PassParameters->VelocityTexture = Inputs.SceneVelocity.Texture; PassParameters->Velocity = Viewports.VelocityParameters; PassParameters->VelocitySampler = GetMotionBlurVelocitySampler(); PassParameters->DepthSampler = GetMotionBlurVelocitySampler(); PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); PassParameters->SvPositionToScreenPos = ( FScreenTransform::SvPositionToViewportUV(Output.ViewRect) * FScreenTransform::ViewportUVToScreenPos); PassParameters->ScreenPosToVelocityUV = FScreenTransform::ChangeTextureBasisFromTo( Inputs.SceneDepth, FScreenTransform::ETextureBasis::ScreenPosition, FScreenTransform::ETextureBasis::TextureUV); PassParameters->CheckerboardEnabled = bVisualizeDebugInfo; PassParameters->VisualizeMode = VisualizeMode; TShaderMapRef PixelShader(View.ShaderMap); FPixelShaderUtils::AddFullscreenPass( GraphBuilder, View.ShaderMap, RDG_EVENT_NAME("VisualizeMotionVectors %dx%d", Output.ViewRect.Width(), Output.ViewRect.Height()), PixelShader, PassParameters, Output.ViewRect); } Output.LoadAction = ERenderTargetLoadAction::ELoad; if (bVisualizeDebugInfo) { AddDrawCanvasPass(GraphBuilder, RDG_EVENT_NAME("VisualizeMotionBlurOverlay"), View, Output, [&View](FCanvas& Canvas) { float X = 20; float Y = 38; const float YStep = 14; const float ColumnWidth = 200; FString Line; Line = FString::Printf(TEXT("Visualize MotionBlur")); Canvas.DrawShadowedString(X, Y += YStep, *Line, GetStatsFont(), FLinearColor(1, 1, 0)); static const auto MotionBlurDebugVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MotionBlurDebug")); const int32 MotionBlurDebug = MotionBlurDebugVar ? MotionBlurDebugVar->GetValueOnRenderThread() : 0; Line = FString::Printf(TEXT("%d, %d"), View.Family->FrameNumber, MotionBlurDebug); Canvas.DrawShadowedString(X, Y += YStep, TEXT("FrameNo, r.MotionBlurDebug:"), GetStatsFont(), FLinearColor(1, 1, 0)); Canvas.DrawShadowedString(X + ColumnWidth, Y, *Line, GetStatsFont(), FLinearColor(1, 1, 0)); static const auto VelocityTestVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VelocityTest")); const int32 VelocityTest = VelocityTestVar ? VelocityTestVar->GetValueOnRenderThread() : 0; Line = FString::Printf(TEXT("%d, %d, %d"), View.Family->bWorldIsPaused, VelocityTest, FVelocityRendering::IsParallelVelocity(View.GetShaderPlatform())); Canvas.DrawShadowedString(X, Y += YStep, TEXT("Paused, r.VelocityTest, Parallel:"), GetStatsFont(), FLinearColor(1, 1, 0)); Canvas.DrawShadowedString(X + ColumnWidth, Y, *Line, GetStatsFont(), FLinearColor(1, 1, 0)); const FSceneViewState* SceneViewState = (const FSceneViewState*)View.State; Line = FString::Printf(TEXT("View=%.4x PrevView=%.4x"), View.ViewMatrices.GetViewMatrix().ComputeHash() & 0xffff, View.PrevViewInfo.ViewMatrices.GetViewMatrix().ComputeHash() & 0xffff); Canvas.DrawShadowedString(X, Y += YStep, TEXT("ViewMatrix:"), GetStatsFont(), FLinearColor(1, 1, 0)); Canvas.DrawShadowedString(X + ColumnWidth, Y, *Line, GetStatsFont(), FLinearColor(1, 1, 0)); }); } if (bOverrideMissingSRFlag) { /** * The OverrideOutput texture did not support SRVs, so we created one that did, but we still need to resolve the output image back * to the OverrideOutput texture too. */ AddCopyTexturePass(GraphBuilder, Output.Texture, Inputs.OverrideOutput.Texture, FRHICopyTextureInfo()); } return FScreenPassTextureSlice::CreateFromScreenPassTexture(GraphBuilder, Output); } FMotionBlurOutputs AddMotionBlurPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FMotionBlurInputs& Inputs) { check(Inputs.SceneColor.IsValid()); check(Inputs.SceneDepth.IsValid()); check(Inputs.SceneVelocity.IsValid()); checkf(Inputs.SceneDepth.ViewRect == Inputs.SceneVelocity.ViewRect, TEXT("The motion blur depth and velocity must have the same viewport.")); checkf(!Inputs.OverrideOutput.IsValid(), TEXT("The motion blur override output support is unimplemented.")); // NOTE: Use SceneDepth as the velocity viewport because SceneVelocity can actually be a 1x1 black texture when there are no moving objects in sight. const FMotionBlurViewports Viewports(FScreenPassTextureViewport(Inputs.SceneColor), FScreenPassTextureViewport(Inputs.SceneDepth)); RDG_EVENT_SCOPE_STAT(GraphBuilder, MotionBlur, "MotionBlur"); RDG_GPU_STAT_SCOPE(GraphBuilder, MotionBlur); FRDGTextureRef VelocityFlatTexture = nullptr; FVecocityTileTextureSRVs VelocityTileTextures; AddMotionBlurVelocityPass( GraphBuilder, View, Viewports, Inputs, &VelocityFlatTexture, &VelocityTileTextures); FMotionBlurOutputs Output; if (Inputs.Filter == EMotionBlurFilter::Separable) { FRDGTextureSRVRef MotionBlurFilterTextureSRV = AddMotionBlurFilterPass( GraphBuilder, View, Inputs, Viewports, Inputs.SceneColor.TextureSRV, VelocityFlatTexture, VelocityTileTextures, nullptr, FIntPoint(0, 0), EMotionBlurFilterPass::Separable0, Inputs.Quality).FullRes.TextureSRV; Output = AddMotionBlurFilterPass( GraphBuilder, View, Inputs, Viewports, MotionBlurFilterTextureSRV, VelocityFlatTexture, VelocityTileTextures, Inputs.PostMotionBlurTranslucency.ColorTexture.Resolve, Inputs.PostMotionBlurTranslucency.ViewRect.Size(), EMotionBlurFilterPass::Separable1, Inputs.Quality); } else { Output = AddMotionBlurFilterPass( GraphBuilder, View, Inputs, Viewports, Inputs.SceneColor.TextureSRV, VelocityFlatTexture, VelocityTileTextures, Inputs.PostMotionBlurTranslucency.ColorTexture.Resolve, Inputs.PostMotionBlurTranslucency.ViewRect.Size(), EMotionBlurFilterPass::Unified, Inputs.Quality); } return Output; }