From 32bbc175b39421fb3013cdd13fe20b3ec5f086d6 Mon Sep 17 00:00:00 2001
From: Adrian Courreges <a.courreges@gmail.com>
Date: Sat, 1 Dec 2018 10:05:17 +0900
Subject: [PATCH] Dynamic-resolution, adapt the rendering resolution depending
 on the GPU time.

Enable with "r.DynamicResolution.Quality 1".

Full details and information: http://www.adriancourreges.com/blog/2018/12/02/ue4-optimized-post-effects/
---
 Engine/Source/Runtime/Engine/Private/SceneView.cpp | 90 +++++++++++++++++++++-
 .../Private/PostProcess/SceneRenderTargets.cpp     |  6 +-
 2 files changed, 94 insertions(+), 2 deletions(-)

diff --git a/Engine/Source/Runtime/Engine/Private/SceneView.cpp b/Engine/Source/Runtime/Engine/Private/SceneView.cpp
index e0a29468283..7e159715703 100644
--- a/Engine/Source/Runtime/Engine/Private/SceneView.cpp
+++ b/Engine/Source/Runtime/Engine/Private/SceneView.cpp
@@ -324,6 +324,41 @@ static TAutoConsoleVariable<int32> CVarAllowTranslucencyAfterDOF(
 	ECVF_RenderThreadSafe);
 
 
+static TAutoConsoleVariable<int> CVarDynamicResolutionQuality(
+	TEXT("r.DynamicResolution.Quality"),
+	0,
+	TEXT("Enable dynamic resolution which will override r.ScreenPercentage and modify it automatically at runtime to fit into some frame time budget.\n")
+	 TEXT("For better result this should be used in combination with 'r.SceneRenderTargetResizeMethod 2'."),
+	ECVF_Scalability | ECVF_Default);
+
+static TAutoConsoleVariable<float> CVarDynamicResolutionMaxScreenPercentage(
+	TEXT("r.DynamicResolution.MaxScreenPercentage"),
+	100.0f,
+	TEXT("Maximum screen percentage we allow the renderer to use when the scene is light."),
+	ECVF_Scalability | ECVF_Default);
+
+static TAutoConsoleVariable<float> CVarDynamicResolutionMinScreenPercentage(
+	TEXT("r.DynamicResolution.MinScreenPercentage"),
+	80.0f,
+	TEXT("Minimum screen percentage we allow the renderer to fall down to when the scene gets very heavy."),
+	ECVF_Scalability | ECVF_Default);
+
+static TAutoConsoleVariable<float> CVarDynamicResolutionMaxTimeBudget(
+	TEXT("r.DynamicResolution.MaxTimeBudget"),
+	29.0f,
+	TEXT("When rendering time exceeds MaxTimeBudget, we switch to a lower resolution to reduce burden on the GPU.\n")
+	TEXT("Defined in ms. It's better to be conservative: for a 60FPS budget, 14 or 15 values are better than 16 to avoid frame drops."),
+	ECVF_Scalability | ECVF_Default);
+
+static TAutoConsoleVariable<float> CVarDynamicResolutionMinTimeBudget(
+	TEXT("r.DynamicResolution.MinTimeBudget"),
+	20.0f,
+	TEXT("When rendering time is lower than MinTimeBudget, we're very fast and we switch to higher resolution, increasing quality and GPU load.\n")
+	TEXT("Defined in ms. The value should be much lower than MaxTimeBudget to avoid some bouncing back and forth between HD and LD.\n")
+	TEXT("It's better to be conservative and have a value low enough you can be sure the increase in resolution won't violate the frame time budget, ")
+	TEXT("the ideal value can vary a lot depending on the difference between MaxScreenPercentage and MinScreenPercentage."),
+	ECVF_Scalability | ECVF_Default);
+
 /** Global vertex color view mode setting when SHOW_VertexColors show flag is set */
 EVertexColorViewMode::Type GVertexColorViewMode = EVertexColorViewMode::Color;
 
@@ -1823,7 +1858,60 @@ void FSceneView::EndFinalPostprocessSettings(const FSceneViewInitOptions& ViewIn
 #endif
 		{
 			static const auto ScreenPercentageCVar = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.ScreenPercentage"));
-			FinalPostProcessSettings.ScreenPercentage *= ScreenPercentageCVar->GetValueOnGameThread() / 100.0f;
+
+			if (CVarDynamicResolutionQuality.GetValueOnGameThread() > 0)
+			{
+				//
+				// Dynamic-resolution path. You can customize the logic here!
+				//
+				// We just bounce between 2 pre-defined ScreenPercentage values with a cool-down period
+				// but you can change this with your own logic, have more transitions, tune transition speed...
+				//
+
+				float Value = ScreenPercentageCVar->GetValueOnGameThread();
+
+				static int NoTouchFrames = 0; // won't change resolution while this is not 0
+				static int HighRes = 1;
+					
+				if (NoTouchFrames > 0)
+				{
+					// We changed resolution recently, cool-down period
+					NoTouchFrames--;
+				}
+				else
+				{
+					// We'are allowed to change res
+
+					// GPU time. Not necessarily for the last frame, it can be 3 frames old to avoid readback stalls.
+					float frameTime = FPlatformTime::ToMilliseconds(GGPUFrameTime); 
+
+					// Are we struggling at high resolution?
+					if (HighRes && (frameTime > CVarDynamicResolutionMaxTimeBudget.GetValueOnGameThread()) && (frameTime < 60.0f))
+					{
+						// Go to low-res and don't scale-up for a while
+						HighRes = 0;
+						NoTouchFrames = 50;
+					}
+					// Or do we have room to bump the resolution when we're very fast?
+					else if (!HighRes && (frameTime > 0.0f) && (frameTime < CVarDynamicResolutionMinTimeBudget.GetValueOnGameThread()))
+					{
+						// Go to high-res but check shortly after if we need to go back to low-res
+						HighRes = 1;
+						NoTouchFrames = 5;
+					}
+				}
+				Value *= ((HighRes ? CVarDynamicResolutionMaxScreenPercentage : CVarDynamicResolutionMinScreenPercentage).GetValueOnGameThread() / 100.0f);
+
+				// Apply the custom percentage to the scene
+				if (Value >= 0.0)
+				{
+					FinalPostProcessSettings.ScreenPercentage *= Value / 100.0f;
+				}
+			}
+			else
+			{
+				FinalPostProcessSettings.ScreenPercentage *= ScreenPercentageCVar->GetValueOnGameThread() / 100.0f;
+			}
 		}
 
 #if WITH_EDITOR
diff --git a/Engine/Source/Runtime/Renderer/Private/PostProcess/SceneRenderTargets.cpp b/Engine/Source/Runtime/Renderer/Private/PostProcess/SceneRenderTargets.cpp
index 58b137e6cca..360d81c4aa3 100644
--- a/Engine/Source/Runtime/Renderer/Private/PostProcess/SceneRenderTargets.cpp
+++ b/Engine/Source/Runtime/Renderer/Private/PostProcess/SceneRenderTargets.cpp
@@ -299,7 +299,11 @@ FIntPoint FSceneRenderTargets::ComputeDesiredSize(const FSceneViewFamily& ViewFa
 	if(!FPlatformProperties::SupportsWindowedMode())
 	{
 		// Force ScreenRes on non windowed platforms.
-		SceneTargetsSizingMethod = RequestedSize;
+		#if 1 // always grow to avoid RT re-allocation
+			SceneTargetsSizingMethod = Grow;
+		#else
+			SceneTargetsSizingMethod = RequestedSize;
+		#endif
 	}
 	else if (GIsEditor && !bIsVRScene)
 	{
-- 
2.14.1.windows.1