From ce66799787cffef8b6fa36ce8734a7ed1675f2f4 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Sat, 18 Feb 2023 16:57:51 +0100 Subject: [PATCH] Implemented pretty much all of the constant buffer setup during dispatch. --- Assets/Scripts/Fsr2Context.cs | 221 ++++++++++++++++++++++++++++++++-- 1 file changed, 209 insertions(+), 12 deletions(-) diff --git a/Assets/Scripts/Fsr2Context.cs b/Assets/Scripts/Fsr2Context.cs index 3b47443..a7deb95 100644 --- a/Assets/Scripts/Fsr2Context.cs +++ b/Assets/Scripts/Fsr2Context.cs @@ -7,6 +7,8 @@ namespace FidelityFX { public class Fsr2Context { + private const int MaxQueuedFrames = 16; + private Fsr2.ContextDescription _contextDescription; private ComputeShader _prepareInputColorShader; @@ -21,15 +23,23 @@ namespace FidelityFX private ComputeBuffer _fsr2ConstantsBuffer; private readonly Fsr2Constants[] _fsr2ConstantsArray = { new Fsr2Constants() }; + private ref Fsr2Constants Constants => ref _fsr2ConstantsArray[0]; private ComputeBuffer _spdConstantsBuffer; private readonly SpdConstants[] _spdConstantsArray = { new SpdConstants() }; + private ref SpdConstants SpdConsts => ref _spdConstantsArray[0]; private ComputeBuffer _rcasConstantsBuffer; private readonly RcasConstants[] _rcasConstantsArray = new RcasConstants[1]; + private ref RcasConstants RcasConsts => ref _rcasConstantsArray[0]; private ComputeBuffer _generateReactiveConstantsBuffer; private readonly GenerateReactiveConstants[] _generateReactiveConstantsArray = { new GenerateReactiveConstants() }; + private ref GenerateReactiveConstants GenReactiveConsts => ref _generateReactiveConstantsArray[0]; + + private bool _firstExecution; + private Vector2 _previousJitterOffset; + private uint _resourceFrameIndex; public void Create(Fsr2.ContextDescription contextDescription) { @@ -40,7 +50,9 @@ namespace FidelityFX _rcasConstantsBuffer = CreateConstantBuffer(); // Set defaults - _fsr2ConstantsArray[0].displaySize = _contextDescription.DisplaySize; + _firstExecution = true; + _resourceFrameIndex = 0; + Constants.displaySize = _contextDescription.DisplaySize; // Generate the data for the LUT const uint lanczos2LutWidth = 128; @@ -74,22 +86,46 @@ namespace FidelityFX LoadComputeShader("FSR2/ffx_fsr2_tcr_autogen_pass", ref _tcrAutogenShader); } - public void Dispatch(Fsr2.DispatchDescription dispatchDescription) + public void Dispatch(Fsr2.DispatchDescription dispatchParams) { - _fsr2ConstantsArray[0].preExposure = dispatchDescription.PreExposure; - _fsr2ConstantsBuffer.SetData(_fsr2ConstantsArray); + if (_firstExecution) + { + // TODO: clear values + } + + // TODO: setup resource indices for buffers that get swapped per frame + + bool resetAccumulation = dispatchParams.Reset || _firstExecution; + _firstExecution = false; + + // TODO Register resources: ... + + SetupConstants(dispatchParams, resetAccumulation); + + // Reactive mask bias + const int threadGroupWorkRegionDim = 8; + int dispatchSrcX = (Constants.renderSize.x + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + int dispatchSrcY = (Constants.renderSize.y + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + int dispatchDstX = (_contextDescription.DisplaySize.x + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + int dispatchDstY = (_contextDescription.DisplaySize.y + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + + if (resetAccumulation) + { + // TODO: clear reconstructed depth for max depth store + } + + // Auto exposure + SetupSpdConstants(dispatchParams); - if (dispatchDescription.EnableSharpening) + if (dispatchParams.EnableSharpening) { - int sharpnessIndex = Mathf.RoundToInt(Mathf.Clamp01(dispatchDescription.Sharpness) * (RcasConfigs.Count - 1)); - _rcasConstantsArray[0] = RcasConfigs[sharpnessIndex]; - _rcasConstantsBuffer.SetData(_rcasConstantsArray); + SetupRcasConstants(dispatchParams); // Run the RCAS sharpening filter on the upscaled image int rcasKernel = _rcasShader.FindKernel("CS"); - _rcasShader.SetTexture(rcasKernel, "r_input_exposure", dispatchDescription.Exposure); - _rcasShader.SetTexture(rcasKernel, "r_rcas_input", dispatchDescription.Input); - _rcasShader.SetTexture(rcasKernel, "rw_upscaled_output", dispatchDescription.Output); + _rcasShader.SetTexture(rcasKernel, "r_input_exposure", dispatchParams.Exposure); + _rcasShader.SetTexture(rcasKernel, "r_rcas_input", dispatchParams.Input); + _rcasShader.SetTexture(rcasKernel, "rw_upscaled_output", dispatchParams.Output); _rcasShader.SetConstantBuffer("cbFSR2", _fsr2ConstantsBuffer, 0, Marshal.SizeOf()); _rcasShader.SetConstantBuffer("cbRCAS", _rcasConstantsBuffer, 0, Marshal.SizeOf()); @@ -101,7 +137,164 @@ namespace FidelityFX } else { - Graphics.Blit(dispatchDescription.Input, dispatchDescription.Output); + Graphics.Blit(dispatchParams.Input, dispatchParams.Output); + } + + _resourceFrameIndex = (_resourceFrameIndex + 1) % MaxQueuedFrames; + + // TODO Unregister resources: release temp RT's + } + + private void SetupConstants(Fsr2.DispatchDescription dispatchParams, bool resetAccumulation) + { + ref Fsr2Constants constants = ref Constants; + + constants.jitterOffset = dispatchParams.JitterOffset; + constants.renderSize = new Vector2Int( + 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); + + // 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; + + // 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.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; + + constants.motionVectorScale = dispatchParams.MotionVectorScale / motionVectorsTargetSize; + + // Compute jitter cancellation + if ((_contextDescription.Flags & Fsr2.InitializationFlags.EnableMotionVectorsJitterCancellation) != 0) + { + constants.motionVectorJitterCancellation = (_previousJitterOffset - constants.jitterOffset) / motionVectorsTargetSize; + _previousJitterOffset = constants.jitterOffset; + } + + int jitterPhaseCount = Fsr2.GetJitterPhaseCount(dispatchParams.RenderSize.x, _contextDescription.DisplaySize.x); + if (resetAccumulation || constants.jitterPhaseCount == 0) + { + constants.jitterPhaseCount = jitterPhaseCount; + } + else + { + int jitterPhaseCountDelta = (int)(jitterPhaseCount - constants.jitterPhaseCount); + if (jitterPhaseCountDelta > 0) + constants.jitterPhaseCount++; + else if (jitterPhaseCountDelta < 0) + constants.jitterPhaseCount--; + } + + // Convert delta time to seconds and clamp to [0, 1] + constants.deltaTime = Mathf.Clamp01(dispatchParams.FrameTimeDelta / 1000.0f); + + if (resetAccumulation) + constants.frameIndex = 0; + else + constants.frameIndex++; + + // Shading change usage of the SPD mip levels + constants.lumaMipLevelToUse = 4; // NOTE: this is derived from a bunch of auto-generated constant values in the FSR2 code + + float mipDiv = 2 << constants.lumaMipLevelToUse; + constants.lumaMipDimensions.x = (int)(constants.maxRenderSize.x / mipDiv); + constants.lumaMipDimensions.y = (int)(constants.maxRenderSize.y / mipDiv); + + _fsr2ConstantsBuffer.SetData(_fsr2ConstantsArray); + } + + private Vector4 SetupDeviceDepthToViewSpaceDepthParams(Fsr2.DispatchDescription dispatchParams) + { + bool inverted = (_contextDescription.Flags & Fsr2.InitializationFlags.EnableDepthInverted) != 0; + bool infinite = (_contextDescription.Flags & Fsr2.InitializationFlags.EnableDepthInfinite) != 0; + + // make sure it has no impact if near and far plane values are swapped in dispatch params + // the flags "inverted" and "infinite" will decide what transform to use + float min = Mathf.Min(dispatchParams.CameraNear, dispatchParams.CameraFar); + float max = Mathf.Max(dispatchParams.CameraNear, dispatchParams.CameraFar); + + if (inverted) + { + (min, max) = (max, min); + } + + float q = max / (min - max); + float d = -1.0f; + + Vector4 matrixElemC = new Vector4(q, -1.0f - Mathf.Epsilon, q, 0.0f + Mathf.Epsilon); + Vector4 matrixElemE = new Vector4(q * min, -min - Mathf.Epsilon, q * min, max); + + // 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); + + int matrixIndex = (inverted ? 2 : 0) + (infinite ? 1 : 0); + return new Vector4( + d * matrixElemC[matrixIndex], + matrixElemE[matrixIndex], + aspect / cotHalfFovY, + 1.0f / cotHalfFovY); + } + + private void SetupRcasConstants(Fsr2.DispatchDescription dispatchParams) + { + int sharpnessIndex = Mathf.RoundToInt(Mathf.Clamp01(dispatchParams.Sharpness) * (RcasConfigs.Count - 1)); + RcasConsts = RcasConfigs[sharpnessIndex]; + + _rcasConstantsBuffer.SetData(_rcasConstantsArray); + } + + private void SetupSpdConstants(Fsr2.DispatchDescription dispatchParams) + { + RectInt rectInfo = new RectInt(0, 0, dispatchParams.RenderSize.x, dispatchParams.RenderSize.y); + SpdSetup(rectInfo, out var dispatchThreadGroupCount, out var workGroupOffset, out var numWorkGroupsAndMips); + + // Downsample + ref SpdConstants spdConstants = ref SpdConsts; + spdConstants.numWorkGroups = (uint)numWorkGroupsAndMips.x; + spdConstants.mips = (uint)numWorkGroupsAndMips.y; + spdConstants.workGroupOffsetX = (uint)workGroupOffset.x; + spdConstants.workGroupOffsetY = (uint)workGroupOffset.y; + spdConstants.renderSizeX = (uint)dispatchParams.RenderSize.x; + spdConstants.renderSizeY = (uint)dispatchParams.RenderSize.y; + + _spdConstantsBuffer.SetData(_spdConstantsArray); + } + + private static void SpdSetup(RectInt rectInfo, + out Vector2Int dispatchThreadGroupCount, out Vector2Int workGroupOffset, out Vector2Int numWorkGroupsAndMips, int mips = -1) + { + workGroupOffset = new Vector2Int(rectInfo.x / 64, rectInfo.y / 64); + + int endIndexX = (rectInfo.x + rectInfo.width - 1) / 64; + int endIndexY = (rectInfo.y + rectInfo.height - 1) / 64; + + dispatchThreadGroupCount = new Vector2Int(endIndexX + 1 - workGroupOffset.x, endIndexY + 1 - workGroupOffset.y); + + numWorkGroupsAndMips = new Vector2Int(dispatchThreadGroupCount.x * dispatchThreadGroupCount.y, mips); + if (mips < 0) + { + float resolution = Math.Max(rectInfo.width, rectInfo.height); + numWorkGroupsAndMips.y = Math.Min(Mathf.FloorToInt(Mathf.Log(resolution, 2.0f)), 12); } } @@ -181,6 +374,10 @@ namespace FidelityFX public readonly uint dummy1; } + /// + /// The FSR2 C++ codebase uses floats bitwise converted to ints to pass sharpness parameters to the RCAS shader. + /// This is not possible in C# without enabling unsafe code compilation, so to avoid that we instead use a table of precomputed values. + /// private static readonly List RcasConfigs = new() { new(1048576000u, 872428544u),