diff --git a/Assets/Resources/FSR2/ffx_fsr2_accumulate_pass.compute b/Assets/Resources/FSR2/ffx_fsr2_accumulate_pass.compute index d99637e..6265645 100644 --- a/Assets/Resources/FSR2/ffx_fsr2_accumulate_pass.compute +++ b/Assets/Resources/FSR2/ffx_fsr2_accumulate_pass.compute @@ -11,4 +11,10 @@ #define FFX_GPU // Compiling for GPU #define FFX_HLSL // Compile for plain HLSL +// Ensure the correct value is defined for this keyword, as it is used to select one of multiple sampler functions +#ifdef FFX_FSR2_OPTION_REPROJECT_USE_LANCZOS_TYPE +#undef FFX_FSR2_OPTION_REPROJECT_USE_LANCZOS_TYPE +#define FFX_FSR2_OPTION_REPROJECT_USE_LANCZOS_TYPE 1 +#endif + #include "shaders/ffx_fsr2_accumulate_pass.hlsl" diff --git a/Assets/Scripts/Fsr2.cs b/Assets/Scripts/Fsr2.cs index 6ea08ef..5fb654a 100644 --- a/Assets/Scripts/Fsr2.cs +++ b/Assets/Scripts/Fsr2.cs @@ -13,7 +13,7 @@ namespace FidelityFX /// /// Creates a new FSR2 context with standard parameters that are appropriate for the current platform. /// - public static Fsr2Context CreateContext(InitializationFlags flags = 0) + public static Fsr2Context CreateContext(Vector2Int displaySize, Vector2Int maxRenderSize, InitializationFlags flags = 0) { if (SystemInfo.usesReversedZBuffer) flags |= InitializationFlags.EnableDepthInverted; @@ -23,7 +23,8 @@ namespace FidelityFX var contextDescription = new ContextDescription { Flags = flags, - DisplaySize = new Vector2Int(Screen.width, Screen.height), + DisplaySize = displaySize, + MaxRenderSize = maxRenderSize, Callbacks = GlobalCallbacks, }; diff --git a/Assets/Scripts/Fsr2Context.cs b/Assets/Scripts/Fsr2Context.cs index 69eeaa1..de549aa 100644 --- a/Assets/Scripts/Fsr2Context.cs +++ b/Assets/Scripts/Fsr2Context.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; +using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; namespace FidelityFX @@ -23,6 +24,9 @@ namespace FidelityFX private Fsr2Pipeline _generateReactivePipeline; private Fsr2Pipeline _tcrAutogeneratePipeline; + private Texture2D _lanczosLutResource; + private RenderTexture _autoExposureResource; + private ComputeBuffer _fsr2ConstantsBuffer; private readonly Fsr2.Fsr2Constants[] _fsr2ConstantsArray = { new Fsr2.Fsr2Constants() }; private ref Fsr2.Fsr2Constants Constants => ref _fsr2ConstantsArray[0]; @@ -58,10 +62,16 @@ namespace FidelityFX Constants.displaySize = _contextDescription.DisplaySize; + CreateResources(); + CreatePipelines(); + } + + private void CreateResources() + { // Generate the data for the LUT - const uint lanczos2LutWidth = 128; + const int lanczos2LutWidth = 128; short[] lanczos2Weights = new short[lanczos2LutWidth]; - for (uint currentLanczosWidthIndex = 0; currentLanczosWidthIndex < lanczos2LutWidth; ++currentLanczosWidthIndex) + for (int currentLanczosWidthIndex = 0; currentLanczosWidthIndex < lanczos2LutWidth; ++currentLanczosWidthIndex) { float x = 2.0f * currentLanczosWidthIndex / (lanczos2LutWidth - 1); float y = Fsr2.Lanczos2(x); @@ -73,10 +83,18 @@ namespace FidelityFX // UAVs *may* be an issue with the PS4 not handling simultaneous reading and writing to an RT properly // Unity does have Graphics.SetRandomWriteTarget for enabling UAV on ComputeBuffers or RTs // Unity doesn't do 1D textures so just default to Texture2D - - CreatePipelines(); - } + // Resource FSR2_LanczosLutData: FFX_RESOURCE_USAGE_READ_ONLY, FFX_SURFACE_FORMAT_R16_SNORM, FFX_RESOURCE_FLAGS_NONE + // TODO FIXME: R16_SNorm not supported? That's weird... This really ought to be a ComputeBuffer, not a Texture2D. Or just use R16_SFloat and upload pre-normalized floats, I guess... + // _lanczosLutResource = new Texture2D(lanczos2LutWidth, 1, GraphicsFormat.R16_SNorm, TextureCreationFlags.None) { name = "FSR2_LanczosLutData" }; + // _lanczosLutResource.SetPixelData(lanczos2Weights, 0); + // _lanczosLutResource.Apply(); + + // Resource FSR2_AutoExposure: FFX_RESOURCE_USAGE_UAV, FFX_SURFACE_FORMAT_R32G32_FLOAT, FFX_RESOURCE_FLAGS_NONE + _autoExposureResource = new RenderTexture(1, 1, 1, GraphicsFormat.R32G32_SFloat) { name = "FSR2_AutoExposure", enableRandomWrite = true }; + _autoExposureResource.Create(); + } + // private void InitShaders() // { // LoadComputeShader("FSR2/ffx_fsr2_compute_luminance_pyramid_pass", ref _computeLuminancePyramidShader, out _computeLuminancePyramidKernel); @@ -92,7 +110,8 @@ namespace FidelityFX private void CreatePipelines() { - _computeLuminancePyramidPipeline = new Fsr2ComputeLuminancePyramidPipeline(_contextDescription.Callbacks, _contextDescription.Flags, _fsr2ConstantsBuffer, _spdConstantsBuffer); + _computeLuminancePyramidPipeline = + new Fsr2ComputeLuminancePyramidPipeline(_contextDescription.Callbacks, _contextDescription.Flags, _fsr2ConstantsBuffer, _spdConstantsBuffer, _autoExposureResource); _accumulatePipeline = new Fsr2AccumulatePipeline(_contextDescription.Callbacks, _contextDescription.Flags, _fsr2ConstantsBuffer); _accumulateSharpenPipeline = new Fsr2AccumulateSharpenPipeline(_contextDescription.Callbacks, _contextDescription.Flags, _fsr2ConstantsBuffer); _rcasPipeline = new Fsr2RcasPipeline(_contextDescription.Callbacks, _contextDescription.Flags, _fsr2ConstantsBuffer, _rcasConstantsBuffer); @@ -110,6 +129,9 @@ namespace FidelityFX DestroyPipeline(ref _reconstructPreviousDepthPipeline); DestroyPipeline(ref _depthClipPipeline); + DestroyResource(ref _autoExposureResource); + DestroyResource(ref _lanczosLutResource); + DestroyConstantBuffer(ref _rcasConstantsBuffer); DestroyConstantBuffer(ref _spdConstantsBuffer); DestroyConstantBuffer(ref _fsr2ConstantsBuffer); @@ -214,32 +236,24 @@ namespace FidelityFX dispatchParams.RenderSize.x > 0 ? dispatchParams.RenderSize.x : dispatchParams.Input.width, dispatchParams.RenderSize.y > 0 ? dispatchParams.RenderSize.y : dispatchParams.Input.height); constants.maxRenderSize = _contextDescription.MaxRenderSize; - constants.inputColorResourceDimensions = - new Vector2Int(dispatchParams.Input.width, dispatchParams.Input.height); + constants.inputColorResourceDimensions = new Vector2Int(dispatchParams.Input.width, dispatchParams.Input.height); // Compute the horizontal FOV for the shader from the vertical one float aspectRatio = (float)dispatchParams.RenderSize.x / dispatchParams.RenderSize.y; float cameraAngleHorizontal = Mathf.Atan(Mathf.Tan(dispatchParams.CameraFovAngleVertical / 2.0f) * aspectRatio) * 2.0f; constants.tanHalfFOV = Mathf.Tan(cameraAngleHorizontal * 0.5f); - constants.viewSpaceToMetersFactor = - (dispatchParams.ViewSpaceToMetersFactor > 0.0f) ? dispatchParams.ViewSpaceToMetersFactor : 1.0f; + constants.viewSpaceToMetersFactor = (dispatchParams.ViewSpaceToMetersFactor > 0.0f) ? dispatchParams.ViewSpaceToMetersFactor : 1.0f; // Compute params to enable device depth to view space depth computation in shader constants.deviceToViewDepth = SetupDeviceDepthToViewSpaceDepthParams(dispatchParams); // To be updated if resource is larger than the actual image size - constants.downscaleFactor = new Vector2( - (float)constants.renderSize.x / _contextDescription.DisplaySize.x, - (float)constants.renderSize.y / _contextDescription.DisplaySize.y); + constants.downscaleFactor = new Vector2((float)constants.renderSize.x / _contextDescription.DisplaySize.x, (float)constants.renderSize.y / _contextDescription.DisplaySize.y); constants.previousFramePreExposure = constants.preExposure; constants.preExposure = (dispatchParams.PreExposure != 0) ? dispatchParams.PreExposure : 1.0f; // Motion vector data - Vector2Int motionVectorsTargetSize = - (_contextDescription.Flags & Fsr2.InitializationFlags.EnableDisplayResolutionMotionVectors) != 0 - ? constants.displaySize - : constants.renderSize; - + Vector2Int motionVectorsTargetSize = (_contextDescription.Flags & Fsr2.InitializationFlags.EnableDisplayResolutionMotionVectors) != 0 ? constants.displaySize : constants.renderSize; constants.motionVectorScale = dispatchParams.MotionVectorScale / motionVectorsTargetSize; // Compute jitter cancellation @@ -302,8 +316,7 @@ namespace FidelityFX // Revert x and y coords float aspect = (float)dispatchParams.RenderSize.x / dispatchParams.RenderSize.y; - float cotHalfFovY = Mathf.Cos(0.5f * dispatchParams.CameraFovAngleVertical) / - Mathf.Sin(0.5f * dispatchParams.CameraFovAngleVertical); + float cotHalfFovY = Mathf.Cos(0.5f * dispatchParams.CameraFovAngleVertical) / Mathf.Sin(0.5f * dispatchParams.CameraFovAngleVertical); int matrixIndex = (inverted ? 2 : 0) + (infinite ? 1 : 0); return new Vector4( @@ -395,6 +408,33 @@ namespace FidelityFX bufferRef = null; } + private static void DestroyResource(ref ComputeBuffer resource) + { + if (resource == null) + return; + + resource.Release(); + resource = null; + } + + private static void DestroyResource(ref Texture2D resource) + { + if (resource == null) + return; + + UnityEngine.Object.Destroy(resource); + resource = null; + } + + private static void DestroyResource(ref RenderTexture resource) + { + if (resource == null) + return; + + resource.Release(); + resource = null; + } + private static void DestroyPipeline(ref Fsr2Pipeline pipeline) { if (pipeline == null) diff --git a/Assets/Scripts/Fsr2Controller.cs b/Assets/Scripts/Fsr2Controller.cs index 73096a6..971a8c9 100644 --- a/Assets/Scripts/Fsr2Controller.cs +++ b/Assets/Scripts/Fsr2Controller.cs @@ -28,6 +28,9 @@ public class Fsr2Controller : MonoBehaviour [HideInInspector] public float renderScale; + 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(); @@ -54,7 +57,8 @@ public class Fsr2Controller : MonoBehaviour { RenderPipelineManager.endContextRendering += OnEndContextRendering; - _context = Fsr2.CreateContext(); + // TODO: destroy and recreate context on screen resolution and/or quality mode change + _context = Fsr2.CreateContext(DisplaySize, RenderSize); _upscaleRT = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.ARGBHalf); _upscaleRT.Create(); @@ -118,6 +122,7 @@ public class Fsr2Controller : MonoBehaviour _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; diff --git a/Assets/Scripts/Fsr2Pipeline.cs b/Assets/Scripts/Fsr2Pipeline.cs index 73b3764..20c54db 100644 --- a/Assets/Scripts/Fsr2Pipeline.cs +++ b/Assets/Scripts/Fsr2Pipeline.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.InteropServices; using UnityEngine; +using UnityEngine.Experimental.Rendering; using UnityEngine.Rendering; namespace FidelityFX @@ -58,13 +59,15 @@ namespace FidelityFX shaderRef = _callbacks.LoadComputeShader(name); kernelIndex = shaderRef.FindKernel("CS"); - + + bool useLut = (SystemInfo.computeSubGroupSize == 64); + // This mirrors the permutation rules from the CreatePipeline* functions if ((flags & Fsr2.InitializationFlags.EnableHighDynamicRange) != 0) shaderRef.EnableKeyword("FFX_FSR2_OPTION_HDR_COLOR_INPUT"); if ((flags & Fsr2.InitializationFlags.EnableDisplayResolutionMotionVectors) == 0) shaderRef.EnableKeyword("FFX_FSR2_OPTION_LOW_RESOLUTION_MOTION_VECTORS"); if ((flags & Fsr2.InitializationFlags.EnableMotionVectorsJitterCancellation) != 0) shaderRef.EnableKeyword("FFX_FSR2_OPTION_JITTERED_MOTION_VECTORS"); if ((flags & Fsr2.InitializationFlags.EnableDepthInverted) != 0) shaderRef.EnableKeyword("FFX_FSR2_OPTION_INVERTED_DEPTH"); - // TODO: enable FFX_FSR2_OPTION_REPROJECT_USE_LANCZOS_TYPE if the device capabilities allow (default subgroup size == 32 or 64) + if (useLut) shaderRef.EnableKeyword("FFX_FSR2_OPTION_REPROJECT_USE_LANCZOS_TYPE"); // TODO: enable FFX_HALF if FP16 is supported (except RCAS) } @@ -86,21 +89,44 @@ namespace FidelityFX internal class Fsr2ComputeLuminancePyramidPipeline : Fsr2Pipeline { private readonly ComputeBuffer _spdConstants; + private readonly RenderTexture _autoExposure; - public Fsr2ComputeLuminancePyramidPipeline(Fsr2Callbacks callbacks, Fsr2.InitializationFlags flags, ComputeBuffer constants, ComputeBuffer spdConstants) + public Fsr2ComputeLuminancePyramidPipeline(Fsr2Callbacks callbacks, Fsr2.InitializationFlags flags, ComputeBuffer constants, ComputeBuffer spdConstants, RenderTexture autoExposure) : base(callbacks, constants) { _spdConstants = spdConstants; + _autoExposure = autoExposure; LoadComputeShader("FSR2/ffx_fsr2_compute_luminance_pyramid_pass", flags); } public override void ScheduleDispatch(CommandBuffer commandBuffer, Fsr2.DispatchDescription dispatchParams, int dispatchX, int dispatchY) { + // Problems to solve: + // - How do resources (render textures) relate to SRV/UAV bindings? How are those tied together? + // - What about the SRV/UAVs that are not related to any resources? Where are those filled in? + // - How do we clear the resources that need to be cleared at dispatch? (SetBufferData) + // - Shouldn't we use a ComputeBuffer for resources that are one-dimensional and clearly not image data? e.g. SPD atomic counter & Lanczos LUT data + + commandBuffer.SetComputeTextureParam(ComputeShader, KernelIndex, SrvInputColor, dispatchParams.Input); + // Resource FSR2_SpdAtomicCounter: FFX_RESOURCE_USAGE_UAV, FFX_SURFACE_FORMAT_R32_UINT, FFX_RESOURCE_FLAGS_ALIASABLE + commandBuffer.GetTemporaryRT(UavSpdAtomicCount, 1, 1, 0, FilterMode.Point, GraphicsFormat.R32_UInt, 1, true); // FSR2_BIND_UAV_SPD_GLOBAL_ATOMIC + // Resource FSR2_ExposureMips: FFX_RESOURCE_USAGE_UAV, FFX_SURFACE_FORMAT_R16_FLOAT, FFX_RESOURCE_FLAGS_ALIASABLE, mipCount = 0 + // See `scheduleDispatch` for the song and dance to bind UAV mip levels to each luminance mipmap... and this shader specifically wants mip levels 4 and 5 + // Looks like we can just bind two separate resources here, shouldn't be necessary to bother with mipmapping nonsense. Be sure to get the right dimensions though. + commandBuffer.GetTemporaryRT(UavExposureMipLumaChange, dispatchParams.RenderSize.x >> 4, dispatchParams.RenderSize.y >> 4, 0, FilterMode.Point, GraphicsFormat.R16_SFloat, 1, true); // FSR2_BIND_UAV_EXPOSURE_MIP_LUMA_CHANGE + commandBuffer.GetTemporaryRT(UavExposureMip5, dispatchParams.RenderSize.x >> 5, dispatchParams.RenderSize.y >> 5, 0, FilterMode.Point, GraphicsFormat.R16_SFloat, 1, true); // FSR2_BIND_UAV_EXPOSURE_MIP_5 + commandBuffer.SetComputeTextureParam(ComputeShader, KernelIndex, UavAutoExposure, _autoExposure); // FSR2_BIND_UAV_AUTO_EXPOSURE commandBuffer.SetComputeConstantBufferParam(ComputeShader, CbFsr2, Constants, 0, Marshal.SizeOf()); commandBuffer.SetComputeConstantBufferParam(ComputeShader, CbSpd, _spdConstants, 0, Marshal.SizeOf()); commandBuffer.DispatchCompute(ComputeShader, KernelIndex, dispatchX, dispatchY, 1); + + // NOTE: since these temp RTs are not bound to a specific shader or kernel, we can set them globally one time and release them after dispatch. + // That way we can share aliasable resources between shaders without any complicated management. + commandBuffer.ReleaseTemporaryRT(UavSpdAtomicCount); + commandBuffer.ReleaseTemporaryRT(UavExposureMipLumaChange); + commandBuffer.ReleaseTemporaryRT(UavExposureMip5); } }