using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using FidelityFX;
using UnityEngine;
using UnityEngine.Rendering;
///
/// This class is responsible for hooking into various Unity events and translating them to the FSR2 subsystem.
/// This includes creation and destruction of the FSR2 context, as well as dispatching commands at the right time.
/// This class also exposes various FSR2 parameters to the Unity inspector.
///
public class Fsr2Controller : MonoBehaviour
{
[SerializeField]
private bool performSharpenPass = true;
[SerializeField, Range(0, 1)]
private float sharpness = 0.8f;
[HideInInspector]
public Camera gameCamera;
[HideInInspector]
public Camera outputCamera;
[HideInInspector]
public float renderScale;
private bool _started;
private Vector2Int DisplaySize => new Vector2Int(Screen.width, Screen.height);
private Vector2Int RenderSize => new Vector2Int(Mathf.FloorToInt(Screen.width * renderScale), Mathf.FloorToInt(Screen.height * renderScale));
private Fsr2Context _context;
private readonly Fsr2.DispatchDescription _dispatchDescription = new Fsr2.DispatchDescription();
private RenderTexture _colorRT, _depthRT, _motionVectorsRT;
private RenderTexture _outputRT;
private Texture2D _exposure;
private Material _copyDepthMat;
private Material CopyDepthMaterial
{
get
{
if (_copyDepthMat == null)
{
var copyDepthShader = Fsr2.GlobalCallbacks.LoadShader("Shaders/FSR2_CopyDepth");
_copyDepthMat = new Material(copyDepthShader);
}
return _copyDepthMat;
}
}
private Material _copyMotionMat;
private Material CopyMotionVectorsMaterial
{
get
{
if (_copyMotionMat == null)
{
var copyMotionShader = Fsr2.GlobalCallbacks.LoadShader("Shaders/FSR2_CopyMotionVectors");
_copyMotionMat = new Material(copyMotionShader);
}
return _copyMotionMat;
}
}
private void Start()
{
_started = true;
OnEnable();
}
private void OnEnable()
{
// Delay OnEnable until we're sure all fields and properties are set
if (!_started)
return;
RenderPipelineManager.endContextRendering += OnEndContextRendering;
// TODO: destroy and recreate context on screen resolution and/or quality mode change
// TODO: enable motion vector jitter cancellation or not?
_context = Fsr2.CreateContext(DisplaySize, RenderSize);
// TODO: do we need a depth buffer for the output? We will need depth & motion vectors for subsequent post-FX. How should FSR2 output these?
_outputRT = new RenderTexture(DisplaySize.x, DisplaySize.y, 24, RenderTextureFormat.ARGBHalf) { enableRandomWrite = true };
_outputRT.Create();
_exposure = new Texture2D(1, 1) { name = "FSR2 Exposure" };
_exposure.SetPixel(0, 0, Color.white);
_exposure.Apply();
}
private void OnDisable()
{
if (_exposure != null)
{
Destroy(_exposure);
_exposure = null;
}
if (_outputRT != null)
{
_outputRT.Release();
_outputRT = null;
}
if (_context != null)
{
_context.Destroy();
_context = null;
}
RenderPipelineManager.endContextRendering -= OnEndContextRendering;
}
public void UpdateInputResources(RenderTexture src)
{
// I hate having to allocate extra RTs just to duplicate already existing Unity render buffers, but AFAIK there is no way to directly address these buffers individually from code
_colorRT = RenderTexture.GetTemporary(src.width, src.height, 0, RenderTextureFormat.ARGBHalf);
_depthRT = RenderTexture.GetTemporary(src.width, src.height, 0, RenderTextureFormat.RFloat);
_motionVectorsRT = RenderTexture.GetTemporary(src.width, src.height, 0, RenderTextureFormat.RGHalf);
// TODO: might be able to combine color + depth into a single RT and separate them out using RenderTextureSubElement
Graphics.Blit(src, _colorRT);
Graphics.Blit(src, _depthRT, CopyDepthMaterial);
Graphics.Blit(src, _motionVectorsRT, CopyMotionVectorsMaterial);
}
// For scriptable rendering pipeline
private void OnEndContextRendering(ScriptableRenderContext context, List cameras)
{
Debug.Log($"OnEndContentRendering, cameras = {string.Join(", ", cameras.Select(c => c.name))}");
}
// For legacy built-in render pipeline
private void OnRenderImage(RenderTexture src, RenderTexture dest)
{
// Ensure the input resources were updated correctly before upscaling
if (_colorRT == null || _depthRT == null || _motionVectorsRT == null)
return;
_dispatchDescription.Color = _colorRT;
_dispatchDescription.Depth = _depthRT;
_dispatchDescription.MotionVectors = _motionVectorsRT;
_dispatchDescription.Output = _outputRT;
_dispatchDescription.Exposure = _exposure;
_dispatchDescription.PreExposure = 1.0f;
_dispatchDescription.EnableSharpening = performSharpenPass;
_dispatchDescription.Sharpness = sharpness;
_dispatchDescription.MotionVectorScale.x = gameCamera.pixelWidth;
_dispatchDescription.MotionVectorScale.y = gameCamera.pixelHeight;
_dispatchDescription.RenderSize = RenderSize;
_dispatchDescription.FrameTimeDelta = Time.unscaledDeltaTime;
_dispatchDescription.CameraNear = gameCamera.nearClipPlane;
_dispatchDescription.CameraFar = gameCamera.farClipPlane;
_dispatchDescription.CameraFovAngleVertical = gameCamera.fieldOfView * Mathf.Deg2Rad;
_context.Dispatch(_dispatchDescription);
RenderTexture.ReleaseTemporary(_colorRT);
RenderTexture.ReleaseTemporary(_depthRT);
RenderTexture.ReleaseTemporary(_motionVectorsRT);
_colorRT = _depthRT = _motionVectorsRT = null;
// Output upscaled image to screen
Graphics.Blit(_outputRT, dest);
}
}