using System; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Rendering; namespace FidelityFX { internal abstract class Fsr2Pipeline: IDisposable { private readonly Fsr2Callbacks _callbacks; protected readonly ComputeBuffer Constants; protected ComputeShader ComputeShader; protected int KernelIndex; protected Fsr2Pipeline(Fsr2Callbacks callbacks, ComputeBuffer constants) { _callbacks = callbacks; Constants = constants; } public virtual void Dispose() { UnloadComputeShader(); } public abstract void ScheduleDispatch(CommandBuffer commandBuffer, Fsr2.DispatchDescription dispatchParams, int dispatchX, int dispatchY); protected void LoadComputeShader(string name, Fsr2.InitializationFlags flags) { LoadComputeShader(name, flags, ref ComputeShader, out KernelIndex); } private void LoadComputeShader(string name, Fsr2.InitializationFlags flags, ref ComputeShader shaderRef, out int kernelIndex) { if (shaderRef == null) shaderRef = _callbacks.LoadComputeShader(name); kernelIndex = shaderRef.FindKernel("CS"); 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) // TODO: enable FFX_HALF if FP16 is supported (except RCAS) } private void UnloadComputeShader() { UnloadComputeShader(ref ComputeShader); } private void UnloadComputeShader(ref ComputeShader shaderRef) { if (shaderRef == null) return; _callbacks.UnloadComputeShader(shaderRef); shaderRef = null; } } internal class Fsr2ComputeLuminancePyramidPipeline : Fsr2Pipeline { public Fsr2ComputeLuminancePyramidPipeline(Fsr2Callbacks callbacks, Fsr2.InitializationFlags flags, ComputeBuffer constants) : base(callbacks, constants) { LoadComputeShader("FSR2/ffx_fsr2_compute_luminance_pyramid_pass", flags); } public override void ScheduleDispatch(CommandBuffer commandBuffer, Fsr2.DispatchDescription dispatchParams, int dispatchX, int dispatchY) { //throw new NotImplementedException(); } } internal class Fsr2AccumulatePipeline : Fsr2Pipeline { public Fsr2AccumulatePipeline(Fsr2Callbacks callbacks, Fsr2.InitializationFlags flags, ComputeBuffer constants) : base(callbacks, constants) { LoadComputeShader("FSR2/ffx_fsr2_accumulate_pass", flags); } public override void ScheduleDispatch(CommandBuffer commandBuffer, Fsr2.DispatchDescription dispatchParams, int dispatchX, int dispatchY) { //throw new NotImplementedException(); } } internal class Fsr2AccumulateSharpenPipeline : Fsr2AccumulatePipeline { private readonly ComputeShader _shaderCopy; public Fsr2AccumulateSharpenPipeline(Fsr2Callbacks callbacks, Fsr2.InitializationFlags flags, ComputeBuffer constants) : base(callbacks, flags, constants) { // Simply loading the accumulate_pass compute shader will give us the same instance as the non-sharpen pipeline // So we have to clone the shader instance and set the extra keyword on the new copy _shaderCopy = UnityEngine.Object.Instantiate(ComputeShader); foreach (var keyword in ComputeShader.enabledKeywords) { _shaderCopy.EnableKeyword(keyword.name); } _shaderCopy.EnableKeyword("FFX_FSR2_OPTION_APPLY_SHARPENING"); } public override void ScheduleDispatch(CommandBuffer commandBuffer, Fsr2.DispatchDescription dispatchParams, int dispatchX, int dispatchY) { // Temporarily swap around the shaders so that the dispatch will bind and execute the correct one ComputeShader tmp = ComputeShader; ComputeShader = _shaderCopy; base.ScheduleDispatch(commandBuffer, dispatchParams, dispatchX, dispatchY); ComputeShader = tmp; } public override void Dispose() { // Since we instantiated this copy, we have to destroy it instead of unloading the shader resource UnityEngine.Object.Destroy(_shaderCopy); base.Dispose(); } } internal class Fsr2RcasPipeline : Fsr2Pipeline { private readonly ComputeBuffer _rcasConstants; public Fsr2RcasPipeline(Fsr2Callbacks callbacks, Fsr2.InitializationFlags flags, ComputeBuffer constants, ComputeBuffer rcasConstants) : base(callbacks, constants) { _rcasConstants = rcasConstants; LoadComputeShader("FSR2/ffx_fsr2_rcas_pass", flags); } public override void ScheduleDispatch(CommandBuffer commandBuffer, Fsr2.DispatchDescription dispatchParams, int dispatchX, int dispatchY) { // Run the RCAS sharpening filter on the upscaled image ComputeShader.SetTexture(KernelIndex, "r_input_exposure", dispatchParams.Exposure); ComputeShader.SetTexture(KernelIndex, "r_rcas_input", dispatchParams.Input); ComputeShader.SetTexture(KernelIndex, "rw_upscaled_output", dispatchParams.Output); ComputeShader.SetConstantBuffer("cbFSR2", Constants, 0, Marshal.SizeOf()); ComputeShader.SetConstantBuffer("cbRCAS", _rcasConstants, 0, Marshal.SizeOf()); commandBuffer.DispatchCompute(ComputeShader, KernelIndex, dispatchX, dispatchY, 1); } } }