using System;
using System.Diagnostics;
using System.Collections.Generic;
using Unity.Collections;
using UnityEditor;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
namespace UnityEngine.Rendering.Universal
{
///
/// Class ScriptableRenderer implements a rendering strategy. It describes how culling and lighting work and
/// the effects supported. A custom scriptable renderer is the lowest level of extensibility of URP. It allows you
/// to implement a fully new rendering strategy at the expense of a lot more complexity and work. However, It's still
/// a lot less work and more maintainable than writing a full-fledged custom render pipeline.
/// If you want to simply extend the existing URP renderers (2D and 3D), using ScriptableRendererFeature should
/// always be considered first.
///
///
/// A renderer can be used for all cameras or be overridden on a per-camera basis. It will implement light culling and setup
/// and describe a list of ScriptableRenderPass to execute in a frame. It will also define the RenderGraph to execute.
/// External users can then again extend your scriptable renderer to support more effects with additional ScriptableRendererFeatures.
///
/// The ScriptableRenderer is a run-time object. The resources and asset data for the renderer are serialized in
/// ScriptableRendererData (more specifically a class derived from ScriptableRendererData which contains additional data for your renderer).
///
/// The high-level steps needed to create and use your own scriptable renderer are:
///
/// 1. Create subclasses of ScriptableRenderer and ScriptableRendererData and implement the rendering logic. Key functions to implement here are:
/// ScriptableRenderer.OnRecordRenderGraph which will define the rendergraph to execute when rendering a camera. And ScriptableRendererData.Create to create
/// an instance of your new ScriptableRenderer subclass.
/// 2. Create an asset of your new ScriptableRendererData subclass and assign it to the renderer asset field in the URP asset so it gets picked
/// up at run time.
///
///
/// You can find a code sample in the URP tests package in the "Graphics/Tests/SRPTests/Packages/com.unity.testing.urp/Scripts/Runtime/CustomRenderPipeline/" folder
/// of the SRP repository.
///
public abstract partial class ScriptableRenderer : IDisposable
{
private static class Profiling
{
private const string k_Name = nameof(ScriptableRenderer);
public static readonly ProfilingSampler setPerCameraShaderVariables = new ProfilingSampler($"{k_Name}.{nameof(SetPerCameraShaderVariables)}");
public static readonly ProfilingSampler sortRenderPasses = new ProfilingSampler($"Sort Render Passes");
public static readonly ProfilingSampler recordRenderGraph = new ProfilingSampler($"On Record Render Graph");
public static readonly ProfilingSampler setupCamera = new ProfilingSampler($"Setup Camera Properties");
public static readonly ProfilingSampler vfxProcessCamera = new ProfilingSampler($"VFX Process Camera");
public static readonly ProfilingSampler addRenderPasses = new ProfilingSampler($"{k_Name}.{nameof(AddRenderPasses)}");
public static readonly ProfilingSampler clearRenderingState = new ProfilingSampler($"{k_Name}.{nameof(ClearRenderingState)}");
public static readonly ProfilingSampler internalFinishRenderingCommon = new ProfilingSampler($"{k_Name}.{nameof(InternalFinishRenderingCommon)}");
public static readonly ProfilingSampler drawGizmos = new ProfilingSampler("DrawGizmos"); //Todo: update to nameof(method reference) once RG version name is cleaned up
public static readonly ProfilingSampler drawWireOverlay = new ProfilingSampler("DrawWireOverlay"); //Todo: update to nameof(method reference) once RG version name is cleaned up
internal static readonly ProfilingSampler beginXRRendering = new ProfilingSampler($"Begin XR Rendering");
internal static readonly ProfilingSampler endXRRendering = new ProfilingSampler($"End XR Rendering");
internal static readonly ProfilingSampler initRenderGraphFrame = new ProfilingSampler($"Initialize Frame");
internal static readonly ProfilingSampler setEditorTarget = new ProfilingSampler($"Set Editor Target");
}
///
/// This setting controls if the camera editor should display the camera stack category.
/// If your scriptable renderer is not supporting stacking this one should return 0.
/// For the UI to show the Camera Stack widget this must at least support CameraRenderType.Base.
///
///
/// The bitmask of the supported camera render types in the renderer's current state.
public virtual int SupportedCameraStackingTypes()
{
return 0;
}
///
/// Check if the given camera render type is supported in the renderer's current state. The default implementation
/// simply checks if the camera type is part of the bitmask.
///
///
/// The camera render type that is checked if supported.
/// True if the given camera render type is supported in the renderer's current state.
public bool SupportsCameraStackingType(CameraRenderType cameraRenderType)
{
return (SupportedCameraStackingTypes() & 1 << (int)cameraRenderType) != 0;
}
// NOTE: This is a temporary solution until ScriptableRenderer has a system for partially shared features.
// TAA (and similar) affect the whole pipe. The code is split into two parts in terms of ownership.
// The ScriptableRenderer "shared" code (Camera) and the ScriptableRenderer "specific" code (the ScriptableRenderPasses).
// For example: TAA is enabled and configured from the Camera, which is used by any ScriptableRenderer.
// TAA also jitters the Camera matrix for all ScriptableRenderers.
// However a Renderer might not implement a motion vector pass, which the TAA needs to function correctly.
//
///
/// Check if the ScriptableRenderer implements a motion vector pass for temporal techniques.
/// The Camera will check this to enable/disable features and/or apply jitter when required.
///
/// For example, Temporal Anti-aliasing in the Camera settings is enabled only if the ScriptableRenderer can support motion vectors.
///
/// Returns true if the ScriptableRenderer implements a motion vector pass. False otherwise.
protected internal virtual bool SupportsMotionVectors()
{
return false;
}
///
/// Check if the ScriptableRenderer implements a camera opaque pass.
///
/// Returns true if the ScriptableRenderer implements a camera opaque pass. False otherwise.
protected internal virtual bool SupportsCameraOpaque()
{
return false;
}
///
/// Check if the ScriptableRenderer implements a camera normal pass.
///
/// Returns true if the ScriptableRenderer implements a camera normal pass. False otherwise.
protected internal virtual bool SupportsCameraNormals()
{
return false;
}
///
/// Configures the supported features for this renderer. When creating custom renderers
/// for Universal Render Pipeline you can choose to opt-in or out for specific features.
///
public class RenderingFeatures
{
///
/// This setting controls if the camera editor should display the camera stack category.
/// Renderers that don't support camera stacking will only render cameras of type CameraRenderType.Base
///
///
///
[Obsolete("cameraStacking has been deprecated use SupportedCameraRenderTypes() in ScriptableRenderer instead. #from(2022.2) #breakingFrom(2023.1)", true)]
public bool cameraStacking { get; set; } = false;
///
/// This setting controls if the Universal Render Pipeline asset should expose the MSAA option.
///
public bool msaa { get; set; } = true;
}
///
/// The class responsible for providing access to debug view settings to renderers and render passes.
///
internal DebugHandler DebugHandler { get; }
///
/// The renderer we are currently rendering with, for low-level render control only.
/// current is null outside rendering scope.
/// Similar to https://docs.unity3d.com/ScriptReference/Camera-current.html
///
internal static ScriptableRenderer current = null;
internal static void SetCameraMatrices(RasterCommandBuffer cmd, UniversalCameraData cameraData, bool setInverseMatrices, bool isTargetFlipped)
{
#if ENABLE_VR && ENABLE_XR_MODULE
if (cameraData.xr.enabled)
{
cameraData.PushBuiltinShaderConstantsXR(cmd, isTargetFlipped);
XRSystemUniversal.MarkShaderProperties(cmd, cameraData.xrUniversal, isTargetFlipped);
return;
}
#endif
// NOTE: the URP default main view/projection matrices are the CameraData view/projection matrices.
Matrix4x4 viewMatrix = cameraData.GetViewMatrix();
Matrix4x4 projectionMatrix = cameraData.GetProjectionMatrix(); // Jittered, non-gpu
// TODO: Investigate why SetViewAndProjectionMatrices is causing y-flip / winding order issue
// for now using cmd.SetViewProjecionMatrices
//SetViewAndProjectionMatrices(cmd, viewMatrix, cameraData.GetDeviceProjectionMatrix(), setInverseMatrices);
// Set the default view/projection, note: projectionMatrix will be set as a gpu-projection (gfx api adjusted) for rendering.
cmd.SetViewProjectionMatrices(viewMatrix, projectionMatrix);
if (setInverseMatrices)
{
Matrix4x4 gpuProjectionMatrix = cameraData.GetGPUProjectionMatrix(isTargetFlipped); // TODO: invProjection might NOT match the actual projection (invP*P==I) as the target flip logic has diverging paths.
Matrix4x4 inverseViewMatrix = Matrix4x4.Inverse(viewMatrix);
Matrix4x4 inverseProjectionMatrix = Matrix4x4.Inverse(gpuProjectionMatrix);
Matrix4x4 inverseViewProjection = inverseViewMatrix * inverseProjectionMatrix;
// There's an inconsistency in handedness between unity_matrixV and unity_WorldToCamera
// Unity changes the handedness of unity_WorldToCamera (see Camera::CalculateMatrixShaderProps)
// we will also change it here to avoid breaking existing shaders. (case 1257518)
Matrix4x4 worldToCameraMatrix = Matrix4x4.Scale(new Vector3(1.0f, 1.0f, -1.0f)) * viewMatrix;
Matrix4x4 cameraToWorldMatrix = worldToCameraMatrix.inverse;
cmd.SetGlobalMatrix(ShaderPropertyId.worldToCameraMatrix, worldToCameraMatrix);
cmd.SetGlobalMatrix(ShaderPropertyId.cameraToWorldMatrix, cameraToWorldMatrix);
cmd.SetGlobalMatrix(ShaderPropertyId.inverseViewMatrix, inverseViewMatrix);
cmd.SetGlobalMatrix(ShaderPropertyId.inverseProjectionMatrix, inverseProjectionMatrix);
cmd.SetGlobalMatrix(ShaderPropertyId.inverseViewAndProjectionMatrix, inverseViewProjection);
}
// TODO: Add SetPerCameraClippingPlaneProperties here once we are sure it correctly behaves in overlay camera for some time
}
void SetPerCameraShaderVariables(RasterCommandBuffer cmd, UniversalCameraData cameraData, Vector2Int cameraTargetSizeCopy, bool isTargetFlipped)
{
using var profScope = new ProfilingScope(Profiling.setPerCameraShaderVariables);
Camera camera = cameraData.camera;
float scaledCameraTargetWidth = (float)cameraTargetSizeCopy.x;
float scaledCameraTargetHeight = (float)cameraTargetSizeCopy.y;
float cameraWidth = (float)camera.pixelWidth;
float cameraHeight = (float)camera.pixelHeight;
// Overlay cameras don't have a viewport. Must use the computed/inherited viewport instead of the camera one.
if (cameraData.renderType == CameraRenderType.Overlay)
{
// Overlay cameras inherits viewport from base.
// pixelRect/Width/Height is the viewport in pixels.
cameraWidth = cameraData.pixelWidth;
cameraHeight = cameraData.pixelHeight;
}
// Use eye texture's width and height as screen params when XR is enabled
if (cameraData.xr.enabled)
{
cameraWidth = (float)cameraTargetSizeCopy.x;
cameraHeight = (float)cameraTargetSizeCopy.y;
useRenderPassEnabled = false;
// Multi-pass needs to set unity_StereoEyeIndex builtin param for skybox-panoramic.shader to work correctly (UUM-120719)
if (!cameraData.xr.singlePassEnabled)
cmd.SetGlobalVector(XRBuiltinShaderConstants.unity_StereoEyeIndex, new Vector4(cameraData.xr.multipassId, 0, 0, 0));
}
if (camera.allowDynamicResolution)
{
scaledCameraTargetWidth *= ScalableBufferManager.widthScaleFactor;
scaledCameraTargetHeight *= ScalableBufferManager.heightScaleFactor;
}
float near = camera.nearClipPlane;
float far = camera.farClipPlane;
float invNear = Mathf.Approximately(near, 0.0f) ? 0.0f : 1.0f / near;
float invFar = Mathf.Approximately(far, 0.0f) ? 0.0f : 1.0f / far;
float isOrthographic = camera.orthographic ? 1.0f : 0.0f;
// From http://www.humus.name/temp/Linearize%20depth.txt
// But as depth component textures on OpenGL always return in 0..1 range (as in D3D), we have to use
// the same constants for both D3D and OpenGL here.
// OpenGL would be this:
// zc0 = (1.0 - far / near) / 2.0;
// zc1 = (1.0 + far / near) / 2.0;
// D3D is this:
float zc0 = 1.0f - far * invNear;
float zc1 = far * invNear;
Vector4 zBufferParams = new Vector4(zc0, zc1, zc0 * invFar, zc1 * invFar);
if (SystemInfo.usesReversedZBuffer)
{
zBufferParams.y += zBufferParams.x;
zBufferParams.x = -zBufferParams.x;
zBufferParams.w += zBufferParams.z;
zBufferParams.z = -zBufferParams.z;
}
// Projection flip sign logic is very deep in GfxDevice::SetInvertProjectionMatrix
// This setup is tailored especially for overlay camera game view
// For other scenarios this will be overwritten correctly by SetupCameraProperties
if (cameraData.renderType == CameraRenderType.Overlay)
{
float projectionFlipSign = isTargetFlipped ? -1.0f : 1.0f;
Vector4 projectionParams = new Vector4(projectionFlipSign, near, far, 1.0f * invFar);
cmd.SetGlobalVector(ShaderPropertyId.projectionParams, projectionParams);
}
Vector4 orthoParams = new Vector4(camera.orthographicSize * cameraData.aspectRatio, camera.orthographicSize, 0.0f, isOrthographic);
// Camera and Screen variables as described in https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html
cmd.SetGlobalVector(ShaderPropertyId.worldSpaceCameraPos, cameraData.worldSpaceCameraPos);
cmd.SetGlobalVector(ShaderPropertyId.screenParams, new Vector4(cameraWidth, cameraHeight, 1.0f + 1.0f / cameraWidth, 1.0f + 1.0f / cameraHeight));
cmd.SetGlobalVector(ShaderPropertyId.scaledScreenParams, new Vector4(scaledCameraTargetWidth, scaledCameraTargetHeight, 1.0f + 1.0f / scaledCameraTargetWidth, 1.0f + 1.0f / scaledCameraTargetHeight));
cmd.SetGlobalVector(ShaderPropertyId.zBufferParams, zBufferParams);
cmd.SetGlobalVector(ShaderPropertyId.orthoParams, orthoParams);
cmd.SetGlobalVector(ShaderPropertyId.screenSize, new Vector4(scaledCameraTargetWidth, scaledCameraTargetHeight, 1.0f / scaledCameraTargetWidth, 1.0f / scaledCameraTargetHeight));
cmd.SetKeyword(ShaderGlobalKeywords.SCREEN_COORD_OVERRIDE, cameraData.useScreenCoordOverride);
cmd.SetGlobalVector(ShaderPropertyId.screenSizeOverride, cameraData.screenSizeOverride);
cmd.SetGlobalVector(ShaderPropertyId.screenCoordScaleBias, cameraData.screenCoordScaleBias);
// { w / RTHandle.maxWidth, h / RTHandle.maxHeight } : xy = currFrame, zw = prevFrame
// TODO(@sandy-carter) set to RTHandles.rtHandleProperties.rtHandleScale once dynamic scaling is set up
cmd.SetGlobalVector(ShaderPropertyId.rtHandleScale, Vector4.one);
// Calculate a bias value which corrects the mip lod selection logic when image scaling is active.
// We clamp this value to 0.0 or less to make sure we don't end up reducing image detail in the downsampling case.
float mipBias = Math.Min((float)-Math.Log(cameraWidth / scaledCameraTargetWidth, 2.0f), 0.0f);
// Temporal Anti-aliasing can use negative mip bias to increase texture sharpness and new information for the jitter.
float taaMipBias = Math.Min(cameraData.taaSettings.mipBias, 0.0f);
mipBias = Math.Min(mipBias, taaMipBias);
cmd.SetGlobalVector(ShaderPropertyId.globalMipBias, new Vector2(mipBias, Mathf.Pow(2.0f, mipBias)));
//Set per camera matrices.
SetCameraMatrices(cmd, cameraData, true, isTargetFlipped);
}
///
/// Set the Camera billboard properties.
///
/// CommandBuffer to submit data to GPU.
/// CameraData containing camera matrices information.
void SetPerCameraBillboardProperties(RasterCommandBuffer cmd, UniversalCameraData cameraData)
{
Matrix4x4 worldToCameraMatrix = cameraData.GetViewMatrix();
Vector3 cameraPos = cameraData.worldSpaceCameraPos;
cmd.SetKeyword(ShaderGlobalKeywords.BillboardFaceCameraPos, QualitySettings.billboardsFaceCameraPosition);
Vector3 billboardTangent;
Vector3 billboardNormal;
float cameraXZAngle;
CalculateBillboardProperties(worldToCameraMatrix, out billboardTangent, out billboardNormal, out cameraXZAngle);
cmd.SetGlobalVector(ShaderPropertyId.billboardNormal, new Vector4(billboardNormal.x, billboardNormal.y, billboardNormal.z, 0.0f));
cmd.SetGlobalVector(ShaderPropertyId.billboardTangent, new Vector4(billboardTangent.x, billboardTangent.y, billboardTangent.z, 0.0f));
cmd.SetGlobalVector(ShaderPropertyId.billboardCameraParams, new Vector4(cameraPos.x, cameraPos.y, cameraPos.z, cameraXZAngle));
}
private static void CalculateBillboardProperties(
in Matrix4x4 worldToCameraMatrix,
out Vector3 billboardTangent,
out Vector3 billboardNormal,
out float cameraXZAngle)
{
Matrix4x4 cameraToWorldMatrix = worldToCameraMatrix;
cameraToWorldMatrix = cameraToWorldMatrix.transpose;
Vector3 cameraToWorldMatrixAxisX = new Vector3(cameraToWorldMatrix.m00, cameraToWorldMatrix.m10, cameraToWorldMatrix.m20);
Vector3 cameraToWorldMatrixAxisY = new Vector3(cameraToWorldMatrix.m01, cameraToWorldMatrix.m11, cameraToWorldMatrix.m21);
Vector3 cameraToWorldMatrixAxisZ = new Vector3(cameraToWorldMatrix.m02, cameraToWorldMatrix.m12, cameraToWorldMatrix.m22);
Vector3 front = cameraToWorldMatrixAxisZ;
Vector3 worldUp = Vector3.up;
Vector3 cross = Vector3.Cross(front, worldUp);
billboardTangent = !Mathf.Approximately(cross.sqrMagnitude, 0.0f)
? cross.normalized
: cameraToWorldMatrixAxisX;
billboardNormal = Vector3.Cross(worldUp, billboardTangent);
billboardNormal = !Mathf.Approximately(billboardNormal.sqrMagnitude, 0.0f)
? billboardNormal.normalized
: cameraToWorldMatrixAxisY;
// SpeedTree generates billboards starting from looking towards X- and rotates counter clock-wisely
Vector3 worldRight = new Vector3(0, 0, 1);
// signed angle is calculated on X-Z plane
float s = worldRight.x * billboardTangent.z - worldRight.z * billboardTangent.x;
float c = worldRight.x * billboardTangent.x + worldRight.z * billboardTangent.z;
cameraXZAngle = Mathf.Atan2(s, c);
// convert to [0,2PI)
if (cameraXZAngle < 0)
cameraXZAngle += 2 * Mathf.PI;
}
private void SetPerCameraClippingPlaneProperties(RasterCommandBuffer cmd, in UniversalCameraData cameraData, bool isTargetFlipped)
{
Matrix4x4 projectionMatrix = cameraData.GetGPUProjectionMatrix(isTargetFlipped);
Matrix4x4 viewMatrix = cameraData.GetViewMatrix();
Matrix4x4 viewProj = CoreMatrixUtils.MultiplyProjectionMatrix(projectionMatrix, viewMatrix, cameraData.camera.orthographic);
Plane[] planes = s_Planes;
GeometryUtility.CalculateFrustumPlanes(viewProj, planes);
Vector4[] cameraWorldClipPlanes = s_VectorPlanes;
for (int i = 0; i < planes.Length; ++i)
cameraWorldClipPlanes[i] = new Vector4(planes[i].normal.x, planes[i].normal.y, planes[i].normal.z, planes[i].distance);
cmd.SetGlobalVectorArray(ShaderPropertyId.cameraWorldClipPlanes, cameraWorldClipPlanes);
}
///
/// Set shader time variables as described in https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html
///
/// CommandBuffer to submit data to GPU.
/// Time.
/// Delta time.
/// Smooth delta time.
static void SetShaderTimeValues(IBaseCommandBuffer cmd, float time, float deltaTime, float smoothDeltaTime)
{
float timeEights = time / 8f;
float timeFourth = time / 4f;
float timeHalf = time / 2f;
float lastTime = time - ShaderUtils.PersistentDeltaTime;
// Time values
Vector4 timeVector = time * new Vector4(1f / 20f, 1f, 2f, 3f);
Vector4 sinTimeVector = new Vector4(Mathf.Sin(timeEights), Mathf.Sin(timeFourth), Mathf.Sin(timeHalf), Mathf.Sin(time));
Vector4 cosTimeVector = new Vector4(Mathf.Cos(timeEights), Mathf.Cos(timeFourth), Mathf.Cos(timeHalf), Mathf.Cos(time));
Vector4 deltaTimeVector = new Vector4(deltaTime, 1f / deltaTime, smoothDeltaTime, 1f / smoothDeltaTime);
Vector4 timeParametersVector = new Vector4(time, Mathf.Sin(time), Mathf.Cos(time), 0.0f);
Vector4 lastTimeParametersVector = new Vector4(lastTime, Mathf.Sin(lastTime), Mathf.Cos(lastTime), 0.0f);
cmd.SetGlobalVector(ShaderPropertyId.time, timeVector);
cmd.SetGlobalVector(ShaderPropertyId.sinTime, sinTimeVector);
cmd.SetGlobalVector(ShaderPropertyId.cosTime, cosTimeVector);
cmd.SetGlobalVector(ShaderPropertyId.deltaTime, deltaTimeVector);
cmd.SetGlobalVector(ShaderPropertyId.timeParameters, timeParametersVector);
cmd.SetGlobalVector(ShaderPropertyId.lastTimeParameters, lastTimeParametersVector);
}
///
/// Returns a list of renderer features added to this renderer.
///
///
protected List rendererFeatures
{
get => m_RendererFeatures;
}
///
/// Returns a list of render passes scheduled to be executed by this renderer.
///
///
protected List activeRenderPassQueue
{
get => m_ActiveRenderPassQueue;
}
///
/// Supported rendering features by this renderer. The scriptable renderer framework will use the returned information
/// to adjust things like inspectors, etc.
///
///
public RenderingFeatures supportedRenderingFeatures { get; set; } = new RenderingFeatures();
///
/// List of unsupported Graphics APIs for this renderer.The scriptable renderer framework will use the returned information
/// to adjust things like inspectors, etc.
///
///
public GraphicsDeviceType[] unsupportedGraphicsDeviceTypes { get; set; } = new GraphicsDeviceType[0];
List m_ActiveRenderPassQueue = new List(32);
List m_RendererFeatures = new List(10);
// The pipeline can only guarantee the camera target texture are valid when the pipeline is executing.
// Trying to access the camera target before or after might be that the pipeline texture have already been disposed.
bool m_IsPipelineExecuting = false;
internal bool useRenderPassEnabled = false;
ContextContainer m_frameData = new();
internal ContextContainer frameData => m_frameData;
private static Plane[] s_Planes = new Plane[6];
private static Vector4[] s_VectorPlanes = new Vector4[6];
///
/// In URP RenderGraph (likely not in Compatibility Mode), this returns if the pipeline will actually perform depth priming.
/// Depth priming is done with a prepass to the activeCameraDepth.
/// Even when the settings on the URP asset requests depth priming the pipeline can decide not to do it (or vice versa).
///
internal bool useDepthPriming { get; set; } = false;
internal bool stripShadowsOffVariants { get; set; } = false;
internal bool stripAdditionalLightOffVariants { get; set; } = false;
///
/// Creates a new ScriptableRenderer instance.
///
/// The ScriptableRendererData data to initialize the renderer.
///
public ScriptableRenderer(ScriptableRendererData data)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
DebugHandler = new DebugHandler();
#endif
foreach (var feature in data.rendererFeatures)
{
if (feature == null)
continue;
feature.Create();
m_RendererFeatures.Add(feature);
}
useRenderPassEnabled = data.useNativeRenderPass;
m_ActiveRenderPassQueue.Clear();
}
///
/// Disposable pattern implementation.
/// Cleans up resources used by the renderer.
///
public void Dispose()
{
// Dispose all renderer features...
for (int i = 0; i < m_RendererFeatures.Count; ++i)
{
if (rendererFeatures[i] == null)
continue;
try
{
// Guard the renderer feature Dispose() call so if it raises any exception,
// it doesn't leave the renderer in a partially destructed state.
rendererFeatures[i].Dispose();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Called by Dispose().
/// Override this function to clean up resources in your renderer.
/// Be sure to call this base dispose in your overridden function to free resources allocated by the base.
///
/// See the definition of IDisposable.
protected virtual void Dispose(bool disposing)
{
DebugHandler?.Dispose();
}
internal virtual void ReleaseRenderTargets()
{
}
///
/// Override this method to configure the culling parameters for the renderer. You can use this to configure if
/// lights should be culled per-object or the maximum shadow distance for example.
///
/// Use this to change culling parameters used by the render pipeline.
/// Current render state information.
public virtual void SetupCullingParameters(ref ScriptableCullingParameters cullingParameters,
ref CameraData cameraData)
{
}
///
/// Called upon finishing rendering the camera stack. You can release any resources created by the renderer here.
///
/// The command buffer where any work should be recorded on..
public virtual void FinishRendering(CommandBuffer cmd)
{
}
///
/// Override this method to initialize anything before starting the recording of the render graph, such as resources.
/// This is the last point where it is ok to call ScriptableRenderer.EnqueuePass as after this function the
/// queue will be sorted for the frame.
///
public virtual void OnBeginRenderGraphFrame()
{
}
///
/// Override this method to record the RenderGraph passes to be used by the RenderGraph render path.
///
/// The rendergraph to schedule passes on.
/// The render context to use when creating rendering lists or performing culling operations. Ideally, graphics work should be executed through rendergraph so is is not recommended to use ScriptableRenderContext.ExecuteCommandBuffer.
internal virtual void OnRecordRenderGraph(RenderGraph renderGraph, ScriptableRenderContext context)
{
}
///
/// Override this method to cleanup things after recording the render graph, such as resources.
/// This executes after the render graph is recorded but before it is compiled and executed.
///
public virtual void OnEndRenderGraphFrame()
{
}
private void InitRenderGraphFrame(RenderGraph renderGraph)
{
using (var builder = renderGraph.AddUnsafePass(Profiling.initRenderGraphFrame.name, out var passData,
Profiling.initRenderGraphFrame))
{
passData.renderer = this;
builder.AllowPassCulling(false);
builder.SetRenderFunc(static (PassData data, UnsafeGraphContext rgContext) =>
{
UnsafeCommandBuffer cmd = rgContext.cmd;
#if UNITY_EDITOR
float time = Application.isPlaying ? Time.time : Time.realtimeSinceStartup;
#else
float time = Time.time;
#endif
float deltaTime = Time.deltaTime;
float smoothDeltaTime = Time.smoothDeltaTime;
ClearRenderingState(cmd);
SetShaderTimeValues(cmd, time, deltaTime, smoothDeltaTime);
});
}
}
private class VFXProcessCameraPassData
{
internal UniversalRenderingData renderingData;
internal Camera camera;
internal VFX.VFXCameraXRSettings cameraXRSettings;
internal XRPass xrPass;
};
internal void ProcessVFXCameraCommand(RenderGraph renderGraph)
{
UniversalRenderingData renderingData = frameData.Get();
UniversalCameraData cameraData = frameData.Get();
XRPass xr = cameraData.xr;
using (var builder = renderGraph.AddUnsafePass("ProcessVFXCameraCommand", out var passData,
Profiling.vfxProcessCamera))
{
passData.camera = cameraData.camera;
passData.renderingData = renderingData;
passData.cameraXRSettings.viewTotal = xr.enabled ? 2u : 1u;
passData.cameraXRSettings.viewCount = xr.enabled ? (uint)xr.viewCount : 1u;
passData.cameraXRSettings.viewOffset = (uint)xr.multipassId;
passData.xrPass = xr.enabled ? xr : null;
builder.AllowPassCulling(false);
builder.SetRenderFunc(static (VFXProcessCameraPassData data, UnsafeGraphContext context) =>
{
if (data.xrPass != null)
data.xrPass.StartSinglePass(context.cmd);
//Triggers dispatch per camera, all global parameters should have been setup at this stage.
CommandBufferHelpers.VFXManager_ProcessCameraCommand(data.camera, context.cmd, data.cameraXRSettings, data.renderingData.cullResults);
if (data.xrPass != null)
data.xrPass.StopSinglePass(context.cmd);
});
}
}
internal void SetupRenderGraphCameraProperties(RenderGraph renderGraph, TextureHandle target)
{
using (var builder = renderGraph.AddRasterRenderPass(Profiling.setupCamera.name, out var passData,
Profiling.setupCamera))
{
passData.renderer = this;
passData.cameraData = frameData.Get();
passData.cameraTargetSizeCopy = new Vector2Int(passData.cameraData.cameraTargetDescriptor.width, passData.cameraData.cameraTargetDescriptor.height);
passData.target = target;
builder.AllowGlobalStateModification(true);
builder.SetRenderFunc(static (PassData data, RasterGraphContext context) =>
{
bool yFlipped = SystemInfo.graphicsUVStartsAtTop && RenderingUtils.IsHandleYFlipped(context, in data.target);
// This is still required because of the following reasons:
// - Camera billboard properties.
// - Camera frustum planes: unity_CameraWorldClipPlanes[6]
// - _ProjectionParams.x logic is deep inside GfxDevice
// NOTE: The only reason we have to call this here and not at the beginning (before shadows)
// is because this need to be called for each eye in multi pass VR.
// The side effect is that this will override some shader properties we already setup and we will have to
// reset them.
if (data.cameraData.renderType == CameraRenderType.Base)
{
context.cmd.SetupCameraProperties(data.cameraData.camera);
data.renderer.SetPerCameraShaderVariables(context.cmd, data.cameraData, data.cameraTargetSizeCopy, yFlipped);
}
else
{
// Set new properties
data.renderer.SetPerCameraShaderVariables(context.cmd, data.cameraData, data.cameraTargetSizeCopy, yFlipped);
data.renderer.SetPerCameraClippingPlaneProperties(context.cmd, in data.cameraData, yFlipped);
data.renderer.SetPerCameraBillboardProperties(context.cmd, data.cameraData);
}
#if UNITY_EDITOR
float time = Application.isPlaying ? Time.time : Time.realtimeSinceStartup;
#else
float time = Time.time;
#endif
float deltaTime = Time.deltaTime;
float smoothDeltaTime = Time.smoothDeltaTime;
// Reset shader time variables as they were overridden in SetupCameraProperties. If we don't do it we might have a mismatch between shadows and main rendering
SetShaderTimeValues(context.cmd, time, deltaTime, smoothDeltaTime);
});
}
}
private class DrawGizmosPassData
{
public RendererListHandle gizmoRenderList;
public TextureHandle color;
public TextureHandle depth;
};
///
/// TODO RENDERGRAPH
///
///
///
///
///
internal void DrawRenderGraphGizmos(RenderGraph renderGraph, ContextContainer frameData, TextureHandle color, TextureHandle depth, GizmoSubset gizmoSubset)
{
#if UNITY_EDITOR
UniversalCameraData cameraData = frameData.Get();
if (!Handles.ShouldRenderGizmos() || cameraData.camera.sceneViewFilterMode == Camera.SceneViewFilterMode.ShowFiltered)
return;
// We cannot draw gizmo rendererlists from an raster pass as the gizmo rendering triggers the MonoBehaviour.OnDrawGizmos or MonoBehaviour.OnDrawGizmosSelected callbacks that could run arbitrary graphics code
// like SetRenderTarget, texture and resource loading, ...
using (var builder = renderGraph.AddUnsafePass("Draw Gizmos Pass", out var passData,
Profiling.drawGizmos))
{
builder.UseTexture(color, AccessFlags.Write);
builder.UseTexture(depth, AccessFlags.ReadWrite);
passData.gizmoRenderList = renderGraph.CreateGizmoRendererList(cameraData.camera, gizmoSubset);
passData.color = color;
passData.depth = depth;
builder.UseRendererList(passData.gizmoRenderList);
builder.AllowPassCulling(false);
builder.SetRenderFunc(static (DrawGizmosPassData data, UnsafeGraphContext rgContext) =>
{
using (new ProfilingScope(rgContext.cmd, Profiling.drawGizmos))
{
rgContext.cmd.SetRenderTarget(data.color, data.depth);
rgContext.cmd.DrawRendererList(data.gizmoRenderList);
}
});
}
#endif
}
private class DrawWireOverlayPassData
{
public RendererListHandle wireOverlayList;
};
internal void DrawRenderGraphWireOverlay(RenderGraph renderGraph, ContextContainer frameData, TextureHandle color)
{
#if UNITY_EDITOR
UniversalCameraData cameraData = frameData.Get();
if (!cameraData.isSceneViewCamera)
return;
using (var builder = renderGraph.AddRasterRenderPass(Profiling.drawWireOverlay.name, out var passData,
Profiling.drawWireOverlay))
{
builder.SetRenderAttachment(color, 0, AccessFlags.Write);
passData.wireOverlayList = renderGraph.CreateWireOverlayRendererList(cameraData.camera);
builder.UseRendererList(passData.wireOverlayList);
builder.AllowPassCulling(false);
builder.SetRenderFunc(static (DrawWireOverlayPassData data, RasterGraphContext rgContext) =>
{
using (new ProfilingScope(rgContext.cmd, Profiling.drawWireOverlay))
{
rgContext.cmd.DrawRendererList(data.wireOverlayList);
}
});
}
#endif
}
private class BeginXRPassData
{
internal UniversalCameraData cameraData;
};
internal void BeginRenderGraphXRRendering(RenderGraph renderGraph)
{
#if ENABLE_VR && ENABLE_XR_MODULE
UniversalCameraData cameraData = frameData.Get();
if (!cameraData.xr.enabled)
return;
bool isDefaultXRViewport = XRSystem.GetRenderViewportScale() == 1.0f;
// For untethered XR, intermediate pass' foveation is currenlty unsupported with non-default viewport.
// Must be configured during the recording timeline before adding other XR intermediate passes.
cameraData.xrUniversal.canFoveateIntermediatePasses = !PlatformAutoDetect.isXRMobile || isDefaultXRViewport;
using (var builder = renderGraph.AddRasterRenderPass("BeginXRRendering", out var passData,
Profiling.beginXRRendering))
{
passData.cameraData = cameraData;
builder.AllowGlobalStateModification(true);
builder.SetRenderFunc((BeginXRPassData data, RasterGraphContext context) =>
{
if (data.cameraData.xr.enabled)
{
if (data.cameraData.xrUniversal.isLateLatchEnabled)
data.cameraData.xrUniversal.canMarkLateLatch = true;
data.cameraData.xr.StartSinglePass(context.cmd);
if (data.cameraData.xr.supportsFoveatedRendering)
{
context.cmd.ConfigureFoveatedRendering(data.cameraData.xr.foveatedRenderingInfo);
if (XRSystem.foveatedRenderingCaps.HasFlag(FoveatedRenderingCaps.NonUniformRaster))
context.cmd.SetKeyword(ShaderGlobalKeywords.FoveatedRenderingNonUniformRaster, true);
}
}
});
}
#endif
}
private class EndXRPassData
{
public UniversalCameraData cameraData;
};
internal void EndRenderGraphXRRendering(RenderGraph renderGraph)
{
#if ENABLE_VR && ENABLE_XR_MODULE
UniversalCameraData cameraData = frameData.Get();
if (!cameraData.xr.enabled)
return;
using (var builder = renderGraph.AddRasterRenderPass("EndXRRendering", out var passData,
Profiling.endXRRendering))
{
passData.cameraData = cameraData;
builder.AllowGlobalStateModification(true);
// Apply MultiviewRenderRegionsCompatible flag only for the first pass in multipass
if (cameraData.xr.multipassId == 0)
{
builder.SetExtendedFeatureFlags(ExtendedFeatureFlags.MultiviewRenderRegionsCompatible);
}
builder.SetRenderFunc((EndXRPassData data, RasterGraphContext context) =>
{
if (data.cameraData.xr.enabled)
{
data.cameraData.xr.StopSinglePass(context.cmd);
}
if (XRSystem.foveatedRenderingCaps != FoveatedRenderingCaps.None)
{
if (XRSystem.foveatedRenderingCaps.HasFlag(FoveatedRenderingCaps.NonUniformRaster))
context.cmd.SetKeyword(ShaderGlobalKeywords.FoveatedRenderingNonUniformRaster, false);
context.cmd.ConfigureFoveatedRendering(IntPtr.Zero);
}
});
}
#endif
}
private class DummyData
{
};
private void SetEditorTarget(RenderGraph renderGraph)
{
using (var builder = renderGraph.AddUnsafePass("SetEditorTarget", out var passData,
Profiling.setEditorTarget))
{
builder.AllowPassCulling(false);
builder.SetRenderFunc(static (DummyData data, UnsafeGraphContext context) =>
{
context.cmd.SetRenderTarget(BuiltinRenderTextureType.CameraTarget,
RenderBufferLoadAction.Load, RenderBufferStoreAction.Store, // color
RenderBufferLoadAction.Load, RenderBufferStoreAction.DontCare); // depth
});
}
}
private class PassData
{
internal ScriptableRenderer renderer;
internal UniversalCameraData cameraData;
internal TextureHandle target;
// The size of the camera target changes during the frame so we must make a copy of it here to preserve its record-time value.
internal Vector2Int cameraTargetSizeCopy;
};
///
/// TODO RENDERGRAPH
///
///
///
internal void RecordRenderGraph(RenderGraph renderGraph, ScriptableRenderContext context)
{
using (new ProfilingScope(ProfilingSampler.Get(URPProfileId.RecordRenderGraph)))
{
OnBeginRenderGraphFrame();
using (new ProfilingScope(Profiling.sortRenderPasses))
{
// Sort the render pass queue
SortStable(m_ActiveRenderPassQueue);
}
InitRenderGraphFrame(renderGraph);
using (new ProfilingScope(Profiling.recordRenderGraph))
{
OnRecordRenderGraph(renderGraph, context);
}
OnEndRenderGraphFrame();
// The editor scene view still relies on some builtin passes (i.e. drawing the scene grid). The builtin
// passes are not explicitly setting RTs and rely on the last active render target being set. Unfortunately
// this does not play nice with the NRP RG path, since we don't use the SetRenderTarget API anymore.
// For this reason, as a workaround, in editor scene view we set explicitly set the RT to SceneViewRT.
// TODO: this will go away once we remove the builtin dependencies and implement the grid in SRP.
#if UNITY_EDITOR
UniversalCameraData cameraData = frameData.Get();
if (cameraData.isSceneViewCamera)
SetEditorTarget(renderGraph);
#endif
}
}
///
/// TODO RENDERGRAPH
///
///
///
internal void FinishRenderGraphRendering(CommandBuffer cmd)
{
UniversalCameraData cameraData = frameData.Get();
OnFinishRenderGraphRendering(cmd);
InternalFinishRenderingCommon(cmd, cameraData.resolveFinalTarget);
}
///
/// TODO RENDERGRAPH
///
///
///
internal virtual void OnFinishRenderGraphRendering(CommandBuffer cmd)
{
}
internal void RecordCustomRenderGraphPassesInEventRange(RenderGraph renderGraph, RenderPassEvent eventStart, RenderPassEvent eventEnd)
{
// Only iterate over the active pass queue if we have a non-empty range
if (eventStart != eventEnd)
{
foreach (ScriptableRenderPass pass in m_ActiveRenderPassQueue)
{
if (pass.renderPassEvent >= eventStart && pass.renderPassEvent < eventEnd)
pass.RecordRenderGraph(renderGraph, m_frameData);
}
}
}
internal void CalculateSplitEventRange(RenderPassEvent startInjectionPoint, RenderPassEvent targetEvent, out RenderPassEvent startEvent, out RenderPassEvent splitEvent, out RenderPassEvent endEvent)
{
int range = ScriptableRenderPass.GetRenderPassEventRange(startInjectionPoint);
startEvent = startInjectionPoint;
endEvent = startEvent + range;
splitEvent = (RenderPassEvent)Math.Clamp((int)targetEvent, (int)startEvent, (int)endEvent);
}
internal void RecordCustomRenderGraphPasses(RenderGraph renderGraph, RenderPassEvent startInjectionPoint, RenderPassEvent endInjectionPoint)
{
int range = ScriptableRenderPass.GetRenderPassEventRange(endInjectionPoint);
RecordCustomRenderGraphPassesInEventRange(renderGraph, startInjectionPoint, endInjectionPoint + range);
}
internal void RecordCustomRenderGraphPasses(RenderGraph renderGraph, RenderPassEvent injectionPoint)
{
RecordCustomRenderGraphPasses(renderGraph, injectionPoint, injectionPoint);
}
///
/// Enqueues a render pass for execution.
///
/// Render pass to be enqueued.
public void EnqueuePass(ScriptableRenderPass pass)
{
m_ActiveRenderPassQueue.Add(pass);
}
///
/// Returns a clear flag based on CameraClearFlags.
///
/// The Camera data.
/// A clear flag that tells if color and/or depth should be cleared.
///
protected static ClearFlag GetCameraClearFlag(ref CameraData cameraData)
{
var universalCameraData = cameraData.universalCameraData;
return GetCameraClearFlag(universalCameraData);
}
///
/// Returns a clear flag based on CameraClearFlags.
///
/// The Camera data.
/// A clear flag that tells if color and/or depth should be cleared.
///
protected static ClearFlag GetCameraClearFlag(UniversalCameraData cameraData)
{
var cameraClearFlags = cameraData.camera.clearFlags;
// Universal RP doesn't support CameraClearFlags.DepthOnly and CameraClearFlags.Nothing.
// CameraClearFlags.DepthOnly has the same effect of CameraClearFlags.SolidColor
// CameraClearFlags.Nothing clears Depth on PC/Desktop and in mobile it clears both
// depth and color.
// CameraClearFlags.Skybox clears depth only.
// Implementation details:
// Camera clear flags are used to initialize the attachments on the first render pass.
// ClearFlag is used together with Tile Load action to figure out how to clear the camera render target.
// In Tile Based GPUs ClearFlag.Depth + RenderBufferLoadAction.DontCare becomes DontCare load action.
// RenderBufferLoadAction.DontCare in PC/Desktop behaves as not clearing screen
// RenderBufferLoadAction.DontCare in Vulkan/Metal behaves as DontCare load action
// RenderBufferLoadAction.DontCare in GLES behaves as glInvalidateBuffer
// Overlay cameras composite on top of previous ones. They don't clear color.
// For overlay cameras we check if depth should be cleared on not.
if (cameraData.renderType == CameraRenderType.Overlay)
return (cameraData.clearDepth) ? ClearFlag.DepthStencil : ClearFlag.None;
// Certain debug modes (e.g. wireframe/overdraw modes) require that we override clear flags and clear everything.
var debugHandler = cameraData.renderer.DebugHandler;
if (debugHandler != null && debugHandler.IsActiveForCamera(cameraData.isPreviewCamera) && debugHandler.IsScreenClearNeeded)
return ClearFlag.All;
// XRTODO: remove once we have visible area of occlusion mesh available
if (cameraClearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null && cameraData.postProcessEnabled && cameraData.xr.enabled)
return ClearFlag.All;
if ((cameraClearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null) ||
cameraClearFlags == CameraClearFlags.Nothing)
{
// Clear color if msaa is used. If color is not cleared will alpha to coverage blend with previous frame if alpha clipping is enabled of any opaque objects.
if (cameraData.cameraTargetDescriptor.msaaSamples > 1)
{
// Sets the clear color to black to make the alpha to coverage blending blend with black when using alpha clipping.
cameraData.camera.backgroundColor = Color.black;
return ClearFlag.DepthStencil | ClearFlag.Color;
}
else
{
return ClearFlag.DepthStencil;
}
}
return ClearFlag.All;
}
///
/// Calls OnCull for each feature added to this renderer.
///
///
/// Current render state information.
internal void OnPreCullRenderPasses(in CameraData cameraData)
{
// Add render passes from custom renderer features
for (int i = 0; i < rendererFeatures.Count; ++i)
{
if (!rendererFeatures[i].isActive)
{
continue;
}
rendererFeatures[i].OnCameraPreCull(this, in cameraData);
}
}
///
/// Calls AddRenderPasses for each feature added to this renderer.
///
///
///
internal void AddRenderPasses(ref RenderingData renderingData)
{
using var profScope = new ProfilingScope(Profiling.addRenderPasses);
// Add render passes from custom renderer features
for (int i = 0; i < rendererFeatures.Count; ++i)
{
if (!rendererFeatures[i].isActive)
{
continue;
}
rendererFeatures[i].AddRenderPasses(this, ref renderingData);
}
// Remove any null render pass that might have been added by user by mistake
int count = activeRenderPassQueue.Count;
for (int i = count - 1; i >= 0; i--)
{
if (activeRenderPassQueue[i] == null)
activeRenderPassQueue.RemoveAt(i);
}
}
static void ClearRenderingState(IBaseCommandBuffer cmd)
{
using var profScope = new ProfilingScope(Profiling.clearRenderingState);
// Reset per-camera shader keywords. They are enabled depending on which render passes are executed.
cmd.SetKeyword(ShaderGlobalKeywords.MainLightShadows, false);
cmd.SetKeyword(ShaderGlobalKeywords.MainLightShadowCascades, false);
cmd.SetKeyword(ShaderGlobalKeywords.AdditionalLightsVertex, false);
cmd.SetKeyword(ShaderGlobalKeywords.AdditionalLightsPixel, false);
cmd.SetKeyword(ShaderGlobalKeywords.ClusterLightLoop, false);
cmd.SetKeyword(ShaderGlobalKeywords.ForwardPlus, false); // Backward compatibility. Deprecated in 6.1.
cmd.SetKeyword(ShaderGlobalKeywords.AdditionalLightShadows, false);
cmd.SetKeyword(ShaderGlobalKeywords.ReflectionProbeBlending, false);
cmd.SetKeyword(ShaderGlobalKeywords.ReflectionProbeBoxProjection, false);
cmd.SetKeyword(ShaderGlobalKeywords.ReflectionProbeAtlas, false);
cmd.SetKeyword(ShaderGlobalKeywords.SoftShadows, false);
cmd.SetKeyword(ShaderGlobalKeywords.SoftShadowsLow, false);
cmd.SetKeyword(ShaderGlobalKeywords.SoftShadowsMedium, false);
cmd.SetKeyword(ShaderGlobalKeywords.SoftShadowsHigh, false);
cmd.SetKeyword(ShaderGlobalKeywords.MixedLightingSubtractive, false);
cmd.SetKeyword(ShaderGlobalKeywords.LightmapShadowMixing, false);
cmd.SetKeyword(ShaderGlobalKeywords.ShadowsShadowMask, false);
cmd.SetKeyword(ShaderGlobalKeywords.LinearToSRGBConversion, false);
cmd.SetKeyword(ShaderGlobalKeywords.LightLayers, false);
cmd.SetKeyword(ShaderGlobalKeywords.ScreenSpaceOcclusion, false);
cmd.SetGlobalVector(ScreenSpaceAmbientOcclusionPass.s_AmbientOcclusionParamID, Vector4.zero);
}
// Scene filtering is enabled when in prefab editing mode
internal bool IsSceneFilteringEnabled(Camera camera)
{
#if UNITY_EDITOR
if (CoreUtils.IsSceneFilteringEnabled() && camera.sceneViewFilterMode == Camera.SceneViewFilterMode.ShowFiltered)
return true;
#endif
return false;
}
// Common ScriptableRenderer.Execute and RenderGraph path
void InternalFinishRenderingCommon(CommandBuffer cmd, bool resolveFinalTarget)
{
using (new ProfilingScope(Profiling.internalFinishRenderingCommon))
{
for (int i = 0; i < m_ActiveRenderPassQueue.Count; ++i)
m_ActiveRenderPassQueue[i].FrameCleanup(cmd);
// Happens when rendering the last camera in the camera stack.
if (resolveFinalTarget)
{
FinishRendering(cmd);
// We finished camera stacking and released all intermediate pipeline textures.
m_IsPipelineExecuting = false;
}
m_ActiveRenderPassQueue.Clear();
}
}
private protected int AdjustAndGetScreenMSAASamples(RenderGraph renderGraph, bool useIntermediateColorTarget)
{
// In the editor (ConfigureTargetTexture in PlayModeView.cs) and many platforms, the system render target is always allocated without MSAA
if (!SystemInfo.supportsMultisampledBackBuffer) return 1;
// For mobile platforms, when URP main rendering is done to an intermediate target and NRP enabled
// we disable multisampling for the system render target as a bandwidth optimization
// doing so, we avoid storing costly MSAA samples back to system memory for nothing
bool canOptimizeScreenMSAASamples = UniversalRenderPipeline.canOptimizeScreenMSAASamples
&& useIntermediateColorTarget
&& renderGraph.nativeRenderPassesEnabled
&& Screen.msaaSamples > 1;
if (canOptimizeScreenMSAASamples)
{
Screen.SetMSAASamples(1);
}
// iOS and macOS corner case
bool screenAPIHasOneFrameDelay = (Application.platform == RuntimePlatform.OSXPlayer || Application.platform == RuntimePlatform.IPhonePlayer);
return screenAPIHasOneFrameDelay ? Mathf.Max(UniversalRenderPipeline.startFrameScreenMSAASamples, 1) : Mathf.Max(Screen.msaaSamples, 1);
}
internal static void SortStable(List list)
{
int j;
for (int i = 1; i < list.Count; ++i)
{
ScriptableRenderPass curr = list[i];
j = i - 1;
for (; j >= 0 && curr < list[j]; --j)
list[j + 1] = list[j];
list[j + 1] = curr;
}
}
///
/// Used to determine if this renderer supports the use of GPU occlusion culling.
///
public virtual bool supportsGPUOcclusion => false;
}
}