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.

357 lines
18 KiB

// Copyright (c) 2024 Nico de Poel
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Rendering;
namespace ArmASR
{
/// <summary>
/// Base class for all the compute passes that make up the ASR process.
/// This loosely matches the FfxPipelineState struct from the original ASR codebase, wrapped in an object-oriented blanket.
/// These classes are responsible for loading compute shaders, managing temporary resources, binding resources to shader kernels and dispatching said shaders.
/// </summary>
internal abstract class AsrPass: IDisposable
{
internal const int ShadingChangeMipLevel = 4; // This matches the FFXM_FSR2_SHADING_CHANGE_MIP_LEVEL define
protected readonly Asr.ContextDescription ContextDescription;
protected readonly AsrResources Resources;
protected readonly ComputeBuffer Constants;
protected ComputeShader ComputeShader;
protected int KernelIndex;
protected Material FragmentMaterial;
protected int FragmentPass;
protected MaterialPropertyBlock FragmentProperties;
private CustomSampler _sampler;
protected AsrPass(Asr.ContextDescription contextDescription, AsrResources resources, ComputeBuffer constants)
{
ContextDescription = contextDescription;
Resources = resources;
Constants = constants;
}
public virtual void Dispose()
{
if (FragmentMaterial != null)
{
Asr.DestroyObject(FragmentMaterial);
FragmentMaterial = null;
}
}
public void ScheduleDispatch(CommandBuffer commandBuffer, Asr.DispatchDescription dispatchParams, int frameIndex, int dispatchX = 0, int dispatchY = 0)
{
BeginSample(commandBuffer);
DoScheduleDispatch(commandBuffer, dispatchParams, frameIndex, dispatchX, dispatchY);
EndSample(commandBuffer);
}
protected abstract void DoScheduleDispatch(CommandBuffer commandBuffer, Asr.DispatchDescription dispatchParams, int frameIndex, int dispatchX, int dispatchY);
protected void InitComputeShader(string passName, ComputeShader shader)
{
if (shader == null)
{
throw new MissingReferenceException($"Shader for ASR pass '{passName}' could not be loaded! Please ensure it is included in the project correctly.");
}
ComputeShader = shader;
KernelIndex = ComputeShader.FindKernel("main");
_sampler = CustomSampler.Create(passName);
}
protected void InitFragmentShader(string passName, Shader shader, int passNumber)
{
if (shader == null)
{
throw new MissingReferenceException($"Shader for ASR pass '{passName}' could not be loaded! Please ensure it is included in the project correctly.");
}
FragmentMaterial = new Material(shader);
FragmentPass = passNumber;
FragmentProperties = new MaterialPropertyBlock();
_sampler = CustomSampler.Create(passName);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void BlitFragment(CommandBuffer commandBuffer, RenderTargetIdentifier renderTarget)
{
commandBuffer.SetRenderTarget(renderTarget);
BlitFragment(commandBuffer);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void BlitFragment(CommandBuffer commandBuffer, RenderTargetIdentifier[] renderTargets)
{
commandBuffer.SetRenderTarget(renderTargets, BuiltinRenderTextureType.None);
BlitFragment(commandBuffer);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BlitFragment(CommandBuffer commandBuffer)
{
commandBuffer.DrawProcedural(Matrix4x4.identity, FragmentMaterial, FragmentPass, MeshTopology.Triangles, 3, 1, FragmentProperties);
}
[Conditional("ENABLE_PROFILER"), MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void BeginSample(CommandBuffer cmd)
{
cmd.BeginSample(_sampler);
}
[Conditional("ENABLE_PROFILER"), MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void EndSample(CommandBuffer cmd)
{
cmd.EndSample(_sampler);
}
}
internal class AsrComputeLuminancePyramidPass : AsrPass
{
private readonly ComputeBuffer _spdConstants;
public AsrComputeLuminancePyramidPass(Asr.ContextDescription contextDescription, AsrResources resources, ComputeBuffer constants, ComputeBuffer spdConstants)
: base(contextDescription, resources, constants)
{
_spdConstants = spdConstants;
InitComputeShader("Compute Luminance Pyramid", contextDescription.Shaders.computeLuminancePyramidPass);
}
protected override void DoScheduleDispatch(CommandBuffer commandBuffer, Asr.DispatchDescription dispatchParams, int frameIndex, int dispatchX, int dispatchY)
{
commandBuffer.SetComputeResourceParam(ComputeShader, KernelIndex, AsrShaderIDs.SrvInputColor, dispatchParams.Color);
commandBuffer.SetComputeTextureParam(ComputeShader, KernelIndex, AsrShaderIDs.UavSpdAtomicCount, Resources.SpdAtomicCounter);
commandBuffer.SetComputeTextureParam(ComputeShader, KernelIndex, AsrShaderIDs.UavExposureMipLumaChange, Resources.SceneLuminance, ShadingChangeMipLevel);
commandBuffer.SetComputeTextureParam(ComputeShader, KernelIndex, AsrShaderIDs.UavExposureMip5, Resources.SceneLuminance, 5);
commandBuffer.SetComputeTextureParam(ComputeShader, KernelIndex, AsrShaderIDs.UavAutoExposure, Resources.AutoExposure);
commandBuffer.SetComputeConstantBufferParam(ComputeShader, AsrShaderIDs.CbFsr2, Constants, 0, Constants.stride);
commandBuffer.SetComputeConstantBufferParam(ComputeShader, AsrShaderIDs.CbSpd, _spdConstants, 0, _spdConstants.stride);
commandBuffer.DispatchCompute(ComputeShader, KernelIndex, dispatchX, dispatchY, 1);
}
}
internal class AsrReconstructPreviousDepthPass : AsrPass
{
private readonly RenderTargetIdentifier[] _mrt = new RenderTargetIdentifier[3];
public AsrReconstructPreviousDepthPass(Asr.ContextDescription contextDescription, AsrResources resources, ComputeBuffer constants)
: base(contextDescription, resources, constants)
{
InitFragmentShader("Reconstruct & Dilate", contextDescription.Shaders.fragmentShader, 1);
}
protected override void DoScheduleDispatch(CommandBuffer commandBuffer, Asr.DispatchDescription dispatchParams, int frameIndex, int dispatchX, int dispatchY)
{
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputColor, dispatchParams.Color);
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputDepth, dispatchParams.Depth);
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputMotionVectors, dispatchParams.MotionVectors);
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputExposure, dispatchParams.Exposure);
// UAV binding in fragment shader, index needs to match the register binding in HLSL
commandBuffer.SetRandomWriteTarget(3, AsrShaderIDs.UavReconstructedPrevNearestDepth);
_mrt[0] = AsrShaderIDs.RtDilatedDepth; // fDepth
_mrt[1] = Resources.DilatedMotionVectors[frameIndex]; // fMotionVector
_mrt[2] = AsrShaderIDs.RtLockInputLuma; // fLuma
FragmentProperties.SetConstantBuffer(AsrShaderIDs.CbFsr2, Constants, 0, Constants.stride);
BlitFragment(commandBuffer, _mrt);
commandBuffer.ClearRandomWriteTargets();
}
}
internal class AsrDepthClipPass : AsrPass
{
private readonly RenderTargetIdentifier[] _mrt = new RenderTargetIdentifier[2];
public AsrDepthClipPass(Asr.ContextDescription contextDescription, AsrResources resources, ComputeBuffer constants)
: base(contextDescription, resources, constants)
{
InitFragmentShader("Depth Clip", contextDescription.Shaders.fragmentShader, 2);
}
protected override void DoScheduleDispatch(CommandBuffer commandBuffer, Asr.DispatchDescription dispatchParams, int frameIndex, int dispatchX, int dispatchY)
{
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputColor, dispatchParams.Color);
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputDepth, dispatchParams.Depth);
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputMotionVectors, dispatchParams.MotionVectors);
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputExposure, dispatchParams.Exposure);
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvReactiveMask, dispatchParams.Reactive);
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvTransparencyAndCompositionMask, dispatchParams.TransparencyAndComposition);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvReconstructedPrevNearestDepth, AsrShaderIDs.UavReconstructedPrevNearestDepth);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvDilatedMotionVectors, Resources.DilatedMotionVectors[frameIndex]);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvDilatedDepth, AsrShaderIDs.UavDilatedDepth);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvPrevDilatedMotionVectors, Resources.DilatedMotionVectors[frameIndex ^ 1]);
_mrt[0] = AsrShaderIDs.RtDilatedReactiveMasks; // fDilatedReactiveMasks
_mrt[1] = AsrShaderIDs.RtPreparedInputColor; // fTonemapped
FragmentProperties.SetConstantBuffer(AsrShaderIDs.CbFsr2, Constants, 0, Constants.stride);
BlitFragment(commandBuffer, _mrt);
}
}
internal class AsrLockPass : AsrPass
{
public AsrLockPass(Asr.ContextDescription contextDescription, AsrResources resources, ComputeBuffer constants)
: base(contextDescription, resources, constants)
{
InitComputeShader("Create Locks", contextDescription.Shaders.lockPass);
}
protected override void DoScheduleDispatch(CommandBuffer commandBuffer, Asr.DispatchDescription dispatchParams, int frameIndex, int dispatchX, int dispatchY)
{
commandBuffer.SetComputeTextureParam(ComputeShader, KernelIndex, AsrShaderIDs.SrvLockInputLuma, AsrShaderIDs.UavLockInputLuma);
commandBuffer.SetComputeTextureParam(ComputeShader, KernelIndex, AsrShaderIDs.UavNewLocks, AsrShaderIDs.UavNewLocks);
commandBuffer.SetComputeTextureParam(ComputeShader, KernelIndex, AsrShaderIDs.UavReconstructedPrevNearestDepth, AsrShaderIDs.UavReconstructedPrevNearestDepth);
commandBuffer.SetComputeConstantBufferParam(ComputeShader, AsrShaderIDs.CbFsr2, Constants, 0, Constants.stride);
commandBuffer.DispatchCompute(ComputeShader, KernelIndex, dispatchX, dispatchY, 1);
}
}
internal class AsrAccumulatePass : AsrPass
{
private readonly RenderTargetIdentifier[] _mrt = new RenderTargetIdentifier[4];
public AsrAccumulatePass(Asr.ContextDescription contextDescription, AsrResources resources, ComputeBuffer constants)
: base(contextDescription, resources, constants)
{
InitFragmentShader("Reproject & Accumulate", contextDescription.Shaders.fragmentShader, 3);
}
protected override void DoScheduleDispatch(CommandBuffer commandBuffer, Asr.DispatchDescription dispatchParams, int frameIndex, int dispatchX, int dispatchY)
{
if ((ContextDescription.Flags & Asr.InitializationFlags.EnableDisplayResolutionMotionVectors) == 0)
{
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvDilatedMotionVectors, Resources.DilatedMotionVectors[frameIndex]);
}
else
{
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputMotionVectors, dispatchParams.MotionVectors);
}
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputExposure, dispatchParams.Exposure);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvDilatedReactiveMasks, AsrShaderIDs.UavDilatedReactiveMasks);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvInternalUpscaled, Resources.InternalUpscaled[frameIndex ^ 1]);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvLockStatus, Resources.LockStatus[frameIndex ^ 1]);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvPreparedInputColor, AsrShaderIDs.UavPreparedInputColor);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvLanczosLut, Resources.LanczosLut);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvUpscaleMaximumBiasLut, Resources.MaximumBiasLut);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvSceneLuminanceMips, Resources.SceneLuminance);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvAutoExposure, Resources.AutoExposure);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvLumaHistory, Resources.LumaHistory[frameIndex ^ 1]);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvInternalTemporalReactive, Resources.InternalReactive[frameIndex ^ 1]);
// UAV binding in fragment shader, index needs to match the register binding in HLSL
commandBuffer.SetRandomWriteTarget(4, AsrShaderIDs.UavNewLocks);
if (ContextDescription.Variant == Asr.Variant.Quality)
{
_mrt[0] = Resources.InternalUpscaled[frameIndex]; // fColorAndWeight
_mrt[1] = Resources.LockStatus[frameIndex]; // fLockStatus
_mrt[2] = Resources.LumaHistory[frameIndex]; // fLumaHistory
}
else
{
_mrt[0] = Resources.InternalUpscaled[frameIndex]; // fUpscaledColor
_mrt[1] = Resources.InternalReactive[frameIndex]; // fTemporalReactive
_mrt[2] = Resources.LockStatus[frameIndex]; // fLockStatus
}
_mrt[3] = dispatchParams.EnableSharpening ? BuiltinRenderTextureType.None : dispatchParams.Output.RenderTarget; // fColor
FragmentProperties.SetConstantBuffer(AsrShaderIDs.CbFsr2, Constants, 0, Constants.stride);
BlitFragment(commandBuffer, _mrt);
commandBuffer.ClearRandomWriteTargets();
}
}
internal class AsrSharpenPass : AsrPass
{
private readonly ComputeBuffer _rcasConstants;
public AsrSharpenPass(Asr.ContextDescription contextDescription, AsrResources resources, ComputeBuffer constants, ComputeBuffer rcasConstants)
: base(contextDescription, resources, constants)
{
_rcasConstants = rcasConstants;
InitFragmentShader("RCAS Sharpening", contextDescription.Shaders.fragmentShader, 4);
}
protected override void DoScheduleDispatch(CommandBuffer commandBuffer, Asr.DispatchDescription dispatchParams, int frameIndex, int dispatchX, int dispatchY)
{
commandBuffer.SetGlobalResource(AsrShaderIDs.SrvInputExposure, dispatchParams.Exposure);
commandBuffer.SetGlobalTexture(AsrShaderIDs.SrvRcasInput, Resources.InternalUpscaled[frameIndex]);
FragmentProperties.SetConstantBuffer(AsrShaderIDs.CbFsr2, Constants, 0, Constants.stride);
FragmentProperties.SetConstantBuffer(AsrShaderIDs.CbRcas, _rcasConstants, 0, _rcasConstants.stride);
BlitFragment(commandBuffer, dispatchParams.Output.RenderTarget);
}
}
internal class AsrGenerateReactivePass : AsrPass
{
private readonly ComputeBuffer _generateReactiveConstants;
public AsrGenerateReactivePass(Asr.ContextDescription contextDescription, AsrResources resources, ComputeBuffer generateReactiveConstants)
: base(contextDescription, resources, null)
{
_generateReactiveConstants = generateReactiveConstants;
InitFragmentShader("Auto-Generate Reactive Mask", contextDescription.Shaders.fragmentShader, 0);
}
protected override void DoScheduleDispatch(CommandBuffer commandBuffer, Asr.DispatchDescription dispatchParams, int frameIndex, int dispatchX, int dispatchY)
{
}
public void ScheduleDispatch(CommandBuffer commandBuffer, in Asr.GenerateReactiveDescription dispatchParams)
{
BeginSample(commandBuffer);
commandBuffer.SetComputeResourceParam(ComputeShader, KernelIndex, AsrShaderIDs.SrvOpaqueOnly, dispatchParams.ColorOpaqueOnly);
commandBuffer.SetComputeResourceParam(ComputeShader, KernelIndex, AsrShaderIDs.SrvInputColor, dispatchParams.ColorPreUpscale);
FragmentProperties.SetConstantBuffer(AsrShaderIDs.CbFsr2, Constants, 0, Constants.stride);
FragmentProperties.SetConstantBuffer(AsrShaderIDs.CbGenReactive, _generateReactiveConstants, 0, _generateReactiveConstants.stride);
BlitFragment(commandBuffer, dispatchParams.OutReactive.RenderTarget);
EndSample(commandBuffer);
}
}
}