|
|
|
@ -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<RcasConstants>(); |
|
|
|
|
|
|
|
// 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<Fsr2Constants>()); |
|
|
|
_rcasShader.SetConstantBuffer("cbRCAS", _rcasConstantsBuffer, 0, Marshal.SizeOf<RcasConstants>()); |
|
|
|
|
|
|
|
@ -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; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 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.
|
|
|
|
/// </summary>
|
|
|
|
private static readonly List<RcasConstants> RcasConfigs = new() |
|
|
|
{ |
|
|
|
new(1048576000u, 872428544u), |
|
|
|
|