/*
* Copyright (c) 2020 - 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
* NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
* property and proprietary rights in and to this material, related
* documentation and any modifications thereto. Any use, reproduction,
* disclosure or distribution of this material and related documentation
* without an express license agreement from NVIDIA CORPORATION or
* its affiliates is strictly prohibited.
*/

#include "DLSSUpscaler.h"


#include "DLSSUpscalerPrivate.h"
#include "DLSSUpscalerHistory.h"
#include "DLSSSettings.h"

#include "VelocityCombinePass.h"

#include "PostProcess/SceneRenderTargets.h"
#include "PostProcess/PostProcessing.h"
#include "SceneTextureParameters.h"
#include "ScreenPass.h"

#include "RayTracing/RaytracingOptions.h"

#include "LegacyScreenPercentageDriver.h"


#define LOCTEXT_NAMESPACE "FDLSSModule"

#ifndef SUPPORTS_POSTPROCESSING_SCREEN_PERCENTAGE
#define SUPPORTS_POSTPROCESSING_SCREEN_PERCENTAGE 1
#endif

static TAutoConsoleVariable<int32> CVarNGXDLSSEnable(
	TEXT("r.NGX.DLSS.Enable"), 1,
	TEXT("Enable/Disable DLSS entirely."),
	ECVF_RenderThreadSafe);

static TAutoConsoleVariable<bool> CVarNGXDLAAEnable(
	TEXT("r.NGX.DLAA.Enable"), false,
	TEXT("Enable/Disable DLAA"),
	ECVF_RenderThreadSafe);

static TAutoConsoleVariable<int32> CVarNGXDLSSAutomationTesting(
	TEXT("r.NGX.DLSS.AutomationTesting"), 0,
	TEXT("Whether the NGX library should be loaded when GIsAutomationTesting is true.(default is false)\n")
	TEXT("Must be set to true before startup. This can be enabled for cases where running automation testing with DLSS desired"),
	ECVF_ReadOnly);

static TAutoConsoleVariable<int32> CVarNGXDLSSPerfQualitySetting(
	TEXT("r.NGX.DLSS.Quality"),
	-1,
	TEXT("DLSS Performance/Quality setting. Not all modes might be supported at runtime, in this case Balanced mode is used as a fallback\n")
	TEXT(" -2: Ultra Performance\n")
	TEXT(" -1: Performance (default)\n")
	TEXT("  0: Balanced\n")
	TEXT("  1: Quality\n")
	TEXT("  2: Ultra Quality\n"),
	ECVF_RenderThreadSafe);

static TAutoConsoleVariable<bool> CVarNGXDLSSAutoQualitySetting(
	TEXT("r.NGX.DLSS.Quality.Auto"), 0,
	TEXT("Enable/Disable DLSS automatically selecting the DLSS quality mode based on the render resolution"),
	ECVF_RenderThreadSafe);

static TAutoConsoleVariable<float> CVarNGXDLSSSharpness(
	TEXT("r.NGX.DLSS.Sharpness"),
	0.0f,
	TEXT("-1.0 to 1.0: Softening/sharpening to apply to the DLSS pass. Negative values soften the image, positive values sharpen. (default: 0.0f)"),
	ECVF_RenderThreadSafe);

static TAutoConsoleVariable<int32> CVarNGXDLSSDilateMotionVectors(
	TEXT("r.NGX.DLSS.DilateMotionVectors"),
	1,
	TEXT(" 0: pass low resolution motion vectors into DLSS\n")
	TEXT(" 1: pass dilated high resolution motion vectors into DLSS. This can help with improving image quality of thin details. (default)"),
	ECVF_RenderThreadSafe);

static TAutoConsoleVariable<int32> CVarNGXDLSSAutoExposure(
	TEXT("r.NGX.DLSS.AutoExposure"), 1,
	TEXT("0: Use the engine-computed exposure value for input images to DLSS - in some cases this may reduce artifacts\n")
	TEXT("1: Enable DLSS internal auto-exposure instead of the application provided one (default)\n"),
	ECVF_RenderThreadSafe);

static TAutoConsoleVariable<int32> CVarNGXDLSSReleaseMemoryOnDelete(
	TEXT("r.NGX.DLSS.ReleaseMemoryOnDelete"), 
	1,
	TEXT("Enabling/disable releasing DLSS related memory on the NGX side when DLSS features get released.(default=1)"),
	ECVF_RenderThreadSafe);


static TAutoConsoleVariable<int32> CVarNGXDLSSFeatureCreationNode(
	TEXT("r.NGX.DLSS.FeatureCreationNode"), -1,
	TEXT("Determines which GPU the DLSS feature is getting created on\n")
	TEXT("-1: Create on the GPU the command list is getting executed on (default)\n")
	TEXT(" 0: Create on GPU node 0 \n")
	TEXT(" 1: Create on GPU node 1 \n"),
	ECVF_RenderThreadSafe);


static TAutoConsoleVariable<int32> CVarNGXDLSSFeatureVisibilityMask(
	TEXT("r.NGX.DLSS.FeatureVisibilityMask"), -1,
	TEXT("Determines which GPU the DLSS feature is visible to\n")
	TEXT("-1: Visible to the GPU the command list is getting executed on (default)\n")
	TEXT(" 1: visible to GPU node 0 \n")
	TEXT(" 2:  visible to GPU node 1 \n")
	TEXT(" 3:  visible to GPU node 0 and GPU node 1\n"),
	ECVF_RenderThreadSafe);

DECLARE_GPU_STAT(DLSS)

BEGIN_SHADER_PARAMETER_STRUCT(FDLSSShaderParameters, )

// Input images
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneColorInput)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneDepthInput)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, EyeAdaptation)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneVelocityInput)


// Output images
RDG_TEXTURE_ACCESS(SceneColorOutput, ERHIAccess::UAVCompute)

END_SHADER_PARAMETER_STRUCT()


FIntPoint FDLSSPassParameters::GetOutputExtent() const
{
	check(Validate());
	check(SceneColorInput);

	FIntPoint InputExtent = SceneColorInput->Desc.Extent;

	FIntPoint QuantizedPrimaryUpscaleViewSize;
	QuantizeSceneBufferSize(OutputViewRect.Size(), QuantizedPrimaryUpscaleViewSize);

	return FIntPoint(
		FMath::Max(InputExtent.X, QuantizedPrimaryUpscaleViewSize.X),
		FMath::Max(InputExtent.Y, QuantizedPrimaryUpscaleViewSize.Y));
}

bool FDLSSPassParameters::Validate() const
{
	checkf(OutputViewRect.Min == FIntPoint::ZeroValue,TEXT("The DLSS OutputViewRect %dx%d must be non-zero"), OutputViewRect.Min.X, OutputViewRect.Min.Y);
	return true;
}

const TCHAR* FDLSSUpscaler::GetDebugName() const
{
	return TEXT("FDLSSUpscaler");
}


static NVSDK_NGX_PerfQuality_Value ToNGXQuality(EDLSSQualityMode Quality)
{
	static_assert(int32(EDLSSQualityMode::NumValues) == 5, "dear DLSS plugin NVIDIA developer, please update this code to handle the new EDLSSQualityMode enum values");
	switch (Quality)
	{
		case EDLSSQualityMode::UltraPerformance:
			return NVSDK_NGX_PerfQuality_Value_UltraPerformance;

		default:
			checkf(false, TEXT("ToNGXQuality should not be called with an out of range EDLSSQualityMode from the higher level code"));
		case EDLSSQualityMode::Performance:
			return NVSDK_NGX_PerfQuality_Value_MaxPerf;

		case EDLSSQualityMode::Balanced:
			return NVSDK_NGX_PerfQuality_Value_Balanced;

		case EDLSSQualityMode::Quality:
			return NVSDK_NGX_PerfQuality_Value_MaxQuality;
		
		case EDLSSQualityMode::UltraQuality:
			return NVSDK_NGX_PerfQuality_Value_UltraQuality;
	}
}

NGXRHI* FDLSSUpscaler::NGXRHIExtensions;
TStaticArray <TSharedPtr<FDLSSUpscaler>, uint32(EDLSSQualityMode::NumValues)> FDLSSUpscaler::DLSSUpscalerInstancesPerViewFamily;
float FDLSSUpscaler::MinResolutionFraction = TNumericLimits <float>::Max();
float FDLSSUpscaler::MaxResolutionFraction = TNumericLimits <float>::Min();


uint32 FDLSSUpscaler::NumRuntimeQualityModes = 0;
TArray<FDLSSOptimalSettings> FDLSSUpscaler::ResolutionSettings;


FDLSSUpscaler* FDLSSUpscaler::GetUpscalerInstanceForViewFamily(const FDLSSUpscaler* InUpscaler, EDLSSQualityMode InQualityMode)
{
	uint32 ArrayIndex = (int32)ToNGXQuality(InQualityMode);
	if (!DLSSUpscalerInstancesPerViewFamily[ArrayIndex])
	{
		DLSSUpscalerInstancesPerViewFamily[ArrayIndex] = MakeShared<FDLSSUpscaler>(InUpscaler, InQualityMode);

	}
	return DLSSUpscalerInstancesPerViewFamily[ArrayIndex].Get();
}

bool FDLSSUpscaler::IsValidUpscalerInstance(const ITemporalUpscaler* InUpscaler)
{
	// DLSSUpscalerInstancesPerViewFamily gets lazily initialized, but we don't want to accidentally treat nullptr as a valid
	// upscaler instance, when we want to check (e.g. in the denoiser) whether DLSS is actually active for the viewfamily
	if (InUpscaler == nullptr)
	{
		return false;
	}

	for (auto UpscalerInstance : DLSSUpscalerInstancesPerViewFamily)
	{
		if (UpscalerInstance.Get() == InUpscaler)
		{
			return true;
		}
	}
	return false;
}

bool FDLSSUpscaler::IsAutoQualityMode()
{
	return CVarNGXDLSSAutoQualitySetting.GetValueOnAnyThread();
}

bool FDLSSUpscaler::IsDLAAMode()
{
	return CVarNGXDLAAEnable.GetValueOnAnyThread();
}

void FDLSSUpscaler::SetAutoQualityMode(bool bAutoQualityMode)
{
	check(IsInGameThread());
	CVarNGXDLSSAutoQualitySetting->Set(bAutoQualityMode, ECVF_SetByCommandline);
}

// make copy & assign quality mode
FDLSSUpscaler::FDLSSUpscaler(const FDLSSUpscaler* InUpscaler, EDLSSQualityMode InQualityMode)
	: FDLSSUpscaler(*InUpscaler)
{
	DLSSQualityMode = InQualityMode;
	check(NGXRHIExtensions);
}


FDLSSUpscaler::FDLSSUpscaler(NGXRHI* InNGXRHIExtensions)
	
{
	UE_LOG(LogDLSS, Log, TEXT("%s Enter"), ANSI_TO_TCHAR(__FUNCTION__));
	
	
	checkf(!NGXRHIExtensions, TEXT("static member NGXRHIExtensions should only be assigned once by this ctor when called during module startup") );
	NGXRHIExtensions = InNGXRHIExtensions;

	ResolutionSettings.Init(FDLSSOptimalSettings(), int32(EDLSSQualityMode::NumValues));

	static_assert(int32(EDLSSQualityMode::NumValues) == 5, "dear DLSS plugin NVIDIA developer, please update this code to handle the new EDLSSQualityMode enum values");
	for (auto QualityMode : { EDLSSQualityMode::UltraPerformance,  EDLSSQualityMode::Performance , EDLSSQualityMode::Balanced, EDLSSQualityMode::Quality,  EDLSSQualityMode::UltraQuality })
	{
		check(ToNGXQuality(QualityMode) < ResolutionSettings.Num());
		check(ToNGXQuality(QualityMode) >= 0);

		FDLSSOptimalSettings OptimalSettings = NGXRHIExtensions->GetDLSSOptimalSettings(ToNGXQuality(QualityMode));
		
		ResolutionSettings[ToNGXQuality(QualityMode)] = OptimalSettings;

		// we only consider non-fixed resolutions for the overall min / max resolution fraction
		if (OptimalSettings.bIsSupported && !OptimalSettings.IsFixedResolution())
		{
			// We use OptimalSettings.OptimalResolutionFraction to avoid getting to "floating point close" to OptimalSettings.{MinMax}ResolutionFraction) 
			MinResolutionFraction = FMath::Min(MinResolutionFraction, OptimalSettings.OptimalResolutionFraction);
			MaxResolutionFraction = FMath::Max(MaxResolutionFraction, OptimalSettings.OptimalResolutionFraction);
			++NumRuntimeQualityModes;
		}

		UE_LOG(LogDLSS, Log, TEXT("QualityMode %d: bSupported = %u, ResolutionFraction = %.4f. MinResolutionFraction=%.4f,  MaxResolutionFraction %.4f"),
			QualityMode, OptimalSettings.bIsSupported, OptimalSettings.OptimalResolutionFraction, OptimalSettings.MinResolutionFraction, OptimalSettings.MaxResolutionFraction);
	}

	// the DLSS module will report DLSS as not supported if there are no supported quality modes at runtime
	UE_LOG(LogDLSS, Log, TEXT("NumRuntimeQualityModes=%u, MinResolutionFraction=%.4f,  MaxResolutionFraction=%.4f"), NumRuntimeQualityModes, MinResolutionFraction, MaxResolutionFraction);

	// Higher levels of the code (e.g. UI) should check whether each mode is actually supported
	// But for now verify early that the DLSS 2.0 modes are supported. Those checks could be removed in the future
	check(IsQualityModeSupported(EDLSSQualityMode::Performance));
	check(IsQualityModeSupported(EDLSSQualityMode::Balanced));
	check(IsQualityModeSupported(EDLSSQualityMode::Quality));


	UE_LOG(LogDLSS, Log, TEXT("%s Leave"), ANSI_TO_TCHAR(__FUNCTION__));
}

FDLSSUpscaler::~FDLSSUpscaler()
{
	UE_LOG(LogDLSS, Log, TEXT("%s Enter"), ANSI_TO_TCHAR(__FUNCTION__));

	UE_LOG(LogDLSS, Log, TEXT("%s Leave"), ANSI_TO_TCHAR(__FUNCTION__));
}

// this gets explicitly called during module shutdown
void FDLSSUpscaler::ReleaseStaticResources()
{
	UE_LOG(LogDLSS, Log, TEXT("%s Enter"), ANSI_TO_TCHAR(__FUNCTION__));
	ResolutionSettings.Empty();
	for (auto& UpscalerInstance : DLSSUpscalerInstancesPerViewFamily)
	{
		UpscalerInstance.Reset();
	}
	
	UE_LOG(LogDLSS, Log, TEXT("%s Leave"), ANSI_TO_TCHAR(__FUNCTION__));
}

#if DLSS_ENGINE_ADDPASSES_RETURN_THROUGH_PARAMS
void FDLSSUpscaler::AddPasses(
#else
ITemporalUpscaler::FOutputs FDLSSUpscaler::AddPasses(
#endif
	FRDGBuilder& GraphBuilder,
	const FViewInfo& View,
	const FPassInputs& PassInputs
#if DLSS_ENGINE_ADDPASSES_RETURN_THROUGH_PARAMS
	, FRDGTextureRef* OutSceneColorTexture
	, FIntRect* OutSceneColorViewRect
	, FRDGTextureRef* OutSceneColorHalfResTexture
	, FIntRect* OutSceneColorHalfResViewRect
#endif
) const
{
#if ENGINE_MAJOR_VERSION < 5
	// For TAAU, this can happen with screen percentages larger than 100%, so not something that DLSS viewports are setup with
	checkf(!PassInputs.bAllowDownsampleSceneColor,TEXT("The DLSS plugin does not support downsampling the scenecolor. Please set r.TemporalAA.AllowDownsampling=0"));
#endif
	checkf(View.PrimaryScreenPercentageMethod == EPrimaryScreenPercentageMethod::TemporalUpscale, TEXT("DLSS requires TemporalUpscale. If you hit this assert, please set r.TemporalAA.Upscale=1"));


	

	const FTemporalAAHistory& InputHistory = View.PrevViewInfo.TemporalAAHistory;
	const TRefCountPtr<ICustomTemporalAAHistory> InputCustomHistory = View.PrevViewInfo.CustomTemporalAAHistory;

	FTemporalAAHistory* OutputHistory = View.ViewState ? &(View.ViewState->PrevFrameViewInfo.TemporalAAHistory) : nullptr;
	TRefCountPtr < ICustomTemporalAAHistory >* OutputCustomHistory = View.ViewState ? &(View.ViewState->PrevFrameViewInfo.CustomTemporalAAHistory) : nullptr;

	
	FDLSSPassParameters DLSSParameters(View);
	const FIntRect SecondaryViewRect = DLSSParameters.OutputViewRect;
#if !DLSS_ENGINE_ADDPASSES_RETURN_THROUGH_PARAMS
	ITemporalUpscaler::FOutputs Outputs;
#endif
	{
		RDG_GPU_STAT_SCOPE(GraphBuilder, DLSS);
		RDG_EVENT_SCOPE(GraphBuilder, "DLSS");

		const bool bDilateMotionVectors = CVarNGXDLSSDilateMotionVectors.GetValueOnRenderThread() != 0;

		FRDGTextureRef CombinedVelocityTexture = AddVelocityCombinePass(GraphBuilder, View, PassInputs.SceneDepthTexture, PassInputs.SceneVelocityTexture, bDilateMotionVectors);

		DLSSParameters.SceneColorInput = PassInputs.SceneColorTexture;
		DLSSParameters.SceneVelocityInput = CombinedVelocityTexture;
		DLSSParameters.SceneDepthInput = PassInputs.SceneDepthTexture;
		DLSSParameters.bHighResolutionMotionVectors = bDilateMotionVectors;
		const FDLSSOutputs DLSSOutputs = AddDLSSPass(
			GraphBuilder,
			View,
			DLSSParameters,
			InputHistory,
			OutputHistory,
			InputCustomHistory,
			OutputCustomHistory
		);

#if DLSS_ENGINE_ADDPASSES_RETURN_THROUGH_PARAMS
		FRDGTextureRef SceneColorTexture = DLSSOutputs.SceneColor;

		*OutSceneColorTexture = SceneColorTexture;
		*OutSceneColorViewRect = SecondaryViewRect;

		*OutSceneColorHalfResTexture = nullptr;
		*OutSceneColorHalfResViewRect = FIntRect(FIntPoint::ZeroValue, FIntPoint::ZeroValue);
#else
		Outputs.FullRes.Texture = DLSSOutputs.SceneColor;
		Outputs.FullRes.ViewRect = SecondaryViewRect;
#endif
	}
#if !DLSS_ENGINE_ADDPASSES_RETURN_THROUGH_PARAMS
	return Outputs;
#endif
}

FDLSSOutputs FDLSSUpscaler::AddDLSSPass(
	FRDGBuilder& GraphBuilder,
	const FViewInfo& View,
	const FDLSSPassParameters& Inputs,
	const FTemporalAAHistory& InputHistory,
	FTemporalAAHistory* OutputHistory,
	const TRefCountPtr<ICustomTemporalAAHistory> InputCustomHistoryInterface,
	TRefCountPtr<ICustomTemporalAAHistory>* OutputCustomHistoryInterface
) const
{
	check(IsValidUpscalerInstance(this));
	check(IsDLSSActive());
	const FDLSSUpscalerHistory* InputCustomHistory = static_cast<const FDLSSUpscalerHistory*>(InputCustomHistoryInterface.GetReference());

	const bool bCameraCut = !InputHistory.IsValid() || View.bCameraCut || !OutputHistory;
	const FIntPoint OutputExtent = Inputs.GetOutputExtent();

	const FIntRect SrcRect = Inputs.InputViewRect;
	const FIntRect DestRect = Inputs.OutputViewRect;

	const float ScaleX = float(SrcRect.Width()) / float(DestRect.Width());
	const float ScaleY = float(SrcRect.Height()) / float(DestRect.Height());

	// FDLSSUpscaler::SetupMainGameViewFamily or FDLSSUpscalerEditor::SetupEditorViewFamily 
	// set DLSSQualityMode by setting an FDLSSUpscaler on the ViewFamily (from the pool in DLSSUpscalerInstancesPerViewFamily)
	
	checkf(DLSSQualityMode != EDLSSQualityMode::NumValues, TEXT("Invalid Quality mode, not initialized"));
	checkf(IsQualityModeSupported(DLSSQualityMode), TEXT("%u is not a valid Quality mode"), DLSSQualityMode);

	// This assert can accidentally hit with small viewrect dimensions (e.g. when resizing an editor view) due to floating point rounding & quantization issues
	// e.g. with 33% screen percentage at 1000 DestRect dimension we get 333/1000 = 0.33 but at 10 DestRect dimension we get 3/10 0.3, thus the assert hits
	
	checkf(DestRect.Width()  < 100 || GetMinResolutionFractionForQuality(DLSSQualityMode) - 0.01f <= ScaleX && ScaleX <= GetMaxResolutionFractionForQuality(DLSSQualityMode) + 0.01f, TEXT("The current resolution fraction %f is out of the supported DLSS range [%f ... %f] for quality mode %d."), ScaleX, GetMinResolutionFractionForQuality(DLSSQualityMode), GetMaxResolutionFractionForQuality(DLSSQualityMode), DLSSQualityMode);
	checkf(DestRect.Height() < 100 || GetMinResolutionFractionForQuality(DLSSQualityMode) - 0.01f <= ScaleY && ScaleY <= GetMaxResolutionFractionForQuality(DLSSQualityMode) + 0.01f, TEXT("The current resolution fraction %f is out of the supported DLSS range [%f ... %f] for quality mode %d."), ScaleY, GetMinResolutionFractionForQuality(DLSSQualityMode), GetMaxResolutionFractionForQuality(DLSSQualityMode), DLSSQualityMode);

	const TCHAR* PassName = TEXT("MainUpsampling");

	// Create outputs
	FDLSSOutputs Outputs;
	{
		FRDGTextureDesc SceneColorDesc = FRDGTextureDesc::Create2D(
			OutputExtent,
			PF_FloatRGBA,
			FClearValueBinding::Black,
			TexCreate_ShaderResource | TexCreate_UAV);

		const TCHAR* OutputName = TEXT("DLSSOutputSceneColor");

		Outputs.SceneColor = GraphBuilder.CreateTexture(
			SceneColorDesc,
			OutputName);
	}

	FDLSSStateRef DLSSState = (InputCustomHistory && InputCustomHistory->DLSSState) ? InputCustomHistory->DLSSState : MakeShared<FDLSSState, ESPMode::ThreadSafe>();
	{
		FDLSSShaderParameters* PassParameters = GraphBuilder.AllocParameters<FDLSSShaderParameters>();

		// Set up common shader parameters
		const FIntPoint InputExtent = Inputs.SceneColorInput->Desc.Extent;
		const FIntRect InputViewRect = Inputs.InputViewRect;
		const FIntRect OutputViewRect = Inputs.OutputViewRect;

		// Input buffer shader parameters
		{
			PassParameters->SceneColorInput = Inputs.SceneColorInput;
			PassParameters->SceneDepthInput = Inputs.SceneDepthInput;
			PassParameters->SceneVelocityInput = Inputs.SceneVelocityInput;
			PassParameters->EyeAdaptation = GetEyeAdaptationTexture(GraphBuilder, View);
		}

		// Outputs 
		{
			PassParameters->SceneColorOutput = Outputs.SceneColor;
		}

		const FVector2D JitterOffset = View.TemporalJitterPixels;
#if ENGINE_MAJOR_VERSION < 5
		const float DeltaWorldTime = View.Family->DeltaWorldTime;
#else
		const float DeltaWorldTime = View.Family->Time.GetDeltaWorldTimeSeconds();
#endif

		const float PreExposure = View.PreExposure;
		const bool bUseAutoExposure = CVarNGXDLSSAutoExposure.GetValueOnRenderThread() != 0;

		const bool bReleaseMemoryOnDelete = CVarNGXDLSSReleaseMemoryOnDelete.GetValueOnRenderThread() != 0;

		const float Sharpness = FMath::Clamp(CVarNGXDLSSSharpness.GetValueOnRenderThread(), -1.0f, 1.0f);
		NGXRHI* LocalNGXRHIExtensions = this->NGXRHIExtensions;
		const int32 NGXPerfQuality = ToNGXQuality(DLSSQualityMode);
		GraphBuilder.AddPass(
			RDG_EVENT_NAME("DLSS %s%s %dx%d -> %dx%d",
				PassName,
				Sharpness != 0.0f ? TEXT(" Sharpen") : TEXT(""),
				SrcRect.Width(), SrcRect.Height(),
				DestRect.Width(), DestRect.Height()),
			PassParameters,
			ERDGPassFlags::Compute | ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
			[LocalNGXRHIExtensions, PassParameters, Inputs, bCameraCut, JitterOffset, DeltaWorldTime, PreExposure, Sharpness, NGXPerfQuality, DLSSState, bUseAutoExposure, bReleaseMemoryOnDelete](FRHICommandListImmediate& RHICmdList)
		{
			FRHIDLSSArguments DLSSArguments;
			FMemory::Memzero(&DLSSArguments, sizeof(DLSSArguments));

			// input parameters
			DLSSArguments.SrcRect = Inputs.InputViewRect;
			DLSSArguments.DestRect = Inputs.OutputViewRect;

			DLSSArguments.Sharpness = Sharpness;
			DLSSArguments.bReset = bCameraCut;


#if ENGINE_MAJOR_VERSION < 5
			DLSSArguments.JitterOffset = JitterOffset;
			DLSSArguments.MotionVectorScale = FVector2D(1.0f, 1.0f);
#else
			DLSSArguments.JitterOffset = FVector2f(JitterOffset);	// LWC_TODO: Precision loss
			DLSSArguments.MotionVectorScale = FVector2f::UnitVector;

#endif ENGINE_MAJOR_VERSION

			DLSSArguments.bHighResolutionMotionVectors = Inputs.bHighResolutionMotionVectors;
			DLSSArguments.DeltaTime = DeltaWorldTime;
			DLSSArguments.bReleaseMemoryOnDelete = bReleaseMemoryOnDelete;

			DLSSArguments.PerfQuality = NGXPerfQuality;

			check(PassParameters->SceneColorInput);
			PassParameters->SceneColorInput->MarkResourceAsUsed();
			DLSSArguments.InputColor = PassParameters->SceneColorInput->GetRHI();
					

			check(PassParameters->SceneVelocityInput);
			PassParameters->SceneVelocityInput->MarkResourceAsUsed();
			DLSSArguments.InputMotionVectors = PassParameters->SceneVelocityInput->GetRHI();

			check(PassParameters->SceneDepthInput);
			PassParameters->SceneDepthInput->MarkResourceAsUsed();
			DLSSArguments.InputDepth = PassParameters->SceneDepthInput->GetRHI();

			check(PassParameters->EyeAdaptation);
			PassParameters->EyeAdaptation->MarkResourceAsUsed();
			DLSSArguments.InputExposure = PassParameters->EyeAdaptation->GetRHI();
			DLSSArguments.PreExposure = PreExposure;
			DLSSArguments.bUseAutoExposure = bUseAutoExposure;
			
			// output images
			check(PassParameters->SceneColorOutput);
			PassParameters->SceneColorOutput->MarkResourceAsUsed();
			DLSSArguments.OutputColor = PassParameters->SceneColorOutput->GetRHI();

			RHICmdList.TransitionResource(ERHIAccess::UAVMask, DLSSArguments.OutputColor);
			RHICmdList.EnqueueLambda(
				[LocalNGXRHIExtensions, DLSSArguments, DLSSState](FRHICommandListImmediate& Cmd) mutable
			{
				const uint32 FeatureCreationNode = CVarNGXDLSSFeatureCreationNode.GetValueOnRenderThread();
				const uint32 FeatureVisibilityMask = CVarNGXDLSSFeatureVisibilityMask.GetValueOnRenderThread();

				DLSSArguments.GPUNode = FeatureCreationNode == -1 ? Cmd.GetGPUMask().ToIndex() : FMath::Clamp(FeatureCreationNode, 0u, GNumExplicitGPUsForRendering - 1);
				DLSSArguments.GPUVisibility = FeatureVisibilityMask == -1 ? Cmd.GetGPUMask().GetNative() : (Cmd.GetGPUMask().All().GetNative() & FeatureVisibilityMask) ;

				LocalNGXRHIExtensions->ExecuteDLSS(Cmd, DLSSArguments, DLSSState);
			});
		});
	}

	if (!View.bStatePrevViewInfoIsReadOnly && OutputHistory)
	{
		OutputHistory->SafeRelease();

		GraphBuilder.QueueTextureExtraction(Outputs.SceneColor, &OutputHistory->RT[0]);

		OutputHistory->ViewportRect = DestRect;
		OutputHistory->ReferenceBufferSize = OutputExtent;
	}


	if (!View.bStatePrevViewInfoIsReadOnly && OutputCustomHistoryInterface)
	{
		if (!OutputCustomHistoryInterface->GetReference())
		{
			(*OutputCustomHistoryInterface) = new FDLSSUpscalerHistory(DLSSState);
		}
	}
	return Outputs;
}

void FDLSSUpscaler::Tick(FRHICommandListImmediate& RHICmdList)
{
	check(NGXRHIExtensions);
	check(IsInRenderingThread());
	// Pass it over to the RHI thread which handles the lifetime of the NGX DLSS resources
	RHICmdList.EnqueueLambda(
		[this](FRHICommandListImmediate& Cmd)
	{
		NGXRHIExtensions->TickPoolElements();
	});
}

bool FDLSSUpscaler::IsQualityModeSupported(EDLSSQualityMode InQualityMode) const
{
	return ResolutionSettings[ToNGXQuality(InQualityMode)].bIsSupported;
}

bool FDLSSUpscaler::IsDLSSActive() const
{
	static const auto CVarTemporalAAUpscaler = IConsoleManager::Get().FindConsoleVariable(TEXT("r.TemporalAA.Upscaler"));
	const bool bDLSSActive =
#if DLSS_ENGINE_HAS_GTEMPORALUPSCALER
		((GTemporalUpscaler == this) || IsValidUpscalerInstance(this)) &&
#else
		((GCustomStaticScreenPercentage == this) || IsValidUpscalerInstance(this)) &&
#endif
		CVarTemporalAAUpscaler && (CVarTemporalAAUpscaler->GetInt() != 0) &&
		((CVarNGXDLSSEnable.GetValueOnAnyThread() != 0) || CVarNGXDLAAEnable.GetValueOnAnyThread());
	return bDLSSActive;
}


void FDLSSUpscaler::SetupMainGameViewFamily(FSceneViewFamily& ViewFamily)
{
	const bool bDLSSActiveWithAutomation = !GIsAutomationTesting || (GIsAutomationTesting && (CVarNGXDLSSAutomationTesting.GetValueOnAnyThread() != 0));
	
	if (IsDLSSActive() && bDLSSActiveWithAutomation)
	{
#if DLSS_ENGINE_HAS_GTEMPORALUPSCALER
		checkf(GTemporalUpscaler == this, TEXT("GTemporalUpscaler is not set to a DLSS upscaler . Please check that only one upscaling plugin is active."));
#endif
		checkf(GCustomStaticScreenPercentage == this, TEXT("GCustomStaticScreenPercentage is not set to a DLSS upscaler. Please check that only one upscaling plugin is active."));

		if (!GIsEditor || (GIsEditor && GIsPlayInEditorWorld && EnableDLSSInPlayInEditorViewports()))
		{
			bool bIsDLAAMode = IsDLAAMode();
			EDLSSQualityMode DLSSQuality;
			if (bIsDLAAMode)
			{
				DLSSQuality = EDLSSQualityMode::Quality;
			}
			else if (IsAutoQualityMode())
			{
				TOptional<EDLSSQualityMode> MaybeDLSSQuality = GetAutoQualityModeFromViewFamily(ViewFamily);
				if (!MaybeDLSSQuality.IsSet())
				{
					return;
				}
				else
				{
					DLSSQuality = MaybeDLSSQuality.GetValue();
				}
			}
			else
			{
				DLSSQuality = GetSupportedQualityModeFromCVarValue(CVarNGXDLSSPerfQualitySetting.GetValueOnGameThread());
			}

			ViewFamily.SetTemporalUpscalerInterface(GetUpscalerInstanceForViewFamily(this, DLSSQuality));

			if (ViewFamily.EngineShowFlags.ScreenPercentage && !ViewFamily.GetScreenPercentageInterface())
			{
				// DLSS uses recommended resolution fraction, DLAA forces a 1.0 scale
				float ResolutionFraction = 1.0f;
				if (!bIsDLAAMode)
				{
					ResolutionFraction = GetOptimalResolutionFractionForQuality(DLSSQuality);
				}
				ViewFamily.SetScreenPercentageInterface(new FLegacyScreenPercentageDriver(
					ViewFamily, ResolutionFraction
#if SUPPORTS_POSTPROCESSING_SCREEN_PERCENTAGE					
					/* AllowPostProcessSettingsScreenPercentage = */ , false
#endif
				
				));
			}
		}
	}
}

#if DLSS_ENGINE_SUPPORTS_CSSPD
void FDLSSUpscaler::SetupViewFamily(FSceneViewFamily& ViewFamily, TSharedPtr<ICustomStaticScreenPercentageData> InScreenPercentageDataInterface)
{
	check(InScreenPercentageDataInterface.IsValid());
	TSharedPtr<FDLSSViewportQualitySetting> ScreenPercentageData = StaticCastSharedPtr<FDLSSViewportQualitySetting>(InScreenPercentageDataInterface);
	
	EDLSSQualityMode Quality = static_cast<EDLSSQualityMode>(ScreenPercentageData->QualitySetting);
	const bool bIsDLAAMode = ScreenPercentageData->bDLAAEnabled;
	if (bIsDLAAMode)
	{
		Quality = EDLSSQualityMode::Quality;
	}
	if (!IsQualityModeSupported(Quality))
	{
		UE_LOG(LogDLSS, Warning, TEXT("DLSS Quality mode is not supported %d"), Quality);
		return;
	}
	const bool bDLSSActiveWithAutomation = !GIsAutomationTesting || (GIsAutomationTesting && (CVarNGXDLSSAutomationTesting.GetValueOnAnyThread() != 0));
	if (IsDLSSActive() && bDLSSActiveWithAutomation)
	{
#if DLSS_ENGINE_HAS_GTEMPORALUPSCALER
		checkf(GTemporalUpscaler == this, TEXT("GTemporalUpscaler is not set to a DLSS upscaler . Please check that only one upscaling plugin is active."));
#endif
		checkf(GCustomStaticScreenPercentage == this, TEXT("GCustomStaticScreenPercentage is not set to a DLSS upscaler. Please check that only one upscaling plugin is active."));

		ViewFamily.SetTemporalUpscalerInterface(GetUpscalerInstanceForViewFamily(this, Quality));

		if (ViewFamily.EngineShowFlags.ScreenPercentage && !ViewFamily.GetScreenPercentageInterface())
		{
			const float ResolutionFraction = bIsDLAAMode ? 1.0f : GetOptimalResolutionFractionForQuality(Quality);
			ViewFamily.SetScreenPercentageInterface(new FLegacyScreenPercentageDriver(
				ViewFamily, ResolutionFraction
				#if SUPPORTS_POSTPROCESSING_SCREEN_PERCENTAGE					
					/* AllowPostProcessSettingsScreenPercentage = */ , false
				#endif
				));
		}
	}
}
#endif

TOptional<EDLSSQualityMode> FDLSSUpscaler::GetAutoQualityModeFromViewFamily(const FSceneViewFamily& ViewFamily) const
{
	if (ensure(ViewFamily.RenderTarget != nullptr))
	{
		FIntPoint ViewSize = ViewFamily.RenderTarget->GetSizeXY();
		int32 Pixels = ViewSize.X * ViewSize.Y;
		return GetAutoQualityModeFromPixels(Pixels);
	}

	return TOptional<EDLSSQualityMode> {};
}

TOptional<EDLSSQualityMode> FDLSSUpscaler::GetAutoQualityModeFromPixels(int PixelCount) const
{
	if (PixelCount >= 8'300'000 && IsQualityModeSupported(EDLSSQualityMode::UltraPerformance))
	{
		return EDLSSQualityMode::UltraPerformance;
	}
	else if (PixelCount >= 3'690'000 && IsQualityModeSupported(EDLSSQualityMode::Performance))
	{
		return EDLSSQualityMode::Performance;
	}
	else if (PixelCount >= 2'030'000 && IsQualityModeSupported(EDLSSQualityMode::Quality))
	{
		return EDLSSQualityMode::Quality;
	}

	return TOptional<EDLSSQualityMode> {};
}


bool  FDLSSUpscaler::EnableDLSSInPlayInEditorViewports() const
{
	if (GetDefault<UDLSSOverrideSettings>()->EnableDLSSInPlayInEditorViewportsOverride == EDLSSSettingOverride::UseProjectSettings)
	{
		return GetDefault<UDLSSSettings>()->bEnableDLSSInPlayInEditorViewports;
	}
	else
	{
		return GetDefault<UDLSSOverrideSettings>()->EnableDLSSInPlayInEditorViewportsOverride == EDLSSSettingOverride::Enabled;
	}
}

float FDLSSUpscaler::GetMinUpsampleResolutionFraction() const
{
	return MinResolutionFraction;
}

float FDLSSUpscaler::GetMaxUpsampleResolutionFraction() const
{
	return MaxResolutionFraction;
}

float FDLSSUpscaler::GetOptimalResolutionFractionForQuality(EDLSSQualityMode Quality) const
{
	checkf(IsQualityModeSupported(Quality),TEXT("%u is not a valid Quality mode"), Quality);
	return ResolutionSettings[ToNGXQuality(Quality)].OptimalResolutionFraction;
}

float  FDLSSUpscaler::GetOptimalSharpnessForQuality(EDLSSQualityMode Quality) const
{
	checkf(IsQualityModeSupported(Quality), TEXT("%u is not a valid Quality mode"), Quality);
	return ResolutionSettings[ToNGXQuality(Quality)].Sharpness;
}

float FDLSSUpscaler::GetMinResolutionFractionForQuality(EDLSSQualityMode Quality) const
{
	checkf(IsQualityModeSupported(Quality), TEXT("%u is not a valid Quality mode"), Quality);
	return ResolutionSettings[ToNGXQuality(Quality)].MinResolutionFraction;
}

float FDLSSUpscaler::GetMaxResolutionFractionForQuality(EDLSSQualityMode Quality) const
{
	checkf(IsQualityModeSupported(Quality), TEXT("%u is not a valid Quality mode"), Quality);
	return ResolutionSettings[ToNGXQuality(Quality)].MaxResolutionFraction;
}

bool FDLSSUpscaler::IsFixedResolutionFraction(EDLSSQualityMode Quality) const
{
	checkf(IsQualityModeSupported(Quality), TEXT("%u is not a valid Quality mode"), Quality);
	return ResolutionSettings[ToNGXQuality(Quality)].IsFixedResolution();
}

#undef LOCTEXT_NAMESPACE
