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); } }