using System; using System.Collections; using System.Collections.Generic; using System.Linq; using FidelityFX; using UnityEngine; using UnityEngine.Experimental.Rendering; 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; [SerializeField] private bool reset; [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 _upscaledOutput; 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; _context = Fsr2.CreateContext(DisplaySize, RenderSize, Fsr2.InitializationFlags.EnableMotionVectorsJitterCancellation); // 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? // TODO: can probably be a temporary RT _upscaledOutput = new RenderTexture(DisplaySize.x, DisplaySize.y, 0, RenderTextureFormat.ARGBHalf) { name = "FSR2 Upscaled Output", enableRandomWrite = true }; _upscaledOutput.Create(); } private void OnDisable() { if (_upscaledOutput != null) { _upscaledOutput.Release(); _upscaledOutput = null; } if (_context != null) { _context.Destroy(); _context = null; } } public void SetJitterOffset(Vector2 jitterOffset) { _dispatchDescription.JitterOffset = jitterOffset; } // For legacy built-in render pipeline private void OnRenderImage(RenderTexture src, RenderTexture dest) { var renderBuffer = gameCamera.targetTexture; var commandBuffer = new CommandBuffer { name = "FSR2" }; int motionVectorsId = Shader.PropertyToID("r_input_motion_vectors"); int upscaledOutputId = Shader.PropertyToID("rw_upscaled_output"); // I hate having to allocate extra RTs just to duplicate already existing Unity render buffers, but AFAIK there is no way to directly address motion vectors from code // TODO: or can we? Look at RenderTargetIdentifier.MotionVectors with SetGlobalTexture!! (Possibly in gameCamera OnRenderImage) commandBuffer.GetTemporaryRT(motionVectorsId, renderBuffer.width, renderBuffer.height, 0, default, RenderTextureFormat.RGHalf); commandBuffer.Blit(renderBuffer, motionVectorsId, CopyMotionVectorsMaterial); if (dest != null) { // We have more image effects lined up after this, so FSR2 can output straight to the intermediate render texture // TODO: we should probably use a shader to include depth & motion vectors into the output commandBuffer.SetGlobalTexture(upscaledOutputId, dest); } else { // We are rendering to the backbuffer, so we need a temporary render texture for FSR2 to output to commandBuffer.GetTemporaryRT(upscaledOutputId, DisplaySize.x, DisplaySize.y, 0, default, GraphicsFormat.R16G16B16A16_SFloat, 1, true); } _dispatchDescription.ColorDepth = renderBuffer; _dispatchDescription.MotionVectors = null; _dispatchDescription.Output = null; _dispatchDescription.Exposure = null; _dispatchDescription.Reactive = null; _dispatchDescription.PreExposure = 0; _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; _dispatchDescription.ViewSpaceToMetersFactor = 1.0f; // 1 unit is 1 meter in Unity _dispatchDescription.Reset = reset; reset = false; _context.Dispatch(_dispatchDescription, commandBuffer); // Output upscaled image to screen // TODO: if `dest` is null, we likely don't care about the depth & motion vectors anymore if (dest == null) { commandBuffer.Blit(upscaledOutputId, dest); commandBuffer.ReleaseTemporaryRT(upscaledOutputId); } commandBuffer.ReleaseTemporaryRT(motionVectorsId); Graphics.ExecuteCommandBuffer(commandBuffer); commandBuffer.Release(); // Shut up the Unity warning about not writing to the destination texture Graphics.SetRenderTarget(dest); } }