274 lines
12 KiB
C#
274 lines
12 KiB
C#
using System.Collections.Generic;
|
|
// ReSharper disable InconsistentNaming
|
|
|
|
namespace UnityEngine.Rendering.Universal.PostProcessing {
|
|
/// <summary>
|
|
/// A render pass for executing custom post processing renderers.
|
|
/// </summary>
|
|
public class CompoundPass : ScriptableRenderPass {
|
|
/// <summary>
|
|
/// The injection point of the pass.
|
|
/// </summary>
|
|
private readonly InjectionPoint _injectionPoint;
|
|
|
|
/// <summary>
|
|
/// The pass name which will be displayed on the command buffer in the frame debugger.
|
|
/// </summary>
|
|
private readonly string m_PassName;
|
|
|
|
/// <summary>
|
|
/// List of all post process renderer instances.
|
|
/// </summary>
|
|
private readonly List<CompoundRenderer> m_PostProcessRenderers;
|
|
|
|
/// <summary>
|
|
/// List of all post process renderer instances that are active for the current camera.
|
|
/// </summary>
|
|
private readonly List<int> m_ActivePostProcessRenderers;
|
|
|
|
/// <summary>
|
|
/// Array of 2 intermediate render targets used to hold intermediate results.
|
|
/// </summary>
|
|
private readonly RTHandle[] m_Intermediate;
|
|
|
|
/// <summary>
|
|
/// Identifies whether the intermediate render targets are allocated or not.
|
|
/// </summary>
|
|
private readonly bool[] m_IntermediateAllocated;
|
|
|
|
/// <summary>
|
|
/// The texture descriptor for the intermediate render targets.
|
|
/// </summary>
|
|
private RenderTextureDescriptor _intermediateDescriptor;
|
|
|
|
/// <summary>
|
|
/// The source of the color data for the render pass
|
|
/// </summary>
|
|
private RTHandle m_Source;
|
|
|
|
/// <summary>
|
|
/// The destination of the color data for the render pass
|
|
/// </summary>
|
|
private RTHandle m_Destination;
|
|
|
|
/// <summary>
|
|
/// A list of profiling samplers, one for each post process renderer
|
|
/// </summary>
|
|
private readonly List<ProfilingSampler> m_ProfilingSamplers;
|
|
|
|
/// <summary>
|
|
/// Gets whether this render pass has any post process renderers to execute
|
|
/// </summary>
|
|
public bool HasPostProcessRenderers => m_PostProcessRenderers.Count != 0;
|
|
|
|
/// <summary>
|
|
/// Construct the custom post-processing render pass
|
|
/// </summary>
|
|
/// <param name="injectionPoint">The post processing injection point</param>
|
|
/// <param name="renderers">The list of classes for the renderers to be executed by this render pass</param>
|
|
public CompoundPass(InjectionPoint injectionPoint, List<CompoundRenderer> renderers) {
|
|
_injectionPoint = injectionPoint;
|
|
m_ProfilingSamplers = new List<ProfilingSampler>(renderers.Count);
|
|
m_PostProcessRenderers = renderers;
|
|
foreach (var renderer in renderers) {
|
|
// Get renderer name and add it to the names list
|
|
var attribute = CompoundRendererFeatureAttribute.GetAttribute(renderer.GetType());
|
|
m_ProfilingSamplers.Add(new ProfilingSampler(attribute?.Name));
|
|
}
|
|
|
|
// Pre-allocate a list for active renderers
|
|
this.m_ActivePostProcessRenderers = new List<int>(renderers.Count);
|
|
// Set render pass event and name based on the injection point.
|
|
switch (injectionPoint) {
|
|
case InjectionPoint.AfterOpaqueAndSky:
|
|
renderPassEvent = RenderPassEvent.AfterRenderingSkybox;
|
|
m_PassName = "[Dustyroom] PostProcess after Opaque & Sky";
|
|
break;
|
|
case InjectionPoint.BeforePostProcess:
|
|
renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
|
m_PassName = "[Dustyroom] PostProcess before PostProcess";
|
|
break;
|
|
case InjectionPoint.AfterPostProcess:
|
|
#if UNITY_2021_2_OR_NEWER
|
|
renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
|
|
#else
|
|
// NOTE: This was initially "AfterRenderingPostProcessing" but it made the builtin post-processing
|
|
// to blit directly to the camera target.
|
|
renderPassEvent = RenderPassEvent.AfterRendering;
|
|
#endif
|
|
m_PassName = "[Dustyroom] PostProcess after PostProcess";
|
|
break;
|
|
}
|
|
|
|
// Initialize the IDs and allocation state of the intermediate render targets
|
|
m_Intermediate = new RTHandle[2];
|
|
m_Intermediate[0] = RTHandles.Alloc("_IntermediateRT0", name: "_IntermediateRT0");
|
|
m_Intermediate[1] = RTHandles.Alloc("_IntermediateRT1", name: "_IntermediateRT1");
|
|
m_IntermediateAllocated = new bool[2];
|
|
m_IntermediateAllocated[0] = false;
|
|
m_IntermediateAllocated[1] = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the corresponding intermediate RT and allocates it if not already allocated
|
|
/// </summary>
|
|
/// <param name="cmd">The command buffer to use for allocation</param>
|
|
/// <param name="index">The intermediate RT index</param>
|
|
/// <returns></returns>
|
|
private RTHandle GetIntermediate(CommandBuffer cmd, int index) {
|
|
if (!m_IntermediateAllocated[index]) {
|
|
cmd.GetTemporaryRT(Shader.PropertyToID(m_Intermediate[index].name), _intermediateDescriptor);
|
|
m_IntermediateAllocated[index] = true;
|
|
}
|
|
|
|
return m_Intermediate[index];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Release allocated intermediate RTs
|
|
/// </summary>
|
|
/// <param name="cmd">The command buffer to use for deallocation</param>
|
|
private void CleanupIntermediate(CommandBuffer cmd) {
|
|
for (int index = 0; index < 2; ++index) {
|
|
if (m_IntermediateAllocated[index]) {
|
|
cmd.ReleaseTemporaryRT(Shader.PropertyToID(m_Intermediate[index].name));
|
|
m_IntermediateAllocated[index] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setup the source and destination render targets
|
|
/// </summary>
|
|
/// <param name="source">Source render target</param>
|
|
/// <param name="destination">Destination render target</param>
|
|
public void Setup(RTHandle source, RTHandle destination) {
|
|
m_Source = source;
|
|
m_Destination = destination;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepares the renderer for executing on this frame and checks if any of them actually requires rendering
|
|
/// </summary>
|
|
/// <returns>True if any renderer will be executed for the given camera. False Otherwise.</returns>
|
|
public bool PrepareRenderers(in RenderingData renderingData) {
|
|
if (renderingData.cameraData.cameraType == CameraType.Preview) return false;
|
|
|
|
// See if current camera is a scene view camera to skip renderers with "visibleInSceneView" = false.
|
|
bool isSceneView = renderingData.cameraData.cameraType == CameraType.SceneView;
|
|
|
|
// Here, we will collect the inputs needed by all the custom post processing effects.
|
|
ScriptableRenderPassInput passInput = ScriptableRenderPassInput.None;
|
|
|
|
// Collect the active renderers
|
|
m_ActivePostProcessRenderers.Clear();
|
|
for (int index = 0; index < m_PostProcessRenderers.Count; index++) {
|
|
var ppRenderer = m_PostProcessRenderers[index];
|
|
// Skips current renderer if "visibleInSceneView" = false and the current camera is a scene view camera.
|
|
if ((isSceneView && !ppRenderer.visibleInSceneView) || renderingData.cameraData.isPreviewCamera) continue;
|
|
// Setup the camera for the renderer and if it will render anything, add to active renderers and get
|
|
// its required inputs.
|
|
if (ppRenderer.Setup(in renderingData, _injectionPoint)) {
|
|
m_ActivePostProcessRenderers.Add(index);
|
|
passInput |= ppRenderer.input;
|
|
}
|
|
}
|
|
|
|
// Configure the pass to tell the renderer what inputs we need
|
|
ConfigureInput(passInput);
|
|
|
|
// return if no renderers are active
|
|
return m_ActivePostProcessRenderers.Count != 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute the custom post processing renderers.
|
|
/// </summary>
|
|
/// <param name="context">The scriptable render context</param>
|
|
/// <param name="renderingData">Current rendering data</param>
|
|
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) {
|
|
// Copy camera target description for intermediate RTs. Disable multisampling and depth buffer for the
|
|
// intermediate targets.
|
|
_intermediateDescriptor = renderingData.cameraData.cameraTargetDescriptor;
|
|
_intermediateDescriptor.msaaSamples = 1;
|
|
_intermediateDescriptor.depthBufferBits = 0;
|
|
|
|
CommandBuffer cmd = CommandBufferPool.Get(m_PassName);
|
|
|
|
context.ExecuteCommandBuffer(cmd);
|
|
cmd.Clear();
|
|
|
|
int width = _intermediateDescriptor.width;
|
|
int height = _intermediateDescriptor.height;
|
|
cmd.SetGlobalVector("_ScreenSize", new Vector4(width, height, 1.0f / width, 1.0f / height));
|
|
|
|
// The variable will be true if the last renderer couldn't blit to destination.
|
|
// This happens if there is only 1 renderer and the source is the same as the destination.
|
|
bool requireBlitBack = false;
|
|
// The current intermediate RT to use as a source.
|
|
int intermediateIndex = 0;
|
|
|
|
for (int index = 0; index < m_ActivePostProcessRenderers.Count; ++index) {
|
|
var rendererIndex = m_ActivePostProcessRenderers[index];
|
|
var renderer = m_PostProcessRenderers[rendererIndex];
|
|
|
|
RTHandle source, destination;
|
|
if (index == 0) {
|
|
// If this is the first renderers then the source will be the external source (not intermediate).
|
|
source = m_Source;
|
|
if (m_ActivePostProcessRenderers.Count == 1) {
|
|
#if UNITY_2023_1_OR_NEWER
|
|
destination = m_Destination;
|
|
#else
|
|
// There is only one renderer, check if the source is the same as the destination
|
|
if (m_Source == m_Destination) {
|
|
// Since we can't bind the same RT as a texture and a render target at the same time,
|
|
// we will blit to an intermediate RT.
|
|
destination = GetIntermediate(cmd, 0);
|
|
// Then we will blit back to the destination.
|
|
requireBlitBack = true;
|
|
} else {
|
|
// Otherwise, we can directly blit from source to destination.
|
|
destination = m_Destination;
|
|
}
|
|
#endif
|
|
} else {
|
|
// If there is more than one renderer, we will need to the intermediate RT anyway.
|
|
destination = GetIntermediate(cmd, intermediateIndex);
|
|
}
|
|
} else {
|
|
// If this is not the first renderer, we will want to the read from the intermediate RT.
|
|
source = GetIntermediate(cmd, intermediateIndex);
|
|
if (index == m_ActivePostProcessRenderers.Count - 1) {
|
|
// If this is the last renderer, blit to the destination directly.
|
|
destination = m_Destination;
|
|
} else {
|
|
// Otherwise, flip the intermediate RT index and set as destination.
|
|
// This will act as a ping pong process between the 2 RT where color data keeps moving back and forth while being processed on each pass.
|
|
intermediateIndex = 1 - intermediateIndex;
|
|
destination = GetIntermediate(cmd, intermediateIndex);
|
|
}
|
|
}
|
|
|
|
using (new ProfilingScope(cmd, m_ProfilingSamplers[rendererIndex])) {
|
|
if (!renderer.Initialized) renderer.InitializeInternal();
|
|
renderer.Render(cmd, source, destination, ref renderingData, _injectionPoint);
|
|
}
|
|
}
|
|
|
|
// If blit back is needed, blit from the intermediate RT to the destination (see above for explanation)
|
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
|
if (requireBlitBack) {
|
|
Blit(cmd, m_Intermediate[0], m_Destination);
|
|
}
|
|
|
|
// Release allocated Intermediate RTs.
|
|
CleanupIntermediate(cmd);
|
|
|
|
// Send command buffer for execution, then release it.
|
|
context.ExecuteCommandBuffer(cmd);
|
|
CommandBufferPool.Release(cmd);
|
|
}
|
|
}
|
|
}
|