diff --git a/Assets/Scripts/Fsr2Controller.cs b/Assets/Scripts/Fsr2Controller.cs index c0a8b58..f817239 100644 --- a/Assets/Scripts/Fsr2Controller.cs +++ b/Assets/Scripts/Fsr2Controller.cs @@ -1,127 +1,145 @@ using System.Collections; -using System.Collections.Generic; 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 // TODO: rename this to Fsr2Dispatcher and move most of the functionality to Fsr2Controller (now SubsampleTest) +[RequireComponent(typeof(Camera))] +public class Fsr2Controller : MonoBehaviour { - [SerializeField] - private bool performSharpenPass = true; + [SerializeField] + private Fsr2.QualityMode qualityMode; + + private Camera _renderCamera; + private RenderTexture _originalRenderTarget; - [SerializeField, Range(0, 1)] - private float sharpness = 0.8f; + private GameObject _displayCameraObject; + private Camera _displayCamera; + private Fsr2Dispatcher _dispatcher; - [SerializeField] - private bool reset; + private Fsr2.QualityMode _prevQualityMode; + private Vector2Int _prevScreenSize; + + private CommandBuffer _opaqueOnlyCommandBuffer; + private CommandBuffer _inputsCommandBuffer; - [HideInInspector] - public Camera renderCamera; + private void OnEnable() + { + _renderCamera = GetComponent(); + if (_displayCameraObject == null) + { + _displayCameraObject = new GameObject("FSR2 Camera Object"); + _displayCameraObject.transform.SetParent(transform); + _displayCameraObject.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); + //outputCameraObject.transform.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector; - [HideInInspector] - public float renderScale; + // Create a camera that does nothing except output the upscaled image + _displayCamera = _displayCameraObject.AddComponent(); + _displayCamera.backgroundColor = Color.clear; + _displayCamera.clearFlags = CameraClearFlags.Nothing; + _displayCamera.eventMask = 0; + _displayCamera.cullingMask = 0; + _displayCamera.useOcclusionCulling = false; + _displayCamera.orthographic = true; + _displayCamera.allowMSAA = false; + _displayCamera.renderingPath = RenderingPath.Forward; - 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)); + _dispatcher = _displayCameraObject.AddComponent(); + } + + _dispatcher.renderCamera = _renderCamera; + _dispatcher.renderScale = 1.0f / Fsr2.GetUpscaleRatioFromQualityMode(qualityMode); + _dispatcher.enabled = true; - private Fsr2Context _context; - private readonly Fsr2.DispatchDescription _dispatchDescription = new Fsr2.DispatchDescription(); - private CommandBuffer _commandBuffer; + Fsr2.GetRenderResolutionFromQualityMode(out var renderWidth, out var renderHeight, Screen.width, Screen.height, qualityMode); + + _originalRenderTarget = _renderCamera.targetTexture; // TODO: if this isn't null, could maybe reuse this for the output texture? - private void Start() - { - _started = true; - OnEnable(); - } + _renderCamera.targetTexture = new RenderTexture( + renderWidth, renderHeight, + _originalRenderTarget != null ? _originalRenderTarget.depth : 32, + _originalRenderTarget != null ? _originalRenderTarget.format : RenderTextureFormat.ARGBHalf) { name = "FSR2 Input Texture" }; + + _renderCamera.targetTexture.Create(); - private void OnEnable() - { - // Delay OnEnable until we're sure all fields and properties are set - if (!_started) - return; + _renderCamera.depthTextureMode |= DepthTextureMode.Depth | DepthTextureMode.MotionVectors; + + _opaqueOnlyCommandBuffer = new CommandBuffer { name = "FSR2 Opaque Input" }; + // TODO: may need to copy the opaque-only render buffer to a temp RT here, in which case we'll need an additional CommandBuffer to release the temp RT again + _opaqueOnlyCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvOpaqueOnly, BuiltinRenderTextureType.CameraTarget, RenderTextureSubElement.Color); + _renderCamera.AddCommandBuffer(CameraEvent.BeforeImageEffectsOpaque, _opaqueOnlyCommandBuffer); + + _inputsCommandBuffer = new CommandBuffer { name = "FSR2 Inputs" }; + _inputsCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvInputColor, BuiltinRenderTextureType.CameraTarget, RenderTextureSubElement.Color); + _inputsCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvInputDepth, BuiltinRenderTextureType.CameraTarget, RenderTextureSubElement.Depth); + _inputsCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvInputMotionVectors, BuiltinRenderTextureType.MotionVectors); + _renderCamera.AddCommandBuffer(CameraEvent.BeforeImageEffects, _inputsCommandBuffer); - _context = Fsr2.CreateContext(DisplaySize, RenderSize, Fsr2.InitializationFlags.EnableMotionVectorsJitterCancellation); - _commandBuffer = new CommandBuffer { name = "FSR2 Dispatch" }; + // Adjust texture mipmap LOD bias by log2(renderResolution/displayResolution) - 1.0; + // May need to leave this to the game dev, as we don't know which textures do and don't belong to the 3D scene + float biasOffset = Fsr2.GetMipmapBiasOffset(renderWidth, Screen.width); + Fsr2.GlobalCallbacks.ApplyMipmapBias(biasOffset); + + _prevScreenSize = new Vector2Int(Screen.width, Screen.height); + _prevQualityMode = qualityMode; } private void OnDisable() { - if (_commandBuffer != null) - { - _commandBuffer.Release(); - _commandBuffer = null; - } + float biasOffset = Fsr2.GetMipmapBiasOffset(_renderCamera.targetTexture.width, _prevScreenSize.x); + Fsr2.GlobalCallbacks.ApplyMipmapBias(-biasOffset); - if (_context != null) - { - _context.Destroy(); - _context = null; - } - } + _renderCamera.RemoveCommandBuffer(CameraEvent.BeforeImageEffectsOpaque, _opaqueOnlyCommandBuffer); + _opaqueOnlyCommandBuffer.Release(); + _opaqueOnlyCommandBuffer = null; + + _renderCamera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, _inputsCommandBuffer); + _inputsCommandBuffer.Release(); + _inputsCommandBuffer = null; - public void SetJitterOffset(Vector2 jitterOffset) - { - _dispatchDescription.JitterOffset = jitterOffset; + _renderCamera.targetTexture.Release(); + _renderCamera.targetTexture = _originalRenderTarget; + + _dispatcher.enabled = false; + _displayCamera.enabled = false; } - // For legacy built-in render pipeline - private void OnRenderImage(RenderTexture src, RenderTexture dest) + private void Update() { - _commandBuffer.Clear(); + _displayCamera.enabled = _renderCamera.enabled; - 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(Fsr2Pipeline.UavUpscaledOutput, dest); - } - else - { - // We are rendering to the backbuffer, so we need a temporary render texture for FSR2 to output to - _commandBuffer.GetTemporaryRT(Fsr2Pipeline.UavUpscaledOutput, DisplaySize.x, DisplaySize.y, 0, default, GraphicsFormat.R16G16B16A16_SFloat, 1, true); - } - - _dispatchDescription.Color = null; - _dispatchDescription.Depth = null; - _dispatchDescription.MotionVectors = null; - _dispatchDescription.Output = null; - _dispatchDescription.Exposure = null; - _dispatchDescription.Reactive = null; - _dispatchDescription.PreExposure = 0; - _dispatchDescription.EnableSharpening = performSharpenPass; - _dispatchDescription.Sharpness = sharpness; - _dispatchDescription.MotionVectorScale.x = -renderCamera.pixelWidth; - _dispatchDescription.MotionVectorScale.y = -renderCamera.pixelHeight; - _dispatchDescription.RenderSize = RenderSize; - _dispatchDescription.FrameTimeDelta = Time.unscaledDeltaTime; - _dispatchDescription.CameraNear = renderCamera.nearClipPlane; - _dispatchDescription.CameraFar = renderCamera.farClipPlane; - _dispatchDescription.CameraFovAngleVertical = renderCamera.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) + if (Screen.width != _prevScreenSize.x || Screen.height != _prevScreenSize.y || qualityMode != _prevQualityMode) { - _commandBuffer.Blit(Fsr2Pipeline.UavUpscaledOutput, dest); - _commandBuffer.ReleaseTemporaryRT(Fsr2Pipeline.UavUpscaledOutput); + // Force all resources to be destroyed and recreated with the new settings + OnDisable(); + OnEnable(); } + } + + private Rect _tempRect; + + private void OnPreRender() + { + _tempRect = _renderCamera.rect; + _renderCamera.aspect = (Screen.width * _tempRect.width) / (Screen.height * _tempRect.height); + _renderCamera.rect = new Rect(0, 0, 1, 1); - Graphics.ExecuteCommandBuffer(_commandBuffer); + // Perform custom jittering of the camera's projection matrix according to FSR2's instructions + int jitterPhaseCount = Fsr2.GetJitterPhaseCount(_renderCamera.targetTexture.width, Screen.width); + Fsr2.GetJitterOffset(out float jitterX, out float jitterY, Time.frameCount, jitterPhaseCount); - // Shut up the Unity warning about not writing to the destination texture - Graphics.SetRenderTarget(dest); + _dispatcher.SetJitterOffset(new Vector2(jitterX, jitterY)); + + var targetTexture = _renderCamera.targetTexture; + jitterX = 2.0f * jitterX / targetTexture.width; + jitterY = 2.0f * jitterY / targetTexture.height; + + var jitterTranslationMatrix = Matrix4x4.Translate(new Vector3(jitterX, jitterY, 0)); + _renderCamera.projectionMatrix = jitterTranslationMatrix * _renderCamera.nonJitteredProjectionMatrix; + } + + private void OnPostRender() + { + _renderCamera.rect = _tempRect; + _renderCamera.ResetProjectionMatrix(); } } diff --git a/Assets/Scripts/Fsr2Controller.cs.meta b/Assets/Scripts/Fsr2Controller.cs.meta index f9c76b6..229652b 100644 --- a/Assets/Scripts/Fsr2Controller.cs.meta +++ b/Assets/Scripts/Fsr2Controller.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d59d37e0744f1344e95cf689136fc9d9 +guid: 955cb66a9ecc20441a7e32934c9b4690 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Fsr2Dispatcher.cs b/Assets/Scripts/Fsr2Dispatcher.cs new file mode 100644 index 0000000..d1cc161 --- /dev/null +++ b/Assets/Scripts/Fsr2Dispatcher.cs @@ -0,0 +1,127 @@ +using System.Collections; +using System.Collections.Generic; +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 Fsr2Dispatcher : MonoBehaviour // TODO: rename this to Fsr2Dispatcher and move most of the functionality to Fsr2Controller (now SubsampleTest) +{ + [SerializeField] + private bool performSharpenPass = true; + + [SerializeField, Range(0, 1)] + private float sharpness = 0.8f; + + [SerializeField] + private bool reset; + + [HideInInspector] + public Camera renderCamera; + + [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 CommandBuffer _commandBuffer; + + 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); + _commandBuffer = new CommandBuffer { name = "FSR2 Dispatch" }; + } + + private void OnDisable() + { + if (_commandBuffer != null) + { + _commandBuffer.Release(); + _commandBuffer = 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) + { + _commandBuffer.Clear(); + + 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(Fsr2Pipeline.UavUpscaledOutput, dest); + } + else + { + // We are rendering to the backbuffer, so we need a temporary render texture for FSR2 to output to + _commandBuffer.GetTemporaryRT(Fsr2Pipeline.UavUpscaledOutput, DisplaySize.x, DisplaySize.y, 0, default, GraphicsFormat.R16G16B16A16_SFloat, 1, true); + } + + _dispatchDescription.Color = null; + _dispatchDescription.Depth = null; + _dispatchDescription.MotionVectors = null; + _dispatchDescription.Output = null; + _dispatchDescription.Exposure = null; + _dispatchDescription.Reactive = null; + _dispatchDescription.PreExposure = 0; + _dispatchDescription.EnableSharpening = performSharpenPass; + _dispatchDescription.Sharpness = sharpness; + _dispatchDescription.MotionVectorScale.x = -renderCamera.pixelWidth; + _dispatchDescription.MotionVectorScale.y = -renderCamera.pixelHeight; + _dispatchDescription.RenderSize = RenderSize; + _dispatchDescription.FrameTimeDelta = Time.unscaledDeltaTime; + _dispatchDescription.CameraNear = renderCamera.nearClipPlane; + _dispatchDescription.CameraFar = renderCamera.farClipPlane; + _dispatchDescription.CameraFovAngleVertical = renderCamera.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(Fsr2Pipeline.UavUpscaledOutput, dest); + _commandBuffer.ReleaseTemporaryRT(Fsr2Pipeline.UavUpscaledOutput); + } + + Graphics.ExecuteCommandBuffer(_commandBuffer); + + // Shut up the Unity warning about not writing to the destination texture + Graphics.SetRenderTarget(dest); + } +} diff --git a/Assets/Scripts/SubsampleTest.cs.meta b/Assets/Scripts/Fsr2Dispatcher.cs.meta similarity index 83% rename from Assets/Scripts/SubsampleTest.cs.meta rename to Assets/Scripts/Fsr2Dispatcher.cs.meta index 229652b..f9c76b6 100644 --- a/Assets/Scripts/SubsampleTest.cs.meta +++ b/Assets/Scripts/Fsr2Dispatcher.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 955cb66a9ecc20441a7e32934c9b4690 +guid: d59d37e0744f1344e95cf689136fc9d9 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/SubsampleTest.cs b/Assets/Scripts/SubsampleTest.cs deleted file mode 100644 index 4fbcd51..0000000 --- a/Assets/Scripts/SubsampleTest.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System.Collections; -using FidelityFX; -using UnityEngine; -using UnityEngine.Rendering; - -[RequireComponent(typeof(Camera))] -public class SubsampleTest : MonoBehaviour // TODO: rename this to Fsr2Controller -{ - [SerializeField] - private Fsr2.QualityMode qualityMode; - - private Camera _renderCamera; - private RenderTexture _originalRenderTarget; - - private GameObject _displayCameraObject; - private Camera _displayCamera; - - private Fsr2Controller _fsr2Controller; - - private Fsr2.QualityMode _prevQualityMode; - private Vector2Int _prevScreenSize; - - private CommandBuffer _opaqueOnlyCommandBuffer; - private CommandBuffer _inputsCommandBuffer; - - private void OnEnable() - { - _renderCamera = GetComponent(); - if (_displayCameraObject == null) - { - _displayCameraObject = new GameObject("FSR2 Camera Object"); - _displayCameraObject.transform.SetParent(transform); - _displayCameraObject.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); - //outputCameraObject.transform.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector; - - // Create a camera that does nothing except output the upscaled image - _displayCamera = _displayCameraObject.AddComponent(); - _displayCamera.backgroundColor = Color.clear; - _displayCamera.clearFlags = CameraClearFlags.Nothing; - _displayCamera.eventMask = 0; - _displayCamera.cullingMask = 0; - _displayCamera.useOcclusionCulling = false; - _displayCamera.orthographic = true; - _displayCamera.allowMSAA = false; - _displayCamera.renderingPath = RenderingPath.Forward; - - _fsr2Controller = _displayCameraObject.AddComponent(); - } - - _fsr2Controller.renderCamera = _renderCamera; - _fsr2Controller.renderScale = 1.0f / Fsr2.GetUpscaleRatioFromQualityMode(qualityMode); - _fsr2Controller.enabled = true; - - Fsr2.GetRenderResolutionFromQualityMode(out var renderWidth, out var renderHeight, Screen.width, Screen.height, qualityMode); - - _originalRenderTarget = _renderCamera.targetTexture; // TODO: if this isn't null, could maybe reuse this for the output texture? - - _renderCamera.targetTexture = new RenderTexture( - renderWidth, renderHeight, - _originalRenderTarget != null ? _originalRenderTarget.depth : 32, - _originalRenderTarget != null ? _originalRenderTarget.format : RenderTextureFormat.ARGBHalf) { name = "FSR2 Input Texture" }; - - _renderCamera.targetTexture.Create(); - - _renderCamera.depthTextureMode |= DepthTextureMode.Depth | DepthTextureMode.MotionVectors; - - _opaqueOnlyCommandBuffer = new CommandBuffer { name = "FSR2 Opaque Input" }; - // TODO: may need to copy the opaque-only render buffer to a temp RT here, in which case we'll need an additional CommandBuffer to release the temp RT again - _opaqueOnlyCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvOpaqueOnly, BuiltinRenderTextureType.CameraTarget, RenderTextureSubElement.Color); - _renderCamera.AddCommandBuffer(CameraEvent.BeforeImageEffectsOpaque, _opaqueOnlyCommandBuffer); - - _inputsCommandBuffer = new CommandBuffer { name = "FSR2 Inputs" }; - _inputsCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvInputColor, BuiltinRenderTextureType.CameraTarget, RenderTextureSubElement.Color); - _inputsCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvInputDepth, BuiltinRenderTextureType.CameraTarget, RenderTextureSubElement.Depth); - _inputsCommandBuffer.SetGlobalTexture(Fsr2Pipeline.SrvInputMotionVectors, BuiltinRenderTextureType.MotionVectors); - _renderCamera.AddCommandBuffer(CameraEvent.BeforeImageEffects, _inputsCommandBuffer); - - // Adjust texture mipmap LOD bias by log2(renderResolution/displayResolution) - 1.0; - // May need to leave this to the game dev, as we don't know which textures do and don't belong to the 3D scene - float biasOffset = Fsr2.GetMipmapBiasOffset(renderWidth, Screen.width); - Fsr2.GlobalCallbacks.ApplyMipmapBias(biasOffset); - - _prevScreenSize = new Vector2Int(Screen.width, Screen.height); - _prevQualityMode = qualityMode; - } - - private void OnDisable() - { - float biasOffset = Fsr2.GetMipmapBiasOffset(_renderCamera.targetTexture.width, _prevScreenSize.x); - Fsr2.GlobalCallbacks.ApplyMipmapBias(-biasOffset); - - _renderCamera.RemoveCommandBuffer(CameraEvent.BeforeImageEffectsOpaque, _opaqueOnlyCommandBuffer); - _opaqueOnlyCommandBuffer.Release(); - _opaqueOnlyCommandBuffer = null; - - _renderCamera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, _inputsCommandBuffer); - _inputsCommandBuffer.Release(); - _inputsCommandBuffer = null; - - _renderCamera.targetTexture.Release(); - _renderCamera.targetTexture = _originalRenderTarget; - - _fsr2Controller.enabled = false; - _displayCamera.enabled = false; - } - - private void Update() - { - _displayCamera.enabled = _renderCamera.enabled; - - if (Screen.width != _prevScreenSize.x || Screen.height != _prevScreenSize.y || qualityMode != _prevQualityMode) - { - // Force all resources to be destroyed and recreated with the new settings - OnDisable(); - OnEnable(); - } - } - - private Rect _tempRect; - - private void OnPreRender() - { - _tempRect = _renderCamera.rect; - _renderCamera.aspect = (Screen.width * _tempRect.width) / (Screen.height * _tempRect.height); - _renderCamera.rect = new Rect(0, 0, 1, 1); - - // Perform custom jittering of the camera's projection matrix according to FSR2's instructions - int jitterPhaseCount = Fsr2.GetJitterPhaseCount(_renderCamera.targetTexture.width, Screen.width); - Fsr2.GetJitterOffset(out float jitterX, out float jitterY, Time.frameCount, jitterPhaseCount); - - _fsr2Controller.SetJitterOffset(new Vector2(jitterX, jitterY)); - - var targetTexture = _renderCamera.targetTexture; - jitterX = 2.0f * jitterX / targetTexture.width; - jitterY = 2.0f * jitterY / targetTexture.height; - - var jitterTranslationMatrix = Matrix4x4.Translate(new Vector3(jitterX, jitterY, 0)); - _renderCamera.projectionMatrix = jitterTranslationMatrix * _renderCamera.nonJitteredProjectionMatrix; - } - - private void OnPostRender() - { - _renderCamera.rect = _tempRect; - _renderCamera.ResetProjectionMatrix(); - } -}