Development repository for FSR2 integration into Unity Post-Processing Stack V2.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
7.6 KiB

using System;
using System.Runtime.InteropServices;
using FidelityFX;
using FidelityFX.FSR2;
using UnityEngine.Experimental.Rendering;
namespace UnityEngine.Rendering.PostProcessing
{
internal abstract class Upscaler
{
public abstract void CreateContext(PostProcessRenderContext context, Upscaling config);
public virtual void DestroyContext()
{
DestroyRenderTexture(ref _reactiveMask);
DestroyConstantsBuffer(ref _reactiveMaskConstants);
DestroyConstantsBuffer(ref _sharpeningConstants);
}
public abstract void Render(PostProcessRenderContext context, Upscaling config);
private ConstantsBuffer<GenerateReactiveConstants> _reactiveMaskConstants;
private RenderTexture _reactiveMask;
/// <summary>
/// Generalized standalone version of the FSR2 reactive mask auto-generating pass that can be used without needing an active FSR2 context.
/// This allows auto-generated reactive masks to be reused for other non-FSR upscaling techniques.
/// </summary>
protected Texture GenerateReactiveMask(CommandBuffer cmd, PostProcessRenderContext context, Upscaling config, GraphicsFormat format = GraphicsFormat.R8_UNorm)
{
ComputeShader shader = context.resources.computeShaders.fsr2Upscaler?.autoGenReactivePass;
if (shader == null)
return Texture2D.blackTexture;
_reactiveMaskConstants ??= ConstantsBuffer<GenerateReactiveConstants>.Create();
if (_reactiveMask == null)
{
// Use a persistent RT so it can easily be passed to native render plugins
CreateRenderTexture(ref _reactiveMask, "Reactive Mask", config.MaxRenderSize, format, true);
}
Vector2Int scaledRenderSize = config.GetScaledRenderSize(context.camera);
const int threadGroupWorkRegionDim = 8;
int dispatchX = (scaledRenderSize.x + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
int dispatchY = (scaledRenderSize.y + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
cmd.BeginSample("Generate Reactive Mask");
_reactiveMaskConstants.Value.scale = config.generateReactiveParameters.scale;
_reactiveMaskConstants.Value.threshold = config.generateReactiveParameters.cutoffThreshold;
_reactiveMaskConstants.Value.binaryValue = config.generateReactiveParameters.binaryValue;
_reactiveMaskConstants.Value.flags = (uint)config.generateReactiveParameters.flags;
_reactiveMaskConstants.UpdateBufferData(cmd);
int kernelIndex = shader.FindKernel("CS");
cmd.SetComputeTextureParam(shader, kernelIndex, Fsr2ShaderIDs.SrvOpaqueOnly, config.ColorOpaqueOnly);
cmd.SetComputeTextureParam(shader, kernelIndex, Fsr2ShaderIDs.SrvInputColor, context.source);
cmd.SetComputeTextureParam(shader, kernelIndex, Fsr2ShaderIDs.UavAutoReactive, _reactiveMask);
cmd.SetComputeConstantBufferParam(shader, Fsr2ShaderIDs.CbGenReactive, _reactiveMaskConstants, 0, Marshal.SizeOf<GenerateReactiveConstants>());
cmd.DispatchCompute(shader, kernelIndex, dispatchX, dispatchY, 1);
cmd.EndSample("Generate Reactive Mask");
return _reactiveMask;
}
[Serializable, StructLayout(LayoutKind.Sequential)]
internal struct GenerateReactiveConstants
{
public float scale;
public float threshold;
public float binaryValue;
public uint flags;
}
private ConstantsBuffer<CasConstants> _sharpeningConstants;
private static readonly int CasInputColor = Shader.PropertyToID("r_input_color");
private static readonly int CasOutputColor = Shader.PropertyToID("rw_output_color");
private static readonly int CasConstantBuffer = Shader.PropertyToID("cbCAS");
/// <summary>
/// Generalized standalone version of the CAS sharpening filter that can be applied after any non-FSR upscaling technique.
/// </summary>
protected void ApplySharpening(CommandBuffer cmd, PostProcessRenderContext context, in Vector2Int imageSize, float sharpness, RenderTargetIdentifier input, RenderTargetIdentifier output)
{
ComputeShader shader = context.resources.computeShaders.casSharpening;
if (shader == null)
{
cmd.CopyTexture(input, output);
return;
}
const int threadGroupWorkRegionDimRcas = 16;
int threadGroupsX = (imageSize.x + threadGroupWorkRegionDimRcas - 1) / threadGroupWorkRegionDimRcas;
int threadGroupsY = (imageSize.y + threadGroupWorkRegionDimRcas - 1) / threadGroupWorkRegionDimRcas;
cmd.BeginSample("CAS Sharpening");
// Compute the constants
_sharpeningConstants ??= ConstantsBuffer<CasConstants>.Create();
int sharpnessIndex = Mathf.RoundToInt(Mathf.Clamp01(sharpness) * (CasConfigs.Length - 1));
_sharpeningConstants.Value = CasConfigs[sharpnessIndex];
_sharpeningConstants.UpdateBufferData(cmd);
// Dispatch CAS
int kernelIndex = shader.FindKernel("CS");
cmd.SetComputeTextureParam(shader, kernelIndex, CasInputColor, input);
cmd.SetComputeTextureParam(shader, kernelIndex, CasOutputColor, output);
cmd.SetComputeConstantBufferParam(shader, CasConstantBuffer, _sharpeningConstants, 0, Marshal.SizeOf<CasConstants>());
cmd.DispatchCompute(shader, kernelIndex, threadGroupsX, threadGroupsY, 1);
cmd.EndSample("CAS Sharpening");
}
[Serializable, StructLayout(LayoutKind.Sequential)]
private struct CasConstants
{
public CasConstants(uint sharpness, uint halfSharp)
{
// Since we don't use CAS for scaling, most of these values end up being constants
scaling0 = scaling1 = 1065353216;
scaling2 = scaling3 = 0;
sharpness0 = sharpness;
sharpness1 = halfSharp;
sharpness2 = 1090519040;
sharpness3 = 0;
}
public readonly uint scaling0;
public readonly uint scaling1;
public readonly uint scaling2;
public readonly uint scaling3;
public readonly uint sharpness0;
public readonly uint sharpness1;
public readonly uint sharpness2;
public readonly uint sharpness3;
}
/// <summary>
/// The FidelityFX C++ codebase uses floats bitwise converted to ints to pass sharpness parameters to the CAS 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 CasConstants[] CasConfigs =
{
new(3187671040u, 45056u),
new(3187831332u, 45075u),
new(3187997869u, 45095u),
new(3188171023u, 45117u),
new(3188351197u, 45139u),
new(3188538827u, 45161u),
new(3188734385u, 45185u),
new(3188938384u, 45210u),
new(3189151382u, 45236u),
new(3189373991u, 45263u),
new(3189606873u, 45292u),
new(3189850757u, 45322u),
new(3190106443u, 45353u),
new(3190374807u, 45386u),
new(3190656816u, 45420u),
new(3190953540u, 45456u),
new(3191266159u, 45494u),
new(3191595985u, 45535u),
new(3191944482u, 45577u),
new(3192313280u, 45622u),
new(3192704205u, 45670u),
};
protected bool CreateRenderTexture(ref RenderTexture rt, string name, in Vector2Int size, GraphicsFormat format, bool enableRandomWrite = false)
{
rt = new RenderTexture(size.x, size.y, 0, format) { name = name, enableRandomWrite = enableRandomWrite };
return rt.Create();
}
protected bool CreateRenderTexture(ref RenderTexture rt, string name, in Vector2Int size, RenderTextureFormat format, bool enableRandomWrite = false)
{
rt = new RenderTexture(size.x, size.y, 0, format) { name = name, enableRandomWrite = enableRandomWrite };
return rt.Create();
}
protected void DestroyRenderTexture(ref RenderTexture rt)
{
if (rt == null)
return;
rt.Release();
rt = null;
}
protected void DestroyConstantsBuffer<TConst>(ref ConstantsBuffer<TConst> cb)
where TConst: struct
{
if (cb == null)
return;
cb.Destroy();
cb = null;
}
}
}